You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg) {
// Constants for sRGB to XYZ conversion (D65 illuminant)
const SRGB_TO_XYZ_MATRIX = [
[0.4124564, 0.3575761, 0.1804375],
[0.2126729, 0.7151522, 0.0721750],
[0.0193339, 0.1191920, 0.9503041]
];
// D65 reference white for XYZ to Lab conversion (Yn = 1.0 normalized)
const D65_REF_X = 0.95047;
const D65_REF_Y = 1.00000;
const D65_REF_Z = 1.08883;
// Constants for XYZ to Lab conversion (CIELAB standard)
const LAB_E = 216 / 24389; // (6/29)^3
// f(t) = K_TERM_COEFF * t + 4/29 for t <= E
// K_TERM_COEFF = (1/3) * (29/6)^2 = 841 / 108
const LAB_K_TERM_COEFF = 841 / 108;
const LAB_4_29 = 4/29;
// Helper function to convert sRGB channel to linear RGB
function srgbToLinear(c_srgb) {
// Input c_srgb is in range [0, 1]
if (c_srgb <= 0.04045) {
return c_srgb / 12.92;
}
return Math.pow((c_srgb + 0.055) / 1.055, 2.4);
}
// Helper function to convert RGB (0-255) to XYZ
function rgbToXyz(r_srgb, g_srgb, b_srgb) {
const r_linear = srgbToLinear(r_srgb / 255);
const g_linear = srgbToLinear(g_srgb / 255);
const b_linear = srgbToLinear(b_srgb / 255);
const X = r_linear * SRGB_TO_XYZ_MATRIX[0][0] + g_linear * SRGB_TO_XYZ_MATRIX[0][1] + b_linear * SRGB_TO_XYZ_MATRIX[0][2];
const Y = r_linear * SRGB_TO_XYZ_MATRIX[1][0] + g_linear * SRGB_TO_XYZ_MATRIX[1][1] + b_linear * SRGB_TO_XYZ_MATRIX[1][2];
const Z = r_linear * SRGB_TO_XYZ_MATRIX[2][0] + g_linear * SRGB_TO_XYZ_MATRIX[2][1] + b_linear * SRGB_TO_XYZ_MATRIX[2][2];
// Resulting X, Y, Z are D65-adapted, with Y_nominal_white = 1.0
return { x: X, y: Y, z: Z };
}
// Helper function 'f' for XYZ to Lab conversion
function xyzToLab_f(t) {
if (t > LAB_E) {
return Math.cbrt(t); // Cube root
}
return (LAB_K_TERM_COEFF * t) + LAB_4_29;
}
// Helper function to convert XYZ to CIELAB
function xyzToLab(x, y, z) {
const fx = xyzToLab_f(x / D65_REF_X);
const fy = xyzToLab_f(y / D65_REF_Y); // y / 1.0 for D65_REF_Y = 1.0
const fz = xyzToLab_f(z / D65_REF_Z);
const L = (116 * fy) - 16;
const a = 500 * (fx - fy);
const b = 200 * (fy - fz);
return { l: L, a: a, b: b };
}
// Helper function to convert CIELAB to CIELCH
function labToLch(l, a, b) {
const C = Math.sqrt(a * a + b * b);
let H_rad = Math.atan2(b, a); // Result is in radians, range [-PI, PI]
let H_deg = H_rad * (180 / Math.PI);
// Ensure hue is in [0, 360)
if (H_deg < 0) {
H_deg += 360;
}
// For achromatic colors (C=0), hue is conventionally 0. atan2(0,0) returns 0, so H_deg will be 0.
return { l: l, c: C, h: H_deg };
}
// Main conversion pipeline: RGB to LCH
function rgbToLch(r, g, b) {
const xyz = rgbToXyz(r, g, b);
const lab = xyzToLab(xyz.x, xyz.y, xyz.z);
return labToLch(lab.l, lab.a, lab.b);
}
// Create the main container element
const container = document.createElement('div');
// Create canvas to draw the image
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
canvas.style.cursor = 'crosshair'; // Indicate interactivity
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0, originalImg.width, originalImg.height);
// Create div to display LCH values
const lchDisplay = document.createElement('div');
lchDisplay.textContent = 'Hover over the image to see LCH values.';
lchDisplay.style.padding = '10px';
lchDisplay.style.fontFamily = 'monospace, sans-serif'; // Monospace for numbers
lchDisplay.style.textAlign = 'center';
lchDisplay.style.minHeight = '1.5em'; // Prevent layout shift
lchDisplay.style.marginTop = '5px';
// Event listener for mouse movement over the canvas
canvas.addEventListener('mousemove', function(event) {
const rect = canvas.getBoundingClientRect();
let x = Math.floor(event.clientX - rect.left);
let y = Math.floor(event.clientY - rect.top);
// Clamp coordinates to be within canvas bounds
x = Math.max(0, Math.min(x, canvas.width - 1));
y = Math.max(0, Math.min(y, canvas.height - 1));
// In case mouse somehow reports outside despite listener being on canvas (e.g. rápido movements / browser quirks)
if (x < 0 || x >= canvas.width || y < 0 || y >= canvas.height) {
lchDisplay.textContent = 'Hover over the image to see LCH values.';
return;
}
const pixelData = ctx.getImageData(x, y, 1, 1).data; // [R, G, B, A]
const [r, g, b] = pixelData;
const lch = rgbToLch(r, g, b);
lchDisplay.textContent = `L: ${lch.l.toFixed(2)}, C: ${lch.c.toFixed(2)}, H: ${lch.h.toFixed(2)}° (at ${x},${y})`;
});
// Event listener for mouse leaving the canvas
canvas.addEventListener('mouseleave', function() {
lchDisplay.textContent = 'Hover over the image to see LCH values.';
});
// Append canvas and LCH display to the container
container.appendChild(canvas);
container.appendChild(lchDisplay);
return container;
}
Apply Changes