The Source for Java Technology Collaboration
User: Password:



Kirill Grouchnikov

Kirill Grouchnikov's Blog

How to create scalable icons with Java2D

Posted by kirillcool on July 23, 2005 at 12:54 AM | Comments (11)

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

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • First things first!

    If only Sun would finally come around after ten years and allow to specify different icon sizes for a Frame/JFrame to support different platforms. What are these vector icons good for, if one still has to deal with the ugly pixel scaling of [J]Frame#setIconImage()?

    Ten years and still no [J]Frame#setIconImages(Image images[]) so one could supply an array of different-sized images and let the frame select the best fitting one for a particular platform.

    Posted by: ewin on July 23, 2005 at 04:44 AM

  • Nice.
    With the new round circles in Mustang these icons will look even better!

    Posted by: mgrev on July 24, 2005 at 01:15 AM

  • scalable graphics rule, but why not just use SVG ?

    Posted by: steltenpower on July 24, 2005 at 02:28 PM

  • steltenpower,
    In order to use SVG, you have to bundle 3rd party (like Batik) that can manipulate them. In addition, you'll have to create the icon files themselves and bundle them. The whole point of the entry was to show how not to bundle graphics.

    Posted by: kirillcool on July 24, 2005 at 11:24 PM

  • looks really pretty! =)

    Posted by: alexlamsl on July 24, 2005 at 11:50 PM

  • Nice. But can you really expect developers (typically not very good at design) or designers (typically not good at coding) to code a nicely designed icon?

    Here's an idea: Utility to convert SVG to Java source code, anyone?

    Posted by: mrsteve on July 26, 2005 at 04:56 AM

  • Doesn't it already exist under the name of Batik?

    Posted by: kirillcool on July 26, 2005 at 05:10 AM

  • With regard to using SVG the open source XUI project (see version 2.0) features an XSvgIcon class that uses a version of TinyLine's TinySVG (81Kb tinylinepp.zip) to create scaleable icons. The class implements the Icon interface and so should be usable in most situations, the XSvgCanvas class also allows an SVG image to be used as a JComponent. The components are pretty much standalone and can be used outside of the XUI project.

    The project also uses Batik's transcoder facility to convert SVG images into PNG artwork for the Synth LAF . The conversion of SVG to PNG is usable outside of this context and the raster images can be generated to user specified sizes but of course the PNG will not be as scalable as the XSvgIcon.
    The Batik libary is also pretty large so you may not want to bundle in a final deliverable, the XUI project dynamically loads Batik to create a sort of preview mode to generate the PNG image and thereafter uses the PNGs if Batik is not found.

    Posted by: luano on July 27, 2005 at 01:17 AM

  • >> Doesn't it already exist under the name of Batik?

    Kirillcool, Batik is huge and I can't see any mainstream desktop app bundling it just for scalable icons. I meant a utility to convert an SVG source file into Java source (or byte) code of primitive paint operations, removing the need for the Batik libraries altogether. Must look into TinyLine...

    Posted by: mrsteve on July 27, 2005 at 06:22 AM

  • Do you have the sources also for the rest of the icons(like in the screen shot) ? :)

    Thanks.

    Posted by: adrian_tarau on December 13, 2005 at 09:47 AM

  • The java code generator would be the ideal thing. Someone should create a module for netbeans that lets you select an SVG image file, and it would generate a java class for you that would render that image. Selecting a whole directory would be nice, as would just being able to select a directory property and having the ant build process automatically do the generation.

    Posted by: greggwon on September 08, 2006 at 06:24 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds