Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Create Simple Icons on the Fly

0.00/5 (No votes)
20 May 2005 1  
Create simple images for icons on the fly and with Java code only.
Sample Image - SimpleIcons.jpg

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|    // Data members used by a specific PatternColorFilter instance..
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|      // The constructor is private to prevent anyone from creating
         // unwanted instances..
13|      super();
14|      // Set the private data members..
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|      // Check the data arrays specified and return null if they are invalid..
23|      if (patternChars == null || patternColours == null || pattern == null) {
24|        // Invalid data arrays..
25|        return null;
26|      } else if (patternChars.length != patternColours.length) {
27|        // Invalid data arrays..
28|        return null;
29|      }
30|      // Obtain the width and height of the pattern array and verify it is even..
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|          // Uneven pattern array..
38|          return null;
39|        }
40|      }
41|      // Create a BufferedImage to work with..
42|      BufferedImage image = new BufferedImage(width, height, transparent ?
                                                 BufferedImage.TYPE_INT_ARGB :
                                                 BufferedImage.TYPE_INT_RGB);
43|      // Create an ImageProducer instance based on a PatternColorFilter instance..
44|      FilteredImageSource fis =
                     new FilteredImageSource(image.getSource(),
                                             new PatternColorFilter(pattern,
                                                                    patternChars,
                                                                    patternColours));
45|      // Produce and return the image..
46|      return c.createImage(fis);
47|    }
48|
49|    public int filterRGB(int x, int y, int rgb) {
50|      // Is there a valid pattern to paint?
51|      if (pattern != null) {
52|        // Get the pattern's character..
53|        char c = pattern[y].charAt(x);
54|        // Loop through the pattern characters..
55|        for (int i = 0; i < patternChars.length; i++) {
56|          if (patternChars[i] == c) {
57|            // Find the matching pixel RGB value and return it..
58|            return patternColours[i].getRGB();
59|          }
60|        }
61|      }
62|      // Return the default RGB value specified by the method's invoker..
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here