Graphics.drawImage from another Thread does not draw anything in Java Swing - java

I'm trying to draw an animation in a JPanel by displaying all the frames as BufferedImage objects and using a Thread to invoke g.drawImage in the JPanels paintComponent(Graphics g) method for each frame, with sleeping in between. My understanding is that invoking g.drawImage from anywhere, as long as g is the Graphics object from the paintComponent, should cause the pixels in the JPanel to be updated, but there is no change in the JPanel. Is that not how Graphics.drawImage works, or is it an issue with using another Thread, or something else all together? An abbreviated version of my code is below, with unnecessary bits removed
class Example extends JPanel{
public Dimension getPreferredSize(){
return new Dimension(500, 500);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g){
BufferedImage temp1;
BufferedImage temp2;
try{
temp1 = ImageIO.read(new File("C:\\Users\\Owner\\Desktop\\test1.png"));
temp2 = ImageIO.read(new File("C:\\Users\\Owner\\Desktop\\test2.png"));
}catch(IOException e){
temp1 = null;
temp2 = null;
}
final BufferedImage image1 = temp1;
final BufferedImage image2 = temp2;
Thread drawThread = new Thread(new Runnable(){
public void run(){
g.drawImage(image1, 0, 0, null);
try{
Thread.sleep(100);
}catch(InterruptedException e){
// omitted
}
g.drawImage(image2, 0, 0, null);
try{
Thread.sleep(100);
}catch(InterruptedException e){
// omitted
}
}
});
drawThread.start();
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
public void run(){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new B());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Your current painting code is wrong. A painting method is for painting only. You would NEVER start a Thread from a painting method. You can't control when the painting method is invoked and every time the method is invoked you would start another Thread.
The Graphics object should only be used as a short duration object that exists for the duration of the painting method. You should not attempt to keep a reference to the object indefinitely.
I'm trying to draw an animation
If you want to do animation then you use a Swing Timer to schedule the animation.
So you should have the image as a property of the class. Then when the Timer fires you change the image property and invoke repaint() on the panel.

Related

Swing Timer and Painting on Canvas

Hi i wanna ask about Swing timer and Canvas. Im doing simple animation for changing color of objects. I made it with Thread.sleep but JFrame was unresponsive while repainting so i change it to Swing Timer. But now when i start the animation its doing nothing timer is working but objects on canvas dont change color.
Here is my function to animate change of colors im using it in overide paint function of canvas
private void paintSearch(Vector<NodeGraph2D> vector,Graphics graphics2D) {
if (!vector.isEmpty()) {
final int[] k = {0};
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (repaint) {
if (k[0] == vector.size())
return;
if (vector == pruferTreeNodes) {
vector.elementAt(k[0]).draw((Graphics2D) graphics2D);
} else {
graphics2D.setColor(Color.GREEN);
((Graphics2D) graphics2D).fill(vector.elementAt(k[0]));
graphics2D.setColor(Color.BLACK);
((Graphics2D) graphics2D).drawString(vector.elementAt(k[0]).getNodeGraph().getName(), vector.elementAt(k[0]).getX1() + 15, vector.elementAt(k[0]).getY1() + 25);
}
k[0] += 1;
}
}
});
timer.start();
}
}
Do you think my usage of timers is bad ? Thanks for response. :)
When doing custom painting in Swing it is a good idea to subclass JPanel (it could be an anonymous class) and to store painting-related data in attributes somewhere accessible to the panel.
Your timer would not do any painting but rather manipulate the painting-related data. You should never attempt to do any painting on a graphics object outside of the EventDispatcherThread of Swing or outside of the paintComponent methods of JComponents. (refer to the documentation of these methods for further information)
Here is an example of how custom painting with a timer manipulating color could look like:
public static void main(String[] args) {
EventQueue.invokeLater(Example::new);
}
// this is the painting-related data that is being manipulated by the timer
private int currentColorIndex;
public Example() {
JFrame frame = new JFrame("Custom Painting");
frame.setSize(640, 480);
frame.setLocationRelativeTo(null);
Color[] allColors = {Color.RED, Color.BLUE, Color.GREEN,
Color.YELLOW, Color.ORANGE, Color.MAGENTA};
JPanel myCustomPanel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
// here the painting related data is being used by the custom JPanel implementation
g.setColor(allColors[currentColorIndex]);
g.fillRect(0, 0, getWidth(), getHeight());
}
};
frame.setContentPane(myCustomPanel);
Timer timer = new Timer(100, e -> {
// the timer does not use any graphics objects, etc, but rather manipulates our painting-related data
currentColorIndex = (currentColorIndex + 1) % allColors.length;
// whenever the painting-related data has changed we need to call repaint() on our custom JPanel implementation
myCustomPanel.repaint();
});
timer.setRepeats(true);
timer.start();
frame.setVisible(true);
}

