TYPE_INT_ARGB_PRE 的影响

Mar*_* A. 1 java bufferedimage premultiplied-alpha

我在ConvolveOp方面遇到了一些问题,可以通过将我正在使用的BufferedImage的 imageType 设置为TYPE_INT_ARGB_PRE来解决(请参阅此处相关的 SO 答案)。

不幸的是,我不完全理解选择这种不同的 imageType 的所有含义,而且我似乎也找不到好的参考,所以让我在这里尝试一下:

将 BufferedImage 的 imageType 从 TYPE_INT_ARGB 更改为 TYPE_INT_ARGB_PRE 会影响哪些绘图操作?它只是BufferedImageOps吗?或者如果将图像绘制到不同的 Graphics 对象上,它是否会影响图像的Graphics对象上的任何绘制命令或图像的渲染方式?

Mar*_*o13 5

这基本上取决于绘画算法是否考虑图像是否使用预乘 alpha 的信息。

正如评论中已经指出的:在大多数情况下,结果是相同的 - 至少对于基本绘图操作而言:无论您将“非预乘”图像绘制到预乘图像中,还是反之亦然,都不会影响结果,因为差异是在内部处理的。

一个特殊情况是BufferedImageOps。JavaDoc 注释明确说明了如何处理 Alpha 通道,并且传递错误类型的图像可能会导致您链接到的问题中描述的不良结果。

很难确定他们决定以这种方式实施的“原因BufferedImageOp。但这里的一个(有点模糊的)说法是:当对单个源的像素进行操作(并组合)时,并且这些像素具有不同的 alpha 值,alpha 通道的处理可能会变得繁琐。Alpha 通道会发生什么并不总是那么明显。

例如,想象一堆像素(这里是 ARGB,具有浮点值):

[1.00, 1.00, 0.00, 0.00]  // 100% red, 100% alpha
[0.00, 0.00, 0.00, 0.00]  // black,      0% alpha
[0.00, 0.00, 0.00, 0.00]  // black,      0% alpha
Run Code Online (Sandbox Code Playgroud)

现在,您想要对这些像素进行卷积(如您链接到的问题)。那么内核可以是

[0.33...]
[0.33...],
[0.33...] 
Run Code Online (Sandbox Code Playgroud)

这意味着结果的中心像素应该只是所有像素的“平均值”(忽略边界 - 大致与 一样ConvolveOp#EDGE_ZERO_FILL)。

然后,卷积将平等对待所有通道。对于非预乘图像,这意味着生成的像素是低不透明度的深红色:

[0.33, 0.33, 0.00, 0.00]
Run Code Online (Sandbox Code Playgroud)

对于预乘图像,假设各分量与其 alpha 值相乘。在这种情况下,生成的像素将是全红色,具有相同的不透明度:

[0.33, 1.00, 0.00, 0.00]
Run Code Online (Sandbox Code Playgroud)

做这背后的数学是很乏味的。事实上,对我来说手动完成这件事太乏味了 - 所以这里是一个例子:

Alpha预乘

以及对应的代码

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.Locale;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class PremultipliedAlphaTest
{
    public static void main(String[] args) 
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }    

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(new PremultipliedAlphaTestPanel());
        f.setSize(550,500);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}


class PremultipliedAlphaTestPanel extends JPanel
{
    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());

        BufferedImage imageS = createImage(BufferedImage.TYPE_INT_ARGB);
        BufferedImage imageP = createImage(BufferedImage.TYPE_INT_ARGB_PRE);

        Kernel kernel = new Kernel(1, 3, 
            new float[]{ 1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f });
        ConvolveOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_ZERO_FILL, null);
        BufferedImage resultS = op.filter(imageS, null);
        BufferedImage resultP = op.filter(imageP, null);

        g.setColor(Color.BLACK);
        g.setFont(new Font("Monospaced", Font.PLAIN, 12));

        g.drawString("Straight:", 10, 40);

        print(g, 2, 1, imageS.getRGB(0, 0));
        print(g, 2, 2, imageS.getRGB(0, 1));
        print(g, 2, 3, imageS.getRGB(0, 2));

        print(g, 7, 2, resultS.getRGB(0, 1));


        g.drawString("Premultiplied:", 10, 240);

        print(g, 2, 5, imageP.getRGB(0, 0));
        print(g, 2, 6, imageP.getRGB(0, 1));
        print(g, 2, 7, imageP.getRGB(0, 2));

        print(g, 7, 6, resultP.getRGB(0, 1));

        g.scale(50, 50);

        g.drawImage(imageS,  1, 1, null);
        g.drawImage(resultS, 6, 1, null);

        g.drawImage(imageP,  1, 5, null);
        g.drawImage(resultP, 6, 5, null);
    }

    private static void print(Graphics2D g, int px, int py, int argb)
    {
        g.drawString(stringFor(argb), px*50+5, py*50+25);
    }


    private static String stringFor(int argb)
    {
        int a = (argb >> 24) & 0xFF;
        int r = (argb >> 16) & 0xFF;
        int g = (argb >>  8) & 0xFF;
        int b = (argb      ) & 0xFF;
        float fa = a / 255.0f;
        float fr = r / 255.0f;
        float fg = g / 255.0f;
        float fb = b / 255.0f;
        return String.format(Locale.ENGLISH,
            "%4.2f %4.2f %4.2f %4.2f", fa, fr, fg, fb);
    }

    private static BufferedImage createImage(int type)
    {
        BufferedImage b = new BufferedImage(1,3, type);
        Graphics2D g = b.createGraphics();
        g.setColor(new Color(1.0f,0.0f,0.0f,1.0f));
        g.fillRect(0, 0, 1, 1);
        g.setColor(new Color(0.0f,0.0f,0.0f,0.0f));
        g.fillRect(0, 1, 1, 1);
        g.setColor(new Color(0.0f,0.0f,0.0f,0.0f));
        g.fillRect(0, 2, 1, 1);
        g.dispose();
        return b;
    }
}
Run Code Online (Sandbox Code Playgroud)