diff --git a/doc/changelog.dox b/doc/changelog.dox index 2977d1c51..8d0fb2062 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -229,6 +229,9 @@ See also: correctly handle all corner cases yet and may assert on certain inputs. - The @ref magnum-sceneconverter "magnum-sceneconverter" `--info` output is now more compact and colored for better readability +- Added `--info-importer`, `--info-converter` and `--info-image-converter` + options to @ref magnum-sceneconverter "magnum-sceneconverter", listing + plugin features and configuration file contents @subsubsection changelog-latest-new-shaders Shaders library @@ -321,6 +324,9 @@ See also: converters together - The @ref magnum-imageconverter "magnum-imageconverter" `--info` output is now more compact and colored for better readability +- Added `--info-importer` and `--info-converter` options to + @ref magnum-imageconverter "magnum-imageconverter", listing plugin features + and configuration file contents @subsubsection changelog-latest-new-vk Vk library diff --git a/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h index 24c827365..330c23b11 100644 --- a/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h +++ b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h @@ -33,6 +33,7 @@ #include #include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Trade/AbstractSceneConverter.h" #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/LightData.h" @@ -52,6 +53,11 @@ using namespace Containers::Literals; particular magnum-sceneconverter and its tests */ namespace { +void printSceneConverterInfo(const Debug::Flags useColor, const Trade::AbstractSceneConverter& converter) { + Trade::Implementation::printPluginInfo(useColor, converter); + Trade::Implementation::printPluginConfigurationInfo(useColor, converter); +} + /** @todo const Array& doesn't work, minmax() would fail to match */ template Containers::String calculateBounds(Containers::Array&& attribute) { /** @todo clean up when Debug::toString() exists */ diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index 208885c27..fa9b3a283 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -40,6 +40,9 @@ endif() if(MAGNUM_WITH_SCENECONVERTER AND CORRADE_TARGET_UNIX) set(SCENECONVERTER_EXECUTABLE_FILENAME $) endif() +if(MAGNUM_WITH_ANYSCENECONVERTER AND NOT MAGNUM_BUILD_PLUGINS_STATIC AND NOT MAGNUM_ANYSCENECONVERTER_BUILD_STATIC) + set(ANYSCENECONVERTER_PLUGIN_FILENAME $) +endif() # First replace ${} variables, then $<> generator expressions configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake @@ -69,6 +72,14 @@ corrade_add_test(SceneToolsSceneConverterImple___Test SceneConverterImplementati SceneConverterImplementationTestFiles/info-skins.txt SceneConverterImplementationTestFiles/info-textures.txt) target_include_directories(SceneToolsSceneConverterImple___Test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) +if(MAGNUM_WITH_ANYSCENECONVERTER) + if(MAGNUM_BUILD_PLUGINS_STATIC OR MAGNUM_ANYSCENECONVERTER_BUILD_STATIC) + target_link_libraries(SceneToolsSceneConverterImple___Test PRIVATE AnySceneConverter) + else() + # So the plugins get properly built when building the test + add_dependencies(SceneToolsSceneConverterImple___Test AnySceneConverter) + endif() +endif() # Executable testing is implemented on Unix platforms only at the moment if(CORRADE_TARGET_UNIX AND NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) @@ -97,6 +108,10 @@ if(CORRADE_TARGET_UNIX AND NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) SceneConverterTestFiles/images-3d-1x1x1.gltf SceneConverterTestFiles/info-data.txt SceneConverterTestFiles/info-data-ignored-output.txt + SceneConverterTestFiles/info-converter.txt + SceneConverterTestFiles/info-image-converter.txt + SceneConverterTestFiles/info-importer.txt + SceneConverterTestFiles/info-importer-ignored-input-output.txt SceneConverterTestFiles/point.obj SceneConverterTestFiles/quad-duplicates-fuzzy.obj SceneConverterTestFiles/quad-duplicates.obj diff --git a/src/Magnum/SceneTools/Test/SceneConverterImplementationTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterImplementationTest.cpp index cbc2b36c5..c529187ac 100644 --- a/src/Magnum/SceneTools/Test/SceneConverterImplementationTest.cpp +++ b/src/Magnum/SceneTools/Test/SceneConverterImplementationTest.cpp @@ -44,6 +44,11 @@ namespace Magnum { namespace SceneTools { namespace Test { namespace { struct SceneConverterImplementationTest: TestSuite::Tester { explicit SceneConverterImplementationTest(); + /* printPluginInfo(), printPluginConfigurationInfo() and + printImporterInfo() tested thoroughly in + Trade/Test/ImageConverterImplementationTest.cpp already */ + void converterInfo(); + void infoEmpty(); void infoScenesObjects(); void infoAnimations(); @@ -60,6 +65,9 @@ struct SceneConverterImplementationTest: TestSuite::Tester { void infoError(); Utility::Arguments _infoArgs; + + /* Explicitly forbid system-wide plugin dependencies */ + PluginManager::Manager _converterManager{"nonexistent"}; }; using namespace Containers::Literals; @@ -86,7 +94,9 @@ const struct { }; SceneConverterImplementationTest::SceneConverterImplementationTest() { - addTests({&SceneConverterImplementationTest::infoEmpty}); + addTests({&SceneConverterImplementationTest::converterInfo, + + &SceneConverterImplementationTest::infoEmpty}); addInstancedTests({&SceneConverterImplementationTest::infoScenesObjects}, Containers::arraySize(InfoScenesObjectsData)); @@ -121,6 +131,47 @@ SceneConverterImplementationTest::SceneConverterImplementationTest() { .addBooleanOption("info-textures") .addBooleanOption("info-images") .addBooleanOption("bounds"); + + /* Load the plugin directly from the build tree. Otherwise it's static and + already loaded. */ + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_converterManager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* To avoid warnings that printImageConverterInfo() / printImporterInfo() + is unused. Again, those are tested in ImageConverterImplementationTest + already. */ + static_cast(Trade::Implementation::printImageConverterInfo); + static_cast(Trade::Implementation::printImporterInfo); +} + +void SceneConverterImplementationTest::converterInfo() { + /* Check if the required plugin can be loaded. Catches also ABI and + interface mismatch errors. */ + if(!(_converterManager.load("AnySceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnySceneConverter plugin can't be loaded."); + + Containers::Pointer converter = _converterManager.instantiate("AnySceneConverter"); + /** @todo pick a plugin that has some actual configuration */ + converter->configuration().setValue("something", "is there"); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printSceneConverterInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, *converter); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printSceneConverterInfo(Debug::Flag::DisableColors, *converter); + CORRADE_COMPARE(out.str(), + "Plugin name: AnySceneConverter\n" + "Features:\n" + " ConvertMeshToFile\n" + " ConvertMultipleToFile\n" + "Configuration:\n" + " something=is there\n"); } void SceneConverterImplementationTest::infoEmpty() { diff --git a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp index 676dca78e..3448621d1 100644 --- a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp +++ b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp @@ -57,21 +57,47 @@ const struct { const char* name; Containers::Array args; const char* requiresImporter; + const char* requiresConverter; + const char* requiresImageConverter; const char* expected; } InfoData[]{ + {"importer", Containers::array({ + "--info-importer", "-i", "someOption=yes"}), + "AnySceneImporter", nullptr, nullptr, + "info-importer.txt"}, + {"converter", Containers::array({ + "-C", "AnySceneConverter", "--info-converter", "-c", "someOption=yes"}), + nullptr, "AnySceneConverter", nullptr, + "info-converter.txt"}, + {"converter, implicit", Containers::array({ + "--info-converter", "-c", "someOption=yes"}), + nullptr, "AnySceneConverter", nullptr, + "info-converter.txt"}, + {"image converter", Containers::array({ + "-P", "AnyImageConverter", "--info-image-converter", "-p", "someOption=yes"}), + nullptr, nullptr, "AnyImageConverter", + "info-image-converter.txt"}, + {"image converter, implicit", Containers::array({ + "--info-image-converter", "-p", "someOption=yes"}), + nullptr, nullptr, "AnyImageConverter", + "info-image-converter.txt"}, + {"importer, ignored input and output", Containers::array({ + "--info-importer", "input.obj", "output.ply"}), + "AnySceneImporter", nullptr, nullptr, + "info-importer-ignored-input-output.txt"}, {"data", Containers::array({ "-I", "ObjImporter", "--info", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}), - "ObjImporter", + "ObjImporter", nullptr, nullptr, "info-data.txt"}, {"data, map", Containers::array({ "--map", "-I", "ObjImporter", "--info", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}), - "ObjImporter", + "ObjImporter", nullptr, nullptr, /** @todo change to something else once we have a plugin that can zero-copy pass the imported data */ "info-data.txt"}, {"data, ignored output file", Containers::array({ "-I", "ObjImporter", "--info", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), "whatever.ply"}), - "ObjImporter", + "ObjImporter", nullptr, nullptr, "info-data-ignored-output.txt"} }; @@ -783,8 +809,14 @@ void SceneConverterTest::info() { /* Check if required plugins can be loaded. Catches also ABI and interface mismatch errors. */ PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + PluginManager::Manager imageConverterManager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + PluginManager::Manager converterManager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; if(data.requiresImporter && !(importerManager.load(data.requiresImporter) & PluginManager::LoadState::Loaded)) CORRADE_SKIP(data.requiresImporter << "plugin can't be loaded."); + if(data.requiresConverter && !(converterManager.load(data.requiresConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresConverter << "plugin can't be loaded."); + if(data.requiresImageConverter && !(imageConverterManager.load(data.requiresImageConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresImageConverter << "plugin can't be loaded."); CORRADE_VERIFY(true); /* capture correct function name */ diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-converter.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-converter.txt new file mode 100644 index 000000000..813a4b257 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-converter.txt @@ -0,0 +1,6 @@ +Plugin name: AnySceneConverter +Features: + ConvertMeshToFile + ConvertMultipleToFile +Configuration: + someOption=yes diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-image-converter.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-image-converter.txt new file mode 100644 index 000000000..3ab9b48e2 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-image-converter.txt @@ -0,0 +1,11 @@ +Plugin name: AnyImageConverter +Features: + Convert1DToFile + Convert2DToFile + Convert3DToFile + ConvertCompressed1DToFile + ConvertCompressed2DToFile + ConvertCompressed3DToFile + Levels +Configuration: + someOption=yes diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer-ignored-input-output.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer-ignored-input-output.txt new file mode 100644 index 000000000..ea9e95c20 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer-ignored-input-output.txt @@ -0,0 +1,5 @@ +Ignoring input file for --info: input.obj +Ignoring output file for --info: output.ply +Plugin name: AnySceneImporter +Features: + FileCallback diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer.txt new file mode 100644 index 000000000..ebbddd1bb --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer.txt @@ -0,0 +1,5 @@ +Plugin name: AnySceneImporter +Features: + FileCallback +Configuration: + someOption=yes diff --git a/src/Magnum/SceneTools/Test/configure.h.cmake b/src/Magnum/SceneTools/Test/configure.h.cmake index 76936f87d..b2cd664c5 100644 --- a/src/Magnum/SceneTools/Test/configure.h.cmake +++ b/src/Magnum/SceneTools/Test/configure.h.cmake @@ -26,6 +26,7 @@ #define SCENETOOLS_TEST_DIR "${SCENETOOLS_TEST_DIR}" #define SCENETOOLS_TEST_OUTPUT_DIR "${SCENETOOLS_TEST_OUTPUT_DIR}" #cmakedefine SCENECONVERTER_EXECUTABLE_FILENAME "${SCENECONVERTER_EXECUTABLE_FILENAME}" +#cmakedefine ANYSCENECONVERTER_PLUGIN_FILENAME "${ANYSCENECONVERTER_PLUGIN_FILENAME}" #ifdef CORRADE_TARGET_WINDOWS #ifdef CORRADE_IS_DEBUG_BUILD diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index cb67576e4..d585f42ed 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -126,7 +126,8 @@ magnum-sceneconverter [-h|--help] [-I|--importer PLUGIN] [-c|--converter-options key=val,key2=val2,…]... [-p|--image-converter-options key=val,key2=val2,…]... [-m|--mesh-converter-options key=val,key2=val2,…]... - [--mesh ID] [--mesh-level INDEX] [--concatenate-meshes] [--info-animations] + [--mesh ID] [--mesh-level INDEX] [--concatenate-meshes] [--info-importer] + [--info-converter] [--info-image-converter] [--info-animations] [--info-images] [--info-lights] [--info-cameras] [--info-materials] [--info-meshes] [--info-objects] [--info-scenes] [--info-skins] [--info-textures] [--info] [--color on|4bit|off|auto] [--bounds] @@ -169,6 +170,11 @@ Arguments: - `--mesh-level LEVEL` --- level to select for single-mesh conversion - `--concatenate-meshes` -- flatten mesh hierarchy and concatenate them all together @m_class{m-label m-warning} **experimental** +- `--info-importer` --- print info about the importer plugin and exit +- `--info-converter` --- print info about the scene or mesh converter plugin + and exit +- `--info-image-converter` --- print info about the image converter plugin + and exit - `--info-animations` --- print into about animations in the input file and exit - `--info-images` --- print into about images in the input file and exit @@ -182,18 +188,25 @@ Arguments: - `--info-skins` --- print into about skins in the input file and exit - `--info-textures` --- print into about textures in the input file and exit - `--info` --- print info about everything in the input file and exit, same - as specifying all other `--info-*` options together + as specifying all other data-related `--info-*` options together - `--color` --- colored output for `--info` (default: `auto`) - `--bounds` --- show bounds of known attributes in `--info` output - `-v`, `--verbose` --- verbose output from importer and converter plugins - `--profile` --- measure import and conversion time -If any of the `--info-*` options are given, the utility will print information -about given data present in the file. In this case no conversion is done and -output file doesn't need to be specified. In case one data references another -and both `--info-*` options are specified, the output will also list reference -count (for example, `--info-scenes` together with `--info-meshes` will print -how many objects reference given mesh). +If any of the `--info-importer`, `--info-converter` or `--info-image-converter` +options are given, the utility will print information about given plugin +specified via the `-I`, `-C` or `-P` option, including its configuration +options potentially overriden with `-i`, `-c` or `-p`. In this case no file is +read and no conversion is done and neither the input nor the output file needs +to be specified. + +If any of the other `--info-*` options are given, the utility will print +information about given data. In this case the input file is read but no +conversion is done and the output file doesn't need to be specified. In case +one data references another and both `--info-*` options are specified, the +output will also list reference count (for example, `--info-scenes` together +with `--info-meshes` will print how many objects reference given mesh). The `-i`, `-c` and `-m` arguments accept a comma-separated list of key/value pairs to set in the importer / converter plugin configuration. If the `=` @@ -239,7 +252,13 @@ using namespace Containers::Literals; namespace { -bool isInfoRequested(const Utility::Arguments& args) { +bool isPluginInfoRequested(const Utility::Arguments& args) { + return args.isSet("info-importer") || + args.isSet("info-converter") || + args.isSet("info-image-converter"); +} + +bool isDataInfoRequested(const Utility::Arguments& args) { return args.isSet("info-animations") || args.isSet("info-images") || args.isSet("info-lights") || @@ -333,6 +352,9 @@ int main(int argc, char** argv) { .addOption("mesh").setHelp("mesh", "convert just a single mesh instead of the whole scene, ignored if --concatenate-meshes is specified", "ID") .addOption("mesh-level").setHelp("mesh-level", "level to select for single-mesh conversion", "index") .addBooleanOption("concatenate-meshes").setHelp("concatenate-meshes", "flatten mesh hierarchy and concatenate them all together") + .addBooleanOption("info-importer").setHelp("info-importer", "print info about the importer plugin and exit") + .addBooleanOption("info-converter").setHelp("info-converter", "print info about the scene or mesh converter plugin and exit") + .addBooleanOption("info-image-converter").setHelp("info-image-converter", "print info about the image converter plugin and exit") .addBooleanOption("info-animations").setHelp("info-animations", "print info about animations in the input file and exit") .addBooleanOption("info-images").setHelp("info-images", "print info about images in the input file and exit") .addBooleanOption("info-lights").setHelp("info-lights", "print info about images in the input file and exit") @@ -343,27 +365,39 @@ int main(int argc, char** argv) { .addBooleanOption("info-scenes").setHelp("info-scenes", "print info about scenes in the input file and exit") .addBooleanOption("info-skins").setHelp("info-skins", "print info about skins in the input file and exit") .addBooleanOption("info-textures").setHelp("info-textures", "print info about textures in the input file and exit") - .addBooleanOption("info").setHelp("info", "print info about everything in the input file and exit, same as specifying all other --info-* options together") + .addBooleanOption("info").setHelp("info", "print info about everything in the input file and exit, same as specifying all other data-related --info-* options together") .addOption("color", "auto").setHelp("color", "colored output for --info", "on|4bit|off|auto") .addBooleanOption("bounds").setHelp("bounds", "show bounds of known attributes in --info output") .addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from importer and converter plugins") .addBooleanOption("profile").setHelp("profile", "measure import and conversion time") .setParseErrorCallback([](const Utility::Arguments& args, Utility::Arguments::ParseError error, const std::string& key) { - /* If --info is passed, we don't need the output argument */ + /* If --info for plugins is passed, we don't need the input */ + if(error == Utility::Arguments::ParseError::MissingArgument && + key == "input" && isPluginInfoRequested(args)) + return true; + /* If --info for plugins or data is passed, we don't need the + output argument */ if(error == Utility::Arguments::ParseError::MissingArgument && - key == "output" && isInfoRequested(args)) return true; + key == "output" && (isPluginInfoRequested(args) || isDataInfoRequested(args))) + return true; /* Handle all other errors as usual */ return false; }) .setGlobalHelp(R"(Converts scenes of different formats. -If any of the --info-* options are given, the utility will print information -about given data present in the file. In this case no conversion is done and -output file doesn't need to be specified. In case one data references another -and both --info-* options are specified, the output will also list reference -count (for example, --info-scenes together with --info-meshes will print how -many objects reference given mesh). +If any of the --info-importer, --info-converter or --info-image-converter +options are given, the utility will print information about given plugin +specified via the -I, -C or -P option. In this case no file is read and no +conversion is done and neither the input nor the output file needs to be +specified. + +If any of the other --info-* options are given, the utility will print +information about given data. In this case the input file is read but no +conversion is done and the output file doesn't need to be specified. In case +one data references another and both --info-* options are specified, the output +will also list reference count (for example, --info-scenes together with +--info-meshes will print how many objects reference given mesh). The -i, -c and -m arguments accept a comma-separated list of key/value pairs to set in the importer / converter plugin configuration. If the = @@ -421,11 +455,17 @@ well, the IDs reference attributes of the first mesh.)") } /* Generic checks */ - if(args.value("output")) { + if(args.value("input")) { /* Not an error in this case, it should be possible to just append --info* to existing command line without having to remove anything. But print a warning at least, it could also be a mistyped option. */ - if(isInfoRequested(args)) + if(isPluginInfoRequested(args)) + Warning{} << "Ignoring input file for --info:" << args.value("input"); + } + if(args.value("output")) { + /* Same as above, it should be possible to just append --info* to + existing command line */ + if(isPluginInfoRequested(args) || isDataInfoRequested(args)) Warning{} << "Ignoring output file for --info:" << args.value("output"); } if(args.isSet("concatenate-meshes") && args.value("mesh")) { @@ -471,6 +511,40 @@ well, the IDs reference attributes of the first mesh.)") if(args.isSet("verbose")) importer->addFlags(Trade::ImporterFlag::Verbose); Implementation::setOptions(*importer, "AnySceneImporter", args.value("importer-options")); + /* Print plugin info, if requested */ + if(args.isSet("info-importer")) { + Trade::Implementation::printImporterInfo(useColor, *importer); + return 0; + } + if(args.isSet("info-converter")) { + Containers::Pointer converter = converterManager.loadAndInstantiate(args.arrayValueCount("converter") ? args.arrayValue("converter", 0) : "AnySceneConverter"); + if(!converter) { + Debug{} << "Available converter plugins:" << ", "_s.join(converterManager.aliasList()); + return 1; + } + + /* Set options, if passed */ + if(args.isSet("verbose")) converter->addFlags(Trade::SceneConverterFlag::Verbose); + if(args.arrayValueCount("converter-options")) + Implementation::setOptions(*converter, "AnySceneConverter", args.arrayValue("converter-options", 0)); + SceneTools::Implementation::printSceneConverterInfo(useColor, *converter); + return 0; + } + if(args.isSet("info-image-converter")) { + Containers::Pointer converter = imageConverterManager.loadAndInstantiate(args.arrayValueCount("image-converter") ? args.arrayValue("image-converter", 0) : "AnyImageConverter"); + if(!converter) { + Debug{} << "Available image converter plugins:" << ", "_s.join(imageConverterManager.aliasList()); + return 1; + } + + /* Set options, if passed */ + if(args.isSet("verbose")) converter->addFlags(Trade::ImageConverterFlag::Verbose); + if(args.arrayValueCount("image-converter-options")) + Implementation::setOptions(*converter, "AnyImageConverter", args.arrayValue("image-converter-options", 0)); + Trade::Implementation::printImageConverterInfo(useColor, *converter); + return 0; + } + /* Wow, C++, you suck. This implicitly initializes to random shit?! Also, because of addSupportedImporterContents() it's not really possible @@ -499,7 +573,7 @@ well, the IDs reference attributes of the first mesh.)") } /* Print file info, if requested */ - if(isInfoRequested(args)) { + if(isDataInfoRequested(args)) { const bool error = SceneTools::Implementation::printInfo(useColor, useColor24, args, *importer, importConversionTime); if(args.isSet("profile")) { diff --git a/src/Magnum/Trade/Implementation/converterUtilities.h b/src/Magnum/Trade/Implementation/converterUtilities.h index d6dbcf57d..587030592 100644 --- a/src/Magnum/Trade/Implementation/converterUtilities.h +++ b/src/Magnum/Trade/Implementation/converterUtilities.h @@ -26,13 +26,21 @@ */ #include +#include /** @todo remove when Debug is stream-free */ #include #include +#include +#include #include +#include #include +#include /** @todo drop once String::replaceAll() has a StringView overload */ +#include +#include #include "Magnum/PixelFormat.h" #include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/AbstractImageConverter.h" #include "Magnum/Trade/ImageData.h" namespace Magnum { namespace Trade { namespace Implementation { @@ -41,6 +49,101 @@ namespace Magnum { namespace Trade { namespace Implementation { particular magnum-imageconverter, magnum-sceneconverter and their tests */ namespace { +/** @todo move these to Magnum/Implementation/converterUtilities.h once also + shaderconverter / fontconverter / etc implements --info-converter etc */ +template void printPluginInfo(const Debug::Flags useColor, const T& plugin) { + const PluginManager::PluginMetadata* metadata = plugin.metadata(); + CORRADE_INTERNAL_ASSERT(metadata); + + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Plugin name:" << Debug::boldColor(Debug::Color::Yellow) << metadata->name() << Debug::resetColor; + const std::vector aliases = metadata->provides(); + if(!aliases.empty()) { + d << Debug::newline << Debug::boldColor(Debug::Color::Default) << "Aliases:" << Debug::resetColor; + for(const std::string& alias: aliases) { + d << Debug::newline << " "; + if(alias == plugin.plugin()) + d << Debug::color(Debug::Color::Yellow); + d << alias; + if(alias == plugin.plugin()) + d << Debug::resetColor; + } + } + + /* Ugly, eh? */ + std::ostringstream out; + Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Debug::packed << plugin.features(); + d << Debug::newline << Debug::boldColor(Debug::Color::Default) << "Features:" << Debug::color(Debug::Color::Cyan) << Debug::newline << " " << Utility::String::replaceAll(out.str(), "|", "\n ") << Debug::resetColor; +} + +void printPluginConfigurationInfo(Debug& d, const Utility::ConfigurationGroup& configuration, const Containers::StringView prefix) { + using namespace Containers::Literals; + + for(Containers::Pair i: configuration.valuesComments()) { + if(i.first()) { + d << Debug::newline << " " << Debug::boldColor(Debug::Color::Blue) << i.first() << Debug::nospace << Debug::color(Debug::Color::Blue) << "=" << Debug::nospace << Debug::color(Debug::Color::Red); + /* Print the value wrapped in quotes if it contains spaces, indent + also all newlines */ + if(i.second().contains('\n')) + /** @todo replaceAll() without std::string */ + d << Utility::format("\"\"\"\n {}\n \"\"\"", Utility::String::replaceAll(i.second(), "\n", "\n ")); + /** @todo less wasteful API for checking leading/trailing zeros? */ + else if(i.second().trimmed() != i.second()) + d << Utility::format("\"{}\"", i.second()); + else + d << i.second(); + d << Debug::resetColor; + } else { + /* Configuration contents are delimited by these markers in order + to include them in Doxygen-generated docs. If we reach them, + it's the end of the (public) configuration values. Don't print + them, don't print even the last newline, and exit. */ + if(i.second() == "# [configuration_]"_s) { + return; + } + + /* Print leading space only if there's actually something */ + d << Debug::newline; + if(i.second()) d << " " << Debug::boldColor(Debug::Color::Black) << i.second() << Debug::resetColor; + } + } + + for(Containers::Pair> i: configuration.groups()) { + const Containers::String name = prefix ? "/"_s.join({prefix, i.first()}) : Containers::String{i.first()}; + + d << Debug::newline << " " << Debug::color(Debug::Color::Blue) << "[" << Debug::nospace << Debug::boldColor(Debug::Color::Blue) << name << Debug::color(Debug::Color::Blue) << Debug::nospace << "]" << Debug::resetColor; + + printPluginConfigurationInfo(d, i.second(), name); + } +} + +void printPluginConfigurationInfo(const Debug::Flags useColor, const PluginManager::AbstractPlugin& plugin) { + const Utility::ConfigurationGroup& configuration = plugin.configuration(); + if(configuration.isEmpty()) return; + + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Configuration:" << Debug::resetColor; + printPluginConfigurationInfo(d, configuration, {}); +} + +void printImporterInfo(const Debug::Flags useColor, const Trade::AbstractImporter& importer) { + printPluginInfo(useColor, importer); + printPluginConfigurationInfo(useColor, importer); +} + +void printImageConverterInfo(const Debug::Flags useColor, const Trade::AbstractImageConverter& converter) { + printPluginInfo(useColor, converter); + + Debug d{useColor|Debug::Flag::NoNewlineAtTheEnd}; + + if(const Containers::String extension = converter.extension()) + d << Debug::boldColor(Debug::Color::Default) << "File extension:" << Debug::resetColor << extension << Debug::newline; + if(const Containers::String mimeType = converter.mimeType()) + d << Debug::boldColor(Debug::Color::Default) << "MIME type:" << Debug::resetColor << mimeType << Debug::newline; + + printPluginConfigurationInfo(useColor, converter); +} + struct Duration { explicit Duration(std::chrono::high_resolution_clock::duration& output): _output(output), _t{std::chrono::high_resolution_clock::now()} {} diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 9ee972963..8351cf12e 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -40,6 +40,17 @@ endif() if(MAGNUM_WITH_IMAGECONVERTER AND CORRADE_TARGET_UNIX) set(IMAGECONVERTER_EXECUTABLE_FILENAME $) endif() +if(NOT MAGNUM_BUILD_PLUGINS_STATIC) + if(MAGNUM_WITH_ANYIMAGEIMPORTER AND NOT MAGNUM_ANYIMAGEIMPORTER_BUILD_STATIC) + set(ANYIMAGEIMPORTER_PLUGIN_FILENAME $) + endif() + if(MAGNUM_WITH_ANYIMAGECONVERTER AND NOT MAGNUM_ANYIMAGECONVERTER_BUILD_STATIC) + set(ANYIMAGECONVERTER_PLUGIN_FILENAME $) + endif() + if(MAGNUM_WITH_TGAIMAGECONVERTER AND NOT MAGNUM_TGAIMAGECONVERTER_BUILD_STATIC) + set(TGAIMAGECONVERTER_PLUGIN_FILENAME $) + endif() +endif() # First replace ${} variables, then $<> generator expressions configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake @@ -69,6 +80,30 @@ corrade_add_test(TradeImageConverterImplementa___Test ImageConverterImplementati FILES ImageConverterImplementationTestFiles/info.txt) target_include_directories(TradeImageConverterImplementa___Test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) +if(MAGNUM_WITH_ANYIMAGEIMPORTER) + if(MAGNUM_BUILD_PLUGINS_STATIC OR MAGNUM_ANYIMAGEIMPORTER_BUILD_STATIC) + target_link_libraries(TradeImageConverterImplementa___Test PRIVATE AnyImageImporter) + else() + # So the plugins get properly built when building the test + add_dependencies(TradeImageConverterImplementa___Test AnyImageImporter) + endif() +endif() +if(MAGNUM_WITH_ANYIMAGECONVERTER) + if(MAGNUM_BUILD_PLUGINS_STATIC OR MAGNUM_ANYIMAGECONVERTER_BUILD_STATIC) + target_link_libraries(TradeImageConverterImplementa___Test PRIVATE AnyImageConverter) + else() + # So the plugins get properly built when building the test + add_dependencies(TradeImageConverterImplementa___Test AnyImageConverter) + endif() +endif() +if(MAGNUM_WITH_TGAIMAGECONVERTER) + if(MAGNUM_BUILD_PLUGINS_STATIC OR MAGNUM_TGAIMAGECONVERTER_BUILD_STATIC) + target_link_libraries(TradeImageConverterImplementa___Test PRIVATE TgaImageConverter) + else() + # So the plugins get properly built when building the test + add_dependencies(TradeImageConverterImplementa___Test TgaImageConverter) + endif() +endif() # Executable testing is implemented on Unix platforms only at the moment if(CORRADE_TARGET_UNIX AND NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) @@ -81,6 +116,9 @@ if(CORRADE_TARGET_UNIX AND NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) FILES ImageConverterTestFiles/info-data.txt ImageConverterTestFiles/info-data-ignored-output.txt + ImageConverterTestFiles/info-converter.txt + ImageConverterTestFiles/info-importer.txt + ImageConverterTestFiles/info-importer-ignored-input-output.txt ImageConverterTestFiles/file.tga) target_include_directories(TradeImageConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_WITH_IMAGECONVERTER) diff --git a/src/Magnum/Trade/Test/ImageConverterImplementationTest.cpp b/src/Magnum/Trade/Test/ImageConverterImplementationTest.cpp index be66e56ea..f83db6201 100644 --- a/src/Magnum/Trade/Test/ImageConverterImplementationTest.cpp +++ b/src/Magnum/Trade/Test/ImageConverterImplementationTest.cpp @@ -27,6 +27,7 @@ #include /** @todo remove once Debug is stream-free */ #include #include +#include #include /** @todo remove once Debug is stream-free */ #include @@ -39,13 +40,325 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct ImageConverterImplementationTest: TestSuite::Tester { explicit ImageConverterImplementationTest(); + void pluginInfo(); + void pluginInfoAliases(); + void pluginConfigurationInfoEmpty(); + void pluginConfigurationInfo(); + void pluginConfigurationInfoDoxygenDelimiter(); + void importerInfo(); + void converterInfo(); + void converterInfoExtensionMimeType(); + void info(); void infoError(); + + /* Explicitly forbid system-wide plugin dependencies */ + PluginManager::Manager _importerManager{"nonexistent"}; + PluginManager::Manager _converterManager{"nonexistent"}; }; ImageConverterImplementationTest::ImageConverterImplementationTest() { - addTests({&ImageConverterImplementationTest::info, + addTests({&ImageConverterImplementationTest::pluginInfo, + &ImageConverterImplementationTest::pluginInfoAliases, + &ImageConverterImplementationTest::pluginConfigurationInfoEmpty, + &ImageConverterImplementationTest::pluginConfigurationInfo, + &ImageConverterImplementationTest::pluginConfigurationInfoDoxygenDelimiter, + &ImageConverterImplementationTest::importerInfo, + &ImageConverterImplementationTest::converterInfo, + &ImageConverterImplementationTest::converterInfoExtensionMimeType, + + &ImageConverterImplementationTest::info, &ImageConverterImplementationTest::infoError}); + + /* Load the plugin directly from the build tree. Otherwise it's static and + already loaded. */ + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_importerManager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_converterManager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + #ifdef TGAIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_converterManager.load(TGAIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif +} + +void ImageConverterImplementationTest::pluginInfo() { + /* Check if the required plugin can be loaded. Catches also ABI and + interface mismatch errors. */ + if(!(_converterManager.load("AnyImageConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageConverter plugin can't be loaded."); + + Containers::Pointer converter = _converterManager.instantiate("AnyImageConverter"); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printPluginInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, *converter); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printPluginInfo(Debug::Flag::DisableColors, *converter); + CORRADE_COMPARE(out.str(), + "Plugin name: AnyImageConverter\n" + "Features:\n" + " Convert1DToFile\n" + " Convert2DToFile\n" + " Convert3DToFile\n" + " ConvertCompressed1DToFile\n" + " ConvertCompressed2DToFile\n" + " ConvertCompressed3DToFile\n" + " Levels\n"); +} + +void ImageConverterImplementationTest::pluginInfoAliases() { + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + + /* Check if the required plugin can be loaded. Catches also ABI and + interface mismatch errors. */ + if(!(importerManager.load("StbImageImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("StbImageImporter plugin can't be loaded."); + + /* Loading under an alias to verify that it's highlighted. Make + StbImageImporter *the* plugin to load PPMs, so it's not replaced by e.g. + DevIlImageImporter. */ + importerManager.setPreferredPlugins("PpmImporter", {"StbImageImporter"}); + Containers::Pointer importer = importerManager.instantiate("PpmImporter"); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printPluginInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, *importer); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printPluginInfo(Debug::Flag::DisableColors, *importer); + CORRADE_COMPARE(out.str(), + "Plugin name: StbImageImporter\n" + "Aliases:\n" + " BmpImporter\n" + " GifImporter\n" + " HdrImporter\n" + " JpegImporter\n" + " PgmImporter\n" + " PicImporter\n" + " PngImporter\n" + " PpmImporter\n" + " PsdImporter\n" + " TgaImporter\n" + "Features:\n" + " OpenData\n"); +} + +void ImageConverterImplementationTest::pluginConfigurationInfoEmpty() { + struct: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { + return Trade::ImporterFeature::FileCallback| + Trade::ImporterFeature::OpenState; + } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printPluginConfigurationInfo(Debug::Flag::DisableColors, importer); + CORRADE_COMPARE(out.str(), ""); +} + +void ImageConverterImplementationTest::pluginConfigurationInfo() { + struct: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::stringstream in; + in << R"([configuration] +# A comment +; Another +value=yes +another=42 +# Empty lines should not have trailing whitespace + +[configuration/group] +spaces=" YES " +newlines=""" +A + L + S + O +""" + +[configuration/group/subgroup] +subvalue=35 + +# Another instance of the same group +[configuration/group] +true=false +)"; + Utility::Configuration conf{in}; + + importer.configuration() = Utility::ConfigurationGroup{*conf.group("configuration")}; + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printPluginConfigurationInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, importer); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printPluginConfigurationInfo(Debug::Flag::DisableColors, importer); + CORRADE_COMPARE(out.str(), + "Configuration:\n" + " # A comment\n" + " ; Another\n" + " value=yes\n" + " another=42\n" + " # Empty lines should not have trailing whitespace\n" + "\n" + " [group]\n" + " spaces=\" YES \"\n" + " newlines=\"\"\"\n" + " A\n" + " L\n" + " S\n" + " O\n" + " \"\"\"\n" + "\n" + " [group/subgroup]\n" + " subvalue=35\n" + "\n" + " # Another instance of the same group\n" + " [group]\n" + " true=false\n"); +} + +void ImageConverterImplementationTest::pluginConfigurationInfoDoxygenDelimiter() { + struct: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::stringstream in; + in << R"(# [configuration_] +[configuration] +# A comment +value=yes +# [configuration_] +privateValue=SECRET +)"; + Utility::Configuration conf{in}; + + importer.configuration() = Utility::ConfigurationGroup{*conf.group("configuration")}; + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printPluginConfigurationInfo(Debug::Flag::DisableColors, importer); + CORRADE_COMPARE(out.str(), + "Configuration:\n" + " # A comment\n" + " value=yes\n"); +} + +void ImageConverterImplementationTest::importerInfo() { + /* Check if the required plugin can be loaded. Catches also ABI and + interface mismatch errors. */ + if(!(_importerManager.load("AnyImageImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter plugin can't be loaded."); + + Containers::Pointer importer = _importerManager.instantiate("AnyImageImporter"); + /** @todo pick a plugin that has some actual configuration */ + importer->configuration().setValue("something", "is there"); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printImporterInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, *importer); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printImporterInfo(Debug::Flag::DisableColors, *importer); + CORRADE_COMPARE(out.str(), + "Plugin name: AnyImageImporter\n" + "Features:\n" + " OpenData\n" + " FileCallback\n" + "Configuration:\n" + " something=is there\n"); +} + +void ImageConverterImplementationTest::converterInfo() { + /* Check if the required plugin can be loaded. Catches also ABI and + interface mismatch errors. */ + if(!(_converterManager.load("AnyImageConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageConverter plugin can't be loaded."); + + Containers::Pointer converter = _converterManager.instantiate("AnyImageConverter"); + /** @todo pick a plugin that has some actual configuration */ + converter->configuration().setValue("something", "is there"); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printImageConverterInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, *converter); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printImageConverterInfo(Debug::Flag::DisableColors, *converter); + CORRADE_COMPARE(out.str(), + "Plugin name: AnyImageConverter\n" + "Features:\n" + " Convert1DToFile\n" + " Convert2DToFile\n" + " Convert3DToFile\n" + " ConvertCompressed1DToFile\n" + " ConvertCompressed2DToFile\n" + " ConvertCompressed3DToFile\n" + " Levels\n" + "Configuration:\n" + " something=is there\n"); +} + +void ImageConverterImplementationTest::converterInfoExtensionMimeType() { + /* Check if the required plugin can be loaded. Catches also ABI and + interface mismatch errors. */ + if(!(_converterManager.load("TgaImageConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("TgaImageConverter plugin can't be loaded."); + + Containers::Pointer converter = _converterManager.instantiate("TgaImageConverter"); + /** @todo pick a plugin that has some actual configuration */ + converter->configuration().setValue("something", "is there"); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printImageConverterInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, *converter); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printImageConverterInfo(Debug::Flag::DisableColors, *converter); + CORRADE_COMPARE(out.str(), + "Plugin name: TgaImageConverter\n" + "Features:\n" + " Convert2DToData\n" + "File extension: tga\n" + "MIME type: image/x-tga\n" + "Configuration:\n" + " something=is there\n"); } void ImageConverterImplementationTest::info() { diff --git a/src/Magnum/Trade/Test/ImageConverterTest.cpp b/src/Magnum/Trade/Test/ImageConverterTest.cpp index b239d90a7..56aa7be8b 100644 --- a/src/Magnum/Trade/Test/ImageConverterTest.cpp +++ b/src/Magnum/Trade/Test/ImageConverterTest.cpp @@ -34,6 +34,7 @@ #include #include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/AbstractImageConverter.h" #include "configure.h" @@ -51,21 +52,38 @@ const struct { const char* name; Containers::Array args; const char* requiresImporter; + const char* requiresConverter; const char* expected; } InfoData[]{ + {"importer", Containers::array({ + "--info-importer", "-i", "someOption=yes"}), + "AnyImageImporter", nullptr, + "info-importer.txt"}, + {"converter", Containers::array({ + "-C", "AnyImageConverter", "--info-converter", "-c", "someOption=yes"}), + nullptr, "AnyImageConverter", + "info-converter.txt"}, + {"converter, implicit", Containers::array({ + "--info-converter", "-c", "someOption=yes"}), + nullptr, "AnyImageConverter", + "info-converter.txt"}, + {"importer, ignored input and output", Containers::array({ + "--info-importer", "a.png", "b.png", "out.jpg"}), + "AnySceneImporter", nullptr, + "info-importer-ignored-input-output.txt"}, {"data", Containers::array({ "-I", "TgaImporter", "--info", Utility::Path::join(TRADE_TEST_DIR, "ImageConverterTestFiles/file.tga")}), - "TgaImporter", + "TgaImporter", nullptr, "info-data.txt"}, {"data, map", Containers::array({ "--map", "-I", "TgaImporter", "--info", Utility::Path::join(TRADE_TEST_DIR, "ImageConverterTestFiles/file.tga")}), - "TgaImporter", + "TgaImporter", nullptr, /** @todo change to something else once we have a plugin that can zero-copy pass the imported data */ "info-data.txt"}, {"data, ignored output file", Containers::array({ "-I", "TgaImporter", "--info", Utility::Path::join(TRADE_TEST_DIR, "ImageConverterTestFiles/file.tga"), "whatever.png"}), - "TgaImporter", + "TgaImporter", nullptr, "info-data-ignored-output.txt"} }; @@ -122,8 +140,11 @@ void ImageConverterTest::info() { /* Check if required plugins can be loaded. Catches also ABI and interface mismatch errors. */ PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + PluginManager::Manager converterManager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; if(data.requiresImporter && !(importerManager.load(data.requiresImporter) & PluginManager::LoadState::Loaded)) CORRADE_SKIP(data.requiresImporter << "plugin can't be loaded."); + if(data.requiresConverter && !(converterManager.load(data.requiresConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresConverter << "plugin can't be loaded."); CORRADE_VERIFY(true); /* capture correct function name */ diff --git a/src/Magnum/Trade/Test/ImageConverterTestFiles/info-converter.txt b/src/Magnum/Trade/Test/ImageConverterTestFiles/info-converter.txt new file mode 100644 index 000000000..3ab9b48e2 --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTestFiles/info-converter.txt @@ -0,0 +1,11 @@ +Plugin name: AnyImageConverter +Features: + Convert1DToFile + Convert2DToFile + Convert3DToFile + ConvertCompressed1DToFile + ConvertCompressed2DToFile + ConvertCompressed3DToFile + Levels +Configuration: + someOption=yes diff --git a/src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer-ignored-input-output.txt b/src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer-ignored-input-output.txt new file mode 100644 index 000000000..fd93eea9b --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer-ignored-input-output.txt @@ -0,0 +1,6 @@ +Ignoring input files for --info: a.png b.png +Ignoring output file for --info: out.jpg +Plugin name: AnyImageImporter +Features: + OpenData + FileCallback diff --git a/src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer.txt b/src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer.txt new file mode 100644 index 000000000..ce49ddde9 --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer.txt @@ -0,0 +1,6 @@ +Plugin name: AnyImageImporter +Features: + OpenData + FileCallback +Configuration: + someOption=yes diff --git a/src/Magnum/Trade/Test/configure.h.cmake b/src/Magnum/Trade/Test/configure.h.cmake index f328ef8db..773af0c07 100644 --- a/src/Magnum/Trade/Test/configure.h.cmake +++ b/src/Magnum/Trade/Test/configure.h.cmake @@ -26,21 +26,28 @@ #define TRADE_TEST_DIR "${TRADE_TEST_DIR}" #define TRADE_TEST_OUTPUT_DIR "${TRADE_TEST_OUTPUT_DIR}" #cmakedefine IMAGECONVERTER_EXECUTABLE_FILENAME "${IMAGECONVERTER_EXECUTABLE_FILENAME}" +#cmakedefine ANYIMAGEIMPORTER_PLUGIN_FILENAME "${ANYIMAGEIMPORTER_PLUGIN_FILENAME}" +#cmakedefine ANYIMAGECONVERTER_PLUGIN_FILENAME "${ANYIMAGECONVERTER_PLUGIN_FILENAME}" +#cmakedefine TGAIMAGECONVERTER_PLUGIN_FILENAME "${TGAIMAGECONVERTER_PLUGIN_FILENAME}" #ifdef CORRADE_TARGET_WINDOWS #ifdef CORRADE_IS_DEBUG_BUILD #define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}" #define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_BINARY_INSTALL_DIR}" #else #define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}" #define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_BINARY_INSTALL_DIR}" #endif #else #ifdef CORRADE_IS_DEBUG_BUILD #define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}" #define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}" #else #define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}" #define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}" #endif #endif diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index db9d2f725..026f15bc8 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -192,7 +192,8 @@ magnum-imageconverter [-h|--help] [-I|--importer PLUGIN] [-i|--importer-options key=val,key2=val2,…] [-c|--converter-options key=val,key2=val2,…]... [-D|--dimensions N] [--image N] [--level N] [--layer N] [--layers] [--levels] [--in-place] - [--info] [--color on|off|auto] [-v|--verbose] [--profile] [--] input output + [--info-importer] [--info-converter] [--info] [--color on|off|auto] + [-v|--verbose] [--profile] [--] input output @endcode Arguments: @@ -221,6 +222,8 @@ Arguments: more - `--levels` --- combine multiple image levels into a single file - `--in-place` --- overwrite the input image with the output +- `--info-importer` --- print info about the importer plugin and exit +- `--info-converter` --- print info about the image converter plugin and exit - `--info` --- print info about the input file and exit - `--color` --- colored output for `--info` (default: `auto`) - `-v`, `--verbose` --- verbose output from importer and converter plugins @@ -231,9 +234,16 @@ tightly-packed square of pixels in given @ref PixelFormat. Specifying `-C` / `--converter raw` will save raw imported data instead of using a converter plugin. -If `--info` is given, the utility will print information about all images -present in the file, independently of the `-D` / `--dimensions` option. In this -case no conversion is done and output file doesn't need to be specified. +If the `--info-importer` or `--info-converter` option is given, the utility +will print information about given plugin specified via the `-I` or `-C` +option, including its configuration options potentially overriden with +`-i` or `-c`. In this case no file is read and no conversion is done and +neither the input nor the output file needs to be specified. + +If `--info` is given, the utility will print information about given data, +independently of the `-D` / `--dimensions` option. In this case the input file +is read but no no conversion is done and output file doesn't need to be +specified. The `-i` / `--importer-options` and `-c` / `--converter-options` arguments accept a comma-separated list of key/value pairs to set in the importer / @@ -258,6 +268,11 @@ using namespace Containers::Literals; namespace { +bool isPluginInfoRequested(const Utility::Arguments& args) { + return args.isSet("info-importer") || + args.isSet("info-converter"); +} + template bool checkCommonFormatFlags(const Utility::Arguments& args, const Containers::Array>& images) { CORRADE_INTERNAL_ASSERT(!images.isEmpty()); const bool compressed = images.front().isCompressed(); @@ -362,15 +377,21 @@ int main(int argc, char** argv) { .addBooleanOption("layers").setHelp("layers", "combine multiple layers into an image with one dimension more") .addBooleanOption("levels").setHelp("layers", "combine multiple image levels into a single file") .addBooleanOption("in-place").setHelp("in-place", "overwrite the input image with the output") + .addBooleanOption("info-importer").setHelp("info-importer", "print info about the importer plugin and exit") + .addBooleanOption("info-converter").setHelp("info-converter", "print info about the image converter plugin and exit") .addBooleanOption("info").setHelp("info", "print info about the input file and exit") .addOption("color", "auto").setHelp("color", "colored output for --info", "on|off|auto") .addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from importer and converter plugins") .addBooleanOption("profile").setHelp("profile", "measure import and conversion time") .setParseErrorCallback([](const Utility::Arguments& args, Utility::Arguments::ParseError error, const std::string& key) { - /* If --in-place or --info is passed, we don't need the output - argument */ + /* If --info for plugins is passed, we don't need the input */ if(error == Utility::Arguments::ParseError::MissingArgument && - key == "output" && (args.isSet("in-place") || args.isSet("info"))) + key == "input" && isPluginInfoRequested(args)) + return true; + /* If --in-place or --info for plugins or data is passed, we don't + need the output argument */ + if(error == Utility::Arguments::ParseError::MissingArgument && + key == "output" && (args.isSet("in-place") || isPluginInfoRequested(args) || args.isSet("info"))) return true; /* Handle all other errors as usual */ @@ -382,9 +403,14 @@ Specifying --importer raw: will treat the input as a raw tightly-packed square of pixels in given pixel format. Specifying -C / --converter raw will save raw imported data instead of using a converter plugin. -If --info is given, the utility will print information about all images present -in the file, independently of the -D / --dimensions option. In this case no -conversion is done and output file doesn't need to be specified. +If the --info-importer or --info-converter option is given, the utility will +print information about given plugin specified via the -I or -C option, +including its configuration options potentially overriden with -i or -c. In +this case no file is read and no conversion is done and neither the input nor +the output file needs to be specified. + +If --info is given, the utility will print information about given data, independently of the -D / --dimensions option. In this case the input file is +read but no conversion is done and output file doesn't need to be specified. The -i / --importer-options and -c / --converter-options arguments accept a comma-separated list of key/value pairs to set in the importer / converter @@ -410,16 +436,26 @@ no -C / --converter is specified, AnyImageConverter is used.)") useColor = Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors; /* Generic checks */ + if(const std::size_t inputCount = args.arrayValueCount("input")) { + /* Not an error in this case, it should be possible to just append + --info* to existing command line without having to remove anything. + But print a warning at least, it could also be a mistyped option. */ + if(isPluginInfoRequested(args)) { + Warning w; + w << "Ignoring input files for --info:"; + for(std::size_t i = 0; i != inputCount; ++i) + w << args.arrayValue("input", i); + } + } if(args.value("output")) { if(args.isSet("in-place")) { Error{} << "Output file shouldn't be set for --in-place:" << args.value("output"); return 1; } - /* Not an error in this case, it should be possible to just append - --info to existing command line without having to remove anything. - But print a warning at least, it could also be a mistyped option. */ - if(args.isSet("info")) + /* Same as above, it should be possible to just append --info* to + existing command line */ + if(isPluginInfoRequested(args) || args.isSet("info")) Warning{} << "Ignoring output file for --info:" << args.value("output"); } @@ -450,7 +486,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") Error{} << "The --levels option can't be combined with raw data output"; return 1; } - if(!args.isSet("layers") && !args.isSet("levels") && args.arrayValueCount("input") > 1) { + if(!args.isSet("layers") && !args.isSet("levels") && args.arrayValueCount("input") > 1 && !isPluginInfoRequested(args)) { Error{} << "Multiple input files require the --layers / --levels option to be set"; return 1; } @@ -463,6 +499,35 @@ no -C / --converter is specified, AnyImageConverter is used.)") args.value("plugin-dir").empty() ? Containers::String{} : Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImageConverter::pluginSearchPaths().back()).second())}; + /* Print plugin info, if requested */ + if(args.isSet("info-importer")) { + Containers::Pointer importer = importerManager.loadAndInstantiate(args.value("importer")); + if(!importer) { + Debug{} << "Available importer plugins:" << ", "_s.join(importerManager.aliasList()); + return 1; + } + + /* Set options, if passed */ + if(args.isSet("verbose")) importer->addFlags(Trade::ImporterFlag::Verbose); + Implementation::setOptions(*importer, "AnyImageImporter", args.value("importer-options")); + Trade::Implementation::printImporterInfo(useColor, *importer); + return 0; + } + if(args.isSet("info-converter")) { + Containers::Pointer converter = converterManager.loadAndInstantiate(args.arrayValueCount("converter") ? args.arrayValue("converter", 0) : "AnyImageConverter"); + if(!converter) { + Debug{} << "Available converter plugins:" << ", "_s.join(converterManager.aliasList()); + return 1; + } + + /* Set options, if passed */ + if(args.isSet("verbose")) converter->addFlags(Trade::ImageConverterFlag::Verbose); + if(args.arrayValueCount("converter-options")) + Implementation::setOptions(*converter, "AnyImageConverter", args.arrayValue("converter-options", 0)); + Trade::Implementation::printImageConverterInfo(useColor, *converter); + return 0; + } + const Int dimensions = args.value("dimensions"); /** @todo make them array options as well? */ const UnsignedInt image = args.value("image");