Please bookmark this page to avoid losing your image tool!

Image Jet Stream 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, streakLength = 10, brightnessThreshold = 128, direction = "horizontal", varyByBrightnessStr = "false") {
    // Validate and normalize parameters
    streakLength = Math.max(1, Math.floor(Number(streakLength)));
    brightnessThreshold = Math.max(0, Math.min(255, Number(brightnessThreshold)));
    const _varyByBrightness = String(varyByBrightnessStr).toLowerCase() === "true";

    const canvas = document.createElement('canvas');
    // Using { willReadFrequently: true } can be an optimization hint for canvas contexts
    // where getImageData/putImageData is used often.
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

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

    if (width === 0 || height === 0) {
        console.error("Image has zero width or height. Cannot process.");
        const errCanvas = document.createElement('canvas');
        errCanvas.width = Math.max(150, width || 150); // Ensure a minimum displayable size
        errCanvas.height = Math.max(50, height || 50);
        const errCtx = errCanvas.getContext('2d');
        errCtx.fillStyle = 'lightgray';
        errCtx.fillRect(0, 0, errCanvas.width, errCanvas.height);
        errCtx.fillStyle = 'red';
        errCtx.textAlign = 'center';
        errCtx.textBaseline = 'middle';
        errCtx.font = '12px Arial';
        errCtx.fillText('Error: Image dimensions are zero.', errCanvas.width / 2, errCanvas.height / 2);
        return errCanvas;
    }

    canvas.width = width;
    canvas.height = height;

    // Draw the original image onto the canvas
    ctx.drawImage(originalImg, 0, 0, width, height);

    let sourceImageData;
    try {
        sourceImageData = ctx.getImageData(0, 0, width, height);
    } catch (e) {
        console.error("Error getting ImageData (e.g., cross-origin issue):", e);
        ctx.clearRect(0, 0, width, height); // Clear previous drawImage
        ctx.fillStyle = 'lightcoral';
        ctx.fillRect(0, 0, width, height);
        ctx.fillStyle = 'white';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.font = '14px Arial';
        ctx.fillText('Error: Could not process image data.', width / 2, height / 2 - 10);
        ctx.font = '12px Arial';
        ctx.fillText('(Possibly due to cross-origin restrictions)', width / 2, height / 2 + 10);
        return canvas;
    }

    const sourceData = sourceImageData.data;
    // Create a new ImageData for the output, initialized with a copy of the source data.
    // This ensures non-streaked pixels and original alpha values are preserved.
    const outputDataArray = new Uint8ClampedArray(sourceData);
    const outputImageData = new ImageData(outputDataArray, width, height);
    const outputData = outputImageData.data; // This is the pixel array we'll modify

    if (direction === "horizontal") {
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                const R_idx = (y * width + x) * 4;
                const G_idx = R_idx + 1;
                const B_idx = R_idx + 2;
                const A_idx = R_idx + 3;

                // Use sourceData for original pixel values
                const R = sourceData[R_idx];
                const G = sourceData[G_idx];
                const B = sourceData[B_idx];
                const A = sourceData[A_idx]; // Alpha of the source pixel

                // Calculate brightness using luminance formula for better perceptual accuracy
                const brightness = 0.299 * R + 0.587 * G + 0.114 * B;

                if (brightness > brightnessThreshold) {
                    let currentAppliedStreakLength = streakLength;
                    if (_varyByBrightness) {
                        // Scale streak length by brightness: brighter pixels streak further
                        currentAppliedStreakLength = Math.max(1, Math.floor((brightness / 255.0) * streakLength));
                    }

                    // Extend the color of the current bright pixel
                    // k=0 is the source pixel itself, which is already set in outputData by the initial copy.
                    // Streaking starts from k=1 (the pixel next to the source).
                    for (let k = 1; k < currentAppliedStreakLength; k++) {
                        const streakX = x + k;
                        if (streakX < width) { // Check bounds
                            const targetIdx = (y * width + streakX) * 4;
                            outputData[targetIdx]     = R;
                            outputData[targetIdx + 1] = G;
                            outputData[targetIdx + 2] = B;
                            outputData[targetIdx + 3] = A; // Propagate source pixel's alpha
                        } else {
                            break; // Streak goes out of image bounds
                        }
                    }
                }
            }
        }
    } else if (direction === "vertical") {
        for (let x = 0; x < width; x++) { // Outer loop: columns
            for (let y = 0; y < height; y++) { // Inner loop: rows in current column
                const R_idx = (y * width + x) * 4;
                const G_idx = R_idx + 1;
                const B_idx = R_idx + 2;
                const A_idx = R_idx + 3;

                const R = sourceData[R_idx];
                const G = sourceData[G_idx];
                const B = sourceData[B_idx];
                const A = sourceData[A_idx];

                const brightness = 0.299 * R + 0.587 * G + 0.114 * B;

                if (brightness > brightnessThreshold) {
                    let currentAppliedStreakLength = streakLength;
                    if (_varyByBrightness) {
                        currentAppliedStreakLength = Math.max(1, Math.floor((brightness / 255.0) * streakLength));
                    }
                    
                    for (let k = 1; k < currentAppliedStreakLength; k++) {
                        const streakY = y + k;
                        if (streakY < height) { // Check bounds
                            const targetIdx = (streakY * width + x) * 4;
                            outputData[targetIdx]     = R;
                            outputData[targetIdx + 1] = G;
                            outputData[targetIdx + 2] = B;
                            outputData[targetIdx + 3] = A;
                        } else {
                            break; 
                        }
                    }
                }
            }
        }
    } else {
        console.warn(`Invalid direction: "${direction}". Must be "horizontal" or "vertical". No filter applied, returning original image principles.`);
        // Since outputData was initialized as a copy of sourceData, doing nothing here
        // means the original image (as drawn on canvas and copied to outputImageData) will be returned.
    }

    // Put the modified pixel data back onto 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 Jet Stream Filter Effect Tool allows users to apply a unique streaking effect to images, creating a visually appealing design element. Users can customize parameters such as streak length, brightness threshold, and direction (horizontal or vertical) to achieve their desired artistic effect. This tool can be particularly useful for graphic designers, digital artists, or anyone looking to enhance their images with a dynamic filter effect for personal projects, social media content, or marketing materials.

Leave a Reply

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