You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, gradientColorsStr = "#0000FF,#4169E1,#228B22,#8FBC8F,#A0522D,#FFFFFF", thresholdsStr = "50,80,120,170,220,255", grayscaleMethod = "luminosity") {
/**
* Converts a HEX color string to an RGB object.
* Supports "RRGGBB", "#RRGGBB", "RGB", "#RGB".
* Returns null if the hex string is invalid.
*/
function hexToRgb(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
// --- 1. Parse and Validate Parameters ---
const parsedColorsHex = gradientColorsStr.split(',').map(c => c.trim());
const parsedThresholdsNum = thresholdsStr.split(',').map(t => {
const num = parseInt(t.trim(), 10);
return isNaN(num) ? -1 : Math.max(0, Math.min(255, num)); // Clamp 0-255, mark invalid as -1
});
let pairedGradient = [];
const len = Math.min(parsedColorsHex.length, parsedThresholdsNum.length);
for (let i = 0; i < len; i++) {
const rgbColor = hexToRgb(parsedColorsHex[i]);
const thresholdVal = parsedThresholdsNum[i];
// Ensure color is valid and threshold was parsed correctly (not -1 from above)
if (rgbColor && thresholdVal !== -1) {
pairedGradient.push({ color: rgbColor, threshold: thresholdVal });
}
}
// Sort by threshold to ensure correct application order
pairedGradient.sort((a, b) => a.threshold - b.threshold);
// Separate back into final arrays for processing
let finalColorsRgb = pairedGradient.map(p => p.color);
let finalThresholds = pairedGradient.map(p => p.threshold);
// Fallback if parsing resulted in an empty or invalid gradient
if (finalColorsRgb.length === 0) {
console.warn("Provided gradient colors/thresholds were invalid or insufficient. Using default gradient.");
// Use the default values specified in function signature
const defaultColorsArray = ("#0000FF,#4169E1,#228B22,#8FBC8F,#A0522D,#FFFFFF").split(',').map(c => c.trim());
const defaultThresholdsArray = ("50,80,120,170,220,255").split(',').map(t => parseInt(t.trim(), 10));
let defaultPairedGradient = [];
for (let i = 0; i < Math.min(defaultColorsArray.length, defaultThresholdsArray.length); i++) {
const rgb = hexToRgb(defaultColorsArray[i]);
if (rgb) { // Default colors should always be valid
defaultPairedGradient.push({ color: rgb, threshold: defaultThresholdsArray[i] });
}
}
defaultPairedGradient.sort((a,b) => a.threshold - b.threshold);
finalColorsRgb = defaultPairedGradient.map(p => p.color);
finalThresholds = defaultPairedGradient.map(p => p.threshold);
// If even defaults fail (e.g. hexToRgb has a bug or default strings are malformed), use a failsafe black/white.
if (finalColorsRgb.length === 0 || finalColorsRgb.some(c => !c)) {
console.error("Critical error: Default gradient processing failed. Using black/white failsafe.");
finalColorsRgb = [ { r: 0, g: 0, b: 0 }, { r: 255, g: 255, b: 255 } ];
finalThresholds = [ 127, 255 ];
}
}
// --- 2. Canvas Setup ---
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Use naturalWidth/Height if available, otherwise fallback to width/height
// This typically gives the actual image dimensions if it's loaded.
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
if (canvas.width === 0 || canvas.height === 0) {
// Handle cases where image might not be fully loaded or is an empty image
console.warn("Image has zero width or height.");
return canvas; // Return empty canvas
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// Potential CORS issue if image is from another domain and canvas is tainted
console.error("Could not getImageData: Canvas might be tainted by cross-origin data.", e);
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear previous drawImage
ctx.fillStyle = 'rgba(200, 200, 200, 0.8)'; // Semi-transparent background
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'red';
ctx.font = `bold ${Math.min(24, canvas.width / 15)}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Error: Cannot process cross-origin image.', canvas.width / 2, canvas.height / 2);
return canvas;
}
const data = imageData.data;
// --- 3. Pixel Processing ---
const lumR = 0.299, lumG = 0.587, lumB = 0.114; // Standard luminosity coefficients
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i+1];
const b = data[i+2];
// Alpha (data[i+3]) is preserved
// Calculate "height" based on grayscale value
let gray;
if (grayscaleMethod === "average") {
gray = (r + g + b) / 3;
} else { // Default to "luminosity" or if method is misspelled
gray = lumR * r + lumG * g + lumB * b;
}
gray = Math.round(Math.max(0, Math.min(255, gray))); // Clamp to 0-255 and round
// Determine output color based on height (gray value)
let R_out, G_out, B_out;
let colorApplied = false;
// finalColorsRgb and finalThresholds are guaranteed non-empty by fallback logic
for (let k = 0; k < finalThresholds.length; k++) {
if (gray <= finalThresholds[k]) {
R_out = finalColorsRgb[k].r;
G_out = finalColorsRgb[k].g;
B_out = finalColorsRgb[k].b;
colorApplied = true;
break;
}
}
if (!colorApplied) {
// If gray is greater than all defined thresholds (e.g., last threshold in user input isn't 255)
// Use the color of the highest defined band.
const lastColor = finalColorsRgb[finalColorsRgb.length - 1];
R_out = lastColor.r;
G_out = lastColor.g;
B_out = lastColor.b;
}
data[i] = R_out;
data[i+1] = G_out;
data[i+2] = B_out;
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes