Reducing colors of an Image?

What is the simplest way to reduce an 32-bit Image object to 256 colors so it can be transformed into a gif with the Acme gifencoder?
Who is Participating?

x

Commented:
Hi. Yep, been very busy.

But I now have a database update running that will take a while so can spare some time...

The hardest question is, qhich colours to use and choose. What colours of RGB to map to.

Well, if you are using these gifs on the web, the answer is done for you. You must extract each part of the RGB values and map them to those shown on this page

http://www.lynda.com/hexv.html

because this is the safe palette that is used for most browsers.

There are only 216 colours in the pallete due to system variations, but you can be sure that if you map a gif to these colours then it will remain the same on most or all platforms.

So every red, green or blue value can be mapped to one of the following six values:

255, 204, 153, 102, 51 or 0.

Does not sound like many colours but then 6*6*6 = 216. Any more than this and you go over the 256 limit.

http://www.lynda.com/hex.html

So, extract each colour component:

RED = rgb & 0xff0000
GREEN = rgb & 0x00ff00
BLUE = rgb & 0x0000ff

Map it to the nearest relevant value above.

ff = 255 so ff becomes ff and so does eveything else above about 230.

I say about 230 as you may need to fiddle with the range boundaries to get the best results.

Then continue for all the remaining 5 ranges so that all values between 229 and 170 become 204 and so on through all the colours.

Try this filter in the above code:

class ColourReduceFilter extends RGBImageFilter {
public ColourReduceFilter() {
// The filter's operation does not depend on the
// pixel's location, so IndexColorModels can be
// filtered directly.
canFilterIndexColorModel = true;
}

public int filterRGB(int x, int y, int rgb) {
return ( ( mapColour(rgb & 0xff000000) ) | // retain alpha transperency
( mapColour(rgb & 0xff0000) ) |
( mapColour(rgb & 0x00ff00) ) |
( mapColour(rgb & 0x0000ff) ) );
}

public int mapColour (int c) {
int mappedCol = 0;

if (c > 230)
mappedCol = 255;
else if (c > 170)
mappedCol = 204;
else if (c > 127)
mappedCol = 153;
else if (c > 75)
mappedCol = 102;
else if (c > 20)
mappedCol = 51;
else
mappedCol = 0;

return mappedCol;
}
}

I haven't compiled this so it may have some typos, but the general idea is sound.
0

Senior Developer/ArchitectCommented:
A tool I have used for some time on Unix is ImageMagick. Among many other capabilities, it will perform color reduction. Versions are available for most Unix systems and Linux as well as Windows (NT and 95), Macintosh, VMS, and OS/2. The package is free and can be downloaded (source and/or binary) from http://www.imagemagick.org/.

Best regards,
Jim Cakalic
0

Author Commented:
But I don't want to use an external software to convert my images. The 32-bit image is generated with Java and should then be converted to a gif with 256-colors...
0

Commented:
One possible solution based on image producers and consumers - there are others but this is perhaps the easiest to grasp at first.

This is some background on how it works...

Java's producer-consumer model makes it simple to create filters that provide many interesting image effects. Just to refresh your memory, an image producer provides the data for an image. An image consumer takes the image data and displays it. When you create an image from an URL, the data read from that URL serves as the image producer. When you create an image from an in-memory array, the MemoryImageSource is the image producer. To display an image, you connect an image producer to an image consumer and the image consumer displays the image. An image filter works like both a producer and a consumer. It acts like a consumer when it receives pixel data from the producer; then it acts like a producer when it sends the pixel data on to the consumer.

[In other words, a filter sits between a producer and a consumer and does something to the image when the producer passes it the data before the consumer gets the data to display it]

Have you tried using an RGBImageFilter? The following example is giving in the JDK docs:

FilteredImageSource is an implementation of the ImageProducer interface which takes an existing image and a filter object and uses them to produce image data for a new filtered version of the original image. Here is an example which filters an image by swapping the red and blue compents:

