Fill Your Borders with Pretty Pictures





Fill Your Borders with Pretty Pictures

Swing comes with a set of customizable borders, but sometimes you want more than they provide. This hack shows how to create a completely imagebased border that can be resized.

Swing has a prefabricated border, called the MatteBorder, which can accept an image in its constructor. For simple tiled backgrounds, such as a checkerboard pattern, this works fine. However, if you want to have particular images in each corner, creating a fully resizable image border, then you'll need something more powerful. Fortunately, Swing makes it very easy to create custom border classes. The image border in this hack will produce a border that looks like Figure.

An image-based border


The first step to any custom border is to subclass AbstractBorder and implement the paintBorder() method. The class will take eight images in the constructor, one for each corner and each side; all the code is shown in Figure.

Building an image-based border
	public class ImageBorder extends AbstractBorder {

		Image top_center, top_left, top_right;
		Image left_center, right_center;
		Image bottom_center, bottom_left, bottom_right;
		Insets insets;

		public ImageBorder(Image top_left, Image top_center, Image top_right,
			Image left_center, Image right_center,
			Image bottom_left, Image bottom_center, Image bottom_right) {

			this.top_left = top_left;
			this.top_center = top_center;
			this.top_right = top_right;
			this.left_center = left_center;
			this.right_center = right_center;
			this.bottom_left = bottom_left;
			this.bottom_center = bottom_center;
			this.bottom_right = bottom_right;
		}

	public void setInsets(Insets insets) {
				this.insets = insets;
			}

			public Insets getBorderInsets(Component c) {
				if(insets != null) {
				return insets;
				} else {
				return new Insets(top_center.getHeight(null),
				left_center.getWidth(null),
				bottom_center.getHeight(null), right_center.getWidth(null));
				}
			}

The two methods after the constructor control the border insets. These are the gaps between the panel's outer edge (and its parent) and the inner edge of the panel where the panel's children are drawn. setInsets() lets you set any size insets, but most of the time you want the insets to be based on the actual images that make up the border. The implementation of getBorderInsets() returns the insets variable if it's not null. However, if the developer didn't set the insets, then they will be derived from the widths and heights of the images that make up each side of the border (top, bottom, left, and right).

To actually draw the border, align the corner images to the appropriate corners and then tile the side images along each border side. Doing this will require using the TexturePaint class, which is an implementation of the Paint interface. Unfortunately, TexturePaint takes only BufferedImages, not regular ones, so you've got to convert your images before use.

BufferedImages are a special form of image that the Java2D framework can read and write at a pixel level. The standard Image is controlled by the operating system and is very difficult to access at the pixel level. Java doesn't let you do a straight conversion between the two kinds of images, but you can just draw one image on top of another, which is what this method in the ImageBorder class does:

	public BufferedImage createBufferedImage(Image img) {
		BufferedImage buff = new BufferedImage(img.getWidth(null),
				img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
		Graphics gfx = buff.createGraphics();
		gfx.drawImage(img, 0, 0, null);
		gfx.dispose();
		return buff;
	}

createBufferedImage() first creates an empty buffered image with the same size as the original image. The image type is TYPE_INT_ARGB, which makes the image have full 24-bit color with an alpha channel (transparency). Next, it draws the original image on top of the buffered image. The dispose() call releases any extra resources so that the code won't waste any memory, and then it returns the newly minted BufferedImage.

With buffered images in hand, the stage is set for actually filling areas of the border with images. The next ImageBorder method, fillTexture(), creates a TexturePaint using the appropriate image and then fills in the requested area:

	public void fillTexture(Graphics2D g2, Image img, int x, int y, int w, int h) {
		BufferedImage buff = createBufferedImage(img);
		Rectangle anchor = new Rectangle(x,y,img.getWidth(null),img.
	getHeight(null));
		TexturePaint paint = new TexturePaint(buff,anchor);
		g2.setPaint(paint);
		g2.fillRect(x,y,w,h);
	}

The second line of this code creates an anchor rectangle. The image will be tiled to fill the entire border area, but the anchor rectangle is needed to specify where the image will be anchored. We normally think of images being anchored to (0,0), which works fine for the upper-left corner of the border but wouldn't work for the other sides. The right corners would need to be right aligned instead of left aligned, as would happen with (0,0). By setting the anchor to be the location and dimensions of the image itself, you take care of anchoring altogether. The tiling will start wherever the single image would have been drawn.

Now that you can fill an area with a properly aligned texture, you are ready for the paintBorder() method, shown in Figure.

Painting the border
	public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height) {
		g.setColor(Color.white);
		g.fillRect(x,y,width,height);

		Graphics2D g2 = (Graphics2D)g;

		int tlw = top_left.getWidth(null);
		int tlh = top_left.getHeight(null);
		int tcw = top_center.getWidth(null);
		int tch = top_center.getHeight(null);
		int trw = top_right.getWidth(null);
		int trh = top_right.getHeight(null);

int lcw = left_center.getWidth(null);
		int lch = left_center.getHeight(null);
		int rcw = right_center.getWidth(null);
		int rch = right_center.getHeight(null);
		int blw = bottom_left.getWidth(null);
		int blh = bottom_left.getHeight(null);
		int bcw = bottom_center.getWidth(null);
		int bch = bottom_center.getHeight(null);
		int brw = bottom_right.getWidth(null);
		int brh = bottom_right.getHeight(null);
		
		fillTexture(g2,top_left,x,y,tlw,tlh);
		fillTexture(g2,top_center,x+tlw,y,width-tlw-trw,tch);
		fillTexture(g2,top_right,x+width-trw,y,trw,trh);
		fillTexture(g2,left_center,x,y+tlh,lcw,height-tlh-blh);
		fillTexture(g2,right_center,x+width-rcw,y+trh,rcw,height-trh-brh);
		fillTexture(g2,bottom_left,x,y+height-blh,blw,blh);
		fillTexture(g2,bottom_center,x+blw,y+height-bch,width-blw-brw,bch);
		fillTexture(g2,bottom_right,x+width-brw,y+height-brh,brw,brh);
	}

The first two lines fill the entire border area with white. Then you have to cast the Graphics to a Graphics2D object because you will be doing some advanced painting later on. Next, save a reference to the width and height of each image (the top left, top center, top right, etc.). Finally, call fillTexture() on each section of the border to fill it in.

The test program shown in Figure creates a panel that uses the ImageBorder. It creates a nested frame, panel, and button, and then it creates an ImageBorder for the panel using eight images.

Testing out an image-based border
	public class ImageBorderHack {

		public static void main(String[] args) {
			JFrame frame = new JFrame("Hack #3: Fill Your Borders with Pretty
							Pictures");
			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			JPanel panel = new JPanel();
			JButton button = new JButton("Image Border Test");
			panel.add(button);

			ImageBorder image_border = new ImageBorder(
				new ImageIcon("images/upper_left.png").getImage(),
				new ImageIcon("images/upper.png").getImage(),
				new ImageIcon("images/upper_right.png").getImage(),

				new ImageIcon("images/left_center.png").getImage(),
				new ImageIcon("images/right_center.png").getImage(),
	   new ImageIcon("images/bottom_left.png").getImage(),
				new ImageIcon("images/bottom_center.png").getImage(),
				new ImageIcon("images/bottom_right.png").getImage()
				);
			panel.setBorder(image_border);

			frame.getContentPane().add(panel);
			frame.pack();
			frame.setVisible(true);
		}

	}

The sample border is made out of a single image sliced into eight pieces using Photoshop (the center image is discarded). You can see these slices in Figure.

Source image in Photoshop with slices


The completed ImageBorder class will take the Photoshop slices and tile them to create the finished border, as seen in Figure.

Completed image border


The best thing about these image-based borders is that you can completely change their look by just dropping in new images, which is easy to do with the slice tool in Photoshop. When you create your own image borders, I recommend starting with a rectangular shape layer and then using filters and effects to create drop shadows, bevels, and stroked borders.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows