You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg,
desaturationAmountParam = "0.8",
contrastAmountParam = "1.8",
brightnessAdjustParam = "-20",
vignetteIntensityParam = "0.6",
vignetteSoftnessParam = "1.5") {
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) {
canvas.width = 0;
canvas.height = 0;
// console.error("Dramatic Photo Filter: Image has zero dimensions."); // Optional: for debugging
return canvas; // Return empty canvas for zero-dimension image
}
canvas.width = width;
canvas.height = height;
try {
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
} catch (e) {
// This can happen if originalImg is not a valid image source (e.g. tainted canvas, broken img)
// console.error("Dramatic Photo Filter: Could not draw image on canvas.", e); // Optional: for debugging
// Return a blank canvas of correct dimensions.
return canvas;
}
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// This can happen due to canvas tainting if the image is cross-origin and CORS isn't set up.
// console.error("Dramatic Photo Filter: Could not get ImageData from canvas. Tainted canvas?", e); // Optional: for debugging
// Return the canvas with the original image drawn (if drawImage succeeded), but without filter.
return canvas;
}
const data = imageData.data;
// Default values for parsing fallback and validation
const DEFAULT_DESATURATION = 0.8;
const DEFAULT_CONTRAST = 1.8;
const DEFAULT_BRIGHTNESS = -20;
const DEFAULT_VIGNETTE_INTENSITY = 0.6;
const DEFAULT_VIGNETTE_SOFTNESS = 1.5; // Must be > 0
// Parse parameters to numbers
let desaturationAmount = parseFloat(desaturationAmountParam);
let contrastAmount = parseFloat(contrastAmountParam);
let brightnessAdjust = parseFloat(brightnessAdjustParam);
let vignetteIntensity = parseFloat(vignetteIntensityParam);
let vignetteSoftness = parseFloat(vignetteSoftnessParam);
// Validate parsed parameters: if NaN, use default. Then clamp to sensible operational ranges.
desaturationAmount = isNaN(desaturationAmount) ? DEFAULT_DESATURATION : Math.max(0, Math.min(1, desaturationAmount));
contrastAmount = isNaN(contrastAmount) ? DEFAULT_CONTRAST : Math.max(0, contrastAmount); // Lower bound 0, no strict upper for flexibility
brightnessAdjust = isNaN(brightnessAdjust) ? DEFAULT_BRIGHTNESS : Math.max(-255, Math.min(255, brightnessAdjust));
vignetteIntensity = isNaN(vignetteIntensity) ? DEFAULT_VIGNETTE_INTENSITY : Math.max(0, Math.min(1, vignetteIntensity));
vignetteSoftness = isNaN(vignetteSoftness) ? DEFAULT_VIGNETTE_SOFTNESS : Math.max(0.1, vignetteSoftness); // Min 0.1 as exponent to avoid issues with pow(0,0)
const centerX = width / 2;
const centerY = height / 2;
// maxDist is distance from center to a corner. Used for vignette falloff.
// This will be > 0 if width or height > 0.
const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
for (let i = 0; i < data.length; i += 4) {
let r_proc = data[i];
let g_proc = data[i + 1];
let b_proc = data[i + 2];
// 1. Desaturation
if (desaturationAmount > 0) {
const gray = 0.299 * r_proc + 0.587 * g_proc + 0.114 * b_proc;
// Lerp (Linear Interpolation) towards gray: (1-amount)*start + amount*end
r_proc = r_proc * (1 - desaturationAmount) + gray * desaturationAmount;
g_proc = g_proc * (1 - desaturationAmount) + gray * desaturationAmount;
b_proc = b_proc * (1 - desaturationAmount) + gray * desaturationAmount;
}
// 2. Contrast
// Formula: NewValue = MidPoint + (OldValue - MidPoint) * Factor
// MidPoint is 128 for 8-bit color values.
// Factor = 1 -> no change. Factor > 1 -> increase contrast. 0 <= Factor < 1 -> decrease contrast.
if (contrastAmount !== 1.0) {
r_proc = 128 + (r_proc - 128) * contrastAmount;
g_proc = 128 + (g_proc - 128) * contrastAmount;
b_proc = 128 + (b_proc - 128) * contrastAmount;
}
// 3. Brightness adjustment
if (brightnessAdjust !== 0) {
r_proc += brightnessAdjust;
g_proc += brightnessAdjust;
b_proc += brightnessAdjust;
}
// Clamp intermediate values before applying multiplicative vignette
r_proc = Math.max(0, Math.min(255, r_proc));
g_proc = Math.max(0, Math.min(255, g_proc));
b_proc = Math.max(0, Math.min(255, b_proc));
// 4. Vignette
// Apply vignette only if intensity is positive and maxDist is positive (to avoid division by zero for 0x0 images, though handled earlier)
if (vignetteIntensity > 0 && maxDist > 0) {
const x_coord = (i / 4) % width; // Current pixel's x coordinate
const y_coord = Math.floor((i / 4) / width); // Current pixel's y coordinate
const dx = x_coord - centerX;
const dy = y_coord - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
// vignetteEffectRatio: 0 at center, up to 1 at maxDist (corner)
// Clamped to max 1 for safety, though dist should not exceed maxDist if calculated from pixel center to image center.
const vignetteEffectRatio = Math.min(1, dist / maxDist);
// Apply softness (power curve for falloff shape). Higher softness means sharper falloff near edges.
// vignetteAmount is 0 (center, no effect) to 1 (edge, full effect based on intensity)
const vignetteAmount = Math.pow(vignetteEffectRatio, vignetteSoftness);
// Darkening multiplier: ranges from 1 (no change) down to (1 - vignetteIntensity) (max darkening)
const darkeningMultiplier = 1.0 - (vignetteAmount * vignetteIntensity);
r_proc *= darkeningMultiplier;
g_proc *= darkeningMultiplier;
b_proc *= darkeningMultiplier;
}
// Final clamp and assign to imageData (ensure integer values using Math.round)
data[i] = Math.round(Math.max(0, Math.min(255, r_proc)));
data[i + 1] = Math.round(Math.max(0, Math.min(255, g_proc)));
data[i + 2] = Math.round(Math.max(0, Math.min(255, b_proc)));
// Alpha channel (data[i+3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes