Please bookmark this page to avoid losing your image tool!

Image Analysis For YTP Tennis Round

(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, roundNumber = 1, overlayText = 'YOUR TURN', paletteSize = 5, hueShiftDegrees = 30) {

    /**
     * Dynamically loads a font from a URL if not already loaded.
     * Caches the loading promise to handle concurrent calls.
     * @param {string} fontFamily - The font-family name.
     * @param {string} fontUrl - The URL of the font file (e.g., woff2).
     */
    const loadFont = async (fontFamily, fontUrl) => {
        if (!window.loadedFontPromises) {
            window.loadedFontPromises = {};
        }
        if (!window.loadedFontPromises[fontFamily]) {
            const font = new FontFace(fontFamily, `url(${fontUrl})`);
            window.loadedFontPromises[fontFamily] = font.load().then(loadedFont => {
                document.fonts.add(loadedFont);
            }).catch(e => {
                console.error(`Font '${fontFamily}' failed to load:`, e);
            });
        }
        await window.loadedFontPromises[fontFamily];
    };

    /**
     * Converts an RGB color value to HSL.
     * @param {number} r - Red value [0, 255].
     * @param {number} g - Green value [0, 255].
     * @param {number} b - Blue value [0, 255].
     * @returns {Array<number>} - [h, s, l] array, h in [0, 360], s,l in [0, 1].
     */
    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 = 0, l = (max + min) / 2;
        if (max !== min) {
            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 * 360, s, l];
    };

    /**
     * Converts an HSL color value to RGB.
     * @param {number} h - Hue value [0, 360].
     * @param {number} s - Saturation value [0, 1].
     * @param {number} l - Lightness value [0, 1].
     * @returns {Array<number>} - [r, g, b] array, each in [0, 255].
     */
    const hslToRgb = (h, s, l) => {
        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;
            h /= 360;
            r = hue2rgb(p, q, h + 1 / 3);
            g = hue2rgb(p, q, h);
            b = hue2rgb(p, q, h - 1 / 3);
        }
        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
    };

    /**
     * Converts RGB components to a CSS hex string.
     * @param {number} r
     * @param {number} g
     * @param {number} b
     * @returns {string} - The hex color string (e.g., "#ff0000").
     */
    const rgbToHex = (r, g, b) => {
        const toHex = c => ('0' + Math.round(c).toString(16)).slice(-2);
        return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
    };

    // 1. Load the required font
    await loadFont('Bangers', 'https://fonts.gstatic.com/s/bangers/v24/FeVQS0BTqb0h60ACL5k.woff2');

    // 2. Define layout constants and create the final canvas
    const {
        width,
        height
    } = originalImg;
    const PALETTE_AREA_HEIGHT = 70;
    const PADDING = 10;
    const FONT_FAMILY = 'Bangers, cursive';

    const finalCanvas = document.createElement('canvas');
    finalCanvas.width = width;
    finalCanvas.height = height + PALETTE_AREA_HEIGHT + PADDING;
    const finalCtx = finalCanvas.getContext('2d');
    finalCtx.fillStyle = '#1a1a1a';
    finalCtx.fillRect(0, 0, finalCanvas.width, finalCanvas.height);

    // 3. Use a temporary canvas for image processing and analysis
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = width;
    tempCanvas.height = height;
    const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });

    // 3a. Analyze ORIGINAL image for dominant colors
    tempCtx.drawImage(originalImg, 0, 0);
    const originalImageData = tempCtx.getImageData(0, 0, width, height);
    const originalData = originalImageData.data;

    const colorGroups = {};
    const quantizationShift = 4; // Group colors by shifting bits
    // For performance, sample 1 in 4 pixels
    for (let i = 0; i < originalData.length; i += 16) {
        const r = originalData[i], g = originalData[i + 1], b = originalData[i + 2], a = originalData[i + 3];

        if (a < 128) continue; // Ignore mostly transparent pixels

        const rKey = r >> quantizationShift,
              gKey = g >> quantizationShift,
              bKey = b >> quantizationShift;
        const key = `${rKey},${gKey},${bKey}`;

        if (!colorGroups[key]) {
            colorGroups[key] = { r: 0, g: 0, b: 0, count: 0 };
        }
        colorGroups[key].r += r;
        colorGroups[key].g += g;
        colorGroups[key].b += b;
        colorGroups[key].count++;
    }

    const sortedColorGroups = Object.values(colorGroups).sort((a, b) => b.count - a.count);
    const dominantColors = sortedColorGroups.slice(0, paletteSize).map(group => ({
        r: group.r / group.count,
        g: group.g / group.count,
        b: group.b / group.count,
    }));

    // 3b. Apply hue shift effect
    for (let i = 0; i < originalData.length; i += 4) {
        let [h, s, l] = rgbToHsl(originalData[i], originalData[i + 1], originalData[i + 2]);
        h = (h + hueShiftDegrees) % 360;
        if (h < 0) h += 360;
        const [r, g, b] = hslToRgb(h, s, l);

        originalData[i] = r; originalData[i + 1] = g; originalData[i + 2] = b;
    }
    tempCtx.putImageData(originalImageData, 0, 0);

    // 4. Compose the final canvas
    // 4a. Draw the processed image
    finalCtx.drawImage(tempCanvas, 0, 0);

    // 4b. Draw text overlay
    const overlayHeight = Math.max(80, height * 0.15);
    finalCtx.fillStyle = 'rgba(0, 0, 0, 0.6)';
    finalCtx.fillRect(0, height - overlayHeight, width, overlayHeight);

    finalCtx.fillStyle = '#FFFFFF';
    finalCtx.textAlign = 'center';
    finalCtx.textBaseline = 'middle';
    finalCtx.font = `${overlayHeight * 0.4}px ${FONT_FAMILY}`;
    finalCtx.fillText(`ROUND ${roundNumber}`, width / 2, height - overlayHeight / 2 - (overlayHeight * 0.1));
    finalCtx.font = `${overlayHeight * 0.25}px ${FONT_FAMILY}`;
    finalCtx.fillText(overlayText, width / 2, height - overlayHeight / 2 + (overlayHeight * 0.25));

    // 4c. Draw color palette
    const paletteY = height + PADDING;
    const swatchWidth = (width - (paletteSize + 1) * PADDING) / Math.max(1, paletteSize);
    const swatchHeight = PALETTE_AREA_HEIGHT - 25;

    finalCtx.textAlign = 'center';
    for (let i = 0; i < dominantColors.length; i++) {
        const color = dominantColors[i];
        const hex = rgbToHex(color.r, color.g, color.b);
        const x = PADDING + i * (swatchWidth + PADDING);

        finalCtx.fillStyle = hex;
        finalCtx.fillRect(x, paletteY, swatchWidth, swatchHeight);

        if (swatchWidth > 40) {
            const luminance = (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255;
            finalCtx.fillStyle = luminance > 0.5 ? '#000000' : '#FFFFFF';
            finalCtx.font = '12px monospace';
            finalCtx.textBaseline = 'bottom';
            finalCtx.fillText(hex, x + swatchWidth / 2, paletteY + swatchHeight - 4);
        }
    }
    finalCtx.fillStyle = '#CCCCCC';
    finalCtx.textBaseline = 'top';
    finalCtx.font = '12px sans-serif';
    finalCtx.fillText('DOMINANT COLORS', width / 2, paletteY + swatchHeight + 5);

    // 5. Return canvas
    return finalCanvas;
}

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 Analysis for YTP Tennis Round’ tool allows users to process images for use in YTP Tennis rounds by analyzing them to extract dominant colors and applying a hue shift effect. This tool is useful for creating visually dynamic images that can be overlaid with text indicating the round number and a user-defined turn message. The output is a canvas that displays the modified image along with a color palette of the dominant colors extracted from the original image, which can enhance the aesthetic of video game or online competition contexts.

Leave a Reply

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