Please bookmark this page to avoid losing your image tool!

Image NES Filter Effect Applicator

(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, pixelationFactor = 1, brightness = 0, contrast = 0) {
    // NES NTSC palette (commonly used, e.g., FCEUX default.pal)
    // Source: https://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Nintendo_Entertainment_System
    // Also see: http://bisqwit.iki.fi/utils/nespalette.php (default.pal for emulators like FCEUX)
    // This palette has 64 entries, with 56 unique colors.
    const NES_PALETTE = [
        [124,124,124], [  0,  0,252], [  0,  0,188], [ 68, 40,188], [148,  0,132], [168,  0, 32], [168, 16,  0], [136, 20,  0],
        [ 80, 48,  0], [  0,120,  0], [  0,104,  0], [  0, 88,  0], [  0, 64, 88], [  0,  0,  0], [  0,  0,  0], [  0,  0,  0], 
        [188,188,188], [  0,120,248], [  0, 88,248], [104, 68,252], [216,  0,204], [228,  0, 88], [248, 56,  0], [228, 92, 16],
        [172,124,  0], [  0,184,  0], [  0,168,  0], [  0,168, 68], [  0,136,136], [  0,  0,  0], [  0,  0,  0], [  0,  0,  0], 
        [248,248,248], [ 60,188,252], [104,136,252], [152,120,248], [248,120,248], [248, 88,152], [248,120, 88], [252,160, 68],
        [248,184,  0], [184,248, 24], [ 88,216, 84], [ 88,248,152], [  0,232,216], [120,120,120], [  0,  0,  0], [  0,  0,  0], 
        [252,252,252], [164,228,252], [184,184,248], [216,184,248], [248,184,248], [248,164,192], [240,208,176], [252,224,168],
        [248,216,120], [216,248,120], [184,248,184], [184,248,216], [  0,252,252], [248,216,248], [  0,  0,  0], [  0,  0,  0]  
    ];

    // Helper function to find the closest color in the NES palette
    // Uses squared Euclidean distance for efficiency
    function findClosestNesColor(r, g, b) {
        let minDistSq = Number.MAX_VALUE;
        let closestColor = NES_PALETTE[13]; // Default to a black color ([0,0,0]) from the palette

        for (const nesColor of NES_PALETTE) {
            const dr = r - nesColor[0];
            const dg = g - nesColor[1];
            const db = b - nesColor[2];
            const distSq = dr * dr + dg * dg + db * db;

            if (distSq < minDistSq) {
                minDistSq = distSq;
                closestColor = nesColor;
            }
            // Optimization: if an exact match is found (distance is 0), no need to search further
            if (minDistSq === 0) {
                break;
            }
        }
        return closestColor;
    }

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

    // Validate and sanitize parameters
    // Ensure pixelationFactor is a positive number, defaulting to 1.
    pixelationFactor = Math.max(1, Number(pixelationFactor) || 1);
    
    // Ensure brightness and contrast are numbers, defaulting to 0, and clamp to typical ranges.
    brightness = Number(brightness) || 0;
    brightness = Math.max(-255, Math.min(255, brightness)); 
    
    contrast = Number(contrast) || 0;
    contrast = Math.max(-255, Math.min(255, contrast));   
    
    // Calculate working dimensions for pixelation effect.
    // Ensure width/height are at least 1 to avoid issues with 0-dimension canvases.
    const workingWidth = Math.max(1, Math.floor(originalImg.width / pixelationFactor));
    const workingHeight = Math.max(1, Math.floor(originalImg.height / pixelationFactor));

    // Create a temporary canvas for processing
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = workingWidth;
    tempCanvas.height = workingHeight;
    const tempCtx = tempCanvas.getContext('2d');

    // Draw original image to temp canvas, downscaling if pixelationFactor > 1
    tempCtx.drawImage(originalImg, 0, 0, originalImg.width, originalImg.height, 0, 0, workingWidth, workingHeight);

    // Get image data from the temporary (potentially downscaled) canvas
    // Note: This can throw a security error if the originalImg is cross-origin and taints the canvas.
    let imageData;
    try {
        imageData = tempCtx.getImageData(0, 0, workingWidth, workingHeight);
    } catch (e) {
        console.error("Error getting ImageData, possibly due to CORS policy:", e);
        // Return an empty or error indicating canvas. For now, let's re-throw or return original.
        // As per requirements, return a canvas. An empty one will do if processing fails.
        outputCanvas.width = originalImg.width;
        outputCanvas.height = originalImg.height;
        outputCtx.fillStyle = "red";
        outputCtx.fillRect(0,0, outputCanvas.width, outputCanvas.height);
        outputCtx.fillStyle = "white";
        outputCtx.textAlign = "center";
        outputCtx.fillText("Error processing image (CORS?)", outputCanvas.width/2, outputCanvas.height/2);
        return outputCanvas;
    }
    const data = imageData.data;

    // Calculate contrast factor (once, outside the loop for performance)
    // Standard contrast formula: Factor = (259 * (contrast + 255)) / (255 * (259 - contrast))
    const contrastFactor = (259 * (contrast + 255)) / (255 * (259 - contrast));

    // Process each pixel
    for (let i = 0; i < data.length; i += 4) {
        let r = data[i];
        let g = data[i + 1];
        let b = data[i + 2];
        // Alpha channel (data[i + 3]) is preserved

        // Apply brightness adjustment
        if (brightness !== 0) {
            r = Math.max(0, Math.min(255, r + brightness));
            g = Math.max(0, Math.min(255, g + brightness));
            b = Math.max(0, Math.min(255, b + brightness));
        }

        // Apply contrast adjustment
        // If contrast is 0, contrastFactor is 1, so color values remain unchanged.
        if (contrast !== 0) { 
            r = Math.max(0, Math.min(255, contrastFactor * (r - 128) + 128));
            g = Math.max(0, Math.min(255, contrastFactor * (g - 128) + 128));
            b = Math.max(0, Math.min(255, contrastFactor * (b - 128) + 128));
        }
        
        // Quantize the adjusted color to the NES palette
        const nesColor = findClosestNesColor(r, g, b);
        data[i] = nesColor[0];
        data[i + 1] = nesColor[1];
        data[i + 2] = nesColor[2];
    }
    
    // Write the modified pixel data back to the temporary canvas
    tempCtx.putImageData(imageData, 0, 0);

    // Prepare the final output canvas with the original image dimensions
    outputCanvas.width = originalImg.width;
    outputCanvas.height = originalImg.height;

    // Disable image smoothing to preserve the blocky, pixelated look when upscaling
    outputCtx.imageSmoothingEnabled = false;
    // For cross-browser compatibility (though .imageSmoothingEnabled is widely supported)
    outputCtx.mozImageSmoothingEnabled = false;    // Older Firefox
    outputCtx.webkitImageSmoothingEnabled = false; // Older Chrome/Safari/Opera
    outputCtx.msImageSmoothingEnabled = false;     // IE/Edge (deprecated)
    
    // Draw the processed image from the temporary canvas to the final output canvas.
    // This step handles upscaling (if pixelationFactor > 1) using nearest-neighbor interpolation
    // due to imageSmoothingEnabled being false.
    outputCtx.drawImage(tempCanvas, 0, 0, workingWidth, workingHeight, 0, 0, outputCanvas.width, outputCanvas.height);

    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 NES Filter Effect Applicator allows users to apply a retro NES-style filter to images. This tool’s features include pixelation, brightness adjustment, and contrast modification, which can transform any image into a nostalgic, pixel art style reminiscent of classic video games. It is ideal for artists, game developers, or anyone looking to give their digital images a unique, vintage look. Whether for personal projects, social media sharing, or creative inspiration, this tool offers an accessible way to create eye-catching visual effects.

Leave a Reply

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