Home | History | Annotate | Download | only in StrokePathRenderer
      1 /*
      2  * Copyright 2012 Google Inc.
      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 "GrStrokePathRenderer.h"
      9 
     10 #include "GrDrawTarget.h"
     11 #include "SkPath.h"
     12 #include "SkStrokeRec.h"
     13 
     14 static bool is_clockwise(const SkVector& before, const SkVector& after) {
     15     return before.cross(after) > 0;
     16 }
     17 
     18 enum IntersectionType {
     19     kNone_IntersectionType,
     20     kIn_IntersectionType,
     21     kOut_IntersectionType
     22 };
     23 
     24 static IntersectionType intersection(const SkPoint& p1, const SkPoint& p2,
     25                                      const SkPoint& p3, const SkPoint& p4,
     26                                      SkPoint& res) {
     27     // Store the values for fast access and easy
     28     // equations-to-code conversion
     29     SkScalar x1 = p1.x(), x2 = p2.x(), x3 = p3.x(), x4 = p4.x();
     30     SkScalar y1 = p1.y(), y2 = p2.y(), y3 = p3.y(), y4 = p4.y();
     31 
     32     SkScalar d = SkScalarMul(x1 - x2, y3 - y4) - SkScalarMul(y1 - y2, x3 - x4);
     33     // If d is zero, there is no intersection
     34     if (SkScalarNearlyZero(d)) {
     35         return kNone_IntersectionType;
     36     }
     37 
     38     // Get the x and y
     39     SkScalar pre  = SkScalarMul(x1, y2) - SkScalarMul(y1, x2),
     40              post = SkScalarMul(x3, y4) - SkScalarMul(y3, x4);
     41     // Compute the point of intersection
     42     res.set(SkScalarDiv(SkScalarMul(pre, x3 - x4) - SkScalarMul(x1 - x2, post), d),
     43             SkScalarDiv(SkScalarMul(pre, y3 - y4) - SkScalarMul(y1 - y2, post), d));
     44 
     45     // Check if the x and y coordinates are within both lines
     46     return (res.x() < GrMin(x1, x2) || res.x() > GrMax(x1, x2) ||
     47             res.x() < GrMin(x3, x4) || res.x() > GrMax(x3, x4) ||
     48             res.y() < GrMin(y1, y2) || res.y() > GrMax(y1, y2) ||
     49             res.y() < GrMin(y3, y4) || res.y() > GrMax(y3, y4)) ?
     50             kOut_IntersectionType : kIn_IntersectionType;
     51 }
     52 
     53 GrStrokePathRenderer::GrStrokePathRenderer() {
     54 }
     55 
     56 bool GrStrokePathRenderer::canDrawPath(const SkPath& path,
     57                                        const SkStrokeRec& stroke,
     58                                        const GrDrawTarget* target,
     59                                        bool antiAlias) const {
     60     // FIXME : put the proper condition once GrDrawTarget::isOpaque is implemented
     61     const bool isOpaque = true; // target->isOpaque();
     62 
     63     // FIXME : remove this requirement once we have AA circles and implement the
     64     //         circle joins/caps appropriately in the ::onDrawPath() function.
     65     const bool requiresAACircle = (stroke.getCap()  == SkPaint::kRound_Cap) ||
     66                                   (stroke.getJoin() == SkPaint::kRound_Join);
     67 
     68     // Indices being stored in uint16, we don't want to overflow the indices capacity
     69     static const int maxVBSize = 1 << 16;
     70     const int maxNbVerts = (path.countPoints() + 1) * 5;
     71 
     72     // Check that the path contains no curved lines, only straight lines
     73     static const uint32_t unsupportedMask = SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask;
     74 
     75     // Must not be filled nor hairline nor semi-transparent
     76     // Note : May require a check to path.isConvex() if AA is supported
     77     return ((stroke.getStyle() == SkStrokeRec::kStroke_Style) && (maxNbVerts < maxVBSize) &&
     78             !path.isInverseFillType() && isOpaque && !requiresAACircle && !antiAlias &&
     79             ((path.getSegmentMasks() & unsupportedMask) == 0));
     80 }
     81 
     82 bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath,
     83                                       const SkStrokeRec& stroke,
     84                                       GrDrawTarget* target,
     85                                       bool antiAlias) {
     86     if (origPath.isEmpty()) {
     87         return true;
     88     }
     89 
     90     SkScalar width = stroke.getWidth();
     91     if (width <= 0) {
     92         return false;
     93     }
     94 
     95     // Get the join type
     96     SkPaint::Join join = stroke.getJoin();
     97     SkScalar miterLimit = stroke.getMiter();
     98     SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit);
     99     if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) {
    100         // If the miter limit is small, treat it as a bevel join
    101         join = SkPaint::kBevel_Join;
    102     }
    103     const bool isMiter       = (join == SkPaint::kMiter_Join);
    104     const bool isBevel       = (join == SkPaint::kBevel_Join);
    105     SkScalar invMiterLimit   = isMiter ? SK_Scalar1 / miterLimit : 0;
    106     SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit);
    107 
    108     // Allocate vertices
    109     const int nbQuads     = origPath.countPoints() + 1; // Could be "-1" if path is not closed
    110     const int extraVerts  = isMiter || isBevel ? 1 : 0;
    111     const int maxVertexCount = nbQuads * (4 + extraVerts);
    112     const int maxIndexCount  = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle
    113     target->drawState()->setDefaultVertexAttribs();
    114     GrDrawTarget::AutoReleaseGeometry arg(target, maxVertexCount, maxIndexCount);
    115     if (!arg.succeeded()) {
    116         return false;
    117     }
    118     SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices());
    119     uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices());
    120     int vCount = 0, iCount = 0;
    121 
    122     // Transform the path into a list of triangles
    123     SkPath::Iter iter(origPath, false);
    124     SkPoint pts[4];
    125     const SkScalar radius = SkScalarMul(width, 0.5f);
    126     SkPoint *firstPt = verts, *lastPt = NULL;
    127     SkVector firstDir, dir;
    128     firstDir.set(0, 0);
    129     dir.set(0, 0);
    130     bool isOpen = true;
    131     for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) {
    132         switch(v) {
    133             case SkPath::kMove_Verb:
    134                 // This will already be handled as pts[0] of the 1st line
    135                 break;
    136             case SkPath::kClose_Verb:
    137                 isOpen = (lastPt == NULL);
    138                 break;
    139             case SkPath::kLine_Verb:
    140             {
    141                 SkVector v0 = dir;
    142                 dir = pts[1] - pts[0];
    143                 if (dir.setLength(radius)) {
    144                     SkVector dirT;
    145                     dirT.set(dir.fY, -dir.fX); // Get perpendicular direction
    146                     SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT,
    147                             l2a = pts[0]-dirT, l2b = pts[1]-dirT;
    148                     SkPoint miterPt[2];
    149                     bool useMiterPoint = false;
    150                     int idx0(-1), idx1(-1);
    151                     if (NULL == lastPt) {
    152                         firstDir = dir;
    153                     } else {
    154                         SkVector v1 = dir;
    155                         if (v0.normalize() && v1.normalize()) {
    156                             SkScalar dotProd = v0.dot(v1);
    157                             // No need for bevel or miter join if the angle
    158                             // is either 0 or 180 degrees
    159                             if (!SkScalarNearlyZero(dotProd + SK_Scalar1) &&
    160                                 !SkScalarNearlyZero(dotProd - SK_Scalar1)) {
    161                                 bool ccw = !is_clockwise(v0, v1);
    162                                 int offset = ccw ? 1 : 0;
    163                                 idx0 = vCount-2+offset;
    164                                 idx1 = vCount+offset;
    165                                 const SkPoint* pt0 = &(lastPt[offset]);
    166                                 const SkPoint* pt1 = ccw ? &l2a : &l1a;
    167                                 switch(join) {
    168                                     case SkPaint::kMiter_Join:
    169                                     {
    170                                         // *Note : Logic is from MiterJoiner
    171 
    172                                         // FIXME : Special case if we have a right angle ?
    173                                         // if (SkScalarNearlyZero(dotProd)) {...}
    174 
    175                                         SkScalar sinHalfAngleSq =
    176                                                 SkScalarHalf(SK_Scalar1 + dotProd);
    177                                         if (sinHalfAngleSq >= invMiterLimitSq) {
    178                                             // Find the miter point (or points if it is further
    179                                             // than the miter limit)
    180                                             const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1;
    181                                             if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) !=
    182                                                 kNone_IntersectionType) {
    183                                                 SkPoint miterPt0 = miterPt[0] - *pt0;
    184                                                 SkPoint miterPt1 = miterPt[0] - *pt1;
    185                                                 SkScalar sqDist0 = miterPt0.dot(miterPt0);
    186                                                 SkScalar sqDist1 = miterPt1.dot(miterPt1);
    187                                                 const SkScalar rSq =
    188                                                         SkScalarDiv(SkScalarMul(radius, radius),
    189                                                                     sinHalfAngleSq);
    190                                                 const SkScalar sqRLimit =
    191                                                         SkScalarMul(sqMiterLimit, rSq);
    192                                                 if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) {
    193                                                     if (sqDist1 > sqRLimit) {
    194                                                         v1.setLength(SkScalarSqrt(sqRLimit));
    195                                                         miterPt[1] = *pt1+v1;
    196                                                     } else {
    197                                                         miterPt[1] = miterPt[0];
    198                                                     }
    199                                                     if (sqDist0 > sqRLimit) {
    200                                                         v0.setLength(SkScalarSqrt(sqRLimit));
    201                                                         miterPt[0] = *pt0+v0;
    202                                                     }
    203                                                 } else {
    204                                                     miterPt[1] = miterPt[0];
    205                                                 }
    206                                                 useMiterPoint = true;
    207                                             }
    208                                         }
    209                                         if (useMiterPoint && (miterPt[1] == miterPt[0])) {
    210                                             break;
    211                                         }
    212                                     }
    213                                     default:
    214                                     case SkPaint::kBevel_Join:
    215                                     {
    216                                         // Note : This currently causes some overdraw where both
    217                                         //        lines initially intersect. We'd need to add
    218                                         //        another line intersection check here if the
    219                                         //        overdraw becomes an issue instead of using the
    220                                         //        current point directly.
    221 
    222                                         // Add center point
    223                                         *verts++ = pts[0]; // Use current point directly
    224                                         // This idx is passed the current point so increment it
    225                                         ++idx1;
    226                                         // Add center triangle
    227                                         *idxs++ = idx0;
    228                                         *idxs++ = vCount;
    229                                         *idxs++ = idx1;
    230                                         vCount++;
    231                                         iCount += 3;
    232                                     }
    233                                     break;
    234                                 }
    235                             }
    236                         }
    237                     }
    238                     *verts++ = l1a;
    239                     *verts++ = l2a;
    240                     lastPt   = verts;
    241                     *verts++ = l1b;
    242                     *verts++ = l2b;
    243 
    244                     if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) {
    245                         firstPt[idx0] = miterPt[0];
    246                         firstPt[idx1] = miterPt[1];
    247                     }
    248 
    249                     // 1st triangle
    250                     *idxs++  = vCount+0;
    251                     *idxs++  = vCount+2;
    252                     *idxs++  = vCount+1;
    253                     // 2nd triangle
    254                     *idxs++  = vCount+1;
    255                     *idxs++  = vCount+2;
    256                     *idxs++  = vCount+3;
    257 
    258                     vCount += 4;
    259                     iCount += 6;
    260                 }
    261             }
    262                 break;
    263             case SkPath::kQuad_Verb:
    264             case SkPath::kCubic_Verb:
    265                 SkDEBUGFAIL("Curves not supported!");
    266             default:
    267                 // Unhandled cases
    268                 SkASSERT(false);
    269         }
    270     }
    271 
    272     if (isOpen) {
    273         // Add caps
    274         switch (stroke.getCap()) {
    275             case SkPaint::kSquare_Cap:
    276                 firstPt[0] -= firstDir;
    277                 firstPt[1] -= firstDir;
    278                 lastPt [0] += dir;
    279                 lastPt [1] += dir;
    280                 break;
    281             case SkPaint::kRound_Cap:
    282                 SkDEBUGFAIL("Round caps not supported!");
    283             default: // No cap
    284                 break;
    285         }
    286     }
    287 
    288     SkASSERT(vCount <= maxVertexCount);
    289     SkASSERT(iCount <= maxIndexCount);
    290 
    291     if (vCount > 0) {
    292         target->drawIndexed(kTriangles_GrPrimitiveType,
    293                             0,        // start vertex
    294                             0,        // start index
    295                             vCount,
    296                             iCount);
    297     }
    298 
    299     return true;
    300 }
    301