Implement outline support for nine patches
b/15856895
Nine patches now have outline round rect metadata stored as optional
png tags. aapt generates these automatically by inspecting the bitmap
pixels to estimate outline bounds and round rect radius, based on
opacity.
Change-Id: I226e328a97873010d9e1adb797ac48f93a31183c
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 12f5b92..28de933 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -77,6 +77,14 @@
int32_t layoutBoundsRight;
int32_t layoutBoundsBottom;
+ // Round rect outline description
+ int32_t outlineInsetsLeft;
+ int32_t outlineInsetsTop;
+ int32_t outlineInsetsRight;
+ int32_t outlineInsetsBottom;
+ float outlineRadius;
+ bool outlineFilled;
+
png_uint_32 allocHeight;
png_bytepp allocRows;
};
@@ -397,6 +405,98 @@
return NO_ERROR;
}
+static void find_max_opacity(png_byte** rows,
+ int startX, int startY, int endX, int endY, int dX, int dY,
+ int* out_inset)
+{
+ bool opaque_within_inset = true;
+ unsigned char max_opacity = 0;
+ int inset = 0;
+ *out_inset = 0;
+ for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+ png_byte* color = rows[y] + x * 4;
+ unsigned char opacity = color[3];
+ if (opacity > max_opacity) {
+ max_opacity = opacity;
+ *out_inset = inset;
+ }
+ if (opacity == 0xff) return;
+ }
+}
+
+static bool is_opaque_over_row(png_byte* row, int startX, int endX)
+{
+ for (int x = startX; x < endX; x++) {
+ png_byte* color = row + x * 4;
+ if (color[3] != 0xff) return false;
+ }
+ return true;
+}
+
+static bool is_opaque_over_col(png_byte** rows, int offsetX, int startY, int endY)
+{
+ for (int y = startY; y < endY; y++) {
+ png_byte* color = rows[y] + offsetX * 4;
+ if (color[3] != 0xff) return false;
+ }
+ return true;
+}
+
+static void get_outline(image_info* image)
+{
+ int midX = image->width / 2;
+ int midY = image->height / 2;
+ int endX = image->width - 2;
+ int endY = image->height - 2;
+
+ // find left and right extent of nine patch content on center row
+ if (image->width > 4) {
+ find_max_opacity(image->rows, 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
+ find_max_opacity(image->rows, endX, midY, midX, -1, -1, 0, &image->outlineInsetsRight);
+ } else {
+ image->outlineInsetsLeft = 0;
+ image->outlineInsetsRight = 0;
+ }
+
+ // find top and bottom extent of nine patch content on center column
+ if (image->height > 4) {
+ find_max_opacity(image->rows, midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
+ find_max_opacity(image->rows, midX, endY, -1, midY, 0, -1, &image->outlineInsetsBottom);
+ } else {
+ image->outlineInsetsTop = 0;
+ image->outlineInsetsBottom = 0;
+ }
+
+ int innerStartX = 1 + image->outlineInsetsLeft;
+ int innerStartY = 1 + image->outlineInsetsTop;
+ int innerEndX = endX - image->outlineInsetsRight;
+ int innerEndY = endY - image->outlineInsetsBottom;
+ int innerMidX = (innerEndX + innerStartX) / 2;
+ int innerMidY = (innerEndY + innerStartY) / 2;
+
+ // assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center
+ image->outlineFilled = is_opaque_over_row(image->rows[innerMidY], innerStartX, innerEndX)
+ && is_opaque_over_col(image->rows, innerMidX, innerStartY, innerStartY);
+
+ int diagonalInset = 0;
+ find_max_opacity(image->rows, innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
+ &diagonalInset);
+
+ // Determine source radius based upon inset
+ // radius = 1 / (sqrt(2) - 1) * inset
+ image->outlineRadius = 2.4142f * diagonalInset;
+
+ NOISY(printf("outline insets %d %d %d %d, rad %f, filled %d\n",
+ image->outlineFilled,
+ image->outlineInsetsLeft,
+ image->outlineInsetsTop,
+ image->outlineInsetsRight,
+ image->outlineInsetsBottom,
+ image->outlineRadius,
+ image->outlineFilled));
+}
+
static uint32_t get_color(
png_bytepp rows, int left, int top, int right, int bottom)
@@ -571,6 +671,9 @@
image->layoutBoundsRight, image->layoutBoundsBottom));
}
+ // use opacity of pixels to estimate the round rect outline
+ get_outline(image);
+
// If padding is not yet specified, take values from size.
if (image->info9Patch.paddingLeft < 0) {
image->info9Patch.paddingLeft = xDivs[0];
@@ -966,9 +1069,10 @@
int bit_depth, interlace_type, compression_type;
int i;
- png_unknown_chunk unknowns[2];
+ png_unknown_chunk unknowns[3];
unknowns[0].data = NULL;
unknowns[1].data = NULL;
+ unknowns[2].data = NULL;
png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep));
if (outRows == (png_bytepp) 0) {
@@ -1038,12 +1142,17 @@
}
if (imageInfo.is9Patch) {
- int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0);
- int p_index = imageInfo.haveLayoutBounds ? 1 : 0;
- int b_index = 0;
+ int chunk_count = 2 + (imageInfo.haveLayoutBounds ? 1 : 0);
+ int p_index = imageInfo.haveLayoutBounds ? 2 : 1;
+ int b_index = 1;
+ int o_index = 0;
+
+ // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
png_byte *chunk_names = imageInfo.haveLayoutBounds
- ? (png_byte*)"npLb\0npTc\0"
- : (png_byte*)"npTc";
+ ? (png_byte*)"npOl\0npLb\0npTc\0"
+ : (png_byte*)"npOl\0npTc";
+
+ // base 9 patch data
NOISY(printf("Adding 9-patch info...\n"));
strcpy((char*)unknowns[p_index].name, "npTc");
unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
@@ -1051,6 +1160,18 @@
// TODO: remove the check below when everything works
checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
+ // automatically generated 9 patch outline data
+ int chunk_size = sizeof(png_uint_32) * 6;
+ strcpy((char*)unknowns[o_index].name, "npOl");
+ unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1);
+ png_byte outputData[chunk_size];
+ memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32));
+ ((float*) outputData)[4] = imageInfo.outlineRadius;
+ ((png_uint_32*) outputData)[5] = imageInfo.outlineFilled ? 1 : 0;
+ memcpy(unknowns[o_index].data, &outputData, chunk_size);
+ unknowns[o_index].size = chunk_size;
+
+ // optional optical inset / layout bounds data
if (imageInfo.haveLayoutBounds) {
int chunk_size = sizeof(png_uint_32) * 4;
strcpy((char*)unknowns[b_index].name, "npLb");
@@ -1099,6 +1220,7 @@
free(outRows);
free(unknowns[0].data);
free(unknowns[1].data);
+ free(unknowns[2].data);
png_get_IHDR(write_ptr, write_info, &width, &height,
&bit_depth, &color_type, &interlace_type,