Disclaimer
Please be advised that the code in this article is supplied as-is and without any guarantee, support or assurance of any kind. It has been tested and works well with JDK1.4 and JDK1.5.
Introduction
Every now and then, the need for small images for icons arises. More often than not, you don't have time to look for icons and drawing images for them with whatever graphics program is tedious at best, especially when writing the code is a more pressing matter. In the old days, for me it was the good old days of my Atari ST, you could design a simple icon by coloring pixels, but since this method also requires you load up some graphic software, it is slower than the oldest method to design icons. The first mouse pointer icons and other OS icons (even before Windows) were drawn using a bitmap matrix, and embedding the icon's data in your code has never been easier. This is what my PatternColorFilter
class aims to achieve.
Background
Before I proceed to describe the code, I will explain how the bitmap matrix is defined. In the iimage displayed above, I use two patterns for the displayed icons:
private final static String[] okPattern = {
"·······••",
"······••·",
"·····••··",
"•···••···",
"••·••····",
"·•••·····",
"··•······"
};
private final static String[] cancelPattern = {
"••·····••",
"·••···••·",
"··••·••··",
"···•••···",
"··••·••··",
"·••···••·",
"••·····••"
};
Whereas the additional data required to actually render the icons in color is given here:
private final static char[] patternChars = {'•', '·'};
private Color[] okColours = {new Color(0, 100, 0), new Color(0, true)};
private Color[] cancelColours = {new Color(180, 0, 0), new Color(0, true)};
The okColours
array defines the Color
instances for use with the okPattern
and the cancelColours
array is used with the cancelPattern String
array. The character array, patternChars
tells the PatternColorFilter
class which pixels to paint with which color from the given color array.
Using the Code
The PatternColorFilter
class has but one useful method, createImage(...)
which returns, as expected, an image matching the pixel matrix specified in the data arrays. The following code-block demonstrates using the PatternColorFilter
class to create the two icon images appearing in the top image:
private ImageIcon okIcon =
new ImageIcon(PatternColorFilter.createImage(this, true, okPattern,
patternChars, okColours));
private ImageIcon cancelIcon =
new ImageIcon(PatternColorFilter.createImage(this, true, cancelPattern,
patternChars, cancelColours));
The method signature for PatternColorFilter.createImage(...)
is this:
public static synchronized Image createImage
(java.awt.Component c, boolean transparent, String[] pattern,
char[] patternChars, Color[] patternColours);
The first parameter must be a Java AWT Component because of the way the image is created, all of which is explained in the next section.
Explaining the Code
Following is the full source-code listing for the PatternColorFilter
class which I will then explain bit by bit:
01| import java.awt.*;
02| import java.awt.image.*;
03|
04| public class PatternColorFilter extends RGBImageFilter {
05|
06| private String[] pattern;
07| private char[] patternChars;
08| private Color[] patternColours;
09| private BufferedImage image;
10|
11| private PatternColorFilter(String[] pattern, char[] patternChars,
Color[] patternColours) {
12|
13| super();
14|
15| this.pattern = pattern;
16| this.patternChars = patternChars;
17| this.patternColours = patternColours;
18| canFilterIndexColorModel = true;
19| }
20|
21| public static synchronized Image createImage
(Component c, boolean transparent, String[] pattern,
char[] patternChars, Color[] patternColours) {
22|
23| if (patternChars == null || patternColours == null || pattern == null) {
24|
25| return null;
26| } else if (patternChars.length != patternColours.length) {
27|
28| return null;
29| }
30|
31| int width = 0;
32| int height = pattern.length;
33| for (int i = 0; i < pattern.length; i++) {
34| if (width == 0) {
35| width = pattern[i].length();
36| } else if (width != pattern[i].length()) {
37|
38| return null;
39| }
40| }
41|
42| BufferedImage image = new BufferedImage(width, height, transparent ?
BufferedImage.TYPE_INT_ARGB :
BufferedImage.TYPE_INT_RGB);
43|
44| FilteredImageSource fis =
new FilteredImageSource(image.getSource(),
new PatternColorFilter(pattern,
patternChars,
patternColours));
45|
46| return c.createImage(fis);
47| }
48|
49| public int filterRGB(int x, int y, int rgb) {
50|
51| if (pattern != null) {
52|
53| char c = pattern[y].charAt(x);
54|
55| for (int i = 0; i < patternChars.length; i++) {
56| if (patternChars[i] == c) {
57|
58| return patternColours[i].getRGB();
59| }
60| }
61| }
62|
63| return rgb;
64| }
65| }
1. Extend RGBImageFilter
Java's most basic Image Processing API has a class that performs all the boring work of filtering an image pixel-matrix for us. To quote the JavaDoc entry for RGBImageFilter:
"This class provides an easy way to create an ImageFilter which modifies the pixels of an image in the default RGB ColorModel. It is meant to be used in conjunction with a FilteredImageSource object to produce filtered versions of existing images. It is an abstract class that provides the calls needed to channel all of the pixel data through a single method which converts pixels one at a time in the default RGB ColorModel regardless of the ColorModel being used by the ImageProducer. The only method which needs to be defined to create a useable image filter is the filterRGB method."
By extending this class, we can take advantage of Java's Image Processing support and perform only the task absolutely necessary for solving the problem of creating an image on the fly from a pixel matrix (lines: 4, 49).
2. Construct Things Wisely
Early on, I had the notion that I would have to ensure my code is thread-safe and can run from various stages in a program's life-cycle. For this reason, each instance of PatternColorFilter
must have its own copy of the data-arrays used to render the image and to enforce this further, all data-members are private
as well as the class constructor, thus enabling only the class itself to create its own instances upon demand (lines: 6-19).
3. Divide and Conquer
As the static
method createImage(...)
demonstrates, an instance of the PatternColorFilter
class is created and discarded after it is used to render the image, which helps keep things safe. The method first verifies that the data arrays used to render the icon are valid and even in their width. An empty BufferedImage instance is created next, which will be the base, together with a PatternColorFilter
instance, for the FilteredImageSource instance. The last line of the method actually does the trick by using the createImage(..)
method of the supplied AWT Component instance, to fill the empty image with color according to the specified pixel and color data arrays (lines: 21-47).
4. Under the Hood
The final method, filterRGB(...)
is indirectly invoked by methods inherited from the RGBImageFilter
class, and since the PatternColorFilter
has private
access to the data-array used to render the image, each image is rendered according to its own data which was supplied to the PatternColorFilter
instance when it was created through the static createImage(...)
method (lines: 49-64).
In Conclusion
I hope this article has shed some light on pixel manipulation of images and creation of images on the fly. Granted, this method of creating an image may not be the fastest or most efficient, especially not for very large images, however, the ease-of-use -- in my opinion -- is unparalleled by any other method employable by a busy programmer trying to meet a deadline. Once the code is stable and there is time to embellish your GUI with professionally created icons, go for it, but while things are just starting, and in the middle of a furious development cycle, the flexibility of the PatternColorFilter
class is quite valuable.