图片资源的管理,一直是网页游戏中头疼的问题。在保持图片质量和功能实现的基础上,图片越小越有利于管理,节省带宽,给玩家带来更好的用户体验。

美术在制作图片的时候,考虑到统一性和方便性,一般会对一系列的图片限定一个尺寸和中心点,在这个基础上进行制图。如一般网页游戏中的角色形象,考虑到要配合装备、武器、战斗及播放特效等动作,图片尺寸都会比角色的实际大小大得多,如下图用的尺寸是640*480,大小70.1KB,而角色部分只占其中很小的一部分。

一般网页游戏的视觉效果都是通过播放一系列的图片来实现,如绚丽的特效,人物的走动、战斗等。以角色为例,大部分游戏中都有的动作:站立、走路、攻击、受伤、死亡等,每个动作又分五个方向;平均每个动作每个方向5~10帧,应该可以想象出图片量了吧。

一:图片管理

一个网页游戏这样的图片资源可能会有成千上万张,如果管理才方便查看和编辑呢。最简单的方法是直接用文件夹,采用规范的文件命名和分类管理等还是可以应付过来的,不过想想那几千张图片头就大,多人查看也是个问题,更可况还需要编辑。所以,开发一个管理工具还是很有必要的,把图片存储到web服务器或是数据库中,提供界面化的操作。工具带来的好处,谁用谁知道!

二:图片裁剪

考虑到网游资源图片的特点:有固定尺寸,有用像素只占其中一小部分,边缘透明像素居多。为何不把周围透明像素裁剪掉,只存中间的有用像素呢。对,裁剪后再存储,只要记住原图片的大小和裁剪的偏移量,就可以把它还原出来。这当然是程序自动裁剪,要叫我用photoshop一张一张慢慢地手动裁剪你去死吧。

图片裁剪后再存储带来的几个好处:

  1. 占用存储空间变小
  2. 加载图片速度加快
  3. 程序运行更加流畅

数据库中应该记录什么数据才能把裁剪后的图片还原呢?图片原始数据是必须的,可以直接存储图片文件的二进制数据,还有就是裁剪后的偏移量和原始图片的宽高;不妨给它定义一个Bean类:

package com.monitor1394;import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;/*** 裁剪后的图片* * @author monitor* Created on 2011-11-28, 10:24:43*/
public class CroppedImage {/** 偏移量X */private int offsetX;/** 偏移量Y */private int offsetY;/** 原始图片的宽 */private int originalWid;/** 原始图片的高 */private int originalHig;/** 图片的二进制数据 */private byte[] imageByte;/** 图片路径 */private String imagePath;/** 裁剪后的图片 */private BufferedImage image;/*** 构造一个需从图片文件加载数据的CroppedImage* * @param filePath 图片文件路径* @throws IOException 如果在读取过程中发生错误*/public CroppedImage(String filePath) throws IOException{File file=new File(filePath);if(file==null) throw new NullPointerException("file is null");BufferedImage sourImage=ImageIO.read(file);if(sourImage==null) throw new NullPointerException("image is null");imagePath=filePath;originalWid=sourImage.getWidth();originalHig=sourImage.getHeight();image=sourImage;}public BufferedImage getImage() {return image;}public void setImage(BufferedImage image) {this.image = image;}public byte[] getImageByte() {return imageByte;}public void setImageByte(byte[] imageByte) {this.imageByte = imageByte;}public String getImagePath() {return imagePath;}public void setImagePath(String imagePath) {this.imagePath = imagePath;}public int getOffsetX() {return offsetX;}public void setOffsetX(int offsetX) {this.offsetX = offsetX;}public int getOffsetY() {return offsetY;}public void setOffsetY(int offsetY) {this.offsetY = offsetY;}public int getOriginalHig() {return originalHig;}public void setOriginalHig(int originalHig) {this.originalHig = originalHig;}public int getOriginalWid() {return originalWid;}public void setOriginalWid(int originalWid) {this.originalWid = originalWid;}@Overridepublic String toString(){return imagePath;}
}

至于图片裁剪,关键在于获得正确的裁剪区域,我在之前一篇很老的文件有提过: 一般PNG图片压缩的Java实现,回顾一下:

/** * 获得裁剪图片保留区域 * @param image 要裁剪的图片 * @return 保留区域 */
private static Rectangle getCutAreaAuto(BufferedImage image){  if(image==null) throw new NullPointerException("图片为空");  int width=image.getWidth();  int height=image.getHeight();  int startX=width;  int startY=height;  int endX=0;  int endY=0;  int []pixel=new int[width*height];  pixel=image.getRGB(0, 0, width, height, pixel, 0, width);  for(int i=0;i<pixel.length;i++){  if(isCutBackPixel(pixel[i])) continue;  else{  int w=i%width;  int h=i/width;  startX=(w<startX)?w:startX;  startY=(h<startY)?h:startY;  endX=(w>endX)?w:endX;  endY=(h>endY)?h:endY;  }  }  if(startX>endX || startY>endY){  startX=startY=0;  endX=width;  endY=height;  }  return new Rectangle(startX, startY, endX-startX, endY-startY);
}  /** * 当前像素是否为背景像素 * @param pixel * @return */
private static boolean isCutBackPixel(int pixel){  int back[]={0,8224125,16777215,8947848,460551,4141853,8289918};  for(int i=0;i<back.length;i++){  if(back[i]==pixel) return true;  }  return false;
}  

三:一些探讨

现在方法大体和以前一致,只是有个需要注意的地方:透明背景像素问题。一般我们认为透明背景像素应该为0,或者说这是最纯正的,只可惜这年头纯都是装出来的。后来慢慢发现透明像素还有其他不一样的值。以下是前面那张图用getRGB方法获得像素数组前3行的部分像素(十六进制和十进制):

================================================================
BufferedImage@1100d7a: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 640 height = 480 #numDataElements 4 dataOff[0] = 3
7e7e7e(8289918) 7e7e7e(8289918) 7e7e7e(8289918) 7f7f7f(8355711) 7f7f7f(8355711) 7e7e7e(8289918) 7f7f7f(8355711) 7e7e7e(8289918) 7f7f7f(8355711)
7e7e7e(8289918) 7e7e7e(8289918) 7f7f7f(8355711) 7e7e7e(8289918) 7f7f7f(8355711) 7f7f7f(8355711) 7e7e7e(8289918) 7e7e7e(8289918) 7f7f7f(8355711)
7e7e7e(8289918) 7e7e7e(8289918) 7e7e7e(8289918) 7e7e7e(8289918) 7e7e7e(8289918) 7e7e7e(8289918) 7e7e7e(8289918) 7e7e7e(8289918) 7e7e7e(8289918) 

图片图形方面的知识不甚了解,下面只从程序角度进行分析。先来看下BufferedImagegetRGB方法:

public int[] getRGB(int startX,int startY,int w,int h,int[] rgbArray,int offset,int scansize)

从图像数据的某一部分返回默认 RGB 颜色模型 (TYPE_INT_ARGB) 和默认 sRGB 颜色空间中整数像素数组。如果该默认模型与该图像的 ColorModel 不匹配,则发生颜色转换。在使用此方法所返回的数据中,每个颜色分量只有 8 位精度。通过图像中指定的坐标 (x, y),ARGB 像素可以按如下方式访问:

    pixel   = rgbArray[offset + (y-startY)*scansize + (x-startX)]; 

如果该区域不在边界内部,则抛出 ArrayOutOfBoundsException。但是,不保证进行显式的边界检查。

参数:
startX - 起始 X 坐标
startY - 起始 Y 坐标
w - 区域的宽度
h - 区域的高度
rgbArray - 如果不为 null,则在此写入 rgb 像素
offset - rgbArray 中的偏移量
scansize - rgbArray 的扫描行间距
返回:
RGB 像素数组。
另请参见:
setRGB(int, int, int),setRGB(int, int, int, int, int[], int, int)

从官方的Javadoc介绍得知,getRGB是以 TYPE_INT_ARGB颜色模型( ColorModel)和 sRGB颜色空间( ColorSpace)作为默认值获取像素数组,如果颜色模型不一致,得到的像素都是经过转化的。图片的像素实际存储在 DataBuffer中,随着图片的类型不同DataBuffer的内部存储结构是不一样的。图形图像方面的知识真是太深奥了,这泥坑我们就不跳进去了,免得无法自拔。感兴趣的童鞋不妨了解一下:

  1. BufferedImage
  2. ColorModel
  3. ColorSpace
  4. Raster
  5. DataBuffer

像素被转化就转化了吧,令人郁闷的是:为什么透明背景像素出现两个值?更甚至,即使几张看似一致的图片,那两个值还是不同的值。这两个问题一般出现在用3dmax渲出来的PNG图片上。不信你用photoshop做一张透明的图片看看,输出的值绝对是0或者FFFFFF。现有四张用3dmax渲出来的图片,用下面的输出方法输出相关信息:

    private void printImageDetail(BufferedImage image){if(image==null) return;System.out.println("================================================================");int wid=image.getWidth();int hig=image.getHeight();int[] pixels=new int[wid*hig];System.out.println(image);image.getRGB(0, 0, wid, hig, pixels, 0, wid);printPixel(pixels,wid); }private void printPixel(int[] pixel,int wid){int count=1;int num=1;for(int m=0;m<pixel.length;m++){if(pixel[m]==0){if((num++)<10)System.out.print(String.format("%6s(%7d) ","000000",pixel[m]));}else{if((num++)<10)System.out.print(String.format("%6s(%7d) ",Integer.toHexString(pixel[m]),pixel[m]));}if((m+1)%wid==0){System.out.println();num=1;if(count++>2)return;}}}

得到结果:

================================================================
BufferedImage@1100d7a: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 640 height = 480 #numDataElements 4 dataOff[0] = 3
888888(8947848) 878787(8882055) 878787(8882055) 878787(8882055) 888888(8947848) 878787(8882055) 878787(8882055) 878787(8882055) 878787(8882055)
888888(8947848) 888888(8947848) 888888(8947848) 888888(8947848) 888888(8947848) 878787(8882055) 888888(8947848) 888888(8947848) 888888(8947848)
878787(8882055) 888888(8947848) 878787(8882055) 878787(8882055) 888888(8947848) 888888(8947848) 878787(8882055) 878787(8882055) 888888(8947848)
================================================================
BufferedImage@5ffb18: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 640 height = 480 #numDataElements 4 dataOff[0] = 3
9c9c9c(10263708) 9c9c9c(10263708) 9c9c9c(10263708) 9c9c9c(10263708) 9c9c9c(10263708) 9c9c9c(10263708) 9b9b9b(10197915) 9c9c9c(10263708) 9c9c9c(10263708)
9b9b9b(10197915) 9c9c9c(10263708) 9c9c9c(10263708) 9c9c9c(10263708) 9b9b9b(10197915) 9c9c9c(10263708) 9b9b9b(10197915) 9b9b9b(10197915) 9c9c9c(10263708)
9c9c9c(10263708) 9b9b9b(10197915) 9c9c9c(10263708) 9c9c9c(10263708) 9b9b9b(10197915) 9b9b9b(10197915) 9c9c9c(10263708) 9b9b9b(10197915) 9c9c9c(10263708)
================================================================
BufferedImage@1c5c1: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 640 height = 480 #numDataElements 4 dataOff[0] = 3
6f6f6f(7303023) 6f6f6f(7303023) 6f6f6f(7303023) 6f6f6f(7303023) 6f6f6f(7303023) 6f6f6f(7303023) 6f6f6f(7303023) 6f6f6f(7303023) 707070(7368816)
6f6f6f(7303023) 707070(7368816) 6f6f6f(7303023) 707070(7368816) 6f6f6f(7303023) 707070(7368816) 707070(7368816) 6f6f6f(7303023) 6f6f6f(7303023)
707070(7368816) 707070(7368816) 707070(7368816) 6f6f6f(7303023) 6f6f6f(7303023) 6f6f6f(7303023) 6f6f6f(7303023) 707070(7368816) 6f6f6f(7303023)
================================================================
BufferedImage@1787038: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 640 height = 480 #numDataElements 4 dataOff[0] = 3
7d7d7d(8224125) 7c7c7c(8158332) 7c7c7c(8158332) 7c7c7c(8158332) 7c7c7c(8158332) 7c7c7c(8158332) 7c7c7c(8158332) 7c7c7c(8158332) 7c7c7c(8158332)
7c7c7c(8158332) 7d7d7d(8224125) 7c7c7c(8158332) 7d7d7d(8224125) 7d7d7d(8224125) 7d7d7d(8224125) 7d7d7d(8224125) 7d7d7d(8224125) 7c7c7c(8158332)
7d7d7d(8224125) 7c7c7c(8158332) 7d7d7d(8224125) 7d7d7d(8224125) 7d7d7d(8224125) 7d7d7d(8224125) 7c7c7c(8158332) 7c7c7c(8158332) 7c7c7c(8158332) 

