转载自: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中图片大小变小了,其实也是很有限的。通过前文,我们了解了这样设置图片质量其实知识改变量化表,在图片质量已经不高的情况下,其改变对图片大小的影响其实是很小的
测试代码
- package cn.mzd.newIM.test;
- import java.awt.AWTException;
- import java.awt.Dimension;
- import java.awt.Rectangle;
- import java.awt.image.BufferedImage;
- import java.awt.image.ColorModel;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.util.Calendar;
- import java.util.GregorianCalendar;
- import java.util.Iterator;
- import javax.imageio.IIOImage;
- import javax.imageio.ImageIO;
- import javax.imageio.ImageWriteParam;
- import javax.imageio.ImageWriter;
- import com.sun.image.codec.jpeg.JPEGCodec;
- import com.sun.image.codec.jpeg.JPEGEncodeParam;
- import com.sun.image.codec.jpeg.JPEGImageEncoder;
- public class ImageSizeTest {
- /**
- * @param args
- * @throws AWTException
- */
- public void getImageSize() throws AWTException {
- java.awt.Robot rb = new java.awt.Robot();
- Dimension d = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
- Rectangle rt = new Rectangle(0, 0, (int) d.getWidth(), (int) d
- .getHeight());
- for (int i = 0; i < 1000; i++) {
- BufferedImage image = rb.createScreenCapture(rt);
- bufferedImageTobytes(image, "gif");
- bufferedImageTobytes(image, "jpeg");
- bufferedImageTobytes(image, 0.9f);
- newCompressImage(image, 0.9f);
- }
- }
- /**
- * 用Format对应格式中ImageIO默认参数把IMAGE打包成BYTE[]
- * @param image
- * @return
- */
- private byte[] bufferedImageTobytes(BufferedImage image, String format) {
- System.out.println(format + "格式开始打包" + getCurrentTime());
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- ImageIO.write(image, format, out);
- } catch (IOException e) {
- e.printStackTrace();
- }
- System.out.println(format + "格式完成打包-----" + getCurrentTime()
- + "----lenth------" + out.toByteArray().length);
- return out.toByteArray();
- }
- /**
- *
- * 自己设置压缩质量来把图片压缩成byte[]
- *
- * @param image
- * 压缩源图片
- * @param quality
- * 压缩质量,在0-1之间,
- * @return 返回的字节数组
- */
- private byte[] bufferedImageTobytes(BufferedImage image, float quality) {
- System.out.println("jpeg" + quality + "质量开始打包" + getCurrentTime());
- // 如果图片空,返回空
- if (image == null) {
- return null;
- }
- // 得到指定Format图片的writer
- Iterator<ImageWriter> iter = ImageIO
- .getImageWritersByFormatName("jpeg");// 得到迭代器
- ImageWriter writer = (ImageWriter) iter.next(); // 得到writer
- // 得到指定writer的输出参数设置(ImageWriteParam )
- ImageWriteParam iwp = writer.getDefaultWriteParam();
- iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩
- iwp.setCompressionQuality(quality); // 设置压缩质量参数
- iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
- ColorModel colorModel = ColorModel.getRGBdefault();
- // 指定压缩时使用的色彩模式
- iwp.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel,
- colorModel.createCompatibleSampleModel(16, 16)));
- // 开始打包图片,写入byte[]
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
- IIOImage iIamge = new IIOImage(image, null, null);
- try {
- // 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput
- // 通过ImageIo中的静态方法,得到byteArrayOutputStream的ImageOutput
- writer.setOutput(ImageIO
- .createImageOutputStream(byteArrayOutputStream));
- writer.write(null, iIamge, iwp);
- } catch (IOException e) {
- System.out.println("write errro");
- e.printStackTrace();
- }
- System.out.println("jpeg" + quality + "质量完成打包-----" + getCurrentTime()
- + "----lenth----" + byteArrayOutputStream.toByteArray().length);
- return byteArrayOutputStream.toByteArray();
- }
- /**
- * 自己定义格式,得到当前系统时间
- *
- * @return
- */
- private String getCurrentTime() {
- Calendar c = new GregorianCalendar();
- int hour = c.get(Calendar.HOUR_OF_DAY);
- int min = c.get(Calendar.MINUTE);
- int second = c.get(Calendar.SECOND);
- int millsecond = c.get(Calendar.MILLISECOND);
- String time = hour + "点" + min + "分" + second + "秒" + millsecond;
- return time;
- }
- /**
- * 通过 com.sun.image.codec.jpeg.JPEGCodec提供的编码器来实现图像压缩
- * @param image
- * @param quality
- * @return
- */
- private byte[] newCompressImage(BufferedImage image, float quality) {
- // 如果图片空,返回空
- if (image == null) {
- return null;
- }
- // 开始开始,写入byte[]
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
- // 设置压缩参数
- JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);
- param.setQuality(quality, false);
- // 设置编码器
- JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(
- byteArrayOutputStream, param);
- System.out.println("newCompressive" + quality + "质量开始打包"
- + getCurrentTime());
- try {
- encoder.encode(image);
- } catch (Exception ef){
- ef.printStackTrace();
- }
- System.out.println("newCompressive" + quality + "质量打包完成"
- + getCurrentTime() + "----lenth----"
- + byteArrayOutputStream.toByteArray().length);
- return byteArrayOutputStream.toByteArray();
- }
- public static void main(String args[]) throws Exception {
- ImageSizeTest test = new ImageSizeTest();
- test.getImageSize();
- }
- }
测试结果依然不理想
突发奇想
GIF采用的是LZW编码进行压缩
JPEG后期的熵编码用的是Huffman,那如果先进行JPEG算法,再进行LZW算法,会有什么样的效果呢?
想干就干
咱写代码来测试一下
测试代码如下
- package cn.mzd.newIM.test;
- import java.awt.AWTException;
- import java.awt.Dimension;
- import java.awt.Rectangle;
- import java.awt.image.BufferedImage;
- import java.awt.image.ColorModel;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.util.Calendar;
- import java.util.GregorianCalendar;
- import java.util.Iterator;
- import javax.imageio.IIOImage;
- import javax.imageio.ImageIO;
- import javax.imageio.ImageWriteParam;
- import javax.imageio.ImageWriter;
- import sun.awt.image.JPEGImageDecoder;
- import com.sun.image.codec.jpeg.JPEGCodec;
- import com.sun.image.codec.jpeg.JPEGEncodeParam;
- import com.sun.image.codec.jpeg.JPEGImageEncoder;
- public class ImageSizeTest {
- /**
- * @param args
- * @throws AWTException
- */
- public void getImageSize() throws AWTException {
- java.awt.Robot rb = new java.awt.Robot();
- Dimension d = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
- Rectangle rt = new Rectangle(0, 0, (int) d.getWidth(), (int) d
- .getHeight());
- for (int i = 0; i < 1000; i++) {
- BufferedImage image = rb.createScreenCapture(rt);
- // bufferedImageTobytes(image, "gif");
- giftest(bufferedImageTobytes(image, "jpeg"));
- giftest(bufferedImageTobytes(image, 0.2f));
- giftest(newCompressImage(image, 0.2f));
- }
- }
- /**
- * 用Format对应格式中ImageIO默认参数把IMAGE打包成BYTE[]
- *
- * @param image
- * @return
- */
- private byte[] bufferedImageTobytes(BufferedImage image, String format) {
- System.out.println(format + "格式开始打包" + getCurrentTime());
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- ImageIO.write(image, format, out);
- } catch (IOException e) {
- e.printStackTrace();
- }
- System.out.println(format + "格式完成打包-----" + getCurrentTime()
- + "----lenth------" + out.toByteArray().length);
- return out.toByteArray();
- }
- /**
- *
- * 自己设置压缩质量来把图片压缩成byte[]
- *
- * @param image
- * 压缩源图片
- * @param quality
- * 压缩质量,在0-1之间,
- * @return 返回的字节数组
- */
- private byte[] bufferedImageTobytes(BufferedImage image, float quality) {
- System.out.println("jpeg" + quality + "质量开始打包" + getCurrentTime());
- // 如果图片空,返回空
- if (image == null) {
- return null;
- }
- // 得到指定Format图片的writer
- Iterator<ImageWriter> iter = ImageIO
- .getImageWritersByFormatName("jpeg");// 得到迭代器
- ImageWriter writer = (ImageWriter) iter.next(); // 得到writer
- // 得到指定writer的输出参数设置(ImageWriteParam )
- ImageWriteParam iwp = writer.getDefaultWriteParam();
- iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩
- iwp.setCompressionQuality(quality); // 设置压缩质量参数
- iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
- ColorModel colorModel = ColorModel.getRGBdefault();
- // 指定压缩时使用的色彩模式
- iwp.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel,
- colorModel.createCompatibleSampleModel(16, 16)));
- // 开始打包图片,写入byte[]
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
- IIOImage iIamge = new IIOImage(image, null, null);
- try {
- // 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput
- // 通过ImageIo中的静态方法,得到byteArrayOutputStream的ImageOutput
- writer.setOutput(ImageIO
- .createImageOutputStream(byteArrayOutputStream));
- writer.write(null, iIamge, iwp);
- } catch (IOException e) {
- System.out.println("write errro");
- e.printStackTrace();
- }
- System.out.println("jpeg" + quality + "质量完成打包-----" + getCurrentTime()
- + "----lenth----" + byteArrayOutputStream.toByteArray().length);
- return byteArrayOutputStream.toByteArray();
- }
- /**
- * 自己定义格式,得到当前系统时间
- *
- * @return
- */
- private String getCurrentTime() {
- Calendar c = new GregorianCalendar();
- int hour = c.get(Calendar.HOUR_OF_DAY);
- int min = c.get(Calendar.MINUTE);
- int second = c.get(Calendar.SECOND);
- int millsecond = c.get(Calendar.MILLISECOND);
- String time = hour + "点" + min + "分" + second + "秒" + millsecond;
- return time;
- }
- /**
- * 通过 com.sun.image.codec.jpeg.JPEGCodec提供的编码器来实现图像压缩
- *
- * @param image
- * @param quality
- * @return
- */
- private byte[] newCompressImage(BufferedImage image, float quality) {
- // 如果图片空,返回空
- if (image == null) {
- return null;
- }
- // 开始开始,写入byte[]
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
- // 设置压缩参数
- JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);
- param.setQuality(quality, false);
- // 设置编码器
- JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(
- byteArrayOutputStream, param);
- System.out.println("newCompressive" + quality + "质量开始打包"
- + getCurrentTime());
- try {
- encoder.encode(image);
- } catch (Exception ef) {
- ef.printStackTrace();
- }
- System.out.println("newCompressive" + quality + "质量打包完成"
- + getCurrentTime() + "----lenth----"
- + byteArrayOutputStream.toByteArray().length);
- return byteArrayOutputStream.toByteArray();
- }
- /**
- * 测试把图片先压缩成JPEG,再用JPEG压缩成GIF
- */
- public byte[] giftest(byte[] imagedata) {
- System.out.println("giftest开始打包" + getCurrentTime());
- BufferedImage image = null;
- ByteArrayInputStream input = new ByteArrayInputStream(imagedata);
- // 得到解码器
- JPEGImageDecoder decoder = (JPEGImageDecoder) JPEGCodec
- .createJPEGDecoder(input);
- // 把JPEG 数据流解压缩
- try {
- image = ((com.sun.image.codec.jpeg.JPEGImageDecoder) decoder)
- .decodeAsBufferedImage();
- } catch (Exception ef) {
- ef.printStackTrace();
- }
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- ImageIO.write(image, "gif", out);
- } catch (IOException e) {
- e.printStackTrace();
- }
- System.out.println("giftest开始打包" + getCurrentTime() + "----lenth----"
- + out.toByteArray().length);
- return out.toByteArray();
- }
- public static void main(String args[]) throws Exception {
- ImageSizeTest test = new ImageSizeTest();
- test.getImageSize();
- }
- }
测试结果就补贴了
发现,对于默认的JPEG参数压缩,GIF能二次压缩到90K左右(类似之间GIF压缩)
而对于自己设定参数的压缩,当质量很高时(高于0.5),GIF效果还是有的
当质量很低时(低于0.1)再进行GIF压缩,大小反而变大-------------------分布均匀
此次试验再次宣告失败
难道我们的监控系统就不能用UDP来实现了吗?
虽然通过压缩图片直接打到保证图片质量和要求大小小于64KB的试验失败了,但是我们还有其他的办法
我们要始终相信灰太狼的那句“我还会再回来的”
具体怎么实现呢?
我想,思路如下----------把图片数据分包,用UDP广播分包后的消息,每个数据包内容里有属于第几个包的第几块
UDP的不可靠性,以及根据概率学知识,大家都知道分的包越多,越危险,于是,我们还是得先对图像进行压缩,再分包,再传送,尽量少分包
在接收方,用一个缓冲区来接收数据包。根本数据包内容中的标识来组装包,根据实时性要求,当接收消息超过3秒还未收到兄弟包,在丢弃。
别以为在这里我要用UDP来实现类型TCP的差错重传和确认机制,事情还没糟糕到需要做那一步。