You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
pixelationFactor = 3,
desaturation = 0.3,
sepiaStrength = 0.1,
greenTintStrength = 0.1,
noiseIntensity = 0.08,
scanLineOpacity = 0.15,
scanLineHeight = 1,
vignetteStrength = 0.6
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure image is loaded, otherwise width/height might be 0 or incorrect
if (!originalImg.complete || typeof originalImg.naturalWidth === "undefined" || originalImg.naturalWidth === 0) {
console.error("Image not loaded or invalid image provided to processImage.");
// Return a placeholder canvas indicating an error
canvas.width = 200;
canvas.height = 150;
ctx.fillStyle = '#333';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText("Error: Image not loaded", canvas.width / 2, canvas.height / 2);
return canvas;
}
const w = originalImg.width;
const h = originalImg.height;
canvas.width = w;
canvas.height = h;
// Validate and sanitize parameters
pixelationFactor = Math.max(1, Math.floor(Number(pixelationFactor) || 1));
desaturation = Math.max(0, Math.min(1, Number(desaturation) || 0));
sepiaStrength = Math.max(0, Math.min(1, Number(sepiaStrength) || 0));
greenTintStrength = Math.max(0, Math.min(1, Number(greenTintStrength) || 0));
noiseIntensity = Math.max(0, Math.min(1, Number(noiseIntensity) || 0));
scanLineOpacity = Math.max(0, Math.min(1, Number(scanLineOpacity) || 0));
scanLineHeight = Math.max(1, Math.floor(Number(scanLineHeight) || 1));
vignetteStrength = Math.max(0, Math.min(1, Number(vignetteStrength) || 0));
// 1. Pixelation
if (pixelationFactor > 1) {
const pw = Math.max(1, Math.floor(w / pixelationFactor));
const ph = Math.max(1, Math.floor(h / pixelationFactor));
const tempCanvas = document.createElement('canvas');
tempCanvas.width = pw;
tempCanvas.height = ph;
const tempCtx = tempCanvas.getContext('2d');
// Draw original image scaled down to pixelated size
tempCtx.drawImage(originalImg, 0, 0, pw, ph);
// Disable smoothing when scaling up for pixelated effect
ctx.imageSmoothingEnabled = false;
// For wider compatibility (though modern browsers support unprefixed)
// ctx.mozImageSmoothingEnabled = false;
// ctx.webkitImageSmoothingEnabled = false;
// ctx.msImageSmoothingEnabled = false;
ctx.drawImage(tempCanvas, 0, 0, w, h);
// Re-enable smoothing for subsequent drawing operations (like vignette)
ctx.imageSmoothingEnabled = true;
// ctx.mozImageSmoothingEnabled = true;
// ctx.webkitImageSmoothingEnabled = true;
// ctx.msImageSmoothingEnabled = true;
} else {
ctx.drawImage(originalImg, 0, 0, w, h);
}
// 2. Image Data processing (Desaturation, Sepia, Green Tint, Noise)
// Only get/put image data if any of these effects are active
if (desaturation > 0 || sepiaStrength > 0 || greenTintStrength > 0 || noiseIntensity > 0) {
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// Desaturation
if (desaturation > 0) {
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
r = r * (1 - desaturation) + gray * desaturation;
g = g * (1 - desaturation) + gray * desaturation;
b = b * (1 - desaturation) + gray * desaturation;
}
// Sepia
if (sepiaStrength > 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 - sepiaStrength) + sr * sepiaStrength;
g = g * (1 - sepiaStrength) + sg * sepiaStrength;
b = b * (1 - sepiaStrength) + sb * sepiaStrength;
}
// Green Tint (emulate CRT phosphors)
if (greenTintStrength > 0) {
const greenBoost = 30 * greenTintStrength; // Max boost of 30 to green
const colorReduction = 15 * greenTintStrength; // Max reduction of 15 from r/b
g += greenBoost;
r -= colorReduction;
b -= colorReduction;
}
// Noise (grayscale noise for a film grain/static look)
if (noiseIntensity > 0) {
// noiseMagnitude determines the max pixel value change due to noise.
// E.g., if noiseIntensity is 0.1, noiseMagnitude is 5. Pixel values change by up to +/-5.
const noiseMagnitude = 50 * noiseIntensity;
const noise = (Math.random() - 0.5) * 2 * noiseMagnitude; // Value between -noiseMagnitude and +noiseMagnitude
r += noise;
g += noise;
b += noise;
}
// Clamp values and assign back
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. Scan Lines
if (scanLineOpacity > 0 && scanLineHeight > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${scanLineOpacity})`;
for (let y = 0; y < h; y += scanLineHeight * 2) { // Creates lines of scanLineHeight, with gaps of scanLineHeight
ctx.fillRect(0, y, w, scanLineHeight);
}
}
// 4. Vignette
if (vignetteStrength > 0) {
// Ensure smoothing is on for a nice gradient if it was turned off
ctx.imageSmoothingEnabled = true;
const centerX = w / 2;
const centerY = h / 2;
// Inner radius is where the vignette effect starts (fully transparent before this point)
const innerRadius = Math.min(w, h) * 0.15;
// Outer radius is where the vignette is at its darkest (or full strength)
const outerRadius = Math.max(w, h) * 0.7;
const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Center of vignette is transparent
gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`); // Edge of vignette is dark
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
}
return canvas;
}
Apply Changes