Example #1
0
DEF_TEST(PathMeasure, reporter) {
    SkPath  path;

    path.moveTo(0, 0);
    path.lineTo(SK_Scalar1, 0);
    path.lineTo(SK_Scalar1, SK_Scalar1);
    path.lineTo(0, SK_Scalar1);

    SkPathMeasure   meas(path, true);
    SkScalar        length = meas.getLength();
    SkASSERT(length == SK_Scalar1*4);

    path.reset();
    path.moveTo(0, 0);
    path.lineTo(SK_Scalar1*3, SK_Scalar1*4);
    meas.setPath(&path, false);
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1*5);

    path.reset();
    path.addCircle(0, 0, SK_Scalar1);
    meas.setPath(&path, true);
    length = meas.getLength();
//    SkDebugf("circle arc-length = %g\n", length);

    // Test the behavior following a close not followed by a move.
    path.reset();
    path.lineTo(SK_Scalar1, 0);
    path.lineTo(SK_Scalar1, SK_Scalar1);
    path.lineTo(0, SK_Scalar1);
    path.close();
    path.lineTo(-SK_Scalar1, 0);
    meas.setPath(&path, false);
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1 * 4);
    meas.nextContour();
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1);
    SkPoint position;
    SkVector tangent;
    REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            -SK_ScalarHalf,
                            0.0001f));
    REPORTER_ASSERT(reporter, position.fY == 0);
    REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);

    // Test degenerate paths
    path.reset();
    path.moveTo(0, 0);
    path.lineTo(0, 0);
    path.lineTo(SK_Scalar1, 0);
    path.quadTo(SK_Scalar1, 0, SK_Scalar1, 0);
    path.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1 * 2);
    path.cubicTo(SK_Scalar1, SK_Scalar1 * 2,
                 SK_Scalar1, SK_Scalar1 * 2,
                 SK_Scalar1, SK_Scalar1 * 2);
    path.cubicTo(SK_Scalar1*2, SK_Scalar1 * 2,
                 SK_Scalar1*3, SK_Scalar1 * 2,
                 SK_Scalar1*4, SK_Scalar1 * 2);
    meas.setPath(&path, false);
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1 * 6);
    REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            SK_ScalarHalf,
                            0.0001f));
    REPORTER_ASSERT(reporter, position.fY == 0);
    REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);
    REPORTER_ASSERT(reporter, meas.getPosTan(2.5f, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX, SK_Scalar1, 0.0001f));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fY, 1.5f));
    REPORTER_ASSERT(reporter, tangent.fX == 0);
    REPORTER_ASSERT(reporter, tangent.fY == SK_Scalar1);
    REPORTER_ASSERT(reporter, meas.getPosTan(4.5f, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            2.5f,
                            0.0001f));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fY,
                            2.0f,
                            0.0001f));
    REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);

    path.reset();
    path.moveTo(0, 0);
    path.lineTo(SK_Scalar1, 0);
    path.moveTo(SK_Scalar1, SK_Scalar1);
    path.moveTo(SK_Scalar1 * 2, SK_Scalar1 * 2);
    path.lineTo(SK_Scalar1, SK_Scalar1 * 2);
    meas.setPath(&path, false);
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1);
    REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            SK_ScalarHalf,
                            0.0001f));
    REPORTER_ASSERT(reporter, position.fY == 0);
    REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);
    meas.nextContour();
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1);
    REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            1.5f,
                            0.0001f));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fY,
                            2.0f,
                            0.0001f));
    REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);

    test_small_segment();
    test_small_segment2();
    test_small_segment3();
}
Example #2
0
void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
    fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
    fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
}
Example #3
0
void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
                            const SkPoint& pt3) {
    bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
    bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
    bool    degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);

    if (degenerateAB + degenerateBC + degenerateCD >= 2) {
        this->lineTo(pt3);
        return;
    }

    SkVector    normalAB, unitAB, normalCD, unitCD;

    // find the first tangent (which might be pt1 or pt2
    {
        const SkPoint*  nextPt = &pt1;
        if (degenerateAB)
            nextPt = &pt2;
        this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
    }

    {
        SkPoint pts[4], tmp[13];
        int         i, count;
        SkVector    n, u;
        SkScalar    tValues[3];

        pts[0] = fPrevPt;
        pts[1] = pt1;
        pts[2] = pt2;
        pts[3] = pt3;

#if 1
        count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
#else
        count = 1;
        memcpy(tmp, pts, 4 * sizeof(SkPoint));
#endif
        n = normalAB;
        u = unitAB;
        for (i = 0; i < count; i++) {
            this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
                           kMaxCubicSubdivide);
            if (i == count - 1) {
                break;
            }
            n = normalCD;
            u = unitCD;

        }

        // check for too pinchy
        for (i = 1; i < count; i++) {
            SkPoint p;
            SkVector    v, c;

            SkEvalCubicAt(pts, tValues[i - 1], &p, &v, &c);

            SkScalar    dot = SkPoint::DotProduct(c, c);
            v.scale(SkScalarInvert(dot));

            if (SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY)) {
                fExtra.addCircle(p.fX, p.fY, fRadius, SkPath::kCW_Direction);
            }
        }

    }

    this->postJoinTo(pt3, normalCD, unitCD);
}
Example #4
0
static SkPath testPath() {
    SkPath path;
    path.addRect(SkRect::MakeXYWH(SkIntToScalar(0), SkIntToScalar(0),
                                  SkIntToScalar(2), SkIntToScalar(1)));
    return path;
}
Example #5
0
void SkScalerContext::internalGetPath(const SkGlyph& glyph, SkPath* fillPath,
                                  SkPath* devPath, SkMatrix* fillToDevMatrix) {
    SkPath  path;
    generatePath(glyph, &path);

    if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
        SkFixed dx = glyph.getSubXFixed();
        SkFixed dy = glyph.getSubYFixed();
        if (dx | dy) {
            path.offset(SkFixedToScalar(dx), SkFixedToScalar(dy));
        }
    }

    if (fRec.fFrameWidth > 0 || fPathEffect != NULL) {
        // need the path in user-space, with only the point-size applied
        // so that our stroking and effects will operate the same way they
        // would if the user had extracted the path themself, and then
        // called drawPath
        SkPath      localPath;
        SkMatrix    matrix, inverse;

        fRec.getMatrixFrom2x2(&matrix);
        if (!matrix.invert(&inverse)) {
            // assume fillPath and devPath are already empty.
            return;
        }
        path.transform(inverse, &localPath);
        // now localPath is only affected by the paint settings, and not the canvas matrix

        SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);

        if (fRec.fFrameWidth > 0) {
            rec.setStrokeStyle(fRec.fFrameWidth,
                               SkToBool(fRec.fFlags & kFrameAndFill_Flag));
            // glyphs are always closed contours, so cap type is ignored,
            // so we just pass something.
            rec.setStrokeParams(SkPaint::kButt_Cap,
                                (SkPaint::Join)fRec.fStrokeJoin,
                                fRec.fMiterLimit);
        }

        if (fPathEffect) {
            SkPath effectPath;
            if (fPathEffect->filterPath(&effectPath, localPath, &rec, NULL)) {
                localPath.swap(effectPath);
            }
        }

        if (rec.needToApply()) {
            SkPath strokePath;
            if (rec.applyToPath(&strokePath, localPath)) {
                localPath.swap(strokePath);
            }
        }

        // now return stuff to the caller
        if (fillToDevMatrix) {
            *fillToDevMatrix = matrix;
        }
        if (devPath) {
            localPath.transform(matrix, devPath);
        }
        if (fillPath) {
            fillPath->swap(localPath);
        }
    } else {   // nothing tricky to do
        if (fillToDevMatrix) {
            fillToDevMatrix->reset();
        }
        if (devPath) {
            if (fillPath == NULL) {
                devPath->swap(path);
            } else {
                *devPath = path;
            }
        }

        if (fillPath) {
            fillPath->swap(path);
        }
    }

    if (devPath) {
        devPath->updateBoundsCache();
    }
    if (fillPath) {
        fillPath->updateBoundsCache();
    }
}
Example #6
0
void GrGLPath::InitPathObject(GrGLGpu* gpu,
                              GrGLuint pathID,
                              const SkPath& skPath,
                              const GrStrokeInfo& stroke) {
    SkASSERT(!stroke.isDashed());
    if (!skPath.isEmpty()) {
        int verbCnt = skPath.countVerbs();
        int pointCnt = skPath.countPoints();
        int minCoordCnt = pointCnt * 2;

        SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt);
        SkSTArray<16, GrGLfloat, true> pathCoords(minCoordCnt);

        SkDEBUGCODE(int numCoords = 0);

        if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) {
            // This branch does type punning, converting SkPoint* to GrGLfloat*.
            SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, sk_point_not_two_floats);
            // This branch does not convert with SkScalarToFloat.
