Browse Source

{scene,image}converter: add --info options for plugins.

Lists features, aliases as well as documented contents of the whole
configuration file. Useful to not need to look up online docs when
working on the command line.
pull/595/head
Vladimír Vondruš 4 years ago
parent
commit
cc9159d387
  1. 6
      doc/changelog.dox
  2. 6
      src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h
  3. 15
      src/Magnum/SceneTools/Test/CMakeLists.txt
  4. 53
      src/Magnum/SceneTools/Test/SceneConverterImplementationTest.cpp
  5. 38
      src/Magnum/SceneTools/Test/SceneConverterTest.cpp
  6. 6
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-converter.txt
  7. 11
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-image-converter.txt
  8. 5
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer-ignored-input-output.txt
  9. 5
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer.txt
  10. 1
      src/Magnum/SceneTools/Test/configure.h.cmake
  11. 116
      src/Magnum/SceneTools/sceneconverter.cpp
  12. 103
      src/Magnum/Trade/Implementation/converterUtilities.h
  13. 38
      src/Magnum/Trade/Test/CMakeLists.txt
  14. 315
      src/Magnum/Trade/Test/ImageConverterImplementationTest.cpp
  15. 27
      src/Magnum/Trade/Test/ImageConverterTest.cpp
  16. 11
      src/Magnum/Trade/Test/ImageConverterTestFiles/info-converter.txt
  17. 6
      src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer-ignored-input-output.txt
  18. 6
      src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer.txt
  19. 7
      src/Magnum/Trade/Test/configure.h.cmake
  20. 95
      src/Magnum/Trade/imageconverter.cpp

6
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

6
src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h

@ -33,6 +33,7 @@
#include <Corrade/Utility/Arguments.h>
#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<class T> Containers::String calculateBounds(Containers::Array<T>&& attribute) {
/** @todo clean up when Debug::toString() exists */

15
src/Magnum/SceneTools/Test/CMakeLists.txt

@ -40,6 +40,9 @@ endif()
if(MAGNUM_WITH_SCENECONVERTER AND CORRADE_TARGET_UNIX)
set(SCENECONVERTER_EXECUTABLE_FILENAME $<TARGET_FILE:magnum-sceneconverter>)
endif()
if(MAGNUM_WITH_ANYSCENECONVERTER AND NOT MAGNUM_BUILD_PLUGINS_STATIC AND NOT MAGNUM_ANYSCENECONVERTER_BUILD_STATIC)
set(ANYSCENECONVERTER_PLUGIN_FILENAME $<TARGET_FILE:AnySceneConverter>)
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}/$<CONFIG>)
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

