NinePatchImpl.cpp revision 6381dd4ff212a95be30d2b445d40ff419ab076b4
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
40static 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
64static 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
70static 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
95SkScalar 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
105void 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
323nextDiv:
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