From 5db55de2e18cdba4a7c7f83212ca40fb6d710b5c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 20 Sep 2024 22:14:21 -0400 Subject: [PATCH 1/3] Remove old-style Python-C++ converters And merge the two files now that there are no old ones. --- src/_backend_agg_wrapper.cpp | 1 - src/_image_wrapper.cpp | 2 +- src/_path_wrapper.cpp | 1 - src/meson.build | 7 +- src/py_converters.cpp | 549 +---------------------------------- src/py_converters.h | 187 +++++++++--- src/py_converters_11.cpp | 22 -- src/py_converters_11.h | 153 ---------- 8 files changed, 166 insertions(+), 756 deletions(-) delete mode 100644 src/py_converters_11.cpp delete mode 100644 src/py_converters_11.h diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index fb241b217fe9..3e41eed7452d 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -5,7 +5,6 @@ #include "numpy_cpp.h" #include "py_converters.h" #include "_backend_agg.h" -#include "py_converters_11.h" namespace py = pybind11; using namespace pybind11::literals; diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 856dcf4ea3ce..0095f52e5997 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -2,7 +2,7 @@ #include #include "_image_resample.h" -#include "py_converters_11.h" +#include "py_converters.h" namespace py = pybind11; using namespace pybind11::literals; diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 13431601e5af..958cb6f5a2b6 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -14,7 +14,6 @@ #include "_backend_agg_basic_types.h" #include "py_adaptors.h" #include "py_converters.h" -#include "py_converters_11.h" namespace py = pybind11; using namespace pybind11::literals; diff --git a/src/meson.build b/src/meson.build index 6f1869cc6ca4..ebd02a5f66c6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -73,8 +73,6 @@ extension_data = { '_backend_agg': { 'subdir': 'matplotlib/backends', 'sources': files( - 'py_converters.cpp', - 'py_converters_11.cpp', '_backend_agg.cpp', '_backend_agg_wrapper.cpp', ), @@ -92,7 +90,6 @@ extension_data = { 'sources': files( 'ft2font.cpp', 'ft2font_wrapper.cpp', - 'py_converters.cpp', ), 'dependencies': [ freetype_dep, pybind11_dep, numpy_dep, agg_dep.partial_dependency(includes: true), @@ -107,7 +104,7 @@ extension_data = { 'subdir': 'matplotlib', 'sources': files( '_image_wrapper.cpp', - 'py_converters_11.cpp', + 'py_converters.cpp', ), 'dependencies': [ pybind11_dep, @@ -118,8 +115,6 @@ extension_data = { '_path': { 'subdir': 'matplotlib', 'sources': files( - 'py_converters.cpp', - 'py_converters_11.cpp', '_path_wrapper.cpp', ), 'dependencies': [numpy_dep, agg_dep, pybind11_dep], diff --git a/src/py_converters.cpp b/src/py_converters.cpp index dee4b0abfd31..1506bcfcf5b7 100644 --- a/src/py_converters.cpp +++ b/src/py_converters.cpp @@ -1,543 +1,22 @@ -#define NO_IMPORT_ARRAY -#define PY_SSIZE_T_CLEAN #include "py_converters.h" -#include "numpy_cpp.h" -#include "agg_basics.h" -#include "agg_color_rgba.h" -#include "agg_math_stroke.h" - -extern "C" { - -static int convert_string_enum(PyObject *obj, const char *name, const char **names, int *values, int *result) -{ - PyObject *bytesobj; - char *str; - - if (obj == NULL || obj == Py_None) { - return 1; - } - - if (PyUnicode_Check(obj)) { - bytesobj = PyUnicode_AsASCIIString(obj); - if (bytesobj == NULL) { - return 0; - } - } else if (PyBytes_Check(obj)) { - Py_INCREF(obj); - bytesobj = obj; - } else { - PyErr_Format(PyExc_TypeError, "%s must be str or bytes", name); - return 0; - } - - str = PyBytes_AsString(bytesobj); - if (str == NULL) { - Py_DECREF(bytesobj); - return 0; - } - - for ( ; *names != NULL; names++, values++) { - if (strncmp(str, *names, 64) == 0) { - *result = *values; - Py_DECREF(bytesobj); - return 1; - } - } - - PyErr_Format(PyExc_ValueError, "invalid %s value", name); - Py_DECREF(bytesobj); - return 0; -} - -int convert_from_method(PyObject *obj, const char *name, converter func, void *p) -{ - PyObject *value; - - value = PyObject_CallMethod(obj, name, NULL); - if (value == NULL) { - if (!PyObject_HasAttrString(obj, name)) { - PyErr_Clear(); - return 1; - } - return 0; - } - - if (!func(value, p)) { - Py_DECREF(value); - return 0; - } - - Py_DECREF(value); - return 1; -} - -int convert_from_attr(PyObject *obj, const char *name, converter func, void *p) -{ - PyObject *value; - - value = PyObject_GetAttrString(obj, name); - if (value == NULL) { - if (!PyObject_HasAttrString(obj, name)) { - PyErr_Clear(); - return 1; - } - return 0; - } - - if (!func(value, p)) { - Py_DECREF(value); - return 0; - } - - Py_DECREF(value); - return 1; -} - -int convert_double(PyObject *obj, void *p) -{ - double *val = (double *)p; - - *val = PyFloat_AsDouble(obj); - if (PyErr_Occurred()) { - return 0; - } - - return 1; -} - -int convert_bool(PyObject *obj, void *p) -{ - bool *val = (bool *)p; - switch (PyObject_IsTrue(obj)) { - case 0: *val = false; break; - case 1: *val = true; break; - default: return 0; // errored. - } - return 1; -} - -int convert_cap(PyObject *capobj, void *capp) -{ - const char *names[] = {"butt", "round", "projecting", NULL}; - int values[] = {agg::butt_cap, agg::round_cap, agg::square_cap}; - int result = agg::butt_cap; - - if (!convert_string_enum(capobj, "capstyle", names, values, &result)) { - return 0; - } - - *(agg::line_cap_e *)capp = (agg::line_cap_e)result; - return 1; -} - -int convert_join(PyObject *joinobj, void *joinp) -{ - const char *names[] = {"miter", "round", "bevel", NULL}; - int values[] = {agg::miter_join_revert, agg::round_join, agg::bevel_join}; - int result = agg::miter_join_revert; - - if (!convert_string_enum(joinobj, "joinstyle", names, values, &result)) { - return 0; - } - - *(agg::line_join_e *)joinp = (agg::line_join_e)result; - return 1; -} - -int convert_rect(PyObject *rectobj, void *rectp) -{ - agg::rect_d *rect = (agg::rect_d *)rectp; - - if (rectobj == NULL || rectobj == Py_None) { - rect->x1 = 0.0; - rect->y1 = 0.0; - rect->x2 = 0.0; - rect->y2 = 0.0; - } else { - PyArrayObject *rect_arr = (PyArrayObject *)PyArray_ContiguousFromAny( - rectobj, NPY_DOUBLE, 1, 2); - if (rect_arr == NULL) { - return 0; - } - - if (PyArray_NDIM(rect_arr) == 2) { - if (PyArray_DIM(rect_arr, 0) != 2 || - PyArray_DIM(rect_arr, 1) != 2) { - PyErr_SetString(PyExc_ValueError, "Invalid bounding box"); - Py_DECREF(rect_arr); - return 0; - } - - } else { // PyArray_NDIM(rect_arr) == 1 - if (PyArray_DIM(rect_arr, 0) != 4) { - PyErr_SetString(PyExc_ValueError, "Invalid bounding box"); - Py_DECREF(rect_arr); - return 0; - } - } - - double *buff = (double *)PyArray_DATA(rect_arr); - rect->x1 = buff[0]; - rect->y1 = buff[1]; - rect->x2 = buff[2]; - rect->y2 = buff[3]; - - Py_DECREF(rect_arr); - } - return 1; -} - -int convert_rgba(PyObject *rgbaobj, void *rgbap) -{ - agg::rgba *rgba = (agg::rgba *)rgbap; - PyObject *rgbatuple = NULL; - int success = 1; - if (rgbaobj == NULL || rgbaobj == Py_None) { - rgba->r = 0.0; - rgba->g = 0.0; - rgba->b = 0.0; - rgba->a = 0.0; - } else { - if (!(rgbatuple = PySequence_Tuple(rgbaobj))) { - success = 0; - goto exit; - } - rgba->a = 1.0; - if (!PyArg_ParseTuple( - rgbatuple, "ddd|d:rgba", &(rgba->r), &(rgba->g), &(rgba->b), &(rgba->a))) { - success = 0; - goto exit; - } - } -exit: - Py_XDECREF(rgbatuple); - return success; -} - -int convert_dashes(PyObject *dashobj, void *dashesp) -{ - Dashes *dashes = (Dashes *)dashesp; - - double dash_offset = 0.0; - PyObject *dashes_seq = NULL; - - if (!PyArg_ParseTuple(dashobj, "dO:dashes", &dash_offset, &dashes_seq)) { - return 0; - } - - if (dashes_seq == Py_None) { - return 1; - } - - if (!PySequence_Check(dashes_seq)) { - PyErr_SetString(PyExc_TypeError, "Invalid dashes sequence"); - return 0; - } - - Py_ssize_t nentries = PySequence_Size(dashes_seq); - // If the dashpattern has odd length, iterate through it twice (in - // accordance with the pdf/ps/svg specs). - Py_ssize_t dash_pattern_length = (nentries % 2) ? 2 * nentries : nentries; - - for (Py_ssize_t i = 0; i < dash_pattern_length; ++i) { - PyObject *item; - double length; - double skip; - - item = PySequence_GetItem(dashes_seq, i % nentries); - if (item == NULL) { - return 0; - } - length = PyFloat_AsDouble(item); - if (PyErr_Occurred()) { - Py_DECREF(item); - return 0; - } - Py_DECREF(item); - - ++i; - - item = PySequence_GetItem(dashes_seq, i % nentries); - if (item == NULL) { - return 0; - } - skip = PyFloat_AsDouble(item); - if (PyErr_Occurred()) { - Py_DECREF(item); - return 0; - } - Py_DECREF(item); - - dashes->add_dash_pair(length, skip); - } - - dashes->set_dash_offset(dash_offset); - - return 1; -} - -int convert_dashes_vector(PyObject *obj, void *dashesp) -{ - DashesVector *dashes = (DashesVector *)dashesp; - - if (!PySequence_Check(obj)) { - return 0; - } - - Py_ssize_t n = PySequence_Size(obj); - - for (Py_ssize_t i = 0; i < n; ++i) { - PyObject *item; - Dashes subdashes; - - item = PySequence_GetItem(obj, i); - if (item == NULL) { - return 0; - } - - if (!convert_dashes(item, &subdashes)) { - Py_DECREF(item); - return 0; - } - Py_DECREF(item); - - dashes->push_back(subdashes); - } - - return 1; -} - -int convert_trans_affine(PyObject *obj, void *transp) +void convert_trans_affine(const py::object& transform, agg::trans_affine& affine) { - agg::trans_affine *trans = (agg::trans_affine *)transp; - - /** If None assume identity transform. */ - if (obj == NULL || obj == Py_None) { - return 1; - } - - PyArrayObject *array = (PyArrayObject *)PyArray_ContiguousFromAny(obj, NPY_DOUBLE, 2, 2); - if (array == NULL) { - return 0; + // If None assume identity transform so leave affine unchanged + if (transform.is_none()) { + return; } - if (PyArray_DIM(array, 0) == 3 && PyArray_DIM(array, 1) == 3) { - double *buffer = (double *)PyArray_DATA(array); - trans->sx = buffer[0]; - trans->shx = buffer[1]; - trans->tx = buffer[2]; - - trans->shy = buffer[3]; - trans->sy = buffer[4]; - trans->ty = buffer[5]; - - Py_DECREF(array); - return 1; + auto array = py::array_t::ensure(transform); + if (!array || array.ndim() != 2 || array.shape(0) != 3 || array.shape(1) != 3) { + throw std::invalid_argument("Invalid affine transformation matrix"); } - Py_DECREF(array); - PyErr_SetString(PyExc_ValueError, "Invalid affine transformation matrix"); - return 0; -} - -int convert_path(PyObject *obj, void *pathp) -{ - mpl::PathIterator *path = (mpl::PathIterator *)pathp; - - PyObject *vertices_obj = NULL; - PyObject *codes_obj = NULL; - PyObject *should_simplify_obj = NULL; - PyObject *simplify_threshold_obj = NULL; - bool should_simplify; - double simplify_threshold; - - int status = 0; - - if (obj == NULL || obj == Py_None) { - return 1; - } - - vertices_obj = PyObject_GetAttrString(obj, "vertices"); - if (vertices_obj == NULL) { - goto exit; - } - - codes_obj = PyObject_GetAttrString(obj, "codes"); - if (codes_obj == NULL) { - goto exit; - } - - should_simplify_obj = PyObject_GetAttrString(obj, "should_simplify"); - if (should_simplify_obj == NULL) { - goto exit; - } - switch (PyObject_IsTrue(should_simplify_obj)) { - case 0: should_simplify = 0; break; - case 1: should_simplify = 1; break; - default: goto exit; // errored. - } - - simplify_threshold_obj = PyObject_GetAttrString(obj, "simplify_threshold"); - if (simplify_threshold_obj == NULL) { - goto exit; - } - simplify_threshold = PyFloat_AsDouble(simplify_threshold_obj); - if (PyErr_Occurred()) { - goto exit; - } - - if (!path->set(vertices_obj, codes_obj, should_simplify, simplify_threshold)) { - goto exit; - } - - status = 1; - -exit: - Py_XDECREF(vertices_obj); - Py_XDECREF(codes_obj); - Py_XDECREF(should_simplify_obj); - Py_XDECREF(simplify_threshold_obj); - - return status; -} - -int convert_pathgen(PyObject *obj, void *pathgenp) -{ - mpl::PathGenerator *paths = (mpl::PathGenerator *)pathgenp; - if (!paths->set(obj)) { - PyErr_SetString(PyExc_TypeError, "Not an iterable of paths"); - return 0; - } - return 1; -} - -int convert_clippath(PyObject *clippath_tuple, void *clippathp) -{ - ClipPath *clippath = (ClipPath *)clippathp; - - if (clippath_tuple != NULL && clippath_tuple != Py_None) { - if (!PyArg_ParseTuple(clippath_tuple, - "O&O&:clippath", - &convert_path, - &clippath->path, - &convert_trans_affine, - &clippath->trans)) { - return 0; - } - } - - return 1; -} - -int convert_snap(PyObject *obj, void *snapp) -{ - e_snap_mode *snap = (e_snap_mode *)snapp; - if (obj == NULL || obj == Py_None) { - *snap = SNAP_AUTO; - } else { - switch (PyObject_IsTrue(obj)) { - case 0: *snap = SNAP_FALSE; break; - case 1: *snap = SNAP_TRUE; break; - default: return 0; // errored. - } - } - return 1; -} - -int convert_sketch_params(PyObject *obj, void *sketchp) -{ - SketchParams *sketch = (SketchParams *)sketchp; - - if (obj == NULL || obj == Py_None) { - sketch->scale = 0.0; - sketch->length = 0.0; - sketch->randomness = 0.0; - } else if (!PyArg_ParseTuple(obj, - "ddd:sketch_params", - &sketch->scale, - &sketch->length, - &sketch->randomness)) { - return 0; - } - - return 1; -} - -int convert_gcagg(PyObject *pygc, void *gcp) -{ - GCAgg *gc = (GCAgg *)gcp; - - if (!(convert_from_attr(pygc, "_linewidth", &convert_double, &gc->linewidth) && - convert_from_attr(pygc, "_alpha", &convert_double, &gc->alpha) && - convert_from_attr(pygc, "_forced_alpha", &convert_bool, &gc->forced_alpha) && - convert_from_attr(pygc, "_rgb", &convert_rgba, &gc->color) && - convert_from_attr(pygc, "_antialiased", &convert_bool, &gc->isaa) && - convert_from_attr(pygc, "_capstyle", &convert_cap, &gc->cap) && - convert_from_attr(pygc, "_joinstyle", &convert_join, &gc->join) && - convert_from_method(pygc, "get_dashes", &convert_dashes, &gc->dashes) && - convert_from_attr(pygc, "_cliprect", &convert_rect, &gc->cliprect) && - convert_from_method(pygc, "get_clip_path", &convert_clippath, &gc->clippath) && - convert_from_method(pygc, "get_snap", &convert_snap, &gc->snap_mode) && - convert_from_method(pygc, "get_hatch_path", &convert_path, &gc->hatchpath) && - convert_from_method(pygc, "get_hatch_color", &convert_rgba, &gc->hatch_color) && - convert_from_method(pygc, "get_hatch_linewidth", &convert_double, &gc->hatch_linewidth) && - convert_from_method(pygc, "get_sketch_params", &convert_sketch_params, &gc->sketch))) { - return 0; - } - - return 1; -} - -int convert_points(PyObject *obj, void *pointsp) -{ - numpy::array_view *points = (numpy::array_view *)pointsp; - if (obj == NULL || obj == Py_None) { - return 1; - } - if (!points->set(obj) - || (points->size() && !check_trailing_shape(*points, "points", 2))) { - return 0; - } - return 1; -} - -int convert_transforms(PyObject *obj, void *transp) -{ - numpy::array_view *trans = (numpy::array_view *)transp; - if (obj == NULL || obj == Py_None) { - return 1; - } - if (!trans->set(obj) - || (trans->size() && !check_trailing_shape(*trans, "transforms", 3, 3))) { - return 0; - } - return 1; -} - -int convert_bboxes(PyObject *obj, void *bboxp) -{ - numpy::array_view *bbox = (numpy::array_view *)bboxp; - if (obj == NULL || obj == Py_None) { - return 1; - } - if (!bbox->set(obj) - || (bbox->size() && !check_trailing_shape(*bbox, "bbox array", 2, 2))) { - return 0; - } - return 1; -} - -int convert_colors(PyObject *obj, void *colorsp) -{ - numpy::array_view *colors = (numpy::array_view *)colorsp; - if (obj == NULL || obj == Py_None) { - return 1; - } - if (!colors->set(obj) - || (colors->size() && !check_trailing_shape(*colors, "colors", 4))) { - return 0; - } - return 1; -} + auto buffer = array.data(); + affine.sx = buffer[0]; + affine.shx = buffer[1]; + affine.tx = buffer[2]; + affine.shy = buffer[3]; + affine.sy = buffer[4]; + affine.ty = buffer[5]; } diff --git a/src/py_converters.h b/src/py_converters.h index b514efdf5d47..360d1e01d075 100644 --- a/src/py_converters.h +++ b/src/py_converters.h @@ -3,44 +3,157 @@ #ifndef MPL_PY_CONVERTERS_H #define MPL_PY_CONVERTERS_H -/*************************************************************************** - * This module contains a number of conversion functions from Python types - * to C++ types. Most of them meet the Python "converter" signature: - * - * typedef int (*converter)(PyObject *, void *); - * - * and thus can be passed as conversion functions to PyArg_ParseTuple - * and friends. +/*************************************************************************************** + * This module contains a number of conversion functions from Python types to C++ types. + * Most of them meet the pybind11 type casters, and thus will automatically be applied + * when a C++ function parameter uses their type. */ -#include -#include "_backend_agg_basic_types.h" - -extern "C" { -typedef int (*converter)(PyObject *, void *); - -int convert_from_attr(PyObject *obj, const char *name, converter func, void *p); -int convert_from_method(PyObject *obj, const char *name, converter func, void *p); - -int convert_double(PyObject *obj, void *p); -int convert_bool(PyObject *obj, void *p); -int convert_cap(PyObject *capobj, void *capp); -int convert_join(PyObject *joinobj, void *joinp); -int convert_rect(PyObject *rectobj, void *rectp); -int convert_rgba(PyObject *rgbaocj, void *rgbap); -int convert_dashes(PyObject *dashobj, void *gcp); -int convert_dashes_vector(PyObject *obj, void *dashesp); -int convert_trans_affine(PyObject *obj, void *transp); -int convert_path(PyObject *obj, void *pathp); -int convert_pathgen(PyObject *obj, void *pathgenp); -int convert_clippath(PyObject *clippath_tuple, void *clippathp); -int convert_snap(PyObject *obj, void *snapp); -int convert_sketch_params(PyObject *obj, void *sketchp); -int convert_gcagg(PyObject *pygc, void *gcp); -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); +#include +#include + +namespace py = pybind11; + +#include "agg_basics.h" +#include "agg_color_rgba.h" +#include "agg_trans_affine.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>(); } -#endif +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: + 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; + } + }; + + 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")); + + 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_H */ diff --git a/src/py_converters_11.cpp b/src/py_converters_11.cpp deleted file mode 100644 index 830ee6336fb4..000000000000 --- a/src/py_converters_11.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "py_converters_11.h" - -void convert_trans_affine(const py::object& transform, agg::trans_affine& affine) -{ - // If None assume identity transform so leave affine unchanged - if (transform.is_none()) { - return; - } - - auto array = py::array_t::ensure(transform); - 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(); - affine.sx = buffer[0]; - affine.shx = buffer[1]; - affine.tx = buffer[2]; - affine.shy = buffer[3]; - affine.sy = buffer[4]; - affine.ty = buffer[5]; -} diff --git a/src/py_converters_11.h b/src/py_converters_11.h deleted file mode 100644 index b093f71b181a..000000000000 --- a/src/py_converters_11.h +++ /dev/null @@ -1,153 +0,0 @@ -#ifndef MPL_PY_CONVERTERS_11_H -#define MPL_PY_CONVERTERS_11_H - -// pybind11 equivalent of py_converters.h - -#include -#include - -namespace py = pybind11; - -#include "agg_basics.h" -#include "agg_color_rgba.h" -#include "agg_trans_affine.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: - 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; - } - }; - - 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")); - - 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 56936346bf2027dea2d620653a33a24f6698ec8c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 20 Sep 2024 22:46:39 -0400 Subject: [PATCH 2/3] Remove NumPy from extensions entirely This should be replaced by pybind11 in all cases now. --- .github/workflows/tests.yml | 2 +- pyproject.toml | 13 - requirements/dev/build-requirements.txt | 1 - requirements/testing/mypy.txt | 1 - src/_backend_agg_wrapper.cpp | 9 - src/_path.h | 5 +- src/_path_wrapper.cpp | 12 +- src/ft2font_wrapper.cpp | 9 - src/meson.build | 56 +-- src/numpy_cpp.h | 582 ------------------------ 10 files changed, 8 insertions(+), 682 deletions(-) delete mode 100644 src/numpy_cpp.h diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 062d742b81d9..df73fe1d2169 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -261,7 +261,7 @@ jobs: # Preinstall build requirements to enable no-build-isolation builds. python -m pip install --upgrade $PRE \ 'contourpy>=1.0.1' cycler fonttools kiwisolver importlib_resources \ - numpy packaging pillow 'pyparsing!=3.1.0' python-dateutil setuptools-scm \ + packaging pillow 'pyparsing!=3.1.0' python-dateutil setuptools-scm \ 'meson-python>=0.13.1' 'pybind11>=2.6' \ -r requirements/testing/all.txt \ ${{ matrix.extra-requirements }} diff --git a/pyproject.toml b/pyproject.toml index 5b5c60d60d54..cd0a5c039758 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,6 @@ requires-python = ">=3.10" # Should be a copy of the build dependencies below. dev = [ "meson-python>=0.13.1", - "numpy>=1.25", "pybind11>=2.6,!=2.13.3", "setuptools_scm>=7", # Not required by us but setuptools_scm without a version, cso _if_ @@ -74,18 +73,6 @@ requires = [ "meson-python>=0.13.1", "pybind11>=2.6,!=2.13.3", "setuptools_scm>=7", - - # Comments on numpy build requirement range: - # - # 1. >=2.0.x is the numpy requirement for wheel builds for distribution - # on PyPI - building against 2.x yields wheels that are also - # ABI-compatible with numpy 1.x at runtime. - # 2. Note that building against numpy 1.x works fine too - users and - # redistributors can do this by installing the numpy version they like - # and disabling build isolation. - # 3. The <2.3 upper bound is for matching the numpy deprecation policy, - # it should not be loosened. - "numpy>=2.0.0rc1,<2.3", ] [tool.meson-python.args] diff --git a/requirements/dev/build-requirements.txt b/requirements/dev/build-requirements.txt index 0861a11c9ee5..b5cb6acdb279 100644 --- a/requirements/dev/build-requirements.txt +++ b/requirements/dev/build-requirements.txt @@ -1,4 +1,3 @@ pybind11!=2.13.3 meson-python -numpy<2.1.0 setuptools-scm diff --git a/requirements/testing/mypy.txt b/requirements/testing/mypy.txt index 0b65050b52de..aa20581ee69b 100644 --- a/requirements/testing/mypy.txt +++ b/requirements/testing/mypy.txt @@ -18,7 +18,6 @@ contourpy>=1.0.1 cycler>=0.10 fonttools>=4.22.0 kiwisolver>=1.3.1 -numpy>=1.19 packaging>=20.0 pillow>=8 pyparsing>=2.3.1 diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 3e41eed7452d..bfc8584d688d 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -2,7 +2,6 @@ #include #include #include "mplutils.h" -#include "numpy_cpp.h" #include "py_converters.h" #include "_backend_agg.h" @@ -188,14 +187,6 @@ PyRendererAgg_draw_gouraud_triangles(RendererAgg *self, PYBIND11_MODULE(_backend_agg, m) { - auto ia = [m]() -> const void* { - import_array(); - return &m; - }; - if (ia() == NULL) { - throw py::error_already_set(); - } - py::class_(m, "RendererAgg", py::buffer_protocol()) .def(py::init(), "width"_a, "height"_a, "dpi"_a) diff --git a/src/_path.h b/src/_path.h index 693862c7a829..f5c06e4a6a15 100644 --- a/src/_path.h +++ b/src/_path.h @@ -18,7 +18,6 @@ #include "path_converters.h" #include "_backend_agg_basic_types.h" -#include "numpy_cpp.h" const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3 }; @@ -1004,7 +1003,7 @@ void convert_path_to_polygons(PathIterator &path, template void -__cleanup_path(VertexSource &source, std::vector &vertices, std::vector &codes) +__cleanup_path(VertexSource &source, std::vector &vertices, std::vector &codes) { unsigned code; double x, y; @@ -1012,7 +1011,7 @@ __cleanup_path(VertexSource &source, std::vector &vertices, std::vector< code = source.vertex(&x, &y); vertices.push_back(x); vertices.push_back(y); - codes.push_back((npy_uint8)code); + codes.push_back(static_cast(code)); } while (code != agg::path_cmd_stop); } diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 958cb6f5a2b6..77c1b40f9514 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -7,8 +7,6 @@ #include #include -#include "numpy_cpp.h" - #include "_path.h" #include "_backend_agg_basic_types.h" @@ -266,7 +264,7 @@ Py_cleanup_path(mpl::PathIterator path, agg::trans_affine trans, bool remove_nan bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2); std::vector vertices; - std::vector codes; + std::vector codes; cleanup_path(path, trans, remove_nans, do_clip, clip_rect, snap_mode, stroke_width, *simplify, return_curves, sketch, vertices, codes); @@ -374,14 +372,6 @@ Py_is_sorted_and_has_non_nan(py::object obj) PYBIND11_MODULE(_path, m) { - 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, diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 9791dc7e2e06..4358646beede 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -4,7 +4,6 @@ #include #include "ft2font.h" -#include "numpy/arrayobject.h" #include #include @@ -955,14 +954,6 @@ PyFT2Font_fname(PyFT2Font *self) PYBIND11_MODULE(ft2font, m) { - auto ia = [m]() -> const void* { - import_array(); - return &m; - }; - if (ia() == NULL) { - throw py::error_already_set(); - } - if (FT_Init_FreeType(&_ft2Library)) { // initialize library throw std::runtime_error("Could not initialize the freetype2 library"); } diff --git a/src/meson.build b/src/meson.build index ebd02a5f66c6..d2bc9e4afccd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,42 +1,3 @@ -# NumPy include directory - needed in all submodules -# The try-except is needed because when things are split across drives on Windows, there -# is no relative path and an exception gets raised. There may be other such cases, so add -# a catch-all and switch to an absolute path. Relative paths are needed when for example -# a virtualenv is placed inside the source tree; Meson rejects absolute paths to places -# inside the source tree. -# For cross-compilation it is often not possible to run the Python interpreter in order -# to retrieve numpy's include directory. It can be specified in the cross file instead: -# -# [properties] -# numpy-include-dir = /abspath/to/host-pythons/site-packages/numpy/core/include -# -# This uses the path as is, and avoids running the interpreter. -incdir_numpy = meson.get_external_property('numpy-include-dir', 'not-given') -if incdir_numpy == 'not-given' - incdir_numpy = run_command(py3, - [ - '-c', - '''import os -import numpy as np -try: - incdir = os.path.relpath(np.get_include()) -except Exception: - incdir = np.get_include() -print(incdir)''' - ], - check: true - ).stdout().strip() -endif -numpy_dep = declare_dependency( - compile_args: [ - '-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION', - # Allow NumPy's printf format specifiers in C++. - '-D__STDC_FORMAT_MACROS=1', - ], - include_directories: include_directories(incdir_numpy), - dependencies: py3_dep, -) - # For cross-compilation it is often not possible to run the Python interpreter in order # to retrieve the platform-specific /dev/null. It can be specified in the cross file # instead: @@ -76,7 +37,7 @@ extension_data = { '_backend_agg.cpp', '_backend_agg_wrapper.cpp', ), - 'dependencies': [agg_dep, numpy_dep, freetype_dep, pybind11_dep], + 'dependencies': [agg_dep, freetype_dep, pybind11_dep], }, '_c_internal_utils': { 'subdir': 'matplotlib', @@ -92,7 +53,7 @@ extension_data = { 'ft2font_wrapper.cpp', ), 'dependencies': [ - freetype_dep, pybind11_dep, numpy_dep, agg_dep.partial_dependency(includes: true), + freetype_dep, pybind11_dep, agg_dep.partial_dependency(includes: true), ], 'cpp_args': [ '-DFREETYPE_BUILD_TYPE="@0@"'.format( @@ -117,7 +78,7 @@ extension_data = { 'sources': files( '_path_wrapper.cpp', ), - 'dependencies': [numpy_dep, agg_dep, pybind11_dep], + 'dependencies': [agg_dep, pybind11_dep], }, '_qhull': { 'subdir': 'matplotlib', @@ -157,16 +118,7 @@ extension_data = { } foreach ext, kwargs : extension_data - # Ensure that PY_ARRAY_UNIQUE_SYMBOL is uniquely defined for each extension. - unique_array_api = '-DPY_ARRAY_UNIQUE_SYMBOL=MPL_@0@_ARRAY_API'.format(ext.replace('.', '_')) - additions = { - 'c_args': [unique_array_api] + kwargs.get('c_args', []), - 'cpp_args': [unique_array_api] + kwargs.get('cpp_args', []), - } - py3.extension_module( - ext, - install: true, - kwargs: kwargs + additions) + py3.extension_module(ext, install: true, kwargs: kwargs) endforeach if get_option('macosx') and host_machine.system() == 'darwin' diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h deleted file mode 100644 index 6b7446337bb7..000000000000 --- a/src/numpy_cpp.h +++ /dev/null @@ -1,582 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -#ifndef MPL_NUMPY_CPP_H -#define MPL_NUMPY_CPP_H -#define PY_SSIZE_T_CLEAN -/*************************************************************************** - * This file is based on original work by Mark Wiebe, available at: - * - * http://github.com/mwiebe/numpy-cpp - * - * However, the needs of matplotlib wrappers, such as treating an - * empty array as having the correct dimensions, have made this rather - * matplotlib-specific, so it's no longer compatible with the - * original. - */ - -#ifdef _POSIX_C_SOURCE -# undef _POSIX_C_SOURCE -#endif -#ifndef _AIX -#ifdef _XOPEN_SOURCE -# undef _XOPEN_SOURCE -#endif -#endif - -// Prevent multiple conflicting definitions of swab from stdlib.h and unistd.h -#if defined(__sun) || defined(sun) -#if defined(_XPG4) -#undef _XPG4 -#endif -#if defined(_XPG3) -#undef _XPG3 -#endif -#endif - -#include -#include - -#include "py_exceptions.h" - -#include - -namespace numpy -{ - -// Type traits for the NumPy types -template -struct type_num_of; - -/* Be careful with bool arrays as python has sizeof(npy_bool) == 1, but it is - * not always the case that sizeof(bool) == 1. Using the array_view_accessors - * is always fine regardless of sizeof(bool), so do this rather than using - * array.data() and pointer arithmetic which will not work correctly if - * sizeof(bool) != 1. */ -template <> struct type_num_of -{ - enum { - value = NPY_BOOL - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_BYTE - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_UBYTE - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_SHORT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_USHORT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_INT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_UINT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_LONG - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_ULONG - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_LONGLONG - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_ULONGLONG - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_FLOAT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_DOUBLE - }; -}; -#if NPY_LONGDOUBLE != NPY_DOUBLE -template <> -struct type_num_of -{ - enum { - value = NPY_LONGDOUBLE - }; -}; -#endif -template <> -struct type_num_of -{ - enum { - value = NPY_CFLOAT - }; -}; -template <> -struct type_num_of > -{ - enum { - value = NPY_CFLOAT - }; -}; -template <> -struct type_num_of -{ - enum { - value = NPY_CDOUBLE - }; -}; -template <> -struct type_num_of > -{ - enum { - value = NPY_CDOUBLE - }; -}; -#if NPY_CLONGDOUBLE != NPY_CDOUBLE -template <> -struct type_num_of -{ - enum { - value = NPY_CLONGDOUBLE - }; -}; -template <> -struct type_num_of > -{ - enum { - value = NPY_CLONGDOUBLE - }; -}; -#endif -template <> -struct type_num_of -{ - enum { - value = NPY_OBJECT - }; -}; -template -struct type_num_of -{ - enum { - value = type_num_of::value - }; -}; -template -struct type_num_of -{ - enum { - value = type_num_of::value - }; -}; - -template -struct is_const -{ - enum { - value = false - }; -}; -template -struct is_const -{ - enum { - value = true - }; -}; - -namespace detail -{ -template