From ce560f105e551318bc0f6ed281801ee814d97abb Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 3 May 2026 16:36:12 -0500 Subject: [PATCH] Cap Mask contrast factor at maximum contrast -Raise the Mask contrast denominator floor from 0.00001 to 0.5, capping the contrast factor at 40. This prevents contrast values near 20 from turning the 8-bit gray lookup into a one-value step function, where tiny mask image differences can create hard transparent edge artifacts during gradient wipes. -Add a regression test covering the maximum contrast transition band. --- src/effects/Mask.cpp | 2 +- tests/Mask.cpp | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index 0b8f7f9c5..51f15685a 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -75,7 +75,7 @@ std::shared_ptr Mask::GetFrame(std::shared_ptr double brightness_value = brightness.GetValue(frame_number); int brightness_adj = static_cast(255 * brightness_value); - float contrast_factor = 20.0f / std::max(0.00001f, 20.0f - static_cast(contrast_value)); + float contrast_factor = 20.0f / std::max(0.5f, 20.0f - static_cast(contrast_value)); const bool output_mask = replace_image; const auto clamp_u8 = [](int value) -> unsigned char { if (value < 0) return 0; diff --git a/tests/Mask.cpp b/tests/Mask.cpp index b8078bd53..cc9de8639 100644 --- a/tests/Mask.cpp +++ b/tests/Mask.cpp @@ -103,6 +103,26 @@ TEST_CASE("Mask replace_image emits grayscale values", "[effect][mask_effect][re CHECK(px1.alpha() == px1.red()); } +TEST_CASE("Mask maximum contrast keeps a narrow transition band", "[effect][mask_effect][contrast]") { + auto frame = std::make_shared(1, 3, 1, "#000000"); + auto image = frame->GetImage(); + image->setPixelColor(0, 0, QColor(255, 0, 0, 255)); + image->setPixelColor(1, 0, QColor(255, 0, 0, 255)); + image->setPixelColor(2, 0, QColor(255, 0, 0, 255)); + + const std::string mask_path = create_mask_png({128, 129, 134}); + Mask mask; + mask.Reader(new QtImageReader(mask_path)); + mask.brightness = Keyframe(0.0); + mask.contrast = Keyframe(20.0); + + auto out = mask.GetFrame(frame, 1); + + CHECK(out->GetImage()->pixelColor(0, 0).alpha() == 127); + CHECK(out->GetImage()->pixelColor(1, 0).alpha() == 87); + CHECK(out->GetImage()->pixelColor(2, 0).alpha() == 0); +} + TEST_CASE("Mask accepts legacy reader json field", "[effect][mask_effect][json]") { const std::string mask_path = create_mask_png({128}); QtImageReader reader(mask_path);