diff --git a/bindings/python/mapnik_image.cpp b/bindings/python/mapnik_image.cpp index e9934252a..375e47326 100644 --- a/bindings/python/mapnik_image.cpp +++ b/bindings/python/mapnik_image.cpp @@ -176,6 +176,7 @@ boost::shared_ptr from_cairo(PycairoSurface* surface) void export_image() { using namespace boost::python; + // NOTE: must match list in include/mapnik/image_compositing.hpp enum_("CompositeOp") .value("clear", mapnik::clear) .value("src", mapnik::src) @@ -204,7 +205,12 @@ void export_image() .value("exclusion", mapnik::exclusion) .value("contrast", mapnik::contrast) .value("invert", mapnik::invert) - .value("invert_rgb", mapnik::invert_rgb) + .value("grain_merge", mapnik::grain_merge) + .value("grain_extract", mapnik::grain_extract) + .value("hue", mapnik::hue) + .value("saturation", mapnik::saturation) + .value("color", mapnik::_color) + .value("value", mapnik::_value) ; class_ >("Image","This class represents a 32 bit RGBA image.",init()) diff --git a/bindings/python/mapnik_raster_symbolizer.cpp b/bindings/python/mapnik_raster_symbolizer.cpp index 69e0acca8..54f66a554 100644 --- a/bindings/python/mapnik_raster_symbolizer.cpp +++ b/bindings/python/mapnik_raster_symbolizer.cpp @@ -28,6 +28,18 @@ #include #include +namespace { + +// https://github.com/mapnik/mapnik/issues/1367 +PyObject* get_premultiplied_impl(mapnik::raster_symbolizer & sym) +{ + boost::optional premultiplied = sym.premultiplied(); + if (premultiplied) + return ::PyBool_FromLong(*premultiplied); + Py_RETURN_NONE; +} + +} using mapnik::raster_symbolizer; void export_raster_symbolizer() @@ -119,5 +131,17 @@ void export_raster_symbolizer() ">>> r = RasterSymbolizer()\n" ">>> r.mesh_size = 32\n" ) + .add_property("premultiplied", + &get_premultiplied_impl, + &raster_symbolizer::set_premultiplied, + "Get/Set premultiplied status of the source image.\n" + "Can be used to override what the source data reports (when in error)\n" + "\n" + "Usage:\n" + "\n" + ">>> from mapnik import RasterSymbolizer\n" + ">>> r = RasterSymbolizer()\n" + ">>> r.premultiplied = False\n" + ) ; } diff --git a/boost/gil/extension/toolbox/hsv.hpp b/boost/gil/extension/toolbox/hsv.hpp index afaf1ff8a..1eb2e335e 100644 --- a/boost/gil/extension/toolbox/hsv.hpp +++ b/boost/gil/extension/toolbox/hsv.hpp @@ -60,9 +60,16 @@ struct default_color_converter_impl< rgb_t, hsv_t > bits32f temp_blue = channel_convert( get_color( src, blue_t() )); bits32f hue, saturation, value; + bits32f min_color, max_color; - bits32f min_color = std::min(temp_red,std::min(temp_green, temp_blue)); - bits32f max_color = std::max(temp_red,std::max(temp_green, temp_blue)); + if( temp_red < temp_green ) { + min_color = std::min( temp_blue, temp_red ); + max_color = std::max( temp_blue, temp_green ); + } + else { + min_color = std::min( temp_blue, temp_green ); + max_color = std::max( temp_blue, temp_red ); + } value = max_color; @@ -85,7 +92,7 @@ struct default_color_converter_impl< rgb_t, hsv_t > } else { - if( std::abs( boost::numeric_cast(temp_red - max_color) ) < 0.0001f ) + if( temp_red == max_color ) { hue = ( temp_green - temp_blue ) / diff; @@ -149,18 +156,23 @@ struct default_color_converter_impl frac = h - i; + // p = value * (1 - saturation) p = get_color( src, value_t() ) * ( 1.f - get_color( src, saturation_t() )); + // q = value * (1 - saturation * hue_frac) + // it drops with increasing distance from floor(hue) q = get_color( src, value_t() ) * ( 1.f - ( get_color( src, saturation_t() ) * frac )); + // t = value * (1 - (saturation * (1 - hue_frac)) + // it grows with increasing distance from floor(hue) t = get_color( src, value_t() ) * ( 1.f - ( get_color( src, saturation_t() ) * ( 1.f - frac ))); - switch( i ) + switch( i % 6 ) { - case 0: + case 0: // red to yellow { red = get_color( src, value_t() ); green = t; @@ -169,7 +181,7 @@ struct default_color_converter_impl break; } - case 1: + case 1: // yellow to green { red = q; green = get_color( src, value_t() ); @@ -178,7 +190,7 @@ struct default_color_converter_impl break; } - case 2: + case 2: // green to cyan { red = p; green = get_color( src, value_t() ); @@ -187,7 +199,7 @@ struct default_color_converter_impl break; } - case 3: + case 3: // cyan to blue { red = p; green = q; @@ -196,7 +208,7 @@ struct default_color_converter_impl break; } - case 4: + case 4: // blue to magenta { red = t; green = p; @@ -205,7 +217,7 @@ struct default_color_converter_impl break; } - case 5: + case 5: // magenta to red { red = get_color( src, value_t() ); green = p; diff --git a/include/mapnik/raster_symbolizer.hpp b/include/mapnik/raster_symbolizer.hpp index 58ea11ab9..657c3fbd2 100644 --- a/include/mapnik/raster_symbolizer.hpp +++ b/include/mapnik/raster_symbolizer.hpp @@ -30,6 +30,7 @@ // boost #include +#include namespace mapnik { @@ -52,6 +53,8 @@ struct MAPNIK_DECL raster_symbolizer : public symbolizer_base double calculate_filter_factor() const; unsigned get_mesh_size() const; void set_mesh_size(unsigned mesh_size); + boost::optional premultiplied() const; + void set_premultiplied(bool premultiplied); private: std::string mode_; @@ -60,6 +63,7 @@ private: raster_colorizer_ptr colorizer_; double filter_factor_; unsigned mesh_size_; + boost::optional premultiplied_; }; } diff --git a/plugins/input/gdal/gdal_featureset.cpp b/plugins/input/gdal/gdal_featureset.cpp index d37132961..3beaa60a9 100644 --- a/plugins/input/gdal/gdal_featureset.cpp +++ b/plugins/input/gdal/gdal_featureset.cpp @@ -489,7 +489,7 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) image.width(), image.height(), GDT_Byte, 4, 4 * image.width()); } - feature->set_raster(mapnik::raster_ptr(new mapnik::raster(intersect, image))); + feature->set_raster(boost::make_shared(intersect, image)); } return feature; } diff --git a/src/agg/process_raster_symbolizer.cpp b/src/agg/process_raster_symbolizer.cpp index d0a701823..b8287208e 100644 --- a/src/agg/process_raster_symbolizer.cpp +++ b/src/agg/process_raster_symbolizer.cpp @@ -98,9 +98,20 @@ void agg_renderer::process(raster_symbolizer const& sym, filter_radius); } } + // handle whether to premultiply the source + // data before compositing + // first, default to what the data reports + bool premultiply_source = !source->premultiplied_alpha_; + // the, allow the user to override + boost::optional is_premultiplied = sym.premultiplied(); + if (is_premultiplied) + { + if (*is_premultiplied) premultiply_source = false; + else premultiply_source = true; + } composite(current_buffer_->data(), target.data_, sym.comp_op(), sym.get_opacity(), - start_x, start_y, !source->premultiplied_alpha_); + start_x, start_y, premultiply_source); } } } diff --git a/src/build.py b/src/build.py index a02b4a041..2b3e5d5c9 100644 --- a/src/build.py +++ b/src/build.py @@ -345,6 +345,8 @@ else: env['LIBMAPNIK_LIBS'] = copy(lib_env['LIBS']) env['LIBMAPNIK_CXXFLAGS'] = libmapnik_cxxflags +mapnik = None + if env['PLATFORM'] == 'Darwin': target_path = env['MAPNIK_LIB_BASE_DEST'] if 'uninstall' not in COMMAND_LINE_TARGETS: @@ -395,3 +397,5 @@ else: env['create_uninstall_target'](env, target2) env['create_uninstall_target'](env, target1) env['create_uninstall_target'](env, target) + +Depends(mapnik, env.subst('../deps/agg/libagg.a')) diff --git a/src/load_map.cpp b/src/load_map.cpp index 8320faaf8..afc124a16 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1254,7 +1254,7 @@ void map_parser::parse_shield_symbolizer(rule & rule, xml_node const& sym) sym.get_opt_attr("no-text"); if (no_text) { - MAPNIK_LOG_ERROR(raster_symbolizer) << "'no-text' is deprecated and will be removed in Mapnik 3.x, to create a ShieldSymbolizer without text just provide an element like: \"' '\""; + MAPNIK_LOG_ERROR(shield_symbolizer) << "'no-text' is deprecated and will be removed in Mapnik 3.x, to create a ShieldSymbolizer without text just provide an element like: \"' '\""; if (*no_text) shield_symbol.set_name(parse_expression("' '")); } @@ -1491,6 +1491,9 @@ void map_parser::parse_raster_symbolizer(rule & rule, xml_node const & sym) optional mesh_size = sym.get_opt_attr("mesh-size"); if (mesh_size) raster_sym.set_mesh_size(*mesh_size); + // premultiplied status of image + optional premultiplied = sym.get_opt_attr("premultiplied"); + if (premultiplied) raster_sym.set_premultiplied(*premultiplied); xml_node::const_iterator cssIter = sym.begin(); xml_node::const_iterator endCss = sym.end(); diff --git a/src/raster_symbolizer.cpp b/src/raster_symbolizer.cpp index 6b3b981ef..cfdf4c632 100644 --- a/src/raster_symbolizer.cpp +++ b/src/raster_symbolizer.cpp @@ -55,7 +55,8 @@ raster_symbolizer::raster_symbolizer(raster_symbolizer const& rhs) opacity_(rhs.opacity_), colorizer_(rhs.colorizer_), filter_factor_(rhs.filter_factor_), - mesh_size_(rhs.mesh_size_) {} + mesh_size_(rhs.mesh_size_), + premultiplied_(rhs.premultiplied_) {} std::string const& raster_symbolizer::get_mode() const { @@ -177,6 +178,15 @@ void raster_symbolizer::set_mesh_size(unsigned mesh_size) mesh_size_=mesh_size; } +void raster_symbolizer::set_premultiplied(bool premultiplied) +{ + premultiplied_=premultiplied; +} + +boost::optional raster_symbolizer::premultiplied() const +{ + return premultiplied_; +} } diff --git a/src/save_map.cpp b/src/save_map.cpp index 30dbb2d7c..e36a6136e 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -180,10 +180,18 @@ public: set_attr( sym_node, "mesh-size", sym.get_mesh_size() ); } - if (sym.get_colorizer()) { + if (sym.get_colorizer()) + { serialize_raster_colorizer(sym_node, sym.get_colorizer(), explicit_defaults_); } + + boost::optional premultiplied = sym.premultiplied(); + if (premultiplied) + { + set_attr( sym_node, "premultiplied", *sym.premultiplied()); + } + serialize_symbolizer_base(sym_node, sym); } diff --git a/tests/python_tests/compositing_test.py b/tests/python_tests/compositing_test.py index cef3aa0e0..a6c048d31 100644 --- a/tests/python_tests/compositing_test.py +++ b/tests/python_tests/compositing_test.py @@ -85,6 +85,9 @@ def validate_pixels_are_premultiplied(image): def test_compare_images(): b = mapnik.Image.open('./images/support/b.png') b.premultiply() + num_ops = len(mapnik.CompositeOp.names) + successes = [] + fails = [] for name in mapnik.CompositeOp.names: a = mapnik.Image.open('./images/support/a.png') a.premultiply() @@ -98,9 +101,16 @@ def test_compare_images(): if not validate_pixels_are_not_premultiplied(a): print '%s not validly demultiplied' % (name) a.save(actual) + if not os.path.exists(expected): + print 'generating expected test image: %s' % expected + a.save(expected) expected_im = mapnik.Image.open(expected) # compare them - eq_(a.tostring(),expected_im.tostring(), 'failed comparing actual (%s) and expected(%s)' % (actual,'tests/python_tests/'+ expected)) + if a.tostring() == expected_im.tostring(): + successes.append(name) + else: + fails.append('failed comparing actual (%s) and expected(%s)' % (actual,'tests/python_tests/'+ expected)) + eq_(len(successes),num_ops,'\n'+'\n'.join(fails)) b.demultiply() # b will be slightly modified by pre and then de multiplication rounding errors # TODO - write test to ensure the image is 99% the same. diff --git a/tests/python_tests/images/composited/color.png b/tests/python_tests/images/composited/color.png new file mode 100644 index 000000000..c9f7e79ca Binary files /dev/null and b/tests/python_tests/images/composited/color.png differ diff --git a/tests/python_tests/images/composited/grain_extract.png b/tests/python_tests/images/composited/grain_extract.png new file mode 100644 index 000000000..cfa03e113 Binary files /dev/null and b/tests/python_tests/images/composited/grain_extract.png differ diff --git a/tests/python_tests/images/composited/grain_merge.png b/tests/python_tests/images/composited/grain_merge.png new file mode 100644 index 000000000..78de8b5dc Binary files /dev/null and b/tests/python_tests/images/composited/grain_merge.png differ diff --git a/tests/python_tests/images/composited/hue.png b/tests/python_tests/images/composited/hue.png new file mode 100644 index 000000000..96ed7a6f1 Binary files /dev/null and b/tests/python_tests/images/composited/hue.png differ diff --git a/tests/python_tests/images/composited/saturation.png b/tests/python_tests/images/composited/saturation.png new file mode 100644 index 000000000..52e9d6c95 Binary files /dev/null and b/tests/python_tests/images/composited/saturation.png differ diff --git a/tests/python_tests/images/composited/value.png b/tests/python_tests/images/composited/value.png new file mode 100644 index 000000000..70bcf4eb3 Binary files /dev/null and b/tests/python_tests/images/composited/value.png differ diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index 95b92c4a1..9b67aa362 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -14,6 +14,18 @@ def setup(): # from another directory we need to chdir() os.chdir(execution_path('.')) +def test_raster_symbolizer(): + s = mapnik.RasterSymbolizer() + eq_(s.comp_op,mapnik.CompositeOp.src_over) # note: mode is deprecated + eq_(s.scaling,mapnik.scaling_method.NEAR) + eq_(s.opacity,1.0) + eq_(s.colorizer,None) + eq_(s.filter_factor,-1) + eq_(s.mesh_size,16) + eq_(s.premultiplied,None) + s.premultiplied = True + eq_(s.premultiplied,True) + def test_line_pattern(): s = mapnik.LinePatternSymbolizer(mapnik.PathExpression('../data/images/dummy.png')) eq_(s.filename, '../data/images/dummy.png') diff --git a/tests/visual_tests/images/tiff-opaque-edge-gdal-256-reference.png b/tests/visual_tests/images/tiff-opaque-edge-gdal-256-reference.png index cb90dea1d..682024a6e 100644 Binary files a/tests/visual_tests/images/tiff-opaque-edge-gdal-256-reference.png and b/tests/visual_tests/images/tiff-opaque-edge-gdal-256-reference.png differ diff --git a/tests/visual_tests/images/tiff-opaque-edge-gdal2-600-reference.png b/tests/visual_tests/images/tiff-opaque-edge-gdal2-600-reference.png new file mode 100644 index 000000000..9c5822705 Binary files /dev/null and b/tests/visual_tests/images/tiff-opaque-edge-gdal2-600-reference.png differ diff --git a/tests/visual_tests/images/tiff-opaque-edge-raster-256-reference.png b/tests/visual_tests/images/tiff-opaque-edge-raster-256-reference.png index cb90dea1d..682024a6e 100644 Binary files a/tests/visual_tests/images/tiff-opaque-edge-raster-256-reference.png and b/tests/visual_tests/images/tiff-opaque-edge-raster-256-reference.png differ diff --git a/tests/visual_tests/images/tiff-opaque-edge-raster2-600-reference.png b/tests/visual_tests/images/tiff-opaque-edge-raster2-600-reference.png new file mode 100644 index 000000000..de98ce206 Binary files /dev/null and b/tests/visual_tests/images/tiff-opaque-edge-raster2-600-reference.png differ diff --git a/tests/visual_tests/styles/tiff-alpha-broken-assoc-alpha-gdal.xml b/tests/visual_tests/styles/tiff-alpha-broken-assoc-alpha-gdal.xml index 358a2260b..6dae4a4ef 100644 --- a/tests/visual_tests/styles/tiff-alpha-broken-assoc-alpha-gdal.xml +++ b/tests/visual_tests/styles/tiff-alpha-broken-assoc-alpha-gdal.xml @@ -4,7 +4,7 @@ - + style - + + ../../data/raster/nodata-edge.tif raster -12329035.765216826,4508650.398543958,-12328653.027947057,4508957.346255356 diff --git a/tests/visual_tests/styles/tiff-opaque-edge-gdal.xml b/tests/visual_tests/styles/tiff-opaque-edge-gdal.xml index 5d62e2d8b..b1c3b3b43 100644 --- a/tests/visual_tests/styles/tiff-opaque-edge-gdal.xml +++ b/tests/visual_tests/styles/tiff-opaque-edge-gdal.xml @@ -1,4 +1,4 @@ - + - + test - ../../data/raster/river.tiff + + + ../../data/raster/river_wgs.tiff gdal diff --git a/tests/visual_tests/styles/tiff-opaque-edge-gdal2.xml b/tests/visual_tests/styles/tiff-opaque-edge-gdal2.xml new file mode 100644 index 000000000..124849988 --- /dev/null +++ b/tests/visual_tests/styles/tiff-opaque-edge-gdal2.xml @@ -0,0 +1,17 @@ + + + + testmerc + + + + ../../data/raster/river_merc.tiff + gdal + + + \ No newline at end of file diff --git a/tests/visual_tests/styles/tiff-opaque-edge-raster.xml b/tests/visual_tests/styles/tiff-opaque-edge-raster.xml index ec5519725..c7cbac762 100644 --- a/tests/visual_tests/styles/tiff-opaque-edge-raster.xml +++ b/tests/visual_tests/styles/tiff-opaque-edge-raster.xml @@ -1,4 +1,4 @@ - + - + test - ../../data/raster/river.tiff + ../../data/raster/river_wgs.tiff raster 0,0,256,210 diff --git a/tests/visual_tests/styles/tiff-opaque-edge-raster2.xml b/tests/visual_tests/styles/tiff-opaque-edge-raster2.xml new file mode 100644 index 000000000..948bccfa2 --- /dev/null +++ b/tests/visual_tests/styles/tiff-opaque-edge-raster2.xml @@ -0,0 +1,18 @@ + + + + testmerc + + + + ../../data/raster/river_merc.tiff + -8249238.098993212,-486119.1339340762,-8024337.037783274,-302066.9754826002 + raster + + + \ No newline at end of file diff --git a/tests/visual_tests/test.py b/tests/visual_tests/test.py index 12938df55..e35ed1c78 100755 --- a/tests/visual_tests/test.py +++ b/tests/visual_tests/test.py @@ -60,6 +60,8 @@ files = [ {'name': "tiff-nodata-edge-raster", 'sizes':[(600,400)]}, {'name': "tiff-opaque-edge-gdal", 'sizes':[(256,256)]}, {'name': "tiff-opaque-edge-raster", 'sizes':[(256,256)]}, + {'name': "tiff-opaque-edge-gdal2", 'sizes':[(600,400)]}, + {'name': "tiff-opaque-edge-raster2", 'sizes':[(600,400)]}, {'name': "shieldsymbolizer-2"}, {'name': "shieldsymbolizer-3"}, {'name': "shieldsymbolizer-4"},