From 4184f75011397f24401f31ba5588bf685ee5472e Mon Sep 17 00:00:00 2001 From: Blake Thompson Date: Wed, 14 Jan 2015 12:42:30 -0600 Subject: [PATCH] Moved premultiply and demultiply out of image_32 and other parts of the code. The image_data object is now responsible for keeping track of its own premultiplied_alpha status. Created a new utility method in image_util to preform premultiplication. Added visitor pattern to several different methods as well to prepare for image_data_any including compositing. Ref #2633 --- bindings/python/mapnik_image.cpp | 23 +- include/mapnik/graphics.hpp | 10 - include/mapnik/image_compositing.hpp | 3 +- include/mapnik/image_data.hpp | 24 +- include/mapnik/image_data_any.hpp | 14 + include/mapnik/image_filter.hpp | 4 +- include/mapnik/image_reader.hpp | 1 - include/mapnik/image_util.hpp | 6 + include/mapnik/raster.hpp | 7 +- .../process_raster_symbolizer.hpp | 18 +- .../input/pgraster/pgraster_wkb_reader.cpp | 10 +- plugins/input/raster/raster_featureset.cpp | 1 - src/agg/agg_renderer.cpp | 9 +- src/agg/process_raster_symbolizer.cpp | 2 +- src/graphics.cpp | 22 +- src/image_compositing.cpp | 66 +++- src/image_util.cpp | 302 ++++++++++++++++-- src/image_util_jpeg.cpp | 6 + src/jpeg_reader.cpp | 3 +- src/marker_cache.cpp | 11 +- src/png_reader.cpp | 1 - src/tiff_reader.cpp | 15 +- src/webp_reader.cpp | 1 - tests/cxx/tiff_io.cpp | 38 +-- 24 files changed, 445 insertions(+), 152 deletions(-) diff --git a/bindings/python/mapnik_image.cpp b/bindings/python/mapnik_image.cpp index b81b7ae39..ef6fb4c37 100644 --- a/bindings/python/mapnik_image.cpp +++ b/bindings/python/mapnik_image.cpp @@ -211,9 +211,24 @@ void blend (image_32 & im, unsigned x, unsigned y, image_32 const& im2, float op im.set_rectangle_alpha2(im2.data(),x,y,opacity); } +bool premultiplied(image_32 &im) +{ + return im.data().get_premultiplied(); +} + +void premultiply(image_32 & im) +{ + mapnik::premultiply_alpha(im.data()); +} + +void demultiply(image_32 & im) +{ + mapnik::demultiply_alpha(im.data()); +} + void composite(image_32 & dst, image_32 & src, mapnik::composite_mode_e mode, float opacity) { - mapnik::composite(dst.data(),src.data(),mode,opacity,0,0,false); + mapnik::composite(dst.data(),src.data(),mode,opacity,0,0); } #if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) @@ -311,9 +326,9 @@ void export_image() arg("mode")=mapnik::src_over, arg("opacity")=1.0f )) - .def("premultiplied",&image_32::premultiplied) - .def("premultiply",&image_32::premultiply) - .def("demultiply",&image_32::demultiply) + .def("premultiplied",&premultiplied) + .def("premultiply",&premultiply) + .def("demultiply",&demultiply) .def("set_pixel",&set_pixel) .def("get_pixel",&get_pixel) .def("clear",&image_32::clear) diff --git a/include/mapnik/graphics.hpp b/include/mapnik/graphics.hpp index c1fe52414..2f40f93a6 100644 --- a/include/mapnik/graphics.hpp +++ b/include/mapnik/graphics.hpp @@ -48,7 +48,6 @@ private: image_data_rgba8 data_; boost::optional background_; bool painted_; - bool premultiplied_; public: using pixel_type = typename image_data_rgba8::pixel_type; image_32(int width,int height); @@ -65,11 +64,6 @@ public: return painted_; } - bool premultiplied() const - { - return premultiplied_; - } - inline void clear() { std::fill(data_.getData(), data_.getData() + data_.width() * data_.height(), 0); @@ -79,10 +73,6 @@ public: void set_background(const color& c); - void premultiply(); - - void demultiply(); - void set_grayscale_to_alpha(); void set_color_to_alpha(color const& c); diff --git a/include/mapnik/image_compositing.hpp b/include/mapnik/image_compositing.hpp index c72f62daa..b41d14b2b 100644 --- a/include/mapnik/image_compositing.hpp +++ b/include/mapnik/image_compositing.hpp @@ -87,8 +87,7 @@ MAPNIK_DECL void composite(T & dst, T const& src, composite_mode_e mode, float opacity=1, int dx=0, - int dy=0, - bool premultiply_src=false); + int dy=0); } #endif // MAPNIK_IMAGE_COMPOSITING_HPP diff --git a/include/mapnik/image_data.hpp b/include/mapnik/image_data.hpp index 833a0f16e..09367db85 100644 --- a/include/mapnik/image_data.hpp +++ b/include/mapnik/image_data.hpp @@ -122,10 +122,11 @@ public: using pixel_type = T; static constexpr std::size_t pixel_size = sizeof(pixel_type); - image_data(int width, int height, bool initialize = true) + image_data(int width, int height, bool initialize = true, bool premultiplied = false) : dimensions_(width, height), buffer_(dimensions_.width() * dimensions_.height() * pixel_size), - pData_(reinterpret_cast(buffer_.data())) + pData_(reinterpret_cast(buffer_.data())), + premultiplied_alpha_(premultiplied) { if (pData_ && initialize) std::fill(pData_, pData_ + dimensions_.width() * dimensions_.height(), 0); } @@ -133,13 +134,15 @@ public: image_data(image_data const& rhs) : dimensions_(rhs.dimensions_), buffer_(rhs.buffer_), - pData_(reinterpret_cast(buffer_.data())) + pData_(reinterpret_cast(buffer_.data())), + premultiplied_alpha_(rhs.premultiplied_alpha_) {} image_data(image_data && rhs) noexcept : dimensions_(std::move(rhs.dimensions_)), - buffer_(std::move(rhs.buffer_)), - pData_(reinterpret_cast(buffer_.data())) + buffer_(std::move(rhs.buffer_)), + pData_(reinterpret_cast(buffer_.data())), + premultiplied_alpha_(std::move(rhs.premultiplied_alpha_)) { rhs.dimensions_ = { 0, 0 }; rhs.pData_ = nullptr; @@ -241,10 +244,21 @@ public: std::copy(buf, buf + (x1 - x0), pData_ + row * dimensions_.width() + x0); } + inline bool get_premultiplied() const + { + return premultiplied_alpha_; + } + + inline void set_premultiplied(bool set) + { + premultiplied_alpha_ = set; + } + private: detail::image_dimensions dimensions_; detail::buffer buffer_; pixel_type *pData_; + bool premultiplied_alpha_; }; using image_data_rgba8 = image_data; diff --git a/include/mapnik/image_data_any.hpp b/include/mapnik/image_data_any.hpp index 336b4220c..50a8c5c2c 100644 --- a/include/mapnik/image_data_any.hpp +++ b/include/mapnik/image_data_any.hpp @@ -34,6 +34,7 @@ struct image_data_null unsigned char* getBytes() { return nullptr;} std::size_t width() const { return 0; } std::size_t height() const { return 0; } + bool get_premultiplied() const { return false; } }; using image_data_base = util::variant + bool operator()(T const& data) const + { + return data.get_premultiplied(); + } +}; } // namespace detail struct image_data_any : image_data_base @@ -109,6 +118,11 @@ struct image_data_any : image_data_base { return util::apply_visitor(detail::get_height_visitor(),*this); } + + bool get_premultiplied() const + { + return util::apply_visitor(detail::get_premultiplied_visitor(),*this); + } }; } diff --git a/include/mapnik/image_filter.hpp b/include/mapnik/image_filter.hpp index 064e94a43..ccebf7ec3 100644 --- a/include/mapnik/image_filter.hpp +++ b/include/mapnik/image_filter.hpp @@ -391,11 +391,11 @@ template void apply_filter(Src & src, Filter const& filter) { { - src.demultiply(); + demultiply_alpha(src.data()); double_buffer tb(src); apply_convolution_3x3(tb.src_view, tb.dst_view, filter); } // ensure ~double_buffer() is called before premultiplying - src.premultiply(); + premultiply_alpha(src.data()); } template diff --git a/include/mapnik/image_reader.hpp b/include/mapnik/image_reader.hpp index 31a12f4f0..178bfb628 100644 --- a/include/mapnik/image_reader.hpp +++ b/include/mapnik/image_reader.hpp @@ -59,7 +59,6 @@ struct MAPNIK_DECL image_reader : private util::noncopyable virtual unsigned width() const = 0; virtual unsigned height() const = 0; virtual bool has_alpha() const = 0; - virtual bool premultiplied_alpha() const = 0; virtual boost::optional > bounding_box() const = 0; virtual void read(unsigned x,unsigned y,image_data_rgba8& image) = 0; virtual image_data_any read(unsigned x, unsigned y, unsigned width, unsigned height) = 0; diff --git a/include/mapnik/image_util.hpp b/include/mapnik/image_util.hpp index 2bee70bdb..c00129b1b 100644 --- a/include/mapnik/image_util.hpp +++ b/include/mapnik/image_util.hpp @@ -109,6 +109,12 @@ MAPNIK_DECL void save_to_stream std::string const& type ); +template +MAPNIK_DECL void premultiply_alpha(T & image); + +template +MAPNIK_DECL void demultiply_alpha(T & image); + template void save_as_png(T const& image, std::string const& filename, diff --git a/include/mapnik/raster.hpp b/include/mapnik/raster.hpp index 8fafd27b2..6b5fbb663 100644 --- a/include/mapnik/raster.hpp +++ b/include/mapnik/raster.hpp @@ -39,18 +39,15 @@ public: box2d ext_; image_data_any data_; double filter_factor_; - bool premultiplied_alpha_; boost::optional nodata_; template raster(box2d const& ext, ImageData && data, - double filter_factor, - bool premultiplied_alpha = false) + double filter_factor) : ext_(ext), data_(std::move(data)), - filter_factor_(filter_factor), - premultiplied_alpha_(premultiplied_alpha) {} + filter_factor_(filter_factor) {} void set_nodata(double nodata) { diff --git a/include/mapnik/renderer_common/process_raster_symbolizer.hpp b/include/mapnik/renderer_common/process_raster_symbolizer.hpp index 7229c60f2..eb950e9ee 100644 --- a/include/mapnik/renderer_common/process_raster_symbolizer.hpp +++ b/include/mapnik/renderer_common/process_raster_symbolizer.hpp @@ -24,6 +24,7 @@ #define MAPNIK_RENDERER_COMMON_PROCESS_RASTER_SYMBOLIZER_HPP // mapnik +#include #include #include #include @@ -204,22 +205,7 @@ void render_raster_symbolizer(raster_symbolizer const& sym, // only premultiply rgba8 images if (source->data_.is()) { - bool premultiply_source = !source->premultiplied_alpha_; - auto is_premultiplied = get_optional(sym, keys::premultiplied, feature, common.vars_); - if (is_premultiplied) - { - if (*is_premultiplied) premultiply_source = false; - else premultiply_source = true; - } - if (premultiply_source) - { - agg::rendering_buffer buffer(source->data_.getBytes(), - source->data_.width(), - source->data_.height(), - source->data_.width() * 4); - agg::pixfmt_rgba32 pixf(buffer); - pixf.premultiply(); - } + mapnik::premultiply_alpha(source->data_); } if (!prj_trans.equal()) diff --git a/plugins/input/pgraster/pgraster_wkb_reader.cpp b/plugins/input/pgraster/pgraster_wkb_reader.cpp index d2e77048c..2932a52b0 100644 --- a/plugins/input/pgraster/pgraster_wkb_reader.cpp +++ b/plugins/input/pgraster/pgraster_wkb_reader.cpp @@ -199,7 +199,7 @@ mapnik::raster_ptr read_data_band(mapnik::box2d const& bbox, data[off] = val; } } - mapnik::raster_ptr raster = std::make_shared(bbox, image, 1.0, true); + mapnik::raster_ptr raster = std::make_shared(bbox, image, 1.0); if ( hasnodata ) raster->set_nodata(val); return raster; } @@ -271,7 +271,7 @@ mapnik::raster_ptr read_grayscale_band(mapnik::box2d const& bbox, uint16_t width, uint16_t height, bool hasnodata, T reader) { - mapnik::image_data_rgba8 image(width,height); + mapnik::image_data_rgba8 image(width,height, true, true); // Start with plain white (ABGR or RGBA depending on endiannes) // TODO: set to transparent instead? image.set(0xffffffff); @@ -292,7 +292,7 @@ mapnik::raster_ptr read_grayscale_band(mapnik::box2d const& bbox, data[off+2] = val; } } - mapnik::raster_ptr raster = std::make_shared(bbox, image, 1.0, true); + mapnik::raster_ptr raster = std::make_shared(bbox, image, 1.0); if ( hasnodata ) raster->set_nodata(val); return raster; } @@ -352,7 +352,7 @@ mapnik::raster_ptr pgraster_wkb_reader::read_grayscale(mapnik::box2d con mapnik::raster_ptr pgraster_wkb_reader::read_rgba(mapnik::box2d const& bbox, uint16_t width, uint16_t height) { - mapnik::image_data_rgba8 image(width, height); + mapnik::image_data_rgba8 image(width, height, true, true); // Start with plain white (ABGR or RGBA depending on endiannes) image.set(0xffffffff); @@ -400,7 +400,7 @@ mapnik::raster_ptr pgraster_wkb_reader::read_rgba(mapnik::box2d const& b } } } - mapnik::raster_ptr raster = std::make_shared(bbox, image, 1.0, true); + mapnik::raster_ptr raster = std::make_shared(bbox, image, 1.0); raster->set_nodata(0xffffffff); return raster; } diff --git a/plugins/input/raster/raster_featureset.cpp b/plugins/input/raster/raster_featureset.cpp index 6f5fa0376..d0e8b6b2b 100644 --- a/plugins/input/raster/raster_featureset.cpp +++ b/plugins/input/raster/raster_featureset.cpp @@ -116,7 +116,6 @@ feature_ptr raster_featureset::next() intersect = t.backward(feature_raster_extent); mapnik::image_data_any data = reader->read(x_off, y_off, width, height); mapnik::raster_ptr raster = std::make_shared(intersect, std::move(data), 1.0); - raster->premultiplied_alpha_ = reader->premultiplied_alpha(); feature->set_raster(raster); } } diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index d1ce80ca0..d8ddbf382 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -145,7 +145,7 @@ void agg_renderer::setup(Map const &m) { for (unsigned y=0;y::end_style_processing(feature_type_style const& st) composite(pixmap_.data(), current_buffer_->data(), *st.comp_op(), st.get_opacity(), -common_.t_.offset(), - -common_.t_.offset(), false); + -common_.t_.offset()); } else if (blend_from || st.get_opacity() < 1.0) { composite(pixmap_.data(), current_buffer_->data(), src_over, st.get_opacity(), -common_.t_.offset(), - -common_.t_.offset(), false); + -common_.t_.offset()); } } // apply any 'direct' image filters @@ -376,8 +376,7 @@ void agg_renderer::render_marker(pixel_position const& pos, composite(current_buffer_->data(), **marker.get_bitmap_data(), comp_op, opacity, std::floor(pos.x - cx + .5), - std::floor(pos.y - cy + .5), - false); + std::floor(pos.y - cy + .5)); } else { diff --git a/src/agg/process_raster_symbolizer.cpp b/src/agg/process_raster_symbolizer.cpp index 4af75bbd6..3bfb6c20a 100644 --- a/src/agg/process_raster_symbolizer.cpp +++ b/src/agg/process_raster_symbolizer.cpp @@ -55,7 +55,7 @@ void agg_renderer::process(raster_symbolizer const& sym, [&](image_data_rgba8 & target, composite_mode_e comp_op, double opacity, int start_x, int start_y) { composite(current_buffer_->data(), target, - comp_op, opacity, start_x, start_y, false); + comp_op, opacity, start_x, start_y); } ); } diff --git a/src/graphics.cpp b/src/graphics.cpp index 901b5fa50..b75df096b 100644 --- a/src/graphics.cpp +++ b/src/graphics.cpp @@ -40,14 +40,12 @@ namespace mapnik { image_32::image_32(int width,int height) : data_(width,height), - painted_(false), - premultiplied_(false) {} + painted_(false) {} image_32::image_32(image_32 const& rhs) : data_(rhs.data_), - painted_(rhs.painted_), - premultiplied_(rhs.premultiplied_) {} + painted_(rhs.painted_) {} image_32::~image_32() {} @@ -123,22 +121,6 @@ boost::optional const& image_32::get_background() const return background_; } -void image_32::premultiply() -{ - agg::rendering_buffer buffer(data_.getBytes(),data_.width(),data_.height(),data_.width() * 4); - agg::pixfmt_rgba32 pixf(buffer); - pixf.premultiply(); - premultiplied_ = true; -} - -void image_32::demultiply() -{ - agg::rendering_buffer buffer(data_.getBytes(),data_.width(),data_.height(),data_.width() * 4); - agg::pixfmt_rgba32_pre pixf(buffer); - pixf.demultiply(); - premultiplied_ = false; -} - void image_32::composite_pixel(unsigned op, int x,int y, unsigned c, unsigned cover, double opacity) { using color_type = agg::rgba8; diff --git a/src/image_compositing.cpp b/src/image_compositing.cpp index 75e9884d2..41192cb6a 100644 --- a/src/image_compositing.cpp +++ b/src/image_compositing.cpp @@ -23,6 +23,7 @@ // mapnik #include #include +#include // boost #pragma GCC diagnostic push @@ -145,14 +146,13 @@ struct rendering_buffer image_data_type const& data_; }; -} +} // end detail ns template <> MAPNIK_DECL void composite(image_data_rgba8 & dst, image_data_rgba8 const& src, composite_mode_e mode, float opacity, int dx, - int dy, - bool premultiply_src) + int dy) { using color = agg::rgba8; using order = agg::order_rgba; @@ -166,7 +166,7 @@ MAPNIK_DECL void composite(image_data_rgba8 & dst, image_data_rgba8 const& src, pixfmt_type pixf(dst_buffer); pixf.comp_op(static_cast(mode)); agg::pixfmt_alpha_blend_rgba pixf_mask(src_buffer); - if (premultiply_src) pixf_mask.premultiply(); + if (!src.get_premultiplied()) pixf_mask.premultiply(); renderer_type ren(pixf); ren.blend_from(pixf_mask,0,dx,dy,unsigned(255*opacity)); } @@ -175,8 +175,7 @@ template <> MAPNIK_DECL void composite(image_data_gray32f & dst, image_data_gray32f const& src, composite_mode_e mode, float opacity, int dx, - int dy, - bool premultiply_src) + int dy) { using const_rendering_buffer = detail::rendering_buffer; using src_pixfmt_type = agg::pixfmt_alpha_blend_gray, const_rendering_buffer, 1, 0>; @@ -191,4 +190,59 @@ MAPNIK_DECL void composite(image_data_gray32f & dst, image_data_gray32f const& s ren.copy_from(pixf_mask,0,dx,dy); } +namespace detail { + +struct composite_visitor +{ + composite_visitor(image_data_any const& src, + composite_mode_e mode, + float opacity, + int dx, + int dy) + : src_(src), + mode_(mode), + opacity_(opacity), + dx_(dx), + dy_(dy) {} + + template + void operator() (T & dst); + + private: + image_data_any const& src_; + composite_mode_e mode_; + float opacity_; + int dx_; + int dy_; +}; + +template +void composite_visitor::operator() (T & dst) +{ + throw std::runtime_error("Error: Composite with " + std::string(typeid(dst).name()) + " is not supported"); +} + +template <> +void composite_visitor::operator() (image_data_rgba8 & dst) +{ + composite(dst, util::get(src_), mode_, opacity_, dx_, dy_); +} + +template <> +void composite_visitor::operator() (image_data_gray32f & dst) +{ + composite(dst, util::get(src_), mode_, opacity_, dx_, dy_); +} + +} // end ns + +template <> +MAPNIK_DECL void composite(image_data_any & dst, image_data_any const& src, composite_mode_e mode, + float opacity, + int dx, + int dy) +{ + util::apply_visitor(detail::composite_visitor(src, mode, opacity, dx, dy), dst); +} + } diff --git a/src/image_util.cpp b/src/image_util.cpp index 8f6c00e13..9a59eee71 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,11 @@ // boost #include +// agg +#include "agg_rendering_buffer.h" +#include "agg_pixfmt_rgba.h" +#include "agg_color_rgba.h" + // stl #include #include @@ -75,7 +81,7 @@ void save_to_file(T const& image, std::ofstream file (filename.c_str(), std::ios::out| std::ios::trunc|std::ios::binary); if (file) { - save_to_stream(image, file, type, palette); + save_to_stream(image, file, type, palette); } else throw ImageWriterException("Could not write file to " + filename ); } @@ -88,7 +94,7 @@ void save_to_file(T const& image, std::ofstream file (filename.c_str(), std::ios::out| std::ios::trunc|std::ios::binary); if (file) { - save_to_stream(image, file, type); + save_to_stream(image, file, type); } else throw ImageWriterException("Could not write file to " + filename ); } @@ -98,6 +104,67 @@ void save_to_stream(T const& image, std::ostream & stream, std::string const& type, rgba_palette const& palette) +{ + if (stream && image.width() > 0 && image.height() > 0) + { + std::string t = type; + std::transform(t.begin(), t.end(), t.begin(), ::tolower); + if (t == "png" || boost::algorithm::starts_with(t, "png")) + { + png_saver_pal visitor(stream, t, palette); + mapnik::util::apply_visitor(visitor, image); + } + else if (boost::algorithm::starts_with(t, "tif")) + { + throw ImageWriterException("palettes are not currently supported when writing to tiff format (yet)"); + } + else if (boost::algorithm::starts_with(t, "jpeg")) + { + throw ImageWriterException("palettes are not currently supported when writing to jpeg format"); + } + else throw ImageWriterException("unknown file type: " + type); + } + else throw ImageWriterException("Could not write to empty stream" ); +} + +// This can be removed once image_data_any and image_view_any are the only +// items using this template +template <> +void save_to_stream(image_data_rgba8 const& image, + std::ostream & stream, + std::string const& type, + rgba_palette const& palette) +{ + if (stream && image.width() > 0 && image.height() > 0) + { + std::string t = type; + std::transform(t.begin(), t.end(), t.begin(), ::tolower); + if (t == "png" || boost::algorithm::starts_with(t, "png")) + { + png_saver_pal visitor(stream, t, palette); + visitor(image); + //mapnik::util::apply_visitor(visitor, image); + } + else if (boost::algorithm::starts_with(t, "tif")) + { + throw ImageWriterException("palettes are not currently supported when writing to tiff format (yet)"); + } + else if (boost::algorithm::starts_with(t, "jpeg")) + { + throw ImageWriterException("palettes are not currently supported when writing to jpeg format"); + } + else throw ImageWriterException("unknown file type: " + type); + } + else throw ImageWriterException("Could not write to empty stream" ); +} + +// This can be removed once image_data_any and image_view_any are the only +// items using this template +template <> +void save_to_stream(image_view_rgba8 const& image, + std::ostream & stream, + std::string const& type, + rgba_palette const& palette) { if (stream && image.width() > 0 && image.height() > 0) { @@ -126,6 +193,82 @@ template void save_to_stream(T const& image, std::ostream & stream, std::string const& type) +{ + if (stream && image.width() > 0 && image.height() > 0) + { + std::string t = type; + std::transform(t.begin(), t.end(), t.begin(), ::tolower); + if (t == "png" || boost::algorithm::starts_with(t, "png")) + { + png_saver visitor(stream, t); + util::apply_visitor(visitor, image); + } + else if (boost::algorithm::starts_with(t, "tif")) + { + tiff_saver visitor(stream, t); + util::apply_visitor(visitor, image); + } + else if (boost::algorithm::starts_with(t, "jpeg")) + { + jpeg_saver visitor(stream, t); + util::apply_visitor(visitor, image); + } + else if (boost::algorithm::starts_with(t, "webp")) + { + webp_saver visitor(stream, t); + util::apply_visitor(visitor, image); + } + else throw ImageWriterException("unknown file type: " + type); + } + else throw ImageWriterException("Could not write to empty stream" ); +} + +// This can be removed once image_data_any and image_view_any are the only +// items using this template +template <> +void save_to_stream(image_data_rgba8 const& image, + std::ostream & stream, + std::string const& type) +{ + if (stream && image.width() > 0 && image.height() > 0) + { + std::string t = type; + std::transform(t.begin(), t.end(), t.begin(), ::tolower); + if (t == "png" || boost::algorithm::starts_with(t, "png")) + { + png_saver visitor(stream, t); + visitor(image); + //util::apply_visitor(visitor, image); + } + else if (boost::algorithm::starts_with(t, "tif")) + { + tiff_saver visitor(stream, t); + visitor(image); + //util::apply_visitor(visitor, image); + } + else if (boost::algorithm::starts_with(t, "jpeg")) + { + jpeg_saver visitor(stream, t); + visitor(image); + //util::apply_visitor(visitor, image); + } + else if (boost::algorithm::starts_with(t, "webp")) + { + webp_saver visitor(stream, t); + visitor(image); + //util::apply_visitor(visitor, image); + } + else throw ImageWriterException("unknown file type: " + type); + } + else throw ImageWriterException("Could not write to empty stream" ); +} + +// This can be removed once image_data_any and image_view_any are the only +// items using this template +template <> +void save_to_stream(image_view_rgba8 const& image, + std::ostream & stream, + std::string const& type) { if (stream && image.width() > 0 && image.height() > 0) { @@ -182,50 +325,157 @@ void save_to_file(T const& image, std::string const& filename, rgba_palette cons else throw ImageWriterException("Could not write file to " + filename ); } +// image_data_rgba8 template void save_to_file(image_data_rgba8 const&, - std::string const&, - std::string const&); + std::string const&, + std::string const&); template void save_to_file(image_data_rgba8 const&, - std::string const&, - std::string const&, - rgba_palette const& palette); + std::string const&, + std::string const&, + rgba_palette const& palette); template void save_to_file(image_data_rgba8 const&, - std::string const&); + std::string const&); template void save_to_file(image_data_rgba8 const&, - std::string const&, - rgba_palette const& palette); + std::string const&, + rgba_palette const& palette); template std::string save_to_string(image_data_rgba8 const&, - std::string const&); + std::string const&); template std::string save_to_string(image_data_rgba8 const&, - std::string const&, - rgba_palette const& palette); + std::string const&, + rgba_palette const& palette); + +// image_view_rgba8 +template void save_to_file (image_view_rgba8 const&, + std::string const&, + std::string const&); template void save_to_file (image_view_rgba8 const&, - std::string const&, - std::string const&); + std::string const&, + std::string const&, + rgba_palette const& palette); template void save_to_file (image_view_rgba8 const&, - std::string const&, - std::string const&, - rgba_palette const& palette); + std::string const&); template void save_to_file (image_view_rgba8 const&, - std::string const&); - -template void save_to_file (image_view_rgba8 const&, - std::string const&, - rgba_palette const& palette); + std::string const&, + rgba_palette const& palette); template std::string save_to_string (image_view_rgba8 const&, - std::string const&); + std::string const&); template std::string save_to_string (image_view_rgba8 const&, - std::string const&, - rgba_palette const& palette); + std::string const&, + rgba_palette const& palette); +// image_data_any +template void save_to_file(image_data_any const&, + std::string const&, + std::string const&); + +template void save_to_file(image_data_any const&, + std::string const&, + std::string const&, + rgba_palette const& palette); + +template void save_to_file(image_data_any const&, + std::string const&); + +template void save_to_file(image_data_any const&, + std::string const&, + rgba_palette const& palette); + +template std::string save_to_string(image_data_any const&, + std::string const&); + +template std::string save_to_string(image_data_any const&, + std::string const&, + rgba_palette const& palette); + + +namespace detail { + +struct premultiply_visitor +{ + template + void operator() (T & data) + { + throw std::runtime_error("Error: Premultiply with " + std::string(typeid(data).name()) + " is not supported"); + } + +}; + +template <> +void premultiply_visitor::operator() (image_data_rgba8 & data) +{ + if (!data.get_premultiplied()) + { + agg::rendering_buffer buffer(data.getBytes(),data.width(),data.height(),data.width() * 4); + agg::pixfmt_rgba32 pixf(buffer); + pixf.premultiply(); + data.set_premultiplied(true); + } } + +struct demultiply_visitor +{ + template + void operator() (T & data) + { + throw std::runtime_error("Error: Premultiply with " + std::string(typeid(data).name()) + " is not supported"); + } + +}; + +template <> +void demultiply_visitor::operator() (image_data_rgba8 & data) +{ + if (data.get_premultiplied()) + { + agg::rendering_buffer buffer(data.getBytes(),data.width(),data.height(),data.width() * 4); + agg::pixfmt_rgba32_pre pixf(buffer); + pixf.demultiply(); + data.set_premultiplied(false); + } +} + +} // end detail ns + +template +void premultiply_alpha(T & image) +{ + util::apply_visitor(detail::premultiply_visitor(), image); +} + +template void premultiply_alpha (image_data_any &); + +// Temporary, can be removed once image_view_any and image_data_any are the only ones passed +template <> +void premultiply_alpha(image_data_rgba8 & image) +{ + detail::premultiply_visitor visit; + visit(image); +} + +template +void demultiply_alpha(T & image) +{ + util::apply_visitor(detail::demultiply_visitor(), image); +} + +template void demultiply_alpha (image_data_any &); + +// Temporary, can be removed once image_view_any and image_data_any are the only ones passed +template <> +void demultiply_alpha(image_data_rgba8 & image) +{ + detail::demultiply_visitor visit; + visit(image); +} + +} // end ns diff --git a/src/image_util_jpeg.cpp b/src/image_util_jpeg.cpp index d44c13974..d096a44fc 100644 --- a/src/image_util_jpeg.cpp +++ b/src/image_util_jpeg.cpp @@ -76,6 +76,12 @@ void jpeg_saver::operator() (image_view_rgba8 const& image) co process_rgba8_jpeg(image, t_, stream_); } +template<> +void jpeg_saver::operator() (image_data_null const& image) const +{ + throw ImageWriterException("Can not save a null image to jpeg"); +} + template void jpeg_saver::operator() (T const& image) const { diff --git a/src/jpeg_reader.cpp b/src/jpeg_reader.cpp index 48ac83e19..d6935572c 100644 --- a/src/jpeg_reader.cpp +++ b/src/jpeg_reader.cpp @@ -85,7 +85,6 @@ public: unsigned height() const final; boost::optional > bounding_box() const final; inline bool has_alpha() const final { return false; } - inline bool premultiplied_alpha() const final { return true; } void read(unsigned x,unsigned y,image_data_rgba8& image) final; image_data_any read(unsigned x, unsigned y, unsigned width, unsigned height) final; private: @@ -323,7 +322,7 @@ void jpeg_reader::read(unsigned x0, unsigned y0, image_data_rgba8& image) template image_data_any jpeg_reader::read(unsigned x, unsigned y, unsigned width, unsigned height) { - image_data_rgba8 data(width,height); + image_data_rgba8 data(width,height, true, true); read(x, y, data); return image_data_any(std::move(data)); } diff --git a/src/marker_cache.cpp b/src/marker_cache.cpp index c7d1b005f..de42f1552 100644 --- a/src/marker_cache.cpp +++ b/src/marker_cache.cpp @@ -210,14 +210,13 @@ boost::optional marker_cache::find(std::string const& uri, unsigned width = reader->width(); unsigned height = reader->height(); BOOST_ASSERT(width > 0 && height > 0); - mapnik::image_ptr image(std::make_shared(width,height)); - reader->read(0,0,*image); - if (!reader->premultiplied_alpha()) + image_data_any im = reader->read(0,0,width,height); + if (!im.is()) { - agg::rendering_buffer buffer(image->getBytes(),image->width(),image->height(),image->width() * 4); - agg::pixfmt_rgba32 pixf(buffer); - pixf.premultiply(); + throw std::runtime_error("Error: Only image_data_rgba8 types are supported currenctly by markers"); } + mapnik::premultiply_alpha(im); + mapnik::image_ptr image(std::make_shared(std::move(util::get(im)))); marker_ptr mark(std::make_shared(image)); result.reset(mark); if (update_cache) diff --git a/src/png_reader.cpp b/src/png_reader.cpp index 318a67b47..21cd38fc0 100644 --- a/src/png_reader.cpp +++ b/src/png_reader.cpp @@ -80,7 +80,6 @@ public: unsigned height() const final; boost::optional > bounding_box() const final; inline bool has_alpha() const final { return has_alpha_; } - bool premultiplied_alpha() const final { return false; } //http://www.libpng.org/pub/png/spec/1.1/PNG-Rationale.html void read(unsigned x,unsigned y,image_data_rgba8& image) final; image_data_any read(unsigned x, unsigned y, unsigned width, unsigned height) final; private: diff --git a/src/tiff_reader.cpp b/src/tiff_reader.cpp index b81df4b68..bfcf54b3a 100644 --- a/src/tiff_reader.cpp +++ b/src/tiff_reader.cpp @@ -153,7 +153,6 @@ public: unsigned height() const final; boost::optional > bounding_box() const final; inline bool has_alpha() const final { return has_alpha_; } - bool premultiplied_alpha() const final; void read(unsigned x,unsigned y,image_data_rgba8& image) final; image_data_any read(unsigned x, unsigned y, unsigned width, unsigned height) final; // methods specific to tiff reader @@ -377,12 +376,6 @@ boost::optional > tiff_reader::bounding_box() const return bbox_; } -template -bool tiff_reader::premultiplied_alpha() const -{ - return premultiplied_alpha_; -} - template void tiff_reader::read(unsigned x,unsigned y,image_data_rgba8& image) { @@ -525,7 +518,7 @@ image_data_any tiff_reader::read(unsigned x0, unsigned y0, unsigned width, un TIFF* tif = open(stream_); if (tif) { - image_data_rgba8 data(width, height); + image_data_rgba8 data(width, height, true, premultiplied_alpha_); std::size_t element_size = sizeof(detail::rgb8); std::size_t size_to_allocate = (TIFFScanlineSize(tif) + element_size - 1)/element_size; const std::unique_ptr scanline(new detail::rgb8[size_to_allocate]); @@ -550,13 +543,13 @@ image_data_any tiff_reader::read(unsigned x0, unsigned y0, unsigned width, un } case 16: { - image_data_rgba8 data(width,height); + image_data_rgba8 data(width,height,true,premultiplied_alpha_); read(x0, y0, data); return image_data_any(std::move(data)); } case 32: { - image_data_rgba8 data(width,height); + image_data_rgba8 data(width,height,true,premultiplied_alpha_); read(x0, y0, data); return image_data_any(std::move(data)); } @@ -574,7 +567,7 @@ image_data_any tiff_reader::read(unsigned x0, unsigned y0, unsigned width, un //PHOTOMETRIC_ITULAB = 10; //PHOTOMETRIC_LOGL = 32844; //PHOTOMETRIC_LOGLUV = 32845; - image_data_rgba8 data(width,height); + image_data_rgba8 data(width,height, true, premultiplied_alpha_); read(x0, y0, data); return image_data_any(std::move(data)); } diff --git a/src/webp_reader.cpp b/src/webp_reader.cpp index 6add17485..ff31e8996 100644 --- a/src/webp_reader.cpp +++ b/src/webp_reader.cpp @@ -124,7 +124,6 @@ public: unsigned height() const final; boost::optional > bounding_box() const final; inline bool has_alpha() const final { return has_alpha_; } - bool premultiplied_alpha() const final { return false; } void read(unsigned x,unsigned y,image_data_rgba8& image) final; image_data_any read(unsigned x, unsigned y, unsigned width, unsigned height) final; private: diff --git a/tests/cxx/tiff_io.cpp b/tests/cxx/tiff_io.cpp index 37cfdddd4..302e7e5ce 100644 --- a/tests/cxx/tiff_io.cpp +++ b/tests/cxx/tiff_io.cpp @@ -26,25 +26,19 @@ REQUIRE( reader2->width() == 256 ); \ REQUIRE( reader2->height() == 256 ); \ -#define TIFF_ASSERT_ALPHA \ +#define TIFF_ASSERT_ALPHA( data ) \ REQUIRE( tiff_reader.has_alpha() == true ); \ - REQUIRE( tiff_reader.premultiplied_alpha() == false ); \ REQUIRE( reader->has_alpha() == true ); \ - REQUIRE( reader->premultiplied_alpha() == false ); \ REQUIRE( tiff_reader2.has_alpha() == true ); \ - REQUIRE( tiff_reader2.premultiplied_alpha() == false ); \ REQUIRE( reader2->has_alpha() == true ); \ - REQUIRE( reader2->premultiplied_alpha() == false ); \ + REQUIRE( data.get_premultiplied() == false ); \ -#define TIFF_ASSERT_NO_ALPHA \ +#define TIFF_ASSERT_NO_ALPHA( data ) \ REQUIRE( tiff_reader.has_alpha() == false ); \ - REQUIRE( tiff_reader.premultiplied_alpha() == false ); \ REQUIRE( reader->has_alpha() == false ); \ - REQUIRE( reader->premultiplied_alpha() == false ); \ REQUIRE( tiff_reader2.has_alpha() == false ); \ - REQUIRE( tiff_reader2.premultiplied_alpha() == false ); \ REQUIRE( reader2->has_alpha() == false ); \ - REQUIRE( reader2->premultiplied_alpha() == false ); \ + REQUIRE( data.get_premultiplied() == false ); \ #define TIFF_ASSERT_SIZE( data,reader ) \ REQUIRE( data.width() == reader->width() ); \ @@ -83,7 +77,7 @@ SECTION("scan rgb8 striped") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -113,7 +107,7 @@ SECTION("scan rgb8 tiled") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -129,7 +123,7 @@ SECTION("rgba8 striped") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_ALPHA + TIFF_ASSERT_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -145,7 +139,7 @@ SECTION("rgba8 tiled") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_ALPHA + TIFF_ASSERT_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -161,7 +155,7 @@ SECTION("rgb8 striped") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -177,7 +171,7 @@ SECTION("rgb8 tiled") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -193,7 +187,7 @@ SECTION("gray8 striped") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -209,7 +203,7 @@ SECTION("gray8 tiled") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -225,7 +219,7 @@ SECTION("gray16 striped") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -241,7 +235,7 @@ SECTION("gray16 tiled") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -257,7 +251,7 @@ SECTION("gray32f striped") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL } @@ -273,7 +267,7 @@ SECTION("gray32f tiled") { mapnik::image_data_any data = reader->read(0, 0, reader->width(), reader->height()); REQUIRE( data.is() == true ); TIFF_ASSERT_SIZE( data,reader ); - TIFF_ASSERT_NO_ALPHA + TIFF_ASSERT_NO_ALPHA( data ); TIFF_READ_ONE_PIXEL }