java : 调用ImageIO.writer从BufferedImage生成jpeg图像的坑

生成jpeg图像这是个非常非常简单的东西了,网上很多介绍是直接用com.sun.image.codec.jpeg.JPEGImageEncoder来实现,如下:

    /**
     * 将原图压缩生成jpeg格式的数据
     * @param source
     * @return
     */
    public static byte[] wirteJPEGBytes(BufferedImage source){
        if(null==source)
            throw new NullPointerException();
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        JPEGImageEncoder jencoder = JPEGCodec.createJPEGEncoder(output);
        JPEGEncodeParam param = jencoder.getDefaultJPEGEncodeParam(source);
        param.setQuality(0.75f, true);
        jencoder.setJPEGEncodeParam(param);
        try {
            jencoder.encode(source);
        } catch (ImageFormatException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return output.toByteArray();    
    }

JPEGImageEncoder只是sun的jpeg编码实现,并不是标准的java API,只在sun jvm中被支持,但在其他的jvm上,并不会被支持。 而且,虽然上面的代码在java 1.6,1.7上都能正常执行,但在如果使用java 1.8,上面这个代码会报错:

访问限制:由于对必需的库 C:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar 具有一定限制,因此无法访问类型 JPEGImageEncoder

所以这个方法是有局限性的。 走捷径是不行的,还是得规规矩矩按java的规范来做,ImageIO类中提供了ImageIO.writer方法可以生成指定的格式的图像,才是正规的实现方式。 但是使用ImageIO.writer方法也是有讲究的。 我原先是这样写的,就是简单的调用ImageIO.writer方法生成jpeg数据:

    /**
     * 将原图压缩生成jpeg格式的数据
     * @param source
     * @return
     * @see #wirteBytes(BufferedImage, String)
     */
    public static byte[] wirteJPEGBytes(BufferedImage source){
        return wirteBytes(source,"JPEG");
    }
    /**
     * 将原图压缩生成jpeg格式的数据
     * @param source
     * @return
     * @see #wirteBytes(BufferedImage, String)
     */
    public static byte[] wirteJPEGBytes(BufferedImage source){
        return wirteBytes(source,"JPEG");
    }
    /**
     * 将{@link BufferedImage}生成formatName指定格式的图像数据
     * @param source
     * @param formatName 图像格式名,图像格式名错误则抛出异常
     * @return
     */
    public static byte[] wirteBytes(BufferedImage source,String formatName){
        Assert.notNull(source, "source");
        Assert.notEmpty(formatName, "formatName");
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        try {           
            if(!ImageIO.write(source, formatName.toLowerCase(), output))
                // 返回false则抛出异常
                throw new IllegalArgumentException(String.format("not found writer for '%s'",formatName));

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return output.toByteArray();        
    }

处理了几万张图像文件都没问题,遇到一张png图像,ImageIO.write居然返回false,抛出异常了。 究其原因,是ImageIO.wite方法在中调用的私有方法getWriter寻找合适的ImageWriter时不仅与formatName相关,还是输入的原图有关(具体是怎么相关的,因为逻辑关系太复杂没有深究),造成getWriter方法找不到对应的ImageWriter。 参考网上别人的写法改成这样就没问题了:

    /**
     * 将{@link BufferedImage}生成formatName指定格式的图像数据
     * @param source
     * @param formatName 图像格式名,图像格式名错误则抛出异常
     * @return
     */
    public static byte[] wirteBytes(BufferedImage source,String formatName){
        Assert.notNull(source, "source");
        Assert.notEmpty(formatName, "formatName");
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        BufferedImage newBufferedImage = new BufferedImage(source.getWidth(),
                source.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g = newBufferedImage.createGraphics();
        try {
            g.drawImage(source, 0, 0,null);
            if(!ImageIO.write(newBufferedImage, formatName, output))
                throw new IllegalArgumentException(String.format("not found writer for '%s'",formatName));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally{
            g.dispose();
        }
        return output.toByteArray();        
    }

基本的思路就是重创建一个大小相同的BufferedImage,然后用Graphics.drawImage方法将原图写入新的BufferedImage对象,通过这一道转换,抹平了,不同类型图像格式生成的BufferedImage对象之间的区别,再调用 ImageIO.write 对新的ImageIO.write对象进行图像处理就不会有问题了。

改进

在我的项目中图像数据是从互联网上搜索到的,遇到的图像格式绝大部分都是jpeg,但也有少量的png,bmp等格式,对于占绝大多数的jpeg图像来说,我最开始的方法都是有效的,而上面的这个方法多出一道工序就显得有些多余,还浪费资源,所以又改进了上述的方法,基本的原理就是先尝试直接ImageIO.write来生成jpeg,如果失败,就用第二种方式。

    /**
     * 将{@link BufferedImage}生成formatName指定格式的图像数据
     * @param source
     * @param formatName 图像格式名,图像格式名错误则抛出异常
     * @return
     */
    public static byte[] wirteBytes(BufferedImage source,String formatName){
        Assert.notNull(source, "source");
        Assert.notEmpty(formatName, "formatName");
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        Graphics2D g = null;
        try {
            for(BufferedImage s=source;!ImageIO.write(s, formatName, output);){
                if(null!=g)
                    throw new IllegalArgumentException(String.format("not found writer for '%s'",formatName));
                s = new BufferedImage(source.getWidth(),
                        source.getHeight(), BufferedImage.TYPE_INT_RGB);
                g = s.createGraphics();
                g.drawImage(source, 0, 0,null);             
            }               
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (null != g)
                g.dispose();
        }
        return output.toByteArray();        
    }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Venyo 的专栏

旋转不变的感知哈希

package com.imageretrieval.features; import com.imageretrieval.utils.ImageUtil;...

342100
来自专栏desperate633

LeetCode 337. House Robber III分析通过上面的问题的一步步分析,我们更好的理解了动态规划的意义,并且将算法优化到最佳

Paste_Image.png Maximum amount of money the thief can rob = 3 + 3 + 1 =...

8120
来自专栏应兆康的专栏

100个Numpy练习【4】

Numpy是Python做数据分析必须掌握的基础库之一,非常适合刚学习完Numpy基础的同学,完成以下习题可以帮助你更好的掌握这个基础库。

812120
来自专栏王磊的博客

c#字符相似度对比通用类

本类适用于比较2个字符的相似度,代码如下: using System; using System.Collections.Generic; using Syst...

42270
来自专栏HansBug's Lab

3385: [Usaco2004 Nov]Lake Counting 数池塘

3385: [Usaco2004 Nov]Lake Counting 数池塘 Time Limit: 1 Sec  Memory Limit: 128 MB S...

28260
来自专栏C语言及其他语言

【每日一题】问题 1218: 排列

关注我们 题目描述 Ray又对数字的列产生了兴趣: 现有四张卡片,用这四张卡片能排列出很多不同的4位数,要求按从小到大的顺序输出这些4位数。 输入 ...

29580
来自专栏自学笔记

Data Structure_图图论带权图

交通运输,社交网络,互联网,工作的安排,闹区活动等等都可以用到图论处理。图可以分成两大类,一类是无向图,就是没有方向的,就好像两个人都互相认识一样,有向图就是单...

8510
来自专栏Jack-Cui

Day9、Python

题目回数是指从左向右读和从右向左读都是一样的数,例如123321,909。请利用filter()滤掉非回数。 1、程序分析     filter()把传...

21000
来自专栏清墨_iOS分享

OpenGLES-04 绘制带颜色的立方体

前面几篇文章都只是绘制了平面图形,接下来我们开始绘制一个真正的3D立方体图形。代码在前一篇文章基础上修改。 绘制立方体之前,我们需要知道这个立方体的各个顶点坐...

43190
来自专栏数据结构与算法

P1789 【Mc生存】插火把

题目背景 初一党应该都知道...... 题目描述 话说有一天linyorson在Mc开了一个超平坦世界,他把这个世界看成一个n*n的方阵,现在他有m个火把和k个...

37750

扫码关注云+社区

领取腾讯云代金券