diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index ce88f504dc1e..3460b429ec12 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -29,6 +29,16 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) lastclippath(NULL), _fill_color(agg::rgba(1, 1, 1, 0)) { + if (dpi <= 0.0) { + throw std::range_error("dpi must be positive"); + } + + if (width >= 1 << 16 || height >= 1 << 16) { + throw std::range_error( + "Image size of " + std::to_string(width) + "x" + std::to_string(height) + + " pixels is too large. It must be less than 2^16 in each direction."); + } + unsigned stride(width * 4); pixBuffer = new agg::int8u[NUMBYTES]; diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 470d459de341..6325df357b1b 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -6,8 +6,11 @@ #ifndef MPL_BACKEND_AGG_H #define MPL_BACKEND_AGG_H +#include + #include #include +#include #include "agg_alpha_mask_u8.h" #include "agg_conv_curve.h" @@ -40,6 +43,8 @@ #include "array.h" #include "agg_workaround.h" +namespace py = pybind11; + /**********************************************************************/ // a helper class to pass agg::buffer objects around. @@ -728,7 +733,7 @@ inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, in rendererBase.reset_clipping(true); if (angle != 0.0) { agg::rendering_buffer srcbuf( - image.data(), (unsigned)image.shape(1), + image.mutable_data(0, 0), (unsigned)image.shape(1), (unsigned)image.shape(0), (unsigned)image.shape(1)); agg::pixfmt_gray8 pixf_img(srcbuf); @@ -828,8 +833,9 @@ inline void RendererAgg::draw_image(GCAgg &gc, bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); agg::rendering_buffer buffer; - buffer.attach( - image.data(), (unsigned)image.shape(1), (unsigned)image.shape(0), -(int)image.shape(1) * 4); + buffer.attach(image.mutable_data(0, 0, 0), + (unsigned)image.shape(1), (unsigned)image.shape(0), + -(int)image.shape(1) * 4); pixfmt pixf(buffer); if (has_clippath) { @@ -1226,14 +1232,27 @@ inline void RendererAgg::draw_gouraud_triangles(GCAgg &gc, ColorArray &colors, agg::trans_affine &trans) { + if (points.shape(0) && !check_trailing_shape(points, "points", 3, 2)) { + throw py::error_already_set(); + } + if (colors.shape(0) && !check_trailing_shape(colors, "colors", 3, 4)) { + throw py::error_already_set(); + } + if (points.shape(0) != colors.shape(0)) { + throw py::value_error( + "points and colors arrays must be the same length, got " + + std::to_string(points.shape(0)) + " points and " + + std::to_string(colors.shape(0)) + "colors"); + } + theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); set_clipbox(gc.cliprect, theRasterizer); bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); for (int i = 0; i < points.shape(0); ++i) { - typename PointArray::sub_t point = points.subarray(i); - typename ColorArray::sub_t color = colors.subarray(i); + auto point = std::bind(points, i, std::placeholders::_1, std::placeholders::_2); + auto color = std::bind(colors, i, std::placeholders::_1, std::placeholders::_2); _draw_gouraud_triangle(point, color, trans, has_clippath); } diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index eaf4bf6f5f9d..79cab02e419d 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -1,566 +1,300 @@ +#include +#include +#include #include "mplutils.h" #include "numpy_cpp.h" #include "py_converters.h" #include "_backend_agg.h" +#include "py_converters_11.h" -typedef struct -{ - PyObject_HEAD - RendererAgg *x; - Py_ssize_t shape[3]; - Py_ssize_t strides[3]; - Py_ssize_t suboffsets[3]; -} PyRendererAgg; - -static PyTypeObject PyRendererAggType; - -typedef struct -{ - PyObject_HEAD - BufferRegion *x; - Py_ssize_t shape[3]; - Py_ssize_t strides[3]; - Py_ssize_t suboffsets[3]; -} PyBufferRegion; - -static PyTypeObject PyBufferRegionType; - +namespace py = pybind11; +using namespace pybind11::literals; /********************************************************************** * BufferRegion * */ -static PyObject *PyBufferRegion_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyBufferRegion *self; - self = (PyBufferRegion *)type->tp_alloc(type, 0); - self->x = NULL; - return (PyObject *)self; -} - -static void PyBufferRegion_dealloc(PyBufferRegion *self) -{ - delete self->x; - Py_TYPE(self)->tp_free((PyObject *)self); -} - /* TODO: This doesn't seem to be used internally. Remove? */ -static PyObject *PyBufferRegion_set_x(PyBufferRegion *self, PyObject *args) +static void +PyBufferRegion_set_x(BufferRegion *self, int x) { - int x; - if (!PyArg_ParseTuple(args, "i:set_x", &x)) { - return NULL; - } - self->x->get_rect().x1 = x; - - Py_RETURN_NONE; -} - -static PyObject *PyBufferRegion_set_y(PyBufferRegion *self, PyObject *args) -{ - int y; - if (!PyArg_ParseTuple(args, "i:set_y", &y)) { - return NULL; - } - self->x->get_rect().y1 = y; - - Py_RETURN_NONE; -} - -static PyObject *PyBufferRegion_get_extents(PyBufferRegion *self, PyObject *args) -{ - agg::rect_i rect = self->x->get_rect(); - - return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2, rect.y2); + self->get_rect().x1 = x; } -int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags) +static void +PyBufferRegion_set_y(BufferRegion *self, int y) { - Py_INCREF(self); - buf->obj = (PyObject *)self; - buf->buf = self->x->get_data(); - buf->len = (Py_ssize_t)self->x->get_width() * (Py_ssize_t)self->x->get_height() * 4; - buf->readonly = 0; - buf->format = (char *)"B"; - buf->ndim = 3; - self->shape[0] = self->x->get_height(); - self->shape[1] = self->x->get_width(); - self->shape[2] = 4; - buf->shape = self->shape; - self->strides[0] = self->x->get_width() * 4; - self->strides[1] = 4; - self->strides[2] = 1; - buf->strides = self->strides; - buf->suboffsets = NULL; - buf->itemsize = 1; - buf->internal = NULL; - - return 1; + self->get_rect().y1 = y; } -static PyTypeObject *PyBufferRegion_init_type() +static py::object +PyBufferRegion_get_extents(BufferRegion *self) { - static PyMethodDef methods[] = { - { "set_x", (PyCFunction)PyBufferRegion_set_x, METH_VARARGS, NULL }, - { "set_y", (PyCFunction)PyBufferRegion_set_y, METH_VARARGS, NULL }, - { "get_extents", (PyCFunction)PyBufferRegion_get_extents, METH_NOARGS, NULL }, - { NULL } - }; - - static PyBufferProcs buffer_procs; - buffer_procs.bf_getbuffer = (getbufferproc)PyBufferRegion_get_buffer; - - PyBufferRegionType.tp_name = "matplotlib.backends._backend_agg.BufferRegion"; - PyBufferRegionType.tp_basicsize = sizeof(PyBufferRegion); - PyBufferRegionType.tp_dealloc = (destructor)PyBufferRegion_dealloc; - PyBufferRegionType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; - PyBufferRegionType.tp_methods = methods; - PyBufferRegionType.tp_new = PyBufferRegion_new; - PyBufferRegionType.tp_as_buffer = &buffer_procs; + agg::rect_i rect = self->get_rect(); - return &PyBufferRegionType; + return py::make_tuple(rect.x1, rect.y1, rect.x2, rect.y2); } /********************************************************************** * RendererAgg * */ -static PyObject *PyRendererAgg_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyRendererAgg *self; - self = (PyRendererAgg *)type->tp_alloc(type, 0); - self->x = NULL; - return (PyObject *)self; -} - -static int PyRendererAgg_init(PyRendererAgg *self, PyObject *args, PyObject *kwds) +static void +PyRendererAgg_draw_path(RendererAgg *self, + GCAgg &gc, + mpl::PathIterator path, + agg::trans_affine trans, + py::object rgbFace) { - unsigned int width; - unsigned int height; - double dpi; - int debug = 0; - - if (!PyArg_ParseTuple(args, "IId|i:RendererAgg", &width, &height, &dpi, &debug)) { - return -1; - } - - if (dpi <= 0.0) { - PyErr_SetString(PyExc_ValueError, "dpi must be positive"); - return -1; + agg::rgba face = rgbFace.cast(); + if (!rgbFace.is_none()) { + if (gc.forced_alpha || rgbFace.cast().size() == 3) { + face.a = gc.alpha; + } } - if (width >= 1 << 16 || height >= 1 << 16) { - PyErr_Format( - PyExc_ValueError, - "Image size of %dx%d pixels is too large. " - "It must be less than 2^16 in each direction.", - width, height); - return -1; - } - - CALL_CPP_INIT("RendererAgg", self->x = new RendererAgg(width, height, dpi)) - - return 0; + self->draw_path(gc, path, trans, face); } -static void PyRendererAgg_dealloc(PyRendererAgg *self) +static void +PyRendererAgg_draw_text_image(RendererAgg *self, + py::array_t image_obj, + double x, + double y, + double angle, + GCAgg &gc) { - delete self->x; - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyObject *PyRendererAgg_draw_path(PyRendererAgg *self, PyObject *args) -{ - GCAgg gc; - mpl::PathIterator path; - agg::trans_affine trans; - PyObject *faceobj = NULL; - agg::rgba face; - - if (!PyArg_ParseTuple(args, - "O&O&O&|O:draw_path", - &convert_gcagg, - &gc, - &convert_path, - &path, - &convert_trans_affine, - &trans, - &faceobj)) { - return NULL; - } - - if (!convert_face(faceobj, gc, &face)) { - return NULL; - } + // TODO: This really shouldn't be mutable, but Agg's renderer buffers aren't const. + auto image = image_obj.mutable_unchecked<2>(); - CALL_CPP("draw_path", (self->x->draw_path(gc, path, trans, face))); - - Py_RETURN_NONE; + self->draw_text_image(gc, image, x, y, angle); } -static PyObject *PyRendererAgg_draw_text_image(PyRendererAgg *self, PyObject *args) +static void +PyRendererAgg_draw_markers(RendererAgg *self, + GCAgg &gc, + mpl::PathIterator marker_path, + agg::trans_affine marker_path_trans, + mpl::PathIterator path, + agg::trans_affine trans, + py::object rgbFace) { - numpy::array_view image; - double x; - double y; - double angle; - GCAgg gc; - - if (!PyArg_ParseTuple(args, - "O&dddO&:draw_text_image", - &image.converter_contiguous, - &image, - &x, - &y, - &angle, - &convert_gcagg, - &gc)) { - return NULL; + agg::rgba face = rgbFace.cast(); + if (!rgbFace.is_none()) { + if (gc.forced_alpha || rgbFace.cast().size() == 3) { + face.a = gc.alpha; + } } - CALL_CPP("draw_text_image", (self->x->draw_text_image(gc, image, x, y, angle))); - - Py_RETURN_NONE; + self->draw_markers(gc, marker_path, marker_path_trans, path, trans, face); } -PyObject *PyRendererAgg_draw_markers(PyRendererAgg *self, PyObject *args) +static void +PyRendererAgg_draw_image(RendererAgg *self, + GCAgg &gc, + double x, + double y, + py::array_t image_obj) { - GCAgg gc; - mpl::PathIterator marker_path; - agg::trans_affine marker_path_trans; - mpl::PathIterator path; - agg::trans_affine trans; - PyObject *faceobj = NULL; - agg::rgba face; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&O&|O:draw_markers", - &convert_gcagg, - &gc, - &convert_path, - &marker_path, - &convert_trans_affine, - &marker_path_trans, - &convert_path, - &path, - &convert_trans_affine, - &trans, - &faceobj)) { - return NULL; - } - - if (!convert_face(faceobj, gc, &face)) { - return NULL; - } - - CALL_CPP("draw_markers", - (self->x->draw_markers(gc, marker_path, marker_path_trans, path, trans, face))); - - Py_RETURN_NONE; -} - -static PyObject *PyRendererAgg_draw_image(PyRendererAgg *self, PyObject *args) -{ - GCAgg gc; - double x; - double y; - numpy::array_view image; - - if (!PyArg_ParseTuple(args, - "O&ddO&:draw_image", - &convert_gcagg, - &gc, - &x, - &y, - &image.converter_contiguous, - &image)) { - return NULL; - } + // TODO: This really shouldn't be mutable, but Agg's renderer buffers aren't const. + auto image = image_obj.mutable_unchecked<3>(); x = mpl_round(x); y = mpl_round(y); gc.alpha = 1.0; - CALL_CPP("draw_image", (self->x->draw_image(gc, x, y, image))); - - Py_RETURN_NONE; + self->draw_image(gc, x, y, image); } -static PyObject * -PyRendererAgg_draw_path_collection(PyRendererAgg *self, PyObject *args) +static void +PyRendererAgg_draw_path_collection(RendererAgg *self, + GCAgg &gc, + agg::trans_affine master_transform, + py::object paths_obj, + py::object transforms_obj, + py::object offsets_obj, + agg::trans_affine offset_trans, + py::object facecolors_obj, + py::object edgecolors_obj, + py::object linewidths_obj, + DashesVector dashes, + py::object antialiaseds_obj, + py::object Py_UNUSED(ignored_obj), + // offset position is no longer used + py::object Py_UNUSED(offset_position_obj)) { - GCAgg gc; - agg::trans_affine master_transform; mpl::PathGenerator paths; numpy::array_view transforms; numpy::array_view offsets; - agg::trans_affine offset_trans; numpy::array_view facecolors; numpy::array_view edgecolors; numpy::array_view linewidths; - DashesVector dashes; numpy::array_view antialiaseds; - PyObject *ignored; - PyObject *offset_position; // offset position is no longer used - - if (!PyArg_ParseTuple(args, - "O&O&O&O&O&O&O&O&O&O&O&OO:draw_path_collection", - &convert_gcagg, - &gc, - &convert_trans_affine, - &master_transform, - &convert_pathgen, - &paths, - &convert_transforms, - &transforms, - &convert_points, - &offsets, - &convert_trans_affine, - &offset_trans, - &convert_colors, - &facecolors, - &convert_colors, - &edgecolors, - &linewidths.converter, - &linewidths, - &convert_dashes_vector, - &dashes, - &antialiaseds.converter, - &antialiaseds, - &ignored, - &offset_position)) { - return NULL; - } - - CALL_CPP("draw_path_collection", - (self->x->draw_path_collection(gc, - master_transform, - paths, - transforms, - offsets, - offset_trans, - facecolors, - edgecolors, - linewidths, - dashes, - antialiaseds))); - - Py_RETURN_NONE; -} -static PyObject *PyRendererAgg_draw_quad_mesh(PyRendererAgg *self, PyObject *args) -{ - GCAgg gc; - agg::trans_affine master_transform; - unsigned int mesh_width; - unsigned int mesh_height; - numpy::array_view coordinates; - numpy::array_view offsets; - agg::trans_affine offset_trans; - numpy::array_view facecolors; - bool antialiased; - numpy::array_view edgecolors; - - if (!PyArg_ParseTuple(args, - "O&O&IIO&O&O&O&O&O&:draw_quad_mesh", - &convert_gcagg, - &gc, - &convert_trans_affine, - &master_transform, - &mesh_width, - &mesh_height, - &coordinates.converter, - &coordinates, - &convert_points, - &offsets, - &convert_trans_affine, - &offset_trans, - &convert_colors, - &facecolors, - &convert_bool, - &antialiased, - &convert_colors, - &edgecolors)) { - return NULL; + if (!convert_pathgen(paths_obj.ptr(), &paths)) { + throw py::error_already_set(); } - - CALL_CPP("draw_quad_mesh", - (self->x->draw_quad_mesh(gc, - master_transform, - mesh_width, - mesh_height, - coordinates, - offsets, - offset_trans, - facecolors, - antialiased, - edgecolors))); - - Py_RETURN_NONE; -} - -static PyObject * -PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args) -{ - GCAgg gc; - numpy::array_view points; - numpy::array_view colors; - agg::trans_affine trans; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&|O:draw_gouraud_triangles", - &convert_gcagg, - &gc, - &points.converter, - &points, - &colors.converter, - &colors, - &convert_trans_affine, - &trans)) { - return NULL; + if (!convert_transforms(transforms_obj.ptr(), &transforms)) { + throw py::error_already_set(); } - if (points.shape(0) && !check_trailing_shape(points, "points", 3, 2)) { - return NULL; + if (!convert_points(offsets_obj.ptr(), &offsets)) { + throw py::error_already_set(); } - if (colors.shape(0) && !check_trailing_shape(colors, "colors", 3, 4)) { - return NULL; + if (!convert_colors(facecolors_obj.ptr(), &facecolors)) { + throw py::error_already_set(); } - if (points.shape(0) != colors.shape(0)) { - PyErr_Format(PyExc_ValueError, - "points and colors arrays must be the same length, got " - "%" NPY_INTP_FMT " points and %" NPY_INTP_FMT "colors", - points.shape(0), colors.shape(0)); - return NULL; + if (!convert_colors(edgecolors_obj.ptr(), &edgecolors)) { + throw py::error_already_set(); + } + if (!linewidths.converter(linewidths_obj.ptr(), &linewidths)) { + throw py::error_already_set(); + } + if (!antialiaseds.converter(antialiaseds_obj.ptr(), &antialiaseds)) { + throw py::error_already_set(); } - CALL_CPP("draw_gouraud_triangles", self->x->draw_gouraud_triangles(gc, points, colors, trans)); - - Py_RETURN_NONE; -} - -int PyRendererAgg_get_buffer(PyRendererAgg *self, Py_buffer *buf, int flags) -{ - Py_INCREF(self); - buf->obj = (PyObject *)self; - buf->buf = self->x->pixBuffer; - buf->len = (Py_ssize_t)self->x->get_width() * (Py_ssize_t)self->x->get_height() * 4; - buf->readonly = 0; - buf->format = (char *)"B"; - buf->ndim = 3; - self->shape[0] = self->x->get_height(); - self->shape[1] = self->x->get_width(); - self->shape[2] = 4; - buf->shape = self->shape; - self->strides[0] = self->x->get_width() * 4; - self->strides[1] = 4; - self->strides[2] = 1; - buf->strides = self->strides; - buf->suboffsets = NULL; - buf->itemsize = 1; - buf->internal = NULL; - - return 1; -} - -static PyObject *PyRendererAgg_clear(PyRendererAgg *self, PyObject *args) -{ - CALL_CPP("clear", self->x->clear()); - - Py_RETURN_NONE; + self->draw_path_collection(gc, + master_transform, + paths, + transforms, + offsets, + offset_trans, + facecolors, + edgecolors, + linewidths, + dashes, + antialiaseds); } -static PyObject *PyRendererAgg_copy_from_bbox(PyRendererAgg *self, PyObject *args) +static void +PyRendererAgg_draw_quad_mesh(RendererAgg *self, + GCAgg &gc, + agg::trans_affine master_transform, + unsigned int mesh_width, + unsigned int mesh_height, + py::array_t coordinates_obj, + py::object offsets_obj, + agg::trans_affine offset_trans, + py::object facecolors_obj, + bool antialiased, + py::object edgecolors_obj) { - agg::rect_d bbox; - BufferRegion *reg; - PyObject *regobj; + numpy::array_view offsets; + numpy::array_view facecolors; + numpy::array_view edgecolors; - if (!PyArg_ParseTuple(args, "O&:copy_from_bbox", &convert_rect, &bbox)) { - return 0; + auto coordinates = coordinates_obj.mutable_unchecked<3>(); + if (!convert_points(offsets_obj.ptr(), &offsets)) { + throw py::error_already_set(); } - - CALL_CPP("copy_from_bbox", (reg = self->x->copy_from_bbox(bbox))); - - regobj = PyBufferRegion_new(&PyBufferRegionType, NULL, NULL); - ((PyBufferRegion *)regobj)->x = reg; - - return regobj; -} - -static PyObject *PyRendererAgg_restore_region(PyRendererAgg *self, PyObject *args) -{ - PyBufferRegion *regobj; - int xx1 = 0, yy1 = 0, xx2 = 0, yy2 = 0, x = 0, y = 0; - - if (!PyArg_ParseTuple(args, - "O!|iiiiii:restore_region", - &PyBufferRegionType, - ®obj, - &xx1, - &yy1, - &xx2, - &yy2, - &x, - &y)) { - return 0; + if (!convert_colors(facecolors_obj.ptr(), &facecolors)) { + throw py::error_already_set(); } - - if (PySequence_Size(args) == 1) { - CALL_CPP("restore_region", self->x->restore_region(*(regobj->x))); - } else { - CALL_CPP("restore_region", self->x->restore_region(*(regobj->x), xx1, yy1, xx2, yy2, x, y)); + if (!convert_colors(edgecolors_obj.ptr(), &edgecolors)) { + throw py::error_already_set(); } - Py_RETURN_NONE; + self->draw_quad_mesh(gc, + master_transform, + mesh_width, + mesh_height, + coordinates, + offsets, + offset_trans, + facecolors, + antialiased, + edgecolors); } -static PyTypeObject *PyRendererAgg_init_type() +static void +PyRendererAgg_draw_gouraud_triangles(RendererAgg *self, + GCAgg &gc, + py::array_t points_obj, + py::array_t colors_obj, + agg::trans_affine trans) { - static PyMethodDef methods[] = { - {"draw_path", (PyCFunction)PyRendererAgg_draw_path, METH_VARARGS, NULL}, - {"draw_markers", (PyCFunction)PyRendererAgg_draw_markers, METH_VARARGS, NULL}, - {"draw_text_image", (PyCFunction)PyRendererAgg_draw_text_image, METH_VARARGS, NULL}, - {"draw_image", (PyCFunction)PyRendererAgg_draw_image, METH_VARARGS, NULL}, - {"draw_path_collection", (PyCFunction)PyRendererAgg_draw_path_collection, METH_VARARGS, NULL}, - {"draw_quad_mesh", (PyCFunction)PyRendererAgg_draw_quad_mesh, METH_VARARGS, NULL}, - {"draw_gouraud_triangles", (PyCFunction)PyRendererAgg_draw_gouraud_triangles, METH_VARARGS, NULL}, - - {"clear", (PyCFunction)PyRendererAgg_clear, METH_NOARGS, NULL}, - - {"copy_from_bbox", (PyCFunction)PyRendererAgg_copy_from_bbox, METH_VARARGS, NULL}, - {"restore_region", (PyCFunction)PyRendererAgg_restore_region, METH_VARARGS, NULL}, - {NULL} - }; + auto points = points_obj.unchecked<3>(); + auto colors = colors_obj.unchecked<3>(); - static PyBufferProcs buffer_procs; - buffer_procs.bf_getbuffer = (getbufferproc)PyRendererAgg_get_buffer; - - PyRendererAggType.tp_name = "matplotlib.backends._backend_agg.RendererAgg"; - PyRendererAggType.tp_basicsize = sizeof(PyRendererAgg); - PyRendererAggType.tp_dealloc = (destructor)PyRendererAgg_dealloc; - PyRendererAggType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; - PyRendererAggType.tp_methods = methods; - PyRendererAggType.tp_init = (initproc)PyRendererAgg_init; - PyRendererAggType.tp_new = PyRendererAgg_new; - PyRendererAggType.tp_as_buffer = &buffer_procs; - - return &PyRendererAggType; + self->draw_gouraud_triangles(gc, points, colors, trans); } -static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_backend_agg" }; - -PyMODINIT_FUNC PyInit__backend_agg(void) +PYBIND11_MODULE(_backend_agg, m) { - import_array(); - PyObject *m; - if (!(m = PyModule_Create(&moduledef)) - || prepare_and_add_type(PyRendererAgg_init_type(), m) - // BufferRegion is not constructible from Python, thus not added to the module. - || PyType_Ready(PyBufferRegion_init_type()) - ) { - Py_XDECREF(m); - return NULL; + auto ia = [m]() -> const void* { + import_array(); + return &m; + }; + if (ia() == NULL) { + throw py::error_already_set(); } - return m; + + py::class_(m, "RendererAgg", py::buffer_protocol()) + .def(py::init(), + "width"_a, "height"_a, "dpi"_a) + + .def("draw_path", &PyRendererAgg_draw_path, + "gc"_a, "path"_a, "trans"_a, "face"_a = nullptr) + .def("draw_markers", &PyRendererAgg_draw_markers, + "gc"_a, "marker_path"_a, "marker_path_trans"_a, "path"_a, "trans"_a, + "face"_a = nullptr) + .def("draw_text_image", &PyRendererAgg_draw_text_image, + "image"_a, "x"_a, "y"_a, "angle"_a, "gc"_a) + .def("draw_image", &PyRendererAgg_draw_image, + "gc"_a, "x"_a, "y"_a, "image"_a) + .def("draw_path_collection", &PyRendererAgg_draw_path_collection, + "gc"_a, "master_transform"_a, "paths"_a, "transforms"_a, "offsets"_a, + "offset_trans"_a, "facecolors"_a, "edgecolors"_a, "linewidths"_a, + "dashes"_a, "antialiaseds"_a, "ignored"_a, "offset_position"_a) + .def("draw_quad_mesh", &PyRendererAgg_draw_quad_mesh, + "gc"_a, "master_transform"_a, "mesh_width"_a, "mesh_height"_a, + "coordinates"_a, "offsets"_a, "offset_trans"_a, "facecolors"_a, + "antialiased"_a, "edgecolors"_a) + .def("draw_gouraud_triangles", &PyRendererAgg_draw_gouraud_triangles, + "gc"_a, "points"_a, "colors"_a, "trans"_a = nullptr) + + .def("clear", &RendererAgg::clear) + + .def("copy_from_bbox", &RendererAgg::copy_from_bbox, + "bbox"_a) + .def("restore_region", + py::overload_cast(&RendererAgg::restore_region), + "region"_a) + .def("restore_region", + py::overload_cast(&RendererAgg::restore_region), + "region"_a, "xx1"_a, "yy1"_a, "xx2"_a, "yy2"_a, "x"_a, "y"_a) + + .def_buffer([](RendererAgg *renderer) -> py::buffer_info { + std::vector shape { + renderer->get_height(), + renderer->get_width(), + 4 + }; + std::vector strides { + renderer->get_width() * 4, + 4, + 1 + }; + return py::buffer_info(renderer->pixBuffer, shape, strides); + }); + + py::class_(m, "BufferRegion", py::buffer_protocol()) + // BufferRegion is not constructible from Python, thus no py::init is added. + .def("set_x", &PyBufferRegion_set_x) + .def("set_y", &PyBufferRegion_set_y) + .def("get_extents", &PyBufferRegion_get_extents) + .def_buffer([](BufferRegion *buffer) -> py::buffer_info { + std::vector shape { + buffer->get_height(), + buffer->get_width(), + 4 + }; + std::vector strides { + buffer->get_width() * 4, + 4, + 1 + }; + return py::buffer_info(buffer->get_data(), shape, strides); + }); } diff --git a/src/meson.build b/src/meson.build index a046b3306ab8..21ca98a639cf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -74,10 +74,11 @@ extension_data = { 'subdir': 'matplotlib/backends', 'sources': files( 'py_converters.cpp', + 'py_converters_11.cpp', '_backend_agg.cpp', '_backend_agg_wrapper.cpp', ), - 'dependencies': [agg_dep, numpy_dep, freetype_dep], + 'dependencies': [agg_dep, numpy_dep, freetype_dep, pybind11_dep], }, '_c_internal_utils': { 'subdir': 'matplotlib', diff --git a/src/py_converters.cpp b/src/py_converters.cpp index e4a04b7bc057..dee4b0abfd31 100644 --- a/src/py_converters.cpp +++ b/src/py_converters.cpp @@ -415,8 +415,6 @@ int convert_pathgen(PyObject *obj, void *pathgenp) int convert_clippath(PyObject *clippath_tuple, void *clippathp) { ClipPath *clippath = (ClipPath *)clippathp; - mpl::PathIterator path; - agg::trans_affine trans; if (clippath_tuple != NULL && clippath_tuple != Py_None) { if (!PyArg_ParseTuple(clippath_tuple, @@ -491,21 +489,6 @@ int convert_gcagg(PyObject *pygc, void *gcp) return 1; } -int convert_face(PyObject *color, GCAgg &gc, agg::rgba *rgba) -{ - if (!convert_rgba(color, rgba)) { - return 0; - } - - if (color != NULL && color != Py_None) { - if (gc.forced_alpha || PySequence_Size(color) == 3) { - rgba->a = gc.alpha; - } - } - - return 1; -} - int convert_points(PyObject *obj, void *pointsp) { numpy::array_view *points = (numpy::array_view *)pointsp; diff --git a/src/py_converters.h b/src/py_converters.h index 2c9dc6d1b860..b514efdf5d47 100644 --- a/src/py_converters.h +++ b/src/py_converters.h @@ -41,8 +41,6 @@ int convert_points(PyObject *pygc, void *pointsp); int convert_transforms(PyObject *pygc, void *transp); int convert_bboxes(PyObject *pygc, void *bboxp); int convert_colors(PyObject *pygc, void *colorsp); - -int convert_face(PyObject *color, GCAgg &gc, agg::rgba *rgba); } #endif diff --git a/src/py_converters_11.h b/src/py_converters_11.h index 911d5fe2b924..ef5d8989c072 100644 --- a/src/py_converters_11.h +++ b/src/py_converters_11.h @@ -8,7 +8,10 @@ namespace py = pybind11; +#include + #include "agg_basics.h" +#include "agg_color_rgba.h" #include "agg_trans_affine.h" #include "path_converters.h" @@ -58,6 +61,36 @@ namespace PYBIND11_NAMESPACE { namespace detail { } }; + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(agg::rgba, const_name("rgba")); + + bool load(handle src, bool) { + if (src.is_none()) { + value.r = 0.0; + value.g = 0.0; + value.b = 0.0; + value.a = 0.0; + } else { + auto rgbatuple = src.cast(); + value.r = rgbatuple[0].cast(); + value.g = rgbatuple[1].cast(); + value.b = rgbatuple[2].cast(); + switch (rgbatuple.size()) { + case 4: + value.a = rgbatuple[3].cast(); + break; + case 3: + value.a = 1.0; + break; + default: + throw py::value_error("RGBA value must be 3- or 4-tuple"); + } + } + return true; + } + }; + template <> struct type_caster { public: PYBIND11_TYPE_CASTER(agg::trans_affine, const_name("trans_affine")); @@ -104,6 +137,37 @@ namespace PYBIND11_NAMESPACE { namespace detail { /* Remove all this macro magic after dropping NumPy usage and just include `py_adaptors.h`. */ #ifdef MPL_PY_ADAPTORS_H + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(agg::line_cap_e, const_name("line_cap_e")); + + bool load(handle src, bool) { + const std::unordered_map enum_values = { + {"butt", agg::butt_cap}, + {"round", agg::round_cap}, + {"projecting", agg::square_cap}, + }; + value = enum_values.at(src.cast()); + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(agg::line_join_e, const_name("line_join_e")); + + bool load(handle src, bool) { + const std::unordered_map enum_values = { + {"miter", agg::miter_join_revert}, + {"round", agg::round_join}, + {"bevel", agg::bevel_join}, + }; + value = agg::miter_join_revert; + value = enum_values.at(src.cast()); + return true; + } + }; + template <> struct type_caster { public: PYBIND11_TYPE_CASTER(mpl::PathIterator, const_name("PathIterator")); @@ -113,14 +177,14 @@ namespace PYBIND11_NAMESPACE { namespace detail { return true; } - auto vertices = src.attr("vertices"); - auto codes = src.attr("codes"); + py::object vertices = src.attr("vertices"); + py::object codes = src.attr("codes"); auto should_simplify = src.attr("should_simplify").cast(); auto simplify_threshold = src.attr("simplify_threshold").cast(); - if (!value.set(vertices.ptr(), codes.ptr(), + if (!value.set(vertices.inc_ref().ptr(), codes.inc_ref().ptr(), should_simplify, simplify_threshold)) { - return false; + throw py::error_already_set(); } return true; @@ -130,6 +194,64 @@ namespace PYBIND11_NAMESPACE { namespace detail { /* Remove all this macro magic after dropping NumPy usage and just include `_backend_agg_basic_types.h`. */ #ifdef MPL_BACKEND_AGG_BASIC_TYPES_H +# ifndef MPL_PY_ADAPTORS_H +# error "py_adaptors.h must be included to get Agg type casters" +# endif + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(ClipPath, const_name("ClipPath")); + + bool load(handle src, bool) { + if (src.is_none()) { + return true; + } + + auto clippath_tuple = src.cast(); + + auto path = clippath_tuple[0]; + if (!path.is_none()) { + value.path = path.cast(); + } + value.trans = clippath_tuple[1].cast(); + + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(Dashes, const_name("Dashes")); + + bool load(handle src, bool) { + auto dash_tuple = src.cast(); + auto dash_offset = dash_tuple[0].cast(); + auto dashes_seq_or_none = dash_tuple[1]; + + if (dashes_seq_or_none.is_none()) { + return true; + } + + auto dashes_seq = dashes_seq_or_none.cast(); + + auto nentries = dashes_seq.size(); + // If the dashpattern has odd length, iterate through it twice (in + // accordance with the pdf/ps/svg specs). + auto dash_pattern_length = (nentries % 2) ? 2 * nentries : nentries; + + for (py::size_t i = 0; i < dash_pattern_length; i += 2) { + auto length = dashes_seq[i % nentries].cast(); + auto skip = dashes_seq[(i + 1) % nentries].cast(); + + value.add_dash_pair(length, skip); + } + + value.set_dash_offset(dash_offset); + + return true; + } + }; + template <> struct type_caster { public: PYBIND11_TYPE_CASTER(SketchParams, const_name("SketchParams")); @@ -137,6 +259,8 @@ namespace PYBIND11_NAMESPACE { namespace detail { bool load(handle src, bool) { if (src.is_none()) { value.scale = 0.0; + value.length = 0.0; + value.randomness = 0.0; return true; } @@ -146,6 +270,32 @@ namespace PYBIND11_NAMESPACE { namespace detail { return true; } }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(GCAgg, const_name("GCAgg")); + + bool load(handle src, bool) { + value.linewidth = src.attr("_linewidth").cast(); + value.alpha = src.attr("_alpha").cast(); + value.forced_alpha = src.attr("_forced_alpha").cast(); + value.color = src.attr("_rgb").cast(); + value.isaa = src.attr("_antialiased").cast(); + value.cap = src.attr("_capstyle").cast(); + value.join = src.attr("_joinstyle").cast(); + value.dashes = src.attr("get_dashes")().cast(); + value.cliprect = src.attr("_cliprect").cast(); + /* value.clippath = src.attr("get_clip_path")().cast(); */ + convert_clippath(src.attr("get_clip_path")().ptr(), &value.clippath); + value.snap_mode = src.attr("get_snap")().cast(); + value.hatchpath = src.attr("get_hatch_path")().cast(); + value.hatch_color = src.attr("get_hatch_color")().cast(); + value.hatch_linewidth = src.attr("get_hatch_linewidth")().cast(); + value.sketch = src.attr("get_sketch_params")().cast(); + + return true; + } + }; #endif }} // namespace PYBIND11_NAMESPACE::detail diff --git a/src/py_exceptions.h b/src/py_exceptions.h index 7a7e004a4a7a..db1977c413dd 100644 --- a/src/py_exceptions.h +++ b/src/py_exceptions.h @@ -46,9 +46,17 @@ class exception : public std::exception } \ return (errorcode); \ } \ + catch (const std::range_error &e) \ + { \ + PyErr_Format(PyExc_ValueError, "In %s: %s", (name), e.what()); \ + { \ + cleanup; \ + } \ + return (errorcode); \ + } \ catch (const std::runtime_error &e) \ { \ - PyErr_Format(PyExc_RuntimeError, "In %s: %s", (name), e.what()); \ + PyErr_Format(PyExc_RuntimeError, "In %s: %s", (name), e.what()); \ { \ cleanup; \ } \