53
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<Trade::AbstractSceneConverter> _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<void>(Trade::Implementation::printImageConverterInfo);
static_cast<void>(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<Trade::AbstractSceneConverter> 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() {

38
src/Magnum/SceneTools/Test/SceneConverterTest.cpp

@ -57,21 +57,47 @@ const struct {
const char* name;
Containers::Array<Containers::String> args;
const char* requiresImporter;
const char* requiresConverter;
const char* requiresImageConverter;
const char* expected;
} InfoData[]{
{"importer", Containers::array<Containers::String>({
"--info-importer", "-i", "someOption=yes"}),
"AnySceneImporter", nullptr, nullptr,
"info-importer.txt"},
{"converter", Containers::array<Containers::String>({
"-C", "AnySceneConverter", "--info-converter", "-c", "someOption=yes"}),
nullptr, "AnySceneConverter", nullptr,
"info-converter.txt"},
{"converter, implicit", Containers::array<Containers::String>({
"--info-converter", "-c", "someOption=yes"}),
nullptr, "AnySceneConverter", nullptr,
"info-converter.txt"},
{"image converter", Containers::array<Containers::String>({
"-P", "AnyImageConverter", "--info-image-converter", "-p", "someOption=yes"}),
nullptr, nullptr, "AnyImageConverter",
"info-image-converter.txt"},
{"image converter, implicit", Containers::array<Containers::String>({
"--info-image-converter", "-p", "someOption=yes"}),
nullptr, nullptr, "AnyImageConverter",
"info-image-converter.txt"},
{"importer, ignored input and output", Containers::array<Containers::String>({
"--info-importer", "input.obj", "output.ply"}),
"AnySceneImporter", nullptr, nullptr,
"info-importer-ignored-input-output.txt"},
{"data", Containers::array<Containers::String>({
"-I", "ObjImporter", "--info", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}),
"ObjImporter",
"ObjImporter", nullptr, nullptr,
"info-data.txt"},
{"data, map", Containers::array<Containers::String>({
"--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<Containers::String>({
"-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<Trade::AbstractImporter> importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR};
PluginManager::Manager<Trade::AbstractImageConverter> imageConverterManager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR};
PluginManager::Manager<Trade::AbstractSceneConverter> 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 */

6
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-converter.txt

@ -0,0 +1,6 @@
Plugin name: AnySceneConverter
Features:
ConvertMeshToFile
ConvertMultipleToFile
Configuration:
someOption=yes

11
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

5
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

5
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-importer.txt

@ -0,0 +1,5 @@
Plugin name: AnySceneImporter
Features:
FileCallback
Configuration:
someOption=yes

1
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

116
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<Containers::StringView>("output")) {
if(args.value<Containers::StringView>("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<Containers::StringView>("input");
}
if(args.value<Containers::StringView>("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<Containers::StringView>("output");
}
if(args.isSet("concatenate-meshes") && args.value<Containers::StringView>("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<Trade::AbstractSceneConverter> 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<Trade::AbstractImageConverter> 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")) {

103
src/Magnum/Trade/Implementation/converterUtilities.h

@ -26,13 +26,21 @@
*/
#include <chrono>
#include <sstream> /** @todo remove when Debug is stream-free */
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/Reference.h>
#include <Corrade/Containers/String.h>
#include <Corrade/PluginManager/PluginMetadata.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/FormatStl.h> /** @todo drop once String::replaceAll() has a StringView overload */
#include <Corrade/Utility/String.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#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<class T> 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<std::string> 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<Containers::StringView, Containers::StringView> 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<Containers::StringView, Containers::Reference<const Utility::ConfigurationGroup>> 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()} {}

38
src/Magnum/Trade/Test/CMakeLists.txt

@ -40,6 +40,17 @@ endif()
if(MAGNUM_WITH_IMAGECONVERTER AND CORRADE_TARGET_UNIX)
set(IMAGECONVERTER_EXECUTABLE_FILENAME $<TARGET_FILE:magnum-imageconverter>)
endif()
if(NOT MAGNUM_BUILD_PLUGINS_STATIC)
if(MAGNUM_WITH_ANYIMAGEIMPORTER AND NOT MAGNUM_ANYIMAGEIMPORTER_BUILD_STATIC)
set(ANYIMAGEIMPORTER_PLUGIN_FILENAME $<TARGET_FILE:AnyImageImporter>)
endif()
if(MAGNUM_WITH_ANYIMAGECONVERTER AND NOT MAGNUM_ANYIMAGECONVERTER_BUILD_STATIC)
set(ANYIMAGECONVERTER_PLUGIN_FILENAME $<TARGET_FILE:AnyImageConverter>)
endif()
if(MAGNUM_WITH_TGAIMAGECONVERTER AND NOT MAGNUM_TGAIMAGECONVERTER_BUILD_STATIC)
set(TGAIMAGECONVERTER_PLUGIN_FILENAME $<TARGET_FILE:TgaImageConverter>)
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}/$<CONFIG>)
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}/$<CONFIG>)
if(MAGNUM_WITH_IMAGECONVERTER)

315
src/Magnum/Trade/Test/ImageConverterImplementationTest.cpp

@ -27,6 +27,7 @@
#include <Corrade/Containers/StringStl.h> /** @todo remove once Debug is stream-free */
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/StringToFile.h>
#include <Corrade/Utility/Configuration.h>
#include <Corrade/Utility/DebugStl.h> /** @todo remove once Debug is stream-free */
#include <Corrade/Utility/Path.h>
@ -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<Trade::AbstractImporter> _importerManager{"nonexistent"};
PluginManager::Manager<Trade::AbstractImageConverter> _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<Trade::AbstractImageConverter> 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<Trade::AbstractImporter> 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<Trade::AbstractImporter> 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<Trade::AbstractImporter> 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<Trade::AbstractImageConverter> 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<Trade::AbstractImageConverter> 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() {

27
src/Magnum/Trade/Test/ImageConverterTest.cpp

@ -34,6 +34,7 @@
#include <Corrade/Utility/Path.h>
#include "Magnum/Trade/AbstractImporter.h"
#include "Magnum/Trade/AbstractImageConverter.h"
#include "configure.h"
@ -51,21 +52,38 @@ const struct {
const char* name;
Containers::Array<Containers::String> args;
const char* requiresImporter;
const char* requiresConverter;
const char* expected;
} InfoData[]{
{"importer", Containers::array<Containers::String>({
"--info-importer", "-i", "someOption=yes"}),
"AnyImageImporter", nullptr,
"info-importer.txt"},
{"converter", Containers::array<Containers::String>({
"-C", "AnyImageConverter", "--info-converter", "-c", "someOption=yes"}),
nullptr, "AnyImageConverter",
"info-converter.txt"},
{"converter, implicit", Containers::array<Containers::String>({
"--info-converter", "-c", "someOption=yes"}),
nullptr, "AnyImageConverter",
"info-converter.txt"},
{"importer, ignored input and output", Containers::array<Containers::String>({
"--info-importer", "a.png", "b.png", "out.jpg"}),
"AnySceneImporter", nullptr,
"info-importer-ignored-input-output.txt"},
{"data", Containers::array<Containers::String>({
"-I", "TgaImporter", "--info", Utility::Path::join(TRADE_TEST_DIR, "ImageConverterTestFiles/file.tga")}),
"TgaImporter",
"TgaImporter", nullptr,
"info-data.txt"},
{"data, map", Containers::array<Containers::String>({
"--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<Containers::String>({
"-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<Trade::AbstractImporter> importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR};
PluginManager::Manager<Trade::AbstractImageConverter> 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 */

11
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

6
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

6
src/Magnum/Trade/Test/ImageConverterTestFiles/info-importer.txt

@ -0,0 +1,6 @@
Plugin name: AnyImageImporter
Features:
OpenData
FileCallback
Configuration:
someOption=yes

7
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

95
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<UnsignedInt dimensions> bool checkCommonFormatFlags(const Utility::Arguments& args, const Containers::Array<Trade::ImageData<dimensions>>& 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:<format> 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<Containers::StringView>("input", i);
}
}
if(args.value<Containers::StringView>("output")) {
if(args.isSet("in-place")) {
Error{} << "Output file shouldn't be set for --in-place:" << args.value<Containers::StringView>("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<Containers::StringView>("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<Trade::AbstractImporter> 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<Trade::AbstractImageConverter> 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<Int>("dimensions");
/** @todo make them array options as well? */
const UnsignedInt image = args.value<UnsignedInt>("image");

Loading…
Cancel
Save