| /* | |
| * Copyright 2015 Google Inc. | |
| * Copyright 2022 Rive | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "gm.hpp" | |
| #include "gmutils.hpp" | |
| #include "rive/renderer.hpp" | |
| #include "rive/math/vec2d.hpp" | |
| #include "rive/math/math_types.hpp" | |
| using namespace rivegm; | |
| using namespace rive; | |
| static void create_ngon(int n, Vec2D* pts, float width, float height) | |
| { | |
| float angleStep = 360.0f / n, angle = 0.0f; | |
| if ((n % 2) == 1) | |
| { | |
| angle = angleStep / 2.0f; | |
| } | |
| for (int i = 0; i < n; ++i) | |
| { | |
| pts[i].x = -sinf(rive::math::degrees_to_radians(angle)) * width; | |
| pts[i].y = cosf(rive::math::degrees_to_radians(angle)) * height; | |
| angle += angleStep; | |
| } | |
| } | |
| namespace ConvexLineOnlyData | |
| { | |
| // narrow rect | |
| const Vec2D gPoints0[] = {{-1.5f, -50.0f}, | |
| {1.5f, -50.0f}, | |
| {1.5f, 50.0f}, | |
| {-1.5f, 50.0f}}; | |
| // narrow rect on an angle | |
| const Vec2D gPoints1[] = {{-50.0f, -49.0f}, | |
| {-49.0f, -50.0f}, | |
| {50.0f, 49.0f}, | |
| {49.0f, 50.0f}}; | |
| // trap - narrow on top - wide on bottom | |
| const Vec2D gPoints2[] = {{-10.0f, -50.0f}, | |
| {10.0f, -50.0f}, | |
| {50.0f, 50.0f}, | |
| {-50.0f, 50.0f}}; | |
| // wide skewed rect | |
| const Vec2D gPoints3[] = {{-50.0f, -50.0f}, | |
| {0.0f, -50.0f}, | |
| {50.0f, 50.0f}, | |
| {0.0f, 50.0f}}; | |
| // thin rect with colinear-ish lines | |
| const Vec2D gPoints4[] = { | |
| {-6.0f, -50.0f}, | |
| {4.0f, -50.0f}, | |
| {5.0f, -25.0f}, // remove if collinear diagonal points are not concave | |
| {6.0f, 0.0f}, | |
| {5.0f, 25.0f}, // remove if collinear diagonal points are not concave | |
| {4.0f, 50.0f}, | |
| {-4.0f, 50.0f}}; | |
| // degenerate | |
| const Vec2D gPoints5[] = {{-0.025f, -0.025f}, | |
| {0.025f, -0.025f}, | |
| {0.025f, 0.025f}, | |
| {-0.025f, 0.025f}}; | |
| // Triangle in which the first point should fuse with last | |
| const Vec2D gPoints6[] = {{-20.0f, -13.0f}, | |
| {-20.0f, -13.05f}, | |
| {20.0f, -13.0f}, | |
| {20.0f, 27.0f}}; | |
| // thin rect with colinear lines | |
| const Vec2D gPoints7[] = {{-10.0f, -50.0f}, | |
| {10.0f, -50.0f}, | |
| {10.0f, -25.0f}, | |
| {10.0f, 0.0f}, | |
| {10.0f, 25.0f}, | |
| {10.0f, 50.0f}, | |
| {-10.0f, 50.0f}}; | |
| // capped teardrop | |
| const Vec2D gPoints8[] = {{50.00f, 50.00f}, | |
| {0.00f, 50.00f}, | |
| {-15.45f, 47.55f}, | |
| {-29.39f, 40.45f}, | |
| {-40.45f, 29.39f}, | |
| {-47.55f, 15.45f}, | |
| {-50.00f, 0.00f}, | |
| {-47.55f, -15.45f}, | |
| {-40.45f, -29.39f}, | |
| {-29.39f, -40.45f}, | |
| {-15.45f, -47.55f}, | |
| {0.00f, -50.00f}, | |
| {50.00f, -50.00f}}; | |
| // teardrop | |
| const Vec2D gPoints9[] = {{4.39f, 40.45f}, | |
| {-9.55f, 47.55f}, | |
| {-25.00f, 50.00f}, | |
| {-40.45f, 47.55f}, | |
| {-54.39f, 40.45f}, | |
| {-65.45f, 29.39f}, | |
| {-72.55f, 15.45f}, | |
| {-75.00f, 0.00f}, | |
| {-72.55f, -15.45f}, | |
| {-65.45f, -29.39f}, | |
| {-54.39f, -40.45f}, | |
| {-40.45f, -47.55f}, | |
| {-25.0f, -50.0f}, | |
| {-9.55f, -47.55f}, | |
| {4.39f, -40.45f}, | |
| {75.00f, 0.00f}}; | |
| // clipped triangle | |
| const Vec2D gPoints10[] = { | |
| {-10.0f, -50.0f}, | |
| {10.0f, -50.0f}, | |
| {50.0f, 31.0f}, | |
| {40.0f, 50.0f}, | |
| {-40.0f, 50.0f}, | |
| {-50.0f, 31.0f}, | |
| }; | |
| const Vec2D* gPoints[] = { | |
| gPoints0, | |
| gPoints1, | |
| gPoints2, | |
| gPoints3, | |
| gPoints4, | |
| gPoints5, | |
| gPoints6, | |
| gPoints7, | |
| gPoints8, | |
| gPoints9, | |
| gPoints10, | |
| }; | |
| const size_t gSizes[] = { | |
| std::size(gPoints0), | |
| std::size(gPoints1), | |
| std::size(gPoints2), | |
| std::size(gPoints3), | |
| std::size(gPoints4), | |
| std::size(gPoints5), | |
| std::size(gPoints6), | |
| std::size(gPoints7), | |
| std::size(gPoints8), | |
| std::size(gPoints9), | |
| std::size(gPoints10), | |
| }; | |
| static_assert(std::size(gSizes) == std::size(gPoints), "array_mismatch"); | |
| } // namespace ConvexLineOnlyData | |
| // This GM is intended to exercise Ganesh's handling of convex line-only | |
| // paths | |
| class ConvexLineOnlyPathsGM : public GM | |
| { | |
| public: | |
| ConvexLineOnlyPathsGM() : GM(kGMWidth, kGMHeight) {} | |
| protected: | |
| static Path GetPath(int index, rivegm::PathDirection dir) | |
| { | |
| std::unique_ptr<Vec2D[]> data(nullptr); | |
| const Vec2D* points; | |
| int numPts; | |
| if (index < (int)std::size(ConvexLineOnlyData::gPoints)) | |
| { | |
| // manually specified | |
| points = ConvexLineOnlyData::gPoints[index]; | |
| numPts = (int)ConvexLineOnlyData::gSizes[index]; | |
| } | |
| else | |
| { | |
| // procedurally generated | |
| float width = (float)kMaxPathHeight / 2; | |
| float height = (float)kMaxPathHeight / 2; | |
| switch (index - std::size(ConvexLineOnlyData::gPoints)) | |
| { | |
| case 0: | |
| numPts = 3; | |
| break; | |
| case 1: | |
| numPts = 4; | |
| break; | |
| case 2: | |
| numPts = 5; | |
| break; | |
| case 3: // squashed pentagon | |
| numPts = 5; | |
| width = (float)kMaxPathHeight / 5; | |
| break; | |
| case 4: | |
| numPts = 6; | |
| break; | |
| case 5: | |
| numPts = 8; | |
| break; | |
| case 6: // squashed octogon | |
| numPts = 8; | |
| width = (float)kMaxPathHeight / 5; | |
| break; | |
| case 7: | |
| numPts = 20; | |
| break; | |
| case 8: | |
| numPts = 100; | |
| break; | |
| default: | |
| numPts = 3; | |
| break; | |
| } | |
| data = std::make_unique<Vec2D[]>(numPts); | |
| create_ngon(numPts, data.get(), width, height); | |
| points = data.get(); | |
| } | |
| PathBuilder builder; | |
| if (rivegm::PathDirection::cw == dir) | |
| { | |
| builder.moveTo(points[0].x, points[0].y); | |
| for (int i = 1; i < numPts; ++i) | |
| { | |
| builder.lineTo(points[i].x, points[i].y); | |
| } | |
| } | |
| else | |
| { | |
| builder.moveTo(points[numPts - 1].x, points[numPts - 1].y); | |
| for (int i = numPts - 2; i >= 0; --i) | |
| { | |
| builder.lineTo(points[i].x, points[i].y); | |
| } | |
| } | |
| builder.close(); | |
| Path path = builder.detach(); | |
| #if 0 | |
| // Each path this method returns should be convex, only composed of | |
| // lines, wound the right direction, and short enough to fit in one | |
| // of the GMs rows. | |
| SkASSERT(path.isConvex()); | |
| SkASSERT(SkPath::kLine_SegmentMask == path.getSegmentMasks()); | |
| SkPathFirstDirection actualDir = SkPathPriv::ComputeFirstDirection(path); | |
| SkASSERT(SkPathPriv::AsFirstDirection(dir) == actualDir); | |
| SkRect bounds = path.getBounds(); | |
| SkASSERT(floatNearlyEqual(bounds.centerX(), 0.0f)); | |
| SkASSERT(bounds.height() <= kMaxPathHeight); | |
| #endif | |
| return path; | |
| } | |
| // Draw a single path several times, shrinking it, flipping its direction | |
| // and changing its start vertex each time. | |
| void drawPath(Renderer* renderer, int index, Vec2D* offset) | |
| { | |
| Vec2D center; | |
| { | |
| Path path = GetPath(index, rivegm::PathDirection::cw); | |
| if (offset->x + kMaxPathWidth /* path.getBounds().width() */ > | |
| kGMWidth) | |
| { | |
| offset->x = 0; | |
| offset->y += kMaxPathHeight; | |
| } | |
| center = {offset->x + | |
| 0.5f * | |
| (kMaxPathHeight /* path.getBounds().width() */), | |
| offset->y}; | |
| offset->x += kMaxPathWidth /* path.getBounds().width() */; | |
| } | |
| const ColorInt colors[2] = {0xff00ff00, 0xff0000ff}; | |
| const rivegm::PathDirection dirs[2] = {rivegm::PathDirection::cw, | |
| rivegm::PathDirection::ccw}; | |
| const float scales[] = {1.0f, 0.75f, 0.5f, 0.25f, 0.1f, 0.01f, 0.001f}; | |
| Paint paint; | |
| for (size_t i = 0; i < std::size(scales); ++i) | |
| { | |
| Path path = GetPath(index, dirs[i % 2]); | |
| renderer->save(); | |
| renderer->translate(center.x, center.y); | |
| renderer->scale(scales[i], scales[i]); | |
| paint->color(colors[i % 2]); | |
| renderer->drawPath(path, paint); | |
| renderer->restore(); | |
| } | |
| } | |
| void onDraw(Renderer* renderer) override | |
| { | |
| // the right edge of the last drawn path | |
| Vec2D offset = {0, 0.5f * (kMaxPathHeight)}; | |
| for (int i = 0; i < kNumPaths; ++i) | |
| { | |
| this->drawPath(renderer, i, &offset); | |
| } | |
| #if 0 | |
| { | |
| // Repro for crbug.com/472723 (Missing AA on portions of graphic with GPU | |
| // rasterization) | |
| Paint p; | |
| Path p1 = PathBuilder() | |
| .moveTo(60.8522949f, 364.671021f) | |
| .moveTo(59.4380493f, 364.671021f) | |
| .moveTo(385.414276f, 690.647217f) | |
| .moveTo(386.121399f, 689.940125f) | |
| .detach(); | |
| renderer->save(); | |
| renderer->translate(356.0f, 50.0f); | |
| renderer->drawPath(p1, p); | |
| renderer->restore(); | |
| // Repro for crbug.com/869172 (SVG path incorrectly simplified when using GPU | |
| // Rasterization). This will only draw anything in the stroke-and-fill version. | |
| Path p2 = PathBuilder() | |
| .moveTo(10.f, 0.f) | |
| .lineTo(38.f, 0.f) | |
| .lineTo(66.f, 0.f) | |
| .lineTo(94.f, 0.f) | |
| .lineTo(122.f, 0.f) | |
| .lineTo(150.f, 0.f) | |
| .lineTo(150.f, 0.f) | |
| .lineTo(122.f, 0.f) | |
| .lineTo(94.f, 0.f) | |
| .lineTo(66.f, 0.f) | |
| .lineTo(38.f, 0.f) | |
| .lineTo(10.f, 0.f) | |
| .close() | |
| .detach(); | |
| renderer->save(); | |
| renderer->translate(0.0f, 500.0f); | |
| renderer->drawPath(p2, p); | |
| renderer->restore(); | |
| // Repro for crbug.com/856137. This path previously caused GrAAConvexTessellator to | |
| // turn inset rings into outsets when adjacent bisector angles converged outside the | |
| // previous ring due to accumulated error. | |
| Path p3 = PathBuilder() | |
| .moveTo(1184.96f, 982.557f) | |
| .lineTo(1183.71f, 982.865f) | |
| .lineTo(1180.99f, 982.734f) | |
| .lineTo(1178.5f, 981.541f) | |
| .lineTo(1176.35f, 979.367f) | |
| .lineTo(1178.94f, 938.854f) | |
| .lineTo(1181.35f, 936.038f) | |
| .lineTo(1183.96f, 934.117f) | |
| .lineTo(1186.67f, 933.195f) | |
| .lineTo(1189.36f, 933.342f) | |
| .lineTo(1191.58f, 934.38f) | |
| .close() | |
| .detach(); | |
| p3->fillRule(FillRule::evenOdd); | |
| renderer->save(); | |
| renderer->transform(Mat2D(0.0893210843f, 0, 0, 0.0893210843f, 79.1197586f, 300)); | |
| renderer->drawPath(p3, p); | |
| renderer->restore(); | |
| } | |
| #endif | |
| } | |
| private: | |
| inline static constexpr int kStrokeWidth = 10; | |
| inline static constexpr int kNumPaths = 20; | |
| inline static constexpr int kMaxPathWidth = 130; | |
| inline static constexpr int kMaxPathHeight = 130; | |
| inline static constexpr int kGMWidth = 540; | |
| inline static constexpr int kGMHeight = 680; | |
| using INHERITED = GM; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| GMREGISTER(convex_lineonly_ths, return new ConvexLineOnlyPathsGM;) |