| /* |
| ** |
| ** 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 |