Visual Servoing Platform  version 3.6.1 under development (2025-03-14)
vpImageIoTinyEXR.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  *
30  * Description:
31  * TinyEXR backend for EXR image I/O operations.
32  */
33 
40 #include <visp3/core/vpConfig.h>
41 
42 #if defined(VISP_HAVE_STBIMAGE) && defined(VISP_HAVE_TINYEXR)
43 
44 #include "vpImageIoBackend.h"
45 
46 #define TINYEXR_USE_MINIZ 0
47 #define TINYEXR_USE_STB_ZLIB 1
48 #include <stb_image.h>
49 #include <stb_image_write.h>
50 
51 #define TINYEXR_IMPLEMENTATION
52 #include <tinyexr.h>
53 
54 BEGIN_VISP_NAMESPACE
55 
56 void readEXRTiny(vpImage<float> &I, const std::string &filename)
57 {
58  EXRVersion exr_version;
59 
60  int ret = ParseEXRVersionFromFile(&exr_version, filename.c_str());
61  if (ret != 0) {
62  throw(vpImageException(vpImageException::ioError, "Error: Invalid EXR file %s", filename.c_str()));
63  }
64 
65  if (exr_version.multipart) {
66  // must be multipart flag is false.
67  throw(vpImageException(vpImageException::ioError, "Error: Multipart EXR images are not supported."));
68  }
69 
70  EXRHeader exr_header;
71  InitEXRHeader(&exr_header);
72 
73  const char *err = nullptr; // or `nullptr` in C++11 or later.
74  ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, filename.c_str(), &err);
75  if (ret != 0) {
76  std::string err_msg(err);
77  FreeEXRErrorMessage(err); // free's buffer for an error message
78  throw(vpImageException(vpImageException::ioError, "Error: Unable to parse EXR header from %s : %s", filename.c_str(), err_msg.c_str()));
79  }
80 
81  // Read HALF channel as FLOAT.
82  for (int i = 0; i < exr_header.num_channels; ++i) {
83  if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
84  exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
85  }
86  }
87 
88  EXRImage exr_image;
89  InitEXRImage(&exr_image);
90 
91  ret = LoadEXRImageFromFile(&exr_image, &exr_header, filename.c_str(), &err);
92 
93  if (ret != 0) {
94  std::string err_msg(err);
95  FreeEXRHeader(&exr_header);
96  FreeEXRErrorMessage(err); // free's buffer for an error message
97  throw(vpImageException(vpImageException::ioError, "Error: Unable to load EXR image from %s : %s", filename.c_str(), err_msg.c_str()));
98  }
99 
100  // `exr_image.images` will be filled when EXR is scanline format.
101  // `exr_image.tiled` will be filled when EXR is tiled format.
102  if (exr_image.images) {
103  I.resize(exr_image.height, exr_image.width);
104  memcpy(I.bitmap, exr_image.images[0], exr_image.height*exr_image.width*sizeof(float));
105  }
106  else if (exr_image.tiles) {
107  I.resize(exr_image.height, exr_image.width);
108  size_t data_width = static_cast<size_t>(exr_header.data_window.max_x - exr_header.data_window.min_x + 1);
109 
110  for (int tile_idx = 0; tile_idx < exr_image.num_tiles; ++tile_idx) {
111  int sx = exr_image.tiles[tile_idx].offset_x * exr_header.tile_size_x;
112  int sy = exr_image.tiles[tile_idx].offset_y * exr_header.tile_size_y;
113  int ex = exr_image.tiles[tile_idx].offset_x * exr_header.tile_size_x + exr_image.tiles[tile_idx].width;
114  int ey = exr_image.tiles[tile_idx].offset_y * exr_header.tile_size_y + exr_image.tiles[tile_idx].height;
115 
116  for (unsigned int y = 0; y < static_cast<unsigned int>(ey - sy); ++y) {
117  for (unsigned int x = 0; x < static_cast<unsigned int>(ex - sx); ++x) {
118  const float *src_image = reinterpret_cast<const float *>(exr_image.tiles[tile_idx].images[0]);
119  I.bitmap[(y + sy) * data_width + (x + sx)] = src_image[y * exr_header.tile_size_x + x];
120  }
121  }
122  }
123  }
124 
125  FreeEXRImage(&exr_image);
126  FreeEXRHeader(&exr_header);
127 }
128 
129 void readEXRTiny(vpImage<vpRGBf> &I, const std::string &filename)
130 {
131  EXRVersion exr_version;
132 
133  int ret = ParseEXRVersionFromFile(&exr_version, filename.c_str());
134  if (ret != 0) {
135  throw(vpImageException(vpImageException::ioError, "Error: Invalid EXR file %s", filename.c_str()));
136  }
137 
138  if (exr_version.multipart) {
139  // must be multipart flag is false.
140  throw(vpImageException(vpImageException::ioError, "Error: Multipart EXR images are not supported."));
141  }
142 
143  EXRHeader exr_header;
144  InitEXRHeader(&exr_header);
145 
146  const char *err = nullptr; // or `nullptr` in C++11 or later.
147  ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, filename.c_str(), &err);
148  if (ret != 0) {
149  std::string err_msg(err);
150  FreeEXRErrorMessage(err); // free's buffer for an error message
151  throw(vpImageException(vpImageException::ioError, "Error: Unable to parse EXR header from %s : %s", filename.c_str(), err_msg.c_str()));
152  }
153 
154  // Read HALF channel as FLOAT.
155  for (int i = 0; i < exr_header.num_channels; ++i) {
156  if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
157  exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
158  }
159  }
160 
161  EXRImage exr_image;
162  InitEXRImage(&exr_image);
163 
164  ret = LoadEXRImageFromFile(&exr_image, &exr_header, filename.c_str(), &err);
165 
166  if (ret != 0) {
167  std::string err_msg(err);
168  FreeEXRHeader(&exr_header);
169  FreeEXRErrorMessage(err); // free's buffer for an error message
170  throw(vpImageException(vpImageException::ioError, "Error: Unable to load EXR image from %s : %s", filename.c_str(), err_msg.c_str()));
171  }
172 
173  // `exr_image.images` will be filled when EXR is scanline format.
174  // `exr_image.tiled` will be filled when EXR is tiled format.
175  if (exr_image.images) {
176  I.resize(exr_image.height, exr_image.width);
177  for (int i = 0; i < exr_image.height; ++i) {
178  for (int j = 0; j < exr_image.width; ++j) {
179  I[i][j].R = reinterpret_cast<float **>(exr_image.images)[2][i * exr_image.width + j];
180  I[i][j].G = reinterpret_cast<float **>(exr_image.images)[1][i * exr_image.width + j];
181  I[i][j].B = reinterpret_cast<float **>(exr_image.images)[0][i * exr_image.width + j];
182  }
183  }
184  }
185  else if (exr_image.tiles) {
186  I.resize(exr_image.height, exr_image.width);
187  size_t data_width = static_cast<size_t>(exr_header.data_window.max_x - exr_header.data_window.min_x + 1);
188 
189  for (int tile_idx = 0; tile_idx < exr_image.num_tiles; ++tile_idx) {
190  int sx = exr_image.tiles[tile_idx].offset_x * exr_header.tile_size_x;
191  int sy = exr_image.tiles[tile_idx].offset_y * exr_header.tile_size_y;
192  int ex = exr_image.tiles[tile_idx].offset_x * exr_header.tile_size_x + exr_image.tiles[tile_idx].width;
193  int ey = exr_image.tiles[tile_idx].offset_y * exr_header.tile_size_y + exr_image.tiles[tile_idx].height;
194 
195  //for (size_t c = 0; c < static_cast<size_t>(exr_header.num_channels); ++c) {
196  // const float *src_image = reinterpret_cast<const float *>(exr_image.tiles[tile_idx].images[c]);
197  // for (size_t y = 0; y < static_cast<size_t>(ey - sy); ++y) {
198  // for (size_t x = 0; x < static_cast<size_t>(ex - sx); ++x) {
199  // reinterpret_cast<float *>(I.bitmap)[(y + sy) * data_width * 3 + (x + sx) * 3 + c] = src_image[y * exr_header.tile_size_x + x];
200  // }
201  // }
202  //}
203 
204  for (unsigned int y = 0; y < static_cast<unsigned int>(ey - sy); ++y) {
205  for (unsigned int x = 0; x < static_cast<unsigned int>(ex - sx); ++x) {
206  for (unsigned int c = 0; c < 3; ++c) {
207  const float *src_image = reinterpret_cast<const float *>(exr_image.tiles[tile_idx].images[c]);
208  reinterpret_cast<float *>(I.bitmap)[(y + sy) * data_width * 3 + (x + sx) * 3 + c] = src_image[y * exr_header.tile_size_x + x];
209  }
210  }
211  }
212  }
213  }
214 
215  FreeEXRImage(&exr_image);
216  FreeEXRHeader(&exr_header);
217 }
218 
219 void writeEXRTiny(const vpImage<float> &I, const std::string &filename)
220 {
221  EXRHeader header;
222  InitEXRHeader(&header);
223 
224  EXRImage image;
225  InitEXRImage(&image);
226 
227  image.num_channels = 1;
228 
229  image.images = (unsigned char **)&I.bitmap;
230  image.width = I.getWidth();
231  image.height = I.getHeight();
232 
233  header.num_channels = 1;
234  header.channels = (EXRChannelInfo *)malloc(sizeof(EXRChannelInfo) * header.num_channels);
235  // Must be (A)BGR order, since most of EXR viewers expect this channel order.
236  strncpy(header.channels[0].name, "Y", 255); header.channels[0].name[strlen("Y")] = '\0';
237 
238  header.pixel_types = (int *)malloc(sizeof(int) * header.num_channels);
239  header.requested_pixel_types = (int *)malloc(sizeof(int) * header.num_channels);
240  header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
241  for (int i = 0; i < header.num_channels; ++i) {
242  header.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image
243  header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of output image to be stored in .EXR
244  }
245 
246  const char *err = nullptr; // or nullptr in C++11 or later.
247  int ret = SaveEXRImageToFile(&image, &header, filename.c_str(), &err);
248  if (ret != TINYEXR_SUCCESS) {
249  std::string err_msg(err);
250  FreeEXRErrorMessage(err); // free's buffer for an error message
251  free(header.channels);
252  free(header.requested_pixel_types);
253  free(header.pixel_types);
254  throw(vpImageException(vpImageException::ioError, "Error: Unable to save EXR image to %s : %s", filename.c_str(), err_msg.c_str()));
255  }
256 
257  free(header.channels);
258  free(header.requested_pixel_types);
259  free(header.pixel_types);
260 }
261 
262 void writeEXRTiny(const vpImage<vpRGBf> &I, const std::string &filename)
263 {
264  EXRHeader header;
265  InitEXRHeader(&header);
266 
267  EXRImage image;
268  InitEXRImage(&image);
269 
270  image.num_channels = 3;
271 
272  std::vector<float> images[3];
273  images[0].resize(I.getSize());
274  images[1].resize(I.getSize());
275  images[2].resize(I.getSize());
276 
277  // Split RGBRGBRGB... into R, G and B layer
278  for (unsigned int i = 0; i < I.getSize(); ++i) {
279  images[0][i] = I.bitmap[i].R;
280  images[1][i] = I.bitmap[i].G;
281  images[2][i] = I.bitmap[i].B;
282  }
283 
284  float *image_ptr[3];
285  image_ptr[0] = &(images[2].at(0)); // B
286  image_ptr[1] = &(images[1].at(0)); // G
287  image_ptr[2] = &(images[0].at(0)); // R
288 
289  image.images = (unsigned char **)image_ptr;
290  image.width = I.getWidth();
291  image.height = I.getHeight();
292 
293  header.num_channels = 3;
294  header.channels = (EXRChannelInfo *)malloc(sizeof(EXRChannelInfo) * header.num_channels);
295  // Must be (A)BGR order, since most of EXR viewers expect this channel order.
296  strncpy(header.channels[0].name, "B", 255); header.channels[0].name[strlen("B")] = '\0';
297  strncpy(header.channels[1].name, "G", 255); header.channels[1].name[strlen("G")] = '\0';
298  strncpy(header.channels[2].name, "R", 255); header.channels[2].name[strlen("R")] = '\0';
299 
300  header.pixel_types = (int *)malloc(sizeof(int) * header.num_channels);
301  header.requested_pixel_types = (int *)malloc(sizeof(int) * header.num_channels);
302  header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
303  for (int i = 0; i < header.num_channels; ++i) {
304  header.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image
305  header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of output image to be stored in .EXR
306  }
307 
308  const char *err = nullptr; // or nullptr in C++11 or later.
309  int ret = SaveEXRImageToFile(&image, &header, filename.c_str(), &err);
310  if (ret != TINYEXR_SUCCESS) {
311  std::string err_msg(err);
312  FreeEXRErrorMessage(err); // free's buffer for an error message
313  free(header.channels);
314  free(header.requested_pixel_types);
315  free(header.pixel_types);
316  throw(vpImageException(vpImageException::ioError, "Error: Unable to save EXR image to %s : %s", filename.c_str(), err_msg.c_str()));
317  }
318 
319  free(header.channels);
320  free(header.requested_pixel_types);
321  free(header.pixel_types);
322 }
323 
324 END_VISP_NAMESPACE
325 
326 #endif
Error that can be emitted by the vpImage class and its derivatives.
@ ioError
Image io error.
unsigned int getWidth() const
Definition: vpImage.h:242
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
Definition: vpImage.h:544
unsigned int getSize() const
Definition: vpImage.h:221
Type * bitmap
points toward the bitmap
Definition: vpImage.h:135
unsigned int getHeight() const
Definition: vpImage.h:181