#ifndef SK_SCALAR_IS_FLOAT
#error Need SK_SCALAR_IS_FLOAT.
#endif
            pathCommands.resize_back(verbCnt);
            pathCoords.resize_back(minCoordCnt);
            skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt);
            skPath.getVerbs(&pathCommands[0], verbCnt);
            for (int i = 0; i < verbCnt; ++i) {
                SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]);
                pathCommands[i] = verb_to_gl_path_cmd(v);
                SkDEBUGCODE(numCoords += num_coords(v));
            }
        } else {
            SkPoint points[4];
            SkPath::RawIter iter(skPath);
            SkPath::Verb verb;
            while ((verb = iter.next(points)) != SkPath::kDone_Verb) {
                pathCommands.push_back(verb_to_gl_path_cmd(verb));
                GrGLfloat coords[6];
                int coordsForVerb;
                switch (verb) {
                    case SkPath::kMove_Verb:
                        points_to_coords(points, 0, 1, coords);
                        coordsForVerb = 2;
                        break;
                    case SkPath::kLine_Verb:
                        points_to_coords(points, 1, 1, coords);
                        coordsForVerb = 2;
                        break;
                    case SkPath::kConic_Verb:
                        points_to_coords(points, 1, 2, coords);
                        coords[4] = SkScalarToFloat(iter.conicWeight());
                        coordsForVerb = 5;
                        break;
                    case SkPath::kQuad_Verb:
                        points_to_coords(points, 1, 2, coords);
                        coordsForVerb = 4;
                        break;
                    case SkPath::kCubic_Verb:
                        points_to_coords(points, 1, 3, coords);
                        coordsForVerb = 6;
                        break;
                    case SkPath::kClose_Verb:
                        continue;
                    default:
                        SkASSERT(false);  // Not reached.
                        continue;
                }
                SkDEBUGCODE(numCoords += num_coords(verb));
                pathCoords.push_back_n(coordsForVerb, coords);
            }
        }

        SkASSERT(verbCnt == pathCommands.count());
        SkASSERT(numCoords == pathCoords.count());

        GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0],
                   pathCoords.count(), GR_GL_FLOAT, &pathCoords[0]));
    } else {
        GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, NULL, 0, GR_GL_FLOAT, NULL));
    }

    if (stroke.needToApply()) {
        SkASSERT(!stroke.isHairlineStyle());
        GR_GL_CALL(gpu->glInterface(),
            PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth())));
        GR_GL_CALL(gpu->glInterface(),
            PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter())));
        GrGLenum join = join_to_gl_join(stroke.getJoin());
        GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join));
        GrGLenum cap = cap_to_gl_cap(stroke.getCap());
        GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap));
        GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f));
    }
}
Example #7
0
static bool get_geometry(const SkPath& path, const SkMatrix& m, PLSVertices& triVertices,
                         PLSVertices& quadVertices, GrResourceProvider* resourceProvider,
                         SkRect bounds) {
    SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance;
    SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, m, bounds);
    int contourCnt;
    int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt, tol);
    if (maxPts <= 0) {
        return 0;
    }
    SkPath linesOnlyPath;
    linesOnlyPath.setFillType(path.getFillType());
    SkSTArray<15, SkPoint, true> quadPoints;
    SkPath::Iter iter(path, true);
    bool done = false;
    while (!done) {
        SkPoint pts[4];
        SkPath::Verb verb = iter.next(pts);
        switch (verb) {
            case SkPath::kMove_Verb:
                SkASSERT(quadPoints.count() % 3 == 0);
                for (int i = 0; i < quadPoints.count(); i += 3) {
                    add_quad(&quadPoints[i], quadVertices);
                }
                quadPoints.reset();
                m.mapPoints(&pts[0], 1);
                linesOnlyPath.moveTo(pts[0]);
                break;
            case SkPath::kLine_Verb:
                m.mapPoints(&pts[1], 1);
                linesOnlyPath.lineTo(pts[1]);
                break;
            case SkPath::kQuad_Verb:
                m.mapPoints(pts, 3);
                linesOnlyPath.lineTo(pts[2]);
                quadPoints.push_back(pts[0]);
                quadPoints.push_back(pts[1]);
                quadPoints.push_back(pts[2]);
                break;
            case SkPath::kCubic_Verb: {
                m.mapPoints(pts, 4);
                SkSTArray<15, SkPoint, true> quads;
                GrPathUtils::convertCubicToQuads(pts, kCubicTolerance, &quads);
                int count = quads.count();
                for (int q = 0; q < count; q += 3) {
                    linesOnlyPath.lineTo(quads[q + 2]);
                    quadPoints.push_back(quads[q]);
                    quadPoints.push_back(quads[q + 1]);
                    quadPoints.push_back(quads[q + 2]);
                }
                break;
            }
            case SkPath::kConic_Verb: {
                m.mapPoints(pts, 3);
                SkScalar weight = iter.conicWeight();
                SkAutoConicToQuads converter;
                const SkPoint* quads = converter.computeQuads(pts, weight, kConicTolerance);
                int count = converter.countQuads();
                for (int i = 0; i < count; ++i) {
                    linesOnlyPath.lineTo(quads[2 * i + 2]);
                    quadPoints.push_back(quads[2 * i]);
                    quadPoints.push_back(quads[2 * i + 1]);
                    quadPoints.push_back(quads[2 * i + 2]);
                }
                break;
            }
            case SkPath::kClose_Verb:
                linesOnlyPath.close();
                break;
            case SkPath::kDone_Verb:
                done = true;
                break;
            default: SkASSERT(false);
        }
    }
    SkASSERT(quadPoints.count() % 3 == 0);
    for (int i = 0; i < quadPoints.count(); i += 3) {
        add_quad(&quadPoints[i], quadVertices);
    }

    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
    GrUniqueKey key;
    GrUniqueKey::Builder builder(&key, kDomain, 2);
    builder[0] = path.getGenerationID();
    builder[1] = path.getFillType();
    builder.finish();
    GrTessellator::WindingVertex* windingVertices;
    int triVertexCount = GrTessellator::PathToVertices(linesOnlyPath, 0, bounds, &windingVertices);
    if (triVertexCount > 0) {
        for (int i = 0; i < triVertexCount; i += 3) {
            SkPoint p1 = windingVertices[i].fPos;
            SkPoint p2 = windingVertices[i + 1].fPos;
            SkPoint p3 = windingVertices[i + 2].fPos;
            int winding = windingVertices[i].fWinding;
            SkASSERT(windingVertices[i + 1].fWinding == winding);
            SkASSERT(windingVertices[i + 2].fWinding == winding);
            SkScalar cross = (p2 - p1).cross(p3 - p1);
            SkPoint bloated[3] = { p1, p2, p3 };
            if (cross < 0.0f) {
                SkTSwap(p1, p3);
            }
            if (bloat_tri(bloated)) {
                triVertices.push_back({ bloated[0], p1, p2, p3, winding });
                triVertices.push_back({ bloated[1], p1, p2, p3, winding });
                triVertices.push_back({ bloated[2], p1, p2, p3, winding });
            }
            else {
                SkScalar minX = SkTMin(p1.fX, SkTMin(p2.fX, p3.fX)) - 1.0f;
                SkScalar minY = SkTMin(p1.fY, SkTMin(p2.fY, p3.fY)) - 1.0f;
                SkScalar maxX = SkTMax(p1.fX, SkTMax(p2.fX, p3.fX)) + 1.0f;
                SkScalar maxY = SkTMax(p1.fY, SkTMax(p2.fY, p3.fY)) + 1.0f;
                triVertices.push_back({ { minX, minY }, p1, p2, p3, winding });
                triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding });
                triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding });
                triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding });
                triVertices.push_back({ { maxX, maxY }, p1, p2, p3, winding });
                triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding });
            }
        }
        delete[] windingVertices;
    }
    return triVertexCount > 0 || quadVertices.count() > 0;
}
Example #8
0
void SkScalerContext::internalGetPath(const SkGlyph& glyph, SkPath* fillPath, SkPath* devPath, SkMatrix* fillToDevMatrix)
{
    SkPath  path;

    this->getGlyphContext(glyph)->generatePath(glyph, &path);

    if (fRec.fFrameWidth > 0 || fPathEffect != NULL)
    {
        // need the path in user-space, with only the point-size applied
        // so that our stroking and effects will operate the same way they
        // would if the user had extracted the path themself, and then
        // called drawPath
        SkPath      localPath;
        SkMatrix    matrix, inverse;

        fRec.getMatrixFrom2x2(&matrix);
        matrix.invert(&inverse);
        path.transform(inverse, &localPath);
        // now localPath is only affected by the paint settings, and not the canvas matrix

        SkScalar width = fRec.fFrameWidth;

        if (fPathEffect)
        {
            SkPath effectPath;

            if (fPathEffect->filterPath(&effectPath, localPath, &width))
                localPath.swap(effectPath);
        }

        if (width > 0)
        {
            SkStroke    stroker;
            SkPath      outline;

            stroker.setWidth(width);
            stroker.setMiterLimit(fRec.fMiterLimit);
            stroker.setJoin((SkPaint::Join)fRec.fStrokeJoin);
            stroker.setDoFill(SkToBool(fRec.fFlags & kFrameAndFill_Flag));
            stroker.strokePath(localPath, &outline);
            localPath.swap(outline);
        }

        // now return stuff to the caller
        if (fillToDevMatrix)
            *fillToDevMatrix = matrix;

        if (devPath)
            localPath.transform(matrix, devPath);

        if (fillPath)
            fillPath->swap(localPath);
    }
    else    // nothing tricky to do
    {
        if (fillToDevMatrix)
            fillToDevMatrix->reset();

        if (devPath)
        {
            if (fillPath == NULL)
                devPath->swap(path);
            else
                *devPath = path;
        }

        if (fillPath)
            fillPath->swap(path);
    }

    if (devPath)
        devPath->updateBoundsCache();
    if (fillPath)
        fillPath->updateBoundsCache();
}
Example #9
0
// Currently asPoints is more restrictive then it needs to be. In the future
// we need to:
//      allow kRound_Cap capping (could allow rotations in the matrix with this)
//      allow paths to be returned
bool SkDashImpl::asPoints(PointData* results, const SkPath& src, const SkStrokeRec& rec,
                          const SkMatrix& matrix, const SkRect* cullRect) const {
    // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out
    if (0 >= rec.getWidth()) {
        return false;
    }

    // TODO: this next test could be eased up. We could allow any number of
    // intervals as long as all the ons match and all the offs match.
    // Additionally, they do not necessarily need to be integers.
    // We cannot allow arbitrary intervals since we want the returned points
    // to be uniformly sized.
    if (fCount != 2 ||
        !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) ||
        !SkScalarIsInt(fIntervals[0]) ||
        !SkScalarIsInt(fIntervals[1])) {
        return false;
    }

    SkPoint pts[2];

    if (!src.isLine(pts)) {
        return false;
    }

    // TODO: this test could be eased up to allow circles
    if (SkPaint::kButt_Cap != rec.getCap()) {
        return false;
    }

    // TODO: this test could be eased up for circles. Rotations could be allowed.
    if (!matrix.rectStaysRect()) {
        return false;
    }

    // See if the line can be limited to something plausible.
    if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) {
        return false;
    }

    SkScalar length = SkPoint::Distance(pts[1], pts[0]);

    SkVector tangent = pts[1] - pts[0];
    if (tangent.isZero()) {
        return false;
    }

    tangent.scale(SkScalarInvert(length));

    // TODO: make this test for horizontal & vertical lines more robust
    bool isXAxis = true;
    if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) ||
        SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) {
        results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth()));
    } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) ||
               SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) {
        results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0]));
        isXAxis = false;
    } else if (SkPaint::kRound_Cap != rec.getCap()) {
        // Angled lines don't have axis-aligned boxes.
        return false;
    }

    if (results) {
        results->fFlags = 0;
        SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength);

        if (SkPaint::kRound_Cap == rec.getCap()) {
            results->fFlags |= PointData::kCircles_PointFlag;
        }

        results->fNumPoints = 0;
        SkScalar len2 = length;
        if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
            SkASSERT(len2 >= clampedInitialDashLength);
            if (0 == fInitialDashIndex) {
                if (clampedInitialDashLength > 0) {
                    if (clampedInitialDashLength >= fIntervals[0]) {
                        ++results->fNumPoints;  // partial first dash
                    }
                    len2 -= clampedInitialDashLength;
                }
                len2 -= fIntervals[1];  // also skip first space
                if (len2 < 0) {
                    len2 = 0;
                }
            } else {
                len2 -= clampedInitialDashLength; // skip initial partial empty
            }
        }
        // Too many midpoints can cause results->fNumPoints to overflow or
        // otherwise cause the results->fPoints allocation below to OOM.
        // Cap it to a sane value.
        SkScalar numIntervals = len2 / fIntervalLength;
        if (!SkScalarIsFinite(numIntervals) || numIntervals > SkDashPath::kMaxDashCount) {
            return false;
        }
        int numMidPoints = SkScalarFloorToInt(numIntervals);
        results->fNumPoints += numMidPoints;
        len2 -= numMidPoints * fIntervalLength;
        bool partialLast = false;
        if (len2 > 0) {
            if (len2 < fIntervals[0]) {
                partialLast = true;
            } else {
                ++numMidPoints;
                ++results->fNumPoints;
            }
        }

        results->fPoints = new SkPoint[results->fNumPoints];

        SkScalar    distance = 0;
        int         curPt = 0;

        if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
            SkASSERT(clampedInitialDashLength <= length);

            if (0 == fInitialDashIndex) {
                if (clampedInitialDashLength > 0) {
                    // partial first block
                    SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
                    SkScalar x = pts[0].fX + tangent.fX * SkScalarHalf(clampedInitialDashLength);
                    SkScalar y = pts[0].fY + tangent.fY * SkScalarHalf(clampedInitialDashLength);
                    SkScalar halfWidth, halfHeight;
                    if (isXAxis) {
                        halfWidth = SkScalarHalf(clampedInitialDashLength);
                        halfHeight = SkScalarHalf(rec.getWidth());
                    } else {
                        halfWidth = SkScalarHalf(rec.getWidth());
                        halfHeight = SkScalarHalf(clampedInitialDashLength);
                    }
                    if (clampedInitialDashLength < fIntervals[0]) {
                        // This one will not be like the others
                        results->fFirst.addRect(x - halfWidth, y - halfHeight,
                                                x + halfWidth, y + halfHeight);
                    } else {
                        SkASSERT(curPt < results->fNumPoints);
                        results->fPoints[curPt].set(x, y);
                        ++curPt;
                    }

                    distance += clampedInitialDashLength;
                }

                distance += fIntervals[1];  // skip over the next blank block too
            } else {
                distance += clampedInitialDashLength;
            }
        }

        if (0 != numMidPoints) {
            distance += SkScalarHalf(fIntervals[0]);

            for (int i = 0; i < numMidPoints; ++i) {
                SkScalar x = pts[0].fX + tangent.fX * distance;
                SkScalar y = pts[0].fY + tangent.fY * distance;

                SkASSERT(curPt < results->fNumPoints);
                results->fPoints[curPt].set(x, y);
                ++curPt;

                distance += fIntervalLength;
            }

            distance -= SkScalarHalf(fIntervals[0]);
        }

        if (partialLast) {
            // partial final block
            SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
            SkScalar temp = length - distance;
            SkASSERT(temp < fIntervals[0]);
            SkScalar x = pts[0].fX + tangent.fX * (distance + SkScalarHalf(temp));
            SkScalar y = pts[0].fY + tangent.fY * (distance + SkScalarHalf(temp));
            SkScalar halfWidth, halfHeight;
            if (isXAxis) {
                halfWidth = SkScalarHalf(temp);
                halfHeight = SkScalarHalf(rec.getWidth());
            } else {
                halfWidth = SkScalarHalf(rec.getWidth());
                halfHeight = SkScalarHalf(temp);
            }
            results->fLast.addRect(x - halfWidth, y - halfHeight,
                                   x + halfWidth, y + halfHeight);
        }

        SkASSERT(curPt == results->fNumPoints);
    }

    return true;
}
Example #10
0
void GrCCFiller::PathInfo::tessellateFan(const GrCCFillGeometry& geometry, int verbsIdx,
                                         int ptsIdx, const SkIRect& clippedDevIBounds,
                                         PrimitiveTallies* newTriangleCounts) {
    using Verb = GrCCFillGeometry::Verb;
    SkASSERT(-1 == fFanTessellationCount);
    SkASSERT(!fFanTessellation);

    const SkTArray<Verb, true>& verbs = geometry.verbs();
    const SkTArray<SkPoint, true>& pts = geometry.points();

    newTriangleCounts->fTriangles =
            newTriangleCounts->fWeightedTriangles = 0;

    // Build an SkPath of the Redbook fan. We use "winding" fill type right now because we are
    // producing a coverage count, and must fill in every region that has non-zero wind. The
    // path processor will convert coverage count to the appropriate fill type later.
    SkPath fan;
    fan.setFillType(SkPath::kWinding_FillType);
    SkASSERT(Verb::kBeginPath == verbs[verbsIdx]);
    for (int i = verbsIdx + 1; i < verbs.count(); ++i) {
        switch (verbs[i]) {
            case Verb::kBeginPath:
                SK_ABORT("Invalid GrCCFillGeometry");
                continue;

            case Verb::kBeginContour:
                fan.moveTo(pts[ptsIdx++]);
                continue;

            case Verb::kLineTo:
                fan.lineTo(pts[ptsIdx++]);
                continue;

            case Verb::kMonotonicQuadraticTo:
            case Verb::kMonotonicConicTo:
                fan.lineTo(pts[ptsIdx + 1]);
                ptsIdx += 2;
                continue;

            case Verb::kMonotonicCubicTo:
                fan.lineTo(pts[ptsIdx + 2]);
                ptsIdx += 3;
                continue;

            case Verb::kEndClosedContour:
            case Verb::kEndOpenContour:
                fan.close();
                continue;
        }
    }

    GrTessellator::WindingVertex* vertices = nullptr;
    fFanTessellationCount =
            GrTessellator::PathToVertices(fan, std::numeric_limits<float>::infinity(),
                                          SkRect::Make(clippedDevIBounds), &vertices);
    if (fFanTessellationCount <= 0) {
        SkASSERT(0 == fFanTessellationCount);
        SkASSERT(nullptr == vertices);
        return;
    }

    SkASSERT(0 == fFanTessellationCount % 3);
    for (int i = 0; i < fFanTessellationCount; i += 3) {
        int tessWinding = vertices[i].fWinding;
        SkASSERT(tessWinding == vertices[i + 1].fWinding);
        SkASSERT(tessWinding == vertices[i + 2].fWinding);

        // Ensure this triangle's points actually wind in the same direction as tessWinding.
        // CCPR shaders use the sign of wind to determine which direction to bloat, so even for
        // "wound" triangles the winding sign and point ordering need to agree.
        float ax = vertices[i].fPos.fX - vertices[i + 1].fPos.fX;
        float ay = vertices[i].fPos.fY - vertices[i + 1].fPos.fY;
        float bx = vertices[i].fPos.fX - vertices[i + 2].fPos.fX;
        float by = vertices[i].fPos.fY - vertices[i + 2].fPos.fY;
        float wind = ax*by - ay*bx;
        if ((wind > 0) != (-tessWinding > 0)) { // Tessellator has opposite winding sense.
            std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos);
        }

        if (1 == abs(tessWinding)) {
            ++newTriangleCounts->fTriangles;
        } else {
            ++newTriangleCounts->fWeightedTriangles;
        }
    }

    fFanTessellation.reset(vertices);
}
Example #11
0
void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts,
                                      GrScissorTest scissorTest, const SkIRect& clippedDevIBounds,
                                      const SkIVector& devToAtlasOffset) {
    SkASSERT(!fInstanceBuffer);  // Can't call after prepareToDraw().
    SkASSERT(!path.isEmpty());

    int currPathPointsIdx = fGeometry.points().count();
    int currPathVerbsIdx = fGeometry.verbs().count();
    PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies();

    fGeometry.beginPath();

    const float* conicWeights = SkPathPriv::ConicWeightData(path);
    int ptsIdx = 0;
    int conicWeightsIdx = 0;
    bool insideContour = false;

    for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
        switch (verb) {
            case SkPath::kMove_Verb:
                if (insideContour) {
                    currPathPrimitiveCounts += fGeometry.endContour();
                }
                fGeometry.beginContour(deviceSpacePts[ptsIdx]);
                ++ptsIdx;
                insideContour = true;
                continue;
            case SkPath::kClose_Verb:
                if (insideContour) {
                    currPathPrimitiveCounts += fGeometry.endContour();
                }
                insideContour = false;
                continue;
            case SkPath::kLine_Verb:
                fGeometry.lineTo(&deviceSpacePts[ptsIdx - 1]);
                ++ptsIdx;
                continue;
            case SkPath::kQuad_Verb:
                fGeometry.quadraticTo(&deviceSpacePts[ptsIdx - 1]);
                ptsIdx += 2;
                continue;
            case SkPath::kCubic_Verb:
                fGeometry.cubicTo(&deviceSpacePts[ptsIdx - 1]);
                ptsIdx += 3;
                continue;
            case SkPath::kConic_Verb:
                fGeometry.conicTo(&deviceSpacePts[ptsIdx - 1], conicWeights[conicWeightsIdx]);
                ptsIdx += 2;
                ++conicWeightsIdx;
                continue;
            default:
                SK_ABORT("Unexpected path verb.");
        }
    }
    SkASSERT(ptsIdx == path.countPoints());
    SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path));

    if (insideContour) {
        currPathPrimitiveCounts += fGeometry.endContour();
    }

    fPathInfos.emplace_back(scissorTest, devToAtlasOffset);

    // Tessellate fans from very large and/or simple paths, in order to reduce overdraw.
    int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 1;
    int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N.
    int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width();
    if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100.
        fPathInfos.back().tessellateFan(fGeometry, currPathVerbsIdx, currPathPointsIdx,
                                        clippedDevIBounds, &currPathPrimitiveCounts);
    }

    fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts;

    if (GrScissorTest::kEnabled == scissorTest) {
        fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled],
                                          clippedDevIBounds.makeOffset(devToAtlasOffset.fX,
                                                                       devToAtlasOffset.fY)};
    }
}
Example #12
0
SkPath create_concave_path(const SkPoint& offset) {
    SkPath concavePath;
    concavePath.moveTo(kMin, kMin);
    concavePath.lineTo(kMid, 105.0f);
    concavePath.lineTo(kMax, kMin);
    concavePath.lineTo(295.0f, kMid);
    concavePath.lineTo(kMax, kMax);
    concavePath.lineTo(kMid, 295.0f);
    concavePath.lineTo(kMin, kMax);
    concavePath.lineTo(105.0f, kMid);
    concavePath.close();

    concavePath.offset(offset.fX, offset.fY);
    return concavePath;
}
// A quad which becomes NaN when interpolated.
static SkPath create_path_22() {
    SkPath path;
    path.moveTo(-5.71889e+13f, 1.36759e+09f);
    path.quadTo(2.45472e+19f, -3.12406e+15f, -2.19589e+18f, 2.79462e+14f);
    return path;
}
// Exercises the case where an edge becomes collinear with *two* of its
// adjacent neighbour edges after splitting.
// This is a reduction from
// http://mooooo.ooo/chebyshev-sine-approximation/horner_ulp.svg
static SkPath create_path_19() {
    SkPath path;
    path.moveTo(  351.99298095703125,         348.23046875);
    path.lineTo(  351.91876220703125,         347.33984375);
    path.lineTo(  351.91876220703125,          346.1953125);
    path.lineTo(  351.90313720703125,           347.734375);
    path.lineTo(  351.90313720703125,          346.1328125);
    path.lineTo(  351.87579345703125,         347.93359375);
    path.lineTo(  351.87579345703125,           345.484375);
    path.lineTo(  351.86407470703125,          347.7890625);
    path.lineTo(  351.86407470703125,          346.2109375);
    path.lineTo(  351.84844970703125,   347.63763427734375);
    path.lineTo(  351.84454345703125,   344.19232177734375);
    path.lineTo(  351.78204345703125,    346.9483642578125);
    path.lineTo( 351.758636474609375,      347.18310546875);
    path.lineTo(  351.75469970703125,               346.75);
    path.lineTo(  351.75469970703125,            345.46875);
    path.lineTo(         352.5546875,            345.46875);
    path.lineTo(        352.55078125,         347.01953125);
    path.lineTo(  351.75079345703125,   347.02313232421875);
    path.lineTo(  351.74688720703125,   346.15203857421875);
    path.lineTo(  351.74688720703125,  347.646148681640625);
    path.lineTo(         352.5390625,         346.94140625);
    path.lineTo(  351.73907470703125,   346.94268798828125);
    path.lineTo(  351.73516845703125,   344.48565673828125);
    path.lineTo(          352.484375,         346.73828125);
    path.lineTo(  351.68438720703125,    346.7401123046875);
    path.lineTo(         352.4765625,           346.546875);
    path.lineTo(  351.67657470703125,   346.54937744140625);
    path.lineTo(        352.47265625,         346.75390625);
    path.lineTo(  351.67266845703125,  346.756622314453125);
    path.lineTo(  351.66876220703125,  345.612091064453125);
    return path;
}
Example #15
0
static void test_clip_bound_opt(skiatest::Reporter* reporter) {
    // Test for crbug.com/229011
    SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(4), SkIntToScalar(4),
                                    SkIntToScalar(2), SkIntToScalar(2));
    SkRect rect2 = SkRect::MakeXYWH(SkIntToScalar(7), SkIntToScalar(7),
                                    SkIntToScalar(1), SkIntToScalar(1));
    SkRect rect3 = SkRect::MakeXYWH(SkIntToScalar(6), SkIntToScalar(6),
                                    SkIntToScalar(1), SkIntToScalar(1));

    SkPath invPath;
    invPath.addOval(rect1);
    invPath.setFillType(SkPath::kInverseEvenOdd_FillType);
    SkPath path;
    path.addOval(rect2);
    SkPath path2;
    path2.addOval(rect3);
    SkIRect clipBounds;
    SkPictureRecorder recorder;

    // Testing conservative-raster-clip that is enabled by PictureRecord
    {
        SkCanvas* canvas = recorder.beginRecording(10, 10);
        canvas->clipPath(invPath, SkRegion::kIntersect_Op);
        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
        REPORTER_ASSERT(reporter, true == nonEmpty);
        REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft);
        REPORTER_ASSERT(reporter, 0 == clipBounds.fTop);
        REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom);
        REPORTER_ASSERT(reporter, 10 == clipBounds.fRight);
    }
    {
        SkCanvas* canvas = recorder.beginRecording(10, 10);
        canvas->clipPath(path, SkRegion::kIntersect_Op);
        canvas->clipPath(invPath, SkRegion::kIntersect_Op);
        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
        REPORTER_ASSERT(reporter, true == nonEmpty);
        REPORTER_ASSERT(reporter, 7 == clipBounds.fLeft);
        REPORTER_ASSERT(reporter, 7 == clipBounds.fTop);
        REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom);
        REPORTER_ASSERT(reporter, 8 == clipBounds.fRight);
    }
    {
        SkCanvas* canvas = recorder.beginRecording(10, 10);
        canvas->clipPath(path, SkRegion::kIntersect_Op);
        canvas->clipPath(invPath, SkRegion::kUnion_Op);
        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
        REPORTER_ASSERT(reporter, true == nonEmpty);
        REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft);
        REPORTER_ASSERT(reporter, 0 == clipBounds.fTop);
        REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom);
        REPORTER_ASSERT(reporter, 10 == clipBounds.fRight);
    }
    {
        SkCanvas* canvas = recorder.beginRecording(10, 10);
        canvas->clipPath(path, SkRegion::kDifference_Op);
        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
        REPORTER_ASSERT(reporter, true == nonEmpty);
        REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft);
        REPORTER_ASSERT(reporter, 0 == clipBounds.fTop);
        REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom);
        REPORTER_ASSERT(reporter, 10 == clipBounds.fRight);
    }
    {
        SkCanvas* canvas = recorder.beginRecording(10, 10);
        canvas->clipPath(path, SkRegion::kReverseDifference_Op);
        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
        // True clip is actually empty in this case, but the best
        // determination we can make using only bounds as input is that the
        // clip is included in the bounds of 'path'.
        REPORTER_ASSERT(reporter, true == nonEmpty);
        REPORTER_ASSERT(reporter, 7 == clipBounds.fLeft);
        REPORTER_ASSERT(reporter, 7 == clipBounds.fTop);
        REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom);
        REPORTER_ASSERT(reporter, 8 == clipBounds.fRight);
    }
    {
        SkCanvas* canvas = recorder.beginRecording(10, 10);
        canvas->clipPath(path, SkRegion::kIntersect_Op);
        canvas->clipPath(path2, SkRegion::kXOR_Op);
        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
        REPORTER_ASSERT(reporter, true == nonEmpty);
        REPORTER_ASSERT(reporter, 6 == clipBounds.fLeft);
        REPORTER_ASSERT(reporter, 6 == clipBounds.fTop);
        REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom);
        REPORTER_ASSERT(reporter, 8 == clipBounds.fRight);
    }
}
Example #16
0
    virtual void onDrawContent(SkCanvas* canvas) {
        SkPath path;
        path.moveTo(SkIntToScalar(0),   SkIntToScalar(50));
        path.quadTo(SkIntToScalar(0),   SkIntToScalar(0),   SkIntToScalar(50),  SkIntToScalar(0));
        path.lineTo(SkIntToScalar(175), SkIntToScalar(0));
        path.quadTo(SkIntToScalar(200), SkIntToScalar(0),   SkIntToScalar(200), SkIntToScalar(25));
        path.lineTo(SkIntToScalar(200), SkIntToScalar(150));
        path.quadTo(SkIntToScalar(200), SkIntToScalar(200), SkIntToScalar(150), SkIntToScalar(200));
        path.lineTo(SkIntToScalar(0),   SkIntToScalar(200));
        path.close();
        path.moveTo(SkIntToScalar(50),  SkIntToScalar(50));
        path.lineTo(SkIntToScalar(150), SkIntToScalar(50));
        path.lineTo(SkIntToScalar(150), SkIntToScalar(125));
        path.quadTo(SkIntToScalar(150), SkIntToScalar(150), SkIntToScalar(125), SkIntToScalar(150));
        path.lineTo(SkIntToScalar(50),  SkIntToScalar(150));
        path.close();
        path.setFillType(SkPath::kEvenOdd_FillType);
        SkColor pathColor = SK_ColorBLACK;
        SkPaint pathPaint;
        pathPaint.setAntiAlias(true);
        pathPaint.setColor(pathColor);

        SkPath clipA;
        clipA.moveTo(SkIntToScalar(10),  SkIntToScalar(20));
        clipA.lineTo(SkIntToScalar(165), SkIntToScalar(22));
        clipA.lineTo(SkIntToScalar(70),  SkIntToScalar(105));
        clipA.lineTo(SkIntToScalar(165), SkIntToScalar(177));
        clipA.lineTo(SkIntToScalar(-5),  SkIntToScalar(180));
        clipA.close();
        SkColor colorA = SK_ColorCYAN;

        SkPath clipB;
        clipB.moveTo(SkIntToScalar(40),  SkIntToScalar(10));
        clipB.lineTo(SkIntToScalar(190), SkIntToScalar(15));
        clipB.lineTo(SkIntToScalar(195), SkIntToScalar(190));
        clipB.lineTo(SkIntToScalar(40),  SkIntToScalar(185));
        clipB.lineTo(SkIntToScalar(155), SkIntToScalar(100));
        clipB.close();
        SkColor colorB = SK_ColorRED;

        SkPaint paint;
        paint.setAntiAlias(true);

        paint.setStyle(SkPaint::kStroke_Style);
        paint.setStrokeWidth(0);

        canvas->translate(SkIntToScalar(10),SkIntToScalar(10));
        canvas->drawPath(path, pathPaint);
        paint.setColor(colorA);
        canvas->drawPath(clipA, paint);
        paint.setColor(colorB);
        canvas->drawPath(clipB, paint);

        static const struct {
            SkRegion::Op fOp;
            const char*  fName;
        } gOps[] = { //extra spaces in names for measureText
            {SkRegion::kIntersect_Op,         "Isect "},
            {SkRegion::kDifference_Op,        "Diff " },
            {SkRegion::kUnion_Op,             "Union "},
            {SkRegion::kXOR_Op,               "Xor "  },
            {SkRegion::kReverseDifference_Op, "RDiff "}
        };

        canvas->translate(0, SkIntToScalar(40));
        canvas->scale(3 * SK_Scalar1 / 4, 3 * SK_Scalar1 / 4);
        canvas->save();

        for (int invA = 0; invA < 2; ++invA) {
            for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) {
                int idx = invA * SK_ARRAY_COUNT(gOps) + op;
                if (!(idx % 3)) {
                    canvas->restore();
                    canvas->translate(0, SkIntToScalar(250));
                    canvas->save();
                }
                canvas->save();
                    // set clip
                    clipA.setFillType(invA ? SkPath::kInverseEvenOdd_FillType :
                                             SkPath::kEvenOdd_FillType);
                    canvas->clipPath(clipA);
                    canvas->clipPath(clipB, gOps[op].fOp);

                    // draw path clipped
                    canvas->drawPath(path, pathPaint);
                canvas->restore();

                // draw path in hairline
                paint.setColor(pathColor);
                canvas->drawPath(path, paint);

                // draw clips in hair line
                paint.setColor(colorA);
                canvas->drawPath(clipA, paint);
                paint.setColor(colorB);
                canvas->drawPath(clipB, paint);

                paint.setTextSize(SkIntToScalar(20));

                SkScalar txtX = SkIntToScalar(55);
                paint.setColor(colorA);
                const char* aTxt = invA ? "InverseA " : "A ";
                canvas->drawText(aTxt, strlen(aTxt), txtX, SkIntToScalar(220), paint);
                txtX += paint.measureText(aTxt, strlen(aTxt));
                paint.setColor(SK_ColorBLACK);
                canvas->drawText(gOps[op].fName, strlen(gOps[op].fName),
                                    txtX, SkIntToScalar(220), paint);
                txtX += paint.measureText(gOps[op].fName, strlen(gOps[op].fName));
                paint.setColor(colorB);
                canvas->drawText("B", 1, txtX, SkIntToScalar(220), paint);

                canvas->translate(SkIntToScalar(250),0);
            }
        }
        canvas->restore();
    }
