Harden stroke sampler edge tests

This commit is contained in:
2026-06-02 17:32:12 +02:00
parent 3ae84de123
commit 06a44705d0
3 changed files with 63 additions and 2 deletions

View File

@@ -95,7 +95,8 @@ Known local toolchain state:
decode, PPI header/layout, settings document, document decode, PPI header/layout, settings document, document
snapshot/per-layer-frame/move/duration/face-pixel/PPI export coverage, snapshot/per-layer-frame/move/duration/face-pixel/PPI export coverage,
snapshot-embedded face-payload rejection, paint brush/final-blend/ 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. color parsing, and layout XML parse coverage.
- `pano_cli inspect-image` reports PNG IHDR metadata as JSON and is covered by - `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. `pano_cli_inspect_png_metadata_smoke` with a tiny IHDR fixture.

View File

@@ -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, `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 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 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 `pp_document` has
started with a pure canvas/layer/frame model, alpha-lock metadata, snapshot started with a pure canvas/layer/frame model, alpha-lock metadata, snapshot
construction, per-layer frame metadata, layer metadata operations, frame construction, per-layer frame metadata, layer metadata operations, frame

View File

@@ -3,6 +3,7 @@
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <limits>
using pp::foundation::StatusCode; using pp::foundation::StatusCode;
using pp::paint::StrokePoint; 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)); 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) void rejects_invalid_sampling_inputs(pp::tests::Harness& h)
{ {
const std::array one_point { 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 = 0.0F, .y = 0.0F },
StrokePoint { .x = std::nanf(""), .y = 1.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 { const std::array valid {
StrokePoint { .x = 0.0F, .y = 0.0F }, StrokePoint { .x = 0.0F, .y = 0.0F },
StrokePoint { .x = 10.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 missing_points = sample_stroke(one_point, StrokeSamplingConfig {});
const auto bad_spacing = sample_stroke(valid, StrokeSamplingConfig { .spacing = 0.0F }); const auto bad_spacing = sample_stroke(valid, StrokeSamplingConfig { .spacing = 0.0F });
const auto infinite_spacing = sample_stroke(
valid,
StrokeSamplingConfig { .spacing = std::numeric_limits<float>::infinity() });
const auto bad_limit = sample_stroke(valid, StrokeSamplingConfig { .max_samples = max_stroke_samples + 1U }); 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 no_distance = sample_stroke(zero_length, StrokeSamplingConfig {});
const auto bad_point = sample_stroke(non_finite, 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 }); 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.ok());
PP_EXPECT(h, missing_points.status().code == StatusCode::invalid_argument); PP_EXPECT(h, missing_points.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !bad_spacing.ok()); PP_EXPECT(h, !bad_spacing.ok());
PP_EXPECT(h, bad_spacing.status().code == StatusCode::invalid_argument); 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.ok());
PP_EXPECT(h, bad_limit.status().code == StatusCode::out_of_range); PP_EXPECT(h, bad_limit.status().code == StatusCode::out_of_range);
PP_EXPECT(h, !no_distance.ok()); PP_EXPECT(h, !no_distance.ok());
PP_EXPECT(h, no_distance.status().code == StatusCode::invalid_argument); PP_EXPECT(h, no_distance.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !bad_point.ok()); PP_EXPECT(h, !bad_point.ok());
PP_EXPECT(h, bad_point.status().code == StatusCode::invalid_argument); 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.ok());
PP_EXPECT(h, too_many.status().code == StatusCode::out_of_range); 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("samples_straight_line_at_fixed_spacing", samples_straight_line_at_fixed_spacing);
harness.run("carries_spacing_across_segments", carries_spacing_across_segments); 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("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); harness.run("rejects_invalid_sampling_inputs", rejects_invalid_sampling_inputs);
return harness.finish(); return harness.finish();
} }