This is an archive of the old MediaWiki-based ImageJ wiki. The current website can be found at imagej.net.

Dithering

Dithering (ImageJ)
Author Gabriel Landini
Maintainer Gabriel Landini (G.Landini at bham. ac. uk)
File Dithering_.txt
Source See below
Latest version v1.0 (17 May 2009)
Development status active


Purpose

This macro takes an 8 bit greyscale or a 24 bit RGB image and performs dithering using one of the methods supported. Dithering is a type of half tone thresholding where greyscale (or RGB channel) intensity is converted into a local density of binary pixels. This is ideal for rendering images in devices with a binary output such as printers (greyscale) or with a small number of colours (colour dithering).

In the case of 24 bit RGB images the macro lets you either apply dithering to the greyscale version of the image or to each of the RGB channels (colour dithering).

Installation

Copy the source code below and save it into a file called "Dithering_.txt" somewhere in the /Plugins folder. Restarting Fiji or ImageJ will show a new command called "Dithering".

Available methods

The following methods have been implemented (there are several more): Floyd-Steinberg, Atkinson, Jarvis-Judice-Ninke, Stucki, Bayer_2x2, Bayer_4x4, Bayer_8x8, Clustered_4x4 and Random.
Scaling dithered images by certain factors introduce numerous artifacts, so it is essential to apply dithering after image resizing.
Dithering results are best rendered when the image is displayed or printed using small pixels.

The following images have been magnified by a factor of 2 to show the individual pixels.

Error creating thumbnail: Unable to save thumbnail to destination

Macro code


// Dithering
// G. Landini at bham. ac. uk
// 17/ May 2009

Dialog.create("Dithering");
items=newArray("Floyd-Steinberg", "Atkinson", "Jarvis-Judice-Ninke","Stucki",
        "Bayer_2x2", "Bayer_4x4", "Bayer_8x8", "Clustered_4x4", "Random");
doColour = false;

Dialog.addChoice("Method", items);
if (bitDepth==24) Dialog.addCheckbox("Colour dithering", false);
Dialog.show() ;
method=Dialog.getChoice();
if (bitDepth==24)
   doColour=Dialog.getCheckbox();

setBatchMode(true);
w=getWidth();
h=getHeight();
run("Duplicate...", "title="+method);
if (bitDepth == 24 && doColour) {
   run("RGB Stack");
   for (i=1; i<4; i++){
     showProgress(i/4);
     setSlice(i);
     dither(method);
   } 
   run("RGB Color");
}
else{
  if (bitDepth>8 && !doColour) run("8-bit");
  dither(method);
}
setBatchMode(false);

