I'm trying to do animation in Java with a translucent JFrame. I have modified the demo code from the Oracle Java Tutorials here. Specifically the GradientTranslucentWindowDemo.
The following code works great in Windows XP SP3 up to 8 and Mac OS X Mountain Lion and even in Linux mostly. The problem in Linux, and what I need help with, is the animation is flickering.
I'm running Ubuntu Linux 12.04 LTS 64bit with nVidia drivers, Metacity and Compiz. PERPIXEL_TRANSLUCENT reports true and is working well.
Is there something I'm missing in the following code or is there something on the Linux side I need to change? I tried setDoubleBuffered(true) on the JPanel, but it did not remove the flickering.
Please reference my code changes to the Demo below:
import static java.awt.GraphicsDevice.WindowTranslucency.PERPIXEL_TRANSLUCENT;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class GradientTranslucentWindowDemo extends JFrame implements ActionListener {
private Timer timer = new Timer(100, this);
private double percentage = 0.0;
private JPanel surface = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (g instanceof Graphics2D) {
final int R = 0;
final int G = 240;
final int B = 240;
Paint p =
new GradientPaint(0.0f, 0.0f, new Color(R, G, B, 0),
0.0f, getHeight(), new Color(R, G, B, 255), false);
Graphics2D g2d = (Graphics2D)g;
// CHANGE 1
// Clear the previous graphics using a completely transparent fill
g2d.setBackground(new Color(0, 0, 0, 0));
g2d.clearRect(0, 0, getWidth(), getHeight());
g2d.setPaint(p);
// CHANGE 2
// Only do a gradient fill for the current percentage of the width
g2d.fillRect(0, 0, (int)Math.ceil(getWidth() * percentage), getHeight());
}
}
};
public GradientTranslucentWindowDemo() {
super("GradientTranslucentWindow");
setBackground(new Color(0,0,0,0));
setSize(new Dimension(300,200));
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// CHANGE 3
// I thought this might remove the flicker, nope
this.surface.setDoubleBuffered(true);
// CHANGE 4
// This seems to be required or the g2d.clearRect doesn't do anything
this.surface.setOpaque(false);
setContentPane(this.surface);
setLayout(new GridBagLayout());
add(new JButton("I am a Button"));
}
// CHANGE 5
// On each tick of the timer increment the percentage until its
// more than one and always repaint
#Override
public void actionPerformed(ActionEvent event) {
this.percentage += 0.05;
if (this.percentage > 1.0) {
this.percentage = 0.0;
}
this.surface.repaint();
}
public static void main(String[] args) {
// Determine what the GraphicsDevice can support.
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
boolean isPerPixelTranslucencySupported =
gd.isWindowTranslucencySupported(PERPIXEL_TRANSLUCENT);
//If translucent windows aren't supported, exit.
if (!isPerPixelTranslucencySupported) {
System.out.println(
"Per-pixel translucency is not supported");
System.exit(0);
}
JFrame.setDefaultLookAndFeelDecorated(true);
// Create the GUI on the event-dispatching thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
GradientTranslucentWindowDemo gtw = new GradientTranslucentWindowDemo();
// Display the window.
gtw.setVisible(true);
// CHANGE 6
// Wait until the window is visible to start the timer
gtw.timer.start();
}
});
}
}
UPDATE 1:
Removing the translucency and choosing a black background fixes the flicker problem. The flicker is most certainly related to having a translucent window. I also noticed that the flicker becomes worse as the window is enlarged.
UPDATE 2:
The line this.surface.setOpaque(false); is what is causing the problem. If this is commented out, the animation does not flicker and there is translucency. However, upon each iteration of the animation it blends with the previous paint (its not clearing the contents before repainting). Doing g2d.setBackground(new Color(0, 0, 0, 0)); and g2d.clearRect(0, 0, getWidth(), getHeight()); does nothing unless this.surface.setOpaque(false); is set. It's almost like this line disables double buffering on linux.
Having a translucent window is a requirement.
Related
Here is a piece of code :
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class QuitButton extends JPanel implements ActionListener
{
static JButton button = new JButton("Panic");
Color[] colors = new Color[9];
boolean pressed = false;
public QuitButton()
{
button.addActionListener(this);
colors[0] = Color.RED;
colors[1] = Color.BLUE;
colors[2] = Color.GREEN;
colors[3] = Color.YELLOW;
colors[4] = Color.BLACK;
colors[5] = Color.PINK;
colors[6] = Color.MAGENTA;
colors[7] = Color.ORANGE;
colors[8] = Color.CYAN;
}
#Override
public void actionPerformed(ActionEvent e)
{
pressed = true;
}
public static void main(String args[])
{
JFrame frame = new JFrame("Do NOT Panic!!");
QuitButton qb = new QuitButton();
frame.add(qb);
frame.add(button);
frame.setLayout(new FlowLayout());
frame.setSize(400, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
//frame.pack();
button.requestFocus();
qb.gameLoop();
}
public void gameLoop()
{
while (true)
{
repaint();
try
{
Thread.sleep(200);
} catch (Exception e)
{
}
}
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
if (pressed == false)
{
super.paint(g2d);
g2d.setColor(Color.GRAY);
g2d.fillRect(0, 0, 400, 400);
} else
{
super.paint(g2d);
Random r = new Random();
int min = 0;
int max = 8;
int index = r.nextInt(max - min) + min;
g2d.setColor(colors[index]);
g2d.fillRect(0, 0, 400, 400);
}
}
The purpose of this program: The rectangle should be grey before but when I click the panic button colours should start changing.
Please don't get confused with the name of the class which is QuitButton.
But my rectangle is not occupying the entire window. Instead I am getting a teeny tiny rectangle like this : http://g.recordit.co/xJAMiQu6fM.gif
I think it is because of the layout I am using and I haven't specified anywhere that the button will be on top. Probably that's why they are coming side by side. I am new to GUI creation and thank you for your help.
You seem to be making some guesses on how to do this, which is not a good way to learn to use a library. Your first step should be to check the relevant tutorials on this, most of which will be found here: Swing Info Since this appears to be homework, I'm not going to give you a code solution but rather suggestions on how to improve:
Override paintComponent, not paint since the latter gives double buffering and is less risky (less painting of borders and child component problems)
In your paintComponent override, be sure to call the super's paintComponent method first to clear "dirty" pixels.
Use a Swing Timer, not a while loop for your game loop. This will prevent your while loop from freezing the Swing event thread, a problem that can freeze your program. Google the tutorial as it is quite helpful.
Do your randomization within the ActionListener's code (here likely the ActionListener for your Swing Timer), not within the painting code. The painting code should not change the state of the object but rather should only display the object's state.
FlowLayout will respect a component's preferredSize, and your component's preferred size is 0,0 or close to it. Change this. Best to override public Dimension getPreferredSize() and return a Dimension that matches your Rectangle's size.
Avoid using "magic" numbers, such as for your rectangle's size, and instead use constants or fields.
Call repaint() within your Timer's ActionListener so the JVM knows to paint the component.
As we are using JDesktopPane with JInternalFrames for our software, I am wondering if we can have Shortcut Icons (to particular Frame) placed inside the desktop pane (Similar to Windows desktop shortcuts). I searched for this but no luck.
Any idea guys to make this happen??
The following is not a "good" solution, but maybe OK-ish for certain application cases. The main difficult is that the central class that comes into play here is JInternalFrame.JDesktopIcon, and the documentation of this class contains a
Warning:
This API should NOT BE USED by Swing applications, as it will go away in future versions of Swing as its functionality is moved into JInternalFrame.
However, the corresponding functionality in JInternalFrame simply is not there. And although one has to accept that the JDesktopIcon class might be removed in future versions, it seems very unlikely for me, considering the broad usage of this class in the internal Swing UI implementations.
However: One option to achieve this is to create a custom extension of the BasicDesktopIconUI. Fortunately, this class handles most of the janitorial work, like dragging support and un-iconifying the frame on double clicks.
As a result, one can easily sneak custom icons into such an implementation (I only used a placeholder here: A red cross on a black background. But it can be an arbitrary Image.)
This is implemented here as a MCVE. The general UI handling may be different in an actual application, but the basic idea is to create the custom UI class and assign it to the internal frame icons.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicDesktopIconUI;
class SimpleDesktopIconUI extends BasicDesktopIconUI
{
private final Icon icon;
SimpleDesktopIconUI(Icon icon)
{
this.icon = icon;
}
#Override
protected void installComponents()
{
frame = desktopIcon.getInternalFrame();
String title = frame.getTitle();
JLabel label = new JLabel(title, icon, SwingConstants.CENTER);
label.setVerticalTextPosition(JLabel.BOTTOM);
label.setHorizontalTextPosition(JLabel.CENTER);
desktopIcon.setBorder(null);
desktopIcon.setOpaque(false);
desktopIcon.setLayout(new GridLayout(1, 1));
desktopIcon.add(label);
}
#Override
protected void uninstallComponents()
{
desktopIcon.setLayout(null);
desktopIcon.removeAll();
frame = null;
}
#Override
public Dimension getMinimumSize(JComponent c)
{
LayoutManager layout = desktopIcon.getLayout();
Dimension size = layout.minimumLayoutSize(desktopIcon);
return new Dimension(size.width + 15, size.height + 15);
}
#Override
public Dimension getPreferredSize(JComponent c)
{
return getMinimumSize(c);
}
#Override
public Dimension getMaximumSize(JComponent c)
{
return getMinimumSize(c);
}
}
public class InternalFrameIconTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Icon icon = new ImageIcon(createImage());
JDesktopPane desktopPane = new JDesktopPane();
for (int i = 0; i < 5; i++)
{
String title = "Test " + i;
if (i == 2)
{
title = "Test 2 with longer title";
}
JInternalFrame internalFrame =
new JInternalFrame(title, true, true, true, true);
internalFrame.setBounds(20 + 50 * i, 300 - 40 * i, 160, 80);
internalFrame.setVisible(true);
desktopPane.add(internalFrame);
internalFrame.getDesktopIcon().setUI(new SimpleDesktopIconUI(icon));
}
f.getContentPane().add(desktopPane);
f.setSize(600, 600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static BufferedImage createImage()
{
int w = 50;
int h = 50;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.RED);
g.drawLine(0, 0, w, h);
g.drawLine(0, h, w, 0);
g.dispose();
return image;
}
}
I was writing a program with some custom rendering, and needed to render a rectangle with a border. I decided to simply call graphics2D.fillRect(), switch to the border color, and call graphics2D.drawRect(). However, even though I make these calls back to back with the same coordinates and sizes, fillRect() does not always fill in the entire area contained by drawRect when the color I'm drawing with is translucent (has alpha). Furthermore, the area painted by fillRect() is sometimes outside of the area contained by drawRect(). Why do these two methods draw things in different places when given different colors?
Here is an example to demonstrate the problem. Clicking the mouse in the window will switch between drawing the fill with alpha and without. Notice that there is a row of pixels at the bottom of the rectangle that is white when drawing with alpha, but that row of pixels is not there when drawing without alpha.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ColorWithAlpha extends JPanel {
private boolean hasAlpha = true;
private static final long serialVersionUID = 1L;
/**
* #param args
*/
public static void main(String[] args) {
// setup a basic frame with a ColorWithAlpha in it
JFrame frame = new JFrame();
JPanel panel = new ColorWithAlpha();
panel.setPreferredSize(new Dimension(500, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.show();
}
public ColorWithAlpha() {
super();
setBackground(Color.WHITE);
this.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent arg0) {
// when the user clicks their mouse, toggle whether we are drawing a color with alhpa or without.
hasAlpha = !hasAlpha;
ColorWithAlpha.this.repaint();
}
#Override
public void mouseEntered(MouseEvent arg0) {}
#Override
public void mouseExited(MouseEvent arg0) {}
#Override
public void mousePressed(MouseEvent arg0) {}
#Override
public void mouseReleased(MouseEvent arg0) {}
});
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Color color = new Color(100, 100, 250);// this color doesnt have an alpha component
// some coordinates that demonstrate the bug. Not all combinations of x,y,width,height will show the bug
int x = -900;
int y = 1557;
int height = 503;
int width = 502;
if (hasAlpha) { // toggle between drawing with alpha and without
color = new Color(200, 100, 250, 100);
}
Graphics2D g2 = (Graphics2D) g;
// this is the transform I was using when I found the bug.
g2.setTransform(new AffineTransform(0.160642570281124, 0.0, 0.0, -0.160642570281124, 250.0, 488.0));
g2.setColor(color);
g2.fillRect(x, y, width, height);
g2.setColor(Color.DARK_GRAY);
g2.setStroke(new BasicStroke(8f));
g2.drawRect(x, y, width, height);
}
}
Scrap that answer, I reread your question and copied your code and found what your talking about. The small white line is due to a round-off error in the painting. Very interesting little problem. Add this after creating your Graphics2D
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
Rendering hints tell the painting class how you want certain procedures to work. I have no idea why adding transparency to the color would make the roundoff different. I figure it must have to do with multiple rendering hints combining together like antialiasing.
Edit:
I submitted a bug for the below (it may take a a few days to become approved though):
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7043319
Some more details:
It works with Windows Sun JDK 1.6 versions 13 and 17
It fails on Ubuntu 11.04 x64 with both OpenJDK 1.6.0_22 and Sun JDK 1.6.0_24
What I want is to make a background image panel with additional panels on top of it (with additional components - e.g. JButtons, custom shapes, etc. - in them) and draw all that correctly. I'm using JLayeredPane for that purpose in my app, but for the sake of an example the below code should suffice. I'm open to suggestions about how to do what I want regardless of the below problem.
I'm running into the issue that the painting is behaving really weird. It doesn't repaint fully (e.g. only the top part above the image), it repaints in - from what I've noticed increasingly spaced - steps (e.g. 1st paint, 3rd paint, 9th paint, 21st paint, 64th paint, etc.). My guess is that I'm going too much into implementation here - is there anything obviously wrong with the below?
On a separate note, there are three commented lines below. Interestingly enough, uncommenting any of them and commenting the following line solves the problem. The images are with the following attributes (and it seems it doesn't matter which image - just the size):
cat.jpg JPEG 640x533 640x533+0+0 8-bit DirectClass 110KB 0.000u 0:00.000
cat-small.jpg JPEG 200x167 200x167+0+0 8-bit DirectClass 7.99KB 0.000u 0:00.000
Here's the Java code I'm having issues with:
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class SwingDrawingPrb {
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
final JFrame frame = new JFrame("SwingDrawingPrb");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Container contentPane = frame.getContentPane();
frame.setLocation(550, 50);
frame.setSize(1000, 800);
frame.setVisible(true);
// ImageIcon image = new ImageIcon(SwingDrawingPrb.class.getResource("/cat-small.jpg"));
ImageIcon image = new ImageIcon(SwingDrawingPrb.class.getResource("/cat.jpg"));
final JPanel imagePanel = new JPanel() {
// Color trans = new Color(255, 0, 0, 255);
Color trans = new Color(255, 0, 0, 64);
protected void paintComponent(Graphics g) {
System.out.println("painting");
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(trans);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.blue);
g.drawLine(0, 0, 1000, 1000);
}
};
imagePanel.setBounds(0, 0, image.getIconWidth() + 200, image.getIconHeight() + 200);
imagePanel.setLayout(null);
// JLabel imageLabel = new JLabel("Hello, world!");
JLabel imageLabel = new JLabel(image);
imageLabel.setBounds(100, 100, image.getIconWidth(), image.getIconHeight());
imageLabel.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
System.out.println("mouseMoved");
imagePanel.repaint();
}
});
imagePanel.add(imageLabel);
contentPane.add(imagePanel);
}
}
You need to add:
imagePanel.setOpaque(false);
See Backgrounds With Transparency for more information.
According to the Javadoc, JComponent.repaint(long) is supposed to schedule a repaint() sometime in the future. When I try using it it always triggers an immediate repaint. What am I doing wrong?
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Repaint
{
public static final boolean works = false;
private static class CustomComponent extends JPanel
{
private float alpha = 0;
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setComposite(
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.setPaint(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
alpha += 0.1;
if (alpha > 1)
alpha = 1;
System.out.println("alpha=" + alpha);
if (!works)
repaint(1000);
}
}
public static void main(String[] args)
{
final JFrame frame = new JFrame();
frame.getContentPane().add(new CustomComponent());
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
if (works)
{
new Timer(1000, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
frame.repaint();
}
}).start();
}
}
}
Note that the Javadoc says the method will cause a repaint to happen within (not after) the specified time.
If you want to schedule something to be repainted, then you should be using a Swing Timer. You should not be scheduling painting from withing the paintComponnt(..) method. You can't control when the paintComponent() method is called.
The parameter says tm - maximum time in milliseconds before update it does not say it won't do so immediately also the javadocs say
Repaints the component. If this
component is a lightweight component,
this results in a call to paint
within tm milliseconds.
If you search a little bit you find that this parameter is ignored in derived classes. ;)