Please bookmark this page to avoid losing your image tool!

Image Tennis Rounds Visualizer With Effects And Audio

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Tennis Rounds Visualizer With Effects and Audio is a web-based tool designed to enhance images with various visual effects and audio playback. Users can apply effects such as swirling, waving, color inversion, and hue adjustments, as well as adjust saturation and lightness. This tool is particularly suitable for artists, designers, and content creators looking to add dynamic visuals and sound effects to their images. It enables the creation of engaging imagery for use in presentations, social media posts, or creative projects, providing an interactive experience that combines visual creativity with audio elements.

Leave a Reply

Your email address will not be published. Required fields are marked *