I am making a game canvas using swing and decided to use JTextField's for the input of a username and password into the panel.
I am buffering an image then rendering it onto the screen instead of drawing everything directly onto the panel real-time.
I have ran into a problem though, I paint a background and have set both of my text fields to opaque, but it seems that whenever I go to enter something into those text field's it flashes a black box where the JTextField is.
It happens in both of my username and password fields. Any idea of what the cause of this could be?
Other helpful information: Whenever I click on a text box, both of the components flash black where the first character would be.
EDIT -- I just noticed that the login button I have also flashes black when MOUSE_ENTERED and MOUSE_EXIT.
public class GamePanel extends JPanel implements Runnable {
public GamePanel(int width, int height) {
this.pWidth = width;
this.pHeight = height;
setController(new LoginGameController(this));
setPreferredSize( new Dimension(pWidth, pHeight));
setBackground(Color.BLACK);
setFocusable(true);
requestFocus(); // the JPanel now has focus, so receives key events
// create game components
addMouseListener(this);
addKeyListener(this);
setLayout(null);
startGame();
}
private void startGame()
// initialise and start the thread
{ if (animator == null) {
animator = new Thread(this);
animator.start();
}
}
public void run() {
while(true) {
gameUpdate();
if(getGraphics() != null){
gameRender(); // render the game to a buffer
paintScreen(); // draw the buffer on-screen
}
try {
Thread.sleep(28);
} catch (InterruptedException e) {}
}
}
private void paintScreen() {
Graphics2D g = (Graphics2D) getGraphics();
if ((g != null) && (img != null))
g.drawImage(img, 0, 0, null);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
private void gameRender() {
if(getWidth() > 0 && getHeight() > 0)
img = createImage(getWidth(), getHeight());
if(img != null) {
Graphics2D g = (Graphics2D) img.getGraphics();
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.BLACK);
g.fillRect(0, 0, pWidth, pHeight);
getController().render(img);
paintComponents(img.getGraphics());
}
}
}
Here is text fields: (from a seperate class entirely calling to the GamePanel using getPanel()...)
//Setup Login fields
usernameTF = new JTextField();
usernameTF.setOpaque(false);
usernameTF.getCaret().setBlinkRate(0);
usernameTF.setForeground(Color.WHITE);
usernameTF.setBounds(USERNAME_FIELD);
usernameTF.setBorder(null);
getPanel().add(usernameTF);
passwordTF = new JPasswordField();
passwordTF.setOpaque(false);
passwordTF.getCaret().setBlinkRate(0);
passwordTF.setForeground(Color.WHITE);
passwordTF.setBounds(PASSWORD_FIELD);
passwordTF.setBorder(null);
getPanel().add(passwordTF);
loginBtn = new JButton();
loginBtn.setOpaque(false);
loginBtn.setBackground(null);
loginBtn.setBorder(null);
loginBtn.setBounds(LOGIN_BUTTON);
loginBtn.addMouseListener(getPanel());
getPanel().add(loginBtn);
Thanks!
The basic problem is, you circumventing Swings repaint process and not honoring the EDT when you up-date your graphics.
JComponent#getGraphics is a temporary/scratch buffer which will be re-draw on the next repaint. Also, if you didn't create the graphics, you shouldn't dispose it!
public void run() {
while(true) {
gameUpdate();
if(getGraphics() != null){
gameRender(); // render the game to a buffer
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
paintScreen(); // draw the buffer on-screen
}
});
} catch (Exception exp) {
exp.printStackTrace(); // please clean this up
}
}
try {
Thread.sleep(28);
} catch (InterruptedException e) {}
}
}
I don't know if this will fix it, but it can't hurt. (This WILL effect you FPS and you should be taking into consideration how long it took to paint and how long you want to wait)
Alternativly, rather then calling paintScreen(), you could call repaint (or have paintScreen do it) and override the paintComponent method to paint your buffer.
This would allow Swing to resume control over the paint process properly.
A window or component has at least three ways to repaint.
on demand from the operating system when it has lost a part of the window buffer.
on demand from a library component when it has changed its state.
manually by manipulating its own frame buffer.
normally, the following pathway is active:
The operating system may request a particular rectangle to be updated. This is picked up by the toolkit. Additionally, a component may signal its change to the toolkit as well through its repaint method. The toolkit keeps collecting an merging incoming repaint requests for a while then asks the window to repaint a particular rectangle. The default action for the window is to paint itself by calling its paintComponent method where it doesn't overlap with solid children and then paint its components recursively.
If you do your own rendering, the toolkit is doing it as well. Since you didn't override your paintComponent, when this method runs, it acts as default. The default action is to do nothing. The toolkit then picks up the empty frame buffer and paints the button (which doesn't paint its background) over it.
You can disable the toolkit's rendering process and do all rendering yourself (grab a buffer, paint into it, then submit). You can call the setIgnoreRepaint(true) method to ignore the requests by OS.
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
I figured out a solution minutes after posting, but due to low reputation I couldn't delete the post
For the fun of it I decided to start working on something which might turn into a game at some point.
I'm trying to draw some circles and move them in a given direction currently. This causes flickering. It's very likely that I oversee something very basic but I can't figure out why it doesn't render smoothly.
My board class looks something like (removed what I deemed unnecessary):
public class Board extends Canvas implements Runnable {
public static void main(String[] args) {
Board board = new Board();
board.setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
JFrame frame = new JFrame("Circles");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(board);
frame.pack();
frame.setVisible(true);
board.start();
}
#Override
public void run() {
while (running) {
process();
repaint();
try {
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
The paint method:
public void paint(Graphics g1) {
super.paint(g1);
Graphics2D g = (Graphics2D) g1;
for (Ball ball : Ball.BALLS) {
g.drawOval((int) ball.getLocation().getX(), (int) ball.getLocation().getY(), ball.getRadius(), ball.getRadius());
}
}
My process method:
private void process() {
if (utilities.randInt(1, 100) < 10 && Ball.getBallCount() < 40) {
Ball.spawnNew(this);
}
for (Ball ball : Ball.BALLS) {
ball.move(this);
}
Ball.BALLS.removeAll(Ball.TO_REMOVE);
Ball.TO_REMOVE.clear();
}
The move method basically increments the x-value of the ball by a given value each time its called (moving it right).
Like I said, I'm unsure why it flickers so if you have any pointers please do tell.
Thanks!
This sounds like a case where you need to perform double-buffering, so that one copy of your canvas can remain shown while you are updating the other.
You're using AWT here, and I don't know how to implement double-buffering manually with AWT. However, if you're willing to use Swing here you can take advantage of automatic double-buffering. See the question about How to make canvas with Swing? as well as Oracle Technology Network's article on Painting in AWT and Swing.
The basic idea would be:
extend javax.swing.JPanel instead of Canvas (which means when you override paint(Graphics) you're now overriding it from javax.swing.JComponent instead of java.awt.Component)
create a constructor with super(true) to enable double-buffering.
Edit: Also, as iccthedral points out, you're better off overriding paintComponent(Graphics) and including a call to super.paintComponent(Graphics). See Difference between paint, paintComponent and paintComponents in Swing.
You need double buffering. To do this you need to create a BufferedImage and get the Graphics from it. Paint everything to the image, render the image on to the screen, then finally fill the image with a the background color or image to reset it.
Sample:
//one time instantiation
BufferedImage b = new BufferedImage(width, height, mode);
In paint(Graphics g):
Graphics buffer = b.getGraphics();
//render all of the stuff on to buffer
Graphics2D g = (Graphics2D) buffer;
for (Ball ball : Ball.BALLS) {
g.drawOval((int) ball.getLocation().getX(), (int) ball.getLocation().getY(), ball.getRadius(), ball.getRadius());
}
g1.drawImage(b, 0, 0, width, height, null);
g.setColor(Color.BLACK);
//reset the image
g.drawRect(0, 0, width, height);
g.dispose();
This is my code please help and explain what I did wrong thank you very much.
Also I am a bit confuse about Thread whether I did correctly way.
public class Fade extends JPanel implements Runnable {
static Image image;
private float alpha = 0f;
static JFrame frame;
public static void main(String[] args) throws IOException {
image = new ImageIcon(ImageIO.read(new File("gummybear.jpg")))
.getImage();
frame = new JFrame("fade frame");
frame.add(new Fade());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(image.getWidth(frame), image.getHeight(frame));
// set picture in the center of screen
frame.setLocationRelativeTo(null);
frame.setVisible(true);
ExecutorService executor = Executors.newFixedThreadPool(1);
Runnable fade = new Fade();
executor.execute(fade);
// executor.shutdown();
// while (!executor.isTerminated()) {
// }
// System.out.println("Finished fade in / fade out threads");
}
public void run() {
while (alpha < 1) {
try {
System.out.println(alpha);
alpha += 0.1f;
this.repaint();
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Fader.class.getName()).log(Level.SEVERE, null,
ex);
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// SRC_OVER: If pixels in the source and the destination overlap, only
// the source
// pixels outside of the overlapping area are rendered. The pixels in
// the overlapping area are not changed.
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
alpha));
g2d.drawImage(image, 0, 0, null);
// Color c = new Color(255, 255, 255, alpha);
// g2d.setColor(c);
// g2d.fillRect(0, 0, image.getWidth(frame), image.getHeight(frame));
System.out.println("repaint");
}
}
...............
Image Stuff
It looks to me that you want to add transparency to an image that was loaded from a JPG file. JPG files don't have an alpha channel (that is used for transparency), so this will not work.
You find a good example for working with transparency (and loading images from JPG) in the one of the Java tutorials: Drawing an Image
There you find a nice example with code that does nearly what you want: change the transparency of an image loaded from a JPG file. The difference is that the opacity value is taken from a slider control instead of a timed variable.
Threading Stuff
Edit: I just realized, that you are using Swing, an API that is not designed to be thread safe.
I just have to point you to the Java Tutorials How to Use Swing Timers and Concurrency in Swing then.
The problem is that you are not changing the alpha value. At least, not the alpha value of the Fade instance that you are showing:
// Here you are adding a "Fade" instance to the frame.
frame.add(new Fade());
...
// Here you are creating a NEW "Fade" instance. Only in
// this instance, the alpha value will be affected
Runnable fade = new Fade();
executor.execute(fade);
Change this to
// Create a Fade instance and add it to the frame
Fade fade = new Fade();
frame.add(fade);
...
// Submit the SAME Fade instance to the executor
executor.execute(fade);
You'll also have to verify that the alpha value remains in [0,1], but this can be done with something like
alpha += 0.1f;
alpha = Math.min(1.0f, alpha);
I have a quite simple animation, a text in a big font moving continuously (pixel by pixel) to the left. The text is first converted to an image, then a timer task is started which repeatedly (every 10-20 ms) decrements the x coordinate of the image by 1, and does a repaint().
This program shows a strange behavior on some systems. On my PC with a nVidia card it runs smoothly. On my Vaio notebook, on a BeagleBoneBlack and on a friend's Mac it stutters heavily. It appears to pause for a while, then jump to the left about 10 pixels, pause again and so on.
What stumps me is the fact that on these systems the animation only stutters if you don't touch the mouse. As long as you move the mouse cursor within the window, no matter how slowly, or drag the window itself around, the animation runs perfectly smooth!
Can anybody explain this? Here is the program:
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
class Textimg extends JComponent
{
String str;
Font font;
int x = 0;
final int ytext = 136;
Image img;
public Textimg(String s)
{
str = s;
font = new Font("Noserif", Font.PLAIN, 96);
setLayout(null);
}
protected void paintComponent(Graphics g)
{
if (img == null)
{
img = createImage(4800, 272);
Graphics gr = img.getGraphics();
gr.setFont(font);
gr.setColor(Color.BLACK);
gr.fillRect(0, 0, 4800, 272);
gr.setColor(new Color(135, 175, 0));
gr.drawString(str, 0, ytext);
gr.dispose();
}
g.drawImage(img, x, 0, this);
}
public void addX(int dif)
{
if (isVisible())
{
x = x + dif;
Graphics g = getGraphics();
if (g != null) paintComponent(g);
}
}
}
public class Banner extends JFrame
{
StringBuffer buf;
int sleeptime = 10;
Banner(String path) throws IOException
{
setSize(new Dimension(480, 272));
setTitle("Java Test");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), "UTF-8"));
buf = new StringBuffer();
while (true)
{
String line = reader.readLine();
if (line == null) break;
buf.append(line);
}
final Textimg textimg = new Textimg(buf.toString());
add(textimg);
textimg.setBounds(0, 0, 480, 272);
final javax.swing.Timer timer = new javax.swing.Timer(200, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
textimg.addX(-1);
}
});
timer.setDelay(sleeptime);
timer.start();
}
//----------------------------------------------------------------------
public static void main(String[] args) throws Exception
{
new Banner(args[0]).setVisible(true);
}
}
Try calling this method when you are done drawing:
Toolkit.getDefaultToolkit().sync();
This flushs the graphics buffer which some systems like Linux use. See the Javadoc: http://docs.oracle.com/javase/7/docs/api/java/awt/Toolkit.html#sync()
Don't, EVER, use getGraphics and you should NEVER call paintComponent yourself, this not how custom painting is done in Swing. Instead, update the state and call repaint.
Don't rely on magic numbers, use the information you have at hand (getWidth and getHeight)
Swing components are doubled buffered, so it's unlikely you would need to create you own buffered strategy. This simple act could be slowing down your painting
You must call super.paintComponent. This is even more important with JComponent, as it is not opaque and failing to do so could result in some nasty paint artefacts.
You should override JComponent#getPreferredSize so it can work with layout managers for efficiently.
You may find a shorter delay produces a better illusion, say 40 milliseconds (roughly 25fps) for example
Take a look at Swing animation running extremely slow, which through some object management and optimisation, was able to increase from 500 animated objects up to 4500.
Also take a look at Performing Custom Painting and Painting in AWT and Swing in particular
Profiling shows that you are saturating the shared thread used by javax.swing.Timer. One mitigation strategy is to use a longer period and/or a larger increment/decrement, as shown here.
Addendum: In addition, you are laboriously re-rendering the entire image in each call to paintComponent(). Instead, render it once using TextLayout, seen here, and draw() only the newly visible portion each time.
Problem solved!
To answer my own question: After realizing that any continuous input (mouse or keyboard) makes the animation run smoothly, I remembered that inputs can be generated by the program itself, using an object of the class java.awt.Robot. That lead to a simple workaround:
Create a Robot and let it press a key or a mouse move in each animation cycle.
final Robot robot = new Robot();
javax.swing.Timer timer = new javax.swing.Timer(initialDelay, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// update your image...
robot.keyPress(62);
}
});
This is a kludge, but works perfectly.
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.