|
12 | 12 | #include <cstddef> |
13 | 13 | #include <stdexcept> |
14 | 14 | #include <string> |
| 15 | +#include <memory> |
15 | 16 |
|
16 | 17 | #include <xtensor/xarray.hpp> |
| 18 | +#include <xtensor/xmath.hpp> |
17 | 19 | #include <xtensor/xeval.hpp> |
18 | 20 |
|
19 | 21 | #include "xtensor_io_config.hpp" |
@@ -46,73 +48,128 @@ namespace xt |
46 | 48 | template <class T = unsigned char> |
47 | 49 | xarray<T> load_image(std::string filename) |
48 | 50 | { |
49 | | - // TODO handle types better |
50 | | - OIIO::ImageInput* in = OIIO::ImageInput::open(filename); |
| 51 | + auto close_file = [](OIIO::ImageInput * file) |
| 52 | + { |
| 53 | + file->close(); |
| 54 | + OIIO::ImageInput::destroy(file); |
| 55 | + }; |
| 56 | + |
| 57 | + std::unique_ptr<OIIO::ImageInput, decltype(close_file)> in(OIIO::ImageInput::open(filename), close_file); |
51 | 58 | if (!in) |
52 | 59 | { |
53 | | - // something went wrong |
54 | | - throw std::runtime_error("Error reading image."); |
| 60 | + throw std::runtime_error("load_image(): Error reading image '" + filename + "'."); |
55 | 61 | } |
| 62 | + |
56 | 63 | const OIIO::ImageSpec& spec = in->spec(); |
57 | | - int xres = spec.width; |
58 | | - int yres = spec.height; |
59 | | - int channels = spec.nchannels; |
60 | 64 |
|
61 | 65 | // allocate memory |
62 | 66 | auto image = xarray<T>::from_shape({static_cast<std::size_t>(spec.height), |
63 | 67 | static_cast<std::size_t>(spec.width), |
64 | 68 | static_cast<std::size_t>(spec.nchannels)}); |
65 | 69 |
|
66 | | - in->read_image(OIIO::TypeDesc::UINT8, image.raw_data()); |
67 | | - in->close(); |
68 | | - OIIO::ImageInput::destroy(in); |
| 70 | + in->read_image(OIIO::BaseTypeFromC<T>::value, image.raw_data()); |
69 | 71 |
|
70 | 72 | return image; |
71 | 73 | } |
72 | 74 |
|
| 75 | + /** \brief Pass options to dump_image(). |
| 76 | + */ |
| 77 | + struct dump_image_options |
| 78 | + { |
| 79 | + /** \brief Initialize options to default values. |
| 80 | + */ |
| 81 | + dump_image_options() |
| 82 | + : spec(0,0,0) |
| 83 | + , autoconvert(true) |
| 84 | + { |
| 85 | + spec.attribute("CompressionQuality", 90); |
| 86 | + } |
| 87 | + |
| 88 | + /** \brief Forward an attribute to an OpenImageIO ImageSpec. |
| 89 | +
|
| 90 | + See the documentation of OIIO::ImageSpec::attribute() for a list |
| 91 | + of supported attributes. |
| 92 | +
|
| 93 | + Default: "CompressionQuality" = 90 |
| 94 | + */ |
| 95 | + template <class T> |
| 96 | + dump_image_options & attribute(OIIO::string_view name, T const & v) |
| 97 | + { |
| 98 | + spec.attribute(name, v); |
| 99 | + return *this; |
| 100 | + } |
| 101 | + |
| 102 | + OIIO::ImageSpec spec; |
| 103 | + bool autoconvert; |
| 104 | + }; |
73 | 105 |
|
74 | 106 | /** |
75 | 107 | * Save image to disk. |
76 | 108 | * The desired image format is deduced from ``filename``. |
77 | 109 | * Supported formats are those supported by OpenImageIO. |
78 | 110 | * Most common formats are supported (jpg, png, gif, bmp, tiff). |
| 111 | + * The shape of the array must be ``HEIGHT x WIDTH`` or ``HEIGHT x WIDTH x CHANNELS``. |
79 | 112 | * |
80 | 113 | * @param filename The path to the desired file |
81 | 114 | * @param data Image data |
82 | | - * @param quality If saving in a compressed format such as JPEG, setting the quality |
83 | | - * will select how strong the compression will reduce the image quality. |
84 | | - * Quality is an integer from 100 (best image quality, less compression) |
85 | | - * to 0 (worst image quality, low filesize). |
| 115 | + * @param options Pass a dump_image_options object to fine-tune image export |
86 | 116 | */ |
87 | 117 | template <class E> |
88 | | - void dump_image(std::string filename, const xexpression<E>& data, int quality = 90) |
| 118 | + void dump_image(std::string filename, const xexpression<E>& data, |
| 119 | + dump_image_options const & options = dump_image_options()) |
89 | 120 | { |
90 | | - auto&& ex = eval(data.derived_cast()); |
| 121 | + using value_type = typename std::decay_t<decltype(data.derived_cast())>::value_type; |
91 | 122 |
|
92 | | - XTENSOR_PRECONDITION(ex.dimension() == 2 || ex.dimension() == 3, |
| 123 | + auto shape = data.derived_cast().shape(); |
| 124 | + XTENSOR_PRECONDITION(shape.size() == 2 || shape.size() == 3, |
93 | 125 | "dump_image(): data must have 2 or 3 dimensions (channels must be last)."); |
94 | 126 |
|
95 | | - OIIO::ImageOutput* out = OIIO::ImageOutput::create(filename); |
| 127 | + auto close_file = [](OIIO::ImageOutput * file) |
| 128 | + { |
| 129 | + file->close(); |
| 130 | + OIIO::ImageOutput::destroy(file); |
| 131 | + }; |
| 132 | + |
| 133 | + std::unique_ptr<OIIO::ImageOutput, decltype(close_file)> out(OIIO::ImageOutput::create(filename), close_file); |
96 | 134 | if (!out) |
97 | 135 | { |
98 | | - // something went wrong |
99 | 136 | throw std::runtime_error("dump_image(): Error opening file '" + filename + "' to write image."); |
100 | 137 | } |
101 | 138 |
|
102 | | - int channels = ex.dimension() == 2 |
103 | | - ? 1 |
104 | | - : static_cast<int>(ex.shape()[2]); |
| 139 | + OIIO::ImageSpec spec = options.spec; |
105 | 140 |
|
106 | | - OIIO::ImageSpec spec(static_cast<int>(ex.shape()[1]), |
107 | | - static_cast<int>(ex.shape()[0]), |
108 | | - channels, OIIO::TypeDesc::UINT8); |
109 | | - |
110 | | - spec.attribute("CompressionQuality", quality); |
| 141 | + spec.width = static_cast<int>(shape[1]); |
| 142 | + spec.height = static_cast<int>(shape[0]); |
| 143 | + spec.nchannels = (shape.size() == 2) |
| 144 | + ? 1 |
| 145 | + : static_cast<int>(shape[2]); |
| 146 | + spec.format = OIIO::BaseTypeFromC<value_type>::value; |
111 | 147 |
|
112 | 148 | out->open(filename, spec); |
113 | | - out->write_image(OIIO::TypeDesc::UINT8, ex.raw_data()); |
114 | | - out->close(); |
115 | | - OIIO::ImageOutput::destroy(out); |
| 149 | + |
| 150 | + auto && ex = eval(data.derived_cast()); |
| 151 | + if(out->spec().format != OIIO::BaseTypeFromC<value_type>::value) |
| 152 | + { |
| 153 | + // OpenImageIO changed the target type because the file format doesn't support value_type. |
| 154 | + // It will do automatic conversion, but the data should be in the range 0...1 |
| 155 | + // for good results. |
| 156 | + auto mM = minmax(ex)(); |
| 157 | + |
| 158 | + if(mM[0] != mM[1]) |
| 159 | + { |
| 160 | + using real_t = real_promote_type_t<value_type>; |
| 161 | + auto && normalized = eval((real_t(1.0) / (mM[1] - mM[0])) * (ex - mM[0])); |
| 162 | + out->write_image(OIIO::BaseTypeFromC<real_t>::value, normalized.raw_data()); |
| 163 | + } |
| 164 | + else |
| 165 | + { |
| 166 | + out->write_image(OIIO::BaseTypeFromC<value_type>::value, ex.raw_data()); |
| 167 | + } |
| 168 | + } |
| 169 | + else |
| 170 | + { |
| 171 | + out->write_image(OIIO::BaseTypeFromC<value_type>::value, ex.raw_data()); |
| 172 | + } |
116 | 173 | } |
117 | 174 | } |
118 | 175 |
|
|
0 commit comments