Graphics2d
,我试图在背景图像上绘制一个BufferedImage
。在这个图像的任意点上,我想要“切割一个圆形洞”,以便让背景显示出来。我希望这个洞不是一个实心的形状,而是一个渐变。换句话说,
BufferedImage
中的每个像素都应该有一个与其距离洞中心成比例的alpha/不透明度。我对
Graphics2d
渐变和AlphaComposite
有一定的了解,但是否有一种方法将它们结合起来?有没有一种(不是非常昂贵的)方法来实现这个效果?
Graphics2d
,我试图在背景图像上绘制一个BufferedImage
。在这个图像的任意点上,我想要“切割一个圆形洞”,以便让背景显示出来。BufferedImage
中的每个像素都应该有一个与其距离洞中心成比例的alpha/不透明度。Graphics2d
渐变和AlphaComposite
有一定的了解,但是否有一种方法将它们结合起来?RadialGradientPaint
和适当的AlphaComposite
来解决。MouseMotionListener
,允许您移动孔,通过将当前鼠标位置传递给updateGradientAt
方法,在那里创建所需的图像:
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"
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);
}
}
您可以调整 RadialGradientPaint
的 fractions
和 colors
来实现不同的效果。例如,这些值...
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
JavaDocs 有一些示例可能有助于找到所需的值。
我在以下相关问题上发布了(类似的)答案:
编辑:对于评论中提出的关于性能的问题的回应
确实很有趣,如何比较 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; 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
还有一些其他优点,主要是上面描述的可能的RadialGradientPaint
参数化,这些是我更喜欢这个解决方案的原因。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
可能越界;距离> r
的像素可以完全从迭代中删除)。
注:
int
的alpha值,请搜索这个网站,你会找到至少2-3种更多的方法。来源:
centerX - r
可能越界)”)。 - user1803551
RadialGradientPaint
的复杂性/灵活性 - 您可以使用它实现一些漂亮的效果,而仅仅“切一个洞”只是这个类最简单的情况之一) - Marco13