Search code examples
javascriptimage-processingcanvasmatrixdithering

monochrome dithering in JavaScript (Bayer, Atkinson, Floyd–Steinberg)


I'm playing with webcam filters in HTML5. Got an Atkinson dither working pretty well for that old-school Mac feeling.

meemoo cam to dither
Live | Code

Now I'm trying to make a Bayer ordered dithering option for a 1989 Gameboy feeling.

I read up on the algorithm, but I'm having trouble converting this pseudocode to JavaScript:

for each y
  for each x
    oldpixel := pixel[x][y] + threshold_map_4x4[x mod 4][y mod 4]
    newpixel := find_closest_palette_color(oldpixel)
    pixel[x][y] := newpixel

Are there any examples out there in AS3, PHP, or JS? Could you explain what is happening with threshold_map_4x4[x mod 4][y mod 4]?


gameboy style gif (made with the Meemoo Gameboy GIFerizer)

Figured it out. In Wikipedia it says "For example, in monochrome rendering, if the value of the pixel (scaled into the 0-9 range) is less than the number in the corresponding cell of the matrix, plot that pixel black, otherwise, plot it white." In js I got good results by averaging the current pixel (0-255) and the map's value (15-240) and comparing it to the threshold (normally 129):

var map = (imageData.data[currentPixel] + bayerThresholdMap[x%4][y%4]) / 2;
imageData.data[currentPixel] = (map < threshold) ? 0 : 255;

Here is my whole monochrome function with different algorithms:

var bayerThresholdMap = [
  [  15, 135,  45, 165 ],
  [ 195,  75, 225, 105 ],
  [  60, 180,  30, 150 ],
  [ 240, 120, 210,  90 ]
];

var lumR = [];
var lumG = [];
var lumB = [];
for (var i=0; i<256; i++) {
  lumR[i] = i*0.299;
  lumG[i] = i*0.587;
  lumB[i] = i*0.114;
}

function monochrome(imageData, threshold, type){

  var imageDataLength = imageData.data.length;

  // Greyscale luminance (sets r pixels to luminance of rgb)
  for (var i = 0; i <= imageDataLength; i += 4) {
    imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i+1]] + lumB[imageData.data[i+2]]);
  }

  var w = imageData.width;
  var newPixel, err;

  for (var currentPixel = 0; currentPixel <= imageDataLength; currentPixel+=4) {

    if (type === "none") {
      // No dithering
      imageData.data[currentPixel] = imageData.data[currentPixel] < threshold ? 0 : 255;
    } else if (type === "bayer") {
      // 4x4 Bayer ordered dithering algorithm
      var x = currentPixel/4 % w;
      var y = Math.floor(currentPixel/4 / w);
      var map = Math.floor( (imageData.data[currentPixel] + bayerThresholdMap[x%4][y%4]) / 2 );
      imageData.data[currentPixel] = (map < threshold) ? 0 : 255;
    } else if (type === "floydsteinberg") {
      // Floyd–Steinberg dithering algorithm
      newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
      err = Math.floor((imageData.data[currentPixel] - newPixel) / 16);
      imageData.data[currentPixel] = newPixel;

      imageData.data[currentPixel       + 4 ] += err*7;
      imageData.data[currentPixel + 4*w - 4 ] += err*3;
      imageData.data[currentPixel + 4*w     ] += err*5;
      imageData.data[currentPixel + 4*w + 4 ] += err*1;
    } else {
      // Bill Atkinson's dithering algorithm
      newPixel = imageData.data[currentPixel] < threshold ? 0 : 255;
      err = Math.floor((imageData.data[currentPixel] - newPixel) / 8);
      imageData.data[currentPixel] = newPixel;

      imageData.data[currentPixel       + 4 ] += err;
      imageData.data[currentPixel       + 8 ] += err;
      imageData.data[currentPixel + 4*w - 4 ] += err;
      imageData.data[currentPixel + 4*w     ] += err;
      imageData.data[currentPixel + 4*w + 4 ] += err;
      imageData.data[currentPixel + 8*w     ] += err;
    }

    // Set g and b pixels equal to r
    imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel];
  }

  return imageData;
}

I'd appreciate optimization hints.


Solution

  • Here are all of my monochrome dither functions, usable as a web worker: https://github.com/meemoo/meemooapp/blob/master/src/nodes/image-monochrome-worker.js

    Live demo with webcam: http://meemoo.org/iframework/#gist/3721129