From 6d473cf2aa1e234f76ab28486545e3a7ac553beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 31 May 2023 20:32:03 +0200 Subject: [PATCH] python: expose various pixel format helpers. --- doc/python/magnum.rst | 28 +++++++++ doc/python/pages/changelog.rst | 4 +- src/python/magnum/magnum.cpp | 108 ++++++++++++++++++++++++++++++++- src/python/magnum/test/test.py | 62 +++++++++++++++++++ 4 files changed, 199 insertions(+), 3 deletions(-) diff --git a/doc/python/magnum.rst b/doc/python/magnum.rst index 4cf11dc..56f49a6 100644 --- a/doc/python/magnum.rst +++ b/doc/python/magnum.rst @@ -38,6 +38,34 @@ :data TARGET_EGL: EGL target :data TARGET_VK: Vulkan interoperability +.. py:enum:: magnum.PixelFormat + + The ``size``, ``channel_format``, ``channel_count``, ``is_normalized``, + ``is_integral``, ``is_floating_point``, ``is_srgb``, + ``is_depth_or_stencil`` and ``is_implementation_specific`` properties match + :dox:`pixelFormatSize()`, :dox:`pixelFormatChannelFormat()` and other + helpers. + + .. code:: pycon + + >>> PixelFormat.RGB8_SRGB.channel_count + 3 + >>> PixelFormat.RGB16F.is_floating_point + True + +.. py:enum:: magnum.CompressedPixelFormat + + The ``block_size``, ``block_data_size`` and ``is_implementation_specific`` + properties match :dox:`compressedPixelFormatBlockSize()`, + :dox:`compressedPixelFormatBlockDataSize()` and other helpers. + + .. code:: pycon + + >>> CompressedPixelFormat.ASTC_6X5_RGBA_SRGB.block_size + Vector(6, 5, 1) + >>> CompressedPixelFormat.ASTC_6X5_RGBA_SRGB.block_data_size + 16 + .. py:class:: magnum.Image1D See :ref:`Image2D` for more information. diff --git a/doc/python/pages/changelog.rst b/doc/python/pages/changelog.rst index 3888bb0..2f76798 100644 --- a/doc/python/pages/changelog.rst +++ b/doc/python/pages/changelog.rst @@ -55,7 +55,9 @@ Changelog :ref:`Matrix3.projection()` - Exposed remaining vector/scalar, exponential and other functions in the :ref:`math ` library -- Exposed the :ref:`CompressedPixelFormat` enum +- Exposed the :ref:`CompressedPixelFormat` enum, various pixel-format-related + helper APIs are now properties on :ref:`PixelFormat` and + :ref:`CompressedPixelFormat` - Exposed :ref:`Color3.from_xyz()`, :ref:`Color3.from_linear_rgb_int()`, :ref:`Color3.to_xyz()`, :ref:`Color3.to_linear_rgb_int()` and equivalent APIs on :ref:`Color4` diff --git a/src/python/magnum/magnum.cpp b/src/python/magnum/magnum.cpp index 0f940f9..43234ef 100644 --- a/src/python/magnum/magnum.cpp +++ b/src/python/magnum/magnum.cpp @@ -302,7 +302,92 @@ void magnum(py::module_& m) { .value("STENCIL8UI", PixelFormat::Stencil8UI) .value("DEPTH16_UNORM_STENCIL8UI", PixelFormat::Depth16UnormStencil8UI) .value("DEPTH24_UNORM_STENCIL8UI", PixelFormat::Depth24UnormStencil8UI) - .value("DEPTH32F_STENCIL8UI", PixelFormat::Depth32FStencil8UI); + .value("DEPTH32F_STENCIL8UI", PixelFormat::Depth32FStencil8UI) + .def_property_readonly("size", [](PixelFormat self) { + if(isPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine size of an implementation-specific format"); + throw py::error_already_set{}; + } + return pixelFormatSize(self); + }, "Size of given pixel format") + .def_property_readonly("channel_format", [](PixelFormat self) { + if(isPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine channel format of an implementation-specific format"); + throw py::error_already_set{}; + } + if(isPixelFormatDepthOrStencil(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine channel format of a depth/stencil format"); + throw py::error_already_set{}; + } + return pixelFormatChannelFormat(self); + }, "Channel format of given pixel format") + .def_property_readonly("channel_count", [](PixelFormat self) { + if(isPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine channel count of an implementation-specific format"); + throw py::error_already_set{}; + } + if(isPixelFormatDepthOrStencil(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine channel count of a depth/stencil format"); + throw py::error_already_set{}; + } + return pixelFormatChannelCount(self); + }, "Channel format of given pixel format") + .def_property_readonly("is_normalized", [](PixelFormat self) { + if(isPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine type of an implementation-specific format"); + throw py::error_already_set{}; + } + if(isPixelFormatDepthOrStencil(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine type of a depth/stencil format"); + throw py::error_already_set{}; + } + return isPixelFormatNormalized(self); + }, "Whether given pixel format is normalized") + .def_property_readonly("is_integral", [](PixelFormat self) { + if(isPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine type of an implementation-specific format"); + throw py::error_already_set{}; + } + if(isPixelFormatDepthOrStencil(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine type of a depth/stencil format"); + throw py::error_already_set{}; + } + return isPixelFormatIntegral(self); + }, "Whether given pixel format is integral") + .def_property_readonly("is_floating_point", [](PixelFormat self) { + if(isPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine type of an implementation-specific format"); + throw py::error_already_set{}; + } + if(isPixelFormatDepthOrStencil(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine type of a depth/stencil format"); + throw py::error_already_set{}; + } + return isPixelFormatFloatingPoint(self); + }, "Whether given pixel format is floating-point") + .def_property_readonly("is_srgb", [](PixelFormat self) { + if(isPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine colorspace of an implementation-specific format"); + throw py::error_already_set{}; + } + if(isPixelFormatDepthOrStencil(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine colorspace of a depth/stencil format"); + throw py::error_already_set{}; + } + return isPixelFormatSrgb(self); + }, "Whether given pixel format is sRGB") + .def_property_readonly("is_depth_or_stencil", [](PixelFormat self) { + if(isPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine type of an implementation-specific format"); + throw py::error_already_set{}; + } + return isPixelFormatDepthOrStencil(self); + }, "Whether given pixel format is depth or stencil") + .def_property_readonly("is_implementation_specific", [](PixelFormat self) { + return isPixelFormatImplementationSpecific(self); + }, "Whether given pixel format wraps an implementation-specific identifier") + /** @todo wrap/unwrap, similarly to custom MeshAttribute etc in Trade */ + ; py::enum_{m, "CompressedPixelFormat", "Format of compressed pixel data"} .value("BC1_RGB_UNORM", CompressedPixelFormat::Bc1RGBUnorm) @@ -410,7 +495,26 @@ void magnum(py::module_& m) { .value("PVRTC_RGB_4PP_UNORM", CompressedPixelFormat::PvrtcRGB4bppUnorm) .value("PVRTC_RGB_4PP_SRGB", CompressedPixelFormat::PvrtcRGB4bppSrgb) .value("PVRTC_RGBA_4PP_UNORM", CompressedPixelFormat::PvrtcRGBA4bppUnorm) - .value("PVRTC_RGBA_4PP_SRGB", CompressedPixelFormat::PvrtcRGBA4bppSrgb); + .value("PVRTC_RGBA_4PP_SRGB", CompressedPixelFormat::PvrtcRGBA4bppSrgb) + .def_property_readonly("block_size", [](CompressedPixelFormat self) { + if(isCompressedPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine size of an implementation-specific format"); + throw py::error_already_set{}; + } + return compressedPixelFormatBlockSize(self); + }, "Block size of given compressed pixel format") + .def_property_readonly("block_data_size", [](CompressedPixelFormat self) { + if(isCompressedPixelFormatImplementationSpecific(self)) { + PyErr_SetString(PyExc_AssertionError, "can't determine size of an implementation-specific format"); + throw py::error_already_set{}; + } + return compressedPixelFormatBlockDataSize(self); + }, "Block data size of given compressed pixel format") + .def_property_readonly("is_implementation_specific", [](CompressedPixelFormat self) { + return isCompressedPixelFormatImplementationSpecific(self); + }, "Whether given compressed pixel format wraps an implementation-specific identifier") + /** @todo wrap/unwrap, similarly to custom MeshAttribute etc in Trade */ + ; py::class_{m, "PixelStorage", "Pixel storage parameters"} .def(py::init(), "Default constructor") diff --git a/src/python/magnum/test/test.py b/src/python/magnum/test/test.py index f9a3dbc..c3b92a7 100644 --- a/src/python/magnum/test/test.py +++ b/src/python/magnum/test/test.py @@ -32,6 +32,68 @@ from magnum import * # tests also corrade.utility.copy() in UtilityCopy +class PixelFormat_(unittest.TestCase): + def test_properties(self): + self.assertEqual(PixelFormat.RGB16_SNORM.size, 3*2) + self.assertEqual(PixelFormat.RG32F.channel_format, PixelFormat.R32F) + self.assertEqual(PixelFormat.RGBA16_SNORM.channel_count, 4) + self.assertTrue(PixelFormat.RGBA8_UNORM.is_normalized) + self.assertFalse(PixelFormat.RGBA16F.is_normalized) + self.assertTrue(PixelFormat.RGB8I.is_integral) + self.assertFalse(PixelFormat.RGB8_SNORM.is_integral) + self.assertTrue(PixelFormat.RGB16F.is_floating_point) + self.assertFalse(PixelFormat.R8_SRGB.is_floating_point) + self.assertTrue(PixelFormat.RG8_SRGB.is_srgb) + self.assertFalse(PixelFormat.RG8_UNORM.is_srgb) + self.assertTrue(PixelFormat.DEPTH32F.is_depth_or_stencil) + self.assertFalse(PixelFormat.RG32I.is_depth_or_stencil) + self.assertTrue(PixelFormat(0x80000001).is_implementation_specific) + self.assertFalse(PixelFormat.RG32I.is_implementation_specific) + + def test_properties_invalid(self): + with self.assertRaisesRegex(AssertionError, "can't determine size of an implementation-specific format"): + PixelFormat(0x80000000).size + with self.assertRaisesRegex(AssertionError, "can't determine channel format of an implementation-specific format"): + PixelFormat(0x80000000).channel_format + with self.assertRaisesRegex(AssertionError, "can't determine channel count of an implementation-specific format"): + PixelFormat(0x80000000).channel_count + with self.assertRaisesRegex(AssertionError, "can't determine type of an implementation-specific format"): + PixelFormat(0x80000000).is_normalized + with self.assertRaisesRegex(AssertionError, "can't determine type of an implementation-specific format"): + PixelFormat(0x80000000).is_integral + with self.assertRaisesRegex(AssertionError, "can't determine type of an implementation-specific format"): + PixelFormat(0x80000000).is_floating_point + with self.assertRaisesRegex(AssertionError, "can't determine colorspace of an implementation-specific format"): + PixelFormat(0x80000000).is_srgb + with self.assertRaisesRegex(AssertionError, "can't determine type of an implementation-specific format"): + PixelFormat(0x80000000).is_depth_or_stencil + + with self.assertRaisesRegex(AssertionError, "can't determine channel format of a depth/stencil format"): + PixelFormat.DEPTH32F.channel_format + with self.assertRaisesRegex(AssertionError, "can't determine channel count of a depth/stencil format"): + PixelFormat.DEPTH32F.channel_count + with self.assertRaisesRegex(AssertionError, "can't determine type of a depth/stencil format"): + PixelFormat.DEPTH32F.is_normalized + with self.assertRaisesRegex(AssertionError, "can't determine type of a depth/stencil format"): + PixelFormat.DEPTH32F.is_integral + with self.assertRaisesRegex(AssertionError, "can't determine type of a depth/stencil format"): + PixelFormat.STENCIL8UI.is_floating_point + with self.assertRaisesRegex(AssertionError, "can't determine colorspace of a depth/stencil format"): + PixelFormat.DEPTH32F_STENCIL8UI.is_srgb + +class CompressedPixelFormat_(unittest.TestCase): + def test_properties(self): + self.assertEqual(CompressedPixelFormat.ASTC_5X4_RGBA_UNORM.block_size, (5, 4, 1)) + self.assertEqual(CompressedPixelFormat.ASTC_5X4_RGBA_UNORM.block_data_size, 128/8) + self.assertTrue(CompressedPixelFormat(0x80000001).is_implementation_specific) + self.assertFalse(CompressedPixelFormat.BC1_RGB_SRGB.is_implementation_specific) + + def test_properties_invalid(self): + with self.assertRaisesRegex(AssertionError, "can't determine size of an implementation-specific format"): + CompressedPixelFormat(0x80000000).block_size + with self.assertRaisesRegex(AssertionError, "can't determine size of an implementation-specific format"): + CompressedPixelFormat(0x80000000).block_data_size + class PixelStorage_(unittest.TestCase): def test_init(self): a = PixelStorage()