1 /* 2 * Copyright 2013 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 #include "gm.h" 9 #include "SkCanvas.h" 10 #include "SkPath.h" 11 #include "SkTArray.h" 12 13 namespace skiagm { 14 15 class HairlinesGM : public GM { 16 protected: 17 18 19 SkString onShortName() override { 20 return SkString("hairlines"); 21 } 22 23 SkISize onISize() override { return SkISize::Make(1250, 1250); } 24 25 void onOnceBeforeDraw() override { 26 { 27 SkPath* lineAnglesPath = &fPaths.push_back(); 28 enum { 29 kNumAngles = 15, 30 kRadius = 40, 31 }; 32 for (int i = 0; i < kNumAngles; ++i) { 33 SkScalar angle = SK_ScalarPI * SkIntToScalar(i) / kNumAngles; 34 SkScalar x = kRadius * SkScalarCos(angle); 35 SkScalar y = kRadius * SkScalarSin(angle); 36 lineAnglesPath->moveTo(x, y); 37 lineAnglesPath->lineTo(-x, -y); 38 } 39 } 40 41 { 42 SkPath* kindaTightQuad = &fPaths.push_back(); 43 kindaTightQuad->moveTo(0, -10 * SK_Scalar1); 44 kindaTightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -10 * SK_Scalar1, 0); 45 } 46 47 { 48 SkPath* tightQuad = &fPaths.push_back(); 49 tightQuad->moveTo(0, -5 * SK_Scalar1); 50 tightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -5 * SK_Scalar1, 0); 51 } 52 53 { 54 SkPath* tighterQuad = &fPaths.push_back(); 55 tighterQuad->moveTo(0, -2 * SK_Scalar1); 56 tighterQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -2 * SK_Scalar1, 0); 57 } 58 59 { 60 SkPath* unevenTighterQuad = &fPaths.push_back(); 61 unevenTighterQuad->moveTo(0, -1 * SK_Scalar1); 62 SkPoint p; 63 p.set(-2 * SK_Scalar1 + 3 * SkIntToScalar(102) / 4, SkIntToScalar(75)); 64 unevenTighterQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), p.fX, p.fY); 65 } 66 67 { 68 SkPath* reallyTightQuad = &fPaths.push_back(); 69 reallyTightQuad->moveTo(0, -1 * SK_Scalar1); 70 reallyTightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -1 * SK_Scalar1, 0); 71 } 72 73 { 74 SkPath* closedQuad = &fPaths.push_back(); 75 closedQuad->moveTo(0, -0); 76 closedQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), 0, 0); 77 } 78 79 { 80 SkPath* unevenClosedQuad = &fPaths.push_back(); 81 unevenClosedQuad->moveTo(0, -0); 82 unevenClosedQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), 83 SkIntToScalar(75), SkIntToScalar(75)); 84 } 85 86 // Two problem cases for gpu hairline renderer found by shapeops testing. These used 87 // to assert that the computed bounding box didn't contain all the vertices. 88 { 89 SkPath* problem1 = &fPaths.push_back(); 90 problem1->moveTo(SkIntToScalar(4), SkIntToScalar(6)); 91 problem1->cubicTo(SkIntToScalar(5), SkIntToScalar(6), 92 SkIntToScalar(5), SkIntToScalar(4), 93 SkIntToScalar(4), SkIntToScalar(0)); 94 problem1->close(); 95 } 96 97 { 98 SkPath* problem2 = &fPaths.push_back(); 99 problem2->moveTo(SkIntToScalar(5), SkIntToScalar(1)); 100 problem2->lineTo(4.32787323f, 1.67212653f); 101 problem2->cubicTo(2.75223875f, 3.24776125f, 102 3.00581908f, 4.51236057f, 103 3.7580452f, 4.37367964f); 104 problem2->cubicTo(4.66472578f, 3.888381f, 105 5.f, 2.875f, 106 5.f, 1.f); 107 problem2->close(); 108 } 109 110 // Three paths that show the same bug (missing end caps) 111 { 112 // A caret (crbug.com/131770) 113 SkPath* bug0 = &fPaths.push_back(); 114 bug0->moveTo(6.5f,5.5f); 115 bug0->lineTo(3.5f,0.5f); 116 bug0->moveTo(0.5f,5.5f); 117 bug0->lineTo(3.5f,0.5f); 118 } 119 120 { 121 // An X (crbug.com/137317) 122 SkPath* bug1 = &fPaths.push_back(); 123 124 bug1->moveTo(1, 1); 125 bug1->lineTo(6, 6); 126 bug1->moveTo(1, 6); 127 bug1->lineTo(6, 1); 128 } 129 130 { 131 // A right angle (crbug.com/137465 and crbug.com/256776) 132 SkPath* bug2 = &fPaths.push_back(); 133 134 bug2->moveTo(5.5f, 5.5f); 135 bug2->lineTo(5.5f, 0.5f); 136 bug2->lineTo(0.5f, 0.5f); 137 } 138 139 { 140 // Arc example to test imperfect truncation bug (crbug.com/295626) 141 constexpr SkScalar kRad = SkIntToScalar(2000); 142 constexpr SkScalar kStartAngle = 262.59717f; 143 constexpr SkScalar kSweepAngle = SkScalarHalf(17.188717f); 144 145 SkPath* bug = &fPaths.push_back(); 146 147 // Add a circular arc 148 SkRect circle = SkRect::MakeLTRB(-kRad, -kRad, kRad, kRad); 149 bug->addArc(circle, kStartAngle, kSweepAngle); 150 151 // Now add the chord that should cap the circular arc 152 SkScalar cosV, sinV = SkScalarSinCos(SkDegreesToRadians(kStartAngle), &cosV); 153 154 SkPoint p0 = SkPoint::Make(kRad * cosV, kRad * sinV); 155 156 sinV = SkScalarSinCos(SkDegreesToRadians(kStartAngle + kSweepAngle), &cosV); 157 158 SkPoint p1 = SkPoint::Make(kRad * cosV, kRad * sinV); 159 160 bug->moveTo(p0); 161 bug->lineTo(p1); 162 } 163 } 164 165 void onDraw(SkCanvas* canvas) override { 166 constexpr SkAlpha kAlphaValue[] = { 0xFF, 0x40 }; 167 constexpr SkScalar kWidths[] = { 0, 0.5f, 1.5f }; 168 169 enum { 170 kMargin = 5, 171 }; 172 int wrapX = 1250 - kMargin; 173 174 SkScalar maxH = 0; 175 canvas->translate(SkIntToScalar(kMargin), SkIntToScalar(kMargin)); 176 canvas->save(); 177 178 SkScalar x = SkIntToScalar(kMargin); 179 for (int p = 0; p < fPaths.count(); ++p) { 180 for (size_t a = 0; a < SK_ARRAY_COUNT(kAlphaValue); ++a) { 181 for (int aa = 0; aa < 2; ++aa) { 182 for (size_t w = 0; w < SK_ARRAY_COUNT(kWidths); w++) { 183 const SkRect& bounds = fPaths[p].getBounds(); 184 185 if (x + bounds.width() > wrapX) { 186 canvas->restore(); 187 canvas->translate(0, maxH + SkIntToScalar(kMargin)); 188 canvas->save(); 189 maxH = 0; 190 x = SkIntToScalar(kMargin); 191 } 192 193 SkPaint paint; 194 paint.setARGB(kAlphaValue[a], 0, 0, 0); 195 paint.setAntiAlias(SkToBool(aa)); 196 paint.setStyle(SkPaint::kStroke_Style); 197 paint.setStrokeWidth(kWidths[w]); 198 199 canvas->save(); 200 canvas->translate(-bounds.fLeft, -bounds.fTop); 201 canvas->drawPath(fPaths[p], paint); 202 canvas->restore(); 203 204 maxH = SkMaxScalar(maxH, bounds.height()); 205 206 SkScalar dx = bounds.width() + SkIntToScalar(kMargin); 207 x += dx; 208 canvas->translate(dx, 0); 209 } 210 } 211 } 212 } 213 canvas->restore(); 214 } 215 216 private: 217 SkTArray<SkPath> fPaths; 218 typedef GM INHERITED; 219 }; 220 221 static void draw_squarehair_tests(SkCanvas* canvas, SkScalar width, SkPaint::Cap cap, bool aa) { 222 SkPaint paint; 223 paint.setStrokeCap(cap); 224 paint.setStrokeWidth(width); 225 paint.setAntiAlias(aa); 226 paint.setStyle(SkPaint::kStroke_Style); 227 canvas->drawLine(10, 10, 20, 10, paint); 228 canvas->drawLine(30, 10, 30, 20, paint); 229 canvas->drawLine(40, 10, 50, 20, paint); 230 SkPath path; 231 path.moveTo(60, 10); 232 path.quadTo(60, 20, 70, 20); 233 path.conicTo(70, 10, 80, 10, 0.707f); 234 canvas->drawPath(path, paint); 235 path.reset(); 236 path.moveTo(90, 10); 237 path.cubicTo(90, 20, 100, 20, 100, 10); 238 path.lineTo(110, 10); 239 canvas->drawPath(path, paint); 240 canvas->translate(0, 30); 241 } 242 243 DEF_SIMPLE_GM(squarehair, canvas, 240, 360) { 244 const bool aliases[] = { false, true }; 245 const SkScalar widths[] = { 0, 0.999f, 1, 1.001f }; 246 const SkPaint::Cap caps[] = { SkPaint::kButt_Cap, SkPaint::kSquare_Cap, SkPaint::kRound_Cap }; 247 for (auto alias : aliases) { 248 canvas->save(); 249 for (auto width : widths) { 250 for (auto cap : caps) { 251 draw_squarehair_tests(canvas, width, cap, alias); 252 } 253 } 254 canvas->restore(); 255 canvas->translate(120, 0); 256 } 257 } 258 259 ////////////////////////////////////////////////////////////////////////////// 260 261 static GM* MyFactory(void*) { return new HairlinesGM; } 262 static GMRegistry reg(MyFactory); 263 264 } 265