Java Graphics2D - 使用渐变不透明度绘制图像

时间:2016-01-09 00:11:48

标签: java image gradient alpha graphics2d

使用BufferedImage,我尝试在背景图片上绘制BufferedImage。在这张图片的任意一点,我想“切开一个圆孔”#34;在绘制的图像中让背景显示出来。

我希望这个洞不是一个坚固的形状,而是一个渐变。换句话说,Graphics2d中的每个像素都应具有与其距孔中心的距离成比例的alpha /不透明度。

我对AlphaComposite渐变和for (i = 0; i < msg.WrittenList.length; i++) { $("#tbWritten").append("<tr><td>" + msg.WrittenList[i] + "</td></tr>"); // $('table > tbody > tr:first').before('<tr><td>Stuff</td></tr>'); } 有点熟悉,但有没有办法将这些结合起来?

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

2 个答案:

答案 0 :(得分:6)

可以使用RadialGradientPaint和相应的AlphaComposite解决此问题。

以下是MCVE,说明了如何做到这一点。它使用与user1803551 used in his answer相同的图像,因此屏幕截图看起来(几乎)相同。但是这个添加了MouseMotionListener,允许您通过将当前鼠标位置传递到updateGradientAt方法来移动孔,在该方法中实际创建所需图像:

  • 首先使用原始图像填充图像
  • 然后它会创建一个RadialGradientPaint,它在中心有一个完全不透明的颜色,在边框处有一个完全透明的颜色(!)。这可能是违反直觉的,但目的是&#34;切断&#34;现有图像的孔,下一步完成:
  • Graphics2D已分配AlphaComposite.DstOut。这个导致&#34;反转&#34;的alpha值,如公式

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

    其中r代表&#34;结果&#34;,s代表&#34;来源&#34;,d代表&#34;目的地&# 34;

结果是在所需位置具有径向渐变透明度的图像,在中心处完全透明,在边界处完全不透明(!)。然后使用PaintComposite的这种组合来填充具有孔的大小和坐标的椭圆。 (也可以进行fillRect调用,填写整个图像 - 它不会改变结果。)

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);
    }
}

您可以使用fractions的{​​{1}}和colors来实现不同的效果。例如,这些值......

RadialGradientPaint

造成一个小而透明的洞,有一个大而柔软的#34;电晕&#34;:

TransparentGradientInImage02.png

而这些值

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) 
};

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

TransparentGradientInImage01.png

RadialGradientPaint JavaDocs有一些示例可能有助于找到所需的值。

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

  

编辑回答有关评论中提出的表现的问题

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) }; / Paint方法的效果与Composite / getRGB方法相比的问题确实很有趣。根据我以前的经验,我的直觉是第一个比第二个快得多,因为,一般来说,setRGB / getRGB往往很慢,内置机制是高度优化(在某些情况下,甚至可能是硬件加速)。

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

setRGB

我电脑上的时间顺序是

// 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();
    }
}

显示... 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 / Paint方法的速度大约是Composite / getRGB方法的两倍。除了性能之外,setRGB / Paint还有其他一些优点,主要是上面描述的Composite的可能参数化,这也是我更喜欢这种解决方案的原因。

答案 1 :(得分:2)

我不知道你是否打算创造这个透明的洞#34;动态地或者如果它是一次性的。我确定有几种方法可以实现您想要的效果,并且我会直接更改像素中的一种,可能不是最佳性能(我只是不要与其他方式进行比较,我认为这取决于你的确切做法。

这里我描绘了澳大利亚臭氧层中的洞:

enter image description here

public class Paint extends JPanel {

    BufferedImage imgA;
    BufferedImage bck;

    Paint() {

        BufferedImage img = null;
        try {
            img = ImageIO.read(getClass().getResource("img.jpg")); // images linked below
            bck = ImageIO.read(getClass().getResource("bck.jpg"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        imgA = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = imgA.createGraphics();
        g2d.drawImage(img, 0, 0, null);
        g2d.dispose();

        transparitize(200, 100, 80);
    }

    private void transparitize(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);
            }
        }
    }

    @Override
    protected void paintComponent(Graphics g) {

        super.paintComponent(g);
        g.drawImage(bck, 0, 0, null);
        g.drawImage(imgA, 0, 0, null);
    }

    @Override
    public Dimension getPreferredSize() {

        return new Dimension(bck.getWidth(), bck.getHeight()); // because bck is larger than imgA, otherwise use Math.max
    }
}

我们的想法是使用getRGB获取像素的ARGB值,更改alpha(或其他任何内容),并使用setRGB进行设置。我创建了一个方法,使径向渐变给定一个中心和一个半径。它当然可以改进,我会留给你(提示:centerX - r可能超出范围; distance > r的像素可以从迭代中完全删除。)

备注:

  • 我绘制了背景图像,并在其上面显示了较小的图像,以便清楚地显示背景图像。
  • 有很多方法可以阅读和更改int的Alpha值,搜索此网站,您至少可以找到2-3种方式。
  • 添加到您最喜欢的顶级容器并运行。

<强>来源: