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