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 static_cast<int>(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