bool GrStencilAndCoverPathRenderer::onDrawPath(const DrawPathArgs& args) {
    GR_AUDIT_TRAIL_AUTO_FRAME(args.fDrawContext->auditTrail(),
                              "GrStencilAndCoverPathRenderer::onDrawPath");
    SkASSERT(!args.fPaint->isAntiAlias() || args.fDrawContext->isStencilBufferMultisampled());
    SkASSERT(!args.fShape->style().strokeRec().isHairlineStyle());

    const SkMatrix& viewMatrix = *args.fViewMatrix;

    SkPath path;
    args.fShape->asPath(&path);

    SkAutoTUnref<GrPath> p(get_gr_path(fResourceProvider, path, args.fShape->style()));

    if (path.isInverseFillType()) {
        SkMatrix invert = SkMatrix::I();
        SkRect bounds =
            SkRect::MakeLTRB(0, 0,
                             SkIntToScalar(args.fDrawContext->width()),
                             SkIntToScalar(args.fDrawContext->height()));
        SkMatrix vmi;
        // mapRect through persp matrix may not be correct
        if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
            vmi.mapRect(&bounds);
            // theoretically could set bloat = 0, instead leave it because of matrix inversion
            // precision.
            SkScalar bloat = viewMatrix.getMaxScale() * SK_ScalarHalf;
            bounds.outset(bloat, bloat);
        } else {
            if (!viewMatrix.invert(&invert)) {
                return false;
            }
        }
        const SkMatrix& viewM = viewMatrix.hasPerspective() ? SkMatrix::I() : viewMatrix;

        SkAutoTUnref<GrDrawBatch> coverBatch(
                GrRectBatchFactory::CreateNonAAFill(args.fPaint->getColor(), viewM, bounds,
                                                    nullptr, &invert));

        // fake inverse with a stencil and cover
        args.fDrawContext->drawContextPriv().stencilPath(*args.fClip, args.fPaint->isAntiAlias(),
                                                         viewMatrix, p);

        {
            static constexpr GrUserStencilSettings kInvertedCoverPass(
                GrUserStencilSettings::StaticInit<
                    0x0000,
                    // We know our rect will hit pixels outside the clip and the user bits will
                    // be 0 outside the clip. So we can't just fill where the user bits are 0. We
                    // also need to check that the clip bit is set.
                    GrUserStencilTest::kEqualIfInClip,
                    0xffff,
                    GrUserStencilOp::kKeep,
                    GrUserStencilOp::kZero,
                    0xffff>()
            );

            GrPipelineBuilder pipelineBuilder(*args.fPaint,
                                              args.fPaint->isAntiAlias() &&
                                              !args.fDrawContext->hasMixedSamples());
            pipelineBuilder.setUserStencil(&kInvertedCoverPass);

            args.fDrawContext->drawBatch(pipelineBuilder, *args.fClip, coverBatch);
        }
    } else {
        static constexpr GrUserStencilSettings kCoverPass(
            GrUserStencilSettings::StaticInit<
                0x0000,
                GrUserStencilTest::kNotEqual,
                0xffff,
                GrUserStencilOp::kZero,
                GrUserStencilOp::kKeep,
                0xffff>()
        );

        SkAutoTUnref<GrDrawBatch> batch(
                GrDrawPathBatch::Create(viewMatrix, args.fPaint->getColor(), p->getFillType(), p));

        GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fPaint->isAntiAlias());
        pipelineBuilder.setUserStencil(&kCoverPass);
        if (args.fAntiAlias) {
            SkASSERT(args.fDrawContext->isStencilBufferMultisampled());
            pipelineBuilder.enableState(GrPipelineBuilder::kHWAntialias_Flag);
        }

        args.fDrawContext->drawBatch(pipelineBuilder, *args.fClip, batch);
    }

    return true;
}
/**
 * Generates the lines and quads to be rendered. Lines are always recorded in
 * device space. We will do a device space bloat to account for the 1pixel
 * thickness.
 * Quads are recorded in device space unless m contains
 * perspective, then in they are in src space. We do this because we will
 * subdivide large quads to reduce over-fill. This subdivision has to be
 * performed before applying the perspective matrix.
 */
