Nicer GUI elements with NinePatches

From JPCT
Revision as of 10:50, 30 December 2013 by Irony (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

A 9-patch is a special type of bitmap that is very useful for GUI elements. When it is blitted larger than the original size, only the inner part gets scaled in two dimensions, while borders are scaled in one dimension (horizontal or vertical) and the corners are not scaled at all. Therefore, it will look good in all resolutions without getting blurry on the corners, even when using a very small source image (which saves memory).

There is no built-in support in JPCT-AE for it, but you can use the implementation below. It's not a 1:1 adaptation of Android's 9Patch though (only scaleable area is supported, not fill area).

First, you will need a source bitmap like this.

Important are the black pixels on the upper and left edge. They need to be on the first horizontal and vertical stripe of the image and show where the scale zone begins and ends. The black borders themselves do not get blitted.
I made the button translucent inside, but that is not mandatory.


Here is the code:


class NinePatch {
	
 	private int[]X = new int[2];
	private int[]Y = new int[2]; //These hold the scale zone coordinates
	private int W, H;  // Input bitmap dimensions
	private Texture tex;
	private String name;
	
	public Space9Patch(Bitmap b, String name) {  // Make sure b is not scaled! If you use a white picture you can blit it in any color later on
		this.name = name;
		
		W = b.getWidth();
		H = b.getHeight();
		X[0] = X[1] = Y[0] = Y[1] = -1; //Start and end positions of inner scale zone 
		
		for (int x=0; x<W; x++) { // Analyze first horizontal stripe of bitmap
			if (b.getPixel(x, 0) == Color.BLACK && X[0] == -1) {	// Look for black pixel
				X[0] = x;   //Save position of first black pixel
			}
			else if (b.getPixel(x, 0) == Color.TRANSPARENT && X[0]>=0) {  //Look for first transparent pixel after black stripe
				X[1] = x-1;  //Save position of last black pixel
				break;
			}
		}
		
		for (int y=0; y<H; y++) {	// Analyze first vertical stripe
			if (b.getPixel(0, y) == Color.BLACK && Y[0] == -1) { //same as above but in vertical direction
				Y[0] = y;
			}
			else if (b.getPixel(0, y) == Color.TRANSPARENT && Y[0]>=0) {
				Y[1] = y-1;
				break;
			}
		}
		
		//Create texture from bitmap
		TextureManager tm = TextureManager.getInstance();
		if (!tm.containsTexture(name)) tm.addTexture(name, new Texture(b, true));
		tex = tm.getTexture(name);
	}
	
	//Draw 9-patch in abitrary size and additional color
	public void blit(FrameBuffer fb, int x, int y, int w, int h, int transp, RGBColor col) { 
		fb.blit(tex, 1, 1, x, y, X[0]-1, Y[0]-1, X[0], Y[0], transp, false, col);
    	        fb.blit(tex, X[0], 1, x+X[0], y, X[1] - X[0]+1, Y[0]-1, w- X[0]- (W-X[1])+1, Y[0], transp, false, col);
		fb.blit(tex, X[1]+1, 1, x+w-(W-X[1])+1, y, W-X[1]-1, Y[0]-1, W-X[1]-1, Y[0], transp, false, col);
		 
		fb.blit(tex, 1, Y[0], x, y+Y[0], X[0]-1, Y[1] - Y[0]+1, X[0], h - Y[0] - (H-Y[1]) + 1, transp, false, col);
		fb.blit(tex, X[0], Y[0], x+X[0], y+Y[0], X[1]-X[0]+1, Y[1]-Y[0]+1, w-X[0] - (W-X[1])+1, h - Y[0] - (H-Y[1])+1, transp, false, col);
		fb.blit(tex, X[1]+1, Y[0], x+w-(W-X[1])+1, y+Y[0], W-X[1]-1, Y[1] - Y[0]+1, W-X[1]-1, h - Y[0] - (H-Y[1])+1, transp, false, col);
		
		fb.blit(tex, 1, Y[1]+1, x, y+h-(H-Y[1])+1, X[0], H-Y[1]-1, X[0], H-Y[1]-1, transp, false, col);
		fb.blit(tex, X[0], Y[1]+1, x+X[0], y+h-(H-Y[1])+1, X[1] - X[0]+1, H-Y[1]-2, w - X[0] - (W-X[1])+1, H-Y[1]-1, transp, false, col);
		fb.blit(tex, X[1]+1, Y[1]+1, x+w-(W-X[1])+1, y+h-(H-Y[1])+1, W-X[1]-1, H-Y[1]-2, W-X[1]-1, H-Y[1]-1, transp, false, col);		
	}
	
	//Blit using original color
	public void blit(FrameBuffer fb, int x, int y, int w, int h, int transp) {
		blit(fb, x, y, w, h, transp, RGBColor.WHITE);
	}
}