Loading bufferedImage disables JPanel's paintCompontent method - java

I have a slight problem with BufferedImage and JPanel. I am creating a game with some 2d animation.
Basically i have an animationHandler that will loop through the pictures and depending on the rotation will also display it correctly. But the problem is - when I load in the pictures my Jpanel wont draw anything. It doesnt event matter if I comment out the custom paint methods - the paintComponent method wont draw anything and it seems like it skips the paintCompontent method. Even though the game doesnt crash and the timer still is running - it wont use the paintComponent method in an extended JPanel.
The class that contains the timer - calls the JPanel throught JPanel.repaint();
Here is the loadImg method
/**
* Test method to check animationHandler and bufferedImgs
*/
private void loadImages() {
BufferedImage b_1;
BufferedImage b_2;
BufferedImage b_3;
BufferedImage b_4;
BufferedImage b_5;
BufferedImage[] imgs = new BufferedImage[5];
try {
b_1 = ImageIO.read(new File("warlock1.png"));
b_2 = ImageIO.read(new File("warlock2.png"));
b_3 = ImageIO.read(new File("warlock3.png"));
b_4 = ImageIO.read(new File("warlock4.png"));
b_5 = ImageIO.read(new File("warlock5.png"));
imgs[0] = b_1;
imgs[1] = b_2;
imgs[2] = b_3;
imgs[3] = b_4;
imgs[4] = b_5;
animationHandler.addAnimation(imgs);
} catch (Exception e) {
e.printStackTrace();
}
}
Cheers!

You're could possibly be loading images on the Swing event thread or EDT (Event Dispatch Thread), and since the EDT is responsible for all Swing graphics and user interactions, this will freeze your Swing application until the loading is complete. The solution: load the images on a background thread such as can be obtained from a SwingWorker object. Please check out the Concurrency in Swing tutorial for more on this subject.
Also, if possible, and if the images aren't too large, it is usually best to load the images once and then hold a reference to them or to an ImageIcon.
And finally, whatever you do, don't load images inside of the paintComponent(...) method. This method must be lean and mean -- as fast as possible, and it should concern itself only with painting and nothing else. Else your program's responsiveness could become pitifully slow.
Also, regarding:
it wont use the paintComponent method in an extended JPanel.
You might want to show us this code.

Related

How can I load images in the background in a Swing application?

I've got a simple Swing/AWT application that runs in full screen mode on Windows. I have a couple of different PNG files that it loads as its own background image depending on context.
It loads them like this:
BufferedImage bufferedImage;
bufferedImage = ImageIO.read(getClass().getResource("/bg1.png"));
Image bgImage1 = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
bufferedImage = ImageIO.read(getClass().getResource("/bg2.png"));
Image bgImage2 = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
bufferedImage = ImageIO.read(getClass().getResource("/bg3.png"));
Image bgImage3 = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
And later draws them like this:
window.repaint();
graphics.drawImage(bgImage1, 0, 0, null);
// draw some other stuff too, like text
And just to be thorough, window is a JWindow variable, and graphics is a Graphics2D variable.
The problem I'm running into happens when I switch out one of the background images for another. The first time I do the switch, calling something like this:
window.repaint();
graphics.drawImage(bgImage2, 0, 0, null);
// draw some other stuff too, like text
...the entire screen goes white for about a second. And then it does successfully show the image, but that flicker is really annoying. My guess is that because the images are relatively large and high resolution (2560x1440), it needs about a second to load them and scale them to the appropriate size.
How can I get it to load those images silently? As in... how do I avoid drawing a blank white screen for a second, that first time it displays a new background image? All subsequent times are already instantaneous, probably because it's truly grabbed them into memory at that point. But simply calling getScaledInstance apparently isn't enough to put things into memory, because it doesn't actually flicker until I call drawImage down the line.
ImageIcon will load in the background as a feature.
You can also accomplish this fairly easily with a background thread e.g.:
final String path = "/example.png";
new SwingWorker<BufferedImage, Void>() {
#Override
public BufferedImage doInBackground() throws IOException {
return ImageIO.read(ClassName.class.getResource(path));
}
#Override
public void done() {
try {
BufferedImage img = get();
// put img somewhere
} catch(InterruptedException ignored) {
} catch(ExecutionException ex) {
ex.printStackTrace(System.err);
}
}
}.execute();
Also,
window.repaint();
graphics.drawImage(bgImage2, 0, 0, null);
This worries me a little bit. In general we do not need to ask for a repaint of an entire top-level container. You should also not be using getGraphics() to paint.
Non-top-level Swing components are double-buffered but if you are painting outside of the paint structure you do not get this. It will result in flickering.
Two good sources for correct custom painting are:
Lesson: Performing Custom Painting
Painting in AWT and Swing
Painting should be done passively by overriding paintComponent on a JComponent. A JLabel can also display an ImageIcon as a feature so you do not necessarily have to do custom painting if you just want to display an image.
I notice that the scaling takes a lot of time, not the loading of the images.
Because of this, I usually store a scaled image so I can use it later on so I only have to scale once.

