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 * 16 * If we run this using Nvidia Path Renderer with: 17 * `path/to/dm --match OverStroke -w gm_out --gpu --config nvpr16` 18 * then we get correct results, so that is a possible direction of attack - 19 * use the GPU and a completely different algorithm to get correctness in 20 * Skia. 21 * 22 * See crbug.com/589769 skbug.com/5405 skbug.com/5406 23 */ 24 25 26#include "gm.h" 27#include "SkPaint.h" 28#include "SkPath.h" 29#include "SkPathMeasure.h" 30 31const SkScalar OVERSTROKE_WIDTH = 500.0f; 32const SkScalar NORMALSTROKE_WIDTH = 3.0f; 33 34//////// path and paint builders 35 36SkPaint make_normal_paint() { 37 SkPaint p; 38 p.setAntiAlias(true); 39 p.setStyle(SkPaint::kStroke_Style); 40 p.setStrokeWidth(NORMALSTROKE_WIDTH); 41 p.setColor(SK_ColorBLUE); 42 43 return p; 44} 45 46SkPaint make_overstroke_paint() { 47 SkPaint p; 48 p.setAntiAlias(true); 49 p.setStyle(SkPaint::kStroke_Style); 50 p.setStrokeWidth(OVERSTROKE_WIDTH); 51 52 return p; 53} 54 55SkPath quad_path() { 56 SkPath path; 57 path.moveTo(0, 0); 58 path.lineTo(100, 0); 59 path.quadTo(50, -40, 60 0, 0); 61 path.close(); 62 63 return path; 64} 65 66SkPath cubic_path() { 67 SkPath path; 68 path.moveTo(0, 0); 69 path.cubicTo(25, 75, 70 75, -50, 71 100, 0); 72 73 return path; 74} 75 76SkPath oval_path() { 77 SkRect oval = SkRect::MakeXYWH(0, -25, 100, 50); 78 79 SkPath path; 80 path.arcTo(oval, 0, 359, true); 81 path.close(); 82 83 return path; 84} 85 86SkPath ribs_path(SkPath path, SkScalar radius) { 87 SkPath ribs; 88 89 const SkScalar spacing = 5.0f; 90 float accum = 0.0f; 91 92 SkPathMeasure meas(path, false); 93 SkScalar length = meas.getLength(); 94 SkPoint pos; 95 SkVector tan; 96 while (accum < length) { 97 if (meas.getPosTan(accum, &pos, &tan)) { 98 tan.scale(radius); 99 tan.rotateCCW(); 100 101 ribs.moveTo(pos.x() + tan.x(), pos.y() + tan.y()); 102 ribs.lineTo(pos.x() - tan.x(), pos.y() - tan.y()); 103 } 104 accum += spacing; 105 } 106 107 return ribs; 108} 109 110void draw_ribs(SkCanvas *canvas, SkPath path) { 111 SkPath ribs = ribs_path(path, OVERSTROKE_WIDTH/2.0f); 112 SkPaint p = make_normal_paint(); 113 p.setStrokeWidth(1); 114 p.setColor(SK_ColorBLUE); 115 p.setColor(SK_ColorGREEN); 116 117 canvas->drawPath(ribs, p); 118} 119 120///////// quads 121 122void draw_small_quad(SkCanvas *canvas) { 123 // scaled so it's visible 124 // canvas->scale(8, 8); 125 126 SkPaint p = make_normal_paint(); 127 SkPath path = quad_path(); 128 129 draw_ribs(canvas, path); 130 canvas->drawPath(path, p); 131} 132 133void draw_large_quad(SkCanvas *canvas) { 134 SkPaint p = make_overstroke_paint(); 135 SkPath path = quad_path(); 136 137 canvas->drawPath(path, p); 138 draw_ribs(canvas, path); 139} 140 141void draw_quad_fillpath(SkCanvas *canvas) { 142 SkPath path = quad_path(); 143 SkPaint p = make_overstroke_paint(); 144 145 SkPaint fillp = make_normal_paint(); 146 fillp.setColor(SK_ColorMAGENTA); 147 148 SkPath fillpath; 149 p.getFillPath(path, &fillpath); 150 151 canvas->drawPath(fillpath, fillp); 152} 153 154void draw_stroked_quad(SkCanvas *canvas) { 155 canvas->translate(400, 0); 156 draw_large_quad(canvas); 157 draw_quad_fillpath(canvas); 158} 159 160////////// cubics 161 162void draw_small_cubic(SkCanvas *canvas) { 163 SkPaint p = make_normal_paint(); 164 SkPath path = cubic_path(); 165 166 draw_ribs(canvas, path); 167 canvas->drawPath(path, p); 168} 169 170void draw_large_cubic(SkCanvas *canvas) { 171 SkPaint p = make_overstroke_paint(); 172 SkPath path = cubic_path(); 173 174 canvas->drawPath(path, p); 175 draw_ribs(canvas, path); 176} 177 178void draw_cubic_fillpath(SkCanvas *canvas) { 179 SkPath path = cubic_path(); 180 SkPaint p = make_overstroke_paint(); 181 182 SkPaint fillp = make_normal_paint(); 183 fillp.setColor(SK_ColorMAGENTA); 184 185 SkPath fillpath; 186 p.getFillPath(path, &fillpath); 187 188 canvas->drawPath(fillpath, fillp); 189} 190 191void draw_stroked_cubic(SkCanvas *canvas) { 192 canvas->translate(400, 0); 193 draw_large_cubic(canvas); 194 draw_cubic_fillpath(canvas); 195} 196 197////////// ovals 198 199void draw_small_oval(SkCanvas *canvas) { 200 SkPaint p = make_normal_paint(); 201 202 SkPath path = oval_path(); 203 204 draw_ribs(canvas, path); 205 canvas->drawPath(path, p); 206} 207 208void draw_large_oval(SkCanvas *canvas) { 209 SkPaint p = make_overstroke_paint(); 210 SkPath path = oval_path(); 211 212 canvas->drawPath(path, p); 213 draw_ribs(canvas, path); 214} 215 216void draw_oval_fillpath(SkCanvas *canvas) { 217 SkPath path = oval_path(); 218 SkPaint p = make_overstroke_paint(); 219 220 SkPaint fillp = make_normal_paint(); 221 fillp.setColor(SK_ColorMAGENTA); 222 223 SkPath fillpath; 224 p.getFillPath(path, &fillpath); 225 226 canvas->drawPath(fillpath, fillp); 227} 228 229void draw_stroked_oval(SkCanvas *canvas) { 230 canvas->translate(400, 0); 231 draw_large_oval(canvas); 232 draw_oval_fillpath(canvas); 233} 234 235////////// gm 236 237void (*examples[])(SkCanvas *canvas) = { 238 draw_small_quad, draw_stroked_quad, draw_small_cubic, 239 draw_stroked_cubic, draw_small_oval, draw_stroked_oval, 240}; 241 242DEF_SIMPLE_GM(OverStroke, canvas, 500, 500) { 243 const size_t length = sizeof(examples) / sizeof(examples[0]); 244 const size_t width = 2; 245 246 for (size_t i = 0; i < length; i++) { 247 int x = (int)(i % width); 248 int y = (int)(i / width); 249 250 canvas->save(); 251 canvas->translate(150.0f * x, 150.0f * y); 252 canvas->scale(0.2f, 0.2f); 253 canvas->translate(300.0f, 400.0f); 254 255 examples[i](canvas); 256 257 canvas->restore(); 258 } 259} 260