hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2016 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
| 8 | |
| 9 | /* |
| 10 | * This GM exercises stroking of paths with large stroke lengths, which is |
| 11 | * referred to as "overstroke" for brevity. In Skia as of 8/2016 we offset |
| 12 | * each part of the curve the request amount even if it makes the offsets |
| 13 | * overlap and create holes. There is not a really great algorithm for this |
| 14 | * and several other 2D graphics engines have the same bug. |
| 15 | * |
Robert Phillips | 84fd1c2 | 2021-03-23 13:18:36 -0400 | [diff] [blame] | 16 | * The old Nvidia Path Renderer used to yield correct results, so a possible |
| 17 | * direction of attack is to use the GPU and a completely different algorithm. |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 18 | * |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 19 | * See crbug.com/589769 skbug.com/5405 skbug.com/5406 |
| 20 | */ |
| 21 | |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 22 | #include "gm/gm.h" |
Ben Wagner | 7fde8e1 | 2019-05-01 17:28:53 -0400 | [diff] [blame] | 23 | #include "include/core/SkCanvas.h" |
| 24 | #include "include/core/SkColor.h" |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 25 | #include "include/core/SkPaint.h" |
Mike Reed | cfb130c | 2020-08-03 11:02:20 -0400 | [diff] [blame] | 26 | #include "include/core/SkPathBuilder.h" |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 27 | #include "include/core/SkPathMeasure.h" |
Kevin Lubick | 177a712 | 2022-12-12 10:25:15 -0500 | [diff] [blame] | 28 | #include "include/core/SkPathUtils.h" |
Ben Wagner | 7fde8e1 | 2019-05-01 17:28:53 -0400 | [diff] [blame] | 29 | #include "include/core/SkPoint.h" |
| 30 | #include "include/core/SkRect.h" |
| 31 | #include "include/core/SkScalar.h" |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 32 | #include "src/core/SkPointPriv.h" |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 33 | |
Ben Wagner | 7fde8e1 | 2019-05-01 17:28:53 -0400 | [diff] [blame] | 34 | #include <cstddef> |
| 35 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 36 | const SkScalar OVERSTROKE_WIDTH = 500.0f; |
| 37 | const SkScalar NORMALSTROKE_WIDTH = 3.0f; |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 38 | |
| 39 | //////// path and paint builders |
| 40 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 41 | SkPaint make_normal_paint() { |
| 42 | SkPaint p; |
| 43 | p.setAntiAlias(true); |
| 44 | p.setStyle(SkPaint::kStroke_Style); |
| 45 | p.setStrokeWidth(NORMALSTROKE_WIDTH); |
| 46 | p.setColor(SK_ColorBLUE); |
| 47 | |
| 48 | return p; |
| 49 | } |
| 50 | |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 51 | SkPaint make_overstroke_paint() { |
| 52 | SkPaint p; |
| 53 | p.setAntiAlias(true); |
| 54 | p.setStyle(SkPaint::kStroke_Style); |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 55 | p.setStrokeWidth(OVERSTROKE_WIDTH); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 56 | |
| 57 | return p; |
| 58 | } |
| 59 | |
| 60 | SkPath quad_path() { |
Mike Reed | 06d7c9d | 2020-08-26 12:56:51 -0400 | [diff] [blame] | 61 | return SkPathBuilder().moveTo(0, 0) |
| 62 | .lineTo(100, 0) |
| 63 | .quadTo(50, -40, 0, 0) |
| 64 | .close() |
| 65 | .detach(); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 66 | } |
| 67 | |
| 68 | SkPath cubic_path() { |
| 69 | SkPath path; |
| 70 | path.moveTo(0, 0); |
| 71 | path.cubicTo(25, 75, |
| 72 | 75, -50, |
| 73 | 100, 0); |
| 74 | |
| 75 | return path; |
| 76 | } |
| 77 | |
| 78 | SkPath oval_path() { |
| 79 | SkRect oval = SkRect::MakeXYWH(0, -25, 100, 50); |
| 80 | |
Mike Reed | cfb130c | 2020-08-03 11:02:20 -0400 | [diff] [blame] | 81 | return SkPathBuilder().arcTo(oval, 0, 359, true).close().detach(); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 82 | } |
| 83 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 84 | SkPath ribs_path(SkPath path, SkScalar radius) { |
| 85 | SkPath ribs; |
| 86 | |
| 87 | const SkScalar spacing = 5.0f; |
| 88 | float accum = 0.0f; |
| 89 | |
| 90 | SkPathMeasure meas(path, false); |
| 91 | SkScalar length = meas.getLength(); |
| 92 | SkPoint pos; |
| 93 | SkVector tan; |
| 94 | while (accum < length) { |
| 95 | if (meas.getPosTan(accum, &pos, &tan)) { |
| 96 | tan.scale(radius); |
Cary Clark | df429f3 | 2017-11-08 11:44:31 -0500 | [diff] [blame] | 97 | SkPointPriv::RotateCCW(&tan); |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 98 | |
| 99 | ribs.moveTo(pos.x() + tan.x(), pos.y() + tan.y()); |
| 100 | ribs.lineTo(pos.x() - tan.x(), pos.y() - tan.y()); |
| 101 | } |
| 102 | accum += spacing; |
| 103 | } |
| 104 | |
| 105 | return ribs; |
| 106 | } |
| 107 | |
| 108 | void draw_ribs(SkCanvas *canvas, SkPath path) { |
| 109 | SkPath ribs = ribs_path(path, OVERSTROKE_WIDTH/2.0f); |
| 110 | SkPaint p = make_normal_paint(); |
| 111 | p.setStrokeWidth(1); |
| 112 | p.setColor(SK_ColorBLUE); |
| 113 | p.setColor(SK_ColorGREEN); |
| 114 | |
| 115 | canvas->drawPath(ribs, p); |
| 116 | } |
| 117 | |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 118 | ///////// quads |
| 119 | |
| 120 | void draw_small_quad(SkCanvas *canvas) { |
| 121 | // scaled so it's visible |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 122 | // canvas->scale(8, 8); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 123 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 124 | SkPaint p = make_normal_paint(); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 125 | SkPath path = quad_path(); |
| 126 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 127 | draw_ribs(canvas, path); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 128 | canvas->drawPath(path, p); |
| 129 | } |
| 130 | |
| 131 | void draw_large_quad(SkCanvas *canvas) { |
| 132 | SkPaint p = make_overstroke_paint(); |
| 133 | SkPath path = quad_path(); |
| 134 | |
| 135 | canvas->drawPath(path, p); |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 136 | draw_ribs(canvas, path); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 137 | } |
| 138 | |
| 139 | void draw_quad_fillpath(SkCanvas *canvas) { |
| 140 | SkPath path = quad_path(); |
| 141 | SkPaint p = make_overstroke_paint(); |
| 142 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 143 | SkPaint fillp = make_normal_paint(); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 144 | fillp.setColor(SK_ColorMAGENTA); |
| 145 | |
| 146 | SkPath fillpath; |
Kevin Lubick | f549128 | 2022-12-15 08:37:49 -0500 | [diff] [blame] | 147 | skpathutils::FillPathWithPaint(path, p, &fillpath); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 148 | |
| 149 | canvas->drawPath(fillpath, fillp); |
| 150 | } |
| 151 | |
| 152 | void draw_stroked_quad(SkCanvas *canvas) { |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 153 | canvas->translate(400, 0); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 154 | draw_large_quad(canvas); |
| 155 | draw_quad_fillpath(canvas); |
| 156 | } |
| 157 | |
| 158 | ////////// cubics |
| 159 | |
| 160 | void draw_small_cubic(SkCanvas *canvas) { |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 161 | SkPaint p = make_normal_paint(); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 162 | SkPath path = cubic_path(); |
| 163 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 164 | draw_ribs(canvas, path); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 165 | canvas->drawPath(path, p); |
| 166 | } |
| 167 | |
| 168 | void draw_large_cubic(SkCanvas *canvas) { |
| 169 | SkPaint p = make_overstroke_paint(); |
| 170 | SkPath path = cubic_path(); |
| 171 | |
| 172 | canvas->drawPath(path, p); |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 173 | draw_ribs(canvas, path); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 174 | } |
| 175 | |
| 176 | void draw_cubic_fillpath(SkCanvas *canvas) { |
| 177 | SkPath path = cubic_path(); |
| 178 | SkPaint p = make_overstroke_paint(); |
| 179 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 180 | SkPaint fillp = make_normal_paint(); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 181 | fillp.setColor(SK_ColorMAGENTA); |
| 182 | |
| 183 | SkPath fillpath; |
Kevin Lubick | f549128 | 2022-12-15 08:37:49 -0500 | [diff] [blame] | 184 | skpathutils::FillPathWithPaint(path, p, &fillpath); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 185 | |
| 186 | canvas->drawPath(fillpath, fillp); |
| 187 | } |
| 188 | |
| 189 | void draw_stroked_cubic(SkCanvas *canvas) { |
| 190 | canvas->translate(400, 0); |
| 191 | draw_large_cubic(canvas); |
| 192 | draw_cubic_fillpath(canvas); |
| 193 | } |
| 194 | |
| 195 | ////////// ovals |
| 196 | |
| 197 | void draw_small_oval(SkCanvas *canvas) { |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 198 | SkPaint p = make_normal_paint(); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 199 | |
| 200 | SkPath path = oval_path(); |
| 201 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 202 | draw_ribs(canvas, path); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 203 | canvas->drawPath(path, p); |
| 204 | } |
| 205 | |
| 206 | void draw_large_oval(SkCanvas *canvas) { |
| 207 | SkPaint p = make_overstroke_paint(); |
| 208 | SkPath path = oval_path(); |
| 209 | |
| 210 | canvas->drawPath(path, p); |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 211 | draw_ribs(canvas, path); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 212 | } |
| 213 | |
| 214 | void draw_oval_fillpath(SkCanvas *canvas) { |
| 215 | SkPath path = oval_path(); |
| 216 | SkPaint p = make_overstroke_paint(); |
| 217 | |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 218 | SkPaint fillp = make_normal_paint(); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 219 | fillp.setColor(SK_ColorMAGENTA); |
| 220 | |
| 221 | SkPath fillpath; |
Kevin Lubick | f549128 | 2022-12-15 08:37:49 -0500 | [diff] [blame] | 222 | skpathutils::FillPathWithPaint(path, p, &fillpath); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 223 | |
| 224 | canvas->drawPath(fillpath, fillp); |
| 225 | } |
| 226 | |
| 227 | void draw_stroked_oval(SkCanvas *canvas) { |
| 228 | canvas->translate(400, 0); |
| 229 | draw_large_oval(canvas); |
| 230 | draw_oval_fillpath(canvas); |
| 231 | } |
| 232 | |
| 233 | ////////// gm |
| 234 | |
| 235 | void (*examples[])(SkCanvas *canvas) = { |
| 236 | draw_small_quad, draw_stroked_quad, draw_small_cubic, |
| 237 | draw_stroked_cubic, draw_small_oval, draw_stroked_oval, |
| 238 | }; |
| 239 | |
| 240 | DEF_SIMPLE_GM(OverStroke, canvas, 500, 500) { |
| 241 | const size_t length = sizeof(examples) / sizeof(examples[0]); |
| 242 | const size_t width = 2; |
| 243 | |
| 244 | for (size_t i = 0; i < length; i++) { |
| 245 | int x = (int)(i % width); |
| 246 | int y = (int)(i / width); |
| 247 | |
| 248 | canvas->save(); |
hstern | bb9b224 | 2016-08-09 08:53:30 -0700 | [diff] [blame] | 249 | canvas->translate(150.0f * x, 150.0f * y); |
| 250 | canvas->scale(0.2f, 0.2f); |
| 251 | canvas->translate(300.0f, 400.0f); |
hstern | 02aea1c | 2016-08-02 10:35:57 -0700 | [diff] [blame] | 252 | |
| 253 | examples[i](canvas); |
| 254 | |
| 255 | canvas->restore(); |
| 256 | } |
| 257 | } |