Please bookmark this page to avoid losing your image tool!

Image Migration Route 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.
function processImage(originalImg,
    numRoutes = 7,
    routeColorStr = "255,220,50,0.7", // R,G,B,A format string
    routeThickness = 3,
    glowColorStr = "255,255,150,0.4", // R,G,B,A format string
    glowRadius = 15,
    originX = 0.5, // 0.0 to 1.0, relative to image width
    originY = 0.5, // 0.0 to 1.0, relative to image height
    waviness = 0.3, // 0.0 (straight) up to e.g. 1.0 for significant waviness
    compositeOp = 'lighter' // e.g., 'lighter', 'screen', 'source-over'
) {

    // Helper function to parse and validate color strings (R,G,B,A)
    function parseColorString(colorStrInput, defaultAlpha = 1.0) {
        if (typeof colorStrInput !== 'string') {
            return { r: 0, g: 0, b: 0, a: 0, valid: false, str: "rgba(0,0,0,0)" };
        }

        const parts = colorStrInput.split(',').map(s => parseFloat(s.trim()));

        if (parts.length < 3 || parts.slice(0, 3).some(isNaN)) {
             // R, G, B must be valid numbers
            return { r: 0, g: 0, b: 0, a: 0, valid: false, str: "rgba(0,0,0,0)" };
        }

        let r = parts[0];
        let g = parts[1];
        let b = parts[2];
        let a = (parts.length > 3 && !isNaN(parts[3])) ? parts[3] : defaultAlpha;

        // Clamp values to valid ranges
        r = Math.max(0, Math.min(255, Math.round(r)));
        g = Math.max(0, Math.min(255, Math.round(g)));
        b = Math.max(0, Math.min(255, Math.round(b)));
        a = Math.max(0, Math.min(1, a));

        return {
            r, g, b, a,
            valid: true,
            str: `rgba(${r},${g},${b},${a})`
        };
    }

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    // Parse colors and numeric parameters
    const parsedRouteColor = parseColorString(routeColorStr);
    const parsedGlowColor = parseColorString(glowColorStr);
    
    const routesToDraw = Math.max(0, Number(numRoutes));
    const currentRouteThickness = Math.max(0, Number(routeThickness));
    const currentGlowRadius = Math.max(0, Number(glowRadius));

    // Early exit if no visible effect will be produced
    const isRouteLineVisible = parsedRouteColor.valid && parsedRouteColor.a > 0 && currentRouteThickness > 0;
    const isGlowVisible = parsedGlowColor.valid && parsedGlowColor.a > 0 && currentGlowRadius > 0;

    if (routesToDraw === 0 || (!isRouteLineVisible && !isGlowVisible)) {
        return canvas; // Return canvas with just the original image
    }

    const actualOriginX = Math.max(0, Math.min(1, originX)) * canvas.width;
    const actualOriginY = Math.max(0, Math.min(1, originY)) * canvas.height;
    const currentWaviness = Math.max(0, waviness);

    const originalCompositeOp = ctx.globalCompositeOperation;
    const validCompositeOps = [
        'source-over', 'source-in', 'source-out', 'source-atop',
        'destination-over', 'destination-in', 'destination-out', 'destination-atop',
        'lighter', 'copy', 'xor', 'multiply', 'screen', 'overlay', 'darken',
        'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light',
        'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity'
    ];
    if (validCompositeOps.includes(compositeOp)) {
        ctx.globalCompositeOperation = compositeOp;
    } else {
        ctx.globalCompositeOperation = 'lighter'; // Fallback default
    }
    
    ctx.lineCap = 'round';

    for (let i = 0; i < routesToDraw; i++) {
        let endX, endY;
        const side = Math.floor(Math.random() * 4); 
        
        switch (side) {
            case 0: // Top border
                endX = Math.random() * canvas.width; 
                endY = 0; 
                break;
            case 1: // Right border
                endX = canvas.width; 
                endY = Math.random() * canvas.height; 
                break;
            case 2: // Bottom border
                endX = Math.random() * canvas.width; 
                endY = canvas.height; 
                break;
            case 3: // Left border
                endX = 0; 
                endY = Math.random() * canvas.height; 
                break;
        }

        const midPointX = (actualOriginX + endX) / 2;
        const midPointY = (actualOriginY + endY) / 2;
        
        const segmentLengthOriginToMid = Math.sqrt(
            Math.pow(midPointX - actualOriginX, 2) + 
            Math.pow(midPointY - actualOriginY, 2)
        );
        
        const maxDeviation = segmentLengthOriginToMid * currentWaviness; 
        const actualDeviation = Math.random() * maxDeviation;
        
        const angleOriginToEnd = Math.atan2(endY - actualOriginY, endX - actualOriginX);
        const deviationSide = (Math.random() < 0.5 ? 1 : -1); // Randomly deviate left or right
        const controlPointAngle = angleOriginToEnd + (Math.PI / 2) * deviationSide;

        const cpX = midPointX + actualDeviation * Math.cos(controlPointAngle);
        const cpY = midPointY + actualDeviation * Math.sin(controlPointAngle);

        ctx.beginPath();
        ctx.moveTo(actualOriginX, actualOriginY);
        ctx.quadraticCurveTo(cpX, cpY, endX, endY);

        // Apply glow using shadowBlur
        if (isGlowVisible) {
            ctx.shadowColor = parsedGlowColor.str;
            ctx.shadowBlur = currentGlowRadius;
        } else {
            ctx.shadowColor = 'transparent'; 
            ctx.shadowBlur = 0;
        }
        
        // Draw the main route line OR a dummy line if only glow is visible
        if (isRouteLineVisible) {
            ctx.strokeStyle = parsedRouteColor.str;
            ctx.lineWidth = currentRouteThickness;
            ctx.stroke();
        } else if (isGlowVisible) { 
            // Route line itself is invisible, but glow is active. Stroke a minimal line for the shadow.
             // Use glow color but very transparent for the stroke itself, shadow will use full glowColor.str
            ctx.strokeStyle = `rgba(${parsedGlowColor.r},${parsedGlowColor.g},${parsedGlowColor.b},0.005)`;
            ctx.lineWidth = 1; // Minimal width to cast shadow effectively
            ctx.stroke();
        }
    }

    // Reset canvas state modifications
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.globalCompositeOperation = originalCompositeOp;

    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 Migration Route Filter Effect Tool allows users to apply artistic route effects on images by drawing colorful paths that can simulate migration routes or other visual pathways. Users can customize the number of routes, their colors, thickness, and glow effects, creating unique and visually appealing designs. This tool is useful for enhancing maps, illustrating travel paths, or creating eye-catching visuals for presentations and social media, making it a versatile option for graphic designers, educators, and content creators.

Leave a Reply

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