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 #include "utils/NinePatch.h" 19 20 #include "SkBitmap.h" 21 #include "SkCanvas.h" 22 #include "SkColorPriv.h" 23 #include "SkNinePatch.h" 24 #include "SkPaint.h" 25 #include "SkUnPreMultiply.h" 26 27 #include <utils/Log.h> 28 29 namespace android { 30 31 static const bool kUseTrace = true; 32 static bool gTrace = false; 33 34 static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) { 35 switch (bitmap.colorType()) { 36 case kN32_SkColorType: 37 *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y)); 38 break; 39 case kRGB_565_SkColorType: 40 *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y)); 41 break; 42 case kARGB_4444_SkColorType: 43 *c = SkUnPreMultiply::PMColorToColor( 44 SkPixel4444ToPixel32(*bitmap.getAddr16(x, y))); 45 break; 46 case kIndex_8_SkColorType: { 47 SkColorTable* ctable = bitmap.getColorTable(); 48 *c = SkUnPreMultiply::PMColorToColor( 49 (*ctable)[*bitmap.getAddr8(x, y)]); 50 break; 51 } 52 default: 53 return false; 54 } 55 return true; 56 } 57 58 static SkColor modAlpha(SkColor c, int alpha) { 59 int scale = alpha + (alpha >> 7); 60 int a = SkColorGetA(c) * scale >> 8; 61 return SkColorSetA(c, a); 62 } 63 64 static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst, 65 const SkBitmap& bitmap, const SkPaint& paint, 66 SkColor initColor, uint32_t colorHint, 67 bool hasXfer) { 68 if (colorHint != android::Res_png_9patch::NO_COLOR) { 69 ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha())); 70 canvas->drawRect(dst, paint); 71 ((SkPaint*)&paint)->setColor(initColor); 72 } else if (src.width() == 1 && src.height() == 1) { 73 SkColor c; 74 if (!getColor(bitmap, src.fLeft, src.fTop, &c)) { 75 goto SLOW_CASE; 76 } 77 if (0 != c || hasXfer) { 78 SkColor prev = paint.getColor(); 79 ((SkPaint*)&paint)->setColor(c); 80 canvas->drawRect(dst, paint); 81 ((SkPaint*)&paint)->setColor(prev); 82 } 83 } else { 84 SLOW_CASE: 85 canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint); 86 } 87 } 88 89 SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint, 90 int srcSpace, int numStrechyPixelsRemaining, 91 int numFixedPixelsRemaining) { 92 SkScalar spaceRemaining = boundsLimit - startingPoint; 93 SkScalar stretchySpaceRemaining = 94 spaceRemaining - SkIntToScalar(numFixedPixelsRemaining); 95 return srcSpace * stretchySpaceRemaining / numStrechyPixelsRemaining; 96 } 97 98 void NinePatch::Draw(SkCanvas* canvas, const SkRect& bounds, 99 const SkBitmap& bitmap, const Res_png_9patch& chunk, 100 const SkPaint* paint, SkRegion** outRegion) { 101 if (canvas && canvas->quickReject(bounds)) { 102 return; 103 } 104 105 SkPaint defaultPaint; 106 if (NULL == paint) { 107 // matches default dither in NinePatchDrawable.java. 108 defaultPaint.setDither(true); 109 paint = &defaultPaint; 110 } 111 112 const int32_t* xDivs = chunk.getXDivs(); 113 const int32_t* yDivs = chunk.getYDivs(); 114 // if our SkCanvas were back by GL we should enable this and draw this as 115 // a mesh, which will be faster in most cases. 116 if ((false)) { 117 SkNinePatch::DrawMesh(canvas, bounds, bitmap, 118 xDivs, chunk.numXDivs, 119 yDivs, chunk.numYDivs, 120 paint); 121 return; 122 } 123 124 if (kUseTrace) { 125 gTrace = true; 126 } 127 128 SkASSERT(canvas || outRegion); 129 130 if (kUseTrace) { 131 if (canvas) { 132 const SkMatrix& m = canvas->getTotalMatrix(); 133 ALOGV("ninepatch [%g %g %g] [%g %g %g]\n", 134 SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]), 135 SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5])); 136 } 137 138 ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), 139 SkScalarToFloat(bounds.height())); 140 ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height()); 141 ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]); 142 ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]); 143 } 144 145 if (bounds.isEmpty() || 146 bitmap.width() == 0 || bitmap.height() == 0 || 147 (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0)) 148 { 149 if (kUseTrace) { 150 ALOGV("======== abort ninepatch draw\n"); 151 } 152 return; 153 } 154 155 // should try a quick-reject test before calling lockPixels 156 157 SkAutoLockPixels alp(bitmap); 158 // after the lock, it is valid to check getPixels() 159 if (bitmap.getPixels() == NULL) 160 return; 161 162 const bool hasXfer = paint->getXfermode() != NULL; 163 SkRect dst; 164 SkIRect src; 165 166 const int32_t x0 = xDivs[0]; 167 const int32_t y0 = 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 // Number of bytes needed for dstRights array. 182 // Need to cast numXDivs to a larger type to avoid overflow. 183 const size_t dstBytes = ((size_t) numXDivs + 1) * sizeof(SkScalar); 184 SkScalar* dstRights = (SkScalar*) alloca(dstBytes); 185 bool dstRightsHaveBeenCached = false; 186 187 int numStretchyXPixelsRemaining = 0; 188 for (i = 0; i < numXDivs; i += 2) { 189 numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i]; 190 } 191 int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining; 192 int numStretchyYPixelsRemaining = 0; 193 for (i = 0; i < numYDivs; i += 2) { 194 numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i]; 195 } 196 int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining; 197 198 if (kUseTrace) { 199 ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n", 200 bitmap.width(), bitmap.height(), 201 SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop), 202 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()), 203 numXDivs, numYDivs); 204 } 205 206 src.fTop = 0; 207 dst.fTop = bounds.fTop; 208 // The first row always starts with the top being at y=0 and the bottom 209 // being either yDivs[1] (if yDivs[0]=0) or yDivs[0]. In the former case 210 // the first row is stretchable along the Y axis, otherwise it is fixed. 211 // The last row always ends with the bottom being bitmap.height and the top 212 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or 213 // yDivs[numYDivs-1]. In the former case the last row is stretchable along 214 // the Y axis, otherwise it is fixed. 215 // 216 // The first and last columns are similarly treated with respect to the X 217 // axis. 218 // 219 // The above is to help explain some of the special casing that goes on the 220 // code below. 221 222 // The initial yDiv and whether the first row is considered stretchable or 223 // not depends on whether yDiv[0] was zero or not. 224 for (j = yIsStretchable ? 1 : 0; 225 j <= numYDivs && src.fTop < bitmapHeight; 226 j++, yIsStretchable = !yIsStretchable) { 227 src.fLeft = 0; 228 dst.fLeft = bounds.fLeft; 229 if (j == numYDivs) { 230 src.fBottom = bitmapHeight; 231 dst.fBottom = bounds.fBottom; 232 } else { 233 src.fBottom = yDivs[j]; 234 const int srcYSize = src.fBottom - src.fTop; 235 if (yIsStretchable) { 236 dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop, 237 srcYSize, 238 numStretchyYPixelsRemaining, 239 numFixedYPixelsRemaining); 240 numStretchyYPixelsRemaining -= srcYSize; 241 } else { 242 dst.fBottom = dst.fTop + SkIntToScalar(srcYSize); 243 numFixedYPixelsRemaining -= srcYSize; 244 } 245 } 246 247 xIsStretchable = initialXIsStretchable; 248 // The initial xDiv and whether the first column is considered 249 // stretchable or not depends on whether xDiv[0] was zero or not. 250 const uint32_t* colors = chunk.getColors(); 251 for (i = xIsStretchable ? 1 : 0; 252 i <= numXDivs && src.fLeft < bitmapWidth; 253 i++, xIsStretchable = !xIsStretchable) { 254 color = colors[colorIndex++]; 255 if (i == numXDivs) { 256 src.fRight = bitmapWidth; 257 dst.fRight = bounds.fRight; 258 } else { 259 src.fRight = 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 //ALOGI("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 (kUseTrace) { 304 ALOGV("-- 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 ALOGV("--- skip patch\n"); 310 } 311 } 312 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor, 313 color, hasXfer); 314 } 315 316 nextDiv: 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 326 } // namespace android 327