From a100b2fe1f30e7025caef3e418f50296534c1399 Mon Sep 17 00:00:00 2001 From: Blake Thompson Date: Fri, 23 Jan 2015 18:08:59 -0600 Subject: [PATCH] Fixed the issues associated with TIFFs, now they always will return as premultiplied in the event they are rgba8 due to the way that the TIFF reader operates. Also added premultiply as a flag inside color class and exposed many of its components, made it so that setting and getting pixels took into consideration the state of the color and the image when dealing with the two. --- bindings/python/mapnik_color.cpp | 22 +++++ bindings/python/mapnik_image.cpp | 22 ++++- include/mapnik/color.hpp | 35 +++++-- include/mapnik/image_view.hpp | 9 ++ include/mapnik/tiff_io.hpp | 15 +-- src/agg/agg_renderer.cpp | 4 +- src/color.cpp | 11 ++- src/image_compositing.cpp | 1 - src/image_util.cpp | 44 ++++----- src/tiff_reader.cpp | 28 ++---- tests/cxx/tiff_io.cpp | 2 +- tests/python_tests/compositing_test.py | 2 +- tests/python_tests/image_test.py | 131 +++++++++++++++++++++++++ tests/python_tests/image_tiff_test.py | 38 ++++--- 14 files changed, 278 insertions(+), 86 deletions(-) diff --git a/bindings/python/mapnik_color.cpp b/bindings/python/mapnik_color.cpp index fc642f449..7210730c2 100644 --- a/bindings/python/mapnik_color.cpp +++ b/bindings/python/mapnik_color.cpp @@ -57,11 +57,27 @@ void export_color () "and an alpha value.\n" "All values between 0 and 255.\n") ) + .def(init( + ( arg("r"), arg("g"), arg("b"), arg("a"), arg("premultiplied") ), + "Creates a new color from its RGB components\n" + "and an alpha value.\n" + "All values between 0 and 255.\n") + ) .def(init( ( arg("r"), arg("g"), arg("b") ), "Creates a new color from its RGB components.\n" "All values between 0 and 255.\n") ) + .def(init( + ( arg("val") ), + "Creates a new color from an unsigned integer.\n" + "All values between 0 and 2^32-1\n") + ) + .def(init( + ( arg("val"), arg("premultiplied") ), + "Creates a new color from an unsigned integer.\n" + "All values between 0 and 2^32-1\n") + ) .def(init( ( arg("color_string") ), "Creates a new color from its CSS string representation.\n" @@ -93,6 +109,12 @@ void export_color () .def_pickle(color_pickle_suite()) .def("__str__",&color::to_string) .def("packed",&color::rgba) + .def("premultiply",&color::premultiply) + .def("set_premultiplied",&color::set_premultiplied) + .def("get_premultiplied",&color::get_premultiplied) + .def("premultiply",&color::premultiply) + .def("demultiply",&color::demultiply) + .def("packed",&color::rgba) .def("to_hex_string",&color::to_hex_string, "Returns the hexadecimal representation of this color.\n" "\n" diff --git a/bindings/python/mapnik_image.cpp b/bindings/python/mapnik_image.cpp index 6263faf4d..a7142e699 100644 --- a/bindings/python/mapnik_image.cpp +++ b/bindings/python/mapnik_image.cpp @@ -131,9 +131,9 @@ void background(mapnik::image_any & im, mapnik::color const& c) mapnik::fill(im, c); } -uint32_t get_pixel(mapnik::image_any const& im, int x, int y) +uint32_t get_pixel(mapnik::image_any const& im, unsigned x, unsigned y) { - if (x < static_cast(im.width()) && y < static_cast(im.height())) + if (x < static_cast(im.width()) && y < static_cast(im.height())) { return mapnik::get_pixel(im, x, y); } @@ -142,8 +142,25 @@ uint32_t get_pixel(mapnik::image_any const& im, int x, int y) return 0; } +mapnik::color get_pixel_color(mapnik::image_any const& im, unsigned x, unsigned y) +{ + if (x < static_cast(im.width()) && y < static_cast(im.height())) + { + return mapnik::get_pixel(im, x, y); + } + PyErr_SetString(PyExc_IndexError, "invalid x,y for image dimensions"); + boost::python::throw_error_already_set(); + return 0; +} + void set_pixel(mapnik::image_any & im, unsigned x, unsigned y, mapnik::color const& c) { + if (x >= static_cast(im.width()) && y >= static_cast(im.height())) + { + PyErr_SetString(PyExc_IndexError, "invalid x,y for image dimensions"); + boost::python::throw_error_already_set(); + return; + } mapnik::set_pixel(im, x, y, c); } @@ -324,6 +341,7 @@ void export_image() .def("demultiply",&demultiply) .def("set_pixel",&set_pixel) .def("get_pixel",&get_pixel) + .def("get_pixel_color",&get_pixel_color) .def("clear",&clear) //TODO(haoyu) The method name 'tostring' might be confusing since they actually return bytes in Python 3 diff --git a/include/mapnik/color.hpp b/include/mapnik/color.hpp index f220ddd07..4d2eb846c 100644 --- a/include/mapnik/color.hpp +++ b/include/mapnik/color.hpp @@ -44,29 +44,39 @@ private: std::uint8_t green_; std::uint8_t blue_; std::uint8_t alpha_; - + bool premultiplied_; public: // default ctor color() : red_(0xff), green_(0xff), blue_(0xff), - alpha_(0xff) + alpha_(0xff), + premultiplied_(false) {} - color(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t alpha = 0xff) + color(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t alpha = 0xff, bool premultiplied = false) : red_(red), green_(green), blue_(blue), - alpha_(alpha) + alpha_(alpha), + premultiplied_(premultiplied) {} + color(std::uint32_t rgba, bool premultiplied = false) + : red_(rgba & 0xff), + green_((rgba >> 8) & 0xff), + blue_((rgba >> 16) & 0xff), + alpha_((rgba >> 24) & 0xff), + premultiplied_(premultiplied) {} + // copy ctor color(const color& rhs) : red_(rhs.red_), green_(rhs.green_), blue_(rhs.blue_), - alpha_(rhs.alpha_) + alpha_(rhs.alpha_), + premultiplied_(rhs.premultiplied_) {} // move ctor @@ -74,14 +84,15 @@ public: : red_(std::move(rhs.red_)), green_(std::move(rhs.green_)), blue_(std::move(rhs.blue_)), - alpha_(std::move(rhs.alpha_)) {} + alpha_(std::move(rhs.alpha_)), + premultiplied_(std::move(rhs.premultiplied_)) {} color( std::string const& str); std::string to_string() const; std::string to_hex_string() const; - void premultiply(); - void demultiply(); + bool premultiply(); + bool demultiply(); color& operator=(color rhs) { @@ -135,6 +146,14 @@ public: { alpha_ = alpha; } + inline bool get_premultiplied() const + { + return premultiplied_; + } + inline void set_premultiplied(bool status) + { + premultiplied_ = status; + } inline unsigned rgba() const { diff --git a/include/mapnik/image_view.hpp b/include/mapnik/image_view.hpp index 0c9a45e82..2256cfbca 100644 --- a/include/mapnik/image_view.hpp +++ b/include/mapnik/image_view.hpp @@ -84,6 +84,10 @@ public: { return height_; } + inline const pixel_type& operator() (std::size_t i, std::size_t j) const + { + return data_(i,j); + } inline unsigned getSize() const { @@ -110,6 +114,11 @@ public: return data_; } + inline bool get_premultiplied() const + { + return data_.get_premultiplied(); + } + private: unsigned x_; unsigned y_; diff --git a/include/mapnik/tiff_io.hpp b/include/mapnik/tiff_io.hpp index 97d2d6a32..ecf6faec1 100644 --- a/include/mapnik/tiff_io.hpp +++ b/include/mapnik/tiff_io.hpp @@ -39,8 +39,6 @@ extern "C" #define TIFF_WRITE_STRIPPED 1 #define TIFF_WRITE_TILED 2 -#include - namespace mapnik { static inline tsize_t tiff_write_proc(thandle_t fd, tdata_t buf, tsize_t size) @@ -200,14 +198,12 @@ struct tag_setter TIFFSetField(output_, TIFFTAG_SAMPLESPERPIXEL, 4); if (data.get_premultiplied()) { - //uint16 extras[] = { EXTRASAMPLE_ASSOCALPHA }; - uint16 extras[] = { EXTRASAMPLE_UNASSALPHA }; + uint16 extras[] = { EXTRASAMPLE_ASSOCALPHA }; TIFFSetField(output_, TIFFTAG_EXTRASAMPLES, 1, extras); } else { - //uint16 extras[] = { EXTRASAMPLE_UNASSALPHA }; - uint16 extras[] = { EXTRASAMPLE_ASSOCALPHA }; + uint16 extras[] = { EXTRASAMPLE_UNASSALPHA }; TIFFSetField(output_, TIFFTAG_EXTRASAMPLES, 1, extras); } if (config_.compression == COMPRESSION_DEFLATE @@ -267,7 +263,7 @@ struct tag_setter private: TIFF * output_; - tiff_config config_; + tiff_config & config_; }; inline void set_tiff_config(TIFF* output, tiff_config & config) @@ -348,10 +344,7 @@ void save_as_tiff(T1 & file, T2 const& image, tiff_config & config) TIFFSetField(output, TIFFTAG_ROWSPERSTRIP, rows_per_strip); std::size_t strip_size = width * rows_per_strip; std::unique_ptr strip_buffer(new pixel_type[strip_size]); - //int end_y=(height/rows_per_strip+1)*rows_per_strip; - int end_y=height; - - for (int y=0; y < end_y; y+=rows_per_strip) + for (int y=0; y < height; y+=rows_per_strip) { int ty1 = std::min(height, static_cast(y + rows_per_strip)) - y; int row = y; diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index 1563c3272..83aaed6bd 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -123,7 +123,9 @@ void agg_renderer::setup(Map const &m) } else { - mapnik::fill(pixmap_,*bg); + mapnik::color bg_color = *bg; + bg_color.set_premultiplied(true); + mapnik::fill(pixmap_,bg_color); } } diff --git a/src/color.cpp b/src/color.cpp index f1146e815..51fb19585 100644 --- a/src/color.cpp +++ b/src/color.cpp @@ -95,23 +95,28 @@ std::string color::to_hex_string() const return str; } -void color::premultiply() +bool color::premultiply() { + if (premultiplied_) return false; agg::rgba8 pre_c = agg::rgba8(red_,green_,blue_,alpha_); pre_c.premultiply(); red_ = pre_c.r; green_ = pre_c.g; blue_ = pre_c.b; + premultiplied_ = true; + return true; } -void color::demultiply() +bool color::demultiply() { - // note: this darkens too much: https://github.com/mapnik/mapnik/issues/1519 + if (!premultiplied_) return false; agg::rgba8 pre_c = agg::rgba8(red_,green_,blue_,alpha_); pre_c.demultiply(); red_ = pre_c.r; green_ = pre_c.g; blue_ = pre_c.b; + premultiplied_ = false; + return true; } } diff --git a/src/image_compositing.cpp b/src/image_compositing.cpp index ea4908d71..6667bce1c 100644 --- a/src/image_compositing.cpp +++ b/src/image_compositing.cpp @@ -169,7 +169,6 @@ MAPNIK_DECL void composite(image_rgba8 & dst, image_rgba8 const& src, composite_ #ifdef MAPNIK_DEBUG if (!src.get_premultiplied()) { - abort(); throw std::runtime_error("SOURCE MUST BE PREMULTIPLIED FOR COMPOSITING!"); } if (!dst.get_premultiplied()) diff --git a/src/image_util.cpp b/src/image_util.cpp index c5239e5af..02f5fdd32 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -783,19 +783,6 @@ struct visitor_fill visitor_fill(T1 const& val) : val_(val) {} - void operator() (image_rgba8 & data) - { - using pixel_type = typename image_rgba8::pixel_type; - bool was_demultiplied = mapnik::demultiply_alpha(data); - pixel_type val = static_cast(val_); - data.set(val); - if (was_demultiplied) - { - mapnik::premultiply_alpha(data); - } - - } - template void operator() (T2 & data) { @@ -817,14 +804,9 @@ struct visitor_fill void operator() (image_rgba8 & data) { using pixel_type = typename image_rgba8::pixel_type; - bool was_demultiplied = mapnik::demultiply_alpha(data); pixel_type val = static_cast(val_.rgba()); data.set(val); - if (was_demultiplied) - { - mapnik::premultiply_alpha(data); - } - + data.set_premultiplied(val_.get_premultiplied()); } template @@ -1069,7 +1051,23 @@ struct visitor_set_pixel void operator() (T2 & data) { using pixel_type = typename T2::pixel_type; - pixel_type val = static_cast(val_.rgba()); + pixel_type val; + if (data.get_premultiplied() && !val_.get_premultiplied()) + { + color tmp(val_); + tmp.premultiply(); + val = static_cast(tmp.rgba()); + } + else if (!data.get_premultiplied() && val_.get_premultiplied()) + { + color tmp(val_); + tmp.demultiply(); + val = static_cast(tmp.rgba()); + } + else + { + val = static_cast(val_.rgba()); + } if (check_bounds(data, x_, y_)) { data(x_, y_) = val; @@ -1163,11 +1161,7 @@ struct visitor_get_pixel { if (check_bounds(data, x_, y_)) { - uint32_t val = static_cast(data(x_, y_)); - return color(static_cast(val), - static_cast((val >>= 8)), - static_cast((val >>= 8)), - static_cast((val >>= 8))); + return color(static_cast(data(x_, y_)), data.get_premultiplied()); } else { diff --git a/src/tiff_reader.cpp b/src/tiff_reader.cpp index 9bbe6fd59..65f405c09 100644 --- a/src/tiff_reader.cpp +++ b/src/tiff_reader.cpp @@ -308,10 +308,10 @@ void tiff_reader::init() { has_alpha_ = true; premultiplied_alpha_ = true; - if (extrasamples == 1 && - sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) + if (extrasamples > 0 && + sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED) { - premultiplied_alpha_ = false; + throw std::runtime_error("Unspecified provided for extra samples to tiff reader."); } } // Try extracting bounding box from geoTIFF tags @@ -641,15 +641,12 @@ void tiff_reader::read_stripped(unsigned x0,unsigned y0,image_rgba8& image) int height=image.height(); unsigned start_y=(y0/rows_per_strip_)*rows_per_strip_; - //unsigned end_y=((y0+height)/rows_per_strip_+1)*rows_per_strip_; unsigned end_y=std::min(y0+height, static_cast(height_)); - //bool laststrip=(static_cast(end_y) > height_)?true:false; - int row,tx0,tx1,ty0,ty1; + int tx0,tx1,ty0,ty1; - //image.set_premultiplied(true); tx0=x0; tx1=std::min(width+x0,static_cast(width_)); - + int row = 0; for (unsigned y=start_y; y < end_y; y+=rows_per_strip_) { ty0 = std::max(y0,y)-y; @@ -660,18 +657,11 @@ void tiff_reader::read_stripped(unsigned x0,unsigned y0,image_rgba8& image) std::clog << "TIFFReadRGBAStrip failed at " << y << " for " << width_ << "/" << height_ << "\n"; break; } - row=y+ty0; - - //int n0=laststrip ? 0:(rows_per_strip_-ty1); - //int n1=laststrip ? (ty1-ty0-1):(rows_per_strip_-ty0-1); - //std::cout << " n0: " << n0 << " n1: " << n1 << " y: " << y << " endy: " << end_y << std::endl; - //for (int n=n1;n>=n0;--n) - //for (int n=n0;n>=n1;++n) - // This is in reverse becauase the TIFFReadRGBAStrip reads inverted? - for (int ty = ty1-1; ty >= ty0; --ty, ++row) + // This is in reverse becauase the TIFFReadRGBAStrip reads inverted + for (unsigned ty = ty1; ty > ty0; --ty) { - //std::cout << "row: " << row <<" s: " << (tx0-x0) << " e: " << (tx1-x0) << " n: " << (n*width_+tx0) << std::endl; - image.setRow(row,tx0-x0,tx1-x0,&strip.getData()[ty*width_+tx0]); + image.setRow(row,tx0-x0,tx1-x0,&strip.getData()[(ty-1)*width_+tx0]); + ++row; } } } diff --git a/tests/cxx/tiff_io.cpp b/tests/cxx/tiff_io.cpp index da3b6c073..7652824ea 100644 --- a/tests/cxx/tiff_io.cpp +++ b/tests/cxx/tiff_io.cpp @@ -31,7 +31,7 @@ REQUIRE( reader->has_alpha() == true ); \ REQUIRE( tiff_reader2.has_alpha() == true ); \ REQUIRE( reader2->has_alpha() == true ); \ - REQUIRE( data.get_premultiplied() == false ); \ + REQUIRE( data.get_premultiplied() == true ); \ #define TIFF_ASSERT_NO_ALPHA( data ) \ REQUIRE( tiff_reader.has_alpha() == false ); \ diff --git a/tests/python_tests/compositing_test.py b/tests/python_tests/compositing_test.py index fc05525e6..bd4b75fe7 100644 --- a/tests/python_tests/compositing_test.py +++ b/tests/python_tests/compositing_test.py @@ -231,7 +231,7 @@ def test_background_image_with_alpha_and_background_color(): m.background_image = '../data/images/yellow_half_trans.png' im = mapnik.Image(m.width,m.height) mapnik.render(m,im) - eq_(get_unique_colors(im),['rgba(255,255,85,191)']) + eq_(get_unique_colors(im),['rgba(255,255,170,191)']) def test_background_image_with_alpha_and_background_color_against_composited_control(): m = mapnik.Map(10,10) diff --git a/tests/python_tests/image_test.py b/tests/python_tests/image_test.py index a4b944bb9..7b233a233 100644 --- a/tests/python_tests/image_test.py +++ b/tests/python_tests/image_test.py @@ -28,6 +28,137 @@ def test_image_premultiply(): eq_(im.demultiply(), False) eq_(im.premultiplied(),False) +def test_image_premultiply_values(): + im = mapnik.Image(256,256) + im.background(mapnik.Color(16, 33, 255, 128)) + im.premultiply() + c = im.get_pixel_color(0,0) + eq_(c.r, 8) + eq_(c.g, 17) + eq_(c.b, 128) + eq_(c.a, 128) + im.demultiply() + # Do to the nature of this operation the result will not be exactly the same + c = im.get_pixel_color(0,0) + eq_(c.r,15) + eq_(c.g,33) + eq_(c.b,255) + eq_(c.a,128) + +def test_background(): + im = mapnik.Image(256,256) + eq_(im.premultiplied(), False) + im.background(mapnik.Color(32,64,125,128)) + eq_(im.premultiplied(), False) + c = im.get_pixel_color(0,0) + eq_(c.get_premultiplied(), False) + eq_(c.r,32) + eq_(c.g,64) + eq_(c.b,125) + eq_(c.a,128) + # Now again with a premultiplied alpha + im.background(mapnik.Color(32,64,125,128,True)) + eq_(im.premultiplied(), True) + c = im.get_pixel_color(0,0) + eq_(c.get_premultiplied(), True) + eq_(c.r,32) + eq_(c.g,64) + eq_(c.b,125) + eq_(c.a,128) + +def test_set_and_get_pixel(): + # Create an image that is not premultiplied + im = mapnik.Image(256,256) + c0 = mapnik.Color(16,33,255,128) + c0_pre = mapnik.Color(16,33,255,128, True) + im.set_pixel(0,0,c0) + im.set_pixel(1,1,c0_pre) + # No differences for non premultiplied pixels + c1_int = mapnik.Color(im.get_pixel(0,0)) + eq_(c0.r, c1_int.r) + eq_(c0.g, c1_int.g) + eq_(c0.b, c1_int.b) + eq_(c0.a, c1_int.a) + c1 = im.get_pixel_color(0,0) + eq_(c0.r, c1.r) + eq_(c0.g, c1.g) + eq_(c0.b, c1.b) + eq_(c0.a, c1.a) + # The premultiplied Color should be demultiplied before being applied. + c0_pre.demultiply() + c1_int = mapnik.Color(im.get_pixel(1,1)) + eq_(c0_pre.r, c1_int.r) + eq_(c0_pre.g, c1_int.g) + eq_(c0_pre.b, c1_int.b) + eq_(c0_pre.a, c1_int.a) + c1 = im.get_pixel_color(1,1) + eq_(c0_pre.r, c1.r) + eq_(c0_pre.g, c1.g) + eq_(c0_pre.b, c1.b) + eq_(c0_pre.a, c1.a) + + # Now create a new image that is premultiplied + im = mapnik.Image(256,256, mapnik.ImageType.rgba8, True, True) + c0 = mapnik.Color(16,33,255,128) + c0_pre = mapnik.Color(16,33,255,128, True) + im.set_pixel(0,0,c0) + im.set_pixel(1,1,c0_pre) + # It should have put pixels that are the same as premultiplied so premultiply c0 + c0.premultiply() + c1_int = mapnik.Color(im.get_pixel(0,0)) + eq_(c0.r, c1_int.r) + eq_(c0.g, c1_int.g) + eq_(c0.b, c1_int.b) + eq_(c0.a, c1_int.a) + c1 = im.get_pixel_color(0,0) + eq_(c0.r, c1.r) + eq_(c0.g, c1.g) + eq_(c0.b, c1.b) + eq_(c0.a, c1.a) + # The premultiplied Color should be the same though + c1_int = mapnik.Color(im.get_pixel(1,1)) + eq_(c0_pre.r, c1_int.r) + eq_(c0_pre.g, c1_int.g) + eq_(c0_pre.b, c1_int.b) + eq_(c0_pre.a, c1_int.a) + c1 = im.get_pixel_color(1,1) + eq_(c0_pre.r, c1.r) + eq_(c0_pre.g, c1.g) + eq_(c0_pre.b, c1.b) + eq_(c0_pre.a, c1.a) + +@raises(IndexError) +def test_set_pixel_out_of_range_1(): + im = mapnik.Image(4,4) + c = mapnik.Color('blue') + im.set_pixel(5,5,c) + +@raises(OverflowError) +def test_set_pixel_out_of_range_2(): + im = mapnik.Image(4,4) + c = mapnik.Color('blue') + im.set_pixel(-1,1,c) + +@raises(IndexError) +def test_get_pixel_out_of_range_1(): + im = mapnik.Image(4,4) + c = im.get_pixel(5,5) + +@raises(OverflowError) +def test_get_pixel_out_of_range_2(): + im = mapnik.Image(4,4) + c = im.get_pixel(-1,1) + +@raises(IndexError) +def test_get_pixel_color_out_of_range_1(): + im = mapnik.Image(4,4) + c = im.get_pixel_color(5,5) + +@raises(OverflowError) +def test_get_pixel_color_out_of_range_2(): + im = mapnik.Image(4,4) + c = im.get_pixel_color(-1,1) + def test_set_color_to_alpha(): im = mapnik.Image(256,256) im.background(mapnik.Color('rgba(12,12,12,255)')) diff --git a/tests/python_tests/image_tiff_test.py b/tests/python_tests/image_tiff_test.py index 001d297a2..634379fbd 100644 --- a/tests/python_tests/image_tiff_test.py +++ b/tests/python_tests/image_tiff_test.py @@ -45,16 +45,19 @@ def test_tiff_round_trip_stripped(): eq_(im.height(),im3.height()) eq_(len(im.tostring()), org_str) eq_(len(im.tostring()),len(im2.tostring())) - eq_(len(im.tostring('png')),len(im2.tostring('png'))) + # Because one will end up with UNASSOC alpha tag which internally the TIFF will multiply, the first to string will not be the same due to the + # difference in tags. But size should still be the same eq_(len(im.tostring('tiff:method=stripped')),len(im2.tostring('tiff:method=stripped'))) - eq_(len(im.tostring()),len(im3.tostring())) - eq_(len(im.tostring('tiff:method=stripped')),len(im3.tostring('tiff:method=stripped'))) + eq_(len(im2.tostring()),len(im3.tostring())) + # Both of these started out premultiplied, so this round trip should be exactly the same! + eq_(len(im2.tostring('tiff:method=stripped')),len(im3.tostring('tiff:method=stripped'))) def test_tiff_round_trip_rows_stripped(): filepath = '/tmp/mapnik-tiff-io-rows_stripped.tiff' filepath2 = '/tmp/mapnik-tiff-io-rows_stripped2.tiff' im = mapnik.Image(255,267) im.background(mapnik.Color('rgba(12,255,128,.5)')) + #im.premultiply() org_str = len(im.tostring()) im.save(filepath,'tiff:method=stripped:rows_per_strip=8') im2 = mapnik.Image.open(filepath) @@ -66,31 +69,35 @@ def test_tiff_round_trip_rows_stripped(): eq_(im.height(),im3.height()) eq_(len(im.tostring()), org_str) eq_(len(im.tostring()),len(im2.tostring())) + # Because one will end up with UNASSOC alpha tag which internally the TIFF will multiply, the first to string will not be the same due to the + # difference in tags. But size should still be the same eq_(len(im.tostring('tiff:method=stripped:rows_per_strip=8')),len(im2.tostring('tiff:method=stripped:rows_per_strip=8'))) - eq_(len(im.tostring()),len(im3.tostring())) - eq_(len(im.tostring('tiff:method=stripped:rows_per_strip=8')),len(im3.tostring('tiff:method=stripped:rows_per_strip=8'))) + eq_(len(im2.tostring()),len(im3.tostring())) + # Both of these started out premultiplied, so this round trip should be exactly the same! + eq_(len(im2.tostring('tiff:method=stripped:rows_per_strip=8')),len(im3.tostring('tiff:method=stripped:rows_per_strip=8'))) def test_tiff_round_trip_buffered_tiled(): filepath = '/tmp/mapnik-tiff-io-buffered-tiled.tiff' filepath2 = '/tmp/mapnik-tiff-io-buffered-tiled2.tiff' + filepath3 = '/tmp/mapnik-tiff-io-buffered-tiled3.tiff' im = mapnik.Image(255,267) - #im = mapnik.Image(256,256) - im.background(mapnik.Color('rgba(1,255,128,.5)')) + im.background(mapnik.Color('rgba(33,255,128,.5)')) im.save(filepath,'tiff:method=tiled:tile_width=32:tile_height=32') im2 = mapnik.Image.open(filepath) im3 = mapnik.Image.fromstring(open(filepath,'r').read()) im2.save(filepath2, 'tiff:method=tiled:tile_width=32:tile_height=32') - im4 = mapnik.Image.open(filepath2) + im3.save(filepath3, 'tiff:method=tiled:tile_width=32:tile_height=32') eq_(im.width(),im2.width()) eq_(im.height(),im2.height()) eq_(im.width(),im3.width()) eq_(im.height(),im3.height()) - eq_(len(im2.tostring()),len(im4.tostring())) - eq_(len(im2.tostring('tiff:method=tiled:tile_width=32:tile_height=32')),len(im4.tostring('tiff:method=tiled:tile_width=32:tile_height=32'))) eq_(len(im.tostring()),len(im2.tostring())) + # Because one will end up with UNASSOC alpha tag which internally the TIFF will multiply, the first to string will not be the same due to the + # difference in tags. But size should still be the same eq_(len(im.tostring('tiff:method=tiled:tile_width=32:tile_height=32')),len(im2.tostring('tiff:method=tiled:tile_width=32:tile_height=32'))) - eq_(len(im.tostring()),len(im3.tostring())) - eq_(len(im.tostring('tiff:method=tiled:tile_width=32:tile_height=32')),len(im3.tostring('tiff:method=tiled:tile_width=32:tile_height=32'))) + eq_(len(im2.tostring()),len(im3.tostring())) + # Both of these started out premultiplied, so this round trip should be exactly the same! + eq_(len(im2.tostring('tiff:method=tiled:tile_width=32:tile_height=32')),len(im3.tostring('tiff:method=tiled:tile_width=32:tile_height=32'))) def test_tiff_round_trip_tiled(): filepath = '/tmp/mapnik-tiff-io-tiled.tiff' @@ -104,9 +111,12 @@ def test_tiff_round_trip_tiled(): eq_(im.width(),im3.width()) eq_(im.height(),im3.height()) eq_(len(im.tostring()),len(im2.tostring())) + # Because one will end up with UNASSOC alpha tag which internally the TIFF will multiply, the first to string will not be the same due to the + # difference in tags. HOWEVER for this type they have the same size? Perhaps is a bad test? eq_(len(im.tostring('tiff:method=tiled')),len(im2.tostring('tiff:method=tiled'))) - eq_(len(im.tostring()),len(im3.tostring())) - eq_(len(im.tostring('tiff:method=tiled')),len(im3.tostring('tiff:method=tiled'))) + eq_(len(im2.tostring()),len(im3.tostring())) + # Both of these started out premultiplied, so this round trip should be exactly the same! + eq_(len(im2.tostring('tiff:method=tiled')),len(im3.tostring('tiff:method=tiled'))) def test_tiff_rgb8_compare():