背景
公司项目需要做一个图片预览的功能,并且要展示图片的分辨率(即宽和高),目前的框架没有这个功能,所以得自己写一个方法去读取上传的附件。百度了一下,发现用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的,网上找了下资料,大部分都是用的第一种方法。
方法一
-
在项目中添加依赖
<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。