You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, sadIntensity = 1, vignetteStrength = 0.75, addRain = 1, addNoise = 1) {
const canvas = document.createElement('canvas');
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
// Fallback if image isn't loaded properly
if (!width || !height) {
return canvas;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Draw the initial image
ctx.drawImage(originalImg, 0, 0, width, height);
// Fetch pixel data to manipulate colors
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
const intensity = Math.max(0, Math.min(1, Number(sadIntensity)));
const noiseLevel = Number(addNoise) > 0 ? 15 * intensity : 0;
// Pixel manipulation for desaturation, darkening, cool tint, and noise
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i+1];
const b = data[i+2];
// Calculate standard luminance (grayscale equivalent)
const lum = r * 0.299 + g * 0.587 + b * 0.114;
// Target "sad" color: dims the image drastically while injecting a cool, depressing blue tint
const tr = lum * 0.65;
const tg = lum * 0.75;
const tb = lum * 0.95;
// Generate subtle random noise for a gritty, film-like depression effect
const noise = noiseLevel > 0 ? (Math.random() - 0.5) * noiseLevel * 2 : 0;
// Apply intensity and noise, clamping to 0-255 boundaries
data[i] = Math.max(0, Math.min(255, r + (tr - r) * intensity + noise));
data[i+1] = Math.max(0, Math.min(255, g + (tg - g) * intensity + noise));
data[i+2] = Math.max(0, Math.min(255, b + (tb - b) * intensity + noise));
}
// Put modified pixels back to the canvas
ctx.putImageData(imageData, 0, 0);
// Apply Vignette overlay (darkened edges that create a heavy, isolated feel)
const vStrength = Math.max(0, Math.min(1, Number(vignetteStrength)));
if (vStrength > 0) {
const gradient = ctx.createRadialGradient(
width / 2, height / 2, Math.min(width, height) * 0.25,
width / 2, height / 2, Math.max(width, height) * 0.8
);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
gradient.addColorStop(1, `rgba(0, 0, 0, ${vStrength})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
// Apply Rain overly mapping (a classic sad meme visual)
const rainFactor = Number(addRain);
if (rainFactor > 0) {
// Calculate proportional raindrops based on image resolution (area)
const rainDrops = Math.floor((width * height) / 2000 * rainFactor);
ctx.lineCap = 'round';
for (let i = 0; i < rainDrops; i++) {
ctx.beginPath();
// Randomly position start point. X is broadened to accommodate slanting inside the frame.
const x = Math.random() * (width + height * 0.5) - height * 0.25;
const y = Math.random() * height;
// Randomize drop length relative to canvas height
const dropLen = Math.random() * (height * 0.05) + (height * 0.02);
// Determine consistent slant angle
const dx = dropLen * 0.25;
const dy = dropLen;
// Variable opacity to simulate depth
const opacity = Math.random() * 0.3 + 0.05;
ctx.strokeStyle = `rgba(180, 200, 220, ${opacity})`;
ctx.lineWidth = Math.max(0.5, Math.min(3, width * 0.0015));
ctx.moveTo(x, y);
ctx.lineTo(x - dx, y + dy);
ctx.stroke();
}
}
return canvas;
}
Apply Changes