| /* |
| * 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); |
| } |