You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, color1 = "#200020", color2 = "#D90077", color3 = "#FFD700", contrast = 1.5, vignetteStrength = 0.4) {
// Helper function to parse hex color string to an {r, g, b} object
function hexToRgb(hex) {
let r = 0, g = 0, b = 0;
// Remove # if present
if (hex.startsWith('#')) {
hex = hex.substring(1);
}
if (hex.length === 3) { // #RGB format
r = parseInt(hex[0] + hex[0], 16);
g = parseInt(hex[1] + hex[1], 16);
b = parseInt(hex[2] + hex[2], 16);
} else if (hex.length === 6) { // #RRGGBB format
r = parseInt(hex.substring(0, 2), 16);
g = parseInt(hex.substring(2, 4), 16);
b = parseInt(hex.substring(4, 6), 16);
} else {
// Fallback to black if invalid hex
console.warn("Invalid hex color:", hex, "defaulting to black.");
return { r: 0, g: 0, b: 0 };
}
// Check for NaN cases from parseInt if hex is malformed (e.g. "GGGGGG")
if (isNaN(r) || isNaN(g) || isNaN(b)) {
console.warn("Invalid characters in hex color:", hex, "defaulting to black.");
return { r: 0, g: 0, b: 0 };
}
return { r, g, b };
}
// Helper function for linear interpolation
function lerp(a, b, t) {
return a * (1 - t) + b * t;
}
// Helper function to clamp a value between min and max
function clamp(value, min, max) {
return Math.max(min, Math.min(value, max));
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (!width || !height) {
console.error("Image has no dimensions or is not loaded properly.");
// Return a small error indicator canvas
canvas.width = 200;
canvas.height = 100;
ctx.fillStyle = 'red';
ctx.font = '12px Arial';
ctx.fillText("Error: Image not loaded or invalid.", 10, 20);
return canvas;
}
canvas.width = width;
canvas.height = height;
// Draw original image onto the canvas to get its pixel data
ctx.drawImage(originalImg, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
const rgb1 = hexToRgb(color1);
const rgb2 = hexToRgb(color2);
const rgb3 = hexToRgb(color3);
// Process 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];
// Apply contrast: (value - midpoint) * factor + midpoint
// Contrast factor: 1.0 is no change. >1 increases, <1 decreases.
r = clamp((r - 128) * contrast + 128, 0, 255);
g = clamp((g - 128) * contrast + 128, 0, 255);
b = clamp((b - 128) * contrast + 128, 0, 255);
// Convert to grayscale (luminosity method)
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
const grayNorm = gray / 255; // Normalized grayscale (0 to 1)
// Apply gradient map based on grayscale value
let finalR, finalG, finalB;
if (grayNorm < 0.5) {
// Interpolate between color1 (dark) and color2 (mid)
const t = grayNorm / 0.5; // t from 0 to 1
finalR = lerp(rgb1.r, rgb2.r, t);
finalG = lerp(rgb1.g, rgb2.g, t);
finalB = lerp(rgb1.b, rgb2.b, t);
} else {
// Interpolate between color2 (mid) and color3 (light)
const t = (grayNorm - 0.5) / 0.5; // t from 0 to 1
finalR = lerp(rgb2.r, rgb3.r, t);
finalG = lerp(rgb2.g, rgb3.g, t);
finalB = lerp(rgb2.b, rgb3.b, t);
}
data[i] = clamp(Math.round(finalR), 0, 255);
data[i + 1] = clamp(Math.round(finalG), 0, 255);
data[i + 2] = clamp(Math.round(finalB), 0, 255);
// Alpha (data[i+3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
// Apply vignette effect
if (vignetteStrength > 0 && vignetteStrength <= 1) {
ctx.globalCompositeOperation = 'source-over'; // Ensure drawing normally
const centerX = width / 2;
const centerY = height / 2;
// Outer radius is the distance to the furthest corner
const outerRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
// Inner radius, where transparency starts to fade. 0.3 is a common value.
const innerRadiusRatio = 0.3;
const innerRadius = outerRadius * innerRadiusRatio;
const gradient = ctx.createRadialGradient(
centerX, centerY, innerRadius,
centerX, centerY, outerRadius
);
// Vignette is transparent in the center, dark at the edges
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${clamp(vignetteStrength, 0, 1)})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
// Reset composite operation if it was changed by other effects (though not in this version)
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes