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
This commit is contained in:
Chris Craik
2014-07-08 17:13:08 -07:00
parent 5028fb0357
commit 47cd8e921d
10 changed files with 280 additions and 90 deletions

View File

@@ -39,14 +39,12 @@ jfieldID gOptions_heightFieldID;
jfieldID gOptions_mimeFieldID;
jfieldID gOptions_mCancelID;
jfieldID gOptions_bitmapFieldID;
jfieldID gBitmap_nativeBitmapFieldID;
jfieldID gBitmap_layoutBoundsFieldID;
#if 0
#define TRACE_BITMAP(code) code
#else
#define TRACE_BITMAP(code)
#endif
jfieldID gBitmap_nativeBitmapFieldID;
jfieldID gBitmap_ninePatchInsetsFieldID;
jclass gInsetStruct_class;
jmethodID gInsetStruct_constructorMethodID;
using namespace android;
@@ -195,8 +193,7 @@ private:
const unsigned int mSize;
};
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
jobject options) {
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
int sampleSize = 1;
@@ -331,12 +328,12 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
}
jbyteArray ninePatchChunk = NULL;
if (peeker.fPatch != NULL) {
if (peeker.mPatch != NULL) {
if (willScale) {
scaleNinePatchChunk(peeker.fPatch, scale);
scaleNinePatchChunk(peeker.mPatch, scale);
}
size_t ninePatchArraySize = peeker.fPatch->serializedSize();
size_t ninePatchArraySize = peeker.mPatch->serializedSize();
ninePatchChunk = env->NewByteArray(ninePatchArraySize);
if (ninePatchChunk == NULL) {
return nullObjectReturn("ninePatchChunk == null");
@@ -347,28 +344,18 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
return nullObjectReturn("primitive array == null");
}
memcpy(array, peeker.fPatch, peeker.fPatchSize);
memcpy(array, peeker.mPatch, peeker.mPatchSize);
env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
}
jintArray layoutBounds = NULL;
if (peeker.fLayoutBounds != NULL) {
layoutBounds = env->NewIntArray(4);
if (layoutBounds == NULL) {
return nullObjectReturn("layoutBounds == null");
}
jint scaledBounds[4];
if (willScale) {
for (int i=0; i<4; i++) {
scaledBounds[i] = (jint)((((jint*)peeker.fLayoutBounds)[i]*scale) + .5f);
}
} else {
memcpy(scaledBounds, (jint*)peeker.fLayoutBounds, sizeof(scaledBounds));
}
env->SetIntArrayRegion(layoutBounds, 0, 4, scaledBounds);
jobject ninePatchInsets = NULL;
if (peeker.mHasInsets) {
ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
peeker.mOutlineRadius, peeker.mOutlineFilled, scale);
if (javaBitmap != NULL) {
env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds);
env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
}
}
@@ -409,10 +396,10 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
}
if (padding) {
if (peeker.fPatch != NULL) {
if (peeker.mPatch != NULL) {
GraphicsJNI::set_jrect(env, padding,
peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
} else {
GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
}
@@ -446,7 +433,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
// now create the java bitmap
return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(),
bitmapCreateFlags, ninePatchChunk, layoutBounds, -1);
bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}
// Need to buffer enough input to be able to rewind as much as might be read by a decoder
@@ -576,7 +563,7 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) {
jclass options_class = env->FindClass("android/graphics/BitmapFactory$Options");
SkASSERT(options_class);
gOptions_bitmapFieldID = getFieldIDCheck(env, options_class, "inBitmap",
"Landroid/graphics/Bitmap;");
"Landroid/graphics/Bitmap;");
gOptions_justBoundsFieldID = getFieldIDCheck(env, options_class, "inJustDecodeBounds", "Z");
gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I");
gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig",
@@ -598,7 +585,12 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) {
jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
SkASSERT(bitmap_class);
gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "J");
gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mOpticalInsets", "[I");
gBitmap_ninePatchInsetsFieldID = getFieldIDCheck(env, bitmap_class, "mNinePatchInsets",
"Landroid/graphics/NinePatch$InsetStruct;");
gInsetStruct_class = (jclass) env->NewGlobalRef(env->FindClass("android/graphics/NinePatch$InsetStruct"));
gInsetStruct_constructorMethodID = env->GetMethodID(gInsetStruct_class, "<init>", "(IIIIIIIIFZF)V");
int ret = AndroidRuntime::registerNativeMethods(env,
"android/graphics/BitmapFactory$Options",
gOptionsMethods,

View File

@@ -415,7 +415,7 @@ static void assert_premultiplied(const SkBitmap& bitmap, bool isPremultiplied) {
}
jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density)
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density)
{
SkASSERT(bitmap);
SkASSERT(bitmap->pixelRef());
@@ -429,17 +429,11 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buff
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
reinterpret_cast<jlong>(bitmap), buffer,
bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
ninepatch, layoutbounds);
ninePatchChunk, ninePatchInsets);
hasException(env); // For the side effect of logging.
return obj;
}
jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
jbyteArray ninepatch, int density)
{
return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninepatch, NULL, density);
}
void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap,
bool isPremultiplied)
{
@@ -720,7 +714,7 @@ int register_android_graphics_Graphics(JNIEnv* env)
gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "J");
gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[B[I)V");
gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V");
gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "(IIZ)V");
gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I");
gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");

