Java中图片按质量压缩

来源:互联网 发布:java 命名 下划线开头 编辑:程序博客网 时间:2024/05/16 02:40

转载自:http://java-mzd.iteye.com/blog/730504


研究了那么久的图片压缩原理之后

虽然没能带回一个自己用JAVA实现的图片压缩软件

但是总算是自己终于对图片压缩有了个清晰的了解

 

好了,废话不多说

继续上次关于远程监控系统中用UDP广播图片遇到的图片压缩大小瓶颈问题

 

首先再次讨论上次给出的那组数据(关于对比ImageIO默认参数下写出GIF/JPEG,以及自己设置参数的JPEG)的讨论

GIF采用的是字典LZW算法,该算法是无损压缩,能提供近乎完美的无损压缩比,我们记得压缩后的图片大小大约为90KB

而JPEG默认情况下为有损压缩,压缩后大小大约200KB

在ImageIO中通过自己设置压缩质量来压缩,我们发现当压缩质量小于0.5以后,图片大小的变化是很缓慢的

用import com.sun.image.codec.jpeg.JPEGCodec提供的编码解码类来设置压缩质量,在同等质量的情况下,虽然比ImageIO中图片大小变小了,其实也是很有限的。通过前文,我们了解了这样设置图片质量其实知识改变量化表,在图片质量已经不高的情况下,其改变对图片大小的影响其实是很小的

 

测试代码

Java代码  收藏代码
  1. package cn.mzd.newIM.test;  
  2.   
  3. import java.awt.AWTException;  
  4. import java.awt.Dimension;  
  5. import java.awt.Rectangle;  
  6. import java.awt.image.BufferedImage;  
  7. import java.awt.image.ColorModel;  
  8. import java.io.ByteArrayOutputStream;  
  9. import java.io.IOException;  
  10. import java.util.Calendar;  
  11. import java.util.GregorianCalendar;  
  12. import java.util.Iterator;  
  13.   
  14. import javax.imageio.IIOImage;  
  15. import javax.imageio.ImageIO;  
  16. import javax.imageio.ImageWriteParam;  
  17. import javax.imageio.ImageWriter;  
  18.   
  19. import com.sun.image.codec.jpeg.JPEGCodec;  
  20. import com.sun.image.codec.jpeg.JPEGEncodeParam;  
  21. import com.sun.image.codec.jpeg.JPEGImageEncoder;  
  22.   
  23. public class ImageSizeTest {  
  24.     /** 
  25.      * @param args 
  26.      * @throws AWTException 
  27.      */  
  28.     public void getImageSize() throws AWTException {  
  29.         java.awt.Robot rb = new java.awt.Robot();  
  30.         Dimension d = java.awt.Toolkit.getDefaultToolkit().getScreenSize();  
  31.         Rectangle rt = new Rectangle(00, (int) d.getWidth(), (int) d  
  32.                 .getHeight());  
  33.         for (int i = 0; i < 1000; i++) {  
  34.             BufferedImage image = rb.createScreenCapture(rt);  
  35.             bufferedImageTobytes(image, "gif");  
  36.             bufferedImageTobytes(image, "jpeg");  
  37.             bufferedImageTobytes(image, 0.9f);  
  38.             newCompressImage(image, 0.9f);  
  39.   
  40.         }  
  41.     }  
  42.   
  43.     /** 
  44.      * 用Format对应格式中ImageIO默认参数把IMAGE打包成BYTE[] 
  45.      * @param image 
  46.      * @return 
  47.      */  
  48.     private byte[] bufferedImageTobytes(BufferedImage image, String format) {  
  49.         System.out.println(format + "格式开始打包" + getCurrentTime());  
  50.         ByteArrayOutputStream out = new ByteArrayOutputStream();  
  51.         try {  
  52.             ImageIO.write(image, format, out);  
  53.         } catch (IOException e) {  
  54.             e.printStackTrace();  
  55.         }  
  56.         System.out.println(format + "格式完成打包-----" + getCurrentTime()  
  57.                 + "----lenth------" + out.toByteArray().length);  
  58.         return out.toByteArray();  
  59.     }  
  60.   
  61.     /** 
  62.      *  
  63.      * 自己设置压缩质量来把图片压缩成byte[] 
  64.      *  
  65.      * @param image 
  66.      *            压缩源图片 
  67.      * @param quality 
  68.      *            压缩质量,在0-1之间, 
  69.      * @return 返回的字节数组 
  70.      */  
  71.     private byte[] bufferedImageTobytes(BufferedImage image, float quality) {  
  72.         System.out.println("jpeg" + quality + "质量开始打包" + getCurrentTime());  
  73.         // 如果图片空,返回空  
  74.         if (image == null) {  
  75.             return null;  
  76.         }     
  77.         // 得到指定Format图片的writer  
  78.         Iterator<ImageWriter> iter = ImageIO  
  79.                 .getImageWritersByFormatName("jpeg");// 得到迭代器  
  80.         ImageWriter writer = (ImageWriter) iter.next(); // 得到writer  
  81.   
  82.         // 得到指定writer的输出参数设置(ImageWriteParam )  
  83.         ImageWriteParam iwp = writer.getDefaultWriteParam();  
  84.         iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩  
  85.         iwp.setCompressionQuality(quality); // 设置压缩质量参数  
  86.   
  87.         iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);  
  88.   
  89.         ColorModel colorModel = ColorModel.getRGBdefault();  
  90.         // 指定压缩时使用的色彩模式  
  91.         iwp.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel,  
  92.                 colorModel.createCompatibleSampleModel(1616)));  
  93.   
  94.         // 开始打包图片,写入byte[]  
  95.         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流  
  96.         IIOImage iIamge = new IIOImage(image, nullnull);  
  97.         try {  
  98.             // 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput  
  99.             // 通过ImageIo中的静态方法,得到byteArrayOutputStream的ImageOutput  
  100.             writer.setOutput(ImageIO  
  101.                     .createImageOutputStream(byteArrayOutputStream));  
  102.             writer.write(null, iIamge, iwp);  
  103.         } catch (IOException e) {  
  104.             System.out.println("write errro");  
  105.             e.printStackTrace();  
  106.         }  
  107.         System.out.println("jpeg" + quality + "质量完成打包-----" + getCurrentTime()  
  108.                 + "----lenth----" + byteArrayOutputStream.toByteArray().length);  
  109.         return byteArrayOutputStream.toByteArray();  
  110.     }  
  111.   
  112.     /** 
  113.      * 自己定义格式,得到当前系统时间 
  114.      *  
  115.      * @return 
  116.      */  
  117.     private String getCurrentTime() {  
  118.         Calendar c = new GregorianCalendar();  
  119.         int hour = c.get(Calendar.HOUR_OF_DAY);  
  120.         int min = c.get(Calendar.MINUTE);  
  121.         int second = c.get(Calendar.SECOND);  
  122.         int millsecond = c.get(Calendar.MILLISECOND);  
  123.         String time = hour + "点" + min + "分" + second + "秒" + millsecond;  
  124.         return time;  
  125.     }  
  126.   
  127.     /** 
  128.      *  通过 com.sun.image.codec.jpeg.JPEGCodec提供的编码器来实现图像压缩 
  129.      * @param image 
  130.      * @param quality 
  131.      * @return 
  132.      */  
  133.     private byte[] newCompressImage(BufferedImage image, float quality) {  
  134.         // 如果图片空,返回空  
  135.         if (image == null) {  
  136.             return null;  
  137.         }  
  138.         // 开始开始,写入byte[]  
  139.         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流  
  140.         // 设置压缩参数  
  141.         JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);  
  142.         param.setQuality(quality, false);  
  143.         // 设置编码器  
  144.         JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(  
  145.                 byteArrayOutputStream, param);  
  146.         System.out.println("newCompressive" + quality + "质量开始打包"  
  147.                 + getCurrentTime());  
  148.         try {  
  149.             encoder.encode(image);  
  150.         } catch (Exception ef){  
  151.             ef.printStackTrace();  
  152.         }  
  153.         System.out.println("newCompressive" + quality + "质量打包完成"  
  154.                 + getCurrentTime() + "----lenth----"  
  155.                 + byteArrayOutputStream.toByteArray().length);  
  156.         return byteArrayOutputStream.toByteArray();  
  157.   
  158.     }  
  159.     public static void main(String args[]) throws Exception {  
  160.         ImageSizeTest test = new ImageSizeTest();  
  161.         test.getImageSize();  
  162.     }  
  163.   
  164. }  

 测试结果依然不理想

 

突发奇想

GIF采用的是LZW编码进行压缩

JPEG后期的熵编码用的是Huffman,那如果先进行JPEG算法,再进行LZW算法,会有什么样的效果呢?

想干就干

咱写代码来测试一下

测试代码如下

 

 

Java代码  收藏代码
  1. package cn.mzd.newIM.test;  
  2.   
  3. import java.awt.AWTException;  
  4. import java.awt.Dimension;  
  5. import java.awt.Rectangle;  
  6. import java.awt.image.BufferedImage;  
  7. import java.awt.image.ColorModel;  
  8. import java.io.ByteArrayInputStream;  
  9. import java.io.ByteArrayOutputStream;  
  10. import java.io.IOException;  
  11. import java.util.Calendar;  
  12. import java.util.GregorianCalendar;  
  13. import java.util.Iterator;  
  14.   
  15. import javax.imageio.IIOImage;  
  16. import javax.imageio.ImageIO;  
  17. import javax.imageio.ImageWriteParam;  
  18. import javax.imageio.ImageWriter;  
  19.   
  20. import sun.awt.image.JPEGImageDecoder;  
  21.   
  22. import com.sun.image.codec.jpeg.JPEGCodec;  
  23. import com.sun.image.codec.jpeg.JPEGEncodeParam;  
  24. import com.sun.image.codec.jpeg.JPEGImageEncoder;  
  25.   
  26. public class ImageSizeTest {  
  27.     /** 
  28.      * @param args 
  29.      * @throws AWTException 
  30.      */  
  31.     public void getImageSize() throws AWTException {  
  32.         java.awt.Robot rb = new java.awt.Robot();  
  33.         Dimension d = java.awt.Toolkit.getDefaultToolkit().getScreenSize();  
  34.         Rectangle rt = new Rectangle(00, (int) d.getWidth(), (int) d  
  35.                 .getHeight());  
  36.         for (int i = 0; i < 1000; i++) {  
  37.             BufferedImage image = rb.createScreenCapture(rt);  
  38.             // bufferedImageTobytes(image, "gif");  
  39.             giftest(bufferedImageTobytes(image, "jpeg"));  
  40.             giftest(bufferedImageTobytes(image, 0.2f));  
  41.             giftest(newCompressImage(image, 0.2f));  
  42.   
  43.         }  
  44.     }  
  45.   
  46.     /** 
  47.      * 用Format对应格式中ImageIO默认参数把IMAGE打包成BYTE[] 
  48.      *  
  49.      * @param image 
  50.      * @return 
  51.      */  
  52.     private byte[] bufferedImageTobytes(BufferedImage image, String format) {  
  53.         System.out.println(format + "格式开始打包" + getCurrentTime());  
  54.         ByteArrayOutputStream out = new ByteArrayOutputStream();  
  55.         try {  
  56.             ImageIO.write(image, format, out);  
  57.         } catch (IOException e) {  
  58.             e.printStackTrace();  
  59.         }  
  60.         System.out.println(format + "格式完成打包-----" + getCurrentTime()  
  61.                 + "----lenth------" + out.toByteArray().length);  
  62.         return out.toByteArray();  
  63.     }  
  64.   
  65.     /** 
  66.      *  
  67.      * 自己设置压缩质量来把图片压缩成byte[] 
  68.      *  
  69.      * @param image 
  70.      *            压缩源图片 
  71.      * @param quality 
  72.      *            压缩质量,在0-1之间, 
  73.      * @return 返回的字节数组 
  74.      */  
  75.     private byte[] bufferedImageTobytes(BufferedImage image, float quality) {  
  76.         System.out.println("jpeg" + quality + "质量开始打包" + getCurrentTime());  
  77.         // 如果图片空,返回空  
  78.         if (image == null) {  
  79.             return null;  
  80.         }  
  81.         // 得到指定Format图片的writer  
  82.         Iterator<ImageWriter> iter = ImageIO  
  83.                 .getImageWritersByFormatName("jpeg");// 得到迭代器  
  84.         ImageWriter writer = (ImageWriter) iter.next(); // 得到writer  
  85.   
  86.         // 得到指定writer的输出参数设置(ImageWriteParam )  
  87.         ImageWriteParam iwp = writer.getDefaultWriteParam();  
  88.         iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩  
  89.         iwp.setCompressionQuality(quality); // 设置压缩质量参数  
  90.   
  91.         iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);  
  92.   
  93.         ColorModel colorModel = ColorModel.getRGBdefault();  
  94.         // 指定压缩时使用的色彩模式  
  95.         iwp.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel,  
  96.                 colorModel.createCompatibleSampleModel(1616)));  
  97.   
  98.         // 开始打包图片,写入byte[]  
  99.         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流  
  100.         IIOImage iIamge = new IIOImage(image, nullnull);  
  101.         try {  
  102.             // 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput  
  103.             // 通过ImageIo中的静态方法,得到byteArrayOutputStream的ImageOutput  
  104.             writer.setOutput(ImageIO  
  105.                     .createImageOutputStream(byteArrayOutputStream));  
  106.             writer.write(null, iIamge, iwp);  
  107.         } catch (IOException e) {  
  108.             System.out.println("write errro");  
  109.             e.printStackTrace();  
  110.         }  
  111.         System.out.println("jpeg" + quality + "质量完成打包-----" + getCurrentTime()  
  112.                 + "----lenth----" + byteArrayOutputStream.toByteArray().length);  
  113.         return byteArrayOutputStream.toByteArray();  
  114.     }  
  115.   
  116.     /** 
  117.      * 自己定义格式,得到当前系统时间 
  118.      *  
  119.      * @return 
  120.      */  
  121.     private String getCurrentTime() {  
  122.         Calendar c = new GregorianCalendar();  
  123.         int hour = c.get(Calendar.HOUR_OF_DAY);  
  124.         int min = c.get(Calendar.MINUTE);  
  125.         int second = c.get(Calendar.SECOND);  
  126.         int millsecond = c.get(Calendar.MILLISECOND);  
  127.         String time = hour + "点" + min + "分" + second + "秒" + millsecond;  
  128.         return time;  
  129.     }  
  130.   
  131.     /** 
  132.      * 通过 com.sun.image.codec.jpeg.JPEGCodec提供的编码器来实现图像压缩 
  133.      *  
  134.      * @param image 
  135.      * @param quality 
  136.      * @return 
  137.      */  
  138.     private byte[] newCompressImage(BufferedImage image, float quality) {  
  139.         // 如果图片空,返回空  
  140.         if (image == null) {  
  141.             return null;  
  142.         }  
  143.         // 开始开始,写入byte[]  
  144.         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流  
  145.         // 设置压缩参数  
  146.         JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);  
  147.         param.setQuality(quality, false);  
  148.         // 设置编码器  
  149.         JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(  
  150.                 byteArrayOutputStream, param);  
  151.         System.out.println("newCompressive" + quality + "质量开始打包"  
  152.                 + getCurrentTime());  
  153.         try {  
  154.             encoder.encode(image);  
  155.         } catch (Exception ef) {  
  156.             ef.printStackTrace();  
  157.         }  
  158.         System.out.println("newCompressive" + quality + "质量打包完成"  
  159.                 + getCurrentTime() + "----lenth----"  
  160.                 + byteArrayOutputStream.toByteArray().length);  
  161.         return byteArrayOutputStream.toByteArray();  
  162.   
  163.     }  
  164.   
  165.     /** 
  166.      * 测试把图片先压缩成JPEG,再用JPEG压缩成GIF 
  167.      */  
  168.     public byte[] giftest(byte[] imagedata) {  
  169.         System.out.println("giftest开始打包" + getCurrentTime());  
  170.         BufferedImage image = null;  
  171.         ByteArrayInputStream input = new ByteArrayInputStream(imagedata);  
  172.         // 得到解码器  
  173.         JPEGImageDecoder decoder = (JPEGImageDecoder) JPEGCodec  
  174.                 .createJPEGDecoder(input);  
  175.         // 把JPEG 数据流解压缩  
  176.         try {  
  177.             image = ((com.sun.image.codec.jpeg.JPEGImageDecoder) decoder)  
  178.                     .decodeAsBufferedImage();  
  179.         } catch (Exception ef) {  
  180.             ef.printStackTrace();  
  181.         }  
  182.         ByteArrayOutputStream out = new ByteArrayOutputStream();  
  183.         try {  
  184.             ImageIO.write(image, "gif", out);  
  185.         } catch (IOException e) {  
  186.             e.printStackTrace();  
  187.         }  
  188.         System.out.println("giftest开始打包" + getCurrentTime() + "----lenth----"  
  189.                 + out.toByteArray().length);  
  190.         return out.toByteArray();  
  191.     }  
  192.   
  193.     public static void main(String args[]) throws Exception {  
  194.         ImageSizeTest test = new ImageSizeTest();  
  195.         test.getImageSize();  
  196.     }  
  197.   
  198. }  

 

测试结果就补贴了

发现,对于默认的JPEG参数压缩,GIF能二次压缩到90K左右(类似之间GIF压缩)

而对于自己设定参数的压缩,当质量很高时(高于0.5),GIF效果还是有的

当质量很低时(低于0.1)再进行GIF压缩,大小反而变大-------------------分布均匀

 

 

此次试验再次宣告失败

 

难道我们的监控系统就不能用UDP来实现了吗?

 

虽然通过压缩图片直接打到保证图片质量和要求大小小于64KB的试验失败了,但是我们还有其他的办法

我们要始终相信灰太狼的那句“我还会再回来的”

 

具体怎么实现呢?

我想,思路如下----------把图片数据分包,用UDP广播分包后的消息,每个数据包内容里有属于第几个包的第几块

UDP的不可靠性,以及根据概率学知识,大家都知道分的包越多,越危险,于是,我们还是得先对图像进行压缩,再分包,再传送,尽量少分包

在接收方,用一个缓冲区来接收数据包。根本数据包内容中的标识来组装包,根据实时性要求,当接收消息超过3秒还未收到兄弟包,在丢弃。

别以为在这里我要用UDP来实现类型TCP的差错重传和确认机制,事情还没糟糕到需要做那一步。

0 0