You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg) {
// Use naturalWidth/Height for intrinsic dimensions, fallback to width/height
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
// 1. Create a temporary canvas to draw the original image and get its pixel data
// This step is necessary to access the image's pixel data.
const sourceCanvas = document.createElement('canvas');
sourceCanvas.width = width;
sourceCanvas.height = height;
const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true }); // Hint for optimization
sourceCtx.drawImage(originalImg, 0, 0, width, height);
let imageData;
try {
imageData = sourceCtx.getImageData(0, 0, width, height);
} catch (e) {
// Handle potential security exceptions (e.g., tainted canvas from cross-origin image)
console.error("Error getting image data:", e);
const errorDiv = document.createElement('div');
errorDiv.textContent = "Could not process image. This may be due to cross-origin restrictions if the image is not hosted on the same domain. Please try with a different image or ensure it's served with appropriate CORS headers.";
errorDiv.style.color = 'red';
errorDiv.style.padding = '10px';
errorDiv.style.fontFamily = 'Arial, sans-serif';
return errorDiv;
}
const data = imageData.data; // Uint8ClampedArray: [R,G,B,A, R,G,B,A, ...]
// 2. Create canvases for displaying Y, Cb, and Cr components
const yCanvas = document.createElement('canvas');
yCanvas.width = width;
yCanvas.height = height;
const yCtx = yCanvas.getContext('2d');
const yImageData = yCtx.createImageData(width, height);
const yData = yImageData.data;
const cbCanvas = document.createElement('canvas');
cbCanvas.width = width;
cbCanvas.height = height;
const cbCtx = cbCanvas.getContext('2d');
const cbImageData = cbCtx.createImageData(width, height);
const cbData = cbImageData.data;
const crCanvas = document.createElement('canvas');
crCanvas.width = width;
crCanvas.height = height;
const crCtx = crCanvas.getContext('2d');
const crImageData = crCtx.createImageData(width, height);
const crData = crImageData.data;
// 3. Process each pixel: convert RGB to YCbCr and prepare for display
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const alpha = data[i + 3]; // Preserve original alpha
// RGB to YCbCr conversion (using common ITU-R BT.601 / JPEG formulas for full-range 0-255)
// Y': Luma component
// Cb: Blue-difference chroma component (B-Y)
// Cr: Red-difference chroma component (R-Y)
const y_val = 0.299 * r + 0.587 * g + 0.114 * b;
const cb_val = 128.0 - 0.168736 * r - 0.331264 * g + 0.500 * b;
const cr_val = 128.0 + 0.500 * r - 0.418688 * g - 0.081312 * b;
// Clamp results to [0, 255] range and round to the nearest integer
const y_clamped = Math.max(0, Math.min(255, Math.round(y_val)));
const cb_clamped = Math.max(0, Math.min(255, Math.round(cb_val))); // Neutral Cb is 128
const cr_clamped = Math.max(0, Math.min(255, Math.round(cr_val))); // Neutral Cr is 128
// Populate Y (Luma) channel data (displayed as grayscale)
yData[i] = y_clamped; // R value for grayscale
yData[i + 1] = y_clamped; // G value for grayscale
yData[i + 2] = y_clamped; // B value for grayscale
yData[i + 3] = alpha; // Alpha
// Populate Cb (Blue-Yellow Chrominance) channel data (colorized display)
// Cb=0 (max negative B-Y, more yellow) -> mapped to Yellow (255,255,0)
// Cb=128 (neutral B-Y) -> mapped to Grayish (127,127,128)
// Cb=255 (max positive B-Y, more blue) -> mapped to Blue (0,0,255)
cbData[i] = (255 - cb_clamped); // R component for Cb visualization
cbData[i + 1] = (255 - cb_clamped); // G component for Cb visualization
cbData[i + 2] = cb_clamped; // B component for Cb visualization
cbData[i + 3] = alpha; // Alpha
// Populate Cr (Red-Cyan Chrominance) channel data (colorized display)
// Cr=0 (max negative R-Y, more cyan) -> mapped to Cyan (0,255,255)
// Cr=128 (neutral R-Y) -> mapped to Grayish (128,127,127)
// Cr=255 (max positive R-Y, more red) -> mapped to Red (255,0,0)
crData[i] = cr_clamped; // R component for Cr visualization
crData[i + 1] = (255 - cr_clamped); // G component for Cr visualization
crData[i + 2] = (255 - cr_clamped); // B component for Cr visualization
crData[i + 3] = alpha; // Alpha
}
// 4. Put the processed image data onto their respective canvases
yCtx.putImageData(yImageData, 0, 0);
cbCtx.putImageData(cbImageData, 0, 0);
crCtx.putImageData(crImageData, 0, 0);
// 5. Create a container element to hold the labeled canvases for display
const mainContainer = document.createElement('div');
mainContainer.style.display = 'flex';
mainContainer.style.flexDirection = 'column'; // Stack Y, Cb, Cr component views vertically
mainContainer.style.gap = '15px'; // Space between each component view
mainContainer.style.padding = '10px'; // Padding around the main container
mainContainer.style.fontFamily = 'Arial, sans-serif'; // Set a common font
mainContainer.style.textAlign = 'center'; // Center-align text (like labels)
// Helper function to create a styled container for a canvas and its label
function createComponentView(canvas, labelText) {
const viewDiv = document.createElement('div');
// Optional styling for individual component view containers:
// viewDiv.style.border = '1px solid #f0f0f0';
// viewDiv.style.padding = '10px';
// viewDiv.style.borderRadius = '4px';
// viewDiv.style.backgroundColor = '#f9f9f9';
const label = document.createElement('p');
label.textContent = labelText;
label.style.margin = '0 0 8px 0'; // Margin below the label, above the canvas
label.style.fontWeight = 'bold';
label.style.fontSize = '1em';
// Style the canvas for responsiveness and proper display
canvas.style.maxWidth = '100%'; // Ensure canvas scales down if container is narrower
canvas.style.height = 'auto'; // Maintain aspect ratio when scaling
canvas.style.display = 'block'; // Remove an_valy default inline element spacing (e.g., bottom margin)
canvas.style.margin = '0 auto'; // Center canvas if its max-width makes it smaller than viewDiv
canvas.style.border = '1px solid #ccc'; // Add a light border around the canvas itself
viewDiv.appendChild(label);
viewDiv.appendChild(canvas);
return viewDiv;
}
// Add the Y, Cb, and Cr component views to the main container
mainContainer.appendChild(createComponentView(yCanvas, 'Y (Luma Component)'));
mainContainer.appendChild(createComponentView(cbCanvas, 'Cb (Blue-Yellow Chrominance Component)'));
mainContainer.appendChild(createComponentView(crCanvas, 'Cr (Red-Cyan Chrominance Component)'));
return mainContainer; // Return the single DOM element containing all views
}
Apply Changes