Sunday, August 22, 2010

Manipulate PNG palette

I am not a graphic designer, thus I don't know how to create fancy icons/images
for my apps and Microsoft
Paint
is just enough for my skills.

For all my apps I've used black-and-white PNG files without transparency and that is ok, but... black and white?! I should be
adding colors.

Image class does not have a method to easily change colors. A workaround could be to call getRGB method and iterate the rgbData array.
A better way is to read the file content, change the palette bytes and create an image from the resulting data. First lets create a helper
method to read a whole InputStream and return a byte array with its content:


private byte [] readStream (InputStream in)
throws java.io.IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte [] buff = new byte [1024];
int size = in.read(buff);

while (size >= 0) {
baos.write(buff, 0, size);
size = in.read(buff);
}

return baos.toByteArray();
}

Next we have to find where is the palette chunk inside the byte array. Here is another helper method:


// return index where P of PLTE is found at buff array or -1
private int getPLTEIndex (byte [] buff) {
int i = -1;
// 4 == "PLTE".size()
if (buff != null && buff.length >= 4) {
boolean foundPalete = false;
boolean endOfBuff = false;
do {
i++;
foundPalete = buff[i] == 'P'
&& buff[i +1] == 'L'
&& buff[i +2] == 'T'
&& buff[i +3] == 'E';
endOfBuff = (i +4 >= buff.length);
} while (!foundPalete && !endOfBuff);
if (endOfBuff) {
i = -1;
}
}
return i;
}

And, finally, a method to change a color from the palette of PNG files with color type 3:


private byte [] setRGBColor (byte [] buff, int colorIndex, int colorNewValue) {
int i = getPLTEIndex(buff);
if (i >= 0) {
i += 4; // 4 == "PLTE".size()
i += (colorIndex * 3); // 3 == RGB bytes
if (i + 3 <= buff.length) {
buff[i] = (byte) (((colorNewValue & 0x00ff0000) >> 16) & 0xff);
buff[i +1] = (byte) (((colorNewValue & 0x0000ff00) >> 8) & 0xff);
buff[i +2] = (byte) ((colorNewValue & 0x000000ff) & 0xff);
}
}
return buff;
}

Below is a sample on how to use all the methods:

InputStream in = getClass().getResourceAsStream("/e.png");
try {
byte [] buff = readStream(in);
Image original = Image.createImage(buff, 0, buff.length);
buff = setRGBColor(buff, 0, 0x00ff0000); // set 1st color to red
buff = setRGBColor(buff, 1, 0x0000ff00); // set 2nd color to green
Image updated = Image.createImage(buff, 0, buff.length);
} catch (IOException ex) {
ex.printStackTrace();
}

Related topics: