| /* |
| * 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 "include/core/SkBitmap.h" |
| #include "include/core/SkCanvas.h" |
| #include "include/core/SkColor.h" |
| #include "include/core/SkImageInfo.h" |
| #include "include/core/SkPaint.h" |
| #include "include/core/SkPath.h" |
| #include "include/core/SkPathBuilder.h" |
| #include "include/core/SkSurface.h" |
| #include "tests/Test.h" |
| |
| #include <cmath> |
| |
| static sk_sp<SkSurface> create_white_raster_surface() { |
| const SkImageInfo info = SkImageInfo::MakeN32Premul(70, 70); |
| sk_sp<SkSurface> surface = SkSurfaces::Raster(info); |
| if (surface) { |
| surface->getCanvas()->clear(SK_ColorWHITE); |
| } |
| return surface; |
| } |
| |
| static SkPaint create_aa_hairline_paint() { |
| SkPaint paint; |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setStrokeWidth(0); |
| paint.setColor(SK_ColorBLACK); |
| paint.setAntiAlias(true); |
| return paint; |
| } |
| |
| static bool color_is_lteq(SkColor a, SkColor b) { |
| return SkColorGetR(a) <= SkColorGetR(b) && SkColorGetG(a) <= SkColorGetG(b) && |
| SkColorGetB(a) <= SkColorGetB(b) && SkColorGetA(a) <= SkColorGetA(b); |
| } |
| |
| static void assert_color_in_range(skiatest::Reporter* reporter, |
| const SkPixmap& pixmap, |
| const std::string& testName, |
| SkIPoint pt, |
| SkColor expected_min, |
| SkColor expected_max) { |
| skiatest::ReporterContext subtest(reporter, testName); |
| |
| SkColor actual = pixmap.getColor(pt.x(), pt.y()); |
| |
| REPORTER_ASSERT(reporter, |
| color_is_lteq(expected_min, actual) && color_is_lteq(actual, expected_max), |
| "%s: actual color (%x) is outside the range of expected colors (between (%x) " |
| "and (%x)) for pixel at (%d, %d)", |
| testName.c_str(), |
| actual, |
| expected_min, |
| expected_max, |
| pt.x(), |
| pt.y()); |
| } |
| |
| static void draw_aa_hairline_path_with_caps_no_offset(skiatest::Reporter* reporter, |
| SkPaint::Cap cap) { |
| sk_sp<SkSurface> surface = create_white_raster_surface(); |
| SkASSERT(surface); |
| |
| SkCanvas* canvas = surface->getCanvas(); |
| |
| SkPaint paint = create_aa_hairline_paint(); |
| paint.setStrokeCap(cap); |
| |
| SkPath path = SkPathBuilder() |
| .moveTo(5, 5) |
| .lineTo(5, 10) |
| .lineTo(10, 10) |
| .lineTo(10, 5) |
| .close() |
| .detach(); |
| |
| canvas->drawPath(path, paint); |
| |
| SkPixmap pixmap; |
| SkASSERT_RELEASE(surface->peekPixels(&pixmap)); |
| |
| auto assert_color = |
| [&](const std::string& name, SkIPoint pt, SkColor expected_min, SkColor expected_max) { |
| assert_color_in_range(reporter, pixmap, name, pt, expected_min, expected_max); |
| }; |
| |
| auto assert_exact_color = [&](const std::string& name, SkIPoint pt, SkColor exact_color) { |
| assert_color_in_range(reporter, pixmap, name, pt, exact_color, exact_color); |
| }; |
| |
| // these asserts are to verify that the contour drawn is a square |
| // with double pixel wide gray lines. The inside and outside of the square |
| // should be white. This should be the expected behavior across all caps |
| |
| assert_exact_color("left of west line", {3, 7}, SK_ColorWHITE); |
| assert_color("on west line", {4, 7}, SK_ColorBLACK, SK_ColorGRAY); |
| assert_color("on west line", {5, 7}, SK_ColorBLACK, SK_ColorGRAY); |
| assert_exact_color("right of west line", {6, 7}, SK_ColorWHITE); |
| |
| assert_exact_color("above south line", {7, 8}, SK_ColorWHITE); |
| assert_color("on south line", {7, 9}, SK_ColorBLACK, SK_ColorGRAY); |
| assert_color("on south line", {7, 10}, SK_ColorBLACK, SK_ColorGRAY); |
| assert_exact_color("below south line", {7, 11}, SK_ColorWHITE); |
| |
| assert_exact_color("left of east line", {8, 7}, SK_ColorWHITE); |
| assert_color("on east line", {9, 7}, SK_ColorBLACK, SK_ColorGRAY); |
| assert_color("on east line", {10, 7}, SK_ColorBLACK, SK_ColorGRAY); |
| assert_exact_color("right of east line", {11, 7}, SK_ColorWHITE); |
| |
| assert_exact_color("above north line", {7, 3}, SK_ColorWHITE); |
| assert_color("on north line", {7, 4}, SK_ColorBLACK, SK_ColorGRAY); |
| assert_color("on north line", {7, 5}, SK_ColorBLACK, SK_ColorGRAY); |
| assert_exact_color("below north line", {7, 6}, SK_ColorWHITE); |
| } |
| |
| DEF_TEST(DrawAntialiasHairline_SquareCappedNoPixelOffsetClosedContour_DoublePixelWideOutput, |
| reporter) { |
| draw_aa_hairline_path_with_caps_no_offset(reporter, SkPaint::kSquare_Cap); |
| } |
| |
| DEF_TEST(DrawAntialiasHairline_RoundCappedNoPixelOffsetClosedContour_DoublePixelWideOutput, |
| reporter) { |
| draw_aa_hairline_path_with_caps_no_offset(reporter, SkPaint::kRound_Cap); |
| } |
| |
| DEF_TEST(DrawAntialiasHairline_ButtCappedNoPixelOffsetClosedContour_DoublePixelWideOutput, |
| reporter) { |
| draw_aa_hairline_path_with_caps_no_offset(reporter, SkPaint::kButt_Cap); |
| } |
| |
| static void draw_aa_hairline_path_with_caps_and_offset(skiatest::Reporter* reporter, |
| SkPaint::Cap cap) { |
| sk_sp<SkSurface> surface = create_white_raster_surface(); |
| SkASSERT(surface); |
| |
| SkCanvas* canvas = surface->getCanvas(); |
| |
| SkPaint paint = create_aa_hairline_paint(); |
| paint.setStrokeCap(cap); |
| |
| SkPath path = SkPathBuilder() |
| .moveTo(5, 5) |
| .lineTo(5, 10) |
| .lineTo(10, 10) |
| .lineTo(10, 5) |
| .close() |
| .detach() |
| .makeOffset(0.5, 0.5); |
| |
| canvas->drawPath(path, paint); |
| |
| SkPixmap pixmap; |
| SkASSERT_RELEASE(surface->peekPixels(&pixmap)); |
| |
| auto assert_exact_color = [&](const std::string& name, SkIPoint pt, SkColor exact_color) { |
| assert_color_in_range(reporter, pixmap, name, pt, exact_color, exact_color); |
| }; |
| |
| // these asserts are to verify that the contour drawn is a square |
| // with single pixel wide black lines. The inside and outside of the square |
| // should be white. This should be the expected behavior across all caps |
| |
| assert_exact_color("left of west line", {4, 7}, SK_ColorWHITE); |
| assert_exact_color("on west line", {5, 7}, SK_ColorBLACK); |
| assert_exact_color("right of west line", {6, 7}, SK_ColorWHITE); |
| |
| assert_exact_color("above south line", {7, 9}, SK_ColorWHITE); |
| assert_exact_color("on south line", {7, 10}, SK_ColorBLACK); |
| assert_exact_color("below south line", {7, 11}, SK_ColorWHITE); |
| |
| assert_exact_color("left of east line", {9, 7}, SK_ColorWHITE); |
| assert_exact_color("on east line", {10, 7}, SK_ColorBLACK); |
| assert_exact_color("right of east line", {11, 7}, SK_ColorWHITE); |
| |
| assert_exact_color("above north line", {7, 4}, SK_ColorWHITE); |
| assert_exact_color("on north line", {7, 5}, SK_ColorBLACK); |
| assert_exact_color("below north line", {7, 6}, SK_ColorWHITE); |
| } |
| |
| DEF_TEST(DrawAntialiasHairline_SquareCappedHalfPixelOffsetClosedContour_SinglePixelWideOutput, |
| reporter) { |
| draw_aa_hairline_path_with_caps_and_offset(reporter, SkPaint::kSquare_Cap); |
| } |
| |
| DEF_TEST(DrawAntialiasHairline_RoundCappedHalfPixelOffsetClosedContour_SinglePixelWideOutput, |
| reporter) { |
| draw_aa_hairline_path_with_caps_and_offset(reporter, SkPaint::kRound_Cap); |
| } |
| |
| DEF_TEST(DrawAntialiasHairline_ButtCappedHalfPixelOffsetClosedContour_SinglePixelWideOutput, |
| reporter) { |
| draw_aa_hairline_path_with_caps_and_offset(reporter, SkPaint::kButt_Cap); |
| } |