I want to be able to resize an image to fit within the bounds of a component (e.g., a JPanel) while preserving the original aspect ratio.
To experiment, I adapted an example from the Head First Java (2ed) book, SimpleGui2B (p. 365), and wrote a method for MyDrawPanel to determine the dimensions of the image and the enclosing JPanel, and then adjust the height or width of the image using getScaledInstance.
When I run the code, I encounter an infinite loop whenever I call drawImage on the scaledImage. Simply calling the scaleImage method does not cause any problems if I do not try to draw the resulting image. I modified the code so that it will draw the original image if run with no arguments, and draw the scaled image if run with any arguments. The image file is a 480x480 JPG, and the JFrame within which I am drawing it is 300x400.
I'm fairly new to Java, so I may be missing something fairly obvious regarding paintComponent and/or drawImage. Any relevant insights / experiences / solutions are welcome -- thanks!
You only need to load the image one time. And if you are going to scale the image you only need to scale it one time. It is inefficient to load the image and scale it every time the component is asked to render itself. You can load and scale at construction or as new mages are passed in for rendering.
edit: adjusted width of comments [ October 13, 2005: Message edited by: Craig Wood ]
Joined: Sep 28, 2005
Craig: thanks for the reply, the information and the code modifications!
I didn't know getScaledInstance was an asynchronous method, so am glad to have a better way to accommodate that, and I hadn't considered pulling the image scaling out of the paintComponent method, so those are both welcome changes.
When I compile and run the code you provided, it does not have an infinite loop, and I am grateful to have that problem resolved. Unfortunately, the image does not get scaled when I supply an additional parameter (e.g., "java SG2B2 t"). The System.out.println() traces I inserted show that the calls to getWidth() and getHeight() in the scaleImage method are now returning 0 (zero) values rather than 292 and 366, respectively, that were returned when my original code is executed, and so no scaling is performed.
As so often happens, this, in turn, provided an unanticipated learning opportunity about Java and arithmetic: the widthRatio and heightRatio computations both involved dividing by zero, and yielded "-Infinity" results (rather than resulting in a run-time error).
I tried to figure out why getWidth() and getHeight() work differently in the two versions of the code, but to no avail. Any further assistance will be further appreciated. Thanks!
The image is being scaled before the component is realized, ie, before MyDrawPanel is rendered in its parent JFrame. When it is rendered its 'paintComponent' method is called. When this happens we can get the real width and height of the parent which in this case is the center section of the BorderLayout of the JFrame. Before either 'pack' or 'setVisible' is called on the JFrame the width and height of MyDrawPanel will be zero.
How to deal with this. I can think of two options. One is to either pass in width and height values to MyDrawPanel for the paneWidth and paneHeight values used inside the 'scaleImage' method or set them inside MyDrawPanel. Then you can scale the image before the component (MyDrawPanel) is realized (ie, before a call to either 'pack' or 'setVisible').
The other is to wait until the first trip through the 'paintComponent' method to scale the image and make sure it is scaled only once. You may come up with some other options/variations. There are lots of ways to put things together. Here are the changes to MyDrawPanel with one way to implement the second option.
Thanks for the image link.
Joined: Sep 28, 2005
That did the trick -- thanks!
I can see, now, that the reason that my earlier version "worked" (w.r.t. scaling) was due to the infinite loop -- eventually, the panel had a size, and so the image was correctly resized (er, repeatedly, forevermore).