diff options
-rw-r--r-- | core/jni/android/graphics/BitmapFactory.cpp | 62 | ||||
-rw-r--r-- | core/jni/android/graphics/Graphics.cpp | 12 | ||||
-rw-r--r-- | core/jni/android/graphics/GraphicsJNI.h | 6 | ||||
-rw-r--r-- | core/jni/android/graphics/NinePatchPeeker.cpp | 24 | ||||
-rw-r--r-- | core/jni/android/graphics/NinePatchPeeker.h | 34 | ||||
-rw-r--r-- | graphics/java/android/graphics/Bitmap.java | 20 | ||||
-rw-r--r-- | graphics/java/android/graphics/NinePatch.java | 34 | ||||
-rw-r--r-- | graphics/java/android/graphics/Rect.java | 13 | ||||
-rw-r--r-- | graphics/java/android/graphics/drawable/NinePatchDrawable.java | 29 | ||||
-rw-r--r-- | tools/aapt/Images.cpp | 134 |
10 files changed, 279 insertions, 89 deletions
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index b8fcff672447..a890eb4b5bf9 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -39,14 +39,12 @@ jfieldID gOptions_heightFieldID; jfieldID gOptions_mimeFieldID; jfieldID gOptions_mCancelID; jfieldID gOptions_bitmapFieldID; + jfieldID gBitmap_nativeBitmapFieldID; -jfieldID gBitmap_layoutBoundsFieldID; +jfieldID gBitmap_ninePatchInsetsFieldID; -#if 0 - #define TRACE_BITMAP(code) code -#else - #define TRACE_BITMAP(code) -#endif +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, diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 9177696957b4..5cc2b9548f18 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -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"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index a03391d5818f..8150edfb16ff 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -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 diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp index 5daa1ad6e2ef..ea5193bde0ed 100644 --- a/core/jni/android/graphics/NinePatchPeeker.cpp +++ b/core/jni/android/graphics/NinePatchPeeker.cpp @@ -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 } diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h index 204386273a5f..8d3e6cf57884 100644 --- a/core/jni/android/graphics/NinePatchPeeker.h +++ b/core/jni/android/graphics/NinePatchPeeker.h @@ -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 diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 9561ac494c25..4b00e22252b4 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -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 */ diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index befac924df90..335bce07c8c1 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -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; /** diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 437d2f485d44..a9a8f37c6dae 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -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); + } + } } diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 24bbf7c10e8f..c0110c98ba7d 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -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) { diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index 12f5b92ab3fa..28de933a315e 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -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, |