页游中的PNG图片资源的裁剪和还原
图片资源的管理,一直是网页游戏中头疼的问题。在保持图片质量和功能实现的基础上,图片越小越有利于管理,节省带宽,给玩家带来更好的用户体验。
美术在制作图片的时候,考虑到统一性和方便性,一般会对一系列的图片限定一个尺寸和中心点,在这个基础上进行制图。如一般网页游戏中的角色形象,考虑到要配合装备、武器、战斗及播放特效等动作,图片尺寸都会比角色的实际大小大得多,如下图用的尺寸是640*480,大小70.1KB,而角色部分只占其中很小的一部分。
一般网页游戏的视觉效果都是通过播放一系列的图片来实现,如绚丽的特效,人物的走动、战斗等。以角色为例,大部分游戏中都有的动作:站立、走路、攻击、受伤、死亡等,每个动作又分五个方向;平均每个动作每个方向5~10帧,应该可以想象出图片量了吧。
一:图片管理
一个网页游戏这样的图片资源可能会有成千上万张,如果管理才方便查看和编辑呢。最简单的方法是直接用文件夹,采用规范的文件命名和分类管理等还是可以应付过来的,不过想想那几千张图片头就大,多人查看也是个问题,更可况还需要编辑。所以,开发一个管理工具还是很有必要的,把图片存储到web服务器或是数据库中,提供界面化的操作。工具带来的好处,谁用谁知道!
二:图片裁剪
考虑到网游资源图片的特点:有固定尺寸,有用像素只占其中一小部分,边缘透明像素居多。为何不把周围透明像素裁剪掉,只存中间的有用像素呢。对,裁剪后再存储,只要记住原图片的大小和裁剪的偏移量,就可以把它还原出来。这当然是程序自动裁剪,要叫我用photoshop一张一张慢慢地手动裁剪你去死吧。
图片裁剪后再存储带来的几个好处:
- 占用存储空间变小
- 加载图片速度加快
- 程序运行更加流畅
数据库中应该记录什么数据才能把裁剪后的图片还原呢?图片原始数据是必须的,可以直接存储图片文件的二进制数据,还有就是裁剪后的偏移量和原始图片的宽高;不妨给它定义一个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)
图片图形方面的知识不甚了解,下面只从程序角度进行分析。先来看下BufferedImage的getRGB方法:
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的内部存储结构是不一样的。图形图像方面的知识真是太深奥了,这泥坑我们就不跳进去了,免得无法自拔。感兴趣的童鞋不妨了解一下:
BufferedImage
ColorModel
ColorSpace
Raster
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图片资源的裁剪和还原相关推荐
- 关于Flash 页游中的那些优化1
一直在从事回合制战斗的RPG页游前端开发,转眼已经三年了,总结记录一下自己的心得,希望也能给别人带来帮助 1. 性能优化 几乎在所有的页游中,我们都会碰到各种各样的性能问题,我们也不例外,游戏的整个性 ...
- 页游中的十大经典游戏题材
http://xin.07073.com/jiedu/976904.html 武侠题材 "飞雪连天射白鹿,笑书神侠倚碧鸳",豪侠们把酒高歌.快意江湖才是武侠的真谛神髓.在武侠题材的 ...
- 在页游中LUA的应用(1)
通常,你希望在你的游戏开始的时候读取一些信息,以配置你的游戏,这些信息通常都是放到一个文本文件中,在你的游戏启动的时候,你需要打开这个文件,然后解析字符串,找到所需要的信息. 或许你认为这样就足够了, ...
- html战旗游戏,战棋页游-策略类战棋网页游戏推荐
战棋类游戏是最经典的一种策略游戏类型,由于战棋游戏每个武将限制了移动范围,活动力非常受限制,因此策略性更强,有时候真的就是差一步移动力导致全军覆没.经典的战棋单机游戏还是很多的,本站上面的三国志曹操传 ...
- 网络游戏安全小议(端游/页游/手游)
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 一.网络 ...
- 页游安全攻与防(转)
原文链接:http://netsecurity.51cto.com/art/201211/364775.htm 页游,最最核心的就是客户端(swf)与服务端的游戏通信了.游戏通信产生的封包,内容是否可 ...
- 页游安全攻与防,SWF加密和隐藏密匙
原文链接:http://netsecurity.51cto.com/art/201211/364775.htm 页游,最最核心的就是客户端(swf)与服务端的游戏通信了.游戏通信产生的封包,内容是否可 ...
- (转)页游安全攻与防,SWF加密和隐藏密匙
原文链接:http://netsecurity.51cto.com/art/201211/364775.htm 页游,最最核心的就是客户端(swf)与服务端的游戏通信了.游戏通信产生的封包,内容是否可 ...
- (转)页游安全功与防
页游,最最核心的就是客户端(swf)与服务端的游戏通信了.游戏通信产生的封包,内容是否可识别,可篡改,可重放,处理逻辑是否有漏洞,都决定了这款游戏是否有重大的漏洞. AD: 网页游戏的安全问题,在刚入 ...
最新文章
- SpringMvc+ajax实现文件跨域上传
- C语言-什么是尾递归
- appium desktop 1.7 byName不能用,重写
- ybtoj洛谷P4406三角形面积并(扫描线)
- java接口测试框架搭建_接口自动化测试框架搭建
- MyBatis-Plus_快速入门0222
- unity 查找所以物体_用Unity来实现一下绳子效果——Obi Rope插件介绍
- 正在搞用web.py做的通讯录
- java第二天学习笔记
- win10打开蓝牙_联想笔记本win10无法连接蓝牙音箱的解决方法
- ATTck 命令执行 —— 远程动态数据交换
- 关于applicaiton.yml不是绿叶子图标的处理办法
- 一种获得深度睡眠的方法
- 十位数和个位数交换python_Python3实现个位数字和十位数字对调, 其乘积不变
- 全网最详细最基础的网络安全入门教程
- Android电视开机进入AV,康佳电视如何设置开机成AV模式-康佳开机直接进电视
- Vue前端自动化测试-Vue Test Utils
- 【教程】layui数据表格添加下拉菜单
- Paragon Hard Disk Manager 17 Business中文版
- android 人脸变形,人脸形变算法——液化变形