Embed layout padding in nine patch images

- Added a new custom PNG chunk that carries the layout padding ints.
- Extract the padding ticks from .9.png images and store in the chunk.
- Load the padding information at runtime into Bitmap and NinePatchDrawable.

- The new chunk is ordered first so that it doesn't cause a problem in older
  versions of the platform.

Bug: 6087201

Change-Id: I5de46167a1d44b3ec21065b0c165e594b1dc8399
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 5e73a5f..3c27caf 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -236,7 +236,7 @@
                                0, 0, width, height, bitmap);

     }

 

-    return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL);

+    return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL, NULL);

 }

 

 static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src,

@@ -248,7 +248,7 @@
         return NULL;

     }

 

-    return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL);

+    return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL, NULL);

 }

 

 static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {

@@ -407,7 +407,7 @@
     bitmap->unlockPixels();

 

     blob.release();

-    return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, density);

+    return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, NULL, density);

 }

 

 static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,

@@ -485,7 +485,7 @@
         env->ReleaseIntArrayElements(offsetXY, array, 0);

     }

 

-    return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL);

+    return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL, NULL);

 }

 

 ///////////////////////////////////////////////////////////////////////////////

diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index dcd1d28..dd59444 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -35,6 +35,7 @@
 jfieldID gOptions_mCancelID;
 jfieldID gOptions_bitmapFieldID;
 jfieldID gBitmap_nativeBitmapFieldID;
+jfieldID gBitmap_layoutBoundsFieldID;
 
 #if 0
     #define TRACE_BITMAP(code)  code
@@ -276,7 +277,7 @@
     }
 
     jbyteArray ninePatchChunk = NULL;
-    if (peeker.fPatchIsValid) {
+    if (peeker.fPatch != NULL) {
         if (willScale) {
             scaleNinePatchChunk(peeker.fPatch, scale);
         }
@@ -296,6 +297,18 @@
         env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
     }
 
+    jintArray layoutBounds = NULL;
+    if (peeker.fLayoutBounds != NULL) {
+        layoutBounds = env->NewIntArray(4);
+        if (layoutBounds == NULL) {
+            return nullObjectReturn("layoutBounds == null");
+        }
+
+        env->SetIntArrayRegion(layoutBounds, 0, 4, (jint*) peeker.fLayoutBounds);
+        if (javaBitmap != NULL) {
+            env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds);
+        }
+    }
     // detach bitmap from its autodeleter, since we want to own it now
     adb.detach();
 
@@ -321,7 +334,7 @@
     }
 
     if (padding) {
-        if (peeker.fPatchIsValid) {
+        if (peeker.fPatch != NULL) {
             GraphicsJNI::set_jrect(env, padding,
                     peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
                     peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
@@ -350,7 +363,7 @@
     }
     // now create the java bitmap
     return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
-            isMutable, ninePatchChunk);
+            isMutable, ninePatchChunk, layoutBounds, -1);
 }
 
 static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
@@ -576,7 +589,7 @@
     jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
     SkASSERT(bitmap_class);
     gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "I");
-
+    gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mLayoutBounds", "[I");
     int ret = AndroidRuntime::registerNativeMethods(env,
                                     "android/graphics/BitmapFactory$Options",
                                     gOptionsMethods,
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 682877a..dd8e84f 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -244,7 +244,7 @@
 
     JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();
     jbyteArray buff = allocator->getStorageObjAndReset();
-    return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, -1);
+    return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, NULL, -1);
 }
 
 static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index a1d41ee..d4c7600 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -345,14 +345,14 @@
 ///////////////////////////////////////////////////////////////////////////////////////////
 
 jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
-                                  bool isMutable, jbyteArray ninepatch, int density)
+                                  bool isMutable, jbyteArray ninepatch, jintArray layoutbounds,
+                                  int density)
 {
     SkASSERT(bitmap);
     SkASSERT(bitmap->pixelRef());
-
     jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
             static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)),
-            buffer, isMutable, ninepatch, density);
+            buffer, isMutable, ninepatch, layoutbounds, density);
     hasException(env); // For the side effect of logging.
     return obj;
 }
