1
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkNinePatch.h"
11#include "SkCanvas.h"
12#include "SkShader.h"
13
14static const uint16_t g3x3Indices[] = {
15    0, 5, 1,    0, 4, 5,
16    1, 6, 2,    1, 5, 6,
17    2, 7, 3,    2, 6, 7,
18
19    4, 9, 5,    4, 8, 9,
20    5, 10, 6,   5, 9, 10,
21    6, 11, 7,   6, 10, 11,
22
23    8, 13, 9,   8, 12, 13,
24    9, 14, 10,  9, 13, 14,
25    10, 15, 11, 10, 14, 15
26};
27
28static int fillIndices(uint16_t indices[], int xCount, int yCount) {
29    uint16_t* startIndices = indices;
30
31    int n = 0;
32    for (int y = 0; y < yCount; y++) {
33        for (int x = 0; x < xCount; x++) {
34            *indices++ = n;
35            *indices++ = n + xCount + 2;
36            *indices++ = n + 1;
37
38            *indices++ = n;
39            *indices++ = n + xCount + 1;
40            *indices++ = n + xCount + 2;
41
42            n += 1;
43        }
44        n += 1;
45    }
46    return static_cast<int>(indices - startIndices);
47}
48
49// Computes the delta between vertices along a single axis
50static SkScalar computeVertexDelta(bool isStretchyVertex,
51                                   SkScalar currentVertex,
52                                   SkScalar prevVertex,
53                                   SkScalar stretchFactor) {
54    // the standard delta between vertices if no stretching is required
55    SkScalar delta = currentVertex - prevVertex;
56
57    // if the stretch factor is negative or zero we need to shrink the 9-patch
58    // to fit within the target bounds.  This means that we will eliminate all
59    // stretchy areas and scale the fixed areas to fit within the target bounds.
60    if (stretchFactor <= 0) {
61        if (isStretchyVertex)
62            delta = 0; // collapse stretchable areas
63        else
64            delta = SkScalarMul(delta, -stretchFactor); // scale fixed areas
65    // if the stretch factor is positive then we use the standard delta for
66    // fixed and scale the stretchable areas to fill the target bounds.
67    } else if (isStretchyVertex) {
68        delta = SkScalarMul(delta, stretchFactor);
69    }
70
71    return delta;
72}
73
74static void fillRow(SkPoint verts[], SkPoint texs[],
75                    const SkScalar vy, const SkScalar ty,
76                    const SkRect& bounds, const int32_t xDivs[], int numXDivs,
77                    const SkScalar stretchX, int width) {
78    SkScalar vx = bounds.fLeft;
79    verts->set(vx, vy); verts++;
80    texs->set(0, ty); texs++;
81
82    SkScalar prev = 0;
83    for (int x = 0; x < numXDivs; x++) {
84
85        const SkScalar tx = SkIntToScalar(xDivs[x]);
86        vx += computeVertexDelta(x & 1, tx, prev, stretchX);
87        prev = tx;
88
89        verts->set(vx, vy); verts++;
90        texs->set(tx, ty); texs++;
91    }
92    verts->set(bounds.fRight, vy); verts++;
93    texs->set(SkIntToScalar(width), ty); texs++;
94}
95
96struct Mesh {
97    const SkPoint*  fVerts;
98    const SkPoint*  fTexs;
99    const SkColor*  fColors;
100    const uint16_t* fIndices;
101};
102
103void SkNinePatch::DrawMesh(SkCanvas* canvas, const SkRect& bounds,
104                           const SkBitmap& bitmap,
105                           const int32_t xDivs[], int numXDivs,
106                           const int32_t yDivs[], int numYDivs,
107                           const SkPaint* paint) {
108    if (bounds.isEmpty() || bitmap.width() == 0 || bitmap.height() == 0) {
109        return;
110    }
111
112    // should try a quick-reject test before calling lockPixels
113    SkAutoLockPixels alp(bitmap);
114    // after the lock, it is valid to check
115    if (!bitmap.readyToDraw()) {
116        return;
117    }
118
119    // check for degenerate divs (just an optimization, not required)
120    {
121        int i;
122        int zeros = 0;
123        for (i = 0; i < numYDivs && yDivs[i] == 0; i++) {
124            zeros += 1;
125        }
126        numYDivs -= zeros;
127        yDivs += zeros;
128        for (i = numYDivs - 1; i >= 0 && yDivs[i] == bitmap.height(); --i) {
129            numYDivs -= 1;
130        }
131    }
132
133    Mesh mesh;
134
135    const int numXStretch = (numXDivs + 1) >> 1;
136    const int numYStretch = (numYDivs + 1) >> 1;
137
138    if (numXStretch < 1 && numYStretch < 1) {
139        canvas->drawBitmapRect(bitmap, NULL, bounds, paint);
140        return;
141    }
142
143    if (false) {
144        int i;
145        for (i = 0; i < numXDivs; i++) {
146            SkDebugf("--- xdivs[%d] %d\n", i, xDivs[i]);
147        }
148        for (i = 0; i < numYDivs; i++) {
149            SkDebugf("--- ydivs[%d] %d\n", i, yDivs[i]);
150        }
151    }
152
153    SkScalar stretchX = 0, stretchY = 0;
154
155    if (numXStretch > 0) {
156        int stretchSize = 0;
157        for (int i = 1; i < numXDivs; i += 2) {
158            stretchSize += xDivs[i] - xDivs[i-1];
159        }
160        const SkScalar fixed = SkIntToScalar(bitmap.width() - stretchSize);
161        if (bounds.width() >= fixed)
162            stretchX = (bounds.width() - fixed) / stretchSize;
163        else // reuse stretchX, but keep it negative as a signal
164            stretchX = SkScalarDiv(-bounds.width(), fixed);
165    }
166
167    if (numYStretch > 0) {
168        int stretchSize = 0;
169        for (int i = 1; i < numYDivs; i += 2) {
170            stretchSize += yDivs[i] - yDivs[i-1];
171        }
172        const SkScalar fixed = SkIntToScalar(bitmap.height() - stretchSize);
173        if (bounds.height() >= fixed)
174            stretchY = (bounds.height() - fixed) / stretchSize;
175        else // reuse stretchX, but keep it negative as a signal
176            stretchY = SkScalarDiv(-bounds.height(), fixed);
177    }
178
179#if 0
180    SkDebugf("---- drawasamesh [%d %d] -> [%g %g] <%d %d> (%g %g)\n",
181             bitmap.width(), bitmap.height(),
182             SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
183             numXDivs + 1, numYDivs + 1,
184             SkScalarToFloat(stretchX), SkScalarToFloat(stretchY));
185#endif
186
187    const int vCount = (numXDivs + 2) * (numYDivs + 2);
188    // number of celss * 2 (tris per cell) * 3 (verts per tri)
189    const int indexCount = (numXDivs + 1) * (numYDivs + 1) * 2 * 3;
190    // allocate 2 times, one for verts, one for texs, plus indices
191    SkAutoMalloc storage(vCount * sizeof(SkPoint) * 2 +
192                         indexCount * sizeof(uint16_t));
193    SkPoint* verts = (SkPoint*)storage.get();
194    SkPoint* texs = verts + vCount;
195    uint16_t* indices = (uint16_t*)(texs + vCount);
196
197    mesh.fVerts = verts;
198    mesh.fTexs = texs;
199    mesh.fColors = NULL;
200    mesh.fIndices = NULL;
201
202    // we use <= for YDivs, since the prebuild indices work for 3x2 and 3x1 too
203    if (numXDivs == 2 && numYDivs <= 2) {
204        mesh.fIndices = g3x3Indices;
205    } else {
206        SkDEBUGCODE(int n =) fillIndices(indices, numXDivs + 1, numYDivs + 1);
207        SkASSERT(n == indexCount);
208        mesh.fIndices = indices;
209    }
210
211    SkScalar vy = bounds.fTop;
212    fillRow(verts, texs, vy, 0, bounds, xDivs, numXDivs,
213            stretchX, bitmap.width());
214    verts += numXDivs + 2;
215    texs += numXDivs + 2;
216    for (int y = 0; y < numYDivs; y++) {
217        const SkScalar ty = SkIntToScalar(yDivs[y]);
218        if (stretchY >= 0) {
219            if (y & 1) {
220                vy += stretchY;
221            } else {
222                vy += ty;
223            }
224        } else {    // shrink fixed sections, and collaps stretchy sections
225            if (y & 1) {
226                ;// do nothing
227            } else {
228                vy += SkScalarMul(ty, -stretchY);
229            }
230        }
231        fillRow(verts, texs, vy, ty, bounds, xDivs, numXDivs,
232                stretchX, bitmap.width());
233        verts += numXDivs + 2;
234        texs += numXDivs + 2;
235    }
236    fillRow(verts, texs, bounds.fBottom, SkIntToScalar(bitmap.height()),
237            bounds, xDivs, numXDivs, stretchX, bitmap.width());
238
239    SkShader* shader = SkShader::CreateBitmapShader(bitmap,
240                                                    SkShader::kClamp_TileMode,
241                                                    SkShader::kClamp_TileMode);
242    SkPaint p;
243    if (paint) {
244        p = *paint;
245    }
246    p.setShader(shader)->unref();
247    canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vCount,
248                         mesh.fVerts, mesh.fTexs, mesh.fColors, NULL,
249                         mesh.fIndices, indexCount, p);
250}
251
252///////////////////////////////////////////////////////////////////////////////
253
254static void drawNineViaRects(SkCanvas* canvas, const SkRect& dst,
255                             const SkBitmap& bitmap, const SkIRect& margins,
256                             const SkPaint* paint) {
257    const int32_t srcX[4] = {
258        0, margins.fLeft, bitmap.width() - margins.fRight, bitmap.width()
259    };
260    const int32_t srcY[4] = {
261        0, margins.fTop, bitmap.height() - margins.fBottom, bitmap.height()
262    };
263    SkScalar dstX[4] = {
264        dst.fLeft, dst.fLeft + SkIntToScalar(margins.fLeft),
265        dst.fRight - SkIntToScalar(margins.fRight), dst.fRight
266    };
267    SkScalar dstY[4] = {
268        dst.fTop, dst.fTop + SkIntToScalar(margins.fTop),
269        dst.fBottom - SkIntToScalar(margins.fBottom), dst.fBottom
270    };
271
272    if (dstX[1] > dstX[2]) {
273        dstX[1] = dstX[0] + (dstX[3] - dstX[0]) * SkIntToScalar(margins.fLeft) /
274            (SkIntToScalar(margins.fLeft) + SkIntToScalar(margins.fRight));
275        dstX[2] = dstX[1];
276    }
277
278    if (dstY[1] > dstY[2]) {
279        dstY[1] = dstY[0] + (dstY[3] - dstY[0]) * SkIntToScalar(margins.fTop) /
280            (SkIntToScalar(margins.fTop) + SkIntToScalar(margins.fBottom));
281        dstY[2] = dstY[1];
282    }
283
284    SkIRect s;
285    SkRect  d;
286    for (int y = 0; y < 3; y++) {
287        s.fTop = srcY[y];
288        s.fBottom = srcY[y+1];
289        d.fTop = dstY[y];
290        d.fBottom = dstY[y+1];
291        for (int x = 0; x < 3; x++) {
292            s.fLeft = srcX[x];
293            s.fRight = srcX[x+1];
294            d.fLeft = dstX[x];
295            d.fRight = dstX[x+1];
296            canvas->drawBitmapRect(bitmap, &s, d, paint);
297        }
298    }
299}
300
301void SkNinePatch::DrawNine(SkCanvas* canvas, const SkRect& bounds,
302                           const SkBitmap& bitmap, const SkIRect& margins,
303                           const SkPaint* paint) {
304    /** Our vertices code has numerical precision problems if the transformed
305     coordinates land directly on a 1/2 pixel boundary. To work around that
306     for now, we only take the vertices case if we are in opengl. Also,
307     when not in GL, the vertices impl is slower (more math) than calling
308     the viaRects code.
309     */
310    if (false /* is our canvas backed by a gpu?*/) {
311        int32_t xDivs[2];
312        int32_t yDivs[2];
313
314        xDivs[0] = margins.fLeft;
315        xDivs[1] = bitmap.width() - margins.fRight;
316        yDivs[0] = margins.fTop;
317        yDivs[1] = bitmap.height() - margins.fBottom;
318
319        if (xDivs[0] > xDivs[1]) {
320            xDivs[0] = bitmap.width() * margins.fLeft /
321                (margins.fLeft + margins.fRight);
322            xDivs[1] = xDivs[0];
323        }
324        if (yDivs[0] > yDivs[1]) {
325            yDivs[0] = bitmap.height() * margins.fTop /
326                (margins.fTop + margins.fBottom);
327            yDivs[1] = yDivs[0];
328        }
329
330        SkNinePatch::DrawMesh(canvas, bounds, bitmap,
331                              xDivs, 2, yDivs, 2, paint);
332    } else {
333        drawNineViaRects(canvas, bounds, bitmap, margins, paint);
334    }
335}
336