Please bookmark this page to avoid losing your image tool!

Image To Line Art 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, edgeThreshold = 50, invertOutput = "false") {
    const canvas = document.createElement('canvas');
    // Use { willReadFrequently: true } for potential performance improvement if available
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    const width = originalImg.naturalWidth || originalImg.width;
    const height = originalImg.naturalHeight || originalImg.height;

    if (width === 0 || height === 0) {
        // Handle case where image might not be loaded or is empty
        // Create a minimal canvas to avoid errors downstream
        canvas.width = 1; 
        canvas.height = 1;
        console.warn("Image To Line Art Converter: Input image has zero width or height.");
        // Optionally, draw a small indicator or leave blank
        ctx.fillStyle = "gray";
        ctx.fillRect(0,0,1,1);
        return canvas;
    }

    canvas.width = width;
    canvas.height = height;

    try {
        ctx.drawImage(originalImg, 0, 0, width, height);
    } catch (e) {
        // This can happen for various reasons, including if originalImg isn't a valid image source
        console.error("Image To Line Art Converter: Could not draw image.", e);
        ctx.fillStyle = "lightgray";
        ctx.fillRect(0, 0, width, height);
        ctx.fillStyle = "red";
        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("Error drawing input image", width / 2, height / 2);
        return canvas;
    }
    
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, width, height);
    } catch (e) {
        // This can happen if the image is from a different origin (tainted canvas)
        console.error("Image To Line Art Converter: Could not get image data. Canvas may be tainted by cross-origin data.", e);
        // Draw an error message on the canvas
        ctx.fillStyle = "lightgray"; // Clear previous content
        ctx.fillRect(0, 0, width, height);
        ctx.fillStyle = "red";
        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("Error: Cannot process cross-origin image", width / 2, height / 2);
        return canvas;
    }
    
    const data = imageData.data;

    // 1. Grayscale conversion
    const grayValues = new Uint8Array(width * height); // 1 byte per pixel for grayscale value
    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        // Using luminance formula (Rec. 709): L = 0.2126*R + 0.7152*G + 0.0722*B
        grayValues[i / 4] = 0.2126 * r + 0.7152 * g + 0.0722 * b;
    }

    // 2. Sobel Edge Detection
    // Sobel kernels
    const Gx = [
        -1, 0, 1,
        -2, 0, 2,
        -1, 0, 1
    ];
    const Gy = [
        -1, -2, -1,
         0,  0,  0,
         1,  2,  1
    ];

    const edgeData = new Uint8ClampedArray(data.length); // Output data for the new image
    const gradientMagnitudes = new Float32Array(width * height); // Stores magnitude for each pixel

    // Iterate through each pixel (excluding 1-pixel border for Sobel operator)
    for (let y = 1; y < height - 1; y++) {
        for (let x = 1; x < width - 1; x++) {
            let sumX = 0;
            let sumY = 0;
            let kernelIndex = 0;

            // Apply 3x3 Sobel kernel
            for (let ky = -1; ky <= 1; ky++) { // Kernel y-offset
                for (let kx = -1; kx <= 1; kx++) { // Kernel x-offset
                    // Calculate index for the grayValues array (1 value per pixel)
                    const pixelIndex = (y + ky) * width + (x + kx);
                    const grayVal = grayValues[pixelIndex];

                    sumX += grayVal * Gx[kernelIndex];
                    sumY += grayVal * Gy[kernelIndex];
                    kernelIndex++;
                }
            }
            // Calculate gradient magnitude: sqrt(Gx^2 + Gy^2)
            const magnitude = Math.sqrt(sumX * sumX + sumY * sumY);
            gradientMagnitudes[y * width + x] = magnitude;
        }
    }

    // 3. Thresholding and constructing the output image
    let thresholdValue = Number(edgeThreshold);
    if (isNaN(thresholdValue) || thresholdValue < 0) { 
        // Default threshold if parsing failed or value is invalid
        thresholdValue = 50; 
    }

    // Determine if colors should be inverted (e.g., white lines on black vs. black lines on white)
    const shouldInvert = String(invertOutput).toLowerCase() === "true";

    const lineColor = shouldInvert ? 255 : 0; // e.g., white if inverted, black otherwise
    const bgColor = shouldInvert ? 0 : 255;   // e.g., black if inverted, white otherwise

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const outputPixelIndex = (y * width + x) * 4;    // Index for edgeData (RGBA)
            const magnitudeIndex = y * width + x;          // Index for gradientMagnitudes

            // For border pixels (where Sobel wasn't applied), magnitude is 0.
            // Using || 0 ensures that if gradientMagnitudes[magnitudeIndex] is undefined/NaN somehow, it defaults to 0.
            const magnitude = gradientMagnitudes[magnitudeIndex] || 0; 

            if (magnitude > thresholdValue) {
                edgeData[outputPixelIndex]     = lineColor;
                edgeData[outputPixelIndex + 1] = lineColor;
                edgeData[outputPixelIndex + 2] = lineColor;
            } else {
                edgeData[outputPixelIndex]     = bgColor;
                edgeData[outputPixelIndex + 1] = bgColor;
                edgeData[outputPixelIndex + 2] = bgColor;
            }
            edgeData[outputPixelIndex + 3] = 255; // Full alpha (opaque)
        }
    }

    // Create new ImageData object from the processed pixel data
    const newImageData = new ImageData(edgeData, width, height);
    // Put the new image data back onto the canvas
    ctx.putImageData(newImageData, 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

The Image To Line Art Converter is a tool that transforms your images into line art representations. By applying edge detection techniques, it extracts outlines and contours from the images, allowing for the creation of stylized images that focus on structure rather than detail. This tool can be particularly useful for artists and designers looking to create illustrations, simplifies complex images for coloring books, or generates graphics for logo design and print applications. Users can adjust the edge detection threshold and invert colors for flexibility in the output style.

Leave a Reply

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