How to apply color quantization to an image

Is there a simple way (preferably a function) to apply color quantization to an image? Specifically I’d like to pick a PImage and an int of colours, and it’d reduce the number of colours to that int, trying to keep it as close as possible to the original.

For reference, imagemagick can do it easily (I’d link to it, but discourse isn’t letting me): magick input.png +dither -colors 6 output.png outputs an image with 6 colours. Not shades, but colours. The +dither is actually not doing dithering but banding, which is closer to what I want.

1 Like

I think posterize is more or less what you want:

POSTERIZE
Limits each channel of the image to the number of colors specified as the parameter. The parameter can be set to values between 2 and 255, but results are most noticeable in the lower ranges.

See https://processing.org/reference/filter_.html

It’s not. Posterize doesn’t allow for exact control over the number of colours the image will end up with.

You have that library: https://github.com/milchreis/processing-imageprocessing

The quantization supported by that library is essentially the same as posterize. It doesn’t allow reducing the image to a specified number of colours.

http://www.java2s.com/Code/Java/2D-Graphics-GUI/Anefficientcolorquantizationalgorithm.htm

2 Likes

Could I please get a bit of guidance on how to apply that in processing? Adding that code gives a ton of errors in the IDE (related to static and colour). Even if I fix those, it’s unclear how to apply the code to a PImage.

Paste that code into a file named “Quantize.java”, and place it into the same folder as your “.pde” file(s). :innocent:

1 Like

Yes, that’s how far I got before. But it doesn’t work right off the bat (as I mentioned, it complains about static and later color, because it tries to assign it to an int). Even if I fix those (removing all static and renaming color to colour), which I’m not sure is the correct way, I still have no idea how to apply it to a PImage.

I’d also take a tutorial that explains how to take generic Java code and make it Processing-compatible.

1 Like
1 Like

How does one use that? I’ve tried

PImage myImage = new Palette(6, "/path/to/image").img;
image(myImage, 0, 0, 600, 600);

and all I get is a gradient of colours.

Why does the class Col exist? It doesn’t seem to be doing anything useful. Same with the addColor function.

Basically the technique is to measure the distance between RGB colors as if they were points in space.

In this example you first create a palette by measuring the distance between each color and black (0,0,0). Then you can evaluate any new colors by returning the palette color they’re closest to.

The Col class is used for making that calculation–it contains a color and a distance measurement. You could do the same with an array of colors and an array of floats, but I find this approach easier.

That explains what the code does (though I still don’t get why have an extra class for Col instead of keeping everything in the same one), but not how to use it.

So are you saying your code doesn’t actually spit out the new image with reduced colours?

Hmm. Just eyeballing it, it looks like Palette.img is a PImage (line 19), and it is populated from the Pallete.colors array by the Palette.createImage() method. When you call Palette.sample(), it writes values into colors[], then calls createImage() which copies them into img. You can now access that image. If you want, you could modify createImage() – and sample() – to return it, rather than void.

That’s what I tried, but all I get is a gradient.

Ah, I thought you wanted the new reduced colors, in a PImage. You want the old image, but now with colors reduced, yes?

Yes.

addColor() is for if you want to build a palette with manually selected colors vs. sampling a source image. Probably not relevant to your project.

One approach would be to use ToxicLibs: https://github.com/postspectacular/toxiclibs/blob/master/examples/color/ImageColors/ImageColors.pde

The tricky part is that to calculate the histogram it uses a float tolerance value, and it’s not known in advance how many colors you will get. If performance is not so important, you could run Histogram.newFromARGBArray multiple times, tweaking the tolerance until you get the number of colors you want.

In C++ I’ve used https://github.com/exoticorn/exoquant You could port that to Java. It allows you to specify the exact number of colors.

One more approach is to avoid reinventing the wheel and call a command line program like imagemagick (if that is allowed by your project’s constraints).

2 Likes