You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, bulgeStrength = 1.6, windFlutter = 45, flutterFrequency = 0.12, upwardWind = 60, scaryFactor = 0.65) {
// Parse parameters to ensure they are standard numbers if passed as strings
bulgeStrength = parseFloat(bulgeStrength);
windFlutter = parseFloat(windFlutter);
flutterFrequency = parseFloat(flutterFrequency);
upwardWind = parseFloat(upwardWind);
scaryFactor = parseFloat(scaryFactor);
const canvas = document.createElement('canvas');
const w = canvas.width = originalImg.width;
const h = canvas.height = originalImg.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0, w, h);
// Safety check for empty images
if (w === 0 || h === 0) return canvas;
const imgData = ctx.getImageData(0, 0, w, h);
// Copy source data for reading because we'll mutate imgData in place
const srcData = new Uint8ClampedArray(imgData.data);
const dstData = imgData.data;
const cx = w / 2;
const cy = h / 2;
// Effectively covers the whole image from the center outwardly
const maxR = Math.max(w, h) * 0.8;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
let dx = x - cx;
let dy = y - cy;
let dist = Math.sqrt(dx * dx + dy * dy);
let sx = x;
let sy = y;
if (dist < maxR) {
let normRadius = dist / maxR;
// Applies the intense bulging outward for the focal point
let sourceRadiusNorm = Math.pow(normRadius, bulgeStrength);
let sourceRadius = sourceRadiusNorm * maxR;
let falloff = 1 - normRadius; // Highest intensity at the center
if (dist > 0) {
// Wind ripple simulates the skin violently flapping
let flapX = Math.sin(y * flutterFrequency) * windFlutter * falloff;
let flapY = Math.cos(x * flutterFrequency) * windFlutter * falloff;
// Creates an intense directional blowing force upwards
let pullUp = (falloff * falloff) * upwardWind;
sx = cx + (dx / dist) * sourceRadius + flapX;
sy = cy + (dy / dist) * sourceRadius + flapY - pullUp;
} else {
sx = cx;
sy = cy;
}
}
// Clamp coordinate boundaries
sx = Math.max(0, Math.min(w - 1, sx));
sy = Math.max(0, Math.min(h - 1, sy));
// Bilinear Interpolation for smooth distortion
const x1 = Math.floor(sx);
const y1 = Math.floor(sy);
const x2 = Math.min(x1 + 1, w - 1);
const y2 = Math.min(y1 + 1, h - 1);
const fx = sx - x1;
const fy = sy - y1;
const ifx = 1 - fx;
const ify = 1 - fy;
const idx11 = (y1 * w + x1) * 4;
const idx21 = (y1 * w + x2) * 4;
const idx12 = (y2 * w + x1) * 4;
const idx22 = (y2 * w + x2) * 4;
// Fetch colors for 4 adjacent pixels
const r11 = srcData[idx11], g11 = srcData[idx11+1], b11 = srcData[idx11+2], a11 = srcData[idx11+3];
const r21 = srcData[idx21], g21 = srcData[idx21+1], b21 = srcData[idx21+2], a21 = srcData[idx21+3];
const r12 = srcData[idx12], g12 = srcData[idx12+1], b12 = srcData[idx12+2], a12 = srcData[idx12+3];
const r22 = srcData[idx22], g22 = srcData[idx22+1], b22 = srcData[idx22+2], a22 = srcData[idx22+3];
// Interpolate X axis
const rTop = r11 * ifx + r21 * fx; const rBot = r12 * ifx + r22 * fx;
const gTop = g11 * ifx + g21 * fx; const gBot = g12 * ifx + g22 * fx;
const bTop = b11 * ifx + b21 * fx; const bBot = b12 * ifx + b22 * fx;
const aTop = a11 * ifx + a21 * fx; const aBot = a12 * ifx + a22 * fx;
// Interpolate Y axis
let rCol = rTop * ify + rBot * fy;
let gCol = gTop * ify + gBot * fy;
let bCol = bTop * ify + bBot * fy;
let aCol = aTop * ify + aBot * fy;
// Apply "Scary" lighting, desaturation, and coloring
if (scaryFactor > 0) {
// 1. Contrast Boost
const contrast = 1 + scaryFactor;
rCol = ((rCol / 255 - 0.5) * contrast + 0.5) * 255;
gCol = ((gCol / 255 - 0.5) * contrast + 0.5) * 255;
bCol = ((bCol / 255 - 0.5) * contrast + 0.5) * 255;
// 2. Creepy Desaturation (Zodiac / Grudge style)
const luminance = 0.299 * rCol + 0.587 * gCol + 0.114 * bCol;
const desat = Math.min(scaryFactor * 0.75, 1.0);
rCol = rCol + (luminance - rCol) * desat;
gCol = gCol + (luminance - gCol) * desat;
bCol = bCol + (luminance - bCol) * desat;
// 3. Claustrophobic Vignette
const vignetteDist = Math.min(dist / maxR, 1.0);
const vignetteStrength = 1.0 - Math.pow(vignetteDist, 2.0) * scaryFactor;
rCol *= vignetteStrength;
gCol *= vignetteStrength;
bCol *= vignetteStrength;
}
// Write final pixels to buffer
const dstIdx = (y * w + x) * 4;
dstData[dstIdx] = Math.max(0, Math.min(255, rCol));
dstData[dstIdx + 1] = Math.max(0, Math.min(255, gCol));
dstData[dstIdx + 2] = Math.max(0, Math.min(255, bCol));
dstData[dstIdx + 3] = Math.max(0, Math.min(255, aCol));
}
}
// Apply the manipulated pixels to the canvas buffer
ctx.putImageData(imgData, 0, 0);
return canvas;
}
Apply Changes