Please bookmark this page to avoid losing your image tool!

Image HSV Color Visualizer

(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.
function processImage(originalImg,
    padding = 10,
    backgroundColor = '#f0f0f0',
    labelColor = 'black',
    labelFont = '16px Arial',
    hueLabelText = 'Hue',
    saturationLabelText = 'Saturation',
    valueLabelText = 'Value'
) {
    const w = originalImg.width;
    const h = originalImg.height;

    // Guard against unloaded or zero-size images
    if (w === 0 || h === 0) {
        const emptyCanvas = document.createElement('canvas');
        // Return a small canvas, optionally with a message
        emptyCanvas.width = Math.max(1, w * 3 + padding * 2); // Try to match expected width if only one dim is 0
        emptyCanvas.height = Math.max(1, h + (labelFont.match(/(\d+)px/) ? parseInt(labelFont.match(/(\d+)px/)[1], 10) : 16) + 14);
        if (emptyCanvas.width < 100 && (hueLabelText || saturationLabelText || valueLabelText )) emptyCanvas.width = 100; // Min width for a message
        if (emptyCanvas.height < 20 && (hueLabelText || saturationLabelText || valueLabelText )) emptyCanvas.height = 20; // Min height for a message

        const emptyCtx = emptyCanvas.getContext('2d');
        emptyCtx.fillStyle = backgroundColor;
        emptyCtx.fillRect(0, 0, emptyCanvas.width, emptyCanvas.height);
        
        if (hueLabelText || saturationLabelText || valueLabelText) { // Only show message if labels were expected
            emptyCtx.fillStyle = labelColor;
            emptyCtx.font = labelFont.replace(/(\d+)px/, `${Math.min(12, parseInt(labelFont.match(/(\d+)px/)?.[1] || 12))}px`); // Smaller font for message
            emptyCtx.textAlign = "center";
            emptyCtx.textBaseline = "middle";
            emptyCtx.fillText("Empty or invalid image", emptyCanvas.width / 2, emptyCanvas.height / 2);
        }
        return emptyCanvas;
    }

    // Helper function: RGB to HSV
    // r_in, g_in, b_in are 0-255.
    // Returns h (0-360 degrees), s (0-1), v (0-1).
    function rgbToHsv(r_in, g_in, b_in) {
        const r_norm = r_in / 255;
        const g_norm = g_in / 255;
        const b_norm = b_in / 255;
        
        const max = Math.max(r_norm, g_norm, b_norm);
        const min = Math.min(r_norm, g_norm, b_norm);
        let h_val;
        const v_val = max;
        const d = max - min;
        const s_val = max === 0 ? 0 : d / max;

        if (d === 0) {
            h_val = 0; // achromatic, hue is undefined but often set to 0
        } else {
            switch (max) {
                case r_norm: h_val = (g_norm - b_norm) / d + (g_norm < b_norm ? 6 : 0); break;
                case g_norm: h_val = (b_norm - r_norm) / d + 2; break;
                case b_norm: h_val = (r_norm - g_norm) / d + 4; break;
            }
            h_val /= 6; // h_val is now in [0, 1]
        }
        return { h: h_val * 360, s: s_val, v: v_val };
    }

    // Helper function: HSV to RGB
    // h_in (0-360 degrees), s_in (0-1), v_in (0-1).
    // Returns r, g, b (0-255).
    function hsvToRgb(h_in, s_in, v_in) {
        let r_out, g_out, b_out;
        
        // Normalize h_in to be in [0, 360)
        let h_normalized = h_in % 360;
        if (h_normalized < 0) h_normalized += 360;

        const h_sector = h_normalized / 60; // h_sector is in [0, 6)
        const i = Math.floor(h_sector);
        const f = h_sector - i; // fractional part
        const p = v_in * (1 - s_in);
        const q = v_in * (1 - f * s_in);
        const t = v_in * (1 - (1 - f) * s_in);

        switch (i) {
            case 0: r_out = v_in; g_out = t; b_out = p; break;
            case 1: r_out = q; g_out = v_in; b_out = p; break;
            case 2: r_out = p; g_out = v_in; b_out = t; break;
            case 3: r_out = p; g_out = q; b_out = v_in; break;
            case 4: r_out = t; g_out = p; b_out = v_in; break;
            case 5: r_out = v_in; g_out = p; b_out = q; break;
            default: r_out = v_in; g_out = v_in; b_out = v_in; break; // Should not happen with h_normalized
        }
        return {
            r: Math.round(r_out * 255),
            g: Math.round(g_out * 255),
            b: Math.round(b_out * 255)
        };
    }

    // 1. Get pixel data from original image
    const offscreenCanvas = document.createElement('canvas');
    offscreenCanvas.width = w;
    offscreenCanvas.height = h;
    const offscreenCtx = offscreenCanvas.getContext('2d', { willReadFrequently: true });
    offscreenCtx.drawImage(originalImg, 0, 0, w, h);
    const imageData = offscreenCtx.getImageData(0, 0, w, h);
    const data = imageData.data;

    // 2. Create ImageData for Hue, Saturation, Value visualizations
    const hueImageData = offscreenCtx.createImageData(w, h);
    const satImageData = offscreenCtx.createImageData(w, h);
    const valImageData = offscreenCtx.createImageData(w, h);

    // 3. Process each pixel
    for (let idx = 0; idx < data.length; idx += 4) {
        const r_px = data[idx];
        const g_px = data[idx + 1];
        const b_px = data[idx + 2];
        const alpha_px = data[idx + 3];

        const { h: hue, s: saturation, v: value } = rgbToHsv(r_px, g_px, b_px);
        
        // Hue visualization: color derived from H, with S=1, V=1. Achromatic pixels (S=0) display as gray based on V.
        const hueColor = saturation === 0 ? hsvToRgb(0, 0, value) : hsvToRgb(hue, 1, 1);
        hueImageData.data[idx]     = hueColor.r;
        hueImageData.data[idx + 1] = hueColor.g;
        hueImageData.data[idx + 2] = hueColor.b;
        hueImageData.data[idx + 3] = alpha_px;

        // Saturation visualization (grayscale: S mapped to 0-255)
        const satGray = Math.round(saturation * 255);
        satImageData.data[idx]     = satGray;
        satImageData.data[idx + 1] = satGray;
        satImageData.data[idx + 2] = satGray;
        satImageData.data[idx + 3] = alpha_px;

        // Value visualization (grayscale: V mapped to 0-255)
        const valGray = Math.round(value * 255);
        valImageData.data[idx]     = valGray;
        valImageData.data[idx + 1] = valGray;
        valImageData.data[idx + 2] = valGray;
        valImageData.data[idx + 3] = alpha_px;
    }

    // 4. Prepare for output canvas
    const fontSizeMatch = labelFont.match(/(\d+)px/);
    const fontSize = fontSizeMatch ? parseInt(fontSizeMatch[1], 10) : 16;
    const actualLabelHeight = Math.max(20, fontSize + 14); // Min 20px height for labels area
    const actualPadding = Math.max(0, padding); // Ensure padding is not negative

    const totalWidth = w * 3 + actualPadding * 2;
    const totalHeight = h + actualLabelHeight;

    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = totalWidth;
    outputCanvas.height = totalHeight;
    const outputCtx = outputCanvas.getContext('2d');

    // Fill background
    outputCtx.fillStyle = backgroundColor;
    outputCtx.fillRect(0, 0, totalWidth, totalHeight);

    // 5. Draw the component images (Hue, Saturation, Value)
    // Draw to temporary canvases first to use putImageData, then drawImage to final canvas
    const tempHueCanvas = document.createElement('canvas');
    tempHueCanvas.width = w; tempHueCanvas.height = h;
    tempHueCanvas.getContext('2d').putImageData(hueImageData, 0, 0);

    const tempSatCanvas = document.createElement('canvas');
    tempSatCanvas.width = w; tempSatCanvas.height = h;
    tempSatCanvas.getContext('2d').putImageData(satImageData, 0, 0);

    const tempValCanvas = document.createElement('canvas');
    tempValCanvas.width = w; tempValCanvas.height = h;
    tempValCanvas.getContext('2d').putImageData(valImageData, 0, 0);

    const imageY = actualLabelHeight;
    outputCtx.drawImage(tempHueCanvas, 0, imageY);
    outputCtx.drawImage(tempSatCanvas, w + actualPadding, imageY);
    outputCtx.drawImage(tempValCanvas, w * 2 + actualPadding * 2, imageY);

    // 6. Add labels
    outputCtx.fillStyle = labelColor;
    outputCtx.font = labelFont;
    outputCtx.textAlign = 'center';
    outputCtx.textBaseline = 'middle'; // Vertically center text in the label area
    
    const labelTextY = actualLabelHeight / 2;

    outputCtx.fillText(hueLabelText, w / 2, labelTextY);
    outputCtx.fillText(saturationLabelText, w + actualPadding + w / 2, labelTextY);
    outputCtx.fillText(valueLabelText, w * 2 + actualPadding * 2 + w / 2, labelTextY);
    
    return outputCanvas;
}

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 HSV Color Visualizer is a tool that allows users to analyze and visualize the hue, saturation, and value (HSV) components of an image. By uploading an image, the tool generates visual representations of these color components in separate areas, making it easy to understand the distribution of colors, the intensity of saturation, and the brightness levels within the image. This tool can be useful for graphic designers, photographers, and anyone working with color in digital images, as it helps in assessing and selecting color palettes, enhancing images, or simply understanding their color composition.

Leave a Reply

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