@@ -360,7 +360,7 @@
 jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
                             jbyteArray ninepatch, int density)
 {
-    return createBitmap(env, bitmap, NULL, isMutable, ninepatch, density);
+    return createBitmap(env, bitmap, NULL, isMutable, ninepatch, NULL, density);
 }
 
 
@@ -587,7 +587,7 @@
     gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
     gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
     gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
-                                            "(I[BZ[BI)V");
+                                            "(I[BZ[B[II)V");
     gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
     gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(I)V");
 
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index cc32f44..c5b06f5 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -53,7 +53,8 @@
         storage array (may be null).
     */
     static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
-                                bool isMutable, jbyteArray ninepatch, int density = -1);
+                                bool isMutable, jbyteArray ninepatch, jintArray layoutbounds,
+                                int density = -1);
 
     static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
                                 jbyteArray ninepatch, int density = -1);
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 365d985..df996af 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -31,14 +31,11 @@
         // this relies on deserialization being done in place
         Res_png_9patch::deserialize(patchNew);
         patchNew->fileToDevice();
-        if (fPatchIsValid) {
-            free(fPatch);
-        }
+        free(fPatch);
         fPatch = patchNew;
         //printf("9patch: (%d,%d)-(%d,%d)\n",
         //       fPatch.sizeLeft, fPatch.sizeTop,
         //       fPatch.sizeRight, fPatch.sizeBottom);
-        fPatchIsValid = true;
 
         // now update our host to force index or 32bit config
         // 'cause we don't want 565 predithered, since as a 9patch, we know
@@ -52,8 +49,9 @@
             SkBitmap::kARGB_8888_Config,
         };
         fHost->setPrefConfigTable(gNo565Pref);
-    } else {
-        fPatch = NULL;
+    } else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) {
+        fLayoutBounds = new int[4];
+        memcpy(fLayoutBounds, data, sizeof(int) * 4);
     }
     return true;    // keep on decoding
 }
diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h
index 207536c..10d268a 100644
--- a/core/jni/android/graphics/NinePatchPeeker.h
+++ b/core/jni/android/graphics/NinePatchPeeker.h
@@ -28,17 +28,17 @@
     NinePatchPeeker(SkImageDecoder* host) {
         // the host lives longer than we do, so a raw ptr is safe
         fHost = host;
-        fPatchIsValid = false;
+        fPatch = NULL;
+        fLayoutBounds = NULL;
     }
 
     ~NinePatchPeeker() {
-        if (fPatchIsValid) {
-            free(fPatch);
-        }
+        free(fPatch);
+        delete fLayoutBounds;
     }
 
-    bool    fPatchIsValid;
     Res_png_9patch*  fPatch;
+    int    *fLayoutBounds;
 
     virtual bool peek(const char tag[], const void* data, size_t length);
 };
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 6f939be..ed5b2f6 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -16,9 +16,12 @@
 
 package android.graphics;
 
+import android.os.Debug;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.DisplayMetrics;
+import android.util.Log;
+
 import java.io.OutputStream;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
@@ -57,6 +60,7 @@
 
     private final boolean mIsMutable;
     private byte[] mNinePatchChunk;   // may be null
+    private int[] mLayoutBounds;   // may be null
     private int mWidth = -1;
     private int mHeight = -1;
     private boolean mRecycled;
@@ -95,6 +99,19 @@
     */
     /*package*/ Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk,
             int density) {
+        this(nativeBitmap, buffer, isMutable, ninePatchChunk, null, density);
+    }
+
+    /**
+     * @noinspection UnusedDeclaration
+     */
+    /*  Private constructor that must received an already allocated native
+        bitmap int (pointer).
+
+        This can be called from JNI code.
+    */
+    /*package*/ Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk,
+            int[] layoutBounds, int density) {
         if (nativeBitmap == 0) {
             throw new RuntimeException("internal error: native bitmap is 0");
         }
@@ -106,6 +123,7 @@
 
         mIsMutable = isMutable;
         mNinePatchChunk = ninePatchChunk;
+        mLayoutBounds = layoutBounds;
         if (density >= 0) {
             mDensity = density;
         }
@@ -164,6 +182,16 @@
     }
 
     /**
+     * Sets the layout bounds as an array of left, top, right, bottom integers
+     * @param padding the array containing the padding values
+     *
+     * @hide
+     */
+    public void setLayoutBounds(int[] bounds) {
+        mLayoutBounds = bounds;
+    }
+
+    /**
      * Free the native object associated with this bitmap, and clear the
      * reference to the pixel data. This will not free the pixel data synchronously;
      * it simply allows it to be garbage collected if there are no other references.
@@ -690,6 +718,14 @@
     }
 
     /**
+     * @hide
+     * @return the layout padding [left, right, top, bottom]
+     */
+    public int[] getLayoutBounds() {
+        return mLayoutBounds;
+    }
+
+    /**
      * Specifies the known formats a bitmap can be compressed into
      */
     public enum CompressFormat {
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index c5705f6..1599e40 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -424,6 +424,7 @@
             throw new ArrayIndexOutOfBoundsException();
         }
         Bitmap bm = nativeDecodeByteArray(data, offset, length, opts);
