Click here to Skip to main content
Click here to Skip to main content

Tagged as

Creating a Swing JImageComponent

, 10 Jul 2014 CPL
Rate this:
Please Sign up or sign in to vote.
Using and/or creating Swing classes for displaying images

Sample Application Screenshot

Introduction

This article shows details of how to create a Swing class in Java™ for use when displaying images in Java™ applets and/or applications. It also includes steps that make rendering of the image fast, and usable in scrollable containers.

For better understanding, especially for beginners, the article uses the JImageComponent implementation, which extends the Swing JComponent, as reference.

Instructions

1. Creating a Sub-class

Creating a sub-class entails extending your class. Normally the super-class will be one (or more) of the Java™ Swing classes.

JImageComponent extends Swing's JComponent:

public class JImageComponent extends javax.swing.JComponent {

    /**
     * Constructs a new JImageComponent object.
     */
    public JImageComponent() {
    }    
}

2. Creating Class Variables

Your class will require various variables to hold important data. They may change as the functionality of the class is extended. In general, it should at least contain two variables: a BufferedImage object for holding the image to paint, and its corresponding Graphics object.

JImageComponent contains two private variables:

    /** Holds the BufferedImage for the image. */
    private BufferedImage bufferedImage = null;

    /** Holds the Graphics for the image. */
    private Graphics imageGraphics = null;

3. Implementing Functionality to Set/Change the Image

Your class will paint the image as described by its variables. You may need to implement functionality to set this image at construction-time, and/or to set/change the image during run-time.

JImageComponent allows for setting the image at construction-time and setting/changing the image during run-time. For brevity only one constructor is listed here.

A constructor that takes a BufferedImage as parameter.

    /**
     * Constructs a new JImageComponent object.
     * 
     * @param bufferedImage
     *        Image to load as default image.
     */
    public JImageComponent(BufferedImage bufferedImage) {
        this.setBufferedImage(bufferedImage);
    }

JImageComponent's method for setting/changing the image during run-time. The method also sets the components bounds, which is explained when discussing implementing functionality for use in scrollable containers.

    /**
     * Sets the buffered image, and updates the components bounds.
     * 
     * @param bufferedImage
     *        The buffered image to set to.
     */
    public void setBufferedImage(BufferedImage bufferedImage) {
        this.bufferedImage = bufferedImage;
        
        // Clear the graphics object if null image specified.
        // Clear the component bounds if null image specified.
        if (this.bufferedImage == null) {
            this.imageGraphics = null;
            this.setBounds(0, 0, 0, 0);
        }
        
        // Set the graphics object.
        // Set the component's bounds.
        else {
            this.imageGraphics = this.bufferedImage.createGraphics();
            this.setBounds(0, 0, this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
        }
    }

4. Implementing Functionality to Set/Load the Image

Your class may implement functionality to set the image by loading an image from a resource.

JImageComponent allows images in the application's archive to be loaded by providing a method that loads an image specified by a uniform resource locator (URL) parameter. Note that you may need to call JImageComponet's repaint() method to draw the image after it is loaded.

    /**
     * Loads image from an URL.
     * 
     * @param imageLocation
     *        URL to image.
     * @throws IOException
     *         Throws an IOException if file cannot be loaded.
     */
    public void loadImage(URL imageLocation) throws IOException {
        this.bufferedImage = ImageIO.read(imageLocation);
        this.setBufferedImage(this.bufferedImage);
    }

You are free to implement as many methods as needed. JImageComponent, for example, also has a method to load an image specified by a File parameter.

    /**
     * Loads image from a file.
     * 
     * @param imageLocation
     *        File to image.
     * @throws IOException
     *         Throws an IOException if file cannot be loaded.
     */
    public void loadImage(File imageLocation) throws IOException {
        this.bufferedImage = ImageIO.read(imageLocation);
        this.setBufferedImage(this.bufferedImage);
    }

5. Implementing Functionality to Paint the Image

This is the real guts and gears of your class. You will need to ensure that your class can draw parts of itself when the image is only partially visible, as well as ensuring that the class can redraw itself when an image is set/loaded, and/or changed/edited. Depending on the super-class you extend, you may need to override several methods related to painting the image.

Swing's JComponent does most of the grunt work for JImageComponent, which overrides the paint(Graphics) method, and two paintImmediately() methods. Attention was paid to drawing the image as specified by the component's visible rectangle. This is explained when discussing implementing functionality for use in scrollable containers.

    /*
     * @see javax.swing.JComponent#paint(java.awt.Graphics)
     */
    @Override
    public void paint(Graphics g) {
        
        // Exit if no image is loaded.
        if (this.bufferedImage == null) {
            return;
        }
        
        // Paint the visible region.
        Rectangle rectangle = this.getVisibleRect();
        paintImmediately(g, rectangle.x, rectangle.y, rectangle.width, rectangle.height);
    };

    /*
     * @see javax.swing.JComponent#paintImmediately(int, int, int, int)
     */
    @Override
    public void paintImmediately(int x, int y, int width, int height) {
        
        // Exit if no image is loaded.
        if (this.bufferedImage == null) {
            return;
        }
        
        // Paint the region specified.
        this.paintImmediately(super.getGraphics(), x, y, width, height);
    }

    /*
     * @see javax.swing.JComponent#paintImmediately(java.awt.Rectangle)
     */
    @Override
    public void paintImmediately(Rectangle rectangle) {
        
        // Exit if no image is loaded.
        if (this.bufferedImage == null) {
            return;
        }
        
        // Paint the region specified.
        this.paintImmediately(super.getGraphics(), rectangle.x, rectangle.y, rectangle.width, rectangle.height);
    }

For simplicity JImageComponent has a private method, paintImmediately(Graphics, int, int, int, int) for doing the actual painting of the image.

    /**
     * Paints the image onto the component.
     * 
     * @param g
     *        The Graphics object of the component onto which the
     *        image region will be painted.
     * @param x
     *        The x value of the region to be painted.
     * @param y
     *        The y value of the region to be painted.
     * @param width
     *        The width of the region to be painted.
     * @param height
     *        The height of the region to be painted.
     */
    private void paintImmediately(Graphics g, int x, int y, int width, int height) {
        
        // Exit if no image is loaded.
        if (this.bufferedImage == null) {
            return;
        }
        
        int imageWidth = this.bufferedImage.getWidth();
        int imageHeight = this.bufferedImage.getHeight();
        
        // Exit if the dimension is beyond that of the image.
        if (x >= imageWidth || y >= imageHeight) {
            return;
        }
        
        // Calculate the rectangle of the image that should be rendered.
        int x1 = x < 0 ? 0 : x;
        int y1 = y < 0 ? 0 : y;
        int x2 = x + width - 1;
        int y2 = y + height - 1;
        
        if (x2 >= imageWidth) {
            x2 = imageWidth - 1;
        }
        
        if (y2 >= imageHeight) {
            y2 = imageHeight - 1;
        }
        
        // Draw the image.
        g.drawImage(this.bufferedImage, x1, y1, x2, y2, x1, y1, x2, y2, null);
    }

6. Implementing Functionality to Paint the Component When Used in Scrollable Containers

A challenge that needs to be addressed is painting your component when used in scrollable container. A programmer may, for example, want to place an image whose dimensions exceed that of the display, in a JScrollPane container.

JImageComponent addresses this challenge by doing three things:

  • Setting the component's bounds when an image is set/loaded.
    (This has already been done.)
  • Paying attention to the component's visibility rectangle when drawing the image.
    (This has already been done.)
  • Providing the layout manager(s) with appropriate layout details.

You may need to implement functionality that provides the layout manager(s) with appropriate layout details if you wish to ensure that your component renders correctly in scrollable containers.

JImageComponent provides the needed layout details by overriding the following methods:

    /**
     * Returns the height of the image.
     */
    @Override
    public int getHeight() {
        if (this.bufferedImage == null) {
            return 0;
        }
        
        return this.bufferedImage.getHeight();
    }

    /**
     * Returns the size of the image.
     */
    @Override
    public Dimension getPreferredSize() {
        if (this.bufferedImage == null) {
            return new Dimension(0, 0);
        }
        
        return new Dimension(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
    }

    /**
     * Returns the size of the image.
     */
    @Override
    public Dimension getSize() {
        if (this.bufferedImage == null) {
            return new Dimension(0, 0);
        }
        
        return new Dimension(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
    }

    /**
     * Returns the width of the image.
     */
    @Override
    public int getWidth() {
        if (this.bufferedImage == null) {
            return 0;
        }
        
        return this.bufferedImage.getWidth();
    }

7. Implementing Functionality to Edit the Image

There are many instances when editing the image will be more efficient than changing the image continuously. An example is when you want to display an animation: it is more efficient to changes the areas that change during each animation image than to create a new image in memory, and set it in the component.

JImageComponent allows this functionality by providing a method to access the image's BufferedImage object, and a method to access the image's Graphics object.

    /**
     * Returns the BufferedImage object for the image.
     * 
     * @return The buffered image.
     */
    public BufferedImage getBufferedImage() {
        return this.bufferedImage;
    }

    /**
     * Returns the Graphics object for the image.
     */
    @Override
    public Graphics getGraphics() {
        return this.imageGraphics;
    }

JImageComponent also provides a method for resizing and/or converting the image:

    /**
     * Resizes and/or converts the image to the specified dimension and type. 
     *
     * Note that the image is "cropped" from the left top corner).
     * 
     * @param width
     *        The new width of the image.
     * @param height
     *        The new height of the image.
     * @param imageType
     *        The new image type (BufferedImage type).
     * @see type java.awt.image.BufferedImage#BufferedImage(int, int, int)
     */
    public void resize(int width, int height, int imageType) {
        
        // Create a new image if none is loaded.
        if (this.bufferedImage == null) {
            setBufferedImage(new BufferedImage(width, height, imageType));
            return;
        }
        
        // Create a new temporary image.
        BufferedImage tempImage = new BufferedImage(width, height, imageType);
        int w = this.bufferedImage.getWidth();
        int h = this.bufferedImage.getHeight();
        
        // Crop width if necessary.
        if (width < w) {
            w = width;
        }
        
        // Crop height if necessary.
        if (height < h) {
            h = height;
        }
        
        // Copy if the type is the same.
        if (this.bufferedImage.getType() == imageType) {
            
            Graphics g = tempImage.getGraphics();
            g.drawImage(this.bufferedImage, 0, 0, w, h, null);
        }
        
        // Copy pixels to force conversion.
        else {
            
            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    tempImage.setRGB(x, y, this.bufferedImage.getRGB(x, y));
                }
            }
        }
        
        // Set the new image.
        setBufferedImage(tempImage);
    }

Using the Code

Programmers who use JImageComponent should remember to call the repaint() method after changing/editing the image.

Programmers who wish to implement and test their JComponent sub-class must adhere to the Swing guidelines regarding execution (http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html):

Java Applets should use the SwingUtilities invokeAndWait():

Java Application may use either SwingUtilities invokeAndWait() or invokeLater().

An example of using invokeAndWait:

    try {
        javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
    catch (InvocationTargetException exception) {
        // TODO Auto-generated catch block
        exception.printStackTrace();
    }
    catch (InterruptedException exception) {
        // TODO Auto-generated catch block
        exception.printStackTrace();
    }

An example of using invokeLater:

    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });

Moving Ahead

This is a brief introduction for creating a sub-class of the Java™ Swing JComponent class for displaying images. From here, there are many venues that can be visited. For example, JImageComponent doesn't make provision for having its borders set/changed.

The same steps may be followed for creating a sub-class of Java™ Abstract Window Toolkit's Component class. Special care will need to be taken though: one pitfall will be making sure that the sub-class makes provision for double-buffering to eliminate (possible) flickering; another may be extending the class to properly catch and/or fire events.

Points of Interest

Programmers may also be interested in using Java™'s 2D Graphics API for use with images.

Swing's JComponent isn't the only class that may be extended for this purpose. Programmers may choose to extend, for example, the JPanel class instead.

In business applications, I was stuck with using native classes like JButton and JLabel for ease of use. In 2014, I improved JImageComponent for use in scrollable containers. Now I continue to use JImageComponent for fun things like testing animations, drawing fractals, and drawing custom images.

History

  • July 2014: Initial submission

License

This article, along with any associated source code and files, is licensed under The Common Public License Version 1.0 (CPL)

Share

About the Author

Osmund Francis
Software Developer
South Africa South Africa
I'm a graduated electronic/computer engineer. My passions are mathematics and electronics. Although I didn't study software engineering as a main subject, I enjoy it just the same; creating useful pieces of software that can be used by people to increase their productivity is a kind service that invigorates me.
 
I started programming in Pascal, later moving on to C/C++ at university. Java™ became my preferred language in 2005 when I started tinkering with Java™ ME to create applications for mobile devices.
 
I now work with various programming languages, including C#, and even some interpreter languages like SQL.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.141022.2 | Last Updated 10 Jul 2014
Article Copyright 2014 by Osmund Francis
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid