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