`

一般PNG图片压缩的Java实现

    博客分类:
  • java
 
阅读更多

由于对资源或网速的要求,在手机游戏或一般的网页游戏中,希望能对图片进最大可能的压缩,以节省资源。最近公司做的项目也有对这方面的需求,于是我在网上 逛了半天,希望能发现现成版的Java方法可以使用(用程序来压缩而不借助于工具,要不然2万多张的图片你想累死人?虽然PS有批量功能,它却无法按原来 的路径存放);失望的是,好像没发现什么能直接使用代码,哪怕是提个解决方案也很少。既然网上找不到合适的,那就自己动手,丰衣足食。

      关于PNG图片的格式我在此就不多说,图片压缩方面的理论知识我也不在这多此一举,网上资料一大堆。开门见山,我们的目标是怎样用Java把PNG图片尽最大可能的压缩;当然,不能看出明显的失真。

      一:BufferedImage类

      在Java中,关于图片处理我们自然而然的想到了BufferedImage类,深入了解它,你会发现其实Java已经帮我们做好了图片压缩了,只是压缩完的图片和我们的需求有一点点偏差.......先看看BufferedImage最常用的构造方法:

 public BufferedImage(int width,int height,int imageType);

      构造一个类型为预定义图像类型之一的 BufferedImage,其中imageType有以下几种:

BufferedImage.TYPE_INT_RGB:8 位 RGB 颜色分量,不带alpha通道。

BufferedImage.TYPE_INT_ARGB:8 位 RGBA 颜色分量,带alpha通道。

BufferedImage.TYPE_INT_ARGB_PRE:8 位 RGBA 颜色分量,已预乘以 alpha。

BufferedImage.TYPE_INT_BGR:8 位 RGB 颜色分量Windows 或 Solaris 风格的图像,不带alpha通道。

BufferedImage.TYPE_3BYTE_BGR:8位GBA颜色分量,用3字节存储Blue、Green和Red三种颜色,不存在alpha。

BufferedImage.TYPE_4BYTE_ABGR:8位RGBA颜色分量,用3字节存储Blue、Green和Red三种颜色以及1字节alpha。

BufferedImage.TYPE_4BYTE_ABGR_PRE:具有用3字节存储的Blue、Green和Red三种颜色以及1字节alpha。

BufferedImage.TYPE_USHORT_565_RGB:具有5-6-5RGB颜色分量(5位Red、6位Green、5位Blue)的图像,不带alpha。

BufferedImage.TYPE_USHORT_555_RGB:具有5-5-5RGB颜色分量(5位Red、5位Green、5位Blue)的图像,不带alpha。

BufferedImage.TYPE_BYTE_GRAY:表示无符号byte灰度级图像(无索引)。

BufferedImage.TYPE_USHORT_GRAY:表示一个无符号short 灰度级图像(无索引)。

BufferedImage.TYPE_BYTE_BINARY:表示一个不透明的以字节打包的 1、2 或 4 位图像。

BufferedImage.TYPE_BYTE_INDEXED:表示带索引的字节图像。  

      其实imageType就是对应着Java内不同格式的压缩方法,编号分别为1-13;下面我们将一张原图用下面的几句代码分别调用不同的参数生成图片看看:

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. for(int i=1;i<=13;i++){  
  2.         tempImage=new BufferedImage(width, height, i);  
  3.         g2D = (Graphics2D) tempImage.getGraphics();  
  4.         g2D.drawImage(sourceImage, 00null);  
  5.         ImageIO.write(tempImage, "png"new File("cut/c_com_"+i+".png"));  
  6.     }  

      原图如下,PNG格式,大小24.0KB:

原图(24.0KB)

     压缩后的图片:

TYPE_INT_RGB(9.21KB)  TYPE_INT_ARGB(11.4KB)  TYPE_INT_ARGB_PRE(11.4KB)  TYPE_INT_BGR(9.21KB)  TYPE_3BYTE_BGR(9.21KB)  TYPE_4BYTE_ABGR(11.4KB)  TYPE_4BYTE_ABGR_PRE(11.4KB)   TYPE_USHORT_565_RGB(5.72KB)   TYPE_USHORT_555_RGB(4.74KB)  TYPE_BYTE_GRAY(3.24KB)  TYPE_USHORT_GRAY(6.62KB)  TYPE_BYTE_BINARY(0.38KB)  TYPE_BYTE_INDEXED(3.50KB)

      从图片看到,黑白照片最小,不过这不是我们想要,排除;最后一张TYPE_BYTE_INDEXED类型的(其实就是PNG8)是彩色,也不大,但是失真太厉害了,排除;剩下的透明的那几个大小都一样,排除;对比剩下背景不透明的那几张,TYPE_USHORT_555_RGB就是我们要的压缩类型了。

       二:555格式的位图

    555格式其实是16位位图中的一种。16位位图最多有65536种颜色。每个色素用16位(2个字节)表示。这种格式叫作高彩色,或叫增强型16位色, 或64K色。16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0。在555格 式下,红、绿、蓝的掩码分别是:0x7C00、0x03E0、0x001F(在BufferedImage源码中也有定义)。

      三:进一步处理

      从图片效果可以看出,555格式非常接近真彩色了,而图像数据又比真彩图像小的多,非常满足我们的要求。但是我们需要背景是透明的,而用TYPE_USHORT_555_RGB生成的图片背景却是不透明的,自然而然的我们想到了把不透明的背景替换成透明的不就行了。

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /** 
  2.   * 将背景为黑色不透明的图片转化为背景透明的图片 
  3.   * @param image 背景为黑色不透明的图片(用555格式转化后的都是黑色不透明的) 
  4.   * @return 转化后的图片 
  5.   */  
  6.  private static BufferedImage getConvertedImage(BufferedImage image){  
  7.      int width=image.getWidth();  
  8.      int height=image.getHeight();  
  9.      BufferedImage convertedImage=null;  
  10.      Graphics2D g2D=null;  
  11.      //采用带1 字节alpha的TYPE_4BYTE_ABGR,可以修改像素的布尔透明  
  12.      convertedImage=new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);  
  13.      g2D = (Graphics2D) convertedImage.getGraphics();  
  14.      g2D.drawImage(image, 00null);  
  15.      //像素替换,直接把背景颜色的像素替换成0  
  16.      for(int i=0;i<width;i++){  
  17.          for(int j=0;j<height;j++){  
  18.              int rgb=convertedImage.getRGB(i, j);  
  19.              if(isBackPixel(rgb)){  
  20.                  convertedImage.setRGB(i, j,0);  
  21.              }  
  22.          }  
  23.      }  
  24.      g2D.drawImage(convertedImage, 00null);  
  25.      return convertedImage;  
  26.  }  

 

   其中的isBackPixel(rgb)用于判断当前像素是否为背景像素:

[java:showcolumns:firstline[1]] view plaincopy
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /** 
  2.  * 判断当前像素是否为黑色不透明的像素(-16777216) 
  3.  * @param pixel 要判断的像素 
  4.  * @return 是背景像素返回true,否则返回false 
  5.  */  
  6. private static boolean isBackPixel(int pixel){  
  7.     int back[]={-16777216};  
  8.     for(int i=0;i<back.length;i++){  
  9.         if(back[i]==pixel) return true;  
  10.     }  
  11.     return false;  
  12. }  

   经转化后的图片如下:

   背景像素替换后(5.28KB)

   转化后稍微大了一点,这个可以接受;要命的是带了一个黑色边框。为什么呢?原因很简单,原图中边框部分的像素是介于透明和不透明之间的,而经过555格式压缩后所有像素都变成了布尔透明,也就是说所有的像素要么是透明的要么就是不透明的。

   最容易想到的方法就是把边框的像素换成原图边框的像素,关键在于怎么判断当前像素是否为图片的边框像素,这个算法可能得花费你一定的时间,下面只是我想到的一种实现:

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /** 
  2.  * 图片压缩 
  3.  * @param sourceImage 要压缩的图片 
  4.  * @return 压缩后的图片 
  5.  * @throws IOException 图片读写异常 
  6.  */  
  7. public static BufferedImage compressImage(BufferedImage sourceImage) throws IOException{  
  8.     if(sourceImage==nullthrow new NullPointerException("空图片");  
  9.     BufferedImage cutedImage=null;  
  10.     BufferedImage tempImage=null;  
  11.     BufferedImage compressedImage=null;  
  12.     Graphics2D g2D=null;  
  13.     //图片自动裁剪  
  14.     cutedImage=cutImageAuto(sourceImage);  
  15.     int width=cutedImage.getWidth();  
  16.     int height=cutedImage.getHeight();  
  17.     //图片格式为555格式  
  18.     tempImage=new BufferedImage(width, height, BufferedImage.TYPE_USHORT_555_RGB);  
  19.     g2D = (Graphics2D) tempImage.getGraphics();  
  20.     g2D.drawImage(sourceImage, 00null);  
  21.     compressedImage=getConvertedImage(tempImage);  
  22.     //经过像素转化后的图片  
  23.     compressedImage=new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);  
  24.     g2D = (Graphics2D) compressedImage.getGraphics();  
  25.     g2D.drawImage(tempImage, 00null);  
  26.     int pixel[]=new int[width*height];  
  27.     int sourcePixel[]=new int[width*height];  
  28.     int currentPixel[]=new int[width*height];  
  29.     sourcePixel=cutedImage.getRGB(00, width, height, sourcePixel, 0, width);  
  30.     currentPixel=tempImage.getRGB(00, width, height, currentPixel, 0, width);  
  31.     for(int i=0;i<currentPixel.length;i++){  
  32.         if(i==0 || i==currentPixel.length-1){  
  33.             pixel[i]=0;  
  34.         //内部像素  
  35.         }else if(i>width && i<currentPixel.length-width){  
  36.             int bef=currentPixel[i-1];  
  37.             int now=currentPixel[i];  
  38.             int aft=currentPixel[i+1];  
  39.             int up=currentPixel[i-width];  
  40.             int down=currentPixel[i+width];  
  41.             //背景像素直接置为0  
  42.             if(isBackPixel(now)){  
  43.                 pixel[i]=0;  
  44.             //边框像素和原图一样  
  45.             }else if((!isBackPixel(now) && isBackPixel(bef))  
  46.                     ||(!isBackPixel(now) && isBackPixel(aft))  
  47.                     ||(!isBackPixel(now) && isBackPixel(up))  
  48.                     ||(!isBackPixel(now) &&isBackPixel(down))  
  49.                    ){  
  50.                 pixel[i]=sourcePixel[i];  
  51.             //其他像素和555压缩后的像素一样  
  52.             }else{  
  53.                 pixel[i]=now;  
  54.             }  
  55.         //边界像素  
  56.         }else{  
  57.             int bef=currentPixel[i-1];  
  58.             int now=currentPixel[i];  
  59.             int aft=currentPixel[i+1];  
  60.             if(isBackPixel(now)){  
  61.                 pixel[i]=0;  
  62.             }else if((!isBackPixel(now) && isBackPixel(bef))  
  63.                     ||(!isBackPixel(now) && isBackPixel(aft))){  
  64.                 pixel[i]=sourcePixel[i];  
  65.             }else{  
  66.                 pixel[i]=now;  
  67.             }  
  68.         }  
  69.     }  
  70.     compressedImage.setRGB(00, width, height, pixel, 0, width);  
  71.     g2D.drawImage(compressedImage, 00null);  
  72.     ImageIO.write(cutedImage, "png"new File("cut/a_cut.png"));  
  73.     ImageIO.write(tempImage, "png"new File("cut/b_555.png"));  
  74.     ImageIO.write(compressedImage, "png"new File("cut/c_com.png"));  
  75.     return compressedImage;  
  76. }  

   其中的cutedImage=cutImageAuto(sourceImage);是对原图进行裁剪,代码如下:

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /** 
  2.  * 图片自动裁剪 
  3.  * @param image 要裁剪的图片 
  4.  * @return 裁剪后的图片 
  5.  */  
  6. public static BufferedImage cutImageAuto(BufferedImage image){  
  7.     Rectangle area=getCutAreaAuto(image);  
  8.     return image.getSubimage(area.x, area.y,area.width, area.height);  
  9. }  
  10.   
  11. /** 
  12.  * 获得裁剪图片保留区域 
  13.  * @param image 要裁剪的图片 
  14.  * @return 保留区域 
  15.  */  
  16. private static Rectangle getCutAreaAuto(BufferedImage image){  
  17.     if(image==nullthrow new NullPointerException("图片为空");  
  18.     int width=image.getWidth();  
  19.     int height=image.getHeight();  
  20.     int startX=width;  
  21.     int startY=height;  
  22.     int endX=0;  
  23.     int endY=0;  
  24.     int []pixel=new int[width*height];  
  25.   
  26.     pixel=image.getRGB(00, width, height, pixel, 0, width);  
  27.     for(int i=0;i<pixel.length;i++){  
  28.         if(isCutBackPixel(pixel[i])) continue;  
  29.         else{  
  30.             int w=i%width;  
  31.             int h=i/width;  
  32.             startX=(w<startX)?w:startX;  
  33.             startY=(h<startY)?h:startY;  
  34.             endX=(w>endX)?w:endX;  
  35.             endY=(h>endY)?h:endY;  
  36.         }  
  37.     }  
  38.     if(startX>endX || startY>endY){  
  39.         startX=startY=0;  
  40.         endX=width;  
  41.         endY=height;  
  42.     }  
  43.     return new Rectangle(startX, startY, endX-startX, endY-startY);  
  44. }  
  45.   
  46. /** 
  47.  * 当前像素是否为背景像素 
  48.  * @param pixel 
  49.  * @return 
  50.  */  
  51. private static boolean isCutBackPixel(int pixel){  
  52.     int back[]={0,8224125,16777215,8947848,460551,4141853,8289918};  
  53.     for(int i=0;i<back.length;i++){  
  54.         if(back[i]==pixel) return true;  
  55.     }  
  56.     return false;  
  57. }  

   改善后得到的图片:

   改善转化方法后的图片(6.34KB)

   实际上,这种方法只适用于图片颜色分明(边框颜色分明,背景颜色唯一),黑色像素不多的图片。一些比较特殊的图片就得特殊处理了,如以下图片:

   蜂(压缩前) 召唤门(压缩前)         压缩后      蜂(压缩后)   召唤门(压缩后)    

   原因是黑色不透明像素也是图片实体的一部分,这样就把它替换成白色透明的了。可以把代码改一下,但是图片的大小会增加不少,就是把程序认为是背景颜色的像 素替换成原图片的像素;将compressImage()方法中的第33、43、61行改成 pixel[i]=sourcePixel[i]; 即可。

分享到:
评论

相关推荐

    PngEncoder.java针对java平台处理png压缩算法

    PngEncoder.java针对java平台处理png压缩算法

    java图片压缩处理(可以压缩为任意大小

    本主题将深入探讨如何使用Java实现图片压缩,并能够将其调整到任意大小,同时保持图片质量并避免变形。 首先,我们需要理解图片压缩的基本原理。图片压缩主要有两种类型:有损压缩和无损压缩。有损压缩会牺牲一部分...

    图片缩放、压缩技术java实现

    通过理解等比缩放和图片压缩的概念,以及如何在Java中实现它们,你可以有效地管理图像资源,提升应用的效率和用户体验。在实际操作中,你还可以探索更多高级功能,如色彩空间转换、滤波和增强等,以进一步优化你的...

    java实现视频压缩

    例如,可以使用JPEG或PNG等图片压缩算法对每一帧进行压缩。 4. **音频处理**:如果视频包含音频,也需要进行压缩。可以使用MP3、AAC等音频编码格式。 5. **打包与写入**:将压缩后的音频和视频数据按照特定的容器...

    png图片压缩工具

    "png图片压缩工具"就是这样一个专门针对PNG格式图片进行压缩的应用。 在J2ME(Java 2 Micro Edition)环境中,由于资源限制,对图片的大小控制尤为重要。PNG图片压缩工具在J2ME平台上运行,可以帮助开发者或用户...

    Java 图片压缩

    在Java编程语言中,处理图像是一项常见的任务,其中包括图片压缩。Java提供了丰富的API来处理图像,其中`java.awt.image.BufferedImage`和`javax.imageio.ImageIO`类是核心工具。本篇文章将深入探讨如何利用Java后台...

    Java提取IPA中的png文件, 并进行解码还原png图片

    要解决这个问题,我们需要使用Java编程语言来实现PNG图片的解码过程。Java作为一种跨平台的语言,拥有丰富的库和工具,可以处理各种类型的数据,包括图像处理。以下是一些关键知识点: 1. **读取IPA文件**:首先,...

    pngout PNG图片压缩利器

    最强大的png压缩器提供了大量自定义的用户选项来提高PNG的压缩率

    java图片压缩文件大小图片大小(支持gif动态图)

    在Java编程语言中,处理图片压缩是一个常见的任务,特别是在网页开发、存储优化或者移动应用中。这个主题主要涉及如何利用Java来减少图片文件的大小,包括静态图片(如JPEG、PNG)以及动态图片(如GIF)。下面我们将...

    wmf格式图片转png完整java示例(带全部jar).rar

    wmf格式图片转png完整java项目示例,包含所需jdk...2,wmf格式的base64压缩数据转png图片(带解压base64数据功能); 二,包含的完整jar: batik-all-1.7.jar commons-io-2.6.jar wmf2svg-0.9.8.jar xml-apis-ext.jar

    png图片压缩加密工具binCompiler

    在处理大量PNG图片时,大小优化和安全加密是两个重要的考虑因素,这正是"png图片压缩加密工具binCompiler"的核心功能。 binCompiler是一个专为PNG图像设计的实用工具,它能够对PNG文件进行高效的压缩,以减小文件...

    PNG无损压缩

    PNG无损压缩的工作原理是通过一种叫做游程编码(Run-Length Encoding, RLE)和霍夫曼编码(Huffman Coding)的算法实现的。RLE主要用于处理连续的相同颜色像素,将它们打包成单一的计数值,从而减少数据量。霍夫曼...

    png图片压缩

    PNG图片压缩是减小游戏体积、提高加载速度和节省用户设备内存的有效手段。 标题"png图片压缩"指的是针对PNG图像文件进行的优化处理,旨在减少其文件大小而不明显降低图像质量。这通常涉及到几种技术,包括颜色量化...

    java图片压缩通用类

    在Java中,我们可以使用多种库来实现图片压缩,如Apache Commons Imaging (以前称为Apache Sanselan)、ImageMagick等。但是,这个"java图片压缩通用类"可能是开发者自定义的一个类,它可能封装了一些常见的图像处理...

    Java处理PNG透明性总结的几种方法,好用

    Java处理PNG透明性总结的几种方法,好用Java处理PNG透明性总结的几种方法,好用Java处理PNG透明性总结的几种方法,好用Java处理PNG透明性总结的几种方法,好用Java处理PNG透明性总结的几种方法,好...

    j2me中的png图片压缩软件

    标题所提及的“j2me中的png图片压缩软件”就是针对这个问题设计的工具。这种软件的主要功能是将原本较大的PNG图片压缩成更小的尺寸,同时尽可能保持图像质量。这通常是通过减少颜色位深度、优化色彩表、删除不必要的...

    java图片压缩

    `ImageSizer.java`这个文件很可能是实现了一个简单的图片压缩工具类。在这个类中,开发者可能已经实现了对JPG、PNG和GIF(非动态)格式图片的压缩功能。下面我们将深入探讨Java中图片压缩的相关知识点。 1. **Java...

    PNGOUTWin png图片压缩工具

    总的来说,PNGOUTWin是一款强大的PNG图片压缩工具,它的易用性、高效性和对图片质量的保护使其成为设计师、开发者和普通用户在处理PNG图片时的理想选择。通过压缩,可以有效地减少图片的存储需求,提升网络传输速度...

    java对图片改变大小,png防止变黑,加圆角 圆角外透明

    这个例子中,我们首先读取PNG图片,然后进行等比例缩放,接着创建一个圆角矩形覆盖整个图像,并应用模糊效果以平滑边缘。最后,我们将处理后的图像保存为新的PNG文件。 请注意,这只是一个基础示例,实际应用中可能...

    tinypng 图片压缩脚本,自动遍历项目图片.zip

    tinypng 图片压缩脚本,自动遍历项目图片,并原地替换,单个 node 文件,无需依赖 npm 包,下载单文件即可使用 软件开发设计:应用软件开发、系统软件开发、移动应用开发、网站开发C++、Java、python、web、C#等语言...

Global site tag (gtag.js) - Google Analytics