1 /***************************************************************************
2 * Copyright (c) Wolf Vollprecht, Sylvain Corlay and Johan Mabille          *
3 * Copyright (c) QuantStack                                                 *
4 *                                                                          *
5 * Distributed under the terms of the BSD 3-Clause License.                 *
6 *                                                                          *
7 * The full license is in the file LICENSE, distributed with this software. *
8 ****************************************************************************/
9 
10 #ifndef XTENSOR_IO_XIMAGE_HPP
11 #define XTENSOR_IO_XIMAGE_HPP
12 
13 #include <cstddef>
14 #include <stdexcept>
15 #include <string>
16 #include <memory>
17 
18 #include <xtensor/xarray.hpp>
19 #include <xtensor/xmath.hpp>
20 #include <xtensor/xeval.hpp>
21 
22 #include "xtensor_io_config.hpp"
23 
24 #ifdef __CLING__
25     #pragma clang diagnostic push
26     // silencing warnings from OpenEXR 2.2.0 / OpenImageIO
27     #pragma clang diagnostic ignored "-Wdeprecated-register"
28     #pragma cling load("OpenImageIO")
29 #endif
30 
31 #include <OpenImageIO/imageio.h>
32 
33 #ifdef __CLING__
34     #pragma clang diagnostic pop
35 #endif
36 
37 namespace xt
38 {
39     /**
40      * Load an image from file at filename.
41      * Storage format is deduced from file ending.
42      *
43      * @param filename The path of the file to load
44      *
45      * @return xarray with image contents. The shape of the xarray
46      *         is ``HEIGHT x WIDTH x CHANNELS`` of the loaded image, where
47      *         ``CHANNELS`` are ordered in standard ``R G B (A)``.
48      */
49     template <class T = unsigned char>
load_image(std::string filename)50     xarray<T> load_image(std::string filename)
51     {
52         auto in(OIIO::ImageInput::open(filename));
53         if (!in)
54         {
55             throw std::runtime_error("load_image(): Error reading image '" + filename + "': " + OIIO::geterror());
56         }
57 
58         const OIIO::ImageSpec& spec = in->spec();
59 
60         // allocate memory
61         auto image = xarray<T>::from_shape({static_cast<std::size_t>(spec.height),
62                                             static_cast<std::size_t>(spec.width),
63                                             static_cast<std::size_t>(spec.nchannels)});
64 
65         in->read_image(OIIO::BaseTypeFromC<T>::value, image.data());
66 
67         in->close();
68 
69         return image;
70     }
71 
72         /** \brief Pass options to dump_image().
73         */
74     struct dump_image_options
75     {
76             /** \brief Initialize options to default values.
77             */
dump_image_optionsxt::dump_image_options78         dump_image_options()
79         : spec(0,0,0)
80         , autoconvert(true)
81         {
82             spec.attribute("CompressionQuality", 90);
83         }
84 
85             /** \brief Forward an attribute to an OpenImageIO ImageSpec.
86 
87                 See the documentation of OIIO::ImageSpec::attribute() for a list
88                 of supported attributes.
89 
90                 Default: "CompressionQuality" = 90
91             */
92         template <class T>
attributext::dump_image_options93         dump_image_options & attribute(OIIO::string_view name, T const & v)
94         {
95             spec.attribute(name, v);
96             return *this;
97         }
98 
99         OIIO::ImageSpec spec;
100         bool autoconvert;
101     };
102 
103     /**
104      * Save image to disk.
105      * The desired image format is deduced from ``filename``.
106      * Supported formats are those supported by OpenImageIO.
107      * Most common formats are supported (jpg, png, gif, bmp, tiff).
108      * The shape of the array must be ``HEIGHT x WIDTH`` or ``HEIGHT x WIDTH x CHANNELS``.
109      *
110      * @param filename The path to the desired file
111      * @param data Image data
112      * @param options Pass a dump_image_options object to fine-tune image export
113      */
114     template <class E>
dump_image(std::string filename,const xexpression<E> & data,dump_image_options const & options=dump_image_options ())115     void dump_image(std::string filename, const xexpression<E>& data,
116                     dump_image_options const & options = dump_image_options())
117     {
118         using value_type = typename std::decay_t<decltype(data.derived_cast())>::value_type;
119 
120         auto shape = data.derived_cast().shape();
121         XTENSOR_PRECONDITION(shape.size() == 2 || shape.size() == 3,
122             "dump_image(): data must have 2 or 3 dimensions (channels must be last).");
123 
124         auto out(OIIO::ImageOutput::create(filename));
125         if (!out)
126         {
127             throw std::runtime_error("dump_image(): Error opening file '" + filename + "' to write image: " + OIIO::geterror());
128         }
129 
130         OIIO::ImageSpec spec = options.spec;
131 
132         spec.width     = static_cast<int>(shape[1]);
133         spec.height    = static_cast<int>(shape[0]);
134         spec.nchannels = (shape.size() == 2)
135                            ? 1
136                            : static_cast<int>(shape[2]);
137         spec.format    = OIIO::BaseTypeFromC<value_type>::value;
138 
139         out->open(filename, spec);
140 
141         auto && ex = eval(data.derived_cast());
142         if(out->spec().format != OIIO::BaseTypeFromC<value_type>::value)
143         {
144             // OpenImageIO changed the target type because the file format doesn't support value_type.
145             // It will do automatic conversion, but the data should be in the range 0...1
146             // for good results.
147             auto mM = minmax(ex)();
148 
149             if(mM[0] != mM[1])
150             {
151                 using real_t = xtl::real_promote_type_t<value_type>;
152                 auto && normalized = eval((real_t(1.0) / (mM[1] - mM[0])) * (ex - mM[0]));
153                 out->write_image(OIIO::BaseTypeFromC<real_t>::value, normalized.data());
154             }
155             else
156             {
157                 out->write_image(OIIO::BaseTypeFromC<value_type>::value, ex.data());
158             }
159         }
160         else
161         {
162             out->write_image(OIIO::BaseTypeFromC<value_type>::value, ex.data());
163         }
164         out->close();
165     }
166 }
167 
168 #endif
169