getWidth() and getHeight() are 0 after calling setPreferredSize() - java

I've been working on my project for a few hours now, only to be constantly frustrated at this.
I have a parent JFrame that adds a JPanel to it, and it is going to be used for the rendering and display of my simulation I'm developing. There are no swing objects that are going to be added to the JPanel, as I will only be using it for rendering shapes using a graphics object.
My code is as such below:
public class SimulationPanel extends JPanel {
private BufferedImage junction;
private Graphics2D graphics;
public SimulationPanel() {
super();
initPanel();
}
private void initPanel() {
this.setPreferredSize(new Dimension(600, 600)); //TODO: bug with not sizing the junction correctly.
junction = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
graphics = junction.createGraphics();
setBackground(Color.white);
System.out.println(getWidth());
}
The code specifically breaks on the second line of the initPanel() method where I try to create a new BufferedImage.
The output from the exception states "Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Width (0) and height (0) must be > 0"
I'm really unsure on why this is. I've tried to use past answers from Stack Overflow but they were unsuccessful in helping.
This is my first post, so I hope it isn't too bad.
Thanks.

When you set the preferred size, you tell the various Java layout managers how you would like your panel to be laid out once it's added to a container. But until it actually is added to the container, it won't have a width or height, and even after it is, it might not have the width and height that you asked for.
One option is to just use 600 directly for the width and height of your new buffered image, and when you add the panel to the JFrame make sure you call pack() on the JFrame to allow the window to size to the preferred size of your panel.

Create your a BufferedImage cache inside your component's paintComponent method. There, you will know the actual size of the component and take that into account for the rendering. The image acts as a cache of your component's content, but you fail to take into account that its size is part of the cached information.
#Override protected void paintComponent(Graphics g) {
// create cache image if necessary
if (null == image ||
image.getWidth() != getWidth() ||
image.getHeight() != getHeight() ||) {
image = new BufferedImage(getWidth(), getHeight());
imageIsInvalid = true;
}
// render to cache if needed
if (imageIsInvalid()) {
renderToImage();
}
// redraw component from cache
// TODO take the clip into account
g.drawImage(image, 0, 0, null);
}

