Impact of TYPE_INT_ARGB_PRE

2019-09-11 00:08发布

I've been having some issues with a ConvolveOp that can be fixed by setting the imageType of the BufferedImage I'm working with to TYPE_INT_ARGB_PRE (see related SO answer here).

Unfortunately I don't fully understand all the implications of selecting this different imageType and I can't seem to find a good reference either, so let me try here:

Which drawing operations are affected by changing the imageType of a BufferedImage from TYPE_INT_ARGB to TYPE_INT_ARGB_PRE? Is it just BufferedImageOps? Or does it affect any of the draw commands on the image's Graphics object or the way the image is rendered if it is drawn onto a different Graphics object?

1条回答
对你真心纯属浪费
2楼-- · 2019-09-11 00:37

This basically depends on whether the painting algorithms take into account the information of whether the image is using premultiplied alpha or not.

As already pointed out in the comment: The results will in most cases be the same - at least for the basic drawing operations: Whether you are painting a "non-premultiplied" image into a premultiplied one, or vice versa, will not affect the result, because the differences are handled internally.

A special case are the BufferedImageOps. The JavaDoc comments explicitly say how the alpha channel is treated, and passing in the wrong kind of image can lead to the undesirable results described in the question that you linked to.

It's hard to pin down "the" reason why they decided to implement the BufferedImageOp this way. But a (somewhat vague) statement here is: When operating on (and combining) the pixels of a single source, and these pixels have different alpha values, the treatment of the alpha channel may become fiddly. It's simply not always perfectly obvious what should happen with the alpha channel.

For example, imagine a stack of thee pixels (here, in ARGB, with floating point values):

[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

Now, you want to do a convolution on these pixels (as in the question that you linked to). The kernel could then be

[0.33...]
[0.33...],
[0.33...] 

which means that the center pixel of the result should just be the "average" of all pixels (ignoring the borders - roughly as with ConvolveOp#EDGE_ZERO_FILL).

The convolution would then treat all channels equally. For a non-premultiplied image, this would mean that the resulting pixel is a dark red with low opacity:

[0.33, 0.33, 0.00, 0.00]

For the premultiplied image, the components are assumed to be multipled with their alpha value. In this case, the resulting pixel would be fully red, with the same opacity:

[0.33, 1.00, 0.00, 0.00]

Doing the maths behind this is tedious. And in fact, too tedious for me to do it manually - so here is an example:

AlphaPremultiplied

and the corresponding code

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;
    }
}
查看更多
登录 后发表回答