.fillOval coordinates not updating during repaint()

I'm following along in a (dated) Java book, and this project is supposed to "animate" a circle moving across the screen. However, when the program is run, the circle remains in one spot. My code looks identical to the book's. Am I forgetting something? Am I calling repaint() at the wrong time?
public class Animation
{
JFrame f;
int x, y;
public static void main(String [] args)
{
Animation a = new Animation();
a.go();
}
public void go()
{
f=new JFrame();
myPanel p=new myPanel();
f.getContentPane().add(p);
f.setSize(300, 300);
f.setVisible(true);
for(int i=0; i<=50; i++)
{
p.repaint();
x++;
y++;
}
}
class myPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(x, y, 40, 40);
}
}
}
So, immediately, two things jump out at me.
It's possible that the loop which is updating the values is NOT running on the Event Dispatching Thread, which could be leading to dirty read/writes
repaint can consolidate requests to reduce the amount of work placed on the Event Queue/Event Dispatching Thread. Since there is not "artificial" delay in the loop, it's possible that all your requests are been reduced to a single update pass by the RepaintManager
The first thing I would do is isolate the responsibility for the management of the position of the oval, because in your current code, it could be updated from anywhere, which is just a mess
class MyPanel extends JPanel {
private Point posy = new Point(0, 0);
public Point getPosy() {
return posy;
}
public void move() {
Point posy = getPosy();
posy.x++;
posy.y++;
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(posy.x, posy.y, 40, 40);
}
}
Next, I'd ensure that the context of the UI is been modified from within the EDT...
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Animation a = new Animation();
a.go();
}
});
}
and finally, I'd make use of Swing Timer to act as a pseudo loop for the animation...
public void go() {
f = new JFrame();
MyPanel p = new MyPanel();
f.getContentPane().add(p);
f.setSize(300, 300);
f.setVisible(true);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
p.move();
}
});
timer.start();
}
There are a number of reasons for using a Swing Timer in this context. First, the "ticks" are executed ON the EDT, making it safe to update the UI from within and it won't block the UI while it's "waiting" between ticks
I would recommend having a look at:
Painting in AWT and Swing which will give you a better understanding into how painting actually works
Concurrency in Swing as it will give you a heads up into some of the pitfalls in trying to do more then one thing within the GUI
How to Use Swing Timers because they are really useful for this kind of thing

I'm trying to move an object but i want it to leave a trail

