Related
I'll start off by telling you what Im trying to do if thats OK, as Im not certain the route Im struggling with is even the best way of achieving my ends.
I have a JFrame containing two JPanels. One contains a number of buttons (buttonPanel), the other is, initially, blank (displayPane). When buttons are pressed the stuff shown in displayPanel changes. The way this is working is each press of a button creates a new object that extends JPanel and then adds that to displayPane
So all the above is working fine and dandy (although I freely admit it may not be the best way of doing it) except for one particular case.
In this particular case I need to create a JLayeredPanel and then draw a clipped image on it. JLayeredPanel because I want to draw some stuff on top of it, clipped because I only want to show part of the area (which exact part is passed to the constructor).
Now, the problem Im having is this. The only way I know to draw a clipped image is through g=thingie.getGraphics(), g.setClip(Shape shape), g.drawImage(various). However, all of that relies on being able to get graphics. But because I am assembling the object first there is no graphics object associated with the JLayeredPane (because its not displayed) so getGraphics is returning null and g.setClip() is throwing a Null Pointer Exception.
Obviously I am doing this wrong somehow and somewhere. Any help would be appreciated, sorry if the question is confusing. I tried to include as much detail as possible and now I am a little concerned I've muddied the issue. I'll keep an eye on this and clarify if required.
Warning!: Wrong answer, see below the line
Why don't you just create a new Graphics object, paint on it and then use it with the update() method?
Graphics g = new Graphics();
g.drawStuff();
thingie.update(g);
This showd be correct
As stated on the comments the previous solution was wrong but it can be done with an Double buffer, create a buffered image and draw on it, then override the paint method of the jLayeredPane pane to draw the image.
private void addStuff() {
BufferedImage bi =
new BufferedImage(100, 100, BufferedImage.TYPE_4BYTE_ABGR);
Graphics bufferedGraphics = bi.getGraphics();
//Paint stuff
bufferedGraphics.drawLine(0, 0, 50, 50);
javax.swing.JLayeredPane layered;
layered = new JLayeredPane() {
#Override
public void paint(Graphics g) {
g.drawImage(bi, 0, 0, null);
}
};
this.add(layered);
this.validate();
this.repaint();
}
Right now I'm working on a program that throws up a bunch of separate (generated at runtime) images, each in their own window. To do this i've tried this approach:
public void display(){
JFrame window = new JFrame("NetPart");
JPanel canvas = new JPanel();
window.getContentPane().add(canvas);
Graphics g = canvas.getGraphics();
Dimension d = getSize();
System.out.println(d);
draw(g,new Point(d.minX*50,d.maxY*50), 50);
window.setSize(d.size(50));
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
window.setVisible(true);
}
public void draw(Graphics g, Point startLoc, int scale){
// generate and draw the image
}
public Dimension getSize(){
//returns my own dimensions class
}
However, this throws a NullPointerException in draw, claiming that the graphics is null. is there any way to externally draw to a JPanel from outside it (not inherit from JPanel and override PaintComponent)? Any help would be appreciated.
If you are drawing your images at runtime you should use BufferedImage.
Create a BufferedImage, call getGraphics() on it to get its Graphics object, draw to it using the Graphics(2D) api, then call Graphics.dispose() (not strictly necessary but a good habit) and as the previous poster suggested either create an ImageIcon with it and put in a JLabel using setIcon() or subclass JPanel / JComponent and draw it in paintComponent
You're finding out that getGraphics() won't work in this way since the Graphics object obtained is null prior to the component being rendered, and even when it is not null, it is not stable and becomes invalid with each redraw. Possible options include:
Creating ImageIcon from each Image, placing the icon in a JLabel and displaying the JLabel.
Biting the bullet and extending JPanel or other JComponent (you don't say why you're trying to avoid this) and displaying the image in the JPanel's paintComponent method.
Doing the above, but first creating a BufferedImage which is displayed in the paintComponent(...) method. Then you can modify the BufferedImage during the program's run if you need to change the display, add new images,...
a program that throws up a bunch of separate (generated at runtime) images,
each in their own window
don't do it that this way, don't create a lots of JFrames, these Object stays in the memory, until current JVM instance exists, result from this concept pretty could be OutOfMemory exceptions
don't create lots of JFrames, create only one JFrame, rest of Containers could be JDialog or JWindow
don't create a new JFrames, JDialogs or JWindows on the runtime, re_use the existing Containers
put these images as Icons to the JList or maybe better would be look at CardLayout
Following code work for visualizing a molecule on a JPanel except it does not rescale when I change the size of JPanel at runtime. The Chemistry Development Kit is used for generating rendered Image. A molecule is passed to MoleculeViewer for visualizing. What am I doing wrong??
What am I doing wrong??
why you needed to setSize(new java.awt.Dimension(400, 400));
put your image = new BufferedImage(this.WIDTH, this.HEIGHT, BufferedImage.TYPE_INT_RGB); as Icon to the JLabel, then you can remove anything about paintComponent()
then you can return JLabel instead of JPanel, but JLabel is translucent by default, or put JLabel to the JPanel by using proper LayoutManager in this case BorderLayout.CENTER
you have to check how way you added MoleculeViewer, what LayoutManager is there used???, because only usage of LayoutManager can this job correctly to resize (or not resize) Container's childs with Top-Level Container,
MoleculeViewer must retuns PreferredSize for its parent
Adding following resolved the not redrawing upon scaling problem
renderer.paint(molecule_, new AWTDrawVisitor(g2), new Rectangle2D.Double(0, 0, w, h), false);
g2.dispose();
}
else
So here's the deal:
I've got a JPanel and a JTextArea inside that one. The JPanel has a background color which has an alpha value of 200, ie. you can paritally see the background image through the JPanel. Ain't that called partial transparency? Anyway, then I've set the JTextArea non-opaque, so that I can fully see through that one:
JPanel p = new JPanel();
p.setBackground(new Color(237, 234, 215, 200);
JTextArea jta = new JTextArea("Blahblahblahblah");
jta.setOpaque(false);
p.add(jta);
Ok, so when I hit a button, the text will be changed like this:
jta.setText("new BlahBlah...");
Then it happens: the first text remains back there with a new partially transparent film on it. And the text I added, comes on top of course, but right there behind you can see the previous one. When I change the text some few times more, the ghost disappears.
The screenshot as a link.
Translation (the screenshot has 3 shots on a row): Left one: "Question # 1 out of 8: (a political question)? (My comment: OK)"Center: "Question # 2 out of 8: (another question, never mind)? (My comment: The ghost is there! [and the arrow pointing it out])"Right-hand side: "Question # 8 out of 8: (another question)? (My comment: OK)"
If I reduce the amount of questions this program swaps, from 8 to 3, for example, the last one looks usually good, the ghost is gone. But sometimes it sticks on no matter what I do. Could it possibly have something to do with not enough memory (I hardly swallow that, though)?
So, please help me out, dudes! I've got the deadline in 48 hrs.
PS. In case you wonder, that language is Finnish. I'm working on a school project: It's gonna be a comparing machine that can have a variety of applications: For example, if you have a public election coming, the press/media may use this kind of machine on the web to get the candidate's opinions on whatever issues, and then voters may enter their opinions too, and the machine calculates which candidates match the voter's thoughts best. See my homepage, ie. the project blog (Finnish) for more screenshots in case you're interested.
an55i
p.setBackground(new Color(237, 234, 215, 200);
Swing does not support transparent backgrounds.
Swing expects a component to be either:
opaque - which implies the component will repaint the entire background with an opaque color first before doing custom painting, or
fully transparent - in which case Swing will first paint the background of the first opaque parent component before doing custom painting.
The setOpaque(...) method is used to control the opaque property of a component.
In either case this makes sure any painting artifacts are removed and custom painting can be done properly.
If you want to use tranparency, then you need to do custom painting yourself to make sure the background is cleared.
The custom painting for the panel would be:
JPanel panel = new JPanel()
{
protected void paintComponent(Graphics g)
{
g.setColor( getBackground() );
g.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
};
panel.setOpaque(false); // background of parent will be painted first
Similar code would be required for every component that uses transparency.
Or, you can check out Background With Transparency for custom class that can be used on any component that will do the above work for you.
Finally I got it showing up right! It's just that:
Both the container (p in this case) and the component (jta) inside must be setOpaque(false);
The container must always clear itself by grabbing the inner component's background color, which - actually - is the color that the inner component kind of gets from it's ancestor, ie. the container itself.
Aren't I somehow correct? Well, at least the code does it's trick.
Class fields:
private JTextArea jta;
private JPanel p;
Later on:
jta = new JTextArea("BlahBlahBLAH");
jta.setBackground(new Color(237, 234, 215, 200));
p = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
if(getContentPane().isAncestorOf(jta)) {
g.setColor(jta.getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
}
};
p.setOpaque(false);
jta.setOpaque(false);
p.add(ta);
And that's it. I even put some Borders and stuff before adding "jta" to "p". And what's coolest, I actually learnt something about this. Thanks for support!
I have a JPanel to which I'd like to add JPEG and PNG images that I generate on the fly.
All the examples I've seen so far in the Swing Tutorials, specially in the Swing examples use ImageIcons.
I'm generating these images as byte arrays, and they are usually larger than the common icon they use in the examples, at 640x480.
Is there any (performance or other) problem in using the ImageIcon class to display an image that size in a JPanel?
What's the usual way of doing it?
How to add an image to a JPanel without using the ImageIcon class?
Edit: A more careful examination of the tutorials and the API shows that you cannot add an ImageIcon directly to a JPanel. Instead, they achieve the same effect by setting the image as an icon of a JLabel. This just doesn't feel right...
If you are using JPanels, then are probably working with Swing. Try this:
BufferedImage myPicture = ImageIO.read(new File("path-to-file"));
JLabel picLabel = new JLabel(new ImageIcon(myPicture));
add(picLabel);
The image is now a swing component. It becomes subject to layout conditions like any other component.
Here's how I do it (with a little more info on how to load an image):
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class ImagePanel extends JPanel{
private BufferedImage image;
public ImagePanel() {
try {
image = ImageIO.read(new File("image name and path"));
} catch (IOException ex) {
// handle exception...
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this); // see javadoc for more info on the parameters
}
}
Fred Haslam's way works fine. I had trouble with the filepath though, since I want to reference an image within my jar. To do this, I used:
BufferedImage wPic = ImageIO.read(this.getClass().getResource("snow.png"));
JLabel wIcon = new JLabel(new ImageIcon(wPic));
Since I only have a finite number (about 10) images that I need to load using this method, it works quite well. It gets file without having to have the correct relative filepath.
I think there is no need to subclass of anything. Just use a Jlabel. You can set an image into a Jlabel. So, resize the Jlabel then fill it with an image. Its OK. This is the way I do.
You can avoid rolling your own Component subclass completely by using the JXImagePanel class from the free SwingX libraries.
Download
JLabel imgLabel = new JLabel(new ImageIcon("path_to_image.png"));
You can subclass JPanel - here is an extract from my ImagePanel, which puts an image in any one of 5 locations, top/left, top/right, middle/middle, bottom/left or bottom/right:
protected void paintComponent(Graphics gc) {
super.paintComponent(gc);
Dimension cs=getSize(); // component size
gc=gc.create();
gc.clipRect(insets.left,insets.top,(cs.width-insets.left-insets.right),(cs.height-insets.top-insets.bottom));
if(mmImage!=null) { gc.drawImage(mmImage,(((cs.width-mmSize.width)/2) +mmHrzShift),(((cs.height-mmSize.height)/2) +mmVrtShift),null); }
if(tlImage!=null) { gc.drawImage(tlImage,(insets.left +tlHrzShift),(insets.top +tlVrtShift),null); }
if(trImage!=null) { gc.drawImage(trImage,(cs.width-insets.right-trSize.width+trHrzShift),(insets.top +trVrtShift),null); }
if(blImage!=null) { gc.drawImage(blImage,(insets.left +blHrzShift),(cs.height-insets.bottom-blSize.height+blVrtShift),null); }
if(brImage!=null) { gc.drawImage(brImage,(cs.width-insets.right-brSize.width+brHrzShift),(cs.height-insets.bottom-brSize.height+brVrtShift),null); }
}
There shouldn't be any problem (other than any general problems you might have with very large images).
If you're talking about adding multiple images to a single panel, I would use ImageIcons. For a single image, I would think about making a custom subclass of JPanel and overriding its paintComponent method to draw the image.
(see 2)
JPanel is almost always the wrong class to subclass. Why wouldn't you subclass JComponent?
There is a slight problem with ImageIcon in that the constructor blocks reading the image. Not really a problem when loading from the application jar, but maybe if you're potentially reading over a network connection. There's plenty of AWT-era examples of using MediaTracker, ImageObserver and friends, even in the JDK demos.
I'm doing something very similar in a private project I'm working on. Thus far I've generated images up to 1024x1024 without any problems (except memory) and can display them very quickly and without any performance problems.
Overriding the paint method of JPanel subclass is overkill and requires more work than you need to do.
The way I do it is:
Class MapIcon implements Icon {...}
OR
Class MapIcon extends ImageIcon {...}
The code you use to generate the image will be in this class. I use a BufferedImage to draw onto then when the paintIcon() is called, use g.drawImvge(bufferedImage); This reduces the amount of flashing done while you generate your images, and you can thread it.
Next I extend JLabel:
Class MapLabel extends Scrollable, MouseMotionListener {...}
This is because I want to put my image on a scroll pane, I.e. display part of the image and have the user scroll around as needed.
So then I use a JScrollPane to hold the MapLabel, which contains only the MapIcon.
MapIcon map = new MapIcon ();
MapLabel mapLabel = new MapLabel (map);
JScrollPane scrollPane = new JScrollPane();
scrollPane.getViewport ().add (mapLabel);
But for your scenario (just show the whole image every time). You need to add the MapLabel to the top JPanel, and make sure to size them all to the full size of the image (by overriding the GetPreferredSize()).
This answer is a complement to #shawalli's answer...
I wanted to reference an image within my jar too, but instead of having a BufferedImage, I simple did this:
JPanel jPanel = new JPanel();
jPanel.add(new JLabel(new ImageIcon(getClass().getClassLoader().getResource("resource/images/polygon.jpg"))));
Create a source folder in your project directory, in this case I called it Images.
JFrame snakeFrame = new JFrame();
snakeFrame.setBounds(100, 200, 800, 800);
snakeFrame.setVisible(true);
snakeFrame.add(new JLabel(new ImageIcon("Images/Snake.png")));
snakeFrame.pack();
You can avoid using own Components and SwingX library and ImageIO class:
File f = new File("hello.jpg");
JLabel imgLabel = new JLabel(new ImageIcon(file.getName()));
I can see many answers, not really addressing the three questions of the OP.
1) A word on performance: byte arrays are likely unefficient unless you can use an exact pixel byte ordering which matches to your display adapters current resolution and color depth.
To achieve the best drawing performance, simply convert your image to a BufferedImage which is generated with a type corresponding to your current graphics configuration. See createCompatibleImage at https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html
These images will be automatically cached on the display card memory after drawing a few times without any programming effort (this is standard in Swing since Java 6), and therefore the actual drawing will take negligible amount of time - if you did not change the image.
Altering the image will come with an additional memory transfer between main memory and GPU memory - which is slow. Avoid "redrawing" the image into a BufferedImage therefore, avoid doing getPixel and setPixel at all means.
For example, if you are developing a game, instead of drawing all the game actors to a BufferedImage and then to a JPanel, it is a lot faster to load all actors as smaller BufferedImages, and draw them one by one in your JPanel code at their proper position - this way there is no additional data transfer between the main memory and GPU memory except of the initial transfer of the images for caching.
ImageIcon will use a BufferedImage under the hood - but basically allocating a BufferedImage with the proper graphics mode is the key, and there is no effort to do this right.
2) The usual way of doing this is to draw a BufferedImage in an overridden paintComponent method of the JPanel. Although Java supports a good amount of additional goodies such as buffer chains controlling VolatileImages cached in the GPU memory, there is no need to use any of these since Java 6 which does a reasonably good job without exposing all of these details of GPU acceleration.
Note that GPU acceleration may not work for certain operations, such as stretching translucent images.
3) Do not add. Just paint it as mentioned above:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
"Adding" makes sense if the image is part of the layout. If you need this as a background or foreground image filling the JPanel, just draw in paintComponent. If you prefer brewing a generic Swing component which can show your image, then it is the same story (you may use a JComponent and override its paintComponent method) - and then add this to your layout of GUI components.
4) How to convert the array to a Bufferedimage
Converting your byte arrays to PNG, then loading it is quite resource intensive. A better way is to convert your existing byte array to a BufferedImage.
For that: do not use for loops and copy pixels. That is very very slow. Instead:
learn the preferred byte structure of the BufferedImage (nowadays it is safe to assume RGB or RGBA, which is 4 bytes per pixel)
learn the scanline and scansize in use (e.g. you might have a 142 pixels wide image - but in the real life that will be stored as a 256 pixel wide byte array since it is faster to process that and mask the unused pixes by the GPU hardware)
then once you have an array build according to these principles, the setRGB array method of the BufferedImage can copy your array to the BufferedImage.