static int gather_lines_and_quads(const SkPath& path,
                                  const SkMatrix& m,
                                  const SkIRect& devClipBounds,
                                  GrAAHairLinePathRenderer::PtArray* lines,
                                  GrAAHairLinePathRenderer::PtArray* quads,
                                  GrAAHairLinePathRenderer::PtArray* conics,
                                  GrAAHairLinePathRenderer::IntArray* quadSubdivCnts,
                                  GrAAHairLinePathRenderer::FloatArray* conicWeights) {
    SkPath::Iter iter(path, false);

    int totalQuadCount = 0;
    SkRect bounds;
    SkIRect ibounds;

    bool persp = m.hasPerspective();

    for (;;) {
        SkPoint pathPts[4];
        SkPoint devPts[4];
        SkPath::Verb verb = iter.next(pathPts);
        switch (verb) {
            case SkPath::kConic_Verb: {
                SkConic dst[4];
                // We chop the conics to create tighter clipping to hide error
                // that appears near max curvature of very thin conics. Thin
                // hyperbolas with high weight still show error.
                int conicCnt = chop_conic(pathPts, dst, iter.conicWeight());
                for (int i = 0; i < conicCnt; ++i) {
                    SkPoint* chopPnts = dst[i].fPts;
                    m.mapPoints(devPts, chopPnts, 3);
                    bounds.setBounds(devPts, 3);
                    bounds.outset(SK_Scalar1, SK_Scalar1);
                    bounds.roundOut(&ibounds);
                    if (SkIRect::Intersects(devClipBounds, ibounds)) {
                        if (is_degen_quad_or_conic(devPts)) {
                            SkPoint* pts = lines->push_back_n(4);
                            pts[0] = devPts[0];
                            pts[1] = devPts[1];
                            pts[2] = devPts[1];
                            pts[3] = devPts[2];
                        } else {
                            // when in perspective keep conics in src space
                            SkPoint* cPts = persp ? chopPnts : devPts;
                            SkPoint* pts = conics->push_back_n(3);
                            pts[0] = cPts[0];
                            pts[1] = cPts[1];
                            pts[2] = cPts[2];
                            conicWeights->push_back() = dst[i].fW;
                        }
                    }
                }
                break;
            }
            case SkPath::kMove_Verb:
                break;
            case SkPath::kLine_Verb:
                m.mapPoints(devPts, pathPts, 2);
                bounds.setBounds(devPts, 2);
                bounds.outset(SK_Scalar1, SK_Scalar1);
                bounds.roundOut(&ibounds);
                if (SkIRect::Intersects(devClipBounds, ibounds)) {
                    SkPoint* pts = lines->push_back_n(2);
                    pts[0] = devPts[0];
                    pts[1] = devPts[1];
                }
                break;
            case SkPath::kQuad_Verb: {
                SkPoint choppedPts[5];
                // Chopping the quad helps when the quad is either degenerate or nearly degenerate.
                // When it is degenerate it allows the approximation with lines to work since the
                // chop point (if there is one) will be at the parabola's vertex. In the nearly
                // degenerate the QuadUVMatrix computed for the points is almost singular which
                // can cause rendering artifacts.
                int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts);
                for (int i = 0; i < n; ++i) {
                    SkPoint* quadPts = choppedPts + i * 2;
                    m.mapPoints(devPts, quadPts, 3);
                    bounds.setBounds(devPts, 3);
                    bounds.outset(SK_Scalar1, SK_Scalar1);
                    bounds.roundOut(&ibounds);

                    if (SkIRect::Intersects(devClipBounds, ibounds)) {
                        int subdiv = num_quad_subdivs(devPts);
                        SkASSERT(subdiv >= -1);
                        if (-1 == subdiv) {
                            SkPoint* pts = lines->push_back_n(4);
                            pts[0] = devPts[0];
                            pts[1] = devPts[1];
                            pts[2] = devPts[1];
                            pts[3] = devPts[2];
                        } else {
                            // when in perspective keep quads in src space
                            SkPoint* qPts = persp ? quadPts : devPts;
                            SkPoint* pts = quads->push_back_n(3);
                            pts[0] = qPts[0];
                            pts[1] = qPts[1];
                            pts[2] = qPts[2];
                            quadSubdivCnts->push_back() = subdiv;
                            totalQuadCount += 1 << subdiv;
                        }
                    }
                }
                break;
            }
            case SkPath::kCubic_Verb:
                m.mapPoints(devPts, pathPts, 4);
                bounds.setBounds(devPts, 4);
                bounds.outset(SK_Scalar1, SK_Scalar1);
                bounds.roundOut(&ibounds);
                if (SkIRect::Intersects(devClipBounds, ibounds)) {
                    PREALLOC_PTARRAY(32) q;
                    // We convert cubics to quadratics (for now).
                    // In perspective have to do conversion in src space.
                    if (persp) {
                        SkScalar tolScale =
                            GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds());
                        GrPathUtils::convertCubicToQuads(pathPts, tolScale, &q);
                    } else {
                        GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, &q);
                    }
                    for (int i = 0; i < q.count(); i += 3) {
                        SkPoint* qInDevSpace;
                        // bounds has to be calculated in device space, but q is
                        // in src space when there is perspective.
                        if (persp) {
                            m.mapPoints(devPts, &q[i], 3);
                            bounds.setBounds(devPts, 3);
                            qInDevSpace = devPts;
                        } else {
                            bounds.setBounds(&q[i], 3);
                            qInDevSpace = &q[i];
                        }
                        bounds.outset(SK_Scalar1, SK_Scalar1);
                        bounds.roundOut(&ibounds);
                        if (SkIRect::Intersects(devClipBounds, ibounds)) {
                            int subdiv = num_quad_subdivs(qInDevSpace);
                            SkASSERT(subdiv >= -1);
                            if (-1 == subdiv) {
                                SkPoint* pts = lines->push_back_n(4);
                                // lines should always be in device coords
                                pts[0] = qInDevSpace[0];
                                pts[1] = qInDevSpace[1];
                                pts[2] = qInDevSpace[1];
                                pts[3] = qInDevSpace[2];
                            } else {
                                SkPoint* pts = quads->push_back_n(3);
                                // q is already in src space when there is no
                                // perspective and dev coords otherwise.
                                pts[0] = q[0 + i];
                                pts[1] = q[1 + i];
                                pts[2] = q[2 + i];
                                quadSubdivCnts->push_back() = subdiv;
                                totalQuadCount += 1 << subdiv;
                            }
                        }
                    }
                }
                break;
            case SkPath::kClose_Verb:
                break;
            case SkPath::kDone_Verb:
                return totalQuadCount;
        }
    }
}
bool GrDefaultPathRenderer::internalDrawPath(GrDrawTarget* target,
                                             GrPipelineBuilder* pipelineBuilder,
                                             GrColor color,
                                             const SkMatrix& viewMatrix,
                                             const SkPath& path,
                                             const GrStrokeInfo& origStroke,
                                             bool stencilOnly) {
    SkTCopyOnFirstWrite<GrStrokeInfo> stroke(origStroke);

    SkScalar hairlineCoverage;
    uint8_t newCoverage = 0xff;
    if (IsStrokeHairlineOrEquivalent(*stroke, viewMatrix, &hairlineCoverage)) {
        newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);

        if (!stroke->isHairlineStyle()) {
            stroke.writable()->setHairlineStyle();
        }
    }

    const bool isHairline = stroke->isHairlineStyle();

    // Save the current xp on the draw state so we can reset it if needed
    SkAutoTUnref<const GrXPFactory> backupXPFactory(SkRef(pipelineBuilder->getXPFactory()));
    // face culling doesn't make sense here
    SkASSERT(GrPipelineBuilder::kBoth_DrawFace == pipelineBuilder->getDrawFace());

    int                         passCount = 0;
    const GrStencilSettings*    passes[3];
    GrPipelineBuilder::DrawFace drawFace[3];
    bool                        reverse = false;
    bool                        lastPassIsBounds;

    if (isHairline) {
        passCount = 1;
        if (stencilOnly) {
            passes[0] = &gDirectToStencil;
        } else {
            passes[0] = NULL;
        }
        lastPassIsBounds = false;
        drawFace[0] = GrPipelineBuilder::kBoth_DrawFace;
    } else {
        if (single_pass_path(path, *stroke)) {
            passCount = 1;
            if (stencilOnly) {
                passes[0] = &gDirectToStencil;
            } else {
                passes[0] = NULL;
            }
            drawFace[0] = GrPipelineBuilder::kBoth_DrawFace;
            lastPassIsBounds = false;
        } else {
            switch (path.getFillType()) {
                case SkPath::kInverseEvenOdd_FillType:
                    reverse = true;
                    // fallthrough
                case SkPath::kEvenOdd_FillType:
                    passes[0] = &gEOStencilPass;
                    if (stencilOnly) {
                        passCount = 1;
                        lastPassIsBounds = false;
                    } else {
                        passCount = 2;
                        lastPassIsBounds = true;
                        if (reverse) {
                            passes[1] = &gInvEOColorPass;
                        } else {
                            passes[1] = &gEOColorPass;
                        }
                    }
                    drawFace[0] = drawFace[1] = GrPipelineBuilder::kBoth_DrawFace;
                    break;

                case SkPath::kInverseWinding_FillType:
                    reverse = true;
                    // fallthrough
                case SkPath::kWinding_FillType:
                    if (fSeparateStencil) {
                        if (fStencilWrapOps) {
                            passes[0] = &gWindStencilSeparateWithWrap;
                        } else {
                            passes[0] = &gWindStencilSeparateNoWrap;
                        }
                        passCount = 2;
                        drawFace[0] = GrPipelineBuilder::kBoth_DrawFace;
                    } else {
                        if (fStencilWrapOps) {
                            passes[0] = &gWindSingleStencilWithWrapInc;
                            passes[1] = &gWindSingleStencilWithWrapDec;
                        } else {
                            passes[0] = &gWindSingleStencilNoWrapInc;
                            passes[1] = &gWindSingleStencilNoWrapDec;
                        }
                        // which is cw and which is ccw is arbitrary.
                        drawFace[0] = GrPipelineBuilder::kCW_DrawFace;
                        drawFace[1] = GrPipelineBuilder::kCCW_DrawFace;
                        passCount = 3;
                    }
                    if (stencilOnly) {
                        lastPassIsBounds = false;
                        --passCount;
                    } else {
                        lastPassIsBounds = true;
                        drawFace[passCount-1] = GrPipelineBuilder::kBoth_DrawFace;
                        if (reverse) {
                            passes[passCount-1] = &gInvWindColorPass;
                        } else {
                            passes[passCount-1] = &gWindColorPass;
                        }
                    }
                    break;
                default:
                    SkDEBUGFAIL("Unknown path fFill!");
                    return false;
            }
        }
    }

    SkScalar tol = GrPathUtils::kDefaultTolerance;
    SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds());

    SkRect devBounds;
    GetPathDevBounds(path, pipelineBuilder->getRenderTarget(), viewMatrix, &devBounds);

    for (int p = 0; p < passCount; ++p) {
        pipelineBuilder->setDrawFace(drawFace[p]);
        if (passes[p]) {
            *pipelineBuilder->stencil() = *passes[p];
        }

        if (lastPassIsBounds && (p == passCount-1)) {
            // Reset the XP Factory on pipelineBuilder
            pipelineBuilder->setXPFactory(backupXPFactory);
            SkRect bounds;
            SkMatrix localMatrix = SkMatrix::I();
            if (reverse) {
                SkASSERT(pipelineBuilder->getRenderTarget());
                // draw over the dev bounds (which will be the whole dst surface for inv fill).
                bounds = devBounds;
                SkMatrix vmi;
                // mapRect through persp matrix may not be correct
                if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
                    vmi.mapRect(&bounds);
                } else {
                    if (!viewMatrix.invert(&localMatrix)) {
                        return false;
                    }
                }
            } else {
                bounds = path.getBounds();
            }
            const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
                                                                               viewMatrix;
            target->drawBWRect(*pipelineBuilder, color, viewM, bounds, NULL, &localMatrix);
        } else {
            if (passCount > 1) {
                pipelineBuilder->setDisableColorXPFactory();
            }

            DefaultPathBatch::Geometry geometry;
            geometry.fColor = color;
            geometry.fPath = path;
            geometry.fTolerance = srcSpaceTol;

            SkAutoTUnref<GrBatch> batch(DefaultPathBatch::Create(geometry, newCoverage, viewMatrix,
                                                                 isHairline, devBounds));

            target->drawBatch(*pipelineBuilder, batch);
        }
    }
    return true;
}
Example #20
0
    void onOnceBeforeDraw() override {
        const SkRect fieldBounds = kBounds.makeOutset(kBallSize / 2, kBallSize / 2);
        const SkRRect ball = SkRRect::MakeOval(SkRect::MakeWH(kBallSize, kBallSize));
        const SkRRect paddle = SkRRect::MakeRectXY(SkRect::MakeWH(kPaddleSize.width(),
                                                                  kPaddleSize.height()),
                                                   kPaddleSize.width() / 2,
                                                   kPaddleSize.width() / 2);
        fBall.initialize(ball,
                         SK_ColorGREEN,
                         SkPoint::Make(kBounds.centerX(), kBounds.centerY()),
                         SkVector::Make(fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax),
                                        fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax)));
        fPaddle0.initialize(paddle,
                            SK_ColorBLUE,
                            SkPoint::Make(fieldBounds.left() - kPaddleSize.width() / 2,
                                          fieldBounds.centerY()),
                            SkVector::Make(0, 0));
        fPaddle1.initialize(paddle,
                            SK_ColorRED,
                            SkPoint::Make(fieldBounds.right() + kPaddleSize.width() / 2,
                                          fieldBounds.centerY()),
                            SkVector::Make(0, 0));

        // Background decoration.
        SkPath bgPath;
        bgPath.moveTo(kBounds.left() , fieldBounds.top());
        bgPath.lineTo(kBounds.right(), fieldBounds.top());
        bgPath.moveTo(kBounds.left() , fieldBounds.bottom());
        bgPath.lineTo(kBounds.right(), fieldBounds.bottom());
        // TODO: stroke-dash support would come in handy right about now.
        for (uint32_t i = 0; i < kBackgroundDashCount; ++i) {
            bgPath.moveTo(kBounds.centerX(),
                          kBounds.top() + (i + 0.25f) * kBounds.height() / kBackgroundDashCount);
            bgPath.lineTo(kBounds.centerX(),
                          kBounds.top() + (i + 0.75f) * kBounds.height() / kBackgroundDashCount);
        }

        sk_sp<SkSVGPath> bg = SkSVGPath::Make();
        bg->setPath(bgPath);
        bg->setFill(SkSVGPaint(SkSVGPaint::Type::kNone));
        bg->setStroke(SkSVGPaint(SkSVGColorType(SK_ColorBLACK)));
        bg->setStrokeWidth(SkSVGLength(kBackgroundStroke));

        // Build the SVG DOM tree.
        sk_sp<SkSVGSVG> root = SkSVGSVG::Make();
        root->appendChild(std::move(bg));
        root->appendChild(fPaddle0.shadowNode);
        root->appendChild(fPaddle1.shadowNode);
        root->appendChild(fBall.shadowNode);
        root->appendChild(fPaddle0.objectNode);
        root->appendChild(fPaddle1.objectNode);
        root->appendChild(fBall.objectNode);

        // Handle everything in a normalized 1x1 space.
        root->setViewBox(SkSVGViewBoxType(SkRect::MakeWH(1, 1)));

        fDom = sk_sp<SkSVGDOM>(new SkSVGDOM());
        fDom->setContainerSize(SkSize::Make(this->width(), this->height()));
        fDom->setRoot(std::move(root));

        // Off we go.
        this->updatePaddleStrategy();
    }
