blob: 9729a13807fcd4604bc8aebcc41c6c9230f14540 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
reed@android.com8a1c16f2008-12-17 15:59:43 +00002/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00003 * 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
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
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 indices - startIndices;
47}
48
49static void fillRow(SkPoint verts[], SkPoint texs[],
50 const SkScalar vy, const SkScalar ty,
51 const SkRect& bounds, const int32_t xDivs[], int numXDivs,
52 const SkScalar stretchX, int width) {
53 SkScalar vx = bounds.fLeft;
54 verts->set(vx, vy); verts++;
55 texs->set(0, ty); texs++;
56 for (int x = 0; x < numXDivs; x++) {
57 SkScalar tx = SkIntToScalar(xDivs[x]);
reed@google.comade907b2011-09-28 12:33:41 +000058 if (stretchX >= 0) {
59 if (x & 1) {
60 vx += stretchX;
61 } else {
62 vx += tx;
63 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 } else {
reed@google.comade907b2011-09-28 12:33:41 +000065 if (x & 1) {
66 ; // do nothing
67 } else {
68 vx += SkScalarMul(tx, -stretchX);
69 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000070 }
71 verts->set(vx, vy); verts++;
72 texs->set(tx, ty); texs++;
73 }
74 verts->set(bounds.fRight, vy); verts++;
75 texs->set(SkIntToScalar(width), ty); texs++;
76}
77
78struct Mesh {
79 const SkPoint* fVerts;
80 const SkPoint* fTexs;
81 const SkColor* fColors;
82 const uint16_t* fIndices;
83};
84
85void SkNinePatch::DrawMesh(SkCanvas* canvas, const SkRect& bounds,
86 const SkBitmap& bitmap,
87 const int32_t xDivs[], int numXDivs,
88 const int32_t yDivs[], int numYDivs,
89 const SkPaint* paint) {
90 if (bounds.isEmpty() || bitmap.width() == 0 || bitmap.height() == 0) {
91 return;
92 }
93
94 // should try a quick-reject test before calling lockPixels
95 SkAutoLockPixels alp(bitmap);
96 // after the lock, it is valid to check
97 if (!bitmap.readyToDraw()) {
98 return;
99 }
100
101 // check for degenerate divs (just an optimization, not required)
102 {
103 int i;
104 int zeros = 0;
105 for (i = 0; i < numYDivs && yDivs[i] == 0; i++) {
106 zeros += 1;
107 }
108 numYDivs -= zeros;
109 yDivs += zeros;
110 for (i = numYDivs - 1; i >= 0 && yDivs[i] == bitmap.height(); --i) {
111 numYDivs -= 1;
112 }
113 }
114
115 Mesh mesh;
116
117 const int numXStretch = (numXDivs + 1) >> 1;
118 const int numYStretch = (numYDivs + 1) >> 1;
119
120 if (numXStretch < 1 && numYStretch < 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 canvas->drawBitmapRect(bitmap, NULL, bounds, paint);
122 return;
123 }
124
125 if (false) {
126 int i;
127 for (i = 0; i < numXDivs; i++) {
128 SkDebugf("--- xdivs[%d] %d\n", i, xDivs[i]);
129 }
130 for (i = 0; i < numYDivs; i++) {
131 SkDebugf("--- ydivs[%d] %d\n", i, yDivs[i]);
132 }
133 }
134
135 SkScalar stretchX = 0, stretchY = 0;
136
137 if (numXStretch > 0) {
138 int stretchSize = 0;
139 for (int i = 1; i < numXDivs; i += 2) {
140 stretchSize += xDivs[i] - xDivs[i-1];
141 }
142 int fixed = bitmap.width() - stretchSize;
143 stretchX = (bounds.width() - SkIntToScalar(fixed)) / numXStretch;
144 if (stretchX < 0) {
reed@google.comade907b2011-09-28 12:33:41 +0000145 // reuse stretchX, but keep it negative as a signal
bsalomon@google.com9d12f5c2011-09-29 18:08:18 +0000146 stretchX = -SkIntToScalar(bitmap.width()) / fixed;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 }
148 }
149
150 if (numYStretch > 0) {
151 int stretchSize = 0;
152 for (int i = 1; i < numYDivs; i += 2) {
153 stretchSize += yDivs[i] - yDivs[i-1];
154 }
155 int fixed = bitmap.height() - stretchSize;
156 stretchY = (bounds.height() - SkIntToScalar(fixed)) / numYStretch;
157 if (stretchY < 0) {
reed@google.comade907b2011-09-28 12:33:41 +0000158 // reuse stretchY, but keep it negative as a signal
bsalomon@google.com9d12f5c2011-09-29 18:08:18 +0000159 stretchY = -SkIntToScalar(bitmap.height()) / fixed;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 }
162
163#if 0
164 SkDebugf("---- drawasamesh [%d %d] -> [%g %g] <%d %d> (%g %g)\n",
165 bitmap.width(), bitmap.height(),
166 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
167 numXDivs + 1, numYDivs + 1,
168 SkScalarToFloat(stretchX), SkScalarToFloat(stretchY));
169#endif
170
171 const int vCount = (numXDivs + 2) * (numYDivs + 2);
172 // number of celss * 2 (tris per cell) * 3 (verts per tri)
173 const int indexCount = (numXDivs + 1) * (numYDivs + 1) * 2 * 3;
174 // allocate 2 times, one for verts, one for texs, plus indices
175 SkAutoMalloc storage(vCount * sizeof(SkPoint) * 2 +
176 indexCount * sizeof(uint16_t));
177 SkPoint* verts = (SkPoint*)storage.get();
178 SkPoint* texs = verts + vCount;
179 uint16_t* indices = (uint16_t*)(texs + vCount);
180
181 mesh.fVerts = verts;
182 mesh.fTexs = texs;
183 mesh.fColors = NULL;
184 mesh.fIndices = NULL;
185
186 // we use <= for YDivs, since the prebuild indices work for 3x2 and 3x1 too
187 if (numXDivs == 2 && numYDivs <= 2) {
188 mesh.fIndices = g3x3Indices;
189 } else {
reed@android.com9781ca52009-04-14 14:28:22 +0000190 SkDEBUGCODE(int n =) fillIndices(indices, numXDivs + 1, numYDivs + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191 SkASSERT(n == indexCount);
192 mesh.fIndices = indices;
193 }
194
195 SkScalar vy = bounds.fTop;
196 fillRow(verts, texs, vy, 0, bounds, xDivs, numXDivs,
197 stretchX, bitmap.width());
198 verts += numXDivs + 2;
199 texs += numXDivs + 2;
200 for (int y = 0; y < numYDivs; y++) {
201 const SkScalar ty = SkIntToScalar(yDivs[y]);
reed@google.comade907b2011-09-28 12:33:41 +0000202 if (stretchY >= 0) {
203 if (y & 1) {
204 vy += stretchY;
205 } else {
206 vy += ty;
207 }
208 } else { // shrink fixed sections, and collaps stretchy sections
209 if (y & 1) {
210 ;// do nothing
211 } else {
212 vy += SkScalarMul(ty, -stretchY);
213 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 }
215 fillRow(verts, texs, vy, ty, bounds, xDivs, numXDivs,
216 stretchX, bitmap.width());
217 verts += numXDivs + 2;
218 texs += numXDivs + 2;
219 }
220 fillRow(verts, texs, bounds.fBottom, SkIntToScalar(bitmap.height()),
221 bounds, xDivs, numXDivs, stretchX, bitmap.width());
222
223 SkShader* shader = SkShader::CreateBitmapShader(bitmap,
224 SkShader::kClamp_TileMode,
225 SkShader::kClamp_TileMode);
226 SkPaint p;
227 if (paint) {
228 p = *paint;
229 }
230 p.setShader(shader)->unref();
231 canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vCount,
232 mesh.fVerts, mesh.fTexs, mesh.fColors, NULL,
233 mesh.fIndices, indexCount, p);
234}
235
236///////////////////////////////////////////////////////////////////////////////
237
238static void drawNineViaRects(SkCanvas* canvas, const SkRect& dst,
239 const SkBitmap& bitmap, const SkIRect& margins,
240 const SkPaint* paint) {
241 const int32_t srcX[4] = {
242 0, margins.fLeft, bitmap.width() - margins.fRight, bitmap.width()
243 };
244 const int32_t srcY[4] = {
245 0, margins.fTop, bitmap.height() - margins.fBottom, bitmap.height()
246 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000247 SkScalar dstX[4] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000248 dst.fLeft, dst.fLeft + SkIntToScalar(margins.fLeft),
249 dst.fRight - SkIntToScalar(margins.fRight), dst.fRight
250 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000251 SkScalar dstY[4] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000252 dst.fTop, dst.fTop + SkIntToScalar(margins.fTop),
253 dst.fBottom - SkIntToScalar(margins.fBottom), dst.fBottom
254 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000255
256 if (dstX[1] > dstX[2]) {
257 dstX[1] = dstX[0] + (dstX[3] - dstX[0]) * SkIntToScalar(margins.fLeft) /
258 (SkIntToScalar(margins.fLeft) + SkIntToScalar(margins.fRight));
259 dstX[2] = dstX[1];
260 }
261
262 if (dstY[1] > dstY[2]) {
263 dstY[1] = dstY[0] + (dstY[3] - dstY[0]) * SkIntToScalar(margins.fTop) /
264 (SkIntToScalar(margins.fTop) + SkIntToScalar(margins.fBottom));
265 dstY[2] = dstY[1];
266 }
267
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 SkIRect s;
269 SkRect d;
270 for (int y = 0; y < 3; y++) {
271 s.fTop = srcY[y];
272 s.fBottom = srcY[y+1];
273 d.fTop = dstY[y];
274 d.fBottom = dstY[y+1];
275 for (int x = 0; x < 3; x++) {
276 s.fLeft = srcX[x];
277 s.fRight = srcX[x+1];
278 d.fLeft = dstX[x];
279 d.fRight = dstX[x+1];
280 canvas->drawBitmapRect(bitmap, &s, d, paint);
281 }
282 }
283}
284
285void SkNinePatch::DrawNine(SkCanvas* canvas, const SkRect& bounds,
286 const SkBitmap& bitmap, const SkIRect& margins,
287 const SkPaint* paint) {
288 /** Our vertices code has numerical precision problems if the transformed
289 coordinates land directly on a 1/2 pixel boundary. To work around that
290 for now, we only take the vertices case if we are in opengl. Also,
291 when not in GL, the vertices impl is slower (more math) than calling
292 the viaRects code.
293 */
reed@google.com3048d4f2011-05-04 13:50:34 +0000294 if (false /* is our canvas backed by a gpu?*/) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 int32_t xDivs[2];
296 int32_t yDivs[2];
297
298 xDivs[0] = margins.fLeft;
299 xDivs[1] = bitmap.width() - margins.fRight;
300 yDivs[0] = margins.fTop;
301 yDivs[1] = bitmap.height() - margins.fBottom;
djsollen@google.com57f49692011-02-23 20:46:31 +0000302
303 if (xDivs[0] > xDivs[1]) {
304 xDivs[0] = bitmap.width() * margins.fLeft /
305 (margins.fLeft + margins.fRight);
306 xDivs[1] = xDivs[0];
307 }
308 if (yDivs[0] > yDivs[1]) {
309 yDivs[0] = bitmap.height() * margins.fTop /
310 (margins.fTop + margins.fBottom);
311 yDivs[1] = yDivs[0];
312 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313
314 SkNinePatch::DrawMesh(canvas, bounds, bitmap,
315 xDivs, 2, yDivs, 2, paint);
316 } else {
317 drawNineViaRects(canvas, bounds, bitmap, margins, paint);
318 }
319}