今天在写图片验证码的业务时候,需要将base64字符串转换为图片,然后在这个图片上画出对应的矩阵块,完成滑块拼图验证码的实现,一个demo如下。
String s = fileToBase64("C:\\Users\\Administrator\\Desktop\\微信图片_20230302221300.jpg");
byte[] decode = Base64.getDecoder().decode(s);
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode)){
// 转化为bufferedImage后期拼图
BufferedImage read = ImageIO.read(byteArrayInputStream);
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
ImageReader reader = (ImageReader) readers.next();
ImageReadParam defaultReadParam = reader.getDefaultReadParam();
ImageInputStream iis = ImageIO.createImageInputStream(byteArrayInputStream);
reader.setInput(iis, true);
// new ctangle和ImageReadParam 实现矩形框,并写到该图片中
reader.read(0,defaultReadParam)
}
catch (IOException e) {
e.printStackTrace();
}
在使用过程中,出现了以下报错:Not a JPEG file: starts with 0xff 0xd9
javax.imageio.IIOException: Not a JPEG file: starts with 0xff 0xd9
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readImageHeader(Native Method)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readNativeHeader(JPEGImageReader.java:628)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.checkTablesOnly(JPEGImageReader.java:347)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.gotoImage(JPEGImageReader.java:495)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readHeader(JPEGImageReader.java:621)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.getWidth(JPEGImageReader.java:735)
说这个图片的文件格式头不是标准的jpg,但是通过调试发现base64编码的图片可以被解析,查阅jpg图片格式发现文件头是以0xff 0xd9
结尾并非以此开始,细想半天一开始怀疑是不是在base64转图片流的时候是不是搞反了,导致从文件末尾开始读,后来debug发现并没有改动。然后又对ImageReader 一顿输出,结果还是错误。然后偶然的一次尝试,将BufferedImage read = ImageIO.read(byteArrayInputStream)
调整至最后一行,奇迹的发现竟然运行通过了,这尼玛……
猜测
一开始以为ImageIO.read在读取文件后导致字节倒叙了,debug一顿然而并没有。
原因
ImageIO.read将byteArrayInputStream流读到了末尾,换言之就是说byteArrayInputStream被消耗了,指针读到了末尾,因为两个图片读取方法操作的一个byteArrayInputStream,所以导致ImageReader读取的是个空的流!可以前后增加:
System.out.println(byteArrayInputStream.available());
BufferedImage read = ImageIO.read(byteArrayInputStream);
System.out.println(byteArrayInputStream.available());
发现第二次打印为0,这就证明了byteArrayInputStream指针指到了末尾,通过:
byteArrayInputStream.reset();
可以将其指到初始位置,从而解决了这个问题。
ImageIO.read源码也是用到了ImageReader,通过源码可以看出至始至终都是在操作byteArrayInputStream并没有copy,ImageReader传入的是ImageInputStream才没有发生该情况。
一致在反复的验证base64是否损失图片格式类型和流的各种子类转换问题了,从而忽视这一个细节。