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 {
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:
private BufferedImage bufferedImage = null;
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.
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.
public void setBufferedImage(BufferedImage bufferedImage) {
this.bufferedImage = bufferedImage;
if (this.bufferedImage == null) {
this.imageGraphics = null;
this.setBounds(0, 0, 0, 0);
}
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.
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.
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.
@Override
public void paint(Graphics g) {
if (this.bufferedImage == null) {
return;
}
Rectangle rectangle = this.getVisibleRect();
paintImmediately(g, rectangle.x, rectangle.y, rectangle.width, rectangle.height);
};
@Override
public void paintImmediately(int x, int y, int width, int height) {
if (this.bufferedImage == null) {
return;
}
this.paintImmediately(super.getGraphics(), x, y, width, height);
}
@Override
public void paintImmediately(Rectangle rectangle) {
if (this.bufferedImage == null) {
return;
}
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.
private void paintImmediately(Graphics g, int x, int y, int width, int height) {
if (this.bufferedImage == null) {
return;
}
int imageWidth = this.bufferedImage.getWidth();
int imageHeight = this.bufferedImage.getHeight();
if (x >= imageWidth || y >= imageHeight) {
return;
}
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;
}
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:
@Override
public int getHeight() {
if (this.bufferedImage == null) {
return 0;
}
return this.bufferedImage.getHeight();
}
@Override
public Dimension getPreferredSize() {
if (this.bufferedImage == null) {
return new Dimension(0, 0);
}
return new Dimension(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
}
@Override
public Dimension getSize() {
if (this.bufferedImage == null) {
return new Dimension(0, 0);
}
return new Dimension(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
}
@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.
public BufferedImage getBufferedImage() {
return this.bufferedImage;
}
@Override
public Graphics getGraphics() {
return this.imageGraphics;
}
JImageComponent
also provides a method for resizing and/or converting the image:
public void resize(int width, int height, int imageType) {
if (this.bufferedImage == null) {
setBufferedImage(new BufferedImage(width, height, imageType));
return;
}
BufferedImage tempImage = new BufferedImage(width, height, imageType);
int w = this.bufferedImage.getWidth();
int h = this.bufferedImage.getHeight();
if (width < w) {
w = width;
}
if (height < h) {
h = height;
}
if (this.bufferedImage.getType() == imageType) {
Graphics g = tempImage.getGraphics();
g.drawImage(this.bufferedImage, 0, 0, w, h, null);
}
else {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
tempImage.setRGB(x, y, this.bufferedImage.getRGB(x, y));
}
}
}
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) {
exception.printStackTrace();
}
catch (InterruptedException exception) {
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