Please bookmark this page to avoid losing your image tool!

Image To Graphic Novel 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.
/**
 * Converts an image into a graphic novel style by applying posterization,
 * halftone shading, and edge detection outlines. This function combines
 * several image processing techniques to mimic the look of comic book art.
 *
 * @param {HTMLImageElement} originalImg The original image element to process.
 * @param {number} [posterizationLevels=5] The number of color levels per channel for posterization (e.g., 2-16). Lower values create a flatter, more stylized look.
 * @param {number} [edgeThreshold=80] The sensitivity for edge detection which creates the "ink" outlines (e.g., 50-150). A lower value detects more lines.
 * @param {number} [halftoneSize=4] The size of the grid for the halftone dot effect. Larger values create bigger dots. Set to 0 to disable this effect.
 * @param {number} [halftoneOpacity=0.2] The opacity of the halftone shading layer (from 0.0 to 1.0).
 * @returns {HTMLCanvasElement} A new canvas element with the graphic novel effect applied.
 */
function processImage(originalImg, posterizationLevels = 5, edgeThreshold = 80, halftoneSize = 4, halftoneOpacity = 0.2) {
    const width = originalImg.width;
    const height = originalImg.height;

    // Create the final canvas that will be returned
    const finalCanvas = document.createElement('canvas');
    finalCanvas.width = width;
    finalCanvas.height = height;
    const finalCtx = finalCanvas.getContext('2d');

    // Create a temporary canvas to get the original image data and for intermediate processing
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = width;
    tempCanvas.height = height;
    const tempCtx = tempCanvas.getContext('2d');
    tempCtx.drawImage(originalImg, 0, 0);
    const originalImageData = tempCtx.getImageData(0, 0, width, height);
    const originalData = originalImageData.data;

    // --- STEP 1: Apply Posterization for the color base layer ---
    const posterizedData = new Uint8ClampedArray(originalData.length);
    const levels = Math.max(2, Math.floor(posterizationLevels)); // Ensure integer levels >= 2
    const step = 255 / (levels - 1);
    for (let i = 0; i < originalData.length; i += 4) {
        posterizedData[i] = Math.round(originalData[i] / step) * step; // R
        posterizedData[i + 1] = Math.round(originalData[i + 1] / step) * step; // G
        posterizedData[i + 2] = Math.round(originalData[i + 2] / step) * step; // B
        posterizedData[i + 3] = originalData[i + 3]; // A
    }
    const posterizedImageData = new ImageData(posterizedData, width, height);

    // Draw the posterized base onto the final canvas
    tempCtx.putImageData(posterizedImageData, 0, 0);
    finalCtx.drawImage(tempCanvas, 0, 0);

    // --- Prepare Grayscale Data (needed for both halftone and outlines) ---
    const grayscaleData = new Uint8ClampedArray(width * height);
    for (let i = 0, j = 0; i < originalData.length; i += 4, j++) {
        const r = originalData[i];
        const g = originalData[i + 1];
        const b = originalData[i + 2];
        grayscaleData[j] = 0.299 * r + 0.587 * g + 0.114 * b; // Luminosity formula
    }

    // --- STEP 2: Apply Halftone Shading Layer ---
    if (halftoneSize > 0) {
        const halftoneCanvas = document.createElement('canvas');
        halftoneCanvas.width = width;
        halftoneCanvas.height = height;
        const halftoneCtx = halftoneCanvas.getContext('2d');
        halftoneCtx.fillStyle = 'black';

        for (let y = 0; y < height; y += halftoneSize) {
            for (let x = 0; x < width; x += halftoneSize) {
                let totalBrightness = 0;
                let count = 0;
                // Calculate the average brightness of the grid cell
                for (let j = 0; j < halftoneSize; j++) {
                    for (let i = 0; i < halftoneSize; i++) {
                        const pixelX = x + i;
                        const pixelY = y + j;
                        if (pixelX < width && pixelY < height) {
                            totalBrightness += grayscaleData[pixelY * width + pixelX];
                            count++;
                        }
                    }
                }
                const avgBrightness = totalBrightness / count;
                // The radius is inversely proportional to brightness (darker = bigger dot)
                const radius = (1 - avgBrightness / 255) * (halftoneSize / 2);

                if (radius > 0.1) { // Only draw if the dot is somewhat visible
                    halftoneCtx.beginPath();
                    halftoneCtx.arc(x + halftoneSize / 2, y + halftoneSize / 2, radius, 0, Math.PI * 2);
                    halftoneCtx.fill();
                }
            }
        }
        // Composite the halftone layer onto the final canvas using a 'multiply' blend mode
        finalCtx.globalAlpha = Math.max(0, Math.min(1, halftoneOpacity));
        finalCtx.globalCompositeOperation = 'multiply';
        finalCtx.drawImage(halftoneCanvas, 0, 0);
        finalCtx.globalAlpha = 1.0; // Reset alpha
        finalCtx.globalCompositeOperation = 'source-over'; // Reset blend mode
    }

    // --- STEP 3: Apply "Ink" Outline Layer using Sobel Edge Detection ---
    const outlineCanvas = document.createElement('canvas');
    outlineCanvas.width = width;
    outlineCanvas.height = height;
    const outlineCtx = outlineCanvas.getContext('2d');
    const outlineImageData = outlineCtx.createImageData(width, height);
    const outlineData = outlineImageData.data;

    const Gx = [
        [-1, 0, 1],
        [-2, 0, 2],
        [-1, 0, 1]
    ];
    const Gy = [
        [1, 2, 1],
        [0, 0, 0],
        [-1, -2, -1]
    ];

    for (let y = 1; y < height - 1; y++) {
        for (let x = 1; x < width - 1; x++) {
            let sumX = 0;
            let sumY = 0;

            // Apply kernels to 3x3 neighborhood
            for (let j = -1; j <= 1; j++) {
                for (let i = -1; i <= 1; i++) {
                    const grayVal = grayscaleData[(y + j) * width + (x + i)];
                    sumX += grayVal * Gx[j + 1][i + 1];
                    sumY += grayVal * Gy[j + 1][i + 1];
                }
            }

            const magnitude = Math.sqrt(sumX * sumX + sumY * sumY);
            const index = (y * width + x) * 4;

            if (magnitude > edgeThreshold) {
                // This is an edge, draw it in black
                outlineData[index] = 0;
                outlineData[index + 1] = 0;
                outlineData[index + 2] = 0;
                outlineData[index + 3] = 255;
            } else {
                // Not an edge, make it fully transparent
                outlineData[index + 3] = 0;
            }
        }
    }

    // Draw the final outline layer on top of everything
    outlineCtx.putImageData(outlineImageData, 0, 0);
    finalCtx.drawImage(outlineCanvas, 0, 0);

    return finalCanvas;
}

Free Image Tool Creator

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

Description

The Image To Graphic Novel Converter transforms standard images into a graphic novel or comic book style. By applying techniques such as posterization, edge detection, and halftone shading, users can create stylized graphics that mimic comic art. This tool is ideal for artists, designers, and enthusiasts looking to produce unique visuals for storytelling, illustration, or creative projects. Its adjustable parameters allow users to customize the level of posterization, edge detection sensitivity, and halftone effects to achieve the desired artistic style.

Leave a Reply

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