diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index b9a628f..3eef5d3 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -166,3 +166,31 @@ :raise RuntimeError: If image import fails :raise IndexError: If :p:`id` is negative or not less than :ref:`image3d_count` + +.. py:class:: magnum.trade.ImageConverterManager + :summary: Manager for :ref:`AbstractImageConverter` plugin instances + + Each plugin returned by :ref:`instantiate()` or :ref:`load_and_instantiate()` + references its owning :ref:`ImageConverterManager` through + :ref:`AbstractImageConverter.manager`, ensuring the manager is not deleted + before the plugin instances are. + +.. py:class:: magnum.trade.AbstractImageConverter + + Similarly to C++, image converter plugins are loaded through + :ref:`ImageConverterManager`: + + .. + >>> from magnum import trade + + .. code:: py + + >>> manager = trade.ImageConverterManager() + >>> converter = manager.load_and_instantiate('PngImageConverter') + + Unlike C++, errors in both API usage and file parsing are reported by + raising an exception. See particular function documentation for detailed + behavior. + +.. py:function:: magnum.trade.AbstractImageConverter.convert_to_file + :raise RuntimeError: If image conversion fails diff --git a/doc/python/pages/changelog.rst b/doc/python/pages/changelog.rst index 131c038..9c6cb8a 100644 --- a/doc/python/pages/changelog.rst +++ b/doc/python/pages/changelog.rst @@ -90,6 +90,7 @@ Changelog :ref:`platform.glfw.Application.exit_event` - Exposed :ref:`platform.glfw.Application.swap_interval` and :ref:`platform.glfw.Application.main_loop_iteration` +- Exposed a basic interface of :ref:`trade.AbstractImageConverter` - Exposed :ref:`Color3.red()` and other convenience constructors (see :gh:`mosra/magnum-bindings#12`) - Fixed issues with an in-source build (see :gh:`mosra/magnum-bindings#13`) diff --git a/package/ci/appveyor-desktop-gles.bat b/package/ci/appveyor-desktop-gles.bat index cf16495..5cbf8ab 100644 --- a/package/ci/appveyor-desktop-gles.bat +++ b/package/ci/appveyor-desktop-gles.bat @@ -75,6 +75,7 @@ cmake .. ^ -DCMAKE_INSTALL_PREFIX=%APPVEYOR_BUILD_FOLDER%/deps ^ -DBUILD_STATIC=%BUILD_STATIC% ^ -DWITH_DDSIMPORTER=ON ^ + -DWITH_STBIMAGECONVERTER=ON ^ -DWITH_STBIMAGEIMPORTER=ON ^ -DWITH_CGLTFIMPORTER=ON ^ -G Ninja || exit /b diff --git a/package/ci/appveyor-desktop.bat b/package/ci/appveyor-desktop.bat index 03999b5..afc6493 100644 --- a/package/ci/appveyor-desktop.bat +++ b/package/ci/appveyor-desktop.bat @@ -82,6 +82,7 @@ cmake .. ^ -DCMAKE_INSTALL_PREFIX=%APPVEYOR_BUILD_FOLDER%/deps ^ -DBUILD_STATIC=%BUILD_STATIC% ^ -DWITH_DDSIMPORTER=ON ^ + -DWITH_STBIMAGECONVERTER=ON ^ -DWITH_STBIMAGEIMPORTER=ON ^ -DWITH_CGLTFIMPORTER=ON ^ %COMPILER_EXTRA% -G Ninja || exit /b diff --git a/package/ci/unix-desktop-gles.sh b/package/ci/unix-desktop-gles.sh index d4b7be3..22209a2 100755 --- a/package/ci/unix-desktop-gles.sh +++ b/package/ci/unix-desktop-gles.sh @@ -58,6 +58,7 @@ cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_STATIC=$BUILD_STATIC \ -DWITH_DDSIMPORTER=ON \ + -DWITH_STBIMAGECONVERTER=ON \ -DWITH_STBIMAGEIMPORTER=ON \ -DWITH_CGLTFIMPORTER=ON \ -G Ninja diff --git a/package/ci/unix-desktop.sh b/package/ci/unix-desktop.sh index f7d2337..c33f384 100755 --- a/package/ci/unix-desktop.sh +++ b/package/ci/unix-desktop.sh @@ -66,6 +66,7 @@ cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_STATIC=$BUILD_STATIC \ -DWITH_DDSIMPORTER=ON \ + -DWITH_STBIMAGECONVERTER=ON \ -DWITH_STBIMAGEIMPORTER=ON \ -DWITH_CGLTFIMPORTER=ON \ -G Ninja diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index c12b059..10fe537 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -25,6 +25,7 @@ import os import sys +import tempfile import unittest from corrade import pluginmanager @@ -265,3 +266,26 @@ class Importer(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "import failed"): importer.image2d(0) + +class ImageConverter(unittest.TestCase): + def test_image2d(self): + importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png')) + image = importer.image2d(0) + + 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"))) + + def test_image2d_failed(self): + importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png')) + image = importer.image2d(0) + + converter = trade.ImageConverterManager().load_and_instantiate('StbImageConverter') + + with tempfile.TemporaryDirectory() as tmp: + with self.assertRaisesRegex(RuntimeError, "conversion failed"): + converter.convert_to_file(image, os.path.join(tmp, "image.hdr")) diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 22660f6..696e4e4 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -29,6 +29,7 @@ #include /** @todo drop once we have our string casters */ #include #include +#include #include #include @@ -241,6 +242,17 @@ template(Trade::AbstractImporter::*f)(UnsignedI return *std::move(out); } +/** @todo drop std::string in favor of our own string caster */ +template void checkResult(Trade::AbstractImageConverter& self, const T& image, const std::string& filename) { + /** @todo log redirection -- but we'd need assertions to not be part of + that so when it dies, the user can still see why */ + bool out = (self.*f)(image, filename); + if(!out) { + PyErr_SetString(PyExc_RuntimeError, "conversion failed"); + throw py::error_already_set{}; + } +} + } void trade(py::module_& m) { @@ -318,6 +330,17 @@ void trade(py::module_& m) { py::class_, PluginManager::AbstractManager> importerManager{m, "ImporterManager", "Manager for importer plugins"}; corrade::manager(importerManager); + + /* Image converter */ + py::class_> abstractImageConverter{m, "AbstractImageConverter", "Interface for image converter plugins"}; + abstractImageConverter + .def("convert_to_file", checkResult, "Convert a 1D image to a file", py::arg("image"), py::arg("filename")) + .def("convert_to_file", checkResult, "Convert a 2D image to a file", py::arg("image"), py::arg("filename")) + .def("convert_to_file", checkResult, "Convert a 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"}; + corrade::manager(imageConverterManager); } }