blob: bbda57329441e336a501fdba1a15fa2ee4cecdf0 [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/*
2** Copyright 2006, The Android Open Source Project
3**
4** Licensed under the Apache License, Version 2.0 (the "License");
5** you may not use this file except in compliance with the License.
6** You may obtain a copy of the License at
7**
8** http://www.apache.org/licenses/LICENSE-2.0
9**
10** Unless required by applicable law or agreed to in writing, software
11** distributed under the License is distributed on an "AS IS" BASIS,
12** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13** See the License for the specific language governing permissions and
14** limitations under the License.
15*/
16
17#include "SkNinePatch.h"
18#include "SkCanvas.h"
19#include "SkShader.h"
20
21static const uint16_t g3x3Indices[] = {
22 0, 5, 1, 0, 4, 5,
23 1, 6, 2, 1, 5, 6,
24 2, 7, 3, 2, 6, 7,
25
26 4, 9, 5, 4, 8, 9,
27 5, 10, 6, 5, 9, 10,
28 6, 11, 7, 6, 10, 11,
29
30 8, 13, 9, 8, 12, 13,
31 9, 14, 10, 9, 13, 14,
32 10, 15, 11, 10, 14, 15
33};
34
35static int fillIndices(uint16_t indices[], int xCount, int yCount) {
36 uint16_t* startIndices = indices;
37
38 int n = 0;
39 for (int y = 0; y < yCount; y++) {
40 for (int x = 0; x < xCount; x++) {
41 *indices++ = n;
42 *indices++ = n + xCount + 2;
43 *indices++ = n + 1;
44
45 *indices++ = n;
46 *indices++ = n + xCount + 1;
47 *indices++ = n + xCount + 2;
48
49 n += 1;
50 }
51 n += 1;
52 }
53 return indices - startIndices;
54}
55
56static void fillRow(SkPoint verts[], SkPoint texs[],
57 const SkScalar vy, const SkScalar ty,
58 const SkRect& bounds, const int32_t xDivs[], int numXDivs,
59 const SkScalar stretchX, int width) {
60 SkScalar vx = bounds.fLeft;
61 verts->set(vx, vy); verts++;
62 texs->set(0, ty); texs++;
63 for (int x = 0; x < numXDivs; x++) {
64 SkScalar tx = SkIntToScalar(xDivs[x]);
65 if (x & 1) {
66 vx += stretchX;
67 } else {
68 vx += tx;
69 }
70 verts->set(vx, vy); verts++;
71 texs->set(tx, ty); texs++;
72 }
73 verts->set(bounds.fRight, vy); verts++;
74 texs->set(SkIntToScalar(width), ty); texs++;
75}
76
77struct Mesh {
78 const SkPoint* fVerts;
79 const SkPoint* fTexs;
80 const SkColor* fColors;
81 const uint16_t* fIndices;
82};
83
84void SkNinePatch::DrawMesh(SkCanvas* canvas, const SkRect& bounds,
85 const SkBitmap& bitmap,
86 const int32_t xDivs[], int numXDivs,
87 const int32_t yDivs[], int numYDivs,
88 const SkPaint* paint) {
89 if (bounds.isEmpty() || bitmap.width() == 0 || bitmap.height() == 0) {
90 return;
91 }
92
93 // should try a quick-reject test before calling lockPixels
94 SkAutoLockPixels alp(bitmap);
95 // after the lock, it is valid to check
96 if (!bitmap.readyToDraw()) {
97 return;
98 }
99
100 // check for degenerate divs (just an optimization, not required)
101 {
102 int i;
103 int zeros = 0;
104 for (i = 0; i < numYDivs && yDivs[i] == 0; i++) {
105 zeros += 1;
106 }
107 numYDivs -= zeros;
108 yDivs += zeros;
109 for (i = numYDivs - 1; i >= 0 && yDivs[i] == bitmap.height(); --i) {
110 numYDivs -= 1;
111 }
112 }
113
114 Mesh mesh;
115
116 const int numXStretch = (numXDivs + 1) >> 1;
117 const int numYStretch = (numYDivs + 1) >> 1;
118
119 if (numXStretch < 1 && numYStretch < 1) {
120 BITMAP_RECT:
121// SkDebugf("------ drawasamesh revert to bitmaprect\n");
122 canvas->drawBitmapRect(bitmap, NULL, bounds, paint);
123 return;
124 }
125
126 if (false) {
127 int i;
128 for (i = 0; i < numXDivs; i++) {
129 SkDebugf("--- xdivs[%d] %d\n", i, xDivs[i]);
130 }
131 for (i = 0; i < numYDivs; i++) {
132 SkDebugf("--- ydivs[%d] %d\n", i, yDivs[i]);
133 }
134 }
135
136 SkScalar stretchX = 0, stretchY = 0;
137
138 if (numXStretch > 0) {
139 int stretchSize = 0;
140 for (int i = 1; i < numXDivs; i += 2) {
141 stretchSize += xDivs[i] - xDivs[i-1];
142 }
143 int fixed = bitmap.width() - stretchSize;
144 stretchX = (bounds.width() - SkIntToScalar(fixed)) / numXStretch;
145 if (stretchX < 0) {
146 goto BITMAP_RECT;
147 }
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) {
158 goto BITMAP_RECT;
159 }
160 }
161
162#if 0
163 SkDebugf("---- drawasamesh [%d %d] -> [%g %g] <%d %d> (%g %g)\n",
164 bitmap.width(), bitmap.height(),
165 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
166 numXDivs + 1, numYDivs + 1,
167 SkScalarToFloat(stretchX), SkScalarToFloat(stretchY));
168#endif
169
170 const int vCount = (numXDivs + 2) * (numYDivs + 2);
171 // number of celss * 2 (tris per cell) * 3 (verts per tri)
172 const int indexCount = (numXDivs + 1) * (numYDivs + 1) * 2 * 3;
173 // allocate 2 times, one for verts, one for texs, plus indices
174 SkAutoMalloc storage(vCount * sizeof(SkPoint) * 2 +
175 indexCount * sizeof(uint16_t));
176 SkPoint* verts = (SkPoint*)storage.get();
177 SkPoint* texs = verts + vCount;
178 uint16_t* indices = (uint16_t*)(texs + vCount);
179
180 mesh.fVerts = verts;
181 mesh.fTexs = texs;
182 mesh.fColors = NULL;
183 mesh.fIndices = NULL;
184
185 // we use <= for YDivs, since the prebuild indices work for 3x2 and 3x1 too
186 if (numXDivs == 2 && numYDivs <= 2) {
187 mesh.fIndices = g3x3Indices;
188 } else {
reed@android.com9781ca52009-04-14 14:28:22 +0000189 SkDEBUGCODE(int n =) fillIndices(indices, numXDivs + 1, numYDivs + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000190 SkASSERT(n == indexCount);
191 mesh.fIndices = indices;
192 }
193
194 SkScalar vy = bounds.fTop;
195 fillRow(verts, texs, vy, 0, bounds, xDivs, numXDivs,
196 stretchX, bitmap.width());
197 verts += numXDivs + 2;
198 texs += numXDivs + 2;
199 for (int y = 0; y < numYDivs; y++) {
200 const SkScalar ty = SkIntToScalar(yDivs[y]);
201 if (y & 1) {
202 vy += stretchY;
203 } else {
204 vy += ty;
205 }
206 fillRow(verts, texs, vy, ty, bounds, xDivs, numXDivs,
207 stretchX, bitmap.width());
208 verts += numXDivs + 2;
209 texs += numXDivs + 2;
210 }
211 fillRow(verts, texs, bounds.fBottom, SkIntToScalar(bitmap.height()),
212 bounds, xDivs, numXDivs, stretchX, bitmap.width());
213
214 SkShader* shader = SkShader::CreateBitmapShader(bitmap,
215 SkShader::kClamp_TileMode,
216 SkShader::kClamp_TileMode);
217 SkPaint p;
218 if (paint) {
219 p = *paint;
220 }
221 p.setShader(shader)->unref();
222 canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vCount,
223 mesh.fVerts, mesh.fTexs, mesh.fColors, NULL,
224 mesh.fIndices, indexCount, p);
225}
226
227///////////////////////////////////////////////////////////////////////////////
228
229static void drawNineViaRects(SkCanvas* canvas, const SkRect& dst,
230 const SkBitmap& bitmap, const SkIRect& margins,
231 const SkPaint* paint) {
232 const int32_t srcX[4] = {
233 0, margins.fLeft, bitmap.width() - margins.fRight, bitmap.width()
234 };
235 const int32_t srcY[4] = {
236 0, margins.fTop, bitmap.height() - margins.fBottom, bitmap.height()
237 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000238 SkScalar dstX[4] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239 dst.fLeft, dst.fLeft + SkIntToScalar(margins.fLeft),
240 dst.fRight - SkIntToScalar(margins.fRight), dst.fRight
241 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000242 SkScalar dstY[4] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 dst.fTop, dst.fTop + SkIntToScalar(margins.fTop),
244 dst.fBottom - SkIntToScalar(margins.fBottom), dst.fBottom
245 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000246
247 if (dstX[1] > dstX[2]) {
248 dstX[1] = dstX[0] + (dstX[3] - dstX[0]) * SkIntToScalar(margins.fLeft) /
249 (SkIntToScalar(margins.fLeft) + SkIntToScalar(margins.fRight));
250 dstX[2] = dstX[1];
251 }
252
253 if (dstY[1] > dstY[2]) {
254 dstY[1] = dstY[0] + (dstY[3] - dstY[0]) * SkIntToScalar(margins.fTop) /
255 (SkIntToScalar(margins.fTop) + SkIntToScalar(margins.fBottom));
256 dstY[2] = dstY[1];
257 }
258
reed@android.com8a1c16f2008-12-17 15:59:43 +0000259 SkIRect s;
260 SkRect d;
261 for (int y = 0; y < 3; y++) {
262 s.fTop = srcY[y];
263 s.fBottom = srcY[y+1];
264 d.fTop = dstY[y];
265 d.fBottom = dstY[y+1];
266 for (int x = 0; x < 3; x++) {
267 s.fLeft = srcX[x];
268 s.fRight = srcX[x+1];
269 d.fLeft = dstX[x];
270 d.fRight = dstX[x+1];
271 canvas->drawBitmapRect(bitmap, &s, d, paint);
272 }
273 }
274}
275
276void SkNinePatch::DrawNine(SkCanvas* canvas, const SkRect& bounds,
277 const SkBitmap& bitmap, const SkIRect& margins,
278 const SkPaint* paint) {
279 /** Our vertices code has numerical precision problems if the transformed
280 coordinates land directly on a 1/2 pixel boundary. To work around that
281 for now, we only take the vertices case if we are in opengl. Also,
282 when not in GL, the vertices impl is slower (more math) than calling
283 the viaRects code.
284 */
reed@google.com3048d4f2011-05-04 13:50:34 +0000285 if (false /* is our canvas backed by a gpu?*/) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 int32_t xDivs[2];
287 int32_t yDivs[2];
288
289 xDivs[0] = margins.fLeft;
290 xDivs[1] = bitmap.width() - margins.fRight;
291 yDivs[0] = margins.fTop;
292 yDivs[1] = bitmap.height() - margins.fBottom;
djsollen@google.com57f49692011-02-23 20:46:31 +0000293
294 if (xDivs[0] > xDivs[1]) {
295 xDivs[0] = bitmap.width() * margins.fLeft /
296 (margins.fLeft + margins.fRight);
297 xDivs[1] = xDivs[0];
298 }
299 if (yDivs[0] > yDivs[1]) {
300 yDivs[0] = bitmap.height() * margins.fTop /
301 (margins.fTop + margins.fBottom);
302 yDivs[1] = yDivs[0];
303 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304
305 SkNinePatch::DrawMesh(canvas, bounds, bitmap,
306 xDivs, 2, yDivs, 2, paint);
307 } else {
308 drawNineViaRects(canvas, bounds, bitmap, margins, paint);
309 }
310}