+
         if (bm == null && opts != null && opts.inBitmap != null) {
             throw new IllegalArgumentException("Problem decoding into existing bitmap");
         }
@@ -554,7 +555,6 @@
         if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
             return bm;
         }
-        
         byte[] np = bm.getNinePatchChunk();
         final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
         if (opts.inScaled || isNinePatch) {
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 043adae..86e824b 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -773,7 +773,13 @@
                 np = null;
                 pad = null;
             }
-            return drawableFromBitmap(res, bm, np, pad, srcName);
+            int[] layoutBounds = bm.getLayoutBounds();
+            Rect layoutBoundsRect = null;
+            if (layoutBounds != null) {
+                layoutBoundsRect = new Rect(layoutBounds[0], layoutBounds[1],
+                                             layoutBounds[2], layoutBounds[3]);
+            }
+            return drawableFromBitmap(res, bm, np, pad, layoutBoundsRect, srcName);
         }
         return null;
     }
@@ -875,7 +881,7 @@
 
         Bitmap bm = BitmapFactory.decodeFile(pathName);
         if (bm != null) {
-            return drawableFromBitmap(null, bm, null, null, pathName);
+            return drawableFromBitmap(null, bm, null, null, null, pathName);
         }
 
         return null;
@@ -956,10 +962,12 @@
     }
 
     private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
-            Rect pad, String srcName) {
+            Rect pad, Rect layoutBounds, String srcName) {
 
         if (np != null) {
-            return new NinePatchDrawable(res, bm, np, pad, srcName);
+            NinePatchDrawable npd = new NinePatchDrawable(res, bm, np, pad, srcName);
+            npd.setLayoutBounds(layoutBounds);
+            return npd;
         }
 
         return new BitmapDrawable(res, bm);
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 18b8bc7..1272071 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -47,6 +47,7 @@
     private NinePatchState mNinePatchState;
     private NinePatch mNinePatch;
     private Rect mPadding;
+    private Rect mLayoutBounds;
     private Paint mPaint;
     private boolean mMutated;
 
@@ -98,6 +99,13 @@
         mNinePatchState.mTargetDensity = mTargetDensity;
     }
 
+    /**
+     * @hide
+     */
+    void setLayoutBounds(Rect layoutBounds) {
+        mLayoutBounds = layoutBounds;
+    }
+
     private void setNinePatchState(NinePatchState state, Resources res) {
         mNinePatchState = state;
         mNinePatch = state.mNinePatch;
@@ -258,7 +266,7 @@
         }
         options.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
 
-        final Rect padding = new Rect();        
+        final Rect padding = new Rect();
         Bitmap bitmap = null;
 
         try {
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 2b9b056..9de685a 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -57,6 +57,13 @@
     bool is9Patch;
     Res_png_9patch info9Patch;
 
+    // Layout padding, if relevant
+    bool haveLayoutBounds;
+    int32_t layoutBoundsLeft;
+    int32_t layoutBoundsTop;
+    int32_t layoutBoundsRight;
+    int32_t layoutBoundsBottom;
+
     png_uint_32 allocHeight;
     png_bytepp allocRows;
 };
@@ -129,33 +136,62 @@
        &interlace_type, &compression_type, NULL);
 }
 