Example #21
0
    void onPrepareDraws(Target* target) const override {
        int instanceCount = fGeoData.count();

        SkMatrix invert;
        if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) {
            SkDebugf("Could not invert viewmatrix\n");
            return;
        }

        // Setup GrGeometryProcessors
        SkAutoTUnref<GrPLSGeometryProcessor> triangleProcessor(
                PLSAATriangleEffect::Create(invert, this->usesLocalCoords()));
        SkAutoTUnref<GrPLSGeometryProcessor> quadProcessor(
                PLSQuadEdgeEffect::Create(invert, this->usesLocalCoords()));

        GrResourceProvider* rp = target->resourceProvider();
        for (int i = 0; i < instanceCount; ++i) {
            const Geometry& args = fGeoData[i];
            SkRect bounds = args.fPath.getBounds();
            args.fViewMatrix.mapRect(&bounds);
            bounds.fLeft = SkScalarFloorToScalar(bounds.fLeft);
            bounds.fTop = SkScalarFloorToScalar(bounds.fTop);
            bounds.fRight = SkScalarCeilToScalar(bounds.fRight);
            bounds.fBottom = SkScalarCeilToScalar(bounds.fBottom);
            triangleProcessor->setBounds(bounds);
            quadProcessor->setBounds(bounds);

            // We use the fact that SkPath::transform path does subdivision based on
            // perspective. Otherwise, we apply the view matrix when copying to the
            // segment representation.
            const SkMatrix* viewMatrix = &args.fViewMatrix;

            // We avoid initializing the path unless we have to
            const SkPath* pathPtr = &args.fPath;
            SkTLazy<SkPath> tmpPath;
            if (viewMatrix->hasPerspective()) {
                SkPath* tmpPathPtr = tmpPath.init(*pathPtr);
                tmpPathPtr->setIsVolatile(true);
                tmpPathPtr->transform(*viewMatrix);
                viewMatrix = &SkMatrix::I();
                pathPtr = tmpPathPtr;
            }

            GrMesh mesh;

            PLSVertices triVertices;
            PLSVertices quadVertices;
            if (!get_geometry(*pathPtr, *viewMatrix, triVertices, quadVertices, rp, bounds)) {
                continue;
            }

            if (triVertices.count()) {
                const GrBuffer* triVertexBuffer;
                int firstTriVertex;
                size_t triStride = triangleProcessor->getVertexStride();
                PLSVertex* triVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace(
                        triStride, triVertices.count(), &triVertexBuffer, &firstTriVertex));
                if (!triVerts) {
                    SkDebugf("Could not allocate vertices\n");
                    return;
                }
                for (int i = 0; i < triVertices.count(); ++i) {
                    triVerts[i] = triVertices[i];
                }
                mesh.init(kTriangles_GrPrimitiveType, triVertexBuffer, firstTriVertex,
                          triVertices.count());
                target->draw(triangleProcessor, mesh);
            }

            if (quadVertices.count()) {
                const GrBuffer* quadVertexBuffer;
                int firstQuadVertex;
                size_t quadStride = quadProcessor->getVertexStride();
                PLSVertex* quadVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace(
                        quadStride, quadVertices.count(), &quadVertexBuffer, &firstQuadVertex));
                if (!quadVerts) {
                    SkDebugf("Could not allocate vertices\n");
                    return;
                }
                for (int i = 0; i < quadVertices.count(); ++i) {
                    quadVerts[i] = quadVertices[i];
                }
                mesh.init(kTriangles_GrPrimitiveType, quadVertexBuffer, firstQuadVertex,
                          quadVertices.count());
                target->draw(quadProcessor, mesh);
            }

            SkAutoTUnref<GrGeometryProcessor> finishProcessor(
                    PLSFinishEffect::Create(this->color(),
                                            pathPtr->getFillType() ==
                                                                SkPath::FillType::kEvenOdd_FillType,
                                            invert,
                                            this->usesLocalCoords()));
            const GrBuffer* rectVertexBuffer;
            size_t finishStride = finishProcessor->getVertexStride();
            int firstRectVertex;
            static const int kRectVertexCount = 6;
            SkPoint* rectVerts = reinterpret_cast<SkPoint*>(target->makeVertexSpace(
                    finishStride, kRectVertexCount, &rectVertexBuffer, &firstRectVertex));
            if (!rectVerts) {
                SkDebugf("Could not allocate vertices\n");
                return;
            }
            rectVerts[0] = { bounds.fLeft, bounds.fTop };
            rectVerts[1] = { bounds.fLeft, bounds.fBottom };
            rectVerts[2] = { bounds.fRight, bounds.fBottom };
            rectVerts[3] = { bounds.fLeft, bounds.fTop };
            rectVerts[4] = { bounds.fRight, bounds.fTop };
            rectVerts[5] = { bounds.fRight, bounds.fBottom };

            mesh.init(kTriangles_GrPrimitiveType, rectVertexBuffer, firstRectVertex,
                      kRectVertexCount);
            target->draw(finishProcessor, mesh);
        }
    }