看下 BufferedImage的源码:

    public String toString() {return new String("BufferedImage@"+Integer.toHexString(hashCode())+": type = "+imageType+" "+colorModel+" "+raster);}

从结果得知,每张图片的imageType、colorModel、raster都几乎是一样的。但是得到的像素数组却截然不同。不妨用photoshop打开什么操作都不做直接保存或是裁剪一小部分,像素全为ffffff:

================================================================
BufferedImage@1100d7a: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 60 height = 46 #numDataElements 4 dataOff[0] = 3
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
================================================================
BufferedImage@5ffb18: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 63 height = 53 #numDataElements 4 dataOff[0] = 3
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
================================================================
BufferedImage@1abc7b9: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 70 height = 52 #numDataElements 4 dataOff[0] = 3
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
================================================================
BufferedImage@1ac3c08: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 68 height = 58 #numDataElements 4 dataOff[0] = 3
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
================================================================
BufferedImage@1c5c1: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 72 height = 63 #numDataElements 4 dataOff[0] = 3
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215)
ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) ffffff(16777215) 

但区别在哪?还望高人指点。3dmax渲染图片参数设置原因?于是叫美工帮忙用3dmax渲出张图片看看:

输出结果:

================================================================
BufferedImage@1100d7a: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@e4f972 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 300 height = 200 #numDataElements 4 dataOff[0] = 3
000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0)
000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0)
000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 

设置方面也没什么特殊的:

(注:上图对应的应该是灰度16位的选项,其实RGB48位的也差不多:)

================================================================
BufferedImage@e4f972: type = 0 ColorModel: #pixelBits = 64 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@b4d3d5 transparency = 3 has alpha = true isAlphaPre = false ShortInterleavedRaster: width = 300 height = 200 #numDataElements 4
000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0)
000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0)
000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0) 000000(      0)

难道还有别的高级设置?这我就无从得知了。我们也暂且不管了,反正就那么一回事(怎么个一回事?我哪知道怎么回事)。

四:终极解决方法

简单的方法和之前一样,来一个不一样的透明像素就把它加到数组里面。就我这种犟驴不觉得烦,真是一根筋,转化一下不就行了。最好是能把透明像素转成0或者ffffff:

将CroppedImage构造方法中的:

    image=sourImage;

改成:

    image=new BufferedImage(originalWid,originalHig,BufferedImage.TYPE_INT_ARGB);Graphics2D g2d=image.createGraphics();g2d.drawImage(sourImage, 0, 0, null);g2d.dispose();

