Please bookmark this page to avoid losing your image tool!

Image Engraving Filter Tool

(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, lineSpacing = 3, lineThickness = 1, brightnessThreshold = 128, lineColor = "black", backgroundColor = "white") {
    const width = originalImg.width;
    const height = originalImg.height;

    const canvas = document.createElement('canvas');
    // Initialize canvas with 0x0 dimensions if original image has no dimensions
    // This prevents errors if width/height are undefined or non-numeric.
    canvas.width = Number(width) || 0;
    canvas.height = Number(height) || 0;
    const ctx = canvas.getContext('2d');

    // If image dimensions are invalid or zero, return an empty (possibly 0x0) canvas.
    if (canvas.width === 0 || canvas.height === 0) {
        return canvas;
    }
    
    // Draw background
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Temporary canvas for source image pixel data
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = canvas.width;
    tempCanvas.height = canvas.height;
    const tempCtx = tempCanvas.getContext('2d', { 
        willReadFrequently: true,
        // Desynchronized can sometimes help with performance by reducing blocking on the main thread,
        // but might not be universally supported or beneficial. Standard is false.
        // desynchronized: true 
    }); 
    
    tempCtx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    let imageData;
    try {
        imageData = tempCtx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        console.error("Could not get image data. This might be due to CORS policy if the image is from another domain and the canvas becomes tainted. Error:", e);
        // Draw an informative error message on the output canvas
        ctx.fillStyle = "rgba(200,0,0,0.8)"; // Error indication background
        ctx.fillRect(0,0,canvas.width,canvas.height);
        
        ctx.font = `bold ${Math.min(24, Math.max(10, canvas.width / 20))}px sans-serif`;
        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        
        const messages = ["Error processing image.", "(Possibly CORS or security restriction)"];
        const lineHeight = parseInt(ctx.font, 10) * 1.2; // Approximate line height

        ctx.fillText(messages[0], canvas.width/2, canvas.height/2 - lineHeight/2);
        ctx.font = `${Math.min(16, Math.max(8, canvas.width / 25))}px sans-serif`;
        ctx.fillText(messages[1], canvas.width/2, canvas.height/2 + lineHeight/2);
        return canvas;
    }
    const pixels = imageData.data;

    // Sanitize parameters to ensure they are within reasonable bounds
    lineSpacing = Math.max(1, Math.floor(Number(lineSpacing) || 3));
    lineThickness = Math.max(1, Math.floor(Number(lineThickness) || 1));
    brightnessThreshold = Math.max(0, Math.min(255, Number(brightnessThreshold) || 128));

    ctx.strokeStyle = String(lineColor) || "black";
    ctx.lineWidth = lineThickness;
    // 'round' line caps make line ends softer, which can look more natural for an engraving effect.
    // Other options: 'butt' (default, sharp square ends), 'square' (adds a square cap).
    ctx.lineCap = 'round'; 

    // Determine the starting y-coordinate for drawing lines.
    // This centers the sampling band for the line's perceived thickness around its drawn y-coordinate.
    const initialY = Math.max(0, Math.floor((lineThickness - 1) / 2));

    for (let y = initialY; y < canvas.height; y += lineSpacing) {
        // Small optimization: if y has already passed canvas height due to large lineSpacing.
        if (y >= canvas.height && lineSpacing > 0) break; 

        let segmentStartX = -1; // Tracks the beginning x-coordinate of a continuous dark line segment

        for (let x = 0; x < canvas.width; x++) {
            let graySum = 0;
            let count = 0; // Number of pixels sampled for averaging
            
            // Define the vertical band of pixels to sample for average brightness.
            // This band is centered at the current line's y-coordinate and has a height of `lineThickness`.
            const halfEffectiveThickness = (lineThickness - 1) / 2;
            const y_scan_start = Math.max(0, Math.round(y - halfEffectiveThickness));
            const y_scan_end = Math.min(canvas.height - 1, Math.round(y + halfEffectiveThickness));

            for (let current_scan_y = y_scan_start; current_scan_y <= y_scan_end; current_scan_y++) {
                // Calculate the index for the red component of the pixel (current_scan_y, x)
                const R_idx = (current_scan_y * canvas.width + x) * 4;
                
                const r_val = pixels[R_idx];
                const g_val = pixels[R_idx + 1];
                const b_val = pixels[R_idx + 2];
                
                // Standard luminance calculation (weighted average for grayscale)
                graySum += (0.299 * r_val + 0.587 * g_val + 0.114 * b_val);
                count++;
            }
            
            const avgGray = (count > 0) ? (graySum / count) : 255; // Default to white if no samples (should not happen in normal flow)

            if (avgGray < brightnessThreshold) {
                // Current area is dark enough to draw a line segment
                if (segmentStartX === -1) {
                    segmentStartX = x; // Start a new segment at this x-coordinate
                }
                // If this is the last pixel in the row and a segment is active, draw it
                if (x === canvas.width - 1 && segmentStartX !== -1) {
                    ctx.beginPath();
                    ctx.moveTo(segmentStartX, y); 
                    ctx.lineTo(x, y); // Draw to the current x (which is the end of the row)
                    ctx.stroke();
                    // segmentStartX will be reset at the start of the next row or if a light pixel is encountered
                }
            } else {
                // Current area is too light; if a segment was active, end and draw it
                if (segmentStartX !== -1) {
                    ctx.beginPath();
                    ctx.moveTo(segmentStartX, y);
                    // Draw the line up to the pixel *before* the current light one (x-1)
                    ctx.lineTo(x - 1, y); 
                    ctx.stroke();
                    segmentStartX = -1; // Reset segment tracking
                }
            }
        }
    }
    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 Engraving Filter Tool transforms ordinary images into artistic engravings by applying a filter that simulates engraved lines. Users can adjust parameters such as line spacing, thickness, brightness threshold, line color, and background color to customize the engraving effect. This tool is ideal for artists, graphic designers, or anyone looking to create unique visual designs for prints, posters, or digital art while giving a classic engraved look to their images.

Leave a Reply

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