Browse Source

imageconverter: ability to combine multiple levels together.

I still have to figure out the interactions between --layers and
--levels and how to implicitly handle conversion of multi-level files.
pull/537/head
Vladimír Vondruš 5 years ago
parent
commit
e017ba135c
  1. 3
      doc/changelog.dox
  2. 155
      src/Magnum/Trade/imageconverter.cpp

3
doc/changelog.dox

@ -209,7 +209,8 @@ See also:
avoid accidentally clearing default flags potentially added in the future. avoid accidentally clearing default flags potentially added in the future.
- Ability to convert also 1D and 3D images with the - Ability to convert also 1D and 3D images with the
@ref magnum-imageconverter "magnum-imageconverter" utility, as well as @ref magnum-imageconverter "magnum-imageconverter" utility, as well as
combining layers into images of one dimension more combining layers into images of one dimension more and creating multi-level
images
@subsubsection changelog-latest-new-vk Vk library @subsubsection changelog-latest-new-vk Vk library

155
src/Magnum/Trade/imageconverter.cpp

@ -35,6 +35,7 @@
#include <Corrade/Utility/Directory.h> #include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/String.h> #include <Corrade/Utility/String.h>
#include "Magnum/ImageView.h"
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
#include "Magnum/Implementation/converterUtilities.h" #include "Magnum/Implementation/converterUtilities.h"
#include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AbstractImporter.h"
@ -71,8 +72,8 @@ magnum-imageconverter [-h|--help] [-I|--importer PLUGIN]
[-C|--converter PLUGIN] [--plugin-dir DIR] [-C|--converter PLUGIN] [--plugin-dir DIR]
[-i|--importer-options key=val,key2=val2,] [-i|--importer-options key=val,key2=val2,]
[-c|--converter-options key=val,key2=val2,] [-D|--dimensions N] [-c|--converter-options key=val,key2=val2,] [-D|--dimensions N]
[--image N] [--level N] [--layers] [--in-place] [--info] [-v|--verbose] [--image N] [--level N] [--layers] [--levels] [--in-place] [--info]
[--] input output [-v|--verbose] [--] input output
@endcode @endcode
Arguments: Arguments:
@ -96,6 +97,7 @@ Arguments:
- `--level N` --- image level to import (default: `0`) - `--level N` --- image level to import (default: `0`)
- `--layers` --- combine multiple layers into an image with one dimension - `--layers` --- combine multiple layers into an image with one dimension
more more
- `--levels` --- combine multiple image levels into a single file
- `--in-place` --- overwrite the input image with the output - `--in-place` --- overwrite the input image with the output
- `--info` --- print info about the input file and exit - `--info` --- print info about the input file and exit
- `-v`, `--verbose` --- verbose output from importer and converter plugins - `-v`, `--verbose` --- verbose output from importer and converter plugins
@ -197,6 +199,28 @@ template<UnsignedInt dimensions> bool checkCommonFormatAndSize(const Utility::Ar
return true; 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) { int main(int argc, char** argv) {
@ -212,6 +236,7 @@ int main(int argc, char** argv) {
.addOption("image", "0").setHelp("image", "image to import", "N") .addOption("image", "0").setHelp("image", "image to import", "N")
.addOption("level", "0").setHelp("level", "image level 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("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("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("info").setHelp("info", "print info about the input file and exit")
.addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from importer and converter plugins") .addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from importer and converter plugins")
@ -256,16 +281,24 @@ key=true; configuration subgroups are delimited with /.)")
} }
/* Mutually incompatible options */ /* Mutually incompatible options */
if(args.isSet("layers") && args.isSet("in-place")) { if(args.isSet("layers") && args.isSet("levels")) {
Error{} << "The --layers option can't be combined with --in-place"; 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; return 1;
} }
if(args.isSet("layers") && args.isSet("info")) { if((args.isSet("layers") || args.isSet("levels")) && args.isSet("in-place")) {
Error{} << "The --layers option can't be combined with --info"; Error{} << "The --layers / --levels option can't be combined with --in-place";
return 1; return 1;
} }
if(!args.isSet("layers") && args.arrayValueCount("input") > 1) { if((args.isSet("layers") || args.isSet("levels")) && args.isSet("info")) {
Error{} << "Multiple input files require the --layers option to be set"; 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; return 1;
} }
@ -469,11 +502,9 @@ key=true; configuration subgroups are delimited with /.)")
} else output = args.value("output"); } else output = args.value("output");
Int outputDimensions; Int outputDimensions;
/* Not strictly needed to be an Optional, acts as a sanity check that we Containers::Array<Trade::ImageData1D> outputImages1D;
don't use something that wasn't populated proparly. */ Containers::Array<Trade::ImageData2D> outputImages2D;
Containers::Optional<Trade::ImageData1D> outputImage1D; Containers::Array<Trade::ImageData3D> outputImages3D;
Containers::Optional<Trade::ImageData2D> outputImage2D;
Containers::Optional<Trade::ImageData3D> outputImage3D;
/* Combine multiple layers into an image of one dimension more */ /* Combine multiple layers into an image of one dimension more */
if(args.isSet("layers")) { if(args.isSet("layers")) {
@ -486,17 +517,17 @@ key=true; configuration subgroups are delimited with /.)")
/** @todo simplify once ImageData is able to allocate on its /** @todo simplify once ImageData is able to allocate on its
own, including correct padding etc */ own, including correct padding etc */
const Vector2i size{images1D.front().size()[0], Int(images1D.size())}; const Vector2i size{images1D.front().size()[0], Int(images1D.size())};
outputImage2D = Trade::ImageData2D{ arrayAppend(outputImages2D, InPlaceInit,
/* Don't want to bother with row padding, it's temporary /* Don't want to bother with row padding, it's temporary
anyway */ anyway */
PixelStorage{}.setAlignment(1), PixelStorage{}.setAlignment(1),
images1D.front().format(), images1D.front().format(),
size, size,
Containers::Array<char>{NoInit, size.product()*images1D.front().pixelSize()} Containers::Array<char>{NoInit, size.product()*images1D.front().pixelSize()}
}; );
/* Copy the pixel data over */ /* Copy the pixel data over */
const Containers::StridedArrayView3D<char> outputPixels = outputImage2D->mutablePixels(); const Containers::StridedArrayView3D<char> outputPixels = outputImages2D.front().mutablePixels();
for(std::size_t i = 0; i != images1D.size(); ++i) for(std::size_t i = 0; i != images1D.size(); ++i)
Utility::copy(images1D[i].pixels(), outputPixels[i]); Utility::copy(images1D[i].pixels(), outputPixels[i]);
@ -514,17 +545,17 @@ key=true; configuration subgroups are delimited with /.)")
/** @todo simplify once ImageData is able to allocate on its /** @todo simplify once ImageData is able to allocate on its
own, including correct padding etc */ own, including correct padding etc */
const Vector3i size{images2D.front().size(), Int(images2D.size())}; const Vector3i size{images2D.front().size(), Int(images2D.size())};
outputImage3D = Trade::ImageData3D{ arrayAppend(outputImages3D, InPlaceInit,
/* Don't want to bother with row padding, it's temporary /* Don't want to bother with row padding, it's temporary
anyway */ anyway */
PixelStorage{}.setAlignment(1), PixelStorage{}.setAlignment(1),
images2D.front().format(), images2D.front().format(),
size, size,
Containers::Array<char>{NoInit, size.product()*images2D.front().pixelSize()} Containers::Array<char>{NoInit, size.product()*images2D.front().pixelSize()}
}; );
/* Copy the pixel data over */ /* Copy the pixel data over */
const Containers::StridedArrayView4D<char> outputPixels = outputImage3D->mutablePixels(); const Containers::StridedArrayView4D<char> outputPixels = outputImages3D.front().mutablePixels();
for(std::size_t i = 0; i != images2D.size(); ++i) for(std::size_t i = 0; i != images2D.size(); ++i)
Utility::copy(images2D[i].pixels(), outputPixels[i]); Utility::copy(images2D[i].pixels(), outputPixels[i]);
@ -539,20 +570,37 @@ key=true; configuration subgroups are delimited with /.)")
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
/* Multi-level conversion, verify that all have the same format and pass
the input through */
} else if(args.isSet("levels")) {
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();
/* Single image conversion, just pass the input through */ /* Single image conversion, just pass the input through */
} else { } else {
if(dimensions == 1) { if(dimensions == 1) {
CORRADE_INTERNAL_ASSERT(images1D.size() == 1); CORRADE_INTERNAL_ASSERT(images1D.size() == 1);
outputDimensions = 1; outputDimensions = 1;
outputImage1D = std::move(images1D.front()); arrayAppend(outputImages1D, std::move(images1D.front()));
} else if(dimensions == 2) { } else if(dimensions == 2) {
CORRADE_INTERNAL_ASSERT(images2D.size() == 1); CORRADE_INTERNAL_ASSERT(images2D.size() == 1);
outputDimensions = 2; outputDimensions = 2;
outputImage2D = std::move(images2D.front()); arrayAppend(outputImages2D, std::move(images2D.front()));
} else if(dimensions == 3) { } else if(dimensions == 3) {
CORRADE_INTERNAL_ASSERT(images3D.size() == 1); CORRADE_INTERNAL_ASSERT(images3D.size() == 1);
outputDimensions = 3; outputDimensions = 3;
outputImage3D = std::move(images3D.front()); arrayAppend(outputImages3D, std::move(images3D.front()));
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
} }
@ -562,40 +610,51 @@ key=true; configuration subgroups are delimited with /.)")
d << "Writing raw image data of size"; d << "Writing raw image data of size";
else else
d << "Converting image of size"; d << "Converting image of size";
if(outputDimensions == 1) if(outputDimensions == 1) {
d << outputImage1D->size(); d << outputImages1D.front().size();
else if(outputDimensions == 2) if(outputImages1D.size() > 1)
d << outputImage2D->size(); d << "(and" << outputImages1D.size() - 1 << "more levels)";
else if(outputDimensions == 3) } else if(outputDimensions == 2) {
d << outputImage3D->size(); 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(); else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
d << "and format"; d << "and format";
if(outputDimensions == 1) { if(outputDimensions == 1) {
if(outputImage1D->isCompressed()) if(outputImages1D.front().isCompressed())
d << outputImage1D->compressedFormat(); d << outputImages1D.front().compressedFormat();
else d << outputImage1D->format(); else d << outputImages1D.front().format();
} else if(outputDimensions == 2) { } else if(outputDimensions == 2) {
if(outputImage2D->isCompressed()) if(outputImages2D.front().isCompressed())
d << outputImage2D->compressedFormat(); d << outputImages2D.front().compressedFormat();
else d << outputImage2D->format(); else d << outputImages2D.front().format();
} else if(outputDimensions == 3) { } else if(outputDimensions == 3) {
if(outputImage3D->isCompressed()) if(outputImages3D.front().isCompressed())
d << outputImage3D->compressedFormat(); d << outputImages3D.front().compressedFormat();
else d << outputImage3D->format(); else d << outputImages3D.front().format();
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
d << "to" << output; d << "to" << output;
} }
/* Save raw data, if requested */ /* Save raw data, if requested. Only for single-level images as the data
layout would be messed up otherwise. */
if(args.value("converter") == "raw") { if(args.value("converter") == "raw") {
Containers::ArrayView<const char> data; Containers::ArrayView<const char> data;
if(outputDimensions == 1) if(outputDimensions == 1) {
data = outputImage1D->data(); CORRADE_INTERNAL_ASSERT(outputImages1D.size() == 1);
else if(outputDimensions == 2) data = outputImages1D.front().data();
data = outputImage3D->data(); } else if(outputDimensions == 2) {
else if(outputDimensions == 3) CORRADE_INTERNAL_ASSERT(outputImages2D.size() == 1);
data = outputImage3D->data(); data = outputImages2D.front().data();
else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); } 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; return Utility::Directory::write(output, data) ? 0 : 1;
} }
@ -616,11 +675,11 @@ key=true; configuration subgroups are delimited with /.)")
/* Save output file */ /* Save output file */
bool converted; bool converted;
if(outputDimensions == 1) if(outputDimensions == 1)
converted = converter->convertToFile(*outputImage1D, output); converted = convertOneOrMoreImages(*converter, outputImages1D, output);
else if(outputDimensions == 2) else if(outputDimensions == 2)
converted = converter->convertToFile(*outputImage2D, output); converted = convertOneOrMoreImages(*converter, outputImages2D, output);
else if(outputDimensions == 3) else if(outputDimensions == 3)
converted = converter->convertToFile(*outputImage3D, output); converted = convertOneOrMoreImages(*converter, outputImages3D, output);
else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
if(!converted) { if(!converted) {
Error{} << "Cannot save file" << output; Error{} << "Cannot save file" << output;

Loading…
Cancel
Save