这样转化后的透明背景像素都变为0,裁剪方法中就可以去掉 isCutBackPixel()方法,直接和0比较:

    private BufferedImage cut(BufferedImage desImage) throws IOException{Rectangle rect=getCutRectangleAuto(desImage);offsetX=rect.x;offsetY=rect.y;return desImage.getSubimage(rect.x, rect.y,rect.width, rect.height);}/*** 自动获取裁剪区域* @param image 要裁剪的图片* @return Rectangle实例*/private Rectangle getCutRectangleAuto(BufferedImage image){if(image==null) throw new NullPointerException("image is null");int width=image.getWidth();int height=image.getHeight();int startX=width;int startY=height;int endX=0;int endY=0;int[] pixel=new int[width*height];pixel=image.getRGB(0, 0, width, height, pixel, 0, width);for(int i=0;i<pixel.length;i++){if(pixel[i]==0) continue;int w=i%width;int h=i/width;startX=(w<startX)?w:startX;startY=(h<startY)?h:startY;endX=(w>endX)?w:endX;endY=(h>endY)?h:endY;}if(startX>=endX || startY>=endY){return new Rectangle(1, 1, 1, 1);}else{return new Rectangle(startX, startY, endX-startX, endY-startY);}}

五:获得图片的字节数组

图片的字节数组是用于存储到数据库的,获取图片也是通过解析这个字节数组得到图片:

    ByteArrayOutputStream bos = new ByteArrayOutputStream();ImageIO.write(image, "png", bos);imageByte = bos.toByteArray();

六:从字节数组获得图片

    /*** 获得裁剪后的图片* @return BufferedImage*/public BufferedImage getImage() {if(image==null){ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);try{image = ImageIO.read(bis);}catch(Exception e){}finally{try{bis.close();}catch(Exception ex){}}}return image;}

 七:还原

       记录了原始数据,还原就简单了:

    /*** 获得原始图片* @return BufferedImage实例*/public BufferedImage getOriginalImage(){if(image==null) image=getImage();if(image==null) return null;Graphics2D g2d=null;BufferedImage sourImage=new BufferedImage(originalWid,originalHig,BufferedImage.TYPE_INT_ARGB);g2d=sourImage.createGraphics();g2d.drawImage(image, offsetX, offsetY, null);g2d.dispose();return sourImage;}

八:小结

很高兴你有耐心看到这里,要拍砖喷饭尽管放马过来。本文主要是探讨了页游中的图片裁剪问题和一些简单的实现,虽没深究,若能抛砖引玉最好不过了。能力有限,缺点难免,欢迎纠正。下面是CroppedImage类的完整代码:

