BufferedImage.getGraphics()。drawImage()改变像素值

时间:2014-05-18 15:21:37

标签: java image bufferedimage

(这是对previous问题的跟进)

BufferedImage从一种类型转换为另一种类型或制作副本的标准推荐方法是使用getGraphics().drawImage()example)。令我惊讶的是,我发现即使源图像和目标图像都属于同一类型,此过程也不会使像素值保持不变!当有一些透明度时,问题会出现。

单像素ARGB图像的示例:

  public static void imagesTestBiIssue() throws IOException {
  //it also happens with TYPE_INT_ARGB
    BufferedImage bi1 = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR);
    // rather arbitrary. low values of alpha tend to give more difference
    int argb = 0x11663322; 
    bi1.setRGB(0, 0, argb);
    int p1 = bi1.getRGB(0, 0);
    BufferedImage bi2 = new BufferedImage(bi1.getWidth(), bi1.getHeight(),
        bi1.getType());
    bi2.getGraphics().drawImage(bi1, 0, 0, null);
    int p2 = bi2.getRGB(0, 0);
    System.out.printf("im1: %08x %s ", p1, formatARGB(p1));
    System.out.printf("im2: %08x %s %s\n", p2, 
        formatARGB(p2), (p1 == p2 ? "" : "DIF"));
  }

 public static String formatARGB(int v) {
    return String.format("(%d,%d,%d,%d)", 
        (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF);
  }

这给出:im1: 11663322 (17,102,51,34) im2: 11692d1e (17,105,45,30) DIF

在某处似乎有一些颜色转换,我无法想象为什么,因为目标和源是相同的类型。这是预期的(或可接受的)行为吗?

2 个答案:

答案 0 :(得分:2)

我终于明白了。尝试设置graphics.setComposite(AlphaComposite.Src)。在我自己的库代码中,我这样做了,但我从来没有多想过......

由于默认合成为AlphaComposite.SrcOver,您实际上半透明像素合成到完全透明的像素上,两者都是非预乘的alpha,所以这里有区别。使用AlphaComposite.Src你基本上只说源是重要的。

另请注意,较低的Alpha值意味着更透明。这意味着对于低alpha,RGB值具有较小的重要性,因为它们在合成时乘以alpha(即。17/255用于示例图像),因此在组合到不透明背景时diff不会产生差异。

所以我说:有些出乎意料吗?是。是否可以接受?可能是。 : - )


以下是代码的更新版本,使用AlphaComposite.Src,输出没有差异:

public static void main(String[] args) {
    //it also happens with TYPE_INT_ARGB
    BufferedImage bi1 = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR);
    // rather arbitrary. low values of alpha tend to give more difference
    int argb = 0x11663322;
    bi1.setRGB(0, 0, argb);
    int p1 = bi1.getRGB(0, 0);
    BufferedImage bi2 = new BufferedImage(bi1.getWidth(), bi1.getHeight(),
            bi1.getType());
    Graphics2D graphics = bi2.createGraphics();
    try {
        graphics.setComposite(AlphaComposite.Src);
        graphics.drawImage(bi1, 0, 0, null);
    }
    finally {
        graphics.dispose();
    }
    int p2 = bi2.getRGB(0, 0);
    System.out.printf("im1: %08x %s ", p1, formatARGB(p1));
    System.out.printf("im2: %08x %s %s\n", p2,
            formatARGB(p2), (p1 == p2 ? "" : "DIF"));
}

public static String formatARGB(int v) {
    return String.format("(%d,%d,%d,%d)",
            (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF);
}

输出:

im1: 11663322 (17,102,51,34) im2: 11663322 (17,102,51,34)

答案 1 :(得分:1)

我想我已经找到了。

我检查过上面的(A,R,G,B)值是"是真实的" (它们存储在DataBuffer中)。但似乎(至少在我的版本中)drawImage()使用/假设alphaPremultiplied值。因此,在图像管道中,原始值在绘图上被预乘(具有8位量化),然后,当存储在目标BufferedImage中时,完成逆变换。对于每个RGB通道,等式约为:

r   <-- original red value
r1 = round( r * a / 255.0 )  <-- alpha multiplied 
r2 = round( r1 * 255.0 /a ) <-- restored

(这仅仅是经验性的 - 我确实没有深入研究资料来源 - 并且存在一些四舍五入的问题,但其实质是存在的。)

这种变换当然是有损的(对于低α值更是如此;特别是,如果alpha为零,它会清除所有值),但它仍然无害......如果我们假设我们只对显示或保持后乘的值。对于某些图像处理场景,这种假设可能是错误的。