I am trying to prevent redrawing an animation by the EDT. The first thing i have done is excluding the actual drawing tasks into a different thread, writing into a VolatileImage, which gets redrawn by EDT within paintComponent method of my corresponding JPanel.
If i exclude the repaint into another thread, this works properly. Nevertheless, i do have positioned a couple of other panels above my animation.
In result, having called the repaint method of my painter (animation) panel, has caused the others to also get redrawn without flickering.
Therefore to redraw the other panels, calling repaint on painter, results in flickering. Repaint of a single panel results in an opaque redraw with rarely flickering.
Does somebody know, how to synchronize an own repaint of a jpanel, for instance into my already available bufferimage. Id say the repaint triggered to EDT results in flickering, since its not synchronized.
My repaint call to animation
#Override
public void KeyframeChanged(Keyframe frame) {
if (painter.isVisible()) {
map.getMainMap().doPaintComponent(painter.getBuffer().getGraphics());
painter.renderAnimation();
painter.updateScreen();
}
}
painter methods:
public void updateScreen() {
Graphics g = this.getGraphics();
if (g != null) // component already visible?
{
// is there a backBuffer to draw?
if (backBuffer != null) {
g.drawImage(backBuffer, 0, 0, null);
} else {
// if not, create one and render on it
createBackBuffer();
renderAnimation();
}
}
}
public void renderAnimation() {
// Do drawing stuff here
}
#Override
protected void paintComponent(Graphics g) {
// super.paintComponent(g);
}// end of paint
Thanks
Thanks for answers and links. I still need to read a few of them. Nevertheless in order to illustrate the current behavior, this small SSCCE shall help.
package repaintexample;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.VolatileImage;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
class Painter extends JPanel {
private VolatileImage backBuffer;
private Graphics2D g2d;
public Painter() {
setDoubleBuffered(false);
setOpaque(false);
}
#Override
public boolean isOptimizedDrawingEnabled() {
return false;
}
private void createBackBuffer() {
backBuffer = this.getGraphicsConfiguration().createCompatibleVolatileImage(1920, 1200);
}
public void adjustBackBufferSize() {
if (backBuffer != null) {
if (getWidth() > backBuffer.getWidth() || getHeight() > backBuffer.getHeight()) {
createBackBuffer();
}
}
}
public void updateScreen(Graphics g) {
if (g != null) // component already visible?
{
// is there a backBuffer to draw?
if (backBuffer != null) {
g.drawImage(backBuffer, 0, 0, null);
} else {
// if not, create one and render on it
createBackBuffer();
}
}
}
public void renderAnimation(int i, int j) {
if (backBuffer == null) {
createBackBuffer();
}
do {
if (backBuffer.validate(getGraphicsConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE) {
createBackBuffer();
}
g2d = (Graphics2D) backBuffer.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.white);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setColor(Color.red);
g2d.fillOval(i, j, 50, 50);
} while (backBuffer.contentsLost());
}
#Override
protected void paintComponent(Graphics g) {
// super.paintComponent(g);
updateScreen(g);
}// end of paint
public VolatileImage getBuffer() {
return backBuffer;
}
}
class ContainerFrame extends JFrame {
private Painter mapPainter;
private JPanel informationPanel; // covers a lot of metainformation - Actually own JTable instance updating the same objects for repainting in each circle
private JPanel controller; // Maptools
private JPanel tabbedPabe; // Change redraw content
public ContainerFrame() {
this.setSize(1600, 1024);
this.setVisible(true);
initComponents();
initPositions();
Thread animation = new Thread(new Runnable() {
#Override
public void run() {
// My application is a mapping application, in which i first draw the tiles, before goin on with the "real" animated stuff
// clearing backbuffer content with g.fillRect(0, 0, getWidth(), getHeight());
while (true) {
for (int i = 0; i < mapPainter.getWidth(); i += 100) {
for (int j = 0; j < mapPainter.getHeight(); j += 5) {
mapPainter.renderAnimation(i, j);
int repaintCase = 2;
switch (repaintCase) {
case 0:
// Default case redrawing via EDT, triggering the others in proper order
mapPainter.repaint();
break;
case 1:
// case repainting by current Thread - necessity of repainting above positioned panels
// results in flickering, since not synchronized
mapPainter.updateScreen(mapPainter.getGraphics());
informationPanel.repaint();
controller.repaint();
tabbedPabe.repaint();
break;
case 2:
// painting components on buffer
// Results in rarely flickering and opague repaint
// is there any common way, to manually repaint onto anything - like image
informationPanel.paintAll(mapPainter.getBuffer().getGraphics());
controller.paintAll(mapPainter.getBuffer().getGraphics());
tabbedPabe.paintAll(mapPainter.getBuffer().getGraphics());
mapPainter.updateScreen(mapPainter.getGraphics());
break;
}
}
}
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
animation.start();
}
private void initComponents() {
mapPainter = new Painter();
mapPainter.setSize(this.getSize());
informationPanel = new JPanel();
informationPanel.setSize(new Dimension(360, 800));
controller = new JPanel();
controller.setSize(new Dimension(500, 250));
tabbedPabe = new JPanel();
tabbedPabe.setSize(new Dimension(300, 300));
this.getLayeredPane().add(mapPainter, JLayeredPane.DEFAULT_LAYER);
this.getLayeredPane().add(controller, JLayeredPane.MODAL_LAYER);
this.getLayeredPane().add(tabbedPabe, JLayeredPane.MODAL_LAYER);
this.getLayeredPane().add(informationPanel, JLayeredPane.MODAL_LAYER);
}
private void initPositions() {
controller.setLocation(mapPainter.getWidth() - controller.getWidth(), mapPainter.getHeight() - controller.getHeight());
tabbedPabe.setLocation(this.getWidth() - tabbedPabe.getWidth(), mapPainter.getHeight() - controller.getHeight() - tabbedPabe.getHeight() - 400);
informationPanel.setLocation(10, mapPainter.getHeight() - informationPanel.getHeight() - 200);
}
}
public class RepaintExample {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
ContainerFrame f = new ContainerFrame();
}
}
I do use case 0 at the moment and do see having either great or pretty bad fps - either 30 or around 6. I am not certain, how that may be possible and i may be able to find sth. in the already posted links. I thought making sure to relieve the EDT at best, could become a proper solution.
Additionally the content of the 3 panels, i illustrated do not need a repaint in the same frequency as the animation does. Unfortunately i haven't found a proper way to prevent the repaint. The only way i have used for quite a while was a paintimmediately in an invokelater call for those areas, which are known as "animated". A common repaint(Rectangle rec) has not been working, since single calls have been summarized to a big one, covering more pixels, than i have passed in.
public void drawCachedSprite(Graphics2D g, CachedSprites sprites, int zoom, double cog, double x, double y, double w, double h) {
try{
pos_x = x;
pos_y = y;
RenderingUtil.getRenderQuality();
transform.setToIdentity();
// Compute the corner, the drawing needs to start with
transform.translate(x - (w / 2.0), y - (h / 2.0));
g.drawImage(sprites.getSprite(DefaultResources.getType(), spriteColor, zoom, cog), transform, null);
} catch (IllegalArgumentException e) {
e.printStackTrace();
System.out.println("width or height not set properly");
}
}
Related
I'm creating a graphical front-end for a JBox2D simulation. The simulation runs incrementally, and in between the updates, the contents of the simulation are supposed to be drawn. Similar to a game except without input.
I only need geometric primitives to draw a JBox2D simulation. This API seemed like the simplest choice, but its design is a bit confusing.
Currently I have one class called Window extending JFrame, that contains as a member another class called Renderer. The Window class only initializes itself and provides an updateDisplay() method (that is called by the main loop), that calls updateDisplay(objects) method on the Renderer. I made these two methods myself and their only purpose is to call repaint() on the Renderer.
Is the JPanel supposed to be used that way? Or am I supposed to use some more sophisticated method for animation (such that involves events and/or time intervals in some back-end thread)?
If you are wanting to schedule the updates at a set interval, javax.swing.Timer provides a Swing-integrated service for it. Timer runs its task on the EDT periodically, without having an explicit loop. (An explicit loop would block the EDT from processing events, which would freeze the UI. I explained this more in-depth here.)
Ultimately doing any kind of painting in Swing you'll still be doing two things:
Overriding paintComponent to do your drawing.
Calling repaint as-needed to request that your drawing be made visible. (Swing normally only repaints when it's needed, for example when some other program's window passes over top of a Swing component.)
If you're doing those two things you're probably doing it right. Swing doesn't really have a high-level API for animation. It's designed primarily with drawing GUI components in mind. It can certainly do some good stuff, but you will have to write a component mostly from scratch, like you're doing.
Painting in AWT and Swing covers some of the 'behind the scenes' stuff if you do not have it bookmarked.
You might look in to JavaFX. I don't know that much about it personally, but it's supposed to be more geared towards animation.
As somewhat of an optimization, one thing that can be done is to paint on a separate image and then paint the image on to the panel in paintComponent. This is especially useful if the painting is long: repaints can be scheduled by the system so this keeps when it happens more under control.
If you aren't drawing to an image, then you'd need to build a model with objects, and paint all of them every time inside paintComponent.
Here's an example of drawing to an image:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Creating a copy of the Graphics
// so any reconfiguration we do on
// it doesn't interfere with what
// Swing is doing.
Graphics2D g2 = (Graphics2D) g.create();
// Drawing the image.
int w = img.getWidth();
int h = img.getHeight();
g2.drawImage(img, 0, 0, w, h, null);
// Drawing a swatch.
Color color = colors[currentColor];
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.black);
g2.drawRect(-1, -1, 17, 17);
// At the end, we dispose the
// Graphics copy we've created
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
};
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
panel.repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// panel to make sure the
// changes are visible
// immediately.
panel.repaint();
prev = next;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
panel.setBackground(Color.white);
panel.addMouseListener(drawer);
panel.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
panel.setCursor(cursor);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
If the routine is long-running and repaints could happen concurrently, double buffering can also be used. Drawing is done to an image which is separate from the one being shown. Then, when the drawing routine is done, the image references are swapped so the update is seamless.
You should typically use double buffering for a game, for example. Double buffering prevents the image from being shown in a partial state. This could happen if, for example, you were using a background thread for the game loop (instead of a Timer) and a repaint happened the game was doing the painting. Without double buffering, this kind of situation would result in flickering or tearing.
Swing components are double buffered by default, so if all of your drawing is happening on the EDT you don't need to write double buffering logic yourself. Swing already does it.
Here is a somewhat more complicated example which shows a long-running task and a buffer swap:
import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
/**
* Left-click to spawn a new background
* painting task.
*/
class DoubleBuffer {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DoubleBuffer();
}
});
}
final int width = 640;
final int height = 480;
BufferedImage createCompatibleImage() {
GraphicsConfiguration gc =
GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
// createCompatibleImage creates an image that is
// optimized for the display device.
// See http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-
return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
// The front image is the one which is
// displayed in the panel.
BufferedImage front = createCompatibleImage();
// The back image is the one that gets
// painted to.
BufferedImage back = createCompatibleImage();
boolean isPainting = false;
final JFrame frame = new JFrame("Double Buffer");
final JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Scaling the image to fit the panel.
Dimension actualSize = getSize();
int w = actualSize.width;
int h = actualSize.height;
g.drawImage(front, 0, 0, w, h, null);
}
};
final MouseAdapter onClick = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (!isPainting) {
isPainting = true;
new PaintTask(e.getPoint()).execute();
}
}
};
DoubleBuffer() {
panel.setPreferredSize(new Dimension(width, height));
panel.setBackground(Color.WHITE);
panel.addMouseListener(onClick);
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
void swap() {
BufferedImage temp = front;
front = back;
back = temp;
}
class PaintTask extends SwingWorker<Void, Void> {
final Point pt;
PaintTask(Point pt) {
this.pt = pt;
}
#Override
public Void doInBackground() {
Random rand = new Random();
synchronized(DoubleBuffer.this) {
Graphics2D g2 = back.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2.setBackground(new Color(0, true));
g2.clearRect(0, 0, width, height);
// (This computes pow(2, rand.nextInt(3) + 7).)
int depth = 1 << ( rand.nextInt(3) + 7 );
float hue = rand.nextInt(depth);
int radius = 1;
int c;
// This loop just draws concentric circles,
// starting from the inside and extending
// outwards until it hits the outside of
// the image.
do {
int rgb = Color.HSBtoRGB(hue / depth, 1, 1);
g2.setColor(new Color(rgb));
int x = pt.x - radius;
int y = pt.y - radius;
int d = radius * 2;
g2.drawOval(x, y, d, d);
++radius;
++hue;
c = (int) (radius * Math.cos(Math.PI / 4));
} while (
(0 <= pt.x - c) || (pt.x + c < width)
|| (0 <= pt.y - c) || (pt.y + c < height)
);
g2.dispose();
back.flush();
return (Void) null;
}
}
#Override
public void done() {
// done() is completed on the EDT,
// so for this small program, this
// is the only place where synchronization
// is necessary.
// paintComponent will see the swap
// happen the next time it is called.
synchronized(DoubleBuffer.this) {
swap();
}
isPainting = false;
panel.repaint();
}
}
}
The painting routine is just intended draw garbage which takes a long time:
For a tightly coupled simulation, javax.swing.Timer is a good choice. Let the timer's listener invoke your implementation of paintComponent(), as shown here and in the example cited here.
For a loosely coupled simulation, let the model evolve in the background thread of a SwingWorker, as shown here. Invoke publish() when apropos to you simulation.
The choice is dictated in part by the nature of the simulation and the duty cycle of the model.
Why not just use stuff from the testbed? It already does everything. Just take the JPanel, controller, and debug draw. It uses Java 2D drawing.
See here for the JPanel that does the buffered rendering:
https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/TestPanelJ2D.java
and here for the debug draw:
https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/DebugDrawJ2D.java
See the TestbedMain.java file to see how the normal testbed is launched, and rip out what you don't need :)
Edits:
Disclaimer: I maintain jbox2d
Here is the package for the testbed framework: https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework
TestbedMain.java is in the j2d folder, here:
https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d
Hi I am creating a news ticker/ text scroller.
I am using the following method:
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Scroll1 extends JPanel{
private int x;
private int x2;
private int y;
private String text;
final int startX=-100;
public Scroll1(int startX)
{
x2=-650;
x = 20;
y=150;
text= "Some Words and others, and now this must be a longer text that takes up the whole panel/ frame for this test to work ";
}
#Override
public void paint(Graphics g)
{
g.setColor(Color.white);
g.fillRect(0, 0, 400, 300);
g.setColor(Color.black);
g.drawString(text, x, y);
g.drawString(text, x2, y);
FontMetrics fm= g.getFontMetrics();
System.out.println(fm.stringWidth(text));;
}
public void start() throws InterruptedException{
while(true){
while(x<= 650){
x++;
x2++;
y = getHeight()/2;
repaint();
Thread.sleep(10);
if(x2>650)
x2=-650;
}
if(x>=0)
{
x=-650;
}
}
}
public static void main(String[] args) {
JFrame frame = new JFrame("Scrolling Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Scroll1 scrolling = new Scroll1(-100);
frame.getContentPane().add(scrolling);
frame.setSize(400, 300);
frame.setVisible(true);
try {
scrolling.start();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Basically it has two strings that are being drawn. One starts at the 0 position and the other starts at -650. I got the -650 number by using the font metrics inside of the paint method. The problem is that I had to hard code that number, and if I did a different string that has different metrics, it would not work. I tried making a instance variable called width that stores the font metrics, but it seems that the width is not inputted until the paint method is called. Is there anyway I can get the metrics before it starts drawing it?
Is there anyway I can get the metrics before it starts drawing it?
Just initialize the variable in the first call to paint (or better yet, paintComponent - see below) - you can do this using a boolean flag, or initialize it's value to an extreme and do a check on the value.
int x = Integer.MIN_VALUE;
...
protected void paintComponent(Graphics g){
super.paintComponent(g);
if ( x == Integer.MIN_VALUE ){
x = -g.getFontMetrics().stringWidth(text);
}
...
}
Some other tips:
Use a Swing Timer to perform animation, or be sure to dispatch Swing specific calls to the EDT using SwingUtilities.
Don't override paint, rather override paintComponent (and be sure to call the parent method super.paintComponent(g))
This is my first time asking a question on here, and I'm hoping I get an answer or an idea that will help me.
I am drawing a large image and scaling it down with drawImage(). I then immediately draw another image with drawImage() that I expect to be drawn on top of the previous one (second). The problem is that drawImage returns immediately, even if it takes ~50 ms to scale and render the first image. Most of the time the second image ends up underneath the first one because it's painted first while the big first one is being processed. Basically is there anyway to force drawImage() to block until it's done, or to somehow check when it has completed?
I'm aware of the ImageObserver parameter, which works fine when downloading an image from the Internet or something, but when using an already-loaded BufferedImage, it never fires ImageUpdate() just after scaling and drawing. Basically since the first image is already "loaded" it never contacts the ImageObserver, it just takes like ~50 ms in it's own thread to process, never notifying when it completes.
Does anyone know how to either force it to block or wait until it's completely done scaling and blitting an image? And obviously using Thread.sleep(xx) is a complete hack and not viable due to differences between computer speeds. All this rendering is happened on the Event thread inside the paint(Graphics g) method.
Thanks!
EDIT: The following is code I currently have to give you an idea of the issue:
public void paint(Graphics window)
{
window.setColor(Color.WHITE);
window.fillRect(0, 0, Settings.width * Settings.aaFactor, Settings.height * Settings.aaFactor);
Graphics2D window2D = (Graphics2D) window;
window2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
window2D.drawImage(this.image, 0, 0, Settings.width, Settings.height, null);
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
window2D.drawImage(this.image2, 0, 0, null);
repaint();
}
EDIT 2: To better explain the problem I'm talking about, I made some sample code that does better what I'm trying to explain. Run it and you will see it flickering where sometimes the first image is on bottom (like it's supposed to be), but most of the time it will be one top (second), which is wrong. Just change the File paths to a small image and a large image.
public class Main extends Applet implements ImageObserver
{
private BufferedImage imageA;
private BufferedImage imageB;
#Override
public void init()
{
try
{
this.imageA = ImageIO.read(new File("C:\\LargeImage.jpg"));
this.imageB = ImageIO.read(new File("C:\\SmallImage.jpg"));
}
catch (IOException e)
{
e.printStackTrace();
}
}
#Override
public void update(Graphics g)
{
paint(g);
}
#Override
public void paint(Graphics g)
{
Graphics2D w = (Graphics2D) g;
w.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
w.drawImage(this.imageA, 0, 0, 50, 50, this);// This takes a while to do (scaling down and drawing)...
w.drawImage(this.imageB, 10, 10, null);// While this is drawn quickly, before A is done.
repaint();
}
#Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
{
System.out.println("ImageObserver fired, done drawing image. NEVER CALLED!");
return false;
}
}
The last argument to drawImage (where you pass null) is an ImageObserver. If you provide your own implementation of that interface (see JavaDoc), you will be informed when the image has been actually drawn.
It is impossible to know when Swing will actually render the contents of a Graphics object to the screen. What is know is it won't happen until AFTER the paint methods return (as the Graphics object hasn't been finalised for rendering until it does).
What you should do is let the Component you are painting to make the decision as to when something needs to updated, this is the way it was designed...(Component implements ImageObserver)
The below example continuously re-scales the master background image as the frame is resized
public class TestPaint03 {
public static void main(String[] args) {
new TestPaint03();
}
public TestPaint03() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new PaintPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class PaintPane extends JPanel {
private BufferedImage background;
private BufferedImage foreground;
private Image scaled;
public PaintPane() {
try {
background = ImageIO.read(new File("/path/to/background/image));
foreground = ImageIO.read(new File("path/to/foreground/image"));
} catch (Exception e) {
}
}
#Override
public void invalidate() {
scaled = null;
super.invalidate();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (background != null) {
if (scaled == null) {
int size = Math.min(getWidth(), getHeight());
scaled = background.getScaledInstance(-1, size, Image.SCALE_SMOOTH);
}
int x = (getWidth() - scaled.getWidth(this)) / 2;
int y = (getHeight() - scaled.getHeight(this)) / 2;
g.drawImage(scaled, x, y, this);
x = (getWidth() - foreground.getWidth()) / 2;
y = (getHeight() - foreground.getHeight()) / 2;
g.drawImage(foreground, x, y, this);
}
}
}
}
While I'm sure you have your reasons, personally I would avoid Applet in favor of JApplet and in fact, I would personally avoid applets or together. I started my career coding applets, they are simply a pain, especially when all you're trying to do is test an idea.
I have recently begun working with double buffering in Java. I have read tutorials that have shown me how to display images and move them using mouse and keyboard events. However this is where I become confused. My program is simple, there is a rectangle at the bottom of my window that is movable by LEFT and RIGHT key events. However I can not figure out for the life of my how to draw another shape onto the screen by an event and continue buffering it.
I would like to be able to push a key to draw a "missile" (which in my case would be a small Oval) at the X and Y position of the rectangle I have drawn already, and have it fire upwards. Much like any classic space shooter.
This however is not as much of a specific, concrete problem of mine, but a concept I do not understand. I learned how to do many of the similar things in Lua, however when it came to drawing new images after initialization or an image upon key events, I was stumped.
My question is this: In what order of Java's init(), stop(), destroy(), start(), run(), paint(), and update() cycle do I use to buffer a new shape/image onto the screen from a key event?
I have searched many tutorials with example code, but with no avail. I have been learning Java for nearly 8 months now, but no matter how basic or simple I try to understand something, it's as if even the most primordial tutorial requires prerequisite knowledge.
My code is as follows.
import java.applet.*;
import java.awt.*;
public class SquareApplet extends Applet implements Runnable
{
int x_pos = 10;
int y_pos = 400;
int rectX = 50;
int rectY = 20;
int x_speed = 5;
private Image dbImage;
private Graphics dbg;
public void init( ) { }
//public void start() { }
public void stop( ) { }
public void destroy( ) { }
//public void run ( ) { }
//public void paint (Graphics g) { }
public void start()
{
// define a new thread
Thread th = new Thread (this);
// start this thread
th.start ();
}
public void run ()
{
// lower ThreadPriority
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
// run a long while (true) this means in our case "always"
while (true) //Runtime
{
if (x_pos > this.getSize().width - rectX) {x_pos = this.getSize().width - rectX;}
if (x_pos < 0) {x_pos = 0 ;}
// repaint the applet
repaint();
try
{
// Stop thread for 20 milliseconds
Thread.sleep (20);
}
catch (InterruptedException beepis)
{
// do nothing
}
// set ThreadPriority to maximum value
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
}
}
public void paint (Graphics g)
{
// set background color
g.setColor(Color.black);
g.fillRect(0,0,this.getSize().width,this.getSize().height);
// set player color
g.setColor (Color.white);
// paint a filled colored rectangle
g.fillRect(x_pos, y_pos, rectX,rectY );
}
public void update (Graphics g)
{
// initialize buffer
if (dbImage == null)
{
dbImage = createImage (this.getSize().width, this.getSize().height);
dbg = dbImage.getGraphics ();
}
// clear screen in background
dbg.setColor (getBackground ());
dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);
// draw elements in background
dbg.setColor (getForeground());
paint (dbg);
// draw image on the screen
g.drawImage (dbImage, 0, 0, this);
}
//KEY EVENTS
public boolean keyDown(Event e, int key)
{
//Up Down Left Right
if (key == Event.LEFT)
{
x_pos -= x_speed;
}
if (key == Event.RIGHT)
{
x_pos += x_speed;
}
return true;
}
}
I found some Java game code online and I am trying to modify it. I converted it from JFrame to Applet, but then my game started to blink every time I repaint screen. I tried double buffering but no difference.
Source:
private void paintDisks(Graphics g) {
try {
for (Disk d : disk)
paintDisk(g, d);
} catch (Exception ex) {
//System.out.println(ex.getMessage());
paintDisks(g); // retry so the disks never not get painted
}
}
private void paintDisk(Graphics g, Disk d) {
if (d == null)
return;
if (diskimg[d.player] == null) {
g.setColor(colour[d.player]);
g.fillOval((int)d.x - 1, (int)d.y - 1, 32, 32);
} else {
g.drawImage(diskimg[d.player],
(int)d.x - 1, (int)d.y - 1,
32, 32, this);
}
}
#Override
public void paint(Graphics g) {
// paint real panel stuff
super.paint(g);
Graphics gr;
if (offScreenBuffer==null ||
(! (offScreenBuffer.getWidth(this) == this.size().width
&& offScreenBuffer.getHeight(this) == this.size().height)))
{
offScreenBuffer = this.createImage(size().width, size().height);
}
gr = offScreenBuffer.getGraphics();
gr.clearRect(0,0,offScreenBuffer.getWidth(this),offScreenBuffer.getHeight(this));
// paint the disks
paintDisks(gr);
// paint the curser ontop of the disks
paintCurser(gr);
g.drawImage(offScreenBuffer, 0, 0, this);
}
#Override
public void run() {
while (true) {
repaint();
try {
Thread.sleep(9, 1);
} catch (InterruptedException ex) {
System.out.println(ex.getMessage());
}
}
}
}
Short answer: do not call super.paint() in your Board.paint() method.
Long answer: Applet is also a container with its own display properties including a background color which you set via setBackground(Color.WHITE); as part of your constructor. By invoking super.paint(g) you are causing the applet to paint its white background to the display graphics, as well as invoke any contained component painting. This is the cause of the flicker - each paint cycle, it is painting the on-screen display white then copying your offscreenBuffer image to the on-screen display.
Probably it is best to be explicit, forget about the Applet background, remove super.paint(g), and just do all the paint steps yourself. You'll need to replace clearRect() with setColor() and fillRect().
Also you should implement update() as well.
#Override
public void update(Graphics g) { paint(g); }
#Override
public void paint(Graphics g) {
// no super.paint(g)
if (offScreenBuffer==null ||
(! (offScreenBuffer.getWidth(this) == this.size().width
&& offScreenBuffer.getHeight(this) == this.size().height)))
{
offScreenBuffer = this.createImage(size().width, size().height);
}
Graphics gr = offScreenBuffer.getGraphics();
// blank the canvas
gr.setColor(Color.WHITE);
gr.fillRect(0,0,offScreenBuffer.getWidth(this),offScreenBuffer.getHeight(this));
// paint the disks
paintDisks(gr);
// paint the curser ontop of the disks
paintCurser(gr);
g.drawImage(offScreenBuffer, 0, 0, this);
}
Take a look at the game engine Bonsai from Ivo Wetzel. I like it a lot.
It uses BufferStrategy, which is, I think the best way to double buffer.