package com.monitor1394;import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;/*** 裁剪后的图片* * @author monitor* Created on 2011-11-28, 10:24:43*/
public class CroppedImage {/** 偏移量X */private int offsetX;/** 偏移量Y */private int offsetY;/** 原始图片的宽 */private int originalWid;/** 原始图片的高 */private int originalHig;/** 图片的二进制数据 */private byte[] imageByte;/** 图片路径 */private String imagePath;/** 裁剪后的图片 */private BufferedImage image;/*** 构造一个需从图片文件加载数据的CroppedImage* * @param filePath 图片文件路径* @throws IOException 如果在读取过程中发生错误*/public CroppedImage(String filePath) throws IOException{File file=new File(filePath);if(file==null) throw new NullPointerException("file is null");BufferedImage sourImage=ImageIO.read(file);if(sourImage==null) throw new NullPointerException("image is null");imagePath=filePath;originalWid=sourImage.getWidth();originalHig=sourImage.getHeight();image=new BufferedImage(originalWid,originalHig,BufferedImage.TYPE_INT_ARGB);Graphics2D g2d=image.createGraphics();g2d.drawImage(sourImage, 0, 0, null);g2d.dispose();image=cut(image);ByteArrayOutputStream bos = new ByteArrayOutputStream();ImageIO.write(image, "png", bos);imageByte = bos.toByteArray();}private BufferedImage cut(BufferedImage desImage) throws IOException{Rectangle rect=getCutRectangleAuto(desImage);offsetX=rect.x;offsetY=rect.y;return desImage.getSubimage(rect.x, rect.y,rect.width, rect.height);}/*** 自动获取裁剪区域* @param image 要裁剪的图片* @return Rectangle实例*/private Rectangle getCutRectangleAuto(BufferedImage image){if(image==null) throw new NullPointerException("image is null");int width=image.getWidth();int height=image.getHeight();int startX=width;int startY=height;int endX=0;int endY=0;int[] pixel=new int[width*height];pixel=image.getRGB(0, 0, width, height, pixel, 0, width);for(int i=0;i<pixel.length;i++){if(pixel[i]==0) continue;int w=i%width;int h=i/width;startX=(w<startX)?w:startX;startY=(h<startY)?h:startY;endX=(w>endX)?w:endX;endY=(h>endY)?h:endY;}if(startX>=endX || startY>=endY){return new Rectangle(1, 1, 1, 1);}else{return new Rectangle(startX, startY, endX-startX, endY-startY);}}/*** 获得裁剪后的图片* @return BufferedImage*/public BufferedImage getImage() {if(image==null){ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);try{image = ImageIO.read(bis);}catch(Exception e){}finally{try{bis.close();}catch(Exception ex){}}}return image;}/*** 获得原始图片* @return BufferedImage实例*/public BufferedImage getOriginalImage(){if(image==null) image=getImage();if(image==null) return null;Graphics2D g2d=null;BufferedImage sourImage=new BufferedImage(originalWid,originalHig,BufferedImage.TYPE_INT_ARGB);g2d=sourImage.createGraphics();g2d.drawImage(image, offsetX, offsetY, null);g2d.dispose();return sourImage;}public void setImage(BufferedImage image) {this.image = image;}public byte[] getImageByte() {return imageByte;}public void setImageByte(byte[] imageByte) {this.imageByte = imageByte;}public String getImagePath() {return imagePath;}public void setImagePath(String imagePath) {this.imagePath = imagePath;}public int getOffsetX() {return offsetX;}public void setOffsetX(int offsetX) {this.offsetX = offsetX;}public int getOffsetY() {return offsetY;}public void setOffsetY(int offsetY) {this.offsetY = offsetY;}public int getOriginalHig() {return originalHig;}public void setOriginalHig(int originalHig) {this.originalHig = originalHig;}public int getOriginalWid() {return originalWid;}public void setOriginalWid(int originalWid) {this.originalWid = originalWid;}@Overridepublic String toString(){return imagePath;}
}

对之前的图片进行裁剪测试,偏移量:(283,158),尺寸:78*93,大小:7.96KB,小了将近10倍,还是相当可观的:

而还原出来的原图大小为10.2KB,相信会有些失真,不过肉眼基本是看不出来的,对页游来说这最好不过。

=============================================================================

生活就是边折腾边享受,进步就是不断发现自己SB的过程

monitor的博客:http://blog.csdn.net/monitor1394

=============================================================================

