You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg) {
const width = originalImg.width;
const height = originalImg.height;
if (width === 0 || height === 0) {
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = 100;
emptyCanvas.height = 30;
const emptyCtx = emptyCanvas.getContext('2d');
emptyCtx.fillStyle = 'white';
emptyCtx.fillRect(0, 0, emptyCanvas.width, emptyCanvas.height);
emptyCtx.fillStyle = 'black';
emptyCtx.font = '12px Arial';
emptyCtx.textAlign = 'center';
emptyCtx.fillText("Empty image", 50, 20);
return emptyCanvas;
}
// 1. Create a working canvas to get image data
const workingCanvas = document.createElement('canvas');
workingCanvas.width = width;
workingCanvas.height = height;
// Add { willReadFrequently: true } for potential performance optimization if supported/needed.
const workingCtx = workingCanvas.getContext('2d', { willReadFrequently: true });
workingCtx.drawImage(originalImg, 0, 0);
let imageData;
try {
imageData = workingCtx.getImageData(0, 0, width, height);
} catch (e) {
console.error("Error getting image data:", e);
const errorCanvas = document.createElement('canvas');
errorCanvas.width = Math.max(300, width * 2); // Adjust size to be somewhat useful
errorCanvas.height = 100;
const errCtx = errorCanvas.getContext('2d');
errCtx.fillStyle = '#f0f0f0';
errCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errCtx.fillStyle = 'red';
errCtx.font = 'bold 16px Arial';
errCtx.textAlign = 'center';
errCtx.fillText("Error: Could not process image.", errorCanvas.width / 2, 35);
errCtx.font = '14px Arial';
errCtx.fillStyle = 'black';
errCtx.fillText("This may be due to cross-origin restrictions if the image is hosted on another server.", errorCanvas.width / 2, 65);
return errorCanvas;
}
const data = imageData.data;
// 2. Prepare ImageData for each CMYK channel
const cImageData = workingCtx.createImageData(width, height);
const mImageData = workingCtx.createImageData(width, height);
const yImageData = workingCtx.createImageData(width, height);
const kImageData = workingCtx.createImageData(width, height);
const cData = cImageData.data;
const mData = mImageData.data;
const yData = yImageData.data;
const kData = kImageData.data;
// 3. Iterate through pixels and convert RGB to CMYK
for (let i = 0; i < data.length; i += 4) {
let r_srgb = data[i];
let g_srgb = data[i + 1];
let b_srgb = data[i + 2];
let alpha = data[i + 3];
let r = r_srgb / 255;
let g = g_srgb / 255;
let b = b_srgb / 255;
let k_val = 1 - Math.max(r, g, b);
let c_val, m_val, y_val;
if (Math.abs(1 - k_val) < 1e-9) { // k_val is essentially 1 (i.e. RGB is black)
c_val = 0;
m_val = 0;
y_val = 0;
} else {
// The (1-k_val) denominator will not be zero here
c_val = (1 - r - k_val) / (1 - k_val);
m_val = (1 - g - k_val) / (1 - k_val);
y_val = (1 - b - k_val) / (1 - k_val);
}
// Clamp CMYK values to [0, 1] range to safeguard against precision errors
c_val = Math.max(0, Math.min(1, c_val));
m_val = Math.max(0, Math.min(1, m_val));
y_val = Math.max(0, Math.min(1, y_val));
k_val = Math.max(0, Math.min(1, k_val)); // k_val should already be in [0,1]
// Convert CMYK (0-1) to grayscale intensity (0-255)
// Convention: 0 = black (max ink), 255 = white (no ink)
// This shows where ink would be applied density.
let c_gray = Math.round((1 - c_val) * 255);
let m_gray = Math.round((1 - m_val) * 255);
let y_gray = Math.round((1 - y_val) * 255);
let k_gray = Math.round((1 - k_val) * 255);
cData[i] = c_gray; cData[i + 1] = c_gray; cData[i + 2] = c_gray; cData[i + 3] = alpha;
mData[i] = m_gray; mData[i + 1] = m_gray; mData[i + 2] = m_gray; mData[i + 3] = alpha;
yData[i] = y_gray; yData[i + 1] = y_gray; yData[i + 2] = y_gray; yData[i + 3] = alpha;
kData[i] = k_gray; kData[i + 1] = k_gray; kData[i + 2] = k_gray; kData[i + 3] = alpha;
}
// 4. Create the output canvas layout
const padding = 15;
const labelHeight = 25;
const fontConfig = '14px Arial';
const labelColor = '#333333';
const borderColor = '#AAAAAA';
const backgroundColor = '#F0F0F0';
const totalWidth = (width * 2) + (padding * 3);
const totalHeight = (height * 2) + (padding * 3) + (labelHeight * 2);
const outputCanvas = document.createElement('canvas');
outputCanvas.width = totalWidth;
outputCanvas.height = totalHeight;
const outCtx = outputCanvas.getContext('2d');
outCtx.fillStyle = backgroundColor;
outCtx.fillRect(0, 0, totalWidth, totalHeight);
const drawChannel = (channelImageData, label, col, row) => {
// Position calculation for current channel block (label + image)
const blockX = padding + col * (width + padding);
const blockY_label = padding + row * (height + padding + labelHeight);
const blockY_image = blockY_label + labelHeight;
// Draw label
outCtx.fillStyle = labelColor;
outCtx.font = fontConfig;
outCtx.textAlign = 'center';
outCtx.textBaseline = 'middle';
outCtx.fillText(label, blockX + width / 2, blockY_label + labelHeight / 2);
// Draw image data for the channel
outCtx.putImageData(channelImageData, blockX, blockY_image);
// Draw border around the channel image
outCtx.strokeStyle = borderColor;
outCtx.lineWidth = 1;
// Adjust by -0.5 for crisp 1px lines
outCtx.strokeRect(blockX - 0.5, blockY_image - 0.5, width + 1, height + 1);
};
drawChannel(cImageData, "Cyan (C)", 0, 0);
drawChannel(mImageData, "Magenta (M)", 1, 0);
drawChannel(yImageData, "Yellow (Y)", 0, 1);
drawChannel(kImageData, "Key/Black (K)", 1, 1);
return outputCanvas;
}
Apply Changes