Please bookmark this page to avoid losing your image tool!

Image Crumpled Paper 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.
function processImage(originalImg, displacementScale = 10, lightingIntensity = 0.3, baseFeatureSize = 64, blurRadius = 4) {
    const w = originalImg.naturalWidth || originalImg.width;
    const h = originalImg.naturalHeight || originalImg.height;

    const canvas = document.createElement('canvas');
    canvas.width = w;
    canvas.height = h;
    const ctx = canvas.getContext('2d');

    // 1. Draw original image to a temporary canvas to get its pixel data
    // This is done because we need the original pixel data for displacement lookup.
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = w;
    tempCanvas.height = h;
    const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true }); // Hint for optimization
    tempCtx.drawImage(originalImg, 0, 0, w, h);
    const originalImageData = tempCtx.getImageData(0, 0, w, h);
    const originalPixels = originalImageData.data;

    // 2. Generate Displacement/Height Map (Noise Map)
    // This map determines how pixels are shifted (displacement) and how light reflects (height).
    const noiseCanvas = document.createElement('canvas');
    noiseCanvas.width = w;
    noiseCanvas.height = h;
    // Hint for optimization as getImageData will be called.
    const noiseCtx = noiseCanvas.getContext('2d', { willReadFrequently: true }); 

    // Create a small canvas for low-resolution random noise.
    // baseFeatureSize determines the "frequency" of the noise.
    const smallNoiseW = Math.max(1, Math.ceil(w / baseFeatureSize));
    const smallNoiseH = Math.max(1, Math.ceil(h / baseFeatureSize));
    
    const smallNoiseCanvas = document.createElement('canvas');
    smallNoiseCanvas.width = smallNoiseW;
    smallNoiseCanvas.height = smallNoiseH;
    // Hint for optimization as getImageData will be called.
    const smallNoiseCtx = smallNoiseCanvas.getContext('2d', { willReadFrequently: true });
    const smallImageData = smallNoiseCtx.createImageData(smallNoiseW, smallNoiseH);
    const smallNoisePixels = smallImageData.data;

    // Fill the small canvas with random grayscale values.
    for (let i = 0; i < smallNoisePixels.length; i += 4) {
        const value = Math.random() * 255;
        smallNoisePixels[i] = value;     // Red channel
        smallNoisePixels[i + 1] = value; // Green channel
        smallNoisePixels[i + 2] = value; // Blue channel
        smallNoisePixels[i + 3] = 255;   // Alpha channel (opaque)
    }
    smallNoiseCtx.putImageData(smallImageData, 0, 0);

    // Scale up the low-resolution noise to the full image size.
    // The browser's image smoothing will interpolate, creating a smoother noise pattern.
    noiseCtx.imageSmoothingEnabled = true; 
    noiseCtx.drawImage(smallNoiseCanvas, 0, 0, smallNoiseW, smallNoiseH, 0, 0, w, h);

    // Blur the noise map to make the crumples smoother.
    // blurRadius controls the smoothness.
    if (blurRadius > 0) {
        noiseCtx.filter = `blur(${blurRadius}px)`;
        // Draw the canvas onto itself to apply the filter.
        noiseCtx.drawImage(noiseCanvas, 0, 0, w, h, 0, 0, w, h); 
        noiseCtx.filter = 'none'; // Reset filter for subsequent operations.
    }
    
    const noiseMapImageData = noiseCtx.getImageData(0, 0, w, h);
    const noiseMapPixels = noiseMapImageData.data;

    // Helper function to get a normalized noise value (0-1) from the noise map.
    // It handles boundary clamping.
    function getNoiseValue(x, y, mapPixels, mapW, mapH) {
        // Round coordinates to nearest integer.
        x = Math.round(x);
        y = Math.round(y);
        // Clamp coordinates to be within map boundaries.
        x = Math.max(0, Math.min(mapW - 1, x));
        y = Math.max(0, Math.min(mapH - 1, y));
        // Return the Red channel value (noise is grayscale) normalized to 0-1.
        return mapPixels[(y * mapW + x) * 4] / 255.0;
    }

    // 3. Apply Displacement and Lighting to each pixel of the output image.
    const outputImageData = ctx.createImageData(w, h);
    const outputPixels = outputImageData.data;

    for (let y = 0; y < h; y++) {
        for (let x = 0; x < w; x++) {
            const targetIdx = (y * w + x) * 4; // Index for the current pixel in outputImageData.

            // Get the height value from the noise map for the current pixel.
            const h_xy = getNoiseValue(x, y, noiseMapPixels, w, h);

            // Calculate the gradient of the noise map at (x,y).
            // This gradient determines the direction and steepness of the "slope".
            const h_x_plus_1 = getNoiseValue(x + 1, y, noiseMapPixels, w, h);
            const h_x_minus_1 = getNoiseValue(x - 1, y, noiseMapPixels, w, h);
            const h_y_plus_1 = getNoiseValue(x, y + 1, noiseMapPixels, w, h);
            const h_y_minus_1 = getNoiseValue(x, y - 1, noiseMapPixels, w, h);

            let gradX, gradY;
            // Use central differences for gradient calculation where possible,
            // and one-sided differences at boundaries.
            if (x === 0) gradX = (h_x_plus_1 - h_xy);
            else if (x === w - 1) gradX = (h_xy - h_x_minus_1);
            else gradX = (h_x_plus_1 - h_x_minus_1) / 2.0;

            if (y === 0) gradY = (h_y_plus_1 - h_xy);
            else if (y === h - 1) gradY = (h_xy - h_y_minus_1);
            else gradY = (h_y_plus_1 - h_y_minus_1) / 2.0;
            
            // Calculate displacement amounts based on gradient and displacementScale.
            // Pixels are shifted along the gradient.
            const dx = gradX * displacementScale;
            const dy = gradY * displacementScale;

            // Determine source coordinates in the original image after displacement.
            const srcX_float = x + dx;
            const srcY_float = y + dy;

            // Round to nearest pixel and clamp source coordinates to image bounds.
            let srcX = Math.round(srcX_float);
            let srcY = Math.round(srcY_float);
            srcX = Math.max(0, Math.min(w - 1, srcX));
            srcY = Math.max(0, Math.min(h - 1, srcY));
            
            const srcIdx = (srcY * w + srcX) * 4; // Index for source pixel in originalPixels.

            // Get color from the original image at displaced coordinates.
            let r = originalPixels[srcIdx];
            let g = originalPixels[srcIdx + 1];
            let b = originalPixels[srcIdx + 2];
            const a = originalPixels[srcIdx + 3]; // Preserve original alpha.

            // Apply lighting effect based on the height (h_xy) from the noise map.
            // (h_xy - 0.5) * 2 scales height from 0-1 to -1-1.
            // Peaks (h_xy > 0.5) become brighter, valleys (h_xy < 0.5) become darker.
            const lightFactor = 1.0 + (h_xy - 0.5) * 2 * lightingIntensity;
            r = Math.min(255, Math.max(0, r * lightFactor));
            g = Math.min(255, Math.max(0, g * lightFactor));
            b = Math.min(255, Math.max(0, b * lightFactor));

            // Set the calculated pixel color in the output image data.
            outputPixels[targetIdx] = r;
            outputPixels[targetIdx + 1] = g;
            outputPixels[targetIdx + 2] = b;
            outputPixels[targetIdx + 3] = a;
        }
    }

    // Draw the processed image data onto the main 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 Crumpled Paper Filter tool applies a unique crumpled paper effect to images, transforming their appearance by adding a textured, 3D-like distortion. Users can customize the displacement scale, lighting intensity, base feature size, and blur radius to achieve the desired effect. This tool is ideal for artists and designers looking to create visually interesting backgrounds or enhance graphic design projects by adding depth and a tactile quality to flat images. It can also be useful for giving photos an artistic flair, making them stand out on social media or in creative portfolios.

Leave a Reply

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