Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright 2006 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 #include "SkStrokerPriv.h"
      9 #include "SkGeometry.h"
     10 #include "SkPath.h"
     11 #include "SkPointPriv.h"
     12 
     13 static void ButtCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
     14                        const SkPoint& stop, SkPath*) {
     15     path->lineTo(stop.fX, stop.fY);
     16 }
     17 
     18 static void RoundCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
     19                         const SkPoint& stop, SkPath*) {
     20     SkVector parallel;
     21     SkPointPriv::RotateCW(normal, &parallel);
     22 
     23     SkPoint projectedCenter = pivot + parallel;
     24 
     25     path->conicTo(projectedCenter + normal, projectedCenter, SK_ScalarRoot2Over2);
     26     path->conicTo(projectedCenter - normal, stop, SK_ScalarRoot2Over2);
     27 }
     28 
     29 static void SquareCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
     30                          const SkPoint& stop, SkPath* otherPath) {
     31     SkVector parallel;
     32     SkPointPriv::RotateCW(normal, &parallel);
     33 
     34     if (otherPath) {
     35         path->setLastPt(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
     36         path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
     37     } else {
     38         path->lineTo(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
     39         path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
     40         path->lineTo(stop.fX, stop.fY);
     41     }
     42 }
     43 
     44 /////////////////////////////////////////////////////////////////////////////
     45 
     46 static bool is_clockwise(const SkVector& before, const SkVector& after) {
     47     return before.fX * after.fY > before.fY * after.fX;
     48 }
     49 
     50 enum AngleType {
     51     kNearly180_AngleType,
     52     kSharp_AngleType,
     53     kShallow_AngleType,
     54     kNearlyLine_AngleType
     55 };
     56 
     57 static AngleType Dot2AngleType(SkScalar dot) {
     58 // need more precise fixed normalization
     59 //  SkASSERT(SkScalarAbs(dot) <= SK_Scalar1 + SK_ScalarNearlyZero);
     60 
     61     if (dot >= 0) { // shallow or line
     62         return SkScalarNearlyZero(SK_Scalar1 - dot) ? kNearlyLine_AngleType : kShallow_AngleType;
     63     } else {           // sharp or 180
     64         return SkScalarNearlyZero(SK_Scalar1 + dot) ? kNearly180_AngleType : kSharp_AngleType;
     65     }
     66 }
     67 
     68 static void HandleInnerJoin(SkPath* inner, const SkPoint& pivot, const SkVector& after) {
     69 #if 1
     70     /*  In the degenerate case that the stroke radius is larger than our segments
     71         just connecting the two inner segments may "show through" as a funny
     72         diagonal. To pseudo-fix this, we go through the pivot point. This adds
     73         an extra point/edge, but I can't see a cheap way to know when this is
     74         not needed :(
     75     */
     76     inner->lineTo(pivot.fX, pivot.fY);
     77 #endif
     78 
     79     inner->lineTo(pivot.fX - after.fX, pivot.fY - after.fY);
     80 }
     81 
     82 static void BluntJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
     83                         const SkPoint& pivot, const SkVector& afterUnitNormal,
     84                         SkScalar radius, SkScalar invMiterLimit, bool, bool) {
     85     SkVector    after;
     86     afterUnitNormal.scale(radius, &after);
     87 
     88     if (!is_clockwise(beforeUnitNormal, afterUnitNormal)) {
     89         SkTSwap<SkPath*>(outer, inner);
     90         after.negate();
     91     }
     92 
     93     outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
     94     HandleInnerJoin(inner, pivot, after);
     95 }
     96 
     97 static void RoundJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
     98                         const SkPoint& pivot, const SkVector& afterUnitNormal,
     99                         SkScalar radius, SkScalar invMiterLimit, bool, bool) {
    100     SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
    101     AngleType   angleType = Dot2AngleType(dotProd);
    102 
    103     if (angleType == kNearlyLine_AngleType)
    104         return;
    105 
    106     SkVector            before = beforeUnitNormal;
    107     SkVector            after = afterUnitNormal;
    108     SkRotationDirection dir = kCW_SkRotationDirection;
    109 
    110     if (!is_clockwise(before, after)) {
    111         SkTSwap<SkPath*>(outer, inner);
    112         before.negate();
    113         after.negate();
    114         dir = kCCW_SkRotationDirection;
    115     }
    116 
    117     SkMatrix    matrix;
    118     matrix.setScale(radius, radius);
    119     matrix.postTranslate(pivot.fX, pivot.fY);
    120     SkConic conics[SkConic::kMaxConicsForArc];
    121     int count = SkConic::BuildUnitArc(before, after, dir, &matrix, conics);
    122     if (count > 0) {
    123         for (int i = 0; i < count; ++i) {
    124             outer->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
    125         }
    126         after.scale(radius);
    127         HandleInnerJoin(inner, pivot, after);
    128     }
    129 }
    130 
    131 #define kOneOverSqrt2   (0.707106781f)
    132 
    133 static void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
    134                         const SkPoint& pivot, const SkVector& afterUnitNormal,
    135                         SkScalar radius, SkScalar invMiterLimit,
    136                         bool prevIsLine, bool currIsLine) {
    137     // negate the dot since we're using normals instead of tangents
    138     SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
    139     AngleType   angleType = Dot2AngleType(dotProd);
    140     SkVector    before = beforeUnitNormal;
    141     SkVector    after = afterUnitNormal;
    142     SkVector    mid;
    143     SkScalar    sinHalfAngle;
    144     bool        ccw;
    145 
    146     if (angleType == kNearlyLine_AngleType) {
    147         return;
    148     }
    149     if (angleType == kNearly180_AngleType) {
    150         currIsLine = false;
    151         goto DO_BLUNT;
    152     }
    153 
    154     ccw = !is_clockwise(before, after);
    155     if (ccw) {
    156         SkTSwap<SkPath*>(outer, inner);
    157         before.negate();
    158         after.negate();
    159     }
    160 
    161     /*  Before we enter the world of square-roots and divides,
    162         check if we're trying to join an upright right angle
    163         (common case for stroking rectangles). If so, special case
    164         that (for speed an accuracy).
    165         Note: we only need to check one normal if dot==0
    166     */
    167     if (0 == dotProd && invMiterLimit <= kOneOverSqrt2) {
    168         mid = (before + after) * radius;
    169         goto DO_MITER;
    170     }
    171 
    172     /*  midLength = radius / sinHalfAngle
    173         if (midLength > miterLimit * radius) abort
    174         if (radius / sinHalf > miterLimit * radius) abort
    175         if (1 / sinHalf > miterLimit) abort
    176         if (1 / miterLimit > sinHalf) abort
    177         My dotProd is opposite sign, since it is built from normals and not tangents
    178         hence 1 + dot instead of 1 - dot in the formula
    179     */
    180     sinHalfAngle = SkScalarSqrt(SkScalarHalf(SK_Scalar1 + dotProd));
    181     if (sinHalfAngle < invMiterLimit) {
    182         currIsLine = false;
    183         goto DO_BLUNT;
    184     }
    185 
    186     // choose the most accurate way to form the initial mid-vector
    187     if (angleType == kSharp_AngleType) {
    188         mid.set(after.fY - before.fY, before.fX - after.fX);
    189         if (ccw) {
    190             mid.negate();
    191         }
    192     } else {
    193         mid.set(before.fX + after.fX, before.fY + after.fY);
    194     }
    195 
    196     mid.setLength(radius / sinHalfAngle);
    197 DO_MITER:
    198     if (prevIsLine) {
    199         outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY);
    200     } else {
    201         outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY);
    202     }
    203 
    204 DO_BLUNT:
    205     after.scale(radius);
    206     if (!currIsLine) {
    207         outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
    208     }
    209     HandleInnerJoin(inner, pivot, after);
    210 }
    211 
    212 /////////////////////////////////////////////////////////////////////////////
    213 
    214 SkStrokerPriv::CapProc SkStrokerPriv::CapFactory(SkPaint::Cap cap) {
    215     const SkStrokerPriv::CapProc gCappers[] = {
    216         ButtCapper, RoundCapper, SquareCapper
    217     };
    218 
    219     SkASSERT((unsigned)cap < SkPaint::kCapCount);
    220     return gCappers[cap];
    221 }
    222 
    223 SkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join) {
    224     const SkStrokerPriv::JoinProc gJoiners[] = {
    225         MiterJoiner, RoundJoiner, BluntJoiner
    226     };
    227 
    228     SkASSERT((unsigned)join < SkPaint::kJoinCount);
    229     return gJoiners[join];
    230 }
    231