You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, p_desaturationAmount = "0.9", p_sepiaIntensity = "0.8", p_contrastValue = "20", p_noiseAmount = "15", p_vignetteStrength = "0.7", p_vignetteSoftness = "0.5", p_imperfectionAmount = "0.3") {
// Parse and validate parameters
const desaturationAmount = isNaN(parseFloat(p_desaturationAmount)) ? 0.9 : Math.max(0, Math.min(1, parseFloat(p_desaturationAmount)));
const sepiaIntensity = isNaN(parseFloat(p_sepiaIntensity)) ? 0.8 : Math.max(0, Math.min(1, parseFloat(p_sepiaIntensity)));
const contrastValue = isNaN(parseFloat(p_contrastValue)) ? 20 : Math.max(-100, Math.min(100, parseFloat(p_contrastValue)));
const noiseAmount = isNaN(parseFloat(p_noiseAmount)) ? 15 : Math.max(0, Math.min(100, parseFloat(p_noiseAmount)));
const vignetteStrength = isNaN(parseFloat(p_vignetteStrength)) ? 0.7 : Math.max(0, Math.min(1, parseFloat(p_vignetteStrength)));
const vignetteSoftness = isNaN(parseFloat(p_vignetteSoftness)) ? 0.5 : Math.max(0, Math.min(1, parseFloat(p_vignetteSoftness)));
const imperfectionAmount = isNaN(parseFloat(p_imperfectionAmount)) ? 0.3 : Math.max(0, Math.min(1, parseFloat(p_imperfectionAmount)));
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure originalImg has loaded and has dimensions
if (!originalImg.naturalWidth || !originalImg.naturalHeight) {
console.error("Image not loaded or has no dimensions.");
// Return an empty canvas or handle error appropriately
canvas.width = 1;
canvas.height = 1;
return canvas;
}
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
const centerX = width / 2;
const centerY = height / 2;
const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
// Contrast factor calculation: F = (259*(C+255))/(255*(259-C))
// C is contrast value from -255 to 255. Our input is -100 to 100. Let's scale it.
// A simpler way for -100 to 100 range: convert to a multiplier.
// Or use the standard formula with contrastValue directly if it's assumed that value is appropriate.
// The formula gives a factor. If contrastValue = 0, factor = 1.
const contrastFactor = (259 * (contrastValue + 255)) / (255 * (259 - contrastValue));
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Desaturation (Luminosity method)
// Apply desaturation based on desaturationAmount
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
r = r * (1 - desaturationAmount) + gray * desaturationAmount;
g = g * (1 - desaturationAmount) + gray * desaturationAmount;
b = b * (1 - desaturationAmount) + gray * desaturationAmount;
// 2. Sepia
if (sepiaIntensity > 0) {
// Standard sepia matrix values applied to the (potentially desaturated) R,G,B
const outR = (r * 0.393) + (g * 0.769) + (b * 0.189);
const outG = (r * 0.349) + (g * 0.686) + (b * 0.168);
const outB = (r * 0.272) + (g * 0.534) + (b * 0.131);
// Mix sepia with current color based on intensity
r = r * (1 - sepiaIntensity) + outR * sepiaIntensity;
g = g * (1 - sepiaIntensity) + outG * sepiaIntensity;
b = b * (1 - sepiaIntensity) + outB * sepiaIntensity;
}
// 3. Contrast
// Color = Factor * (Color - 128) + 128
r = contrastFactor * (r - 128) + 128;
g = contrastFactor * (g - 128) + 128;
b = contrastFactor * (b - 128) + 128;
// 4. Noise
if (noiseAmount > 0) {
// Add noise ranging from -noiseAmount to +noiseAmount
const noiseVal = (Math.random() - 0.5) * noiseAmount * 2;
r += noiseVal;
g += noiseVal;
b += noiseVal;
}
// Clip values intermediate step (before vignette)
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 5. Vignette
if (vignetteStrength > 0) {
const x = (i / 4) % width;
const y = Math.floor((i / 4) / width);
const dx = centerX - x;
const dy = centerY - y;
const dist = Math.sqrt(dx * dx + dy * dy);
// vignetteSoftness: 0 = harder (linear falloff), 1 = softer (more curved falloff, larger bright center)
// Exponent 'p' controls the curve: p=1 for linear, p=4 for very soft/wide bright center.
const p_exponent = 1 + vignetteSoftness * 3; // Maps 0-1 to 1-4
// vignetteFactor decreases from 1 (center) to (1 - vignetteStrength) at corners
const vignetteEffect = vignetteStrength * Math.pow(dist / maxDist, p_exponent);
const vignetteFactor = 1.0 - vignetteEffect;
r *= vignetteFactor;
g *= vignetteFactor;
b *= vignetteFactor;
}
// Final clip
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);
// 6. Imperfections (Scratches and Dust) - drawn on top
if (imperfectionAmount > 0) {
// Scratches
const numScratches = Math.floor(imperfectionAmount * 15 * Math.min(1, (width*height)/(800*600) ) ); // Scale with image size, max ~15 for 800x600
for (let k = 0; k < numScratches; k++) {
ctx.beginPath();
const x1 = Math.random() * width;
const y1 = Math.random() * height;
const length = (Math.random() * 0.3 + 0.05) * Math.min(width, height); // 5-35% of min dimension
const angle = Math.random() * Math.PI * 2;
const x2 = x1 + Math.cos(angle) * length;
const y2 = y1 + Math.sin(angle) * length;
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = Math.random() * 1.2 + 0.2; // Thin scratches
const scratchOpacity = (Math.random() * 0.3 + 0.05) * imperfectionAmount;
// Scratches are usually lighter or darker based on emulsion damage
const lightness = Math.random() > 0.6 ? 200 : 40; // Light or dark scratches
ctx.strokeStyle = `rgba(${lightness}, ${lightness}, ${lightness}, ${scratchOpacity})`;
ctx.stroke();
}
// Dust spots
const numDustSpots = Math.floor(imperfectionAmount * 700 * Math.min(1, (width*height)/(800*600) ) ); // Scale density, max ~700 for 800x600
for (let k = 0; k < numDustSpots; k++) {
ctx.beginPath();
const x = Math.random() * width;
const y = Math.random() * height;
const radius = Math.random() * 1.5 + 0.3; // Small spots
ctx.arc(x, y, radius, 0, Math.PI * 2);
const dustOpacity = (Math.random() * 0.4 + 0.1) * imperfectionAmount;
const lightness = Math.random() > 0.5 ? 220 : 30; // Light or dark dust
ctx.fillStyle = `rgba(${lightness}, ${lightness}, ${lightness}, ${dustOpacity})`;
ctx.fill();
}
}
return canvas;
}
Apply Changes