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