| /* |
| * Copyright 2016 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. |
| */ |
| |
| /* |
| * This GM exercises stroking of paths with large stroke lengths, which is |
| * referred to as "overstroke" for brevity. In Skia as of 8/2016 we offset |
| * each part of the curve the request amount even if it makes the offsets |
| * overlap and create holes. There is not a really great algorithm for this |
| * and several other 2D graphics engines have the same bug. |
| * |
| * The old Nvidia Path Renderer used to yield correct results, so a possible |
| * direction of attack is to use the GPU and a completely different algorithm. |
| * |
| * See crbug.com/589769 skbug.com/5405 skbug.com/5406 |
| */ |
| |
| #include "gm.hpp" |
| #include "gmutils.hpp" |
| #include "rive/renderer.hpp" |
| #include "rive/math/vec2d.hpp" |
| #include "rive/math/contour_measure.hpp" |
| |
| using namespace rivegm; |
| using namespace rive; |
| |
| const float OVERSTROKE_WIDTH = 500.0f; |
| const float NORMALSTROKE_WIDTH = 3.0f; |
| |
| //////// path and paint builders |
| |
| Paint make_normal_paint() |
| { |
| Paint p; |
| p->style(RenderPaintStyle::stroke); |
| p->thickness(NORMALSTROKE_WIDTH); |
| p->color(0xff0000ff); |
| |
| return p; |
| } |
| |
| Paint make_overstroke_paint() |
| { |
| Paint p; |
| p->style(RenderPaintStyle::stroke); |
| p->thickness(OVERSTROKE_WIDTH); |
| |
| return p; |
| } |
| |
| RawPath quad_path() |
| { |
| RawPath path; |
| path.moveTo(0, 0); |
| path.lineTo(100, 0); |
| path.quadTo(50, -40, 0, 0); |
| path.close(); |
| return path; |
| } |
| |
| RawPath cubic_path() |
| { |
| RawPath path; |
| path.moveTo(0, 0); |
| path.cubicTo(25, 75, 75, -50, 100, 0); |
| return path; |
| } |
| |
| RawPath oval_path() |
| { |
| RawPath path; |
| path.addOval({0, -25, 100, 25}); |
| return path; |
| } |
| |
| Path ribs_path(const RawPath& path, float radius) |
| { |
| Path ribs; |
| |
| const float spacing = 5.0f; |
| float accum = 0.0f; |
| |
| ContourMeasureIter iter(&path); |
| while (auto meas = iter.next()) |
| { |
| while (accum < meas->length()) |
| { |
| auto posTan = meas->getPosTan(accum); |
| Vec2D pos = posTan.pos; |
| // there appeara to be a bug somewhere that is not normalizing this |
| // when it should, so i am doing it here |
| Vec2D tan = posTan.tan.normalized(); |
| tan = {tan.y * radius, -tan.x * radius}; |
| |
| Vec2D start = pos + tan; |
| Vec2D end = pos - tan; |
| |
| ribs->moveTo(start.x, start.y); |
| ribs->lineTo(end.x, end.y); |
| |
| accum += spacing; |
| } |
| accum += meas->length(); |
| } |
| |
| /* |
| SkPathMeasure meas(skpath, false); |
| SkScalar length = meas.getLength(); |
| SkPoint pos; |
| SkVector tan; |
| while (accum < length) |
| { |
| if (meas.getPosTan(accum, &pos, &tan)) |
| { |
| tan.scale(radius); |
| tan = {tan.y(), -tan.x()}; |
| |
| ribs->moveTo(pos.x() + tan.x(), pos.y() + tan.y()); |
| ribs->lineTo(pos.x() - tan.x(), pos.y() - tan.y()); |
| } |
| accum += spacing; |
| } |
| */ |
| |
| return ribs; |
| } |
| |
| void draw_ribs(Renderer* canvas, const RawPath& path) |
| { |
| Path ribs = ribs_path(path, OVERSTROKE_WIDTH / 2.0f); |
| Paint p = make_normal_paint(); |
| p->thickness(1); |
| p->color(0xff00ff00); |
| |
| canvas->drawPath(ribs, p); |
| } |
| |
| ///////// quads |
| |
| void draw_small_quad(Renderer* canvas) |
| { |
| // scaled so it's visible |
| // canvas->scale(8, 8); |
| |
| Paint p = make_normal_paint(); |
| RawPath path = quad_path(); |
| |
| auto renderPath = renderPathFromRawPath(path); |
| |
| draw_ribs(canvas, path); |
| canvas->drawPath(renderPath.get(), p); |
| } |
| |
| void draw_large_quad(Renderer* canvas) |
| { |
| Paint p = make_overstroke_paint(); |
| RawPath path = quad_path(); |
| |
| auto renderPath = renderPathFromRawPath(path); |
| |
| canvas->drawPath(renderPath.get(), p); |
| draw_ribs(canvas, path); |
| } |
| |
| void draw_stroked_quad(Renderer* canvas) |
| { |
| canvas->translate(400, 0); |
| draw_large_quad(canvas); |
| } |
| |
| ////////// cubics |
| |
| void draw_small_cubic(Renderer* canvas) |
| { |
| Paint p = make_normal_paint(); |
| RawPath path = cubic_path(); |
| |
| auto renderPath = renderPathFromRawPath(path); |
| |
| draw_ribs(canvas, path); |
| canvas->drawPath(renderPath.get(), p); |
| } |
| |
| void draw_large_cubic(Renderer* canvas) |
| { |
| Paint p = make_overstroke_paint(); |
| RawPath path = cubic_path(); |
| |
| auto renderPath = renderPathFromRawPath(path); |
| |
| canvas->drawPath(renderPath.get(), p); |
| draw_ribs(canvas, path); |
| } |
| |
| void draw_stroked_cubic(Renderer* canvas) |
| { |
| canvas->translate(400, 0); |
| draw_large_cubic(canvas); |
| } |
| |
| ////////// ovals |
| |
| void draw_small_oval(Renderer* canvas) |
| { |
| Paint p = make_normal_paint(); |
| RawPath path = oval_path(); |
| |
| auto renderPath = renderPathFromRawPath(path); |
| |
| draw_ribs(canvas, path); |
| canvas->drawPath(renderPath.get(), p); |
| } |
| |
| void draw_large_oval(Renderer* canvas) |
| { |
| Paint p = make_overstroke_paint(); |
| RawPath path = oval_path(); |
| |
| auto renderPath = renderPathFromRawPath(path); |
| |
| canvas->drawPath(renderPath.get(), p); |
| draw_ribs(canvas, path); |
| } |
| |
| void draw_stroked_oval(Renderer* canvas) |
| { |
| canvas->translate(400, 0); |
| draw_large_oval(canvas); |
| } |
| |
| ////////// gm |
| |
| void (*examples[])(Renderer* canvas) = { |
| draw_small_quad, |
| draw_stroked_quad, |
| draw_small_cubic, |
| draw_stroked_cubic, |
| draw_small_oval, |
| draw_stroked_oval, |
| }; |
| |
| DEF_SIMPLE_GM(OverStroke, 500, 500, canvas) |
| { |
| const size_t length = sizeof(examples) / sizeof(examples[0]); |
| const size_t width = 2; |
| |
| for (size_t i = 0; i < length; i++) |
| { |
| int x = (int)(i % width); |
| int y = (int)(i / width); |
| |
| canvas->save(); |
| canvas->translate(150.0f * x, 150.0f * y); |
| canvas->scale(0.2f, 0.2f); |
| canvas->translate(300.0f, 400.0f); |
| |
| examples[i](canvas); |
| |
| canvas->restore(); |
| } |
| } |