记录ImageIO读取文件为null的问题及Java获取图片的宽高

背景

公司项目需要做一个图片预览的功能,并且要展示图片的分辨率(即宽和高),目前的框架没有这个功能,所以得自己写一个方法去读取上传的附件。百度了一下,发现用Java包中的ImageIO就可以得到图片的宽和高。但是,有部分图片会读出null,然后空指针异常。主要代码如下:

File file = new File(filePath);
BufferedImage image = ImageIO.read(file);
result.put("width",image.getWidth());
result.put("height",image.getHeight());

解决过程

造成这个问题的原因是图片的后缀可能是jpg、png,但是图片实际上是另一种格式(比如webp、tif)。先打印一下ImageIO支持的文件格式,代码如下:

// JDK8输出:JPG jpg bmp BMP gif GIF WBMP png PNG wbmp jpeg JPEG
// JDK11输出:JPG jpg tiff bmp BMP gif GIF WBMP png PNG JPEG tif TIF TIFF wbmp jpeg
for (int i = 0; i < ImageIO.getReaderFormatNames().length; i++) {
          System.out.print(ImageIO.getReaderFormatNames()[i] + " ");
      }

可以看出ImageIO不支持读取webp格式,JDK8之前不支持读取tif格式图片。
如果图片源格式是tif,那么在项目中加入下面的依赖就行:

<dependency>
    <groupId>com.twelvemonkeys.imageio</groupId>
    <artifactId>imageio-tiff</artifactId>
    <version>3.4.1</version>
</dependency>

ImageIO 会自动搜索类路径下继承的接口和子类不用改代码。

最终解决

我的图片源格式是webp的,网上找了下资料,大部分都是用的第一种方法。

方法一

  • 点击下载webp-imageio-core-0.1.1.jar包

  • 在项目中添加依赖

    <dependency>
    	<groupId>com.github.nintha</groupId>
    	<artifactId>webp-imageio-core</artifactId>
    	<version>0.1.1</version>
    	<scope>system</scope>
    	<systemPath>${project.basedir}/src/main/resources/libs/webp-imageio-core-0.1.1.jar</systemPath>
    </dependency>
    

    参考链接

我做了两步后一直报下面的错误:

Caused by: java.lang.UnsatisfiedLinkError: com.luciad.imageio.webp.WebPDecoderOptions.createDecoderOptions()J
	at com.luciad.imageio.webp.WebPDecoderOptions.createDecoderOptions(Native Method) ~[webp-imageio-core-0.1.1.jar:?]
	at com.luciad.imageio.webp.WebPDecoderOptions.<init>(WebPDecoderOptions.java:26) ~[webp-imageio-core-0.1.1.jar:?]
	at com.luciad.imageio.webp.WebPReadParam.<init>(WebPReadParam.java:24) ~[webp-imageio-core-0.1.1.jar:?]
	at com.aspirecn.kjcgkyg.controller.RecommendResultController.queryImageInfo(RecommendResultController.java:271) ~[classes/:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_291]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_291]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_291]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_291]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197) ~[spring-web-5.3.4.jar:5.3.4]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141) ~[spring-web-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060) [spring-webmvc-5.3.4.jar:5.3.4]
	... 55 more

试了把webp-imageio-core-0.1.1.jar包中的dll文件放到jdk/jre/lib目录也没解决,于是放弃这种方法。

方法二(最终解决)

  • 写个工具类获取文件的mimeType,代码如下:
public class ImgeMimeTypeUtil {

