You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, numSamplesXStr = "5", numSamplesYStr = "5", swatchSizeStr = "40", fontSizeStr = "10") {
// Parameter parsing and validation
// Ensure parameters are numbers with valid defaults and constraints
// Guideline 2: Parameters (except originalImg) can be string or number.
// We'll convert them to numbers for internal use.
const numSamplesX = Math.max(1, parseInt(String(numSamplesXStr), 10) || 5);
const numSamplesY = Math.max(1, parseInt(String(numSamplesYStr), 10) || 5);
const swatchSize = Math.max(10, parseInt(String(swatchSizeStr), 10) || 40);
const fontSize = Math.max(8, parseInt(String(fontSizeStr), 10) || 10);
// Color conversion helper functions: RGB -> XYZ -> Lab -> LCH (HCL)
// sRGB (0-1 range) to Linear RGB (0-1 range)
function srgbToLinear(c) {
// Clamp input to 0-1 range to handle potential floating point inaccuracies
c = Math.max(0, Math.min(1, c));
if (c <= 0.04045) {
return c / 12.92;
} else {
return Math.pow((c + 0.055) / 1.055, 2.4);
}
}
// RGB (0-255 range) to XYZ (D65 illuminant)
function rgbToXyz(r, g, b) {
const r_norm = r / 255;
const g_norm = g / 255;
const b_norm = b / 255;
const r_lin = srgbToLinear(r_norm);
const g_lin = srgbToLinear(g_norm);
const b_lin = srgbToLinear(b_norm);
// sRGB to XYZ transformation matrix (D65 illuminant)
// Coefficients from http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
const x = r_lin * 0.4124564 + g_lin * 0.3575761 + b_lin * 0.1804375;
const y = r_lin * 0.2126729 + g_lin * 0.7151522 + b_lin * 0.0721750;
const z = r_lin * 0.0193339 + g_lin * 0.1191920 + b_lin * 0.9503041;
return [x, y, z];
}
// Constants for XYZ to Lab conversion (D65 reference white)
const D65_XN = 0.95047;
const D65_YN = 1.00000;
const D65_ZN = 1.08883;
// Epsilon and Kappa for CIELAB formula
const XYZ_LAB_EPSILON = 216 / 24389; // (6/29)^3
const XYZ_LAB_KAPPA = 24389 / 27; // (29/3)^3, used in the f(t) function variant (kappa*t+16)/116
// The f(t) function for CIELAB conversion
function xyz_to_lab_term_func(t) {
if (t > XYZ_LAB_EPSILON) {
return Math.cbrt(t); // t^(1/3)
} else {
return (XYZ_LAB_KAPPA * t + 16) / 116;
}
}
// XYZ to CIELAB
function xyzToLab(x, y, z) {
const xr = x / D65_XN;
const yr = y / D65_YN;
const zr = z / D65_ZN;
const fx = xyz_to_lab_term_func(xr);
const fy = xyz_to_lab_term_func(yr);
const fz = xyz_to_lab_term_func(zr);
const l_star = (116 * fy) - 16;
const a_star = 500 * (fx - fy);
const b_star = 200 * (fy - fz);
return [l_star, a_star, b_star];
}
// CIELAB to LCH (HCL: L=Lightness, C=Chroma, H=Hue)
function labToLch(l_star, a_star, b_star) {
const c_star = Math.sqrt(a_star * a_star + b_star * b_star); // Chroma
let h_star_deg = 0; // Hue in degrees
// Hue is undefined if Chroma is 0 (achromatic colors like white, gray, black)
// We conventionally set it to 0 in such cases.
if (c_star !== 0) {
const h_star_rad = Math.atan2(b_star, a_star); // Hue in radians
h_star_deg = h_star_rad * (180 / Math.PI);
if (h_star_deg < 0) { // Ensure hue is in [0, 360) range
h_star_deg += 360;
}
}
// L_hcl (Lightness) is l_star from CIELAB
return [l_star, c_star, h_star_deg];
}
// Create a source canvas to get image pixel data
const srcCanvas = document.createElement('canvas');
// Use willReadFrequently for potential performance optimization if supported
const srcCtx = srcCanvas.getContext('2d', { willReadFrequently: true });
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
// Handle cases where image might not have loaded or has invalid dimensions
if (!imgWidth || !imgHeight || typeof imgWidth !== 'number' || typeof imgHeight !== 'number' || imgWidth <= 0 || imgHeight <= 0) {
console.error("Image HCL Color Showcase: Image has invalid dimensions.", originalImg);
const errCanvas = document.createElement('canvas');
errCanvas.width = 350; errCanvas.height = 60;
const errCtx = errCanvas.getContext('2d');
errCtx.fillStyle = "rgba(255,230,230,1)";
errCtx.fillRect(0,0,errCanvas.width, errCanvas.height);
errCtx.strokeStyle = "red";
errCtx.strokeRect(0.5,0.5,errCanvas.width-1, errCanvas.height-1);
errCtx.fillStyle = "#A00000";
errCtx.font = `12px Arial`;
errCtx.fillText("Error: Image has invalid or zero dimensions.", 10, 25);
errCtx.fillText("Please provide a valid loaded image.", 10, 45);
return errCanvas;
}
srcCanvas.width = imgWidth;
srcCanvas.height = imgHeight;
try {
srcCtx.drawImage(originalImg, 0, 0);
} catch (e) {
console.error("Image HCL Color Showcase: Error drawing image to source canvas.", e);
const errCanvas = document.createElement('canvas');
errCanvas.width = 300; errCanvas.height = 50;
const errCtx = errCanvas.getContext('2d');
errCtx.fillStyle = "rgba(255,230,230,1)";
errCtx.fillRect(0,0,errCanvas.width, errCanvas.height);
errCtx.strokeStyle = "red";
errCtx.strokeRect(0.5,0.5,errCanvas.width-1, errCanvas.height-1);
errCtx.fillStyle = "#A00000";
errCtx.font = `12px Arial`;
errCtx.fillText("Error drawing image. It might be invalid.", 10, 25);
return errCanvas;
}
// Prepare output canvas
const outCanvas = document.createElement('canvas');
const outCtx = outCanvas.getContext('2d');
const textLineHeight = fontSize + 4; // Height for one line of text including spacing
const totalTextBlockHeight = 3 * textLineHeight; // For L, C, H values
const interElementPadding = 5; // Padding around elements within a cell (e.g., swatch to text)
// Calculate dimensions for each cell (swatch + text)
const cellContentWidth = swatchSize;
const cellContentHeight = swatchSize + interElementPadding + totalTextBlockHeight;
const cellTotalWidth = cellContentWidth + 2 * interElementPadding; // Add padding for left/right of cell
const cellTotalHeight = cellContentHeight + 2 * interElementPadding; // Add padding for top/bottom of cell
outCanvas.width = numSamplesX * cellTotalWidth;
outCanvas.height = numSamplesY * cellTotalHeight;
// Fill background of the output canvas
outCtx.fillStyle = '#FFFFFF'; // White background
outCtx.fillRect(0, 0, outCanvas.width, outCanvas.height);
outCtx.font = `${fontSize}px Arial`;
outCtx.textBaseline = 'top'; // Makes y-coordinate calculations for text simpler
for (let j = 0; j < numSamplesY; j++) { // Iterate over rows of samples
for (let i = 0; i < numSamplesX; i++) { // Iterate over columns of samples
// Calculate sample coordinates in the original image (center of grid cells)
const imgSampleX = (i + 0.5) * imgWidth / numSamplesX;
const imgSampleY = (j + 0.5) * imgHeight / numSamplesY;
// Ensure pixel coordinates are within image bounds
const imgPixelX = Math.max(0, Math.min(imgWidth - 1, Math.floor(imgSampleX)));
const imgPixelY = Math.max(0, Math.min(imgHeight - 1, Math.floor(imgSampleY)));
let R = 0, G = 0, B = 0; // Default to black if pixel data read fails
try {
// Get pixel data [R, G, B, A] for the sampled point
const pixelData = srcCtx.getImageData(imgPixelX, imgPixelY, 1, 1).data;
R = pixelData[0]; G = pixelData[1]; B = pixelData[2];
} catch (e) {
// This error can occur if the canvas is tainted (e.g., cross-origin image without CORS)
// The problem statement implies originalImg is a usable JS Image object, so this is defensive.
console.error("Image HCL Color Showcase: Error reading pixel data (getImageData). Canvas might be tainted.", e);
// Allow loop to continue, R,G,B will be 0,0,0 (black) for this sample
}
// Convert sampled RGB color to HCL
const [x_xyz, y_xyz, z_xyz] = rgbToXyz(R, G, B);
const [l_lab, a_lab, b_lab] = xyzToLab(x_xyz, y_xyz, z_xyz);
const [L_hcl, C_hcl, H_hcl] = labToLch(l_lab, a_lab, b_lab);
// Calculate current cell's top-left (base) X and Y on the output canvas
const currentCellBaseX = i * cellTotalWidth;
const currentCellBaseY = j * cellTotalHeight;
// Draw the color swatch
const swatchX = currentCellBaseX + interElementPadding;
const swatchY = currentCellBaseY + interElementPadding;
outCtx.fillStyle = `rgb(${R}, ${G}, ${B})`;
outCtx.fillRect(swatchX, swatchY, swatchSize, swatchSize);
// Draw a border around the swatch for better visibility
outCtx.strokeStyle = '#888888'; // Medium gray border
outCtx.lineWidth = 1;
// Stroke slightly inside the swatch rect to avoid issues if cells are packed adjacently
outCtx.strokeRect(swatchX + 0.5, swatchY + 0.5, swatchSize -1, swatchSize-1);
// Draw HCL text values below the swatch
outCtx.fillStyle = '#000000'; // Black text
outCtx.textAlign = 'center'; // Center text horizontally under the swatch
const textCenterX = swatchX + swatchSize / 2; // Horizontal center for text
let textCurrentY = swatchY + swatchSize + interElementPadding; // Initial Y for the first line of text
outCtx.fillText(`L: ${L_hcl.toFixed(1)}`, textCenterX, textCurrentY);
textCurrentY += textLineHeight; // Move to next line
outCtx.fillText(`C: ${C_hcl.toFixed(1)}`, textCenterX, textCurrentY);
textCurrentY += textLineHeight; // Move to next line
outCtx.fillText(`H: ${H_hcl.toFixed(1)}°`, textCenterX, textCurrentY);
}
}
return outCanvas;
}
Apply Changes