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:
Blake Thompson 2015-05-11 13:12:13 -05:00
parent 1ca5ae4446
commit f54164da75
4 changed files with 311 additions and 1 deletions

View file

@ -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);

View file

@ -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)

View 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

View 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