Drawing to canvas

so i am dabbling in making a game and i am a fairly messy worker and then i go back and tidy it up. So please ignore any stupid naming conventions or methods or anything. As I go back later and tidy up.
So basically I am trying to make a main menu, i just want to draw something to the screen and have it stay there. I am writing the graphics to a image ATM and displaying the image as i tried just drawing to the canvas and it just flickered up onto the screen and then it went blank. I changed it to a runnable so i could put in sleep() to try and error hunt. I've removed all my error hunting code now. It turns out when i have a sleep() before or after the image is put on the screen for longer than 50ms the rectangle stays on screen and doesn't flicker away like 1ms after it was drawn.
I'm not sure what's going on. I am probably not that clear and I apologies.
import java.awt.Canvas;
import java.awt.Graphics;
import javax.swing.JFrame;
public class test extends Thread{
public static void main(String[] args){
test t = new test();
t.start();
}
#Override
public void run() {
JFrame f = new JFrame("Test");
Canvas c = new Canvas();
Graphics g;
c.setSize(400, 400);
f.add(c);
f.pack();
f.setVisible(true);
c.setVisible(true);
try {
sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
g = c.getGraphics();
g.drawRect(10, 10, 40, 68);
}
}
Basically, this is not how painting in AWT works. Painting should be done within the context of a component's paint method. In AWT Canvas seems to be the preferred base component to use.
You should never use getGraphics, apart from been able to return null, it will only represent the Graphics context from the last paint cycle, anything added to it could be wiped about by newer paint events...which is probably happening to you.
Take a look at Painting in AWT and Swing for details about how painting works.
Having said all that, I would discourage you from using AWT based API as it was replaced by Swing some 15 years ago.
Take a look at Performing Custom Painting for more details about painting in Swing
You're kind of getting the idea, but there are a few standard design patterns of creating a game, for starters check out this code.
Basically, you'll need something called a game loop which will update and render to the display a certain amount of times every second (framerate or fps). When it renders, it will clear and redraw the image in a new location at a certain interval, if your fps was 60 you would be rendering (clearing, and redrawing) 60 times a second. We can also introduce more advanced concepts such as buffer strategies (shown in the example above) to reduce tearing and flickering.
To sum it up, you're kind of there, you just need to constantly do that g = c.getGraphics() so...
while (true) {
g = c.getGraphics();
// set color
// draw a rectangle
g.dispose();
}
Note how I added the dispose, this will just free up any unused memory. Just to clarify, this is by no means good code, but it will give you a place to start :)
The flickering is due to only having 1 screen, to stop this flickering you need a buffer strategy. Basically having two buffers is like having two screens, whilst you render on one 'screen' you can clear and draw the next step on the second screen and switch between the two buffers; this reduces the flickering.

saving bufferedimage to gif

I have an application that plots images on a JPanel. I want to dispose of the GUI altogether, so that the application is called from code only, with no user interface, and so that the images are saved to a gif file only.
I have experimented with code from this question, and it seems to work fine when I call the save gif code from within the old GUI. However, the save gif code is not seeming to work when I remove the GUI, and I am wondering if that might be because the JPanel it is trying to print has not actually made it to a GUI container like a JFrame, etc.
The reason I am not posting code is that it is too verbose, an I am just asking for a simple, boilerplate answer.
I have experimented with writeablerasters for other applications. But that would require a major recoding project for this application, which paints by using the graphics.drawLine() method. Can anyone suggest a code-efficient way to take contents of a "phantom" JPanel, get it into a BufferedImage, and then save it as a gif without ever putting the JPanel in a GUI?
Since your application draws on a JPanel then it just needs a Graphics object. You can use one from a BufferedImage which then you will save to file. Example code:
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
// Pass this graphics object to your application to perform the drawing
g.setColor(Color.red);
g.drawRoundRect(10, 10, 20, 20, 5, 5);
// save it to disk
ImageIO.write(image, "png", new File("test.png"));
This is difficult to answer without any example code, however.
If the component you are trying to render is not displayed on the screen, it is likely that it has not being laid out.
Before painting, you should make sure you size the component.
componentToBePainted.setSize(componentToBePainted.getPreferredSize());
You should also be using print or printAll over paint. When not attached to a native peer, paint can cause issue

