blob: 6d2339c276f9c518b3f4a19b9db00e7911e58dba [file] [log] [blame]
caryclark@google.com07393ca2013-04-08 11:47:37 +00001/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7#include "SkReduceOrder.h"
8
9int SkReduceOrder::reduce(const SkDLine& line) {
10 fLine[0] = line[0];
11 int different = line[0] != line[1];
12 fLine[1] = line[different];
13 return 1 + different;
14}
15
16static double interp_quad_coords(double a, double b, double c, double t) {
17 double ab = SkDInterp(a, b, t);
18 double bc = SkDInterp(b, c, t);
19 return SkDInterp(ab, bc, t);
20}
21
22static int coincident_line(const SkDQuad& quad, SkDQuad& reduction) {
23 reduction[0] = reduction[1] = quad[0];
24 return 1;
25}
26
27static int reductionLineCount(const SkDQuad& reduction) {
28 return 1 + !reduction[0].approximatelyEqual(reduction[1]);
29}
30
31static int vertical_line(const SkDQuad& quad, SkReduceOrder::Style reduceStyle,
32 SkDQuad& reduction) {
33 double tValue;
34 reduction[0] = quad[0];
35 reduction[1] = quad[2];
36 if (reduceStyle == SkReduceOrder::kFill_Style) {
37 return reductionLineCount(reduction);
38 }
39 int smaller = reduction[1].fY > reduction[0].fY;
40 int larger = smaller ^ 1;
41 if (SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValue)) {
42 double yExtrema = interp_quad_coords(quad[0].fY, quad[1].fY, quad[2].fY, tValue);
43 if (reduction[smaller].fY > yExtrema) {
44 reduction[smaller].fY = yExtrema;
45 } else if (reduction[larger].fY < yExtrema) {
46 reduction[larger].fY = yExtrema;
47 }
48 }
49 return reductionLineCount(reduction);
50}
51
52static int horizontal_line(const SkDQuad& quad, SkReduceOrder::Style reduceStyle,
53 SkDQuad& reduction) {
54 double tValue;
55 reduction[0] = quad[0];
56 reduction[1] = quad[2];
57 if (reduceStyle == SkReduceOrder::kFill_Style) {
58 return reductionLineCount(reduction);
59 }
60 int smaller = reduction[1].fX > reduction[0].fX;
61 int larger = smaller ^ 1;
62 if (SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, &tValue)) {
63 double xExtrema = interp_quad_coords(quad[0].fX, quad[1].fX, quad[2].fX, tValue);
64 if (reduction[smaller].fX > xExtrema) {
65 reduction[smaller].fX = xExtrema;
66 } else if (reduction[larger].fX < xExtrema) {
67 reduction[larger].fX = xExtrema;
68 }
69 }
70 return reductionLineCount(reduction);
71}
72
73static int check_linear(const SkDQuad& quad, SkReduceOrder::Style reduceStyle,
74 int minX, int maxX, int minY, int maxY, SkDQuad& reduction) {
75 int startIndex = 0;
76 int endIndex = 2;
77 while (quad[startIndex].approximatelyEqual(quad[endIndex])) {
78 --endIndex;
79 if (endIndex == 0) {
80 SkDebugf("%s shouldn't get here if all four points are about equal", __FUNCTION__);
81 SkASSERT(0);
82 }
83 }
84 if (!quad.isLinear(startIndex, endIndex)) {
85 return 0;
86 }
87 // four are colinear: return line formed by outside
88 reduction[0] = quad[0];
89 reduction[1] = quad[2];
90 if (reduceStyle == SkReduceOrder::kFill_Style) {
91 return reductionLineCount(reduction);
92 }
93 int sameSide;
94 bool useX = quad[maxX].fX - quad[minX].fX >= quad[maxY].fY - quad[minY].fY;
95 if (useX) {
96 sameSide = SkDSign(quad[0].fX - quad[1].fX) + SkDSign(quad[2].fX - quad[1].fX);
97 } else {
98 sameSide = SkDSign(quad[0].fY - quad[1].fY) + SkDSign(quad[2].fY - quad[1].fY);
99 }
100 if ((sameSide & 3) != 2) {
101 return reductionLineCount(reduction);
102 }
103 double tValue;
104 int root;
105 if (useX) {
106 root = SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, &tValue);
107 } else {
108 root = SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValue);
109 }
110 if (root) {
111 SkDPoint extrema;
112 extrema.fX = interp_quad_coords(quad[0].fX, quad[1].fX, quad[2].fX, tValue);
113 extrema.fY = interp_quad_coords(quad[0].fY, quad[1].fY, quad[2].fY, tValue);
114 // sameSide > 0 means mid is smaller than either [0] or [2], so replace smaller
115 int replace;
116 if (useX) {
117 if ((extrema.fX < quad[0].fX) ^ (extrema.fX < quad[2].fX)) {
118 return reductionLineCount(reduction);
119 }
120 replace = ((extrema.fX < quad[0].fX) | (extrema.fX < quad[2].fX))
121 ^ (quad[0].fX < quad[2].fX);
122 } else {
123 if ((extrema.fY < quad[0].fY) ^ (extrema.fY < quad[2].fY)) {
124 return reductionLineCount(reduction);
125 }
126 replace = ((extrema.fY < quad[0].fY) | (extrema.fY < quad[2].fY))
127 ^ (quad[0].fY < quad[2].fY);
128 }
129 reduction[replace] = extrema;
130 }
131 return reductionLineCount(reduction);
132}
133
134// reduce to a quadratic or smaller
135// look for identical points
136// look for all four points in a line
137 // note that three points in a line doesn't simplify a cubic
138// look for approximation with single quadratic
139 // save approximation with multiple quadratics for later
140int SkReduceOrder::reduce(const SkDQuad& quad, Style reduceStyle) {
141 int index, minX, maxX, minY, maxY;
142 int minXSet, minYSet;
143 minX = maxX = minY = maxY = 0;
144 minXSet = minYSet = 0;
145 for (index = 1; index < 3; ++index) {
146 if (quad[minX].fX > quad[index].fX) {
147 minX = index;
148 }
149 if (quad[minY].fY > quad[index].fY) {
150 minY = index;
151 }
152 if (quad[maxX].fX < quad[index].fX) {
153 maxX = index;
154 }
155 if (quad[maxY].fY < quad[index].fY) {
156 maxY = index;
157 }
158 }
159 for (index = 0; index < 3; ++index) {
160 if (AlmostEqualUlps(quad[index].fX, quad[minX].fX)) {
161 minXSet |= 1 << index;
162 }
163 if (AlmostEqualUlps(quad[index].fY, quad[minY].fY)) {
164 minYSet |= 1 << index;
165 }
166 }
167 if (minXSet == 0x7) { // test for vertical line
168 if (minYSet == 0x7) { // return 1 if all four are coincident
169 return coincident_line(quad, fQuad);
170 }
171 return vertical_line(quad, reduceStyle, fQuad);
172 }
173 if (minYSet == 0xF) { // test for horizontal line
174 return horizontal_line(quad, reduceStyle, fQuad);
175 }
176 int result = check_linear(quad, reduceStyle, minX, maxX, minY, maxY, fQuad);
177 if (result) {
178 return result;
179 }
180 fQuad = quad;
181 return 3;
182}
183
184////////////////////////////////////////////////////////////////////////////////////
185
186static double interp_cubic_coords(const double* src, double t) {
187 double ab = SkDInterp(src[0], src[2], t);
188 double bc = SkDInterp(src[2], src[4], t);
189 double cd = SkDInterp(src[4], src[6], t);
190 double abc = SkDInterp(ab, bc, t);
191 double bcd = SkDInterp(bc, cd, t);
192 return SkDInterp(abc, bcd, t);
193}
194
195static int coincident_line(const SkDCubic& cubic, SkDCubic& reduction) {
196 reduction[0] = reduction[1] = cubic[0];
197 return 1;
198}
199
200static int reductionLineCount(const SkDCubic& reduction) {
201 return 1 + !reduction[0].approximatelyEqual(reduction[1]);
202}
203
204static int vertical_line(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle,
205 SkDCubic& reduction) {
206 double tValues[2];
207 reduction[0] = cubic[0];
208 reduction[1] = cubic[3];
209 if (reduceStyle == SkReduceOrder::kFill_Style) {
210 return reductionLineCount(reduction);
211 }
212 int smaller = reduction[1].fY > reduction[0].fY;
213 int larger = smaller ^ 1;
214 int roots = SkDCubic::FindExtrema(cubic[0].fY, cubic[1].fY, cubic[2].fY, cubic[3].fY, tValues);
215 for (int index = 0; index < roots; ++index) {
216 double yExtrema = interp_cubic_coords(&cubic[0].fY, tValues[index]);
217 if (reduction[smaller].fY > yExtrema) {
218 reduction[smaller].fY = yExtrema;
219 continue;
220 }
221 if (reduction[larger].fY < yExtrema) {
222 reduction[larger].fY = yExtrema;
223 }
224 }
225 return reductionLineCount(reduction);
226}
227
228static int horizontal_line(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle,
229 SkDCubic& reduction) {
230 double tValues[2];
231 reduction[0] = cubic[0];
232 reduction[1] = cubic[3];
233 if (reduceStyle == SkReduceOrder::kFill_Style) {
234 return reductionLineCount(reduction);
235 }
236 int smaller = reduction[1].fX > reduction[0].fX;
237 int larger = smaller ^ 1;
238 int roots = SkDCubic::FindExtrema(cubic[0].fX, cubic[1].fX, cubic[2].fX, cubic[3].fX, tValues);
239 for (int index = 0; index < roots; ++index) {
240 double xExtrema = interp_cubic_coords(&cubic[0].fX, tValues[index]);
241 if (reduction[smaller].fX > xExtrema) {
242 reduction[smaller].fX = xExtrema;
243 continue;
244 }
245 if (reduction[larger].fX < xExtrema) {
246 reduction[larger].fX = xExtrema;
247 }
248 }
249 return reductionLineCount(reduction);
250}
251
252// check to see if it is a quadratic or a line
253static int check_quadratic(const SkDCubic& cubic, SkDCubic& reduction) {
254 double dx10 = cubic[1].fX - cubic[0].fX;
255 double dx23 = cubic[2].fX - cubic[3].fX;
256 double midX = cubic[0].fX + dx10 * 3 / 2;
caryclark@google.com7ec5e392013-04-17 16:19:02 +0000257 double sideAx = midX - cubic[3].fX;
258 double sideBx = dx23 * 3 / 2;
259 if (approximately_zero(sideAx) ? !approximately_equal(sideAx, sideBx)
260 : !AlmostEqualUlps(sideAx, sideBx)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000261 return 0;
262 }
263 double dy10 = cubic[1].fY - cubic[0].fY;
264 double dy23 = cubic[2].fY - cubic[3].fY;
265 double midY = cubic[0].fY + dy10 * 3 / 2;
caryclark@google.com7ec5e392013-04-17 16:19:02 +0000266 double sideAy = midY - cubic[3].fY;
267 double sideBy = dy23 * 3 / 2;
268 if (approximately_zero(sideAy) ? !approximately_equal(sideAy, sideBy)
269 : !AlmostEqualUlps(sideAy, sideBy)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000270 return 0;
271 }
272 reduction[0] = cubic[0];
273 reduction[1].fX = midX;
274 reduction[1].fY = midY;
275 reduction[2] = cubic[3];
276 return 3;
277}
278
279static int check_linear(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle,
280 int minX, int maxX, int minY, int maxY, SkDCubic& reduction) {
281 int startIndex = 0;
282 int endIndex = 3;
283 while (cubic[startIndex].approximatelyEqual(cubic[endIndex])) {
284 --endIndex;
285 if (endIndex == 0) {
286 SkDebugf("%s shouldn't get here if all four points are about equal\n", __FUNCTION__);
287 SkASSERT(0);
288 }
289 }
290 if (!cubic.isLinear(startIndex, endIndex)) {
291 return 0;
292 }
293 // four are colinear: return line formed by outside
294 reduction[0] = cubic[0];
295 reduction[1] = cubic[3];
296 if (reduceStyle == SkReduceOrder::kFill_Style) {
297 return reductionLineCount(reduction);
298 }
299 int sameSide1;
300 int sameSide2;
301 bool useX = cubic[maxX].fX - cubic[minX].fX >= cubic[maxY].fY - cubic[minY].fY;
302 if (useX) {
303 sameSide1 = SkDSign(cubic[0].fX - cubic[1].fX) + SkDSign(cubic[3].fX - cubic[1].fX);
304 sameSide2 = SkDSign(cubic[0].fX - cubic[2].fX) + SkDSign(cubic[3].fX - cubic[2].fX);
305 } else {
306 sameSide1 = SkDSign(cubic[0].fY - cubic[1].fY) + SkDSign(cubic[3].fY - cubic[1].fY);
307 sameSide2 = SkDSign(cubic[0].fY - cubic[2].fY) + SkDSign(cubic[3].fY - cubic[2].fY);
308 }
309 if (sameSide1 == sameSide2 && (sameSide1 & 3) != 2) {
310 return reductionLineCount(reduction);
311 }
312 double tValues[2];
313 int roots;
314 if (useX) {
315 roots = SkDCubic::FindExtrema(cubic[0].fX, cubic[1].fX, cubic[2].fX, cubic[3].fX, tValues);
316 } else {
317 roots = SkDCubic::FindExtrema(cubic[0].fY, cubic[1].fY, cubic[2].fY, cubic[3].fY, tValues);
318 }
319 for (int index = 0; index < roots; ++index) {
320 SkDPoint extrema;
321 extrema.fX = interp_cubic_coords(&cubic[0].fX, tValues[index]);
322 extrema.fY = interp_cubic_coords(&cubic[0].fY, tValues[index]);
323 // sameSide > 0 means mid is smaller than either [0] or [3], so replace smaller
324 int replace;
325 if (useX) {
326 if ((extrema.fX < cubic[0].fX) ^ (extrema.fX < cubic[3].fX)) {
327 continue;
328 }
329 replace = ((extrema.fX < cubic[0].fX) | (extrema.fX < cubic[3].fX))
330 ^ (cubic[0].fX < cubic[3].fX);
331 } else {
332 if ((extrema.fY < cubic[0].fY) ^ (extrema.fY < cubic[3].fY)) {
333 continue;
334 }
335 replace = ((extrema.fY < cubic[0].fY) | (extrema.fY < cubic[3].fY))
336 ^ (cubic[0].fY < cubic[3].fY);
337 }
338 reduction[replace] = extrema;
339 }
340 return reductionLineCount(reduction);
341}
342
343/* food for thought:
344http://objectmix.com/graphics/132906-fast-precision-driven-cubic-quadratic-piecewise-degree-reduction-algos-2-a.html
345
346Given points c1, c2, c3 and c4 of a cubic Bezier, the points of the
347corresponding quadratic Bezier are (given in convex combinations of
348points):
349
350q1 = (11/13)c1 + (3/13)c2 -(3/13)c3 + (2/13)c4
351q2 = -c1 + (3/2)c2 + (3/2)c3 - c4
352q3 = (2/13)c1 - (3/13)c2 + (3/13)c3 + (11/13)c4
353
354Of course, this curve does not interpolate the end-points, but it would
355be interesting to see the behaviour of such a curve in an applet.
356
357--
358Kalle Rutanen
359http://kaba.hilvi.org
360
361*/
362
363// reduce to a quadratic or smaller
364// look for identical points
365// look for all four points in a line
366 // note that three points in a line doesn't simplify a cubic
367// look for approximation with single quadratic
368 // save approximation with multiple quadratics for later
369int SkReduceOrder::reduce(const SkDCubic& cubic, Quadratics allowQuadratics,
370 Style reduceStyle) {
371 int index, minX, maxX, minY, maxY;
372 int minXSet, minYSet;
373 minX = maxX = minY = maxY = 0;
374 minXSet = minYSet = 0;
375 for (index = 1; index < 4; ++index) {
376 if (cubic[minX].fX > cubic[index].fX) {
377 minX = index;
378 }
379 if (cubic[minY].fY > cubic[index].fY) {
380 minY = index;
381 }
382 if (cubic[maxX].fX < cubic[index].fX) {
383 maxX = index;
384 }
385 if (cubic[maxY].fY < cubic[index].fY) {
386 maxY = index;
387 }
388 }
389 for (index = 0; index < 4; ++index) {
390 double cx = cubic[index].fX;
391 double cy = cubic[index].fY;
392 double denom = SkTMax(fabs(cx), SkTMax(fabs(cy),
393 SkTMax(fabs(cubic[minX].fX), fabs(cubic[minY].fY))));
394 if (denom == 0) {
395 minXSet |= 1 << index;
396 minYSet |= 1 << index;
397 continue;
398 }
399 double inv = 1 / denom;
400 if (approximately_equal_half(cx * inv, cubic[minX].fX * inv)) {
401 minXSet |= 1 << index;
402 }
403 if (approximately_equal_half(cy * inv, cubic[minY].fY * inv)) {
404 minYSet |= 1 << index;
405 }
406 }
407 if (minXSet == 0xF) { // test for vertical line
408 if (minYSet == 0xF) { // return 1 if all four are coincident
409 return coincident_line(cubic, fCubic);
410 }
411 return vertical_line(cubic, reduceStyle, fCubic);
412 }
413 if (minYSet == 0xF) { // test for horizontal line
414 return horizontal_line(cubic, reduceStyle, fCubic);
415 }
416 int result = check_linear(cubic, reduceStyle, minX, maxX, minY, maxY, fCubic);
417 if (result) {
418 return result;
419 }
420 if (allowQuadratics == SkReduceOrder::kAllow_Quadratics
421 && (result = check_quadratic(cubic, fCubic))) {
422 return result;
423 }
424 fCubic = cubic;
425 return 4;
426}
427
428SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkTDArray<SkPoint>* reducePts) {
429 SkDQuad quad;
430 quad.set(a);
431 SkReduceOrder reducer;
432 int order = reducer.reduce(quad, kFill_Style);
433 if (order == 2) { // quad became line
434 for (int index = 0; index < order; ++index) {
435 SkPoint* pt = reducePts->append();
436 pt->fX = SkDoubleToScalar(reducer.fLine[index].fX);
437 pt->fY = SkDoubleToScalar(reducer.fLine[index].fY);
438 }
439 }
reed@google.com277c3f82013-05-31 15:17:50 +0000440 return SkPathOpsPointsToVerb(order - 1);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000441}
442
443SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkTDArray<SkPoint>* reducePts) {
444 SkDCubic cubic;
445 cubic.set(a);
446 SkReduceOrder reducer;
447 int order = reducer.reduce(cubic, kAllow_Quadratics, kFill_Style);
448 if (order == 2 || order == 3) { // cubic became line or quad
449 for (int index = 0; index < order; ++index) {
450 SkPoint* pt = reducePts->append();
451 pt->fX = SkDoubleToScalar(reducer.fQuad[index].fX);
452 pt->fY = SkDoubleToScalar(reducer.fQuad[index].fY);
453 }
454 }
reed@google.com277c3f82013-05-31 15:17:50 +0000455 return SkPathOpsPointsToVerb(order - 1);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000456}