From 879779b568ad7536a398da55664b9b29e8a35e21 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 11 Oct 2023 07:45:59 -0400 Subject: [PATCH 1/7] Convert path extension to pybind11 --- src/_path_wrapper.cpp | 880 +++++++++++++++++------------------------- src/meson.build | 3 +- 2 files changed, 347 insertions(+), 536 deletions(-) diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 72774576574a..bc2314952e56 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -1,138 +1,111 @@ +#include +#include + +#include +#include +#include +#include +#include + #include "numpy_cpp.h" #include "_path.h" #include "py_converters.h" +#include "py_converters_11.h" #include "py_adaptors.h" -PyObject *convert_polygon_vector(std::vector &polygons) +namespace py = pybind11; +using namespace pybind11::literals; + +py::list +convert_polygon_vector(std::vector &polygons) { - PyObject *pyresult = PyList_New(polygons.size()); + auto result = py::list(polygons.size()); for (size_t i = 0; i < polygons.size(); ++i) { Polygon poly = polygons[i]; - npy_intp dims[2]; - dims[1] = 2; - - dims[0] = (npy_intp)poly.size(); - - numpy::array_view subresult(dims); - memcpy(subresult.data(), &poly[0], sizeof(double) * poly.size() * 2); - - if (PyList_SetItem(pyresult, i, subresult.pyobj())) { - Py_DECREF(pyresult); - return NULL; - } + py::ssize_t dims[] = { static_cast(poly.size()), 2 }; + result[i] = py::array(dims, reinterpret_cast(poly.data())); } - return pyresult; + return result; } -const char *Py_point_in_path__doc__ = - "point_in_path(x, y, radius, path, trans)\n" - "--\n\n"; - -static PyObject *Py_point_in_path(PyObject *self, PyObject *args) +static bool +Py_point_in_path(double x, double y, double r, py::object path_obj, + py::object trans_obj) { - double x, y, r; mpl::PathIterator path; agg::trans_affine trans; - bool result; - if (!PyArg_ParseTuple(args, - "dddO&O&:point_in_path", - &x, - &y, - &r, - &convert_path, - &path, - &convert_trans_affine, - &trans)) { - return NULL; + if (!convert_path(path_obj.ptr(), &path)) { + throw py::error_already_set(); } + convert_trans_affine(trans_obj, trans); - CALL_CPP("point_in_path", (result = point_in_path(x, y, r, path, trans))); - - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } + return point_in_path(x, y, r, path, trans); } -const char *Py_points_in_path__doc__ = - "points_in_path(points, radius, path, trans)\n" - "--\n\n"; - -static PyObject *Py_points_in_path(PyObject *self, PyObject *args) +static py::array_t +Py_points_in_path(py::array_t points_obj, double r, py::object path_obj, + py::object trans_obj) { - numpy::array_view points; - double r; + numpy::array_view points; mpl::PathIterator path; agg::trans_affine trans; - if (!PyArg_ParseTuple(args, - "O&dO&O&:points_in_path", - &convert_points, - &points, - &r, - &convert_path, - &path, - &convert_trans_affine, - &trans)) { - return NULL; + if (!convert_points(points_obj.ptr(), &points)) { + throw py::error_already_set(); + } + if (!convert_path(path_obj.ptr(), &path)) { + throw py::error_already_set(); } + convert_trans_affine(trans_obj, trans); if (!check_trailing_shape(points, "points", 2)) { - return NULL; + throw py::error_already_set(); } - npy_intp dims[] = { (npy_intp)points.shape(0) }; - numpy::array_view results(dims); + py::ssize_t dims[] = { static_cast(points.size()) }; + py::array_t results(dims); + auto results_mutable = results.mutable_unchecked<1>(); - CALL_CPP("points_in_path", (points_in_path(points, r, path, trans, results))); + points_in_path(points, r, path, trans, results_mutable); - return results.pyobj(); + return results; } -const char *Py_update_path_extents__doc__ = - "update_path_extents(path, trans, rect, minpos, ignore)\n" - "--\n\n"; - -static PyObject *Py_update_path_extents(PyObject *self, PyObject *args) +static py::tuple +Py_update_path_extents(py::object path_obj, py::object trans_obj, py::object rect_obj, + py::array_t minpos, bool ignore) { mpl::PathIterator path; agg::trans_affine trans; agg::rect_d rect; - numpy::array_view minpos; - int ignore; - int changed; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&i:update_path_extents", - &convert_path, - &path, - &convert_trans_affine, - &trans, - &convert_rect, - &rect, - &minpos.converter, - &minpos, - &ignore)) { - return NULL; + bool changed; + + if (!convert_path(path_obj.ptr(), &path)) { + throw py::error_already_set(); + } + convert_trans_affine(trans_obj, trans); + if (!convert_rect(rect_obj.ptr(), &rect)) { + throw py::error_already_set(); } + if (minpos.ndim() != 1) { + throw py::value_error( + "minpos must be 1D, got " + std::to_string(minpos.ndim())); + } if (minpos.shape(0) != 2) { - PyErr_Format(PyExc_ValueError, - "minpos must be of length 2, got %" NPY_INTP_FMT, - minpos.shape(0)); - return NULL; + throw py::value_error( + "minpos must be of length 2, got " + std::to_string(minpos.shape(0))); } extent_limits e; if (ignore) { - CALL_CPP("update_path_extents", reset_limits(e)); + reset_limits(e); } else { if (rect.x1 > rect.x2) { e.x0 = std::numeric_limits::infinity(); @@ -148,37 +121,34 @@ static PyObject *Py_update_path_extents(PyObject *self, PyObject *args) e.y0 = rect.y1; e.y1 = rect.y2; } - e.xm = minpos(0); - e.ym = minpos(1); + e.xm = *minpos.data(0); + e.ym = *minpos.data(1); } - CALL_CPP("update_path_extents", (update_path_extents(path, trans, e))); + update_path_extents(path, trans, e); changed = (e.x0 != rect.x1 || e.y0 != rect.y1 || e.x1 != rect.x2 || e.y1 != rect.y2 || - e.xm != minpos(0) || e.ym != minpos(1)); - - npy_intp extentsdims[] = { 2, 2 }; - numpy::array_view outextents(extentsdims); - outextents(0, 0) = e.x0; - outextents(0, 1) = e.y0; - outextents(1, 0) = e.x1; - outextents(1, 1) = e.y1; - - npy_intp minposdims[] = { 2 }; - numpy::array_view outminpos(minposdims); - outminpos(0) = e.xm; - outminpos(1) = e.ym; - - return Py_BuildValue( - "NNi", outextents.pyobj(), outminpos.pyobj(), changed); -} + e.xm != *minpos.data(0) || e.ym != *minpos.data(1)); + + py::ssize_t extentsdims[] = { 2, 2 }; + py::array_t outextents(extentsdims); + *outextents.mutable_data(0, 0) = e.x0; + *outextents.mutable_data(0, 1) = e.y0; + *outextents.mutable_data(1, 0) = e.x1; + *outextents.mutable_data(1, 1) = e.y1; + + py::ssize_t minposdims[] = { 2 }; + py::array_t outminpos(minposdims); + *outminpos.mutable_data(0) = e.xm; + *outminpos.mutable_data(1) = e.ym; -const char *Py_get_path_collection_extents__doc__ = - "get_path_collection_extents(" - "master_transform, paths, transforms, offsets, offset_transform)\n" - "--\n\n"; + return py::make_tuple(outextents, outminpos, changed); +} -static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args) +static py::tuple +Py_get_path_collection_extents(py::object master_transform_obj, py::object paths_obj, + py::object transforms_obj, py::object offsets_obj, + py::object offset_trans_obj) { agg::trans_affine master_transform; mpl::PathGenerator paths; @@ -187,391 +157,248 @@ static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args) agg::trans_affine offset_trans; extent_limits e; - if (!PyArg_ParseTuple(args, - "O&O&O&O&O&:get_path_collection_extents", - &convert_trans_affine, - &master_transform, - &convert_pathgen, - &paths, - &convert_transforms, - &transforms, - &convert_points, - &offsets, - &convert_trans_affine, - &offset_trans)) { - return NULL; - } - - CALL_CPP("get_path_collection_extents", - (get_path_collection_extents( - master_transform, paths, transforms, offsets, offset_trans, e))); - - npy_intp dims[] = { 2, 2 }; - numpy::array_view extents(dims); - extents(0, 0) = e.x0; - extents(0, 1) = e.y0; - extents(1, 0) = e.x1; - extents(1, 1) = e.y1; - - npy_intp minposdims[] = { 2 }; - numpy::array_view minpos(minposdims); - minpos(0) = e.xm; - minpos(1) = e.ym; - - return Py_BuildValue("NN", extents.pyobj(), minpos.pyobj()); -} + convert_trans_affine(master_transform_obj, master_transform); + 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(); + } + convert_trans_affine(offset_trans_obj, offset_trans); + + get_path_collection_extents( + master_transform, paths, transforms, offsets, offset_trans, e); -const char *Py_point_in_path_collection__doc__ = - "point_in_path_collection(" - "x, y, radius, master_transform, paths, transforms, offsets, " - "offset_trans, filled)\n" - "--\n\n"; + py::ssize_t dims[] = { 2, 2 }; + py::array_t extents(dims); + *extents.mutable_data(0, 0) = e.x0; + *extents.mutable_data(0, 1) = e.y0; + *extents.mutable_data(1, 0) = e.x1; + *extents.mutable_data(1, 1) = e.y1; -static PyObject *Py_point_in_path_collection(PyObject *self, PyObject *args) + py::ssize_t minposdims[] = { 2 }; + py::array_t minpos(minposdims); + *minpos.mutable_data(0) = e.xm; + *minpos.mutable_data(1) = e.ym; + + return py::make_tuple(extents, minpos); +} + +static py::object +Py_point_in_path_collection(double x, double y, double radius, + py::object master_transform_obj, py::object paths_obj, + py::object transforms_obj, py::object offsets_obj, + py::object offset_trans_obj, bool filled) { - double x, y, radius; agg::trans_affine master_transform; mpl::PathGenerator paths; numpy::array_view transforms; numpy::array_view offsets; agg::trans_affine offset_trans; - bool filled; std::vector result; - if (!PyArg_ParseTuple(args, - "dddO&O&O&O&O&O&:point_in_path_collection", - &x, - &y, - &radius, - &convert_trans_affine, - &master_transform, - &convert_pathgen, - &paths, - &convert_transforms, - &transforms, - &convert_points, - &offsets, - &convert_trans_affine, - &offset_trans, - &convert_bool, - &filled)) { - return NULL; - } - - CALL_CPP("point_in_path_collection", - (point_in_path_collection(x, - y, - radius, - master_transform, - paths, - transforms, - offsets, - offset_trans, - filled, - result))); - - npy_intp dims[] = {(npy_intp)result.size() }; - numpy::array_view pyresult(dims); - if (result.size() > 0) { - memcpy(pyresult.data(), &result[0], result.size() * sizeof(int)); - } - return pyresult.pyobj(); -} + convert_trans_affine(master_transform_obj, master_transform); + 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(); + } + convert_trans_affine(offset_trans_obj, offset_trans); -const char *Py_path_in_path__doc__ = - "path_in_path(path_a, trans_a, path_b, trans_b)\n" - "--\n\n"; + point_in_path_collection(x, y, radius, master_transform, paths, transforms, offsets, + offset_trans, filled, result); -static PyObject *Py_path_in_path(PyObject *self, PyObject *args) + py::ssize_t dims[] = { static_cast(result.size()) }; + return py::array(dims, result.data()); +} + +static bool +Py_path_in_path(py::object a_obj, py::object atrans_obj, + py::object b_obj, py::object btrans_obj) { mpl::PathIterator a; agg::trans_affine atrans; mpl::PathIterator b; agg::trans_affine btrans; - bool result; - if (!PyArg_ParseTuple(args, - "O&O&O&O&:path_in_path", - &convert_path, - &a, - &convert_trans_affine, - &atrans, - &convert_path, - &b, - &convert_trans_affine, - &btrans)) { - return NULL; + if (!convert_path(a_obj.ptr(), &a)) { + throw py::error_already_set(); } - - CALL_CPP("path_in_path", (result = path_in_path(a, atrans, b, btrans))); - - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; + convert_trans_affine(atrans_obj, atrans); + if (!convert_path(b_obj.ptr(), &b)) { + throw py::error_already_set(); } -} + convert_trans_affine(btrans_obj, btrans); -const char *Py_clip_path_to_rect__doc__ = - "clip_path_to_rect(path, rect, inside)\n" - "--\n\n"; + return path_in_path(a, atrans, b, btrans); +} -static PyObject *Py_clip_path_to_rect(PyObject *self, PyObject *args) +static py::list +Py_clip_path_to_rect(py::object path_obj, py::object rect_obj, + bool inside) { mpl::PathIterator path; agg::rect_d rect; - bool inside; std::vector result; - if (!PyArg_ParseTuple(args, - "O&O&O&:clip_path_to_rect", - &convert_path, - &path, - &convert_rect, - &rect, - &convert_bool, - &inside)) { - return NULL; + if (!convert_path(path_obj.ptr(), &path)) { + throw py::error_already_set(); + } + if (!convert_rect(rect_obj.ptr(), &rect)) { + throw py::error_already_set(); } - CALL_CPP("clip_path_to_rect", (clip_path_to_rect(path, rect, inside, result))); + clip_path_to_rect(path, rect, inside, result); return convert_polygon_vector(result); } -const char *Py_affine_transform__doc__ = - "affine_transform(points, trans)\n" - "--\n\n"; - -static PyObject *Py_affine_transform(PyObject *self, PyObject *args) +static py::object +Py_affine_transform(py::array_t vertices_arr, + py::object trans_obj) { - PyObject *vertices_obj; agg::trans_affine trans; - if (!PyArg_ParseTuple(args, - "OO&:affine_transform", - &vertices_obj, - &convert_trans_affine, - &trans)) { - return NULL; - } - - PyArrayObject* vertices_arr = (PyArrayObject *)PyArray_ContiguousFromAny(vertices_obj, NPY_DOUBLE, 1, 2); - if (vertices_arr == NULL) { - return NULL; - } + convert_trans_affine(trans_obj, trans); - if (PyArray_NDIM(vertices_arr) == 2) { - numpy::array_view vertices(vertices_arr); - Py_DECREF(vertices_arr); + if (vertices_arr.ndim() == 2) { + auto vertices = vertices_arr.unchecked<2>(); if(!check_trailing_shape(vertices, "vertices", 2)) { - return NULL; + throw py::error_already_set(); } - npy_intp dims[] = { (npy_intp)vertices.shape(0), 2 }; - numpy::array_view result(dims); - CALL_CPP("affine_transform", (affine_transform_2d(vertices, trans, result))); - return result.pyobj(); - } else { // PyArray_NDIM(vertices_arr) == 1 - numpy::array_view vertices(vertices_arr); - Py_DECREF(vertices_arr); + py::ssize_t dims[] = { vertices.shape(0), 2 }; + py::array_t result(dims); + auto result_mutable = result.mutable_unchecked<2>(); + + affine_transform_2d(vertices, trans, result_mutable); + return result; + } else if (vertices_arr.ndim() == 1) { + auto vertices = vertices_arr.unchecked<1>(); - npy_intp dims[] = { (npy_intp)vertices.shape(0) }; - numpy::array_view result(dims); - CALL_CPP("affine_transform", (affine_transform_1d(vertices, trans, result))); - return result.pyobj(); + py::ssize_t dims[] = { vertices.shape(0) }; + py::array_t result(dims); + auto result_mutable = result.mutable_unchecked<1>(); + + affine_transform_1d(vertices, trans, result_mutable); + return result; + } else { + throw py::value_error( + "vertices must be 1D or 2D, not" + std::to_string(vertices_arr.ndim()) + "D"); } } -const char *Py_count_bboxes_overlapping_bbox__doc__ = - "count_bboxes_overlapping_bbox(bbox, bboxes)\n" - "--\n\n"; - -static PyObject *Py_count_bboxes_overlapping_bbox(PyObject *self, PyObject *args) +static int +Py_count_bboxes_overlapping_bbox(py::object bbox_obj, py::object bboxes_obj) { agg::rect_d bbox; numpy::array_view bboxes; - int result; - if (!PyArg_ParseTuple(args, - "O&O&:count_bboxes_overlapping_bbox", - &convert_rect, - &bbox, - &convert_bboxes, - &bboxes)) { - return NULL; + if (!convert_rect(bbox_obj.ptr(), &bbox)) { + throw py::error_already_set(); + } + if (!convert_bboxes(bboxes_obj.ptr(), &bboxes)) { + throw py::error_already_set(); } - CALL_CPP("count_bboxes_overlapping_bbox", - (result = count_bboxes_overlapping_bbox(bbox, bboxes))); - - return PyLong_FromLong(result); + return count_bboxes_overlapping_bbox(bbox, bboxes); } -const char *Py_path_intersects_path__doc__ = - "path_intersects_path(path1, path2, filled=False)\n" - "--\n\n"; - -static PyObject *Py_path_intersects_path(PyObject *self, PyObject *args, PyObject *kwds) +static bool +Py_path_intersects_path(py::object p1_obj, py::object p2_obj, bool filled) { mpl::PathIterator p1; mpl::PathIterator p2; agg::trans_affine t1; agg::trans_affine t2; - int filled = 0; - const char *names[] = { "p1", "p2", "filled", NULL }; bool result; - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O&O&i:path_intersects_path", - (char **)names, - &convert_path, - &p1, - &convert_path, - &p2, - &filled)) { - return NULL; + if (!convert_path(p1_obj.ptr(), &p1)) { + throw py::error_already_set(); + } + if (!convert_path(p2_obj.ptr(), &p2)) { + throw py::error_already_set(); } - CALL_CPP("path_intersects_path", (result = path_intersects_path(p1, p2))); + result = path_intersects_path(p1, p2); if (filled) { if (!result) { - CALL_CPP("path_intersects_path", - (result = path_in_path(p1, t1, p2, t2))); + result = path_in_path(p1, t1, p2, t2); } if (!result) { - CALL_CPP("path_intersects_path", - (result = path_in_path(p2, t1, p1, t2))); + result = path_in_path(p2, t1, p1, t2); } } - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } + return result; } -const char *Py_path_intersects_rectangle__doc__ = - "path_intersects_rectangle(" - "path, rect_x1, rect_y1, rect_x2, rect_y2, filled=False)\n" - "--\n\n"; - -static PyObject *Py_path_intersects_rectangle(PyObject *self, PyObject *args, PyObject *kwds) +static bool +Py_path_intersects_rectangle(py::object path_obj, double rect_x1, double rect_y1, + double rect_x2, double rect_y2, bool filled) { mpl::PathIterator path; - double rect_x1, rect_y1, rect_x2, rect_y2; - bool filled = false; - const char *names[] = { "path", "rect_x1", "rect_y1", "rect_x2", "rect_y2", "filled", NULL }; - bool result; - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O&dddd|O&:path_intersects_rectangle", - (char **)names, - &convert_path, - &path, - &rect_x1, - &rect_y1, - &rect_x2, - &rect_y2, - &convert_bool, - &filled)) { - return NULL; - } - - CALL_CPP("path_intersects_rectangle", (result = path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled))); - - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; + if (!convert_path(path_obj.ptr(), &path)) { + throw py::error_already_set(); } -} -const char *Py_convert_path_to_polygons__doc__ = - "convert_path_to_polygons(path, trans, width=0, height=0)\n" - "--\n\n"; + return path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled); +} -static PyObject *Py_convert_path_to_polygons(PyObject *self, PyObject *args, PyObject *kwds) +static py::list +Py_convert_path_to_polygons(py::object path_obj, py::object trans_obj, + double width, double height, bool closed_only) { mpl::PathIterator path; agg::trans_affine trans; - double width = 0.0, height = 0.0; - int closed_only = 1; std::vector result; - const char *names[] = { "path", "transform", "width", "height", "closed_only", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O&O&|ddi:convert_path_to_polygons", - (char **)names, - &convert_path, - &path, - &convert_trans_affine, - &trans, - &width, - &height, - &closed_only)) { - return NULL; + if (!convert_path(path_obj.ptr(), &path)) { + throw py::error_already_set(); } + convert_trans_affine(trans_obj, trans); - CALL_CPP("convert_path_to_polygons", - (convert_path_to_polygons(path, trans, width, height, closed_only, result))); + convert_path_to_polygons(path, trans, width, height, closed_only, result); return convert_polygon_vector(result); } -const char *Py_cleanup_path__doc__ = - "cleanup_path(" - "path, trans, remove_nans, clip_rect, snap_mode, stroke_width, simplify, " - "return_curves, sketch)\n" - "--\n\n"; - -static PyObject *Py_cleanup_path(PyObject *self, PyObject *args) +static py::tuple +Py_cleanup_path(py::object path_obj, py::object trans_obj, bool remove_nans, + py::object clip_rect_obj, py::object snap_mode_obj, double stroke_width, + std::optional simplify, bool return_curves, py::object sketch_obj) { mpl::PathIterator path; agg::trans_affine trans; - bool remove_nans; agg::rect_d clip_rect; e_snap_mode snap_mode; - double stroke_width; - PyObject *simplifyobj; - bool simplify = false; - bool return_curves; SketchParams sketch; - if (!PyArg_ParseTuple(args, - "O&O&O&O&O&dOO&O&:cleanup_path", - &convert_path, - &path, - &convert_trans_affine, - &trans, - &convert_bool, - &remove_nans, - &convert_rect, - &clip_rect, - &convert_snap, - &snap_mode, - &stroke_width, - &simplifyobj, - &convert_bool, - &return_curves, - &convert_sketch_params, - &sketch)) { - return NULL; - } - - if (simplifyobj == Py_None) { + if (!convert_path(path_obj.ptr(), &path)) { + throw py::error_already_set(); + } + convert_trans_affine(trans_obj, trans); + if (!convert_rect(clip_rect_obj.ptr(), &clip_rect)) { + throw py::error_already_set(); + } + if (!convert_snap(snap_mode_obj.ptr(), &snap_mode)) { + throw py::error_already_set(); + } + if (!convert_sketch_params(sketch_obj.ptr(), &sketch)) { + throw py::error_already_set(); + } + + if (!simplify.has_value()) { simplify = path.should_simplify(); - } else { - switch (PyObject_IsTrue(simplifyobj)) { - case 0: simplify = false; break; - case 1: simplify = true; break; - default: return NULL; // errored. - } } bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2); @@ -579,139 +406,106 @@ static PyObject *Py_cleanup_path(PyObject *self, PyObject *args) std::vector vertices; std::vector codes; - CALL_CPP("cleanup_path", - (cleanup_path(path, - trans, - remove_nans, - do_clip, - clip_rect, - snap_mode, - stroke_width, - simplify, - return_curves, - sketch, - vertices, - codes))); - - size_t length = codes.size(); + cleanup_path(path, trans, remove_nans, do_clip, clip_rect, snap_mode, stroke_width, + simplify.value(), return_curves, sketch, vertices, codes); - npy_intp vertices_dims[] = {(npy_intp)length, 2 }; - numpy::array_view pyvertices(vertices_dims); + auto length = static_cast(codes.size()); - npy_intp codes_dims[] = {(npy_intp)length }; - numpy::array_view pycodes(codes_dims); + py::ssize_t vertices_dims[] = { length, 2 }; + py::array pyvertices(vertices_dims, vertices.data()); - memcpy(pyvertices.data(), &vertices[0], sizeof(double) * 2 * length); - memcpy(pycodes.data(), &codes[0], sizeof(unsigned char) * length); + py::ssize_t codes_dims[] = { length }; + py::array pycodes(codes_dims, codes.data()); - return Py_BuildValue("NN", pyvertices.pyobj(), pycodes.pyobj()); + return py::make_tuple(pyvertices, pycodes); } const char *Py_convert_to_string__doc__ = - "convert_to_string(" - "path, trans, clip_rect, simplify, sketch, precision, codes, postfix)\n" - "--\n\n" - "Convert *path* to a bytestring.\n" - "\n" - "The first five parameters (up to *sketch*) are interpreted as in\n" - "`.cleanup_path`. The following ones are detailed below.\n" - "\n" - "Parameters\n" - "----------\n" - "path : Path\n" - "trans : Transform or None\n" - "clip_rect : sequence of 4 floats, or None\n" - "simplify : bool\n" - "sketch : tuple of 3 floats, or None\n" - "precision : int\n" - " The precision used to \"%.*f\"-format the values. Trailing zeros\n" - " and decimal points are always removed. (precision=-1 is a special\n" - " case used to implement ttconv-back-compatible conversion.)\n" - "codes : sequence of 5 bytestrings\n" - " The bytes representation of each opcode (MOVETO, LINETO, CURVE3,\n" - " CURVE4, CLOSEPOLY), in that order. If the bytes for CURVE3 is\n" - " empty, quad segments are automatically converted to cubic ones\n" - " (this is used by backends such as pdf and ps, which do not support\n" - " quads).\n" - "postfix : bool\n" - " Whether the opcode comes after the values (True) or before (False).\n" - ; - -static PyObject *Py_convert_to_string(PyObject *self, PyObject *args) +R"""(-- + +Convert *path* to a bytestring. + +The first five parameters (up to *sketch*) are interpreted as in `.cleanup_path`. The +following ones are detailed below. + +Parameters +---------- +path : Path +trans : Transform or None +clip_rect : sequence of 4 floats, or None +simplify : bool +sketch : tuple of 3 floats, or None +precision : int + The precision used to "%.*f"-format the values. Trailing zeros and decimal points + are always removed. (precision=-1 is a special case used to implement + ttconv-back-compatible conversion.) +codes : sequence of 5 bytestrings + The bytes representation of each opcode (MOVETO, LINETO, CURVE3, CURVE4, CLOSEPOLY), + in that order. If the bytes for CURVE3 is empty, quad segments are automatically + converted to cubic ones (this is used by backends such as pdf and ps, which do not + support quads). +postfix : bool + Whether the opcode comes after the values (True) or before (False). +)"""; + +static py::object +Py_convert_to_string(py::object path_obj, py::object trans_obj, py::object cliprect_obj, + std::optional simplify, py::object sketch_obj, int precision, + std::array codes_obj, bool postfix) { mpl::PathIterator path; agg::trans_affine trans; agg::rect_d cliprect; - PyObject *simplifyobj; - bool simplify = false; SketchParams sketch; - int precision; char *codes[5]; - bool postfix; std::string buffer; bool status; - if (!PyArg_ParseTuple(args, - "O&O&O&OO&i(yyyyy)O&:convert_to_string", - &convert_path, - &path, - &convert_trans_affine, - &trans, - &convert_rect, - &cliprect, - &simplifyobj, - &convert_sketch_params, - &sketch, - &precision, - &codes[0], - &codes[1], - &codes[2], - &codes[3], - &codes[4], - &convert_bool, - &postfix)) { - return NULL; - } - - if (simplifyobj == Py_None) { + if (!convert_path(path_obj.ptr(), &path)) { + throw py::error_already_set(); + } + convert_trans_affine(trans_obj, trans); + if (!convert_rect(cliprect_obj.ptr(), &cliprect)) { + throw py::error_already_set(); + } + if (!convert_sketch_params(sketch_obj.ptr(), &sketch)) { + throw py::error_already_set(); + } + + for (auto i = 0; i < 5; ++i) { + codes[i] = const_cast(codes_obj[i].c_str()); + } + + if (!simplify.has_value()) { simplify = path.should_simplify(); - } else { - switch (PyObject_IsTrue(simplifyobj)) { - case 0: simplify = false; break; - case 1: simplify = true; break; - default: return NULL; // errored. - } } - CALL_CPP("convert_to_string", - (status = convert_to_string( - path, trans, cliprect, simplify, sketch, - precision, codes, postfix, buffer))); + status = convert_to_string(path, trans, cliprect, simplify.value(), sketch, + precision, codes, postfix, buffer); if (!status) { - PyErr_SetString(PyExc_ValueError, "Malformed path codes"); - return NULL; + throw py::value_error("Malformed path codes"); } - return PyBytes_FromStringAndSize(buffer.c_str(), buffer.size()); + return py::bytes(buffer); } - const char *Py_is_sorted_and_has_non_nan__doc__ = - "is_sorted_and_has_non_nan(array, /)\n" - "--\n\n" - "Return whether the 1D *array* is monotonically increasing, ignoring NaNs,\n" - "and has at least one non-nan value."; +R"""(-- + +Return whether the 1D *array* is monotonically increasing, ignoring NaNs, and has at +least one non-nan value.)"""; -static PyObject *Py_is_sorted_and_has_non_nan(PyObject *self, PyObject *obj) +static bool +Py_is_sorted_and_has_non_nan(py::object obj) { bool result; PyArrayObject *array = (PyArrayObject *)PyArray_CheckFromAny( - obj, NULL, 1, 1, NPY_ARRAY_NOTSWAPPED, NULL); + obj.ptr(), NULL, 1, 1, NPY_ARRAY_NOTSWAPPED, NULL); if (array == NULL) { - return NULL; + throw py::error_already_set(); } /* Handle just the most common types here, otherwise coerce to double */ @@ -733,48 +527,64 @@ static PyObject *Py_is_sorted_and_has_non_nan(PyObject *self, PyObject *obj) break; default: Py_DECREF(array); - array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1); + array = (PyArrayObject *)PyArray_FromObject(obj.ptr(), NPY_DOUBLE, 1, 1); if (array == NULL) { - return NULL; + throw py::error_already_set(); } result = is_sorted_and_has_non_nan(array); } Py_DECREF(array); - if (result) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } + return result; } - -static PyMethodDef module_functions[] = { - {"point_in_path", (PyCFunction)Py_point_in_path, METH_VARARGS, Py_point_in_path__doc__}, - {"points_in_path", (PyCFunction)Py_points_in_path, METH_VARARGS, Py_points_in_path__doc__}, - {"update_path_extents", (PyCFunction)Py_update_path_extents, METH_VARARGS, Py_update_path_extents__doc__}, - {"get_path_collection_extents", (PyCFunction)Py_get_path_collection_extents, METH_VARARGS, Py_get_path_collection_extents__doc__}, - {"point_in_path_collection", (PyCFunction)Py_point_in_path_collection, METH_VARARGS, Py_point_in_path_collection__doc__}, - {"path_in_path", (PyCFunction)Py_path_in_path, METH_VARARGS, Py_path_in_path__doc__}, - {"clip_path_to_rect", (PyCFunction)Py_clip_path_to_rect, METH_VARARGS, Py_clip_path_to_rect__doc__}, - {"affine_transform", (PyCFunction)Py_affine_transform, METH_VARARGS, Py_affine_transform__doc__}, - {"count_bboxes_overlapping_bbox", (PyCFunction)Py_count_bboxes_overlapping_bbox, METH_VARARGS, Py_count_bboxes_overlapping_bbox__doc__}, - {"path_intersects_path", (PyCFunction)Py_path_intersects_path, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_path__doc__}, - {"path_intersects_rectangle", (PyCFunction)Py_path_intersects_rectangle, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_rectangle__doc__}, - {"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS|METH_KEYWORDS, Py_convert_path_to_polygons__doc__}, - {"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__}, - {"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__}, - {"is_sorted_and_has_non_nan", (PyCFunction)Py_is_sorted_and_has_non_nan, METH_O, Py_is_sorted_and_has_non_nan__doc__}, - {NULL} -}; - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, "_path", NULL, 0, module_functions -}; - -PyMODINIT_FUNC PyInit__path(void) +PYBIND11_MODULE(_path, m) { - import_array(); - return PyModule_Create(&moduledef); + auto ia = [m]() -> const void* { + import_array(); + return &m; + }; + if (ia() == NULL) { + throw py::error_already_set(); + } + + m.def("point_in_path", &Py_point_in_path, + "x"_a, "y"_a, "radius"_a, "path"_a, "trans"_a); + m.def("points_in_path", &Py_points_in_path, + "points"_a, "radius"_a, "path"_a, "trans"_a); + m.def("update_path_extents", &Py_update_path_extents, + "path"_a, "trans"_a, "rect"_a, "minpos"_a, "ignore"_a); + m.def("get_path_collection_extents", &Py_get_path_collection_extents, + "master_transform"_a, "paths"_a, "transforms"_a, "offsets"_a, + "offset_transform"_a); + m.def("point_in_path_collection", &Py_point_in_path_collection, + "x"_a, "y"_a, "radius"_a, "master_transform"_a, "paths"_a, "transforms"_a, + "offsets"_a, "offset_trans"_a, "filled"_a); + m.def("path_in_path", &Py_path_in_path, + "path_a"_a, "trans_a"_a, "path_b"_a, "trans_b"_a); + m.def("clip_path_to_rect", &Py_clip_path_to_rect, + "path"_a, "rect"_a, "inside"_a); + m.def("affine_transform", &Py_affine_transform, + "points"_a, "trans"_a); + m.def("count_bboxes_overlapping_bbox", &Py_count_bboxes_overlapping_bbox, + "bbox"_a, "bboxes"_a); + m.def("path_intersects_path", &Py_path_intersects_path, + "path1"_a, "path2"_a, "filled"_a = false); + m.def("path_intersects_rectangle", &Py_path_intersects_rectangle, + "path"_a, "rect_x1"_a, "rect_y1"_a, "rect_x2"_a, "rect_y2"_a, + "filled"_a = false); + m.def("convert_path_to_polygons", &Py_convert_path_to_polygons, + "path"_a, "trans"_a, "width"_a = 0.0, "height"_a = 0.0, + "closed_only"_a = false); + m.def("cleanup_path", &Py_cleanup_path, + "path"_a, "trans"_a, "remove_nans"_a, "clip_rect"_a, "snap_mode"_a, + "stroke_width"_a, "simplify"_a, "return_curves"_a, "sketch"_a); + m.def("convert_to_string", &Py_convert_to_string, + "path"_a, "trans"_a, "clip_rect"_a, "simplify"_a, "sketch"_a, "precision"_a, + "codes"_a, "postfix"_a, + Py_convert_to_string__doc__); + m.def("is_sorted_and_has_non_nan", &Py_is_sorted_and_has_non_nan, + "array"_a, + Py_is_sorted_and_has_non_nan__doc__); } diff --git a/src/meson.build b/src/meson.build index d83dab0ffc28..bbef93c13d92 100644 --- a/src/meson.build +++ b/src/meson.build @@ -118,9 +118,10 @@ extension_data = { 'subdir': 'matplotlib', 'sources': files( 'py_converters.cpp', + 'py_converters_11.cpp', '_path_wrapper.cpp', ), - 'dependencies': [numpy_dep, agg_dep], + 'dependencies': [numpy_dep, agg_dep, pybind11_dep], }, '_qhull': { 'subdir': 'matplotlib', From 25fbbd3527be8d65ea429246c27fee6f0db8606b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 11 Oct 2023 07:54:02 -0400 Subject: [PATCH 2/7] Add a pybind11 type caster for agg::rect_d --- src/_path_wrapper.cpp | 31 +++++---------------------- src/py_converters_11.h | 48 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index bc2314952e56..851b9129934e 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -77,21 +77,17 @@ Py_points_in_path(py::array_t points_obj, double r, py::object path_obj, } static py::tuple -Py_update_path_extents(py::object path_obj, py::object trans_obj, py::object rect_obj, +Py_update_path_extents(py::object path_obj, py::object trans_obj, agg::rect_d rect, py::array_t minpos, bool ignore) { mpl::PathIterator path; agg::trans_affine trans; - agg::rect_d rect; bool changed; if (!convert_path(path_obj.ptr(), &path)) { throw py::error_already_set(); } convert_trans_affine(trans_obj, trans); - if (!convert_rect(rect_obj.ptr(), &rect)) { - throw py::error_already_set(); - } if (minpos.ndim() != 1) { throw py::value_error( @@ -241,19 +237,14 @@ Py_path_in_path(py::object a_obj, py::object atrans_obj, } static py::list -Py_clip_path_to_rect(py::object path_obj, py::object rect_obj, - bool inside) +Py_clip_path_to_rect(py::object path_obj, agg::rect_d rect, bool inside) { mpl::PathIterator path; - agg::rect_d rect; std::vector result; if (!convert_path(path_obj.ptr(), &path)) { throw py::error_already_set(); } - if (!convert_rect(rect_obj.ptr(), &rect)) { - throw py::error_already_set(); - } clip_path_to_rect(path, rect, inside, result); @@ -297,14 +288,10 @@ Py_affine_transform(py::array_t bboxes; - if (!convert_rect(bbox_obj.ptr(), &bbox)) { - throw py::error_already_set(); - } if (!convert_bboxes(bboxes_obj.ptr(), &bboxes)) { throw py::error_already_set(); } @@ -374,12 +361,11 @@ Py_convert_path_to_polygons(py::object path_obj, py::object trans_obj, static py::tuple Py_cleanup_path(py::object path_obj, py::object trans_obj, bool remove_nans, - py::object clip_rect_obj, py::object snap_mode_obj, double stroke_width, + agg::rect_d clip_rect, py::object snap_mode_obj, double stroke_width, std::optional simplify, bool return_curves, py::object sketch_obj) { mpl::PathIterator path; agg::trans_affine trans; - agg::rect_d clip_rect; e_snap_mode snap_mode; SketchParams sketch; @@ -387,9 +373,6 @@ Py_cleanup_path(py::object path_obj, py::object trans_obj, bool remove_nans, throw py::error_already_set(); } convert_trans_affine(trans_obj, trans); - if (!convert_rect(clip_rect_obj.ptr(), &clip_rect)) { - throw py::error_already_set(); - } if (!convert_snap(snap_mode_obj.ptr(), &snap_mode)) { throw py::error_already_set(); } @@ -449,13 +432,12 @@ postfix : bool )"""; static py::object -Py_convert_to_string(py::object path_obj, py::object trans_obj, py::object cliprect_obj, +Py_convert_to_string(py::object path_obj, py::object trans_obj, agg::rect_d cliprect, std::optional simplify, py::object sketch_obj, int precision, std::array codes_obj, bool postfix) { mpl::PathIterator path; agg::trans_affine trans; - agg::rect_d cliprect; SketchParams sketch; char *codes[5]; std::string buffer; @@ -465,9 +447,6 @@ Py_convert_to_string(py::object path_obj, py::object trans_obj, py::object clipr throw py::error_already_set(); } convert_trans_affine(trans_obj, trans); - if (!convert_rect(cliprect_obj.ptr(), &cliprect)) { - throw py::error_already_set(); - } if (!convert_sketch_params(sketch_obj.ptr(), &sketch)) { throw py::error_already_set(); } diff --git a/src/py_converters_11.h b/src/py_converters_11.h index 9af617d3ee8b..b86955d29055 100644 --- a/src/py_converters_11.h +++ b/src/py_converters_11.h @@ -8,8 +8,54 @@ namespace py = pybind11; +#include "agg_basics.h" #include "agg_trans_affine.h" void convert_trans_affine(const py::object& transform, agg::trans_affine& affine); -#endif +namespace PYBIND11_NAMESPACE { namespace detail { + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(agg::rect_d, const_name("rect_d")); + + bool load(handle src, bool) { + if (src.is_none()) { + value.x1 = 0.0; + value.y1 = 0.0; + value.x2 = 0.0; + value.y2 = 0.0; + return true; + } + + auto rect_arr = py::array_t::ensure(src); + + if (rect_arr.ndim() == 2) { + if (rect_arr.shape(0) != 2 || rect_arr.shape(1) != 2) { + throw py::value_error("Invalid bounding box"); + } + + value.x1 = *rect_arr.data(0, 0); + value.y1 = *rect_arr.data(0, 1); + value.x2 = *rect_arr.data(1, 0); + value.y2 = *rect_arr.data(1, 1); + + } else if (rect_arr.ndim() == 1) { + if (rect_arr.shape(0) != 4) { + throw py::value_error("Invalid bounding box"); + } + + value.x1 = *rect_arr.data(0); + value.y1 = *rect_arr.data(1); + value.x2 = *rect_arr.data(2); + value.y2 = *rect_arr.data(3); + + } else { + throw py::value_error("Invalid bounding box"); + } + + return true; + } + }; +}} // namespace PYBIND11_NAMESPACE::detail + +#endif /* MPL_PY_CONVERTERS_11_H */ From 4a56e5223d6c30ac9aabc7e931d7da13a4fd0a73 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 11 Oct 2023 17:34:55 -0400 Subject: [PATCH 3/7] Add a pybind11 type caster for agg::trans_affine --- src/_path_wrapper.cpp | 56 +++++++++++------------------------------- src/py_converters_11.h | 28 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 851b9129934e..1381cf34f016 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -34,26 +34,23 @@ convert_polygon_vector(std::vector &polygons) static bool Py_point_in_path(double x, double y, double r, py::object path_obj, - py::object trans_obj) + agg::trans_affine trans) { mpl::PathIterator path; - agg::trans_affine trans; if (!convert_path(path_obj.ptr(), &path)) { throw py::error_already_set(); } - convert_trans_affine(trans_obj, trans); return point_in_path(x, y, r, path, trans); } static py::array_t Py_points_in_path(py::array_t points_obj, double r, py::object path_obj, - py::object trans_obj) + agg::trans_affine trans) { numpy::array_view points; mpl::PathIterator path; - agg::trans_affine trans; if (!convert_points(points_obj.ptr(), &points)) { throw py::error_already_set(); @@ -61,7 +58,6 @@ Py_points_in_path(py::array_t points_obj, double r, py::object path_obj, if (!convert_path(path_obj.ptr(), &path)) { throw py::error_already_set(); } - convert_trans_affine(trans_obj, trans); if (!check_trailing_shape(points, "points", 2)) { throw py::error_already_set(); @@ -77,17 +73,15 @@ Py_points_in_path(py::array_t points_obj, double r, py::object path_obj, } static py::tuple -Py_update_path_extents(py::object path_obj, py::object trans_obj, agg::rect_d rect, +Py_update_path_extents(py::object path_obj, agg::trans_affine trans, agg::rect_d rect, py::array_t minpos, bool ignore) { mpl::PathIterator path; - agg::trans_affine trans; bool changed; if (!convert_path(path_obj.ptr(), &path)) { throw py::error_already_set(); } - convert_trans_affine(trans_obj, trans); if (minpos.ndim() != 1) { throw py::value_error( @@ -142,18 +136,15 @@ Py_update_path_extents(py::object path_obj, py::object trans_obj, agg::rect_d re } static py::tuple -Py_get_path_collection_extents(py::object master_transform_obj, py::object paths_obj, - py::object transforms_obj, py::object offsets_obj, - py::object offset_trans_obj) +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) { - agg::trans_affine master_transform; mpl::PathGenerator paths; numpy::array_view transforms; numpy::array_view offsets; - agg::trans_affine offset_trans; extent_limits e; - convert_trans_affine(master_transform_obj, master_transform); if (!convert_pathgen(paths_obj.ptr(), &paths)) { throw py::error_already_set(); } @@ -163,7 +154,6 @@ Py_get_path_collection_extents(py::object master_transform_obj, py::object paths if (!convert_points(offsets_obj.ptr(), &offsets)) { throw py::error_already_set(); } - convert_trans_affine(offset_trans_obj, offset_trans); get_path_collection_extents( master_transform, paths, transforms, offsets, offset_trans, e); @@ -185,18 +175,15 @@ Py_get_path_collection_extents(py::object master_transform_obj, py::object paths static py::object Py_point_in_path_collection(double x, double y, double radius, - py::object master_transform_obj, py::object paths_obj, + agg::trans_affine master_transform, py::object paths_obj, py::object transforms_obj, py::object offsets_obj, - py::object offset_trans_obj, bool filled) + agg::trans_affine offset_trans, bool filled) { - agg::trans_affine master_transform; mpl::PathGenerator paths; numpy::array_view transforms; numpy::array_view offsets; - agg::trans_affine offset_trans; std::vector result; - convert_trans_affine(master_transform_obj, master_transform); if (!convert_pathgen(paths_obj.ptr(), &paths)) { throw py::error_already_set(); } @@ -206,7 +193,6 @@ Py_point_in_path_collection(double x, double y, double radius, if (!convert_points(offsets_obj.ptr(), &offsets)) { throw py::error_already_set(); } - convert_trans_affine(offset_trans_obj, offset_trans); point_in_path_collection(x, y, radius, master_transform, paths, transforms, offsets, offset_trans, filled, result); @@ -216,22 +202,18 @@ Py_point_in_path_collection(double x, double y, double radius, } static bool -Py_path_in_path(py::object a_obj, py::object atrans_obj, - py::object b_obj, py::object btrans_obj) +Py_path_in_path(py::object a_obj, agg::trans_affine atrans, + py::object b_obj, agg::trans_affine btrans) { mpl::PathIterator a; - agg::trans_affine atrans; mpl::PathIterator b; - agg::trans_affine btrans; if (!convert_path(a_obj.ptr(), &a)) { throw py::error_already_set(); } - convert_trans_affine(atrans_obj, atrans); if (!convert_path(b_obj.ptr(), &b)) { throw py::error_already_set(); } - convert_trans_affine(btrans_obj, btrans); return path_in_path(a, atrans, b, btrans); } @@ -253,12 +235,8 @@ Py_clip_path_to_rect(py::object path_obj, agg::rect_d rect, bool inside) static py::object Py_affine_transform(py::array_t vertices_arr, - py::object trans_obj) + agg::trans_affine trans) { - agg::trans_affine trans; - - convert_trans_affine(trans_obj, trans); - if (vertices_arr.ndim() == 2) { auto vertices = vertices_arr.unchecked<2>(); @@ -342,17 +320,15 @@ Py_path_intersects_rectangle(py::object path_obj, double rect_x1, double rect_y1 } static py::list -Py_convert_path_to_polygons(py::object path_obj, py::object trans_obj, +Py_convert_path_to_polygons(py::object path_obj, agg::trans_affine trans, double width, double height, bool closed_only) { mpl::PathIterator path; - agg::trans_affine trans; std::vector result; if (!convert_path(path_obj.ptr(), &path)) { throw py::error_already_set(); } - convert_trans_affine(trans_obj, trans); convert_path_to_polygons(path, trans, width, height, closed_only, result); @@ -360,19 +336,17 @@ Py_convert_path_to_polygons(py::object path_obj, py::object trans_obj, } static py::tuple -Py_cleanup_path(py::object path_obj, py::object trans_obj, bool remove_nans, +Py_cleanup_path(py::object path_obj, agg::trans_affine trans, bool remove_nans, agg::rect_d clip_rect, py::object snap_mode_obj, double stroke_width, std::optional simplify, bool return_curves, py::object sketch_obj) { mpl::PathIterator path; - agg::trans_affine trans; e_snap_mode snap_mode; SketchParams sketch; if (!convert_path(path_obj.ptr(), &path)) { throw py::error_already_set(); } - convert_trans_affine(trans_obj, trans); if (!convert_snap(snap_mode_obj.ptr(), &snap_mode)) { throw py::error_already_set(); } @@ -432,12 +406,11 @@ postfix : bool )"""; static py::object -Py_convert_to_string(py::object path_obj, py::object trans_obj, agg::rect_d cliprect, +Py_convert_to_string(py::object path_obj, agg::trans_affine trans, agg::rect_d cliprect, std::optional simplify, py::object sketch_obj, int precision, std::array codes_obj, bool postfix) { mpl::PathIterator path; - agg::trans_affine trans; SketchParams sketch; char *codes[5]; std::string buffer; @@ -446,7 +419,6 @@ Py_convert_to_string(py::object path_obj, py::object trans_obj, agg::rect_d clip if (!convert_path(path_obj.ptr(), &path)) { throw py::error_already_set(); } - convert_trans_affine(trans_obj, trans); if (!convert_sketch_params(sketch_obj.ptr(), &sketch)) { throw py::error_already_set(); } diff --git a/src/py_converters_11.h b/src/py_converters_11.h index b86955d29055..1ab354f2dc6a 100644 --- a/src/py_converters_11.h +++ b/src/py_converters_11.h @@ -56,6 +56,34 @@ namespace PYBIND11_NAMESPACE { namespace detail { return true; } }; + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(agg::trans_affine, const_name("trans_affine")); + + bool load(handle src, bool) { + // If None assume identity transform so leave affine unchanged + if (src.is_none()) { + return true; + } + + auto array = py::array_t::ensure(src); + if (!array || array.ndim() != 2 || + array.shape(0) != 3 || array.shape(1) != 3) { + throw std::invalid_argument("Invalid affine transformation matrix"); + } + + auto buffer = array.data(); + value.sx = buffer[0]; + value.shx = buffer[1]; + value.tx = buffer[2]; + value.shy = buffer[3]; + value.sy = buffer[4]; + value.ty = buffer[5]; + + return true; + } + }; }} // namespace PYBIND11_NAMESPACE::detail #endif /* MPL_PY_CONVERTERS_11_H */ From d32a372ea40cb31c7df259795695cf7ffbf38451 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 19 Oct 2023 21:46:24 -0400 Subject: [PATCH 4/7] Add a pybind11 type caster for mpl::PathIterator --- src/_path_wrapper.cpp | 87 ++++++++---------------------------------- src/py_converters_11.h | 26 +++++++++++++ 2 files changed, 41 insertions(+), 72 deletions(-) diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 1381cf34f016..733b0d80f09c 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -11,9 +11,9 @@ #include "_path.h" +#include "py_adaptors.h" #include "py_converters.h" #include "py_converters_11.h" -#include "py_adaptors.h" namespace py = pybind11; using namespace pybind11::literals; @@ -33,31 +33,21 @@ convert_polygon_vector(std::vector &polygons) } static bool -Py_point_in_path(double x, double y, double r, py::object path_obj, +Py_point_in_path(double x, double y, double r, mpl::PathIterator path, agg::trans_affine trans) { - mpl::PathIterator path; - - if (!convert_path(path_obj.ptr(), &path)) { - throw py::error_already_set(); - } - return point_in_path(x, y, r, path, trans); } static py::array_t -Py_points_in_path(py::array_t points_obj, double r, py::object path_obj, +Py_points_in_path(py::array_t points_obj, double r, mpl::PathIterator path, agg::trans_affine trans) { numpy::array_view points; - mpl::PathIterator path; if (!convert_points(points_obj.ptr(), &points)) { throw py::error_already_set(); } - if (!convert_path(path_obj.ptr(), &path)) { - throw py::error_already_set(); - } if (!check_trailing_shape(points, "points", 2)) { throw py::error_already_set(); @@ -73,16 +63,11 @@ Py_points_in_path(py::array_t points_obj, double r, py::object path_obj, } static py::tuple -Py_update_path_extents(py::object path_obj, agg::trans_affine trans, agg::rect_d rect, - py::array_t minpos, bool ignore) +Py_update_path_extents(mpl::PathIterator path, agg::trans_affine trans, + agg::rect_d rect, py::array_t minpos, bool ignore) { - mpl::PathIterator path; bool changed; - if (!convert_path(path_obj.ptr(), &path)) { - throw py::error_already_set(); - } - if (minpos.ndim() != 1) { throw py::value_error( "minpos must be 1D, got " + std::to_string(minpos.ndim())); @@ -202,32 +187,17 @@ Py_point_in_path_collection(double x, double y, double radius, } static bool -Py_path_in_path(py::object a_obj, agg::trans_affine atrans, - py::object b_obj, agg::trans_affine btrans) +Py_path_in_path(mpl::PathIterator a, agg::trans_affine atrans, + mpl::PathIterator b, agg::trans_affine btrans) { - mpl::PathIterator a; - mpl::PathIterator b; - - if (!convert_path(a_obj.ptr(), &a)) { - throw py::error_already_set(); - } - if (!convert_path(b_obj.ptr(), &b)) { - throw py::error_already_set(); - } - return path_in_path(a, atrans, b, btrans); } static py::list -Py_clip_path_to_rect(py::object path_obj, agg::rect_d rect, bool inside) +Py_clip_path_to_rect(mpl::PathIterator path, agg::rect_d rect, bool inside) { - mpl::PathIterator path; std::vector result; - if (!convert_path(path_obj.ptr(), &path)) { - throw py::error_already_set(); - } - clip_path_to_rect(path, rect, inside, result); return convert_polygon_vector(result); @@ -278,21 +248,12 @@ Py_count_bboxes_overlapping_bbox(agg::rect_d bbox, py::object bboxes_obj) } static bool -Py_path_intersects_path(py::object p1_obj, py::object p2_obj, bool filled) +Py_path_intersects_path(mpl::PathIterator p1, mpl::PathIterator p2, bool filled) { - mpl::PathIterator p1; - mpl::PathIterator p2; agg::trans_affine t1; agg::trans_affine t2; bool result; - if (!convert_path(p1_obj.ptr(), &p1)) { - throw py::error_already_set(); - } - if (!convert_path(p2_obj.ptr(), &p2)) { - throw py::error_already_set(); - } - result = path_intersects_path(p1, p2); if (filled) { if (!result) { @@ -307,46 +268,31 @@ Py_path_intersects_path(py::object p1_obj, py::object p2_obj, bool filled) } static bool -Py_path_intersects_rectangle(py::object path_obj, double rect_x1, double rect_y1, +Py_path_intersects_rectangle(mpl::PathIterator path, double rect_x1, double rect_y1, double rect_x2, double rect_y2, bool filled) { - mpl::PathIterator path; - - if (!convert_path(path_obj.ptr(), &path)) { - throw py::error_already_set(); - } - return path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled); } static py::list -Py_convert_path_to_polygons(py::object path_obj, agg::trans_affine trans, +Py_convert_path_to_polygons(mpl::PathIterator path, agg::trans_affine trans, double width, double height, bool closed_only) { - mpl::PathIterator path; std::vector result; - if (!convert_path(path_obj.ptr(), &path)) { - throw py::error_already_set(); - } - convert_path_to_polygons(path, trans, width, height, closed_only, result); return convert_polygon_vector(result); } static py::tuple -Py_cleanup_path(py::object path_obj, agg::trans_affine trans, bool remove_nans, +Py_cleanup_path(mpl::PathIterator path, agg::trans_affine trans, bool remove_nans, agg::rect_d clip_rect, py::object snap_mode_obj, double stroke_width, std::optional simplify, bool return_curves, py::object sketch_obj) { - mpl::PathIterator path; e_snap_mode snap_mode; SketchParams sketch; - if (!convert_path(path_obj.ptr(), &path)) { - throw py::error_already_set(); - } if (!convert_snap(snap_mode_obj.ptr(), &snap_mode)) { throw py::error_already_set(); } @@ -406,19 +352,16 @@ postfix : bool )"""; static py::object -Py_convert_to_string(py::object path_obj, agg::trans_affine trans, agg::rect_d cliprect, - std::optional simplify, py::object sketch_obj, int precision, +Py_convert_to_string(mpl::PathIterator path, agg::trans_affine trans, + agg::rect_d cliprect, std::optional simplify, + py::object sketch_obj, int precision, std::array codes_obj, bool postfix) { - mpl::PathIterator path; SketchParams sketch; char *codes[5]; std::string buffer; bool status; - if (!convert_path(path_obj.ptr(), &path)) { - throw py::error_already_set(); - } if (!convert_sketch_params(sketch_obj.ptr(), &sketch)) { throw py::error_already_set(); } diff --git a/src/py_converters_11.h b/src/py_converters_11.h index 1ab354f2dc6a..0b4244da1ad8 100644 --- a/src/py_converters_11.h +++ b/src/py_converters_11.h @@ -84,6 +84,32 @@ namespace PYBIND11_NAMESPACE { namespace detail { 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(mpl::PathIterator, const_name("PathIterator")); + + bool load(handle src, bool) { + if (src.is_none()) { + return true; + } + + auto vertices = src.attr("vertices"); + auto 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(), + should_simplify, simplify_threshold)) { + return false; + } + + return true; + } + }; +#endif }} // namespace PYBIND11_NAMESPACE::detail #endif /* MPL_PY_CONVERTERS_11_H */ From 925d619fe637d0564f280c0ccab2ee0bb2f88ffa Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 21 Oct 2023 04:08:07 -0400 Subject: [PATCH 5/7] Add a pybind11 type caster for e_snap_mode --- src/_path_wrapper.cpp | 6 +----- src/py_converters_11.h | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 733b0d80f09c..c0cf8eee6c77 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -287,15 +287,11 @@ Py_convert_path_to_polygons(mpl::PathIterator path, agg::trans_affine trans, static py::tuple Py_cleanup_path(mpl::PathIterator path, agg::trans_affine trans, bool remove_nans, - agg::rect_d clip_rect, py::object snap_mode_obj, double stroke_width, + agg::rect_d clip_rect, e_snap_mode snap_mode, double stroke_width, std::optional simplify, bool return_curves, py::object sketch_obj) { - e_snap_mode snap_mode; SketchParams sketch; - if (!convert_snap(snap_mode_obj.ptr(), &snap_mode)) { - throw py::error_already_set(); - } if (!convert_sketch_params(sketch_obj.ptr(), &sketch)) { throw py::error_already_set(); } diff --git a/src/py_converters_11.h b/src/py_converters_11.h index 0b4244da1ad8..c453982bfcde 100644 --- a/src/py_converters_11.h +++ b/src/py_converters_11.h @@ -10,6 +10,7 @@ namespace py = pybind11; #include "agg_basics.h" #include "agg_trans_affine.h" +#include "path_converters.h" void convert_trans_affine(const py::object& transform, agg::trans_affine& affine); @@ -85,6 +86,22 @@ 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; + } + }; + /* Remove all this macro magic after dropping NumPy usage and just include `py_adaptors.h`. */ #ifdef MPL_PY_ADAPTORS_H template <> struct type_caster { From 22c26890ac2866c28753828881cf6f1ac6611bf4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 21 Oct 2023 04:20:12 -0400 Subject: [PATCH 6/7] Add a pybind11 type caster for SketchParams --- src/_path_wrapper.cpp | 16 +++------------- src/py_converters_11.h | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index c0cf8eee6c77..b52ebf71f410 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -11,6 +11,7 @@ #include "_path.h" +#include "_backend_agg_basic_types.h" #include "py_adaptors.h" #include "py_converters.h" #include "py_converters_11.h" @@ -288,14 +289,8 @@ Py_convert_path_to_polygons(mpl::PathIterator path, agg::trans_affine trans, static py::tuple Py_cleanup_path(mpl::PathIterator path, agg::trans_affine trans, bool remove_nans, agg::rect_d clip_rect, e_snap_mode snap_mode, double stroke_width, - std::optional simplify, bool return_curves, py::object sketch_obj) + std::optional simplify, bool return_curves, SketchParams sketch) { - SketchParams sketch; - - if (!convert_sketch_params(sketch_obj.ptr(), &sketch)) { - throw py::error_already_set(); - } - if (!simplify.has_value()) { simplify = path.should_simplify(); } @@ -350,18 +345,13 @@ postfix : bool static py::object Py_convert_to_string(mpl::PathIterator path, agg::trans_affine trans, agg::rect_d cliprect, std::optional simplify, - py::object sketch_obj, int precision, + SketchParams sketch, int precision, std::array codes_obj, bool postfix) { - SketchParams sketch; char *codes[5]; std::string buffer; bool status; - if (!convert_sketch_params(sketch_obj.ptr(), &sketch)) { - throw py::error_already_set(); - } - for (auto i = 0; i < 5; ++i) { codes[i] = const_cast(codes_obj[i].c_str()); } diff --git a/src/py_converters_11.h b/src/py_converters_11.h index c453982bfcde..911d5fe2b924 100644 --- a/src/py_converters_11.h +++ b/src/py_converters_11.h @@ -127,6 +127,26 @@ namespace PYBIND11_NAMESPACE { namespace detail { } }; #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 + 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; + return true; + } + + auto params = src.cast>(); + std::tie(value.scale, value.length, value.randomness) = params; + + return true; + } + }; +#endif }} // namespace PYBIND11_NAMESPACE::detail #endif /* MPL_PY_CONVERTERS_11_H */ From a906be6d8bfefca1247f1dfa4c465179aa42ff4d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 15 Jan 2024 20:22:07 -0500 Subject: [PATCH 7/7] Optimize convert_polygon_vector a bit --- src/_path_wrapper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index b52ebf71f410..72ed2aef01f7 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -25,9 +25,9 @@ convert_polygon_vector(std::vector &polygons) auto result = py::list(polygons.size()); for (size_t i = 0; i < polygons.size(); ++i) { - Polygon poly = polygons[i]; + const auto& poly = polygons[i]; py::ssize_t dims[] = { static_cast(poly.size()), 2 }; - result[i] = py::array(dims, reinterpret_cast(poly.data())); + result[i] = py::array(dims, reinterpret_cast(poly.data())); } return result;