Java Graphics2D - 使用渐变不透明度绘制图像 [英] Java Graphics2D - draw an image with gradient opacity

查看:654
本文介绍了Java Graphics2D - 使用渐变不透明度绘制图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 Graphics2d ,我试图在背景图像上绘制 BufferedImage 。在这张图片的任意一点,我想在绘制的图像中切出一个圆形孔,让背景显示出来。



我想要的洞不是是一个坚实的形状,而是一个渐变。换句话说, BufferedImage 中的每个像素应该具有与其距孔中心的距离成比例的alpha /不透明度。



我对 Graphics2d 渐​​变以及 AlphaComposite 有点熟悉,但有没有办法将它们组合起来?



是否有(不是非常昂贵的)方式来实现这种效果?

解决方案

这可以通过 RadialGradientPaint 和相应的 AlphaComposite 来解决。



以下是



而这些值

  float fractions [] = {0.0f,0.9f,1.0f}; 
颜色[] = {
新颜色(0,0,0,255),
新颜色(0,0,0,255),
新颜色(0,0,0 ,0)
};

造成一个大而清晰透明的中心,带有一个小电晕:





的RadialGradientPaint 的JavaDoc 有一些实例中,可能有助于找到所需的值。






我发布(相似)答案的一些相关问题:









EDIT作为回应问题关于评论中提到的表现


关于 Paint的表现的问题 / 复合方法与 getRGB / setRGB 方法确实很有趣。根据我之前的经验,我的直觉是第一个比第二个快得多,因为,一般来说, getRGB / setRGB 往往很慢,并且内置机制经过高度优化(在某些情况下,甚至可能是硬件加速)。



In事实上, Paint / Composite 方法更快 getRGB / setRGB 接近,但不如我预期的那么多。以下当然不是一个非常深刻的基准(我没有使用Caliper或JMH),但应该对实际表现给出一个很好的估计:

  //注意:这不是一个复杂的基准,
//但是给出了关于性能的粗略估计

import java.awt .AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.image.BufferedImage;

public class TransparentGradientInImagePerformance
{
public static void main(String [] args)
{
int w = 1000;
int h = 1000;
BufferedImage image0 = new BufferedImage(w,h,
BufferedImage.TYPE_INT_ARGB);
BufferedImage image1 = new BufferedImage(w,h,
BufferedImage.TYPE_INT_ARGB);

早在= 0之前;在= 0之后长
;
int runs = 100;
for(int radius = 100; radius< = 400; radius + = 10)
{
before = System.nanoTime();
for(int i = 0; i< runs; i ++)
{
transparitize(image0,w / 2,h / 2,radius);
}
= System.nanoTime();
System.out.println(
Radius+ radius +with getRGB / setRGB:+(after-before)/ 1e6);

before = System.nanoTime();
for(int i = 0; i< runs; i ++)
{
updateGradientAt(image0,image1,new Point(w / 2,h / 2),radius);
}
= System.nanoTime();
System.out.println(
Radius+ radius +with paint+(after-before)/ 1e6);
}
}

private static void transparitize(
BufferedImage imgA,int centerX,int centerY,int r)
{

for(int x = centerX - r; x< centerX + r; x ++)
{
for(int y = centerY - r; y< centerY + r; y ++)
{
双倍距离= Math.sqrt(
Math.pow(Math.abs(centerX - x),2)+
Math.pow(Math.abs(centerY - y), 2));
如果(距离> r)
继续;
int argb = imgA.getRGB(x,y);
int a =(argb>> 24)& 255;
double factor = distance / r;
argb =(argb - (a<<< 24)+((int)(a * factor)<< 24));
imgA.setRGB(x,y,argb);
}
}
}

私有静态无效updateGradientAt(BufferedImage的originalImage,
的BufferedImage imageWithGradient,点对点,INT半径)
{
Graphics2D g = imageWithGradient.createGraphics();
g.drawImage(originalImage,0,0,null);

float fractions [] = {0.0f,1.0f};
颜色[] = {新颜色(0,0,0,255),新颜色(0,0,0,0)};
RadialGradientPaint paint = new RadialGradientPaint(point,radius,
fractions,colors);
g.setPaint(paint);

g.setComposite(AlphaComposite.DstOut);
g.fillOval(point.x - radius,point.y - radius,radius * 2,radius * 2);
g.dispose();
}
}

我的电脑上的时间顺序是

  ... 
半径390与getRGB / setRGB:1518.224404
半径390与油漆764.11017
半径400的getRGB / setRGB:1612.854049
半径400与油漆794.695199

表明该 Paint / Composite 方法的速度大约是 getRGB / setRGB 方法。除了性能之外, Paint / Composite 还有其他一些优点,主要是<$ c $的可能参数化c> RadialGradientPaint 如上所述,这就是为什么我更喜欢这个解决方案。


Using Graphics2d, I am trying to draw a BufferedImage on top of a background image. At an arbitrary point in this image, I would like to "cut a circular hole" in the drawn image to let the background show through.

I would like the hole not be a solid shape, but rather a gradient. In other words, every pixel in the BufferedImage should have an alpha/opacity proportional to its distance from the center of the hole.

I am somewhat familiar with Graphics2d gradients and with AlphaComposite, but is there a way to combine these?

Is there a (not insanely expensive) way to achieve this effect?

解决方案

This can be solved with a RadialGradientPaint and the appropriate AlphaComposite.

The following is a MCVE that shows how this can be done. It uses the same images as user1803551 used in his answer, so a screenshot would look (nearly) the same. But this one adds a MouseMotionListener that allows you to move the hole around, by passing the current mouse position to the updateGradientAt method, where the actual creation of the desired image takes place:

  • It first fills the image with the original image
  • Then it creates a RadialGradientPaint, which has a fully opaque color in the center, and a completely transparent color at the border (!). This may seen counterintuitive, but the intention is to "cut out" the hole out of an existing image, which is done with the next step:
  • An AlphaComposite.DstOut is assigned to the Graphics2D. This one causes an "inversion" of the alpha values, as in the formula

    Ar = Ad*(1-As)
    Cr = Cd*(1-As)
    

    where r stands for "result", s stands for "source", and d stands for "destination"

The result is an image that has the radial gradient transparency at the desired location, being fully transparent at the center and fully opaque at the border (!). This combination of Paint and Composite is then used for filling an oval with the size and coordinates of the hole. (One could also do a fillRect call, filling the whole image - it would not change the outcome).

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

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

public class TransparentGradientInImage
{
    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);

        TransparentGradientInImagePanel p =
            new TransparentGradientInImagePanel();
        f.getContentPane().add(p);
        f.setSize(800, 600);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

}

class TransparentGradientInImagePanel extends JPanel
{
    private BufferedImage background;
    private BufferedImage originalImage;
    private BufferedImage imageWithGradient;

