Creating a Swing JImageComponent





5.00/5 (4 votes)
Using and/or creating Swing classes for displaying images
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