-static bool is_tick(png_bytep p, bool transparent, const char** outError)
+#define COLOR_TRANSPARENT 0
+#define COLOR_WHITE 0xFFFFFFFF
+#define COLOR_TICK  0xFF000000
+#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF
+
+enum {
+    TICK_TYPE_NONE,
+    TICK_TYPE_TICK,
+    TICK_TYPE_LAYOUT_BOUNDS,
+    TICK_TYPE_BOTH
+};
+
+static int tick_type(png_bytep p, bool transparent, const char** outError)
 {
+    png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
     if (transparent) {
         if (p[3] == 0) {
-            return false;
+            return TICK_TYPE_NONE;
         }
+        if (color == COLOR_LAYOUT_BOUNDS_TICK) {
+            return TICK_TYPE_LAYOUT_BOUNDS;
+        }
+        if (color == COLOR_TICK) {
+            return TICK_TYPE_TICK;
+        }
+
+        // Error cases
         if (p[3] != 0xff) {
             *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
-            return false;
+            return TICK_TYPE_NONE;
         }
         if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
-            *outError = "Ticks in transparent frame must be black";
+            *outError = "Ticks in transparent frame must be black or red";
         }
-        return true;
+        return TICK_TYPE_TICK;
     }
 
     if (p[3] != 0xFF) {
         *outError = "White frame must be a solid color (no alpha)";
     }
-    if (p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF) {
-        return false;
+    if (color == COLOR_WHITE) {
+        return TICK_TYPE_NONE;
     }
+    if (color == COLOR_TICK) {
+        return TICK_TYPE_TICK;
+    }
+    if (color == COLOR_LAYOUT_BOUNDS_TICK) {
+        return TICK_TYPE_LAYOUT_BOUNDS;
+    }
+
     if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
-        *outError = "Ticks in white frame must be black";
-        return false;
+        *outError = "Ticks in white frame must be black or red";
+        return TICK_TYPE_NONE;
     }
-    return true;
+    return TICK_TYPE_TICK;
 }
 
 enum {
@@ -175,7 +211,7 @@
     bool found = false;
 
     for (i=1; i<width-1; i++) {
-        if (is_tick(row+i*4, transparent, outError)) {
+        if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) {
             if (state == TICK_START ||
                 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
                 *outLeft = i-1;
@@ -224,7 +260,7 @@
     bool found = false;
 
     for (i=1; i<height-1; i++) {
-        if (is_tick(rows[i]+offset, transparent, outError)) {
+        if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) {
             if (state == TICK_START ||
                 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
                 *outTop = i-1;
@@ -262,6 +298,83 @@
     return NO_ERROR;
 }
 
+static status_t get_horizontal_layout_bounds_ticks(
+        png_bytep row, int width, bool transparent, bool required,
+        int32_t* outLeft, int32_t* outRight, const char** outError)
+{
+    int i;
+    *outLeft = *outRight = 0;
+
+    // Look for left tick
+    if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) {
+        // Starting with a layout padding tick
+        i = 1;
+        while (i < width - 1) {
+            (*outLeft)++;
+            i++;
+            int tick = tick_type(row + i * 4, transparent, outError);
+            if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+                break;
+            }
+        }
+    }
+
+    // Look for right tick
+    if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) {
+        // Ending with a layout padding tick
+        i = width - 2;
+        while (i > 1) {
+            (*outRight)++;
+            i--;
+            int tick = tick_type(row+i*4, transparent, outError);
+            if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+                break;
+            }
+        }
+    }
+
+    return NO_ERROR;
+}
+
+static status_t get_vertical_layout_bounds_ticks(
+        png_bytepp rows, int offset, int height, bool transparent, bool required,
+        int32_t* outTop, int32_t* outBottom, const char** outError)
+{
+    int i;
+    *outTop = *outBottom = 0;
+
+    // Look for top tick
+    if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) {
+        // Starting with a layout padding tick
+        i = 1;
+        while (i < height - 1) {
+            (*outTop)++;
+            i++;
+            int tick = tick_type(rows[i] + offset, transparent, outError);
+            if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+                break;
+            }
+        }
+    }
+
+    // Look for bottom tick
+    if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) {
+        // Ending with a layout padding tick
+        i = height - 2;
+        while (i > 1) {
+            (*outBottom)++;
+            i--;
+            int tick = tick_type(rows[i] + offset, transparent, outError);
+            if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+                break;
+            }
+        }
+    }
+
+    return NO_ERROR;
+}
+
+
 static uint32_t get_color(
     png_bytepp rows, int left, int top, int right, int bottom)
 {
@@ -353,6 +466,9 @@
     image->info9Patch.paddingLeft = image->info9Patch.paddingRight =
         image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
 
+    image->layoutBoundsLeft = image->layoutBoundsRight =
+        image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
     png_bytep p = image->rows[0];
     bool transparent = p[3] == 0;
     bool hasColor = false;
@@ -408,6 +524,25 @@
         goto getout;
     }
 