    TransparentGradientInImagePanel()
    {
        try
        {
            background = ImageIO.read(
                new File("night-sky-astrophotography-1.jpg"));
            originalImage = convertToARGB(ImageIO.read(new File("7bI1Y.jpg")));
            imageWithGradient = convertToARGB(originalImage);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        addMouseMotionListener(new MouseAdapter()
        {
            @Override
            public void mouseMoved(MouseEvent e)
            {
                updateGradientAt(e.getPoint());
            }
        });
    }


    private void updateGradientAt(Point point)
    {
        Graphics2D g = imageWithGradient.createGraphics();
        g.drawImage(originalImage, 0, 0, null);

        int radius = 100;
        float fractions[] = { 0.0f, 1.0f };
        Color colors[] = { new Color(0,0,0,255), new Color(0,0,0,0) };
        RadialGradientPaint paint = 
            new RadialGradientPaint(point, radius, fractions, colors);
        g.setPaint(paint);

        g.setComposite(AlphaComposite.DstOut);
        g.fillOval(point.x - radius, point.y - radius, radius * 2, radius * 2);
        g.dispose();
        repaint();
    }

    private static BufferedImage convertToARGB(BufferedImage image)
    {
        BufferedImage newImage =
            new BufferedImage(image.getWidth(), image.getHeight(),
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return newImage;
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawImage(background, 0, 0, null);
        g.drawImage(imageWithGradient, 0, 0, null);
    }
}

You may play with the fractions and colors of the RadialGradientPaint to achieve different effects. For example, these values...

float fractions[] = { 0.0f, 0.1f, 1.0f };
Color colors[] = { 
    new Color(0,0,0,255), 
    new Color(0,0,0,255), 
    new Color(0,0,0,0) 
};

cause a small, transparent hole, with a large, soft "corona":

whereas these values

float fractions[] = { 0.0f, 0.9f, 1.0f };
Color colors[] = { 
    new Color(0,0,0,255), 
    new Color(0,0,0,255), 
    new Color(0,0,0,0) 
};

cause a large, sharply transparent center, with a small "corona":

The RadialGradientPaint JavaDocs have some examples that may help to find the desired values.


Some related questions where I posted (similar) answers:


EDIT In response to the question about the performance that was asked in the comments

The question of how the performance of the Paint/Composite approach compares to the getRGB/setRGB approach is indeed interesting. From my previous experience, my gut feeling would have been that the first one is much faster than the second, because, in general, getRGB/setRGB tends to be slow, and the built-in mechanisms are highly optimized (and, in some cases, may even be hardware accelerated).

In fact, the Paint/Composite approach is faster than the getRGB/setRGB approach, but not as much as I expected. The following is of course not a really profound "benchmark" (I didn't employ Caliper or JMH for this), but should give a good estimation about the actual performance:

// NOTE: This is not really a sophisticated "Benchmark", 
// but gives a rough estimate about the performance

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.image.BufferedImage;

public class TransparentGradientInImagePerformance
{
    public static void main(String[] args)
    {
        int w = 1000;
        int h = 1000;
        BufferedImage image0 = new BufferedImage(w, h,
            BufferedImage.TYPE_INT_ARGB);
        BufferedImage image1 = new BufferedImage(w, h,
            BufferedImage.TYPE_INT_ARGB);

        long before = 0;
        long after = 0;
        int runs = 100;
        for (int radius = 100; radius <=400; radius += 10)
        {
            before = System.nanoTime();
            for (int i=0; i<runs; i++)
            {
                transparitize(image0, w/2, h/2, radius);
            }
            after = System.nanoTime();
            System.out.println(
                "Radius "+radius+" with getRGB/setRGB: "+(after-before)/1e6);

            before = System.nanoTime();
            for (int i=0; i<runs; i++)
            {
                updateGradientAt(image0, image1, new Point(w/2, h/2), radius);
            }
            after = System.nanoTime();
            System.out.println(
                "Radius "+radius+" with paint          "+(after-before)/1e6);
        }
    }

    private static void transparitize(
        BufferedImage imgA, int centerX, int centerY, int r)
    {

        for (int x = centerX - r; x < centerX + r; x++)
        {
            for (int y = centerY - r; y < centerY + r; y++)
            {
                double distance = Math.sqrt(
                    Math.pow(Math.abs(centerX - x), 2) +
                    Math.pow(Math.abs(centerY - y), 2));
                if (distance > r)
                    continue;
                int argb = imgA.getRGB(x, y);
                int a = (argb >> 24) & 255;
                double factor = distance / r;
                argb = (argb - (a << 24) + ((int) (a * factor) << 24));
                imgA.setRGB(x, y, argb);
            }
        }
    }

    private static void updateGradientAt(BufferedImage originalImage,
        BufferedImage imageWithGradient, Point point, int radius)
    {
        Graphics2D g = imageWithGradient.createGraphics();
        g.drawImage(originalImage, 0, 0, null);

        float fractions[] = { 0.0f, 1.0f };
        Color colors[] = { new Color(0, 0, 0, 255), new Color(0, 0, 0, 0) };
        RadialGradientPaint paint = new RadialGradientPaint(point, radius,
            fractions, colors);
        g.setPaint(paint);

        g.setComposite(AlphaComposite.DstOut);
        g.fillOval(point.x - radius, point.y - radius, radius * 2, radius * 2);
        g.dispose();
    }
}

The timings on my PC are along the lines of

...
Radius 390 with getRGB/setRGB: 1518.224404
Radius 390 with paint          764.11017
Radius 400 with getRGB/setRGB: 1612.854049
Radius 400 with paint          794.695199

showing that the Paint/Composite method is roughly twice as fast as the getRGB/setRGB method. Apart from the performance, the Paint/Composite has some other advantages, mainly the possible parametrizations of the RadialGradientPaint that are described above, which are reasons why I would prefer this solution.

这篇关于Java Graphics2D - 使用渐变不透明度绘制图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