Please bookmark this page to avoid losing your image tool!

Image Vaporwave Filter Application

(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, pinkAmount = 0.3, cyanAmount = 0.3, chromaticOffset = 3, glitchProbability = 0.05, glitchOffset = 15, glitchBlockSize = 8, scanlineOpacity = 0.1, scanlineHeight = 1) {
    // Ensure numerical parameters and provide safe fallbacks for critical ones
    pinkAmount = Number(pinkAmount);
    cyanAmount = Number(cyanAmount);
    chromaticOffset = Number(chromaticOffset);
    glitchProbability = Number(glitchProbability);
    glitchOffset = Number(glitchOffset);
    glitchBlockSize = Math.max(1, Number(glitchBlockSize)); // Must be at least 1 to prevent infinite loops
    scanlineOpacity = Number(scanlineOpacity);
    scanlineHeight = Math.max(1, Number(scanlineHeight));   // Must be at least 1

    const canvas = document.createElement('canvas');
    // Opt-in for performance hint for frequent getImageData/putImageData calls
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); 
    
    // Basic check for image validity
    if (!originalImg || typeof originalImg.width === 'undefined' || originalImg.width === 0 || originalImg.height === 0) {
        console.error("Vaporwave Filter: Invalid image input or image not loaded.");
        // Return a canvas with an error message
        canvas.width = 200;
        canvas.height = 50;
        ctx.fillStyle = "black";
        ctx.fillRect(0,0,200,50);
        ctx.fillStyle = "red";
        ctx.font = "12px Arial";
        ctx.fillText("Error: Invalid image input.", 10, 30);
        return canvas;
    }

    const width = originalImg.width;
    const height = originalImg.height;
    canvas.width = width;
    canvas.height = height;

    // Draw original image to canvas as the starting point
    ctx.drawImage(originalImg, 0, 0, width, height);

    // --- Step 1: Chromatic Aberration ---
    if (chromaticOffset > 0) {
        const originalImageData = ctx.getImageData(0, 0, width, height);
        // Create new ImageData object. Using ctx.createImageData is preferred.
        const aberratedImageData = ctx.createImageData(width, height); 
        const origData = originalImageData.data;
        const newData = aberratedImageData.data;

        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                const i = (y * width + x) * 4;
                
                // Calculate source pixel x-coordinates for R and B channels with clamping
                const rX = Math.max(0, Math.min(width - 1, x - chromaticOffset));
                const bX = Math.max(0, Math.min(width - 1, x + chromaticOffset));

                // Calculate source indices in the flat pixel array
                const rIdx = (y * width + rX) * 4;
                const gIdx = i; // Green channel from current pixel (no shift)
                const bIdx = (y * width + bX) * 4;

                newData[i]     = origData[rIdx];     // Red channel
                newData[i + 1] = origData[gIdx + 1]; // Green channel
                newData[i + 2] = origData[bIdx + 2]; // Blue channel
                newData[i + 3] = origData[gIdx + 3]; // Alpha channel
            }
        }
        ctx.putImageData(aberratedImageData, 0, 0);
    }

    // --- Step 2: Color Tinting (Pinks/Cyans) ---
    if (pinkAmount > 0 || cyanAmount > 0) {
        const currentImageData = ctx.getImageData(0, 0, width, height);
        const data = currentImageData.data;
        for (let i = 0; i < data.length; i += 4) {
            let r = data[i];
            let g = data[i + 1];
            let b = data[i + 2];

            // Additive tinting, scaled by amounts. These constants (40, 20) can be tuned.
            let newR = r + 40 * pinkAmount;
            let newG = g + 40 * cyanAmount;
            let newB = b + 20 * (pinkAmount + cyanAmount); // Blue is part of both magenta (pink) and cyan

            // Slightly reduce the influence of the "opposite" tint to make pinks more pinkish and cyans more cyanish.
            // Normalize amounts to be between 0 and 1 for this factor to avoid overly strong reduction.
            const normCyan = Math.min(1, Math.max(0, cyanAmount));
            const normPink = Math.min(1, Math.max(0, pinkAmount));
            newR = newR * (1 - 0.3 * normCyan); 
            newG = newG * (1 - 0.3 * normPink);

            data[i]   = Math.min(255, Math.max(0, newR));
            data[i + 1] = Math.min(255, Math.max(0, newG));
            data[i + 2] = Math.min(255, Math.max(0, newB));
        }
        ctx.putImageData(currentImageData, 0, 0);
    }

    // --- Step 3: Glitch Effect (Horizontal Block Shift) ---
    if (glitchProbability > 0 && glitchOffset > 0) {
        const imageDataToGlitch = ctx.getImageData(0, 0, width, height);
        const dataToGlitch = imageDataToGlitch.data;

        for (let blockY = 0; blockY < height; blockY += glitchBlockSize) {
            if (Math.random() < glitchProbability) {
                // Random shift amount (-glitchOffset to +glitchOffset)
                const shift = Math.floor((Math.random() * 2 - 1) * glitchOffset);
                if (shift === 0) continue; // No shift, skip this block

                // Actual height of the current block (can be less than glitchBlockSize at the image bottom)
                const currentBlockActualHeight = Math.min(glitchBlockSize, height - blockY);

                for (let lineInBlock = 0; lineInBlock < currentBlockActualHeight; lineInBlock++) {
                    const currentLineY = blockY + lineInBlock;
                    
                    // Buffer for the original row data of the current line before shifting
                    const rowPixelData = new Uint8ClampedArray(width * 4);
                    const rowStartIndexInFullData = currentLineY * width * 4;
                    // Efficiently copy the row data
                    rowPixelData.set(dataToGlitch.subarray(rowStartIndexInFullData, rowStartIndexInFullData + width * 4));
                    
                    // Write the shifted row back into the main data array (dataToGlitch)
                    for (let x = 0; x < width; x++) {
                        let sourceX = x - shift;
                        // Modulo arithmetic for wrap-around behavior
                        sourceX = (sourceX % width + width) % width; 
                        
                        const targetIdxInFullData = (currentLineY * width + x) * 4;
                        const sourcePixelInRowArrayIdx = sourceX * 4;

                        dataToGlitch[targetIdxInFullData]     = rowPixelData[sourcePixelInRowArrayIdx];
                        dataToGlitch[targetIdxInFullData + 1] = rowPixelData[sourcePixelInRowArrayIdx + 1];
                        dataToGlitch[targetIdxInFullData + 2] = rowPixelData[sourcePixelInRowArrayIdx + 2];
                        dataToGlitch[targetIdxInFullData + 3] = rowPixelData[sourcePixelInRowArrayIdx + 3];
                    }
                }
            }
        }
        ctx.putImageData(imageDataToGlitch, 0, 0);
    }
    
    // --- Step 4: Scanlines ---
    if (scanlineOpacity > 0) {
        // Using a dark, slightly purple color for scanlines, fitting the vaporwave theme
        ctx.fillStyle = `rgba(30, 0, 50, ${scanlineOpacity})`; 
        
        // Draw lines with gaps of the same height
        const scanlineStep = scanlineHeight * 2; 
        for (let slY = 0; slY < height; slY += scanlineStep) { 
            ctx.fillRect(0, slY, width, scanlineHeight);
        }
    }

    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 Vaporwave Filter Application allows you to apply a retro-inspired vaporwave effect to your images. With customizable parameters, users can adjust the levels of pink and cyan tinting, add a chromatic aberration effect, apply glitch effects, and overlay scanlines. This tool is ideal for artists or designers looking to create nostalgic, surreal visuals for social media, digital art projects, or personal expression.

Leave a Reply

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