简体   繁体   中英

Drawing fully transparent “white” in Java BufferedImage

This might sound like a bit of strange title, but bear with me, there is a reason:

I am trying to generate a white glow around a text on a gray background.

To generate the glow, I created a new BufferedImage that's bigger than the text, then I drew the text in white onto the canvas of the image and ran a Gaussian Blur over the image via a ConvolveOp , hoping for something like this:

在此处输入图片说明

At first I was a bit surprised when the glow turned out darker than the gray background of the text:

在此处输入图片说明

But after a bit of thinking, I understood the problem:

The convolution operates on each color channel (R, G, B, and A) independently to calculate the blurred image. The transparent background of the picture has color value 0x00000000, ie a fully transparent black ! So, when the convolution filter runs over the image, it not only blends the alpha value, but also mixes the black into the RGB values of the white pixels. This is why the glow comes out dark.

To fix this, I need to initialize the image to 0x00FFFFFF, ie a fully transparent white instead, but if I just set that color and fill a rectangle with it, it simply does nothing as Java says "well, it's a fully transparent rectangle that you're drawing! That's not going to change the image... Let me optimize that away for you... Done... You're welcome.".

If I instead set the color to 0x01FFFFFF, ie an almost fully transparent white, it does draw the rectangle and the glow looks beautiful, except I end up with a very faint white box around it...

Is there a way I can initialize the image to 0x00FFFFFF everywhere?

UPDATE:

I found one way, but it's probably as non-optimal as you can get:

I draw an opaque white rectangle onto the image and then I run a RescaleOp over the image that sets all alpha values to 0. This works, but it's probably a terrible approach as far as performance goes.

Can I do better somehow?

PS: I'm also open to entirely different suggestions for creating such a glow effect

The main reason why the glow appeared darker with your initial approach is most likely that you did not use an image with a premultiplied alpha component. The JavaDoc of ConvolveOp contains some information about how the alpha component is treated during a convolution.

You could work around this with an "almost fully transparent white". But alternatively, you may simply use an image with premultiplied alpha, ie one with the type TYPE_INT_ARGB_PRE .

Here is a MCVE that draws a panel with some text, and some pulsing glow around the text (remove the timer and set a fixed radius to remove the pulse - I couldn't resist playing around a little here ...).

辉光

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;

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

public class TextGlowTest
{
    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 TextGlowPanel());
        f.setSize(300,200);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class TextGlowPanel extends JPanel
{
    private BufferedImage image;
    private int radius = 1;

    TextGlowPanel()
    {
        Timer t = new Timer(50, new ActionListener()
        {
            long startMillis = -1;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (startMillis == -1)
                {
                    startMillis = System.currentTimeMillis();
                }
                long d = System.currentTimeMillis() - startMillis;
                double s = d / 1000.0;
                radius = (int)(1 + 15 * (Math.sin(s * 3) * 0.5 + 0.5));
                repaint();
            }
        });
        t.start();
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        gr.setColor(Color.GRAY);

        int w = getWidth();
        int h = getHeight();
        gr.fillRect(0, 0, w, h);

        if (image == null || image.getWidth() != w || image.getHeight() != h)
        {
            // Must be prmultiplied!
            image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
        }

        Graphics2D g = image.createGraphics();
        Font font = g.getFont().deriveFont(70.0f).deriveFont(Font.BOLD);
        g.setFont(font);

        g.setComposite(AlphaComposite.Src);
        g.setColor(new Color(255,255,255,0));
        g.fillRect(0,0,w,h);

        g.setComposite(AlphaComposite.SrcOver);
        g.setColor(new Color(255,255,255,0));
        g.fillRect(0,0,w,h);

        g.setColor(Color.WHITE);
        g.drawString("Glow!", 50, 100);

        image = getGaussianBlurFilter(radius, true).filter(image, null);
        image = getGaussianBlurFilter(radius, false).filter(image, null);

        g.dispose();

        g = image.createGraphics();
        g.setFont(font);
        g.setColor(Color.BLUE);
        g.drawString("Glow!", 50, 100);
        g.dispose();

        gr.drawImage(image, 0, 0, null);
    }


    // From
    // http://www.java2s.com/Code/Java/Advanced-Graphics/GaussianBlurDemo.htm
    public static ConvolveOp getGaussianBlurFilter(
        int radius, boolean horizontal)
    {
        if (radius < 1)
        {
            throw new IllegalArgumentException("Radius must be >= 1");
        }

        int size = radius * 2 + 1;
        float[] data = new float[size];

        float sigma = radius / 3.0f;
        float twoSigmaSquare = 2.0f * sigma * sigma;
        float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI);
        float total = 0.0f;

        for (int i = -radius; i <= radius; i++)
        {
            float distance = i * i;
            int index = i + radius;
            data[index] =
                (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot;
            total += data[index];
        }

        for (int i = 0; i < data.length; i++)
        {
            data[i] /= total;
        }

        Kernel kernel = null;
        if (horizontal)
        {
            kernel = new Kernel(size, 1, data);
        }
        else
        {
            kernel = new Kernel(1, size, data);
        }
        return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
    }
}

I've found that clearRect should paint a transparent color.

g.setBackground(new Color(0x00FFFFFF, true));
g.clearRect(0, 0, img.getWidth(), img.getHeight());

You should also be able to force the BufferedImage to fill with a transparent color by setting the pixel data directly.

public static void forceFill(BufferedImage img, int rgb) {
    for(int x = 0; x < img.getWidth(); x++) {
        for(int y = 0; y < img.getHeight(); y++) {
            img.setRGB(x, y, rgb);
        }
    }
}

It is not clearly documented but I tested it and setRGB appears to accept an ARGB value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM