mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
719 lines
33 KiB
719 lines
33 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021 Vladimír Vondruš <mosra@centrum.cz> |
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a |
|
copy of this software and associated documentation files (the "Software"), |
|
to deal in the Software without restriction, including without limitation |
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|
and/or sell copies of the Software, and to permit persons to whom the |
|
Software is furnished to do so, subject to the following conditions: |
|
|
|
The above copyright notice and this permission notice shall be included |
|
in all copies or substantial portions of the Software. |
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
DEALINGS IN THE SOFTWARE. |
|
*/ |
|
|
|
#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> |
|
#include <Corrade/Utility/String.h> |
|
|
|
#include "Magnum/ImageView.h" |
|
#include "Magnum/PixelFormat.h" |
|
#include "Magnum/Implementation/converterUtilities.h" |
|
#include "Magnum/Trade/AbstractImporter.h" |
|
#include "Magnum/Trade/AbstractImageConverter.h" |
|
#include "Magnum/Trade/ImageData.h" |
|
#include "Magnum/Trade/Implementation/converterUtilities.h" |
|
|
|
namespace Magnum { |
|
|
|
/** @page magnum-imageconverter Image conversion utility |
|
@brief Converts images of different formats |
|
|
|
@m_footernavigation |
|
@m_keywords{magnum-imageconverter imageconverter} |
|
|
|
This utility is built if both `WITH_TRADE` and `WITH_IMAGECONVERTER` is enabled |
|
when building Magnum. To use this utility with CMake, you need to request the |
|
`imageconverter` component of the `Magnum` package and use the |
|
`Magnum::imageconverter` target for example in a custom command: |
|
|
|
@code{.cmake} |
|
find_package(Magnum REQUIRED imageconverter) |
|
|
|
add_custom_command(OUTPUT ... COMMAND Magnum::imageconverter ...) |
|
@endcode |
|
|
|
See @ref building, @ref cmake and the @ref Trade namespace for more |
|
information. |
|
|
|
@section magnum-imageconverter-usage Usage |
|
|
|
@code{.sh} |
|
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] [--layers] [--levels] [--in-place] [--info] |
|
[-v|--verbose] [--] input output |
|
@endcode |
|
|
|
Arguments: |
|
|
|
- `input` --- input image |
|
- `output` --- output image; ignored if `--info` is present, disallowed for |
|
`--in-place` |
|
- `-h`, `--help` --- display this help message and exit |
|
- `-I`, `--importer PLUGIN` --- image importer plugin (default: |
|
@ref Trade::AnyImageImporter "AnyImageImporter") |
|
- `-C`, `--converter PLUGIN` --- image converter plugin (default: |
|
@ref Trade::AnyImageConverter "AnyImageConverter") |
|
- `--plugin-dir DIR` --- override base plugin dir |
|
- `-i`, `--importer-options key=val,key2=val2,…` --- configuration options to |
|
pass to the importer |
|
- `-c`, `--converter-options key=val,key2=val2,…` --- configuration options |
|
to pass to the converter |
|
- `-D`, `--dimensions N` --- import and convert image of given dimensions |
|
(default: `2`) |
|
- `--image N` --- image to import (default: `0`) |
|
- `--level N` --- import given image level instead of all |
|
- `--layers` --- combine multiple layers into an image with one dimension |
|
more |
|
- `--levels` --- combine multiple image levels into a single file |
|
- `--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 |
|
|
|
Specifying `--importer raw:<format>` will treat the input as a raw |
|
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. |
|
|
|
The `-i` / `--importer-options` and `-c` / `--converter-options` arguments |
|
accept a comma-separated list of key/value pairs to set in the importer / |
|
converter plugin configuration. If the `=` character is omitted, it's |
|
equivalent to saying `key=true`; configuration subgroups are delimited with |
|
`/`. |
|
|
|
@section magnum-imageconverter-example Example usage |
|
|
|
Converting a JPEG file to a PNG: |
|
|
|
@code{.sh} |
|
magnum-imageconverter image.jpg image.png |
|
@endcode |
|
|
|
Creating a JPEG file with 95% quality from a PNG, by setting a |
|
@ref Trade-JpegImageConverter-configuration "plugin-specific configuration option". |
|
Note that currently the proxy @ref Trade::AnyImageImporter "AnyImageImporter" |
|
and @ref Trade::AnyImageConverter "AnyImageConverter" plugins don't know how to |
|
correctly propagate options to the target plugin, so you need to specify |
|
`--importer` / `--converter` explicitly when using the `-i` / `-c` options. |
|
|
|
@m_class{m-console-wrap} |
|
|
|
@code{.sh} |
|
magnum-imageconverter image.png image.jpg -c jpegQuality=0.95 --converter JpegImageConverter |
|
@endcode |
|
|
|
Extracting raw (uncompressed, compressed) data from a DDS file for manual |
|
inspection: |
|
|
|
@code{.sh} |
|
magnum-imageconverter image.dds --converter raw data.dat |
|
@endcode |
|
|
|
@see @ref magnum-sceneconverter |
|
*/ |
|
|
|
} |
|
|
|
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; |
|
} |
|
|
|
template<template<UnsignedInt, class> class View, UnsignedInt dimensions> bool convertOneOrMoreImages(Trade::AbstractImageConverter& converter, const Containers::Array<Trade::ImageData<dimensions>>& outputImages, const std::string& output) { |
|
Containers::Array<View<dimensions, const char>> views; |
|
arrayReserve(views, outputImages.size()); |
|
for(const Trade::ImageData<dimensions>& outputImage: outputImages) |
|
arrayAppend(views, View<dimensions, const char>{outputImage}); |
|
return converter.convertToFile(views, output); |
|
} |
|
|
|
template<UnsignedInt dimensions> bool convertOneOrMoreImages(Trade::AbstractImageConverter& converter, const Containers::Array<Trade::ImageData<dimensions>>& outputImages, const std::string& output) { |
|
/* If there's just one image, convert it using the single-level API. |
|
Otherwise the multi-level entrypoint would require the plugin to support |
|
multi-level conversion, and only some file formats have that. */ |
|
if(outputImages.size() == 1) |
|
return converter.convertToFile(outputImages.front(), output); |
|
|
|
CORRADE_INTERNAL_ASSERT(!outputImages.empty()); |
|
if(outputImages.front().isCompressed()) |
|
return convertOneOrMoreImages<CompressedImageView, dimensions>(converter, outputImages, output); |
|
else |
|
return convertOneOrMoreImages<ImageView, dimensions>(converter, outputImages, output); |
|
} |
|
|
|
} |
|
|
|
int main(int argc, char** argv) { |
|
Utility::Arguments args; |
|
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") |
|
.addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR") |
|
.addOption('i', "importer-options").setHelp("importer-options", "configuration options to pass to the importer", "key=val,key2=val2,…") |
|
.addOption('c', "converter-options").setHelp("converter-options", "configuration options to pass to the converter", "key=val,key2=val2,…") |
|
.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").setHelp("level", "import given image level instead of all", "N") |
|
.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").setHelp("info", "print info about the input file and exit") |
|
.addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from importer and converter plugins") |
|
.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(error == Utility::Arguments::ParseError::MissingArgument && |
|
key == "output" && (args.isSet("in-place") || args.isSet("info"))) |
|
return true; |
|
|
|
/* Handle all other errors as usual */ |
|
return false; |
|
}) |
|
.setGlobalHelp(R"(Converts images of different formats. |
|
|
|
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. |
|
|
|
The -i / --importer-options and -c / --converter-options arguments accept a |
|
comma-separated list of key/value pairs to set in the importer / converter |
|
plugin configuration. If the = character is omitted, it's equivalent to saying |
|
key=true; configuration subgroups are delimited with /.)") |
|
.parse(argc, argv); |
|
|
|
/* Generic checks */ |
|
if(!args.value<Containers::StringView>("output").isEmpty()) { |
|
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")) |
|
Warning{} << "Ignoring output file for --info:" << args.value<Containers::StringView>("output"); |
|
} |
|
|
|
/* Mutually incompatible options */ |
|
if(args.isSet("layers") && args.isSet("levels")) { |
|
Error{} << "The --layers and --levels options can't be used together. First combine layers of each level and then all levels in a second step."; |
|
return 1; |
|
} |
|
if((args.isSet("layers") || args.isSet("levels")) && args.isSet("in-place")) { |
|
Error{} << "The --layers / --levels option can't be combined with --in-place"; |
|
return 1; |
|
} |
|
if((args.isSet("layers") || args.isSet("levels")) && args.isSet("info")) { |
|
Error{} << "The --layers / --levels option can't be combined with --info"; |
|
return 1; |
|
} |
|
if(args.isSet("levels") && args.value("converter") == "raw") { |
|
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) { |
|
Error{} << "Multiple input files require the --layers / --levels 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])}; |
|
|
|
const Int dimensions = args.value<Int>("dimensions"); |
|
/** @todo make them array options as well? */ |
|
const UnsignedInt image = args.value<UnsignedInt>("image"); |
|
Containers::Optional<UnsignedInt> level; |
|
if(!args.value("level").empty()) level = args.value<UnsignedInt>("level"); |
|
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 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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
/* 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; |
|
} |
|
|
|
/* Open input file */ |
|
if(!importer->openFile(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()) { |
|
Error{} << "No images found in" << input; |
|
return 1; |
|
} |
|
|
|
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; |
|
} |
|
|
|
/* Import all levels of the input or just one if specified */ |
|
UnsignedInt minLevel, maxLevel; |
|
if(level) { |
|
minLevel = *level; |
|
maxLevel = *level + 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 { |
|
minLevel = 0; |
|
maxLevel = importer->image1DLevelCount(image); |
|
if(maxLevel > 1 && (args.isSet("layers") || args.isSet("levels") || args.value("converter") == "raw")) { |
|
Error{} << "Cannot use --layers / --levels or raw output with multi-level input images. Specify --level N to extract just one level from each."; |
|
return 1; |
|
} |
|
} |
|
for(; minLevel != maxLevel; ++minLevel) { |
|
if(Containers::Optional<Trade::ImageData1D> image1D = importer->image1D(image, minLevel)) { |
|
arrayAppend(images1D, std::move(*image1D)); |
|
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; |
|
} |
|
|
|
/* Import all levels of the input or just one if specified */ |
|
UnsignedInt minLevel, maxLevel; |
|
if(level) { |
|
minLevel = *level; |
|
maxLevel = *level + 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; |
|
} |
|
} else { |
|
minLevel = 0; |
|
maxLevel = importer->image2DLevelCount(image); |
|
if(maxLevel > 1 && (args.isSet("layers") || args.isSet("levels") || args.value("converter") == "raw")) { |
|
Error{} << "Cannot use --layers / --levels or raw output with multi-level input images. Specify --level N to extract just one level from each."; |
|
return 1; |
|
} |
|
} |
|
for(; minLevel != maxLevel; ++minLevel) { |
|
if(Containers::Optional<Trade::ImageData2D> image2D = importer->image2D(image, minLevel)) { |
|
arrayAppend(images2D, std::move(*image2D)); |
|
imported = true; |
|
} |
|
} |
|
|
|
} 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; |
|
} |
|
|
|
/* Import all levels of the input or just one if specified */ |
|
UnsignedInt minLevel, maxLevel; |
|
if(level) { |
|
minLevel = *level; |
|
maxLevel = *level + 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; |
|
} |
|
} else { |
|
minLevel = 0; |
|
maxLevel = importer->image3DLevelCount(image); |
|
if(maxLevel > 1 && (args.isSet("layers") || args.isSet("levels") || args.value("converter") == "raw")) { |
|
Error{} << "Cannot use --layers / --levels or raw output with multi-level input images. Specify --level N to extract just one level from each."; |
|
return 1; |
|
} |
|
} |
|
for(; minLevel != maxLevel; ++minLevel) { |
|
if(Containers::Optional<Trade::ImageData3D> image3D = importer->image3D(image, minLevel)) { |
|
arrayAppend(images3D, std::move(*image3D)); |
|
imported = true; |
|
} |
|
} |
|
|
|
} else { |
|
Error{} << "Invalid --dimensions option:" << args.value("dimensions"); |
|
return 1; |
|
} |
|
|
|
if(!imported) { |
|
Error{} << "Cannot import image" << image << Debug::nospace << ":" << Debug::nospace << level << "from" << input; |
|
return 4; |
|
} |
|
} |
|
} |
|
|
|
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; |
|
Containers::Array<Trade::ImageData1D> outputImages1D; |
|
Containers::Array<Trade::ImageData2D> outputImages2D; |
|
Containers::Array<Trade::ImageData3D> outputImages3D; |
|
|
|
/* 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())}; |
|
arrayAppend(outputImages2D, InPlaceInit, |
|
/* 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 = outputImages2D.front().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; |
|
} |
|
|
|
} 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())}; |
|
arrayAppend(outputImages3D, InPlaceInit, |
|
/* 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 = outputImages3D.front().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; |
|
} |
|
|
|
} else if(dimensions == 3) { |
|
Error{} << "The --layers option can be only used with 1D and 2D inputs, not 3D"; |
|
return 1; |
|
|
|
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); |
|
|
|
/* Single-image (potentially multi-level) conversion, verify that all have |
|
the same format and pass the input through. This happens either if |
|
--levels is set or if the (single) input image is multi-level. */ |
|
} else { |
|
if(dimensions == 1) { |
|
if(!checkCommonFormat(args, images1D)) return 1; |
|
outputDimensions = 1; |
|
outputImages1D = std::move(images1D); |
|
} else if(dimensions == 2) { |
|
if(!checkCommonFormat(args, images2D)) return 1; |
|
outputDimensions = 2; |
|
outputImages2D = std::move(images2D); |
|
} else if(dimensions == 3) { |
|
if(!checkCommonFormat(args, images3D)) return 1; |
|
outputDimensions = 3; |
|
outputImages3D = std::move(images3D); |
|
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); |
|
} |
|
|
|
{ |
|
Debug d; |
|
if(args.value("converter") == "raw") |
|
d << "Writing raw image data of size"; |
|
else |
|
d << "Converting image of size"; |
|
if(outputDimensions == 1) { |
|
d << outputImages1D.front().size(); |
|
if(outputImages1D.size() > 1) |
|
d << "(and" << outputImages1D.size() - 1 << "more levels)"; |
|
} else if(outputDimensions == 2) { |
|
d << outputImages2D.front().size(); |
|
if(outputImages2D.size() > 1) |
|
d << "(and" << outputImages2D.size() - 1 << "more levels)"; |
|
} else if(outputDimensions == 3) { |
|
d << outputImages3D.front().size(); |
|
if(outputImages3D.size() > 1) |
|
d << "(and" << outputImages3D.size() - 1 << "more levels)"; |
|
} |
|
else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); |
|
d << "and format"; |
|
if(outputDimensions == 1) { |
|
if(outputImages1D.front().isCompressed()) |
|
d << outputImages1D.front().compressedFormat(); |
|
else d << outputImages1D.front().format(); |
|
} else if(outputDimensions == 2) { |
|
if(outputImages2D.front().isCompressed()) |
|
d << outputImages2D.front().compressedFormat(); |
|
else d << outputImages2D.front().format(); |
|
} else if(outputDimensions == 3) { |
|
if(outputImages3D.front().isCompressed()) |
|
d << outputImages3D.front().compressedFormat(); |
|
else d << outputImages3D.front().format(); |
|
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); |
|
d << "to" << output; |
|
} |
|
|
|
/* Save raw data, if requested. Only for single-level images as the data |
|
layout would be messed up otherwise. */ |
|
if(args.value("converter") == "raw") { |
|
Containers::ArrayView<const char> data; |
|
if(outputDimensions == 1) { |
|
CORRADE_INTERNAL_ASSERT(outputImages1D.size() == 1); |
|
data = outputImages1D.front().data(); |
|
} else if(outputDimensions == 2) { |
|
CORRADE_INTERNAL_ASSERT(outputImages2D.size() == 1); |
|
data = outputImages2D.front().data(); |
|
} else if(outputDimensions == 3) { |
|
CORRADE_INTERNAL_ASSERT(outputImages3D.size() == 1); |
|
data = outputImages3D.front().data(); |
|
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); |
|
return Utility::Directory::write(output, data) ? 0 : 1; |
|
} |
|
|
|
/* Load converter plugin */ |
|
PluginManager::Manager<Trade::AbstractImageConverter> converterManager{ |
|
args.value("plugin-dir").empty() ? std::string{} : |
|
Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractImageConverter::pluginSearchPaths()[0])}; |
|
Containers::Pointer<Trade::AbstractImageConverter> converter = converterManager.loadAndInstantiate(args.value("converter")); |
|
if(!converter) { |
|
Debug{} << "Available converter plugins:" << Utility::String::join(converterManager.aliasList(), ", "); |
|
return 2; |
|
} |
|
|
|
/* Set options, if passed */ |
|
if(args.isSet("verbose")) converter->addFlags(Trade::ImageConverterFlag::Verbose); |
|
Implementation::setOptions(*converter, "AnyImageConverter", args.value("converter-options")); |
|
|
|
/* Save output file */ |
|
bool converted; |
|
if(outputDimensions == 1) |
|
converted = convertOneOrMoreImages(*converter, outputImages1D, output); |
|
else if(outputDimensions == 2) |
|
converted = convertOneOrMoreImages(*converter, outputImages2D, output); |
|
else if(outputDimensions == 3) |
|
converted = convertOneOrMoreImages(*converter, outputImages3D, output); |
|
else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); |
|
if(!converted) { |
|
Error{} << "Cannot save file" << output; |
|
return 5; |
|
} |
|
}
|
|
|