Please bookmark this page to avoid losing your image tool!

Image Miniature 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, focusPointY = 0.5, focusDepth = 0.1, blurRadius = 5, blurTransition = 0.15, saturation = 1.2, contrast = 1.1) {
    const w = originalImg.naturalWidth || originalImg.width;
    const h = originalImg.naturalHeight || originalImg.height;

    // 1. Create output canvas
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = w;
    outputCanvas.height = h;
    const outputCtx = outputCanvas.getContext('2d');
    if (!outputCtx) {
        console.error("Could not get 2D context for output canvas.");
        // Return an empty canvas or throw an error if context cannot be created
        return outputCanvas;
    }

    // 2. Create sharp canvas (copy of original)
    const sharpCanvas = document.createElement('canvas');
    sharpCanvas.width = w;
    sharpCanvas.height = h;
    const sharpCtx = sharpCanvas.getContext('2d');
     if (!sharpCtx) {
        console.error("Could not get 2D context for sharp canvas.");
        return outputCanvas;
    }
    sharpCtx.drawImage(originalImg, 0, 0, w, h);

    // 3. Create blurred canvas
    const blurCanvas = document.createElement('canvas');
    blurCanvas.width = w;
    blurCanvas.height = h;
    const blurCtx = blurCanvas.getContext('2d');
    if (!blurCtx) {
        console.error("Could not get 2D context for blur canvas.");
        return outputCanvas;
    }

    if (blurRadius > 0) {
        blurCtx.filter = `blur(${blurRadius}px)`;
        blurCtx.drawImage(originalImg, 0, 0, w, h);
        blurCtx.filter = 'none'; // Reset filter for any subsequent operations on blurCtx
    } else {
        // If no blur, blurred canvas is same as sharp
        blurCtx.drawImage(originalImg, 0, 0, w, h);
    }

    // 4. Create mask canvas
    const maskCanvas = document.createElement('canvas');
    maskCanvas.width = w;
    maskCanvas.height = h;
    const maskCtx = maskCanvas.getContext('2d');
    if (!maskCtx) {
        console.error("Could not get 2D context for mask canvas.");
        return outputCanvas;
    }

    // Calculate normalized stop points for the mask gradient
    // These are relative to image height (0.0 to 1.0)
    let norm_y1_sharp = focusPointY - focusDepth / 2;       // Top of fully sharp area
    let norm_y2_sharp = focusPointY + focusDepth / 2;       // Bottom of fully sharp area
    let norm_y0_trans_start = norm_y1_sharp - blurTransition; // Top of transition (to sharp)
    let norm_y3_trans_end = norm_y2_sharp + blurTransition;   // Bottom of transition (to blur)

    // Clamp initial calculations to [0, 1] range
    norm_y0_trans_start = Math.max(0, Math.min(1, norm_y0_trans_start));
    norm_y1_sharp       = Math.max(0, Math.min(1, norm_y1_sharp));
    norm_y2_sharp       = Math.max(0, Math.min(1, norm_y2_sharp));
    norm_y3_trans_end   = Math.max(0, Math.min(1, norm_y3_trans_end));
    
    // Ensure logical order of points (y0 <= y1 <= y2 <= y3)
    // This handles cases like zero/negative focusDepth or blurTransition
    if (norm_y1_sharp > norm_y2_sharp) { 
        const mid = (norm_y1_sharp + norm_y2_sharp) / 2;
        norm_y1_sharp = mid;
        norm_y2_sharp = mid;
    }
    if (norm_y0_trans_start > norm_y1_sharp) norm_y0_trans_start = norm_y1_sharp;
    if (norm_y3_trans_end < norm_y2_sharp) norm_y3_trans_end = norm_y2_sharp;
    
    // Build gradient stops for the mask (white = sharp, black = blurred)
    const stops = [];
    // Determine color at the very top (offset 0)
    if (norm_y1_sharp <= 0) stops.push({ offset: 0, color: 'white' }); // Sharp area starts at/before top
    else if (norm_y0_trans_start <= 0) stops.push({ offset: 0, color: 'black' }); // Transition starts at/before top (gradient from black)
    else stops.push({ offset: 0, color: 'black' }); // Fully blurred at top

    // Add intermediate transition points if they are strictly between 0 and 1
    if (norm_y0_trans_start > 0 && norm_y0_trans_start < 1) stops.push({ offset: norm_y0_trans_start, color: 'black' });
    if (norm_y1_sharp > 0 && norm_y1_sharp < 1) stops.push({ offset: norm_y1_sharp, color: 'white' });
    if (norm_y2_sharp > 0 && norm_y2_sharp < 1) stops.push({ offset: norm_y2_sharp, color: 'white' });
    if (norm_y3_trans_end > 0 && norm_y3_trans_end < 1) stops.push({ offset: norm_y3_trans_end, color: 'black' });

    // Determine color at the very bottom (offset 1)
    if (norm_y2_sharp >= 1) stops.push({ offset: 1, color: 'white' }); // Sharp area ends at/after bottom
    else if (norm_y3_trans_end >= 1) stops.push({ offset: 1, color: 'black' }); // Transition ends at/after bottom (gradient to black)
    else stops.push({ offset: 1, color: 'black' }); // Fully blurred at bottom
    
    // Create unique sorted gradient stops. Map ensures last entry for a given offset wins.
    const uniqueSortedStops = Array.from(
        new Map(stops.map(s => [s.offset, s]))
    ).sort((a,b) => a[0] - b[0]) // Sort by offset (Map keys are a[0])
    .map(entry => entry[1]); // Get the stop object itself (Map values are a[1])

    const grad = maskCtx.createLinearGradient(0, 0, 0, h);
    for (const stop of uniqueSortedStops) {
        grad.addColorStop(stop.offset, stop.color);
    }
    maskCtx.fillStyle = grad;
    maskCtx.fillRect(0, 0, w, h);

    // 5. Composite the layers
    // Start with the sharp image on the output canvas
    outputCtx.drawImage(sharpCanvas, 0, 0, w, h);

    // Mask the sharp image: keep sharp image where mask is white (alpha=1), transparent where mask is black (alpha=0)
    // 'destination-in': The existing content (sharpImage) is kept where it and the new shape (mask) overlap. 
    //                   Mask's luminance is used as alpha.
    outputCtx.globalCompositeOperation = 'destination-in';
    outputCtx.drawImage(maskCanvas, 0, 0, w, h);

    // Draw the blurred image *behind* the masked sharp image
    // 'destination-over': The new shape (blurImage) is drawn behind the existing content.
    outputCtx.globalCompositeOperation = 'destination-over';
    outputCtx.drawImage(blurCanvas, 0, 0, w, h);

    // Reset composite operation for any further drawing
    outputCtx.globalCompositeOperation = 'source-over';

    // 6. Apply final saturation and contrast adjustments
    if (saturation !== 1 || contrast !== 1) {
        if (typeof outputCtx.filter !== 'undefined') {
            const tempFilterCanvas = document.createElement('canvas');
            tempFilterCanvas.width = w;
            tempFilterCanvas.height = h;
            const tempFilterCtx = tempFilterCanvas.getContext('2d');
            
            if (!tempFilterCtx) {
                console.warn("Could not get 2D context for temporary filter canvas. Skipping saturation/contrast.");
            } else {
                tempFilterCtx.filter = `saturate(${saturation}) contrast(${contrast})`;
                tempFilterCtx.drawImage(outputCanvas, 0, 0, w, h); // Draw from current output to temp, applying filter
                
                // Copy filtered result back to outputCanvas
                outputCtx.clearRect(0,0,w,h); // Clear original output content
                outputCtx.drawImage(tempFilterCanvas, 0, 0, w,h); // Copy filtered image back
            }
        } else {
            console.warn("Canvas filters (saturate, contrast) not supported by this browser.");
        }
    }

    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 Miniature Filter tool allows users to create a focused miniature effect on images, simulating a shallow depth of field. This tool is particularly useful for photographers and graphic designers who want to emphasize a specific area of an image while softening the rest, giving it a unique artistic touch. Users can adjust parameters such as the focus point, depth of field, blur radius, and apply saturation and contrast enhancements, making it suitable for various creative projects including social media posts, presentations, and personal artwork.

Leave a Reply

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