Image src = getImage("doc:///demo/images/duke/T1.gif");
ImageFilter colorfilter = new RedBlueSwapFilter();
Image img = createImage(new FilteredImageSource(src.getSource(), colorfilter));

where RedBlueSwapFilter is:

class RedBlueSwapFilter extends RGBImageFilter {
public RedBlueSwapFilter() {
// The filter's operation does not depend on the
// pixel's location, so IndexColorModels can be
// filtered directly.
canFilterIndexColorModel = true;
}

public int filterRGB(int x, int y, int rgb) {
return ((rgb & 0xff00ff00)
| ((rgb & 0xff0000) >> 16)
| ((rgb & 0xff) << 16));
}
}

As you can see, the int RGB variable consists of 4 parts:

0x means use hex notation.
Then the next two digits are the Alpha (giving 256 possible intensities for the Alpha channel)
Then the next two are the Red value of the pixel (giving 256 possible intensities) and the next two are the Green and the final two are the Blue.

So you extract from the int rgb variable the parts of the colour you are interested in and process them separately. This is how the RedBlueSwapFilter works...

If you understand that then you are nearly there.

What you need is a filter that maps all the image colours down to only 256. This is done quite simply by making each colour map down from a complete possible range of 0 - FF in hex (which is 24 bit colour - 8 bits for each colour giving 16 million colours) down to only a possible 256 colours in total.

I'd give you the answer now, but to be honest I'm actually just going to lunch :) so I'll have a look at writing you a complete solution later.

I also get the feeling that you could do this by manipulating a colormodel object or perhaps there is a relevant BufferedImageOp available but I have not got time to look into this in any great detail at the moment.

This should you get you started anyway,

Let me know if you have any further questions...

NOTE: You could probably use an external program from within your Java app to process the file if it supports working from the command line. Just save your 32bit image, run the external program from with in java, wait for it to finish and then pick up the converted file but this is not particularly neat - it depends on how you are using the program.

0

Commented:
HI JOD,

I am seeing Your comments after a long time

:)
0

Author Commented:
I haven't actually tried this yet, but it looks reasonable. But I guess I this "color reduction algorithm" won't be high-quality, but it guess it will work for my application...

What I really wanted to know was whether Java had some standard-class that could do the color-reduction for me. Maybe with error-difussion or some other color-reduction-algorithm...
0

Commented:
I have could not see an off the shelf solution to do what you want in Java but you would think there was woudn't you.

I guess the problem is not simple and the theory of colour conversion quite complex so I'm not sure if there is an easier way though there are certainly other ways. The problem of quality in the reduced colour image is always an issue and a dithering algorithm may be far more accurate. The above will not dither the image at all - you would need to extend the code considerably to do this effectively.

For logos and business style graphics the above will be fine. For pictures and photographic material the quality will vary wildly.

Anyway, some Colour conversion theory is here, for example:

http://java.sun.com/j2se/1.3/docs/guide/2d/spec/j2d-color.fm2.html

Not as straightforward as you might hope...

The code above is fairly basic but should do the job and it is pretty easy to do as well - only about twenty lines of code in total.

There is another even simpler way using a LookUpOp

BufferedImage destImg = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);

byte reverse[] = new byte[256];
for (int j=0; j<200; j++){
reverse[j]=(byte)(256-j);
}

ByteLookupTable blut=new ByteLookupTable(0, reverse);
LookupOp lop = new LookupOp(blut, null);
lop.filter(sourceImg,destImg);

This works by creatiung an array of 256 bytes each with the relevant colour value in for your new colour mapping. So the above example will copy sourceImg to destImg but reverse all the colours in the image by using a look up table of colours that is the wrong way round (goes from high to low instead of low to high).

0

Commented:
Perhaps look up the source of imagemagick from the link given above if you are feeling really brave and see what algorithms they use....
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.