Please bookmark this page to avoid losing your image tool!

Image Concertina Wire Filter Effect 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.
async function processImage(originalImg, wireColorStr = "200,200,200", bgColorStr = "30,30,30", edgeThreshold = 50, wireThickness = 1, invertColorsStr = "false") {
    
    // Helper to parse "R,G,B" string into an object {r, g, b}
    function parseColor(colorStr) {
        const str = String(colorStr); // Ensure it's a string
        const parts = str.split(',').map(s => parseInt(s.trim(), 10));
        return {
            r: (parts.length > 0 && !isNaN(parts[0])) ? Math.max(0, Math.min(255, parts[0])) : 0,
            g: (parts.length > 1 && !isNaN(parts[1])) ? Math.max(0, Math.min(255, parts[1])) : 0,
            b: (parts.length > 2 && !isNaN(parts[2])) ? Math.max(0, Math.min(255, parts[2])) : 0
        };
    }

    // --- Parameter Sanity Checks and Parsing ---
    const finalWireColorObj = parseColor(wireColorStr);
    const finalBgColorObj = parseColor(bgColorStr);
    const invert = String(invertColorsStr).toLowerCase() === "true";

    const wColor = invert ? finalBgColorObj : finalWireColorObj;
    const bColor = invert ? finalWireColorObj : finalBgColorObj;

    let numWireThickness = (typeof wireThickness === 'string') ? parseInt(wireThickness, 10) : wireThickness;
    if (isNaN(numWireThickness) || numWireThickness < 1) {
        numWireThickness = 1; // Default to 1 if invalid or less than 1
    }
    const T_thickness = numWireThickness;

    let numEdgeThreshold = (typeof edgeThreshold === 'string') ? parseInt(edgeThreshold, 10) : edgeThreshold;
    if (isNaN(numEdgeThreshold)) {
        numEdgeThreshold = 50; // Default if invalid
    }
    // Clamp threshold to a practical range for normalized gradients (0-255)
    const clampedEdgeThreshold = Math.max(0, Math.min(255, numEdgeThreshold));


    // --- Canvas Setup ---
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

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

    if (width === 0 || height === 0) {
        console.error("Image has zero dimensions. Returning a minimal canvas.");
        canvas.width = 1; 
        canvas.height = 1;
        // Optionally fill with a default color or leave blank
        const tempCtx = canvas.getContext('2d');
        if (tempCtx) {
            tempCtx.fillStyle = `rgb(${bColor.r},${bColor.g},${bColor.b})`;
            tempCtx.fillRect(0,0,1,1);
        }
        return canvas;
    }
    canvas.width = width;
    canvas.height = height;

    ctx.drawImage(originalImg, 0, 0, width, height);
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;

    // --- Image Processing ---
    const grayscaleData = new Uint8ClampedArray(width * height);
    const gradientData = new Float32Array(width * height); // Use Float32Array for precision

    // 1. Grayscale Conversion
    for (let i = 0, j = 0; i < data.length; i += 4, j++) {
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        grayscaleData[j] = 0.299 * r + 0.587 * g + 0.114 * b;
    }

    // 2. Sobel Edge Detection
    const Gx_kernel = [
        [-1, 0, 1],
        [-2, 0, 2],
        [-1, 0, 1]
    ];
    const Gy_kernel = [
        [-1, -2, -1],
        [ 0,  0,  0],
        [ 1,  2,  1]
    ];

    let maxGradient = 0;

    for (let y = 1; y < height - 1; y++) { // Iterate skipping border pixels
        for (let x = 1; x < width - 1; x++) {
            let sumX = 0;
            let sumY = 0;
            for (let ky = -1; ky <= 1; ky++) {
                for (let kx = -1; kx <= 1; kx++) {
                    const pixelIndex = (y + ky) * width + (x + kx);
                    const grayVal = grayscaleData[pixelIndex];
                    sumX += grayVal * Gx_kernel[ky + 1][kx + 1];
                    sumY += grayVal * Gy_kernel[ky + 1][kx + 1];
                }
            }
            const magnitude = Math.sqrt(sumX * sumX + sumY * sumY);
            gradientData[y * width + x] = magnitude;
            if (magnitude > maxGradient) {
                maxGradient = magnitude;
            }
        }
    }

    // Normalize gradient data to 0-255 range
    const normalizedGradientData = new Uint8ClampedArray(width * height);
    if (maxGradient > 0) { // Avoid division by zero for flat images
        for (let i = 0; i < gradientData.length; i++) {
            // Border pixels (not processed by Sobel loop) will have gradientData[i] = 0
            // as Float32Array is zero-initialized.
            normalizedGradientData[i] = (gradientData[i] / maxGradient) * 255;
        }
    }
    
    // 3. Render Output Image
    const outputImageData = ctx.createImageData(width, height);
    const outputData = outputImageData.data;

    // Initialize output with background color
    for (let i = 0; i < outputData.length; i += 4) {
        outputData[i]     = bColor.r;
        outputData[i + 1] = bColor.g;
        outputData[i + 2] = bColor.b;
        outputData[i + 3] = 255; // Alpha
    }
    
    // Calculate offsets for thickening the wire
    // For a thickness T, this creates a T x T square centered at (x,y)
    const iterStartOffset = -Math.floor(T_thickness / 2);
    const iterEndOffset   =  Math.floor((T_thickness - 1) / 2);

    // Draw wires based on thresholded gradients and thickness
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const gradValue = normalizedGradientData[y * width + x]; // Border pixels will have gradValue = 0

            if (gradValue > clampedEdgeThreshold) {
                // This pixel (x,y) is an edge center. Draw a block for thickness.
                for (let dy = iterStartOffset; dy <= iterEndOffset; dy++) {
                    for (let dx = iterStartOffset; dx <= iterEndOffset; dx++) {
                        const currentY = y + dy;
                        const currentX = x + dx;

                        // Check bounds for the thickened pixel
                        if (currentY >= 0 && currentY < height && currentX >= 0 && currentX < width) {
                            const targetIdx = (currentY * width + currentX) * 4;
                            outputData[targetIdx]     = wColor.r;
                            outputData[targetIdx + 1] = wColor.g;
                            outputData[targetIdx + 2] = wColor.b;
                            outputData[targetIdx + 3] = 255; // Alpha
                        }
                    }
                }
            }
        }
    }

    ctx.putImageData(outputImageData, 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 Concertina Wire Filter Effect Tool allows users to transform images by applying a concertina wire effect. This tool features customizable options such as wire color, background color, edge detection threshold, and wire thickness. It is suitable for creating artistic interpretations of images, enhancing visual effects for graphic design projects, or simply for fun graphic transformations. Ideal use cases include editing images for creative presentations, digital art projects, and social media content creation.

Leave a Reply

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