LibGfx: Match vImage premultiply/unpremultiply rounding behavior

Our Color::to_premultiplied() and Color::to_unpremultiplied() used
integer truncation.

Apple’s Accelerate framework (and many other libraries) use
round-to-nearest, which avoids bias and produces results that differ
by ±1 in many cases.

This commit switches both helpers to round-to-nearest and clamps the
results to [0,255]. For alpha==0 we now return fully transparent black
(0,0,0,0) to align with common conventions, instead of preserving RGB.
This commit is contained in:
Andreas Kling 2025-08-23 13:44:28 +02:00 committed by Jelle Raaijmakers
commit 67432e35f1
Notes: github-actions[bot] 2025-08-23 12:10:23 +00:00
2 changed files with 13 additions and 11 deletions

View file

@ -317,22 +317,24 @@ public:
constexpr Color to_premultiplied() const constexpr Color to_premultiplied() const
{ {
return Color( u32 a = alpha();
red() * alpha() / 255, u8 r = static_cast<u8>((red() * a + 127) / 255);
green() * alpha() / 255, u8 g = static_cast<u8>((green() * a + 127) / 255);
blue() * alpha() / 255, u8 b = static_cast<u8>((blue() * a + 127) / 255);
alpha()); return Color(r, g, b, a);
} }
constexpr Color to_unpremultiplied() const constexpr Color to_unpremultiplied() const
{ {
if (alpha() == 0 || alpha() == 255) u32 a = alpha();
if (a == 0)
return Color(0, 0, 0, 0);
if (a == 255)
return *this; return *this;
return Color( u8 r = static_cast<u8>(min(255u, (red() * 255u + a / 2) / a));
red() * 255 / alpha(), u8 g = static_cast<u8>(min(255u, (green() * 255u + a / 2) / a));
green() * 255 / alpha(), u8 b = static_cast<u8>(min(255u, (blue() * 255u + a / 2) / a));
blue() * 255 / alpha(), return Color(r, g, b, a);
alpha());
} }
constexpr Color blend(Color source) const constexpr Color blend(Color source) const

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before After
Before After