Please bookmark this page to avoid losing your image tool!

Image Ocean Current 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, amplitude = 15, waveLength = 80, phase = 0, flowAngle = 0, tintColorStr = "rgba(0, 40, 80, 0.1)") {

    // Nested helper function for color parsing
    function _parseColorToObject(colorStr) {
        if (!colorStr || typeof colorStr !== 'string' || colorStr.toLowerCase() === 'transparent' || colorStr.trim() === "") {
            return null; // No tint if string is empty, null, transparent, or not a string
        }
        // Use a temporary 1x1 canvas to parse the color string
        // This is a robust way to support various color formats (hex, rgb, rgba, named colors)
        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = 1; tempCanvas.height = 1;
        // Add willReadFrequently hint for getContext if using getImageData frequently,
        // though for a single parse, it's minor.
        const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true }); 
        
        if (!tempCtx) { 
            console.warn("Could not create temp context for color parsing.");
            return null; 
        }

        tempCtx.fillStyle = colorStr;
        tempCtx.fillRect(0, 0, 1, 1); 
        
        try {
            const imageData = tempCtx.getImageData(0, 0, 1, 1);
            if (!imageData) { // Should not happen with fillRect
                return null;
            }
            const [r, g, b, a_byte] = imageData.data;
            return { r, g, b, a: a_byte / 255 };
        } catch(e) {
            console.warn(`Could not parse color string: "${colorStr}"`, e);
            return null;
        }
    }

    if (!(originalImg instanceof HTMLImageElement)) {
        console.error("Parameter 'originalImg' is not an HTMLImageElement.");
        const errCanvas = document.createElement('canvas'); errCanvas.width = 1; errCanvas.height = 1; return errCanvas;
    }

    try {
        if (typeof originalImg.decode === 'function') {
            await originalImg.decode();
        } else {
            if (!originalImg.complete || originalImg.naturalWidth === 0) {
                 if (!originalImg.src && !originalImg.currentSrc) { // currentSrc for <img> in DOM
                    throw new Error("Image has no src attribute to load from.");
                 }
                 await new Promise((resolve, reject) => {
                    originalImg.onload = resolve;
                    originalImg.onerror = (err) => reject(new Error(`Image failed to load from src: ${originalImg.src || originalImg.currentSrc}. Error: ${err}`));
                 });
            }
        }
    } catch (error) {
        console.error("Image could not be loaded or decoded:", error.message || error);
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 150; errorCanvas.height = 30; // Small canvas for error message
        const ctx = errorCanvas.getContext('2d');
        if (ctx) {
            ctx.font = '12px Arial';
            ctx.fillStyle = 'red';
            ctx.fillText('Error loading image.', 5, 20);
        }
        return errorCanvas;
    }
    
    const width = originalImg.naturalWidth;
    const height = originalImg.naturalHeight;

    if (width === 0 || height === 0) {
        console.error("Image has zero dimensions after loading attempt.");
        const errorCanvas = document.createElement('canvas'); errorCanvas.width = 1; errorCanvas.height = 1; return errorCanvas;
    }

    // Output canvas, where the final image will be drawn
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');

    // Source canvas to get ImageData from the original image
    // This is necessary because we need raw pixel data for manipulation.
    const srcCanvas = document.createElement('canvas');
    srcCanvas.width = width;
    srcCanvas.height = height;
    const srcCtx = srcCanvas.getContext('2d', { willReadFrequently: true }); 
    srcCtx.drawImage(originalImg, 0, 0, width, height);
    
    let srcImageData;
    try {
        srcImageData = srcCtx.getImageData(0, 0, width, height);
    } catch (e) {
        console.error("Could not get image data from source canvas (cross-origin issue?):", e);
        // Fallback: draw the original image onto the output canvas
        ctx.drawImage(originalImg, 0, 0, width, height);
        // Attempt to apply tint over the original image if pixel manipulation fails
        const tint = _parseColorToObject(tintColorStr);
         if (tint && tint.a > 0) {
            ctx.fillStyle = `rgba(${tint.r},${tint.g},${tint.b},${tint.a})`;
            ctx.fillRect(0,0,width,height);
        }
        return canvas;
    }
    
    const srcData = srcImageData.data;
    // Create blank ImageData for the destination (output canvas)
    const dstImageData = ctx.createImageData(width, height);
    const dstData = dstImageData.data;

    // Pre-calculate effect parameters
    const angleRad = flowAngle * Math.PI / 180;
    const cosA = Math.cos(angleRad);
    const sinA = Math.sin(angleRad);
    
    let currentWaveLength = waveLength;
    if (currentWaveLength <= 0) {
        // A non-positive wavelength is problematic. 
        // Make frequency effectively zero to disable wave, resulting in uniform displacement (if amplitude > 0) or no change.
        console.warn("waveLength parameter must be positive. Wave effect will be minimal or uniform.");
        currentWaveLength = Math.max(width, height) * 1e6; // Effectively infinite wavelength
    }
    const freq = (2 * Math.PI) / currentWaveLength;


    // Apply the "ocean current" distortion pixel by pixel
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            // Calculate `yp`, a coordinate perpendicular to the flow direction.
            // The wave pattern is based on this `yp` coordinate.
            const yp = -x * sinA + y * cosA; 
            
            // Calculate displacement magnitude using a sinusoidal wave.
            // `amplitude` controls the strength, `freq` controls wave density, `phase` shifts the wave.
            const displacement = amplitude * Math.sin(yp * freq + phase);

            // Determine the source pixel coordinates by shifting the current pixel (x,y)
            // *along* the flow direction (`cosA`, `sinA`) by the `displacement` amount.
            const srcX_float = x + displacement * cosA;
            const srcY_float = y + displacement * sinA;

            // Use nearest neighbor sampling (rounding) to find the source pixel.
            const srcX = Math.round(srcX_float);
            const srcY = Math.round(srcY_float);

            // Clamp source coordinates to ensure they are within the image bounds.
            const clampedSrcX = Math.max(0, Math.min(width - 1, srcX));
            const clampedSrcY = Math.max(0, Math.min(height - 1, srcY));

            // Calculate array indices for source and destination pixel data.
            const srcIndex = (clampedSrcY * width + clampedSrcX) * 4; // 4 bytes per pixel (R,G,B,A)
            const dstIndex = (y * width + x) * 4;

            // Copy pixel data (R,G,B,A) from source to destination.
            dstData[dstIndex]     = srcData[srcIndex];
            dstData[dstIndex + 1] = srcData[srcIndex + 1];
            dstData[dstIndex + 2] = srcData[srcIndex + 2];
            dstData[dstIndex + 3] = srcData[srcIndex + 3]; // Preserve original alpha
        }
    }

    // Apply tint if specified
    const tint = _parseColorToObject(tintColorStr);
    if (tint && tint.a > 0) { // tint.a is opacity from 0 (transparent) to 1 (opaque)
        const tintR = tint.r; // Tint color components (0-255)
        const tintG = tint.g;
        const tintB = tint.b;
        const tintA = tint.a; // Alpha of the tint layer itself

        for (let i = 0; i < dstData.length; i += 4) {
            // Alpha blending: FinalColor = PixelColor * (1 - TintAlpha) + TintColor * TintAlpha
            // This blends the tint color over the (already distorted) pixel color.
            dstData[i]     = Math.round(dstData[i] * (1 - tintA) + tintR * tintA);
            dstData[i + 1] = Math.round(dstData[i + 1] * (1 - tintA) + tintG * tintA);
            dstData[i + 2] = Math.round(dstData[i + 2] * (1 - tintA) + tintB * tintA);
            // The pixel's original alpha (dstData[i+3]) is preserved from the distorted image.
            // If the tint itself should affect the overall alpha, a different blending model for alpha would be needed.
        }
    }

    // Draw the processed (distorted and tinted) image data onto the output canvas
    ctx.putImageData(dstImageData, 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 Ocean Current Filter Effect Tool allows users to apply a dynamic ocean current filter effect to images. This tool can manipulate the appearance of an image by introducing flowing wave patterns that simulate the motion of ocean currents. Users can adjust parameters such as amplitude, wavelength, phase, and flow angle to customize the intensity and direction of the effect. Additionally, a tint color can be applied to enhance the visual appeal of the image. This tool is beneficial for artists, graphic designers, and anyone looking to create visually striking images for digital artwork, social media, or marketing materials.

Leave a Reply

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