You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, swirlFactor = 0, swirlRadius = 200, waveAmplitude = 0, waveFrequency = 20, invertColors = "false", hueShift = 0, saturationShift = 0, lightnessShift = 0, rotationAngle = 0, playSound = "false") {
// --- 1. Parameter Parsing & Validation ---
const doInvert = invertColors.toLowerCase() === 'true';
const doPlaySound = playSound.toLowerCase() === 'true';
swirlFactor = Number(swirlFactor) || 0;
swirlRadius = Math.max(1, Number(swirlRadius)) || 200;
waveAmplitude = Number(waveAmplitude) || 0;
waveFrequency = Math.max(1, Number(waveFrequency)) || 20;
hueShift = Number(hueShift) || 0;
saturationShift = Number(saturationShift) || 0;
lightnessShift = Number(lightnessShift) || 0;
rotationAngle = Number(rotationAngle) || 0;
// --- 2. Audio Effect ---
// Helper function to play a sound effect using Web Audio API
async function playTennisSound() {
// Create or reuse an AudioContext. Store it statically on the function object
// to avoid creating a new one on every call.
if (!processImage.audioContext) {
try {
processImage.audioContext = new(window.AudioContext || window.webkitAudioContext)();
} catch (e) {
console.error("Web Audio API is not supported in this browser.", e);
return;
}
}
const audioCtx = processImage.audioContext;
// Browsers require user interaction to start an AudioContext.
// We attempt to resume it if it's in a suspended state.
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
const now = audioCtx.currentTime;
// Sound characteristics for a "tennis hit"
oscillator.type = 'triangle';
gainNode.gain.setValueAtTime(0.5, now);
// A quick pitch drop to simulate a "thwack"
oscillator.frequency.setValueAtTime(400, now);
oscillator.frequency.exponentialRampToValueAtTime(150, now + 0.1);
// A fast decay in volume
gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
oscillator.start(now);
oscillator.stop(now + 0.3);
}
if (doPlaySound) {
await playTennisSound();
}
// --- 3. Canvas Setup ---
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', {
willReadFrequently: true
});
const w = canvas.width = originalImg.width;
const h = canvas.height = originalImg.height;
// --- 4. Initial Draw with Rotation ---
// Apply rotation transformation before any pixel manipulation
ctx.translate(w / 2, h / 2);
ctx.rotate(rotationAngle * Math.PI / 180);
ctx.translate(-w / 2, -h / 2);
ctx.drawImage(originalImg, 0, 0, w, h);
const originalData = ctx.getImageData(0, 0, w, h);
const newData = ctx.createImageData(w, h);
const originalPixels = originalData.data;
const newPixels = newData.data;
ctx.resetTransform(); // Clear rotation for the final putImageData
// --- 5. HSL Color Conversion Helpers ---
const rgbToHsl = (r, g, b) => {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h * 360, s: s * 100, l: l * 100 };
};
const hslToRgb = (h, s, l) => {
s /= 100; l /= 100;
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
const h_normalized = h / 360;
r = hue2rgb(p, q, h_normalized + 1/3);
g = hue2rgb(p, q, h_normalized);
b = hue2rgb(p, q, h_normalized - 1/3);
}
return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
};
// --- 6. Pixel Processing Loop ---
const centerX = w / 2;
const centerY = h / 2;
const swirlStrength = swirlFactor * Math.PI / 180; // Convert degrees to radians for easier math
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
// --- Calculate source coordinates with inverse transforms ---
// Start with the destination pixel's coordinates
let sourceX = x;
let sourceY = y;
// Apply inverse swirl transform
if (swirlFactor !== 0) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < swirlRadius) {
const angle = Math.atan2(dy, dx);
const percent = (swirlRadius - distance) / swirlRadius;
const swirlAngle = percent * percent * swirlStrength;
const finalAngle = angle - swirlAngle; // Subtract to reverse
sourceX = centerX + distance * Math.cos(finalAngle);
sourceY = centerY + distance * Math.sin(finalAngle);
}
}
// Apply inverse wave transform
if (waveAmplitude !== 0) {
// To find the source for a horizontal wave, we subtract the offset
sourceX -= waveAmplitude * Math.sin(sourceY / waveFrequency);
}
// --- Fetch source pixel color ---
// Clamp coordinates to stay within image bounds
const sx = Math.max(0, Math.min(w - 1, Math.round(sourceX)));
const sy = Math.max(0, Math.min(h - 1, Math.round(sourceY)));
const srcIndex = (sy * w + sx) * 4;
let r = originalPixels[srcIndex];
let g = originalPixels[srcIndex + 1];
let b = originalPixels[srcIndex + 2];
let a = originalPixels[srcIndex + 3];
// --- Apply color filters ---
if (doInvert) {
r = 255 - r;
g = 255 - g;
b = 255 - b;
}
if (hueShift !== 0 || saturationShift !== 0 || lightnessShift !== 0) {
let hsl = rgbToHsl(r, g, b);
hsl.h = (hsl.h + hueShift) % 360;
if (hsl.h < 0) hsl.h += 360;
hsl.s = Math.max(0, Math.min(100, hsl.s + saturationShift));
hsl.l = Math.max(0, Math.min(100, hsl.l + lightnessShift));
let newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
r = newRgb.r;
g = newRgb.g;
b = newRgb.b;
}
// --- Write to new image data ---
const destIndex = (y * w + x) * 4;
newPixels[destIndex] = r;
newPixels[destIndex + 1] = g;
newPixels[destIndex + 2] = b;
newPixels[destIndex + 3] = a;
}
}
// --- 7. Finalize and Return ---
ctx.putImageData(newData, 0, 0);
return canvas;
}
Apply Changes