NinePatchImpl.cpp revision 4c5efe9290543b723b76a8bd48518da1ae1dcb26
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
29namespace android {
30
31static const bool kUseTrace = true;
32static bool gTrace = false;
33
34static 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
58static 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
64static 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, &src, dst, &paint);
86    }
87}
88
89SkScalar 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
98void 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
316nextDiv:
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