How to apply color quantization to an image

This is an interesting problem and I used to use it years ago to keep image file sizes down but I can’t remember what software I used. So it piqued my interest enough to create a sketch that uses the Quantize class code in the link provided by @jb4x (thanks).

So here is the result ouput from my sketch
sample

I have simply created a new tab called Quantize.java and copied the code from the link into it. NOTE that the tab name must match precisely the class name to make it a top level class.
The Quatize class expects the pixel data to be in a 2D array wheres PImage stores pixel data in a 1D array so I have created a class called QImage which performs all the conversion and use the Quatize class to do its stuff.

Anyway the main tab sketch code is shown below but if you want you can download the entire sketch from here

Enjoy :joy:

PImage img;
QImage qimg256, qimg64, qimg16;

void setup() {
  size(520, 400);
  img = loadImage("london.jpg");
  textSize(14);
  fill(0);
  background(255);
  // Make sonme quantized images
  qimg256 = new QImage(img, 256);
  qimg64 = new QImage(img, 64);
  qimg16 = new QImage(img, 16);

  text("32 bit ARGB", 10, 18);
  image(img, 0, 20);
  text("256 colors", 270, 18);
  image(qimg256.getImage(), 260, 20);

  text("64 colors", 10, 218);
  image(qimg64.getImage(), 0, 220);
  text("16 colors", 270, 218);
  image(qimg16.getImage(), 260, 220);
  save("sample.png");
}

/**
 This class is used to store the result of the image quantization.
 The final image comprises a color table and a 2D array containing 
 an index into the color table.
 It also creates a new PImage with the reduced color set for 
 convenience.
 */
public class QImage {
  final int[][] pixels;
  final int w, h;
  final int[] colortable;
  final PImage reducedImage;

  /**
   img = the PImage we want to quantize
   maxNbrColors - color table size
   */
  public QImage(PImage img, int maxNbrColors) {
    // Pixel data needs to be in 2D array for Quantize class.
    w = img.width;
    h = img.height;
    pixels = new int[h][w];
    img.loadPixels();
    int[] p = img.pixels;
    int n = 0;
    for (int y = 0; y < h; y++) {
      for (int x = 0; x < w; x++) {
        pixels[y][x] = p[n++];
      }
    } 
    // Quantize the image
    colortable = Quantize.quantizeImage(pixels, maxNbrColors);
    //Create a PImage with the reduced color pallette
    reducedImage = createImage(w, h, ARGB);
    for (int y = 0; y < h; y++) {
      for (int x = 0; x < w; x++) {
        reducedImage.set(x, y, colortable[pixels[y][x]]);
      }
    }
  }

  /**
   Convenience method to draw the quatized image at a 
   given position.
   */
  public void displayRAW(int px, int py) {
    for (int y = 0; y < h; y++) {
      for (int x = 0; x < w; x++) {
        set(px + x, py+y, colortable[pixels[y][x]]);
      }
    }
  }

  /**
   Get the pixel color index data
   */
  public int[][] getPixels() {
    return pixels;
  }

  /**
   Get the color table data
   */
  public int[] getColorTable() {
    return colortable;
  }

  /**
   Get the maximum number of colors in the reduced image.
   The actual number of unique colors maybe less than this.
   */
  public int nbrColors() {
    return colortable.length;
  }

  /**
   Convenience method to get the quatized image as a PImage
   that can be used directly in processing
   */
  public PImage getImage() {
    return reducedImage;
  }
}
4 Likes

Nice, @quark!

This could also be done with the ImageJ implementation of Median Cut.

The nice thing about that implementation is it is based on java.awt.Image, so it takes a pixels[] array – no 2D conversion class required.

2 Likes

In the other thread, @Kevin also shares some code he wrote to do k-means color clustering using opencv.Mat.

Cool,this is exactly what I was looking for but how can I save a png image with a 16 color indexed palette ?

Although PNG supports 16 color indexed images I have no idea how you might actually save the image in this format.

No matter what file format an image is saved in, when loaded into a computer it will be converted to ARGB (32bit color) for display. So I am not sure why you would want to bother with an indexed palette.

Seems to be two possibilities

  1. find a library that does this for you
  2. create your own low level routine to save the image data in that format.
2 Likes

Halfway between a library and your own image format routine – there was also a solution by @Architector_4 a while back that involved byte-copying a specific palette onto an image file, although that was for a pretty specific problem.

https://forum.processing.org/two/discussion/28065/how-to-save-image-with-different-settings-bit-depth-etc.

As a little update, I guess it’s worth noting that since then I’ve migrated my solution to pure Java and put it into a GitHub repo. It takes in an image, parses it as 24 bits per pixel, and manually byte-by-byte constructs an 8-bit indexed BMP image with a hardcoded palette of 256 colors.

I don’t know the PNG format enough, but assuming that it isn’t too complex, I guess a similar solution could be used here aswell.

1 Like