Skip to content
Prev Previous commit
Next Next commit
Fix precision loss with Agg compositing
  • Loading branch information
ayshih committed Aug 26, 2026
commit 2ccbbd3a81185a77987cbe78471d05dfb2ac8838
2 changes: 1 addition & 1 deletion src/_backend_agg.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class RendererAgg
{
public:

typedef agg::comp_op_adaptor_rgba_plain<agg::rgba8, agg::order_rgba> comp_op_blender_plain;
typedef fixed_comp_op_adaptor_rgba8_plain<agg::order_rgba> comp_op_blender_plain;
typedef agg::pixfmt_custom_blend_rgba<comp_op_blender_plain, agg::rendering_buffer> pixfmt;
typedef agg::renderer_base<pixfmt> renderer_base;
typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_aa;
Expand Down
63 changes: 62 additions & 1 deletion src/agg_workaround.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

/**********************************************************************
WORKAROUND: This class is to workaround a bug in Agg SVN where the
blending of RGBA32 pixels does not preserve enough precision
blending of RGBA8 pixels does not preserve enough precision
*/

template<class ColorT, class Order>
Expand All @@ -31,6 +31,9 @@ struct fixed_blender_rgba_plain : agg::conv_rgba_plain<ColorT, Order>
value_type cr, value_type cg, value_type cb, value_type alpha)
{
if(alpha == 0) return;

// The following code does not accurately transform to higher bit depth
// TODO: Improve the accuracy of this code when we are prepared to regenerate all baseline images
Comment thread
story645 marked this conversation as resolved.
calc_type a = p[Order::A];
calc_type r = p[Order::R] * a;
calc_type g = p[Order::G] * a;
Expand All @@ -44,6 +47,64 @@ struct fixed_blender_rgba_plain : agg::conv_rgba_plain<ColorT, Order>
};


/**********************************************************************
WORKAROUND: As above, this class is to workaround a bug in Agg where
the plain->plain blending of RGBA8 pixels is forced back to 8-bit
values between steps, which degrades the calculation. This class
supports compositing operators (aka blend modes), and falls back to
the above class for normal alpha blending.
*/

template<class Order>
struct fixed_comp_op_adaptor_rgba8_plain
{
typedef agg::rgba8 color_type;
typedef Order order_type;
typedef typename color_type::value_type value_type;
typedef typename color_type::calc_type calc_type;
typedef typename color_type::long_type long_type;

static AGG_INLINE void blend_pix(unsigned op, value_type* p,
value_type r, value_type g, value_type b, value_type a, agg::cover_type cover)
{
if(op == agg::comp_op_src_over)
{
// This operator is simply normal alpha blending, so fall back to the above class
// We need to special-case fully opaque to maintain past behavior
if(a == 255 && cover == agg::cover_full) {
p[Order::A] = a;
p[Order::R] = r;
p[Order::G] = g;
p[Order::B] = b;
return;
}
fixed_blender_rgba_plain<agg::rgba8, Order>::blend_pix(p, r, g, b, a, cover);
}
else
{
if(a == 0 || cover == 0) return;

// Convert to RGBA32 (floats) and premultiply
float d[4];
for(int i = 0; i < 4; i++) d[i] = agg::rgba8::to_double(p[i]);
agg::multiplier_rgba<agg::rgba32, Order>::premultiply(d);

double sa = agg::rgba8::to_double(a);
double sr = agg::rgba8::to_double(r) * sa;
double sg = agg::rgba8::to_double(g) * sa;
double sb = agg::rgba8::to_double(b) * sa;

// Blend as floats (internally, doubles)
agg::comp_op_table_rgba<agg::rgba32, Order>::g_comp_op_func[op](d, sr, sg, sb, sa, cover);

// Demultiply and return to RGBA8 space
agg::multiplier_rgba<agg::rgba32, Order>::demultiply(d);
for(int i = 0; i < 4; i++) p[i] = agg::rgba8::from_double(d[i]);
}
}
};


/**********************************************************************
This class provides higher-accuracy nearest-neighbor interpolation for
affine transforms than span_interpolator_linear by using
Expand Down