You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, intensity = 30, maxHorizontalShift = 20, colorDistortion = 0.3, noiseLevel = 0.1) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
if (canvas.width === 0 || canvas.height === 0) {
// Handle cases where image dimensions are not available (e.g. image not loaded yet)
// Return a minimal canvas or could throw an error.
canvas.width = 1; // Avoid division by zero or errors with 0-size canvas
canvas.height = 1;
console.warn("Original image has zero width or height. Ensure the image is loaded before processing.");
return canvas;
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
if (intensity === 0) {
return canvas; // No effect if intensity is 0
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
const width = canvas.width;
const height = canvas.height;
// Create a copy of pixel data to read from, ensuring glitches are based on the state before this iteration's effects.
const originalPixels = new Uint8ClampedArray(pixels);
const intensityFactor = intensity / 100; // Normalized intensity (0.0 to 1.0)
const intensityFactorSqrt = Math.sqrt(intensityFactor); // Used for non-linear scaling of some effects
// Number of glitch iterations/bands. More intensity = more glitches.
const glitchIterations = Math.floor(intensityFactor * 50 + 10); // Min 10, Max 60 iterations
for (let i = 0; i < glitchIterations; i++) {
const startY = Math.floor(Math.random() * height);
// Determine the height of this glitch strip. Scales with intensity.
// Max ~2% of image height at full intensity for a single strip. Min 1 pixel.
const stripHeightMaxRandomPart = (height * 0.01 * (intensityFactor / 0.5) + 1); // (intensityFactor / 0.5) normalizes intensityFactor to 0-2 range
const stripHeight = Math.floor(Math.random() * stripHeightMaxRandomPart) + 1;
// Determine the horizontal shift for this strip. Scales with maxHorizontalShift and intensity.
const currentActualShift = Math.floor((Math.random() - 0.5) * 2 * maxHorizontalShift * intensityFactorSqrt);
// Probabilities for applying color distortion and noise within this strip
const applyColorDistortionThisStrip = Math.random() < colorDistortion;
const applyNoiseThisStrip = Math.random() < noiseLevel;
// Determine color channel separation if color distortion is applied.
// Scales with colorDistortion parameter and intensity. Max ~8px at Nfull intensity/distortion.
const channelSeparation = applyColorDistortionThisStrip ?
Math.floor(Math.random() * 8 * colorDistortion * intensityFactorSqrt + 1) : 0;
for (let y = startY; y < Math.min(startY + stripHeight, height); y++) {
for (let x = 0; x < width; x++) {
const targetIdx = (y * width + x) * 4;
// Determine base source X coordinate after applying the horizontal shift for this strip.
// Uses a robust modulo operation for potentially negative intermediate results.
const x_content_src_base = ((x - currentActualShift) % width + width) % width;
let r_final, g_final, b_final, a_final;
if (applyColorDistortionThisStrip) {
// Apply chromatic aberration like effect by sampling R, G, B channels from slightly different source pixels
const r_src_x = Math.max(0, Math.min(width - 1, x_content_src_base - channelSeparation));
const g_src_x = x_content_src_base; // Green channel from the central (shifted) position
const b_src_x = Math.max(0, Math.min(width - 1, x_content_src_base + channelSeparation));
r_final = originalPixels[(y * width + r_src_x) * 4];
g_final = originalPixels[(y * width + g_src_x) * 4 + 1];
b_final = originalPixels[(y * width + b_src_x) * 4 + 2];
a_final = originalPixels[(y * width + g_src_x) * 4 + 3]; // Alpha from the central source pixel
} else {
// No color distortion, just use the pixel from the shifted horizontal position
const srcIdx = (y * width + x_content_src_base) * 4;
r_final = originalPixels[srcIdx];
g_final = originalPixels[srcIdx + 1];
b_final = originalPixels[srcIdx + 2];
a_final = originalPixels[srcIdx + 3];
}
if (applyNoiseThisStrip) {
// Add random noise to R, G, B channels
// Noise magnitude scales with noiseLevel parameter. Max +/-30 (out of 255) at noiseLevel=1.0.
const noiseMagnitude = (Math.random() - 0.5) * 60 * noiseLevel;
r_final = Math.max(0, Math.min(255, r_final + noiseMagnitude));
g_final = Math.max(0, Math.min(255, g_final + noiseMagnitude));
b_final = Math.max(0, Math.min(255, b_final + noiseMagnitude));
}
pixels[targetIdx] = r_final;
pixels[targetIdx + 1] = g_final;
pixels[targetIdx + 2] = b_final;
pixels[targetIdx + 3] = a_final; // Preserve alpha from the (potentially shifted) source
}
}
}
// Static Blocks: Overwrite parts of the image with solid color blocks or B/W static
// Number of static blocks scales with intensity. Max ~3 blocks at full intensity.
const numStaticBlocks = Math.floor(Math.random() * (intensityFactor * 3));
for (let k = 0; k < numStaticBlocks; k++) {
const blockX = Math.floor(Math.random() * width * 0.9); // Start block within most of the image
const blockY = Math.floor(Math.random() * height * 0.9);
// Block size also somewhat scales with intensity.
const blockMaxWidth = width * 0.20 * intensityFactor + width * 0.05; // Max 20% width from intensity + 5% base width
const blockMaxHeight = height * 0.03 * intensityFactor + height * 0.01; // Max 3% height from intensity + 1% base height
const minBlockWidth = width * 0.01; // Min 1% width
const minBlockHeight = Math.max(1, height * 0.005); // Min 0.5% height, at least 1px
const currentBlockWidth = Math.floor(Math.random() * blockMaxWidth + minBlockWidth);
const currentBlockHeight = Math.floor(Math.random() * blockMaxHeight + minBlockHeight);
const isColorBlock = Math.random() > 0.4; // 60% chance of a colored block, 40% B/W static
let R_block, G_block, B_block;
if (isColorBlock) {
// For color blocks, tend towards darker, primary/secondary "glitchy" colors
R_block = (Math.random() > 0.6 ? Math.floor(Math.random() * 150) : 0);
G_block = (Math.random() > 0.6 ? Math.floor(Math.random() * 150) : 0);
B_block = (Math.random() > 0.6 ? Math.floor(Math.random() * 150) : 0);
} else {
// For B/W static, simple black or white pixels
const c = Math.random() > 0.5 ? 255 : 0;
R_block = c; G_block = c; B_block = c;
}
for (let y = blockY; y < Math.min(blockY + currentBlockHeight, height); y++) {
for (let x = blockX; x < Math.min(blockX + currentBlockWidth, width); x++) {
const idx = (y * width + x) * 4;
pixels[idx] = R_block;
pixels[idx + 1] = G_block;
pixels[idx + 2] = B_block;
// Static blocks are typically opaque. Preserve transparency only if original area was fully transparent.
if (originalPixels[idx+3] > 0) {
pixels[idx+3] = 255; // Make opaque over non-transparent original parts
} else {
pixels[idx+3] = 0; // Preserve full transparency
}
}
}
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes