JPanel's BufferedImage Comes Then Goes
Sometimes it correctly displays my BufferedImage drawing, but if you run the program a few times, it ceases to display anything.
I've vastly simplified the compilable code below to simply draw a single rectangle into a BufferedImage contained by a JPanel. Usually, but not always, a rectangle will appear as planned, but if you run the program a few times things begin to go awry; a rectangle will appear for only a millisecond then disappear, or not appear at all in the first place. Please help me find the problem there.
Also, I can't get my listeners to do anything at all. In the Action menu, "Re-run Program" is supposed to discard the first rectangle and draw another one. The "Paint On This Image" selection should keep the first rectangle while drawing another randomly placed rectangle on, or near, the first one.
Here's the stripped down, compilable, runnable code:
Code Java:
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
public class ArtMaker extends JFrame
{
private static final int WIDTH = 600;
private static final int HEIGHT = 600;
private JMenuBar mBar;
private JMenu menu1;
private JMenuItem menuItem1;
private JMenuItem menuItem2;
private PixelPainter panel;
private ArtMaker artMaker;
public ArtMaker()
{
setTitle("ArtMaker (First Experiments)");
setSize(WIDTH, HEIGHT);
setResizable(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
createContents();
panel = new PixelPainter();
add(panel);
validate();
setVisible(true);
}
//***************************************************************
private void createContents()
{
mBar = new JMenuBar();
menu1 = new JMenu("Actions");
menuItem1 = new JMenuItem("Re-run Program");
menuItem2 = new JMenuItem("Paint On This Image");
//these two listeners don't hear so well
// when items are chosen in the "Actions" menu
menuItem1.addActionListener(new RunListener());
menuItem2.addActionListener(new RunListener());
menu1.add(menuItem1);
menu1.add(menuItem2);
mBar.add(menu1);
setJMenuBar(mBar);
}
//***************************************************************
// these two listeners don't work when items are chosen in the
// "Actions" menu
private class RunListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == menuItem1)
{
panel.repaint();
}
else if (e.getSource() == menuItem2)
{
panel.paintAgain();
}
}
}
//***************************************************************
public static void main(String[] args)
{
System.setProperty("apple.laf.useScreenMenuBar", "true");
new ArtMaker();
}
} // end class ArtMaker
Then here's the class with the paintComponent():
Code Java:
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class PixelPainter extends JPanel
{
private static final int WIDTH = 600;
private static final int HEIGHT = 600;
private static int[] colors = {0, 0, 64, 0, 128, 0, 0, 0};
private static Color black = new Color(0, 0, 0);
private static Color midGray = new Color(128, 128, 128);
private static Color white = new Color(255, 255, 255);
private static Color ltGray = new Color(192, 192, 192);
private static Color dkGray = new Color(64, 64, 64);
private static Color grayWhite = new Color(223, 223, 223);
private static Color[] color = {ltGray, dkGray, black, midGray,
black, dkGray};
private static BufferedImage art;
//***************************************************************
public PixelPainter()
{
repaint();
}
//***************************************************************
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (art == null)
{
int x = 290;
int y = 290;
int w = this.getWidth();
int h = this.getHeight();
art = (BufferedImage)(this.createImage(w,h));
Graphics2D cg = art.createGraphics();
g2.setColor(colorChooser());
g2.drawRect(x, y, ((int)(Math.random() * 300)),
((int)(Math.random() * 300)));
cg.drawImage(art, null, 0, 0);
}
}
//***************************************************************
// This method is my rookie attempt to draw a new rectangle on top of the
// the first one whenever the "Paint On This Image" is chosen from the
// Action menu. But my listeners aren't working, and this method probably
// isn't my best bet, either.
public void paintAgain()
{
Graphics2D g2d = art.createGraphics();
if (art != null)
{
int x = 250;
int y = 250;
g2d.setColor(colorChooser());
g2d.drawRect(x, y, ((int)(Math.random() * 300)),
((int)(Math.random() * 300)));
g2d.drawImage(art, null, 0, 0);
}
}
//***************************************************************
public static Color colorChooser()
{
int randomPicker = ((int)(Math.random() * 6));
Color colorChoice = color[randomPicker];
return colorChoice;
} // end colorChooser
} // end class PixelPainter
Any suggestions would be greatly appreciated!
Re: JPanel's BufferedImage Comes Then Goes
You appear to be trying to draw the BufferedImage, art, into itself. Why?
Also, you should be drawing with the BufferedImage's Graphics object outside of the paintComponent method, and you should always dispose of this Graphics object when done with it. You only draw the BufferedImage into the JPanel if art is null to begin with -- why?
Consider drawing the BufferedImage in the paintAgain method, and calling that method in the class's constructor. Consider *always* drawing the BufferedImage in the JPanel's paintComponent method unless it is null. If it is null, don't create it in paintComponent.
--- Update ---
Other suggestions:
- If this were my program, I'd move my size constants into the JPanel and use them in my PixelPainter's public getPreferredSize() method override.
- I'd call paintAgain in my PixelPainter's constructor and would not call repaint there.
- My paintComponent method would draw the art image if it is not null, but wouldn't do anything else (at this point).
- I'd make art a non-static variable.
- paintAgain could create the art BufferedImage in a lazy way (create it if its null), and would draw the random rectangles.
- I would dispose of g2d when done with it in this method.
- I would call repaint() at the bottom of this method.
- My JFrame would not have its size set.
- Rather I'd call pack() on the JFrame after adding the PixelPainter JPanel and before setting it visible.
Re: JPanel's BufferedImage Comes Then Goes
Your always a big help, Curmudgeon. Thanks again.
To answer your "why's": Because for me there doesn't seem to be the kind of impressive online tutoring documentation that effectively articulates the countless why's and how's and when's necessary to have a total aha moment about Java image manipulation. Like most noobs, I try to take a gazillion murky explanations and stitch them together.
Your taking the time to assess my messes is a helpful step forward.
I'll try your suggestions.
Re: JPanel's BufferedImage Comes Then Goes
@Curmudgeon -
While implementing your suggestions I ran into this problem: the compiler won't let me call paintAgain() from the constructor. Here's the error message:
./PixelPainter.java:26: paintAgain(java.awt.Graphics) in PixelPainter cannot be applied to ()
paintAgain();
^
Why? My new code:
Code Java:
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
public class ArtMaker extends JFrame
{
public PixelPainter panel;
private JMenuBar mBar;
private JMenu menu1;
private JMenuItem menuItem1;
private JMenuItem menuItem2;
public ArtMaker()
{
setTitle("ArtMaker (First Experiments)");
setResizable(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
createContents();
panel = new PixelPainter();
add(panel);
pack();
validate();
setVisible(true);
}
//***************************************************************
private void createContents()
{
mBar = new JMenuBar();
menu1 = new JMenu("Actions");
menuItem1 = new JMenuItem("Re-run Program");
menuItem2 = new JMenuItem("Paint On This Image");
//these two listeners don't hear so well
// when items are chosen in the "Actions" menu
menuItem1.addActionListener(new RunListener());
menuItem2.addActionListener(new RunListener());
menu1.add(menuItem1);
menu1.add(menuItem2);
mBar.add(menu1);
setJMenuBar(mBar);
}
//***************************************************************
// these two listeners don't work when items are chosen in the
// "Actions" menu
private class RunListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
panel.repaint();
}
}
//***************************************************************
public static void main(String[] args)
{
System.setProperty("apple.laf.useScreenMenuBar", "true");
new ArtMaker();
}
} // end class ArtMaker
...and the JPanel:
Code Java:
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class PixelPainter extends JPanel
{
private static final int WIDTH = 600;
private static final int HEIGHT = 600;
private static int[] colors = {0, 0, 64, 0, 128, 0, 0, 0};
private static Color black = new Color(0, 0, 0);
private static Color midGray = new Color(128, 128, 128);
private static Color white = new Color(255, 255, 255);
private static Color ltGray = new Color(192, 192, 192);
private static Color dkGray = new Color(64, 64, 64);
private static Color grayWhite = new Color(223, 223, 223);
private static Color[] color = {ltGray, dkGray, black, midGray,
black, dkGray};
private BufferedImage art = new BufferedImage(WIDTH, HEIGHT,
BufferedImage.TYPE_INT_RGB);
//***************************************************************
public PixelPainter()
{
paintAgain();
}
//***************************************************************
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (art != null)
{
g2.drawImage(art, null, 0, 0);
}
g2.dispose();
} // end paintComponent()
//***************************************************************
//
public void paintAgain(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
if (art == null)
{
g2 = art.createGraphics();
int x = 250;
int y = 250;
g2.setColor(colorChooser());
g2.drawRect(x, y, ((int)(Math.random() * 300)),
((int)(Math.random() * 300)));
}
g2.dispose();
repaint();
} // end paintAgain()
//***************************************************************
public static Color colorChooser()
{
int randomPicker = ((int)(Math.random() * 6));
Color colorChoice = color[randomPicker];
return colorChoice;
} // end colorChooser
} // end class PixelPainter
Until I get it to compile I can't even try the new paintAgain() method to see if it works.
Re: JPanel's BufferedImage Comes Then Goes
The paintAgain() method takes a Graphics object for an arg. There is no Graphics object available in the constructor. The paintComponent() method has one. Try calling it from inside the paintComponent method.
Why is there a paintAgain() method? Why not do all the drawing from the paintComponent() method?
Re: JPanel's BufferedImage Comes Then Goes
Quote:
Why is there a paintAgain() method? Why not do all the drawing from the paintComponent() method?
Two reasons:
1) Because Curmudgeon suggested I take all of my drawing out of paintComponent. Here's what he said:
Quote:
Also, you should be drawing with the BufferedImage's Graphics object outside of the paintComponent method, and you should always dispose of this Graphics object when done with it.
Please see his post above. And...
2) Because later I want to be able to call another draw method from a menu selection that draws something else on top of the existing drawing. Can't have two paintComponents(), so I'll need another draw method for that.
By the way, adding a Graphics argument to the paintAgain() call in the constructor, like this - paintAgain(g), doesn't compile either. Here's the error message:
./PixelPainter.java:26: cannot find symbol
symbol : variable g
location: class PixelPainter
paintAgain(g);
^
Re: JPanel's BufferedImage Comes Then Goes
Quote:
cannot find symbol
symbol : variable g
Where is the variable: g defined that is in scope (within the same pair of {}s) as where it is being used?
It looks like you are trying to write code without understanding what the different parts of the code are supposed to do. I'll leave the design to you and Curmudgeon.
Re: JPanel's BufferedImage Comes Then Goes
That's not true at all. I'm just new to drawing and I don't understand how to get the BufferedImage "art" into a graphics context outside of paintComponent(). I've tried getGraphics() for a Graphics context and createGraphics() for a Graphics2D context. One tutorial suggested the using Graphics argument (yes, outside of paintComponent) as you can see above. Those two methods are not difficult to use. But it's not working here for some unknown reason. So I've turned to the forum for some insights.
How can I get my BufferedImage art into a Graphics context so I can draw into it, so I can draw it onto my JPanel?
Re: JPanel's BufferedImage Comes Then Goes
I based my statement on the above code. It looks like it was coded without defining a variable g and giving that variable a value.
Why does the paintAgain() method need an arg? It looks like it sets the value of g2 itself and never uses what is passed to it.
Re: JPanel's BufferedImage Comes Then Goes
It uses its arg the same way paintComponent does. It immediately converts the Graphics context to a Graphics2D in order to draw into the BufferedImage.
As far as paintAgain(g) is concerned, having that un-declared Graphics variable was a last-ditch, desperation attempt to get a Graphics context outside of paintComponent() - to see if, per outside chance, imitating paintComponent() might work. Nothing else worked.
Re: JPanel's BufferedImage Comes Then Goes
Try removing the Graphics parameter for the paintAgain() method since it is never used.
Re: JPanel's BufferedImage Comes Then Goes
I've tried that. Without that g arg, the code looks fine. It should work. That's what's so weird.
I'll make it work. Thanks for your time.
Re: JPanel's BufferedImage Comes Then Goes
Regarding my design recommendations: I figured that the paintAgain method should be where you create your randomized parameters including the rectangle sizes and colors, that would be used in the paintComponent(...) method. This paintAgain method could be called by some actionPerformed method and again would create new random values.
You could draw directly in the paintAgain method if you are drawing on a BufferedImage object, and if so, then the paintAgain() method would extract the Graphics2D object from the BufferedImage, draw with it, then dispose it, then call repaint() which suggests the JVM to call the paint and paintComponent methods. In the paintComponent method you would draw the BufferedImage with the Graphics object supplied by the JVM and you would not dispose of this.
If on the other hand you desire to draw directly in paintComponent, then there would be no need for a BufferedIamge, and you would change the random fields in paintAgain() call repaint() and the paintComponent method would use those fields to draw the rectangles.
Re: JPanel's BufferedImage Comes Then Goes
Check the code in paintAgain(). Is this what you want:
Re: JPanel's BufferedImage Comes Then Goes
I like that plan. I think that, and several pieces of pizza, ought to work. You're a titan, Curmudgeon.
Norm - Here, removing that code doesn't improve or detract. The code I posted here is just a stripped down version of my program for posting sake.
Re: JPanel's BufferedImage Comes Then Goes
Quote:
The code I posted here is just a stripped down version of my program for posting sake.
Its a waste of time posting code that does not compile, execute and show the problem.
The posted code would not show the problem. It would get a NPE.
Re: JPanel's BufferedImage Comes Then Goes
Norm - I did post complete code that compiles. Look at the beginning of this thread. But after I made changes suggested to me, I posted the new code because now it wouldn't compile - to get help figuring out why it wouldn't compile. All the code I posted was more than ample for the problems I was experiencing. Even better was the fact that I took the time to strip it down and isolate relevant code. To assume I omitted relevant code would be mistaken.
1 Attachment(s)
Re: JPanel's BufferedImage Comes Then Goes
Making the changes I suggested I get this image:
Re: JPanel's BufferedImage Comes Then Goes
Very good!
My bad --- I misread your suggestion. You correctly said:
Quote:
Try calling it from inside the paintComponent method.
But I misread that as saying
Quote:
Try drawing inside paintComponent instead of paintAgain.
Moving my paintAgain call into paintComponent was the right call. I can't thank you enough, Norm!!!