页游中的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

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

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