| /* | |
| * Copyright 2026 Google LLC | |
| * | |
| * 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/SkFont.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/SkSurface.h" | |
| #include "include/core/SkTypes.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| static constexpr size_t kScale = 4; | |
| static constexpr int kGridWH = 70; | |
| static void draw_grid(SkCanvas* canvas) { | |
| SkPaint gridPaint; | |
| gridPaint.setColor(SK_ColorDKGRAY); | |
| gridPaint.setStyle(SkPaint::Style::kStroke_Style); | |
| gridPaint.setStrokeWidth(0); | |
| for (int y = 0; y <= (int)kGridWH; ++y) { | |
| canvas->drawLine(0, y, kGridWH, y, gridPaint); | |
| } | |
| for (int x = 0; x <= (int)kGridWH; ++x) { | |
| canvas->drawLine(x, 0, x, kGridWH, gridPaint); | |
| } | |
| } | |
| static SkRect highlight_box(SkPoint p) { | |
| constexpr float kOffset = 2; | |
| return SkRect::MakeXYWH(p.x() - kOffset, p.y() - kOffset, kOffset * 2, kOffset * 2); | |
| } | |
| // This function is for drawing highlight boxes around the parts of the contour we expect | |
| // there to be a capped edge (i.e. the beginning and end of an open contour) | |
| static void draw_highlights(SkCanvas* canvas, std::initializer_list<SkPath> paths) { | |
| SkPaint highlightPaint; | |
| highlightPaint.setStyle(SkPaint::kStroke_Style); | |
| highlightPaint.setStrokeWidth(0); | |
| highlightPaint.setColor(SK_ColorRED); | |
| highlightPaint.setAntiAlias(true); | |
| for (auto const& path : paths) { | |
| canvas->drawRect(highlight_box(path.points().front()), highlightPaint); | |
| canvas->drawRect(highlight_box(path.points().back()), highlightPaint); | |
| } | |
| } | |
| // We would like to test the ability to draw closed and open contours with | |
| // capped hairlines. Capped hairlines have interesting behavior because they extend out | |
| // by up to half a pixel, which has led to unintended effects when closing | |
| // contours with antialiasing on. | |
| // The contours here represent all simple path types, | |
| // We include contours on pixel lines and offset by 0.5 pixels, as these differ in behavior. | |
| // * First row: on pixel line, open contours (which should not show caps) | |
| // * Second row: off pixel line, open contours | |
| // * Third row: on pixel line, closed contours | |
| // * Fourth row: off pixel line, closed contours | |
| static void draw_hairline_contours_with_caps(SkCanvas* canvas, SkPaint::Cap cap) { | |
| SkPaint paint; | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setStrokeWidth(0); | |
| paint.setColor(SK_ColorBLACK); | |
| paint.setAntiAlias(true); | |
| paint.setStrokeCap(cap); | |
| auto pathSurface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(kGridWH, kGridWH)); | |
| SkASSERT_RELEASE(pathSurface); | |
| auto pathCanvas = pathSurface->getCanvas(); | |
| SkASSERT_RELEASE(pathCanvas); | |
| // first row | |
| auto lineOnOpen = SkPathBuilder().lineTo(0, 5).lineTo(5, 5).lineTo(5, 0).detach() | |
| .makeOffset(5, 5); | |
| pathCanvas->drawPath(lineOnOpen, paint); | |
| auto quadOnOpen = SkPathBuilder().quadTo(15, 5, 0, 10).detach() | |
| .makeOffset(20, 5); | |
| pathCanvas->drawPath(quadOnOpen, paint); | |
| auto cubicOnOpen = SkPathBuilder().cubicTo(-5, 0, -5, 5, 0, 10).detach() | |
| .makeOffset(40, 5); | |
| pathCanvas->drawPath(cubicOnOpen, paint); | |
| // second row | |
| auto lineOffOpen = lineOnOpen | |
| .makeOffset(0.5, 0.5).makeOffset(0, 15); | |
| pathCanvas->drawPath(lineOffOpen, paint); | |
| auto quadOffOpen = quadOnOpen | |
| .makeOffset(0.5, 0.5).makeOffset(0, 15); | |
| pathCanvas->drawPath(quadOffOpen, paint); | |
| auto cubicOffOpen = cubicOnOpen | |
| .makeOffset(0.5, 0.5).makeOffset(0, 15); | |
| pathCanvas->drawPath(cubicOffOpen, paint); | |
| // third row | |
| auto lineOnClosed = SkPathBuilder(lineOnOpen).close().detach() | |
| .makeOffset(0, 30); | |
| pathCanvas->drawPath(lineOnClosed, paint); | |
| auto quadOnClosed = SkPathBuilder(quadOnOpen).close().detach() | |
| .makeOffset(0, 30); | |
| pathCanvas->drawPath(quadOnClosed, paint); | |
| auto cubicOnClosed = SkPathBuilder(cubicOnOpen).close().detach() | |
| .makeOffset(0, 30); | |
| pathCanvas->drawPath(cubicOnClosed, paint); | |
| // forth row | |
| auto lineOffClosed = SkPathBuilder(lineOnOpen).close().detach() | |
| .makeOffset(0.5, 0.5).makeOffset(0, 45); | |
| pathCanvas->drawPath(lineOffClosed, paint); | |
| auto quadOffClosed = SkPathBuilder(quadOnOpen).close().detach() | |
| .makeOffset(0.5, 0.5).makeOffset(0, 45); | |
| pathCanvas->drawPath(quadOffClosed, paint); | |
| auto cubicOffClosed = SkPathBuilder(cubicOnOpen).close().detach() | |
| .makeOffset(0.5, 0.5).makeOffset(0, 45); | |
| pathCanvas->drawPath(cubicOffClosed, paint); | |
| auto pathImg = pathSurface->makeImageSnapshot(); | |
| canvas->drawImage(pathImg, 0, 0); | |
| canvas->scale(kScale, kScale); | |
| canvas->drawImage(pathImg, 15, 0); | |
| canvas->translate(15, 0); | |
| draw_grid(canvas); | |
| draw_highlights(canvas, {lineOnOpen, quadOnOpen, cubicOnOpen, lineOffOpen, quadOffOpen, cubicOffOpen}); | |
| } | |
| #define WIDTH 250 | |
| #define HEIGHT 250 | |
| DEF_SIMPLE_GM(hairlines_buttcap, canvas, WIDTH, HEIGHT) { | |
| draw_hairline_contours_with_caps(canvas, SkPaint::kButt_Cap); | |
| } | |
| DEF_SIMPLE_GM(hairlines_roundcap, canvas, WIDTH, HEIGHT) { | |
| draw_hairline_contours_with_caps(canvas, SkPaint::kRound_Cap); | |
| } | |
| DEF_SIMPLE_GM(hairlines_squarecap, canvas, WIDTH, HEIGHT) { | |
| draw_hairline_contours_with_caps(canvas, SkPaint::kSquare_Cap); | |
| } |