• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
14 static 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 
fillIndices(uint16_t indices[],int xCount,int yCount)28 static 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 indices - startIndices;
47 }
48 
49 // Computes the delta between vertices along a single axis
computeVertexDelta(bool isStretchyVertex,SkScalar currentVertex,SkScalar prevVertex,SkScalar stretchFactor)50 static 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 
fillRow(SkPoint verts[],SkPoint texs[],const SkScalar vy,const SkScalar ty,const SkRect & bounds,const int32_t xDivs[],int numXDivs,const SkScalar stretchX,int width)74 static 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 
96 struct Mesh {
97     const SkPoint*  fVerts;
98     const SkPoint*  fTexs;
99     const SkColor*  fColors;
100     const uint16_t* fIndices;
101 };
102 
DrawMesh(SkCanvas * canvas,const SkRect & bounds,const SkBitmap & bitmap,const int32_t xDivs[],int numXDivs,const int32_t yDivs[],int numYDivs,const SkPaint * paint)103 void 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 
drawNineViaRects(SkCanvas * canvas,const SkRect & dst,const SkBitmap & bitmap,const SkIRect & margins,const SkPaint * paint)254 static 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 
DrawNine(SkCanvas * canvas,const SkRect & bounds,const SkBitmap & bitmap,const SkIRect & margins,const SkPaint * paint)301 void 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