blob: 16e815d4c861883d030a6ffabd587343975c79fa [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]);
58 if (x & 1) {
59 vx += stretchX;
60 } else {
61 vx += tx;
62 }
63 verts->set(vx, vy); verts++;
64 texs->set(tx, ty); texs++;
65 }
66 verts->set(bounds.fRight, vy); verts++;
67 texs->set(SkIntToScalar(width), ty); texs++;
68}
69
70struct Mesh {
71 const SkPoint* fVerts;
72 const SkPoint* fTexs;
73 const SkColor* fColors;
74 const uint16_t* fIndices;
75};
76
77void SkNinePatch::DrawMesh(SkCanvas* canvas, const SkRect& bounds,
78 const SkBitmap& bitmap,
79 const int32_t xDivs[], int numXDivs,
80 const int32_t yDivs[], int numYDivs,
81 const SkPaint* paint) {
82 if (bounds.isEmpty() || bitmap.width() == 0 || bitmap.height() == 0) {
83 return;
84 }
85
86 // should try a quick-reject test before calling lockPixels
87 SkAutoLockPixels alp(bitmap);
88 // after the lock, it is valid to check
89 if (!bitmap.readyToDraw()) {
90 return;
91 }
92
93 // check for degenerate divs (just an optimization, not required)
94 {
95 int i;
96 int zeros = 0;
97 for (i = 0; i < numYDivs && yDivs[i] == 0; i++) {
98 zeros += 1;
99 }
100 numYDivs -= zeros;
101 yDivs += zeros;
102 for (i = numYDivs - 1; i >= 0 && yDivs[i] == bitmap.height(); --i) {
103 numYDivs -= 1;
104 }
105 }
106
107 Mesh mesh;
108
109 const int numXStretch = (numXDivs + 1) >> 1;
110 const int numYStretch = (numYDivs + 1) >> 1;
111
112 if (numXStretch < 1 && numYStretch < 1) {
113 BITMAP_RECT:
114// SkDebugf("------ drawasamesh revert to bitmaprect\n");
115 canvas->drawBitmapRect(bitmap, NULL, bounds, paint);
116 return;
117 }
118
119 if (false) {
120 int i;
121 for (i = 0; i < numXDivs; i++) {
122 SkDebugf("--- xdivs[%d] %d\n", i, xDivs[i]);
123 }
124 for (i = 0; i < numYDivs; i++) {
125 SkDebugf("--- ydivs[%d] %d\n", i, yDivs[i]);
126 }
127 }
128
129 SkScalar stretchX = 0, stretchY = 0;
130
131 if (numXStretch > 0) {
132 int stretchSize = 0;
133 for (int i = 1; i < numXDivs; i += 2) {
134 stretchSize += xDivs[i] - xDivs[i-1];
135 }
136 int fixed = bitmap.width() - stretchSize;
137 stretchX = (bounds.width() - SkIntToScalar(fixed)) / numXStretch;
138 if (stretchX < 0) {
139 goto BITMAP_RECT;
140 }
141 }
142
143 if (numYStretch > 0) {
144 int stretchSize = 0;
145 for (int i = 1; i < numYDivs; i += 2) {
146 stretchSize += yDivs[i] - yDivs[i-1];
147 }
148 int fixed = bitmap.height() - stretchSize;
149 stretchY = (bounds.height() - SkIntToScalar(fixed)) / numYStretch;
150 if (stretchY < 0) {
151 goto BITMAP_RECT;
152 }
153 }
154
155#if 0
156 SkDebugf("---- drawasamesh [%d %d] -> [%g %g] <%d %d> (%g %g)\n",
157 bitmap.width(), bitmap.height(),
158 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
159 numXDivs + 1, numYDivs + 1,
160 SkScalarToFloat(stretchX), SkScalarToFloat(stretchY));
161#endif
162
163 const int vCount = (numXDivs + 2) * (numYDivs + 2);
164 // number of celss * 2 (tris per cell) * 3 (verts per tri)
165 const int indexCount = (numXDivs + 1) * (numYDivs + 1) * 2 * 3;
166 // allocate 2 times, one for verts, one for texs, plus indices
167 SkAutoMalloc storage(vCount * sizeof(SkPoint) * 2 +
168 indexCount * sizeof(uint16_t));
169 SkPoint* verts = (SkPoint*)storage.get();
170 SkPoint* texs = verts + vCount;
171 uint16_t* indices = (uint16_t*)(texs + vCount);
172
173 mesh.fVerts = verts;
174 mesh.fTexs = texs;
175 mesh.fColors = NULL;
176 mesh.fIndices = NULL;
177
178 // we use <= for YDivs, since the prebuild indices work for 3x2 and 3x1 too
179 if (numXDivs == 2 && numYDivs <= 2) {
180 mesh.fIndices = g3x3Indices;
181 } else {
reed@android.com9781ca52009-04-14 14:28:22 +0000182 SkDEBUGCODE(int n =) fillIndices(indices, numXDivs + 1, numYDivs + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183 SkASSERT(n == indexCount);
184 mesh.fIndices = indices;
185 }
186
187 SkScalar vy = bounds.fTop;
188 fillRow(verts, texs, vy, 0, bounds, xDivs, numXDivs,
189 stretchX, bitmap.width());
190 verts += numXDivs + 2;
191 texs += numXDivs + 2;
192 for (int y = 0; y < numYDivs; y++) {
193 const SkScalar ty = SkIntToScalar(yDivs[y]);
194 if (y & 1) {
195 vy += stretchY;
196 } else {
197 vy += ty;
198 }
199 fillRow(verts, texs, vy, ty, bounds, xDivs, numXDivs,
200 stretchX, bitmap.width());
201 verts += numXDivs + 2;
202 texs += numXDivs + 2;
203 }
204 fillRow(verts, texs, bounds.fBottom, SkIntToScalar(bitmap.height()),
205 bounds, xDivs, numXDivs, stretchX, bitmap.width());
206
207 SkShader* shader = SkShader::CreateBitmapShader(bitmap,
208 SkShader::kClamp_TileMode,
209 SkShader::kClamp_TileMode);
210 SkPaint p;
211 if (paint) {
212 p = *paint;
213 }
214 p.setShader(shader)->unref();
215 canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vCount,
216 mesh.fVerts, mesh.fTexs, mesh.fColors, NULL,
217 mesh.fIndices, indexCount, p);
218}
219
220///////////////////////////////////////////////////////////////////////////////
221
222static void drawNineViaRects(SkCanvas* canvas, const SkRect& dst,
223 const SkBitmap& bitmap, const SkIRect& margins,
224 const SkPaint* paint) {
225 const int32_t srcX[4] = {
226 0, margins.fLeft, bitmap.width() - margins.fRight, bitmap.width()
227 };
228 const int32_t srcY[4] = {
229 0, margins.fTop, bitmap.height() - margins.fBottom, bitmap.height()
230 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000231 SkScalar dstX[4] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232 dst.fLeft, dst.fLeft + SkIntToScalar(margins.fLeft),
233 dst.fRight - SkIntToScalar(margins.fRight), dst.fRight
234 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000235 SkScalar dstY[4] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 dst.fTop, dst.fTop + SkIntToScalar(margins.fTop),
237 dst.fBottom - SkIntToScalar(margins.fBottom), dst.fBottom
238 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000239
240 if (dstX[1] > dstX[2]) {
241 dstX[1] = dstX[0] + (dstX[3] - dstX[0]) * SkIntToScalar(margins.fLeft) /
242 (SkIntToScalar(margins.fLeft) + SkIntToScalar(margins.fRight));
243 dstX[2] = dstX[1];
244 }
245
246 if (dstY[1] > dstY[2]) {
247 dstY[1] = dstY[0] + (dstY[3] - dstY[0]) * SkIntToScalar(margins.fTop) /
248 (SkIntToScalar(margins.fTop) + SkIntToScalar(margins.fBottom));
249 dstY[2] = dstY[1];
250 }
251
reed@android.com8a1c16f2008-12-17 15:59:43 +0000252 SkIRect s;
253 SkRect d;
254 for (int y = 0; y < 3; y++) {
255 s.fTop = srcY[y];
256 s.fBottom = srcY[y+1];
257 d.fTop = dstY[y];
258 d.fBottom = dstY[y+1];
259 for (int x = 0; x < 3; x++) {
260 s.fLeft = srcX[x];
261 s.fRight = srcX[x+1];
262 d.fLeft = dstX[x];
263 d.fRight = dstX[x+1];
264 canvas->drawBitmapRect(bitmap, &s, d, paint);
265 }
266 }
267}
268
269void SkNinePatch::DrawNine(SkCanvas* canvas, const SkRect& bounds,
270 const SkBitmap& bitmap, const SkIRect& margins,
271 const SkPaint* paint) {
272 /** Our vertices code has numerical precision problems if the transformed
273 coordinates land directly on a 1/2 pixel boundary. To work around that
274 for now, we only take the vertices case if we are in opengl. Also,
275 when not in GL, the vertices impl is slower (more math) than calling
276 the viaRects code.
277 */
reed@google.com3048d4f2011-05-04 13:50:34 +0000278 if (false /* is our canvas backed by a gpu?*/) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 int32_t xDivs[2];
280 int32_t yDivs[2];
281
282 xDivs[0] = margins.fLeft;
283 xDivs[1] = bitmap.width() - margins.fRight;
284 yDivs[0] = margins.fTop;
285 yDivs[1] = bitmap.height() - margins.fBottom;
djsollen@google.com57f49692011-02-23 20:46:31 +0000286
287 if (xDivs[0] > xDivs[1]) {
288 xDivs[0] = bitmap.width() * margins.fLeft /
289 (margins.fLeft + margins.fRight);
290 xDivs[1] = xDivs[0];
291 }
292 if (yDivs[0] > yDivs[1]) {
293 yDivs[0] = bitmap.height() * margins.fTop /
294 (margins.fTop + margins.fBottom);
295 yDivs[1] = yDivs[0];
296 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297
298 SkNinePatch::DrawMesh(canvas, bounds, bitmap,
299 xDivs, 2, yDivs, 2, paint);
300 } else {
301 drawNineViaRects(canvas, bounds, bitmap, margins, paint);
302 }
303}