I'm working for the first time with images in a JFrame, and I have some problems. I succeeded in putting an image on my JFrame, and now i want after 2 seconds to remove my image from the JFrame. But after 2 seconds, the image does not disappear, unless I resize the frame or i minimize and after that maximize the frame. Help me if you can. Thanks.
Here is the code:
File f = new File("2.jpg");
System.out.println("Picture " + f.getAbsolutePath());
BufferedImage image = ImageIO.read(f);
MyBufferedImage img = new MyBufferedImage(image);
img.resize(400, 300);
img.setSize(400, 300);
img.setLocation(50, 50);
getContentPane().add(img);
this.setSize(600, 400);
this.setLocationRelativeTo(null);
this.setVisible(true);
Thread.sleep(2000);
System.out.println("2 seconds over");
getContentPane().remove(img);
Here is the MyBufferedImage class:
public class MyBufferedImage extends JComponent{
private BufferedImage image;
private int nPaint;
private int avgTime;
private long previousSecondsTime;
public MyBufferedImage(BufferedImage b) {
super();
this.image = b;
this.nPaint = 0;
this.avgTime = 0;
this.previousSecondsTime = System.currentTimeMillis();
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
g2D.setColor(Color.BLACK);
g2D.fillRect(0, 0, this.getWidth(), this.getHeight());
long currentTimeA = System.currentTimeMillis();
//g2D.drawImage(this.image, 320, 0, 0, 240, 0, 0, 640, 480, null);
g2D.drawImage(image, 0,0, null);
long currentTimeB = System.currentTimeMillis();
this.avgTime += currentTimeB - currentTimeA;
this.nPaint++;
if (currentTimeB - this.previousSecondsTime > 1000) {
System.out.format("Drawn FPS: %d\n", nPaint++);
System.out.format("Average time of drawings in the last sec.: %.1f ms\n", (double) this.avgTime / this.nPaint++);
this.previousSecondsTime = currentTimeB;
this.avgTime = 0;
this.nPaint = 0;
}
}
}
just call this.repaint() after removing the image and all will be well ;)
You probably need to invalidate the frame component forcing a redraw.
Probably your best bet is to look at the update/repaint methods.
Have you tried calling
getContentPane().revalidate() ;
after the call to remove?
You should ensure that you are removing your image from your component in the Event Dispatch Thread. Give this a try:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
getContentPane().remove(img);
}
}
Your img will either need to be a global or declared final in local scope for this to work. Take a look at concepts on Swing Threads if you are not already familiar.
Note: The remove call on Container will call invalidate() if the content pane is considered valid.
Give
SwingUtilities.updateComponentTreeUI(this); a Shot
Related
I'm making a gravity simulator and I need it animate live so the user can watch it. I've been able to make it trace out the path the object would take.
But as you can see it just traces it out and then displays the window. I think my problem is because all of this in the section of code that builds the JPanel but I don't know how to change it properly.
Here's what I'm doing for my window:
import java.awt.*;
import javax.swing.*;
import java.lang.Math;
public class Universe {
public static void main(String[] args) throws InterruptedException {
new Universe();
}
public Universe() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Gravity Simulator");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
int paneWidth = 500;
int paneHeight = 500;
#Override
public Dimension getPreferredSize() {
return new Dimension(paneWidth, paneHeight);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int size = Math.min(getWidth()-4, getHeight()-4) / 10;
int width = getWidth() - (size * 2);
int height = getHeight() - (size * 2);
int x0=paneWidth/2; int y0=paneHeight/2; int radius0=20;
int y = (getHeight() - (size * 10)) / 2;
for (int horz = 0; horz < 2; horz++) {
int x = (getWidth() - (size * 10)) / 2;
for (int vert = 0; vert < 10; vert++) {
g.drawRect(x, y, size, size);
drawCircle(g, x+25, y+25, 5);//A massive object would go here this just proof of concept
x += size;
}
y += size;
}
double[] velocity={5,-2};
MassiveObject planet = new MassiveObject(g, 20, 50, velocity, 250, 150);
planet.draw(g);
MassiveObject rock = new MassiveObject(g, 2, 25, velocity, 275, 300);
rock.draw(g);
double sGravity = fGrav(planet, rock);
//double dis = massDis(planet, rock);
System.out.println("Distance: "+massDis(planet, rock));
System.out.println("Gravity: "+sGravity+" Newtons of force(gravity is multiplied by "+1000000+")");
double[] traj = objectTrajetory(planet, rock, rock.getMass());
int t = 0;
try {
while(true) {
//double k = sGravity/dis;
//x and y components of motion
double xm = traj[0];
double ym = traj[1];
double[] nVelocity= {xm,ym};
//////////////////////////////
//set new position of object
rock.setX(rock.getX()+(xm));
rock.setY(rock.getY()+(ym));
rock.setVelocity(nVelocity);
rock.draw(g);
t++;
System.out.println("position changed: "+rock.getCoords());
traj = objectTrajetory(planet, rock, 1);
Thread.sleep(100);
if (t> 15){break;}
}
}
catch(Exception e) {
}
//System.out.println("Distance: "+massDis(planet, rock));
//System.out.println("Gravity: "+fGrav(planet, rock)+" Newtons of force(gravity is multiplied by "+1000000+")");
g2d.dispose();
}
And here is the code for the draw function of my MassiveObject:
public void draw(Graphics g){
Graphics2D g2d = (Graphics2D) g;
Ellipse2D.Double circle = new Ellipse2D.Double(this.x0-(this.radius/2), this.y0-(this.radius/2), this.radius, this.radius);
g2d.setColor(Color.GRAY);
g2d.fill(circle);
}
So basically what I'm asking is how can I make it run that algorithm to paste the MassiveObject at its new location after the window is already pulled up so the user can watch it happening instead of it just building the window with it already on it?
The logic of your animation shouldn't be in the paintComponent() method. The paintComponent() method should just paint the current frame of animation. The code inside paintComponent() is run inside a special thread dedicated to handling all UI paints, responding to clicks etc. So for as long as paintComponent() is running, nothing else can happen in the UI, hence your application "grinds to a halt".
The logic to periodically update the state and then order a repaint should be in a separate thread (or the main thread). When it has updated the state and needs the next frame to be drawn, it then calls the panel's repaint() method. Because you're doing this in another thread, you would surround it in SwingUtilities.invokeLater(). This orders Swing to to call back into the paintComponent():
while (true) {
// Update state used by the paintComponent() method
updateObjectPositions();
// Now draw the new animation frame
SwingUtilities.invokeLater(() -> {
universePanel.repaint(0, 0, universeWidth, universeHeight);
});
Thread.sleep(...);
}
Because the drawing and updating are happening in different threads, you need to make sure that the data is shared between the threads in a thread-safe way. If you're just starting out and the calculations are very quick, then you could put the updateObjectPositions() method inside the invokeLater() so that the update to the data and the redraw happen in the UI thread. But remember that the code inside the invokeLater() will be blocking the UI for as long as it runs, so it should be as brief as possible and just handle a single frame. Crucially, your while loop and sleep should not go inside the invokeLater() or inside any UI code such as paintComponent().
Thanks a lot for the help, I was able to get the program animating the way I wanted it to and it was exactly as you all suggested. I removed my logic from the paintComponent() and put it inside the JPanel pane, ran a timer to continuously update the position, and then ran the repaint() function at the end of each loop in timer.
public class TestPane extends JPanel {
int paneWidth = 1200;
int paneHeight = 1200;
double[] velocity={4,4};
MassiveObject planet = new MassiveObject( 50, 50, velocity, paneWidth/2,paneHeight/2);
MassiveObject rock = new MassiveObject( 2, 25, velocity, 150, 200);
double[] traj = objectTrajetory(planet, rock, rock.getMass());
double xm=0.00;
double ym=0.00;
public TestPane() {
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
xm = traj[0];
ym = traj[1];
double[] nVelocity= {xm,ym};
//////////////////////////////
//set new position of object
rock.setX(rock.getX()+(xm));
rock.setY(rock.getY()+(ym));
rock.setVelocity(nVelocity);
System.out.println("position changed: "+rock.getCoords());
repaint();
traj = objectTrajetory(planet, rock, 1);
rock.setX(rock.getX()+(xm));
rock.setY(rock.getY()+(ym));
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(paneWidth, paneHeight);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int size = Math.min(getWidth()-4, getHeight()-4) / 10;
int width = getWidth() - (size * 2);
int height = getHeight() - (size * 2);
int x0=paneWidth/2; int y0=paneHeight/2; int radius0=20;
rock.draw(g);
planet.draw(g);
g2d.dispose();
}
The program now animates pretty smoothly instead of just spitting out a plot of the path it would take.
Snap of Animated Orbit
I am designing a cookie clicker like game. I have a class that extends JPanel and in the constructor I load some images into an Image object. But when I go to paint them in paintComponent the console start spewing NullPointerExceptions for a few seconds then stops and the window loads properly. My only guess is that the images are taking time to load and that paintComponent is trying to paint them before they have loaded entirely. Any help is appreciated. Thanks.
public PanelClass() {
frame.setSize(screenSize);
frame.add(this);
frame.setUndecorated(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
addMouseListener(this);
addMouseMotionListener(this);
buttonGraphics.start();
try {
background = ImageIO.read(new File("res/img/Background.png")).getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH);
steamLogo[0] = ImageIO.read(new File("res/img/SteamLogo.png")).getScaledInstance(getWidth()/5, getWidth()/5, Image.SCALE_SMOOTH);
for (int i = 1; i < steamLogo.length; i++) {
steamLogo[i] = steamLogo[0].getScaledInstance(steamLogo[0].getWidth(null)-(i*2), steamLogo[0].getWidth(null)-(i*2), Image.SCALE_SMOOTH);
}
} catch (IOException e) {
e.printStackTrace();
}
logoHitbox = new Ellipse2D.Double((getWidth()/2)-(steamLogo[0].getWidth(null)/2), (getHeight()/2)-(steamLogo[0].getHeight(null)/2), getWidth()/5, getWidth()/5);
}
public void paintComponent(Graphics g) {
g.drawImage(background, 0, 0, null);
g.drawImage(steamLogo[curLogo], (getWidth()/2)-(steamLogo[curLogo].getWidth(null)/2), (getHeight()/2)-(steamLogo[curLogo].getHeight(null)/2), null);
if(clicked > 1) {
g.setColor(new Color(0,0,0,155-(clicked*7)));
g.fillOval((getWidth()/2)-(steamLogo[curLogo].getWidth(null)/2), (getHeight()/2)-(steamLogo[curLogo].getHeight(null)/2), (int)steamLogo[curLogo].getWidth(null), (int)steamLogo[curLogo].getHeight(null));
}
}
I'm pretty sure that this code should draw an oval on the screen next to the word text. However, the word is all the appears, the rest of the screen is black. This seems to happen with any primitive shape. I'd like to think I know java fairly well, but graphical things have been really confusing to me. I'm at my wit's end with this and any help would be appreciated.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.util.ArrayList;
public class Game extends JPanel implements Runnable {
int W = 4;
int H = 3;
int windowSize = 300;
boolean running;
static boolean drawHitBoxes = true;
int FPSLimit = 30;
private Thread thread;
private BufferedImage buffer;
private Graphics2D g;
public Game() {
super();
setPreferredSize(new Dimension(W * windowSize, H * windowSize));
setFocusable(true);
requestFocus();
}
public void addNotify() {
super.addNotify();
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void run() {
running = true;
buffer = new BufferedImage(W * windowSize, H * windowSize,
BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) buffer.getGraphics();
// citList.add(new Citizen(200, 200, "Joe"));
long startTime;
long waitTime;
long frameTime = 1000 / FPSLimit; // /How long one frame should take
long currentFrameTime;
while (running) {
startTime = System.nanoTime(); // record when loop starts
gameUpdate();
gameRender();
gameDraw();
// Calculate how long the current frame took
currentFrameTime = (System.nanoTime() - startTime) / 1000000;
waitTime = frameTime - currentFrameTime;
try {
Thread.sleep(waitTime);
} catch (Exception e) {
} // Sleep for the remaining time
}
}
private void gameUpdate() {
// for(Citizen i:citList){i.update();} //Update citizens
}
private void gameRender() {
g.setColor(Color.WHITE);
g.drawOval(100, 100, W - 100, H - 100);
g.setColor(Color.WHITE);
g.drawString("Text.", 100, 100);
System.out.println("Drawing white box.");
// for(Citizen i:citList){i.draw(g);} //Draw citizens
}
private void gameDraw() {
Graphics gMain = this.getGraphics();
gMain.drawImage(buffer, 0, 0, null);
}
}
g.drawOval(100, 100, W-100, H-100);
W is 4 and H is 3, and so since W-100 is -96 and H-100 is -97, making your 3rd and 4th parameters negative, which doesn't make sense for the Graphics#drawOval(...) method since how can an oval's width and height be negative. Solution: be sure to use only positive parameters that make sense when calling this method. Probably what you want is:
// but you'll also want to avoid magic numbers such as 100 & 200 as well
g.drawOval(100, 100, W * windowSize - 200, H * windowSize - 200);
As an aside, myself, I prefer using passive graphics, drawing in paintComponent and am fearful whenever I see Swing code that has a Graphics or Graphics2D instance field.. Also your code looks to not obey Swing threading rules as it appears to be making Swing calls off of the Swing event thread.
I think it's better to create the paintComponent method and transfer your gameRender and gameDraw there and in your while loop replace their method calls with repaint(). Here is the code that works.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.util.ArrayList;
public class Game extends JPanel implements Runnable {
int W = 4;
int H = 3;
int windowSize = 300;
boolean running;
static boolean drawHitBoxes = true;
int FPSLimit = 30;
private Thread thread;
private BufferedImage buffer;
private Graphics2D g;
public Game() {
super();
setPreferredSize(new Dimension(W * windowSize, H * windowSize));
setFocusable(true);
requestFocus();
}
public void addNotify() {
super.addNotify();
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void run() {
running = true;
buffer = new BufferedImage(W * windowSize, H * windowSize,
BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) buffer.getGraphics();
// citList.add(new Citizen(200, 200, "Joe"));
long startTime;
long waitTime;
long frameTime = 1000 / FPSLimit; // /How long one frame should take
long currentFrameTime;
while (running) {
startTime = System.nanoTime(); // record when loop starts
gameUpdate();
//gameRender();
//gameDraw();
repaint();
// Calculate how long the current frame took
currentFrameTime = (System.nanoTime() - startTime) / 1000000;
waitTime = frameTime - currentFrameTime;
try {
Thread.sleep(waitTime);
} catch (Exception e) {
} // Sleep for the remaining time
}
}
private void gameUpdate() {
// for(Citizen i:citList){i.update();} //Update citizens
}
private void gameRender() {
g.setColor(Color.WHITE);
//g.drawOval(100, 100, W - 100, H - 100);
g.drawOval(100, 100, 100, 100);
g.setColor(Color.WHITE);
g.drawString("Text.", 100, 100);
//System.out.println("Drawing white box.");
// for(Citizen i:citList){i.draw(g);} //Draw citizens
}
private void gameDraw(Graphics gMain) {
//Graphics gMain = this.getGraphics();
gMain.drawImage(buffer, 0, 0, null);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
gameRender();
gameDraw(g);
}
}
As point out by #Hovercraft in his answer, the W-100 and H-100 in the code `g.drawOval(100, 100, W - 100, H - 100);' will yield negative numbers. I don't know exactly what values do you want to result in there but I just replace them with 100 just to remove the bug.
I want to move Images slowly.But drawImage() method only takes int values.Is there any method to move pictures slowly.I want to make ground move to the left a little bit slowly.
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class Board extends JPanel {
private Image ground;
private Image city;
private int x = 0;
public Board() {
ground = Toolkit.getDefaultToolkit().getImage("Resources\\ground2.png");
city = Toolkit.getDefaultToolkit().getImage("Resources\\background2.png");
}
public void paint(Graphics g){
super.paint(g);
g.drawImage(ground, x--, 500, 600, 200, this);
g.drawImage(ground, x + 600, 500, 600, 200, this);
repaint();
g.drawImage(city, 0, 0, 600, 500, this);
if(x == -600){
x = 0;
}
}
}
You can use Swing Timer for changing your x variable and repainting. Add next code to your Board constructor:
Timer t = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
x--;
repaint();
}
});
t.start();
Also do custom paintings in paintComponent() method instead of paint():
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(ground, x, 500, 600, 200, this);
g.drawImage(ground, x + 600, 500, 600, 200, this);
g.drawImage(city, 0, 0, 600, 500, this);
if (x == -600) {
x = 0;
}
}
And don't call repaint() inside paint() or paintComponent() method.
drawImage() method only takes int values.Is there any method to move pictures slowly.
Sure. Use an AffineTransform translate instance. They can work with double values. The resulting image drawing will then be 'dithered' along the edges to show what appears to be 'sub pixel accuracy' rendering.
I believe that the rounding to int of your x isn't the problem. The real one is that you need to move using a time based animation instead of a frame based animation. You can use an approach based on float ( double is too much for your purpose ) or with integer doing some easy steps.
Add a member:
private long startTime = 0;
remove the int x as member.
And then change your code in your draw routine using something like:
public void paint(Graphics g){
super.paint(g);
long delta;
if ( startTime == 0 ) {
startTime = System.currentTimeMillis();
delta = 0;
} else {
long now = System.currentTimeMillis();
delta = now - startTime;
}
//using startTime instead of lastTime increase very slow speed accuracy
const long speed = 30; //pixel/sec
//threshold with your image width
int x = (int)((( speed * delta ) / 1000l)%600l);
//
//--- your draw code ---
//
}
Et voila!
I'm trying to develop a full-screen application, but I'm having issues with double buffers.
public void create ()
{
window = new JWindow ();
window.setIgnoreRepaint (true);
GraphicsEnvironment.getLocalGraphicsEnvironment ().getDefaultScreenDevice ().setFullScreenWindow (window);
window.setVisible (true);
window.createBufferStrategy (2);
}
public void renderCycle ()
{
BufferStrategy strategy = window.getBufferStrategy ();
while (true)
{
render ((Graphics2D) strategy.getDrawGraphics ());
strategy.show ();
}
}
public void render (Graphics2D g)
{
g.setColor (Color.WHITE);
g.drawString ("Veikia", 100, 100);
}
I see a heavy flickering - it seems as if the text is drawn only on every other buffer and remaining buffers contain white background. What could be the problem?
I just tried this MultiBufferTest. I didn't see any rendering artifact until the lag period fell below the monitor's corresponding refresh rate. Your example appears to have no delay between frames.
I added a few lines to show the frame period:
...
g.fillRect(0, 0, bounds.width, bounds.height);
g.setColor(Color.black); // added
g.drawString(String.valueOf(lag), 100, 100); // added
bufferStrategy.show();
...