这个算法是参考一位高人的文章,直接读取并修改png格式图片的调色板,然后生成新的调色板替代原来的。
这样可以实现游戏中常见的变色效果,可以解决游戏容量有限,不能存放太多精灵图片的问题。
具体过程其实并不复杂,大家可以先搜索资料,先看看png图片的格式定义。这个算法正是找到调色板区,根据原有格式修改之后,生成新的crc校验码,然后替换原来的调色板。这样就可以用一个png图片,创建多个变色副本。
public class PalettedImage {
public Image getPalettedImage(byte[] data, int[] originalColors,
int[] palettedColors) {
byte[] tempData = new byte[data.length];
System.arraycopy(data, 0, tempData, 0, data.length);
Image img = null;
int[] parameter = {0, 0, 0};
analyze(tempData, parameter);
for (int i = 0; i < originalColors.length; i++) {
replaceColor(tempData, parameter, originalColors[i],
palettedColors[i]);
}
fillData(tempData, parameter);
try {
img = Image.createImage(tempData, 0, data.length);
} catch (Exception e) {
System.out.println("getPalettedImage && " + e.toString());
}
return img;
}
private void analyze(byte[] data, int[] para) {
int offset = 8;
int chunkLen = 0;
while (data[offset + 4] != 0x50 || data[offset + 5] != 0x4c
|| data[offset + 6] != 0x54 || data[offset + 7] != 0x45) {
chunkLen = readInt(data, offset);
offset += (4 + 4 + chunkLen + 4);
}
chunkLen = readInt(data, offset);
para[2] = chunkLen / 3;
para[0] = offset + 8;
para[1] = offset + 8 + chunkLen;
}
private int readInt(byte[] data, int offset) {
return ((data[offset] & 0xFF) << 24)
| ((data[offset + 1] & 0xFF) << 16)
| ((data[offset + 2] & 0xFF) << 8) | (data[offset + 3] & 0xFF);
}
private void replaceColor(byte[] data, int[] para, int oldColor,
int newColor) {
byte rr = (byte) ((oldColor >> 16) & 0xff);
byte gg = (byte) ((oldColor >> 8) & 0xff);
byte bb = (byte) (oldColor & 0xff);
for (int i = 0, offset = para[0], temp = 0; i < para[2]; i++,
offset += 3) {
if (rr == data[offset] && gg == data[offset + 1]
&& bb == data[offset + 2]) {
data[offset] = (byte) ((newColor >> 16) & 0xff);
data[offset + 1] = (byte) ((newColor >> 8) & 0xff);
data[offset + 2] = (byte) (newColor & 0xff);
break;
}
}
}
private void fillData(byte[] data, int[] para) {
int checksum = update_crc(data, para[0] - 4, para[2] * 3 + 4);
data[para[1]] = (byte) ((checksum >> 24) & 0xff);
data[para[1] + 1] = (byte) ((checksum >> 16) & 0xff);
data[para[1] + 2] = (byte) ((checksum >> 8) & 0xff);
data[para[1] + 3] = (byte) ((checksum) & 0xff);
}
private int update_crc(byte[] buf, int off, int len) {
int c = 0xffffffff;
int n, k;
int xx;
int[] crc_table = new int[256];
for (n = 0; n < 256; n++) {
xx = n;
for (k = 0; k < 8; k++) {
if ((xx & 1) == 1) {
xx = 0xedb88320 ^ (xx >>> 1);
} else {
xx = xx >>> 1;
}
}
crc_table[n] = xx;
}
for (n = off; n < len + off; n++) {
c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >>> 8);
}
return (c ^ 0xffffffff);
}
}
接口就是getPalettedImage()函数,只需要输入原始图片的byte数组,以及需要替换颜色的颜色值还有目标颜色值就行了。因为可以同时替换多个颜色,所以输入参数是代表颜色的整形的数组。总之,要保证原始颜色与目标颜色一一对应就好了。方法简单实用。
昨天研究了BB同志发的算法一文,实验了一晚上,终于有点收获,发出来给大家共享.
我查阅了很多关于Png格式的文章,最后得到的心得就是,对于"索引类型的图片",即肯定包含PLTE调色板的Png图片,就可以通过对调色板的修改,再通过CRC算法生成新的验证码,再还原成新图片就可以得到所要的效果,为此,我写了如下的类.
/*
用于PNG图片变色的类
使用要求,需要美工将原始图片的需要换色的点
设置为纯红色0xff0000
*/
import java.io.*;
import java.lang.*;
import javax.microedition.lcdui.*;
public class coloredImage {
public coloredImage() {
}
public Image getColoredImage(String s, int newcolor) {
try {
byte[] pixel;
InputStream is = getClass().getResourceAsStream(s);
int i = 0;
while (is.read() != -1) {
i++;
}
pixel = new byte[i];
is = null;
is = getClass().getResourceAsStream(s);
is.read(pixel);
imgConvert(pixel, newcolor);
return (Image.createImage(pixel, 0, pixel.length));
} catch (Exception e) {
return null;
}
}
public void imgConvert(byte content[], int color) {
try {
int start = 0;
int newcolor = -1;
for (int idx = 0; idx < 8; idx++) {
if (content[idx] == 0x50 && content[idx + 1] == 0x4c
&& content[idx + 2] == 0x54 && content[idx + 3] == 0x45) {
start = idx;
}
}
for (int idx = 0; idx < 4; idx++) {
newcolor = pixelConvert(content[start + idx], newcolor);
}
int r, g, b, length;
length = (content[start - 4] & 0xff << 24) |
(content[start - 3] & 0xff << 16) |
(content[start - 2] & 0xff << 8) |
(content[start - 1] & 0xff);
for (int i = 0; i < length; i++) {
r = content[start + 4 + i] & 0xff;
g = content[start + 4 + i + 1] & 0xff;
b = content[start + 4 + i + 2] & 0xff;
if (r == 255 && g == 0 && b == 0) {
r = color >> 16 & 0xff;
g = color >> 8 & 0xff;
b = color & 0xff;
content[start + 4 + i] = (byte) r;
content[start + 4 + i + 1] = (byte) g;
content[start + 4 + i + 2] = (byte) b;
}
newcolor = pixelConvert(content[start + 4 + i], newcolor);
newcolor = pixelConvert(content[start + 4 + i + 1], newcolor);
newcolor = pixelConvert(content[start + 4 + i + 2], newcolor);
}
newcolor = ~newcolor;
content[start + 4 + length] = (byte) (newcolor >> 24);
content[start + 4 + length + 1] = (byte) (newcolor >> 16);
content[start + 4 + length + 2] = (byte) (newcolor >> 8);
content[start + 4 + length + 3] = (byte) (newcolor);
} catch (Exception e) {}
}
/**
* CRC检验算法
* @param pixel 像素
* @param color 颜色值
* @return
*/
public int pixelConvert(byte pixel, int color) {
int tmp = pixel & 0xff;
color ^= tmp;
for (int idx = 0; idx < 8; idx++) {
if ((color & 1) != 0) {
color = color >>> 1 ^ 0xedb88320;
} else {
color >>>= 1;
}
}
return color;
}
}
这个类提供了一个转换图片颜色的方法getColoredImage,只要将源图片的路径以及需要转换的新颜色作为参数调用就可以得到转换后的图片.
相应的我把我的主类写出来供大家参考其用法.
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;
import com.nokia.mid.ui.*;
import java.io.*;
public class pengzhuang extends MIDlet {
class test extends FullCanvas {
Image[] a;
byte[] pix;
coloredImage ci;
public test() {
ci = new coloredImage();
a = new Image[4];
a[0] = ci.getColoredImage("/char.png", 0x0000ff);
a[1] = ci.getColoredImage("/char.png", 0x00ff00);
a[2] = ci.getColoredImage("/char.png", 0xffffff);
a[3] = ci.getColoredImage("/char.png", 0x00ffff);
}
public void keyPressed(int i) {
}
public void paint(Graphics g) {
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(a[0], 0, 0, 0);
g.drawImage(a[1], 30, 30, 0);
g.drawImage(a[2], 60, 60, 0);
g.drawImage(a[3], 90, 90, 0);
}
}
private Display display;
test t;
public pengzhuang() {
try {
t = new test();
} catch (Exception e) {
}
display = Display.getDisplay(this);
}
public void startApp() {
display.setCurrent(t);
}
public void pauseApp() {
}
public void destroyApp(boolean boo) {
}
}
其中用到的源图是
运行程序后的效果为