Please bookmark this page to avoid losing your image tool!

Image Crosshatch Filter

(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.
async function processImage(originalImg, lineColor = "black", backgroundColor = "white", lineWidth = 1, spacing = 8, segmentLength = 5, angle1 = 45, threshold1 = 170, angle2 = -45, threshold2 = 85) {

    const w = originalImg.naturalWidth;
    const h = originalImg.naturalHeight;

    // Parameter validation and type conversion
    spacing = Math.max(1, Number(spacing));
    lineWidth = Math.max(0.1, Number(lineWidth)); // Canvas lineWidth 0 can be inconsistent
    segmentLength = Math.max(1, Number(segmentLength));
    threshold1 = Math.max(0, Math.min(255, Number(threshold1)));
    threshold2 = Math.max(0, Math.min(255, Number(threshold2)));
    angle1 = Number(angle1);
    angle2 = Number(angle2);
    
    // Create a temporary canvas to get pixel data from originalImg
    // This is necessary because getImageData works on a canvas context.
    const tempCanvas = document.createElement('canvas');
    const tempCtx = tempCanvas.getContext('2d');
    tempCanvas.width = w;
    tempCanvas.height = h;

    let imageData;
    let sourcePixels;

    if (w > 0 && h > 0) {
        tempCtx.drawImage(originalImg, 0, 0, w, h);
        try {
            imageData = tempCtx.getImageData(0, 0, w, h);
            sourcePixels = imageData.data;
        } catch (e) {
            // Handle potential security errors if the image is tainted (e.g., cross-origin)
            console.error("Crosshatch filter: Could not get image data. Image might be cross-origin tainted.", e);
            
            // Fallback: return a canvas with an error message
            const errorCanvas = document.createElement('canvas');
            errorCanvas.width = Math.max(200, w); // Ensure some minimum size for message
            errorCanvas.height = Math.max(100, h);
            const errorCtx = errorCanvas.getContext('2d');
            errorCtx.fillStyle = "lightcoral";
            errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
            errorCtx.fillStyle = "black";
            errorCtx.font = "16px Arial";
            errorCtx.textAlign = "center";
            errorCtx.textBaseline = "middle";
            errorCtx.fillText("Error: Cannot process image.", errorCanvas.width/2, errorCanvas.height/2 - 10);
            errorCtx.fillText("Image may be cross-origin tainted.", errorCanvas.width/2, errorCanvas.height/2 + 10);
            return errorCanvas;
        }
    } else {
        // Handle cases where image dimensions are zero (e.g., image not loaded)
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = Math.max(1, w);
        emptyCanvas.height = Math.max(1, h);
        // optionally draw a small note or leave it blank
        return emptyCanvas;
    }


    // Create the output canvas
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = w;
    outputCanvas.height = h;
    const outputCtx = outputCanvas.getContext('2d');

    // Fill background
    outputCtx.fillStyle = backgroundColor;
    outputCtx.fillRect(0, 0, w, h);

    // Set line properties for hatching
    outputCtx.strokeStyle = lineColor;
    outputCtx.lineWidth = lineWidth;
    outputCtx.lineCap = "round"; // Makes dashed lines look a bit nicer

    // Helper function to get brightness of a pixel (0-255, 0=black, 255=white)
    function getBrightness(x, y) {
        x = Math.floor(x);
        y = Math.floor(y);

        // If point is outside canvas, treat as white (no lines drawn there)
        if (x < 0 || x >= w || y < 0 || y >= h) {
            return 255; 
        }

        const idx = (y * w + x) * 4; // Each pixel has 4 components (R,G,B,A)
        const r = sourcePixels[idx];
        const g = sourcePixels[idx + 1];
        const b = sourcePixels[idx + 2];
        
        // Standard luminance calculation
        return 0.299 * r + 0.587 * g + 0.114 * b;
    }

    // Function to draw one set of hatch lines based on brightness
    function drawHatchSet(angleDegrees, currentSpacing, brightnessThreshold) {
        const angleRad = angleDegrees * Math.PI / 180;
        const cosA = Math.cos(angleRad);
        const sinA = Math.sin(angleRad);

        // Vector defining the direction of the lines
        const lineDirX = -sinA;
        const lineDirY = cosA;

        outputCtx.beginPath(); // Start a new path for all segments in this set for efficiency

        // Determine the range of 'd' values. 'd' is the perpendicular distance from origin to a line.
        // Calculated by projecting canvas corners onto the normal vector of the lines.
        const d_projections = [
            0,                      // Projection of (0,0)
            w * cosA,               // Projection of (w,0)
            h * sinA,               // Projection of (0,h)
            w * cosA + h * sinA     // Projection of (w,h)
        ];
        const min_d_proj = Math.min(...d_projections);
        const max_d_proj = Math.max(...d_projections);
        
        // Estimate number of lines needed to cover the projection range, plus a margin
        const numLinesEstimate = Math.ceil((max_d_proj - min_d_proj) / currentSpacing) + 2;

        for (let i = -1; i < numLinesEstimate; i++) { // Iterate through conceptual parallel lines
            const d = min_d_proj + i * currentSpacing;

            // Iterate along this current conceptual line using a parameter 't'
            // The line passes through M=(d*cosA, d*sinA) and is parallel to (lineDirX, lineDirY)
            // The iteration range for 't' should cover the canvas diagonal to ensure full coverage.
            const lineIterationLength = Math.sqrt(w*w + h*h) * 1.1; // A bit more than canvas diagonal

            for (let t = -lineIterationLength / 2; t < lineIterationLength / 2; t += segmentLength) {
                // Calculate the center point (currentX, currentY) of the current potential segment
                const currentX = d * cosA + t * lineDirX;
                const currentY = d * sinA + t * lineDirY;

                const brightness = getBrightness(currentX, currentY);

                // If the brightness at this point is below the threshold (i.e., dark enough), draw a segment
                if (brightness < brightnessThreshold) {
                    const halfSeg = segmentLength / 2;
                    const startX = currentX - halfSeg * lineDirX;
                    const startY = currentY - halfSeg * lineDirY;
                    const endX = currentX + halfSeg * lineDirX;
                    const endY = currentY + halfSeg * lineDirY;
                    
                    outputCtx.moveTo(startX, startY);
                    outputCtx.lineTo(endX, endY);
                }
            }
        }
        outputCtx.stroke(); // Draw all accumulated line segments for this set
    }

    // Draw the two sets of hatch lines.
    // The first set is drawn for pixels darker than threshold1.
    drawHatchSet(angle1, spacing, threshold1);
    
    // The second set is drawn for pixels darker than threshold2.
    // Typically, threshold2 is lower (represents darker areas) than threshold1,
    // resulting in denser crosshatching in darker parts of the image.
    drawHatchSet(angle2, spacing, threshold2);
    
    return outputCanvas;
}

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 Crosshatch Filter allows users to apply a crosshatch effect to images, transforming them into artistic representations. Users can customize parameters such as line color, background color, line width, spacing, segment length, and angles for the hatch lines. This tool is particularly useful for graphic designers, artists, and anyone looking to create unique visual styles for digital images. It can be utilized for enhancing illustrations, creating textures for backgrounds, or simply for artistic experimentation with photos.

Leave a Reply

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