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