NinePatchImpl.cpp revision 6381dd4ff212a95be30d2b445d40ff419ab076b4
1/* 2** 3** Copyright 2006, The Android Open Source Project 4** 5** Licensed under the Apache License, Version 2.0 (the "License"); 6** you may not use this file except in compliance with the License. 7** You may obtain a copy of the License at 8** 9** http://www.apache.org/licenses/LICENSE-2.0 10** 11** Unless required by applicable law or agreed to in writing, software 12** distributed under the License is distributed on an "AS IS" BASIS, 13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14** See the License for the specific language governing permissions and 15** limitations under the License. 16*/ 17 18#define LOG_TAG "NinePatch" 19#define LOG_NDEBUG 1 20 21#include <androidfw/ResourceTypes.h> 22#include <utils/Log.h> 23 24#include "SkBitmap.h" 25#include "SkCanvas.h" 26#include "SkNinePatch.h" 27#include "SkPaint.h" 28#include "SkUnPreMultiply.h" 29 30#define USE_TRACE 31 32#ifdef USE_TRACE 33 static bool gTrace; 34#endif 35 36#include "SkColorPriv.h" 37 38#include <utils/Log.h> 39 40static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) { 41 switch (bitmap.getConfig()) { 42 case SkBitmap::kARGB_8888_Config: 43 *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y)); 44 break; 45 case SkBitmap::kRGB_565_Config: 46 *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y)); 47 break; 48 case SkBitmap::kARGB_4444_Config: 49 *c = SkUnPreMultiply::PMColorToColor( 50 SkPixel4444ToPixel32(*bitmap.getAddr16(x, y))); 51 break; 52 case SkBitmap::kIndex8_Config: { 53 SkColorTable* ctable = bitmap.getColorTable(); 54 *c = SkUnPreMultiply::PMColorToColor( 55 (*ctable)[*bitmap.getAddr8(x, y)]); 56 break; 57 } 58 default: 59 return false; 60 } 61 return true; 62} 63 64static SkColor modAlpha(SkColor c, int alpha) { 65 int scale = alpha + (alpha >> 7); 66 int a = SkColorGetA(c) * scale >> 8; 67 return SkColorSetA(c, a); 68} 69 70static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst, 71 const SkBitmap& bitmap, const SkPaint& paint, 72 SkColor initColor, uint32_t colorHint, 73 bool hasXfer) { 74 if (colorHint != android::Res_png_9patch::NO_COLOR) { 75 ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha())); 76 canvas->drawRect(dst, paint); 77 ((SkPaint*)&paint)->setColor(initColor); 78 } else if (src.width() == 1 && src.height() == 1) { 79 SkColor c; 80 if (!getColor(bitmap, src.fLeft, src.fTop, &c)) { 81 goto SLOW_CASE; 82 } 83 if (0 != c || hasXfer) { 84 SkColor prev = paint.getColor(); 85 ((SkPaint*)&paint)->setColor(c); 86 canvas->drawRect(dst, paint); 87 ((SkPaint*)&paint)->setColor(prev); 88 } 89 } else { 90 SLOW_CASE: 91 canvas->drawBitmapRect(bitmap, &src, dst, &paint); 92 } 93} 94 95SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint, 96 int srcSpace, int numStrechyPixelsRemaining, 97 int numFixedPixelsRemaining) { 98 SkScalar spaceRemaining = boundsLimit - startingPoint; 99 SkScalar stretchySpaceRemaining = 100 spaceRemaining - SkIntToScalar(numFixedPixelsRemaining); 101 return SkScalarMulDiv(srcSpace, stretchySpaceRemaining, 102 numStrechyPixelsRemaining); 103} 104 105void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds, 106 const SkBitmap& bitmap, const android::Res_png_9patch& chunk, 107 const SkPaint* paint, SkRegion** outRegion) { 108 if (canvas && canvas->quickReject(bounds)) { 109 return; 110 } 111 112 SkPaint defaultPaint; 113 if (NULL == paint) { 114 // matches default dither in NinePatchDrawable.java. 115 defaultPaint.setDither(true); 116 paint = &defaultPaint; 117 } 118 119 const int32_t* xDivs = chunk.getXDivs(); 120 const int32_t* yDivs = chunk.getYDivs(); 121 // if our SkCanvas were back by GL we should enable this and draw this as 122 // a mesh, which will be faster in most cases. 123 if (false) { 124 SkNinePatch::DrawMesh(canvas, bounds, bitmap, 125 xDivs, chunk.numXDivs, 126 yDivs, chunk.numYDivs, 127 paint); 128 return; 129 } 130 131#ifdef USE_TRACE 132 gTrace = true; 133#endif 134 135 SkASSERT(canvas || outRegion); 136 137#ifdef USE_TRACE 138 if (canvas) { 139 const SkMatrix& m = canvas->getTotalMatrix(); 140 ALOGV("ninepatch [%g %g %g] [%g %g %g]\n", 141 SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]), 142 SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5])); 143 } 144#endif 145 146#ifdef USE_TRACE 147 if (gTrace) { 148 ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height())); 149 ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height()); 150 ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]); 151 ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]); 152 } 153#endif 154 155 if (bounds.isEmpty() || 156 bitmap.width() == 0 || bitmap.height() == 0 || 157 (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0)) 158 { 159#ifdef USE_TRACE 160 if (gTrace) ALOGV("======== abort ninepatch draw\n"); 161#endif 162 return; 163 } 164 165 // should try a quick-reject test before calling lockPixels 166 167 SkAutoLockPixels alp(bitmap); 168 // after the lock, it is valid to check getPixels() 169 if (bitmap.getPixels() == NULL) 170 return; 171 172 const bool hasXfer = paint->getXfermode() != NULL; 173 SkRect dst; 174 SkIRect src; 175 176 const int32_t x0 = xDivs[0]; 177 const int32_t y0 = yDivs[0]; 178 const SkColor initColor = ((SkPaint*)paint)->getColor(); 179 const uint8_t numXDivs = chunk.numXDivs; 180 const uint8_t numYDivs = chunk.numYDivs; 181 int i; 182 int j; 183 int colorIndex = 0; 184 uint32_t color; 185 bool xIsStretchable; 186 const bool initialXIsStretchable = (x0 == 0); 187 bool yIsStretchable = (y0 == 0); 188 const int bitmapWidth = bitmap.width(); 189 const int bitmapHeight = bitmap.height(); 190 191 SkScalar* dstRights = (SkScalar*) alloca((numXDivs + 1) * sizeof(SkScalar)); 192 bool dstRightsHaveBeenCached = false; 193 194 int numStretchyXPixelsRemaining = 0; 195 for (i = 0; i < numXDivs; i += 2) { 196 numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i]; 197 } 198 int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining; 199 int numStretchyYPixelsRemaining = 0; 200 for (i = 0; i < numYDivs; i += 2) { 201 numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i]; 202 } 203 int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining; 204 205#ifdef USE_TRACE 206 ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n", 207 bitmap.width(), bitmap.height(), 208 SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop), 209 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()), 210 numXDivs, numYDivs); 211#endif 212 213 src.fTop = 0; 214 dst.fTop = bounds.fTop; 215 // The first row always starts with the top being at y=0 and the bottom 216 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case 217 // the first row is stretchable along the Y axis, otherwise it is fixed. 218 // The last row always ends with the bottom being bitmap.height and the top 219 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or 220 // yDivs[numYDivs-1]. In the former case the last row is stretchable along 221 // the Y axis, otherwise it is fixed. 222 // 223 // The first and last columns are similarly treated with respect to the X 224 // axis. 225 // 226 // The above is to help explain some of the special casing that goes on the 227 // code below. 228 229 // The initial yDiv and whether the first row is considered stretchable or 230 // not depends on whether yDiv[0] was zero or not. 231 for (j = yIsStretchable ? 1 : 0; 232 j <= numYDivs && src.fTop < bitmapHeight; 233 j++, yIsStretchable = !yIsStretchable) { 234 src.fLeft = 0; 235 dst.fLeft = bounds.fLeft; 236 if (j == numYDivs) { 237 src.fBottom = bitmapHeight; 238 dst.fBottom = bounds.fBottom; 239 } else { 240 src.fBottom = yDivs[j]; 241 const int srcYSize = src.fBottom - src.fTop; 242 if (yIsStretchable) { 243 dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop, 244 srcYSize, 245 numStretchyYPixelsRemaining, 246 numFixedYPixelsRemaining); 247 numStretchyYPixelsRemaining -= srcYSize; 248 } else { 249 dst.fBottom = dst.fTop + SkIntToScalar(srcYSize); 250 numFixedYPixelsRemaining -= srcYSize; 251 } 252 } 253 254 xIsStretchable = initialXIsStretchable; 255 // The initial xDiv and whether the first column is considered 256 // stretchable or not depends on whether xDiv[0] was zero or not. 257 const uint32_t* colors = chunk.getColors(); 258 for (i = xIsStretchable ? 1 : 0; 259 i <= numXDivs && src.fLeft < bitmapWidth; 260 i++, xIsStretchable = !xIsStretchable) { 261 color = colors[colorIndex++]; 262 if (i == numXDivs) { 263 src.fRight = bitmapWidth; 264 dst.fRight = bounds.fRight; 265 } else { 266 src.fRight = xDivs[i]; 267 if (dstRightsHaveBeenCached) { 268 dst.fRight = dstRights[i]; 269 } else { 270 const int srcXSize = src.fRight - src.fLeft; 271 if (xIsStretchable) { 272 dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft, 273 srcXSize, 274 numStretchyXPixelsRemaining, 275 numFixedXPixelsRemaining); 276 numStretchyXPixelsRemaining -= srcXSize; 277 } else { 278 dst.fRight = dst.fLeft + SkIntToScalar(srcXSize); 279 numFixedXPixelsRemaining -= srcXSize; 280 } 281 dstRights[i] = dst.fRight; 282 } 283 } 284 // If this horizontal patch is too small to be displayed, leave 285 // the destination left edge where it is and go on to the next patch 286 // in the source. 287 if (src.fLeft >= src.fRight) { 288 src.fLeft = src.fRight; 289 continue; 290 } 291 // Make sure that we actually have room to draw any bits 292 if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) { 293 goto nextDiv; 294 } 295 // If this patch is transparent, skip and don't draw. 296 if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) { 297 if (outRegion) { 298 if (*outRegion == NULL) { 299 *outRegion = new SkRegion(); 300 } 301 SkIRect idst; 302 dst.round(&idst); 303 //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n", 304 // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom); 305 (*outRegion)->op(idst, SkRegion::kUnion_Op); 306 } 307 goto nextDiv; 308 } 309 if (canvas) { 310#ifdef USE_TRACE 311 ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n", 312 src.fLeft, src.fTop, src.width(), src.height(), 313 SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop), 314 SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height())); 315 if (2 == src.width() && SkIntToScalar(5) == dst.width()) { 316 ALOGV("--- skip patch\n"); 317 } 318#endif 319 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor, 320 color, hasXfer); 321 } 322 323nextDiv: 324 src.fLeft = src.fRight; 325 dst.fLeft = dst.fRight; 326 } 327 src.fTop = src.fBottom; 328 dst.fTop = dst.fBottom; 329 dstRightsHaveBeenCached = true; 330 } 331} 332