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