简体   繁体   English

在Java BufferedImage中绘制完全透明的“白色”

[英]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: 为了生成光晕,我创建了一个比文本大的新BufferedImage,然后将白色文本绘制到图像的画布上,并通过ConvolveOp在图像上运行高斯模糊,以期实现以下目的:

在此处输入图片说明

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. 卷积在每个颜色通道(R,G,B和A)上独立运行以计算模糊图像。 The transparent background of the picture has color value 0x00000000, ie a fully transparent black ! 图片的透明背景的颜色值为0x00000000,即完全透明的黑色 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. 因此,当卷积滤镜在图像上运行时,它不仅会混合alpha值,还会将黑色混入白色像素的RGB值。 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.". 要解决此问题,我需要将图像初始化为0x00FFFFFF,即改为完全透明的白色 ,但是如果我只是设置该颜色并用它填充一个矩形,则它根本不起作用,就像Java所说的“好吧,这是一个完全透明的矩形,你在画画!这不会改变图像的。让我为你优化它。完成。不客气。”

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... 如果我改为将颜色设置为0x01FFFFFF,即几乎完全透明的白色,则它确实绘制了矩形,并且发光看起来很漂亮,除非我最终在其周围出现了一个非常暗的白色框...

Is there a way I can initialize the image to 0x00FFFFFF everywhere? 有没有一种方法可以在任何地方将图像初始化为0x00FFFFFF?

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. 我在图像上绘制了一个不透明的白色矩形,然后在图像上运行RescaleOp ,将所有alpha值都设置为0。这是可行的,但是就性能而言,这可能是一种糟糕的方法。

Can I do better somehow? 我可以以某种方式做得更好吗?

PS: I'm also open to entirely different suggestions for creating such a glow effect PS:我也乐于接受完全不同的建议来创建这种发光效果

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. 初始方法使辉光显得更暗的主要原因是您未使用带有预乘alpha分量的图像。 The JavaDoc of ConvolveOp contains some information about how the alpha component is treated during a convolution. ConvolveOp的JavaDoc包含一些有关在卷积期间如何处理alpha分量的信息。

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 . 但是,或者,您可以简单地使用具有预乘alpha的图像,即类型为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 ...). 这是一个MCVE ,它在面板上绘制一些文本,以及一些围绕文本的脉冲发光(删除计时器并设置固定的半径以删除脉冲-我在这里忍不住玩了一点……)。

辉光

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. 我发现clearRect应该绘制透明颜色。

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. 您还应该能够通过直接设置像素数据来强制BufferedImage用透明颜色填充。

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. 它没有明确记录,但我对其进行了测试,并且setRGB似乎接受ARGB值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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