Please bookmark this page to avoid losing your image tool!

WW2 Era Comic Style Image Converter

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, dotSizeParam = "auto", edgeToleranceParam = "60", colorLevelsParam = "4", misregistrationParam = "auto") {
    const width = originalImg.width;
    const height = originalImg.height;

    // Create a working canvas
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');

    // Fill with white to remove transparency correctly
    ctx.fillStyle = '#FFFFFF';
    ctx.fillRect(0, 0, width, height);
    ctx.drawImage(originalImg, 0, 0);

    const srcData = ctx.getImageData(0, 0, width, height).data;
    const outImgData = ctx.createImageData(width, height);
    const outData = outImgData.data;

    // Parameter parsing and auto-scaling
    const baseScale = Math.max(width, height) / 1000;
    
    const autoDotSize = Math.max(3, Math.round(6 * baseScale));
    const dotSize = dotSizeParam === "auto" ? autoDotSize : (parseFloat(dotSizeParam) || autoDotSize);
    
    const edgeTolerance = parseFloat(edgeToleranceParam) || 60;
    const levels = Math.max(2, parseInt(colorLevelsParam) || 4);
    
    const autoMisreg = Math.max(1, Math.round(3 * baseScale));
    const misreg = misregistrationParam === "auto" ? autoMisreg : (parseFloat(misregistrationParam) || autoMisreg);

    // 1. Calculate base Luma for fast edge detection
    const luma = new Int32Array(width * height);
    for (let i = 0; i < width * height; i++) {
        luma[i] = srcData[i * 4] * 0.299 + srcData[i * 4 + 1] * 0.587 + srcData[i * 4 + 2] * 0.114;
    }

    // 2. Simple Box Blur on Luma to reduce noise before Sobel
    const blurredLuma = new Int32Array(width * height);
    for (let y = 1; y < height - 1; y++) {
        for (let x = 1; x < width - 1; x++) {
            const idx = y * width + x;
            blurredLuma[idx] = (
                luma[idx - width - 1] + luma[idx - width] + luma[idx - width + 1] +
                luma[idx - 1] + luma[idx] + luma[idx + 1] +
                luma[idx + width - 1] + luma[idx + width] + luma[idx + width + 1]
            ) / 9;
        }
    }

    // 3. Sobel Edge Detection 
    const edges = new Uint8Array(width * height);
    for (let y = 2; y < height - 2; y++) {
        for (let x = 2; x < width - 2; x++) {
            const idx = y * width + x;
            const p00 = blurredLuma[idx - width - 1]; const p01 = blurredLuma[idx - width]; const p02 = blurredLuma[idx - width + 1];
            const p10 = blurredLuma[idx - 1];                                                 const p12 = blurredLuma[idx + 1];
            const p20 = blurredLuma[idx + width - 1]; const p21 = blurredLuma[idx + width]; const p22 = blurredLuma[idx + width + 1];

            const gx = (p02 + 2 * p12 + p22) - (p00 + 2 * p10 + p20);
            const gy = (p20 + 2 * p21 + p22) - (p00 + 2 * p01 + p02);
            
            if (Math.abs(gx) + Math.abs(gy) > edgeTolerance) {
                edges[idx] = 1;
            }
        }
    }

    // 4. Dilate edges to create thick Comic-style Inks
    const thickEdges = new Uint8Array(width * height);
    for (let y = 2; y < height - 2; y++) {
        for (let x = 2; x < width - 2; x++) {
            const idx = y * width + x;
            if (edges[idx] === 1) {
                // Cross + Diagonal dilation for smooth, thick shapes
                thickEdges[idx] = 1;
                thickEdges[idx - 1] = 1;
                thickEdges[idx + 1] = 1;
                thickEdges[idx - width] = 1;
                thickEdges[idx + width] = 1;
                thickEdges[idx - width - 1] = 1;
                thickEdges[idx - width + 1] = 1;
                thickEdges[idx + width - 1] = 1;
                thickEdges[idx + width + 1] = 1;
            }
        }
    }

    // 5. CMYK Halftone Rendering
    const freq = Math.PI * 2 / dotSize;
    // Rotation angles for halftone screens
    const rC = 15 * Math.PI / 180, cosC = Math.cos(rC), sinC = Math.sin(rC);
    const rM = 75 * Math.PI / 180, cosM = Math.cos(rM), sinM = Math.sin(rM);
    const rY = 0 * Math.PI / 180,  cosY = Math.cos(rY), sinY = Math.sin(rY);
    const rK = 45 * Math.PI / 180, cosK = Math.cos(rK), sinK = Math.sin(rK);

    // WW2 Era Vintage Ink Palette (Subdued)
    const pPaper = [240, 235, 215];
    const iCyan  = [0, 140, 200];
    const iMag   = [200, 60, 100];
    const iYel   = [230, 200, 30];
    const iBlack = [30, 25, 30];

    // Offsets to simulate misregistration during printing
    const cX = misreg, cY = misreg;
    const mX = -misreg, mY = misreg;
    const yX = misreg, yY = -misreg;

    function getPosterizedColor(ox, oy, baseX, baseY) {
        let cx = baseX + ox;
        let cy = baseY + oy;
        cx = Math.max(0, Math.min(width - 1, cx));
        cy = Math.max(0, Math.min(height - 1, cy));

        const idx = (cy * width + cx) * 4;
        let cR = srcData[idx];
        let cG = srcData[idx + 1];
        let cB = srcData[idx + 2];

        // Contrast boost to make primary colors pop more
        cR = Math.min(255, Math.max(0, (cR - 128) * 1.3 + 128));
        cG = Math.min(255, Math.max(0, (cG - 128) * 1.3 + 128));
        cB = Math.min(255, Math.max(0, (cB - 128) * 1.3 + 128));

        // Quantization (Posterization)
        const step = 255 / (levels - 1);
        cR = Math.round(cR / step) * step;
        cG = Math.round(cG / step) * step;
        cB = Math.round(cB / step) * step;

        return [cR, cG, cB];
    }

    const hw = width / 2;
    const hh = height / 2;
    const maxDistSq = hw * hw + hh * hh;

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const idx = y * width + x;
            const outIdx = idx * 4;

            // Apply vignette and film grain
            const distSq = (x - hw) * (x - hw) + (y - hh) * (y - hh);
            const vignette = 1.0 - (distSq / maxDistSq) * 0.35;
            const noise = (Math.random() * 0.08) + 0.96;

            if (thickEdges[idx]) {
                outData[outIdx]     = iBlack[0] * vignette * noise;
                outData[outIdx + 1] = iBlack[1] * vignette * noise;
                outData[outIdx + 2] = iBlack[2] * vignette * noise;
                outData[outIdx + 3] = 255;
                continue;
            }

            // Extract channels from misregistered locations
            const colC = getPosterizedColor(cX, cY, x, y);
            const valC = 1 - colC[0] / 255;

            const colM = getPosterizedColor(mX, mY, x, y);
            const valM = 1 - colM[1] / 255;

            const colY = getPosterizedColor(yX, yY, x, y);
            const valY = 1 - colY[2] / 255;

            const colK = getPosterizedColor(0, 0, x, y);
            const kBase = Math.min(1 - colK[0] / 255, 1 - colK[1] / 255, 1 - colK[2] / 255);
            const kInk = kBase * 0.8; // 80% Gray Component Replacement

            // Adjusted CMY
            const adjC = Math.max(0, valC - kInk);
            const adjM = Math.max(0, valM - kInk);
            const adjY = Math.max(0, valY - kInk);

            // Sine wave threshold halftoning
            const tc = (Math.sin((x * cosC - y * sinC) * freq) + Math.sin((x * sinC + y * cosC) * freq)) / 4 + 0.5;
            const tm = (Math.sin((x * cosM - y * sinM) * freq) + Math.sin((x * sinM + y * cosM) * freq)) / 4 + 0.5;
            const ty = (Math.sin((x * cosY - y * sinY) * freq) + Math.sin((x * sinY + y * cosY) * freq)) / 4 + 0.5;
            const tk = (Math.sin((x * cosK - y * sinK) * freq) + Math.sin((x * sinK + y * cosK) * freq)) / 4 + 0.5;

            let resR = pPaper[0], resG = pPaper[1], resB = pPaper[2];

            // Subtractive synthesis
            if (adjC > tc) { resR = resR * iCyan[0] / 255;  resG = resG * iCyan[1] / 255;  resB = resB * iCyan[2] / 255; }
            if (adjM > tm) { resR = resR * iMag[0] / 255;   resG = resG * iMag[1] / 255;   resB = resB * iMag[2] / 255; }
            if (adjY > ty) { resR = resR * iYel[0] / 255;   resG = resG * iYel[1] / 255;   resB = resB * iYel[2] / 255; }
            if (kInk > tk) { resR = resR * iBlack[0] / 255; resG = resG * iBlack[1] / 255; resB = resB * iBlack[2] / 255; }

            outData[outIdx]     = Math.min(255, Math.max(0, resR * vignette * noise));
            outData[outIdx + 1] = Math.min(255, Math.max(0, resG * vignette * noise));
            outData[outIdx + 2] = Math.min(255, Math.max(0, resB * vignette * noise));
            outData[outIdx + 3] = 255;
        }
    }

    ctx.putImageData(outImgData, 0, 0);
    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

This tool transforms your photos into vintage, World War II-era comic book illustrations. It uses advanced image processing to apply thick ink outlines, CMYK halftone dot patterns, and a subdued color palette to recreate the aesthetic of mid-20th-century print media. It even simulates authentic printing imperfections, such as color misregistration, film grain, and a subtle vignette. This is an ideal tool for artists, graphic designers, or anyone looking to give their images a nostalgic, retro comic book feel for social media, posters, or creative projects.

Leave a Reply

Your email address will not be published. Required fields are marked *