Please bookmark this page to avoid losing your image tool!

Image Graffiti Filter Effect Application

(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, edgeStrength = 1.0, posterizeLevels = 4, outlineColorStr = "0,0,0") {
    const canvas = document.createElement('canvas');
    // The { willReadFrequently: true } attribute can optimize repeated getImageData/putImageData calls.
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // Ensure originalImg is loaded and has dimensions.
    // This function expects a fully loaded image object.
    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        console.error("Image Graffiti Filter: Original image has zero dimensions. Ensure it's loaded.");
        // Return an empty canvas or a canvas indicating an error.
        // For simplicity, returning the (potentially 0x0) canvas.
        // A more robust error handling might draw an error message on a fixed-size canvas.
        return canvas;
    }

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        // This can happen due to tainted canvas (e.g., cross-origin image without CORS)
        console.error("Image Graffiti Filter: Could not get image data due to security or other error.", e);
        // Fallback: Return a canvas with an error message.
        // Clear canvas (in case drawImage succeeded but getImageData failed)
        ctx.clearRect(0, 0, canvas.width, canvas.height); 
        ctx.fillStyle = "rgba(200, 200, 200, 0.8)"; // Light gray background
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "red";
        ctx.textAlign = "center";
        ctx.font = "16px Arial";
        ctx.fillText("Error: Cannot process image.", canvas.width / 2, canvas.height / 2 - 10);
        ctx.fillText("(Possibly CORS issue)", canvas.width / 2, canvas.height / 2 + 10);
        return canvas;
    }
    
    const data = imageData.data;
    const width = canvas.width;
    const height = canvas.height;

    const outputImageData = ctx.createImageData(width, height);
    const outputData = outputImageData.data;

    // Parse and validate outline color string "r,g,b"
    let [or, og, ob] = outlineColorStr.split(',').map(s => parseInt(s.trim(), 10));
    if (isNaN(or) || or < 0 || or > 255 || 
        isNaN(og) || og < 0 || og > 255 || 
        isNaN(ob) || ob < 0 || ob > 255) {
        [or, og, ob] = [0, 0, 0]; // Default to black if parsing fails or values are out of range
    }

    // Validate and set posterizeLevels
    posterizeLevels = Math.max(2, Math.floor(posterizeLevels)); // Ensure at least 2 levels
    const posterizeFactor = 255 / (posterizeLevels - 1);

    // Create temporary arrays for intermediate pixel data (posterized colors and grayscale)
    // Using Uint8ClampedArray is memory-efficient for storing values from 0-255.
    const posterizedR = new Uint8ClampedArray(width * height);
    const posterizedG = new Uint8ClampedArray(width * height);
    const posterizedB = new Uint8ClampedArray(width * height);
    const grayscale = new Uint8ClampedArray(width * height);

    // 1. First Pass: Apply Posterization and Calculate Grayscale of the original image
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const i = (y * width + x) * 4;          // Index for the original imageData.data array (RGBA)
            const pixelArrIdx = y * width + x; // Index for our 1D single-channel arrays

            const r = data[i];
            const g = data[i + 1];
            const b = data[i + 2];
            // Alpha (data[i+3]) is handled in the final composition step

            // Posterize each color channel
            posterizedR[pixelArrIdx] = Math.round((r / 255) * (posterizeLevels - 1)) * posterizeFactor;
            posterizedG[pixelArrIdx] = Math.round((g / 255) * (posterizeLevels - 1)) * posterizeFactor;
            posterizedB[pixelArrIdx] = Math.round((b / 255) * (posterizeLevels - 1)) * posterizeFactor;
            
            // Calculate grayscale value (luminance) of the original pixel for edge detection
            grayscale[pixelArrIdx] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
        }
    }
    
    // Map edgeStrength (parameter, e.g., 0.0 to 2.0) to an edge detection threshold for Sobel.
    // A higher edgeStrength means more sensitivity to edges (resulting in a lower numerical threshold).
    const effectiveEdgeStrength = Math.min(Math.max(edgeStrength, 0), 2.5); // Clamp strength (0=no edges, 2.5=very sensitive)
    // Threshold range: high value (less sensitive) to low value (more sensitive)
    // Example: 0 -> 150, 1.0 -> 90, 2.0 -> 30, 2.5 -> 0 (all gradients are edges)
    const edgeThreshold = Math.max(0, 150 - (effectiveEdgeStrength * 60)); 
    const thresholdSq = edgeThreshold * edgeThreshold; // Compare squared magnitude to avoid Math.sqrt

    // 2. Second Pass: Edge Detection (Sobel operator on grayscale) and Final Image Composition
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const i = (y * width + x) * 4;          // Index for the outputImageData.data array (RGBA)
            const pixelArrIdx = y * width + x; // Index for our 1D single-channel arrays

            let isEdge = false;
            // Apply Sobel operator for non-border pixels to avoid out-of-bounds access
            if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {
                // Get grayscale values from the 3x3 neighborhood
                const p_tl = grayscale[(y - 1) * width + (x - 1)]; // top-left
                const p_tc = grayscale[(y - 1) * width + x    ]; // top-center
                const p_tr = grayscale[(y - 1) * width + (x + 1)]; // top-right
                const p_ml = grayscale[y       * width + (x - 1)]; // middle-left
                // current pixel: grayscale[pixelArrIdx] or grayscale[y * width + x]
                const p_mr = grayscale[y       * width + (x + 1)]; // middle-right
                const p_bl = grayscale[(y + 1) * width + (x - 1)]; // bottom-left
                const p_bc = grayscale[(y + 1) * width + x    ]; // bottom-center
                const p_br = grayscale[(y + 1) * width + (x + 1)]; // bottom-right

                // Sobel Gx (horizontal gradient)
                const Gx = (p_tr + 2 * p_mr + p_br) - (p_tl + 2 * p_ml + p_bl);
                // Sobel Gy (vertical gradient)
                const Gy = (p_bl + 2 * p_bc + p_br) - (p_tl + 2 * p_tc + p_tr);
                
                const magnitudeSq = Gx * Gx + Gy * Gy; // Squared magnitude
                if (magnitudeSq > thresholdSq) {
                    isEdge = true;
                }
            }
            // Border pixels (where x=0, x=width-1, y=0, or y=height-1) will not be marked as edges
            // by this Sobel implementation, they'll receive the posterized color.

            if (isEdge) {
                outputData[i]     = or; // Outline Red
                outputData[i + 1] = og; // Outline Green
                outputData[i + 2] = ob; // Outline Blue
                outputData[i + 3] = data[i + 3]; // Preserve original alpha
            } else {
                outputData[i]     = posterizedR[pixelArrIdx];
                outputData[i + 1] = posterizedG[pixelArrIdx];
                outputData[i + 2] = posterizedB[pixelArrIdx];
                outputData[i + 3] = data[i + 3]; // Preserve original alpha
            }
        }
    }

    // Write the processed pixel data back to the canvas
    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 Graffiti Filter Effect Application allows users to transform their images with a unique graffiti-style filter. This tool applies posterization and edge detection effects to create a visually striking output that enhances the artistic quality of the original image. Users can customize the edge strength and the color of the outlines, making it ideal for creating engaging artwork for social media, promotional materials, or personal projects. Whether for digital art, design work, or simply for fun, this tool provides a creative way to reimagine photos and graphics.

Leave a Reply

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