| /* | |
| * Copyright 2016 Google Inc. | |
| * | |
| * 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/40036571 skbug.com/40036572 | |
| */ | |
| #include "gm/gm.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/core/SkPathMeasure.h" | |
| #include "include/core/SkPathUtils.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkScalar.h" | |
| #include "src/core/SkPointPriv.h" | |
| #include <cstddef> | |
| const SkScalar OVERSTROKE_WIDTH = 500.0f; | |
| const SkScalar NORMALSTROKE_WIDTH = 3.0f; | |
| //////// path and paint builders | |
| SkPaint make_normal_paint() { | |
| SkPaint p; | |
| p.setAntiAlias(true); | |
| p.setStyle(SkPaint::kStroke_Style); | |
| p.setStrokeWidth(NORMALSTROKE_WIDTH); | |
| p.setColor(SK_ColorBLUE); | |
| return p; | |
| } | |
| SkPaint make_overstroke_paint() { | |
| SkPaint p; | |
| p.setAntiAlias(true); | |
| p.setStyle(SkPaint::kStroke_Style); | |
| p.setStrokeWidth(OVERSTROKE_WIDTH); | |
| return p; | |
| } | |
| SkPath quad_path() { | |
| return SkPathBuilder().moveTo(0, 0) | |
| .lineTo(100, 0) | |
| .quadTo(50, -40, 0, 0) | |
| .close() | |
| .detach(); | |
| } | |
| SkPath cubic_path() { | |
| return SkPathBuilder() | |
| .moveTo(0, 0) | |
| .cubicTo(25, 75, 75, -50, 100, 0) | |
| .detach(); | |
| } | |
| SkPath oval_path() { | |
| SkRect oval = SkRect::MakeXYWH(0, -25, 100, 50); | |
| return SkPathBuilder().arcTo(oval, 0, 359, true).close().detach(); | |
| } | |
| SkPath ribs_path(SkPath path, SkScalar radius) { | |
| SkPathBuilder ribs; | |
| const SkScalar spacing = 5.0f; | |
| float accum = 0.0f; | |
| SkPathMeasure meas(path, false); | |
| SkScalar length = meas.getLength(); | |
| SkPoint pos; | |
| SkVector tan; | |
| while (accum < length) { | |
| if (meas.getPosTan(accum, &pos, &tan)) { | |
| tan.scale(radius); | |
| SkPointPriv::RotateCCW(&tan); | |
| ribs.addLine(pos + tan, pos - tan); | |
| } | |
| accum += spacing; | |
| } | |
| return ribs.detach(); | |
| } | |
| void draw_ribs(SkCanvas *canvas, SkPath path) { | |
| SkPath ribs = ribs_path(path, OVERSTROKE_WIDTH/2.0f); | |
| SkPaint p = make_normal_paint(); | |
| p.setStrokeWidth(1); | |
| p.setColor(SK_ColorBLUE); | |
| p.setColor(SK_ColorGREEN); | |
| canvas->drawPath(ribs, p); | |
| } | |
| ///////// quads | |
| void draw_small_quad(SkCanvas *canvas) { | |
| // scaled so it's visible | |
| // canvas->scale(8, 8); | |
| SkPaint p = make_normal_paint(); | |
| SkPath path = quad_path(); | |
| draw_ribs(canvas, path); | |
| canvas->drawPath(path, p); | |
| } | |
| void draw_large_quad(SkCanvas *canvas) { | |
| SkPaint p = make_overstroke_paint(); | |
| SkPath path = quad_path(); | |
| canvas->drawPath(path, p); | |
| draw_ribs(canvas, path); | |
| } | |
| void draw_quad_fillpath(SkCanvas *canvas) { | |
| SkPath path = quad_path(); | |
| SkPaint p = make_overstroke_paint(); | |
| SkPaint fillp = make_normal_paint(); | |
| fillp.setColor(SK_ColorMAGENTA); | |
| SkPathBuilder fillpath; | |
| skpathutils::FillPathWithPaint(path, p, &fillpath); | |
| canvas->drawPath(fillpath.detach(), fillp); | |
| } | |
| void draw_stroked_quad(SkCanvas *canvas) { | |
| canvas->translate(400, 0); | |
| draw_large_quad(canvas); | |
| draw_quad_fillpath(canvas); | |
| } | |
| ////////// cubics | |
| void draw_small_cubic(SkCanvas *canvas) { | |
| SkPaint p = make_normal_paint(); | |
| SkPath path = cubic_path(); | |
| draw_ribs(canvas, path); | |
| canvas->drawPath(path, p); | |
| } | |
| void draw_large_cubic(SkCanvas *canvas) { | |
| SkPaint p = make_overstroke_paint(); | |
| SkPath path = cubic_path(); | |
| canvas->drawPath(path, p); | |
| draw_ribs(canvas, path); | |
| } | |
| void draw_cubic_fillpath(SkCanvas *canvas) { | |
| SkPath path = cubic_path(); | |
| SkPaint p = make_overstroke_paint(); | |
| SkPaint fillp = make_normal_paint(); | |
| fillp.setColor(SK_ColorMAGENTA); | |
| SkPathBuilder fillpath; | |
| skpathutils::FillPathWithPaint(path, p, &fillpath); | |
| canvas->drawPath(fillpath.detach(), fillp); | |
| } | |
| void draw_stroked_cubic(SkCanvas *canvas) { | |
| canvas->translate(400, 0); | |
| draw_large_cubic(canvas); | |
| draw_cubic_fillpath(canvas); | |
| } | |
| ////////// ovals | |
| void draw_small_oval(SkCanvas *canvas) { | |
| SkPaint p = make_normal_paint(); | |
| SkPath path = oval_path(); | |
| draw_ribs(canvas, path); | |
| canvas->drawPath(path, p); | |
| } | |
| void draw_large_oval(SkCanvas *canvas) { | |
| SkPaint p = make_overstroke_paint(); | |
| SkPath path = oval_path(); | |
| canvas->drawPath(path, p); | |
| draw_ribs(canvas, path); | |
| } | |
| void draw_oval_fillpath(SkCanvas *canvas) { | |
| SkPath path = oval_path(); | |
| SkPaint p = make_overstroke_paint(); | |
| SkPaint fillp = make_normal_paint(); | |
| fillp.setColor(SK_ColorMAGENTA); | |
| SkPath fillpath = skpathutils::FillPathWithPaint(path, p); | |
| canvas->drawPath(fillpath, fillp); | |
| } | |
| void draw_stroked_oval(SkCanvas *canvas) { | |
| canvas->translate(400, 0); | |
| draw_large_oval(canvas); | |
| draw_oval_fillpath(canvas); | |
| } | |
| ////////// gm | |
| void (*examples[])(SkCanvas *canvas) = { | |
| draw_small_quad, draw_stroked_quad, draw_small_cubic, | |
| draw_stroked_cubic, draw_small_oval, draw_stroked_oval, | |
| }; | |
| DEF_SIMPLE_GM(OverStroke, canvas, 500, 500) { | |
| 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(); | |
| } | |
| } |