Please bookmark this page to avoid losing your image tool!

NES Style Image Filter Creator

(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, pixelSize = "auto", ditherStrength = "0", contrast = "15") {
  // Determine pixel block size. If "auto", scale dynamically to emulate NES 256 pixel width resolution.
  const pSize = pixelSize === "auto" 
    ? Math.max(1, Math.round(originalImg.width / 256)) 
    : Math.max(1, parseInt(pixelSize, 10) || 4);
    
  const dStrength = parseInt(ditherStrength, 10) || 0;
  const cStrength = parseInt(contrast, 10) || 0;
  
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  
  canvas.width = originalImg.width;
  canvas.height = originalImg.height;
  
  // Draw original image to extract its pixel data
  ctx.drawImage(originalImg, 0, 0);
  const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const data = imgData.data;
  const width = canvas.width;
  const height = canvas.height;
  
  // NES 54-Color Palette Definition
  const nesPalette = [
    [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],
    [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],
    [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],
    [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]
  ];
  
  // Basic Bayer matrix for optional dithering
  const bayerMatrix = [
    [ 0,  8,  2, 10],
    [12,  4, 14,  6],
    [ 3, 11,  1,  9],
    [15,  7, 13,  5]
  ];
  
  // Map RGB values to the nearest matching NES palette color using Euclidean distance
  function getClosestColor(r, g, b) {
    let minDistance = Infinity;
    let closest = nesPalette[0];
    for (let i = 0; i < nesPalette.length; i++) {
      let color = nesPalette[i];
      let dr = r - color[0];
      let dg = g - color[1];
      let db = b - color[2];
      let distance = dr*dr + dg*dg + db*db;
      
      if (distance < minDistance) {
        minDistance = distance;
        closest = color;
      }
    }
    return closest;
  }
  
  const outData = new Uint8ClampedArray(data.length);
  // Pre-calculate contrast enhancement factor if set
  const contrastFactor = cStrength !== 0 ? (259 * (cStrength + 255)) / (255 * (259 - cStrength)) : 1;
  
  // Process the image in grid blocks based on the pixel size
  for (let y = 0; y < height; y += pSize) {
    for (let x = 0; x < width; x += pSize) {
      
      let sumR = 0, sumG = 0, sumB = 0, sumAlpha = 0, count = 0;
      
      // Calculate the average color for this entire block
      for (let by = 0; by < pSize; by++) {
        for (let bx = 0; bx < pSize; bx++) {
          let px = x + bx;
          let py = y + by;
          if (px < width && py < height) {
            let offset = (py * width + px) * 4;
            sumR += data[offset];
            sumG += data[offset+1];
            sumB += data[offset+2];
            sumAlpha += data[offset+3];
            count++;
          }
        }
      }
      
      let avgAlpha = sumAlpha / count;
      
      // Keep mostly invisible elements as fully transparent like retro hard-edge sprites
      if (avgAlpha < 128) {
        for (let by = 0; by < pSize; by++) {
          for (let bx = 0; bx < pSize; bx++) {
            let px = x + bx;
            let py = y + by;
            if (px < width && py < height) {
              let offset = (py * width + px) * 4;
              outData[offset] = 0;
              outData[offset+1] = 0;
              outData[offset+2] = 0;
              outData[offset+3] = 0;
            }
          }
        }
        continue;
      }
      
      let avgR = sumR / count;
      let avgG = sumG / count;
      let avgB = sumB / count;
      
      // Apply contrast mapping 
      if (cStrength !== 0) {
        avgR = contrastFactor * (avgR - 128) + 128;
        avgG = contrastFactor * (avgG - 128) + 128;
        avgB = contrastFactor * (avgB - 128) + 128;
      }
      
      // Apply structural Bayer dithering formula conditionally if enabled
      if (dStrength > 0) {
        let bX = Math.floor(x / pSize) % 4;
        let bY = Math.floor(y / pSize) % 4;
        let factor = (bayerMatrix[bY][bX] / 16) - 0.5;
        avgR += factor * dStrength;
        avgG += factor * dStrength;
        avgB += factor * dStrength;
      }
      
      // Ensure exact limits before color distance check
      avgR = Math.max(0, Math.min(255, avgR));
      avgG = Math.max(0, Math.min(255, avgG));
      avgB = Math.max(0, Math.min(255, avgB));
      
      let closestColor = getClosestColor(avgR, avgG, avgB);
      
      // Fill original size mapping output with the synthesized classic NES colors
      for (let by = 0; by < pSize; by++) {
        for (let bx = 0; bx < pSize; bx++) {
          let px = x + bx;
          let py = y + by;
          if (px < width && py < height) {
            let offset = (py * width + px) * 4;
            outData[offset] = closestColor[0];
            outData[offset+1] = closestColor[1];
            outData[offset+2] = closestColor[2];
            outData[offset+3] = 255;
          }
        }
      }
      
    }
  }
  
  // Map our calculated new 8-bit visual array to actual imagery on Canvas
  const finalImgData = new ImageData(outData, width, height);
  ctx.putImageData(finalImgData, 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 NES Style Image Filter Creator transforms modern images into a retro, 8-bit aesthetic reminiscent of classic Nintendo Entertainment System graphics. The tool works by downsampling images into pixel blocks, applying a specialized 54-color palette, and offering adjustable settings for pixel size, contrast, and dither strength to achieve an authentic vintage look. This tool is ideal for game developers creating pixel art assets, social media users wanting a nostalgic aesthetic, or digital artists looking to experiment with retro color grading and dithering effects.

Leave a Reply

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