diff --git a/doc/python/corrade.utility.rst b/doc/python/corrade.utility.rst index 8bbdb27..3365fbc 100644 --- a/doc/python/corrade.utility.rst +++ b/doc/python/corrade.utility.rst @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. .. +.. py:function:: corrade.utility.copy + :raise AssertionError: If :p:`src` and :p:`dst` sizes, type sizes or types are different + .. py:function:: corrade.utility.ConfigurationGroup.group :raise KeyError: If group :p:`name` doesn't exist diff --git a/doc/python/pages/changelog.rst b/doc/python/pages/changelog.rst index 0b5ec76..6f89411 100644 --- a/doc/python/pages/changelog.rst +++ b/doc/python/pages/changelog.rst @@ -135,6 +135,8 @@ Changelog - Exposed :ref:`Color3.red()` and other convenience constructors (see :gh:`mosra/magnum-bindings#12`) - Exposed the :ref:`scenetools` and :ref:`text` libraries +- Exposed :ref:`utility.copy()` for convenient, fast and safe copying of + multi-dimensional strided arrays - Exposed the minimal interface of :ref:`utility.ConfigurationGroup` and :ref:`utility.Configuration` - Exposed :ref:`pluginmanager.AbstractManager.set_preferred_plugins()`, diff --git a/src/python/corrade/test/test_utility.py b/src/python/corrade/test/test_utility.py index c703306..8122022 100644 --- a/src/python/corrade/test/test_utility.py +++ b/src/python/corrade/test/test_utility.py @@ -30,6 +30,9 @@ import unittest from corrade import utility +# The copy() algorithms can't be tested here as there's nothing that would +# return a StridedArrayView. A test is in magnum/test/test.py instead. + # Tests also the ConfigurationGroup bindings, as a ConfigurationGroup cannot be # constructed as a standalone type class Configuration(unittest.TestCase): diff --git a/src/python/corrade/utility.cpp b/src/python/corrade/utility.cpp index 564107e..ab84244 100644 --- a/src/python/corrade/utility.cpp +++ b/src/python/corrade/utility.cpp @@ -24,16 +24,45 @@ */ #include +#include #include #include #include "corrade/bootstrap.h" +#include "Corrade/Containers/StridedArrayViewPythonBindings.h" + namespace corrade { +template void algorithmsCopy(py::module_& m) { + m.def("copy", [](const Containers::PyStridedArrayView& src, const Containers::PyStridedArrayView& dst) { + if(src.size() != dst.size()) { + PyErr_SetString(PyExc_AssertionError, "sizes don't match"); + throw py::error_already_set{}; + } + if(src.itemsize != dst.itemsize) { + PyErr_SetString(PyExc_AssertionError, "type sizes don't match"); + throw py::error_already_set{}; + } + if(Containers::StringView{src.format} != Containers::StringView{dst.format}) { + PyErr_SetString(PyExc_AssertionError, "types don't match"); + throw py::error_already_set{}; + } + + Utility::copy( + Containers::arrayCast(Containers::StridedArrayView{src}, src.itemsize), + Containers::arrayCast(Containers::StridedArrayView{dst}, dst.itemsize)); + }, "Copy a strided array view to another", py::arg("src"), py::arg("dst")); +} + void utility(py::module_& m) { m.doc() = "Utilities"; + algorithmsCopy<1>(m); + algorithmsCopy<2>(m); + algorithmsCopy<3>(m); + algorithmsCopy<4>(m); + py::class_{m, "ConfigurationGroup", "Group of values in a configuration file"} .def_property_readonly("has_groups", &Utility::ConfigurationGroup::hasGroups, "Whether this group has any subgroups") .def("group", [](Utility::ConfigurationGroup& self, const std::string& name) { diff --git a/src/python/magnum/test/test.py b/src/python/magnum/test/test.py index 09b33e1..f9a3dbc 100644 --- a/src/python/magnum/test/test.py +++ b/src/python/magnum/test/test.py @@ -27,8 +27,11 @@ import array import sys import unittest +from corrade import utility from magnum import * +# tests also corrade.utility.copy() in UtilityCopy + class PixelStorage_(unittest.TestCase): def test_init(self): a = PixelStorage() @@ -447,3 +450,64 @@ class ImageView(unittest.TestCase): with self.assertRaisesRegex(NotImplementedError, "access to this pixel format is not implemented yet, sorry"): a.pixels + +class UtilityCopy(unittest.TestCase): + def test_1d(self): + a_data = array.array('f', [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + a = ImageView1D(PixelFormat.RGB32F, 2, a_data) + + b_data = array.array('f', [0.0]*6) + b = MutableImageView1D(PixelFormat.RGB32F, 2, b_data) + + utility.copy(a.pixels, b.pixels) + self.assertEqual(list(b_data), [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + + def test_2d(self): + a_data = array.array('f', [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + a = ImageView2D(PixelFormat.RG32F, (2, 2), a_data) + + b_data = array.array('f', [0.0]*8) + b = MutableImageView2D(PixelFormat.RG32F, (2, 2), b_data) + + utility.copy(a.pixels, b.pixels) + self.assertEqual(list(b_data), [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + + def test_3d(self): + a_data = array.array('f', [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + a = ImageView3D(PixelFormat.R32F, (3, 1, 3), a_data) + + b_data = array.array('f', [0.0]*9) + b = MutableImageView3D(PixelFormat.R32F, (3, 1, 3), b_data) + + utility.copy(a.pixels, b.pixels) + self.assertEqual(list(b_data), [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + + def test_size_mismatch(self): + a_data = array.array('f', [0.0]*12) + a = ImageView2D(PixelFormat.RG32F, (3, 2), a_data) + + b_data = array.array('f', [0.0]*12) + b = MutableImageView2D(PixelFormat.RG32F, (2, 3), b_data) + + with self.assertRaisesRegex(AssertionError, "sizes don't match"): + utility.copy(a.pixels, b.pixels) + + def test_itemsize_mismatch(self): + a_data = array.array('f', [0.0]*12) + a = ImageView2D(PixelFormat.RG32F, (3, 2), a_data) + + b_data = array.array('f', [0.0]*18) + b = MutableImageView2D(PixelFormat.RGB32F, (3, 2), b_data) + + with self.assertRaisesRegex(AssertionError, "type sizes don't match"): + utility.copy(a.pixels, b.pixels) + + def test_format_mismatch(self): + a_data = array.array('f', [0.0]*12) + a = ImageView2D(PixelFormat.RG32F, (3, 2), a_data) + + b_data = array.array('I', [0]*18) + b = MutableImageView2D(PixelFormat.RG32UI, (3, 2), b_data) + + with self.assertRaisesRegex(AssertionError, "types don't match"): + utility.copy(a.pixels, b.pixels)