/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2024 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
// mapnik
#include
#include
#include
#include
#include
#include
#include
#include
//stl
#include
//pybind11
#include
#include
#include
using mapnik::image_any;
using mapnik::image_reader;
using mapnik::get_image_reader;
using mapnik::type_from_filename;
using mapnik::save_to_file;
namespace py = pybind11;
namespace {
// output 'raw' pixels
py::object to_string1(image_any const& im)
{
return py::bytes(reinterpret_cast(im.bytes()), im.size());
}
// encode (png,jpeg)
py::object to_string2(image_any const & im, std::string const& format)
{
std::string s = mapnik::save_to_string(im, format);
return py::bytes(s.data(), s.length());
}
py::object to_string3(image_any const & im, std::string const& format, mapnik::rgba_palette const& pal)
{
std::string s = mapnik::save_to_string(im, format, pal);
return py::bytes(s.data(), s.length());
}
void save_to_file1(mapnik::image_any const& im, std::string const& filename)
{
save_to_file(im,filename);
}
void save_to_file2(mapnik::image_any const& im, std::string const& filename, std::string const& type)
{
save_to_file(im,filename,type);
}
void save_to_file3(mapnik::image_any const& im, std::string const& filename, std::string const& type, mapnik::rgba_palette const& pal)
{
save_to_file(im,filename,type,pal);
}
mapnik::image_view_any get_view(mapnik::image_any const& data,unsigned x,unsigned y, unsigned w,unsigned h)
{
return mapnik::create_view(data,x,y,w,h);
}
bool is_solid(mapnik::image_any const& im)
{
return mapnik::is_solid(im);
}
void fill_color(mapnik::image_any & im, mapnik::color const& c)
{
mapnik::fill(im, c);
}
void fill_int(mapnik::image_any & im, int val)
{
mapnik::fill(im, val);
}
void fill_double(mapnik::image_any & im, double val)
{
mapnik::fill(im, val);
}
std::shared_ptr copy(mapnik::image_any const& im, mapnik::image_dtype type, double offset, double scaling)
{
return std::make_shared(mapnik::image_copy(im, type, offset, scaling));
}
std::size_t compare(mapnik::image_any const& im1, mapnik::image_any const& im2, double threshold, bool alpha)
{
return mapnik::compare(im1, im2, threshold, alpha);
}
struct get_pixel_visitor
{
get_pixel_visitor(unsigned x, unsigned y)
: x_(x), y_(y) {}
py::object operator() (mapnik::image_null const&)
{
throw std::runtime_error("Can not return a null image from a pixel (shouldn't have reached here)");
}
template
py::object operator() (T const& im)
{
using pixel_type = typename T::pixel_type;
using python_type = typename std::conditional::type;
return python_type(mapnik::get_pixel(im, x_, y_));
}
private:
unsigned x_;
unsigned y_;
};
py::object get_pixel(mapnik::image_any const& im, int x, int y)
{
if (x < 0 || x >= static_cast(im.width()) ||
y < 0 || y >= static_cast(im.height()))
{
throw std::out_of_range("invalid x,y for image dimensions");
}
return mapnik::util::apply_visitor(get_pixel_visitor(x, y), im);
}
mapnik::color get_pixel_color(mapnik::image_any const& im, int x, int y)
{
if (x < 0 || x >= static_cast(im.width()) ||
y < 0 || y >= static_cast(im.height()))
{
throw std::out_of_range("invalid x,y for image dimensions");
}
return mapnik::get_pixel(im, x, y);
}
template
void set_pixel(mapnik::image_any & im, int x, int y, T c)
{
if (x < 0 || x >= static_cast(im.width()) ||
y < 0 || y >= static_cast(im.height()))
{
throw std::out_of_range("invalid x,y for image dimensions");
}
mapnik::set_pixel(im, x, y, c);
}
mapnik::image_dtype get_type(mapnik::image_any & im)
{
return im.get_dtype();
}
std::shared_ptr open_from_file(std::string const& filename)
{
auto type = type_from_filename(filename);
if (type)
{
std::unique_ptr reader(get_image_reader(filename,*type));
if (reader.get())
{
return std::make_shared(reader->read(0,0,reader->width(),reader->height()));
}
throw mapnik::image_reader_exception("Failed to load: " + filename);
}
throw mapnik::image_reader_exception("Unsupported image format:" + filename);
}
std::shared_ptr open_from_file2(py::args const& args)
{
auto filename = args[0].cast();
std::uint32_t x0 = args[1].cast();
std::uint32_t y0 = args[2].cast();
std::uint32_t width = args[3].cast();
std::uint32_t height = args[4].cast();
auto type = type_from_filename(filename);
if (type)
{
std::unique_ptr reader(get_image_reader(filename,*type));
if (reader.get())
{
return std::make_shared(reader->read(x0, y0, width, height));
}
throw mapnik::image_reader_exception("Failed to load: " + filename);
}
throw mapnik::image_reader_exception("Unsupported image format:" + filename);
}
std::shared_ptr from_string(std::string const& str)
{
std::unique_ptr reader(get_image_reader(str.c_str(),str.size()));
if (reader.get())
{
return std::make_shared(reader->read(0,0,reader->width(), reader->height()));
}
throw mapnik::image_reader_exception("Failed to load image from String" );
}
std::shared_ptr from_buffer(py::bytes const& obj)
{
std::string_view view = std::string_view(obj);
std::unique_ptr reader
(get_image_reader(reinterpret_cast(view.data()), view.length()));
if (reader.get())
{
return std::make_shared(reader->read(0, 0, reader->width(), reader->height()));
}
throw mapnik::image_reader_exception("Failed to load image from Buffer" );
}
std::shared_ptr from_memoryview(py::memoryview const& memview)
{
auto buf = py::buffer(memview);
py::buffer_info info = buf.request();
std::unique_ptr reader
(get_image_reader(reinterpret_cast(info.ptr), info.size));
if (reader.get())
{
return std::make_shared(reader->read(0, 0, reader->width(), reader->height()));
}
throw mapnik::image_reader_exception("Failed to load image from Buffer" );
}
void set_grayscale_to_alpha(image_any & im)
{
mapnik::set_grayscale_to_alpha(im);
}
void set_grayscale_to_alpha_c(image_any & im, mapnik::color const& c)
{
mapnik::set_grayscale_to_alpha(im, c);
}
void set_color_to_alpha(image_any & im, mapnik::color const& c)
{
mapnik::set_color_to_alpha(im, c);
}
void apply_opacity(image_any & im, float opacity)
{
mapnik::apply_opacity(im, opacity);
}
bool premultiplied(image_any &im)
{
return im.get_premultiplied();
}
bool premultiply(image_any & im)
{
return mapnik::premultiply_alpha(im);
}
bool demultiply(image_any & im)
{
return mapnik::demultiply_alpha(im);
}
void clear(image_any & im)
{
mapnik::fill(im, 0);
}
void composite(image_any & dst, image_any & src, mapnik::composite_mode_e mode, float opacity, int dx, int dy)
{
bool demultiply_dst = mapnik::premultiply_alpha(dst);
bool demultiply_src = mapnik::premultiply_alpha(src);
mapnik::composite(dst,src,mode,opacity,dx,dy);
if (demultiply_dst)
{
mapnik::demultiply_alpha(dst);
}
if (demultiply_src)
{
mapnik::demultiply_alpha(src);
}
}
std::shared_ptr from_cairo(py::object const& surface)
{
py::object ImageSurface = py::module_::import("cairo").attr("ImageSurface");
py::object get_width = ImageSurface.attr("get_width");
py::object get_height = ImageSurface.attr("get_height");
py::object get_format = ImageSurface.attr("get_format");
py::object get_data = ImageSurface.attr("get_data");
int format = py::int_(get_format(surface));
int width = py::int_(get_width(surface));
int height = py::int_(get_height(surface));
if (format == 0 ) // cairo.Format.ARGB32
{
mapnik::image_rgba8 image{width, height};
py::memoryview view = get_data(surface);
auto buf = py::buffer(view);
py::buffer_info info = buf.request();
const std::unique_ptr out_row(new unsigned int[width]);
unsigned int const* in_row = reinterpret_cast(info.ptr);
for (int row = 0; row < height; row++, in_row += width)
{
for (int column = 0; column < width; column++)
{
unsigned int in = in_row[column];
unsigned int a = (in >> 24) & 0xff;
unsigned int r = (in >> 16) & 0xff;
unsigned int g = (in >> 8) & 0xff;
unsigned int b = (in >> 0) & 0xff;
out_row[column] = mapnik::color(r, g, b, a).rgba();
}
image.set_row(row, out_row.get(), width);
}
return std::make_shared(std::move(image));
}
else if (format == 1 ) // cairo.Format.RGB24
{
mapnik::image_rgba8 image{width, height};
py::memoryview view = get_data(surface);
auto buf = py::buffer(view);
py::buffer_info info = buf.request();
const std::unique_ptr out_row(new unsigned int[width]);
unsigned int const* in_row = reinterpret_cast(info.ptr);
for (int row = 0; row < height; row++, in_row += width)
{
for (int column = 0; column < width; column++)
{
unsigned int in = in_row[column];
unsigned int r = (in >> 16) & 0xff;
unsigned int g = (in >> 8) & 0xff;
unsigned int b = (in >> 0) & 0xff;
out_row[column] = mapnik::color(r, g, b, 255).rgba();
}
image.set_row(row, out_row.get(), width);
}
return std::make_shared(std::move(image));
}
throw std::runtime_error("Unable to convert this Cairo format to rgba8 image");
}
} // namespace
void export_image(py::module const& m)
{
py::native_enum(m, "ImageType", "enum.Enum")
.value("rgba8", mapnik::image_dtype_rgba8)
.value("gray8", mapnik::image_dtype_gray8)
.value("gray8s", mapnik::image_dtype_gray8s)
.value("gray16", mapnik::image_dtype_gray16)
.value("gray16s", mapnik::image_dtype_gray16s)
.value("gray32", mapnik::image_dtype_gray32)
.value("gray32s", mapnik::image_dtype_gray32s)
.value("gray32f", mapnik::image_dtype_gray32f)
.value("gray64", mapnik::image_dtype_gray64)
.value("gray64s", mapnik::image_dtype_gray64s)
.value("gray64f", mapnik::image_dtype_gray64f)
.finalize()
;
py::class_(m, "Image","This class represents a image.")
.def(py::init())
.def(py::init())
.def(py::init())
.def(py::init())
.def(py::init())
.def("width",&image_any::width)
.def("height",&image_any::height)
.def("view",&get_view)
.def("painted",&image_any::painted)
.def("is_solid",&is_solid)
.def("fill",&fill_color)
.def("fill",&fill_int)
.def("fill",&fill_double)
.def("set_grayscale_to_alpha",&set_grayscale_to_alpha, "Set the grayscale values to the alpha channel of the Image")
.def("set_grayscale_to_alpha",&set_grayscale_to_alpha_c, "Set the grayscale values to the alpha channel of the Image")
.def("set_color_to_alpha",&set_color_to_alpha, "Set a given color to the alpha channel of the Image")
.def("apply_opacity",&apply_opacity, "Set the opacity of the Image relative to the current alpha of each pixel.")
.def("composite",&composite,
py::arg("image"),
py::arg("mode") = mapnik::src_over,
py::arg("opacity") = 1.0f,
py::arg("dx") = 0,
py::arg("dy") = 0
)
.def("compare",&compare,
py::arg("image"),
py::arg("threshold")=0.0,
py::arg("alpha")=true
)
.def("copy",©,
py::arg("type"),
py::arg("offset")=0.0,
py::arg("scaling")=1.0
)
.def_property("offset",
&image_any::get_offset,
&image_any::set_offset,
"Gets or sets the offset component.\n")
.def_property("scaling",
&image_any::get_scaling,
&image_any::set_scaling,
"Gets or sets the offset component.\n")
.def("premultiplied",&premultiplied)
.def("premultiply",&premultiply)
.def("demultiply",&demultiply)
.def("set_pixel",&set_pixel)
.def("set_pixel",&set_pixel)
.def("set_pixel",&set_pixel)
.def("get_pixel_color",&get_pixel_color,
py::arg("x"), py::arg("y"))
.def("get_pixel", &get_pixel)
.def("get_type",&get_type)
.def("clear",&clear)
.def("to_string",&to_string1)
.def("to_string",&to_string2)
.def("to_string",&to_string3)
.def("save", &save_to_file1)
.def("save", &save_to_file2)
.def("save", &save_to_file3)
.def_static("open",open_from_file)
.def_static("open",open_from_file2)
.def_static("from_buffer",&from_buffer)
.def_static("from_memoryview",&from_memoryview)
.def_static("from_string",&from_string)
.def_static("from_cairo",&from_cairo)
;
}