/* * 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/SkPaint.h" #include "include/core/SkPathBuilder.h" #include "include/core/SkScalar.h" #include "include/core/SkSize.h" #include "include/core/SkString.h" #include "include/core/SkTypes.h" namespace skiagm { // This GM tests a grab-bag of non-closed paths. All these paths look like // closed rects, but they don't call path.close(). Depending on the stroke // settings these slightly different paths give widely different results. class NonClosedPathsGM: public GM { public: NonClosedPathsGM() {} enum ClosureType { TotallyNonClosed, // The last point doesn't coincide with the first one in the contour. // The path looks not closed at all. FakeCloseCorner, // The last point coincides with the first one at a corner. // The path looks closed, but final rendering has 2 ends with cap. FakeCloseMiddle, // The last point coincides with the first one in the middle of a line. // The path looks closed, and the final rendering looks closed too. kClosureTypeCount }; protected: SkString onShortName() override { return SkString("nonclosedpaths"); } // 12 * 18 + 3 cases, every case is 100 * 100 pixels. SkISize onISize() override { return SkISize::Make(1220, 1920); } // Use rect-like geometry for non-closed path, for right angles make it // easier to show the visual difference of lineCap and lineJoin. static SkPath MakePath(ClosureType type) { SkPathBuilder path; if (FakeCloseMiddle == type) { path.moveTo(30, 50); path.lineTo(30, 30); } else { path.moveTo(30, 30); } path.lineTo(70, 30); path.lineTo(70, 70); path.lineTo(30, 70); path.lineTo(30, 50); if (FakeCloseCorner == type) { path.lineTo(30, 30); } return path.detach(); } // Set the location for the current test on the canvas static void SetLocation(SkCanvas* canvas, int counter, int lineNum) { SkScalar x = SK_Scalar1 * 100 * (counter % lineNum) + 10 + SK_Scalar1 / 4; SkScalar y = SK_Scalar1 * 100 * (counter / lineNum) + 10 + 3 * SK_Scalar1 / 4; canvas->translate(x, y); } void onDraw(SkCanvas* canvas) override { // Stroke widths are: // 0(may use hairline rendering), 10(common case for stroke-style) // 40 and 50(>= geometry width/height, make the contour filled in fact) constexpr int kStrokeWidth[] = {0, 10, 40, 50}; int numWidths = SK_ARRAY_COUNT(kStrokeWidth); constexpr SkPaint::Style kStyle[] = { SkPaint::kStroke_Style, SkPaint::kStrokeAndFill_Style }; constexpr SkPaint::Cap kCap[] = { SkPaint::kButt_Cap, SkPaint::kRound_Cap, SkPaint::kSquare_Cap }; constexpr SkPaint::Join kJoin[] = { SkPaint::kMiter_Join, SkPaint::kRound_Join, SkPaint::kBevel_Join }; constexpr ClosureType kType[] = { TotallyNonClosed, FakeCloseCorner, FakeCloseMiddle }; int counter = 0; SkPaint paint; paint.setAntiAlias(true); // For stroke style painter and fill-and-stroke style painter for (size_t type = 0; type < kClosureTypeCount; ++type) { for (size_t style = 0; style < SK_ARRAY_COUNT(kStyle); ++style) { for (size_t cap = 0; cap < SK_ARRAY_COUNT(kCap); ++cap) { for (size_t join = 0; join < SK_ARRAY_COUNT(kJoin); ++join) { for (int width = 0; width < numWidths; ++width) { canvas->save(); SetLocation(canvas, counter, SkPaint::kJoinCount * numWidths); SkPath path = MakePath(kType[type]); paint.setStyle(kStyle[style]); paint.setStrokeCap(kCap[cap]); paint.setStrokeJoin(kJoin[join]); paint.setStrokeWidth(SkIntToScalar(kStrokeWidth[width])); canvas->drawPath(path, paint); canvas->restore(); ++counter; } } } } } // For fill style painter paint.setStyle(SkPaint::kFill_Style); for (size_t type = 0; type < kClosureTypeCount; ++type) { canvas->save(); SetLocation(canvas, counter, SkPaint::kJoinCount * numWidths); SkPath path = MakePath(kType[type]); canvas->drawPath(path, paint); canvas->restore(); ++counter; } } private: using INHERITED = GM; }; ////////////////////////////////////////////////////////////////////////////// DEF_GM(return new NonClosedPathsGM;) } // namespace skiagm