Displaying a Scaled Image with Java Swing

Duke

I’m working on a small application to help me with naming and sorting my pictures, so one of the basic things it needs to do is load and scale an image. Found lots of information here and there on this, and this post is to share what I came up with.

When searching around trying to figure stuff like this out, it’s hard to credit where you found everything. A lot of search results point to forum.java.sun.com, with discussions that are alternatively helpful and abusive to newbie questions. When I started leaning towards a solution based on overriding the “paint” method, one of the forum threads pointed to a good tutorial: “Lesson: Performing Custom Painting,” and an in-depth explanation: “Painting in AWT and Swing.”

The core of this is a class that extends javax.swing.JPanel, uses javax.imageio.ImageIO to read the file in to a java.awt.Image class, and then calls the Image.getScaledInstance() method to scale the image. We override the JPanel’s paintComponent(Graphics g) method to draw the image. (Notice, “paintComponent” is overridden, not “paint.” In my reading I found it is recommended to not override paint.)

There is a way to determine the progress of the image loading and take action based on that, but I didn’t get in to that. I just assume the image is there and that works for my example so I’m not going to worry about it right now. Take a look at the classes in javax.imageio.event if you want to show a progress bar or code defensively around a not-fully loaded image. There is also java.awt.image.BufferedImage that could be used, but I’m getting by ok with plain old Image for now. (This isn’t a high-powered graphics application, you know.) :-)

Trial and Error

Before finally showing the code, I’ll mention a couple of things I tried that didn’t work out so well, which explains some of the reasons behind the eventual solution:

It’s Easier to Gain Than to Lose

Before settling on the custom JPanel approach, I tried using the icon property of the JLabel along with its componentResized event handler to trigger rescaling the image. But this only worked as the image was growing. The JLabel was anchored to grow and shrink with the containing JFrame (form), but when resizing the window to be smaller, the event handler didn’t get called. The JLabel stayed at its largest size. (I later tried and saw that the JPanel did this also.)

Stuck in a Rut

In my paintComponent override, I initially tried scaling the image in the Graphics g.drawImage() statement, but this would cause the method to be called repeatedly (and continually!) despite nothing else changing. The CPU would then start churning away, going up to 99%, with the fan whirring away under the high load.

Searching on this situation brought up some hints. One thread mentioned that you shouldn’t do anything in paintComponent that changes your component, as it will then keep redrawing itself. (Or something like that.) This didn’t seem like it should be a problem in my case. I didn’t think I should be changing anything by drawing the scaled image instead of the base image, and in any case, the next time around the scaled image should be the same as the size hadn’t changed. But it seemed clear that scaling the image dynamically in the paintComponent method was the cause of it getting called over and over and running up the CPU usage (some might say utilization, but I don’t care for that word, although it’s not as bad as saying “utilize” instead of “use”), so I had to try something different.

I created a separate method in ImagePanel (The JPanel extended class) to scale the image, and then called that from the containing JFrame’s componentResized event handler, so that when the ImagePanel component was called on to repaint itself, it would be drawing a plain image (which I had previously noted did not cause the loop problem). And that seems to do the trick.

The Code

I’m working on this project in NetBeans. I’m not going to post the whole project at this time, because I don’t think it’s necessary to demonstrate this particular approach. I’ll leave out all the generated GUI code in my sample. This means I don’t have a nice self-contained demo for you, but if you’re trying to do this you should already have a point of entry to work with, I hope.

ImagePanel.java

(This class has the complete code.)

/*
 * ImagePanel.java
 *
 * Copyright (C) 2007  Scott Carpenter (scottc at movingtofreedom dot org)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 * Created on November 9, 2007, 4:07 PM
 *
 */

package PicHelper;

import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.imageio.*;

public class ImagePanel extends JPanel {

    private Image image;
    private Image scaledImage;
    private int imageWidth = 0;
    private int imageHeight = 0;
    //private long paintCount = 0;

    //constructor
    public ImagePanel() {
        super();
    }

    public void loadImage(String file) throws IOException {
        image = ImageIO.read(new File(file));
        //might be a situation where image isn't fully loaded, and
        //  should check for that before setting…
        imageWidth = image.getWidth(this);
        imageHeight = image.getHeight(this);
        setScaledImage();
    }

    //e.g., containing frame might call this from formComponentResized
    public void scaleImage() {
        setScaledImage();
    }

    //override paintComponent
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if ( scaledImage != null ) {
            //System.out.println("ImagePanel paintComponent " + ++paintCount);
            g.drawImage(scaledImage, 0, 0, this);
        }
    }

    private void setScaledImage() {
        if ( image != null ) {

            //use floats so division below won't round
            float iw = imageWidth;
            float ih = imageHeight;
            float pw = this.getWidth();   //panel width
            float ph = this.getHeight();  //panel height

            if ( pw < iw || ph < ih ) {

                /* compare some ratios and then decide which side of image to anchor to panel
                   and scale the other side
                   (this is all based on empirical observations and not at all grounded in theory)*/

                //System.out.println("pw/ph=" + pw/ph + ", iw/ih=" + iw/ih);

                if ( (pw / ph) > (iw / ih) ) {
                    iw = -1;
                    ih = ph;
                } else {
                    iw = pw;
                    ih = -1;
                }

                //prevent errors if panel is 0 wide or high
                if (iw == 0) {
                    iw = -1;
                }
                if (ih == 0) {
                    ih = -1;
                }

                scaledImage = image.getScaledInstance(
                            new Float(iw).intValue(), new Float(ih).intValue(), Image.SCALE_DEFAULT);

            } else {
                scaledImage = image;
            }

            //System.out.println("iw = " + iw + ", ih = " + ih + ", pw = " + pw + ", ph = " + ph);
        }
    }

}

(Updated, 13 Nov 2007: Simplified the scaling code. For a while I had been dividing ints and not realizing the result would be an int, and this led to some hairier-than-necessary comparisons to preserve the aspect ratio. I later changed to floats but didn’t think to reevaluate the code.)

PicHelperUI.java

NetBeans makes it easy to add event handlers and shields me from having to understand all the details. I left in one part of the generated code to show where the formComponentResized listener is added.

/*
 * PicHelperUI.java
 *
 * Copyright (C) 2007  Scott Carpenter (scottc at movingtofreedom dot org)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 * Created on November 10, 2007, 3:15 PM
 *
 */

package PicHelper;

public class PicHelperUI extends javax.swing.JFrame {

    //constructor
    public PicHelperUI() {
        initComponents();
        //temp
        try {
            picPanel.loadImage("pics/taller.jpg");
            //picPanel.loadImage("pics/wider.jpg");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // GENERATED CODE
    private void initComponents() {

	// Snipped a bunch of GUI code, but wanted to show:
        addComponentListener(new java.awt.event.ComponentAdapter() {
            public void componentResized(java.awt.event.ComponentEvent evt) {
                formComponentResized(evt);
            }
        });
        // Snipped a bunch more GUI code
    }
    // END GENERATED CODE

    private void formComponentResized(java.awt.event.ComponentEvent evt) {
        picPanel.scaleImage();
    }                                     

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new PicHelperUI().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify
    private javax.swing.JPanel panelSide;
    private javax.swing.JPanel panelTop;
    private PicHelper.ImagePanel picPanel;
    // End of variables declaration

}

Related: Picture Filer: A free Java program for naming and sorting image files (Has a newer version of ImagePanel.)

If you enjoyed this article, please subscribe for free!
Via the atom or rss feed, or enter your email address to get updates when new entries are posted:
(Your email will not be shared nor used for anything other than sending new posts. See the policies page for more about subscriptions and privacy.)

You can skip to the end and leave a response. Pinging is currently not allowed.

Comments

  1. Be thankful for the javax imageio libraries. Back when I first started loading JPEGs with the old awt image loaders, they were horribly slow. It could take 10 to 15 seconds to load an average quality JPEG. BufferedImages help too and they’re really not too complicated. I’ve got examples in my Isszy application at http://penguindreams.org/page/see/Isszy

    I apologize if the code looks horrid. It was an early project. :)

  2. Hi, Sumit. I do often feel thankful when I start learning something and find references to older ways. The challenge is to find the page that says, “Don’t use X anymore. Y is much better,” before finding the page explaining X to you. For images, that would be the MediaSomethingOrOther method, I think.

    Thanks for the pointer to your project — looks like you had the same idea. I’d probably be able to cannibalize some of your code, but I don’t see any kind of license information with it. Would you consider releasing it under GPL v3?

You can follow any responses to this entry through the
comments feed.

Say Your Say

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

By submitting your comment here, you agree to license it under the same Creative Commons Attribution-ShareAlike 3.0 License as the movingtofreedom.org web site. Please see policies for more information about comments and privacy.