You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
sepiaIntensity = 0.6, // Number: 0.0 (no sepia) to 1.0 (full sepia)
saturationReduction = 0.3, // Number: 0.0 (full color) to 1.0 (grayscale)
contrastAdjustment = 1.1, // Number: e.g., 0.5 (low contrast) to 2.0 (high contrast); 1.0 is no change
brightnessOffset = 5, // Number: e.g., -50 (darker) to 50 (brighter); 0 is no change
noiseAmount = 15, // Number: e.g., 0 (no noise) to 50 (heavy noise)
vignetteStrength = 0.5, // Number: 0.0 (no vignette) to 1.0 (fully opaque edges)
vignetteStartRadiusFactor = 0.5, // Number: 0.0 to 1.0; Center transparent area radius, relative to image diagonal
vignetteEndRadiusFactor = 0.95 // Number: 0.0 to 1.0; Radius where vignette reaches full strength, relative to image diagonal
) {
// Ensure image is loaded
if (!originalImg.complete || originalImg.naturalWidth === 0) {
try {
await new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => reject(new Error("Image loading timed out for nostalgic filter.")), 5000);
originalImg.onload = () => {
clearTimeout(timeoutId);
if (originalImg.naturalWidth === 0) { // Still possible if onload fires for e.g. an empty SVG
reject(new Error("Image loaded successfully but has zero dimensions."));
} else {
resolve();
}
};
originalImg.onerror = () => {
clearTimeout(timeoutId);
reject(new Error("Image failed to load for nostalgic filter."));
};
// If src was set and it already failed, onerror might not fire again.
// If src was never set, onload/onerror will not fire. Timeout handles this.
// If image has already completed loading but resulted in 0 dimensions (e.g. broken link)
if (originalImg.complete && originalImg.naturalWidth === 0 && originalImg.src) {
clearTimeout(timeoutId);
reject(new Error("Image indicates completion but has zero dimensions (possibly broken link or empty image)."));
} else if (originalImg.complete && originalImg.naturalWidth > 0) { // Already loaded
clearTimeout(timeoutId);
resolve();
}
// For an image element with no src, it will timeout.
});
} catch (e) {
console.error(e.message);
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 200;
errorCanvas.height = 100;
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = '#f0f0f0';
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = 'red';
errorCtx.font = '12px Arial';
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
const lines = e.message.match(/.{1,25}/g) || ["Image error"]; // Wrap text
lines.forEach((line, index) => {
errorCtx.fillText(line, errorCanvas.width / 2, errorCanvas.height / 2 - (lines.length-1)*7 + index*14);
});
return errorCanvas;
}
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
if (width === 0 || height === 0) { // Should be caught by earlier checks
console.warn("Nostalgic filter: Image has zero width or height after loading checks.");
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;
// Clamp input parameters to sensible ranges
const currentSepiaIntensity = Math.max(0, Math.min(1, sepiaIntensity));
const currentSaturationReduction = Math.max(0, Math.min(1, saturationReduction));
const currentContrastAdjustment = Math.max(0, contrastAdjustment); // Allow >1, 0 means black
const currentNoiseAmount = Math.max(0, noiseAmount);
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Saturation Reduction
if (currentSaturationReduction > 0) {
const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Luminosity
r = r * (1 - currentSaturationReduction) + gray * currentSaturationReduction;
g = g * (1 - currentSaturationReduction) + gray * currentSaturationReduction;
b = b * (1 - currentSaturationReduction) + gray * currentSaturationReduction;
}
// 2. Sepia Tone
if (currentSepiaIntensity > 0) {
const sr = 0.393 * r + 0.769 * g + 0.189 * b;
const sg = 0.349 * r + 0.686 * g + 0.168 * b;
const sb = 0.272 * r + 0.534 * g + 0.131 * b;
r = r * (1 - currentSepiaIntensity) + sr * currentSepiaIntensity;
g = g * (1 - currentSepiaIntensity) + sg * currentSepiaIntensity;
b = b * (1 - currentSepiaIntensity) + sb * currentSepiaIntensity;
}
// 3. Brightness Adjustment
if (brightnessOffset !== 0) {
r += brightnessOffset;
g += brightnessOffset;
b += brightnessOffset;
}
// 4. Contrast Adjustment
if (currentContrastAdjustment !== 1.0) {
r = (((r / 255.0 - 0.5) * currentContrastAdjustment) + 0.5) * 255.0;
g = (((g / 255.0 - 0.5) * currentContrastAdjustment) + 0.5) * 255.0;
b = (((b / 255.0 - 0.5) * currentContrastAdjustment) + 0.5) * 255.0;
}
// 5. Noise
if (currentNoiseAmount > 0) {
const noiseVal = (Math.random() - 0.5) * 2 * currentNoiseAmount;
r += noiseVal;
g += noiseVal;
b += noiseVal;
}
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. Vignette
const currentVignetteStrength = Math.max(0, Math.min(1, vignetteStrength));
const currentStartRadiusFactor = Math.max(0, Math.min(1, vignetteStartRadiusFactor));
const currentEndRadiusFactor = Math.max(0, Math.min(1, vignetteEndRadiusFactor));
if (currentVignetteStrength > 0.001 && currentEndRadiusFactor > currentStartRadiusFactor) {
const centerX = width / 2;
const centerY = height / 2;
const maxRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
const innerR = maxRadius * currentStartRadiusFactor;
const outerR = maxRadius * currentEndRadiusFactor;
const gradient = ctx.createRadialGradient(centerX, centerY, innerR, centerX, centerY, outerR);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${currentVignetteStrength})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
return canvas;
}
Apply Changes