You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, desaturationLevel = 0.8, contrast = 130, brightness = -30, noiseAmount = 20, tintColor = "rgba(20, 40, 80, 0.2)", vignetteStrength = 0.7, blurRadius = 0.5) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const w = originalImg.naturalWidth;
const h = originalImg.naturalHeight;
if (w === 0 || h === 0) {
// Handle cases where image might not be loaded or is invalid
canvas.width = 1; // avoid 0x0 canvas
canvas.height = 1;
console.warn("Photo Haunting Filter: Original image has zero width or height.");
return canvas;
}
canvas.width = w;
canvas.height = h;
// 1. Apply blur (if any) and draw image
// The filter applies to subsequent drawing operations.
if (blurRadius > 0) {
ctx.filter = `blur(${blurRadius}px)`;
}
ctx.drawImage(originalImg, 0, 0, w, h);
// Reset filter so it doesn't affect subsequent tint/vignette drawing
if (blurRadius > 0) {
ctx.filter = 'none';
}
// 2. Pixel manipulations (desaturation, brightness, contrast, noise)
// Only get/put image data if necessary
if (desaturationLevel > 0 || contrast !== 100 || brightness !== 0 || noiseAmount > 0) {
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
const contrastFactor = contrast / 100.0;
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 preserved
// Desaturation
if (desaturationLevel > 0) {
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
// Lerp towards gray
r = r * (1 - desaturationLevel) + gray * desaturationLevel;
g = g * (1 - desaturationLevel) + gray * desaturationLevel;
b = b * (1 - desaturationLevel) + gray * desaturationLevel;
}
// Contrast
if (contrastFactor !== 1.0) { // contrast = 100 means factor is 1.0
r = (r - 128) * contrastFactor + 128;
g = (g - 128) * contrastFactor + 128;
b = (b - 128) * contrastFactor + 128;
}
// Brightness
if (brightness !== 0) {
r += brightness;
g += brightness;
b += brightness;
}
// Noise
if (noiseAmount > 0) {
const noiseValue = (Math.random() - 0.5) * noiseAmount;
r += noiseValue;
g += noiseValue;
b += noiseValue;
}
// Clamp values
data[i] = Math.max(0, Math.min(255, r));
data[i+1] = Math.max(0, Math.min(255, g));
data[i+2] = Math.max(0, Math.min(255, b));
}
ctx.putImageData(imageData, 0, 0);
}
// 3. Apply tint layer
// Ensure tintColor is a non-empty string and not "none" (case-insensitive for "none" might be good, but string equality is ok for now)
if (tintColor && typeof tintColor === 'string' && tintColor.toLowerCase() !== "none" && tintColor.trim() !== "") {
try {
// The browser's fillStyle will parse the color string.
// If it's an RGBA string, its alpha component will be used for blending with source-over.
ctx.globalCompositeOperation = 'source-over'; // Default blending mode
ctx.fillStyle = tintColor;
ctx.fillRect(0, 0, w, h);
} catch (e) {
// This catch block might not be effective for invalid fillStyle strings,
// as browsers often silently ignore them.
console.warn("Photo Haunting Filter: Could not apply tintColor. Ensure it's a valid CSS color string.", e);
}
}
// 4. Apply vignette layer
if (vignetteStrength > 0) {
const centerX = w / 2;
const centerY = h / 2;
const maxRadius = Math.sqrt(centerX * centerX + centerY * centerY);
// Calculate inner radius based on vignetteStrength to control the size of the clear area
// innerRadiusFactor decreases as vignetteStrength increases, making the vignette larger.
// Range from 0.7 (for strength 0) down to 0.1 (for strength 1.0)
const innerRadiusFactor = 0.7 - vignetteStrength * 0.6;
const actualInnerRadius = maxRadius * Math.max(0, innerRadiusFactor); // Ensure non-negative
const gradient = ctx.createRadialGradient(centerX, centerY, actualInnerRadius, centerX, centerY, maxRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Transparent center
gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`); // Dark edges with opacity based on strength
ctx.globalCompositeOperation = 'source-over'; // Apply on top
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
}
// Reset composite operation just in case, though subsequent ops are not expected in this function.
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes