From 5e84ce0dfcacefe7ea432a8b4baa179f642999a1 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 31 Aug 2012 18:04:08 -0700 Subject: [PATCH] agg compositing: change src_over alpha to avoid pixel artifacts by reordering computations and add basic tests comparing src_over composting to normal agg alpha blending - closes #1452 - refs #1313, #1454, #1369 --- deps/agg/include/agg_pixfmt_rgba.h | 2 +- tests/cpp_tests/agg_blend_src_over_test.cpp | 202 ++++++++++++++++++++ 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 tests/cpp_tests/agg_blend_src_over_test.cpp diff --git a/deps/agg/include/agg_pixfmt_rgba.h b/deps/agg/include/agg_pixfmt_rgba.h index e0d3982ff..6fb194091 100644 --- a/deps/agg/include/agg_pixfmt_rgba.h +++ b/deps/agg/include/agg_pixfmt_rgba.h @@ -329,7 +329,7 @@ namespace agg p[Order::R] = (value_type)(sr + ((p[Order::R] * s1a + base_mask) >> base_shift)); p[Order::G] = (value_type)(sg + ((p[Order::G] * s1a + base_mask) >> base_shift)); p[Order::B] = (value_type)(sb + ((p[Order::B] * s1a + base_mask) >> base_shift)); - p[Order::A] = (value_type)(sa + p[Order::A] - ((sa * p[Order::A] + base_mask) >> base_shift)); + p[Order::A] = (value_type)(sa + ((p[Order::A] * s1a + base_mask) >> base_shift)); } }; diff --git a/tests/cpp_tests/agg_blend_src_over_test.cpp b/tests/cpp_tests/agg_blend_src_over_test.cpp new file mode 100644 index 000000000..9598b8cb5 --- /dev/null +++ b/tests/cpp_tests/agg_blend_src_over_test.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include +#include "agg_color_rgba.h" +#include "agg_pixfmt_rgba.h" +#include "agg_rendering_buffer.h" +#include "agg_renderer_base.h" + +typedef agg::rgba8 color; +typedef agg::order_rgba order; + +std::string to_string(color const& c) +{ + std::ostringstream s; + s << "rgba(" << (unsigned)c.r << "," << (unsigned)c.g << "," << (unsigned)c.b << "," << (unsigned)c.a << ")"; + return s.str(); +} + +template +color blend(color const& source, color const& dest, unsigned cover=255) +{ + unsigned stride = 4; + unsigned size = 1; + + color source_pre = source; + source_pre.premultiply(); + color dest_pre = dest; + dest_pre.premultiply(); + + unsigned char* buffer = new unsigned char[size*size*stride]; + memset(buffer, 0, size*size*stride); + buffer[0] = dest_pre.r; + buffer[1] = dest_pre.g; + buffer[2] = dest_pre.b; + buffer[3] = dest_pre.a; + // http://www.antigrain.com/doc/basic_renderers/basic_renderers.agdoc.html + agg::rendering_buffer rbuf(buffer, + size, + size, + size * stride); + color::value_type* psource = (color::value_type*)rbuf.row_ptr(0,0,1); + blender::blend_pix(psource,source_pre.r,source_pre.g,source_pre.b,source_pre.a,cover); + color color_result(psource[0],psource[1],psource[2],psource[3]); + color_result.demultiply(); + delete [] buffer; + return color_result; +} + +// agg::pixfmt_alpha_blend_rgba +color normal_blend(color const& source, color const& dest, unsigned cover=255) +{ + typedef agg::renderer_base renderer_type; + unsigned stride = 4; + unsigned size = 1; + color source_pre = source; + source_pre.premultiply(); + color dest_pre = dest; + dest_pre.premultiply(); + // source buffer + unsigned char* source_buffer = new unsigned char[size*size*stride]; + memset(source_buffer, 0, size*size*stride); + source_buffer[0] = source_pre.r; + source_buffer[1] = source_pre.g; + source_buffer[2] = source_pre.b; + source_buffer[3] = source_pre.a; + agg::rendering_buffer source_rbuffer(source_buffer,size,size,size * 4); + agg::pixfmt_rgba32_pre pixf_source(source_rbuffer); + + // destination buffer + unsigned char* dest_buffer = new unsigned char[size*size*stride]; + memset(dest_buffer, 0, size*size*stride); + dest_buffer[0] = dest_pre.r; + dest_buffer[1] = dest_pre.g; + dest_buffer[2] = dest_pre.b; + dest_buffer[3] = dest_pre.a; + agg::rendering_buffer dest_rbuffer(dest_buffer,size,size,size * 4); + agg::pixfmt_rgba32_pre pixf_dest(dest_rbuffer); + + // renderer: blends source into destination + renderer_type ren(pixf_dest); + ren.blend_from(pixf_source,0,0,0,cover); + color color_result(dest_buffer[0],dest_buffer[1],dest_buffer[2],dest_buffer[3]); + color_result.demultiply(); + delete [] source_buffer; + delete [] dest_buffer; + return color_result; +} + + + +namespace agg { + +// the original agg template code for src_over +// before we changed A as per https://github.com/mapnik/mapnik/issues/1452 +template struct comp_op_rgba_src_over2 +{ + typedef ColorT color_type; + typedef Order order_type; + typedef typename color_type::value_type value_type; + typedef typename color_type::calc_type calc_type; + enum base_scale_e + { + base_shift = color_type::base_shift, + base_mask = color_type::base_mask + }; + + // Dca' = Sca + Dca.(1 - Sa) + // Da' = Sa + Da - Sa.Da + static void blend_pix(value_type* p, + unsigned sr, unsigned sg, unsigned sb, + unsigned sa, unsigned cover) + { + if(cover < 255) + { + sr = (sr * cover + 255) >> 8; + sg = (sg * cover + 255) >> 8; + sb = (sb * cover + 255) >> 8; + sa = (sa * cover + 255) >> 8; + } + calc_type s1a = base_mask - sa; + p[Order::R] = (value_type)(sr + ((p[Order::R] * s1a + base_mask) >> base_shift)); + p[Order::G] = (value_type)(sg + ((p[Order::G] * s1a + base_mask) >> base_shift)); + p[Order::B] = (value_type)(sb + ((p[Order::B] * s1a + base_mask) >> base_shift)); + p[Order::A] = (value_type)(sa + p[Order::A] - ((sa * p[Order::A] + base_mask) >> base_shift)); + } +}; + +} + +int main( int, char*[] ) +{ + typedef agg::comp_op_rgba_src_over2 source_over_old_agg; + typedef agg::comp_op_rgba_src_over source_over; + + color white(255,255,255,255); + color black(0,0,0,255); + + BOOST_TEST_EQ( to_string(blend(white,white)), to_string(white) ); + BOOST_TEST_EQ( to_string(blend(white,black)), to_string(white) ); + BOOST_TEST_EQ( to_string(blend(black,white)), to_string(black) ); + + // https://github.com/mapnik/mapnik/issues/1452#issuecomment-8154646 + color near_white(254,254,254,254); // Source + color near_trans(1,1,1,1); // Dest + color expected(252,252,252,255); // expected result + BOOST_TEST_EQ( to_string(blend(near_white,near_trans)), to_string(color(252,252,252,254)) ); + BOOST_TEST_EQ( to_string(blend(near_white,near_trans)), to_string(expected) ); + BOOST_TEST_EQ( to_string(normal_blend(near_white,near_trans)), to_string(expected) ); + + // using normal_blend as expected, compare a variety of other colors + + { + color source(128,128,128,255); + color dest(128,128,128,255); + unsigned cover = 128; + std::string expected = to_string(normal_blend(source,dest,cover)); + BOOST_TEST_EQ( to_string(blend(source,dest,cover)), expected ); + BOOST_TEST_EQ( to_string(blend(source,dest,cover)), expected ); + } + + { + color source(128,128,128,255); + color dest(128,128,128,255); + unsigned cover = 245; + std::string expected = to_string(normal_blend(source,dest,cover)); + BOOST_TEST_EQ( to_string(blend(source,dest,cover)), expected ); + BOOST_TEST_EQ( to_string(blend(source,dest,cover)), expected ); + } + + { + // fails, why? + color source(127,127,127,127); + color dest(127,127,127,127); + unsigned cover = 255; + std::string expected = to_string(normal_blend(source,dest,cover)); + BOOST_TEST_EQ( to_string(blend(source,dest,cover)), expected ); + BOOST_TEST_EQ( to_string(blend(source,dest,cover)), expected ); + } + + { + // fails, why? + color source(128,128,128,128); + color dest(128,128,128,128); + unsigned cover = 128; + std::string expected = to_string(normal_blend(source,dest,cover)); + BOOST_TEST_EQ( to_string(blend(source,dest,cover)), expected ); + BOOST_TEST_EQ( to_string(blend(source,dest,cover)), expected ); + } + + if (!::boost::detail::test_errors()) { + std::clog << "C++ AGG blending: \x1b[1;32m✓ \x1b[0m\n"; +#if BOOST_VERSION >= 104600 + ::boost::detail::report_errors_remind().called_report_errors_function = true; +#endif + } else { + std::clog << "C++ AGG blending: "; + return ::boost::report_errors(); + } +}