You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, severity = 5, hasPhysicalDamage = 1, colorCorruption = 1) {
// Coerce parameters to numbers and bound safely
severity = Math.max(0, Math.min(20, Number(severity)));
hasPhysicalDamage = Number(hasPhysicalDamage);
colorCorruption = Number(colorCorruption);
// Setup Canvas
const canvas = document.createElement('canvas');
const w = originalImg.width || originalImg.naturalWidth;
const h = originalImg.height || originalImg.naturalHeight;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Draw original image
ctx.drawImage(originalImg, 0, 0, w, h);
let impactX = -1;
let impactY = -1;
// 1. Simulate Physical Damage Layer (Broken Glass & Liquid Crystal Leak)
if (hasPhysicalDamage === 1) {
// Decide a random point of impact for the monitor screen break
impactX = Math.floor(w * (0.2 + Math.random() * 0.6));
impactY = Math.floor(h * (0.2 + Math.random() * 0.6));
// Draw the primary liquid crystal "bleed" (a jagged, dark blob)
ctx.fillStyle = '#0a0a0f';
ctx.beginPath();
for (let angle = 0; angle < Math.PI * 2; angle += 0.15) {
const radius = Math.min(w, h) * (0.02 + Math.random() * 0.08);
const x = impactX + Math.cos(angle) * radius;
const y = impactY + Math.sin(angle) * radius;
if (angle === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.fill();
// Draw crystal leakage branches radiating from the crack
for (let i = 0; i < 15; i++) {
ctx.beginPath();
ctx.moveTo(impactX, impactY);
let currX = impactX;
let currY = impactY;
const angleBase = (Math.PI * 2 / 15) * i + (Math.random() - 0.5);
ctx.strokeStyle = '#050508';
// Random walk outward to create jagged fracture lines
for (let j = 0; j < 12; j++) {
const step = Math.min(w, h) * (0.02 + Math.random() * 0.04);
const angle = angleBase + (Math.random() - 0.5) * 1.5;
currX += Math.cos(angle) * step;
currY += Math.sin(angle) * step;
ctx.lineTo(currX, currY);
ctx.lineWidth = Math.max(1, 4 - j * 0.3);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(currX, currY);
}
}
// Introduce bright vertical "ribbon sync" failure lines directly over the physical break
const syncColors = ['#ff0000', '#00ff00', '#0000ff', '#00ffff', '#ff00ff', '#ffff00', '#ffffff', '#000000'];
for(let c = 0; c < 8; c++) {
const color = syncColors[Math.floor(Math.random() * syncColors.length)];
ctx.fillStyle = color;
const glWidth = Math.random() * 4 + 1;
const glX = impactX + (Math.random() - 0.5) * 40;
ctx.fillRect(glX, 0, glWidth, h);
}
// Horizontal sync freeze below the line of impact (classic broken panel artifact)
ctx.fillStyle = 'rgba(255, 255, 255, 0.15)';
ctx.fillRect(0, impactY, w, h - impactY);
for(let c = 0; c < 4; c++) {
const color = syncColors[Math.floor(Math.random() * syncColors.length)];
ctx.fillStyle = color;
const glHeight = Math.random() * 3 + 1;
const glY = impactY + (Math.random() - 0.5) * 30;
ctx.fillRect(0, glY, w, glHeight);
}
}
// 2. Fetch pixels to create Logic Board / Timing Controller (T-CON) failures
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
const srcData = new Uint8ClampedArray(data); // Immutable copy for referencing original pixels during shifts
// Chromatic aberration / Ghosting severity
const shiftAmount = Math.floor(severity * (w * 0.003));
// Generate corrupted "T-CON bands" (areas where screen processes colors incorrectly)
const tconBands = [];
if (colorCorruption === 1) {
const numBands = Math.floor(severity * 1.5);
for(let i=0; i<numBands; i++) {
tconBands.push({
yStart: Math.random() * h,
height: Math.random() * (h * 0.15),
type: Math.floor(Math.random() * 3) // 0: color inverted, 1: bit-crushed/solarized, 2: drop channel
});
}
}
// Process Pixels
for (let y = 0; y < h; y++) {
// Determine whether the current line falls within a T-CON failing band
let activeBand = null;
for (let b = 0; b < tconBands.length; b++) {
if (y >= tconBands[b].yStart && y < tconBands[b].yStart + tconBands[b].height) {
activeBand = tconBands[b];
break;
}
}
// Horizontal Sync loss (wavy line jitter based on severity)
let rowOffset = 0;
if (severity > 3 && Math.random() < 0.03) {
rowOffset = Math.floor((Math.random() - 0.5) * severity * 4);
}
for (let x = 0; x < w; x++) {
const i = (y * w + x) * 4;
// Chromatic separation / ghosting offset by row
let srcX_R = Math.min(w - 1, Math.max(0, x + shiftAmount + rowOffset));
let srcX_B = Math.min(w - 1, Math.max(0, x - shiftAmount + rowOffset));
let srcX_G = Math.min(w - 1, Math.max(0, x + rowOffset));
const iR = (y * w + srcX_R) * 4;
const iG = (y * w + srcX_G) * 4;
const iB = (y * w + srcX_B) * 4;
let r = srcData[iR];
let g = srcData[iG + 1];
let b = srcData[iB + 2];
// Apply T-Con Board Colorspace Corruption
if (activeBand) {
if (activeBand.type === 0) {
// Color Inversion
r = 255 - r;
g = 255 - g;
b = 255 - b;
} else if (activeBand.type === 1) {
// Solarization Wrap-around
r = r > 127 ? 255 - r : r;
g = g > 127 ? 255 - g : g;
b = b > 127 ? 255 - b : b;
} else if (activeBand.type === 2) {
// Color Channel Loss (Classic dying cable look)
if (x % 3 === 0) r = 0;
if (x % 3 === 1) g = 0;
if (x % 3 === 2) b = 0;
}
}
// LCD Panel Subpixel Matrix Interference (Scanline effect)
if (y % 3 === 0) {
r = r * 0.8;
g = g * 0.8;
b = b * 0.8;
}
// Image Retention / Posterization in the lower section if physically damaged
if (hasPhysicalDamage === 1 && y > impactY + 30) {
const reduce = 32; // Color crushing constraint
r = Math.floor(r / reduce) * reduce;
g = Math.floor(g / reduce) * reduce;
b = Math.floor(b / reduce) * reduce;
}
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
// leave alpha channel unmodified to preserve transparency format boundaries
}
}
// 3. Isolated Stuck & Dead Subpixels
const totalPixels = w * h;
const numDead = Math.floor(totalPixels * (severity * 0.00015));
const pureColors = [
[255, 0, 0], [0, 255, 0], [0, 0, 255], [0, 0, 0], [255, 255, 255], [0, 255, 255], [255, 0, 255]
];
for (let p = 0; p < numDead; p++) {
const dx = Math.floor(Math.random() * w);
const dy = Math.floor(Math.random() * h);
const i = (dy * w + dx) * 4;
const c = pureColors[Math.floor(Math.random() * pureColors.length)];
data[i] = c[0];
data[i+1] = c[1];
data[i+2] = c[2];
data[i+3] = 255;
}
// 4. Random Thin Ribbon Cable Synchronization Streaks (Vertical)
const numRibbons = Math.floor(severity * 3);
for (let l = 0; l < numRibbons; l++) {
const rx = Math.floor(Math.random() * w);
const rWidth = Math.floor(Math.random() * 3) + 1;
const c = pureColors[Math.floor(Math.random() * pureColors.length)];
const yStart = Math.random() > 0.4 ? 0 : Math.floor(Math.random() * h);
const yEnd = Math.random() > 0.4 ? h : Math.floor(Math.random() * h + yStart);
for (let ry = yStart; ry < yEnd; ry++) {
if (ry >= h) continue;
for (let rw = 0; rw < rWidth; rw++) {
if (rx + rw >= w) continue;
const i = (ry * w + (rx + rw)) * 4;
data[i] = c[0];
data[i+1] = c[1];
data[i+2] = c[2];
data[i+3] = 255;
}
}
}
// Embed all modifications
ctx.putImageData(imgData, 0, 0);
return canvas;
}
Apply Changes