圖片資源的管理,一直是網頁遊戲中頭疼的問題。在保持圖片質量和功能實現的基礎上,圖片越小越有利於管理,節省帶寬,給玩家帶來更好的用戶體驗。
美術在製作圖片的時候,考慮到統一性和方便性,一般會對一系列的圖片限定一個尺寸和中心點,在這個基礎上進行製圖。如一般網頁遊戲中的角色形象,考慮到要配合裝備、武器、戰鬥及播放特效等動作,圖片尺寸都會比角色的實際大小大得多,如下圖用的尺寸是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;
}
@Override
public 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)
ColorModel
)和sRGB顏色空間(ColorSpace
)作爲默認值獲取像素數組,如果顏色模型不一致,得到的像素都是經過轉化的。圖片的像素實際存儲在DataBuffer
中,隨着圖片的類型不同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;
}
@Override
public String toString(){
return imagePath;
}
}
對之前的圖片進行裁剪測試,偏移量:(283,158),尺寸:78*93,大小:7.96KB,小了將近10倍,還是相當可觀的:
而還原出來的原圖大小爲10.2KB,相信會有些失真,不過肉眼基本是看不出來的,對頁游來說這最好不過。
=============================================================================
生活就是邊折騰邊享受,進步就是不斷髮現自己SB的過程
monitor的博客:http://blog.csdn.net/monitor1394
=============================================================================