You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
desaturationFactor = 0.7, // 0.0 (original color) to 1.0 (grayscale)
sepiaAmount = 0.4, // 0.0 (none) to 1.0 (full sepia tones blending)
contrastLevel = 25, // 0 (no change) to 100 (high contrast).
noiseAmount = 15, // 0 (none) to 50 (max noise pixel offset by this value)
vignetteStrength = 0.5 // 0.0 (none) to 1.0 (edges fully black if 1.0)
) {
// Helper function for clamping values between min and max
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (width === 0 || height === 0) {
// Return a small, empty canvas if image dimensions are invalid
console.warn("Original image has zero width or height. Returning 1x1 canvas.");
canvas.width = 1;
canvas.height = 1;
return canvas;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
const centerX = width / 2;
const centerY = height / 2;
// maxDist is the distance from the center to the farthest corner
const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// Alpha data[i+3] is typically not modified for such filters
// 1. Apply Sepia Tone (if sepiaAmount > 0)
// Blends current color with its sepia version
if (sepiaAmount > 0) {
const sr = clamp(r * 0.393 + g * 0.769 + b * 0.189, 0, 255);
const sg = clamp(r * 0.349 + g * 0.686 + b * 0.168, 0, 255);
const sb = clamp(r * 0.272 + g * 0.534 + b * 0.131, 0, 255);
r = r * (1 - sepiaAmount) + sr * sepiaAmount;
g = g * (1 - sepiaAmount) + sg * sepiaAmount;
b = b * (1 - sepiaAmount) + sb * sepiaAmount;
// Clamp after blending
r = clamp(r, 0, 255);
g = clamp(g, 0, 255);
b = clamp(b, 0, 255);
}
// 2. Desaturation (if desaturationFactor > 0)
// Blends current color with its grayscale equivalent
if (desaturationFactor > 0) {
// Standard luminance calculation for grayscale
const gray = r * 0.299 + g * 0.587 + b * 0.114;
r = r * (1 - desaturationFactor) + gray * desaturationFactor;
g = g * (1 - desaturationFactor) + gray * desaturationFactor;
b = b * (1 - desaturationFactor) + gray * desaturationFactor;
// Clamp after desaturation
r = clamp(r, 0, 255);
g = clamp(g, 0, 255);
b = clamp(b, 0, 255);
}
// 3. Adjust Contrast (if contrastLevel is not 0)
// contrastLevel is 0-100. 0 means no change.
if (contrastLevel !== 0) {
// This formula maps contrastLevel (0-100) to a factor.
// If contrastLevel = 0, factor = 1 (no change).
// If contrastLevel = 100, factor approx 2.27.
// The value of contrastLevel must be < 259. Our range 0-100 is safe.
const factor = (259 * (contrastLevel + 255)) / (255 * (259 - contrastLevel));
r = clamp(factor * (r - 128) + 128, 0, 255);
g = clamp(factor * (g - 128) + 128, 0, 255);
b = clamp(factor * (b - 128) + 128, 0, 255);
}
// 4. Add Noise (if noiseAmount > 0)
if (noiseAmount > 0) {
// Generates noise in [-noiseAmount, +noiseAmount]
const noise = (Math.random() - 0.5) * 2 * noiseAmount;
r = clamp(r + noise, 0, 255);
g = clamp(g + noise, 0, 255);
b = clamp(b + noise, 0, 255);
}
// 5. Apply Vignette (if vignetteStrength > 0 and image has dimensions)
if (vignetteStrength > 0 && maxDist > 0) {
const x = (i / 4) % width;
const y = Math.floor((i / 4) / width);
const dx = x - centerX;
const dy = y - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
// effectiveDist is 0 at center, 1 at the farthest corner.
const effectiveDist = dist / maxDist;
// reductionFactor = 0 at center, up to vignetteStrength at the edge.
// Power of 2.0 gives a quadratic falloff (smoother).
const reductionFactor = Math.pow(effectiveDist, 2.0) * vignetteStrength;
// Apply darkening. (1.0 - reductionFactor) scales color.
// Value is 1.0 at center, and (1.0 - vignetteStrength) at the edge.
r = clamp(r * (1.0 - reductionFactor), 0, 255);
g = clamp(g * (1.0 - reductionFactor), 0, 255);
b = clamp(b * (1.0 - reductionFactor), 0, 255);
}
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
// data[i+3] (alpha channel) is preserved
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes