`
java_mzd
  • 浏览: 580359 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

java中图片按质量压缩的再一次小结

阅读更多

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

虽然没能带回一个自己用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的差错重传和确认机制,事情还没糟糕到需要做那一步。

分享到:
评论
2 楼 2047699523 2015-04-25  
Java压缩图片util,可等比例宽高不失真压缩,也可直接指定压缩后的宽高
http://www.zuidaima.com/share/2156534163491840.htm
1 楼 syx278250658 2011-12-21  
ImageIO压缩gif 是很给力 但太耗时了 不知道可有好的方法解决不?

相关推荐

Global site tag (gtag.js) - Google Analytics