// A degenerate segments case which exercises inactive edges being
// made active by splitting.
static SkPath create_path_13() {
    SkPath path;
    path.moveTo(690.62127685546875f, 509.25555419921875f);
    path.lineTo(99.336181640625f,    511.71405029296875f);
    path.lineTo(708.362548828125f,   512.4349365234375f);
    path.lineTo(729.9940185546875f,  516.3114013671875f);
    path.lineTo(738.708984375f,      518.76995849609375f);
    path.lineTo(678.3463134765625f,  510.0819091796875f);
    path.lineTo(681.21795654296875f, 504.81378173828125f);
    path.moveTo(758.52764892578125f, 521.55963134765625f);
    path.lineTo(719.1549072265625f,  514.50372314453125f);
    path.lineTo(689.59063720703125f, 512.0628662109375f);
    path.lineTo(679.78216552734375f, 507.447845458984375f);
    return path;
}
bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath,
                                      const SkStrokeRec& stroke,
                                      GrDrawTarget* target,
                                      bool antiAlias) {
    if (origPath.isEmpty()) {
        return true;
    }

    SkScalar width = stroke.getWidth();
    if (width <= 0) {
        return false;
    }

    // Get the join type
    SkPaint::Join join = stroke.getJoin();
    SkScalar miterLimit = stroke.getMiter();
    SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit);
    if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) {
        // If the miter limit is small, treat it as a bevel join
        join = SkPaint::kBevel_Join;
    }
    const bool isMiter       = (join == SkPaint::kMiter_Join);
    const bool isBevel       = (join == SkPaint::kBevel_Join);
    SkScalar invMiterLimit   = isMiter ? SK_Scalar1 / miterLimit : 0;
    SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit);

    // Allocate vertices
    const int nbQuads     = origPath.countPoints() + 1; // Could be "-1" if path is not closed
    const int extraVerts  = isMiter || isBevel ? 1 : 0;
    const int maxVertexCount = nbQuads * (4 + extraVerts);
    const int maxIndexCount  = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle
    target->drawState()->setDefaultVertexAttribs();
    GrDrawTarget::AutoReleaseGeometry arg(target, maxVertexCount, maxIndexCount);
    if (!arg.succeeded()) {
        return false;
    }
    SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices());
    uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices());
    int vCount = 0, iCount = 0;

    // Transform the path into a list of triangles
    SkPath::Iter iter(origPath, false);
    SkPoint pts[4];
    const SkScalar radius = SkScalarMul(width, 0.5f);
    SkPoint *firstPt = verts, *lastPt = NULL;
    SkVector firstDir, dir;
    firstDir.set(0, 0);
    dir.set(0, 0);
    bool isOpen = true;
    for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) {
        switch(v) {
            case SkPath::kMove_Verb:
                // This will already be handled as pts[0] of the 1st line
                break;
            case SkPath::kClose_Verb:
                isOpen = (lastPt == NULL);
                break;
            case SkPath::kLine_Verb:
            {
                SkVector v0 = dir;
                dir = pts[1] - pts[0];
                if (dir.setLength(radius)) {
                    SkVector dirT;
                    dirT.set(dir.fY, -dir.fX); // Get perpendicular direction
                    SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT,
                            l2a = pts[0]-dirT, l2b = pts[1]-dirT;
                    SkPoint miterPt[2];
                    bool useMiterPoint = false;
                    int idx0(-1), idx1(-1);
                    if (NULL == lastPt) {
                        firstDir = dir;
                    } else {
                        SkVector v1 = dir;
                        if (v0.normalize() && v1.normalize()) {
                            SkScalar dotProd = v0.dot(v1);
                            // No need for bevel or miter join if the angle
                            // is either 0 or 180 degrees
                            if (!SkScalarNearlyZero(dotProd + SK_Scalar1) &&
                                !SkScalarNearlyZero(dotProd - SK_Scalar1)) {
                                bool ccw = !is_clockwise(v0, v1);
                                int offset = ccw ? 1 : 0;
                                idx0 = vCount-2+offset;
                                idx1 = vCount+offset;
                                const SkPoint* pt0 = &(lastPt[offset]);
                                const SkPoint* pt1 = ccw ? &l2a : &l1a;
                                switch(join) {
                                    case SkPaint::kMiter_Join:
                                    {
                                        // *Note : Logic is from MiterJoiner

                                        // FIXME : Special case if we have a right angle ?
                                        // if (SkScalarNearlyZero(dotProd)) {...}

                                        SkScalar sinHalfAngleSq =
                                                SkScalarHalf(SK_Scalar1 + dotProd);
                                        if (sinHalfAngleSq >= invMiterLimitSq) {
                                            // Find the miter point (or points if it is further
                                            // than the miter limit)
                                            const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1;
                                            if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) !=
                                                kNone_IntersectionType) {
                                                SkPoint miterPt0 = miterPt[0] - *pt0;
                                                SkPoint miterPt1 = miterPt[0] - *pt1;
                                                SkScalar sqDist0 = miterPt0.dot(miterPt0);
                                                SkScalar sqDist1 = miterPt1.dot(miterPt1);
                                                const SkScalar rSq =
                                                        SkScalarDiv(SkScalarMul(radius, radius),
                                                                    sinHalfAngleSq);
                                                const SkScalar sqRLimit =
                                                        SkScalarMul(sqMiterLimit, rSq);
                                                if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) {
                                                    if (sqDist1 > sqRLimit) {
                                                        v1.setLength(SkScalarSqrt(sqRLimit));
                                                        miterPt[1] = *pt1+v1;
                                                    } else {
                                                        miterPt[1] = miterPt[0];
                                                    }
                                                    if (sqDist0 > sqRLimit) {
                                                        v0.setLength(SkScalarSqrt(sqRLimit));
                                                        miterPt[0] = *pt0+v0;
                                                    }
                                                } else {
                                                    miterPt[1] = miterPt[0];
                                                }
                                                useMiterPoint = true;
                                            }
                                        }
                                        if (useMiterPoint && (miterPt[1] == miterPt[0])) {
                                            break;
                                        }
                                    }
                                    default:
                                    case SkPaint::kBevel_Join:
                                    {
                                        // Note : This currently causes some overdraw where both
                                        //        lines initially intersect. We'd need to add
                                        //        another line intersection check here if the
                                        //        overdraw becomes an issue instead of using the
                                        //        current point directly.

                                        // Add center point
                                        *verts++ = pts[0]; // Use current point directly
                                        // This idx is passed the current point so increment it
                                        ++idx1;
                                        // Add center triangle
                                        *idxs++ = idx0;
                                        *idxs++ = vCount;
                                        *idxs++ = idx1;
                                        vCount++;
                                        iCount += 3;
                                    }
                                    break;
                                }
                            }
                        }
                    }
                    *verts++ = l1a;
                    *verts++ = l2a;
                    lastPt   = verts;
                    *verts++ = l1b;
                    *verts++ = l2b;

                    if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) {
                        firstPt[idx0] = miterPt[0];
                        firstPt[idx1] = miterPt[1];
                    }

                    // 1st triangle
                    *idxs++  = vCount+0;
                    *idxs++  = vCount+2;
                    *idxs++  = vCount+1;
                    // 2nd triangle
                    *idxs++  = vCount+1;
                    *idxs++  = vCount+2;
                    *idxs++  = vCount+3;

                    vCount += 4;
                    iCount += 6;
                }
            }
                break;
            case SkPath::kQuad_Verb:
            case SkPath::kCubic_Verb:
                SkDEBUGFAIL("Curves not supported!");
            default:
                // Unhandled cases
                SkASSERT(false);
        }
    }

    if (isOpen) {
        // Add caps
        switch (stroke.getCap()) {
            case SkPaint::kSquare_Cap:
                firstPt[0] -= firstDir;
                firstPt[1] -= firstDir;
                lastPt [0] += dir;
                lastPt [1] += dir;
                break;
            case SkPaint::kRound_Cap:
                SkDEBUGFAIL("Round caps not supported!");
            default: // No cap
                break;
        }
    }

    SkASSERT(vCount <= maxVertexCount);
    SkASSERT(iCount <= maxIndexCount);

    if (vCount > 0) {
        target->drawIndexed(kTriangles_GrPrimitiveType,
                            0,        // start vertex
                            0,        // start index
                            vCount,
                            iCount);
    }

    return true;
}
Example #24
0
static sk_sp<SkPathEffect> MakeDotEffect(SkScalar radius, const SkMatrix& matrix) {
    SkPath path;
    path.addCircle(0, 0, radius);
    return SkPath2DPathEffect::Make(matrix, path);
}
Example #25
0
void WRasterImage::Impl::drawPlainPath(SkPath &p, const WPainterPath& path)
{
  const std::vector<WPainterPath::Segment>& segments = path.segments();

  if (segments.size() > 0
      && segments[0].type() != WPainterPath::Segment::MoveTo)
    p.moveTo(SkDoubleToScalar(0), SkDoubleToScalar(0));

  for (unsigned i = 0; i < segments.size(); ++i) {
    const WPainterPath::Segment s = segments[i];

    switch (s.type()) {
    case WPainterPath::Segment::MoveTo:
      p.moveTo(SkDoubleToScalar(s.x()), SkDoubleToScalar(s.y()));
      break;
    case WPainterPath::Segment::LineTo:
      p.lineTo(SkDoubleToScalar(s.x()), SkDoubleToScalar(s.y()));
      break;
    case WPainterPath::Segment::CubicC1: {
      const double x1 = s.x();
      const double y1 = s.y();
      const double x2 = segments[i+1].x();
      const double y2 = segments[i+1].y();
      const double x3 = segments[i+2].x();
      const double y3 = segments[i+2].y();
      p.cubicTo(SkDoubleToScalar(x1), SkDoubleToScalar(y1),
                SkDoubleToScalar(x2), SkDoubleToScalar(y2),
                SkDoubleToScalar(x3), SkDoubleToScalar(y3));
      i += 2;
      break;
    }
    case WPainterPath::Segment::CubicC2:
    case WPainterPath::Segment::CubicEnd:
      assert(false);
    case WPainterPath::Segment::ArcC: {
      const double x = s.x();
      const double y = s.y();
      const double width = segments[i+1].x();
      const double height = segments[i+1].y();
      const double startAngle = segments[i+2].x();
      const double sweepAngle = segments[i+2].y();
      SkRect rect = SkRect::MakeXYWH(SkDoubleToScalar(x - width),
				     SkDoubleToScalar(y - height),
				     SkDoubleToScalar(width * 2.0),
				     SkDoubleToScalar(height * 2.0));
      if (sweepAngle != 360) 
	p.arcTo(rect,
		SkDoubleToScalar(-startAngle), SkDoubleToScalar(-sweepAngle),
		false);
      else
	p.addOval(rect, SkPath::kCCW_Direction);

      i += 2;
      break;
    }
    case WPainterPath::Segment::ArcR:
    case WPainterPath::Segment::ArcAngleSweep:
      assert(false);
    case WPainterPath::Segment::QuadC: {
      const double x1 = s.x();
      const double y1 = s.y();
      const double x2 = segments[i+1].x();
      const double y2 = segments[i+1].y();
      
      p.quadTo(SkDoubleToScalar(x1), SkDoubleToScalar(y1),
              SkDoubleToScalar(x2), SkDoubleToScalar(y2));

      i += 1;

      break;
    }
    case WPainterPath::Segment::QuadEnd:
      assert(false);
    }
  }
}
bool GrStencilAndCoverPathRenderer::onDrawPath(const SkPath& path,
                                               const SkStroke& stroke,
                                               GrDrawTarget* target,
                                               bool antiAlias) {
    GrAssert(!antiAlias);
    GrAssert(0 != stroke.getWidthIfStroked());

    GrDrawState* drawState = target->drawState();
    GrAssert(drawState->getStencil().isDisabled());

    SkAutoTUnref<GrPath> p(fGpu->createPath(path));

    SkPath::FillType nonInvertedFill = SkPath::NonInverseFill(path.getFillType());
    target->stencilPath(p, stroke, nonInvertedFill);

    // TODO: Use built in cover operation rather than a rect draw. This will require making our
    // fragment shaders be able to eat varyings generated by a matrix.

    // fill the path, zero out the stencil
    GrRect bounds = p->getBounds();
    SkScalar bloat = drawState->getViewMatrix().getMaxStretch() * SK_ScalarHalf;
    GrDrawState::AutoDeviceCoordDraw adcd;

    if (nonInvertedFill == path.getFillType()) {
        GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
            kZero_StencilOp,
            kZero_StencilOp,
            kNotEqual_StencilFunc,
            0xffff,
            0x0000,
            0xffff);
        *drawState->stencil() = kStencilPass;
    } else {
        GR_STATIC_CONST_SAME_STENCIL(kInvertedStencilPass,
            kZero_StencilOp,
            kZero_StencilOp,
            // We know our rect will hit pixels outside the clip and the user bits will be 0
            // outside the clip. So we can't just fill where the user bits are 0. We also need to
            // check that the clip bit is set.
            kEqualIfInClip_StencilFunc,
            0xffff,
            0x0000,
            0xffff);
        SkMatrix vmi;
        bounds.setLTRB(0, 0,
                       SkIntToScalar(drawState->getRenderTarget()->width()),
                       SkIntToScalar(drawState->getRenderTarget()->height()));
        // mapRect through persp matrix may not be correct
        if (!drawState->getViewMatrix().hasPerspective() && drawState->getViewInverse(&vmi)) {
            vmi.mapRect(&bounds);
            // theoretically could set bloat = 0, instead leave it because of matrix inversion
            // precision.
        } else {
            adcd.set(drawState);
            bloat = 0;
        }
        *drawState->stencil() = kInvertedStencilPass;
    }
    bounds.outset(bloat, bloat);
    target->drawSimpleRect(bounds, NULL);
    target->drawState()->stencil()->setDisabled();
    return true;
}
Example #27
0
void SkPathStroker::cubic_to(const SkPoint pts[4],
                      const SkVector& normalAB, const SkVector& unitNormalAB,
                      SkVector* normalCD, SkVector* unitNormalCD,
                      int subDivide) {
    SkVector    ab = pts[1] - pts[0];
    SkVector    cd = pts[3] - pts[2];
    SkVector    normalBC, unitNormalBC;

    bool    degenerateAB = degenerate_vector(ab);
    bool    degenerateCD = degenerate_vector(cd);

    if (degenerateAB && degenerateCD) {
DRAW_LINE:
        this->line_to(pts[3], normalAB);
        *normalCD = normalAB;
        *unitNormalCD = unitNormalAB;
        return;
    }

    if (degenerateAB) {
        ab = pts[2] - pts[0];
        degenerateAB = degenerate_vector(ab);
    }
    if (degenerateCD) {
        cd = pts[3] - pts[1];
        degenerateCD = degenerate_vector(cd);
    }
    if (degenerateAB || degenerateCD) {
        goto DRAW_LINE;
    }
    SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
    bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
                                               &normalBC, &unitNormalBC);

    if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
             normals_too_curvy(unitNormalBC, *unitNormalCD)) {
        // subdivide if we can
        if (--subDivide < 0) {
            goto DRAW_LINE;
        }
        SkPoint     tmp[7];
        SkVector    norm, unit, dummy, unitDummy;

        SkChopCubicAtHalf(pts, tmp);
        this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
                       subDivide);
        // we use dummys since we already have a valid (and more accurate)
        // normals for CD
        this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
    } else {
        SkVector    normalB, normalC;
        
        // need normals to inset/outset the off-curve pts B and C

        if (0) {    // this is normal to the line between our adjacent pts
            normalB = pts[2] - pts[0];
            normalB.rotateCCW();
            SkAssertResult(normalB.setLength(fRadius));

            normalC = pts[3] - pts[1];
            normalC.rotateCCW();
            SkAssertResult(normalC.setLength(fRadius));
        } else {    // miter-join
            SkVector    unitBC = pts[2] - pts[1];
            unitBC.normalize();
            unitBC.rotateCCW();

            normalB = unitNormalAB + unitBC;
            normalC = *unitNormalCD + unitBC;

            SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
            SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
                                        SkScalarSqrt((SK_Scalar1 + dot)/2))));
            dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
            SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
                                        SkScalarSqrt((SK_Scalar1 + dot)/2))));
        }

        fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
                        pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
                        pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);

        fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
                        pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
                        pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
    }
}
Example #28
0
static void test_gpu_veto(skiatest::Reporter* reporter) {
    SkPictureRecorder recorder;

    SkCanvas* canvas = recorder.beginRecording(100, 100);
    {
        SkPath path;
        path.moveTo(0, 0);
        path.lineTo(50, 50);

        SkScalar intervals[] = { 1.0f, 1.0f };
        sk_sp<SkPathEffect> dash(SkDashPathEffect::Make(intervals, 2, 0));

        SkPaint paint;
        paint.setStyle(SkPaint::kStroke_Style);
        paint.setPathEffect(dash);

        for (int i = 0; i < 50; ++i) {
            canvas->drawPath(path, paint);
        }
    }
    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
    // path effects currently render an SkPicture undesireable for GPU rendering

    const char *reason = nullptr;
    REPORTER_ASSERT(reporter,
        !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization(&reason));
    REPORTER_ASSERT(reporter, reason);

    canvas = recorder.beginRecording(100, 100);
    {
        SkPath path;

        path.moveTo(0, 0);
        path.lineTo(0, 50);
        path.lineTo(25, 25);
        path.lineTo(50, 50);
        path.lineTo(50, 0);
        path.close();
        REPORTER_ASSERT(reporter, !path.isConvex());

        SkPaint paint;
        paint.setAntiAlias(true);
        for (int i = 0; i < 50; ++i) {
            canvas->drawPath(path, paint);
        }
    }
    picture = recorder.finishRecordingAsPicture();
    // A lot of small AA concave paths should be fine for GPU rendering
    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());

    canvas = recorder.beginRecording(100, 100);
    {
        SkPath path;

        path.moveTo(0, 0);
        path.lineTo(0, 100);
        path.lineTo(50, 50);
        path.lineTo(100, 100);
        path.lineTo(100, 0);
        path.close();
        REPORTER_ASSERT(reporter, !path.isConvex());

        SkPaint paint;
        paint.setAntiAlias(true);
        for (int i = 0; i < 50; ++i) {
            canvas->drawPath(path, paint);
        }
    }
    picture = recorder.finishRecordingAsPicture();
    // A lot of large AA concave paths currently render an SkPicture undesireable for GPU rendering
    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());

    canvas = recorder.beginRecording(100, 100);
    {
        SkPath path;

        path.moveTo(0, 0);
        path.lineTo(0, 50);
        path.lineTo(25, 25);
        path.lineTo(50, 50);
        path.lineTo(50, 0);
        path.close();
        REPORTER_ASSERT(reporter, !path.isConvex());

        SkPaint paint;
        paint.setAntiAlias(true);
        paint.setStyle(SkPaint::kStroke_Style);
        paint.setStrokeWidth(0);
        for (int i = 0; i < 50; ++i) {
            canvas->drawPath(path, paint);
        }
    }
    picture = recorder.finishRecordingAsPicture();
    // hairline stroked AA concave paths are fine for GPU rendering
    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());

    canvas = recorder.beginRecording(100, 100);
    {
        SkPaint paint;
        SkScalar intervals [] = { 10, 20 };
        paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25));

        SkPoint points [2] = { { 0, 0 }, { 100, 0 } };

        for (int i = 0; i < 50; ++i) {
            canvas->drawPoints(SkCanvas::kLines_PointMode, 2, points, paint);
        }
    }
    picture = recorder.finishRecordingAsPicture();
    // fast-path dashed effects are fine for GPU rendering ...
    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());

    canvas = recorder.beginRecording(100, 100);
    {
        SkPaint paint;
        SkScalar intervals [] = { 10, 20 };
        paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25));

        for (int i = 0; i < 50; ++i) {
            canvas->drawRect(SkRect::MakeWH(10, 10), paint);
        }
    }
    picture = recorder.finishRecordingAsPicture();
    // ... but only when applied to drawPoint() calls
    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());

    canvas = recorder.beginRecording(100, 100);
    {
        const SkPath convexClip = make_convex_path();
        const SkPath concaveClip = make_concave_path();

        for (int i = 0; i < 50; ++i) {
            canvas->clipPath(convexClip);
            canvas->clipPath(concaveClip);
            canvas->clipPath(convexClip, SkRegion::kIntersect_Op, true);
            canvas->drawRect(SkRect::MakeWH(100, 100), SkPaint());
        }
    }
    picture = recorder.finishRecordingAsPicture();
    // Convex clips and non-AA concave clips are fine on the GPU.
    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());

    canvas = recorder.beginRecording(100, 100);
    {
        const SkPath concaveClip = make_concave_path();
        for (int i = 0; i < 50; ++i) {
            canvas->clipPath(concaveClip, SkRegion::kIntersect_Op, true);
            canvas->drawRect(SkRect::MakeWH(100, 100), SkPaint());
        }
    }
    picture = recorder.finishRecordingAsPicture();
    // ... but AA concave clips are not.
    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());

    // Nest the previous picture inside a new one.
    canvas = recorder.beginRecording(100, 100);
    {
        canvas->drawPicture(picture);
    }
    picture = recorder.finishRecordingAsPicture();
    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
}
Example #29
0
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
    SkASSERT(&src != NULL && dst != NULL);

    SkScalar radius = SkScalarHalf(fWidth);

    dst->reset();
    if (radius <= 0) {
        return;
    }
    
