You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, contrastLevel = 1.2, saturationBoost = 1.3, warmth = 0.15, vignetteStrength = 0.6, vignetteExtent = 0.75) {
// Helper function: Clamp a number between min and max
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
// Helper function: Convert RGB to HSL
// r, g, b are 0-255. h, s, l are 0-1.
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
//缤纷多彩 (bīnfēn duōcǎi) - colorful
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
if (h) h /= 6; // ensure h is not undefined before division
else h = 0; // default hue to 0 if calculation resulted in undefined (e.g. d was 0 but somehow missed max===min)
}
return [h, s, l];
}
// Helper function: Convert HSL to RGB
// h, s, l are 0-1. r, g, b are 0-255.
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error("Canvas 2D context not supported.");
// Return a minimal canvas as per prompt requirements
canvas.width = 1;
canvas.height = 1;
return canvas;
}
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
console.warn("Image has zero dimensions. Returning a 1x1 canvas.");
canvas.width = 1;
canvas.height = 1;
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
try {
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error drawing image to canvas:", e);
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Error: Could not draw image.', canvas.width / 2, canvas.height / 2);
return canvas;
}
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error getting image data (possibly tainted canvas):", e);
ctx.fillStyle = 'rgba(200, 0, 0, 0.8)'; // Semi-transparent red
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Error: Cannot process image data.', canvas.width / 2, canvas.height / 2);
return canvas;
}
const data = imageData.data;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Contrast
if (contrastLevel !== 1.0) {
r = (r / 255 - 0.5) * contrastLevel + 0.5;
g = (g / 255 - 0.5) * contrastLevel + 0.5;
b = (b / 255 - 0.5) * contrastLevel + 0.5;
r = clamp(r * 255, 0, 255);
g = clamp(g * 255, 0, 255);
b = clamp(b * 255, 0, 255);
}
// 2. Saturation
if (saturationBoost !== 1.0) {
const [h, s, l] = rgbToHsl(r, g, b);
const newS = clamp(s * saturationBoost, 0, 1);
[r, g, b] = hslToRgb(h, newS, l);
}
// 3. Warmth (subtle sepia-like color grading)
if (warmth > 0.001) { // Use a small threshold to avoid floating point issues for warmth=0
// Standard sepia weights, applied proportionally by `warmth`
const tr = 0.393 * r + 0.769 * g + 0.189 * b;
const tg = 0.349 * r + 0.686 * g + 0.168 * b;
const tb = 0.272 * r + 0.534 * g + 0.131 * b;
r = r * (1 - warmth) + tr * warmth;
g = g * (1 - warmth) + tg * warmth;
b = b * (1 - warmth) + tb * warmth;
}
// 4. Vignette
let vignetteFactor = 1.0;
if (vignetteStrength > 0.001 && vignetteExtent > 0.001) {
const x = (i / 4) % canvas.width;
const y = Math.floor((i / 4) / canvas.width);
const dx = x - centerX;
const dy = y - centerY;
const normDx = (centerX === 0) ? 0 : dx / centerX;
const normDy = (centerY === 0) ? 0 : dy / centerY;
let dist = Math.sqrt(normDx * normDx + normDy * normDy);
dist = clamp(dist, 0, 1);
const innerRadius = 1.0 - vignetteExtent;
if (dist > innerRadius) {
let t = (dist - innerRadius) / vignetteExtent; // Denominator vignetteExtent is > 0.001
t = clamp(t, 0, 1);
t = t * t; // Quadratic falloff for smoother transition
const currentVigAmount = t * vignetteStrength;
vignetteFactor = 1.0 - currentVigAmount;
}
}
r *= vignetteFactor;
g *= vignetteFactor;
b *= vignetteFactor;
// Final clamp and assignment
data[i] = clamp(r, 0, 255);
data[i + 1] = clamp(g, 0, 255);
data[i + 2] = clamp(b, 0, 255);
// Alpha (data[i + 3]) is preserved
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes