版权声明:本文为博主原创文章,未经博主允许不得转载。
由于对资源或网速的要求,在手机游戏或一般的网页游戏中,希望能对图片进最大可能的压缩,以节省资源。最近公司做的项目也有对这方面的需求,于是我在网上逛了半天,希望能发现现成版的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;下面我们将一张原图用下面的几句代码分别调用不同的参数生成图片看看:
- for(int i=1;i<=13;i++){
- tempImage=new BufferedImage(width, height, i);
- g2D = (Graphics2D) tempImage.getGraphics();
- g2D.drawImage(sourceImage, 0, 0, null);
- ImageIO.write(tempImage, "png", new File("cut/c_com_"+i+".png"));
- }
原图如下,PNG格式,大小24.0KB:
压缩后的图片:
从图片看到,黑白照片最小,不过这不是我们想要,排除;最后一张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生成的图片背景却是不透明的,自然而然的我们想到了把不透明的背景替换成透明的不就行了。
- /**
- * 将背景为黑色不透明的图片转化为背景透明的图片
- * @param image 背景为黑色不透明的图片(用555格式转化后的都是黑色不透明的)
- * @return 转化后的图片
- */
- private static BufferedImage getConvertedImage(BufferedImage image){
- int width=image.getWidth();
- int height=image.getHeight();
- BufferedImage convertedImage=null;
- Graphics2D g2D=null;
- //采用带1 字节alpha的TYPE_4BYTE_ABGR,可以修改像素的布尔透明
- convertedImage=new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
- g2D = (Graphics2D) convertedImage.getGraphics();
- g2D.drawImage(image, 0, 0, null);
- //像素替换,直接把背景颜色的像素替换成0
- for(int i=0;i<width;i++){
- for(int j=0;j<height;j++){
- int rgb=convertedImage.getRGB(i, j);
- if(isBackPixel(rgb)){
- convertedImage.setRGB(i, j,0);
- }
- }
- }
- g2D.drawImage(convertedImage, 0, 0, null);
- return convertedImage;
- }
其中的isBackPixel(rgb)用于判断当前像素是否为背景像素:
- /**
- * 判断当前像素是否为黑色不透明的像素(-16777216)
- * @param pixel 要判断的像素
- * @return 是背景像素返回true,否则返回false
- */
- private static boolean isBackPixel(int pixel){
- int back[]={-16777216};
- for(int i=0;i<back.length;i++){
- if(back[i]==pixel) return true;
- }
- return false;
- }
经转化后的图片如下:
转化后稍微大了一点,这个可以接受;要命的是带了一个黑色边框。为什么呢?原因很简单,原图中边框部分的像素是介于透明和不透明之间的,而经过555格式压缩后所有像素都变成了布尔透明,也就是说所有的像素要么是透明的要么就是不透明的。
最容易想到的方法就是把边框的像素换成原图边框的像素,关键在于怎么判断当前像素是否为图片的边框像素,这个算法可能得花费你一定的时间,下面只是我想到的一种实现:
- /**
- * 图片压缩
- * @param sourceImage 要压缩的图片
- * @return 压缩后的图片
- * @throws IOException 图片读写异常
- */
- public static BufferedImage compressImage(BufferedImage sourceImage) throws IOException{
- if(sourceImage==null) throw new NullPointerException("空图片");
- BufferedImage cutedImage=null;
- BufferedImage tempImage=null;
- BufferedImage compressedImage=null;
- Graphics2D g2D=null;
- //图片自动裁剪
- cutedImage=cutImageAuto(sourceImage);
- int width=cutedImage.getWidth();
- int height=cutedImage.getHeight();
- //图片格式为555格式
- tempImage=new BufferedImage(width, height, BufferedImage.TYPE_USHORT_555_RGB);
- g2D = (Graphics2D) tempImage.getGraphics();
- g2D.drawImage(sourceImage, 0, 0, null);
- compressedImage=getConvertedImage(tempImage);
- //经过像素转化后的图片
- compressedImage=new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
- g2D = (Graphics2D) compressedImage.getGraphics();
- g2D.drawImage(tempImage, 0, 0, null);
- int pixel[]=new int[width*height];
- int sourcePixel[]=new int[width*height];
- int currentPixel[]=new int[width*height];
- sourcePixel=cutedImage.getRGB(0, 0, width, height, sourcePixel, 0, width);
- currentPixel=tempImage.getRGB(0, 0, width, height, currentPixel, 0, width);
- for(int i=0;i<currentPixel.length;i++){
- if(i==0 || i==currentPixel.length-1){
- pixel[i]=0;
- //内部像素
- }else if(i>width && i<currentPixel.length-width){
- int bef=currentPixel[i-1];
- int now=currentPixel[i];
- int aft=currentPixel[i+1];
- int up=currentPixel[i-width];
- int down=currentPixel[i+width];
- //背景像素直接置为0
- if(isBackPixel(now)){
- pixel[i]=0;
- //边框像素和原图一样
- }else if((!isBackPixel(now) && isBackPixel(bef))
- ||(!isBackPixel(now) && isBackPixel(aft))
- ||(!isBackPixel(now) && isBackPixel(up))
- ||(!isBackPixel(now) &&isBackPixel(down))
- ){
- pixel[i]=sourcePixel[i];
- //其他像素和555压缩后的像素一样
- }else{
- pixel[i]=now;
- }
- //边界像素
- }else{
- int bef=currentPixel[i-1];
- int now=currentPixel[i];
- int aft=currentPixel[i+1];
- if(isBackPixel(now)){
- pixel[i]=0;
- }else if((!isBackPixel(now) && isBackPixel(bef))
- ||(!isBackPixel(now) && isBackPixel(aft))){
- pixel[i]=sourcePixel[i];
- }else{
- pixel[i]=now;
- }
- }
- }
- compressedImage.setRGB(0, 0, width, height, pixel, 0, width);
- g2D.drawImage(compressedImage, 0, 0, null);
- ImageIO.write(cutedImage, "png", new File("cut/a_cut.png"));
- ImageIO.write(tempImage, "png", new File("cut/b_555.png"));
- ImageIO.write(compressedImage, "png", new File("cut/c_com.png"));
- return compressedImage;
- }
其中的cutedImage=cutImageAuto(sourceImage);是对原图进行裁剪,代码如下:
- /**
- * 图片自动裁剪
- * @param image 要裁剪的图片
- * @return 裁剪后的图片
- */
- public static BufferedImage cutImageAuto(BufferedImage image){
- Rectangle area=getCutAreaAuto(image);
- return image.getSubimage(area.x, area.y,area.width, area.height);
- }
- /**
- * 获得裁剪图片保留区域
- * @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;
- }
改善后得到的图片:
实际上,这种方法只适用于图片颜色分明(边框颜色分明,背景颜色唯一),黑色像素不多的图片。一些比较特殊的图片就得特殊处理了,如以下图片:
压缩后
原因是黑色不透明像素也是图片实体的一部分,这样就把它替换成白色透明的了。可以把代码改一下,但是图片的大小会增加不少,就是把程序认为是背景颜色的像素替换成原图片的像素;将compressImage()方法中的第33、43、61行改成 pixel[i]=sourcePixel[i]; 即可。
相关推荐
数字图像处理教学演示网站-数字图像处理教学演示网站源码-数字图像处理教学演示网站java代码-数字图像处理教学演示项目-数字图像处理教学演示项目代码-数字图像处理教学演示系统-数字图像处理教学演示系统源码-数字...
OTSU算法也称最大类间差法,有时也称之为大津算法,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和...
音视频-图像处理-识别杂草和土壤背景的图像处理方法研究.pdf
OpenCV-Python图像处理:区分前景背景权重的图像融合案例.rar
该代码提供一个用于从背景单一、物体相对简单且相互分离的图片中获得图片中的物体最小外接圆的圆心与半径的matlab函数:[PIC,center,radius]=m_minCircumferentialCircle(pic,background);进而在其原图上通过所得...
音视频-编解码-雾天背景下的工业电视图像清晰化处理关键技术问题的研究.pdf
电信设备-建立图像处理中基于运动信息的背景模型的方法.zip
和panel 的前景和背景定义了一组颜色,并为菜单定义可文字的大小和颜色属性,你可以将这个 主题应用在你程序当中所有的Activity里。 风格和主题都是资源。你可以用android提供的一些默认的风格和主题资源,你也可以...
转化后图片清晰度发生了变化(处理后有黑色像素化点) 3.效率不好。。 ============== 经过几天的模索、找资料、研究 1。保存后背景色不会变黑色(已测,在PS和页面中确已透明) 2。清晰度只有一点细微变化,处理后...
主要为大家详细介绍了java处理图片背景颜色的方法,蓝底寸照批量转换为白底,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
图像处理-BackgroundMattingV2-实时高分辨率背景抠像
提出了一种基于YUV颜色空间与支持向量机的复杂背景文本区域定位方法。算法将图像由RGB颜色空间转换至YUV颜色空间;...实验结果表明,提出的方法定位准确率在65%以上,可以有效地实现复杂背景下图像文本区域的定位。
为了实现复杂背景下过渡区的准确提取,首先采用基于直方图的非均衡拉伸算法对原始图像进行处理,以提高整体对比度;然后采用基于小波分析的能量特征图像法区分目标区域和背景区域,这是因为能量特征的选取与背景纹理...
内容概要:使用MATLAB代码实现绿幕视频的背景视频替换,主要通过HSV色彩空间对每帧图像的颜色进行处理,以及通过形态学处理完善处理效果,再融合前景和后景视频,就获得了处理好的视频。 适用人群:适合初学MATLAB的...
本毕业设计旨在探索并实现一种基于Java图形图像处理的方法,以提高图像处理的速度和精度。该方法将使用Java语言编写,并利用Java图形处理库来实现图像处理。 ## 研究背景 随着计算机技术的发展,图形图像处理在...
**VB图像处理工具设计(论文+源代码)** 本资源提供了一个基于Visual Basic(VB)的图像处理工具设计的完整论文和源代码。该工具旨在通过简单的操作实现图像的多种处理效果,如缩放、旋转、翻转、裁剪及颜色调整等...
首先用视频第一帧图像HSV空间的色度H和亮度V作为背景特征进行初始化,建立两种包含色度和亮度特征的背景模型类,即初始化得到的原始背景类和受光照或者其他因素影响得到的在原始背景周围波动的背景波动类,利用这两...
vue-cli3.0图片打包,小于10k的图片没有自动转base64问题处理
由于IE7以下不支持图片的背景的透明.根据IE特性,我们可以用IE的滤镜来实现透明处理 你只要包含下面这个pngfix.js文件就行,如下: <!--[if lt IE 7]> <script defer type="text/javascript" src="/Js/pngfix.js"></...
NULL 博文链接:https://wdhagl-126-com.iteye.com/blog/580227