You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
audioUrl = "https://actions.google.com/sounds/v1/alarms/digital_watch_alarm_long.ogg",
ringtoneVolume = 0.8
) {
// Create the main container
const container = document.createElement('div');
container.style.position = 'relative';
container.style.display = 'inline-block';
container.style.maxWidth = '100%';
container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
container.style.overflow = 'hidden';
container.style.borderRadius = '8px';
container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
// Create and setup the canvas
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto';
canvas.style.display = 'block';
// Initial draw of the image
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0, width, height);
// Add a slight dark tint initially
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.fillRect(0, 0, width, height);
container.appendChild(canvas);
// Create the audio element
const audio = document.createElement('audio');
audio.crossOrigin = 'anonymous'; // Important for Web Audio API if grabbing external audio
audio.src = audioUrl;
audio.loop = true;
audio.volume = Math.max(0, Math.min(1, Number(ringtoneVolume)));
// Create interactive UI Overlay
const uiOverlay = document.createElement('div');
uiOverlay.style.position = 'absolute';
uiOverlay.style.top = '0';
uiOverlay.style.left = '0';
uiOverlay.style.width = '100%';
uiOverlay.style.height = '100%';
uiOverlay.style.display = 'flex';
uiOverlay.style.alignItems = 'center';
uiOverlay.style.justifyContent = 'center';
uiOverlay.style.cursor = 'pointer';
uiOverlay.style.transition = 'background-color 0.3s';
// Play/Pause Button
const playBtn = document.createElement('div');
playBtn.innerHTML = '▶'; // Play icon
playBtn.style.fontSize = '64px';
playBtn.style.color = 'white';
playBtn.style.textShadow = '0 2px 10px rgba(0,0,0,0.6)';
playBtn.style.transition = 'opacity 0.3s';
uiOverlay.appendChild(playBtn);
container.appendChild(uiOverlay);
// Audio Visualizer Variables
let audioCtx, analyser, source;
let isInitialized = false;
let isPlaying = false;
let animationId;
function initAudio() {
try {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioCtx.createAnalyser();
source = audioCtx.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(audioCtx.destination);
analyser.fftSize = 128; // Gives 64 frequency bins
isInitialized = true;
} catch (e) {
console.warn("Web Audio API could not be initialized:", e);
}
}
function drawVisualizer() {
if (!isPlaying) {
// Draw static state when paused
ctx.clearRect(0, 0, width, height);
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.fillRect(0, 0, width, height);
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
return;
}
ctx.clearRect(0, 0, width, height);
ctx.drawImage(originalImg, 0, 0, width, height);
// Darken background to make visualizer pop out
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, width, height);
if (isInitialized) {
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(dataArray);
const centerX = width / 2;
const centerY = height / 2;
const radius = Math.min(width, height) / 4;
// Draw inner circle
ctx.lineWidth = 4;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.stroke();
// Draw frequency bars
const angleStep = (2 * Math.PI) / bufferLength;
for (let i = 0; i < bufferLength; i++) {
// Normalize frequency value
const v = dataArray[i] / 255.0;
// Max height for bars is about 1/3 of the shortest dimension
const barHeight = v * (Math.min(width, height) / 3);
const angle = i * angleStep - Math.PI / 2; // Offset by -90deg to start at top
const startX = centerX + Math.cos(angle) * (radius + 5);
const startY = centerY + Math.sin(angle) * (radius + 5);
const endX = centerX + Math.cos(angle) * (radius + 5 + barHeight);
const endY = centerY + Math.sin(angle) * (radius + 5 + barHeight);
ctx.strokeStyle = `hsla(${(i / bufferLength) * 360}, 100%, 65%, 0.9)`;
ctx.lineWidth = Math.max(2, (radius * 2 * Math.PI) / bufferLength * 0.7);
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.stroke();
}
}
animationId = requestAnimationFrame(drawVisualizer);
}
// UI Event listeners
uiOverlay.addEventListener('mouseenter', () => {
playBtn.style.opacity = '1';
});
uiOverlay.addEventListener('mouseleave', () => {
if (isPlaying) {
playBtn.style.opacity = '0';
}
});
uiOverlay.addEventListener('click', async () => {
// Init audio context on first user interaction
if (!isInitialized) {
initAudio();
}
// Resume context per browser autoplay policies
if (audioCtx && audioCtx.state === 'suspended') {
await audioCtx.resume();
}
if (audio.paused) {
try {
await audio.play();
isPlaying = true;
playBtn.innerHTML = '⏸'; // Pause icon
playBtn.style.opacity = '0'; // Hide button on play, shows on hover
if (!animationId) {
drawVisualizer();
}
} catch (err) {
console.error("Audio playback failed:", err);
alert("Could not play audio. Check URL and cross-origin permissions.");
}
} else {
audio.pause();
isPlaying = false;
playBtn.innerHTML = '▶'; // Play icon
playBtn.style.opacity = '1';
}
});
return container;
}
Apply Changes