| /* | |
| * Copyright 2012 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/core/SkPathUtils.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkStrokeRec.h" | |
| #include "src/core/SkFloatBits.h" | |
| #include "src/core/SkPathPriv.h" | |
| #include "tests/Test.h" | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| static bool equal(const SkRect& a, const SkRect& b) { | |
| return SkScalarNearlyEqual(a.left(), b.left()) && | |
| SkScalarNearlyEqual(a.top(), b.top()) && | |
| SkScalarNearlyEqual(a.right(), b.right()) && | |
| SkScalarNearlyEqual(a.bottom(), b.bottom()); | |
| } | |
| static void test_strokecubic(skiatest::Reporter* reporter) { | |
| uint32_t hexCubicVals[] = { | |
| 0x424c1086, 0x44bcf0cb, // fX=51.0161362 fY=1511.52478 | |
| 0x424c107c, 0x44bcf0cb, // fX=51.0160980 fY=1511.52478 | |
| 0x424c10c2, 0x44bcf0cb, // fX=51.0163651 fY=1511.52478 | |
| 0x424c1119, 0x44bcf0ca, // fX=51.0166969 fY=1511.52466 | |
| }; | |
| SkPoint cubicVals[] = { | |
| {51.0161362f, 1511.52478f }, | |
| {51.0160980f, 1511.52478f }, | |
| {51.0163651f, 1511.52478f }, | |
| {51.0166969f, 1511.52466f }, | |
| }; | |
| SkPaint paint; | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setStrokeWidth(0.394537568f); | |
| SkPathBuilder builder; | |
| builder.moveTo(cubicVals[0]); | |
| builder.cubicTo(cubicVals[1], cubicVals[2], cubicVals[3]); | |
| skpathutils::FillPathWithPaint(builder.detach(), paint); | |
| builder.moveTo(SkBits2Float(hexCubicVals[0]), SkBits2Float(hexCubicVals[1])); | |
| builder.cubicTo(SkBits2Float(hexCubicVals[2]), SkBits2Float(hexCubicVals[3]), | |
| SkBits2Float(hexCubicVals[4]), SkBits2Float(hexCubicVals[5]), | |
| SkBits2Float(hexCubicVals[6]), SkBits2Float(hexCubicVals[7])); | |
| skpathutils::FillPathWithPaint(builder.detach(), paint); | |
| } | |
| static void test_strokerect(skiatest::Reporter* reporter) { | |
| const SkScalar width = SkIntToScalar(10); | |
| SkPaint paint; | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setStrokeWidth(width); | |
| SkRect r = { 0, 0, SkIntToScalar(200), SkIntToScalar(100) }; | |
| SkRect outer(r); | |
| outer.outset(width/2, width/2); | |
| static const auto joins = std::to_array<SkPaint::Join>({ | |
| SkPaint::kMiter_Join, | |
| SkPaint::kRound_Join, | |
| SkPaint::kBevel_Join, | |
| }); | |
| for (size_t i = 0; i < std::size(joins); ++i) { | |
| paint.setStrokeJoin(joins[i]); | |
| SkPath path = SkPath::Rect(r); | |
| SkPath fillPath = skpathutils::FillPathWithPaint(path, paint); | |
| REPORTER_ASSERT(reporter, equal(outer, fillPath.getBounds())); | |
| bool isMiter = SkPaint::kMiter_Join == joins[i]; | |
| SkRect nested[2]; | |
| REPORTER_ASSERT(reporter, SkPathPriv::IsNestedFillRects(fillPath, nested) == isMiter); | |
| if (isMiter) { | |
| SkRect inner(r); | |
| inner.inset(width/2, width/2); | |
| REPORTER_ASSERT(reporter, equal(nested[0], outer)); | |
| REPORTER_ASSERT(reporter, equal(nested[1], inner)); | |
| } | |
| } | |
| } | |
| static void test_strokerec_equality(skiatest::Reporter* reporter) { | |
| { | |
| SkStrokeRec s1(SkStrokeRec::kFill_InitStyle); | |
| SkStrokeRec s2(SkStrokeRec::kFill_InitStyle); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| // Test that style mismatch is detected. | |
| s2.setHairlineStyle(); | |
| REPORTER_ASSERT(reporter, !s1.hasEqualEffect(s2)); | |
| s1.setHairlineStyle(); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| // ResScale is not part of equality. | |
| s1.setResScale(2.1f); | |
| s2.setResScale(1.2f); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| s1.setFillStyle(); | |
| s2.setFillStyle(); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| s1.setStrokeStyle(1.0f, false); | |
| s2.setStrokeStyle(1.0f, false); | |
| s1.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kRound_Join, 2.9f); | |
| s2.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kRound_Join, 2.9f); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| } | |
| // Stroke parameters on fill or hairline style are not part of equality. | |
| { | |
| SkStrokeRec s1(SkStrokeRec::kFill_InitStyle); | |
| SkStrokeRec s2(SkStrokeRec::kFill_InitStyle); | |
| for (int i = 0; i < 2; ++i) { | |
| s1.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kRound_Join, 2.9f); | |
| s2.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kRound_Join, 2.1f); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| s2.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 2.9f); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| s2.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 2.9f); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| s1.setHairlineStyle(); | |
| s2.setHairlineStyle(); | |
| } | |
| } | |
| // Stroke parameters on stroke style are part of equality. | |
| { | |
| SkStrokeRec s1(SkStrokeRec::kFill_InitStyle); | |
| SkStrokeRec s2(SkStrokeRec::kFill_InitStyle); | |
| s1.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kRound_Join, 2.9f); | |
| s2.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kRound_Join, 2.9f); | |
| s1.setStrokeStyle(1.0f, false); | |
| s2.setStrokeStyle(1.0f, true); | |
| REPORTER_ASSERT(reporter, !s1.hasEqualEffect(s2)); | |
| s2.setStrokeStyle(2.1f, false); | |
| REPORTER_ASSERT(reporter, !s1.hasEqualEffect(s2)); | |
| s2.setStrokeStyle(1.0f, false); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| s2.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kRound_Join, 2.1f); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); // Miter limit not relevant to butt caps. | |
| s2.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 2.9f); | |
| REPORTER_ASSERT(reporter, !s1.hasEqualEffect(s2)); | |
| s2.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 2.9f); | |
| REPORTER_ASSERT(reporter, !s1.hasEqualEffect(s2)); | |
| // Sets fill. | |
| s1.setStrokeStyle(0.0f, true); | |
| s2.setStrokeStyle(0.0f, true); | |
| REPORTER_ASSERT(reporter, s1.hasEqualEffect(s2)); | |
| } | |
| } | |
| // From skbug.com/40037699. The large stroke width can cause numerical instabilities. | |
| static void test_big_stroke(skiatest::Reporter* reporter) { | |
| SkPaint paint; | |
| paint.setStyle(SkPaint::kStrokeAndFill_Style); | |
| paint.setStrokeWidth(1.49679073e+10f); | |
| SkPathBuilder builder; | |
| builder.moveTo(SkBits2Float(0x46380000), SkBits2Float(0xc6380000)); // 11776, -11776 | |
| builder.lineTo(SkBits2Float(0x46a00000), SkBits2Float(0xc6a00000)); // 20480, -20480 | |
| builder.lineTo(SkBits2Float(0x468c0000), SkBits2Float(0xc68c0000)); // 17920, -17920 | |
| builder.lineTo(SkBits2Float(0x46100000), SkBits2Float(0xc6100000)); // 9216, -9216 | |
| builder.lineTo(SkBits2Float(0x46380000), SkBits2Float(0xc6380000)); // 11776, -11776 | |
| builder.close(); | |
| (void)skpathutils::FillPathWithPaint(builder.detach(), paint); | |
| } | |
| DEF_TEST(Stroke, reporter) { | |
| test_strokecubic(reporter); | |
| test_strokerect(reporter); | |
| test_strokerec_equality(reporter); | |
| test_big_stroke(reporter); | |
| } |