#ifdef SK_SCALAR_IS_FIXED
    void (*proc)(SkPoint pts[], int count) = identity_proc;
    if (needs_to_shrink(src)) {
        proc = shift_down_2_proc;
        radius >>= 2;
        if (radius == 0) {
            return;
        }
    }
#endif

    SkPathStroker   stroker(radius, fMiterLimit, this->getCap(),
                            this->getJoin());

    SkPath::Iter    iter(src, false);
    SkPoint         pts[4];
    SkPath::Verb    verb, lastSegment = SkPath::kMove_Verb;

    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
        switch (verb) {
            case SkPath::kMove_Verb:
                APPLY_PROC(proc, &pts[0], 1);
                stroker.moveTo(pts[0]);
                break;
            case SkPath::kLine_Verb:
                APPLY_PROC(proc, &pts[1], 1);
                stroker.lineTo(pts[1]);
                lastSegment = verb;
                break;
            case SkPath::kQuad_Verb:
                APPLY_PROC(proc, &pts[1], 2);
                stroker.quadTo(pts[1], pts[2]);
                lastSegment = verb;
                break;
            case SkPath::kCubic_Verb:
                APPLY_PROC(proc, &pts[1], 3);
                stroker.cubicTo(pts[1], pts[2], pts[3]);
                lastSegment = verb;
                break;
            case SkPath::kClose_Verb:
                stroker.close(lastSegment == SkPath::kLine_Verb);
                break;
            default:
                break;
        }
    }
    stroker.done(dst, lastSegment == SkPath::kLine_Verb);