+    // Find left and right of layout padding...
+    get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false,
+                                        &image->layoutBoundsLeft,
+                                        &image->layoutBoundsRight, &errorMsg);
+
+    get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false,
+                                        &image->layoutBoundsTop,
+                                        &image->layoutBoundsBottom, &errorMsg);
+
+    image->haveLayoutBounds = image->layoutBoundsLeft != 0
+                               || image->layoutBoundsRight != 0
+                               || image->layoutBoundsTop != 0
+                               || image->layoutBoundsBottom != 0;
+
+    if (image->haveLayoutBounds) {
+        NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
+                image->layoutBoundsRight, image->layoutBoundsBottom));
+    }
+
     // Copy patch data into image
     image->info9Patch.numXDivs = numXDivs;
     image->info9Patch.numYDivs = numYDivs;
@@ -845,8 +980,9 @@
     int bit_depth, interlace_type, compression_type;
     int i;
 
-    png_unknown_chunk unknowns[1];
+    png_unknown_chunk unknowns[2];
     unknowns[0].data = NULL;
+    unknowns[1].data = NULL;
 
     png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep));
     if (outRows == (png_bytepp) 0) {
@@ -916,23 +1052,42 @@
     }
 
     if (imageInfo.is9Patch) {
+        int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0);
+        int p_index = imageInfo.haveLayoutBounds ? 1 : 0;
+        int b_index = 0;
+        png_byte *chunk_names = imageInfo.haveLayoutBounds
+                ? (png_byte*)"npLb\0npTc\0"
+                : (png_byte*)"npTc";
         NOISY(printf("Adding 9-patch info...\n"));
-        strcpy((char*)unknowns[0].name, "npTc");
-        unknowns[0].data = (png_byte*)imageInfo.info9Patch.serialize();
-        unknowns[0].size = imageInfo.info9Patch.serializedSize();
+        strcpy((char*)unknowns[p_index].name, "npTc");
+        unknowns[p_index].data = (png_byte*)imageInfo.info9Patch.serialize();
+        unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
         // TODO: remove the check below when everything works
-        checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[0].data);
+        checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
+
+        if (imageInfo.haveLayoutBounds) {
+            int chunk_size = sizeof(png_uint_32) * 4;
+            strcpy((char*)unknowns[b_index].name, "npLb");
+            unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
+            memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
+            unknowns[b_index].size = chunk_size;
+        }
+
         png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
-                                    (png_byte*)"npTc", 1);
-        png_set_unknown_chunks(write_ptr, write_info, unknowns, 1);
+                                    chunk_names, chunk_count);
+        png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count);
         // XXX I can't get this to work without forcibly changing
         // the location to what I want...  which apparently is supposed
         // to be a private API, but everything else I have tried results
         // in the location being set to what I -last- wrote so I never
         // get written. :p
         png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
+        if (imageInfo.haveLayoutBounds) {
+            png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE);
+        }
     }
 
+
     png_write_info(write_ptr, write_info);
 
     png_bytepp rows;
@@ -954,6 +1109,7 @@
     }
     free(outRows);
     free(unknowns[0].data);
+    free(unknowns[1].data);
 
     png_get_IHDR(write_ptr, write_info, &width, &height,
        &bit_depth, &color_type, &interlace_type,
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index eadec02..b76b8cf 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -524,7 +524,8 @@
         int nativeInt = sManager.addNewDelegate(delegate);
 
         // and create/return a new Bitmap with it
-        return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density);
+        return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, 
+                density);
     }
 
     /**