Java常用图像处理类库与ImageIO

ImageIO读取PNG/JPEG抛出“Bad PNG signature”并非图像损坏,而是因元数据或非标编码导致原生支持不足;需用getImageReadersByFormatName检查reader,失败则换TwelveMonkeys;透明PNG黑底因误用TYPE_INT_RGB,须改用TYPE_INT_ARGB并校验hasAlpha;WebP/AVIF/HEIC等新格式需插件或外部工具;多线程下返回null源于reader流状态复用,应每次新建FileInputStream。

ImageIO 读取 PNG/JPEG 时抛出 IOException: Bad PNG signature 怎么办

这通常不是图像损坏,而是 ImageIO.read() 默认只支持标准文件头,遇到带额外元数据(如 EXIF、XMP)或非标准编码的 PNG/JPEG 就会失败。尤其常见于手机拍照直传、微信/钉钉转发图、某些相机导出图。

  • 先用 FileInputStream + ImageIO.getImageReadersByFormatName("png") 检查是否有可用 reader,避免静默失败
  • 若返回空迭代器,说明格式不被原生支持,需换库(如 TwelveMonkeys ImageIO
  • 不要依赖 ImageIO.getReaderFormatNames() 的返回值做判断——它只列“已注册”的格式,不反映实际能否解析

ImageIO.write() 保存透明 PNG

却变成黑底

根本原因是 BufferedImage 类型选错:BufferedImage.TYPE_INT_RGB 不支持 alpha 通道,写入时自动丢弃透明度并填充黑色背景。

  • 必须用 BufferedImage.TYPE_INT_ARGBBufferedImage.TYPE_4BYTE_ABGR
  • 创建后记得调用 Graphics2D.setComposite(AlphaComposite.Src) 避免后续绘制污染 alpha 值
  • 保存前检查:用 image.getColorModel().hasAlpha() 确认 alpha 存在,比看类型更可靠
BufferedImage src = ImageIO.read(new File("input.png"));
BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = dst.createGraphics();
g.drawImage(src, 0, 0, null);
g.dispose();
ImageIO.write(dst, "png", new File("output.png"));

ImageIO 不支持 WebP / AVIF / HEIC,但又不想引入大库怎么办

ImageIO 是 JDK 内置机制,扩展性差,原生只支持 BMP/GIF/JPEG/PNG。WebP 等现代格式需插件式 reader,而 TwelveMonkeys ImageIO 是最轻量的选择(仅 3–5 MB,无反射/动态代理开销)。

  • 添加 Maven 依赖:com.twelvemonkeys.imageio:imageio-webp 即可直接用 ImageIO.read() 读 WebP
  • 注意:HEIC 需额外系统级依赖(libheif),Java 层无法纯靠 jar 解决
  • AVIF 目前(JDK 21)仍无稳定 reader,别硬套 ImageIO,改用 ffmpeg CLI 调用更现实

为什么 ImageIO.read() 在多线程下偶尔返回 null

不是线程安全问题,而是 ImageIO 内部缓存了 ImageReader 实例,某些 reader(尤其是 GIF)在并发调用 read() 时会复用流状态,导致后续读取跳过关键字节,最终返回 null。

  • 每次调用前都用 new FileInputStream(file) 重开流,别复用同一个 InputStream
  • 避免用 ByteArrayInputStream 包装大图字节数组——GC 压力大且 reader 可能缓存引用
  • 真要高性能并发读图,建议上 imgscalropencv-java,它们对线程模型有明确设计
ImageIO 适合简单场景,但一旦涉及兼容性、透明度、多线程或新格式,它的边界就非常清晰——它不是图像处理引擎,只是个格式桥接层。真正卡住的时候,往往不是代码写得不对,而是默认假设错了。