| /* |
| * Copyright 2024 Rive |
| */ |
| |
| #include "gm.hpp" |
| #include "gmutils.hpp" |
| #include "rive/math/bezier_utils.hpp" |
| #include "rive/math/math_types.hpp" |
| #include "rive/math/wangs_formula.hpp" |
| |
| using namespace rivegm; |
| |
| namespace rive::gpu |
| { |
| class FeatherGM : public GM |
| { |
| public: |
| FeatherGM() : GM(1800, 2100) |
| { |
| m_paint = TestingWindow::Get()->factory()->makeRenderPaint(); |
| m_paint->color(0xffffffff); |
| } |
| |
| ColorInt clearColor() const override { return 0xff000000; } |
| |
| void onDraw(Renderer* renderer) override |
| { |
| renderer->scale(1.5f, 1.5f); |
| for (int y = 0; y < 7; ++y) |
| { |
| renderer->save(); |
| for (int x = 0; x < 6; ++x) |
| { |
| renderer->save(); |
| renderer->translate(50, 50); |
| // For the y=0 case, this checks that epsilon size feathers |
| // generate smooth AA. |
| m_paint->feather(expf(y)); |
| drawCell(renderer, x, y, m_paint.get()); |
| renderer->restore(); |
| renderer->translate(200, 0); |
| } |
| renderer->restore(); |
| renderer->translate(0, 200); |
| } |
| } |
| |
| private: |
| virtual void drawCell(Renderer*, int x, int y, RenderPaint*) = 0; |
| |
| rcp<RenderPaint> m_paint; |
| }; |
| |
| // Check that basic shapes feather correctly (enough). |
| class FeatherShapesGM : public FeatherGM |
| { |
| public: |
| void onOnceBeforeDraw() override |
| { |
| m_shapes.reserve(6); |
| |
| auto& square = m_shapes.emplace_back(makeShape()); |
| square->addRect(0, 0, 100, 100); |
| |
| auto& circle = m_shapes.emplace_back(makeShape()); |
| path_addOval(circle.get(), |
| AABB{0, 0, 100, 100}, |
| rivegm::PathDirection::clockwise); |
| |
| auto shark = makeShape(); |
| shark->moveTo(376, 1422); |
| shark->cubicTo(774, 526, 60, 660, 398, 329); |
| shark->cubicTo(639.333374f, |
| 149.666656f, |
| 905.333313f, |
| 258.666656f, |
| 1196, |
| 656); |
| shark->cubicTo(686, 460, 686, 660, 1370, 1006); |
| // "cuspy" flat line with control points outside T=0..1. |
| shark->cubicTo(1701.333374f, |
| 867.333374f, |
| 44.666626f, |
| 1560.666626f, |
| 376, |
| 1422); |
| m_shapes.emplace_back(makeShape()) |
| ->addRenderPath(shark.get(), |
| Mat2D().scale({.11f, .11f}).translate({-40, -38})); |
| |
| auto& cusp = m_shapes.emplace_back(makeShape()); |
| cusp->lineTo(100, 0); |
| cusp->cubicTo(0, 100, 0, 0, 100, 100); |
| cusp->lineTo(0, 100); |
| cusp->cubicTo(50, 67, -50, 33, 0, 0); |
| |
| float r = 40; |
| auto& rrect = m_shapes.emplace_back(makeShape()); |
| rrect->moveTo(r, 0); |
| rrect->lineTo(100 - r, 0); |
| rrect->cubicTo(100 - r / 2, 0, 100, r / 2, 100, r); |
| rrect->lineTo(100, 100 - r); |
| rrect->cubicTo(100, 100 - r / 5, 100 - r / 5, 100, 100 - r, 100); |
| rrect->lineTo(r, 100); |
| rrect->cubicTo(0 + r / 3, 100, 0, 100 - r / 3, 0, 100 - r); |
| rrect->lineTo(0, r); |
| rrect->cubicTo(0, 0, 0, 0, r, 0); |
| |
| auto& irrect = m_shapes.emplace_back(makeShape()); |
| irrect->addRenderPath(square.get(), Mat2D()); |
| irrect->addRenderPath(rrect.get(), |
| Mat2D(-60.f / 100, 0, 0, 60.f / 100, 80, 20)); |
| } |
| |
| protected: |
| class Shape : public RenderPath |
| { |
| public: |
| Shape() : m_path(FillRule::clockwise) {} |
| |
| RenderPath* renderPath() override { return m_path.get(); } |
| void rewind() override { m_path = Path(); } |
| void fillRule(FillRule value) override { m_path->fillRule(value); } |
| void moveTo(float x, float y) override |
| { |
| m_path->moveTo(x, y); |
| m_begin = m_pen = {x, y}; |
| } |
| void lineTo(float x, float y) override |
| { |
| m_path->lineTo(x, y); |
| m_pen = {x, y}; |
| } |
| void cubicTo(float ox, float oy, float ix, float iy, float x, float y) |
| override |
| { |
| m_path->cubicTo(ox, oy, ix, iy, x, y); |
| m_pen = {x, y}; |
| } |
| void close() override |
| { |
| m_path->close(); |
| m_pen = m_begin; |
| } |
| void addRenderPath(RenderPath* path, const Mat2D& transform) override |
| { |
| auto shape = static_cast<Shape*>(path); |
| m_path->addRenderPath(shape->renderPath(), transform); |
| m_pen = shape->m_pen; |
| m_begin = shape->m_begin; |
| } |
| |
| void addRawPath(const RawPath& path) override |
| { |
| m_path->addRawPath(path); |
| } |
| |
| protected: |
| Path m_path; |
| Vec2D m_pen = {0, 0}; |
| Vec2D m_begin = {0, 0}; |
| }; |
| |
| virtual std::unique_ptr<Shape> makeShape() |
| { |
| return std::make_unique<Shape>(); |
| } |
| |
| void drawCell(Renderer* renderer, int x, int y, RenderPaint* paint) override |
| { |
| renderer->drawPath(m_shapes[x]->renderPath(), paint); |
| } |
| |
| std::vector<std::unique_ptr<Shape>> m_shapes; |
| }; |
| GMREGISTER(feather_shapes, return new FeatherShapesGM) |
| |
| // Validate corners by tessellating shapes into polygons and then feathering |
| // them. |
| class FeatherPolyShapesGM : public FeatherShapesGM |
| { |
| private: |
| class PolyShape : public Shape |
| { |
| public: |
| void cubicTo(float ox, float oy, float ix, float iy, float x, float y) |
| override |
| { |
| Vec2D p[4] = {m_pen, {ox, oy}, {ix, iy}, {x, y}}; |
| int n = wangs_formula::cubic(p, 8); |
| n = std::max(n, 3); |
| n = (n & ~1) + 1; |
| math::EvalCubic ec(p); |
| float dt = 2.f / n; |
| float4 t = dt * float4{.5f, .5f, 1, 1}; |
| for (int i = 1; i < n; i += 2, t += dt) |
| { |
| float4 result = ec(t); |
| Shape::lineTo(result.x, result.y); |
| Shape::lineTo(result.z, result.w); |
| } |
| Shape::lineTo(x, y); |
| } |
| }; |
| |
| virtual std::unique_ptr<Shape> makeShape() |
| { |
| return std::make_unique<PolyShape>(); |
| } |
| }; |
| GMREGISTER(feather_polyshapes, return new FeatherPolyShapesGM) |
| |
| // Check that corners don't have artifacts. |
| class FeatherCornerGM : public FeatherGM |
| { |
| private: |
| virtual void drawCell(Renderer* renderer, |
| int x, |
| int y, |
| RenderPaint* paint) override |
| { |
| float theta; |
| if (x == 0) |
| { |
| theta = math::PI; |
| } |
| else if (x == 1) |
| { |
| theta = math::PI / 2; |
| } |
| else |
| { |
| theta = math::PI * powf((5 - x) / 5.f, 2.71828f); |
| } |
| renderer->clipPath(PathBuilder::Rect({-20, -20, 120, 120})); |
| float left = 3 * math::PI; |
| Vec2D v0 = Vec2D(cosf(left), sinf(left)); |
| Vec2D v1 = Vec2D(cosf(left - theta), sinf(left - theta)); |
| Path path(FillRule::clockwise); |
| path->move(200 * v0 + Vec2D{0, 200}); |
| path->line(200 * v0); |
| path->lineTo(0, 0); |
| path->line(200 * v1); |
| path->line(200 * v1 + Vec2D{0, 200}); |
| renderer->translate(50, 50); |
| renderer->drawPath(path, paint); |
| } |
| }; |
| GMREGISTER(feather_corner, return new FeatherCornerGM) |
| |
| // Check that tightly rounded corners don't have artifacts. |
| class FeatherRoundCornerGM : public FeatherGM |
| { |
| private: |
| virtual void drawCell(Renderer* renderer, |
| int x, |
| int y, |
| RenderPaint* paint) override |
| { |
| float theta = math::PI * powf((5 - x) / 5.f, 1.5f); |
| renderer->clipPath(PathBuilder::Rect({-20, -20, 120, 120})); |
| float down = math::PI / 2; |
| Vec2D v0 = Vec2D(cosf(down + theta / 2), sinf(down + theta / 2)); |
| Vec2D v1 = Vec2D(cosf(down - theta / 2), sinf(down - theta / 2)); |
| Path path(FillRule::clockwise); |
| path->move(200 * v0 + Vec2D{0, 200}); |
| path->line(200 * v0); |
| path->line(75 * v0); |
| path->cubic({0, 0}, {0, 0}, 75 * v1); |
| path->line(200 * v1); |
| path->line(200 * v1 + Vec2D{0, 200}); |
| renderer->translate(50, 50); |
| renderer->drawPath(path, paint); |
| } |
| }; |
| GMREGISTER(feather_roundcorner, return new FeatherRoundCornerGM) |
| |
| // Check that the cusp points on a squashed ellipse don't have artifacts. |
| class FeatherEllipseGM : public FeatherGM |
| { |
| |
| private: |
| virtual void drawCell(Renderer* renderer, |
| int x, |
| int y, |
| RenderPaint* paint) override |
| { |
| auto unitCircle = PathBuilder::Circle(0, 0, 1); |
| float squash = powf((5 - x) / 5.f, 2.71828f); |
| Path ellipse(FillRule::clockwise); |
| ellipse->addRenderPath(unitCircle, Mat2D::fromScale(50 * squash, 50)); |
| renderer->translate(50, 50); |
| renderer->drawPath(ellipse, paint); |
| } |
| }; |
| GMREGISTER(feather_ellipse, return new FeatherEllipseGM) |
| |
| // Check that a non-degenerate cubic cusps and near-cusps don't have artifacts. |
| class FeatherCuspGM : public FeatherGM |
| { |
| private: |
| virtual void drawCell(Renderer* renderer, |
| int x, |
| int y, |
| RenderPaint* paint) override |
| { |
| float dx = 10 * copysignf(powf(fabsf(x - 3.f), 1.75f), x - 3); |
| Path cusp(FillRule::clockwise); |
| cusp->moveTo(0, 100); |
| cusp->moveTo(0, 100); |
| cusp->cubicTo(100 + dx, 0, 0 - dx, 0, 100, 100); |
| renderer->drawPath(cusp, paint); |
| } |
| }; |
| GMREGISTER(feather_cusp, return new FeatherCuspGM) |
| |
| // Check that basic strokes feather correctly (enough). |
| class FeatherStrokesGM : public FeatherGM |
| { |
| public: |
| FeatherStrokesGM() : FeatherGM() |
| { |
| m_strokes.reserve(6); |
| |
| auto& square = m_strokes.emplace_back(); |
| square->addRect(0, 0, 100, 100); |
| |
| m_strokes.emplace_back(PathBuilder::Circle(50, 50, 50)); |
| |
| auto& serp = m_strokes.emplace_back(); |
| serp->moveTo(0, 100); |
| serp->cubicTo(60, 0, 30, 0, 100, 100); |
| |
| auto& cusp = m_strokes.emplace_back(); |
| cusp->lineTo(100, 0); |
| cusp->cubicTo(0, 100, 0, 0, 100, 100); |
| cusp->lineTo(0, 100); |
| cusp->cubicTo(50, 67, -50, 33, 0, 0); |
| |
| auto& loop = m_strokes.emplace_back(); |
| loop->moveTo(25, 100); |
| loop->cubicTo(250, -20, -150, -20, 75, 100); |
| |
| auto& irrect = m_strokes.emplace_back(); |
| float r = 40; |
| Path rrect; |
| rrect->moveTo(r, 0); |
| rrect->lineTo(100 - r, 0); |
| rrect->cubicTo(100 - r / 2, 0, 100, r / 2, 100, r); |
| rrect->lineTo(100, 100 - r); |
| rrect->cubicTo(100, 100 - r / 5, 100 - r / 5, 100, 100 - r, 100); |
| rrect->lineTo(r, 100); |
| rrect->cubicTo(0 + r / 3, 100, 0, 100 - r / 3, 0, 100 - r); |
| rrect->lineTo(0, r); |
| rrect->cubicTo(0, 0, 0, 0, r, 0); |
| irrect->addRenderPath(square, Mat2D()); |
| irrect->addRenderPath(rrect, |
| Mat2D(-80.f / 100, 0, 0, 80.f / 100, 90, 10)); |
| } |
| |
| private: |
| virtual void drawCell(Renderer* renderer, |
| int x, |
| int y, |
| RenderPaint* paint) override |
| { |
| paint->style(RenderPaintStyle::stroke); |
| paint->thickness(15); |
| // Feathers ignore the join. |
| paint->join((y & 1) ? StrokeJoin::bevel : StrokeJoin::miter); |
| // Feathers ignore the cap. |
| paint->cap((y & 1) ? StrokeCap::square : StrokeCap::butt); |
| renderer->drawPath(m_strokes[x].get(), paint); |
| } |
| |
| std::vector<Path> m_strokes; |
| }; |
| GMREGISTER(feather_strokes, return new FeatherStrokesGM) |
| } // namespace rive::gpu |