So basically it won't leave a trail. I tried to remove super.paint and i've tried to make multiple but it either creates an error or it doesn't. I've gone throught it atleast 10 times which is why i went here. Thanks in advance!
import javax.swing.*;
import java.awt.*;
public class Grafik extends JPanel {
private int x = 0;
private void moveBall()
{
x += 1;
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillRect(x, 50, 20, 80);
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
Grafik grafik = new Grafik();
frame.setSize(700, 800);
frame.setLocation(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(grafik);
frame.setTitle("Mitt spel");
frame.setResizable(false);
frame.setVisible(true);
while(true)
{
grafik.repaint();
grafik.moveBall();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Problems/Solutions:
Swing painting is done in the paintComponent method override, not the paint method.
Be sure to call the `super's paintComponent method first thing in your own override method, so the JPanel can do house-keeping painting
Your animation loop should be a Swing Timer, not a while (true) loop as the latter risks running afoul of Swing threading rules
If you want to create an animation but leave persisting images within the drawing then
Either create an ArrayList of objects, perhaps Points, that represent the trail, and in your paintComponent method draw the trail using a for loop that iterates through the ArrayList, or
use a BufferedImage as a background image, one that is drawn within the paintComponent method immediately after calling super.paintComponent(g) but before drawing the moving sprint. Draw your trail into this BufferedImage by getting a Graphics object out of it, by calling getGraphics() on the BufferedImage.

Java swing drawing graphics nullpointer?

I want do draw a rectangle in my JFrame window, but I'am always getting a nullpointer error..
Why is it happening? what is the best (correct) way to draw graphics like rectangles, gradients, etc or something like falling snow in swing?
This is the Exception:
Exception in thread "Thread-0" java.lang.NullPointerException
at gui.Window.run(Window.java:24)
at gui.Window$1.run(Window.java:34)
at java.lang.Thread.run(Unknown Source)
And source:
public class Window extends JFrame implements Runnable {
private boolean run = true;
public Window() {
super.setSize(500, 500);
super.setTitle("MY GUI");
super.setDefaultCloseOperation(EXIT_ON_CLOSE);
super.setContentPane(new Container());
}
#Override
public void run() {
Graphics g = super.getContentPane().getGraphics();
while (this.run) {
g.setColor(new Color(0, 0, 0, 255));
g.fillRect(0, 0, 200, 200);
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
#Override
public void run() {
Window window = new Window();
window.run();
}
}).start();
}
}
Error line 24: g.setColor(new Color(0, 0, 0, 255));
Why is it doing that?
The code you posted makes no sense.
First of all, every interaction with Swing components (except calls to repaint()) must be done in the event dispatch thread.
Second, there is no point in running an infinite loop that would constantly paint the same thing on a Graphics.
Third, that's not how it works. You can't get the Graphics associated to a component and paint on it. Instead, you must override the paintComponent(Graphics) method of a Swing component, wait for swing to call this method, and use the provided Graphics argument to paint whatever you want. If you want to change what is being painted, then you need to call repaint() on this element. Don't do that with JFrame. Create a subclass of JComponent or JPanel, and add an instance of the subclass to a JFrame, and then make this JFrame visible:
public class CustomComponent extends JComponent {
#Override
public void paintComponent(Graphics g) {
// paint here
}
#Override
public Dimension getPreferredSize() {
// return preferred size here
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.add(new CustomComponent());
f.pack();
f.setVisible(true);
}
});
}
}
getGraphics will return null if the component is not visible.
To make your Window visible you have to call setVisible(bool).
You also have to be careful using threads with Swing.

JFrame image display at frame resize

I have this JFrame containing a children of JPanel wherein it displays the image which is declared in this manner.
BufferedImage image = ImageIO.read(filename);
The program displays the image properly. But the only thing is, it requires to resize the frame to display the image.
Is there a possible way to display the image once the frame appears?
You should override paintComponent(Graphics g) and draw the image therein. In this case, you should do this for the JPanel component (I think? If not, do this for the JComponent(s) you're referring to). Also, since Swing is not thread-safe, ensure these modifications are performed in the EDT.
EXAMPLE
public class Demo{
private static BufferedImage bi;
public static void main(String[] args){
try{
loadImage();
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
createAndShowGUI();
}
});
}
catch (IOException e){
// handle exception
}
}
private static void loadImage() throws IOException{
bi = ImageIO.read(new File("src/resource/braveheart.PNG"));
}
private static void createAndShowGUI(){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel(){
#Override
protected void paintComponent(Graphics g){
Graphics g2 = g.create();
g2.drawImage(bi, 0, 0, getWidth(), getHeight(), null);
g2.dispose();
}
#Override
public Dimension getPreferredSize(){
return new Dimension(bi.getWidth(), bi.getHeight());
}
};
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
OUTPUT
It's important to keep in mind that this example ignores rendering hints, so when you maximize the JFrame, the image quality will be very poor. :)
EDIT
When answering this question, I assumed you had a basic understanding of Swing. I suppose I assumed too much. It is important to mention that all components should be added to the top-level container before it's been realized (i.e. made visible). This will ensure that everything is rendered without having to resize your frame. As others have suggested, you could have simply used a JLabel to render the image, and then added it to your JPanel. Instead, I promoted custom painting, which is perfectly acceptable, and to me, cleaner.
for dispaly Image or ImageIcon is better look for JLabel (basic stuff)

Categories