diff options
Diffstat (limited to 'libs/hwui/utils/NinePatchImpl.cpp')
-rw-r--r-- | libs/hwui/utils/NinePatchImpl.cpp | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/libs/hwui/utils/NinePatchImpl.cpp b/libs/hwui/utils/NinePatchImpl.cpp new file mode 100644 index 000000000000..985f3fb66814 --- /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, SkRect::Make(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 |