Changed the way that set alpha worked, renaming old method to multiply_alpha, added new set_alpha, that simply sets the alpha. Added protection for overflows and underflows. Added unit tests to cover all code
This commit is contained in:
parent
1ca5ae4446
commit
f54164da75
4 changed files with 311 additions and 1 deletions
|
@ -147,6 +147,12 @@ MAPNIK_DECL void set_alpha (image_any & image, float opacity);
|
|||
template <typename T>
|
||||
MAPNIK_DECL void set_alpha (T & image, float opacity);
|
||||
|
||||
// MULTIPLY ALPHA
|
||||
MAPNIK_DECL void multiply_alpha (image_any & image, float opacity);
|
||||
|
||||
template <typename T>
|
||||
MAPNIK_DECL void multiply_alpha (T & image, float opacity);
|
||||
|
||||
// SET GRAYSCALE TO ALPHA
|
||||
MAPNIK_DECL void set_grayscale_to_alpha (image_any & image);
|
||||
MAPNIK_DECL void set_grayscale_to_alpha (image_any & image, color const& c);
|
||||
|
|
|
@ -632,6 +632,19 @@ struct visitor_set_alpha
|
|||
void operator() (image_rgba8 & data)
|
||||
{
|
||||
using pixel_type = image_rgba8::pixel_type;
|
||||
pixel_type a1;
|
||||
try
|
||||
{
|
||||
a1 = numeric_cast<std::uint8_t>(255.0 * opacity_);
|
||||
}
|
||||
catch(negative_overflow&)
|
||||
{
|
||||
a1 = std::numeric_limits<std::uint8_t>::min();
|
||||
}
|
||||
catch(positive_overflow&)
|
||||
{
|
||||
a1 = std::numeric_limits<std::uint8_t>::max();
|
||||
}
|
||||
for (unsigned int y = 0; y < data.height(); ++y)
|
||||
{
|
||||
pixel_type* row_to = data.get_row(y);
|
||||
|
@ -639,7 +652,6 @@ struct visitor_set_alpha
|
|||
{
|
||||
pixel_type rgba = row_to[x];
|
||||
pixel_type a0 = (rgba >> 24) & 0xff;
|
||||
pixel_type a1 = pixel_type( ((rgba >> 24) & 0xff) * opacity_ );
|
||||
//unsigned a1 = opacity;
|
||||
if (a0 == a1) continue;
|
||||
|
||||
|
@ -703,6 +715,97 @@ template MAPNIK_DECL void set_alpha(image_gray64f &, float);
|
|||
|
||||
namespace detail {
|
||||
|
||||
struct visitor_multiply_alpha
|
||||
{
|
||||
visitor_multiply_alpha(float opacity)
|
||||
: opacity_(opacity) {}
|
||||
|
||||
void operator() (image_rgba8 & data)
|
||||
{
|
||||
using pixel_type = image_rgba8::pixel_type;
|
||||
for (unsigned int y = 0; y < data.height(); ++y)
|
||||
{
|
||||
pixel_type* row_to = data.get_row(y);
|
||||
for (unsigned int x = 0; x < data.width(); ++x)
|
||||
{
|
||||
pixel_type rgba = row_to[x];
|
||||
pixel_type a0 = (rgba >> 24) & 0xff;
|
||||
pixel_type a1;
|
||||
try
|
||||
{
|
||||
a1 = numeric_cast<std::uint8_t>(((rgba >> 24) & 0xff) * opacity_);
|
||||
}
|
||||
catch(negative_overflow&)
|
||||
{
|
||||
a1 = std::numeric_limits<std::uint8_t>::min();
|
||||
}
|
||||
catch(positive_overflow&)
|
||||
{
|
||||
a1 = std::numeric_limits<std::uint8_t>::max();
|
||||
}
|
||||
//unsigned a1 = opacity;
|
||||
if (a0 == a1) continue;
|
||||
|
||||
pixel_type r = rgba & 0xff;
|
||||
pixel_type g = (rgba >> 8 ) & 0xff;
|
||||
pixel_type b = (rgba >> 16) & 0xff;
|
||||
|
||||
row_to[x] = (a1 << 24)| (b << 16) | (g << 8) | (r) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator() (T & data)
|
||||
{
|
||||
throw std::runtime_error("Error: multiply_alpha with " + std::string(typeid(data).name()) + " is not supported");
|
||||
}
|
||||
|
||||
private:
|
||||
float opacity_;
|
||||
|
||||
};
|
||||
|
||||
} // end detail ns
|
||||
|
||||
MAPNIK_DECL void multiply_alpha(image_any & data, float opacity)
|
||||
{
|
||||
// Prior to calling the data must not be premultiplied
|
||||
bool remultiply = mapnik::demultiply_alpha(data);
|
||||
util::apply_visitor(detail::visitor_multiply_alpha(opacity), data);
|
||||
if (remultiply)
|
||||
{
|
||||
mapnik::premultiply_alpha(data);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
MAPNIK_DECL void multiply_alpha(T & data, float opacity)
|
||||
{
|
||||
// Prior to calling the data must not be premultiplied
|
||||
bool remultiply = mapnik::demultiply_alpha(data);
|
||||
detail::visitor_multiply_alpha visit(opacity);
|
||||
visit(data);
|
||||
if (remultiply)
|
||||
{
|
||||
mapnik::premultiply_alpha(data);
|
||||
}
|
||||
}
|
||||
|
||||
template MAPNIK_DECL void multiply_alpha(image_rgba8 &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray8 &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray8s &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray16 &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray16s &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray32 &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray32s &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray32f &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray64 &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray64s &, float);
|
||||
template MAPNIK_DECL void multiply_alpha(image_gray64f &, float);
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct visitor_set_grayscale_to_alpha
|
||||
{
|
||||
void operator() (image_rgba8 & data)
|
||||
|
|
100
test/unit/imaging/image_multiply_alpha.cpp
Normal file
100
test/unit/imaging/image_multiply_alpha.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
#include "catch.hpp"
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/image_any.hpp>
|
||||
#include <mapnik/color.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
|
||||
TEST_CASE("image multiply_alpha") {
|
||||
|
||||
SECTION("test rgba8") {
|
||||
|
||||
mapnik::image_rgba8 im(4,4);
|
||||
mapnik::image_rgba8 im2(4,4,true,true); // Initialize as already premultiplied
|
||||
mapnik::image_any im_any(mapnik::image_rgba8(4,4));
|
||||
mapnik::image_any im2_any(mapnik::image_rgba8(4,4,true,true));
|
||||
|
||||
// Fill the images with meaningfull values
|
||||
mapnik::color c1(57,70,128,128); // This color is not premultiplied
|
||||
mapnik::color c2(57,70,128,128, true); // This color is premultiplied
|
||||
mapnik::fill(im, c1); // Because c1 is not premultiplied it will make the image not premultiplied
|
||||
mapnik::fill(im_any, c1); // Because c1 is not premultiplied it will make the image not premultiplied
|
||||
mapnik::fill(im2, c2); // Because c1 is premultiplied it will make the image premultiplied
|
||||
mapnik::fill(im2_any, c2); // Because c1 is premultiplied it will make the image premultiplied
|
||||
|
||||
mapnik::multiply_alpha(im, 0.75);
|
||||
mapnik::multiply_alpha(im_any, 0.75);
|
||||
mapnik::multiply_alpha(im2, 0.75);
|
||||
mapnik::multiply_alpha(im2_any, 0.75);
|
||||
|
||||
mapnik::color out;
|
||||
// This should have only changed the alpha, as it was not premultipleid
|
||||
out = mapnik::get_pixel<mapnik::color>(im, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 57);
|
||||
CHECK(static_cast<int>(out.green()) == 70);
|
||||
CHECK(static_cast<int>(out.blue()) == 128);
|
||||
CHECK(static_cast<int>(out.alpha()) == 96);
|
||||
out = mapnik::get_pixel<mapnik::color>(im_any, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 57);
|
||||
CHECK(static_cast<int>(out.green()) == 70);
|
||||
CHECK(static_cast<int>(out.blue()) == 128);
|
||||
CHECK(static_cast<int>(out.alpha()) == 96);
|
||||
// This will be different because it is demultiplied then premultiplied again after setting alpha
|
||||
out = mapnik::get_pixel<mapnik::color>(im2, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 43);
|
||||
CHECK(static_cast<int>(out.green()) == 53);
|
||||
CHECK(static_cast<int>(out.blue()) == 96);
|
||||
CHECK(static_cast<int>(out.alpha()) == 96);
|
||||
out = mapnik::get_pixel<mapnik::color>(im2_any, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 43);
|
||||
CHECK(static_cast<int>(out.green()) == 53);
|
||||
CHECK(static_cast<int>(out.blue()) == 96);
|
||||
CHECK(static_cast<int>(out.alpha()) == 96);
|
||||
|
||||
} // END SECTION
|
||||
|
||||
SECTION("test rgba8 overflow") {
|
||||
|
||||
mapnik::image_rgba8 im(4,4);
|
||||
mapnik::color c(128,128,128,128); // This color is premultiplied
|
||||
mapnik::fill(im, c); // Because c1 is not premultiplied it will make the image not premultiplied
|
||||
|
||||
mapnik::multiply_alpha(im, 2.5);
|
||||
|
||||
mapnik::color out;
|
||||
out = mapnik::get_pixel<mapnik::color>(im, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 128);
|
||||
CHECK(static_cast<int>(out.green()) == 128);
|
||||
CHECK(static_cast<int>(out.blue()) == 128);
|
||||
CHECK(static_cast<int>(out.alpha()) == 255);
|
||||
|
||||
} // END SECTION
|
||||
|
||||
SECTION("test rgba8 underflow") {
|
||||
|
||||
mapnik::image_rgba8 im(4,4);
|
||||
mapnik::color c(128,128,128,128); // This color is premultiplied
|
||||
mapnik::fill(im, c); // Because c1 is not premultiplied it will make the image not premultiplied
|
||||
|
||||
mapnik::multiply_alpha(im, -2.5);
|
||||
|
||||
mapnik::color out;
|
||||
out = mapnik::get_pixel<mapnik::color>(im, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 128);
|
||||
CHECK(static_cast<int>(out.green()) == 128);
|
||||
CHECK(static_cast<int>(out.blue()) == 128);
|
||||
CHECK(static_cast<int>(out.alpha()) == 0);
|
||||
|
||||
} // END SECTION
|
||||
|
||||
SECTION("test gray8") {
|
||||
|
||||
mapnik::image_gray8 im(4,4);
|
||||
mapnik::image_any im_any(mapnik::image_gray8(4,4));
|
||||
|
||||
CHECK_THROWS(mapnik::multiply_alpha(im, 0.25));
|
||||
CHECK_THROWS(mapnik::multiply_alpha(im_any, 0.25));
|
||||
|
||||
} // END SECTION
|
||||
} // END TEST_CASE
|
||||
|
101
test/unit/imaging/image_set_alpha.cpp
Normal file
101
test/unit/imaging/image_set_alpha.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#include "catch.hpp"
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/image_any.hpp>
|
||||
#include <mapnik/color.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
|
||||
TEST_CASE("image set_alpha") {
|
||||
|
||||
SECTION("test rgba8") {
|
||||
|
||||
mapnik::image_rgba8 im(4,4);
|
||||
mapnik::image_rgba8 im2(4,4,true,true); // Initialize as already premultiplied
|
||||
mapnik::image_any im_any(mapnik::image_rgba8(4,4));
|
||||
mapnik::image_any im2_any(mapnik::image_rgba8(4,4,true,true));
|
||||
|
||||
// Fill the images with meaningfull values
|
||||
mapnik::color c1(57,70,128,128); // This color is not premultiplied
|
||||
mapnik::color c2(57,70,128,128, true); // This color is premultiplied
|
||||
mapnik::fill(im, c1); // Because c1 is not premultiplied it will make the image not premultiplied
|
||||
mapnik::fill(im_any, c1); // Because c1 is not premultiplied it will make the image not premultiplied
|
||||
mapnik::fill(im2, c2); // Because c1 is premultiplied it will make the image premultiplied
|
||||
mapnik::fill(im2_any, c2); // Because c1 is premultiplied it will make the image premultiplied
|
||||
|
||||
mapnik::set_alpha(im, 0.25);
|
||||
mapnik::set_alpha(im_any, 0.25);
|
||||
mapnik::set_alpha(im2, 0.25);
|
||||
mapnik::set_alpha(im2_any, 0.25);
|
||||
|
||||
|
||||
mapnik::color out;
|
||||
// This should have only changed the alpha, as it was not premultipleid
|
||||
out = mapnik::get_pixel<mapnik::color>(im, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 57);
|
||||
CHECK(static_cast<int>(out.green()) == 70);
|
||||
CHECK(static_cast<int>(out.blue()) == 128);
|
||||
CHECK(static_cast<int>(out.alpha()) == 63);
|
||||
out = mapnik::get_pixel<mapnik::color>(im_any, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 57);
|
||||
CHECK(static_cast<int>(out.green()) == 70);
|
||||
CHECK(static_cast<int>(out.blue()) == 128);
|
||||
CHECK(static_cast<int>(out.alpha()) == 63);
|
||||
// This will be different because it is demultiplied then premultiplied again after setting alpha
|
||||
out = mapnik::get_pixel<mapnik::color>(im2, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 28);
|
||||
CHECK(static_cast<int>(out.green()) == 35);
|
||||
CHECK(static_cast<int>(out.blue()) == 63);
|
||||
CHECK(static_cast<int>(out.alpha()) == 63);
|
||||
out = mapnik::get_pixel<mapnik::color>(im2_any, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 28);
|
||||
CHECK(static_cast<int>(out.green()) == 35);
|
||||
CHECK(static_cast<int>(out.blue()) == 63);
|
||||
CHECK(static_cast<int>(out.alpha()) == 63);
|
||||
|
||||
} // END SECTION
|
||||
|
||||
SECTION("test rgba8 overflow") {
|
||||
|
||||
mapnik::image_rgba8 im(4,4);
|
||||
mapnik::color c(128,128,128,128); // This color is premultiplied
|
||||
mapnik::fill(im, c); // Because c1 is not premultiplied it will make the image not premultiplied
|
||||
|
||||
mapnik::set_alpha(im, 1.25);
|
||||
|
||||
mapnik::color out;
|
||||
out = mapnik::get_pixel<mapnik::color>(im, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 128);
|
||||
CHECK(static_cast<int>(out.green()) == 128);
|
||||
CHECK(static_cast<int>(out.blue()) == 128);
|
||||
CHECK(static_cast<int>(out.alpha()) == 255);
|
||||
|
||||
} // END SECTION
|
||||
|
||||
SECTION("test rgba8 underflow") {
|
||||
|
||||
mapnik::image_rgba8 im(4,4);
|
||||
mapnik::color c(128,128,128,128); // This color is premultiplied
|
||||
mapnik::fill(im, c); // Because c1 is not premultiplied it will make the image not premultiplied
|
||||
|
||||
mapnik::set_alpha(im, -1.25);
|
||||
|
||||
mapnik::color out;
|
||||
out = mapnik::get_pixel<mapnik::color>(im, 0, 0);
|
||||
CHECK(static_cast<int>(out.red()) == 128);
|
||||
CHECK(static_cast<int>(out.green()) == 128);
|
||||
CHECK(static_cast<int>(out.blue()) == 128);
|
||||
CHECK(static_cast<int>(out.alpha()) == 0);
|
||||
|
||||
} // END SECTION
|
||||
|
||||
SECTION("test gray8") {
|
||||
|
||||
mapnik::image_gray8 im(4,4);
|
||||
mapnik::image_any im_any(mapnik::image_gray8(4,4));
|
||||
|
||||
CHECK_THROWS(mapnik::set_alpha(im, 0.25));
|
||||
CHECK_THROWS(mapnik::set_alpha(im_any, 0.25));
|
||||
|
||||
} // END SECTION
|
||||
|
||||
} // END TEST_CASE
|
Loading…
Add table
Reference in a new issue