From 85e8fae9b8455436ea5927aba2515ddb8eb3187e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 1 Jun 2023 00:03:59 +0200 Subject: [PATCH] python: expose compressed image APIs in AbstractImageConverter. --- doc/python/magnum.trade.rst | 60 ++++++++++++++++++++++++ package/ci/appveyor-desktop-gles.bat | 3 ++ package/ci/appveyor-desktop.bat | 3 ++ package/ci/unix-desktop-gles.sh | 3 ++ package/ci/unix-desktop.sh | 3 ++ src/python/magnum/test/test_trade.py | 68 ++++++++++++++++++++++++++-- src/python/magnum/trade.cpp | 18 +++++++- 7 files changed, 153 insertions(+), 5 deletions(-) diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index 199b09b..b98d23f 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -595,12 +595,48 @@ raising an exception. See particular function documentation for detailed behavior. +.. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.trade.ImageData1D) + :raise RuntimeError: If image conversion fails +.. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.trade.ImageData2D) + :raise RuntimeError: If image conversion fails +.. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.trade.ImageData3D) + :raise RuntimeError: If image conversion fails .. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.ImageView1D) :raise RuntimeError: If image conversion fails .. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.ImageView2D) :raise RuntimeError: If image conversion fails .. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.ImageView3D) :raise RuntimeError: If image conversion fails +.. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.CompressedImageView1D) + :raise RuntimeError: If image conversion fails +.. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.CompressedImageView2D) + :raise RuntimeError: If image conversion fails +.. py:function:: magnum.trade.AbstractImageConverter.convert(self, image: magnum.CompressedImageView3D) + :raise RuntimeError: If image conversion fails + +.. py:function:: magnum.trade.AbstractImageConverter.convert_to_file(self, image: magnum.trade.ImageData1D, filename: str) + :raise RuntimeError: If image conversion fails + + For compatibility with :ref:`os.path`, on Windows this function converts + all backslashes in :p:`filename` to forward slashes before passing it to + :dox:`Trade::AbstractImageConverter::convertToFile()`, which expects + forward slashes as directory separators on all platforms. + +.. py:function:: magnum.trade.AbstractImageConverter.convert_to_file(self, image: magnum.trade.ImageData2D, filename: str) + :raise RuntimeError: If image conversion fails + + For compatibility with :ref:`os.path`, on Windows this function converts + all backslashes in :p:`filename` to forward slashes before passing it to + :dox:`Trade::AbstractImageConverter::convertToFile()`, which expects + forward slashes as directory separators on all platforms. + +.. py:function:: magnum.trade.AbstractImageConverter.convert_to_file(self, image: magnum.trade.ImageData3D, filename: str) + :raise RuntimeError: If image conversion fails + + For compatibility with :ref:`os.path`, on Windows this function converts + all backslashes in :p:`filename` to forward slashes before passing it to + :dox:`Trade::AbstractImageConverter::convertToFile()`, which expects + forward slashes as directory separators on all platforms. .. py:function:: magnum.trade.AbstractImageConverter.convert_to_file(self, image: magnum.ImageView1D, filename: str) :raise RuntimeError: If image conversion fails @@ -626,6 +662,30 @@ :dox:`Trade::AbstractImageConverter::convertToFile()`, which expects forward slashes as directory separators on all platforms. +.. py:function:: magnum.trade.AbstractImageConverter.convert_to_file(self, image: magnum.CompressedImageView1D, filename: str) + :raise RuntimeError: If image conversion fails + + For compatibility with :ref:`os.path`, on Windows this function converts + all backslashes in :p:`filename` to forward slashes before passing it to + :dox:`Trade::AbstractImageConverter::convertToFile()`, which expects + forward slashes as directory separators on all platforms. + +.. py:function:: magnum.trade.AbstractImageConverter.convert_to_file(self, image: magnum.CompressedImageView2D, filename: str) + :raise RuntimeError: If image conversion fails + + For compatibility with :ref:`os.path`, on Windows this function converts + all backslashes in :p:`filename` to forward slashes before passing it to + :dox:`Trade::AbstractImageConverter::convertToFile()`, which expects + forward slashes as directory separators on all platforms. + +.. py:function:: magnum.trade.AbstractImageConverter.convert_to_file(self, image: magnum.CompressedImageView3D, filename: str) + :raise RuntimeError: If image conversion fails + + For compatibility with :ref:`os.path`, on Windows this function converts + all backslashes in :p:`filename` to forward slashes before passing it to + :dox:`Trade::AbstractImageConverter::convertToFile()`, which expects + forward slashes as directory separators on all platforms. + .. py:class:: magnum.trade.SceneConverterManager :summary: Manager for :ref:`AbstractSceneConverter` plugin instances diff --git a/package/ci/appveyor-desktop-gles.bat b/package/ci/appveyor-desktop-gles.bat index 548607b..e3bb095 100644 --- a/package/ci/appveyor-desktop-gles.bat +++ b/package/ci/appveyor-desktop-gles.bat @@ -78,9 +78,12 @@ cmake .. ^ -DCMAKE_BUILD_TYPE=Release ^ -DCMAKE_INSTALL_PREFIX=%APPVEYOR_BUILD_FOLDER%/deps ^ -DMAGNUM_BUILD_STATIC=%BUILD_STATIC% ^ + -DMAGNUM_WITH_BCDECIMAGECONVERTER=ON ^ -DMAGNUM_WITH_DDSIMPORTER=ON ^ + -DMAGNUM_WITH_ETCDECIMAGECONVERTER=ON ^ -DMAGNUM_WITH_GLTFIMPORTER=ON ^ -DMAGNUM_WITH_GLTFSCENECONVERTER=ON ^ + -DMAGNUM_WITH_KTXIMAGECONVERTER=ON ^ -DMAGNUM_WITH_MESHOPTIMIZERSCENECONVERTER=ON ^ -DMAGNUM_WITH_PRIMITIVEIMPORTER=ON ^ -DMAGNUM_WITH_STANFORDSCENECONVERTER=ON ^ diff --git a/package/ci/appveyor-desktop.bat b/package/ci/appveyor-desktop.bat index fff811f..bf4cb8c 100644 --- a/package/ci/appveyor-desktop.bat +++ b/package/ci/appveyor-desktop.bat @@ -88,9 +88,12 @@ cmake .. ^ -DCMAKE_BUILD_TYPE=Release ^ -DCMAKE_INSTALL_PREFIX=%APPVEYOR_BUILD_FOLDER%/deps ^ -DMAGNUM_BUILD_STATIC=%BUILD_STATIC% ^ + -DMAGNUM_WITH_BCDECIMAGECONVERTER=ON ^ -DMAGNUM_WITH_DDSIMPORTER=ON ^ + -DMAGNUM_WITH_ETCDECIMAGECONVERTER=ON ^ -DMAGNUM_WITH_GLTFIMPORTER=ON ^ -DMAGNUM_WITH_GLTFSCENECONVERTER=ON ^ + -DMAGNUM_WITH_KTXIMAGECONVERTER=ON ^ -DMAGNUM_WITH_MESHOPTIMIZERSCENECONVERTER=%EXCEPT_MSVC2017% ^ -DMAGNUM_WITH_PRIMITIVEIMPORTER=ON ^ -DMAGNUM_WITH_STANFORDSCENECONVERTER=ON ^ diff --git a/package/ci/unix-desktop-gles.sh b/package/ci/unix-desktop-gles.sh index 223edac..bb3b036 100755 --- a/package/ci/unix-desktop-gles.sh +++ b/package/ci/unix-desktop-gles.sh @@ -65,9 +65,12 @@ cmake .. \ -DCMAKE_PREFIX_PATH=$HOME/swiftshader \ -DCMAKE_INSTALL_RPATH="$HOME/deps/lib;$HOME/swiftshader/lib" \ -DMAGNUM_BUILD_STATIC=$BUILD_STATIC \ + -DMAGNUM_WITH_BCDECIMAGECONVERTER=ON \ -DMAGNUM_WITH_DDSIMPORTER=ON \ + -DMAGNUM_WITH_ETCDECIMAGECONVERTER=ON \ -DMAGNUM_WITH_GLTFIMPORTER=ON \ -DMAGNUM_WITH_GLTFSCENECONVERTER=ON \ + -DMAGNUM_WITH_KTXIMAGECONVERTER=ON \ -DMAGNUM_WITH_MESHOPTIMIZERSCENECONVERTER=ON \ -DMAGNUM_WITH_PRIMITIVEIMPORTER=ON \ -DMAGNUM_WITH_STANFORDSCENECONVERTER=ON \ diff --git a/package/ci/unix-desktop.sh b/package/ci/unix-desktop.sh index 6fb1f5e..c717cea 100755 --- a/package/ci/unix-desktop.sh +++ b/package/ci/unix-desktop.sh @@ -69,9 +69,12 @@ cmake .. \ -DCMAKE_INSTALL_RPATH=$HOME/deps/lib \ -DCMAKE_BUILD_TYPE=Release \ -DMAGNUM_BUILD_STATIC=$BUILD_STATIC \ + -DMAGNUM_WITH_BCDECIMAGECONVERTER=ON \ -DMAGNUM_WITH_DDSIMPORTER=ON \ + -DMAGNUM_WITH_ETCDECIMAGECONVERTER=ON \ -DMAGNUM_WITH_GLTFIMPORTER=ON \ -DMAGNUM_WITH_GLTFSCENECONVERTER=ON \ + -DMAGNUM_WITH_KTXIMAGECONVERTER=ON \ -DMAGNUM_WITH_MESHOPTIMIZERSCENECONVERTER=ON \ -DMAGNUM_WITH_PRIMITIVEIMPORTER=ON \ -DMAGNUM_WITH_STANFORDSCENECONVERTER=ON \ diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index 8736435..0a5803c 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -1664,8 +1664,11 @@ class ImageConverter(unittest.TestCase): converter = trade.ImageConverterManager().load_and_instantiate('StbResizeImageConverter') converter.configuration['size'] = "1 1" - converted = converter.convert(image) - self.assertEqual(converted.size, Vector2i(1, 1)) + # Both ImageView and ImageData should work + converted1 = converter.convert(image) + converted2 = converter.convert(ImageView2D(image)) + self.assertEqual(converted1.size, Vector2i(1, 1)) + self.assertEqual(converted2.size, Vector2i(1, 1)) def test_image2d_failed(self): importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') @@ -1679,6 +1682,31 @@ class ImageConverter(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "conversion failed"): converter.convert(image) + def test_compressed_image2d(self): + importer = trade.ImporterManager().load_and_instantiate('DdsImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'rgba_dxt1.dds')) + image = importer.image2d(0) + self.assertTrue(image.is_compressed) + + converter = trade.ImageConverterManager().load_and_instantiate('BcDecImageConverter') + + # Both ImageData and CompressedImageView should work + converted1 = converter.convert(image) + converted2 = converter.convert(CompressedImageView2D(image)) + self.assertFalse(converted1.is_compressed) + self.assertFalse(converted2.is_compressed) + + def test_compressed_image2d_failed(self): + importer = trade.ImporterManager().load_and_instantiate('DdsImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'rgba_dxt1.dds')) + image = importer.image2d(0) + self.assertTrue(image.is_compressed) + + converter = trade.ImageConverterManager().load_and_instantiate('EtcDecImageConverter') + + with self.assertRaisesRegex(RuntimeError, "conversion failed"): + converter.convert(image) + def test_image2d_to_file(self): importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png')) @@ -1687,8 +1715,11 @@ class ImageConverter(unittest.TestCase): converter = trade.ImageConverterManager().load_and_instantiate('StbImageConverter') with tempfile.TemporaryDirectory() as tmp: - converter.convert_to_file(image, os.path.join(tmp, "image.png")) - self.assertTrue(os.path.exists(os.path.join(tmp, "image.png"))) + # Both ImageData and ImageView should work + converter.convert_to_file(image, os.path.join(tmp, "image1.png")) + converter.convert_to_file(ImageView2D(image), os.path.join(tmp, "image2.png")) + self.assertTrue(os.path.exists(os.path.join(tmp, "image1.png"))) + self.assertTrue(os.path.exists(os.path.join(tmp, "image2.png"))) def test_image2d_to_file_failed(self): importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') @@ -1701,6 +1732,35 @@ class ImageConverter(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "conversion failed"): converter.convert_to_file(image, os.path.join(tmp, "image.hdr")) + def test_compressed_image2d_to_file(self): + importer = trade.ImporterManager().load_and_instantiate('DdsImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'rgba_dxt1.dds')) + image = importer.image2d(0) + self.assertTrue(image.is_compressed) + + converter = trade.ImageConverterManager().load_and_instantiate('KtxImageConverter') + + with tempfile.TemporaryDirectory() as tmp: + # Both ImageData and CompressedImageView should work + converter.convert_to_file(image, os.path.join(tmp, "image1.ktx2")) + converter.convert_to_file(CompressedImageView2D(image), os.path.join(tmp, "image2.ktx2")) + self.assertTrue(os.path.exists(os.path.join(tmp, "image1.ktx2"))) + self.assertTrue(os.path.exists(os.path.join(tmp, "image2.ktx2"))) + + def test_compressed_image2d_to_file_failed(self): + importer = trade.ImporterManager().load_and_instantiate('DdsImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'rgba_dxt1.dds')) + image = importer.image2d(0) + self.assertTrue(image.is_compressed) + + converter = trade.ImageConverterManager().load_and_instantiate('KtxImageConverter') + # Set something stupid in the config to make it fail + converter.configuration['swizzle'] = "haha" + + with tempfile.TemporaryDirectory() as tmp: + with self.assertRaisesRegex(RuntimeError, "conversion failed"): + converter.convert_to_file(image, os.path.join(tmp, "image.ktx2")) + class SceneConverter(unittest.TestCase): def test_scenecontents_for_importer(self): # Silly, yes, but don't want to enable StanfordImporter just for this diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 0868d00..38f9192 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -1721,12 +1721,28 @@ void trade(py::module_& m) { }, [](Trade::AbstractImageConverter& self, Trade::ImageConverterFlag flags) { self.setFlags(flags); }, "Converter flags") + /* ImageData overloads should be first so they correctly dispatch to + either a compressed or a non-compressed overload. With the views + being first it'd just pick whichever of them is earliest as + ImageData is implicitly convertible to each. */ + .def("convert", checkImageConverterResult, "Convert a 1D image data", py::arg("image")) + .def("convert", checkImageConverterResult, "Convert a 2D image data", py::arg("image")) + .def("convert", checkImageConverterResult, "Convert a 3D image data", py::arg("image")) .def("convert", checkImageConverterResult, "Convert a 1D image", py::arg("image")) .def("convert", checkImageConverterResult, "Convert a 2D image", py::arg("image")) .def("convert", checkImageConverterResult, "Convert a 3D image", py::arg("image")) + .def("convert", checkImageConverterResult, "Convert a compressed 1D image", py::arg("image")) + .def("convert", checkImageConverterResult, "Convert a compressed 2D image", py::arg("image")) + .def("convert", checkImageConverterResult, "Convert a compressed 3D image", py::arg("image")) + .def("convert_to_file", checkImageConverterResult, "Convert a 1D image data to a file", py::arg("image"), py::arg("filename")) + .def("convert_to_file", checkImageConverterResult, "Convert a 2D image data to a file", py::arg("image"), py::arg("filename")) + .def("convert_to_file", checkImageConverterResult, "Convert a 3D image data to a file", py::arg("image"), py::arg("filename")) .def("convert_to_file", checkImageConverterResult, "Convert a 1D image to a file", py::arg("image"), py::arg("filename")) .def("convert_to_file", checkImageConverterResult, "Convert a 2D image to a file", py::arg("image"), py::arg("filename")) - .def("convert_to_file", checkImageConverterResult, "Convert a 3D image to a file", py::arg("image"), py::arg("filename")); + .def("convert_to_file", checkImageConverterResult, "Convert a 3D image to a file", py::arg("image"), py::arg("filename")) + .def("convert_to_file", checkImageConverterResult, "Convert a compressed 1D image to a file", py::arg("image"), py::arg("filename")) + .def("convert_to_file", checkImageConverterResult, "Convert a compressed 2D image to a file", py::arg("image"), py::arg("filename")) + .def("convert_to_file", checkImageConverterResult, "Convert a compressed 3D image to a file", py::arg("image"), py::arg("filename")); corrade::plugin(abstractImageConverter); py::class_, PluginManager::AbstractManager> imageConverterManager{m, "ImageConverterManager", "Manager for image converter plugins"};