You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, bands = "90", modulationDepth = "4", carrierFreq = "0.5", colorPreset = "vfd", bgHex = "#0a0c10") {
// Parse parameters and apply defaults
let numBands = parseInt(bands, 10);
if (isNaN(numBands) || numBands < 10) numBands = 90;
let depth = parseFloat(modulationDepth);
if (isNaN(depth)) depth = 4;
let freq = parseFloat(carrierFreq);
if (isNaN(freq)) freq = 0.5;
const preset = String(colorPreset).toLowerCase();
const bgColor = String(bgHex) || "#0a0c10";
// Initialize Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.width;
canvas.height = originalImg.height;
// Draw the original image to extract pixel data
ctx.drawImage(originalImg, 0, 0);
let imgData;
try {
imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// Fallback in case of CORS canvas tainting without proper image headers
return canvas;
}
const data = imgData.data;
// Clear and fill background
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Setup for the Sanyo Vocoder Oscilloscope/Visualizer Effect
const stepY = Math.max(2, canvas.height / numBands);
// Adaptive stepping on X to balance performance and segment size
const stepX = Math.max(1, Math.min(4, Math.floor(canvas.width / 250)));
// Use "screen" for that glowing, vintage electronic luminescent display feel
ctx.globalCompositeOperation = 'screen';
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
// Loop through each frequency band (represented as rows)
for (let y = stepY / 2; y < canvas.height; y += stepY) {
let prevX = 0;
let prevY = y;
let isFirst = true;
for (let x = 0; x <= canvas.width; x += stepX) {
const px = Math.min(canvas.width - 1, Math.floor(x));
const py = Math.min(canvas.height - 1, Math.floor(y));
// Extract RGB
const idx = (py * canvas.width + px) * 4;
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
// Calculate pixel luminance (normalized 0.0 - 1.0)
const luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// Apply vocoder math mapping luminance to waveform amplitude
const envelope = luma * depth * stepY;
// Apply a subtle high-frequency carrier wave for the vintage synth look
const ripple = Math.sin(x * freq) * (luma * depth * stepY * 0.25);
// Displace y-position upwards proportional to brightness
const currentY = y - envelope + ripple;
if (!isFirst) {
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(x, currentY);
// Dynamically adjust alpha opacity based on brightness
const alpha = (0.2 + luma * 0.8).toFixed(2);
let strokeColor = '';
switch(preset) {
case 'vfd':
// Vintage Vacuum Fluorescent Display (Cyan/Green)
strokeColor = `rgba(0, 255, 204, ${alpha})`;
break;
case 'neon':
// Cyberpunk alternate magenta/cyan
const isAlt = Math.floor(y / stepY) % 2 === 0;
strokeColor = isAlt ? `rgba(0, 255, 255, ${alpha})` : `rgba(255, 0, 255, ${alpha})`;
break;
case 'amber':
// Retro monochrome monitor amber
strokeColor = `rgba(255, 176, 0, ${alpha})`;
break;
case 'monochrome':
// Pure white phosphor
strokeColor = `rgba(255, 255, 255, ${alpha})`;
break;
case 'original':
default:
// Disrupted representation of original palette
strokeColor = `rgba(${r}, ${g}, ${b}, ${Math.min(1.0, parseFloat(alpha) + 0.2)})`;
break;
}
ctx.strokeStyle = strokeColor;
// Brighter regions get slightly thicker lines
ctx.lineWidth = Math.max(0.5, stepY * 0.25 * (0.4 + luma * 0.6));
ctx.stroke();
}
prevX = x;
prevY = currentY;
isFirst = false;
}
}
// Polish with a vintage CRT scanline overlay effect
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = 'rgba(0, 0, 0, 0.35)'; // Darken lines
// Scanline gap width mapping
const scanGap = Math.max(2, Math.floor(canvas.height / 300));
for (let i = 0; i < canvas.height; i += scanGap * 1.5) {
ctx.fillRect(0, i, canvas.width, scanGap * 0.5);
}
return canvas;
}
Apply Changes