NinePatchImpl.cpp revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 our canvas is GL, draw this as a mesh, which will be faster than 107 // in parts (which is faster for raster) 108 if (canvas && canvas->getViewport(NULL)) { 109 SkNinePatch::DrawMesh(canvas, bounds, bitmap, 110 chunk.xDivs, chunk.numXDivs, 111 chunk.yDivs, chunk.numYDivs, 112 paint); 113 return; 114 } 115 116#ifdef USE_TRACE 117 gTrace = true; 118#endif 119 120 SkASSERT(canvas || outRegion); 121 122#if 0 123 if (canvas) { 124 const SkMatrix& m = canvas->getTotalMatrix(); 125 SkDebugf("ninepatch [%g %g %g] [%g %g %g]\n", 126 SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]), 127 SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5])); 128 } 129#endif 130 131#ifdef USE_TRACE 132 if (gTrace) { 133 SkDEBUGF(("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()))); 134 SkDEBUGF(("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height())); 135 SkDEBUGF(("======== ninepatch xDivs [%d,%d]\n", chunk.xDivs[0], chunk.xDivs[1])); 136 SkDEBUGF(("======== ninepatch yDivs [%d,%d]\n", chunk.yDivs[0], chunk.yDivs[1])); 137 } 138#endif 139 140 if (bounds.isEmpty() || 141 bitmap.width() == 0 || bitmap.height() == 0 || 142 (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0)) 143 { 144#ifdef USE_TRACE 145 if (gTrace) SkDEBUGF(("======== abort ninepatch draw\n")); 146#endif 147 return; 148 } 149 150 // should try a quick-reject test before calling lockPixels 151 152 SkAutoLockPixels alp(bitmap); 153 // after the lock, it is valid to check getPixels() 154 if (bitmap.getPixels() == NULL) 155 return; 156 157 SkPaint defaultPaint; 158 if (NULL == paint) { 159 paint = &defaultPaint; 160 } 161 162 const bool hasXfer = paint->getXfermode() != NULL; 163 SkRect dst; 164 SkIRect src; 165 166 const int32_t x0 = chunk.xDivs[0]; 167 const int32_t y0 = chunk.yDivs[0]; 168 const SkColor initColor = ((SkPaint*)paint)->getColor(); 169 const uint8_t numXDivs = chunk.numXDivs; 170 const uint8_t numYDivs = chunk.numYDivs; 171 int i; 172 int j; 173 int colorIndex = 0; 174 uint32_t color; 175 bool xIsStretchable; 176 const bool initialXIsStretchable = (x0 == 0); 177 bool yIsStretchable = (y0 == 0); 178 const int bitmapWidth = bitmap.width(); 179 const int bitmapHeight = bitmap.height(); 180 181 SkScalar* dstRights = (SkScalar*) alloca((numXDivs + 1) * sizeof(SkScalar)); 182 bool dstRightsHaveBeenCached = false; 183 184 int numStretchyXPixelsRemaining = 0; 185 for (i = 0; i < numXDivs; i += 2) { 186 numStretchyXPixelsRemaining += chunk.xDivs[i + 1] - chunk.xDivs[i]; 187 } 188 int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining; 189 int numStretchyYPixelsRemaining = 0; 190 for (i = 0; i < numYDivs; i += 2) { 191 numStretchyYPixelsRemaining += chunk.yDivs[i + 1] - chunk.yDivs[i]; 192 } 193 int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining; 194 195#if 0 196 SkDebugf("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n", 197 bitmap.width(), bitmap.height(), 198 SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop), 199 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()), 200 numXDivs, numYDivs); 201#endif 202 203 src.fTop = 0; 204 dst.fTop = bounds.fTop; 205 // The first row always starts with the top being at y=0 and the bottom 206 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case 207 // the first row is stretchable along the Y axis, otherwise it is fixed. 208 // The last row always ends with the bottom being bitmap.height and the top 209 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or 210 // yDivs[numYDivs-1]. In the former case the last row is stretchable along 211 // the Y axis, otherwise it is fixed. 212 // 213 // The first and last columns are similarly treated with respect to the X 214 // axis. 215 // 216 // The above is to help explain some of the special casing that goes on the 217 // code below. 218 219 // The initial yDiv and whether the first row is considered stretchable or 220 // not depends on whether yDiv[0] was zero or not. 221 for (j = yIsStretchable ? 1 : 0; 222 j <= numYDivs && src.fTop < bitmapHeight; 223 j++, yIsStretchable = !yIsStretchable) { 224 src.fLeft = 0; 225 dst.fLeft = bounds.fLeft; 226 if (j == numYDivs) { 227 src.fBottom = bitmapHeight; 228 dst.fBottom = bounds.fBottom; 229 } else { 230 src.fBottom = chunk.yDivs[j]; 231 const int srcYSize = src.fBottom - src.fTop; 232 if (yIsStretchable) { 233 dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop, 234 srcYSize, 235 numStretchyYPixelsRemaining, 236 numFixedYPixelsRemaining); 237 numStretchyYPixelsRemaining -= srcYSize; 238 } else { 239 dst.fBottom = dst.fTop + SkIntToScalar(srcYSize); 240 numFixedYPixelsRemaining -= srcYSize; 241 } 242 } 243 244 xIsStretchable = initialXIsStretchable; 245 // The initial xDiv and whether the first column is considered 246 // stretchable or not depends on whether xDiv[0] was zero or not. 247 for (i = xIsStretchable ? 1 : 0; 248 i <= numXDivs && src.fLeft < bitmapWidth; 249 i++, xIsStretchable = !xIsStretchable) { 250 color = chunk.colors[colorIndex++]; 251 if (i == numXDivs) { 252 src.fRight = bitmapWidth; 253 dst.fRight = bounds.fRight; 254 } else { 255 src.fRight = chunk.xDivs[i]; 256 if (dstRightsHaveBeenCached) { 257 dst.fRight = dstRights[i]; 258 } else { 259 const int srcXSize = src.fRight - src.fLeft; 260 if (xIsStretchable) { 261 dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft, 262 srcXSize, 263 numStretchyXPixelsRemaining, 264 numFixedXPixelsRemaining); 265 numStretchyXPixelsRemaining -= srcXSize; 266 } else { 267 dst.fRight = dst.fLeft + SkIntToScalar(srcXSize); 268 numFixedXPixelsRemaining -= srcXSize; 269 } 270 dstRights[i] = dst.fRight; 271 } 272 } 273 // If this horizontal patch is too small to be displayed, leave 274 // the destination left edge where it is and go on to the next patch 275 // in the source. 276 if (src.fLeft >= src.fRight) { 277 src.fLeft = src.fRight; 278 continue; 279 } 280 // Make sure that we actually have room to draw any bits 281 if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) { 282 goto nextDiv; 283 } 284 // If this patch is transparent, skip and don't draw. 285 if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) { 286 if (outRegion) { 287 if (*outRegion == NULL) { 288 *outRegion = new SkRegion(); 289 } 290 SkIRect idst; 291 dst.round(&idst); 292 //LOGI("Adding trans rect: (%d,%d)-(%d,%d)\n", 293 // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom); 294 (*outRegion)->op(idst, SkRegion::kUnion_Op); 295 } 296 goto nextDiv; 297 } 298 if (canvas) { 299#if 0 300 SkDebugf("-- src [%d %d %d %d] dst [%g %g %g %g]\n", 301 src.fLeft, src.fTop, src.width(), src.height(), 302 SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop), 303 SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height())); 304 if (2 == src.width() && SkIntToScalar(5) == dst.width()) { 305 SkDebugf("--- skip patch\n"); 306 } 307#endif 308 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor, 309 color, hasXfer); 310 } 311 312nextDiv: 313 src.fLeft = src.fRight; 314 dst.fLeft = dst.fRight; 315 } 316 src.fTop = src.fBottom; 317 dst.fTop = dst.fBottom; 318 dstRightsHaveBeenCached = true; 319 } 320} 321