非常奇怪的 Java2D setClip() 效果 - 错误?
Very weird Java2D setClip() effect - bug?
我想创建一个具有自定义形状和透明度的对话框,想想指向某个组件的信息气泡。
为此,我将 JPanel
添加到 JDialog
并覆盖面板的 paintComponent(Graphics)
方法。面板本身包含常规 JLabels
和 JButtons
。
工作正常,但只要我在面板绘制代码中使用 Graphics2D.setClip(Shape)
,组件就会被背景透支。如果我不设置剪辑(设置为全新的 Graphics2D
对象,同样如此),一切正常。
这让我很困惑,我不知道我能做些什么来解决它。
P.S.: 我不能在 JDialog
上使用 setShape(Shape)
,因为那里没有抗锯齿功能。
P.P.S.: 实际用例是绘制一个大的背景图像,必须在信息气泡形状处精确切割。
下面的 SSCCE 演示了鼠标多次悬停在右上角的 'x' 上时出现的问题。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import java.awt.Polygon;
import java.awt.Shape;
public class Java2DTransparencySSCE extends JDialog {
JPanel panel;
private JButton close;
private JLabel headline;
private JLabel mainText;
public Java2DTransparencySSCE() {
setLayout(new BorderLayout());
setFocusable(false);
setFocusableWindowState(false);
setUndecorated(true);
setBackground(new Color(0, 0, 0, 0));
panel = new JPanel(new GridBagLayout()) {
@Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
Graphics2D gImg = (Graphics2D) g.create();
// if the line below is removed, everything works..
Shape oldClip= gImg.getClip();
// both the shape and a standard rectangular clip break everything
// gImg.setClip(50, 50, 50, 50);
Polygon shape = new Polygon(new int[] {0, 100, 100, 50, 0}, new int[] {200, 200, 100, 50, 100}, 5);
gImg.setClip(shape);
gImg.setColor(new Color(255, 0, 0, 50));
gImg.fill(shape);
gImg.setClip(oldClip);
gImg.dispose();
}
};
panel.setOpaque(false);
add(panel, BorderLayout.CENTER);
headline = new JLabel("This is a title");
mainText = new JLabel("<html><div style=\"line-height: 150%;width:100px \">This is some sort of text which is rather long and thus pretty boring to actually read. Thanks for reading anyway!</div></html>");
close = new JButton("X");
close.setBorderPainted(false);
close.setContentAreaFilled(false);
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
layoutPanel();
}
private void layoutPanel() {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.insets = new Insets(10, 10, 10, 10);
constraints.fill = GridBagConstraints.BOTH;
constraints.weightx = 1;
panel.add(headline, constraints);
constraints.gridx += 1;
constraints.weightx = 0;
constraints.fill = GridBagConstraints.NONE;
panel.add(close, constraints);
constraints.gridx = 0;
constraints.gridy += 1;
constraints.gridwidth = 2;
constraints.weightx = 1;
constraints.weighty = 1;
constraints.fill = GridBagConstraints.BOTH;
panel.add(mainText, constraints);
pack();
}
public static void main(String[] args) {
JFrame parent = new JFrame();
JPanel pane = new JPanel(new BorderLayout());
pane.add(new JTextArea("This is a text."));
parent.setContentPane(pane);
parent.setSize(400, 400);
parent.setLocation(400, 300);
parent.setDefaultCloseOperation(JDialog.EXIT_ON_CLOSE);
parent.setVisible(true);
JDialog dialog = new Java2DTransparencySSCE();
dialog.setLocation(500, 400);
dialog.setAlwaysOnTop(true);
dialog.setVisible(true);
}
}
编辑: 为了规避 Windows 上的这个特定错误,我添加了以下内容,这在我的用例中没有导致问题或性能下降(可能因您而异) ):
JDialog dialog = new Java2DTransparencySSCE() {
@Override
public void paint(Graphics g) {
g.setClip(null);
super.paint(g);
}
};
您应该在自定义绘图之前存储原始剪辑并在之后恢复。
Shape oldCLip=g2d.getClip();
...
your code
...
g2d.setClip(oldClip);
您应该在所有绘图代码之后调用 super.paintComponent(g)(这将是您覆盖的最后一行)。
这样您的绘图将位于子组件下方。
当鼠标悬停在 JButton
上时,面板仅根据需要重新绘制的边界重新绘制自身 - 按钮的边界(如果您选中 oldClip
,它应该是 JButton
).更改剪辑边界会导致 alpha 颜色成为每个先前绘制调用的合成,因为剪辑未被 super.paintComponent
清除并且 JDialog 的背景是完全透明的。
I want to create a dialog with a custom shape and transparency, think info bubble pointing to some component.
考虑使用轻量级组件方法 - 您可以通过设置包含对话框项的 JFrame 的玻璃面板,根据需要切换玻璃面板内容的可见性 and/or 来实现。
On Mac OS X 10.9, Java 8,我没有发现异常。我看到与下面的变体相同的外观,其中既没有创建也没有处置派生的图形上下文。 API 表明 "programmers should call dispose when finished using a Graphics
object only if it was created directly from a component or another Graphics object." 我不确定内部实现有何不同,但这可能是罪魁祸首。
@Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
Graphics2D gImg = (Graphics2D) g;
Shape oldClip= gImg.getClip();
Polygon shape = new Polygon(
new int[] { 0, 100, 100, 50, 0},
new int[] {200, 200, 100, 50, 100}, 5);
gImg.setClip(shape);
gImg.setColor(new Color(255, 0, 0, 50));
gImg.fill(shape);
gImg.setClip(oldClip);
}
我想创建一个具有自定义形状和透明度的对话框,想想指向某个组件的信息气泡。
为此,我将 JPanel
添加到 JDialog
并覆盖面板的 paintComponent(Graphics)
方法。面板本身包含常规 JLabels
和 JButtons
。
工作正常,但只要我在面板绘制代码中使用 Graphics2D.setClip(Shape)
,组件就会被背景透支。如果我不设置剪辑(设置为全新的 Graphics2D
对象,同样如此),一切正常。
这让我很困惑,我不知道我能做些什么来解决它。
P.S.: 我不能在 JDialog
上使用 setShape(Shape)
,因为那里没有抗锯齿功能。
P.P.S.: 实际用例是绘制一个大的背景图像,必须在信息气泡形状处精确切割。
下面的 SSCCE 演示了鼠标多次悬停在右上角的 'x' 上时出现的问题。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import java.awt.Polygon;
import java.awt.Shape;
public class Java2DTransparencySSCE extends JDialog {
JPanel panel;
private JButton close;
private JLabel headline;
private JLabel mainText;
public Java2DTransparencySSCE() {
setLayout(new BorderLayout());
setFocusable(false);
setFocusableWindowState(false);
setUndecorated(true);
setBackground(new Color(0, 0, 0, 0));
panel = new JPanel(new GridBagLayout()) {
@Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
Graphics2D gImg = (Graphics2D) g.create();
// if the line below is removed, everything works..
Shape oldClip= gImg.getClip();
// both the shape and a standard rectangular clip break everything
// gImg.setClip(50, 50, 50, 50);
Polygon shape = new Polygon(new int[] {0, 100, 100, 50, 0}, new int[] {200, 200, 100, 50, 100}, 5);
gImg.setClip(shape);
gImg.setColor(new Color(255, 0, 0, 50));
gImg.fill(shape);
gImg.setClip(oldClip);
gImg.dispose();
}
};
panel.setOpaque(false);
add(panel, BorderLayout.CENTER);
headline = new JLabel("This is a title");
mainText = new JLabel("<html><div style=\"line-height: 150%;width:100px \">This is some sort of text which is rather long and thus pretty boring to actually read. Thanks for reading anyway!</div></html>");
close = new JButton("X");
close.setBorderPainted(false);
close.setContentAreaFilled(false);
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
layoutPanel();
}
private void layoutPanel() {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.insets = new Insets(10, 10, 10, 10);
constraints.fill = GridBagConstraints.BOTH;
constraints.weightx = 1;
panel.add(headline, constraints);
constraints.gridx += 1;
constraints.weightx = 0;
constraints.fill = GridBagConstraints.NONE;
panel.add(close, constraints);
constraints.gridx = 0;
constraints.gridy += 1;
constraints.gridwidth = 2;
constraints.weightx = 1;
constraints.weighty = 1;
constraints.fill = GridBagConstraints.BOTH;
panel.add(mainText, constraints);
pack();
}
public static void main(String[] args) {
JFrame parent = new JFrame();
JPanel pane = new JPanel(new BorderLayout());
pane.add(new JTextArea("This is a text."));
parent.setContentPane(pane);
parent.setSize(400, 400);
parent.setLocation(400, 300);
parent.setDefaultCloseOperation(JDialog.EXIT_ON_CLOSE);
parent.setVisible(true);
JDialog dialog = new Java2DTransparencySSCE();
dialog.setLocation(500, 400);
dialog.setAlwaysOnTop(true);
dialog.setVisible(true);
}
}
编辑: 为了规避 Windows 上的这个特定错误,我添加了以下内容,这在我的用例中没有导致问题或性能下降(可能因您而异) ):
JDialog dialog = new Java2DTransparencySSCE() {
@Override
public void paint(Graphics g) {
g.setClip(null);
super.paint(g);
}
};
您应该在自定义绘图之前存储原始剪辑并在之后恢复。
Shape oldCLip=g2d.getClip();
...
your code
...
g2d.setClip(oldClip);
您应该在所有绘图代码之后调用 super.paintComponent(g)(这将是您覆盖的最后一行)。
这样您的绘图将位于子组件下方。
当鼠标悬停在 JButton
上时,面板仅根据需要重新绘制的边界重新绘制自身 - 按钮的边界(如果您选中 oldClip
,它应该是 JButton
).更改剪辑边界会导致 alpha 颜色成为每个先前绘制调用的合成,因为剪辑未被 super.paintComponent
清除并且 JDialog 的背景是完全透明的。
I want to create a dialog with a custom shape and transparency, think info bubble pointing to some component.
考虑使用轻量级组件方法 - 您可以通过设置包含对话框项的 JFrame 的玻璃面板,根据需要切换玻璃面板内容的可见性 and/or 来实现。
On Mac OS X 10.9, Java 8,我没有发现异常。我看到与下面的变体相同的外观,其中既没有创建也没有处置派生的图形上下文。 API 表明 "programmers should call dispose when finished using a Graphics
object only if it was created directly from a component or another Graphics object." 我不确定内部实现有何不同,但这可能是罪魁祸首。
@Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
Graphics2D gImg = (Graphics2D) g;
Shape oldClip= gImg.getClip();
Polygon shape = new Polygon(
new int[] { 0, 100, 100, 50, 0},
new int[] {200, 200, 100, 50, 100}, 5);
gImg.setClip(shape);
gImg.setColor(new Color(255, 0, 0, 50));
gImg.fill(shape);
gImg.setClip(oldClip);
}