Java Graphics2D - 绘制具有渐变不透明度的图像
Java Graphics2D - draw an image with gradient opacity
使用 Graphics2d
,我试图在背景图像上绘制 BufferedImage
。在此图像中的任意点,我想 "cut a circular hole" 在绘制的图像中让背景显示出来。
我希望孔不是实心形状,而是渐变形状。换句话说,BufferedImage
中的每个像素都应该有一个 alpha/opacity 与其到孔中心的距离成正比。
我对 Graphics2d
渐变和 AlphaComposite
有点熟悉,但是有没有办法将它们结合起来?
是否有(不是非常昂贵的)方法来实现这种效果?
我不知道您是打算动态创建这个透明 "hole" 还是 one-time 东西。我敢肯定有几种方法可以完成您想要的,我正在展示其中一种直接更改像素的方法, 可能不是最好的 performance-wise(我只是不要将它与其他方式进行比较,我认为这将取决于您的具体操作。
这里我描绘了澳大利亚上空臭氧层的空洞:
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
的像素可以从迭代中完全删除)。
备注:
- 我画了背景图片,并在上面画了较小的 over-image 只是为了清楚地显示背景的样子。
- 读取和更改
int
的 alpha 值的方法有很多,搜索此站点,您至少会找到 2-3 种方法。
- 添加到您最喜欢的顶级容器并运行。
来源:
- Converting a BufferedImage to another type
- Image of Sydney, Australia
- Image of night sky
这可以用 RadialGradientPaint
和适当的 AlphaComposite
来解决。
以下是 MCVE that shows how this can be done. It uses the same images as ,因此屏幕截图看起来(几乎)相同。但是这个添加了一个 MouseMotionListener
允许您通过将当前鼠标位置传递给 updateGradientAt
方法来移动孔,在该方法中实际创建所需的图像:
- 它首先用原始图像填充图像
- 然后它创建了一个
RadialGradientPaint
,它的中心是完全不透明的颜色,而边界是完全透明的颜色 (!)。这可能看起来违反直觉,但其目的是 "cut out" 从现有图像中挖出洞,这是通过下一步完成的:
一个 AlphaComposite.DstOut
被分配给了 Graphics2D
。这会导致 "inversion" 的 alpha 值,如公式
Ar = Ad*(1-As)
Cr = Cd*(1-As)
其中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);
}
}
你可以把玩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)
};
造成一个小而透明的孔,带有一个大而柔软的"corona":
而这些值
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)
};
造成一个大而清晰的中心,一个小 "corona":
RadialGradientPaint
JavaDocs 有一些示例可能有助于找到所需的值。
我发布的一些相关问题(类似)答案:
- Java2D Alpha Mapping images
- How to do 2D shadow casting in Java?
EDIT In response to the question about the performance that was asked in the comments
Paint
/Composite
方法的性能与 getRGB
/setRGB
方法相比如何的问题确实很有趣。根据我以前的经验,我的直觉是第一个比第二个快得多,因为一般来说,getRGB
/setRGB
往往很慢,而 built-in机制得到高度优化(在某些情况下,甚至可能是硬件加速的)。
事实上,Paint
/Composite
方法 比 getRGB
/setRGB
方法快,但不是正如我所料。以下当然不是很深奥"benchmark"(我没有为此使用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();
}
}
我 PC 上的时间与
...
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
可能的参数化,这就是我更喜欢这个解决方案的原因。
使用 Graphics2d
,我试图在背景图像上绘制 BufferedImage
。在此图像中的任意点,我想 "cut a circular hole" 在绘制的图像中让背景显示出来。
我希望孔不是实心形状,而是渐变形状。换句话说,BufferedImage
中的每个像素都应该有一个 alpha/opacity 与其到孔中心的距离成正比。
我对 Graphics2d
渐变和 AlphaComposite
有点熟悉,但是有没有办法将它们结合起来?
是否有(不是非常昂贵的)方法来实现这种效果?
我不知道您是打算动态创建这个透明 "hole" 还是 one-time 东西。我敢肯定有几种方法可以完成您想要的,我正在展示其中一种直接更改像素的方法, 可能不是最好的 performance-wise(我只是不要将它与其他方式进行比较,我认为这将取决于您的具体操作。
这里我描绘了澳大利亚上空臭氧层的空洞:
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
的像素可以从迭代中完全删除)。
备注:
- 我画了背景图片,并在上面画了较小的 over-image 只是为了清楚地显示背景的样子。
- 读取和更改
int
的 alpha 值的方法有很多,搜索此站点,您至少会找到 2-3 种方法。 - 添加到您最喜欢的顶级容器并运行。
来源:
- Converting a BufferedImage to another type
- Image of Sydney, Australia
- Image of night sky
这可以用 RadialGradientPaint
和适当的 AlphaComposite
来解决。
以下是 MCVE that shows how this can be done. It uses the same images as MouseMotionListener
允许您通过将当前鼠标位置传递给 updateGradientAt
方法来移动孔,在该方法中实际创建所需的图像:
- 它首先用原始图像填充图像
- 然后它创建了一个
RadialGradientPaint
,它的中心是完全不透明的颜色,而边界是完全透明的颜色 (!)。这可能看起来违反直觉,但其目的是 "cut out" 从现有图像中挖出洞,这是通过下一步完成的: 一个
AlphaComposite.DstOut
被分配给了Graphics2D
。这会导致 "inversion" 的 alpha 值,如公式Ar = Ad*(1-As) Cr = Cd*(1-As)
其中
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);
}
}
你可以把玩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)
};
造成一个小而透明的孔,带有一个大而柔软的"corona":
而这些值
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)
};
造成一个大而清晰的中心,一个小 "corona":
RadialGradientPaint
JavaDocs 有一些示例可能有助于找到所需的值。
我发布的一些相关问题(类似)答案:
- Java2D Alpha Mapping images
- How to do 2D shadow casting in Java?
EDIT In response to the question about the performance that was asked in the comments
Paint
/Composite
方法的性能与 getRGB
/setRGB
方法相比如何的问题确实很有趣。根据我以前的经验,我的直觉是第一个比第二个快得多,因为一般来说,getRGB
/setRGB
往往很慢,而 built-in机制得到高度优化(在某些情况下,甚至可能是硬件加速的)。
事实上,Paint
/Composite
方法 比 getRGB
/setRGB
方法快,但不是正如我所料。以下当然不是很深奥"benchmark"(我没有为此使用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();
}
}
我 PC 上的时间与
...
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
可能的参数化,这就是我更喜欢这个解决方案的原因。