From 06a44705d08319a9b7305bed7fed2eb6d16c8bf6 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 2 Jun 2026 17:32:12 +0200 Subject: [PATCH] Harden stroke sampler edge tests --- docs/modernization/build-inventory.md | 3 +- docs/modernization/roadmap.md | 3 +- tests/paint/stroke_tests.cpp | 59 +++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 1deec0f..de52a62 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -95,7 +95,8 @@ Known local toolchain state: decode, PPI header/layout, settings document, document snapshot/per-layer-frame/move/duration/face-pixel/PPI export coverage, snapshot-embedded face-payload rejection, paint brush/final-blend/ - stroke-alpha-blend/stroke/stroke-script coverage, renderer shader descriptor and OpenGL capability coverage, UI + stroke-alpha-blend/stroke spacing/stroke stress/stroke-script coverage, + renderer shader descriptor and OpenGL capability coverage, UI color parsing, and layout XML parse coverage. - `pano_cli inspect-image` reports PNG IHDR metadata as JSON and is covered by `pano_cli_inspect_png_metadata_smoke` with a tiny IHDR fixture. diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 7830532..8c2a003 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -320,7 +320,8 @@ corrupt/truncated/unsupported, extreme-dimension, and key/value limit tests. `pp_paint` has started with pure brush parameter validation/stamp evaluation, CPU reference math for the five current final RGBA shader blend modes plus the shader-style stroke-alpha blend modes used by pattern/dual-brush mixing, and deterministic -stroke spacing/interpolation plus a pure text stroke-script parser. +stroke spacing/interpolation plus duplicate-segment, non-finite, sample-limit, +and 1001-sample stress coverage, plus a pure text stroke-script parser. `pp_document` has started with a pure canvas/layer/frame model, alpha-lock metadata, snapshot construction, per-layer frame metadata, layer metadata operations, frame diff --git a/tests/paint/stroke_tests.cpp b/tests/paint/stroke_tests.cpp index a23cd40..002a248 100644 --- a/tests/paint/stroke_tests.cpp +++ b/tests/paint/stroke_tests.cpp @@ -3,6 +3,7 @@ #include #include +#include using pp::foundation::StatusCode; using pp::paint::StrokePoint; @@ -76,6 +77,50 @@ void can_skip_endpoint_and_clamps_pressure(pp::tests::Harness& h) PP_EXPECT(h, near(result.value()[2].distance, 4.0F)); } +void skips_zero_length_segments_without_resetting_spacing(pp::tests::Harness& h) +{ + const std::array points { + StrokePoint { .x = 0.0F, .y = 0.0F, .pressure = 0.2F }, + StrokePoint { .x = 0.0F, .y = 0.0F, .pressure = 0.8F }, + StrokePoint { .x = 6.0F, .y = 0.0F, .pressure = 0.8F }, + StrokePoint { .x = 6.0F, .y = 0.0F, .pressure = 0.1F }, + StrokePoint { .x = 6.0F, .y = 4.0F, .pressure = 0.1F }, + }; + + const auto result = sample_stroke(points, StrokeSamplingConfig { .spacing = 3.0F }); + PP_EXPECT(h, result.ok()); + PP_EXPECT(h, result.value().size() == 5U); + PP_EXPECT(h, near(result.value()[1].x, 3.0F)); + PP_EXPECT(h, near(result.value()[1].y, 0.0F)); + PP_EXPECT(h, near(result.value()[2].x, 6.0F)); + PP_EXPECT(h, near(result.value()[2].y, 0.0F)); + PP_EXPECT(h, near(result.value()[3].x, 6.0F)); + PP_EXPECT(h, near(result.value()[3].y, 3.0F)); + PP_EXPECT(h, near(result.value()[4].distance, 10.0F)); +} + +void samples_large_stroke_with_deterministic_count(pp::tests::Harness& h) +{ + const std::array points { + StrokePoint { .x = 0.0F, .y = 0.0F, .pressure = 0.0F }, + StrokePoint { .x = 1000.0F, .y = 0.0F, .pressure = 1.0F }, + }; + + const auto result = sample_stroke( + points, + StrokeSamplingConfig { + .spacing = 1.0F, + .max_samples = 1001U, + }); + PP_EXPECT(h, result.ok()); + PP_EXPECT(h, result.value().size() == 1001U); + PP_EXPECT(h, near(result.value()[0].distance, 0.0F)); + PP_EXPECT(h, near(result.value()[500].x, 500.0F)); + PP_EXPECT(h, near(result.value()[500].pressure, 0.5F)); + PP_EXPECT(h, near(result.value().back().x, 1000.0F)); + PP_EXPECT(h, near(result.value().back().distance, 1000.0F)); +} + void rejects_invalid_sampling_inputs(pp::tests::Harness& h) { const std::array one_point { @@ -89,6 +134,10 @@ void rejects_invalid_sampling_inputs(pp::tests::Harness& h) StrokePoint { .x = 0.0F, .y = 0.0F }, StrokePoint { .x = std::nanf(""), .y = 1.0F }, }; + const std::array non_finite_pressure { + StrokePoint { .x = 0.0F, .y = 0.0F }, + StrokePoint { .x = 1.0F, .y = 1.0F, .pressure = std::nanf("") }, + }; const std::array valid { StrokePoint { .x = 0.0F, .y = 0.0F }, StrokePoint { .x = 10.0F, .y = 0.0F }, @@ -96,21 +145,29 @@ void rejects_invalid_sampling_inputs(pp::tests::Harness& h) const auto missing_points = sample_stroke(one_point, StrokeSamplingConfig {}); const auto bad_spacing = sample_stroke(valid, StrokeSamplingConfig { .spacing = 0.0F }); + const auto infinite_spacing = sample_stroke( + valid, + StrokeSamplingConfig { .spacing = std::numeric_limits::infinity() }); const auto bad_limit = sample_stroke(valid, StrokeSamplingConfig { .max_samples = max_stroke_samples + 1U }); const auto no_distance = sample_stroke(zero_length, StrokeSamplingConfig {}); const auto bad_point = sample_stroke(non_finite, StrokeSamplingConfig {}); + const auto bad_pressure = sample_stroke(non_finite_pressure, StrokeSamplingConfig {}); const auto too_many = sample_stroke(valid, StrokeSamplingConfig { .spacing = 1.0F, .max_samples = 2U }); PP_EXPECT(h, !missing_points.ok()); PP_EXPECT(h, missing_points.status().code == StatusCode::invalid_argument); PP_EXPECT(h, !bad_spacing.ok()); PP_EXPECT(h, bad_spacing.status().code == StatusCode::invalid_argument); + PP_EXPECT(h, !infinite_spacing.ok()); + PP_EXPECT(h, infinite_spacing.status().code == StatusCode::invalid_argument); PP_EXPECT(h, !bad_limit.ok()); PP_EXPECT(h, bad_limit.status().code == StatusCode::out_of_range); PP_EXPECT(h, !no_distance.ok()); PP_EXPECT(h, no_distance.status().code == StatusCode::invalid_argument); PP_EXPECT(h, !bad_point.ok()); PP_EXPECT(h, bad_point.status().code == StatusCode::invalid_argument); + PP_EXPECT(h, !bad_pressure.ok()); + PP_EXPECT(h, bad_pressure.status().code == StatusCode::invalid_argument); PP_EXPECT(h, !too_many.ok()); PP_EXPECT(h, too_many.status().code == StatusCode::out_of_range); } @@ -123,6 +180,8 @@ int main() harness.run("samples_straight_line_at_fixed_spacing", samples_straight_line_at_fixed_spacing); harness.run("carries_spacing_across_segments", carries_spacing_across_segments); harness.run("can_skip_endpoint_and_clamps_pressure", can_skip_endpoint_and_clamps_pressure); + harness.run("skips_zero_length_segments_without_resetting_spacing", skips_zero_length_segments_without_resetting_spacing); + harness.run("samples_large_stroke_with_deterministic_count", samples_large_stroke_with_deterministic_count); harness.run("rejects_invalid_sampling_inputs", rejects_invalid_sampling_inputs); return harness.finish(); }