Add ninePatch support to Canvas.h
Change-Id: Ic095291fe55911c6501c1bdefa4b8da973c77319
diff --git a/libs/hwui/utils/NinePatchImpl.cpp b/libs/hwui/utils/NinePatchImpl.cpp
new file mode 100644
index 0000000..f51f5df
--- /dev/null
+++ b/libs/hwui/utils/NinePatchImpl.cpp
@@ -0,0 +1,326 @@
+/*
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include "utils/NinePatch.h"
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkNinePatch.h"
+#include "SkPaint.h"
+#include "SkUnPreMultiply.h"
+
+#include <utils/Log.h>
+
+namespace android {
+
+static const bool kUseTrace = true;
+static bool gTrace = false;
+
+static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
+ switch (bitmap.colorType()) {
+ case kN32_SkColorType:
+ *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
+ break;
+ case kRGB_565_SkColorType:
+ *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
+ break;
+ case kARGB_4444_SkColorType:
+ *c = SkUnPreMultiply::PMColorToColor(
+ SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
+ break;
+ case kIndex_8_SkColorType: {
+ SkColorTable* ctable = bitmap.getColorTable();
+ *c = SkUnPreMultiply::PMColorToColor(
+ (*ctable)[*bitmap.getAddr8(x, y)]);
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+static SkColor modAlpha(SkColor c, int alpha) {
+ int scale = alpha + (alpha >> 7);
+ int a = SkColorGetA(c) * scale >> 8;
+ return SkColorSetA(c, a);
+}
+
+static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst,
+ const SkBitmap& bitmap, const SkPaint& paint,
+ SkColor initColor, uint32_t colorHint,
+ bool hasXfer) {
+ if (colorHint != android::Res_png_9patch::NO_COLOR) {
+ ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha()));
+ canvas->drawRect(dst, paint);
+ ((SkPaint*)&paint)->setColor(initColor);
+ } else if (src.width() == 1 && src.height() == 1) {
+ SkColor c;
+ if (!getColor(bitmap, src.fLeft, src.fTop, &c)) {
+ goto SLOW_CASE;
+ }
+ if (0 != c || hasXfer) {
+ SkColor prev = paint.getColor();
+ ((SkPaint*)&paint)->setColor(c);
+ canvas->drawRect(dst, paint);
+ ((SkPaint*)&paint)->setColor(prev);
+ }
+ } else {
+ SLOW_CASE:
+ canvas->drawBitmapRect(bitmap, &src, dst, &paint);
+ }
+}
+
+SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint,
+ int srcSpace, int numStrechyPixelsRemaining,
+ int numFixedPixelsRemaining) {
+ SkScalar spaceRemaining = boundsLimit - startingPoint;
+ SkScalar stretchySpaceRemaining =
+ spaceRemaining - SkIntToScalar(numFixedPixelsRemaining);
+ return srcSpace * stretchySpaceRemaining / numStrechyPixelsRemaining;
+}
+
+void NinePatch::Draw(SkCanvas* canvas, const SkRect& bounds,
+ const SkBitmap& bitmap, const Res_png_9patch& chunk,
+ const SkPaint* paint, SkRegion** outRegion) {
+ if (canvas && canvas->quickReject(bounds)) {
+ return;
+ }
+
+ SkPaint defaultPaint;
+ if (NULL == paint) {
+ // matches default dither in NinePatchDrawable.java.
+ defaultPaint.setDither(true);
+ paint = &defaultPaint;
+ }
+
+ const int32_t* xDivs = chunk.getXDivs();
+ const int32_t* yDivs = chunk.getYDivs();
+ // if our SkCanvas were back by GL we should enable this and draw this as
+ // a mesh, which will be faster in most cases.
+ if ((false)) {
+ SkNinePatch::DrawMesh(canvas, bounds, bitmap,
+ xDivs, chunk.numXDivs,
+ yDivs, chunk.numYDivs,
+ paint);
+ return;
+ }
+
+ if (kUseTrace) {
+ gTrace = true;
+ }
+
+ SkASSERT(canvas || outRegion);
+
+ if (kUseTrace) {
+ if (canvas) {
+ const SkMatrix& m = canvas->getTotalMatrix();
+ ALOGV("ninepatch [%g %g %g] [%g %g %g]\n",
+ SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
+ SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
+ }
+
+ ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()),
+ SkScalarToFloat(bounds.height()));
+ ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height());
+ ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]);
+ ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]);
+ }
+
+ if (bounds.isEmpty() ||
+ bitmap.width() == 0 || bitmap.height() == 0 ||
+ (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
+ {
+ if (kUseTrace) {
+ ALOGV("======== abort ninepatch draw\n");
+ }
+ return;
+ }
+
+ // should try a quick-reject test before calling lockPixels
+
+ SkAutoLockPixels alp(bitmap);
+ // after the lock, it is valid to check getPixels()
+ if (bitmap.getPixels() == NULL)
+ return;
+
+ const bool hasXfer = paint->getXfermode() != NULL;
+ SkRect dst;
+ SkIRect src;
+
+ const int32_t x0 = xDivs[0];
+ const int32_t y0 = yDivs[0];
+ const SkColor initColor = ((SkPaint*)paint)->getColor();
+ const uint8_t numXDivs = chunk.numXDivs;
+ const uint8_t numYDivs = chunk.numYDivs;
+ int i;
+ int j;
+ int colorIndex = 0;
+ uint32_t color;
+ bool xIsStretchable;
+ const bool initialXIsStretchable = (x0 == 0);
+ bool yIsStretchable = (y0 == 0);
+ const int bitmapWidth = bitmap.width();
+ const int bitmapHeight = bitmap.height();
+
+ // Number of bytes needed for dstRights array.
+ // Need to cast numXDivs to a larger type to avoid overflow.
+ const size_t dstBytes = ((size_t) numXDivs + 1) * sizeof(SkScalar);
+ SkScalar* dstRights = (SkScalar*) alloca(dstBytes);
+ bool dstRightsHaveBeenCached = false;
+
+ int numStretchyXPixelsRemaining = 0;
+ for (i = 0; i < numXDivs; i += 2) {
+ numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i];
+ }
+ int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
+ int numStretchyYPixelsRemaining = 0;
+ for (i = 0; i < numYDivs; i += 2) {
+ numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i];
+ }
+ int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
+
+ if (kUseTrace) {
+ ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
+ bitmap.width(), bitmap.height(),
+ SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
+ SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
+ numXDivs, numYDivs);
+ }
+
+ src.fTop = 0;
+ dst.fTop = bounds.fTop;
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) or yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = yIsStretchable ? 1 : 0;
+ j <= numYDivs && src.fTop < bitmapHeight;
+ j++, yIsStretchable = !yIsStretchable) {
+ src.fLeft = 0;
+ dst.fLeft = bounds.fLeft;
+ if (j == numYDivs) {
+ src.fBottom = bitmapHeight;
+ dst.fBottom = bounds.fBottom;
+ } else {
+ src.fBottom = yDivs[j];
+ const int srcYSize = src.fBottom - src.fTop;
+ if (yIsStretchable) {
+ dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop,
+ srcYSize,
+ numStretchyYPixelsRemaining,
+ numFixedYPixelsRemaining);
+ numStretchyYPixelsRemaining -= srcYSize;
+ } else {
+ dst.fBottom = dst.fTop + SkIntToScalar(srcYSize);
+ numFixedYPixelsRemaining -= srcYSize;
+ }
+ }
+
+ xIsStretchable = initialXIsStretchable;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ const uint32_t* colors = chunk.getColors();
+ for (i = xIsStretchable ? 1 : 0;
+ i <= numXDivs && src.fLeft < bitmapWidth;
+ i++, xIsStretchable = !xIsStretchable) {
+ color = colors[colorIndex++];
+ if (i == numXDivs) {
+ src.fRight = bitmapWidth;
+ dst.fRight = bounds.fRight;
+ } else {
+ src.fRight = xDivs[i];
+ if (dstRightsHaveBeenCached) {
+ dst.fRight = dstRights[i];
+ } else {
+ const int srcXSize = src.fRight - src.fLeft;
+ if (xIsStretchable) {
+ dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft,
+ srcXSize,
+ numStretchyXPixelsRemaining,
+ numFixedXPixelsRemaining);
+ numStretchyXPixelsRemaining -= srcXSize;
+ } else {
+ dst.fRight = dst.fLeft + SkIntToScalar(srcXSize);
+ numFixedXPixelsRemaining -= srcXSize;
+ }
+ dstRights[i] = dst.fRight;
+ }
+ }
+ // If this horizontal patch is too small to be displayed, leave
+ // the destination left edge where it is and go on to the next patch
+ // in the source.
+ if (src.fLeft >= src.fRight) {
+ src.fLeft = src.fRight;
+ continue;
+ }
+ // Make sure that we actually have room to draw any bits
+ if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) {
+ goto nextDiv;
+ }
+ // If this patch is transparent, skip and don't draw.
+ if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) {
+ if (outRegion) {
+ if (*outRegion == NULL) {
+ *outRegion = new SkRegion();
+ }
+ SkIRect idst;
+ dst.round(&idst);
+ //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n",
+ // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom);
+ (*outRegion)->op(idst, SkRegion::kUnion_Op);
+ }
+ goto nextDiv;
+ }
+ if (canvas) {
+ if (kUseTrace) {
+ ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
+ src.fLeft, src.fTop, src.width(), src.height(),
+ SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop),
+ SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height()));
+ if (2 == src.width() && SkIntToScalar(5) == dst.width()) {
+ ALOGV("--- skip patch\n");
+ }
+ }
+ drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
+ color, hasXfer);
+ }
+
+nextDiv:
+ src.fLeft = src.fRight;
+ dst.fLeft = dst.fRight;
+ }
+ src.fTop = src.fBottom;
+ dst.fTop = dst.fBottom;
+ dstRightsHaveBeenCached = true;
+ }
+}
+
+} // namespace android