blob: 4a5308bb07193d605500d95ff9584d7da2f77d2f [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,
rmistry@google.comd6176b02012-08-23 18:14:13 +000018
reed@android.com8a1c16f2008-12-17 15:59:43 +000019 4, 9, 5, 4, 8, 9,
20 5, 10, 6, 5, 9, 10,
21 6, 11, 7, 6, 10, 11,
rmistry@google.comd6176b02012-08-23 18:14:13 +000022
reed@android.com8a1c16f2008-12-17 15:59:43 +000023 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;
rmistry@google.comd6176b02012-08-23 18:14:13 +000030
reed@android.com8a1c16f2008-12-17 15:59:43 +000031 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;
rmistry@google.comd6176b02012-08-23 18:14:13 +000037
reed@android.com8a1c16f2008-12-17 15:59:43 +000038 *indices++ = n;
39 *indices++ = n + xCount + 1;
40 *indices++ = n + xCount + 2;
rmistry@google.comd6176b02012-08-23 18:14:13 +000041
reed@android.com8a1c16f2008-12-17 15:59:43 +000042 n += 1;
43 }
44 n += 1;
45 }
robertphillips@google.com8b169312013-10-15 17:47:36 +000046 return static_cast<int>(indices - startIndices);
reed@android.com8a1c16f2008-12-17 15:59:43 +000047}
48
djsollen@google.com60abb072012-02-15 18:49:15 +000049// Computes the delta between vertices along a single axis
50static 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
reed@android.com8a1c16f2008-12-17 15:59:43 +000074static 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++;
djsollen@google.com60abb072012-02-15 18:49:15 +000081
82 SkScalar prev = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 for (int x = 0; x < numXDivs; x++) {
djsollen@google.com60abb072012-02-15 18:49:15 +000084
85 const SkScalar tx = SkIntToScalar(xDivs[x]);
86 vx += computeVertexDelta(x & 1, tx, prev, stretchX);
87 prev = tx;
88
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 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
96struct Mesh {
97 const SkPoint* fVerts;
98 const SkPoint* fTexs;
99 const SkColor* fColors;
100 const uint16_t* fIndices;
101};
102
103void 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 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000111
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 // 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 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000118
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 // 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 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000132
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 Mesh mesh;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000134
reed@android.com8a1c16f2008-12-17 15:59:43 +0000135 const int numXStretch = (numXDivs + 1) >> 1;
136 const int numYStretch = (numYDivs + 1) >> 1;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000137
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138 if (numXStretch < 1 && numYStretch < 1) {
reed84984ef2015-07-17 07:09:43 -0700139 canvas->drawBitmapRect(bitmap, bounds, paint);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 return;
141 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000142
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143 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 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000152
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 SkScalar stretchX = 0, stretchY = 0;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000154
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 if (numXStretch > 0) {
156 int stretchSize = 0;
157 for (int i = 1; i < numXDivs; i += 2) {
158 stretchSize += xDivs[i] - xDivs[i-1];
159 }
djsollen@google.com60abb072012-02-15 18:49:15 +0000160 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
reed80ea19c2015-05-12 10:37:34 -0700164 stretchX = -bounds.width() / fixed;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000166
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167 if (numYStretch > 0) {
168 int stretchSize = 0;
169 for (int i = 1; i < numYDivs; i += 2) {
170 stretchSize += yDivs[i] - yDivs[i-1];
171 }
djsollen@google.com60abb072012-02-15 18:49:15 +0000172 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
reed80ea19c2015-05-12 10:37:34 -0700176 stretchY = -bounds.height() / fixed;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000177 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000178
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179#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);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000196
reed@android.com8a1c16f2008-12-17 15:59:43 +0000197 mesh.fVerts = verts;
198 mesh.fTexs = texs;
199 mesh.fColors = NULL;
200 mesh.fIndices = NULL;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000201
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 // 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 {
reed@android.com9781ca52009-04-14 14:28:22 +0000206 SkDEBUGCODE(int n =) fillIndices(indices, numXDivs + 1, numYDivs + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207 SkASSERT(n == indexCount);
208 mesh.fIndices = indices;
209 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000210
reed@android.com8a1c16f2008-12-17 15:59:43 +0000211 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]);
reed@google.comade907b2011-09-28 12:33:41 +0000218 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 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000230 }
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());
rmistry@google.comd6176b02012-08-23 18:14:13 +0000238
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239 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
254static 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 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000263 SkScalar dstX[4] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000264 dst.fLeft, dst.fLeft + SkIntToScalar(margins.fLeft),
265 dst.fRight - SkIntToScalar(margins.fRight), dst.fRight
266 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000267 SkScalar dstY[4] = {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 dst.fTop, dst.fTop + SkIntToScalar(margins.fTop),
269 dst.fBottom - SkIntToScalar(margins.fBottom), dst.fBottom
270 };
djsollen@google.com57f49692011-02-23 20:46:31 +0000271
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
reed@android.com8a1c16f2008-12-17 15:59:43 +0000284 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];
reed84984ef2015-07-17 07:09:43 -0700296 canvas->drawBitmapRect(bitmap, s, d, paint);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 }
298 }
299}
300
301void 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 */
reed@google.com3048d4f2011-05-04 13:50:34 +0000310 if (false /* is our canvas backed by a gpu?*/) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 int32_t xDivs[2];
312 int32_t yDivs[2];
rmistry@google.comd6176b02012-08-23 18:14:13 +0000313
reed@android.com8a1c16f2008-12-17 15:59:43 +0000314 xDivs[0] = margins.fLeft;
315 xDivs[1] = bitmap.width() - margins.fRight;
316 yDivs[0] = margins.fTop;
317 yDivs[1] = bitmap.height() - margins.fBottom;
djsollen@google.com57f49692011-02-23 20:46:31 +0000318
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 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000329
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 SkNinePatch::DrawMesh(canvas, bounds, bitmap,
331 xDivs, 2, yDivs, 2, paint);
332 } else {
333 drawNineViaRects(canvas, bounds, bitmap, margins, paint);
334 }
335}