Home | History | Annotate | Download | only in core
      1 
      2 /*
      3  * Copyright 2009 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 
      9 
     10 #include "SkEdgeClipper.h"
     11 #include "SkGeometry.h"
     12 
     13 static bool quick_reject(const SkRect& bounds, const SkRect& clip) {
     14     return bounds.fTop >= clip.fBottom || bounds.fBottom <= clip.fTop;
     15 }
     16 
     17 static inline void clamp_le(SkScalar& value, SkScalar max) {
     18     if (value > max) {
     19         value = max;
     20     }
     21 }
     22 
     23 static inline void clamp_ge(SkScalar& value, SkScalar min) {
     24     if (value < min) {
     25         value = min;
     26     }
     27 }
     28 
     29 /*  src[] must be monotonic in Y. This routine copies src into dst, and sorts
     30  it to be increasing in Y. If it had to reverse the order of the points,
     31  it returns true, otherwise it returns false
     32  */
     33 static bool sort_increasing_Y(SkPoint dst[], const SkPoint src[], int count) {
     34     // we need the data to be monotonically increasing in Y
     35     if (src[0].fY > src[count - 1].fY) {
     36         for (int i = 0; i < count; i++) {
     37             dst[i] = src[count - i - 1];
     38         }
     39         return true;
     40     } else {
     41         memcpy(dst, src, count * sizeof(SkPoint));
     42         return false;
     43     }
     44 }
     45 
     46 ///////////////////////////////////////////////////////////////////////////////
     47 
     48 static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2,
     49                            SkScalar target, SkScalar* t) {
     50     /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2
     51      *  We solve for t, using quadratic equation, hence we have to rearrange
     52      * our cooefficents to look like At^2 + Bt + C
     53      */
     54     SkScalar A = c0 - c1 - c1 + c2;
     55     SkScalar B = 2*(c1 - c0);
     56     SkScalar C = c0 - target;
     57 
     58     SkScalar roots[2];  // we only expect one, but make room for 2 for safety
     59     int count = SkFindUnitQuadRoots(A, B, C, roots);
     60     if (count) {
     61         *t = roots[0];
     62         return true;
     63     }
     64     return false;
     65 }
     66 
     67 static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
     68     return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t);
     69 }
     70 
     71 static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) {
     72     return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t);
     73 }
     74 
     75 // Modify pts[] in place so that it is clipped in Y to the clip rect
     76 static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) {
     77     SkScalar t;
     78     SkPoint tmp[5]; // for SkChopQuadAt
     79 
     80     // are we partially above
     81     if (pts[0].fY < clip.fTop) {
     82         if (chopMonoQuadAtY(pts, clip.fTop, &t)) {
     83             // take the 2nd chopped quad
     84             SkChopQuadAt(pts, tmp, t);
     85             // clamp to clean up imprecise numerics in the chop
     86             tmp[2].fY = clip.fTop;
     87             clamp_ge(tmp[3].fY, clip.fTop);
     88 
     89             pts[0] = tmp[2];
     90             pts[1] = tmp[3];
     91         } else {
     92             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
     93             // so we just clamp against the top
     94             for (int i = 0; i < 3; i++) {
     95                 if (pts[i].fY < clip.fTop) {
     96                     pts[i].fY = clip.fTop;
     97                 }
     98             }
     99         }
    100     }
    101 
    102     // are we partially below
    103     if (pts[2].fY > clip.fBottom) {
    104         if (chopMonoQuadAtY(pts, clip.fBottom, &t)) {
    105             SkChopQuadAt(pts, tmp, t);
    106             // clamp to clean up imprecise numerics in the chop
    107             clamp_le(tmp[1].fY, clip.fBottom);
    108             tmp[2].fY = clip.fBottom;
    109 
    110             pts[1] = tmp[1];
    111             pts[2] = tmp[2];
    112         } else {
    113             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
    114             // so we just clamp against the bottom
    115             for (int i = 0; i < 3; i++) {
    116                 if (pts[i].fY > clip.fBottom) {
    117                     pts[i].fY = clip.fBottom;
    118                 }
    119             }
    120         }
    121     }
    122 }
    123 
    124 // srcPts[] must be monotonic in X and Y
    125 void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) {
    126     SkPoint pts[3];
    127     bool reverse = sort_increasing_Y(pts, srcPts, 3);
    128 
    129     // are we completely above or below
    130     if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
    131         return;
    132     }
    133 
    134     // Now chop so that pts is contained within clip in Y
    135     chop_quad_in_Y(pts, clip);
    136 
    137     if (pts[0].fX > pts[2].fX) {
    138         SkTSwap<SkPoint>(pts[0], pts[2]);
    139         reverse = !reverse;
    140     }
    141     SkASSERT(pts[0].fX <= pts[1].fX);
    142     SkASSERT(pts[1].fX <= pts[2].fX);
    143 
    144     // Now chop in X has needed, and record the segments
    145 
    146     if (pts[2].fX <= clip.fLeft) {  // wholly to the left
    147         this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
    148         return;
    149     }
    150     if (pts[0].fX >= clip.fRight) {  // wholly to the right
    151         this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
    152         return;
    153     }
    154 
    155     SkScalar t;
    156     SkPoint tmp[5]; // for SkChopQuadAt
    157 
    158     // are we partially to the left
    159     if (pts[0].fX < clip.fLeft) {
    160         if (chopMonoQuadAtX(pts, clip.fLeft, &t)) {
    161             SkChopQuadAt(pts, tmp, t);
    162             this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse);
    163             // clamp to clean up imprecise numerics in the chop
    164             tmp[2].fX = clip.fLeft;
    165             clamp_ge(tmp[3].fX, clip.fLeft);
    166 
    167             pts[0] = tmp[2];
    168             pts[1] = tmp[3];
    169         } else {
    170             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
    171             // so we just clamp against the left
    172             this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
    173             return;
    174         }
    175     }
    176 
    177     // are we partially to the right
    178     if (pts[2].fX > clip.fRight) {
    179         if (chopMonoQuadAtX(pts, clip.fRight, &t)) {
    180             SkChopQuadAt(pts, tmp, t);
    181             // clamp to clean up imprecise numerics in the chop
    182             clamp_le(tmp[1].fX, clip.fRight);
    183             tmp[2].fX = clip.fRight;
    184 
    185             this->appendQuad(tmp, reverse);
    186             this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse);
    187         } else {
    188             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
    189             // so we just clamp against the right
    190             this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
    191         }
    192     } else {    // wholly inside the clip
    193         this->appendQuad(pts, reverse);
    194     }
    195 }
    196 
    197 bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) {
    198     fCurrPoint = fPoints;
    199     fCurrVerb = fVerbs;
    200 
    201     SkRect  bounds;
    202     bounds.set(srcPts, 3);
    203 
    204     if (!quick_reject(bounds, clip)) {
    205         SkPoint monoY[5];
    206         int countY = SkChopQuadAtYExtrema(srcPts, monoY);
    207         for (int y = 0; y <= countY; y++) {
    208             SkPoint monoX[5];
    209             int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX);
    210             for (int x = 0; x <= countX; x++) {
    211                 this->clipMonoQuad(&monoX[x * 2], clip);
    212                 SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
    213                 SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
    214             }
    215         }
    216     }
    217 
    218     *fCurrVerb = SkPath::kDone_Verb;
    219     fCurrPoint = fPoints;
    220     fCurrVerb = fVerbs;
    221     return SkPath::kDone_Verb != fVerbs[0];
    222 }
    223 
    224 ///////////////////////////////////////////////////////////////////////////////
    225 
    226 static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
    227                                  SkScalar D, SkScalar t) {
    228     return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
    229 }
    230 
    231 /*  Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
    232     t value such that cubic(t) = target
    233  */
    234 static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
    235                            SkScalar target, SkScalar* t) {
    236  //   SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
    237     SkASSERT(c0 < target && target < c3);
    238 
    239     SkScalar D = c0 - target;
    240     SkScalar A = c3 + 3*(c1 - c2) - c0;
    241     SkScalar B = 3*(c2 - c1 - c1 + c0);
    242     SkScalar C = 3*(c1 - c0);
    243 
    244     const SkScalar TOLERANCE = SK_Scalar1 / 4096;
    245     SkScalar minT = 0;
    246     SkScalar maxT = SK_Scalar1;
    247     SkScalar mid;
    248 
    249     // This is a lot of iterations. Is there a faster way?
    250     for (int i = 0; i < 24; i++) {
    251         mid = SkScalarAve(minT, maxT);
    252         SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
    253         if (delta < 0) {
    254             minT = mid;
    255             delta = -delta;
    256         } else {
    257             maxT = mid;
    258         }
    259         if (delta < TOLERANCE) {
    260             break;
    261         }
    262     }
    263     *t = mid;
    264 //    SkDebugf("-- evalCubicAt %d delta %g\n", i, eval_cubic_coeff(A, B, C, D, *t));
    265     return true;
    266 }
    267 
    268 static bool chopMonoCubicAtY(SkPoint pts[4], SkScalar y, SkScalar* t) {
    269     return chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, t);
    270 }
    271 
    272 static bool chopMonoCubicAtX(SkPoint pts[4], SkScalar x, SkScalar* t) {
    273     return chopMonoCubicAt(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, x, t);
    274 }
    275 
    276 // Modify pts[] in place so that it is clipped in Y to the clip rect
    277 static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) {
    278 
    279     // are we partially above
    280     if (pts[0].fY < clip.fTop) {
    281         SkScalar t;
    282         if (chopMonoCubicAtY(pts, clip.fTop, &t)) {
    283             SkPoint tmp[7];
    284             SkChopCubicAt(pts, tmp, t);
    285 
    286             // tmp[3, 4, 5].fY should all be to the below clip.fTop.
    287             // Since we can't trust the numerics of
    288             // the chopper, we force those conditions now
    289             tmp[3].fY = clip.fTop;
    290             clamp_ge(tmp[4].fY, clip.fTop);
    291             clamp_ge(tmp[5].fY, clip.fTop);
    292 
    293             pts[0] = tmp[3];
    294             pts[1] = tmp[4];
    295             pts[2] = tmp[5];
    296         } else {
    297             // if chopMonoCubicAtY failed, then we may have hit inexact numerics
    298             // so we just clamp against the top
    299             for (int i = 0; i < 4; i++) {
    300                 clamp_ge(pts[i].fY, clip.fTop);
    301             }
    302         }
    303     }
    304 
    305     // are we partially below
    306     if (pts[3].fY > clip.fBottom) {
    307         SkScalar t;
    308         if (chopMonoCubicAtY(pts, clip.fBottom, &t)) {
    309             SkPoint tmp[7];
    310             SkChopCubicAt(pts, tmp, t);
    311             tmp[3].fY = clip.fBottom;
    312             clamp_le(tmp[2].fY, clip.fBottom);
    313 
    314             pts[1] = tmp[1];
    315             pts[2] = tmp[2];
    316             pts[3] = tmp[3];
    317         } else {
    318             // if chopMonoCubicAtY failed, then we may have hit inexact numerics
    319             // so we just clamp against the bottom
    320             for (int i = 0; i < 4; i++) {
    321                 clamp_le(pts[i].fY, clip.fBottom);
    322             }
    323         }
    324     }
    325 }
    326 
    327 // srcPts[] must be monotonic in X and Y
    328 void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) {
    329     SkPoint pts[4];
    330     bool reverse = sort_increasing_Y(pts, src, 4);
    331 
    332     // are we completely above or below
    333     if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
    334         return;
    335     }
    336 
    337     // Now chop so that pts is contained within clip in Y
    338     chop_cubic_in_Y(pts, clip);
    339 
    340     if (pts[0].fX > pts[3].fX) {
    341         SkTSwap<SkPoint>(pts[0], pts[3]);
    342         SkTSwap<SkPoint>(pts[1], pts[2]);
    343         reverse = !reverse;
    344     }
    345 
    346     // Now chop in X has needed, and record the segments
    347 
    348     if (pts[3].fX <= clip.fLeft) {  // wholly to the left
    349         this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
    350         return;
    351     }
    352     if (pts[0].fX >= clip.fRight) {  // wholly to the right
    353         this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
    354         return;
    355     }
    356 
    357     // are we partially to the left
    358     if (pts[0].fX < clip.fLeft) {
    359         SkScalar t;
    360         if (chopMonoCubicAtX(pts, clip.fLeft, &t)) {
    361             SkPoint tmp[7];
    362             SkChopCubicAt(pts, tmp, t);
    363             this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse);
    364 
    365             // tmp[3, 4, 5].fX should all be to the right of clip.fLeft.
    366             // Since we can't trust the numerics of
    367             // the chopper, we force those conditions now
    368             tmp[3].fX = clip.fLeft;
    369             clamp_ge(tmp[4].fX, clip.fLeft);
    370             clamp_ge(tmp[5].fX, clip.fLeft);
    371 
    372             pts[0] = tmp[3];
    373             pts[1] = tmp[4];
    374             pts[2] = tmp[5];
    375         } else {
    376             // if chopMonocubicAtY failed, then we may have hit inexact numerics
    377             // so we just clamp against the left
    378             this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
    379             return;
    380         }
    381     }
    382 
    383     // are we partially to the right
    384     if (pts[3].fX > clip.fRight) {
    385         SkScalar t;
    386         if (chopMonoCubicAtX(pts, clip.fRight, &t)) {
    387             SkPoint tmp[7];
    388             SkChopCubicAt(pts, tmp, t);
    389             tmp[3].fX = clip.fRight;
    390             clamp_le(tmp[2].fX, clip.fRight);
    391             clamp_le(tmp[1].fX, clip.fRight);
    392 
    393             this->appendCubic(tmp, reverse);
    394             this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse);
    395         } else {
    396             // if chopMonoCubicAtX failed, then we may have hit inexact numerics
    397             // so we just clamp against the right
    398             this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
    399         }
    400     } else {    // wholly inside the clip
    401         this->appendCubic(pts, reverse);
    402     }
    403 }
    404 
    405 bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) {
    406     fCurrPoint = fPoints;
    407     fCurrVerb = fVerbs;
    408 
    409     SkRect  bounds;
    410     bounds.set(srcPts, 4);
    411 
    412     if (!quick_reject(bounds, clip)) {
    413         SkPoint monoY[10];
    414         int countY = SkChopCubicAtYExtrema(srcPts, monoY);
    415         for (int y = 0; y <= countY; y++) {
    416             SkPoint monoX[10];
    417             int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX);
    418             for (int x = 0; x <= countX; x++) {
    419                 this->clipMonoCubic(&monoX[x * 3], clip);
    420                 SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
    421                 SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
    422             }
    423         }
    424     }
    425 
    426     *fCurrVerb = SkPath::kDone_Verb;
    427     fCurrPoint = fPoints;
    428     fCurrVerb = fVerbs;
    429     return SkPath::kDone_Verb != fVerbs[0];
    430 }
    431 
    432 ///////////////////////////////////////////////////////////////////////////////
    433 
    434 void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1,
    435                                 bool reverse) {
    436     *fCurrVerb++ = SkPath::kLine_Verb;
    437 
    438     if (reverse) {
    439         SkTSwap<SkScalar>(y0, y1);
    440     }
    441     fCurrPoint[0].set(x, y0);
    442     fCurrPoint[1].set(x, y1);
    443     fCurrPoint += 2;
    444 }
    445 
    446 void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) {
    447     *fCurrVerb++ = SkPath::kQuad_Verb;
    448 
    449     if (reverse) {
    450         fCurrPoint[0] = pts[2];
    451         fCurrPoint[2] = pts[0];
    452     } else {
    453         fCurrPoint[0] = pts[0];
    454         fCurrPoint[2] = pts[2];
    455     }
    456     fCurrPoint[1] = pts[1];
    457     fCurrPoint += 3;
    458 }
    459 
    460 void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) {
    461     *fCurrVerb++ = SkPath::kCubic_Verb;
    462 
    463     if (reverse) {
    464         for (int i = 0; i < 4; i++) {
    465             fCurrPoint[i] = pts[3 - i];
    466         }
    467     } else {
    468         memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint));
    469     }
    470     fCurrPoint += 4;
    471 }
    472 
    473 SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) {
    474     SkPath::Verb verb = *fCurrVerb;
    475 
    476     switch (verb) {
    477         case SkPath::kLine_Verb:
    478             memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint));
    479             fCurrPoint += 2;
    480             fCurrVerb += 1;
    481             break;
    482         case SkPath::kQuad_Verb:
    483             memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint));
    484             fCurrPoint += 3;
    485             fCurrVerb += 1;
    486             break;
    487         case SkPath::kCubic_Verb:
    488             memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint));
    489             fCurrPoint += 4;
    490             fCurrVerb += 1;
    491             break;
    492         case SkPath::kDone_Verb:
    493             break;
    494         default:
    495             SkDEBUGFAIL("unexpected verb in quadclippper2 iter");
    496             break;
    497     }
    498     return verb;
    499 }
    500 
    501 ///////////////////////////////////////////////////////////////////////////////
    502 
    503 #ifdef SK_DEBUG
    504 static void assert_monotonic(const SkScalar coord[], int count) {
    505     if (coord[0] > coord[(count - 1) * 2]) {
    506         for (int i = 1; i < count; i++) {
    507             SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]);
    508         }
    509     } else if (coord[0] < coord[(count - 1) * 2]) {
    510         for (int i = 1; i < count; i++) {
    511             SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]);
    512         }
    513     } else {
    514         for (int i = 1; i < count; i++) {
    515             SkASSERT(coord[2 * (i - 1)] == coord[i * 2]);
    516         }
    517     }
    518 }
    519 
    520 void sk_assert_monotonic_y(const SkPoint pts[], int count) {
    521     if (count > 1) {
    522         assert_monotonic(&pts[0].fY, count);
    523     }
    524 }
    525 
    526 void sk_assert_monotonic_x(const SkPoint pts[], int count) {
    527     if (count > 1) {
    528         assert_monotonic(&pts[0].fX, count);
    529     }
    530 }
    531 #endif
    532