You can edit the below JavaScript code to customize the image tool.
Apply Changes
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;
}
Apply Changes