Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions builtins/libAfterImage/draw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;
}
Expand Down Expand Up @@ -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 ;
Expand Down
2 changes: 2 additions & 0 deletions graf2d/asimage/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ASImageGui
)

ROOT_INSTALL_HEADERS()

ROOT_ADD_TEST_SUBDIRECTORY(test)
7 changes: 7 additions & 0 deletions graf2d/asimage/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
68 changes: 68 additions & 0 deletions graf2d/asimage/test/tasimage_draw.cxx
Original file line number Diff line number Diff line change
@@ -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");
}
Loading