• 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 #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 
getColor(const SkBitmap & bitmap,int x,int y,SkColor * c)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 
modAlpha(SkColor c,int alpha)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 
drawStretchyPatch(SkCanvas * canvas,SkIRect & src,const SkRect & dst,const SkBitmap & bitmap,const SkPaint & paint,SkColor initColor,uint32_t colorHint,bool hasXfer)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 
calculateStretch(SkScalar boundsLimit,SkScalar startingPoint,int srcSpace,int numStrechyPixelsRemaining,int numFixedPixelsRemaining)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 
Draw(SkCanvas * canvas,const SkRect & bounds,const SkBitmap & bitmap,const Res_png_9patch & chunk,const SkPaint * paint,SkRegion ** outRegion)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