Page history Edit this page How do I edit this website?
Original MediaWiki page

Dithering

Name

Dithering

Software

ImageJ

Author

Gabriel Landini

Maintainer

Gabriel Landini (G.Landini at bham. ac. uk)

File

Dithering_.txt

Source

See below

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 “/plugins/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.

Macro code

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

Dialog.create("/plugins/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);
    }
  }
 }
}