You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, posterizeSteps = "6", edgeSensitivity = "40", neonColor = "#df1920") {
let pSteps = parseInt(posterizeSteps, 10);
if (isNaN(pSteps) || pSteps < 2) pSteps = 6;
let eThresh = parseInt(edgeSensitivity, 10);
if (isNaN(eThresh)) eThresh = 40;
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Initial draw with higher saturation for that vibrant "The Series" 2D animation style
ctx.filter = 'saturate(1.6) contrast(1.1)';
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none';
// Image Processing: Cel-shaded / Comic filter (Edge detection + Posterization)
let imgData;
try {
imgData = ctx.getImageData(0, 0, width, height);
} catch (e) {
// Fallback for CORS issues - silently return canvas drawn so far
return canvas;
}
const data = imgData.data;
const outData = new Uint8ClampedArray(data.length);
const w = width;
// Preserve alpha channel
for (let i = 0; i < data.length; i += 4) {
outData[i + 3] = data[i + 3];
}
const stepSize = 255 / (pSteps - 1);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * w + x) * 4;
// Border pixels (skip edge detection logic to prevent out-of-bounds)
if (x === 0 || x === w - 1 || y === 0 || y === height - 1) {
outData[i] = Math.round((data[i] / 255) * (pSteps - 1)) * stepSize;
outData[i+1] = Math.round((data[i+1] / 255) * (pSteps - 1)) * stepSize;
outData[i+2] = Math.round((data[i+2] / 255) * (pSteps - 1)) * stepSize;
continue;
}
// Quantize/Posterize colors
let r = Math.round((data[i] / 255) * (pSteps - 1)) * stepSize;
let g = Math.round((data[i+1] / 255) * (pSteps - 1)) * stepSize;
let b = Math.round((data[i+2] / 255) * (pSteps - 1)) * stepSize;
// Edge Detection (Approximated Roberts Cross on Luma for comic outlines)
const right = (y * w + (x + 1)) * 4;
const down = ((y + 1) * w + x) * 4;
const luma = 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];
const lumaR = 0.299 * data[right] + 0.587 * data[right+1] + 0.114 * data[right+2];
const lumaD = 0.299 * data[down] + 0.587 * data[down+1] + 0.114 * data[down+2];
const diffX = Math.abs(luma - lumaR);
const diffY = Math.abs(luma - lumaD);
if (diffX + diffY > eThresh) {
// Apply a dark indigo/slate color for outlines typical in modern sci-fi animation
r = 25; g = 20; b = 35;
}
outData[i] = r;
outData[i+1] = g;
outData[i+2] = b;
}
}
// Draw the processed cartoon pixels back to canvas
ctx.putImageData(new ImageData(outData, width, height), 0, 0);
// 2. San Fransokyo Tech Lighting (Gradient Overlay map)
// Red (Baymax/Hiro) -> Purple/Cyan (Night San Fransokyo Neon)
const overlayGrad = ctx.createLinearGradient(0, 0, width, height);
overlayGrad.addColorStop(0, "rgba(223, 25, 32, 0.35)");
overlayGrad.addColorStop(0.5, "rgba(100, 30, 200, 0.25)");
overlayGrad.addColorStop(1, "rgba(0, 200, 255, 0.35)");
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = overlayGrad;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'source-over';
// 3. Hexagonal Grid Pattern (Microbots / Tech shield thematic)
const hexSize = Math.max(12, width * 0.015);
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.globalAlpha = 0.12;
ctx.beginPath();
const hW = Math.sqrt(3) * hexSize;
const hH = 2 * hexSize;
for (let y = -hH; y < height + hH; y += hH * 0.75) {
for (let x = -hW; x < width + hW; x += hW) {
// Offset every other row
let cx = x + (Math.round(y / (hH * 0.75)) % 2) * (hW / 2);
let cy = y;
for (let k = 0; k < 6; k++) {
let angle = (Math.PI / 180) * (60 * k - 30);
let px = cx + hexSize * Math.cos(angle);
let py = cy + hexSize * Math.sin(angle);
if (k === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.lineTo(cx + hexSize * Math.cos(-Math.PI / 6), cy + hexSize * Math.sin(-Math.PI / 6));
}
}
ctx.stroke();
ctx.globalAlpha = 1.0;
// 4. Sci-Fi HUD & Framing (Big Hero 6 AU specific decorations)
const margin = Math.max(15, width * 0.035);
ctx.strokeStyle = neonColor;
ctx.lineJoin = 'round';
ctx.lineWidth = Math.max(2, width * 0.008);
ctx.shadowColor = neonColor;
ctx.shadowBlur = 12;
// Frame corner accents
const cornerLen = Math.max(30, width * 0.08);
ctx.beginPath();
// Top Left
ctx.moveTo(margin + cornerLen, margin);
ctx.lineTo(margin, margin);
ctx.lineTo(margin, margin + cornerLen);
// Top Right
ctx.moveTo(width - margin - cornerLen, margin);
ctx.lineTo(width - margin, margin);
ctx.lineTo(width - margin, margin + cornerLen);
// Bottom Left
ctx.moveTo(margin + cornerLen, height - margin);
ctx.lineTo(margin, height - margin);
ctx.lineTo(margin, height - margin - cornerLen);
// Bottom Right
ctx.moveTo(width - margin - cornerLen, height - margin);
ctx.lineTo(width - margin, height - margin);
ctx.lineTo(width - margin, height - margin - cornerLen);
ctx.stroke();
// Disable shadow for crisp text
ctx.shadowBlur = 0;
// Futuristic display text
ctx.fillStyle = neonColor;
const fontSize = Math.max(14, width * 0.02);
ctx.font = `bold ${fontSize}px "Courier New", Courier, monospace`;
ctx.fillText("SF-SYST // ALIGN-AXIS", margin, margin + cornerLen + fontSize * 1.5);
ctx.fillText("PROTOCOL: BAYMAX_V2", margin, margin + cornerLen + fontSize * 2.8);
// Radar / Target icon in bottom right
const radarX = width - margin - (cornerLen * 0.6);
const radarY = height - margin - (cornerLen * 0.6);
const radarR = Math.max(20, width * 0.04);
ctx.strokeStyle = neonColor;
ctx.lineWidth = Math.max(1.5, width * 0.003);
// Outer circle
ctx.beginPath();
ctx.arc(radarX, radarY, radarR, 0, Math.PI * 2);
ctx.stroke();
// Inner broken circle
ctx.beginPath();
ctx.arc(radarX, radarY, radarR * 0.6, Math.PI, Math.PI * 2.5);
ctx.stroke();
// Radar sweep line
ctx.beginPath();
ctx.moveTo(radarX, radarY);
ctx.lineTo(radarX + radarR * Math.cos(-Math.PI/4), radarY + radarR * Math.sin(-Math.PI/4));
ctx.stroke();
// Subtle center crosshairs overlay
ctx.beginPath();
ctx.moveTo(width * 0.5 - 25, height * 0.5);
ctx.lineTo(width * 0.5 + 25, height * 0.5);
ctx.moveTo(width * 0.5, height * 0.5 - 25);
ctx.lineTo(width * 0.5, height * 0.5 + 25);
ctx.lineWidth = 1.5;
ctx.globalAlpha = 0.4;
ctx.stroke();
ctx.globalAlpha = 1.0;
return canvas;
}
Apply Changes