Please bookmark this page to avoid losing your image tool!

Image YIQ Color Viewer

(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) {
    let width = originalImg.width;
    let height = originalImg.height;

    if (width === 0 || height === 0) {
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = 200; // A small default size
        emptyCanvas.height = 50;
        const emptyCtx = emptyCanvas.getContext('2d');
        if (emptyCtx) {
            emptyCtx.font = "12px Arial";
            emptyCtx.fillStyle = "black";
            emptyCtx.fillText("Image has zero dimensions or is not loaded.", 10, 20);
        }
        return emptyCanvas;
    }

    // Max typical canvas dimension. Larger images will be scaled down.
    const MAX_CANVAS_SIDE = 16384; // Using a more conservative limit like 16384px. Some browsers go to 32767.
    
    let effectiveWidth = width;
    let effectiveHeight = height; // This will be the height of one component (Y, I, or Q)

    // Check if total height (height * 3 for Y, I, Q) exceeds max
    if (height * 3 > MAX_CANVAS_SIDE) {
        const scaleFactor = MAX_CANVAS_SIDE / (height * 3);
        effectiveWidth = Math.floor(width * scaleFactor);
        effectiveHeight = Math.floor(height * scaleFactor);
        // Ensure minimum 1px dimension if original was non-zero
        if (width > 0 && effectiveWidth === 0) effectiveWidth = 1;
        if (height > 0 && effectiveHeight === 0) effectiveHeight = 1;
    }

    // Check if width exceeds max (even after potential scaling for height)
    if (effectiveWidth > MAX_CANVAS_SIDE) {
        const scaleFactor = MAX_CANVAS_SIDE / effectiveWidth;
        effectiveWidth = MAX_CANVAS_SIDE;
        // Adjust effectiveHeight proportionally
        effectiveHeight = Math.floor(effectiveHeight * scaleFactor);
        if (height > 0 && effectiveHeight === 0) effectiveHeight = 1;
    }
    
    // If scaling drastically reduced dimensions to 0, handle it.
    if (effectiveWidth === 0 || effectiveHeight === 0) {
        const scaledTooSmallCanvas = document.createElement('canvas');
        scaledTooSmallCanvas.width = 200;
        scaledTooSmallCanvas.height = 50;
        const SASCtx = scaledTooSmallCanvas.getContext('2d');
        if (SASCtx) {
            SASCtx.font = "12px Arial";
            SASCtx.fillStyle = "black";
            SASCtx.fillText("Image too large and scaled to zero dimension.", 10, 20);
        }
        return scaledTooSmallCanvas;
    }


    // 1. Temporary canvas to get pixel data from originalImg
    // The image is drawn (potentially scaled) to effectiveWidth/effectiveHeight
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = effectiveWidth;
    tempCanvas.height = effectiveHeight;
    const tempCtx = tempCanvas.getContext('2d');
    tempCtx.drawImage(originalImg, 0, 0, effectiveWidth, effectiveHeight);
    
    let imageData;
    try {
        imageData = tempCtx.getImageData(0, 0, effectiveWidth, effectiveHeight);
    } catch (e) {
        // Handle potential CORS issues if image isn't fully accessible
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 300;
        errorCanvas.height = 100;
        const errorCtx = errorCanvas.getContext('2d');
        if (errorCtx) {
            errorCtx.font = "12px Arial";
            errorCtx.fillStyle = "red";
            errorCtx.fillText("Error: Could not process image.", 10, 20);
            errorCtx.fillText("This might be due to CORS policy.", 10, 40);
            console.error("Error getting image data:", e);
        }
        return errorCanvas;
    }
    const data = imageData.data;

    // 2. Output canvas setup
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = effectiveWidth;
    outputCanvas.height = effectiveHeight * 3; // Y, I, Q stacked vertically
    const outputCtx = outputCanvas.getContext('2d');
    const outputImageData = outputCtx.createImageData(effectiveWidth, effectiveHeight * 3);
    const outputData = outputImageData.data;

    // YIQ constants
    // Y = 0.299*R + 0.587*G + 0.114*B
    // I = 0.595716*R - 0.274453*G - 0.321263*B  (Range approx +/- 0.595716 if R,G,B are 0-1)
    // Q = 0.211456*R - 0.522591*G + 0.311135*B  (Range approx +/- 0.522591 if R,G,B are 0-1)
    // For R,G,B in [0,255], I_val_max = 0.595716 * 255, Q_val_max = 0.522591 * 255
    const I_MAX_RANGE = 0.595716 * 255;
    const Q_MAX_RANGE = 0.522591 * 255;

    // 3. Pixel processing loop
    for (let y = 0; y < effectiveHeight; y++) {
        for (let x = 0; x < effectiveWidth; x++) {
            const index = (y * effectiveWidth + x) * 4; // Index for input imageData.data
            const r = data[index];
            const g = data[index + 1];
            const b = data[index + 2];
            // const alpha = data[index + 3]; // Alpha is not used in YIQ conversion

            // RGB to YIQ conversion
            const y_val = 0.299 * r + 0.587 * g + 0.114 * b;
            const i_val = 0.595716 * r - 0.274453 * g - 0.321263 * b;
            const q_val = 0.211456 * r - 0.522591 * g + 0.311135 * b;

            // Normalize I and Q components to [0, 255] for grayscale display
            // Y component (y_val) is already in [0, 255] range.
            // For I and Q, their range is [-MAX_RANGE, +MAX_RANGE].
            // Formula: (value + MAX_RANGE) / (2 * MAX_RANGE) * 255
            let norm_i = (i_val + I_MAX_RANGE) / (2 * I_MAX_RANGE) * 255;
            let norm_q = (q_val + Q_MAX_RANGE) / (2 * Q_MAX_RANGE) * 255;

            // Clamp final values to [0, 255] and round to integers
            const final_y = Math.max(0, Math.min(255, Math.round(y_val)));
            const final_i = Math.max(0, Math.min(255, Math.round(norm_i)));
            const final_q = Math.max(0, Math.min(255, Math.round(norm_q)));

            // Set pixels in outputData for Y component (top third)
            const output_idx_y = (y * effectiveWidth + x) * 4;
            outputData[output_idx_y]     = final_y;
            outputData[output_idx_y + 1] = final_y;
            outputData[output_idx_y + 2] = final_y;
            outputData[output_idx_y + 3] = 255; // Alpha

            // Set pixels for I component (middle third)
            // Offset y by `effectiveHeight` for this section
            const output_idx_i = ((y + effectiveHeight) * effectiveWidth + x) * 4;
            outputData[output_idx_i]     = final_i;
            outputData[output_idx_i + 1] = final_i;
            outputData[output_idx_i + 2] = final_i;
            outputData[output_idx_i + 3] = 255; // Alpha

            // Set pixels for Q component (bottom third)
            // Offset y by `2 * effectiveHeight` for this section
            const output_idx_q = ((y + 2 * effectiveHeight) * effectiveWidth + x) * 4;
            outputData[output_idx_q]     = final_q;
            outputData[output_idx_q + 1] = final_q;
            outputData[output_idx_q + 2] = final_q;
            outputData[output_idx_q + 3] = 255; // Alpha
        }
    }

    // 4. Put processed image data onto the output canvas
    outputCtx.putImageData(outputImageData, 0, 0);

    // 5. Add labels
    const textYOffset = 20; // Vertical offset for text from the top of each section
    const textXOffset = 10; // Horizontal offset for text
    
    outputCtx.font = "bold 16px Arial";
    outputCtx.strokeStyle = 'black'; // Outline color
    outputCtx.lineWidth = 2;        // Outline width
    outputCtx.fillStyle = 'white';  // Text color

    // Only draw labels if there's enough vertical space in each component's display area
    if (effectiveHeight >= textYOffset + 5) { // +5 for a small margin below text
        // Y Label
        outputCtx.strokeText("Y Component", textXOffset, textYOffset);
        outputCtx.fillText("Y Component", textXOffset, textYOffset);

        // I Label
        outputCtx.strokeText("I Component", textXOffset, effectiveHeight + textYOffset);
        outputCtx.fillText("I Component", textXOffset, effectiveHeight + textYOffset);

        // Q Label
        outputCtx.strokeText("Q Component", textXOffset, 2 * effectiveHeight + textYOffset);
        outputCtx.fillText("Q Component", textXOffset, 2 * effectiveHeight + textYOffset);
    }
    
    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 YIQ Color Viewer is a web tool that converts RGB images to the YIQ color space, commonly used in television broadcasting. The tool processes the input image and separates it into three components: Y (luminance), I (in-phase), and Q (quadrature). Each component is displayed vertically, allowing users to analyze the different aspects of the image’s color representation. This tool is useful for graphic designers, video editors, and educators needing to understand color space representations or analyze image data for various applications in visual media.

Leave a Reply

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