blob: bcca118541ab7b60c6bd3c2d6af73621f4bb167a [file] [log] [blame] [edit]
/*
* 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();
}
}