用 javax.imageio 实现图片压缩原理是啥?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用 javax.imageio 实现图片压缩原理是啥?相关的知识,希望对你有一定的参考价值。
参考技术A 下面是传进byte[],传出byte[]数组的,稍微修改即可即可,(data为传过来的byte[]数组)Java代码
ByteArrayInputStream is = new ByteArrayInputStream(data);
BufferedImage src = null;
ByteArrayOutputStream out = null;
ImageWriter imgWrier;
ImageWriteParam imgWriteParams;
// 指定写图片的方式为 jpg
imgWrier = ImageIO.getImageWritersByFormatName("jpg").next();
imgWriteParams = new javax.imageio.plugins.jpeg.JPEGImageWriteParam(null);
// 要使用压缩,必须指定压缩方式为MODE_EXPLICIT
imgWriteParams.setCompressionMode(imgWriteParams.MODE_EXPLICIT);
// 这里指定压缩的程度,参数qality是取值0~1范围内,
imgWriteParams.setCompressionQuality((float)0.1/data.length);
imgWriteParams.setProgressiveMode(imgWriteParams.MODE_DISABLED);
ColorModel colorModel = ColorModel.getRGBdefault();
// 指定压缩时使用的色彩模式
imgWriteParams.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel, colorModel
.createCompatibleSampleModel(16, 16)));
try
src = ImageIO.read(is);
out = new ByteArrayOutputStream(data.length);
imgWrier.reset();
// 必须先指定 out值,才能调用write方法, ImageOutputStream可以通过任何 OutputStream构造
imgWrier.setOutput(ImageIO.createImageOutputStream(out));
// 调用write方法,就可以向输入流写图片
imgWrier.write(null, new IIOImage(src, null, null), imgWriteParams);
out.flush();
out.close();
is.close();
data = out.toByteArray();
catch(Exception e)
e.printStackTrace();
在 Java 中使用 ImageIO 设置 jpg 压缩级别
【中文标题】在 Java 中使用 ImageIO 设置 jpg 压缩级别【英文标题】:Setting jpg compression level with ImageIO in Java 【发布时间】:2013-06-11 02:06:41 【问题描述】:我正在使用 javax.imageio.ImageIO
将 BufferedImage
保存为 jpeg 文件。
特别是,我创建了以下 Java 函数:
public static void getScreenShot(BufferedImage capture, Path folder, String filename)
try
ImageIO.write(capture, "jpeg", new File(folder.toString()+"/"+filename+".jpg"));
catch (AWTException | IOException ex)
Logger.getLogger(ScreenShotMaker.class.getName()).log(Level.SEVERE, null, ex);
与任何图像处理软件一样,我希望更改 jpeg 文件的压缩级别。但是,我正在寻找ImageIO
中似乎缺少的这个选项。
我可以设置压缩级别吗?如何设置?
【问题讨论】:
【参考方案1】:您必须使用JPEGImageWriteParam
,然后使用ImageWriter.write()
保存图像。写之前,通过ImageWriter.setOutput
设置输出。
设置压缩级别如下:
JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(1f);
1f
是一个浮点数,代表 100% 质量。如果我没记错的话,默认值是大约 70%。
编辑
然后,您必须执行以下操作才能获取ImageWriter
的实例。有两种方法,一种是短的,一种是长的(我都保留了,以防万一)。
捷径(由lapo在一条评论中建议)是:
final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
// specifies where the jpg image has to be written
writer.setOutput(new FileImageOutputStream(
new File(folder.toString() + "/" + filename + ".jpg")));
// writes the file with given compression level
// from your JPEGImageWriteParam instance
writer.write(null, new IIOImage(capture, null, null), jpegParams);
或更长的路
// use IIORegistry to get the available services
IIORegistry registry = IIORegistry.getDefaultInstance();
// return an iterator for the available ImageWriterSpi for jpeg images
Iterator<ImageWriterSpi> services = registry.getServiceProviders(ImageWriterSpi.class,
new ServiceRegistry.Filter()
@Override
public boolean filter(Object provider)
if (!(provider instanceof ImageWriterSpi)) return false;
ImageWriterSpi writerSPI = (ImageWriterSpi) provider;
String[] formatNames = writerSPI.getFormatNames();
for (int i = 0; i < formatNames.length; i++)
if (formatNames[i].equalsIgnoreCase("JPEG"))
return true;
return false;
,
true);
//...assuming that servies.hasNext() == true, I get the first available service.
ImageWriterSpi writerSpi = services.next();
ImageWriter writer = writerSpi.createWriterInstance();
// specifies where the jpg image has to be written
writer.setOutput(new FileImageOutputStream(
new File(folder.toString() + "/" + filename + ".jpg")));
// writes the file with given compression level
// from your JPEGImageWriteParam instance
writer.write(null, new IIOImage(capture, null, null), jpegParams);
【讨论】:
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
不要忘记关闭 FileImageOutputStream 或处置 ImageWriter
有谁知道我在哪里可以参考使用的确切默认值?
@brad 使用 jpegParams.getCompressionQuality()
而不设置它。 (你仍然需要先设置jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT)
。)至少对我来说,默认是0.75
。
Here is the documentation stating that the default value is 0.75.【参考方案2】:
更简洁的方法是直接从ImageIO
获取ImageWriter
:
ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(0.7f);
ImageOutputStream outputStream = createOutputStream(); // For example implementations see below
jpgWriter.setOutput(outputStream);
IIOImage outputImage = new IIOImage(image, null, null);
jpgWriter.write(null, outputImage, jpgWriteParam);
jpgWriter.dispose();
需要调用ImageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT)
才能显式设置压缩级别(质量)。
ImageWriteParam.setCompressionQuality()
1.0f
是最高质量,最低压缩,而0.0f
是最低质量,最高压缩。
ImageWriter.setOutput
应该传递一个ImageOutputStream
。虽然该方法接受Object
,但根据文档,它通常不受支持:
使用除
ImageOutputStream
之外的通用Object
旨在供直接与输出设备或成像协议交互的编写者使用。合法类的集合由作者的服务提供者的getOutputTypes
方法公布;大多数作者将返回一个仅包含ImageOutputStream.class
的单元素数组,以表明他们只接受ImageOutputStream
。
大多数情况应该由这两个类来处理:
FileImageOutputStream
- ImageOutputStream
的实现,将其输出直接写入 File
或 RandomAccessFile
。
MemoryCacheImageOutputStream
- ImageOutputStream
的实现,将其输出写入常规 OutputStream
。通常与ByteArrayOutputStream
一起使用(感谢您的提示,@lmiguelmh!)。
【讨论】:
+1,不知道为什么手动过滤更复杂的是1.已发布,2.接受,3.这么长时间没有改进...... 不想写磁盘的朋友:ByteArrayOutputStream baos = new ByteArrayOutputStream(); writer.setOutput(new MemoryCacheImageOutputStream(baos)); ... baos.flush(); byte[] returnImage = baos.toByteArray(); baos.close();
令我惊讶的是,我回到了同样的问题,我现在无法编辑我的评论。如果你不按照我之前说的做,直接使用ByteArrayOutputStream
你会得到java.lang.IllegalArgumentException: Illegal output type!
@lmiguelmh:自创建后 5 分钟后您将无法编辑您的评论。另外,我将更新我的答案以解决输出流的问题,感谢您提出。
我使用了jpgWriter.setOutput(ImageIO.createImageOutputStream(new File(path)))
,因为FileImageOutputStream
出了点问题。【参考方案3】:
更通用的方法是(来自Igor's answer):
static void saveImage(BufferedImage image,File jpegFiletoSave,float quality) throws IOException
// save jpeg image with specific quality. "1f" corresponds to 100% , "0.7f" corresponds to 70%
ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(quality);
jpgWriter.setOutput(ImageIO.createImageOutputStream(jpegFiletoSave));
IIOImage outputImage = new IIOImage(image, null, null);
jpgWriter.write(null, outputImage, jpgWriteParam);
jpgWriter.dispose();
【讨论】:
【参考方案4】:在我的古老图书馆中找到了相同的方法:
/**
* Work method.
* Reads the jpeg image in rendImage, compresses the image, and writes it back out to outfile.
* JPEGQuality ranges between 0.0F and 1.0F, 0-lowest, 1-highest. ios is closed internally
*
* @param rendImage [@link RenderedImage instance with an Rendered Image
* @param ios @link ImageOutputStream instance,
* note that it is disposed in this method
* @param JPEGQuality float value for the JPEG compression quality (0..1(max))
* @return @code true if image was successfully compressed
* else @code false on any error, e.g. bad (null) parameters
*/
public static boolean compressJpegFile( RenderedImage rendImage, ImageOutputStream ios, float JPEGQuality )
if ( rendImage == null )
return false;
if ( ios == null )
return false;
if ( ( JPEGQuality <= 0.0F ) || ( JPEGQuality > 1.0F ) )
return false;
ImageWriter writer = null;
try
// Find a jpeg writer
Iterator iter = ImageIO.getImageWritersByFormatName( "jpg" );
if ( iter.hasNext() )
writer = (ImageWriter) iter.next();
if ( writer == null )
throw new IllegalArgumentException( "jpg writer not found by call to ImageIO.getImageWritersByFormatName( \"jpg\" )" );
writer.setOutput( ios );
// Set the compression quality
ImageWriteParam iwparam = new MyImageWriteParam();
iwparam.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
iwparam.setCompressionQuality( JPEGQuality );
// float res = iwparam.getCompressionQuality();
// Write the image
writer.write( null, new IIOImage( rendImage, null, null ), iwparam );
return true;
catch ( Exception e )
return false;
finally
if ( writer != null )
writer.dispose();
// Cleanup
try
ios.flush();
ios.close();
catch ( IOException e )
【讨论】:
【参考方案5】:如果您需要 byte[] 输出,请参阅下面@user_3pij 答案的修改版本:
private static byte[] compressImageToByteArray(BufferedImage image, float quality)
throws IOException
// save jpeg image with specific quality. "1f" corresponds to 100% , "0.7f" corresponds to
// 70%
ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(quality);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
jpgWriter.setOutput(ios);
IIOImage outputImage = new IIOImage(image, null, null);
jpgWriter.write(null, outputImage, jpgWriteParam);
byte[] result = bos.toByteArray();
jpgWriter.dispose();
return result;
【讨论】:
以上是关于用 javax.imageio 实现图片压缩原理是啥?的主要内容,如果未能解决你的问题,请参考以下文章
jdk内置类javax.imageio.ImageIO支持的图片处理格式
javax.imageio.IIOException: Can't read input file!完美解决
struts2产生验证码时, 绘图BufferedImage对象 已经弄好了,发送的用啥输出流呢?
javax.imageio.IIOException:无法在 Tomcat 9、OpenJDK 11 和 Geoserver 中创建 ImageInputStream