页游中的PNG图片资源的裁剪和还原相关推荐

  1. 关于Flash 页游中的那些优化1

    一直在从事回合制战斗的RPG页游前端开发,转眼已经三年了,总结记录一下自己的心得,希望也能给别人带来帮助 1. 性能优化 几乎在所有的页游中,我们都会碰到各种各样的性能问题,我们也不例外,游戏的整个性 ...

  2. 页游中的十大经典游戏题材

    http://xin.07073.com/jiedu/976904.html 武侠题材 "飞雪连天射白鹿,笑书神侠倚碧鸳",豪侠们把酒高歌.快意江湖才是武侠的真谛神髓.在武侠题材的 ...

  3. 在页游中LUA的应用(1)

    通常,你希望在你的游戏开始的时候读取一些信息,以配置你的游戏,这些信息通常都是放到一个文本文件中,在你的游戏启动的时候,你需要打开这个文件,然后解析字符串,找到所需要的信息. 或许你认为这样就足够了, ...

  4. html战旗游戏,战棋页游-策略类战棋网页游戏推荐

    战棋类游戏是最经典的一种策略游戏类型,由于战棋游戏每个武将限制了移动范围,活动力非常受限制,因此策略性更强,有时候真的就是差一步移动力导致全军覆没.经典的战棋单机游戏还是很多的,本站上面的三国志曹操传 ...

  5. 网络游戏安全小议(端游/页游/手游)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 一.网络 ...

  6. 页游安全攻与防(转)

    原文链接:http://netsecurity.51cto.com/art/201211/364775.htm 页游,最最核心的就是客户端(swf)与服务端的游戏通信了.游戏通信产生的封包,内容是否可 ...

  7. 页游安全攻与防,SWF加密和隐藏密匙

    原文链接:http://netsecurity.51cto.com/art/201211/364775.htm 页游,最最核心的就是客户端(swf)与服务端的游戏通信了.游戏通信产生的封包,内容是否可 ...

  8. (转)页游安全攻与防,SWF加密和隐藏密匙

    原文链接:http://netsecurity.51cto.com/art/201211/364775.htm 页游,最最核心的就是客户端(swf)与服务端的游戏通信了.游戏通信产生的封包,内容是否可 ...

  9. (转)页游安全功与防

    页游,最最核心的就是客户端(swf)与服务端的游戏通信了.游戏通信产生的封包,内容是否可识别,可篡改,可重放,处理逻辑是否有漏洞,都决定了这款游戏是否有重大的漏洞. AD: 网页游戏的安全问题,在刚入 ...

最新文章

  1. SpringMvc+ajax实现文件跨域上传
  2. C语言-什么是尾递归
  3. appium desktop 1.7 byName不能用,重写
  4. ybtoj洛谷P4406三角形面积并(扫描线)
  5. java接口测试框架搭建_接口自动化测试框架搭建
  6. MyBatis-Plus_快速入门0222
  7. unity 查找所以物体_用Unity来实现一下绳子效果——Obi Rope插件介绍
  8. 正在搞用web.py做的通讯录
  9. java第二天学习笔记
  10. win10打开蓝牙_联想笔记本win10无法连接蓝牙音箱的解决方法
  11. ATTck 命令执行 —— 远程动态数据交换
  12. 关于applicaiton.yml不是绿叶子图标的处理办法
  13. 一种获得深度睡眠的方法
  14. 十位数和个位数交换python_Python3实现个位数字和十位数字对调, 其乘积不变
  15. 全网最详细最基础的网络安全入门教程
  16. Android电视开机进入AV,康佳电视如何设置开机成AV模式-康佳开机直接进电视
  17. Vue前端自动化测试-Vue Test Utils
  18. 【教程】layui数据表格添加下拉菜单
  19. Paragon Hard Disk Manager 17 Business中文版
  20. android 人脸变形,人脸形变算法——液化变形

热门文章

  1. 粒度计行业调研报告 - 市场现状分析与发展前景预测
  2. 在线涂改图片 php,php网站怎么修改图片
  3. TD3:双延迟深度确定性策略梯度
  4. 数据库系统概论第五版知识大纲
  5. Java基本数据类型详细介绍一览
  6. acm算法有用吗?写给自己。
  7. 货币转换程序(双符号)python代码_如何实现python汇率转换代码
  8. 开源安全运维平台OSSIM疑难解析:资源下载
  9. Nutanix推出超融合软件创新功能,强化市场领导地位
  10. 流程生产订单和离散生产订单的区别_浅谈流程型和离散型MES的区别