diff --git a/src/_backend_agg_basic_types.h b/src/_backend_agg_basic_types.h index 4fbf846d8cb4..e3e6be9a4532 100644 --- a/src/_backend_agg_basic_types.h +++ b/src/_backend_agg_basic_types.h @@ -4,6 +4,9 @@ /* Contains some simple types from the Agg backend that are also used by other modules */ +#include + +#include #include #include "agg_color_rgba.h" @@ -13,6 +16,8 @@ #include "py_adaptors.h" +namespace py = pybind11; + struct ClipPath { mpl::PathIterator path; @@ -121,4 +126,132 @@ class GCAgg GCAgg &operator=(const GCAgg &); }; +namespace PYBIND11_NAMESPACE { namespace detail { + 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 = enum_values.at(src.cast()); + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(ClipPath, const_name("ClipPath")); + + bool load(handle src, bool) { + if (src.is_none()) { + return true; + } + + auto [path, trans] = + src.cast, agg::trans_affine>>(); + if (path) { + value.path = *path; + } + value.trans = trans; + + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(Dashes, const_name("Dashes")); + + bool load(handle src, bool) { + auto [dash_offset, dashes_seq_or_none] = + src.cast>>(); + + if (!dashes_seq_or_none) { + return true; + } + + auto dashes_seq = *dashes_seq_or_none; + + 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")); + + bool load(handle src, bool) { + if (src.is_none()) { + value.scale = 0.0; + value.length = 0.0; + value.randomness = 0.0; + return true; + } + + auto params = src.cast>(); + std::tie(value.scale, value.length, value.randomness) = params; + + 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(); + 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; + } + }; +}} // namespace PYBIND11_NAMESPACE::detail + #endif diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 79cab02e419d..fb241b217fe9 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -111,48 +111,25 @@ 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, + mpl::PathGenerator paths, + py::array_t transforms_obj, + py::array_t offsets_obj, agg::trans_affine offset_trans, - py::object facecolors_obj, - py::object edgecolors_obj, - py::object linewidths_obj, + py::array_t facecolors_obj, + py::array_t edgecolors_obj, + py::array_t linewidths_obj, DashesVector dashes, - py::object antialiaseds_obj, + py::array_t antialiaseds_obj, py::object Py_UNUSED(ignored_obj), // offset position is no longer used py::object Py_UNUSED(offset_position_obj)) { - mpl::PathGenerator paths; - numpy::array_view transforms; - numpy::array_view offsets; - numpy::array_view facecolors; - numpy::array_view edgecolors; - numpy::array_view linewidths; - numpy::array_view antialiaseds; - - if (!convert_pathgen(paths_obj.ptr(), &paths)) { - throw py::error_already_set(); - } - if (!convert_transforms(transforms_obj.ptr(), &transforms)) { - throw py::error_already_set(); - } - if (!convert_points(offsets_obj.ptr(), &offsets)) { - throw py::error_already_set(); - } - if (!convert_colors(facecolors_obj.ptr(), &facecolors)) { - throw py::error_already_set(); - } - 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(); - } + auto transforms = convert_transforms(transforms_obj); + auto offsets = convert_points(offsets_obj); + auto facecolors = convert_colors(facecolors_obj); + auto edgecolors = convert_colors(edgecolors_obj); + auto linewidths = linewidths_obj.unchecked<1>(); + auto antialiaseds = antialiaseds_obj.unchecked<1>(); self->draw_path_collection(gc, master_transform, @@ -174,26 +151,16 @@ PyRendererAgg_draw_quad_mesh(RendererAgg *self, unsigned int mesh_width, unsigned int mesh_height, py::array_t coordinates_obj, - py::object offsets_obj, + py::array_t offsets_obj, agg::trans_affine offset_trans, - py::object facecolors_obj, + py::array_t facecolors_obj, bool antialiased, - py::object edgecolors_obj) + py::array_t edgecolors_obj) { - numpy::array_view offsets; - numpy::array_view facecolors; - numpy::array_view edgecolors; - auto coordinates = coordinates_obj.mutable_unchecked<3>(); - if (!convert_points(offsets_obj.ptr(), &offsets)) { - throw py::error_already_set(); - } - if (!convert_colors(facecolors_obj.ptr(), &facecolors)) { - throw py::error_already_set(); - } - if (!convert_colors(edgecolors_obj.ptr(), &edgecolors)) { - throw py::error_already_set(); - } + auto offsets = convert_points(offsets_obj); + auto facecolors = convert_colors(facecolors_obj); + auto edgecolors = convert_colors(edgecolors_obj); self->draw_quad_mesh(gc, master_transform, diff --git a/src/_path.h b/src/_path.h index 7f17d0bc2933..693862c7a829 100644 --- a/src/_path.h +++ b/src/_path.h @@ -245,8 +245,7 @@ inline void points_in_path(PointArray &points, typedef agg::conv_curve curve_t; typedef agg::conv_contour contour_t; - size_t i; - for (i = 0; i < safe_first_shape(points); ++i) { + for (auto i = 0; i < safe_first_shape(points); ++i) { result[i] = false; } @@ -270,10 +269,11 @@ template inline bool point_in_path( double x, double y, const double r, PathIterator &path, agg::trans_affine &trans) { - npy_intp shape[] = {1, 2}; - numpy::array_view points(shape); - points(0, 0) = x; - points(0, 1) = y; + py::ssize_t shape[] = {1, 2}; + py::array_t points_arr(shape); + *points_arr.mutable_data(0, 0) = x; + *points_arr.mutable_data(0, 1) = y; + auto points = points_arr.mutable_unchecked<2>(); int result[1]; result[0] = 0; @@ -292,10 +292,11 @@ inline bool point_on_path( typedef agg::conv_curve curve_t; typedef agg::conv_stroke stroke_t; - npy_intp shape[] = {1, 2}; - numpy::array_view points(shape); - points(0, 0) = x; - points(0, 1) = y; + py::ssize_t shape[] = {1, 2}; + py::array_t points_arr(shape); + *points_arr.mutable_data(0, 0) = x; + *points_arr.mutable_data(0, 1) = y; + auto points = points_arr.mutable_unchecked<2>(); int result[1]; result[0] = 0; @@ -382,20 +383,19 @@ void get_path_collection_extents(agg::trans_affine &master_transform, throw std::runtime_error("Offsets array must have shape (N, 2)"); } - size_t Npaths = paths.size(); - size_t Noffsets = safe_first_shape(offsets); - size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = std::min(safe_first_shape(transforms), N); - size_t i; + auto Npaths = paths.size(); + auto Noffsets = safe_first_shape(offsets); + auto N = std::max(Npaths, Noffsets); + auto Ntransforms = std::min(safe_first_shape(transforms), N); agg::trans_affine trans; reset_limits(extent); - for (i = 0; i < N; ++i) { + for (auto i = 0; i < N; ++i) { typename PathGenerator::path_iterator path(paths(i % Npaths)); if (Ntransforms) { - size_t ti = i % Ntransforms; + py::ssize_t ti = i % Ntransforms; trans = agg::trans_affine(transforms(ti, 0, 0), transforms(ti, 1, 0), transforms(ti, 0, 1), @@ -429,24 +429,23 @@ void point_in_path_collection(double x, bool filled, std::vector &result) { - size_t Npaths = paths.size(); + auto Npaths = paths.size(); if (Npaths == 0) { return; } - size_t Noffsets = safe_first_shape(offsets); - size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = std::min(safe_first_shape(transforms), N); - size_t i; + auto Noffsets = safe_first_shape(offsets); + auto N = std::max(Npaths, Noffsets); + auto Ntransforms = std::min(safe_first_shape(transforms), N); agg::trans_affine trans; - for (i = 0; i < N; ++i) { + for (auto i = 0; i < N; ++i) { typename PathGenerator::path_iterator path = paths(i % Npaths); if (Ntransforms) { - size_t ti = i % Ntransforms; + auto ti = i % Ntransforms; trans = agg::trans_affine(transforms(ti, 0, 0), transforms(ti, 1, 0), transforms(ti, 0, 1), @@ -1224,17 +1223,15 @@ bool convert_to_string(PathIterator &path, } template -bool is_sorted_and_has_non_nan(PyArrayObject *array) +bool is_sorted_and_has_non_nan(py::array_t array) { - char* ptr = PyArray_BYTES(array); - npy_intp size = PyArray_DIM(array, 0), - stride = PyArray_STRIDE(array, 0); + auto size = array.shape(0); using limits = std::numeric_limits; T last = limits::has_infinity ? -limits::infinity() : limits::min(); bool found_non_nan = false; - for (npy_intp i = 0; i < size; ++i, ptr += stride) { - T current = *(T*)ptr; + for (auto i = 0; i < size; ++i) { + T current = *array.data(i); // The following tests !isnan(current), but also works for integral // types. (The isnan(IntegralType) overload is absent on MSVC.) if (current == current) { diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index b4eb5d19177f..13431601e5af 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -44,17 +44,9 @@ static py::array_t Py_points_in_path(py::array_t points_obj, double r, mpl::PathIterator path, agg::trans_affine trans) { - numpy::array_view points; + auto points = convert_points(points_obj); - if (!convert_points(points_obj.ptr(), &points)) { - throw py::error_already_set(); - } - - if (!check_trailing_shape(points, "points", 2)) { - throw py::error_already_set(); - } - - py::ssize_t dims[] = { static_cast(points.size()) }; + py::ssize_t dims[] = { points.shape(0) }; py::array_t results(dims); auto results_mutable = results.mutable_unchecked<1>(); @@ -123,24 +115,15 @@ Py_update_path_extents(mpl::PathIterator path, agg::trans_affine trans, static py::tuple Py_get_path_collection_extents(agg::trans_affine master_transform, - py::object paths_obj, py::object transforms_obj, - py::object offsets_obj, agg::trans_affine offset_trans) + mpl::PathGenerator paths, + py::array_t transforms_obj, + py::array_t offsets_obj, + agg::trans_affine offset_trans) { - mpl::PathGenerator paths; - numpy::array_view transforms; - numpy::array_view offsets; + auto transforms = convert_transforms(transforms_obj); + auto offsets = convert_points(offsets_obj); extent_limits e; - if (!convert_pathgen(paths_obj.ptr(), &paths)) { - throw py::error_already_set(); - } - if (!convert_transforms(transforms_obj.ptr(), &transforms)) { - throw py::error_already_set(); - } - if (!convert_points(offsets_obj.ptr(), &offsets)) { - throw py::error_already_set(); - } - get_path_collection_extents( master_transform, paths, transforms, offsets, offset_trans, e); @@ -161,25 +144,15 @@ Py_get_path_collection_extents(agg::trans_affine master_transform, static py::object Py_point_in_path_collection(double x, double y, double radius, - agg::trans_affine master_transform, py::object paths_obj, - py::object transforms_obj, py::object offsets_obj, + agg::trans_affine master_transform, mpl::PathGenerator paths, + py::array_t transforms_obj, + py::array_t offsets_obj, agg::trans_affine offset_trans, bool filled) { - mpl::PathGenerator paths; - numpy::array_view transforms; - numpy::array_view offsets; + auto transforms = convert_transforms(transforms_obj); + auto offsets = convert_points(offsets_obj); std::vector result; - if (!convert_pathgen(paths_obj.ptr(), &paths)) { - throw py::error_already_set(); - } - if (!convert_transforms(transforms_obj.ptr(), &transforms)) { - throw py::error_already_set(); - } - if (!convert_points(offsets_obj.ptr(), &offsets)) { - throw py::error_already_set(); - } - point_in_path_collection(x, y, radius, master_transform, paths, transforms, offsets, offset_trans, filled, result); @@ -237,13 +210,9 @@ Py_affine_transform(py::array_t bboxes_obj) { - numpy::array_view bboxes; - - if (!convert_bboxes(bboxes_obj.ptr(), &bboxes)) { - throw py::error_already_set(); - } + auto bboxes = convert_bboxes(bboxes_obj); return count_bboxes_overlapping_bbox(bbox, bboxes); } @@ -381,41 +350,26 @@ Py_is_sorted_and_has_non_nan(py::object obj) { bool result; - PyArrayObject *array = (PyArrayObject *)PyArray_CheckFromAny( - obj.ptr(), NULL, 1, 1, NPY_ARRAY_NOTSWAPPED, NULL); - - if (array == NULL) { - throw py::error_already_set(); + py::array array = py::array::ensure(obj); + if (array.ndim() != 1) { + throw std::invalid_argument("array must be 1D"); } + auto dtype = array.dtype(); /* Handle just the most common types here, otherwise coerce to double */ - switch (PyArray_TYPE(array)) { - case NPY_INT: - result = is_sorted_and_has_non_nan(array); - break; - case NPY_LONG: - result = is_sorted_and_has_non_nan(array); - break; - case NPY_LONGLONG: - result = is_sorted_and_has_non_nan(array); - break; - case NPY_FLOAT: - result = is_sorted_and_has_non_nan(array); - break; - case NPY_DOUBLE: - result = is_sorted_and_has_non_nan(array); - break; - default: - Py_DECREF(array); - array = (PyArrayObject *)PyArray_FromObject(obj.ptr(), NPY_DOUBLE, 1, 1); - if (array == NULL) { - throw py::error_already_set(); - } - result = is_sorted_and_has_non_nan(array); + if (dtype.equal(py::dtype::of())) { + result = is_sorted_and_has_non_nan(array); + } else if (dtype.equal(py::dtype::of())) { + result = is_sorted_and_has_non_nan(array); + } else if (dtype.equal(py::dtype::of())) { + result = is_sorted_and_has_non_nan(array); + } else if (dtype.equal(py::dtype::of())) { + result = is_sorted_and_has_non_nan(array); + } else { + array = py::array_t::ensure(obj); + result = is_sorted_and_has_non_nan(array); } - Py_DECREF(array); - return result; } diff --git a/src/mplutils.h b/src/mplutils.h index 58eb32d190ec..b7a80a84429c 100644 --- a/src/mplutils.h +++ b/src/mplutils.h @@ -67,10 +67,25 @@ inline int prepare_and_add_type(PyTypeObject *type, PyObject *module) #ifdef __cplusplus // not for macosx.m // Check that array has shape (N, d1) or (N, d1, d2). We cast d1, d2 to longs // so that we don't need to access the NPY_INTP_FMT macro here. +#include +#include + +namespace py = pybind11; template inline bool check_trailing_shape(T array, char const* name, long d1) { + if (array.ndim() != 2) { + PyErr_Format(PyExc_ValueError, + "Expected 2-dimensional array, got %ld", + array.ndim()); + return false; + } + if (array.size() == 0) { + // Sometimes things come through as atleast_2d, etc., but they're empty, so + // don't bother enforcing the trailing shape. + return true; + } if (array.shape(1) != d1) { PyErr_Format(PyExc_ValueError, "%s must have shape (N, %ld), got (%ld, %ld)", @@ -83,6 +98,17 @@ inline bool check_trailing_shape(T array, char const* name, long d1) template inline bool check_trailing_shape(T array, char const* name, long d1, long d2) { + if (array.ndim() != 3) { + PyErr_Format(PyExc_ValueError, + "Expected 3-dimensional array, got %ld", + array.ndim()); + return false; + } + if (array.size() == 0) { + // Sometimes things come through as atleast_3d, etc., but they're empty, so + // don't bother enforcing the trailing shape. + return true; + } if (array.shape(1) != d1 || array.shape(2) != d2) { PyErr_Format(PyExc_ValueError, "%s must have shape (N, %ld, %ld), got (%ld, %ld, %ld)", @@ -91,6 +117,25 @@ inline bool check_trailing_shape(T array, char const* name, long d1, long d2) } return true; } + +/* In most cases, code should use safe_first_shape(obj) instead of obj.shape(0), since + safe_first_shape(obj) == 0 when any dimension is 0. */ +template +py::ssize_t +safe_first_shape(const py::detail::unchecked_reference &a) +{ + bool empty = (ND == 0); + for (py::ssize_t i = 0; i < ND; i++) { + if (a.shape(i) == 0) { + empty = true; + } + } + if (empty) { + return 0; + } else { + return a.shape(0); + } +} #endif #endif diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index 6165789b7603..6b7446337bb7 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -365,10 +365,6 @@ class array_view : public detail::array_view_accessors public: typedef T value_type; - enum { - ndim = ND - }; - array_view() : m_arr(NULL), m_data(NULL) { m_shape = zeros; @@ -492,6 +488,10 @@ class array_view : public detail::array_view_accessors return true; } + npy_intp ndim() const { + return ND; + } + npy_intp shape(size_t i) const { if (i >= ND) { diff --git a/src/path_converters.h b/src/path_converters.h index 6d242e74415b..6877ab6ed4c3 100644 --- a/src/path_converters.h +++ b/src/path_converters.h @@ -3,6 +3,8 @@ #ifndef MPL_PATH_CONVERTERS_H #define MPL_PATH_CONVERTERS_H +#include + #include #include #include @@ -530,6 +532,24 @@ enum e_snap_mode { SNAP_TRUE }; +namespace PYBIND11_NAMESPACE { namespace detail { + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(e_snap_mode, const_name("e_snap_mode")); + + bool load(handle src, bool) { + if (src.is_none()) { + value = SNAP_AUTO; + return true; + } + + value = src.cast() ? SNAP_TRUE : SNAP_FALSE; + + return true; + } + }; +}} // namespace PYBIND11_NAMESPACE::detail + template class PathSnapper { diff --git a/src/py_adaptors.h b/src/py_adaptors.h index b0cec6c1d004..298943006ce8 100644 --- a/src/py_adaptors.h +++ b/src/py_adaptors.h @@ -8,16 +8,12 @@ * structures to C++ and Agg-friendly interfaces. */ -#include - -#include "numpy/arrayobject.h" +#include +#include #include "agg_basics.h" -#include "py_exceptions.h" -extern "C" { -int convert_path(PyObject *obj, void *pathp); -} +namespace py = pybind11; namespace mpl { @@ -35,8 +31,8 @@ class PathIterator underlying data arrays, so that Python reference counting can work. */ - PyArrayObject *m_vertices; - PyArrayObject *m_codes; + py::array_t m_vertices; + py::array_t m_codes; unsigned m_iterator; unsigned m_total_vertices; @@ -50,38 +46,29 @@ class PathIterator public: inline PathIterator() - : m_vertices(NULL), - m_codes(NULL), - m_iterator(0), + : m_iterator(0), m_total_vertices(0), m_should_simplify(false), m_simplify_threshold(1.0 / 9.0) { } - inline PathIterator(PyObject *vertices, - PyObject *codes, - bool should_simplify, + inline PathIterator(py::object vertices, py::object codes, bool should_simplify, double simplify_threshold) - : m_vertices(NULL), m_codes(NULL), m_iterator(0) + : m_iterator(0) { - if (!set(vertices, codes, should_simplify, simplify_threshold)) - throw mpl::exception(); + set(vertices, codes, should_simplify, simplify_threshold); } - inline PathIterator(PyObject *vertices, PyObject *codes) - : m_vertices(NULL), m_codes(NULL), m_iterator(0) + inline PathIterator(py::object vertices, py::object codes) + : m_iterator(0) { - if (!set(vertices, codes)) - throw mpl::exception(); + set(vertices, codes); } inline PathIterator(const PathIterator &other) { - Py_XINCREF(other.m_vertices); m_vertices = other.m_vertices; - - Py_XINCREF(other.m_codes); m_codes = other.m_codes; m_iterator = 0; @@ -91,47 +78,45 @@ class PathIterator m_simplify_threshold = other.m_simplify_threshold; } - ~PathIterator() - { - Py_XDECREF(m_vertices); - Py_XDECREF(m_codes); - } - - inline int - set(PyObject *vertices, PyObject *codes, bool should_simplify, double simplify_threshold) + inline void + set(py::object vertices, py::object codes, bool should_simplify, double simplify_threshold) { m_should_simplify = should_simplify; m_simplify_threshold = simplify_threshold; - Py_XDECREF(m_vertices); - m_vertices = (PyArrayObject *)PyArray_FromObject(vertices, NPY_DOUBLE, 2, 2); - - if (!m_vertices || PyArray_DIM(m_vertices, 1) != 2) { - PyErr_SetString(PyExc_ValueError, "Invalid vertices array"); - return 0; + m_vertices = vertices.cast>(); + if (m_vertices.ndim() != 2 || m_vertices.shape(1) != 2) { + throw py::value_error("Invalid vertices array"); } + m_total_vertices = m_vertices.shape(0); - Py_XDECREF(m_codes); - m_codes = NULL; - - if (codes != NULL && codes != Py_None) { - m_codes = (PyArrayObject *)PyArray_FromObject(codes, NPY_UINT8, 1, 1); - - if (!m_codes || PyArray_DIM(m_codes, 0) != PyArray_DIM(m_vertices, 0)) { - PyErr_SetString(PyExc_ValueError, "Invalid codes array"); - return 0; + m_codes.release().dec_ref(); + if (!codes.is_none()) { + m_codes = codes.cast>(); + if (m_codes.ndim() != 1 || m_codes.shape(0) != m_total_vertices) { + throw py::value_error("Invalid codes array"); } } - m_total_vertices = (unsigned)PyArray_DIM(m_vertices, 0); m_iterator = 0; + } + inline int + set(PyObject *vertices, PyObject *codes, bool should_simplify, double simplify_threshold) + { + try { + set(py::reinterpret_borrow(vertices), + py::reinterpret_borrow(codes), + should_simplify, simplify_threshold); + } catch(const py::error_already_set &) { + return 0; + } return 1; } - inline int set(PyObject *vertices, PyObject *codes) + inline void set(py::object vertices, py::object codes) { - return set(vertices, codes, false, 0.0); + set(vertices, codes, false, 0.0); } inline unsigned vertex(double *x, double *y) @@ -144,12 +129,11 @@ class PathIterator const size_t idx = m_iterator++; - char *pair = (char *)PyArray_GETPTR2(m_vertices, idx, 0); - *x = *(double *)pair; - *y = *(double *)(pair + PyArray_STRIDE(m_vertices, 1)); + *x = *m_vertices.data(idx, 0); + *y = *m_vertices.data(idx, 1); - if (m_codes != NULL) { - return (unsigned)(*(char *)PyArray_GETPTR1(m_codes, idx)); + if (m_codes) { + return *m_codes.data(idx); } else { return idx == 0 ? agg::path_cmd_move_to : agg::path_cmd_line_to; } @@ -177,42 +161,38 @@ class PathIterator inline bool has_codes() const { - return m_codes != NULL; + return bool(m_codes); } inline void *get_id() { - return (void *)m_vertices; + return (void *)m_vertices.ptr(); } }; class PathGenerator { - PyObject *m_paths; + py::sequence m_paths; Py_ssize_t m_npaths; public: typedef PathIterator path_iterator; - PathGenerator() : m_paths(NULL), m_npaths(0) {} + PathGenerator() : m_npaths(0) {} - ~PathGenerator() + void set(py::object obj) { - Py_XDECREF(m_paths); + m_paths = obj.cast(); + m_npaths = m_paths.size(); } int set(PyObject *obj) { - if (!PySequence_Check(obj)) { + try { + set(py::reinterpret_borrow(obj)); + } catch(const py::error_already_set &) { return 0; } - - Py_XDECREF(m_paths); - m_paths = obj; - Py_INCREF(m_paths); - - m_npaths = PySequence_Size(m_paths); - return 1; } @@ -229,20 +209,44 @@ class PathGenerator path_iterator operator()(size_t i) { path_iterator path; - PyObject *item; - item = PySequence_GetItem(m_paths, i % m_npaths); - if (item == NULL) { - throw mpl::exception(); - } - if (!convert_path(item, &path)) { - Py_DECREF(item); - throw mpl::exception(); - } - Py_DECREF(item); + auto item = m_paths[i % m_npaths]; + path = item.cast(); return path; } }; } +namespace PYBIND11_NAMESPACE { namespace detail { + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(mpl::PathIterator, const_name("PathIterator")); + + bool load(handle src, bool) { + if (src.is_none()) { + return true; + } + + 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(); + + value.set(vertices, codes, should_simplify, simplify_threshold); + + return true; + } + }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(mpl::PathGenerator, const_name("PathGenerator")); + + bool load(handle src, bool) { + value.set(py::reinterpret_borrow(src)); + return true; + } + }; +}} // namespace PYBIND11_NAMESPACE::detail + #endif diff --git a/src/py_converters_11.h b/src/py_converters_11.h index ef5d8989c072..b093f71b181a 100644 --- a/src/py_converters_11.h +++ b/src/py_converters_11.h @@ -8,15 +8,45 @@ namespace py = pybind11; -#include - #include "agg_basics.h" #include "agg_color_rgba.h" #include "agg_trans_affine.h" -#include "path_converters.h" +#include "mplutils.h" void convert_trans_affine(const py::object& transform, agg::trans_affine& affine); +inline auto convert_points(py::array_t obj) +{ + if (!check_trailing_shape(obj, "points", 2)) { + throw py::error_already_set(); + } + return obj.unchecked<2>(); +} + +inline auto convert_transforms(py::array_t obj) +{ + if (!check_trailing_shape(obj, "transforms", 3, 3)) { + throw py::error_already_set(); + } + return obj.unchecked<3>(); +} + +inline auto convert_bboxes(py::array_t obj) +{ + if (!check_trailing_shape(obj, "bbox array", 2, 2)) { + throw py::error_already_set(); + } + return obj.unchecked<3>(); +} + +inline auto convert_colors(py::array_t obj) +{ + if (!check_trailing_shape(obj, "colors", 4)) { + throw py::error_already_set(); + } + return obj.unchecked<2>(); +} + namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster { public: @@ -118,185 +148,6 @@ namespace PYBIND11_NAMESPACE { namespace detail { return true; } }; - - template <> struct type_caster { - public: - PYBIND11_TYPE_CASTER(e_snap_mode, const_name("e_snap_mode")); - - bool load(handle src, bool) { - if (src.is_none()) { - value = SNAP_AUTO; - return true; - } - - value = src.cast() ? SNAP_TRUE : SNAP_FALSE; - - return true; - } - }; - -/* 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")); - - bool load(handle src, bool) { - if (src.is_none()) { - return true; - } - - 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.inc_ref().ptr(), codes.inc_ref().ptr(), - should_simplify, simplify_threshold)) { - throw py::error_already_set(); - } - - return true; - } - }; -#endif - -/* 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")); - - bool load(handle src, bool) { - if (src.is_none()) { - value.scale = 0.0; - value.length = 0.0; - value.randomness = 0.0; - return true; - } - - auto params = src.cast>(); - std::tie(value.scale, value.length, value.randomness) = params; - - 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 #endif /* MPL_PY_CONVERTERS_11_H */