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.
Related
My application is a simple game of Brick Breaker. In order to paint the visuals of the application I'm using the paintComponent method. The application also has several buttons that are added using the following code:
levelMenu = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double scale = screenSize.getHeight()/1080;
Graphics2D g2d = (Graphics2D) g;
g2d.scale(scale, scale);
g2d.drawImage(background, 0, 0, null);
}
};
levelMenu.setLayout(new FlowLayout());
JPanel subPanel = new JPanel(new GridLayout(20, 2, 10, 10));
subPanel.setBackground(Constants.CLEAR);
subPanel.add(new JLabel());
subPanel.add(new JLabel());
for (JButton level: levelList) {
subPanel.add(level);
}
subPanel.add(new JLabel());
subPanel.add(back);
levelMenu.add(subPanel);
this.add(levelMenu);
The issue Im having is that the buttons are being added, but also they seem to be painted in the background image of the application:
The buttons on the right dont work, and are just images. Any clue how to fix this issue.
The primary issue comes down to...
Graphics2D g2d = (Graphics2D) g;
g2d.scale(scale, scale);
g2d.drawImage(background, 0, 0, null);
The Graphics context passed to the paintComponent method is a shared resource, all the components rendered within the paint pass will use it. This means that any changes you make to it will also affect them. You should be especially aware of transformations (like translate and scale).
A general practice is to make a snapshot of the state of Graphics context before you use it, this allows you to make changes to the copy with affecting the original, something like...
Graphics2D g2d = (Graphics2D) g.create();
g2d.scale(scale, scale);
g2d.drawImage(background, 0, 0, null);
g2d.dispose();
The other issue is subPanel.setBackground(Constants.CLEAR);. I'm assuming that this is a alpha based color. Swing component's don't support alpha based colors, they are either fully opaque or fully transparent (although you can fake it). This is controlled through the use of setOpaque
Now, I strongly recommend that you stop and go have a read through:
Performing Custom Painting
Painting in AWT and Swing
Use below code snippet for your reference:
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicPanelUI;
class MyPanelUI extends BasicPanelUI {
public void paint(Graphics g, JComponent c) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Graphics2D g2d = (Graphics2D) g;
Image img = toolkit.getImage("/usr/share/backgrounds/warty-final-ubuntu.png");
g2d.drawImage(img, 0, 0, null);
}
public Dimension getPreferredSize(JComponent c) {
return super.getPreferredSize(c);
}
}
public class PanelBGTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setUI(new MyPanelUI());
panel.setLayout(new FlowLayout());
panel.add(new JButton("This is button"));
SwingUtilities.updateComponentTreeUI(frame);
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setVisible(true);
}
}
using paint component is not good practice so its always better to extent your component UI class from basic component UI class and override the paint() method. This way swing will take care of all the rendering / re-rendering part and your component(s) added to panel will be visible too.
Now, I strongly recommend that you stop and go have a read through:
Performing Custom Painting
Painting in AWT and Swing
BufferStrategy and BufferCapabilities
How to Use Layered Panes
I've been trying to get the Graphics2d object to work without success. I've searched for an answer on both the Oracle tutorial site and Stackoverflow without finding an answer.
The problem I have is that when I call the methods lineTo, fill, and drawRect, I get a blank grey square in my window, instead of the shapes that I want.
package main;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GraphicsTesting extends JPanel {
private static final long serialVersionUID = 6096199371167913312L;
static BufferedImage buffImag = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
static Graphics2D graff = buffImag.createGraphics();
Point2D.Double point = new Point2D.Double(10, 10);
static Graphics gra = buffImag.createGraphics();
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D) g;
GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 4);
gp.moveTo(30, 55);
gp.lineTo(168, 384);
gp.lineTo(462, 81);
gp.lineTo(321, 423);
gp.lineTo(269, 243);
g2.setColor(new Color(112, 150, 134));
g2.fill(gp);
g2.setColor(new Color(56, 112, 232));
g2.draw(gp);
g2.setColor(new Color(152, 1, 210));
g2.drawRect(25, 152, 380, 405);
g2.drawImage(buffImag, 0, 0, 500, 0, 0, 500, 500, 500, null);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500,500);
frame.setVisible(true);
GraphicsTesting gT = new GraphicsTesting();
frame.setContentPane(gT);
gT.paint(gra);
}
}
Your code seems fine and I tried to run it on my machine. It produces what you can see on the following screen shot. I think this is what you expect to get, right? Your problem might be coming from a faulty Java installation or an os-related issue. Which virtual machine are you using and on which operating system?
As a side note, your code is not complete though, as the following import is missing
import java.awt.geom.Point2D;
There are several issues with your code, but the main one causing your problem is that you are making the frame visible before adding the panel to it.
Move your setVisible(true) line to here:
frame.setContentPane(gT);
frame.setVisible(true);
gT.paint(gra);
I was trying to use XOR mode in Graphics to draw a 1bit texture in color against a flat background, when I encountered a behaviour in Graphics I don't understand.
Here is an example of what I mean, isolated:
package teststuff;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class XORTest extends JFrame {
public XORTest() {
super("Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500, 400);
setIgnoreRepaint(true);
setResizable(false);
setVisible(true);
createBufferStrategy(2);
Graphics graphics = getBufferStrategy().getDrawGraphics();
graphics.setColor(Color.black);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.setColor(Color.green);
graphics.fillRect(30, 40, 100, 200);
graphics.setXORMode(Color.white); // (*)
graphics.fillRect(60, 80, 100, 200);
graphics.dispose();
getBufferStrategy().show();
}
public static void main(String[] args) {
XORTest test = new XORTest();
}
}
If I uncomment the line marked with (*), two green rectangles are drawn as expected. If I leave it, nothing is drawn into the component, not even the black background or green rectangle that is drawn beforehand. Even more odd, it worked once. I had the color as Color.green instead of white before. After I changed it, it drew as expected. But when I closed the application and started it again, it didn't work anymore and it hasn't since.
Is this a bug in java? In my jre? Undocumented behaviour for Graphics? I'm on Windows and running the example on the jdk7.
Screenshots: Imgur album because it won't let me post 3 links
The third screenshot is the code as it is above, the first with (*) commented and the second is how it looked the one time it worked (I created that in GIMP because I didn't take a screenshot then).
Without a compelling reason to the contrary, it's easier and more reliable to override paintComponent() in JPanel, which is double buffered by default. With a compelling reason, follow the guidelines in BufferStrategy and BufferCapabilities. Also note,
Override getPreferredSize() to specify the preferred size of a component.
Swing GUI objects should be constructed and manipulated only on the event dispatch thread.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/16721780/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new XORPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static class XORPanel extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(190, 320);
}
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
graphics.setColor(Color.black);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.setColor(Color.green);
graphics.fillRect(30, 40, 100, 200);
graphics.setXORMode(Color.white);
graphics.fillRect(60, 80, 100, 200);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}
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.
I have three questions/problems. (NOTE - I don't have enough reputation to post pics, so I linked them. And I needed to obfuscate them...)
1) I created a panel that holds my game graphics (the player area). The panel is supposed to be 800x800 and clip everything that lies below and to the right. But when I add the graphics panel to a JFrame, it doesn't clip. So images go over on the right and left.
This is a picture of how to game starts. Ideally, the graphics would start in this rectangle the whole time:
Picture #1: http://i.stack.imgur.com/idL8f.png
Now, here's what happens when I press play to start.
Picture #2: http://i.stack.imgur.com/dxtbe.png
How can I set the panel/frame so that the graphics only occupy 800x800 (like the first picture) and everything else is clipped?
2) I'm a bit confused about how I can set up the JFrame. This is how I want it to be layed out:
Picture #3: http://i.stack.imgur.com/ZyJS5.png
How would you lay the JFrame/Panels out? I was thinking BorderLayout, but I'm not certain it would work out.
3) For this game, my class that extends JFrame also contains main(). Is this bad practice?** Are you supposed to not extend JFrame on the main class?
The easiest way to get an 800x800 panel is to use setPreferredSize() and then pack() the JFrame that contains is. Conveniently, pack() "Causes this Window to be sized to fit the preferred size and layouts of its subcomponents."
2). See A Visual Guide to Layout Managers for layout suggestions. You can use nested panels to achieve your desired layout.
3). There's nothing wrong with extending JFrame, but there's little point unless you are modifying the behavior of JFrame. In contrast, JPanel is a convenient container for grouping components; it was designed to be extended. You might examine this example in that regard.
Addendum:
I don't want the panel to show anything but the 800 pixels in the x and y direction.
You can override paintComponent() and copy whatever portion of the image is desired. In the example below, g.drawImage(img, 0, 0, null) draws the top-left 800 pixels of the image, while g.drawImage(img, 0, 0, getWidth(), getHeight(), null) scales the image the panel's size. Note that f.setResizable(false) prevents changing the window's size.
Addendum: You can also copy arbitrary portions of the source image to arbitrary areas of the
the destination panel, as shown below. Also consider overriding getPreferredSize(), as suggested here.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
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;
/** #see http://stackoverflow.com/q/3851847 */
public class MyPanel extends JPanel {
private BufferedImage img;
public MyPanel() {
this.setPreferredSize(new Dimension(800, 800));
try {
img = ImageIO.read(new File("../scratch/image.png"));
} catch (IOException ex) {
ex.printStackTrace(System.err);
}
}
#Override
protected void paintComponent(Graphics g) {
// g.drawImage(img, 0, 0, 800, 800, null);
// g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
g.drawImage(img, 0, 0, 800, 800, 0, 0, 800, 800, this);
}
private void display() {
JFrame f = new JFrame("MyPanel");
// f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MyPanel().display();
}
});
}
}