function dither(method) {
 if (method=="Floyd-Steinberg"){
  w1=7/16;
  w2=3/16;
  w3=5/16;
  w4=1/16;
  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      oldpixel = getPixel(x,y);
      if (oldpixel<128) newpixel=0; else newpixel=255;
      setPixel(x,y, newpixel);
      quant_error = oldpixel - newpixel;
      setPixel(x+1,y, getPixel(x+1,y) + w1 * quant_error);
      setPixel(x-1,y+1, getPixel(x-1,y+1) + w2 * quant_error);
      setPixel(x,y+1, getPixel(x,y+1) + w3 * quant_error);
      setPixel(x+1,y+1, getPixel(x+1,y+1) + w4 * quant_error);
    }
  }
 }
 else if (method=="Atkinson"){
  w1=1/8;
  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      oldpixel = getPixel(x,y);
      if (oldpixel<128) newpixel = 0; else newpixel=255;
      setPixel(x,y, newpixel);
      quant_error = oldpixel - newpixel;
      setPixel(x+1,y, getPixel(x+1,y) + w1 * quant_error);
      setPixel(x+2,y, getPixel(x+2,y) + w1 * quant_error);
      setPixel(x-1,y+1, getPixel(x-1,y+1) + w1 * quant_error);
      setPixel(x,y+1, getPixel(x,y+1) + w1 * quant_error);
      setPixel(x+1,y+1, getPixel(x+1,y+1) + w1 * quant_error);
      setPixel(x,y+2, getPixel(x,y+2) + w1 * quant_error);
    }
  }
 }
 else if (method=="Jarvis-Judice-Ninke"){
  w7=7/48;
  w5=5/48;
  w3=3/48;
  w1=1/48;
  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      oldpixel = getPixel(x,y);
      if (oldpixel<128) newpixel = 0; else newpixel=255;
      setPixel(x,y, newpixel);
      quant_error = oldpixel - newpixel;
      setPixel(x+1,y, getPixel(x+1,y) + w7 * quant_error);
      setPixel(x+2,y, getPixel(x+2,y) + w5 * quant_error);
      setPixel(x-2,y+1, getPixel(x-2,y+1) + w3 * quant_error);
      setPixel(x-1,y+1, getPixel(x-1,y+1) + w5 * quant_error);
      setPixel(x,y+1, getPixel(x,y+1) + w7 * quant_error);
      setPixel(x+1,y+1, getPixel(x+1,y+1) + w5 * quant_error);
      setPixel(x+2,y+1, getPixel(x+2,y+1) + w3 * quant_error);
      setPixel(x-2,y+2, getPixel(x-2,y+2) + w1 * quant_error);
      setPixel(x-1,y+2, getPixel(x-1,y+2) + w3 * quant_error);
      setPixel(x,y+2, getPixel(x,y+2) + w5 * quant_error);
      setPixel(x+1,y+2, getPixel(x+1,y+2) + w3 * quant_error);
      setPixel(x+2,y+2, getPixel(x+2,y+2) + w1 * quant_error);
    }
  }
 }
 else if (method=="Stucki"){
  w8= 8/42;
  w7=7/42;
  w5=5/42;
  w4= 4/42;
  w2=2/42;
  w1=1/42;
  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      oldpixel = getPixel(x,y);
      if (oldpixel<128) newpixel = 0; else newpixel=255;
      setPixel(x,y, newpixel);
      quant_error = oldpixel - newpixel;
      setPixel(x+1,y, getPixel(x+1,y) + w7 * quant_error);
      setPixel(x+2,y, getPixel(x+2,y) + w5 * quant_error);
      setPixel(x-2,y+1, getPixel(x-2,y+1) + w2 * quant_error);
      setPixel(x-1,y+1, getPixel(x-1,y+1) + w4 * quant_error);
      setPixel(x,y+1, getPixel(x,y+1) + w8 * quant_error);
      setPixel(x+1,y+1, getPixel(x+1,y+1) + w4 * quant_error);
      setPixel(x+2,y+1, getPixel(x+2,y+1) + w2 * quant_error);
      setPixel(x-2,y+2, getPixel(x-2,y+2) + w1 * quant_error);
      setPixel(x-1,y+2, getPixel(x-1,y+2) + w2 * quant_error);
      setPixel(x,y+2, getPixel(x,y+2) + w4 * quant_error);
      setPixel(x+1,y+2, getPixel(x+1,y+2) + w2 * quant_error);
      setPixel(x+2,y+2, getPixel(x+2,y+2) + w1 * quant_error);
    }
  }
 }
 else if (method=="Bayer_2x2"){
  d= newArray(2,3,4,1);
  for (i=0; i<d.length; i++)
    d[i]=d[i] * 64 - 1;

  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      if (getPixel(x,y)>=d[(y%2*2+x%2)])
          newpixel = 255; 
      else 
          newpixel=0;
      setPixel(x,y, newpixel);
    }
  }
 }
 else if (method=="Bayer_4x4"){
  d= newArray(0.1250, 1.0000, 0.1875, 0.8125, 0.6250, 0.3750, 0.6875,
     0.4375,0.2500, 0.8750, 0.0625, 0.9375, 0.7500, 0.5000, 0.5625, 0.3125);
  for (i=0; i<d.length; i++)
    d[i]*=255;
  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      if (getPixel(x,y)>=d[(y%4*4+x%4)])
          newpixel = 255; 
      else 
          newpixel = 0;
      setPixel(x,y, newpixel);
    }
  }
 }
 else if (method=="Bayer_8x8"){
  d= newArray(1, 33,  9, 41, 3,  35, 11, 43, 49, 17, 57, 25, 51, 19, 59, 27, 13, 45, 5, 
    37, 15, 47, 7, 39, 61, 29, 53, 21, 63, 31, 55, 23, 4, 36, 12, 44, 2, 34, 10, 42, 52,
    20, 60, 28, 50, 18, 58, 26, 16, 48, 8, 40, 14, 46, 6, 38, 64, 32, 56, 24, 62, 30,
    54, 22);

  for (i=0; i<d.length; i++)
    d[i]=d[i] * 4 - 1;

  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      if (getPixel(x,y)>=d[(y%8*8+x%8)])
          newpixel = 255; 
      else 
          newpixel = 0;
      setPixel(x,y, newpixel);

    }
  }
 }
 else if (method=="Clustered_4x4"){
  d= newArray(0.7500, 0.3750, 0.6250, 0.2500, 0.0625, 1.0000, 0.8750, 0.4375,
    0.5000, 0.8125, 0.9375, 0.1250, 0.1875, 0.5625, 0.3125, 0.6875);
  for (i=0; i<d.length; i++)
    d[i]*=255;

  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      if (getPixel(x,y)>=d[(y%4*4+x%4)])
          newpixel = 255; 
      else 
          newpixel = 0;
      setPixel(x,y, newpixel);
    }
  }
 }
else if (method=="Random"){
  w1=1/8;
  for (y=0; y<h; y++){
    for (x=0; x<w; x++){
      if(getPixel(x,y)> random*255)
        setPixel(x,y, 255);
      else   
        setPixel(x,y, 0);
    }
  }
 }
}