View File

@@ -76,10 +76,12 @@ public:
bitmap's SkAlphaType must already be in sync with bitmapCreateFlags.
*/
static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density = -1);
int bitmapCreateFlags, jbyteArray ninePatch, jobject ninePatchInsets, int density = -1);
static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
jbyteArray ninepatch, int density = -1);
jbyteArray ninePatch, int density = -1) {
return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninePatch, NULL, density);
}
/** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
sync with isPremultiplied

View File

@@ -21,7 +21,7 @@
using namespace android;
bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) {
if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
if (!strcmp("npTc", tag) && length >= sizeof(Res_png_9patch)) {
Res_png_9patch* patch = (Res_png_9patch*) data;
size_t patchSize = patch->serializedSize();
assert(length == patchSize);
@@ -30,12 +30,9 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) {
memcpy(patchNew, patch, patchSize);
Res_png_9patch::deserialize(patchNew);
patchNew->fileToDevice();
free(fPatch);
fPatch = patchNew;
fPatchSize = patchSize;
//printf("9patch: (%d,%d)-(%d,%d)\n",
// fPatch.sizeLeft, fPatch.sizeTop,
// fPatch.sizeRight, fPatch.sizeBottom);
free(mPatch);
mPatch = patchNew;
mPatchSize = patchSize;
// now update our host to force index or 32bit config
// 'cause we don't want 565 predithered, since as a 9patch, we know
@@ -47,10 +44,15 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) {
table.fPrefFor_8bpc_NoAlpha_src = SkBitmap::kARGB_8888_Config;
table.fPrefFor_8bpc_YesAlpha_src = SkBitmap::kARGB_8888_Config;
fHost->setPrefConfigTable(table);
} else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) {
fLayoutBounds = new int[4];
memcpy(fLayoutBounds, data, sizeof(int) * 4);
mHost->setPrefConfigTable(table);
} else if (!strcmp("npLb", tag) && length == sizeof(int32_t) * 4) {
mHasInsets = true;
memcpy(&mOpticalInsets, data, sizeof(int32_t) * 4);
} else if (!strcmp("npOl", tag) && length == 24) { // 4 int32_ts, 1 float, 1 int32_t sized bool
mHasInsets = true;
memcpy(&mOutlineInsets, data, sizeof(int32_t) * 4);
mOutlineRadius = ((const float*)data)[4];
mOutlineFilled = ((const int32_t*)data)[5] & 0x01;
}
return true; // keep on decoding
}

View File

@@ -23,26 +23,34 @@
using namespace android;
class NinePatchPeeker : public SkImageDecoder::Peeker {
SkImageDecoder* fHost;
private:
// the host lives longer than we do, so a raw ptr is safe
SkImageDecoder* mHost;
public:
NinePatchPeeker(SkImageDecoder* host) {
// the host lives longer than we do, so a raw ptr is safe
fHost = host;
fPatch = NULL;
fPatchSize = 0;
fLayoutBounds = NULL;
NinePatchPeeker(SkImageDecoder* host)
: mHost(host)
, mPatch(NULL)
, mPatchSize(0)
, mHasInsets(false)
, mOutlineRadius(0)
, mOutlineFilled(false) {
memset(mOpticalInsets, 0, 4 * sizeof(int32_t));
memset(mOutlineInsets, 0, 4 * sizeof(int32_t));
}
~NinePatchPeeker() {
free(fPatch);
delete fLayoutBounds;
free(mPatch);
}
Res_png_9patch* fPatch;
size_t fPatchSize;
int *fLayoutBounds;
virtual bool peek(const char tag[], const void* data, size_t length);
Res_png_9patch* mPatch;
size_t mPatchSize;
bool mHasInsets;
int32_t mOpticalInsets[4];
int32_t mOutlineInsets[4];
float mOutlineRadius;
bool mOutlineFilled;
};
#endif // NinePatchPeeker_h

View File

@@ -73,8 +73,8 @@ public final class Bitmap implements Parcelable {
*/
private boolean mRequestPremultiplied;
private byte[] mNinePatchChunk; // may be null
private int[] mOpticalInsets; // may be null
private byte[] mNinePatchChunk; // may be null
private NinePatch.InsetStruct mNinePatchInsets; // may be null
private int mWidth;
private int mHeight;
private boolean mRecycled;
@@ -111,7 +111,7 @@ public final class Bitmap implements Parcelable {
@SuppressWarnings({"UnusedDeclaration"}) // called from JNI
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, int[] opticalInsets) {
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}
@@ -126,7 +126,7 @@ public final class Bitmap implements Parcelable {
mFinalizer = new BitmapFinalizer(nativeBitmap);
mNinePatchChunk = ninePatchChunk;
mOpticalInsets = opticalInsets;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
mDensity = density;
}
@@ -946,16 +946,18 @@ public final class Bitmap implements Parcelable {
* @hide
*/
public void getOpticalInsets(@NonNull Rect outInsets) {
if (mOpticalInsets == null) {
if (mNinePatchInsets == null) {
outInsets.setEmpty();
} else {
outInsets.left = mOpticalInsets[0];
outInsets.top = mOpticalInsets[1];
outInsets.right = mOpticalInsets[2];
outInsets.bottom = mOpticalInsets[3];
outInsets.set(mNinePatchInsets.opticalRect);
}
}
/** @hide */
public NinePatch.InsetStruct getNinePatchInsets() {
return mNinePatchInsets;
}
/**
* Specifies the known formats a bitmap can be compressed into
*/

View File

@@ -16,7 +16,6 @@
package android.graphics;
/**
* The NinePatch class permits drawing a bitmap in nine or more sections.
* Essentially, it allows the creation of custom graphics that will scale the
@@ -32,6 +31,39 @@ package android.graphics;
* </p>
*/
public class NinePatch {
/**
* Struct of inset information attached to a 9 patch bitmap.
*
* Present on a 9 patch bitmap if it optical insets were manually included,
* or if outline insets were automatically included by aapt.
*
* @hide
*/
public static class InsetStruct {
@SuppressWarnings({"UnusedDeclaration"}) // called from JNI
InsetStruct(int opticalLeft, int opticalTop, int opticalRight, int opticalBottom,
int outlineLeft, int outlineTop, int outlineRight, int outlineBottom,
float outlineRadius, boolean outlineFilled, float decodeScale) {
opticalRect = new Rect(opticalLeft, opticalTop, opticalRight, opticalBottom);
outlineRect = new Rect(outlineLeft, outlineTop, outlineRight, outlineBottom);
if (decodeScale != 1.0f) {
// if bitmap was scaled when decoded, scale the insets from the metadata values
opticalRect.scale(decodeScale);
// round inward while scaling outline, as the outline should always be conservative
outlineRect.scaleRoundIn(decodeScale);
}
this.outlineRadius = outlineRadius * decodeScale;
this.outlineFilled = outlineFilled;
}
public final Rect opticalRect;
public final Rect outlineRect;
public final float outlineRadius;
public final boolean outlineFilled;
}
private final Bitmap mBitmap;
/**

View File

@@ -609,4 +609,17 @@ public final class Rect implements Parcelable {
bottom = (int) (bottom * scale + 0.5f);
}
}
/**
* Scales up the rect by the given scale, rounding values toward the inside.
* @hide
*/
public void scaleRoundIn(float scale) {
if (scale != 1.0f) {
left = (int) Math.ceil(left * scale);
top = (int) Math.ceil(top * scale);
right = (int) Math.floor(right * scale);
bottom = (int) Math.floor(bottom * scale);
}
}
}

View File

@@ -16,6 +16,7 @@
package android.graphics.drawable;
import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -26,6 +27,7 @@ import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.NinePatch;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
@@ -281,14 +283,35 @@ public class NinePatchDrawable extends Drawable {
return false;
}
@Override
public boolean getOutline(@NonNull Outline outline) {
final Rect bounds = getBounds();
if (bounds.isEmpty()) return false;
if (mNinePatchState != null) {
NinePatch.InsetStruct insets = mNinePatchState.getBitmap().getNinePatchInsets();
if (insets != null) {
final Rect outlineInsets = insets.outlineRect;
outline.setRoundRect(bounds.left + outlineInsets.left,
bounds.top + outlineInsets.top,
bounds.right - outlineInsets.right,
bounds.bottom - outlineInsets.bottom,
insets.outlineRadius);
outline.setFilled(insets.outlineFilled);
return true;
}
}
return super.getOutline(outline);
}
/**
* @hide
*/
@Override
public Insets getOpticalInsets() {
if (needsMirroring()) {
return Insets.of(mOpticalInsets.right, mOpticalInsets.top, mOpticalInsets.right,
mOpticalInsets.bottom);
return Insets.of(mOpticalInsets.right, mOpticalInsets.top,
mOpticalInsets.left, mOpticalInsets.bottom);
} else {
return mOpticalInsets;
}
@@ -574,7 +597,7 @@ public class NinePatchDrawable extends Drawable {
}
NinePatchState(NinePatch ninePatch, Rect padding) {
this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false);
this(ninePatch, padding, null, DEFAULT_DITHER, false);
}
NinePatchState(NinePatch ninePatch, Rect padding, Rect opticalInsets) {

View File

@@ -77,6 +77,14 @@ struct image_info
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 @@ static status_t get_vertical_layout_bounds_ticks(
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 @@ static status_t do_9patch(const char* imageName, image_info* image)
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 @@ static void write_png(const char* imageName,
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 @@ static void write_png(const char* imageName,
}
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 @@ static void write_png(const char* imageName,
// 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 @@ static void write_png(const char* imageName,
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,