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