Tuesday, May 20, 2008

Tuesday, August 15, 2006

Changing colors of an image in real time

Recently I needed the ability to change colors of a picture in real-time. I was developing a game where I needed to paint the player costumes many times within the game. I have researched on the internet and didn't find any J2ME code available.

I digged all around for an algorithm and preferrably Java-compatible code that changed colors.

After reading some codes that did the trick in other languages, I finally figured out how to create a routine to change the colors. The basics behind it is the following:

The MIDP 2.0 API has the ability to extract an array of RBG data from an image by calling the Image's getRGB() method. This method takes the following parameters:

getRGB(int[] rgbData, int offset, int scanlength, int x, int y, int width, int height)

Where:
rgbData - an array of integers in which the ARGB pixel data is stored
offset - the index into the array where the first ARGB value is stored
scanlength - the relative offset in the array between corresponding pixels in consecutive rows of the region
x - the x-coordinate of the upper left corner of the region
y - the y-coordinate of the upper left corner of the region
width - the width of the region
height - the height of the region
So, to get all image data you need:

int[] raw = new int[image.getWidth() * image.getHeight()];
image.getRGB(raw, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight());


What you have now is an array of integers representing each pixel of the image in the AARRGGBB format where RR is red, GG is green, BB is blue and the leading AA is alpha). The alpha attibute defines the opacity of that particular pixel and 00 means completly transparent and FF (255) means completly opaque (solid).

So, to change colors we need to change all pixels that matches an RGB value regardless of what the alpha byte is. To do that we need to "mask" the pixel value with the "&" operator. The first mask we need is to extract (and later on preserve) the alpha value. This mask is 0xff000000 and, as you probably noticed, the ff matches the AA position on the integer (it is the leftmost byte on the integer).

Once we have the alpha value, we need to mask the integer again to extract the RGB color. The mask to do that is the opposite of the previous mask: 0x00ffffff. This mask drops the leftmost byte and extracts the desired RGB value.

Now that you have the alpha value and the RGB value, all you need to do is iterate throughout the pixels array and match the RGB color with the one you are replacing. This step is illustrated on the following snippet:

// replace the colors
for (int i = 0; i < raw.length; i++) {
    int currentPixel = raw[i];
    int currentMaskedPixel = currentPixel & noAlphaBitmask;

    // check each color to replace
    for (int j = 0; j < maskedOldColor.length; j++) {
        if (currentMaskedPixel == maskedOldColor[j]) {
            raw[i] =
                (currentPixel & alphaOnlyBitmask) |
                maskedNewColor[j];
            break;
        }
    }
}