diff --git a/graf2d/asimage/CMakeLists.txt b/graf2d/asimage/CMakeLists.txt index 3b5188c126edb..870cceea03d37 100644 --- a/graf2d/asimage/CMakeLists.txt +++ b/graf2d/asimage/CMakeLists.txt @@ -68,3 +68,5 @@ if (x11) endif() ROOT_INSTALL_HEADERS() + +ROOT_ADD_TEST_SUBDIRECTORY(test) diff --git a/graf2d/asimage/src/libAfterImage/draw.c b/graf2d/asimage/src/libAfterImage/draw.c index b1606bd1c3fbe..e4d10bb6fa2b8 100644 --- a/graf2d/asimage/src/libAfterImage/draw.c +++ b/graf2d/asimage/src/libAfterImage/draw.c @@ -340,7 +340,7 @@ apply_tool_point_colored(ASDrawContext *ctx, int curr_x, int curr_y, CARD32 rati dst += curr_y * cw + curr_x; if (get_flags(ctx->flags, ASDrawCTX_UsingScratch)) { - CARD32 value = (ARGB32_ALPHA8(ctx->tool->matrix[0])*ratio)/255 ; + CARD32 value = ratio ; /* coverage only; alpha applied on merge */ if( *dst < value ) *dst = value ; } @@ -391,7 +391,7 @@ fill_hline_notile_colored(ASDrawContext *ctx, int x_from, int y, int x_to, CARD3 { while( x1 <= x2 ) { - CARD32 value = (ARGB32_ALPHA8(ctx->tool->matrix[0])*ratio)/255 ; + CARD32 value = ratio ; /* coverage only; alpha applied on merge */ if( dst[x1] < value ) dst[x1] = value ; ++x1 ; diff --git a/graf2d/asimage/test/CMakeLists.txt b/graf2d/asimage/test/CMakeLists.txt new file mode 100644 index 0000000000000..c08327d3e21db --- /dev/null +++ b/graf2d/asimage/test/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +ROOT_ADD_GTEST(TASImageDraw tasimage_draw.cxx LIBRARIES ASImage) diff --git a/graf2d/asimage/test/tasimage_draw.cxx b/graf2d/asimage/test/tasimage_draw.cxx new file mode 100644 index 0000000000000..bc8b942e2fb5c --- /dev/null +++ b/graf2d/asimage/test/tasimage_draw.cxx @@ -0,0 +1,68 @@ +#include "gtest/gtest.h" + +#include "TASImage.h" + +namespace { + +constexpr UInt_t kSize = 64; + +constexpr UInt_t kPixels = kSize * kSize; + +// Index of the four canvas corners. +constexpr UInt_t kCorners[4] = {0, kSize - 1, (kSize - 1) * kSize, kPixels - 1}; + +// Draw a filled circle of `colour` well inside a kSize x kSize image and check +// that the fill stayed inside it. The corner values are compared against what +// they were before drawing rather than against a constant, so the test does not +// depend on how a fresh TASImage is initialised. +void CheckFilledCircleStaysInside(const char *colour) +{ + TASImage img(kSize, kSize); + + UInt_t *argb = img.GetArgbArray(); + ASSERT_NE(argb, nullptr); + + UInt_t before[4]; + for (int i = 0; i < 4; ++i) + before[i] = argb[kCorners[i]]; + const UInt_t centre = (kSize / 2) * kSize + kSize / 2; + const UInt_t centreBefore = argb[centre]; + + img.DrawCircle(kSize / 2, kSize / 2, kSize / 4, colour, -1); + + argb = img.GetArgbArray(); + ASSERT_NE(argb, nullptr); + + for (int i = 0; i < 4; ++i) + EXPECT_EQ(argb[kCorners[i]], before[i]) << "the fill escaped the circle and reached corner " << i; + + EXPECT_NE(argb[centre], centreBefore) << "the circle was not filled at all"; +} + +} // namespace + +// https://github.com/root-project/root/issues/23014 +// +// libAfterImage scaled the coverage it wrote into the scratch canvas by the +// brush alpha, which made the flood fill that closes a filled shape depend on +// that alpha. Two symptoms followed, and this geometry shows both: at an alpha +// of 0x8C or below the fill never terminated, and between 0x8D and 0xFE it +// returned but leaked through the anti-aliased outline and covered the whole +// image. Only a fully opaque brush behaved correctly. + +TEST(TASImage, FilledCircleOpaque) +{ + CheckFilledCircleStaysInside("#FFFF0000"); +} + +// Used to leak out of the circle and fill the whole image. +TEST(TASImage, FilledCircleHighAlpha) +{ + CheckFilledCircleStaysInside("#C0FF0000"); +} + +// Used to hang: the colour from the issue report. +TEST(TASImage, FilledCircleSemiTransparent) +{ + CheckFilledCircleStaysInside("#7FFF0000"); +} diff --git a/test/stressGraphics.ref b/test/stressGraphics.ref index 38df8ed5a50e5..1416b0ac19ec0 100644 --- a/test/stressGraphics.ref +++ b/test/stressGraphics.ref @@ -49,7 +49,7 @@ waves 3303861 100 712368 3000 62688 19000 123056 7000 3396789 1000 tf12 3954 100 14714 300 17378 7800 9629 2000 3794 200 tspline 9073 100 22210 300 27691 7800 20712 2000 9157 200 - tscatter 15595 200 22620 400 28599 7800 42618 2000 15460 200 + tscatter 15595 200 22620 400 28599 7800 44900 2000 15460 200 tefficiency 12926 200 24364 300 24021 6800 12637 2000 12584 200 profile_2d 29664 200 19414 300 23024 6500 12821 2000 31317 200 profile_2dpoly 26463 400 23542 300 38792 8500 30458 4000 26759 400 diff --git a/test/stressGraphics_zlibng.ref b/test/stressGraphics_zlibng.ref index b7ca1568c66ee..f8d586158d9ff 100644 --- a/test/stressGraphics_zlibng.ref +++ b/test/stressGraphics_zlibng.ref @@ -49,7 +49,7 @@ waves 3303861 100 709707 3000 62761 19000 122867 7000 3396789 1000 tf12 3954 100 14714 300 17378 7800 9629 2000 3794 200 tspline 9073 100 22210 300 27691 7800 20712 2000 9157 200 - tscatter 15595 200 22420 400 28599 7800 42618 2000 15460 200 + tscatter 15595 200 22420 400 28599 7800 44760 2000 15460 200 tefficiency 12926 200 24364 300 24021 6800 12637 2000 12584 200 profile_2d 29664 200 19414 300 23024 6500 12821 2000 31317 200 profile_2dpoly 26463 400 23542 300 38792 8500 30458 4000 26759 400