You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, titleText = "UNIVERSAL", subtitleText = "AUDIO TO IMAGE FANFARE", textColor = "#FFD700") {
// Create the main container
const container = document.createElement('div');
container.style.position = 'relative';
container.style.width = '100%';
container.style.maxWidth = '800px';
container.style.margin = '0 auto';
container.style.fontFamily = 'Arial, sans-serif';
container.style.overflow = 'hidden';
container.style.backgroundColor = '#000';
container.style.border = '2px solid #333';
container.style.borderRadius = '8px';
container.style.boxShadow = '0 10px 30px rgba(0,0,0,0.8)';
// Create Canvas
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 450; // 16:9 Aspect Ratio
canvas.style.width = '100%';
canvas.style.display = 'block';
container.appendChild(canvas);
const ctx = canvas.getContext('2d');
// Create the "Play Fanfare" overlay button
const btn = document.createElement('button');
btn.innerHTML = '🎬 Generate & Play Fanfare';
btn.style.position = 'absolute';
btn.style.top = '50%';
btn.style.left = '50%';
btn.style.transform = 'translate(-50%, -50%)';
btn.style.padding = '15px 30px';
btn.style.fontSize = '20px';
btn.style.fontWeight = 'bold';
btn.style.cursor = 'pointer';
btn.style.color = '#333';
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
btn.style.border = '2px solid #FFF';
btn.style.borderRadius = '30px';
btn.style.boxShadow = '0 0 20px rgba(0,0,0,0.8)';
btn.style.transition = 'all 0.3s ease';
btn.style.zIndex = 10;
btn.onmouseover = () => {
btn.style.backgroundColor = '#FFF';
btn.style.transform = 'translate(-50%, -50%) scale(1.05)';
};
btn.onmouseout = () => {
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
btn.style.transform = 'translate(-50%, -50%) scale(1)';
};
container.appendChild(btn);
// Pre-generate stars for the space background
const stars = [];
for (let i = 0; i < 250; i++) {
stars.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
r: Math.random() * 1.5,
a: Math.random() * Math.PI * 2
});
}
let animating = false;
let startTime = 0;
let animationId = null;
// Helper to draw the frame based on elapsed time
function drawFrame(time) {
if (!startTime) startTime = time;
const elapsed = (time - startTime) / 1000; // in seconds
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const globeRadius = 160;
// 1. Draw Space Background
ctx.fillStyle = '#01010a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
stars.forEach(s => {
const opacity = (Math.sin(elapsed * 2 + s.a) + 1) / 2;
ctx.fillStyle = `rgba(255, 255, 255, ${opacity * 0.8})`;
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fill();
});
// 2. Cinematic Sunburst / Light Rays behind the globe
const rayAlpha = Math.min(1, Math.max(0, elapsed - 0.5) / 2);
if (rayAlpha > 0) {
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(elapsed * 0.05); // Slow rotation of rays
const numRays = 40;
const rayLen = 500;
for (let i = 0; i < numRays; i++) {
const rot = (i / numRays) * Math.PI * 2;
ctx.rotate(rot);
const rayGrad = ctx.createLinearGradient(globeRadius - 20, 0, rayLen, 0);
rayGrad.addColorStop(0, `rgba(255, 230, 150, ${0.4 * rayAlpha})`);
rayGrad.addColorStop(1, 'rgba(255, 230, 150, 0)');
ctx.fillStyle = rayGrad;
ctx.beginPath();
ctx.moveTo(globeRadius - 20, -10);
ctx.lineTo(rayLen, -30);
ctx.lineTo(rayLen, 30);
ctx.lineTo(globeRadius - 20, 10);
ctx.fill();
ctx.rotate(-rot);
}
ctx.restore();
}
// 3. Draw Rotating Globe (using originalImg)
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, globeRadius, 0, Math.PI * 2);
ctx.clip();
const imgAspect = originalImg.width / originalImg.height;
const drawH = globeRadius * 2;
const drawW = drawH * imgAspect;
const speed = 40; // Spinning speed (pixels per second)
const panX = (elapsed * speed) % drawW;
// Tile the image to make it seamlessly loop
const numTiles = Math.ceil((globeRadius * 2) / drawW) + 1;
for (let i = -1; i < numTiles; i++) {
ctx.drawImage(originalImg, cx - globeRadius - panX + (i * drawW), cy - globeRadius, drawW, drawH);
}
// Apply spherical shadow for 3D effect
const sphereGrad = ctx.createRadialGradient(cx - globeRadius/2.5, cy - globeRadius/2.5, globeRadius/5, cx, cy, globeRadius);
sphereGrad.addColorStop(0, 'rgba(255,255,255,0.3)');
sphereGrad.addColorStop(0.6, 'rgba(0,0,0,0.2)');
sphereGrad.addColorStop(0.95, 'rgba(0,0,0,0.85)');
sphereGrad.addColorStop(1, 'rgba(0,0,0,1)');
ctx.fillStyle = sphereGrad;
ctx.fillRect(cx - globeRadius, cy - globeRadius, globeRadius * 2, globeRadius * 2);
ctx.restore();
// Outer glow
const glowAlpha = Math.min(0.6, elapsed / 3);
const outerGlow = ctx.createRadialGradient(cx, cy, globeRadius, cx, cy, globeRadius + 30);
outerGlow.addColorStop(0, `rgba(100, 200, 255, ${glowAlpha})`);
outerGlow.addColorStop(1, 'rgba(100, 200, 255, 0)');
ctx.fillStyle = outerGlow;
ctx.beginPath();
ctx.arc(cx, cy, globeRadius + 30, 0, Math.PI * 2);
ctx.fill();
// 4. Draw Cinematic Arching Text
// Timing syncs with music hit at 3.4 seconds
const introPhase = Math.min(1, Math.max(0, elapsed - 0.5) / 2.9);
if (introPhase > 0) {
// Cubic ease-out calculation for smooth settling
const easeOut = 1 - Math.pow(1 - introPhase, 3);
const scale = 0.5 + (easeOut * 0.5);
const ringRotation = (1 - easeOut) * (Math.PI / 2.5); // Sweeps in from an angle
ctx.font = 'bold 55px "Trebuchet MS", Helvetica, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const chars = titleText.split('');
const totalSweep = Math.PI * 0.75;
chars.forEach((c, idx) => {
const charProgress = idx / (chars.length - 1 || 1);
const angle = -totalSweep/2 + totalSweep * charProgress;
ctx.save();
ctx.translate(cx, cy);
// Add gentle scale and perspective tilt
ctx.scale(scale, scale);
// Orbit math around the globe (elliptical)
const orbitX = Math.sin(angle - ringRotation) * (globeRadius + 45);
const orbitY = Math.cos(angle - ringRotation) * 45;
ctx.translate(orbitX, orbitY);
// Rotate letter slightly to follow the curve
ctx.rotate((angle - ringRotation) * 0.35);
// Golden text gradient
const textGrad = ctx.createLinearGradient(0, -30, 0, 30);
textGrad.addColorStop(0, textColor);
textGrad.addColorStop(0.4, '#FFFFE0');
textGrad.addColorStop(0.6, '#DAA520');
textGrad.addColorStop(1, '#8B6508');
ctx.fillStyle = textGrad;
// Solid shadow
ctx.shadowColor = 'rgba(0,0,0,0.9)';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 4;
ctx.fillText(c, 0, 0);
// Thin stroke to define edges
ctx.shadowColor = 'transparent';
ctx.strokeStyle = '#222';
ctx.lineWidth = 1.0;
ctx.strokeText(c, 0, 0);
ctx.restore();
});
}
// 5. Fade in Subtitle Text
if (elapsed > 4.0) {
const subAlpha = Math.min(1, (elapsed - 4.0) / 1.5);
ctx.fillStyle = `rgba(255, 255, 255, ${subAlpha * 0.8})`;
ctx.font = '14px Arial, sans-serif';
ctx.textAlign = 'center';
ctx.letterSpacing = '5px'; // HTML5 canvas hack for letter spacing requires modern canvas or spaces
const spacedSub = subtitleText.split('').join(String.fromCharCode(8202));
ctx.fillText(spacedSub, cx, cy + globeRadius + 60);
}
// 6. Global Fade from Black at start
if (elapsed < 1.0) {
ctx.fillStyle = `rgba(0,0,0,${1 - (elapsed / 1.0)})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
if (animating && elapsed < 12.0) {
animationId = requestAnimationFrame(drawFrame);
} else if (animating) {
animating = false;
}
}
// Synthesize the classic "David Newman-esc" Orchestral Fanfare
function startAudioFanfare() {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (!AudioContext) return;
const audioCtx = new AudioContext();
const masterGain = audioCtx.createGain();
masterGain.gain.value = 0.6; // Base volume
masterGain.connect(audioCtx.destination);
// Helper func to play synthesized brass instruments
function playBrassWave(freq, type, tStart, tDur) {
const osc = audioCtx.createOscillator();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime + tStart);
const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
// Classic swelling brass filter envelope
filter.frequency.setValueAtTime(300, audioCtx.currentTime + tStart);
filter.frequency.linearRampToValueAtTime(3500, audioCtx.currentTime + tStart + Math.min(tDur*0.4, 0.5));
filter.frequency.exponentialRampToValueAtTime(500, audioCtx.currentTime + tStart + tDur);
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(0, audioCtx.currentTime + tStart);
gain.gain.linearRampToValueAtTime(1, audioCtx.currentTime + tStart + 0.05);
gain.gain.setValueAtTime(1, audioCtx.currentTime + tStart + tDur - 0.2);
gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + tStart + tDur);
osc.connect(filter);
filter.connect(gain);
gain.connect(masterGain);
osc.start(audioCtx.currentTime + tStart);
osc.stop(audioCtx.currentTime + tStart + tDur + 0.1);
}
// Generate synthetic drum hit (tympani & crash)
function playDrumHit(tStart) {
// Tympani (low boom)
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(120, audioCtx.currentTime + tStart);
osc.frequency.exponentialRampToValueAtTime(40, audioCtx.currentTime + tStart + 0.4);
gain.gain.setValueAtTime(1.5, audioCtx.currentTime + tStart);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + tStart + 2.0);
osc.connect(gain);
gain.connect(masterGain);
osc.start(audioCtx.currentTime + tStart);
osc.stop(audioCtx.currentTime + tStart + 2.1);
// Crash noise generator
const bufferSize = audioCtx.sampleRate * 1.0;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const noiseFilter = audioCtx.createBiquadFilter();
noiseFilter.type = 'highpass';
noiseFilter.frequency.value = 1000;
const nGain = audioCtx.createGain();
nGain.gain.setValueAtTime(0.7, audioCtx.currentTime + tStart);
nGain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + tStart + 1.0);
noise.connect(noiseFilter);
noiseFilter.connect(nGain);
nGain.connect(masterGain);
noise.start(audioCtx.currentTime + tStart);
noise.stop(audioCtx.currentTime + tStart + 1.2);
}
// Newman fanfare approximate note sheet
const fanfareNotes = [
// Opening Stabs
{f: 196.00, t: 0.5, d: 0.8}, // G3
{f: 196.00, t: 1.5, d: 0.8}, // G3
// Running triplet buildup
{f: 196.00, t: 2.5, d: 0.3}, // G3
{f: 220.00, t: 2.8, d: 0.3}, // A3
{f: 246.94, t: 3.1, d: 0.3}, // B3
// Epic chord land (Synced visually to 3.4s)
{f: 261.63, t: 3.4, d: 2.0}, // C4
{f: 130.81, t: 3.4, d: 2.0}, // C3 harmony
{f: 164.81, t: 3.4, d: 2.0}, // E3 harmony
// The melody rise
{f: 196.00, t: 5.6, d: 0.4}, // G3
{f: 261.63, t: 6.0, d: 0.4}, // C4
{f: 329.63, t: 6.4, d: 0.4}, // E4
// Final resolved crescendo
{f: 392.00, t: 6.8, d: 4.5}, // G4
{f: 261.63, t: 6.8, d: 4.5}, // C4
{f: 523.25, t: 6.8, d: 4.5}, // C5
{f: 130.81, t: 6.8, d: 4.5} // C3 (Bass)
];
// Synthesize full chord for each note mapping
fanfareNotes.forEach(n => {
playBrassWave(n.f, 'sawtooth', n.t, n.d);
playBrassWave(n.f * 1.005, 'sawtooth', n.t, n.d); // slight detune for chorusing
playBrassWave(n.f * 0.5, 'square', n.t, n.d); // sub octave fatness
});
// Add cinematic drums mapped to the main beats
playDrumHit(0.5);
playDrumHit(1.5);
playDrumHit(3.4); // Syncs precisely to text scaling finish
playDrumHit(6.8); // Final major swelling hit
// Background Snare Roll building up to t=3.4
const rollDuration = 2.9;
const sBuffer = audioCtx.createBuffer(1, audioCtx.sampleRate * rollDuration, audioCtx.sampleRate);
const sData = sBuffer.getChannelData(0);
for(let i=0; i < sBuffer.length; i++) sData[i] = (Math.random()*2 - 1) * 0.5;
const snareSrc = audioCtx.createBufferSource();
snareSrc.buffer = sBuffer;
const snareFilter = audioCtx.createBiquadFilter();
snareFilter.type = 'bandpass';
snareFilter.frequency.value = 1200;
const snareGain = audioCtx.createGain();
snareGain.gain.setValueAtTime(0, audioCtx.currentTime + 0.5);
snareGain.gain.exponentialRampToValueAtTime(0.8, audioCtx.currentTime + 3.3);
snareGain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 3.4);
snareSrc.connect(snareFilter);
snareFilter.connect(snareGain);
snareGain.connect(masterGain);
snareSrc.start(audioCtx.currentTime + 0.5);
snareSrc.stop(audioCtx.currentTime + 3.4);
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
// Launch mechanism
btn.addEventListener('click', () => {
btn.style.display = 'none';
if (animationId) cancelAnimationFrame(animationId);
// Kick off Drawing & Synth Track concurrently!
animating = true;
startTime = 0;
requestAnimationFrame(drawFrame);
startAudioFanfare();
});
// Draw initial static frame (Pitch Black)
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
return container;
}
Apply Changes