两种修改png图片颜色方法的对比

两种修改 png 图片颜色方法的对比
在手机游戏开发中,为了节省资源,我们常常修改png图片以实现一张图片多种显示效果。有两种办法可以实现这个功能:
第一种是装载png图片,使用getRGB()取得取得图片的RGB颜色数据,然后修改RGB颜色数据,再用Image的静态方法createRGBImage()将修改后的RGB颜色数据生成新的png图片。示例代码:testImage为测试的Image对象,imgW,imgH为其宽和高
// 取得图片的RGB数据--这个数组是比较大的,因此修改RGB数据生成新的图片效率很低
rgbData = new int[imgW * imgH];
testImage.getRGB(rgbData,0,imgW,0,0,imgW,imgH);
// 修改RGB数据并生成新的图片
rgbImage = Image.createRGBImage(changeRGBData(rgbData),imgW,imgH,true);
第二中方法是取得png图片的二进制数据,修改其中的调色板域(PLTE chunk)数据,再使用
Image的静态对象createImage(byte[] imageData,int imageOffset,int imageLength)将修改后的二进制数据生成新的png对象。示例代码:查看代码中的getPLTEModifidImage()函数。
如果对png的数据格式组成不熟悉,可以查阅: http://www.w3.org/TR/PNG/
或者(不错的中文介绍): http://www.ismyway.com/png/png-struct1.htm
通常,图片的RGB颜色数据是比较大的,而调色板数据远比RGB颜色数据要少的多,修改RGB颜色数据效率要高的多。下面我使用一张大小为52*28的png图片作为测试图片,从以下测试中打印的数据就可看出(1456对比144):
Load file Gold total data is 1084 ――――――――图片的二进制字节数据个数
--------RGB data length is 1456―――――――――RGB颜色字节数据个数
--------The number of PLTE chunks is 48―――调色板数据块个数(实际字节数据为 48*3
效果图:
附测试的源代码:
//-----------------------------------------------------Code--START------------------------------------------------
/**
* Dicription : Create png image by modifying PLTE chunk.
* Author : 飘飘白云
* Created date : 2006-07-03
* Modified history :
*/
import java.io.DataInputStream;
import java.io.IOException;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class CreatePNG extends MIDlet
{
GameCanvas canvas;
Display display;
public CreatePNG()
{
super();
// TODO Auto-generated constructor stub
try {
display = Display.getDisplay(this);
canvas = new GameCanvas(this);
} catch (Exception e) {}
}
protected void startApp() throws MIDletStateChangeException
{
// TODO Auto-generated method stub
if( canvas != null ){
display.setCurrent(canvas);
canvas.start();
}
}
protected void pauseApp()
{
// TODO Auto-generated method stub
canvas.beExit = true;
}
protected void destroyApp( boolean arg0) throws MIDletStateChangeException
{
// TODO Auto-generated method stub
}
}
class GameCanvas extends Canvas implements Runnable
{
CreatePNG app;
Thread mainThread;
Image offImage = null;
public Graphics g = null;
public boolean beExit = false;
long sleepTime = 30;
int scrW;
int scrH;
Font font;
int fontH;
int fontW;
//-------------------------------------
int gameMode;
int subMode;
static final int smInit = 0;
static final int smProc = 1;
static final int smEnd = 2;
static final int gmStart = 0;
static final int gmProcPNGData = 1;
//--------------------------------------
Image testImage = null;
Image rgbImage = null;
Image plteImage = null;
int imgW;
int imgH;
int[] rgbData = null;
byte[] imgData = null;
String testFile = "Gold";
public GameCanvas(MIDlet midlet)
{
app = (CreatePNG)midlet;
}
public void start()
{
changeGameMode(gmStart);
mainThread = new Thread(this);
mainThread.start();
}
public void changeGameMode(int gm)
{
gameMode = gm;
subMode = smInit;
}
protected void paint( Graphics _g)
{
// TODO Auto-generated method stub
if( offImage != null)
_g.drawImage(offImage, 0, 0, 0);
}
public void run()
{
// TODO Auto-generated method stub
gameInit();
while( !beExit )
{
gameMain();
gameDraw();
repaint();
gcWait(sleepTime);
}
if( beExit ) {
try {
app.destroyApp(true);
}
catch ( MIDletStateChangeException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
void gcWait(long tm)
{
System.gc();
try {
Thread.sleep(tm);
}
catch ( InterruptedException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
void println(String str)
{
System.out.println( str );
}
void print(String str)
{
System.out.print( str );
}
void gameInit()
{
scrW = getWidth();
scrH = getHeight();
if(g == null || offImage == null) {
offImage = Image.createImage(scrW, scrH);
g = offImage.getGraphics();
g.translate(0,0);
}
repaint();
font = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN,Font.SIZE_MEDIUM);
g.setFont(font);
changeGameMode( gmStart );
}
/**
* 装载图片
* @param fileName 图片名称
* @return image 对象
*/
Image loadImage( String fileName)
{
Image tmp = null;
try {
tmp = Image.createImage("/" + fileName + ".png");
}
catch ( IOException e ) {
println("[Error] >> loadImage(): " + fileName + " Failed!");
e.printStackTrace();
}
if( tmp == null ) {
println("[Error] >> loadImage(): " + fileName + " Failed!");
}
return tmp;
}
/**
* 读取文件的二进制数据
* @param fileName 文件名
* @return 文件二进制数据的字节数组.
*/
byte[] loadFile(String fileName)
{
DataInputStream isr = null;
byte[] bData;
try {
try {
isr = new DataInputStream(getClass().getResourceAsStream("/" + fileName + ".png"));
}
catch (Exception e) {
}
int res_size = 0;
while (isr.read() != -1)
res_size++;
if (isr.markSupported())
isr.reset();
else {
isr.close();
isr = new DataInputStream(getClass().getResourceAsStream("/" + fileName + ".png"));
}
bData = new byte[res_size];
int read_bytes = isr.read(bData, 0, res_size);
isr.close();
System.gc();
println("Load file " + fileName + " total data is " + read_bytes);
return bData;
}
catch (Exception e) {
println("Error>>loadFile " + fileName);
return null;
}
}
void gameMain()
{
if( gameMode == gmStart )
{
if( subMode == smInit )
{
// 装载图片
testImage = loadImage(testFile);
imgW = testImage.getWidth();
imgH = testImage.getHeight();
subMode = smProc ;
}
else if( subMode == smProc )
{
// 取得图片的RGB数据--这个数组是比较大的,因此修改RGB数据生成新的图片效率很低
rgbData = new int[imgW * imgH];
testImage.getRGB(rgbData,0,imgW,0,0,imgW,imgH);
println("--------RGB data length is - "+ rgbData.length);
changeGameMode(gmProcPNGData);
}
}
else if( gameMode == gmProcPNGData )
{
if( subMode == smInit )
{
// 取得图片的二进制数据
imgData = loadFile(testFile);
subMode = smProc ;
println("----Start---All Image Data--length-" + imgData.length);
for( int i = 0; i< imgData.length; i++ ){
if( i% 10 == 0 )
println("");
System.out.print(" " + imgData[i]);
}
println("/n----End---All Image Data--length-" + imgData.length);
// 修改调色板数据,改变图片颜色,这种方法修改的数据远比修改RGB数据要少,效率高很多
plteImage = getPLTEModifidImage(imgData,3);
}
else if( subMode == smProc )
{
// 修改RGB数据生成新的图片,非常低效
rgbImage = Image.createRGBImage(changeRGBData(rgbData),imgW,imgH,true);
subMode = smEnd;
}
else if( subMode == smEnd )
{
}
}
}
void gameDraw()
{
if( gameMode == gmStart )
{
if( subMode == smProc ){
// 描绘原始图片
g.drawImage(testImage,20,20,0);
}
}
else if( gameMode == gmProcPNGData )
{
if( subMode == smProc ){
// 描绘修改调试板数据生成的图片
g.drawImage(plteImage,20,80,0);
}
else if( subMode == smEnd ) {
// 描绘修改RGB数据生成的图片,也可以使用drawRGB()直接描绘RGB数据,不过要注意透明问题.
g.drawImage(rgbImage,20,140,0);
}
}
}
/**
* 修改png图片的调色板数据生成新的png图片
* @param imageSrc png图片的二进制数据字节数组
* @param type 修改策略
* @return 新的png图片
*/
public Image getPLTEModifidImage(byte[] imageSrc , int type)
{
if (imageSrc == null || imageSrc.length <= 1)
return null;
if (crcTable == null)
makeCrcTable();
// PLTE chunk数据域的类型标识
// see http://www.w3.org/TR/PNG/#11PLTE
String[] sPLTE = {"50", "4c", "54", "45"};
int i,j;
int pos = 0,startPos = 0;
byte[] data = imageSrc;
for (i = 0; i < data.length; i++)
{
if (Integer.toHexString(data[i]).equals(sPLTE[0])
&& Integer.toHexString(data[i + 1]).equals(sPLTE[1])
&& Integer.toHexString(data[i + 2]).equals(sPLTE[2])
&& Integer.toHexString(data[i + 3]).equals(sPLTE[3]))
{
pos = i;
break;
}
}
pos -= 4;
startPos = pos;
println("/n======================start Palette data process========================");
println("-------------Palette chunk start pos = " + pos);
// 取得PLTE chunk数据域的数据长度().
int imageNbColors = (
((data[pos] << 24) & 0xff000000)
| ((data[pos + 1] << 16) & 0x00ff0000)
| ((data[pos + 2] << 8 ) & 0x0000ff00)
| ((data[pos + 3] ) & 0x000000ff));
// 计算的PLTE chunk数据个数(每个PLTE chunk数据由R,G,B三个字节数据组成)
imageNbColors = imageNbColors/3;
// 为整形的PLTE chunk data分配空间
int imageRGBColors[] = new int[ imageNbColors ];
//12 = 数据长度(4个字节) + 类型标识(4个字节) + 校验码(4个字节)
for( i = pos,j = 0; i < pos + 12 + imageNbColors * 3 ; i++,j++ ){
if( j >= 8 && (j - 8)%3 == 0 ){
println("");
}
System.out.print(" " + data[i]);
}
pos += 8;
println("/n--------The number of PLTE chunks is " + imageNbColors + "------------");
if (imageRGBColors == null)
return null;
// 生成整形的PLTE chunk data
for( i = 0; i < imageNbColors; i++ )
{
imageRGBColors[i] = (
(data[pos + 0] & 0x000000ff) << 16)
| ((data[pos + 1] & 0x000000ff) << 8)
| ((data[pos + 2] & 0x000000ff));
if( i % 10 == 0 && i != 0) {
println("");
}
print(" " + imageRGBColors[i]);
pos += 3;
}
// 修改 PLTE chunk data
switch (type)
{
case 0:
// don't modify
break;
case 1: {
int l,r,g,b;
// gray
for (j = 1; j < imageNbColors; j++) {
r = imageRGBColors[j];
g = (r & 0x00FF00) >> 8;
b = r & 0x0000FF;
r = (r & 0xFF0000) >> 16;
l = (b + g * 6 + r * 3) / 16;
imageRGBColors[j] = l << 16 | l << 8 | l;
}
break;
}
case 2: {
// white
for (j = 1; j < imageNbColors; j++)
imageRGBColors[j] = 0xFFFFFF;
break;
}
case 3: {
int l,r,g,b;
// red
for (j = 1; j < imageNbColors; j++) {
r = imageRGBColors[j];
g = (r & 0x00FF00) >> 8;
b = r & 0x0000FF;
r = (r & 0xFF0000) >> 16;
l = (b + g * 6 + r * 3) / 10;
imageRGBColors[j] = l << 16;
}
break;
}
}
// 生成新的 PLTE chunk data
pos = startPos + 8;
for( i = 0; i < imageNbColors ;i++)
{
data[pos ] = (byte)((imageRGBColors[i] >> 16) ) ;
data[pos + 1 ] = (byte)((imageRGBColors[i] >> 8) );
data[pos + 2] = (byte)(imageRGBColors[i] );
pos += 3;
}
// 更新 CRC 校验码
int crc = updateCrcChunk( data, startPos + 4, startPos + 4 + 4 + ( imageNbColors * 3 ) );
data[pos + 0] = (byte)(crc >> 24 & 0x000000FF);
data[pos + 1] = (byte)(crc >> 16 & 0x000000FF);
data[pos + 2] = (byte)(crc >> 8 & 0x000000FF);
data[pos + 3] = (byte)(crc& 0x000000FF);
pos = startPos;
println("/n---------------------New plte data crc " + crc +"--------------------------------");
for( i = 0; i < 4 + 4 + ( imageNbColors * 3 ) + 4; i++ )
{
if( i >= 8 && (i - 8)%3 == 0 ){
println("");
}
print(" " + data[pos + i]);
}
println("/n=========================End plte data-=================================");
return Image.createImage(data,0,data.length);
}
/**
* 修改png图片的RGB数据
* @param rgbSrc png图片的RGB数据的字节数组
* @return 新的RGB数据的字节数组
*/
public int[] changeRGBData(int[] rgbSrc)
{
int len = rgbSrc.length;
int[] ret = new int[len];
int a,r,g,b;
int tmp;
for( int i = 0; i < len; i++ )
{
b = rgbSrc[i];
a = ((b & 0xff000000) >> 24);
r = ((b & 0x00ff0000) >> 16);
g = ((b & 0x0000ff00) >> 8);
b = ((b & 0x000000ff) >> 0);
// exchange red and blue
tmp = r;
r = b;
b = tmp;
ret[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
return ret;
}
// CRC校验表,加速CRC计算
static long crcTable[];
static public void makeCrcTable()
{
long c;
int n, k;
crcTable = new long[256];
for (n = 0; n < 256; n++) {
c = (long) n;
for (k = 0; k < 8; k++) {
if ((c & 1) == 1) {
c = 0xEDB88320L ^ (c >> 1);
}
else {
c = c >> 1;
}
}
crcTable[n] = c;
}
}
/**
* 计算CRC校验码
*/
static public int updateCrcChunk(byte[] buf, int dataOffsetStart,int dataOffsetEnd)
{
long c = 0xFFFFFFFFL;
for (int i = dataOffsetStart; i < dataOffsetEnd; i++)
c = (crcTable[(int) ((c ^ buf[i]) & 0xFF)] ^ (c >> 8));
return (int) (c ^ 0xFFFFFFFFL);
}
}
//-----------------------------------------------------Code------END----------------------------------------------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值