You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Graphics2D.drawImage为何抛出NullPointerException?

解决Printable接口drawImage异步抛出NullPointerException的问题

问题场景

  • 基于Java/Kotlin开发打印应用,通过Printable接口实现页面打印
  • 调用drawImage时异步抛出NullPointerException,但图像可正常显示
  • 已在Temurin 17.0.5、Oracle OpenJDK 19.0.1版本验证,仅出现在Ubuntu 22.04系统
  • 异常在drawImage返回后触发,即使等待ImageObserver通知仍无法避免
  • 两条逻辑一致的drawImage调用中,仅第一条触发异常

异常栈信息

java.lang.NullPointerException: Cannot invoke "java.awt.image.ImageConsumer.setColorModel(java.awt.image.ColorModel)" because "this.theConsumer" is null
    at java.desktop/sun.awt.image.OffScreenImageSource.sendPixels(OffScreenImageSource.java:171)
    at java.desktop/sun.awt.image.OffScreenImageSource.produce(OffScreenImageSource.java:187)
    at java.desktop/sun.awt.image.OffScreenImageSource.addConsumer(OffScreenImageSource.java:66)
    at java.desktop/sun.awt.image.OffScreenImageSource.startProduction(OffScreenImageSource.java:80)
    at java.desktop/java.awt.image.FilteredImageSource.startProduction(FilteredImageSource.java:184)
    at java.desktop/sun.awt.image.ImageRepresentation.startProduction(ImageRepresentation.java:732)
    at java.desktop/sun.awt.image.ToolkitImage.addWatcher(ToolkitImage.java:221)
    at java.desktop/sun.awt.image.ToolkitImage.getHeight(ToolkitImage.java:142)
    at java.desktop/sun.print.PeekGraphics$ImageWaiter.waitForDimensions(PeekGraphics.java:1888)
    at java.desktop/sun.print.PeekGraphics$ImageWaiter.<init>(PeekGraphics.java:1876)
    at java.desktop/sun.print.PeekGraphics.drawImage(PeekGraphics.java:955)
    ... 后续为业务代码栈

业务代码片段

class MyPrintable : Printable {
    override fun print(graphics: Graphics, pageFormat: PageFormat, pageIndex: Int): Int {
        if (pageIndex > 0) {
            return NO_SUCH_PAGE
        }
        val g2d = graphics as Graphics2D

        drawString(...)
        drawString(...)

        val image1: BufferedImage = loadImage1()
        // 该行异步抛出NPE
        drawImage(image1.getScaledInstance(width, height, Image.SCALE_SMOOTH), x, y, null)

        val image2: BufferedImage = loadImage2()
        drawImage(image1.getScaledInstance(width, height, Image.SCALE_SMOOTH), x2, y2, null)

        return PAGE_EXISTS
    }
}

问题原因

从异常栈可定位到:OffScreenImageSource中的theConsumer引用为null,根源是getScaledInstance返回的是延迟加载的ToolkitImage,打印系统的PeekGraphics在处理该图像时,内部ImageWaiter等待图像尺寸的流程中,出现了图像消费者引用丢失的竞态条件。Ubuntu 22.04的AWT实现与打印上下文的异步图像加载逻辑存在兼容性问题,第一条图像的加载流程刚好触发了该竞态。

解决方案

方案1:提前生成BufferedImage(推荐)

直接手动完成图像缩放,生成已完全加载的BufferedImage,彻底规避延迟加载的异步问题:

private fun scaleImage(source: BufferedImage, targetWidth: Int, targetHeight: Int): BufferedImage {
    val scaledImage = BufferedImage(targetWidth, targetHeight, source.type)
    val g2d = scaledImage.createGraphics()
    try {
        // 设置平滑插值,替代SCALE_SMOOTH效果
        g2d.setRenderingHint(
            RenderingHints.KEY_INTERPOLATION,
            RenderingHints.VALUE_INTERPOLATION_BILINEAR
        )
        g2d.drawImage(source, 0, 0, targetWidth, targetHeight, null)
    } finally {
        g2d.dispose()
    }
    return scaledImage
}

修改打印方法中的调用:

val image1: BufferedImage = loadImage1()
val scaledImage1 = scaleImage(image1, width, height)
g2d.drawImage(scaledImage1, x, y, null)

val image2: BufferedImage = loadImage2()
val scaledImage2 = scaleImage(image2, width, height)
g2d.drawImage(scaledImage2, x2, y2, null)

方案2:等待图像完全加载后再使用

若必须使用getScaledInstance,可通过MediaTracker强制等待图像加载完成:

private fun waitForImage(image: Image) {
    val tracker = MediaTracker(java.awt.Component())
    tracker.addImage(image, 0)
    try {
        tracker.waitForAll()
    } catch (e: InterruptedException) {
        Thread.currentThread().interrupt()
    }
}

调用方式:

val scaledImage1 = image1.getScaledInstance(width, height, Image.SCALE_SMOOTH)
waitForImage(scaledImage1)
g2d.drawImage(scaledImage1, x, y, null)

方案3:传入有效的ImageObserver

避免传入null作为ImageObserver,直接传递打印上下文的Graphics2D(它实现了ImageObserver接口),帮助打印系统正确跟踪图像加载状态:

g2d.drawImage(scaledImage1, x, y, g2d)

总结

优先选择方案1,直接生成BufferedImage是Java打印场景中处理图像的最佳实践,能彻底规避不同平台AWT实现的差异问题,消除异步加载导致的竞态条件。

内容的提问来源于stack exchange,提问作者Sean

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 16:31:02