頁遊中的PNG圖片資源的裁剪和還原

        圖片資源的管理,一直是網頁遊戲中頭疼的問題。在保持圖片質量和功能實現的基礎上,圖片越小越有利於管理,節省帶寬,給玩家帶來更好的用戶體驗。

        美術在製作圖片的時候,考慮到統一性和方便性,一般會對一系列的圖片限定一個尺寸和中心點,在這個基礎上進行製圖。如一般網頁遊戲中的角色形象,考慮到要配合裝備、武器、戰鬥及播放特效等動作,圖片尺寸都會比角色的實際大小大得多,如下圖用的尺寸是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;
    }
    
    @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) 

       圖片圖形方面的知識不甚瞭解,下面只從程序角度進行分析。先來看下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;
    }
    
    @Override
    public String toString(){
        return imagePath;
    }
}
        對之前的圖片進行裁剪測試,偏移量:(283,158),尺寸:78*93,大小:7.96KB,小了將近10倍,還是相當可觀的:


        而還原出來的原圖大小爲10.2KB,相信會有些失真,不過肉眼基本是看不出來的,對頁游來說這最好不過。


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

生活就是邊折騰邊享受,進步就是不斷髮現自己SB的過程

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章