diff --git a/doc/changelog.dox b/doc/changelog.dox index 568d6f97d..81348dbe0 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -361,6 +361,8 @@ See also: plugin to apply the option to with `-I` / `-C`. For better usability, the plugins also warn if the user specifies and option that is not present in the target implementation. +- @relativeref{Trade,AnyImageConverter} now implements also conversion of 3D + and multi-level 2D/3D images for formats that support it (such as OpenEXR) - Added @ref Trade::PhongMaterialData::hasCommonTextureTransformation(), @ref Trade::PhongMaterialData::ambientTextureMatrix(), @ref Trade::PhongMaterialData::diffuseTextureMatrix(), diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp index b68381258..e00acaf4e 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp @@ -133,13 +133,47 @@ bool AnyImageConverter::doConvertToFile(const ImageView2D& image, const Containe return converter->convertToFile(image, filename); } -bool AnyImageConverter::doConvertToFile(const ImageView3D&, const Containers::StringView filename) { +bool AnyImageConverter::doConvertToFile(const ImageView3D& image, const Containers::StringView filename) { CORRADE_INTERNAL_ASSERT(manager()); - /* No file formats to store 3D data yet */ + /** @todo once Directory is std::string-free, use splitExtension(), but + only if we don't detect more than one extension yet */ + const Containers::String normalized = Utility::String::lowercase(filename); - Error{} << "Trade::AnyImageConverter::convertToFile(): cannot determine the format of" << filename << "for a 3D image"; - return false; + /* Detect the plugin from extension */ + Containers::StringView plugin; + if(normalized.hasSuffix(".exr"_s)) + plugin = "OpenExrImageConverter"_s; + else { + Error{} << "Trade::AnyImageConverter::convertToFile(): cannot determine the format of" << filename << "for a 3D image"; + return false; + } + + /* Try to load the plugin */ + if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) { + Error{} << "Trade::AnyImageConverter::convertToFile(): cannot load the" << plugin << "plugin"; + return false; + } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); + if(flags() & ImageConverterFlag::Verbose) { + Debug d; + d << "Trade::AnyImageConverter::convertToFile(): using" << plugin; + if(plugin != metadata->name()) + d << "(provided by" << metadata->name() << Debug::nospace << ")"; + } + + /* Instantiate the plugin, propagate flags */ + Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); + converter->setFlags(flags()); + + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageConverter::convertToFile():", {}, metadata->name(), configuration(), converter->configuration()); + + /* Try to convert the file (error output should be printed by the plugin + itself) */ + return converter->convertToFile(image, filename); } bool AnyImageConverter::doConvertToFile(const CompressedImageView1D&, const Containers::StringView filename) { @@ -178,22 +212,90 @@ bool AnyImageConverter::doConvertToFile(Containers::ArrayView return false; } -bool AnyImageConverter::doConvertToFile(Containers::ArrayView, const Containers::StringView filename) { +bool AnyImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { CORRADE_INTERNAL_ASSERT(manager()); - /* No file formats to store multi-level 2D data yet */ + /** @todo once Directory is std::string-free, use splitExtension(), but + only if we don't detect more than one extension yet */ + const Containers::String normalized = Utility::String::lowercase(filename); - Error{} << "Trade::AnyImageConverter::convertToFile(): cannot determine the format of" << filename << "for a multi-level 2D image"; - return false; + /* Detect the plugin from extension */ + Containers::StringView plugin; + if(normalized.hasSuffix(".exr"_s)) + plugin = "OpenExrImageConverter"_s; + else { + Error{} << "Trade::AnyImageConverter::convertToFile(): cannot determine the format of" << filename << "for a multi-level 2D image"; + return false; + } + + /* Try to load the plugin */ + if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) { + Error{} << "Trade::AnyImageConverter::convertToFile(): cannot load the" << plugin << "plugin"; + return false; + } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); + if(flags() & ImageConverterFlag::Verbose) { + Debug d; + d << "Trade::AnyImageConverter::convertToFile(): using" << plugin; + if(plugin != metadata->name()) + d << "(provided by" << metadata->name() << Debug::nospace << ")"; + } + + /* Instantiate the plugin, propagate flags */ + Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); + converter->setFlags(flags()); + + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageConverter::convertToFile():", {}, metadata->name(), configuration(), converter->configuration()); + + /* Try to convert the file (error output should be printed by the plugin + itself) */ + return converter->convertToFile(imageLevels, filename); } -bool AnyImageConverter::doConvertToFile(Containers::ArrayView, const Containers::StringView filename) { +bool AnyImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { CORRADE_INTERNAL_ASSERT(manager()); - /* No file formats to store multi-level 3D data yet */ + /** @todo once Directory is std::string-free, use splitExtension(), but + only if we don't detect more than one extension yet */ + const Containers::String normalized = Utility::String::lowercase(filename); - Error{} << "Trade::AnyImageConverter::convertToFile(): cannot determine the format of" << filename << "for a multi-level 3D image"; - return false; + /* Detect the plugin from extension */ + Containers::StringView plugin; + if(normalized.hasSuffix(".exr"_s)) + plugin = "OpenExrImageConverter"_s; + else { + Error{} << "Trade::AnyImageConverter::convertToFile(): cannot determine the format of" << filename << "for a multi-level 3D image"; + return false; + } + + /* Try to load the plugin */ + if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) { + Error{} << "Trade::AnyImageConverter::convertToFile(): cannot load the" << plugin << "plugin"; + return false; + } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); + if(flags() & ImageConverterFlag::Verbose) { + Debug d; + d << "Trade::AnyImageConverter::convertToFile(): using" << plugin; + if(plugin != metadata->name()) + d << "(provided by" << metadata->name() << Debug::nospace << ")"; + } + + /* Instantiate the plugin, propagate flags */ + Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); + converter->setFlags(flags()); + + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageConverter::convertToFile():", {}, metadata->name(), configuration(), converter->configuration()); + + /* Try to convert the file (error output should be printed by the plugin + itself) */ + return converter->convertToFile(imageLevels, filename); } bool AnyImageConverter::doConvertToFile(Containers::ArrayView, const Containers::StringView filename) { diff --git a/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp b/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp index 6b4b33426..1b8d53462 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp +++ b/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp @@ -24,6 +24,7 @@ */ #include +#include /* std::thread::hardware_concurrency(), sigh */ #include #include #include @@ -150,6 +151,36 @@ constexpr struct { {"PNG", "file.png", "PngImageConverter"} }; +constexpr struct { + const char* name; + const char* filename; + const char* plugin; +} Detect3DData[]{ + {"EXR", "file.exr", "OpenExrImageConverter"}, + /* Have at least one test case with uppercase */ + {"EXR uppercase", "FIL~1.EXR", "OpenExrImageConverter"} +}; + +constexpr struct { + const char* name; + const char* filename; + const char* plugin; +} DetectLevels2DData[]{ + {"EXR", "file.exr", "OpenExrImageConverter"}, + /* Have at least one test case with uppercase */ + {"EXR uppercase", "FIL~1.EXR", "OpenExrImageConverter"} +}; + +constexpr struct { + const char* name; + const char* filename; + const char* plugin; +} DetectLevels3DData[]{ + {"EXR", "file.exr", "OpenExrImageConverter"}, + /* Have at least one test case with uppercase */ + {"EXR uppercase", "FIL~1.EXR", "OpenExrImageConverter"} +}; + AnyImageConverterTest::AnyImageConverterTest() { addTests({&AnyImageConverterTest::convert1D, &AnyImageConverterTest::convert2D, @@ -170,15 +201,22 @@ AnyImageConverterTest::AnyImageConverterTest() { addInstancedTests({&AnyImageConverterTest::detect2D}, Containers::arraySize(Detect2DData)); - addTests({&AnyImageConverterTest::detect3D, - &AnyImageConverterTest::detectCompressed1D, + addInstancedTests({&AnyImageConverterTest::detect3D}, + Containers::arraySize(Detect3DData)); + + addTests({&AnyImageConverterTest::detectCompressed1D, &AnyImageConverterTest::detectCompressed2D, &AnyImageConverterTest::detectCompressed3D, - &AnyImageConverterTest::detectLevels1D, - &AnyImageConverterTest::detectLevels2D, - &AnyImageConverterTest::detectLevels3D, - &AnyImageConverterTest::detectCompressedLevels1D, + &AnyImageConverterTest::detectLevels1D}); + + addInstancedTests({&AnyImageConverterTest::detectLevels2D}, + Containers::arraySize(DetectLevels2DData)); + + addInstancedTests({&AnyImageConverterTest::detectLevels3D}, + Containers::arraySize(DetectLevels3DData)); + + addTests({&AnyImageConverterTest::detectCompressedLevels1D, &AnyImageConverterTest::detectCompressedLevels2D, &AnyImageConverterTest::detectCompressedLevels3D, @@ -260,9 +298,25 @@ constexpr const char Data[] = { 1, 2, 3, 2, 3, 4, 0, 0 }; +constexpr Float CubeData[] = { + 0.125f, + 0.250f, + 0.375f, + 0.500f, + 0.625f, + 0.750f +}; + +constexpr Float FloatData[] = { + 0.125f, 0.250f, 0.375f, + 0.500f, 0.625f, 0.750f +}; + const ImageView1D Image1D{PixelFormat::RGB8Unorm, 2, Data}; const ImageView2D Image2D{PixelFormat::RGB8Unorm, {2, 3}, Data}; +const ImageView2D Image2DFloat{PixelFormat::Depth32F, {3, 2}, FloatData}; const ImageView3D Image3D{PixelFormat::RGB8Unorm, {2, 3, 2}, Data}; +const ImageView3D ImageCube{PixelFormat::Depth32F, {1, 1, 6}, CubeData}; const CompressedImageView1D CompressedImage1D{CompressedPixelFormat::Bc1RGBAUnorm, 3, Data}; const CompressedImageView2D CompressedImage2D{CompressedPixelFormat::Bc1RGBAUnorm, {1, 3}, Data}; const CompressedImageView3D CompressedImage3D{CompressedPixelFormat::Bc1RGBAUnorm, {1, 1, 3}, Data}; @@ -287,7 +341,25 @@ void AnyImageConverterTest::convert2D() { } void AnyImageConverterTest::convert3D() { - CORRADE_SKIP("No file formats to store 3D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "cube.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + /* Well, this is in fact the same as propagateConfiguration3D() but we + can't really do much else. */ + converter->configuration().setValue("envmap", "cube"); + CORRADE_VERIFY(converter->convertToFile(ImageCube, filename)); + CORRADE_VERIFY(Utility::Directory::exists(filename)); } void AnyImageConverterTest::convertCompressed1D() { @@ -307,11 +379,49 @@ void AnyImageConverterTest::convertLevels1D() { } void AnyImageConverterTest::convertLevels2D() { - CORRADE_SKIP("No file formats to store multi-level 2D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "output.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + /* Just test that the exported file exists */ + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. */ + CORRADE_VERIFY(converter->convertToFile({Image2DFloat}, filename)); + CORRADE_VERIFY(Utility::Directory::exists(filename)); } void AnyImageConverterTest::convertLevels3D() { - CORRADE_SKIP("No file formats to store multi-level 3D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "cube.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + /* Well, this is in fact the same as propagateConfigurationLevels3D() but + we can't really do much else. */ + converter->configuration().setValue("envmap", "cube"); + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. */ + CORRADE_VERIFY(converter->convertToFile({ImageCube}, filename)); + CORRADE_VERIFY(Utility::Directory::exists(filename)); } void AnyImageConverterTest::convertCompressedLevels1D() { @@ -352,7 +462,25 @@ void AnyImageConverterTest::detect2D() { } void AnyImageConverterTest::detect3D() { - CORRADE_SKIP("No file formats to store 3D data yet."); + auto&& data = Detect3DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer converter = _manager.instantiate("AnyImageConverter"); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertToFile(Image2D, data.filename)); + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnyImageConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); + #else + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnyImageConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); + #endif } void AnyImageConverterTest::detectCompressed1D() { @@ -372,11 +500,53 @@ void AnyImageConverterTest::detectLevels1D() { } void AnyImageConverterTest::detectLevels2D() { - CORRADE_SKIP("No file formats to store multi-level 2D data yet."); + auto&& data = DetectLevels2DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer converter = _manager.instantiate("AnyImageConverter"); + + std::ostringstream out; + Error redirectError{&out}; + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. */ + CORRADE_VERIFY(!converter->convertToFile({Image2D}, data.filename)); + /* Can't use raw string literals in macros on GCC 4.8 */ + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnyImageConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); + #else + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnyImageConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); + #endif } void AnyImageConverterTest::detectLevels3D() { - CORRADE_SKIP("No file formats to store multi-level 3D data yet."); + auto&& data = DetectLevels3DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer converter = _manager.instantiate("AnyImageConverter"); + + std::ostringstream out; + Error redirectError{&out}; + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. */ + CORRADE_VERIFY(!converter->convertToFile({Image3D}, data.filename)); + /* Can't use raw string literals in macros on GCC 4.8 */ + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnyImageConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); + #else + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnyImageConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); + #endif } void AnyImageConverterTest::detectCompressedLevels1D() { @@ -527,7 +697,43 @@ void AnyImageConverterTest::propagateFlags2D() { } void AnyImageConverterTest::propagateFlags3D() { - CORRADE_SKIP("No file formats to store 3D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "cube.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("envmap", "cube"); + /* This will make the verbose output print the detected hardware thread + count, but also the info about updating global thread count for the + first time. Thus run it once w/o a verbose flag and then again with to + filter out the other message. */ + /** @todo switch to testing something else when there is */ + converter->configuration().setValue("threads", 0); + CORRADE_VERIFY(converter->convertToFile(ImageCube, filename)); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + converter->setFlags(ImageConverterFlag::Verbose); + std::ostringstream out; + { + Debug redirectOutput{&out}; + CORRADE_VERIFY(converter->convertToFile(ImageCube, filename)); + } + CORRADE_VERIFY(Utility::Directory::exists(filename)); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::AnyImageConverter::convertToFile(): using OpenExrImageConverter\n" + "Trade::OpenExrImageConverter::convertToData(): autodetected hardware concurrency to {} threads\n", + std::thread::hardware_concurrency())); } void AnyImageConverterTest::propagateFlagsCompressed1D() { @@ -547,11 +753,86 @@ void AnyImageConverterTest::propagateFlagsLevels1D() { } void AnyImageConverterTest::propagateFlagsLevels2D() { - CORRADE_SKIP("No file formats to store multi-level 2D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "output.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + /* This will make the verbose output print the detected hardware thread + count, but also the info about updating global thread count for the + first time. Thus run it once w/o a verbose flag and then again with to + filter out the other message. */ + /** @todo switch to testing something else when there is */ + converter->configuration().setValue("threads", 0); + CORRADE_VERIFY(converter->convertToFile({Image2DFloat}, filename)); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + converter->setFlags(ImageConverterFlag::Verbose); + std::ostringstream out; + { + Debug redirectOutput{&out}; + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. */ + CORRADE_VERIFY(converter->convertToFile({Image2DFloat}, filename)); + } + CORRADE_VERIFY(Utility::Directory::exists(filename)); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::AnyImageConverter::convertToFile(): using OpenExrImageConverter\n" + "Trade::OpenExrImageConverter::convertToData(): autodetected hardware concurrency to {} threads\n", + std::thread::hardware_concurrency())); } void AnyImageConverterTest::propagateFlagsLevels3D() { - CORRADE_SKIP("No file formats to store multi-level 3D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "cube.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("envmap", "cube"); + /* This will make the verbose output print the detected hardware thread + count, but also the info about updating global thread count for the + first time. Thus run it once w/o a verbose flag and then again with to + filter out the other message. */ + /** @todo switch to testing something else when there is */ + converter->configuration().setValue("threads", 0); + CORRADE_VERIFY(converter->convertToFile({ImageCube}, filename)); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + converter->setFlags(ImageConverterFlag::Verbose); + std::ostringstream out; + { + Debug redirectOutput{&out}; + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. */ + CORRADE_VERIFY(converter->convertToFile({ImageCube}, filename)); + } + CORRADE_VERIFY(Utility::Directory::exists(filename)); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::AnyImageConverter::convertToFile(): using OpenExrImageConverter\n" + "Trade::OpenExrImageConverter::convertToData(): autodetected hardware concurrency to {} threads\n", + std::thread::hardware_concurrency())); } void AnyImageConverterTest::propagateFlagsCompressedLevels1D() { @@ -584,24 +865,38 @@ void AnyImageConverterTest::propagateConfiguration2D() { if(Utility::Directory::exists(filename)) CORRADE_VERIFY(Utility::Directory::rm(filename)); - const Float depth32fData[] = { - 0.125f, 0.250f, 0.375f, - 0.500f, 0.625f, 0.750f - }; - - const ImageView2D depth32f{PixelFormat::Depth32F, {3, 2}, depth32fData}; - Containers::Pointer converter = manager.instantiate("AnyImageConverter"); converter->configuration().setValue("layer", "left"); converter->configuration().setValue("depth", "height"); - CORRADE_VERIFY(converter->convertToFile(depth32f, filename)); + CORRADE_VERIFY(converter->convertToFile(Image2DFloat, filename)); /* Compare to an expected output to ensure the custom channels names were used */ CORRADE_COMPARE_AS(filename, EXR_FILE, TestSuite::Compare::File); } void AnyImageConverterTest::propagateConfiguration3D() { - CORRADE_SKIP("No file formats to store 3D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "cube.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + /* This should be enough to test -- 3D images can be saved only if this + option is set */ + converter->configuration().setValue("envmap", "cube"); + CORRADE_VERIFY(converter->convertToFile(ImageCube, filename)); + /* Compare to an expected output to ensure we actually saved the file + including the metadata. This also doubles as a generator for the + cube.exr file that AnyImageImporterTest uses. */ + CORRADE_COMPARE_AS(filename, EXR_CUBE_FILE, TestSuite::Compare::File); } void AnyImageConverterTest::propagateConfigurationUnknown1D() { @@ -623,7 +918,23 @@ void AnyImageConverterTest::propagateConfigurationUnknown2D() { } void AnyImageConverterTest::propagateConfigurationUnknown3D() { - CORRADE_SKIP("No file formats to store 3D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + /* Just test that the exported file exists */ + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("envmap", "cube"); + converter->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertToFile(ImageCube, Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "output.exr"))); + CORRADE_COMPARE(out.str(), "Trade::AnyImageConverter::convertToFile(): option noSuchOption not recognized by OpenExrImageConverter\n"); } void AnyImageConverterTest::propagateConfigurationCompressed1D() { @@ -655,11 +966,57 @@ void AnyImageConverterTest::propagateConfigurationLevels1D() { } void AnyImageConverterTest::propagateConfigurationLevels2D() { - CORRADE_SKIP("No file formats to store multi-level 2D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "depth32f-custom-channels.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("layer", "left"); + converter->configuration().setValue("depth", "height"); + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. For + OpenExrImageConverter both single and list are the same code path so we + can reuse the same expected file. */ + CORRADE_VERIFY(converter->convertToFile({Image2DFloat}, filename)); + /* Compare to an expected output to ensure the custom channels names were + used */ + CORRADE_COMPARE_AS(filename, EXR_FILE, TestSuite::Compare::File); } void AnyImageConverterTest::propagateConfigurationLevels3D() { - CORRADE_SKIP("No file formats to store multi-level 3D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "cube.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + /* This should be enough to test -- 3D images can be saved only if this + option is set */ + converter->configuration().setValue("envmap", "cube"); + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. For + OpenExrImageConverter both single and list are the same code path so we + can reuse the same expected file. */ + CORRADE_VERIFY(converter->convertToFile({ImageCube}, filename)); + /* Compare to an expected output to ensure we actually saved the file */ + CORRADE_COMPARE_AS(filename, EXR_CUBE_FILE, TestSuite::Compare::File); } void AnyImageConverterTest::propagateConfigurationUnknownLevels1D() { @@ -667,11 +1024,50 @@ void AnyImageConverterTest::propagateConfigurationUnknownLevels1D() { } void AnyImageConverterTest::propagateConfigurationUnknownLevels2D() { - CORRADE_SKIP("No file formats to store multi-level 2D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "depth32f-custom-channels.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. */ + CORRADE_VERIFY(converter->convertToFile({Image2DFloat}, Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "output.exr"))); + CORRADE_COMPARE(out.str(), "Trade::AnyImageConverter::convertToFile(): option noSuchOption not recognized by OpenExrImageConverter\n"); } void AnyImageConverterTest::propagateConfigurationUnknownLevels3D() { - CORRADE_SKIP("No file formats to store multi-level 3D data yet."); + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + /* Just test that the exported file exists */ + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("envmap", "cube"); + converter->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + /* Using the list API even though there's just one image, which should + still trigger the correct code path for AnyImageConverter. */ + CORRADE_VERIFY(converter->convertToFile({ImageCube}, Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "output.exr"))); + CORRADE_COMPARE(out.str(), "Trade::AnyImageConverter::convertToFile(): option noSuchOption not recognized by OpenExrImageConverter\n"); } void AnyImageConverterTest::propagateConfigurationCompressedLevels1D() { diff --git a/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt index 1a8d49da1..7ae7bad96 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt @@ -23,12 +23,18 @@ # DEALINGS IN THE SOFTWARE. # +# Needed to test verbose output from OpenExrImageConverter (detected thread +# count), unfortunately no better option for testing flags right now +find_package(Threads REQUIRED) + if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(ANYIMAGECONVERTER_TEST_OUTPUT_DIR "write") set(EXR_FILE depth32f-custom-channels.exr) + set(EXR_CUBE_FILE cube.exr) else() set(ANYIMAGECONVERTER_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(EXR_FILE ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr) + set(EXR_CUBE_FILE ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/AnyImageImporter/Test/cube.exr) endif() # CMake before 3.8 has broken $ expressions for iOS (see @@ -49,8 +55,14 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(AnyImageConverterTest AnyImageConverterTest.cpp - LIBRARIES MagnumTrade + LIBRARIES + MagnumTrade + # Needed to test verbose output from OpenExrImageConverter (detected + # thread count), unfortunately no better option for testing flags right + # now + Threads::Threads FILES + ../../AnyImageImporter/Test/cube.exr ../../AnyImageImporter/Test/depth32f-custom-channels.exr) target_include_directories(AnyImageConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_ANYIMAGECONVERTER_BUILD_STATIC) diff --git a/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake b/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake index 4719ac532..b0460c3e1 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake @@ -27,6 +27,7 @@ #cmakedefine TGAIMAGECONVERTER_PLUGIN_FILENAME "${TGAIMAGECONVERTER_PLUGIN_FILENAME}" #define ANYIMAGECONVERTER_TEST_OUTPUT_DIR "${ANYIMAGECONVERTER_TEST_OUTPUT_DIR}" #define EXR_FILE "${EXR_FILE}" +#define EXR_CUBE_FILE "${EXR_CUBE_FILE}" #ifdef CORRADE_TARGET_WINDOWS #ifdef CORRADE_IS_DEBUG_BUILD diff --git a/src/MagnumPlugins/AnyImageImporter/Test/cube.exr b/src/MagnumPlugins/AnyImageImporter/Test/cube.exr new file mode 100644 index 000000000..4f3975883 Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/cube.exr differ