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