Home | History | Annotate | Download | only in pathops
      1 /*
      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 "SkGeometry.h"
      8 #include "SkReduceOrder.h"
      9 
     10 int SkReduceOrder::reduce(const SkDLine& line) {
     11     fLine[0] = line[0];
     12     int different = line[0] != line[1];
     13     fLine[1] = line[different];
     14     return 1 + different;
     15 }
     16 
     17 static int coincident_line(const SkDQuad& quad, SkDQuad& reduction) {
     18     reduction[0] = reduction[1] = quad[0];
     19     return 1;
     20 }
     21 
     22 static int reductionLineCount(const SkDQuad& reduction) {
     23     return 1 + !reduction[0].approximatelyEqual(reduction[1]);
     24 }
     25 
     26 static int vertical_line(const SkDQuad& quad, SkDQuad& reduction) {
     27     reduction[0] = quad[0];
     28     reduction[1] = quad[2];
     29     return reductionLineCount(reduction);
     30 }
     31 
     32 static int horizontal_line(const SkDQuad& quad, SkDQuad& reduction) {
     33     reduction[0] = quad[0];
     34     reduction[1] = quad[2];
     35     return reductionLineCount(reduction);
     36 }
     37 
     38 static int check_linear(const SkDQuad& quad,
     39         int minX, int maxX, int minY, int maxY, SkDQuad& reduction) {
     40     if (!quad.isLinear(0, 2)) {
     41         return 0;
     42     }
     43     // four are colinear: return line formed by outside
     44     reduction[0] = quad[0];
     45     reduction[1] = quad[2];
     46     return reductionLineCount(reduction);
     47 }
     48 
     49 // reduce to a quadratic or smaller
     50 // look for identical points
     51 // look for all four points in a line
     52     // note that three points in a line doesn't simplify a cubic
     53 // look for approximation with single quadratic
     54     // save approximation with multiple quadratics for later
     55 int SkReduceOrder::reduce(const SkDQuad& quad) {
     56     int index, minX, maxX, minY, maxY;
     57     int minXSet, minYSet;
     58     minX = maxX = minY = maxY = 0;
     59     minXSet = minYSet = 0;
     60     for (index = 1; index < 3; ++index) {
     61         if (quad[minX].fX > quad[index].fX) {
     62             minX = index;
     63         }
     64         if (quad[minY].fY > quad[index].fY) {
     65             minY = index;
     66         }
     67         if (quad[maxX].fX < quad[index].fX) {
     68             maxX = index;
     69         }
     70         if (quad[maxY].fY < quad[index].fY) {
     71             maxY = index;
     72         }
     73     }
     74     for (index = 0; index < 3; ++index) {
     75         if (AlmostEqualUlps(quad[index].fX, quad[minX].fX)) {
     76             minXSet |= 1 << index;
     77         }
     78         if (AlmostEqualUlps(quad[index].fY, quad[minY].fY)) {
     79             minYSet |= 1 << index;
     80         }
     81     }
     82     if ((minXSet & 0x05) == 0x5 && (minYSet & 0x05) == 0x5) { // test for degenerate
     83         // this quad starts and ends at the same place, so never contributes
     84         // to the fill
     85         return coincident_line(quad, fQuad);
     86     }
     87     if (minXSet == 0x7) {  // test for vertical line
     88         return vertical_line(quad, fQuad);
     89     }
     90     if (minYSet == 0x7) {  // test for horizontal line
     91         return horizontal_line(quad, fQuad);
     92     }
     93     int result = check_linear(quad, minX, maxX, minY, maxY, fQuad);
     94     if (result) {
     95         return result;
     96     }
     97     fQuad = quad;
     98     return 3;
     99 }
    100 
    101 ////////////////////////////////////////////////////////////////////////////////////
    102 
    103 static int coincident_line(const SkDCubic& cubic, SkDCubic& reduction) {
    104     reduction[0] = reduction[1] = cubic[0];
    105     return 1;
    106 }
    107 
    108 static int reductionLineCount(const SkDCubic& reduction) {
    109     return 1 + !reduction[0].approximatelyEqual(reduction[1]);
    110 }
    111 
    112 static int vertical_line(const SkDCubic& cubic, SkDCubic& reduction) {
    113     reduction[0] = cubic[0];
    114     reduction[1] = cubic[3];
    115     return reductionLineCount(reduction);
    116 }
    117 
    118 static int horizontal_line(const SkDCubic& cubic, SkDCubic& reduction) {
    119     reduction[0] = cubic[0];
    120     reduction[1] = cubic[3];
    121     return reductionLineCount(reduction);
    122 }
    123 
    124 // check to see if it is a quadratic or a line
    125 static int check_quadratic(const SkDCubic& cubic, SkDCubic& reduction) {
    126     double dx10 = cubic[1].fX - cubic[0].fX;
    127     double dx23 = cubic[2].fX - cubic[3].fX;
    128     double midX = cubic[0].fX + dx10 * 3 / 2;
    129     double sideAx = midX - cubic[3].fX;
    130     double sideBx = dx23 * 3 / 2;
    131     if (approximately_zero(sideAx) ? !approximately_equal(sideAx, sideBx)
    132             : !AlmostEqualUlps_Pin(sideAx, sideBx)) {
    133         return 0;
    134     }
    135     double dy10 = cubic[1].fY - cubic[0].fY;
    136     double dy23 = cubic[2].fY - cubic[3].fY;
    137     double midY = cubic[0].fY + dy10 * 3 / 2;
    138     double sideAy = midY - cubic[3].fY;
    139     double sideBy = dy23 * 3 / 2;
    140     if (approximately_zero(sideAy) ? !approximately_equal(sideAy, sideBy)
    141             : !AlmostEqualUlps_Pin(sideAy, sideBy)) {
    142         return 0;
    143     }
    144     reduction[0] = cubic[0];
    145     reduction[1].fX = midX;
    146     reduction[1].fY = midY;
    147     reduction[2] = cubic[3];
    148     return 3;
    149 }
    150 
    151 static int check_linear(const SkDCubic& cubic,
    152         int minX, int maxX, int minY, int maxY, SkDCubic& reduction) {
    153     if (!cubic.isLinear(0, 3)) {
    154         return 0;
    155     }
    156     // four are colinear: return line formed by outside
    157     reduction[0] = cubic[0];
    158     reduction[1] = cubic[3];
    159     return reductionLineCount(reduction);
    160 }
    161 
    162 /* food for thought:
    163 http://objectmix.com/graphics/132906-fast-precision-driven-cubic-quadratic-piecewise-degree-reduction-algos-2-a.html
    164 
    165 Given points c1, c2, c3 and c4 of a cubic Bezier, the points of the
    166 corresponding quadratic Bezier are (given in convex combinations of
    167 points):
    168 
    169 q1 = (11/13)c1 + (3/13)c2 -(3/13)c3 + (2/13)c4
    170 q2 = -c1 + (3/2)c2 + (3/2)c3 - c4
    171 q3 = (2/13)c1 - (3/13)c2 + (3/13)c3 + (11/13)c4
    172 
    173 Of course, this curve does not interpolate the end-points, but it would
    174 be interesting to see the behaviour of such a curve in an applet.
    175 
    176 --
    177 Kalle Rutanen
    178 http://kaba.hilvi.org
    179 
    180 */
    181 
    182 // reduce to a quadratic or smaller
    183 // look for identical points
    184 // look for all four points in a line
    185     // note that three points in a line doesn't simplify a cubic
    186 // look for approximation with single quadratic
    187     // save approximation with multiple quadratics for later
    188 int SkReduceOrder::reduce(const SkDCubic& cubic, Quadratics allowQuadratics) {
    189     int index, minX, maxX, minY, maxY;
    190     int minXSet, minYSet;
    191     minX = maxX = minY = maxY = 0;
    192     minXSet = minYSet = 0;
    193     for (index = 1; index < 4; ++index) {
    194         if (cubic[minX].fX > cubic[index].fX) {
    195             minX = index;
    196         }
    197         if (cubic[minY].fY > cubic[index].fY) {
    198             minY = index;
    199         }
    200         if (cubic[maxX].fX < cubic[index].fX) {
    201             maxX = index;
    202         }
    203         if (cubic[maxY].fY < cubic[index].fY) {
    204             maxY = index;
    205         }
    206     }
    207     for (index = 0; index < 4; ++index) {
    208         double cx = cubic[index].fX;
    209         double cy = cubic[index].fY;
    210         double denom = SkTMax(fabs(cx), SkTMax(fabs(cy),
    211                 SkTMax(fabs(cubic[minX].fX), fabs(cubic[minY].fY))));
    212         if (denom == 0) {
    213             minXSet |= 1 << index;
    214             minYSet |= 1 << index;
    215             continue;
    216         }
    217         double inv = 1 / denom;
    218         if (approximately_equal_half(cx * inv, cubic[minX].fX * inv)) {
    219             minXSet |= 1 << index;
    220         }
    221         if (approximately_equal_half(cy * inv, cubic[minY].fY * inv)) {
    222             minYSet |= 1 << index;
    223         }
    224     }
    225     if (minXSet == 0xF) {  // test for vertical line
    226         if (minYSet == 0xF) {  // return 1 if all four are coincident
    227             return coincident_line(cubic, fCubic);
    228         }
    229         return vertical_line(cubic, fCubic);
    230     }
    231     if (minYSet == 0xF) {  // test for horizontal line
    232         return horizontal_line(cubic, fCubic);
    233     }
    234     int result = check_linear(cubic, minX, maxX, minY, maxY, fCubic);
    235     if (result) {
    236         return result;
    237     }
    238     if (allowQuadratics == SkReduceOrder::kAllow_Quadratics
    239             && (result = check_quadratic(cubic, fCubic))) {
    240         return result;
    241     }
    242     fCubic = cubic;
    243     return 4;
    244 }
    245 
    246 SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkPoint* reducePts) {
    247     SkDQuad quad;
    248     quad.set(a);
    249     SkReduceOrder reducer;
    250     int order = reducer.reduce(quad);
    251     if (order == 2) {  // quad became line
    252         for (int index = 0; index < order; ++index) {
    253             *reducePts++ = reducer.fLine[index].asSkPoint();
    254         }
    255     }
    256     return SkPathOpsPointsToVerb(order - 1);
    257 }
    258 
    259 SkPath::Verb SkReduceOrder::Conic(const SkConic& c, SkPoint* reducePts) {
    260     SkPath::Verb verb = SkReduceOrder::Quad(c.fPts, reducePts);
    261     if (verb > SkPath::kLine_Verb && c.fW == 1) {
    262         return SkPath::kQuad_Verb;
    263     }
    264     return verb == SkPath::kQuad_Verb ? SkPath::kConic_Verb : verb;
    265 }
    266 
    267 SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkPoint* reducePts) {
    268     if (SkDPoint::ApproximatelyEqual(a[0], a[1]) && SkDPoint::ApproximatelyEqual(a[0], a[2])
    269             && SkDPoint::ApproximatelyEqual(a[0], a[3])) {
    270         reducePts[0] = a[0];
    271         return SkPath::kMove_Verb;
    272     }
    273     SkDCubic cubic;
    274     cubic.set(a);
    275     SkReduceOrder reducer;
    276     int order = reducer.reduce(cubic, kAllow_Quadratics);
    277     if (order == 2 || order == 3) {  // cubic became line or quad
    278         for (int index = 0; index < order; ++index) {
    279             *reducePts++ = reducer.fQuad[index].asSkPoint();
    280         }
    281     }
    282     return SkPathOpsPointsToVerb(order - 1);
    283 }
    284