The reason this won't work is that pack() is not called (to set all the width and height values) until after the panel has been initiated which is why height and width are not set yet. And BufferedImage will throw an exception if the width or height are non-positive integers.
So why don't you just set the values yourself? Here is how to do it in your example:
private void initPanel() {
final int width = 600;
final int height = 600;
this.setPreferredSize(new Dimension(width, height));
junction = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
graphics = junction.createGraphics();
setBackground(Color.white);
}
Alternatively: If you have a requirement where the image has to be resized with the component then you need to. I'm pretty sure when pack() is called that it will fire the ComponentListener.componentResized() event so this should work when you initiate the component even if you don't resize the component. So instead do this in your code:
private void initPanel() {
this.setPreferredSize(new Dimension(600, 600));
this.addComponentListener(new ComponentListener() {
public void componentResized(ComponentEvent e) {
Component c = (Component) e.getSource();
Dimension d = c.getSize();
resizeImage(d);
}
});
this.setBackground(Color.white);
}
public void resizeImage(Dimension d) {
junction = new BufferedImage(d.getWidth(), d.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
graphics = junction.createGraphics();
}

Related

How can I display two JPanels with images on a JFrame, and both of the img to be visible?

I am trying to set as background an aquarium (which is a class that extends JPanel and contain the aquarium img), and on top a fish (which is also a class that extends a JPanel and contain the fish img).
The problem is that it shows only one image instead of fish in top of the aquarium (either aquarium, or fish depending on which one is added first to the JFrame).
Main
public class Core {
JFrame window;
JLabel label;
ImageIcon img;
Aquarium aquarium = new Aquarium();
JavaFish javaFish = new JavaFish();
public void start() {
window = new JFrame();
window.setPreferredSize(new Dimension(600, 400));
window.setVisible(true);
window.setTitle("Java Game");
aquarium.add(javaFish);
window.add(aquarium);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
Core c = new Core();
c.start();
}
}
Aquarium
public class Aquarium extends JPanel {
private BufferedImage img;
//Initiate aquarium width
public int width;
//Initiate aquarium height
public int height;
#Override
protected void paintComponent(Graphics g) {
width = getSize().width;
height = getSize().height;
try {
img = ImageIO.read(new File("img/AquariumBackground.png"));
} catch (IOException e) {
e.printStackTrace();
System.out.println("Image not fount!");
}
g.drawImage(img, 0, 0, width, height, this);
}
}
Fish
public class JavaFish extends JPanel {
BufferedImage img;
int xPos = 50;
int yPos = 50;
public JavaFish() {
this.setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
BufferedImage JavaFish = LoadImage("img/JavaFish.png");
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(JavaFish, xPos, yPos, 100, 100, null);
repaint();
}
BufferedImage LoadImage(String FileName) {
img = null;
try {
img = ImageIO.read(new File (FileName));
} catch (IOException e) {
e.printStackTrace();
}
return img;
}
}
The problem is that it shows only one image instead of fish in top of the aquarium (either aquarium, or fish depending on which one is added first to the JFrame).
By default a JPanel uses a FlowLayout which respects the preferred size of any component added to it.
By default a JFrame uses a BorderLayout and if you don't specify a constraint the component gets added to the CENTER of the BorderLayout, which means the component is automatically resized to fill the space of the frame.
So the component you add to the frame will be sized to fill the frame. The component you add to the panel will have a size of (0, 0) so there is nothing to paint.
So some custom painting tips:
Override the getPreferredSize() method of the panel to return the size of the image so the layout manager can do its job
Invoke super.paintComponent(..) as the first statement to make sure the background gets cleared.
Don't read the image in the paintComponent() method. This method can be called whenever Swing determines the component needs to be repainted, so it not efficient to keep reading the image. Instead the image should be read in the constructor of the class.
Don't invoke repaint() in a painting method. This will cause an infinite painting loop.
Also, components should be added to the frame BEFORE you make the frame visible.
Having said all of the above, Alerra's suggestion in the comment to paint both images in the same panel is a good idea. It simplifies the painting and you can even paint multiple fish easily by keeping an ArrayList of imgages that you want to paint. Then you would just paint the background and then iterates through the ArrayList to paint the individual fish.
Check out Custom Painting Approaches for a working example. The example only draws Rectangle, but the concept is the same.

How to put transparent JPanel over an opaque JPanel?

I have added webcam to my software using com.github.sarxos.webcam. It has a JPanel named WebcamPanel and has predefined webcam sizes while I need my custom size of pictures. I managed to crop the images taken from webcam at 640 x 480. I want to put a red rectangle over the WebcamPanel to show that this part of the image will be saved.
public class CardPanel {
Dimension panelDim = new Dimension(640, 480);
public Cardpanel(){
//....Button Defined earlier
btnTakePhoto.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
webcameFrame();
}
});
}
private void webcamFrame(){
imageFrame = new JFrame("Photo Capture");
// Did some calculations to put window at center
imageFrame.setBounds(screenSize.width / 2 - frameWidth / 2, screenSize.height / 2 - frameHeight / 2, frameWidth,
frameHeight);
imageFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
imageFrame.setContentPane(contentPane);
JPanel webcamWindow = new JPanel();
RedHighlighter redHighlighter = new RedHighlighter();
Webcam webcam = Webcam.getDefault();
webcam.setViewSize(WebcamResolution.VGA.getSize());
webcamPanel = new WebcamPanel(webcam);
webcamPanel.setFillArea(true);
webcamPanel.setMirrored(false);
webcamPanel.setPreferredSize(panelDim);
webcamWindow.add(webcamPanel);
webcamWindow.add(redHighlighter);
hBox.add(webcamWindow);
}
// Sub Class just for drawing the rectangle
public class RedHighlighter extends JPanel{
public RedHighlighter() {
// If you delete the following line, nothing will appear
setPreferredSize(new Dimension(400, 400));
}
#Override
public void paint(Graphics g) {
g.setColor(Color.RED);
g.drawRect(100, 100, 200, 200);
}
}
}
I used JLayeredPanes but no matter what you do it will cover whole size and will show only one item at a time.
Overriding paint method helped me draw the rectangle but it's on side and not on top.
As you can see the rectangle has pushed WebcamPanel towards left. I want webcamPanel to remain in it's position while the rectangle on top of it at center. Please suggest an efficient approach to this problem. Thanks!
The one JPanel is being pushed over due to the layout managers that you are using. If you want one JPanel to overly another, you'll want to consider using a JLayeredPane, with the video images in the lower level, perhaps the JLayeredPane.DEFAULT layer, and the drawing JPanel above it.
Other options and issues:
You could potentially draw in the same JPanel that the image is being displayed in by displaying the image in a paintComponent method as well as the drawing (in lines of code after the image is displayed.
Look into use of a JLayer as a way of adding a drawing "decoration" over your image.
Always override paintComponent, not paint
Always call the super's painting method within your override.
It worked!
public class MyWebcamPanel extends WebcamPanel {
/**
*
*/
private static final long serialVersionUID = 2808353446021354508L;
public MyWebcamPanel(Webcam webcam) {
super(webcam);
}
#Override
protected void paintComponent(Graphics g) {
int x = 180;
int y = 87;
super.paintComponent(g);
g.setColor(Color.RED);
g.drawRect(x, y, 640-2*x, 480-2*y);
}
}

JLabel image fading out to a color

Im making an application that should be able to read files in a given directory and then display all image files in a fullscreen borderless window, it should also display text files, but i havent started on that part yet, so never mind the system.out part. So far ive made the JFrame fullscreen and borderless, and ive made an Arraylist containing the files i want shown. I then add a jpanel with the file in the constructor, this jpanel adds the picture to a jlabel and displays it, afterward i remove the jpanel and start over with the next picture.
What i need is a way to make the images fade in from a given color, and then fade out to that same color.
this is where i add the panels and remove them again
for (File f : files) {
String fileName = f.getName();
if (fileName.endsWith(".txt")) {
System.out.println("Txt");
System.out.println(fileName);
System.out.println("--");
} else if (fileName.endsWith(".png") || fileName.endsWith(".jpg") || fileName.endsWith("bmp")) {
AlbumPanel albumpan = new AlbumPanel(connect, f, this);
add(albumpan, BorderLayout.CENTER);
pack();
try {
Thread.sleep(current.getFormat().getPicLength()*1000);
} catch (InterruptedException ex) {
}
remove(albumpan);
}
}
And this is the JPanel
public class AlbumPanel extends JPanel {
BufferedImage image;
ImageIcon icon;
IConnect connect;
File pic;
JFrame presWin;
public AlbumPanel(IConnect connect, File pic, JFrame presWin) {
this.connect = connect;
this.pic = pic;
this.presWin = presWin;
this.setLayout(new GridBagLayout());
try {
image = ImageIO.read(pic);
} catch (Exception e) {
System.out.println(e);
}
image = resize(image, presWin.getWidth(), presWin.getHeight());
icon = new ImageIcon(image);
JLabel picLabel = new JLabel();
picLabel.setIcon(icon);
add(picLabel);
setVisible(true);
}
private BufferedImage resize(BufferedImage image, int width, int height) {
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
Graphics2D g2d = (Graphics2D) bi.createGraphics();
g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
g2d.drawImage(image, 0, 0, width, height, null);
g2d.dispose();
return bi;
}
Override the rendering method (probably paintComponent) on the control you are using. Call super.paintComponent and then draw a semi-transparent rectangle of your "fade out" color over it.
Inside paintComponent you are passed a Graphics object. Methods on that object can be used to do things, including drawing a rectangle on the screen. You probably want fillRect.
Pick the opacity to be 0 when the image is fully displayed then move towards 1 when it's fully faded out.
You will need something to trigger redraws at regular intervals (a Swing Timer may be good enough).
You can use Color's transparency (the constructor public Color(int r, int g, int b, int a) where the last variable is alpha.
Start a Timer and change the Color's transparency from 0 to 255 and back filling the image (or the panel) with the Color.

Is there ever a reason drawImage won't actually draw an image?

private void renderLevelBackground(Graphics2D g2) {
Image backgroundImage = model.getBackgroundImage();
g2.drawImage(backgroundImage, 0, 0, null);
}
This is my code, I know that the backgroundImage is loaded properly because if I getWidth/Height it gives me the correct values. Is there anyway to test if it's the image or if it's the method somehow? It just won't draw on my screen despite the fact that I've used the drawImage method many times in this project already, all of which work flawlessly.
Thanks
"Is there ever a reason drawImage won't actually draw an image?"
Possibility, there is no image due to a bad path provided. Use ImageIO.read() which will cause an exception if the image file is not found. For example
public class ImagePanel extends JPanel {
private BufferedImage image;
public ImagePanel() {
try {
image = ImageIO.read(getClass().getResource("/resources/image.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
If the image is not found, it will throw an IO exception.
Possibility, the panel to which you are drawing, has no preferred size (0 x 0), and you are adding it a container wit layout that respects preferred sizes, so the ImagePanel will not be able to show the image. For example
public class ImagePanel extends JPanel {
protected void paintComponent(...) {
...
}
JPanel panel = new JPanel(); // default FlowLayout that respects preferred sizes
panel.add(new ImagePanel());
To fix this, you can override the gerPreferredSize() of the ImagePanel
public class ImagePanel extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
}
Possibility, you are not calling your method within the graphics context of the paint[Component] method.
protected void paintComponent(Graphics 2d) {
super.paintComponent(g);
Grapchics2D g2 = (Graphics2D)g;
renderLevelBackground(g2);
}
Other than that, these are all just guesses, and you should provide some more code code (preferably an MCVE) to help us better help you with the problem.
Possible Alternative to your approach. It seems like (from the little code snippet and it method signature semantics) you want to change the background image, when a level have changed. Consider using setBackgroundImage(BufferedImage image) method in your panel class, when the image is drawn. You can then set the background, whenever need be. Something like
public class BackgroundPanel extends JPanel {
private BufferedImage backgroundImage;
public void setBackgroundImage(BufferedImage image) {
this.backgroundImage = image;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Grpahics2D g2 = (Graphics2D)g;
if (image != null) {
g2.drawImage(backgroundImage, 0, 0, this);
}
}
}
So whenever you call setBackgroundImage, the image will change, and be repainted.
You may need to explicitly tell the JComponent that you are painting onto that it needs to be redrawn with revalidate() and repaint()

Drawing from another class wont take the right dimensions of the JPanel

Ok i have a JPanel such as this one :
public class GUI {
JFrame frame = new JFrame("Net");
JPanel panel = new JPanel();
public GUI()
{
frame.setSize(835,650);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
frame.add(panel);
panel.setSize(600,600);
panel.setLocation(215,5);
panel.add(new DrawPlanes(300,300,200,Color.BLACK));}
There are some other panels in there tables etc. My main is this one :
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
//new GUI();
new GUI().buildTable();
}
});
And there i another class this one :
public class DrawPlanes extends JPanel
{
private static int centreX, centreY, radius;
private Color colour;
public DrawPlanes()
{
centreX = 300;
centreY = 300;
radius = 200;
colour = Color.BLACK;
}
public DrawPlanes(int centreX,int centreY, int radius, Color colour)
{
this.centreX = centreX;
this.centreY = centreY;
this.radius = radius;
this.colour = colour;
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
System.out.println("ppp");
Graphics2D g2D = (Graphics2D) g;
g2D.setStroke(new BasicStroke(2F));
g.setColor(Color.BLACK);
g.drawOval(centreX - radius , centreY - radius, radius * 2 , radius * 2);
......
}
}
Ok so i ve setted the background colour of my panel red to see whats going on the whole panel its red but there is a small grey square where i believe the drawings are. I ve tried to change opaque as there might be an incompatibility issue but nothing changed at all.Any suggestions,is there anything that i m missing?
link of what is the result of
panel.add(new DrawPlanes(300,300,200,Color.BLACK))
http://dc626.4shared.com/img/FeYopZC1/s7/142d22f1be0/2013-12-08_142846.png?async&rand=0.9010817544924218
what does the DrawPlanes class draws when i checked it having the main and a panel etc in the DrawPlanes itself http://dc626.4shared.com/img/NPDkiQRJ/s7/142d22f23b0/1451491_586878858047235_191988.jpg?async&rand=0.27479583155781395 .When i apply a layout manager that grey square just moves to the center when i use getPreferredSize overriden or not the whole red panel appears grey.
frame.add(component) function eventually add your component to frame's content pane which has BorderLayout as default layout manager.
A JPanel uses FlowLayout as default layout which respect component's preferred size.
As your 'panel' and 'frame' is satisfying above two as a default, size hint with setSize(Dimension) or setBounds(Dimenstion) to component won't have effect.
You should provide size hint using setPreferredSize(Dimenstion)(to DrawPanel instance of your context) and if specifying minimum/maximum size is needed setMinimumSize(Dimenstion) and setMaximumSize(Dimenstion).
However it is considered as a better practice to override getXXXSize(Dimenstion): xxx represents Preferred/Minimmum/Maximum always which allows to adjust size of component with it's content.
Instead of calling setSize(Dimension) on a window it is preferable to call pack() at the end of addition of child components.
We should call setVisible(true) after finishing addition of all of the child components and following above point, after pack().
Please, start with the tutorial: Laying Out Components Within a Container
Edit:
Let us edit your GUI() constructor code and see what happens:
public GUI()
{
frame.setSize(835,650);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//frame.setVisible(true); // <<--- call it at the end of the code
frame.add(panel);
//panel.setSize(600,600); <<--- removing set size
panel.add(new DrawPlanes(300,300,200,Color.BLACK));
frame.setVisible(true);
}
And the DrawPnales will extend JComponent:
public class DrawPlanes extends JComponent{
public DrawPlanes(int centreX,int centreY, int radius, Color colour)
{
this.centreX = centreX;
this.centreY = centreY;
this.radius = radius;
this.colour = colour;
}
#Override
public Dimension getPreferredSize() {
return new Dimenstion(width, height);
// ^ provide your required size
}
}
If this still doesn't make any sense to you, then please try learning Swing layout managers a little bit further. Otherwise no matter how hard we hit our head on the table, possibly we won't be able to achieve any thing.
You're missing the fact that panels are laid out inside their container using a LayoutManager. The default layout manager of JPanel is a FlowLayout. The FlowLayout uses the preferred width and height of the components it lays out to decide where to place them in the container, and which size they should have. But your DrawPlanes panel doesn't override getPreferredSize(), so its preferred size is the default one.
Every time you use setSize() on a component or frame, you have a 99.9% probability of doing something wrong. Learn layout managers. If you design a custom component like your DrawPlanes component, which is not just a container for other components, but has a custom paintComponent() method, then override getPreferredSize(), getMaximumSize() and getMinimumSize() to tell the layout managers how they should display your component, and to make sure your custom component always has the appropriate size. You never set the size of a JButton, right? That's because the JButton itself decides, based on the text and icon it contains, which size it should have.

Categories

Resources