import java.math.BigInteger;\r
import java.security.MessageDigest;\r
import java.security.NoSuchAlgorithmException;\r
+import java.util.Arrays;\r
import java.util.HashMap;\r
import java.util.regex.Matcher;\r
import java.util.regex.Pattern;\r
\r
// Strip ".9" from file name, read ninepatch split pixels, and strip ninepatch split pixels.\r
int[] splits = null;\r
+ int[] pads = null;\r
if (name.endsWith(".9")) {\r
name = name.substring(0, name.length() - 2);\r
splits = getSplits(image, name);\r
+ pads = getPads(image, name, splits);\r
// Strip split pixels.\r
BufferedImage newImage = new BufferedImage(image.getWidth() - 2, image.getHeight() - 2, BufferedImage.TYPE_4BYTE_ABGR);\r
newImage.getGraphics().drawImage(image, 0, 0, newImage.getWidth(), newImage.getHeight(), 1, 1, image.getWidth() - 1,\r
// Ninepatches won't be rotated or whitespace stripped.\r
rect = new Rect(image, 0, 0, image.getWidth(), image.getHeight());\r
rect.splits = splits;\r
+ rect.pads = pads;\r
rect.canRotate = false;\r
}\r
\r
* has left, right, top, bottom. */\r
private int[] getSplits (BufferedImage image, String name) {\r
WritableRaster raster = image.getRaster();\r
- int[] rgba = new int[4];\r
+ \r
+ int startX = getSplitPoint(raster, name, 1, 0, true, true);\r
+ int endX = getSplitPoint(raster, name, startX, 0, false, true);\r
+ int startY = getSplitPoint(raster, name, 0, 1, true, false);\r
+ int endY = getSplitPoint(raster, name, 0, startY, false, false);\r
\r
- int startX = 1;\r
- for (int x = 1; x < raster.getWidth() - 1; x++) {\r
- raster.getPixel(x, 0, rgba);\r
- if (rgba[3] == 0) continue;\r
- if (rgba[0] != 0 || rgba[1] != 0 || rgba[2] != 0 || rgba[3] != 255) splitError(x, 0, rgba, name);\r
- startX = x;\r
- break;\r
- }\r
- int endX;\r
- for (endX = startX; endX < raster.getWidth() - 1; endX++) {\r
- raster.getPixel(endX, 0, rgba);\r
- if (rgba[3] == 0) break;\r
- if (rgba[0] != 0 || rgba[1] != 0 || rgba[2] != 0 || rgba[3] != 255) splitError(endX, 0, rgba, name);\r
+ // Ensure pixels after the end are not invalid.\r
+ getSplitPoint(raster, name, endX+1, 0, true, true);\r
+ getSplitPoint(raster, name, 0, endY+1, true, false);\r
+ \r
+ // No splits, or all splits.\r
+ if (startX == 0 && endX == 0 && startY == 0 && endY == 0) \r
+ return null;\r
+\r
+ // Subtraction here is because the coordinates were computed before the 1px border was stripped.\r
+ if (startX != 0) {\r
+ startX--;\r
+ endX = raster.getWidth() - 2 - (endX - 1);\r
+ } else {\r
+ // If no start point was ever found, we assume full stretch.\r
+ endX = raster.getWidth() - 2;\r
}\r
- for (int x = endX + 1; x < raster.getWidth() - 1; x++) {\r
- raster.getPixel(x, 0, rgba);\r
- if (rgba[3] != 0) splitError(x, 0, rgba, name);\r
+ if (startY != 0) {\r
+ startY--;\r
+ endY = raster.getHeight() - 2 - (endY - 1);\r
+ } else {\r
+ // If no start point was ever found, we assume full stretch.\r
+ endY = raster.getHeight() - 2;\r
}\r
+ \r
+ return new int[] {startX, endX, startY, endY};\r
+ }\r
+ \r
+ /** Returns the pads, or null if the image had no pads or the pads match the splits. Pads are an int[4] that\r
+ * has left, right, top, bottom. */\r
+ private int[] getPads (BufferedImage image, String name, int[] splits) {\r
+ WritableRaster raster = image.getRaster();\r
+ \r
+ int bottom = raster.getHeight() - 1;\r
+ int right = raster.getWidth() - 1;\r
+ \r
+ int startX = getSplitPoint(raster, name, 1, bottom, true, true);\r
+ int startY = getSplitPoint(raster, name, right, 1, true, false);\r
+ \r
+ // No need to hunt for the end if a start was never found.\r
+ int endX = 0;\r
+ int endY = 0;\r
+ if (startX != 0)\r
+ endX = getSplitPoint(raster, name, startX+1, bottom, false, true);\r
+ if (startY != 0)\r
+ endY = getSplitPoint(raster, name, right, startY+1, false, false);\r
+ \r
+ // Ensure pixels after the end are not invalid.\r
+ getSplitPoint(raster, name, endX+1, bottom, true, true);\r
+ getSplitPoint(raster, name, right, endY+1, true, false);\r
+ \r
+ // No pads.\r
+ if (startX == 0 && endX == 0 && startY == 0 && endY == 0) {\r
+ return null;\r
+ } \r
\r
- int startY = 1;\r
- for (int y = 1; y < raster.getHeight() - 1; y++) {\r
- raster.getPixel(0, y, rgba);\r
- if (rgba[3] == 0) continue;\r
- if (rgba[0] != 0 || rgba[1] != 0 || rgba[2] != 0 || rgba[3] != 255) splitError(0, y, rgba, name);\r
- startY = y;\r
- break;\r
+ // Subtraction here is because the coordinates were computed before the 1px border was stripped.\r
+ if (startX > 0) {\r
+ startX--;\r
+ endX = raster.getWidth() - 2 - (endX - 1);\r
+ } else {\r
+ // If no start point was ever found, we assume full stretch.\r
+ endX = raster.getWidth() - 2;\r
}\r
- int endY;\r
- for (endY = startY; endY < raster.getHeight() - 1; endY++) {\r
- raster.getPixel(0, endY, rgba);\r
- if (rgba[3] == 0) break;\r
- if (rgba[0] != 0 || rgba[1] != 0 || rgba[2] != 0 || rgba[3] != 255) splitError(0, endY, rgba, name);\r
+ if (startY > 0) {\r
+ startY--;\r
+ endY = raster.getHeight() - 2 - (endY - 1);\r
+ } else {\r
+ // If no start point was ever found, we assume full stretch.\r
+ endY = raster.getHeight() - 2;\r
}\r
- for (int y = endY + 1; y < raster.getHeight() - 1; y++) {\r
- raster.getPixel(0, y, rgba);\r
- if (rgba[3] != 0) splitError(0, y, rgba, name);\r
+ \r
+ int[] pads = new int[] {startX, endX, startY, endY};\r
+ \r
+ if ((splits != null) && Arrays.equals(pads, splits)) {\r
+ return null;\r
}\r
+ \r
+ return pads;\r
+ }\r
+ \r
+ /** Hunts for the start or end of a sequence of split pixels. Begins searching at (startX, startY) then follows\r
+ * along the x or y axis (depending on value of xAxis) for the first non-transparent pixel if startPoint is true,\r
+ * or the first transparent pixel if startPoint is false. Returns 0 if none found, as 0 is considered an invalid\r
+ * split point being in the outer border which will be stripped. */\r
+ private int getSplitPoint(WritableRaster raster, String name, int startX, int startY, boolean startPoint, boolean xAxis) \r
+ {\r
+ int[] rgba = new int[4];\r
\r
- // No splits, or all splits.\r
- if (startX == 1 && endX == 1 && startY == 1 && endY == 1) return null;\r
-\r
- // Subtraction here is because the coordinates were computed before the 1px border was stripped.\r
- return new int[] {startX - 1, raster.getWidth() - 2 - (endX - 1), startY - 1, raster.getHeight() - 2 - (endY - 1)};\r
+ int next = xAxis ? startX : startY;\r
+ int end = xAxis ? raster.getWidth() : raster.getHeight();\r
+ int breakA = startPoint ? 255 : 0;\r
+ \r
+ int x = startX;\r
+ int y = startY;\r
+ while (next != end) {\r
+ if (xAxis)\r
+ x = next;\r
+ else\r
+ y = next;\r
+ \r
+ raster.getPixel(x, y, rgba);\r
+ if (rgba[3] == breakA)\r
+ return next;\r
+ \r
+ if (!startPoint && (rgba[0] != 0 || rgba[1] != 0 || rgba[2] != 0 || rgba[3] != 255))\r
+ splitError(x, y, rgba, name);\r
+ \r
+ next++;\r
+ }\r
+ \r
+ return 0;\r
}\r
\r
static private String hash (BufferedImage image) {\r
private float[] vertices = new float[9 * 4 * 5];\r
private int idx;\r
private final Color color = new Color(Color.WHITE);\r
-\r
+ \r
+ /** Padding overrides are stored by NinePatch for use by elements such as tables that use\r
+ * a NinePatch for their background. If these values are not set, the width/height of \r
+ * border patches are used (eg. leftWidth, rightWidth, topHeight, and bottomHeight).\r
+ */\r
+ private int padLeft = -1, padRight = -1, padTop = -1, padBottom = -1;\r
+ \r
public NinePatch (Texture texture, int left, int right, int top, int bottom) {\r
this(new TextureRegion(texture), left, right, top, bottom);\r
}\r
public float getTotalHeight () {\r
return topHeight + middleHeight + bottomHeight;\r
}\r
+ \r
+ public void setPadding(int left, int right, int top, int bottom) {\r
+ this.padLeft = left;\r
+ this.padRight = right;\r
+ this.padTop = top;\r
+ this.padBottom = bottom;\r
+ }\r
+ \r
+ public void setPadLeft(int left) {\r
+ this.padLeft = left;\r
+ }\r
+ \r
+ public void setPadRight(int right) {\r
+ this.padRight = right;\r
+ }\r
+ \r
+ public void setPadTop(int top) {\r
+ this.padTop = top;\r
+ }\r
+ \r
+ public void setPadBottom(int bottom) {\r
+ this.padBottom = bottom;\r
+ }\r
+ \r
+ public float getPadLeft() {\r
+ if (this.padLeft == -1)\r
+ return this.getLeftWidth();\r
+ return this.padLeft;\r
+ }\r
+ \r
+ public float getPadRight() {\r
+ if (this.padRight == -1)\r
+ return this.getRightWidth();\r
+ return this.padRight;\r
+ }\r
+ \r
+ public float getPadTop() {\r
+ if (this.padTop == -1)\r
+ return this.getTopHeight();\r
+ return this.padTop;\r
+ }\r
+ \r
+ public float getPadBottom() {\r
+ if (this.padBottom == -1)\r
+ return this.getBottomHeight();\r
+ return this.padBottom;\r
+ } \r
}\r
public int height;\r
public boolean flip;\r
public int[] splits;\r
+ public int[] pads;\r
}\r
\r
final Array<Page> pages = new Array<Page>();\r
if (readTuple(reader) == 4) { // split is optional\r
region.splits = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]),\r
Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])};\r
- readTuple(reader);\r
+ \r
+ if (readTuple(reader) == 4) { // pads are optional, but only present with splits\r
+ region.pads = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]),\r
+ Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])};\r
+ \r
+ readTuple(reader);\r
+ }\r
}\r
\r
region.originalWidth = Integer.parseInt(tuple[0]);\r
atlasRegion.originalWidth = region.originalWidth;\r
atlasRegion.rotate = region.rotate;\r
atlasRegion.splits = region.splits;\r
+ atlasRegion.pads = region.pads;\r
if (region.flip) atlasRegion.flip(false, true);\r
regions.add(atlasRegion);\r
}\r
if (region.name.equals(name)) {\r
int[] splits = region.splits;\r
if (splits == null) throw new IllegalArgumentException("Region does not have ninepatch splits: " + name);\r
- return new NinePatch(region, splits[0], splits[1], splits[2], splits[3]);\r
+ NinePatch retVal = new NinePatch(region, splits[0], splits[1], splits[2], splits[3]);\r
+ if (region.pads != null) {\r
+ retVal.setPadding(region.pads[0], region.pads[1], region.pads[2], region.pads[3]);\r
+ }\r
+ return retVal;\r
}\r
}\r
return null;\r
\r
/** The ninepatch splits, or null if not a ninepatch. Has 4 elements: left, right, top, bottom. */\r
public int[] splits;\r
+ \r
+ /** The ninepatch pads, or null if not a ninepatch or ninepatch does not override padding.\r
+ * Has 4 elements: left, right, top, bottom. */\r
+ public int[] pads;\r
\r
public AtlasRegion (Texture texture, int x, int y, int width, int height) {\r
super(texture, x, y, width, height);\r