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