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