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

背景

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

1
2
3
4
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支持的文件格式,代码如下:

1
2
3
4
5
// 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,那么在项目中加入下面的依赖就行:

1
2
3
4
5
<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包

  • 在项目中添加依赖

    1
    2
    3
    4
    5
    6
    7
    <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>

    参考链接

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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格式单独处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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。