|
|
|
|
@ -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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|