You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, masterCurvePoints = "0,0;255,255", redCurvePoints = "0,0;255,255", greenCurvePoints = "0,0;255,255", blueCurvePoints = "0,0;255,255") {
/**
* Parses a string of curve points into an array of {x, y} objects.
* @param {string} pointsStr - A string in the format "x1,y1;x2,y2;...".
* @returns {Array<Object>} An array of sorted, unique points, e.g., [{x: 0, y: 0}, {x: 255, y: 255}].
*/
const parsePoints = (pointsStr) => {
if (!pointsStr || typeof pointsStr !== 'string' || pointsStr.trim() === '') {
return [{ x: 0, y: 0 }, { x: 255, y: 255 }];
}
const points = pointsStr.split(';')
.map(p => {
const parts = p.split(',').map(s => s.trim());
if (parts.length !== 2) return null;
const x = parseInt(parts[0], 10);
const y = parseInt(parts[1], 10);
if (isNaN(x) || isNaN(y)) return null;
// Clamp coordinates to the valid 0-255 range
return { x: Math.max(0, Math.min(255, x)), y: Math.max(0, Math.min(255, y)) };
})
.filter(p => p !== null);
// Ensure start and end points exist
if (!points.find(p => p.x === 0)) {
points.push({ x: 0, y: 0 });
}
if (!points.find(p => p.x === 255)) {
points.push({ x: 255, y: 255 });
}
// Ensure points are unique by 'x' and sorted
const uniquePoints = Array.from(new Map(points.map(p => [p.x, p])).values());
uniquePoints.sort((a, b) => a.x - b.x);
return uniquePoints.length > 1 ? uniquePoints : [{ x: 0, y: 0 }, { x: 255, y: 255 }];
};
/**
* Creates a lookup table (LUT) from an array of curve points.
* The LUT maps every input value (0-255) to an output value based on the curve.
* @param {Array<Object>} points - A sorted array of {x, y} points.
* @returns {Uint8ClampedArray} A 256-element array for value mapping.
*/
const createLut = (points) => {
const lut = new Uint8ClampedArray(256);
let pointIndex = 0;
for (let i = 0; i < 256; i++) {
// Find the two control points that the current value 'i' is between
while (pointIndex < points.length - 2 && i > points[pointIndex + 1].x) {
pointIndex++;
}
const p1 = points[pointIndex];
const p2 = points[pointIndex + 1];
// Perform linear interpolation between the two points
const t = (p1.x === p2.x) ? 0 : (i - p1.x) / (p2.x - p1.x);
const value = p1.y + t * (p2.y - p1.y);
lut[i] = value; // Uint8ClampedArray automatically clamps the value to 0-255
}
return lut;
};
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0);
// Bail out if the image has no size
if (canvas.width === 0 || canvas.height === 0) {
return canvas;
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Create lookup tables for each curve
const masterLut = createLut(parsePoints(masterCurvePoints));
const redLut = createLut(parsePoints(redCurvePoints));
const greenLut = createLut(parsePoints(greenCurvePoints));
const blueLut = createLut(parsePoints(blueCurvePoints));
// Apply the curves to each pixel
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Apply the master curve to all channels
r = masterLut[r];
g = masterLut[g];
b = masterLut[b];
// 2. Apply the individual channel curves
data[i] = redLut[r];
data[i + 1] = greenLut[g];
data[i + 2] = blueLut[b];
// data[i + 3] is the alpha channel, which we leave untouched.
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes