|  | /* | 
|  | * Copyright 2013 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #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/SkPoint.h" | 
|  | #include "include/core/SkRect.h" | 
|  | #include "include/core/SkScalar.h" | 
|  | #include "include/core/SkSize.h" | 
|  | #include "include/core/SkString.h" | 
|  | #include "include/core/SkTypes.h" | 
|  | #include "include/private/SkTArray.h" | 
|  |  | 
|  | namespace skiagm { | 
|  |  | 
|  | class HairlinesGM : public GM { | 
|  | protected: | 
|  |  | 
|  |  | 
|  | SkString onShortName() override { | 
|  | return SkString("hairlines"); | 
|  | } | 
|  |  | 
|  | SkISize onISize() override { return SkISize::Make(1250, 1250); } | 
|  |  | 
|  | void onOnceBeforeDraw() override { | 
|  | { | 
|  | SkPathBuilder lineAngles; | 
|  | enum { | 
|  | kNumAngles = 15, | 
|  | kRadius = 40, | 
|  | }; | 
|  | for (int i = 0; i < kNumAngles; ++i) { | 
|  | SkScalar angle = SK_ScalarPI * SkIntToScalar(i) / kNumAngles; | 
|  | SkScalar x = kRadius * SkScalarCos(angle); | 
|  | SkScalar y = kRadius * SkScalarSin(angle); | 
|  | lineAngles.moveTo(x, y).lineTo(-x, -y); | 
|  | } | 
|  | fPaths.push_back(lineAngles.detach()); | 
|  | } | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(0, -10) | 
|  | .quadTo(100, 100, -10, 0) | 
|  | .detach()); | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(0, -5) | 
|  | .quadTo(100, 100, -5, 0) | 
|  | .detach()); | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(0, -2) | 
|  | .quadTo(100, 100, -2, 0) | 
|  | .detach()); | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(0, -1) | 
|  | .quadTo(100, 100, -2 + 306.0f / 4, 75) | 
|  | .detach()); | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(0, -1) | 
|  | .quadTo(100, 100, -1, 0) | 
|  | .detach()); | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(0, -0) | 
|  | .quadTo(100, 100, 0, 0) | 
|  | .detach()); | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(0, -0) | 
|  | .quadTo(100, 100, 75, 75) | 
|  | .detach()); | 
|  |  | 
|  | // Two problem cases for gpu hairline renderer found by shapeops testing. These used | 
|  | // to assert that the computed bounding box didn't contain all the vertices. | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(4, 6) | 
|  | .cubicTo(5, 6, 5, 4, 4, 0) | 
|  | .close() | 
|  | .detach()); | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(5, 1) | 
|  | .lineTo( 4.32787323f, 1.67212653f) | 
|  | .cubicTo(2.75223875f, 3.24776125f, | 
|  | 3.00581908f, 4.51236057f, | 
|  | 3.7580452f,  4.37367964f) | 
|  | .cubicTo(4.66472578f, 3.888381f, | 
|  | 5.f,         2.875f, | 
|  | 5.f,         1.f) | 
|  | .close() | 
|  | .detach()); | 
|  |  | 
|  | // Three paths that show the same bug (missing end caps) | 
|  |  | 
|  | fPaths.push_back(SkPathBuilder().moveTo(6.5f,5.5f) | 
|  | .lineTo(3.5f,0.5f) | 
|  | .moveTo(0.5f,5.5f) | 
|  | .lineTo(3.5f,0.5f) | 
|  | .detach()); | 
|  |  | 
|  | // An X (crbug.com/137317) | 
|  | fPaths.push_back(SkPathBuilder().moveTo(1, 1) | 
|  | .lineTo(6, 6) | 
|  | .moveTo(1, 6) | 
|  | .lineTo(6, 1) | 
|  | .detach()); | 
|  |  | 
|  | // A right angle (crbug.com/137465 and crbug.com/256776) | 
|  | fPaths.push_back(SkPathBuilder().moveTo(5.5f, 5.5f) | 
|  | .lineTo(5.5f, 0.5f) | 
|  | .lineTo(0.5f, 0.5f) | 
|  | .detach()); | 
|  |  | 
|  | { | 
|  | // Arc example to test imperfect truncation bug (crbug.com/295626) | 
|  | constexpr SkScalar kRad = SkIntToScalar(2000); | 
|  | constexpr SkScalar kStartAngle = 262.59717f; | 
|  | constexpr SkScalar kSweepAngle = SkScalarHalf(17.188717f); | 
|  |  | 
|  | SkPathBuilder bug; | 
|  |  | 
|  | // Add a circular arc | 
|  | SkRect circle = SkRect::MakeLTRB(-kRad, -kRad, kRad, kRad); | 
|  | bug.addArc(circle, kStartAngle, kSweepAngle); | 
|  |  | 
|  | // Now add the chord that should cap the circular arc | 
|  | SkPoint p0 = { kRad * SkScalarCos(SkDegreesToRadians(kStartAngle)), | 
|  | kRad * SkScalarSin(SkDegreesToRadians(kStartAngle)) }; | 
|  |  | 
|  | SkPoint p1 = { kRad * SkScalarCos(SkDegreesToRadians(kStartAngle + kSweepAngle)), | 
|  | kRad * SkScalarSin(SkDegreesToRadians(kStartAngle + kSweepAngle)) }; | 
|  |  | 
|  | bug.moveTo(p0); | 
|  | bug.lineTo(p1); | 
|  | fPaths.push_back(bug.detach()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void onDraw(SkCanvas* canvas) override { | 
|  | constexpr SkAlpha kAlphaValue[] = { 0xFF, 0x40 }; | 
|  | constexpr SkScalar kWidths[] = { 0, 0.5f, 1.5f }; | 
|  |  | 
|  | enum { | 
|  | kMargin = 5, | 
|  | }; | 
|  | int wrapX = 1250 - kMargin; | 
|  |  | 
|  | SkScalar maxH = 0; | 
|  | canvas->translate(SkIntToScalar(kMargin), SkIntToScalar(kMargin)); | 
|  | canvas->save(); | 
|  |  | 
|  | SkScalar x = SkIntToScalar(kMargin); | 
|  | for (int p = 0; p < fPaths.count(); ++p) { | 
|  | for (size_t a = 0; a < SK_ARRAY_COUNT(kAlphaValue); ++a) { | 
|  | for (int aa = 0; aa < 2; ++aa) { | 
|  | for (size_t w = 0; w < SK_ARRAY_COUNT(kWidths); w++) { | 
|  | const SkRect& bounds = fPaths[p].getBounds(); | 
|  |  | 
|  | if (x + bounds.width() > wrapX) { | 
|  | canvas->restore(); | 
|  | canvas->translate(0, maxH + SkIntToScalar(kMargin)); | 
|  | canvas->save(); | 
|  | maxH = 0; | 
|  | x = SkIntToScalar(kMargin); | 
|  | } | 
|  |  | 
|  | SkPaint paint; | 
|  | paint.setARGB(kAlphaValue[a], 0, 0, 0); | 
|  | paint.setAntiAlias(SkToBool(aa)); | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | paint.setStrokeWidth(kWidths[w]); | 
|  |  | 
|  | canvas->save(); | 
|  | canvas->translate(-bounds.fLeft, -bounds.fTop); | 
|  | canvas->drawPath(fPaths[p], paint); | 
|  | canvas->restore(); | 
|  |  | 
|  | maxH = std::max(maxH, bounds.height()); | 
|  |  | 
|  | SkScalar dx = bounds.width() + SkIntToScalar(kMargin); | 
|  | x += dx; | 
|  | canvas->translate(dx, 0); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | canvas->restore(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | SkTArray<SkPath> fPaths; | 
|  | using INHERITED = GM; | 
|  | }; | 
|  |  | 
|  | static void draw_squarehair_tests(SkCanvas* canvas, SkScalar width, SkPaint::Cap cap, bool aa) { | 
|  | SkPaint paint; | 
|  | paint.setStrokeCap(cap); | 
|  | paint.setStrokeWidth(width); | 
|  | paint.setAntiAlias(aa); | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | canvas->drawLine(10, 10, 20, 10, paint); | 
|  | canvas->drawLine(30, 10, 30, 20, paint); | 
|  | canvas->drawLine(40, 10, 50, 20, paint); | 
|  | SkPathBuilder path; | 
|  | path.moveTo(60, 10); | 
|  | path.quadTo(60, 20, 70, 20); | 
|  | path.conicTo(70, 10, 80, 10, 0.707f); | 
|  | canvas->drawPath(path.detach(), paint); | 
|  |  | 
|  | path.moveTo(90, 10); | 
|  | path.cubicTo(90, 20, 100, 20, 100, 10); | 
|  | path.lineTo(110, 10); | 
|  | canvas->drawPath(path.detach(), paint); | 
|  | canvas->translate(0, 30); | 
|  | } | 
|  |  | 
|  | DEF_SIMPLE_GM(squarehair, canvas, 240, 360) { | 
|  | const bool aliases[] = { false, true }; | 
|  | const SkScalar widths[] = { 0, 0.999f, 1, 1.001f }; | 
|  | const SkPaint::Cap caps[] = { SkPaint::kButt_Cap, SkPaint::kSquare_Cap, SkPaint::kRound_Cap }; | 
|  | for (auto alias : aliases) { | 
|  | canvas->save(); | 
|  | for (auto width : widths) { | 
|  | for (auto cap : caps) { | 
|  | draw_squarehair_tests(canvas, width, cap, alias); | 
|  | } | 
|  | } | 
|  | canvas->restore(); | 
|  | canvas->translate(120, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | // GM to test subdivision of hairlines | 
|  | static void draw_subdivided_quad(SkCanvas* canvas, int x0, int y0, int x1, int y1, SkColor color) { | 
|  | SkPaint paint; | 
|  | paint.setStrokeWidth(1); | 
|  | paint.setAntiAlias(true); | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | paint.setColor(color); | 
|  |  | 
|  | canvas->drawPath(SkPathBuilder().moveTo(0,0) | 
|  | .quadTo(SkIntToScalar(x0), SkIntToScalar(y0), | 
|  | SkIntToScalar(x1), SkIntToScalar(y1)) | 
|  | .detach(), | 
|  | paint); | 
|  | } | 
|  |  | 
|  | DEF_SIMPLE_GM(hairline_subdiv, canvas, 512, 256) { | 
|  | // no subdivisions | 
|  | canvas->translate(45, -25); | 
|  | draw_subdivided_quad(canvas, 334, 334, 467, 267, SK_ColorBLACK); | 
|  |  | 
|  | // one subdivision | 
|  | canvas->translate(-185, -150); | 
|  | draw_subdivided_quad(canvas, 472, 472, 660, 378, SK_ColorRED); | 
|  |  | 
|  | // two subdivisions | 
|  | canvas->translate(-275, -200); | 
|  | draw_subdivided_quad(canvas, 668, 668, 934, 535, SK_ColorGREEN); | 
|  |  | 
|  | // three subdivisions | 
|  | canvas->translate(-385, -260); | 
|  | draw_subdivided_quad(canvas, 944, 944, 1320, 756, SK_ColorBLUE); | 
|  | } | 
|  |  | 
|  | ////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | DEF_GM( return new HairlinesGM; ) | 
|  |  | 
|  | }  // namespace skiagm |