版权声明:本文为博主原创文章,未经博主允许不得转载。
由于对资源或网速的要求,在手机游戏或一般的网页游戏中,希望能对图片进最大可能的压缩,以节省资源。最近公司做的项目也有对这方面的需求,于是我在网上逛了半天,希望能发现现成版的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代码-数字图像处理教学演示项目-数字图像处理教学演示项目代码-数字图像处理教学演示系统-数字图像处理教学演示系统源码-数字...
### 人工智能-图像处理-数字图像处理测试仪的研制 #### 一、引言与背景 数字图像处理技术自上个世纪20年代初具雏形以来,经历了一个漫长的发展过程,直至上个世纪80年代,随着计算机科学的飞速进步,逐渐形成了...
### 人工智能-图像处理-数字图像处理系统下位机设计 #### 一、引言 在当前信息技术飞速发展的背景下,现场可编程门阵列(Field Programmable Gate Array,简称FPGA)作为一种高度灵活的集成电路设计技术,在多个...
图像处理背景 - 图像处理是一门综合性的学科,涉及数学、计算机科学、物理学等多个领域。 - 图像处理技术广泛应用于医学成像、安全监控、卫星遥感等多个领域。 #### 2. 图像处理中的数学问题 - **逆问题**:在...
### 人工智能-图像处理-基于数字图像处理的车辆牌照识别技术的研究 #### 一、绪论 1. **图像的概念**: - 图像是光线透过物体或者被物体反射后,进入人的眼睛中,在大脑中形成的印象或认知。 - 它是主观对客观的...
OTSU算法也称最大类间差法,有时也称之为大津算法,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和...
### 人工智能-图像处理-数字图像处理技术在牛肉品质分级中的应用研究 #### 知识点一:数字图像处理技术概述 数字图像处理技术是指利用计算机对图像进行一系列操作(如采集、转换、分析等)的技术集合。这些技术...
### 人工智能-图像处理-数字图像处理在电厂火焰检测中的应用 #### 1. 引言及背景 在火力发电厂中,为了确保燃煤锅炉的安全运行,必须对炉膛内的火焰进行实时有效的检测。传统的火焰检测方法主要是通过红外线、可见...
这一步可能需要处理对话框的大小和图片的拉伸问题,以确保背景适应对话框。 4. **处理窗口消息**: 当对话框大小改变时,需要更新背景图片的绘制。为此,你需要重写`OnSize()`函数,重新绘制背景图片以适应新尺寸...
### 人工智能-图像处理-基于图像处理的混凝土裂缝宽度检测技术的研究 #### 一、课题背景与意义 随着社会进步和经济快速发展,基础设施建设规模日益扩大,高强度、高性能的混凝土建筑材料得到了广泛应用。然而,随...
本资源包包含164个gif格式的loading动画,GIF是一种常见的动态图像格式,支持透明背景。这种格式的优势在于兼容性好,可以在大多数设备和浏览器上正常显示,且文件大小相对较小,有利于减少页面加载时间。透明背景的...
### 人工智能-图像处理-数字图像处理技术在尺寸公差检测中的应用研究 #### 一、研究背景与意义 在当前工业自动化与智能化快速发展的背景下,对于产品尺寸公差的精密检测需求日益增加。特别是在制药行业中,玻璃瓶...
在OpenCV-Python图像处理中,区分前景与背景权重的图像融合是一项重要的技术,它广泛应用于计算机视觉、图像分析和机器学习等领域。本案例重点探讨如何利用Python和OpenCV库来实现这一功能,以创建高质量的图像融合...
Java 处理图片背景颜色的方法 Java 处理图片背景颜色的方法是指利用 Java 语言来处理图片的背景颜色,例如将蓝底寸照批量转换为白底。这种方法可以批量处理大量图片,具有很高的实用价值。 title 中的“Java 处理...
- GDIPlus转换图像格式”是易语言环境下,利用GDI+进行高效、灵活的图像格式转换的工具,它的核心价值在于解决了PNG到JPG转换时可能出现的背景色问题,并提供了自定义背景色的能力,从而满足了多样化的图像处理需求...
该代码提供一个用于从背景单一、物体相对简单且相互分离的图片中获得图片中的物体最小外接圆的圆心与半径的matlab函数:[PIC,center,radius]=m_minCircumferentialCircle(pic,background);进而在其原图上通过所得...
### 人工智能-图像处理-基于数字图像处理的陶瓷瓶裂纹检测研究 #### 一、陶瓷材料无损检测概述 1. **国内外陶瓷材料无损检测现状**: - 陶瓷材料作为现代材料三大支柱之一,因其独特的物理化学性能而在众多工业...
此外,背景去除是图像处理中的关键环节,文章采用了Canny边缘检测算法和形态学理论来分离滑石矿品的轮廓,有效去除背景干扰,提高目标矿石的识别精度。 总的来说,本研究通过综合应用小波分析、OpenCV库和图像处理...
在MATLAB中,创建具有透明背景的图形是许多开发者的需求,特别是在进行图像处理或可视化时。标题"matlab开发-透明背景"所涉及的核心技术主要包含两个方面:一是如何实现图像的透明效果,二是如何在不使用`alphadata`...
### 数字图像处理实验报告-灰度和彩色图像处理 #### 实验背景 随着计算机技术的发展,数字图像处理成为了一门重要的学科,在诸多领域都有着广泛的应用。本实验旨在通过一系列具体的图像处理任务,帮助学生深入理解...