使用Graphics2d
,我试图BufferedImage
在背景图像上绘制.在这张图片的任意一点,我想在绘制的图像中"切出一个圆孔"让背景显示出来.
我希望这个洞不是一个坚固的形状,而是一个渐变.换句话说,BufferedImage
应该具有与其距孔中心的距离成比例的α/不透明度.
我对Graphics2d
渐变有点熟悉AlphaComposite
,但有没有办法将它们结合起来?
是否有(不是非常昂贵)的方式来实现这种效果?
这可以通过RadialGradientPaint
适当的方法解决AlphaComposite
.
以下是MCVE,展示了如何做到这一点.它使用与他的答案中使用的user1803551相同的图像,因此屏幕截图看起来(几乎)相同.但是这个添加了一个MouseMotionListener
允许您通过将当前鼠标位置传递给updateGradientAt
方法来移动孔周围,在该方法中实际创建所需图像:
它首先用原始图像填充图像
然后它创建一个RadialGradientPaint
,在中心有一个完全不透明的颜色,在边框(!)有一个完全透明的颜色.这可能是违反直觉的,但目的是从现有图像中"切出"这个洞,这是通过下一步完成的:
一个AlphaComposite.DstOut
被分配到Graphics2D
.这个导致α值的"反转",如公式中所示
Ar = Ad*(1-As) Cr = Cd*(1-As)
where r
代表"result",s
代表"source",d
代表"destination"
结果是在所需位置具有径向渐变透明度的图像,在中心处完全透明并且在边界处完全不透明(!).的这种组合Paint
并Composite
然后被用于与尺寸和孔的填充坐标的椭圆形.(人们也可以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
和colors
的RadialGradientPaint
,以达到不同的效果.例如,这些价值......
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) };
导致一个小而透明的孔,有一个大而柔软的"电晕":
而这些价值观
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) };
造成一个大而清晰透明的中心,带有一个小"电晕":
该RadialGradientPaint
JavaDoc中有一些例子,可以帮助找到所需的值.
我发布的一些相关问题(类似)答案:
Java2D Alpha Mapping图像
如何在Java中进行2D阴影投射?
编辑回答有关评论中提出的表现的问题
如何将性能问题Paint
/ Composite
方法比作getRGB
/ setRGB
做法的确有趣.根据我以前的经验,我的直觉是第一个比第二个快得多,因为,一般来说,getRGB
/ setRGB
往往很慢,并且内置机制被高度优化(在某些情况下,可能甚至是硬件加速).
事实上,Paint
/ Composite
办法是比快getRGB
/ setRGB
方法,但并不如我预期的多.以下当然不是一个非常深刻的"基准"(我没有使用Caliper或JMH),但应该对实际性能给出一个很好的估计:
// 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; ir) 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
还有一些其他优点,主要RadialGradientPaint
是上面描述的可能的参数化,这是我更喜欢这个解决方案的原因.