Skip to main content

How to create scalable icons with Java2D

Posted by kirillcool on July 23, 2005 at 12:54 AM PDT

In one of my previous entries i've shown how to use Java2D to create layered icons for your application. Unfortunately, most of the time we think about icons in pixel-precision format, instead of thinking of them as vector graphics. Let's see an example first:

icons.png



The icons are shown starting from 10*10 to 36*36 size. As you can see, the icon components are nicely scaled (including inner graphics width, highlight in the top-left corner and so on). What's more important, each row is created by the same function that gets an icon size as a single parameter. Let's see the code of the function that created the first two rows:

First, let's define a function that will return us an Icon:

     
   public static Icon getSuccessMarkerIcon(int dimension) {
      return new ImageIcon(getSuccessMarker(dimension));
   }

Now, we define a function that creates a BufferedImage:

     
   public static BufferedImage getSuccessMarker(int dimension) {

First, we create a new image and set it to anti-aliased mode:

     
      // new RGB image with transparency channel
      BufferedImage image = new BufferedImage(dimension, dimension,
            BufferedImage.TYPE_INT_ARGB);

      // create new graphics and set anti-aliasing hint
      Graphics2D graphics = (Graphics2D) image.getGraphics().create();
      graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

Fill the background:

     
      // green background fill
      graphics.setColor(new Color(0, 196, 0));
      graphics.fillOval(0, 0, dimension - 1, dimension - 1);

Create a white spot in the top-left corner (to simulate 3D shining effect) - note that we set clipping area to the icon circle (so that all the rest will remain transparent when our icon will be shown on non-white background):

     
      // create spot in the upper-left corner using temporary graphics
      // with clip set to the icon outline
      GradientPaint spot = new GradientPaint(0, 0, new Color(255, 255, 255,
            200), dimension, dimension, new Color(255, 255, 255, 0));
      Graphics2D tempGraphics = (Graphics2D) graphics.create();
      tempGraphics.setPaint(spot);
      tempGraphics.setClip(new Ellipse2D.Double(0, 0, dimension - 1,
            dimension - 1));
      tempGraphics.fillRect(0, 0, dimension, dimension);
      tempGraphics.dispose();

Draw the outline (must be done after the white gradient so the outline is not affected by it):

     
      // draw outline of the icon
      graphics.setColor(new Color(0, 0, 0, 128));
      graphics.drawOval(0, 0, dimension - 1, dimension - 1);

Compute the stroke width for the V sign. This sign is created using the same path with different strokes, one for the outer rim (wider), and one for the inner filling (narrower).

     
      // draw the V sign
      float dimOuter = (float) (0.5f * Math.pow(dimension, 0.75));
      float dimInner = (float) (0.28f * Math.pow(dimension, 0.75));

Create a GeneralPath for the V sign

     
      // create the path itself
      GeneralPath gp = new GeneralPath();
      gp.moveTo(0.25f * dimension, 0.45f * dimension);
      gp.lineTo(0.45f * dimension, 0.65f * dimension);
      gp.lineTo(0.85f * dimension, 0.12f * dimension);

Draw the path twice

     
      // draw blackish outline
      graphics.setStroke(new BasicStroke(dimOuter, BasicStroke.CAP_ROUND,
            BasicStroke.JOIN_ROUND));
      graphics.setColor(new Color(0, 0, 0, 196));
      graphics.draw(gp);
      // draw white inside
      graphics.setStroke(new BasicStroke(dimInner, BasicStroke.CAP_ROUND,
            BasicStroke.JOIN_ROUND));
      graphics.setColor(Color.white);
      graphics.draw(gp);

Dispose of the temp graphics and return the image

     
      // dispose
      graphics.dispose();
      return image;
   }

For slightly curved mark (the second row), use the following path:

     
      GeneralPath gp = new GeneralPath();
      gp.moveTo(0.25f * dimension, 0.45f * dimension);
      gp.quadTo(0.35f * dimension, 0.52f * dimension, 0.45f * dimension,
            0.65f * dimension);
      gp.quadTo(0.65f * dimension, 0.3f * dimension, 0.85f * dimension,
            0.12f * dimension);

The code for the other rows is very similar. One trick for triangles is to create the perimeter path that specifies rounded corners (using quadTo as above).

Using this technique, you no longer have to bundle icons of different sizes in your application. In the example below, there are three versions of the same error icon, one 9*9 (overlayed on top of another icon in the tree cell renderer), another 11*11 (in the left gutter panel) and the last 13*13 (in the message table panel). All of them were created on the fly without the need to worry if you have the corresponding icon bundled with your application.


milano.png

Related Topics >>