diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp index 2c555b3d0..66e09ea32 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp @@ -94,9 +94,31 @@ void AnyImageImporter::doOpenFile(const Containers::StringView filename) { plugin = "JpegImporter"_s; else if(normalizedExtension == ".jp2"_s) plugin = "Jpeg2000Importer"_s; - else if(normalizedExtension == ".ktx2"_s) + else if(normalizedExtension == ".ktx2"_s) { plugin = "KtxImporter"_s; - else if(normalizedExtension == ".mng"_s) + + /* KtxImporter delegates to BasisImporter in case the file is + Basis-compressed, so that's a good default choice. However, if it + isn't available, we should try delegating to BasisImporter instead, + so people that have just Basis-compressed KTX files don't need to + have KtxImporter as well. + + BasisImporter unfortunately can't handle non-Basis-compressed KTX + files, so in case people have just BasisImporter and not + KtxImporter, it'll fail, but with a clear message suggesting to use + KtxImporter. If neither BasisImporter would be available, it'd fail + too (complaining that KtxImporter isn't available), so the behavior + is roughly the same. + + Further discussion and reasoning here: + https://github.com/mosra/magnum-plugins/pull/112#discussion_r734976174 */ + if(manager()->loadState("KtxImporter"_s) == PluginManager::LoadState::NotFound && + manager()->loadState("BasisImporter"_s) != PluginManager::LoadState::NotFound) { + if(flags() & ImporterFlag::Verbose) + Debug{} << "Trade::AnyImageImporter::openFile(): KtxImporter not found, trying a fallback"; + plugin = "BasisImporter"_s; + } + } else if(normalizedExtension == ".mng"_s) plugin = "MngImporter"_s; else if(normalizedExtension == ".pbm"_s) plugin = "PbmImporter"_s; @@ -200,10 +222,19 @@ void AnyImageImporter::doOpenData(Containers::Array&& data, DataFlags) { else if(dataString.hasPrefix("\xff\xd8\xff"_s)) plugin = "JpegImporter"_s; /* https://github.khronos.org/KTX-Specification/#_identifier */ - else if(dataString.hasPrefix("\xabKTX 20\xbb\r\n\x1a\n"_s)) + else if(dataString.hasPrefix("\xabKTX 20\xbb\r\n\x1a\n"_s)) { plugin = "KtxImporter"_s; + + /* Same logic as in doOpenFile() for *.ktx2 files, see above for more + information */ + if(manager()->loadState("KtxImporter"_s) == PluginManager::LoadState::NotFound && + manager()->loadState("BasisImporter"_s) != PluginManager::LoadState::NotFound) { + if(flags() & ImporterFlag::Verbose) + Debug{} << "Trade::AnyImageImporter::openData(): KtxImporter not found, trying a fallback"; + plugin = "BasisImporter"_s; + } /* https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header */ - else if(dataString.hasPrefix("\x89PNG\x0d\x0a\x1a\x0a"_s)) + } else if(dataString.hasPrefix("\x89PNG\x0d\x0a\x1a\x0a"_s)) plugin = "PngImporter"_s; /* http://paulbourke.net/dataformats/tiff/, http://paulbourke.net/dataformats/tiff/tiff_summary.pdf */ diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h index 7917facc4..4d5f67cbe 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h @@ -79,7 +79,8 @@ Supported formats: - JPEG 2000 (`*.jp2`), loaded with any plugin that provides `Jpeg2000Importer` - KTX2 (`*.ktx2` or data with corresponding signature), loaded with - @ref KtxImporter or any other plugin that provides it + @ref KtxImporter or any other plugin that provides it. If not found, + @ref BasisImporter is tried as a fallback. - Multiple-image Network Graphics (`*.mng`), loaded with any plugin that provides `MngImporter` - Portable Bitmap (`*.pbm`), loaded with any plugin that provides `PbmImporter` diff --git a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp index 112e583ee..9409d6faa 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp +++ b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,9 @@ struct AnyImageImporterTest: TestSuite::Tester { void load3D(); void detect(); + void ktxBasisFallbackFile(); + void ktxBasisFallbackData(); + void unknownExtension(); void unknownSignature(); void emptyData(); @@ -129,6 +133,31 @@ constexpr struct { /* Not testing everything, just the most important ones */ }; +const struct { + const char* name; + bool ktxImporterPresent; + bool basisImporterPresent; + bool verbose; + const char* expectedMessage; +} KtxBasisFallbackData[]{ + {"both KtxImporter and BasisImporter present", true, true, true, + "Trade::AnyImageImporter::{}(): using KtxImporter\n"}, + {"only KtxImporter present", true, false, true, + "Trade::AnyImageImporter::{}(): using KtxImporter\n"}, + {"only BasisImporter present", false, true, true, + "Trade::AnyImageImporter::{0}(): KtxImporter not found, trying a fallback\n" + "Trade::AnyImageImporter::{0}(): using BasisImporter\n"}, + {"only BasisImporter present, verbose output disabled", false, true, false, + nullptr}, + {"neither present", false, false, true, + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + "PluginManager::Manager::load(): plugin KtxImporter is not static and was not found in nonexistent\n" + #else + "PluginManager::Manager::load(): plugin KtxImporter was not found\n" + #endif + "Trade::AnyImageImporter::{}(): cannot load the KtxImporter plugin"} +}; + using namespace Containers::Literals; const struct { @@ -173,6 +202,10 @@ AnyImageImporterTest::AnyImageImporterTest() { addInstancedTests({&AnyImageImporterTest::detect}, Containers::arraySize(DetectData)); + addInstancedTests({&AnyImageImporterTest::ktxBasisFallbackFile, + &AnyImageImporterTest::ktxBasisFallbackData}, + Containers::arraySize(KtxBasisFallbackData)); + addTests({&AnyImageImporterTest::unknownExtension}); addInstancedTests({&AnyImageImporterTest::unknownSignature}, @@ -310,6 +343,85 @@ void AnyImageImporterTest::detect() { #endif } +void AnyImageImporterTest::ktxBasisFallbackFile() { + auto&& data = KtxBasisFallbackData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(data.ktxImporterPresent && !(manager.load("KtxImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("KtxImporter plugin can't be loaded."); + if(data.basisImporterPresent && !(manager.load("BasisImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("BasisImporter plugin can't be loaded."); + + /* Set invalid plugin directory to ensure the remaining plugins don't get + loaded after this point */ + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + manager.setPluginDirectory("nonexistent"); + #endif + + Containers::Pointer importer = manager.instantiate("AnyImageImporter"); + if(data.verbose) + importer->setFlags(ImporterFlag::Verbose); + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* We don't care if the file opens (it won't if BasisImporter isn't + present), just verifying if correct plugin got picked by checking the + message. */ + importer->openFile(Utility::Path::join(ANYIMAGEIMPORTER_TEST_DIR, "basis.ktx2")); + if(data.expectedMessage) CORRADE_COMPARE_AS(out.str(), + Utility::formatString(data.expectedMessage, "openFile"), + TestSuite::Compare::StringHasPrefix); + else CORRADE_COMPARE(out.str(), ""); +} + +void AnyImageImporterTest::ktxBasisFallbackData() { + auto&& data = KtxBasisFallbackData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(data.ktxImporterPresent && !(manager.load("KtxImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("KtxImporter plugin can't be loaded."); + if(data.basisImporterPresent && !(manager.load("BasisImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("BasisImporter plugin can't be loaded."); + + /* Set invalid plugin directory to ensure the remaining plugins don't get + loaded after this point */ + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + manager.setPluginDirectory("nonexistent"); + #endif + + Containers::Pointer importer = manager.instantiate("AnyImageImporter"); + if(data.verbose) + importer->setFlags(ImporterFlag::Verbose); + + Containers::Optional> read = Utility::Path::read(Utility::Path::join(ANYIMAGEIMPORTER_TEST_DIR, "basis.ktx2")); + CORRADE_VERIFY(read); + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* We don't care if the file opens (it won't if BasisImporter isn't + present), just verifying if correct plugin got picked by checking the + message. */ + importer->openData(*read); + if(data.expectedMessage) CORRADE_COMPARE_AS(out.str(), + Utility::formatString(data.expectedMessage, "openData"), + TestSuite::Compare::StringHasPrefix); + else CORRADE_COMPARE(out.str(), ""); +} + void AnyImageImporterTest::unknownExtension() { Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); diff --git a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt index f6599c548..a5aa3a79e 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt @@ -56,6 +56,8 @@ corrade_add_test(AnyImageImporterTest AnyImageImporterTest.cpp # Generated by AnyImageConverterTest::convert{1D,3D}() 1d.ktx2 3d.ktx2 + # rgb.ktx2 from BasisImporter test data (in magnum-plugins), renamed + basis.ktx2 gray.jpg image.exr image.tiff diff --git a/src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 b/src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 new file mode 100644 index 000000000..41cc6ed21 Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 differ