Browse Source

python: expose Image.

Owning counterpart to ImageView.
pull/8/head
Vladimír Vondruš 7 years ago
parent
commit
e07e61c1ef
  1. 15
      doc/python/magnum.rst
  2. 1
      src/python/magnum/__init__.py
  3. 36
      src/python/magnum/magnum.cpp
  4. 95
      src/python/magnum/test/test.py

15
doc/python/magnum.rst

@ -36,6 +36,21 @@
:data TARGET_WEBGL: WebGL target
:data TARGET_VK: Vulkan interoperability
.. py:class:: magnum.Image1D
See `Image2D` for more information.
.. py:class:: magnum.Image2D
An owning counterpart to `ImageView2D` / `MutableImageView2D`. Holds its
own data buffer, thus doesn't have an equivalent to `ImageView2D.owner`.
Implicitly convertible to `ImageView2D` / `MutableImageView2D`, so all APIs
consuming image views work with this type as well.
.. py:class:: magnum.Image3D
See `Image2D` for more information.
.. py:class:: magnum.ImageView1D
See `ImageView2D` for more information.

1
src/python/magnum/__init__.py

@ -89,6 +89,7 @@ __all__ = [
'MeshPrimitive', 'MeshIndexType',
'PixelFormat', 'PixelStorage',
'Image1D', 'Image2D', 'Image3D',
'ImageView1D', 'ImageView2D', 'ImageView3D',
'MutableImageView1D', 'MutableImageView2D', 'MutableImageView3D',

36
src/python/magnum/magnum.cpp

@ -26,6 +26,7 @@
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <Corrade/Containers/StridedArrayView.h>
#include <Magnum/Image.h>
#include <Magnum/ImageView.h>
#include <Magnum/Mesh.h>
#include <Magnum/PixelFormat.h>
@ -46,6 +47,29 @@ namespace py = pybind11;
namespace magnum { namespace {
template<class T> void image(py::class_<T>& c) {
c
/* Constructors. Only the ones taking the generic format and *not*
taking an Array, as Python has no way to "move" it in */
.def(py::init<const PixelStorage&, PixelFormat>(), "Construct an image placeholder")
.def(py::init<PixelFormat>(), "Construct an image placeholder")
/* Properties */
.def_property_readonly("storage", &T::storage, "Storage of pixel data")
.def_property_readonly("format", &T::format, "Format of pixel data")
/** @todo formatExtra() */
.def_property_readonly("pixel_size", &T::pixelSize, "Pixel size (in bytes)")
.def_property_readonly("size", [](T& self) {
return PyDimensionTraits<T::Dimensions, Int>::from(self.size());
}, "Image size")
.def_property_readonly("data", [](T& self) {
return Containers::pyArrayViewHolder(self.data(), self.data() ? py::cast(self) : py::none{});
}, "Image data")
.def_property_readonly("pixels", [](T& self) {
return Containers::pyArrayViewHolder(self.pixels(), self.data() ? py::cast(self) : py::none{});
}, "View on pixel data");
}
template<class T> void imageView(py::class_<T, PyImageViewHolder<T>>& c) {
/*
Missing APIs:
@ -53,6 +77,8 @@ template<class T> void imageView(py::class_<T, PyImageViewHolder<T>>& c) {
Type, ErasedType, Dimensions
*/
py::implicitly_convertible<Image<T::Dimensions>, T>();
c
/* Constructors. The variants *not* taking an array view have to be
first, otherwise things fail on systems that don't have numpy
@ -88,6 +114,9 @@ template<class T> void imageView(py::class_<T, PyImageViewHolder<T>>& c) {
.def(py::init([](PixelFormat format, const typename PyDimensionTraits<T::Dimensions, Int>::VectorType& size, const Containers::ArrayView<typename T::Type>& data) {
return pyImageViewHolder(T{format, size, data}, pyObjectHolderFor<Containers::PyArrayViewHolder>(data).owner);
}), "Constructor")
.def(py::init([](Image<T::Dimensions>& image) {
return pyImageViewHolder(T{image}, image.data() ? py::cast(image) : py::none{});
}), "Construct a view on an image")
/* Properties */
.def_property_readonly("storage", &T::storage, "Storage of pixel data")
@ -254,6 +283,13 @@ void magnum(py::module& m) {
.def_property("skip",
&PixelStorage::skip, &PixelStorage::setSkip, "Pixel, row and image skip");
py::class_<Image1D> image1D{m, "Image1D", "One-dimensional image"};
py::class_<Image2D> image2D{m, "Image2D", "Two-dimensional image"};
py::class_<Image3D> image3D{m, "Image3D", "Three-dimensional image"};
image(image1D);
image(image2D);
image(image3D);
py::class_<ImageView1D, PyImageViewHolder<ImageView1D>> imageView1D{m, "ImageView1D", "One-dimensional image view"};
py::class_<ImageView2D, PyImageViewHolder<ImageView2D>> imageView2D{m, "ImageView2D", "Two-dimensional image view"};
py::class_<ImageView3D, PyImageViewHolder<ImageView3D>> imageView3D{m, "ImageView3D", "Three-dimensional image view"};

95
src/python/magnum/test/test.py

@ -47,6 +47,66 @@ class PixelStorage_(unittest.TestCase):
self.assertEqual(a.image_height, 256)
self.assertEqual(a.skip, Vector3i(3, 1, 2))
class Image(unittest.TestCase):
def test_init(self):
storage = PixelStorage()
storage.alignment = 1
a = Image2D(storage, PixelFormat.RGB8_UNORM)
self.assertEqual(a.storage.alignment, 1)
self.assertEqual(a.size, Vector2i())
self.assertEqual(a.format, PixelFormat.RGB8_UNORM)
self.assertEqual(len(a.data), 0)
b = Image2D(PixelFormat.R8I)
self.assertEqual(b.storage.alignment, 4)
self.assertEqual(b.size, Vector2i())
self.assertEqual(b.format, PixelFormat.R8I)
self.assertEqual(len(b.data), 0)
@unittest.skip("No way to create a non-empty Image at the moment")
def test_data(self):
a = Image2D(PixelFormat.R8I, Vector2i(3, 17)) # TODO
a_refcount = sys.getrefcount(a)
data = a.data
self.assertEqual(len(data), 3*17*1)
self.assertIs(data.owner, a)
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
del data
self.assertEqual(sys.getrefcount(a), a_refcount)
def test_data_empty(self):
a = Image2D(PixelFormat.R8I)
a_refcount = sys.getrefcount(a)
data = a.data
self.assertEqual(len(data), 0)
self.assertIs(data.owner, None)
self.assertEqual(sys.getrefcount(a), a_refcount)
@unittest.skip("No way to create a non-empty Image at the moment")
def test_pixels(self):
a = Image2D(PixelFormat.RG32UI, Vector2i(3, 17)) # TODO
a_refcount = sys.getrefcount(a)
pixels = a.pixels
self.assertEqual(pixels.size, (3, 17, 8))
self.assertIs(pixels.owner, a)
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
del pixels
self.assertEqual(sys.getrefcount(a), a_refcount)
def test_pixels_empty(self):
a = Image2D(PixelFormat.R8I)
a_refcount = sys.getrefcount(a)
pixels = a.pixels
self.assertEqual(pixels.size, (0, 0, 1))
self.assertIs(pixels.owner, None)
self.assertEqual(sys.getrefcount(a), a_refcount)
class ImageView(unittest.TestCase):
def test_init(self):
# 2x4 RGB pixels, padded for alignment
@ -135,6 +195,41 @@ class ImageView(unittest.TestCase):
self.assertIs(b.owner, data)
self.assertEqual(sys.getrefcount(data), data_refcount + 2)
@unittest.skip("No way to create a non-empty Image at the moment")
def test_init_image(self):
a = Image2D(PixelFormat.R32F, Vector2i(3, 17)) # TODO
a_refcount = sys.getrefcount(a)
view = ImageView2D(a)
self.assertEqual(view.size, (3, 17))
self.assertIs(view.owner, a)
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
del view
self.assertEqual(sys.getrefcount(a), a_refcount)
mview = MutableImageView2D(a)
self.assertEqual(mview.size, (3, 17))
self.assertIs(mview.owner, a)
self.assertEqual(sys.getrefcount(a), a_refcount + 1)
del mview
self.assertEqual(sys.getrefcount(a), a_refcount)
def test_init_image_empty(self):
a = Image2D(PixelFormat.R32F)
a_refcount = sys.getrefcount(a)
view = ImageView2D(a)
self.assertEqual(view.size, (0, 0))
self.assertIs(view.owner, None)
self.assertEqual(sys.getrefcount(a), a_refcount)
mview = MutableImageView2D(a)
self.assertEqual(mview.size, (0, 0))
self.assertIs(mview.owner, None)
self.assertEqual(sys.getrefcount(a), a_refcount)
def test_set_data(self):
# 2x4 RGB pixels, padded for alignment
data = (b'rgbRGB '

Loading…
Cancel
Save