Random numbers in an applet displayed at runtime update each frame, or get overdrawn by background images

Edit: uses awt...
I am trying to display random numbers in an applet based maths game, and have run into an issue. Either one of two things happen depending on where I call the method:
The numbers generate and display properly, without automatically updating each frame but get drawn over by the background and artwork each frame as they're being updated at runtime, or...
The number displayed on-screen appear above the background elements but are redrawn fresh every frame.
Quick illustration:
private int setNumberOne() {
return rand.nextInt(11) + 2;
}
private int setNumberTwo() {
return rand.nextInt(11) + 2;
}
private int setAnswer() {
return setNumberTwo() * setNumberOne();
}
private void displayOutput() {
Graphics2D g2d = graphics();
int one = setNumberOne();
int ans = setAnswer();
setNumberTwo();
g2d.setColor(Color.WHITE);
g2d.drawString(one + " x ? = " + ans, 480, 480);
}
Calling this function in the initialisation method displays a static question that I can update elsewhere when specific triggers are met, however everything else gets drawn on top of it by the update event, rendering it invisible. The only way I've managed to see this working is to remove the other images from the game for testing.
Is there a way to set the "precedence" of GUI elements in an applet?
I'm currently looking at attempting to include the displayOutput() method in it's own thread, but I'm not so experienced with Java and its proving very hard to resolve.
I also tried to not allow the background to update at runtime, but the result was that the moving game objects left trails all over the screen.
What am I missing? If anyone has any suggestions as to how this can be correctly implemented, I'd be delighted to hear them.
update: "...draw in a [...] Component's paint(...) method if AWT, and use the Graphics object passed in by the JVM" -
Resolved, Thank you!
Don't use getGraphics() for your Graphics component as this will return a non-persisting object that will get drawn over with the next repaint. Instead draw in a JComponent's paintComponent(...) method if this is a Swing app (you don't say) or a Component's paint(...) method if AWT, and use the Graphics object passed in by the JVM. Mostly read the tutorials on how to draw with Java as this has been well explained there.

How to wait until JComponent is fully painted?

I need a way to wait until a (Swing) JComponent is fully painted. This actual problem arises from an openmap application: The task is to draw a map (mapBean) with a couple of layers and create an image from that map.
Unfortunatly, and it's clearly documented, the image formatter takes the current state from the map to create the picture, and there's a chance, especially when maps become complex, that the formatter ist called before the mapBean, a JComponent, is painted.
Although explained with this openmap application, the problem is quite general and supposedly Swing related. Right now, I just wait a fixed time (one second) but that does not eliminate the risk of creating incomplete maps...
Edit
Some more details - I have to start with constructing a (OpenMap) MapPanel, which internallz creates a MapBean (JComponent subclass) and a MapHandler. Then I feed the MapHandler with geographical Layers and the Framework starts painting the geographical data 'on' the JComponent type MapBean.
After adding all layers to the Map, I use another framework class to create a JPG image (or: the byte[] that holds the image data). And this can cause problem, if I don't wait: this 'image creator' creates the image from the current state of the map bean, and if I call this 'image creator' to early, some map layers are not painted and missing. Pretty annoying...
java.awt.EventQueue.invokeLater will allow you to run a task after the paint operation has finished. If it is doing some kind of asynchronous load, then it will be API specific (as MediaTracker does for Image).
You might also try using an off screen buffer to render your image in:
MapBean map = new MapBean();
map.setData(...);
BufferedImage bi = new BufferedImage(...);
map.paintComponent(bi.getGraphics());
writeToFile(bi);
It sounds like you need to synchronize updating your JComponent's underlying data with Swing's paint cycle. You can subclass your JComponent and decorate the paintComponent() method, you might also look at ImageObserver.imageUpdate(), though I'm not sure if that is going to tell you what you want.
public class DrawingCycleAwareComponent extends MyOtherJComponent {
private final Object m_synchLock = new Object();
protected void paintComponent(Graphics g) {
synchronized (m_synchLock) {
super.paintComponent(g);
}
}
protected void updateData(Object data) {
if (SwingUtilities.isEventDispatchThread()) {
reallySetMyData(data); // don't deadlock yourself if running in swing
}
else {
synchronized (m_synchLock) {
reallySetMyData(data);
}
}
}
}

Categories

Resources