Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright 2009 The Android Open Source Project
      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 
      8 
      9 #include "SkEdgeClipper.h"
     10 #include "SkGeometry.h"
     11 #include "SkLineClipper.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 bool SkEdgeClipper::clipLine(SkPoint p0, SkPoint p1, const SkRect& clip) {
     47     fCurrPoint = fPoints;
     48     fCurrVerb = fVerbs;
     49 
     50     SkPoint lines[SkLineClipper::kMaxPoints];
     51     const SkPoint pts[] = { p0, p1 };
     52     int lineCount = SkLineClipper::ClipLine(pts, clip, lines, fCanCullToTheRight);
     53     for (int i = 0; i < lineCount; i++) {
     54         this->appendLine(lines[i], lines[i + 1]);
     55     }
     56 
     57     *fCurrVerb = SkPath::kDone_Verb;
     58     fCurrPoint = fPoints;
     59     fCurrVerb = fVerbs;
     60     return SkPath::kDone_Verb != fVerbs[0];
     61 }
     62 
     63 ///////////////////////////////////////////////////////////////////////////////
     64 
     65 static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2,
     66                            SkScalar target, SkScalar* t) {
     67     /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2
     68      *  We solve for t, using quadratic equation, hence we have to rearrange
     69      * our cooefficents to look like At^2 + Bt + C
     70      */
     71     SkScalar A = c0 - c1 - c1 + c2;
     72     SkScalar B = 2*(c1 - c0);
     73     SkScalar C = c0 - target;
     74 
     75     SkScalar roots[2];  // we only expect one, but make room for 2 for safety
     76     int count = SkFindUnitQuadRoots(A, B, C, roots);
     77     if (count) {
     78         *t = roots[0];
     79         return true;
     80     }
     81     return false;
     82 }
     83 
     84 static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
     85     return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t);
     86 }
     87 
     88 static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) {
     89     return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t);
     90 }
     91 
     92 // Modify pts[] in place so that it is clipped in Y to the clip rect
     93 static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) {
     94     SkScalar t;
     95     SkPoint tmp[5]; // for SkChopQuadAt
     96 
     97     // are we partially above
     98     if (pts[0].fY < clip.fTop) {
     99         if (chopMonoQuadAtY(pts, clip.fTop, &t)) {
    100             // take the 2nd chopped quad
    101             SkChopQuadAt(pts, tmp, t);
    102             // clamp to clean up imprecise numerics in the chop
    103             tmp[2].fY = clip.fTop;
    104             clamp_ge(tmp[3].fY, clip.fTop);
    105 
    106             pts[0] = tmp[2];
    107             pts[1] = tmp[3];
    108         } else {
    109             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
    110             // so we just clamp against the top
    111             for (int i = 0; i < 3; i++) {
    112                 if (pts[i].fY < clip.fTop) {
    113                     pts[i].fY = clip.fTop;
    114                 }
    115             }
    116         }
    117     }
    118 
    119     // are we partially below
    120     if (pts[2].fY > clip.fBottom) {
    121         if (chopMonoQuadAtY(pts, clip.fBottom, &t)) {
    122             SkChopQuadAt(pts, tmp, t);
    123             // clamp to clean up imprecise numerics in the chop
    124             clamp_le(tmp[1].fY, clip.fBottom);
    125             tmp[2].fY = clip.fBottom;
    126 
    127             pts[1] = tmp[1];
    128             pts[2] = tmp[2];
    129         } else {
    130             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
    131             // so we just clamp against the bottom
    132             for (int i = 0; i < 3; i++) {
    133                 if (pts[i].fY > clip.fBottom) {
    134                     pts[i].fY = clip.fBottom;
    135                 }
    136             }
    137         }
    138     }
    139 }
    140 
    141 // srcPts[] must be monotonic in X and Y
    142 void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) {
    143     SkPoint pts[3];
    144     bool reverse = sort_increasing_Y(pts, srcPts, 3);
    145 
    146     // are we completely above or below
    147     if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
    148         return;
    149     }
    150 
    151     // Now chop so that pts is contained within clip in Y
    152     chop_quad_in_Y(pts, clip);
    153 
    154     if (pts[0].fX > pts[2].fX) {
    155         SkTSwap<SkPoint>(pts[0], pts[2]);
    156         reverse = !reverse;
    157     }
    158     SkASSERT(pts[0].fX <= pts[1].fX);
    159     SkASSERT(pts[1].fX <= pts[2].fX);
    160 
    161     // Now chop in X has needed, and record the segments
    162 
    163     if (pts[2].fX <= clip.fLeft) {  // wholly to the left
    164         this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
    165         return;
    166     }
    167     if (pts[0].fX >= clip.fRight) {  // wholly to the right
    168         if (!this->canCullToTheRight()) {
    169             this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
    170         }
    171         return;
    172     }
    173 
    174     SkScalar t;
    175     SkPoint tmp[5]; // for SkChopQuadAt
    176 
    177     // are we partially to the left
    178     if (pts[0].fX < clip.fLeft) {
    179         if (chopMonoQuadAtX(pts, clip.fLeft, &t)) {
    180             SkChopQuadAt(pts, tmp, t);
    181             this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse);
    182             // clamp to clean up imprecise numerics in the chop
    183             tmp[2].fX = clip.fLeft;
    184             clamp_ge(tmp[3].fX, clip.fLeft);
    185 
    186             pts[0] = tmp[2];
    187             pts[1] = tmp[3];
    188         } else {
    189             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
    190             // so we just clamp against the left
    191             this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
    192             return;
    193         }
    194     }
    195 
    196     // are we partially to the right
    197     if (pts[2].fX > clip.fRight) {
    198         if (chopMonoQuadAtX(pts, clip.fRight, &t)) {
    199             SkChopQuadAt(pts, tmp, t);
    200             // clamp to clean up imprecise numerics in the chop
    201             clamp_le(tmp[1].fX, clip.fRight);
    202             tmp[2].fX = clip.fRight;
    203 
    204             this->appendQuad(tmp, reverse);
    205             this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse);
    206         } else {
    207             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
    208             // so we just clamp against the right
    209             this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
    210         }
    211     } else {    // wholly inside the clip
    212         this->appendQuad(pts, reverse);
    213     }
    214 }
    215 
    216 bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) {
    217     fCurrPoint = fPoints;
    218     fCurrVerb = fVerbs;
    219 
    220     SkRect  bounds;
    221     bounds.set(srcPts, 3);
    222 
    223     if (!quick_reject(bounds, clip)) {
    224         SkPoint monoY[5];
    225         int countY = SkChopQuadAtYExtrema(srcPts, monoY);
    226         for (int y = 0; y <= countY; y++) {
    227             SkPoint monoX[5];
    228             int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX);
    229             for (int x = 0; x <= countX; x++) {
    230                 this->clipMonoQuad(&monoX[x * 2], clip);
    231                 SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
    232                 SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
    233             }
    234         }
    235     }
    236 
    237     *fCurrVerb = SkPath::kDone_Verb;
    238     fCurrPoint = fPoints;
    239     fCurrVerb = fVerbs;
    240     return SkPath::kDone_Verb != fVerbs[0];
    241 }
    242 
    243 ///////////////////////////////////////////////////////////////////////////////
    244 
    245 static SkScalar mono_cubic_closestT(const SkScalar src[], SkScalar x) {
    246     SkScalar t = 0.5f;
    247     SkScalar lastT;
    248     SkScalar bestT  SK_INIT_TO_AVOID_WARNING;
    249     SkScalar step = 0.25f;
    250     SkScalar D = src[0];
    251     SkScalar A = src[6] + 3*(src[2] - src[4]) - D;
    252     SkScalar B = 3*(src[4] - src[2] - src[2] + D);
    253     SkScalar C = 3*(src[2] - D);
    254     x -= D;
    255     SkScalar closest = SK_ScalarMax;
    256     do {
    257         SkScalar loc = ((A * t + B) * t + C) * t;
    258         SkScalar dist = SkScalarAbs(loc - x);
    259         if (closest > dist) {
    260             closest = dist;
    261             bestT = t;
    262         }
    263         lastT = t;
    264         t += loc < x ? step : -step;
    265         step *= 0.5f;
    266     } while (closest > 0.25f && lastT != t);
    267     return bestT;
    268 }
    269 
    270 static void chop_mono_cubic_at_y(SkPoint src[4], SkScalar y, SkPoint dst[7]) {
    271     if (SkChopMonoCubicAtY(src, y, dst)) {
    272         return;
    273     }
    274     SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fY, y));
    275 }
    276 
    277 // Modify pts[] in place so that it is clipped in Y to the clip rect
    278 static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) {
    279 
    280     // are we partially above
    281     if (pts[0].fY < clip.fTop) {
    282         SkPoint tmp[7];
    283         chop_mono_cubic_at_y(pts, clip.fTop, tmp);
    284 
    285         /*
    286          *  For a large range in the points, we can do a poor job of chopping, such that the t
    287          *  we computed resulted in the lower cubic still being partly above the clip.
    288          *
    289          *  If just the first or first 2 Y values are above the fTop, we can just smash them
    290          *  down. If the first 3 Ys are above fTop, we can't smash all 3, as that can really
    291          *  distort the cubic. In this case, we take the first output (tmp[3..6] and treat it as
    292          *  a guess, and re-chop against fTop. Then we fall through to checking if we need to
    293          *  smash the first 1 or 2 Y values.
    294          */
    295         if (tmp[3].fY < clip.fTop && tmp[4].fY < clip.fTop && tmp[5].fY < clip.fTop) {
    296             SkPoint tmp2[4];
    297             memcpy(tmp2, &tmp[3].fX, 4 * sizeof(SkPoint));
    298             chop_mono_cubic_at_y(tmp2, clip.fTop, tmp);
    299         }
    300 
    301         // tmp[3, 4].fY should all be to the below clip.fTop.
    302         // Since we can't trust the numerics of the chopper, we force those conditions now
    303         tmp[3].fY = clip.fTop;
    304         clamp_ge(tmp[4].fY, clip.fTop);
    305 
    306         pts[0] = tmp[3];
    307         pts[1] = tmp[4];
    308         pts[2] = tmp[5];
    309     }
    310 
    311     // are we partially below
    312     if (pts[3].fY > clip.fBottom) {
    313         SkPoint tmp[7];
    314         chop_mono_cubic_at_y(pts, clip.fBottom, tmp);
    315         tmp[3].fY = clip.fBottom;
    316         clamp_le(tmp[2].fY, clip.fBottom);
    317 
    318         pts[1] = tmp[1];
    319         pts[2] = tmp[2];
    320         pts[3] = tmp[3];
    321     }
    322 }
    323 
    324 static void chop_mono_cubic_at_x(SkPoint src[4], SkScalar x, SkPoint dst[7]) {
    325     if (SkChopMonoCubicAtX(src, x, dst)) {
    326         return;
    327     }
    328     SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fX, x));
    329 }
    330 
    331 // srcPts[] must be monotonic in X and Y
    332 void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) {
    333     SkPoint pts[4];
    334     bool reverse = sort_increasing_Y(pts, src, 4);
    335 
    336     // are we completely above or below
    337     if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
    338         return;
    339     }
    340 
    341     // Now chop so that pts is contained within clip in Y
    342     chop_cubic_in_Y(pts, clip);
    343 
    344     if (pts[0].fX > pts[3].fX) {
    345         SkTSwap<SkPoint>(pts[0], pts[3]);
    346         SkTSwap<SkPoint>(pts[1], pts[2]);
    347         reverse = !reverse;
    348     }
    349 
    350     // Now chop in X has needed, and record the segments
    351 
    352     if (pts[3].fX <= clip.fLeft) {  // wholly to the left
    353         this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
    354         return;
    355     }
    356     if (pts[0].fX >= clip.fRight) {  // wholly to the right
    357         if (!this->canCullToTheRight()) {
    358             this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
    359         }
    360         return;
    361     }
    362 
    363     // are we partially to the left
    364     if (pts[0].fX < clip.fLeft) {
    365         SkPoint tmp[7];
    366         chop_mono_cubic_at_x(pts, clip.fLeft, tmp);
    367         this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse);
    368 
    369         // tmp[3, 4].fX should all be to the right of clip.fLeft.
    370         // Since we can't trust the numerics of
    371         // the chopper, we force those conditions now
    372         tmp[3].fX = clip.fLeft;
    373         clamp_ge(tmp[4].fX, clip.fLeft);
    374 
    375         pts[0] = tmp[3];
    376         pts[1] = tmp[4];
    377         pts[2] = tmp[5];
    378     }
    379 
    380     // are we partially to the right
    381     if (pts[3].fX > clip.fRight) {
    382         SkPoint tmp[7];
    383         chop_mono_cubic_at_x(pts, clip.fRight, tmp);
    384         tmp[3].fX = clip.fRight;
    385         clamp_le(tmp[2].fX, clip.fRight);
    386 
    387         this->appendCubic(tmp, reverse);
    388         this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse);
    389     } else {    // wholly inside the clip
    390         this->appendCubic(pts, reverse);
    391     }
    392 }
    393 
    394 static SkRect compute_cubic_bounds(const SkPoint pts[4]) {
    395     SkRect r;
    396     r.set(pts, 4);
    397     return r;
    398 }
    399 
    400 static bool too_big_for_reliable_float_math(const SkRect& r) {
    401     // limit set as the largest float value for which we can still reliably compute things like
    402     // - chopping at XY extrema
    403     // - chopping at Y or X values for clipping
    404     //
    405     // Current value chosen just by experiment. Larger (and still succeeds) is always better.
    406     //
    407     const SkScalar limit = 1 << 22;
    408     return r.fLeft < -limit || r.fTop < -limit || r.fRight > limit || r.fBottom > limit;
    409 }
    410 
    411 bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) {
    412     fCurrPoint = fPoints;
    413     fCurrVerb = fVerbs;
    414 
    415     const SkRect bounds = compute_cubic_bounds(srcPts);
    416     // check if we're clipped out vertically
    417     if (bounds.fBottom > clip.fTop && bounds.fTop < clip.fBottom) {
    418         if (too_big_for_reliable_float_math(bounds)) {
    419             // can't safely clip the cubic, so we give up and draw a line (which we can safely clip)
    420             //
    421             // If we rewrote chopcubicat*extrema and chopmonocubic using doubles, we could very
    422             // likely always handle the cubic safely, but (it seems) at a big loss in speed, so
    423             // we'd only want to take that alternate impl if needed. Perhaps a TODO to try it.
    424             //
    425             return this->clipLine(srcPts[0], srcPts[3], clip);
    426         } else {
    427             SkPoint monoY[10];
    428             int countY = SkChopCubicAtYExtrema(srcPts, monoY);
    429             for (int y = 0; y <= countY; y++) {
    430                 SkPoint monoX[10];
    431                 int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX);
    432                 for (int x = 0; x <= countX; x++) {
    433                     this->clipMonoCubic(&monoX[x * 3], clip);
    434                     SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
    435                     SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
    436                 }
    437             }
    438         }
    439     }
    440 
    441     *fCurrVerb = SkPath::kDone_Verb;
    442     fCurrPoint = fPoints;
    443     fCurrVerb = fVerbs;
    444     return SkPath::kDone_Verb != fVerbs[0];
    445 }
    446 
    447 ///////////////////////////////////////////////////////////////////////////////
    448 
    449 void SkEdgeClipper::appendLine(SkPoint p0, SkPoint p1) {
    450     *fCurrVerb++ = SkPath::kLine_Verb;
    451     fCurrPoint[0] = p0;
    452     fCurrPoint[1] = p1;
    453     fCurrPoint += 2;
    454 }
    455 
    456 void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1,
    457                                 bool reverse) {
    458     *fCurrVerb++ = SkPath::kLine_Verb;
    459 
    460     if (reverse) {
    461         SkTSwap<SkScalar>(y0, y1);
    462     }
    463     fCurrPoint[0].set(x, y0);
    464     fCurrPoint[1].set(x, y1);
    465     fCurrPoint += 2;
    466 }
    467 
    468 void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) {
    469     *fCurrVerb++ = SkPath::kQuad_Verb;
    470 
    471     if (reverse) {
    472         fCurrPoint[0] = pts[2];
    473         fCurrPoint[2] = pts[0];
    474     } else {
    475         fCurrPoint[0] = pts[0];
    476         fCurrPoint[2] = pts[2];
    477     }
    478     fCurrPoint[1] = pts[1];
    479     fCurrPoint += 3;
    480 }
    481 
    482 void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) {
    483     *fCurrVerb++ = SkPath::kCubic_Verb;
    484 
    485     if (reverse) {
    486         for (int i = 0; i < 4; i++) {
    487             fCurrPoint[i] = pts[3 - i];
    488         }
    489     } else {
    490         memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint));
    491     }
    492     fCurrPoint += 4;
    493 }
    494 
    495 SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) {
    496     SkPath::Verb verb = *fCurrVerb;
    497 
    498     switch (verb) {
    499         case SkPath::kLine_Verb:
    500             memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint));
    501             fCurrPoint += 2;
    502             fCurrVerb += 1;
    503             break;
    504         case SkPath::kQuad_Verb:
    505             memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint));
    506             fCurrPoint += 3;
    507             fCurrVerb += 1;
    508             break;
    509         case SkPath::kCubic_Verb:
    510             memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint));
    511             fCurrPoint += 4;
    512             fCurrVerb += 1;
    513             break;
    514         case SkPath::kDone_Verb:
    515             break;
    516         default:
    517             SkDEBUGFAIL("unexpected verb in quadclippper2 iter");
    518             break;
    519     }
    520     return verb;
    521 }
    522 
    523 ///////////////////////////////////////////////////////////////////////////////
    524 
    525 #ifdef SK_DEBUG
    526 static void assert_monotonic(const SkScalar coord[], int count) {
    527     if (coord[0] > coord[(count - 1) * 2]) {
    528         for (int i = 1; i < count; i++) {
    529             SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]);
    530         }
    531     } else if (coord[0] < coord[(count - 1) * 2]) {
    532         for (int i = 1; i < count; i++) {
    533             SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]);
    534         }
    535     } else {
    536         for (int i = 1; i < count; i++) {
    537             SkASSERT(coord[2 * (i - 1)] == coord[i * 2]);
    538         }
    539     }
    540 }
    541 
    542 void sk_assert_monotonic_y(const SkPoint pts[], int count) {
    543     if (count > 1) {
    544         assert_monotonic(&pts[0].fY, count);
    545     }
    546 }
    547 
    548 void sk_assert_monotonic_x(const SkPoint pts[], int count) {
    549     if (count > 1) {
    550         assert_monotonic(&pts[0].fX, count);
    551     }
    552 }
    553 #endif
    554