blob: 46a87a266b7cf048c7decd2de4776202d204bf30 [file] [log] [blame] [edit]
/*
* Copyright 2011 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/math_types.hpp"
#include "rive/math/vec2d.hpp"
#include "common/rand.hpp"
using namespace rivegm;
using namespace rive;
#define W 400
#define H 400
#define N 50
constexpr float SW = static_cast<float>(W);
constexpr float SH = static_cast<float>(H);
static void rnd_rect(AABB* r, RenderPaint* paint, Rand& rand)
{
float x = (static_cast<int32_t>(rand.u32() >> 16)) * 1.52587890625e-5f * W;
float y = (static_cast<int32_t>(rand.u32() >> 16)) * 1.52587890625e-5f * H;
float w =
(static_cast<int32_t>(rand.u32() >> 16)) * 1.52587890625e-5f * (W >> 2);
float h =
(static_cast<int32_t>(rand.u32() >> 16)) * 1.52587890625e-5f * (H >> 2);
float hoffset =
(static_cast<int32_t>(static_cast<int32_t>(rand.u32()) >> 15) *
1.52587890625e-5f);
float woffset =
(static_cast<int32_t>(static_cast<int32_t>(rand.u32()) >> 15) *
1.52587890625e-5f);
r->minX = x;
r->minY = y;
r->maxX = x + w;
r->maxY = y + h;
r->offset(-w / 2 + woffset, -h / 2 + hoffset);
paint->color(rand.u32());
// paint->setAlphaf(1.0f);
}
class StrokesGM : public GM
{
public:
StrokesGM() : GM(W, H) {}
protected:
void onDraw(Renderer* renderer) override
{
Paint paint;
paint->style(RenderPaintStyle::stroke);
paint->thickness(static_cast<float>(9) / 2);
renderer->save();
renderer->clipPath(PathBuilder::Rect({static_cast<float>(2),
static_cast<float>(2),
SW - static_cast<float>(2),
SH - static_cast<float>(2)}));
Rand rand;
for (int i = 0; i < N; i++)
{
AABB r;
rnd_rect(&r, paint, rand);
renderer->drawPath(PathBuilder::Oval(r), paint);
rnd_rect(&r, paint, rand);
renderer->drawPath(
PathBuilder::RRect(r, r.width() / 4, r.height() / 4),
paint);
rnd_rect(&r, paint, rand);
}
renderer->restore();
}
};
/* See
https://code.google.com/p/chromium/issues/detail?id=422974 and
http://jsfiddle.net/1xnku3sg/2/
*/
class ZeroLenStrokesGM : public GM
{
public:
ZeroLenStrokesGM() : GM(W * 3 / 7, H * 3 / 4) {}
private:
Path fMoveHfPath, fMoveZfPath, /* fDashedfPath, */ fRefPath[4];
Path fCubicPath, fQuadPath, fLinePath;
protected:
void onOnceBeforeDraw() override
{
// SkAssertResult(SkParsePath::FromSVGString("M0,0h0M10,0h0M20,0h0",
// &fMoveHfPath));
// SkAssertResult(SkParsePath::FromSVGString("M0,0zM10,0zM20,0z",
// &fMoveZfPath)); SkAssertResult(SkParsePath::FromSVGString("M0,0h25",
// &fDashedfPath)); SkAssertResult(SkParsePath::FromSVGString("M 0 0 C 0
// 0 0 0 0 0", &fCubicPath));
// SkAssertResult(SkParsePath::FromSVGString("M 0 0 Q 0 0 0 0",
// &fQuadPath)); SkAssertResult(SkParsePath::FromSVGString("M 0 0 L 0
// 0", &fLinePath));
fMoveHfPath = PathBuilder()
.moveTo(0, 0)
.lineTo(0, 0)
.moveTo(10, 0)
.lineTo(10, 0)
.moveTo(20, 0)
.lineTo(20, 0)
.detach();
fMoveZfPath = PathBuilder()
.moveTo(0, 0)
.close()
.moveTo(10, 0)
.close()
.moveTo(20, 0)
.close()
.detach();
fCubicPath =
PathBuilder().moveTo(0, 0).cubicTo(0, 0, 0, 0, 0, 0).detach();
fQuadPath = PathBuilder().moveTo(0, 0).quadTo(0, 0, 0, 0).detach();
fLinePath = PathBuilder().moveTo(0, 0).lineTo(0, 0).detach();
PathBuilder b[4];
for (int i = 0; i < 3; ++i)
{
b[0].addOval({i * 10.f - 5, 0 - 5, i * 10.f + 5, 0 + 5});
b[1].addOval({i * 10.f - 10, 0 - 10, i * 10.f + 10, 0 + 10});
b[2].addRect({i * 10.f - 4, -2, i * 10.f + 4, 6});
b[3].addRect({i * 10.f - 10, -10, i * 10.f + 10, 10});
}
for (int i = 0; i < 4; ++i)
{
fRefPath[i] = b[i].detach();
}
}
void onDraw(Renderer* renderer) override
{
Paint fillPaint, strokePaint; // , dashPaint;
strokePaint->style(RenderPaintStyle::stroke);
StrokeCap caps[3] = {StrokeCap::round,
StrokeCap::square,
StrokeCap::butt};
for (int i = 0; i < 3; ++i)
{
fillPaint->color(0xff000000);
strokePaint->color(0xff000000);
strokePaint->thickness(i ? 8.f : 10.f);
strokePaint->cap(caps[i]);
renderer->save();
renderer->translate(10 + i * 100.f, 10);
renderer->drawPath(fMoveHfPath, strokePaint);
renderer->translate(0, 20);
renderer->drawPath(fMoveZfPath, strokePaint);
#if 0
dashPaint = strokePaint;
const float intervals[] = {0, 10};
dashPaint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
SkPath fillPath;
dashPaint.getFillPath(fDashedfPath, &fillPath);
canvas->translate(0, 20);
canvas->drawPath(fDashedfPath, dashPaint);
#endif
renderer->translate(0, 20);
renderer->drawPath(fRefPath[i * 2], fillPaint);
strokePaint->thickness(20);
strokePaint->color(0x80000000);
renderer->translate(0, 50);
renderer->drawPath(fMoveHfPath, strokePaint);
renderer->translate(0, 30);
renderer->drawPath(fMoveZfPath, strokePaint);
renderer->translate(0, 30);
fillPaint->color(0x80000000);
renderer->drawPath(fRefPath[1 + i * 2], fillPaint);
renderer->translate(0, 30);
renderer->drawPath(fCubicPath, strokePaint);
renderer->translate(0, 30);
renderer->drawPath(fQuadPath, strokePaint);
renderer->translate(0, 30);
renderer->drawPath(fLinePath, strokePaint);
renderer->restore();
}
}
};
class TeenyStrokesGM : public GM
{
public:
TeenyStrokesGM() : GM(W, H * .5) {}
private:
static void line(float scale, Renderer* canvas, ColorInt color)
{
Paint p;
p->style(RenderPaintStyle::stroke);
p->color(color);
canvas->translate(50, 0);
canvas->save();
p->thickness(scale * 5);
canvas->scale(1 / scale, 1 / scale);
// canvas->drawLine(20 * scale, 20 * scale, 20 * scale, 100 * scale, p);
// canvas->drawLine(20 * scale, 20 * scale, 100 * scale, 100 * scale,
// p);
canvas->drawPath(PathBuilder()
.moveTo(20 * scale, 20 * scale)
.lineTo(20 * scale, 100 * scale)
.detach(),
p);
canvas->drawPath(PathBuilder()
.moveTo(20 * scale, 20 * scale)
.lineTo(100 * scale, 100 * scale)
.detach(),
p);
canvas->restore();
}
void onDraw(Renderer* canvas) override
{
line(0.00005f, canvas, 0xff000000);
line(0.000045f, canvas, 0xffff0000);
line(0.0000035f, canvas, 0xff00ff00);
line(0.000003f, canvas, 0xff0000ff);
line(0.000002f, canvas, 0xff000000);
}
};
DEF_SIMPLE_GM(CubicStroke, 384, 384, canvas)
{
Paint p;
p->style(RenderPaintStyle::stroke);
p->thickness(1.0720f);
Path path;
path->moveTo(-6000, -6000);
path->cubicTo(-3500, 5500, -500, 5500, 2500, -6500);
canvas->drawPath(path, p);
p->thickness(1.0721f);
canvas->translate(10, 10);
canvas->drawPath(path, p);
p->thickness(1.0722f);
canvas->translate(10, 10);
canvas->drawPath(path, p);
}
DEF_SIMPLE_GM(zerolinestroke, 90, 120, canvas)
{
Paint paint;
paint->style(RenderPaintStyle::stroke);
paint->thickness(20);
paint->cap(StrokeCap::round);
Path path;
path->moveTo(30, 90);
path->lineTo(30, 90);
path->lineTo(60, 90);
path->lineTo(60, 90);
canvas->drawPath(path, paint);
path = Path();
path->moveTo(30, 30);
path->lineTo(60, 30);
canvas->drawPath(path, paint);
path = Path();
path->moveTo(30, 60);
path->lineTo(30, 60);
path->lineTo(60, 60);
canvas->drawPath(path, paint);
}
DEF_SIMPLE_GM(quadcap, 70, 30, canvas)
{
canvas->translate(-100, 5);
Paint p;
p->style(RenderPaintStyle::stroke);
p->thickness(1);
PathBuilder b;
Vec2D pts[] = {{105.738571f, 13.126318f},
{105.738571f, 13.126318f},
{123.753784f, 1.f}};
Vec2D tangent = pts[1] - pts[2];
tangent = tangent.normalized();
Vec2D pts2[3];
memcpy(pts2, pts, sizeof(pts));
const float capOutset = rive::math::PI / 8;
pts2[0].x += tangent.x * capOutset;
pts2[0].y += tangent.y * capOutset;
pts2[1].x += tangent.x * capOutset;
pts2[1].y += tangent.y * capOutset;
pts2[2].x += -tangent.x * capOutset;
pts2[2].y += -tangent.y * capOutset;
b.moveTo(pts2[0].x, pts2[0].y);
b.quadTo(pts2[1].x, pts2[1].y, pts2[2].x, pts2[2].y);
canvas->drawPath(b.detach(), p);
b.moveTo(pts[0].x, pts[0].y);
b.quadTo(pts[1].x, pts[1].y, pts[2].x, pts[2].y);
p->cap(StrokeCap::round);
canvas->translate(30, 0);
canvas->drawPath(b.detach(), p);
}
class Strokes2GM : public GM
{
public:
Strokes2GM() : GM(W, H) {}
private:
Path fPath;
protected:
void onOnceBeforeDraw() override
{
Rand rand;
fPath->moveTo(0, 0);
for (int i = 0; i < 13; i++)
{
float x =
(static_cast<int32_t>(rand.u32() >> 16) * 1.52587890625e-5f) *
(W >> 1);
float y =
(static_cast<int32_t>(rand.u32() >> 16) * 1.52587890625e-5f) *
(H >> 1);
fPath->lineTo(x, y);
}
}
void onDraw(Renderer* canvas) override
{
Paint paint;
paint->style(RenderPaintStyle::stroke);
paint->thickness(static_cast<float>(9) / 2);
canvas->save();
canvas->clipPath(PathBuilder::Rect({static_cast<float>(2),
static_cast<float>(2),
SW - static_cast<float>(2),
SH - static_cast<float>(2)}));
Rand rand;
for (int i = 0; i < N / 2; i++)
{
AABB r;
rnd_rect(&r, paint, rand);
canvas->translate(SW / 2, SH / 2);
canvas->transform(Mat2D::fromRotation(15 * math::PI / 180));
canvas->translate(-SW / 2, -SH / 2);
canvas->drawPath(fPath, paint);
}
canvas->restore();
}
};
//////////////////////////////////////////////////////////////////////////////
static AABB inset(AABB r)
{
r.inset(r.width() / 10, r.height() / 10);
return r;
}
class Strokes3GM : public GM
{
static void make0(PathBuilder* path, const AABB& bounds)
{
path->addRect(bounds, rivegm::PathDirection::cw);
path->addRect(inset(bounds), rivegm::PathDirection::cw);
}
static void make1(PathBuilder* path, const AABB& bounds)
{
path->addRect(bounds, rivegm::PathDirection::cw);
path->addRect(inset(bounds), rivegm::PathDirection::ccw);
}
static void make2(PathBuilder* path, const AABB& bounds)
{
path->addOval(bounds, rivegm::PathDirection::cw);
path->addOval(inset(bounds), rivegm::PathDirection::cw);
}
static void make3(PathBuilder* path, const AABB& bounds)
{
path->addOval(bounds, rivegm::PathDirection::cw);
path->addOval(inset(bounds), rivegm::PathDirection::ccw);
}
static void make4(PathBuilder* path, const AABB& bounds)
{
path->addRect(bounds, rivegm::PathDirection::cw);
AABB r = bounds;
r.inset(bounds.width() / 10, -bounds.height() / 10);
path->addOval(r, rivegm::PathDirection::cw);
}
static void make5(PathBuilder* path, const AABB& bounds)
{
path->addRect(bounds, rivegm::PathDirection::cw);
AABB r = bounds;
r.inset(bounds.width() / 10, -bounds.height() / 10);
path->addOval(r, rivegm::PathDirection::ccw);
}
public:
Strokes3GM() : GM(1500, 1500) {}
protected:
void onDraw(Renderer* canvas) override
{
Paint origPaint;
origPaint->style(RenderPaintStyle::stroke);
// Paint fillPaint;
// fillPaint->color(0xffff0000);
Paint strokePaint;
strokePaint->style(RenderPaintStyle::stroke);
strokePaint->color(0xFF4444FF);
void (*procs[])(
PathBuilder*,
const AABB&) = {make0, make1, make2, make3, make4, make5};
canvas->translate(static_cast<float>(20), static_cast<float>(80));
AABB bounds = {0, 0, static_cast<float>(50), static_cast<float>(50)};
float dx = bounds.width() * 4 / 3;
float dy = bounds.height() * 5;
for (size_t i = 0; i < std::size(procs); ++i)
{
PathBuilder orig;
procs[i](&orig, bounds);
Path orig2 = orig.detach();
canvas->save();
for (int j = 0; j < 13; ++j)
{
float thickness = 1.0f * j * j;
strokePaint->thickness(thickness);
canvas->drawPath(orig2, strokePaint);
canvas->drawPath(orig2, origPaint);
// Path fill;
// strokePaint.getFillPath(orig, &fill);
// canvas->drawPath(fill, fillPaint);
canvas->translate(dx + thickness, 0);
}
canvas->restore();
canvas->translate(0, dy);
}
}
};
class Strokes4GM : public GM
{
public:
Strokes4GM() : GM(W, H / 4) {}
protected:
void onDraw(Renderer* canvas) override
{
Paint paint;
paint->style(RenderPaintStyle::stroke);
paint->thickness(0.055f);
canvas->scale(1000, 1000);
canvas->drawPath(
PathBuilder::Oval({-1.97f, 2 - 1.97f, 1.97f, 2 + 1.97f}),
paint);
}
};
// Test stroking for curves that produce degenerate tangents when t is 0 or 1
// (see bug 4191)
class Strokes5GM : public GM
{
public:
Strokes5GM() : GM(W, H) {}
protected:
void onDraw(Renderer* canvas) override
{
Paint p;
p->color(0xffff0000);
p->style(RenderPaintStyle::stroke);
p->thickness(40);
p->cap(StrokeCap::butt);
PathBuilder path;
path.moveTo(157.474f, 111.753f);
path.cubicTo(128.5f, 111.5f, 35.5f, 29.5f, 35.5f, 29.5f);
canvas->drawPath(path.detach(), p);
path.moveTo(250, 50);
path.quadTo(280, 80, 280, 80);
canvas->drawPath(path.detach(), p);
path.moveTo(150, 50);
path.quadTo(180, 80, 180, 80);
canvas->drawPath(path.detach(), p);
path.moveTo(157.474f, 311.753f);
path.cubicTo(157.474f, 311.753f, 85.5f, 229.5f, 35.5f, 229.5f);
canvas->drawPath(path.detach(), p);
path.moveTo(280, 250);
path.quadTo(280, 250, 310, 280);
canvas->drawPath(path.detach(), p);
path.moveTo(180, 250);
path.quadTo(180, 250, 210, 280);
canvas->drawPath(path.detach(), p);
}
};
//////////////////////////////////////////////////////////////////////////////
GMREGISTER(strokes_round, return new StrokesGM;)
GMREGISTER(strokes_poly, return new Strokes2GM;)
GMREGISTER(strokes3, return new Strokes3GM;)
GMREGISTER(strokes_zoomed, return new Strokes4GM;)
GMREGISTER(zero_control_stroke, return new Strokes5GM;)
GMREGISTER(zeroPath, return new ZeroLenStrokesGM;)
GMREGISTER(teenyStrokes, return new TeenyStrokesGM;)
DEF_SIMPLE_GM(inner_join_geometry, 1000, 700, canvas)
{
// These paths trigger cases where we must add inner join geometry.
// skbug.com/11964
const Vec2D pathPoints[] = {
/*moveTo*/ /*lineTo*/ /*lineTo*/
{119, 71},
{129, 151},
{230, 24},
{200, 144},
{129, 151},
{230, 24},
{192, 176},
{224, 175},
{281, 103},
{233, 205},
{224, 175},
{281, 103},
{121, 216},
{234, 189},
{195, 147},
{141, 216},
{254, 189},
{238, 250},
{159, 202},
{269, 197},
{289, 165},
{159, 202},
{269, 197},
{287, 227},
};
Paint pathPaint;
pathPaint->style(RenderPaintStyle::stroke);
pathPaint->thickness(100);
#if 0
Paint skeletonPaint;
skeletonPaint->style(RenderPaintStyle::stroke);
skeletonPaint->thickness(1);
skeletonPaint->color(0xffff0000);
#endif
canvas->translate(0, 50);
for (size_t i = 0; i < std::size(pathPoints) / 3; i++)
{
// auto path = SkPath::Polygon(pathPoints + i * 3, 3, false);
PathBuilder path;
size_t j = i * 3;
path.moveTo(pathPoints[j].x, pathPoints[j].y);
path.lineTo(pathPoints[j + 1].x, pathPoints[j + 1].y);
path.lineTo(pathPoints[j + 2].x, pathPoints[j + 2].y);
canvas->drawPath(path.detach(), pathPaint);
#if 0
SkPath fillPath;
pathPaint.getFillPath(path, &fillPath);
canvas->drawPath(fillPath, skeletonPaint);
#endif
canvas->translate(200, 0);
if ((i + 1) % 4 == 0)
{
canvas->translate(-800, 200);
}
}
}
DEF_SIMPLE_GM(skbug12244, 150, 150, canvas)
{
// Should look like a stroked triangle; these vertices are the results of
// the SkStroker but we draw as a filled path in order to highlight that
// it's the GPU triangulating path renderer that's the source of the
// problem, and not the stroking operation. The original path was a simple:
// m(0,0), l(100, 40), l(0, 80), l(0,0) with a stroke width of 15px
Path path;
path->moveTo(2.7854299545288085938, -6.9635753631591796875);
path->lineTo(120.194366455078125, 40);
path->lineTo(-7.5000004768371582031, 91.07775115966796875);
path->lineTo(-7.5000004768371582031, -11.077748298645019531);
path->lineTo(2.7854299545288085938, -6.9635753631591796875);
path->moveTo(-2.7854299545288085938, 6.9635753631591796875);
path->lineTo(0, 0);
path->lineTo(7.5, 0);
path->lineTo(7.5000004768371582031, 68.92224884033203125);
path->lineTo(79.805633544921875, 40);
path->lineTo(-2.7854299545288085938, 6.9635753631591796875);
Paint p;
p->color(0xff00ff00);
canvas->translate(20.f, 20.f);
canvas->drawPath(path, p);
}