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