You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, strength = 0.8, warmth = 5, contrast = 15, saturation = 10, vignetteAmount = 25) {
// Helper function to clamp values between a min and max
const clamp = (val, min = 0, max = 255) => Math.max(min, Math.min(max, val));
const canvas = document.createElement('canvas');
// Use naturalWidth/Height to get the original image dimensions, not affected by CSS or display size
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error("Could not get 2D context from canvas.");
// Fallback: Return a canvas with the original image drawn, if context fails.
// Or one might throw an error or return null.
const fallbackCanvas = document.createElement('canvas');
fallbackCanvas.width = canvas.width;
fallbackCanvas.height = canvas.height;
const fbCtx = fallbackCanvas.getContext('2d');
if (fbCtx) {
fbCtx.drawImage(originalImg, 0, 0, fallbackCanvas.width, fallbackCanvas.height);
}
return fallbackCanvas;
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// Handle potential security errors if the image is cross-origin and canvas is tainted
console.error("Could not getImageData due to security restrictions (CORS) or other error: ", e);
// Return the canvas with the original image drawn, as pixel manipulation is not possible.
return canvas;
}
const data = imageData.data;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Calculate the furthest distance from the center (e.g., to a corner)
const maxDist = (canvas.width === 0 || canvas.height === 0) ? 0 : Math.sqrt(centerX * centerX + centerY * centerY);
// Scale parameters for use in calculations
const warmthAdj = warmth;
// contrast: A value of 0 means no change (multiplier 1.0).
// A positive value increases contrast. E.g., 15 gives a 1.15x multiplier.
const contrastMultiplier = 1.0 + (contrast / 100.0);
// saturation: A value of 0 means no change.
// E.g., 10 gives a 1.1x multiplier relative to grayscale.
const saturationMultiplier = 1.0 + (saturation / 100.0);
// vignettePower: 0 results in no vignette. 100 results in a strong vignette.
// E.g., 25 means vignette reduces brightness by up to 25% at the edges.
const vignettePower = vignetteAmount / 100.0;
for (let i = 0; i < data.length; i += 4) {
const r0 = data[i];
const g0 = data[i+1];
const b0 = data[i+2];
let r_eff = r0;
let g_eff = g0;
let b_eff = b0;
// 1. Saturation adjustment
if (saturationMultiplier !== 1.0) {
const L = 0.299 * r_eff + 0.587 * g_eff + 0.114 * b_eff; // Calculate luminance
r_eff = L + saturationMultiplier * (r_eff - L);
g_eff = L + saturationMultiplier * (g_eff - L);
b_eff = L + saturationMultiplier * (b_eff - L);
r_eff = clamp(r_eff);
g_eff = clamp(g_eff);
b_eff = clamp(b_eff);
}
// 2. Contrast adjustment
if (contrastMultiplier !== 1.0) {
r_eff = ((r_eff / 255.0 - 0.5) * contrastMultiplier + 0.5) * 255.0;
g_eff = ((g_eff / 255.0 - 0.5) * contrastMultiplier + 0.5) * 255.0;
b_eff = ((b_eff / 255.0 - 0.5) * contrastMultiplier + 0.5) * 255.0;
r_eff = clamp(r_eff);
g_eff = clamp(g_eff);
b_eff = clamp(b_eff);
}
// 3. Black Lift (subtle "film fade" characteristic)
// This is a fixed part of the filter character.
const fadeAmount = 3; // A small, fixed amount for a subtle lift in shadows
if (fadeAmount > 0) {
// Apply lift more to darker areas: (1 - color/255) is larger for darker colors.
r_eff = r_eff + fadeAmount * (1 - r_eff / 255.0);
g_eff = g_eff + fadeAmount * (1 - g_eff / 255.0);
b_eff = b_eff + fadeAmount * (1 - b_eff / 255.0);
r_eff = clamp(r_eff);
g_eff = clamp(g_eff);
b_eff = clamp(b_eff);
}
// 4. Warmth adjustment value
if (warmthAdj !== 0) {
r_eff += warmthAdj;
b_eff -= warmthAdj; // Adding to Red, subtracting from Blue results in warmer image
r_eff = clamp(r_eff);
// g_eff is not directly changed by this simple warmth model
b_eff = clamp(b_eff);
}
// 5. Vignette effect
if (vignettePower > 0 && maxDist > 0) {
const currentX = (i / 4) % canvas.width;
const currentY = Math.floor((i / 4) / canvas.width);
const dx = currentX - centerX;
const dy = currentY - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
const normalizedDist = dist / maxDist;
// vignette falloff power (2.0 is quadratic, 2.5 makes it somewhat tighter)
const vignetteFalloff = 2.5;
const vignetteEffect = 1.0 - Math.pow(normalizedDist, vignetteFalloff) * vignettePower;
const clampedVignetteEffect = clamp(vignetteEffect, 0, 1); // Ensure factor is [0,1]
r_eff *= clampedVignetteEffect;
g_eff *= clampedVignetteEffect;
b_eff *= clampedVignetteEffect;
// Already clamped after each operation, but good to be sure
r_eff = clamp(r_eff);
g_eff = clamp(g_eff);
b_eff = clamp(b_eff);
}
// Apply overall strength: linear interpolation between original and effected pixel
// If strength is 1, use fully effected pixel. If 0, use original (though r_eff starts as original).
r_eff = r0 * (1 - strength) + r_eff * strength;
g_eff = g0 * (1 - strength) + g_eff * strength;
b_eff = b0 * (1 - strength) + b_eff * strength;
data[i] = Math.round(clamp(r_eff));
data[i+1] = Math.round(clamp(g_eff));
data[i+2] = Math.round(clamp(b_eff));
// data[i+3] is alpha, remains unchanged.
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes