Dynamically generating rounded corners in Java

I was looking at re-skinning a sporting team website that I had recently created, and really liked the look of the new rounded corners that grace so many websites. There are two main techniques that are used to achieve that look:

  1. Having a number of 1×1 pixel divs that shape the corners
  2. Having some rounded corner images

I don’t really like the first technique because it seems so inefficient (not to mention that it reminds me of the 1×1-pixel fillers of the 90’s), and the second wasn’t able to be used for my purposes because each team on my website can pick their own colour scheme from the full 24-bit RGB colour space (and I didn’t want to have to create 16777216 different corner combinations). So, what to do?

Generating Images Dynamically

I got thinking about whether I could generate the images programmatically. Looking at a very simple corner image up close, they really are quite simple and I figured that I could write a very simple image generator where the image shape was hard-coded, but where the colours could be substituted in at runtime. Initially I thought that I would use the Java2D imaging libraries, but when I stopped to consider that each page would probably have 3 or 4 sections that are bounded by rounded corners, that would mean that I would be creating 12-16 images for each page load. Whatever technique I used would have to be very lightweight and fast. To top it all off, I also wanted to support transparency in the images so that my rounded corners could overlay on a background colour nicely.

It seemed to me that PNG was an excellent choice for a graphics format. It is a simple format, handles transparent pixels, and has pretty good support from all common browsers (with the exception that IE5 and IE6 cannot (natively) handle partially transparent images). Given that most corner images are quite small (say, 10 by 10 pixels) I thought I would write a PNG generator that takes an array of, say, 100 values that represent the red, green, blue and transparency values for each pixel. To create an 5×5 image that looked like (zoomed to 15×15 for clarity) would look like:

	// A 5x5 image with each line being
	// red, green, black, black, black respectively
	byte[] data = new byte[] {
		(byte)255, (byte)0, (byte)0, (byte)255,
		(byte)255, (byte)0, (byte)0, (byte)255,
		(byte)255, (byte)0, (byte)0, (byte)255,
		(byte)255, (byte)0, (byte)0, (byte)255,
		(byte)255, (byte)0, (byte)0, (byte)255,

		(byte)0, (byte)255, (byte)0, (byte)255,
		(byte)0, (byte)255, (byte)0, (byte)255,
		(byte)0, (byte)255, (byte)0, (byte)255,
		(byte)0, (byte)255, (byte)0, (byte)255,
		(byte)0, (byte)255, (byte)0, (byte)255,

		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,

		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,

		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
		(byte)0, (byte)0, (byte)0, (byte)255,
	};
	int compress = 9;
	boolean alpha = true;
	PNGEncoder encoder = new PNGEncoder(5, 5, compress, alpha, data);

	// now let's write to the servlet output stream
	byte[] bytes = encoder.encode();
	resp.setContentLength(bytes.length);
	resp.setContentType("image/png");
	resp.getOutputStream().write(bytes, 0, bytes.length);
	resp.getOutputStream().flush();

So, as you can see, it is quite simple to invoke and pass data to. However, it still isn’t particularly easy to define your images (it is a bit hard to visualise from the above code what the output image would look like).

Memories…

Looking at a zoomed-in image, it reminded me of when, as a kid, I was using sprites to write animation for my Commodore 64. It struck me that I could do a similar thing here. What if I were to define a “sprite” and then just change the colours of each pixel at runtime? For example:

	PNGSprite sprite = new PNGSprite(5, 5,
		"RRRRR"+
		"GGGGG"+
		"XXXXX"+
		"XXXXX"+
		"XXXXX");

	sprite.setColour('R', (byte)255, (byte)0,   (byte)0);
	sprite.setColour('G', (byte)0,   (byte)255, (byte)0);
	sprite.setColour('X', (byte)0,   (byte)0,   (byte)0);

	byte[] bytes = sprite.encode();

Much simpler, no? All the PNGSprite.encode() method does is build an array of bytes like in the first example by iterating over the colours and substituting the correct RGB value.

Improving Performance

This technique, however, can be further improved. Users of the website typically tend to be looking at the same pages again and again, which means that the images do not change. Using the technique above, there is little opportunity for caching based on the passed colours. If we refactor slightly to make the sprite definition a constant and pass the colours into the encode() method, it gives us an opportunity to perform some caching. So, the new code would look like:

	private static final PNGSprite sprite = new PNGSprite(5, 5,
		"RRRRR"+
		"GGGGG"+
		"XXXXX"+
		"XXXXX"+
		"XXXXX");

	...

	byte[] bytes = sprite.encode(new PNGSprite.Pixel[] {
		new PNGSprite.Pixel('R', (byte)255, (byte)0,   (byte)0),
		new PNGSprite.Pixel('G', (byte)0,   (byte)255, (byte)0),
		new PNGSprite.Pixel('X', (byte)0,   (byte)0,   (byte)0),
	});

A Simple Cache

Not a huge change, but it now gives us an opportunity to perform some simple caching. Now, there are many, many caching algorithms but for the sake of the exercise let’s just create a simple one that caches them forever. Feel free to implement your own algorithm that better suits your performance/memory requirements.

	public class PNGSpriteCached extends PNGSprite
	{
		private HashMap _cache = new HashMap();
		public PNGSpriteCached(int width, int height, String pattern)
		{
			super(width, height, pattern);
		}

		/**
		 * Overloads the superclass' implementation to check for a
		 * cached local copy. If not found, then we ask the superclass
		 * to encode it and we store it for later use
		 */
		public byte[] encode(Pixel[] pixels) throws IOException
		{
			Iterator iterator = _cache.keySet().iterator();
			while (iterator.hasNext())
			{
				Pixel[] cachedPixels = (Pixel[])iterator.next();
				if (Arrays.equals(cachedPixels, pixels))
					return (byte[])_cache.get(cachedPixels);
			}
			byte[] bytes = super.encode(pixels);
			_cache.put(pixels, bytes);

			return bytes;
		}
	}

To take advantage of the caching, the only change we have to make is as follows:

	private static final PNGSprite sprite = new PNGSpriteCached(5, 5,
		"RRRRR"+
		"GGGGG"+
		"XXXXX"+
		"XXXXX"+
		"XXXXX");

Returning to Rounded Corners

So, getting back to our original requirement for generating rounded corners dynamically… let’s create a servlet that can create each of the four corners in whatever colour we want. For the following example, I am using 9×9 corners with a solid colour and a transparent outer section.

public class ImageServlet extends HttpServlet
{
	private static final PNGSprite tl = new PNGSpriteCached(9, 9,
			".......XX"+
			".....XXXX"+
			"....XXXXX"+
			"...XXXXXX"+
			"..XXXXXXX"+
			".XXXXXXXX"+
			".XXXXXXXX"+
			"XXXXXXXXX"+
			"XXXXXXXXX");
	private static final PNGSprite tr = new PNGSpriteCached(9, 9,
			"XX......."+
			"XXXX....."+
			"XXXXX...."+
			"XXXXXX..."+
			"XXXXXXX.."+
			"XXXXXXXX."+
			"XXXXXXXX."+
			"XXXXXXXXX"+
			"XXXXXXXXX");
	private static final PNGSprite bl = new PNGSpriteCached(9, 9,
			"XXXXXXXXX"+
			"XXXXXXXXX"+
			".XXXXXXXX"+
			".XXXXXXXX"+
			"..XXXXXXX"+
			"...XXXXXX"+
			"....XXXXX"+
			".....XXXX"+
			".......XX");
	private static final PNGSprite br = new PNGSpriteCached(9, 9,
			"XXXXXXXXX"+
			"XXXXXXXXX"+
			"XXXXXXXX."+
			"XXXXXXXX."+
			"XXXXXXX.."+
			"XXXXXX..."+
			"XXXXX...."+
			"XXXX....."+
			"XX.......");

	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException
	{
		String id = req.getParameter("id");
		PNGSprite sprite = null;
		if ("tl".equals(id))
			sprite = tl;
		else if ("tr".equals(id))
			sprite = tr;
		else if ("bl".equals(id))
			sprite = bl;
		else if ("br".equals(id))
			sprite = br;

		if (sprite != null)
		{
			String col = req.getParameter("col");
			byte[] bytes = sprite.encode(new PNGSprite.Pixel[] {
			    new PNGSprite.Pixel('X', col),
			    new PNGSprite.Pixel('.', (byte)0, (byte)0, (byte)0, (byte)0)
			});

			resp.setContentLength(bytes.length);
			resp.setContentType("image/png");
			resp.getOutputStream().write(bytes, 0, bytes.length);
			resp.getOutputStream().flush();
		}
		else
		{
			resp.sendError(404);
		}
	}
}

Then, to actually invoke them, we would use the following HTML markup:

<img src="http://www.myserver.com/image?id=tl&col=00ff00ff"/>
<img src="http://www.myserver.com/image?id=tl&col=00ff00ff"/>
<img src="http://www.myserver.com/image?id=tl&col=00ff00ff"/>
<img src="http://www.myserver.com/image?id=tl&col=00ff00ff"/>

Thus creating the following images:



To download the source code for PNGEncoder.java, PNGSprite.java, and PNGSpriteCached.java, click here.

Leave a Reply