Browse Source

imageconverter: ability to combine multiple image layers together.

Hey, this was fun, after all!
pull/537/head
Vladimír Vondruš 5 years ago
parent
commit
b83099a8c6
  1. 3
      doc/changelog.dox
  2. 513
      src/Magnum/Trade/imageconverter.cpp

3
doc/changelog.dox

@ -208,7 +208,8 @@ See also:
are encouraged over @relativeref{Trade::AbstractImporter,setFlags()} as it
avoid accidentally clearing default flags potentially added in the future.
- Ability to convert also 1D and 3D images with the
@ref magnum-imageconverter "magnum-imageconverter" utility.
@ref magnum-imageconverter "magnum-imageconverter" utility, as well as
combining layers into images of one dimension more
@subsubsection changelog-latest-new-vk Vk library

513
src/Magnum/Trade/imageconverter.cpp

@ -26,8 +26,10 @@
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/StaticArray.h>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/Utility/Arguments.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
@ -69,7 +71,7 @@ magnum-imageconverter [-h|--help] [-I|--importer PLUGIN]
[-C|--converter PLUGIN] [--plugin-dir DIR]
[-i|--importer-options key=val,key2=val2,]
[-c|--converter-options key=val,key2=val2,] [-D|--dimensions N]
[--image N] [--level N] [--in-place] [--info] [-v|--verbose]
[--image N] [--level N] [--layers] [--in-place] [--info] [-v|--verbose]
[--] input output
@endcode
@ -92,6 +94,8 @@ Arguments:
(default: `2`)
- `--image N` --- image to import (default: `0`)
- `--level N` --- image level to import (default: `0`)
- `--layers` --- combine multiple layers into an image with one dimension
more
- `--in-place` --- overwrite the input image with the output
- `--info` --- print info about the input file and exit
- `-v`, `--verbose` --- verbose output from importer and converter plugins
@ -146,9 +150,58 @@ magnum-imageconverter image.dds --converter raw data.dat
using namespace Magnum;
namespace {
template<UnsignedInt dimensions> bool checkCommonFormat(const Utility::Arguments& args, const Containers::Array<Trade::ImageData<dimensions>>& images) {
CORRADE_INTERNAL_ASSERT(!images.empty());
const bool compressed = images.front().isCompressed();
PixelFormat format{};
CompressedPixelFormat compressedFormat{};
if(!compressed) format = images.front().format();
else compressedFormat = images.front().compressedFormat();
for(std::size_t i = 1; i != images.size(); ++i) {
if(images[i].isCompressed() != compressed ||
(!compressed && images[i].format() != format) ||
(compressed && images[i].compressedFormat() != compressedFormat))
{
Error e;
e << "Images have different formats," << args.arrayValue("input", i) << "has";
if(images[i].isCompressed())
e << images[i].compressedFormat();
else
e << images[i].format();
e << Debug::nospace << ", expected";
if(compressed)
e << compressedFormat;
else
e << format;
return false;
}
}
return true;
}
template<UnsignedInt dimensions> bool checkCommonFormatAndSize(const Utility::Arguments& args, const Containers::Array<Trade::ImageData<dimensions>>& images) {
if(!checkCommonFormat(args, images)) return false;
CORRADE_INTERNAL_ASSERT(!images.empty());
Math::Vector<dimensions, Int> size = images.front().size();
for(std::size_t i = 1; i != images.size(); ++i) {
if(images[i].size() != size) {
Error{} << "Images have different sizes," << args.arrayValue("input", i) << "has a size of" << images[i].size() << Debug::nospace << ", expected" << size;
return false;
}
}
return true;
}
}
int main(int argc, char** argv) {
Utility::Arguments args;
args.addArgument("input").setHelp("input", "input image")
args.addArrayArgument("input").setHelp("input", "input image(s)")
.addArgument("output").setHelp("output", "output image; ignored if --info is present, disallowed for --in-place")
.addOption('I', "importer", "AnyImageImporter").setHelp("importer", "image importer plugin", "PLUGIN")
.addOption('C', "converter", "AnyImageConverter").setHelp("converter", "image converter plugin", "PLUGIN")
@ -158,6 +211,7 @@ int main(int argc, char** argv) {
.addOption('D', "dimensions", "2").setHelp("dimensions", "import and convert image of given dimensions", "N")
.addOption("image", "0").setHelp("image", "image to import", "N")
.addOption("level", "0").setHelp("level", "image level to import", "N")
.addBooleanOption("layers").setHelp("layers", "combine multiple layers into an image with one dimension more")
.addBooleanOption("in-place").setHelp("in-place", "overwrite the input image with the output")
.addBooleanOption("info").setHelp("info", "print info about the input file and exit")
.addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from importer and converter plugins")
@ -201,182 +255,306 @@ key=true; configuration subgroups are delimited with /.)")
Warning{} << "Ignoring output file for --info:" << args.value<Containers::StringView>("output");
}
/* Mutually incompatible options */
if(args.isSet("layers") && args.isSet("in-place")) {
Error{} << "The --layers option can't be combined with --in-place";
return 1;
}
if(args.isSet("layers") && args.isSet("info")) {
Error{} << "The --layers option can't be combined with --info";
return 1;
}
if(!args.isSet("layers") && args.arrayValueCount("input") > 1) {
Error{} << "Multiple input files require the --layers option to be set";
return 1;
}
PluginManager::Manager<Trade::AbstractImporter> importerManager{
args.value("plugin-dir").empty() ? std::string{} :
Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0])};
/* Load raw data, if requested; assume it's a tightly-packed square of
given format */
/** @todo implement image slicing and then use `--slice "0 0 w h"` to
specify non-rectangular size (and +x +y to specify padding?) */
const std::string input = args.value("input");
const Int dimensions = args.value<Int>("dimensions");
/** @todo make them array options as well? */
const UnsignedInt image = args.value<UnsignedInt>("image");
const UnsignedInt level = args.value<UnsignedInt>("level");
Containers::Optional<Trade::ImageData1D> image1D;
Containers::Optional<Trade::ImageData2D> image2D;
Containers::Optional<Trade::ImageData3D> image3D;
if(Utility::String::beginsWith(args.value("importer"), "raw:")) {
if(dimensions != 2) {
Error{} << "Raw data inputs can be only used for 2D images";
return 1;
}
Containers::Array<Trade::ImageData1D> images1D;
Containers::Array<Trade::ImageData2D> images2D;
Containers::Array<Trade::ImageData3D> images3D;
for(std::size_t i = 0, max = args.arrayValueCount("input"); i != max; ++i) {
const std::string input = args.arrayValue("input", i);
/* Load raw data, if requested; assume it's a tightly-packed square of
given format */
/** @todo implement image slicing and then use `--slice "0 0 w h"` to
specify non-rectangular size (and +x +y to specify padding?) */
if(Utility::String::beginsWith(args.value("importer"), "raw:")) {
if(dimensions != 2) {
Error{} << "Raw data inputs can be only used for 2D images";
return 1;
}
/** @todo Any chance to do this without using internal APIs? */
const PixelFormat format = Utility::ConfigurationValue<PixelFormat>::fromString(args.value("importer").substr(4), {});
const UnsignedInt pixelSize = Magnum::pixelSize(format);
if(format == PixelFormat{}) {
Error{} << "Invalid raw pixel format" << args.value("importer");
return 4;
}
/** @todo Any chance to do this without using internal APIs? */
const PixelFormat format = Utility::ConfigurationValue<PixelFormat>::fromString(args.value("importer").substr(4), {});
const UnsignedInt pixelSize = Magnum::pixelSize(format);
if(format == PixelFormat{}) {
Error{} << "Invalid raw pixel format" << args.value("importer");
return 4;
}
/** @todo simplify once read() reliably returns an Optional */
if(!Utility::Directory::exists(input)) {
Error{} << "Cannot open file" << input;
return 3;
}
Containers::Array<char> data = Utility::Directory::read(input);
auto side = Int(std::sqrt(data.size()/pixelSize));
if(data.size() % pixelSize || side*side*pixelSize != data.size()) {
Error{} << "File of size" << data.size() << "is not a tightly-packed square of" << format;
return 5;
}
/** @todo simplify once read() reliably returns an Optional */
if(!Utility::Directory::exists(input)) {
Error{} << "Cannot open file" << input;
return 3;
}
Containers::Array<char> data = Utility::Directory::read(input);
auto side = Int(std::sqrt(data.size()/pixelSize));
if(data.size() % pixelSize || side*side*pixelSize != data.size()) {
Error{} << "File of size" << data.size() << "is not a tightly-packed square of" << format;
return 5;
}
/* Print image info, if requested */
if(args.isSet("info")) {
Debug{} << "Image 0:\n Mip 0:" << format << Vector2i{side};
return 0;
}
/* Print image info, if requested */
if(args.isSet("info")) {
Debug{} << "Image 0:\n Mip 0:" << format << Vector2i{side};
return 0;
}
image2D = Trade::ImageData2D(format, Vector2i{side}, std::move(data));
arrayAppend(images2D, InPlaceInit, format, Vector2i{side}, std::move(data));
/* Otherwise load it using an importer plugin */
} else {
Containers::Pointer<Trade::AbstractImporter> importer = importerManager.loadAndInstantiate(args.value("importer"));
if(!importer) {
Debug{} << "Available importer plugins:" << Utility::String::join(importerManager.aliasList(), ", ");
return 1;
}
/* Otherwise load it using an importer plugin */
} else {
Containers::Pointer<Trade::AbstractImporter> importer = importerManager.loadAndInstantiate(args.value("importer"));
if(!importer) {
Debug{} << "Available importer plugins:" << Utility::String::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"));
/* Set options, if passed */
if(args.isSet("verbose")) importer->addFlags(Trade::ImporterFlag::Verbose);
Implementation::setOptions(*importer, "AnyImageImporter", args.value("importer-options"));
/* Print image info, if requested. This is always done for just one
file, checked above. */
if(args.isSet("info")) {
/* Open the file, but don't fail when an image can't be
opened */
if(!importer->openFile(input)) {
Error() << "Cannot open file" << input;
return 3;
}
if(!importer->image1DCount() && !importer->image2DCount() && !importer->image3DCount()) {
Debug{} << "No images found in" << input;
return 0;
}
/* Parse everything first to avoid errors interleaved with
output. In case the images have all just a single level and
no names, write them in a compact way without listing
levels. */
bool error = false, compact = true;
Containers::Array<Trade::Implementation::ImageInfo> infos =
Trade::Implementation::imageInfo(*importer, error, compact);
for(const Trade::Implementation::ImageInfo& info: infos) {
Debug d;
if(info.level == 0) {
if(info.size.z()) d << "3D image";
else if(info.size.y()) d << "2D image";
else d << "1D image";
d << info.image << Debug::nospace << ":";
if(!info.name.empty()) d << info.name;
if(!compact) d << Debug::newline;
}
if(!compact) d << " Level" << info.level << Debug::nospace << ":";
if(info.compressed) d << info.compressedFormat;
else d << info.format;
if(info.size.z()) d << info.size;
else if(info.size.y()) d << info.size.xy();
else d << Math::Vector<1, Int>(info.size.x());
}
return error ? 1 : 0;
}
/* Print image info, if requested */
if(args.isSet("info")) {
/* Open the file, but don't fail when an image can't be opened */
/* Open input file */
if(!importer->openFile(input)) {
Error() << "Cannot open file" << input;
Error{} << "Cannot open file" << input;
return 3;
}
/* Bail early if there's no image whatsoever. More detailed errors
with hints are provided for each dimension below. */
if(!importer->image1DCount() && !importer->image2DCount() && !importer->image3DCount()) {
Debug{} << "No images found in" << input;
return 0;
Error{} << "No images found in" << input;
return 1;
}
/* Parse everything first to avoid errors interleaved with output.
In case the images have all just a single level and no names,
write them in a compact way without listing levels. */
bool error = false, compact = true;
Containers::Array<Trade::Implementation::ImageInfo> infos =
Trade::Implementation::imageInfo(*importer, error, compact);
for(const Trade::Implementation::ImageInfo& info: infos) {
Debug d;
if(info.level == 0) {
if(info.size.z()) d << "3D image";
else if(info.size.y()) d << "2D image";
else d << "1D image";
d << info.image << Debug::nospace << ":";
if(!info.name.empty()) d << info.name;
if(!compact) d << Debug::newline;
bool imported = false;
if(dimensions == 1) {
if(!importer->image1DCount()) {
Error{} << "No 1D images found in" << input << Debug::nospace << ". Specify -D2 or -D3 for 2D or 3D image conversion.";
return 1;
}
if(image >= importer->image1DCount()) {
Error{} << "1D image number" << image << "not found in" << input << Debug::nospace << ", the file has only" << importer->image1DCount() << "1D images";
return 1;
}
if(level >= importer->image1DLevelCount(image)) {
Error{} << "1D image" << image << "in" << input << "doesn't have a level number" << level << Debug::nospace << ", only" << importer->image1DLevelCount(image) << "levels";
return 1;
}
if(!compact) d << " Level" << info.level << Debug::nospace << ":";
if(info.compressed) d << info.compressedFormat;
else d << info.format;
if(info.size.z()) d << info.size;
else if(info.size.y()) d << info.size.xy();
else d << Math::Vector<1, Int>(info.size.x());
}
return error ? 1 : 0;
}
if(Containers::Optional<Trade::ImageData1D> image1D = importer->image1D(image, level)) {
arrayAppend(images1D, std::move(*image1D));
imported = true;
}
/* Open input file */
if(!importer->openFile(input)) {
Error{} << "Cannot open file" << input;
return 3;
}
} else if(dimensions == 2) {
if(!importer->image2DCount()) {
Error{} << "No 2D images found in" << input << Debug::nospace << ". Specify -D1 or -D3 for 1D or 3D image conversion.";
return 1;
}
if(image >= importer->image2DCount()) {
Error{} << "2D image number" << image << "not found in" << input << Debug::nospace << ", the file has only" << importer->image2DCount() << "2D images";
return 1;
}
if(level >= importer->image2DLevelCount(image)) {
Error{} << "2D image" << image << "in" << input << "doesn't have a level number" << level << Debug::nospace << ", only" << importer->image2DLevelCount(image) << "levels";
return 1;
}
/* Bail early if there's no image whatsoever. More detailed errors with
hints are provided for each dimension below. */
if(!importer->image1DCount() && !importer->image2DCount() && !importer->image3DCount()) {
Error{} << "No images found in" << input;
return 1;
}
if(Containers::Optional<Trade::ImageData2D> image2D = importer->image2D(image, level)) {
arrayAppend(images2D, std::move(*image2D));
imported = true;
}
bool imported;
if(dimensions == 1) {
if(!importer->image1DCount()) {
Error{} << "No 1D images found in" << input << Debug::nospace << ". Specify -D2 or -D3 for 2D or 3D image conversion.";
return 1;
}
if(image >= importer->image1DCount()) {
Error{} << "1D image number" << image << "not found in" << input << Debug::nospace << ", the file has only" << importer->image1DCount() << "1D images";
return 1;
}
if(level >= importer->image1DLevelCount(image)) {
Error{} << "1D image" << image << "in" << input << "doesn't have a level number" << level << Debug::nospace << ", only" << importer->image1DLevelCount(image) << "levels";
return 1;
}
} else if(dimensions == 3) {
if(!importer->image3DCount()) {
Error{} << "No 3D images found in" << input << Debug::nospace << ". Specify -D1 or -D2 for 1D or 2D image conversion.";
return 1;
}
if(image >= importer->image3DCount()) {
Error{} << "3D image number" << image << "not found in" << input << Debug::nospace << ", the file has only" << importer->image3DCount() << "3D images";
return 1;
}
if(level >= importer->image3DLevelCount(image)) {
Error{} << "3D image" << image << "in" << input << "doesn't have a level number" << level << Debug::nospace << ", only" << importer->image3DLevelCount(image) << "levels";
return 1;
}
imported = !!(image1D = importer->image1D(image, level));
if(Containers::Optional<Trade::ImageData3D> image3D = importer->image3D(image, level)) {
arrayAppend(images3D, std::move(*image3D));
imported = true;
}
} else if(dimensions == 2) {
if(!importer->image2DCount()) {
Error{} << "No 2D images found in" << input << Debug::nospace << ". Specify -D1 or -D3 for 1D or 3D image conversion.";
return 1;
}
if(image >= importer->image2DCount()) {
Error{} << "2D image number" << image << "not found in" << input << Debug::nospace << ", the file has only" << importer->image2DCount() << "2D images";
return 1;
}
if(level >= importer->image2DLevelCount(image)) {
Error{} << "2D image" << image << "in" << input << "doesn't have a level number" << level << Debug::nospace << ", only" << importer->image2DLevelCount(image) << "levels";
} else {
Error{} << "Invalid --dimensions option:" << args.value("dimensions");
return 1;
}
imported = !!(image2D = importer->image2D(image, level));
} else if(dimensions == 3) {
if(!importer->image3DCount()) {
Error{} << "No 3D images found in" << input << Debug::nospace << ". Specify -D1 or -D2 for 1D or 2D image conversion.";
return 1;
if(!imported) {
Error{} << "Cannot import image" << image << Debug::nospace << ":" << Debug::nospace << level << "from" << input;
return 4;
}
if(image >= importer->image3DCount()) {
Error{} << "3D image number" << image << "not found in" << input << Debug::nospace << ", the file has only" << importer->image3DCount() << "3D images";
}
}
std::string output;
if(args.isSet("in-place")) {
/* Should have been checked in a graceful way above */
CORRADE_INTERNAL_ASSERT(args.arrayValueCount("input") == 1);
output = args.arrayValue("input", 0);
} else output = args.value("output");
Int outputDimensions;
/* Not strictly needed to be an Optional, acts as a sanity check that we
don't use something that wasn't populated proparly. */
Containers::Optional<Trade::ImageData1D> outputImage1D;
Containers::Optional<Trade::ImageData2D> outputImage2D;
Containers::Optional<Trade::ImageData3D> outputImage3D;
/* Combine multiple layers into an image of one dimension more */
if(args.isSet("layers")) {
if(dimensions == 1) {
if(!checkCommonFormatAndSize(args, images1D)) return 1;
outputDimensions = 2;
if(!images1D.front().isCompressed()) {
/* Allocate a new image */
/** @todo simplify once ImageData is able to allocate on its
own, including correct padding etc */
const Vector2i size{images1D.front().size()[0], Int(images1D.size())};
outputImage2D = Trade::ImageData2D{
/* Don't want to bother with row padding, it's temporary
anyway */
PixelStorage{}.setAlignment(1),
images1D.front().format(),
size,
Containers::Array<char>{NoInit, size.product()*images1D.front().pixelSize()}
};
/* Copy the pixel data over */
const Containers::StridedArrayView3D<char> outputPixels = outputImage2D->mutablePixels();
for(std::size_t i = 0; i != images1D.size(); ++i)
Utility::copy(images1D[i].pixels(), outputPixels[i]);
} else {
Error{} << "The --layers option isn't implemented for compressed images yet.";
return 1;
}
if(level >= importer->image3DLevelCount(image)) {
Error{} << "3D image" << image << "in" << input << "doesn't have a level number" << level << Debug::nospace << ", only" << importer->image3DLevelCount(image) << "levels";
} else if(dimensions == 2) {
if(!checkCommonFormatAndSize(args, images2D)) return 1;
outputDimensions = 3;
if(!images2D.front().isCompressed()) {
/* Allocate a new image */
/** @todo simplify once ImageData is able to allocate on its
own, including correct padding etc */
const Vector3i size{images2D.front().size(), Int(images2D.size())};
outputImage3D = Trade::ImageData3D{
/* Don't want to bother with row padding, it's temporary
anyway */
PixelStorage{}.setAlignment(1),
images2D.front().format(),
size,
Containers::Array<char>{NoInit, size.product()*images2D.front().pixelSize()}
};
/* Copy the pixel data over */
const Containers::StridedArrayView4D<char> outputPixels = outputImage3D->mutablePixels();
for(std::size_t i = 0; i != images2D.size(); ++i)
Utility::copy(images2D[i].pixels(), outputPixels[i]);
} else {
Error{} << "The --layers option isn't implemented for compressed images yet.";
return 1;
}
imported = !!(image3D = importer->image3D(image, level));
} else {
Error{} << "Invalid --dimensions option:" << args.value("dimensions");
} else if(dimensions == 3) {
Error{} << "The --layers option can be only used with 1D and 2D inputs, not 3D";
return 1;
}
if(!imported) {
Error{} << "Cannot import image" << image << Debug::nospace << ":" << Debug::nospace << level << "from" << input;
return 4;
}
}
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
const std::string output = args.value(args.isSet("in-place") ? "input" : "output");
/* Single image conversion, just pass the input through */
} else {
if(dimensions == 1) {
CORRADE_INTERNAL_ASSERT(images1D.size() == 1);
outputDimensions = 1;
outputImage1D = std::move(images1D.front());
} else if(dimensions == 2) {
CORRADE_INTERNAL_ASSERT(images2D.size() == 1);
outputDimensions = 2;
outputImage2D = std::move(images2D.front());
} else if(dimensions == 3) {
CORRADE_INTERNAL_ASSERT(images3D.size() == 1);
outputDimensions = 3;
outputImage3D = std::move(images3D.front());
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
}
{
Debug d;
@ -384,23 +562,26 @@ key=true; configuration subgroups are delimited with /.)")
d << "Writing raw image data of size";
else
d << "Converting image of size";
if(dimensions == 1)
d << image1D->size();
else if(dimensions == 2)
d << image2D->size();
else if(dimensions == 3)
d << image3D->size();
if(outputDimensions == 1)
d << outputImage1D->size();
else if(outputDimensions == 2)
d << outputImage2D->size();
else if(outputDimensions == 3)
d << outputImage3D->size();
else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
d << "and format";
if(dimensions == 1) {
if(image1D->isCompressed()) d << image1D->compressedFormat();
else d << image1D->format();
} else if(dimensions == 2) {
if(image2D->isCompressed()) d << image2D->compressedFormat();
else d << image2D->format();
} else if(dimensions == 3) {
if(image3D->isCompressed()) d << image3D->compressedFormat();
else d << image3D->format();
if(outputDimensions == 1) {
if(outputImage1D->isCompressed())
d << outputImage1D->compressedFormat();
else d << outputImage1D->format();
} else if(outputDimensions == 2) {
if(outputImage2D->isCompressed())
d << outputImage2D->compressedFormat();
else d << outputImage2D->format();
} else if(outputDimensions == 3) {
if(outputImage3D->isCompressed())
d << outputImage3D->compressedFormat();
else d << outputImage3D->format();
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
d << "to" << output;
}
@ -408,12 +589,12 @@ key=true; configuration subgroups are delimited with /.)")
/* Save raw data, if requested */
if(args.value("converter") == "raw") {
Containers::ArrayView<const char> data;
if(dimensions == 1)
data = image1D->data();
else if(dimensions == 2)
data = image2D->data();
else if(dimensions == 3)
data = image3D->data();
if(outputDimensions == 1)
data = outputImage1D->data();
else if(outputDimensions == 2)
data = outputImage3D->data();
else if(outputDimensions == 3)
data = outputImage3D->data();
else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
return Utility::Directory::write(output, data) ? 0 : 1;
}
@ -434,15 +615,15 @@ key=true; configuration subgroups are delimited with /.)")
/* Save output file */
bool converted;
if(dimensions == 1)
converted = converter->convertToFile(*image1D, output);
else if(dimensions == 2)
converted = converter->convertToFile(*image2D, output);
else if(dimensions == 3)
converted = converter->convertToFile(*image3D, output);
if(outputDimensions == 1)
converted = converter->convertToFile(*outputImage1D, output);
else if(outputDimensions == 2)
converted = converter->convertToFile(*outputImage2D, output);
else if(outputDimensions == 3)
converted = converter->convertToFile(*outputImage3D, output);
else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
if(!converted) {
Error() << "Cannot save file" << output;
Error{} << "Cannot save file" << output;
return 5;
}
}

Loading…
Cancel
Save