• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getColor(const SkBitmap & bitmap,int x,int y,SkColor * c)40 static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
41     switch (bitmap.colorType()) {
42         case kN32_SkColorType:
43             *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
44             break;
45         case kRGB_565_SkColorType:
46             *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
47             break;
48         case kARGB_4444_SkColorType:
49             *c = SkUnPreMultiply::PMColorToColor(
50                                 SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
51             break;
52         case kIndex_8_SkColorType: {
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 
modAlpha(SkColor c,int alpha)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 
drawStretchyPatch(SkCanvas * canvas,SkIRect & src,const SkRect & dst,const SkBitmap & bitmap,const SkPaint & paint,SkColor initColor,uint32_t colorHint,bool hasXfer)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 
calculateStretch(SkScalar boundsLimit,SkScalar startingPoint,int srcSpace,int numStrechyPixelsRemaining,int numFixedPixelsRemaining)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 
NinePatch_Draw(SkCanvas * canvas,const SkRect & bounds,const SkBitmap & bitmap,const android::Res_png_9patch & chunk,const SkPaint * paint,SkRegion ** outRegion)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     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 
323 nextDiv:
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