Recently I had to load .9.png file manually (a nine-patch) and create a NinePatch object out of it in libGDX. Since libGDX doesn’t provide any tool to do that manually (only via atlases), I had to write the code myself. Hopefully it will help someone else too:
/**
* This method loads 9-patch from given file and creates a NinePatch object out of it.
* <p>
* Note that a new Texture is generated by this method that needs to be manually disposed of when
* the returning NinePatch is no longer needed (by doing "NinePatch.getTexture().dispose()").
* <p>
* libGDX's code that processes 9-patch markers is located in {@link ImageProcessor} class
* (the code here is not based on the libGDX's code though).
*
* @param ninePatchFile file handle of a 9-patch file (it normally ends in ".9.png").
* @return NinePatch object created from 9-patch image file
* @author Betalord
*/
public static NinePatch readNinePatch(FileHandle ninePatchFile) {
// NOTE: point (0,0) is in left-top corner of a texture.
final int black = Color.rgba8888(Color.BLACK);
Pixmap pm = new Pixmap(ninePatchFile);
Pixmap pmTex = new Pixmap(pm.getWidth()-2, pm.getHeight()-2, pm.getFormat()); // cut out the black markers of nine-patch
pmTex.drawPixmap(pm, 0, 0, 1, 1, pm.getWidth()-2, pm.getHeight()-2);
Texture t = new Texture(pmTex);
// process it:
// let's detect scalable area:
int left = -1; // the left-most black pixel
int right = -1; // the right-most black pixel
int top = -1; // the top-most black pixel
int bottom = -1; // the bottom-most black pixel
boolean last = false; // was last pixel black?
for (int i = 0; i < pm.getWidth(); i++) {
int p = pm.getPixel(i, 0);
if (!last && p == black) left = i;
if (last && p != black) {
right = i-1;
break;
}
if (p == black)
last = true;
}
last = false; // was last pixel black?
for (int i = 0; i < pm.getHeight(); i++) {
int p = pm.getPixel(0, i);
if (!last && p == black) top = i;
if (last && p != black) {
bottom = i-1;
break;
}
if (p == black)
last = true;
}
// now let's detect padding (fill area):
int pad_left = -1, pad_right = -1, pad_top = -1, pad_bottom = -1;
last = false; // was last pixel black?
for (int i = 0; i < pm.getWidth(); i++) {
int p = pm.getPixel(i, pm.getHeight()-1);
if (!last && p == black) pad_left = i;
if (last && p != black) {
pad_right = i-1;
break;
}
if (p == black)
last = true;
}
last = false; // was last pixel black?
for (int i = 0; i < pm.getHeight(); i++) {
int p = pm.getPixel(pm.getWidth()-1, i);
if (!last && p == black) pad_top = i;
if (last && p != black) {
pad_bottom = i-1;
break;
}
if (p == black)
last = true;
}
// construct a 9-patch:
TextureRegion
tl, ml, bl, // top-left, middle-left, bottom-left
tm, mm, bm, // top-middle, middle-middle, bottom-middle
tr, mr, br; // top-right, middle-right, bottom-right
/*
* Order of processing:
* #####
* #147#
* #258#
* #369#
* #####
*/
// TextureRegion constructor args: x, y, width, height
// adjust markers to match "inner" texture's dimensions (which is missing 9-match black marker borders):
left--;
right--;
top--;
bottom--;
pad_left--;
pad_right--;
pad_top--;
pad_bottom--;
tl = new TextureRegion(t, 0, 0, left, top);
ml = new TextureRegion(t, 0, top, left, bottom-top+1);
bl = new TextureRegion(t, 0, bottom+1, left, t.getHeight()-bottom-1);
tm = new TextureRegion(t, left, 0, right-left+1, top);
mm = new TextureRegion(t, left, top, right-left+1, bottom-top+1);
bm = new TextureRegion(t, left, bottom+1, right-left+1, t.getHeight()-bottom-1);
tr = new TextureRegion(t, right+1, 0, t.getWidth()-right-1, top);
mr = new TextureRegion(t, right+1, top, t.getWidth()-right-1, bottom-top+1);
br = new TextureRegion(t, right+1, bottom+1, t.getWidth()-right-1, t.getHeight()-bottom-1);
NinePatch n = new NinePatch(tl, tm, tr, ml, mm, mr, bl, bm, br);
// set the padding:
n.setPadding(pad_left, t.getWidth()-pad_right-1, pad_top, t.getHeight()-pad_bottom-1); // this code hasn't really been tested yet (should work though)!
// clean up the memory (except for the texture itself which should be disposed by the called):
pm.dispose();
pmTex.dispose();
return n;
}