    /**
     * 获取文件的mimeType
     * @param filename
     * @return
     */
    public static String getMimeType(String filename) {
        try {
            String mimeType = readType(filename);
            return String.format("image/%s", mimeType);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 读取文件类型
     * @param filename
     * @return
     * @throws IOException
     */
    public static String readType(String filename) throws IOException {

        FileInputStream fis = null;
        try {
            File f = new File(filename);
            if (!f.exists() || f.isDirectory() || f.length() < 8) {
                throw new IOException("the file [" + f.getAbsolutePath()
                        + "] is not image !");
            }

            fis = new FileInputStream(f);
            byte[] bufHeaders = readInputStreamAt(fis, 0, 8);
            if (isJPEGHeader(bufHeaders)) {
                long skiplength = f.length() - 2 - 8; //第一次读取时已经读了8个byte,因此需要减掉
                byte[] bufFooters = readInputStreamAt(fis, skiplength, 2);
                if (isJPEGFooter(bufFooters)) {
                    return "jpeg";
                }
            }
            if (isPNG(bufHeaders)) {
                return "png";
            }
            if (isGIF(bufHeaders)) {

                return "gif";
            }
            if (isWEBP(bufHeaders)) {
                return "webp";
            }
            if (isBMP(bufHeaders)) {
                return "bmp";
            }
            if (isICON(bufHeaders)) {
                return "ico";
            }
            throw new IOException("the image's format is unkown!");

        } catch (FileNotFoundException e) {
            throw e;
        } finally {
            try {
                if (fis != null)
                    fis.close();
            } catch (Exception e) {
            }
        }

    }

    /**
     * 标示一致性比较
     * @param buf 待检测标示
     * @param markBuf 标识符字节数组
     * @return 返回false标示标示不匹配
     */
    private static boolean compare(byte[] buf, byte[] markBuf) {
        for (int i = 0; i < markBuf.length; i++) {
            byte b = markBuf[i];
            byte a = buf[i];

            if (a != b) {
                return false;
            }
        }
        return true;
    }

    /**
     *
     * @param fis 输入流对象
     * @param skiplength 跳过位置长度
     * @param length 要读取的长度
     * @return 字节数组
     * @throws IOException
     */
    private static byte[] readInputStreamAt(FileInputStream fis,
                                            long skiplength, int length) throws IOException {
        byte[] buf = new byte[length];
        fis.skip(skiplength); //
        int read = fis.read(buf, 0, length);
        return buf;
    }
    private static boolean isBMP(byte[] buf){
        byte[] markBuf = "BM".getBytes(); //BMP图片文件的前两个字节
        return compare(buf, markBuf);
    }

    private static boolean isICON(byte[] buf) {
        byte[] markBuf = {0, 0, 1, 0, 1, 0, 32, 32};
        return compare(buf, markBuf);
    }
    private static boolean isWEBP(byte[] buf) {
        byte[] markBuf = "RIFF".getBytes(); //WebP图片识别符
        return compare(buf, markBuf);
    }

    private static boolean isGIF(byte[] buf) {
        byte[] markBuf = "GIF89a".getBytes(); //GIF识别符
        if(compare(buf, markBuf))
        {
            return true;
        }
        markBuf = "GIF87a".getBytes(); //GIF识别符
        if(compare(buf, markBuf))
        {
            return true;
        }
        return false;
    }
    private static boolean isPNG(byte[] buf) {
        byte[] markBuf = {(byte) 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A}; //PNG识别符
        // new String(buf).indexOf("PNG")>0 //也可以使用这种方式
        return compare(buf, markBuf);
    }

    private static boolean isJPEGHeader(byte[] buf) {
        byte[] markBuf = {(byte) 0xff, (byte) 0xd8}; //JPEG开始符
        return compare(buf, markBuf);
    }

    private static boolean isJPEGFooter(byte[] buf)//JPEG结束符
    {
        byte[] markBuf = {(byte) 0xff, (byte) 0xd9};
        return compare(buf, markBuf);
    }
}
  • 在业务代码中对webp格式单独处理
String fileType = ImgeMimeTypeUtil.getMimeType(filePath);
if (fileType != null && "image/webp".equals(fileType)) {
    FileInputStream file = new FileInputStream(filePath);
    byte[] bytes = new byte[64];
    file.read(bytes, 0, bytes.length);
    int width = ((int) bytes[27] & 0xff) << 8 | ((int) bytes[26] & 0xff);
    int height = ((int) bytes[29] & 0xff) << 8 | ((int) bytes[28] & 0xff);
    result.put("width",width);
    result.put("height",height);
} else {
    File file = new File(filePath);
    BufferedImage image = ImageIO.read(file);
    result.put("width",image.getWidth());
    result.put("height",image.getHeight());
}

至此,终于解决,完美!

附(快速查看webp格式)

直接把图片用记事本打开,如果第一行有显示"WEBPVP8"之类的文字就是webp格式的图片。
在这里插入图片描述
如图所示,文件名虽然是jpg后缀但是内容却是webp。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以使用Java ImageIO库中的ImageWriter类来实现调整图片DPI的功能。下面是一个示例代码: ```java import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.imageio.ImageWriter; import javax.imageio.plugins.jpeg.JPEGImageWriteParam; import javax.imageio.stream.ImageOutputStream; public class AdjustDPI { public static void main(String[] args) throws IOException { // 读取图片 BufferedImage image = ImageIO.read(new File("input.jpg")); // 获取JPEG图像写入器 ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next(); // 配置JPEG图像写入参数 JPEGImageWriteParam writeParam = new JPEGImageWriteParam(null); writeParam.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT); writeParam.setCompressionQuality(1f); // 设置输出流 ImageOutputStream outputStream = ImageIO.createImageOutputStream(new File("output.jpg")); writer.setOutput(outputStream); // 设置DPI float dpi = 300f; writer.prepareWriteSequence(null); writer.writeToSequence(new javax.imageio.IIOImage(image, null, null), writeParam); // 关闭输出流 writer.endWriteSequence(); outputStream.close(); } } ``` 在上述代码中,我们首先使用`ImageIO.read`方法读取了一张图片,并使用`ImageIO.getImageWritersByFormatName`获取JPEG图像写入器。然后,我们配置了JPEG图像写入参数,设置输出流,并使用`writer.prepareWriteSequence`方法开始写入序列。最后,我们设置了DPI,并使用`writer.writeToSequence`方法将图片写入输出流,最后通过`writer.endWriteSequence`关闭输出流。 注意,这个示例代码仅适用于JPEG格式的图片。如果你需要处理其他格式的图片,需要调整代码中的参数和对象。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值