You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Applies a Synthwave filter with retro North Korean TV effects to an image.
* This includes a duotone color scheme, chromatic aberration, noise, scanlines, and a vignette.
*
* @param {HTMLImageElement} originalImg The original image element.
* @param {string} darkColor The hex color for the dark tones of the image.
* @param {string} lightColor The hex color for the light tones of the image.
* @param {number} vignetteStrength The opacity of the vignette effect (0 to 1).
* @param {number} scanlineStrength The opacity of the scanlines (0 to 1).
* @param {number} scanlineHeight The height of each scanline bar in pixels.
* @param {number} noiseStrength The amount of random noise to add (0 to 1).
* @param {number} chromaticAberrationStrength The pixel offset for the color channel separation.
* @param {number} blurAmount The amount of blur to apply to the base image in pixels.
* @returns {HTMLCanvasElement} A new canvas element with the filter applied.
*/
function processImage(
originalImg,
darkColor = '#0f0c29',
lightColor = '#ff0055',
vignetteStrength = 0.7,
scanlineStrength = 0.2,
scanlineHeight = 2,
noiseStrength = 0.15,
chromaticAberrationStrength = 5,
blurAmount = 1
) {
// 1. Setup
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
// Helper to parse hex colors
const hexToRgb = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
};
const darkRgb = hexToRgb(darkColor);
const lightRgb = hexToRgb(lightColor);
if (!darkRgb || !lightRgb) {
console.error("Invalid hex color format. Provide colors like '#ff0000'.");
ctx.drawImage(originalImg, 0, 0, width, height);
return canvas;
}
// 2. Apply initial blur and draw the image
ctx.filter = `blur(${blurAmount}px)`;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none';
// 3. Apply Duotone and Noise
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Greyscale luminance (using the standard formula)
const luminance = (0.299 * r + 0.589 * g + 0.114 * b) / 255;
// Duotone: Interpolate between the dark and light colors
const newR = darkRgb.r * (1 - luminance) + lightRgb.r * luminance;
const newG = darkRgb.g * (1 - luminance) + lightRgb.g * luminance;
const newB = darkRgb.b * (1 - luminance) + lightRgb.b * luminance;
// Noise: Add random value to each channel
const noise = (Math.random() - 0.5) * 255 * noiseStrength;
// Apply and clamp values between 0 and 255
data[i] = Math.max(0, Math.min(255, newR + noise));
data[i + 1] = Math.max(0, Math.min(255, newG + noise));
data[i + 2] = Math.max(0, Math.min(255, newB + noise));
}
ctx.putImageData(imageData, 0, 0);
// 4. Apply Chromatic Aberration
const sourceData = ctx.getImageData(0, 0, width, height).data;
const finalImageData = ctx.createImageData(width, height);
const finalData = finalImageData.data;
const offset = Math.round(chromaticAberrationStrength);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4;
// Clamp coordinates to stay within image bounds
const redX = Math.max(0, Math.min(width - 1, x - offset));
const blueX = Math.max(0, Math.min(width - 1, x + offset));
const redIndex = (y * width + redX) * 4;
const greenIndex = i; // Green channel is not shifted
const blueIndex = (y * width + blueX) * 4;
finalData[i] = sourceData[redIndex]; // Red channel from left
finalData[i + 1] = sourceData[greenIndex + 1]; // Green channel from center
finalData[i + 2] = sourceData[blueIndex + 2]; // Blue channel from right
finalData[i + 3] = sourceData[greenIndex + 3]; // Alpha from center
}
}
ctx.putImageData(finalImageData, 0, 0);
// 5. Apply overlays (Scanlines and Vignette)
// Scanlines
if (scanlineStrength > 0 && scanlineHeight > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${scanlineStrength})`;
for (let y = 0; y < height; y += scanlineHeight * 2) {
ctx.fillRect(0, y, width, scanlineHeight);
}
}
// Vignette
if (vignetteStrength > 0) {
const centerX = width / 2;
const centerY = height / 2;
const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY);
const innerRadius = outerRadius * 0.3;
const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
// 6. Return the final canvas
return canvas;
}
Apply Changes