blob: ee5c39b05d7ffaa21abb4347642ad5750db36a13 [file] [log] [blame] [edit]
/*
* 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