summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/jni/android/graphics/BitmapFactory.cpp62
-rw-r--r--core/jni/android/graphics/Graphics.cpp12
-rw-r--r--core/jni/android/graphics/GraphicsJNI.h6
-rw-r--r--core/jni/android/graphics/NinePatchPeeker.cpp24
-rw-r--r--core/jni/android/graphics/NinePatchPeeker.h34
-rw-r--r--graphics/java/android/graphics/Bitmap.java20
-rw-r--r--graphics/java/android/graphics/NinePatch.java34
-rw-r--r--graphics/java/android/graphics/Rect.java13
-rw-r--r--graphics/java/android/graphics/drawable/NinePatchDrawable.java29
-rw-r--r--tools/aapt/Images.cpp134
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,