#ifdef SK_SCALAR_IS_FIXED
    // undo our previous down_shift
    if (shift_down_2_proc == proc) {
        // need a real shift methid on path. antialias paths could use this too
        SkMatrix matrix;
        matrix.setScale(SkIntToScalar(4), SkIntToScalar(4));
        dst->transform(matrix);
    }
#endif

    if (fDoFill) {
        if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
            dst->reverseAddPath(src);
        } else {
            dst->addPath(src);
        }
    } else {
        //  Seems like we can assume that a 2-point src would always result in
        //  a convex stroke, but testing has proved otherwise.
        //  TODO: fix the stroker to make this assumption true (without making
        //  it slower that the work that will be done in computeConvexity())
#if 0
        // this test results in a non-convex stroke :(
        static void test(SkCanvas* canvas) {
            SkPoint pts[] = { 146.333328,  192.333328, 300.333344, 293.333344 };
            SkPaint paint;
            paint.setStrokeWidth(7);
            paint.setStrokeCap(SkPaint::kRound_Cap);
            canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
        }        
#endif
#if 0
        if (2 == src.countPoints()) {
            dst->setIsConvex(true);
        }
#endif
    }
void GetLocalBounds(const SkPath& path, const SkDrawShadowRec& rec, const SkMatrix& ctm,
                    SkRect* bounds) {
    SkRect ambientBounds = path.getBounds();
    SkScalar occluderZ;
    if (SkScalarNearlyZero(rec.fZPlaneParams.fX) && SkScalarNearlyZero(rec.fZPlaneParams.fY)) {
        occluderZ = rec.fZPlaneParams.fZ;
    } else {
        occluderZ = compute_z(ambientBounds.fLeft, ambientBounds.fTop, rec.fZPlaneParams);
        occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fTop,
                                                rec.fZPlaneParams));
        occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fLeft, ambientBounds.fBottom,
                                                rec.fZPlaneParams));
        occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fBottom,
                                                rec.fZPlaneParams));
    }
    SkScalar ambientBlur;
    SkScalar spotBlur;
    SkScalar spotScale;
    SkPoint spotOffset;
    if (ctm.hasPerspective()) {
        // transform ambient and spot bounds into device space
        ctm.mapRect(&ambientBounds);

        // get ambient blur (in device space)
        ambientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ);

        // get spot params (in device space)
        SkPoint devLightPos = SkPoint::Make(rec.fLightPos.fX, rec.fLightPos.fY);
        ctm.mapPoints(&devLightPos, 1);
        SkDrawShadowMetrics::GetSpotParams(occluderZ, devLightPos.fX, devLightPos.fY,
                                           rec.fLightPos.fZ, rec.fLightRadius,
                                           &spotBlur, &spotScale, &spotOffset);
    } else {
        SkScalar devToSrcScale = SkScalarInvert(ctm.getMinScale());

        // get ambient blur (in local space)
        SkScalar devSpaceAmbientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ);
        ambientBlur = devSpaceAmbientBlur*devToSrcScale;

        // get spot params (in local space)
        SkDrawShadowMetrics::GetSpotParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY,
                                           rec.fLightPos.fZ, rec.fLightRadius,
                                           &spotBlur, &spotScale, &spotOffset);

        // convert spot blur to local space
        spotBlur *= devToSrcScale;
    }

    // in both cases, adjust ambient and spot bounds
    SkRect spotBounds = ambientBounds;
    ambientBounds.outset(ambientBlur, ambientBlur);
    spotBounds.fLeft *= spotScale;
    spotBounds.fTop *= spotScale;
    spotBounds.fRight *= spotScale;
    spotBounds.fBottom *= spotScale;
    spotBounds.offset(spotOffset.fX, spotOffset.fY);
    spotBounds.outset(spotBlur, spotBlur);

    // merge bounds
    *bounds = ambientBounds;
    bounds->join(spotBounds);
    // outset a bit to account for floating point error
    bounds->outset(1, 1);

    // if perspective, transform back to src space
    if (ctm.hasPerspective()) {
        // TODO: create tighter mapping from dev rect back to src rect
        SkMatrix inverse;
        if (ctm.invert(&inverse)) {
            inverse.mapRect(bounds);
        }
    }
}