ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ImageIO.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ImageIO.h"
9 
10 // clang-format off
11 #include <cstddef>
12 #include <cstdio>
13 #include <jpeglib.h> // Include after cstddef to define size_t
14 #include <png.h>
15 // clang-format on
16 #include <FileSystem.h>
17 #include <Logging.h>
18 
19 #include <array>
20 #include <fstream>
21 #include <unordered_map>
22 
23 namespace cloudViewer {
24 
25 namespace {
26 using namespace io;
27 
30 void jpeg_error_throw(j_common_ptr p_cinfo) {
31  if (p_cinfo->is_decompressor)
32  jpeg_destroy_decompress(
33  reinterpret_cast<jpeg_decompress_struct *>(p_cinfo));
34  else
35  jpeg_destroy_compress(
36  reinterpret_cast<jpeg_compress_struct *>(p_cinfo));
37  char buffer[JMSG_LENGTH_MAX];
38  (*p_cinfo->err->format_message)(p_cinfo, buffer);
39  throw std::runtime_error(buffer);
40 }
41 
42 void SetPNGImageFromImage(const geometry::Image &image,
43  int quality,
44  png_image &pngimage) {
45  pngimage.width = image.width_;
46  pngimage.height = image.height_;
47  pngimage.format = pngimage.flags = 0;
48 
49  if (image.bytes_per_channel_ == 2) {
50  pngimage.format |= PNG_FORMAT_FLAG_LINEAR;
51  }
52  if (image.num_of_channels_ >= 3) {
53  pngimage.format |= PNG_FORMAT_FLAG_COLOR;
54  }
55  if (image.num_of_channels_ == 4) {
56  pngimage.format |= PNG_FORMAT_FLAG_ALPHA;
57  }
58  if (quality <= 2) {
59  pngimage.flags |= PNG_IMAGE_FLAG_FAST;
60  }
61 }
62 
63 using signature_decoder_t =
64  std::pair<std::string,
65  std::function<bool(const std::string &, geometry::Image &)>>;
66 static const std::array<signature_decoder_t, 2> signature_decoder_list{
67  {{"\x89\x50\x4e\x47\xd\xa\x1a\xa", ReadImageFromPNG},
68  {"\xFF\xD8\xFF", ReadImageFromJPG}}};
69 static constexpr uint8_t MAX_SIGNATURE_LEN = 8;
70 
71 static const std::unordered_map<
72  std::string,
73  std::function<bool(const std::string &, const geometry::Image &, int)>>
74  file_extension_to_image_write_function{
75  {"png", WriteImageToPNG},
76  {"jpg", WriteImageToJPG},
77  {"jpeg", WriteImageToJPG},
78  };
79 
80 } // unnamed namespace
81 
82 namespace io {
83 std::shared_ptr<geometry::Image> CreateImageFromFile(
84  const std::string &filename) {
85  auto image = std::make_shared<geometry::Image>();
87  return image;
88 }
89 
90 std::shared_ptr<geometry::Image> CreateImageFromMemory(
91  const std::string &image_format,
92  const unsigned char *image_data_ptr,
93  size_t image_data_size) {
94  auto image = std::make_shared<geometry::Image>();
95  ReadImageFromMemory(image_format, image_data_ptr, image_data_size, *image);
96  return image;
97 }
98 
99 bool ReadImage(const std::string &filename, geometry::Image &image) {
100  std::string signature_buffer(MAX_SIGNATURE_LEN, 0);
101  std::ifstream file(filename, std::ios::binary);
102  file.read(&signature_buffer[0], MAX_SIGNATURE_LEN);
103  std::string err_msg;
104  if (!file) {
105  err_msg = "Read geometry::Image failed for file {}. I/O error.";
106  } else {
107  file.close();
108  for (const auto &signature_decoder : signature_decoder_list) {
109  if (signature_buffer.compare(0, signature_decoder.first.size(),
110  signature_decoder.first) == 0) {
111  return signature_decoder.second(filename, image);
112  }
113  }
114  err_msg =
115  "Read geometry::Image failed for file {}. Unknown file "
116  "signature, only PNG and JPG are supported.";
117  }
118  image.Clear();
119  utility::LogWarning(err_msg.c_str(), filename);
120  return false;
121 }
122 
123 bool ReadImageFromMemory(const std::string &image_format,
124  const unsigned char *image_data_ptr,
125  size_t image_data_size,
127  std::string format = image_format;
128  std::transform(format.begin(), format.end(), format.begin(), ::tolower);
129  if (format == "png") {
130  return ReadPNGFromMemory(image_data_ptr, image_data_size, image);
131  } else if (format == "jpg") {
132  return ReadJPGFromMemory(image_data_ptr, image_data_size, image);
133  } else {
134  utility::LogWarning("The format of {} is not supported", format);
135  return false;
136  }
137 }
138 
139 bool WriteImage(const std::string &filename,
140  const geometry::Image &image,
141  int quality /* = kCloudViewerImageIODefaultQuality*/) {
142  std::string filename_ext =
144  if (filename_ext.empty()) {
146  "Write geometry::Image failed: unknown file extension.");
147  return false;
148  }
149  auto map_itr = file_extension_to_image_write_function.find(filename_ext);
150  if (map_itr == file_extension_to_image_write_function.end()) {
152  "Write geometry::Image failed: unknown file extension.");
153  return false;
154  }
155  return map_itr->second(filename, image, quality);
156 }
157 
158 bool ReadImageFromPNG(const std::string &filename, geometry::Image &image) {
159  png_image pngimage;
160  memset(&pngimage, 0, sizeof(pngimage));
161  pngimage.version = PNG_IMAGE_VERSION;
162  if (png_image_begin_read_from_file(&pngimage, filename.c_str()) == 0) {
163  utility::LogWarning("Read PNG failed: unable to parse header.");
164  return false;
165  }
166 
167  // Clear colormap flag if necessary to ensure libpng expands the colo
168  // indexed pixels to full color
169  if (pngimage.format & PNG_FORMAT_FLAG_COLORMAP) {
170  pngimage.format &= ~PNG_FORMAT_FLAG_COLORMAP;
171  }
172 
173  image.Prepare(pngimage.width, pngimage.height,
174  PNG_IMAGE_SAMPLE_CHANNELS(pngimage.format),
175  PNG_IMAGE_SAMPLE_COMPONENT_SIZE(pngimage.format));
176 
177  if (png_image_finish_read(&pngimage, NULL, image.data_.data(), 0, NULL) ==
178  0) {
179  utility::LogWarning("Read PNG failed: unable to read file: {}",
180  filename);
181  utility::LogWarning("PNG error: {}", pngimage.message);
182  return false;
183  }
184  return true;
185 }
186 
187 bool ReadPNGFromMemory(const unsigned char *image_data_ptr,
188  size_t image_data_size,
190  png_image pngimage;
191  memset(&pngimage, 0, sizeof(pngimage));
192  pngimage.version = PNG_IMAGE_VERSION;
193  if (png_image_begin_read_from_memory(&pngimage, image_data_ptr,
194  image_data_size) == 0) {
195  utility::LogWarning("Read PNG failed: unable to parse header.");
196  image.Clear();
197  return false;
198  }
199 
200  // Clear colormap flag if necessary to ensure libpng expands the colo
201  // indexed pixels to full color
202  if (pngimage.format & PNG_FORMAT_FLAG_COLORMAP) {
203  pngimage.format &= ~PNG_FORMAT_FLAG_COLORMAP;
204  }
205 
206  image.Prepare(pngimage.width, pngimage.height,
207  PNG_IMAGE_SAMPLE_CHANNELS(pngimage.format),
208  PNG_IMAGE_SAMPLE_COMPONENT_SIZE(pngimage.format));
209 
210  if (png_image_finish_read(&pngimage, NULL, image.data_.data(), 0, NULL) ==
211  0) {
212  utility::LogWarning("PNG error: {}", pngimage.message);
213  image.Clear();
214  return false;
215  }
216  return true;
217 }
218 
219 bool WriteImageToPNG(const std::string &filename,
220  const geometry::Image &image,
221  int quality /* = kCloudViewerImageIODefaultQuality*/) {
222  if (!image.HasData()) {
223  utility::LogWarning("Write PNG failed: image has no data.");
224  return false;
225  }
226  if (quality == kCloudViewerImageIODefaultQuality) // Set default quality
227  quality = 6;
228  if (quality < 0 || quality > 9) {
230  "Write PNG failed: quality ({}) must be in the range [0,9]",
231  quality);
232  return false;
233  }
234 
235  png_image pngimage;
236  memset(&pngimage, 0, sizeof(pngimage));
237  pngimage.version = PNG_IMAGE_VERSION;
238  SetPNGImageFromImage(image, quality, pngimage);
239  if (png_image_write_to_file(&pngimage, filename.c_str(), 0,
240  image.data_.data(), 0, NULL) == 0) {
241  utility::LogWarning("Write PNG failed: unable to write file: {}",
242  filename);
243  return false;
244  }
245  return true;
246 }
247 
248 bool ReadImageFromJPG(const std::string &filename, geometry::Image &image) {
249  struct jpeg_decompress_struct cinfo;
250  struct jpeg_error_mgr jerr;
251  FILE *file_in;
252  JSAMPARRAY buffer;
253 
254  if ((file_in = utility::filesystem::FOpen(filename, "rb")) == NULL) {
255  utility::LogWarning("Read JPG failed: unable to open file: {}",
256  filename);
257  image.Clear();
258  return false;
259  }
260 
261  try {
262  cinfo.err = jpeg_std_error(&jerr);
263  jerr.error_exit = jpeg_error_throw;
264  jpeg_create_decompress(&cinfo);
265  jpeg_stdio_src(&cinfo, file_in);
266  jpeg_read_header(&cinfo, TRUE);
267 
268  // We only support two channel types: gray, and RGB.
269  int num_of_channels = 3;
270  int bytes_per_channel = 1;
271  switch (cinfo.jpeg_color_space) {
272  case JCS_RGB:
273  case JCS_YCbCr:
274  cinfo.out_color_space = JCS_RGB;
275  cinfo.out_color_components = 3;
276  num_of_channels = 3;
277  break;
278  case JCS_GRAYSCALE:
279  cinfo.jpeg_color_space = JCS_GRAYSCALE;
280  cinfo.out_color_components = 1;
281  num_of_channels = 1;
282  break;
283  case JCS_CMYK:
284  case JCS_YCCK:
285  default:
287  "Read JPG failed: color space not supported.");
288  jpeg_destroy_decompress(&cinfo);
289  fclose(file_in);
290  image.Clear();
291  return false;
292  }
293  jpeg_start_decompress(&cinfo);
294  image.Prepare(cinfo.output_width, cinfo.output_height, num_of_channels,
295  bytes_per_channel);
296  int row_stride = cinfo.output_width * cinfo.output_components;
297  buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE,
298  row_stride, 1);
299  uint8_t *pdata = image.data_.data();
300  while (cinfo.output_scanline < cinfo.output_height) {
301  jpeg_read_scanlines(&cinfo, buffer, 1);
302  memcpy(pdata, buffer[0], row_stride);
303  pdata += row_stride;
304  }
305  jpeg_finish_decompress(&cinfo);
306  jpeg_destroy_decompress(&cinfo);
307  fclose(file_in);
308  return true;
309  } catch (const std::runtime_error &err) {
310  fclose(file_in);
311  image.Clear();
312  utility::LogWarning("libjpeg error: {}", err.what());
313  return false;
314  }
315 }
316 
317 bool ReadJPGFromMemory(const unsigned char *image_data_ptr,
318  size_t image_data_size,
320  struct jpeg_decompress_struct cinfo;
321  struct jpeg_error_mgr jerr;
322  JSAMPARRAY buffer;
323 
324  try {
325  cinfo.err = jpeg_std_error(&jerr);
326  jerr.error_exit = jpeg_error_throw;
327  jpeg_create_decompress(&cinfo);
328  jpeg_mem_src(&cinfo, image_data_ptr, image_data_size);
329  jpeg_read_header(&cinfo, TRUE);
330 
331  // We only support two channel types: gray, and RGB.
332  int num_of_channels = 3;
333  int bytes_per_channel = 1;
334  switch (cinfo.jpeg_color_space) {
335  case JCS_RGB:
336  case JCS_YCbCr:
337  cinfo.out_color_space = JCS_RGB;
338  cinfo.out_color_components = 3;
339  num_of_channels = 3;
340  break;
341  case JCS_GRAYSCALE:
342  cinfo.jpeg_color_space = JCS_GRAYSCALE;
343  cinfo.out_color_components = 1;
344  num_of_channels = 1;
345  break;
346  case JCS_CMYK:
347  case JCS_YCCK:
348  default:
350  "Read JPG failed: color space not supported.");
351  jpeg_destroy_decompress(&cinfo);
352  image.Clear();
353  return false;
354  }
355  jpeg_start_decompress(&cinfo);
356  image.Prepare(cinfo.output_width, cinfo.output_height, num_of_channels,
357  bytes_per_channel);
358  int row_stride = cinfo.output_width * cinfo.output_components;
359  buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE,
360  row_stride, 1);
361  uint8_t *pdata = image.data_.data();
362  while (cinfo.output_scanline < cinfo.output_height) {
363  jpeg_read_scanlines(&cinfo, buffer, 1);
364  memcpy(pdata, buffer[0], row_stride);
365  pdata += row_stride;
366  }
367  jpeg_finish_decompress(&cinfo);
368  jpeg_destroy_decompress(&cinfo);
369  return true;
370  } catch (const std::runtime_error &err) {
371  image.Clear();
372  utility::LogWarning(err.what());
373  return false;
374  }
375 }
376 
377 bool WriteImageToJPG(const std::string &filename,
378  const geometry::Image &image,
379  int quality /* = kCloudViewerImageIODefaultQuality*/) {
380  if (!image.HasData()) {
381  utility::LogWarning("Write JPG failed: image has no data.");
382  return false;
383  }
384  if (image.bytes_per_channel_ != 1 ||
385  (image.num_of_channels_ != 1 && image.num_of_channels_ != 3)) {
386  utility::LogWarning("Write JPG failed: unsupported image data.");
387  return false;
388  }
389  if (quality == kCloudViewerImageIODefaultQuality) // Set default quality
390  quality = 90;
391  if (quality < 0 || quality > 100) {
393  "Write JPG failed: image quality should be in the range "
394  "[0,100].");
395  return false;
396  }
397 
398  struct jpeg_compress_struct cinfo;
399  struct jpeg_error_mgr jerr;
400  FILE *file_out;
401  JSAMPROW row_pointer[1];
402 
403  if ((file_out = utility::filesystem::FOpen(filename, "wb")) == NULL) {
404  utility::LogWarning("Write JPG failed: unable to open file: {}",
405  filename);
406  return false;
407  }
408 
409  try {
410  cinfo.err = jpeg_std_error(&jerr);
411  jerr.error_exit = jpeg_error_throw;
412  jpeg_create_compress(&cinfo);
413  jpeg_stdio_dest(&cinfo, file_out);
414  cinfo.image_width = image.width_;
415  cinfo.image_height = image.height_;
416  cinfo.input_components = image.num_of_channels_;
417  cinfo.in_color_space =
418  (cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB);
419  jpeg_set_defaults(&cinfo);
420  jpeg_set_quality(&cinfo, quality, TRUE);
421  jpeg_start_compress(&cinfo, TRUE);
422  int row_stride = image.width_ * image.num_of_channels_;
423  const uint8_t *pdata = image.data_.data();
424  std::vector<uint8_t> buffer(row_stride);
425  while (cinfo.next_scanline < cinfo.image_height) {
426  memcpy(buffer.data(), pdata, row_stride);
427  row_pointer[0] = buffer.data();
428  jpeg_write_scanlines(&cinfo, row_pointer, 1);
429  pdata += row_stride;
430  }
431  jpeg_finish_compress(&cinfo);
432  fclose(file_out);
433  jpeg_destroy_compress(&cinfo);
434  return true;
435  } catch (const std::runtime_error &err) {
436  fclose(file_out);
437  utility::LogWarning(err.what());
438  return false;
439  }
440 }
441 
442 } // namespace io
443 } // namespace cloudViewer
std::string filename
std::shared_ptr< core::Tensor > image
filament::Texture::InternalFormat format
filament::Texture::Format image_format
#define NULL
The Image class stores image with customizable width, height, num of channels and bytes per channel.
Definition: Image.h:33
#define LogWarning(...)
Definition: Logging.h:72
#define TRUE
Definition: dbfopen.c:236
bool WriteImage(const std::string &filename, const geometry::Image &image, int quality=kCloudViewerImageIODefaultQuality)
Definition: ImageIO.cpp:139
bool ReadImageFromJPG(const std::string &filename, geometry::Image &image)
Definition: ImageIO.cpp:248
constexpr int kCloudViewerImageIODefaultQuality
Definition: ImageIO.h:47
bool ReadJPGFromMemory(const unsigned char *image_data_ptr, size_t image_data_size, geometry::Image &image)
Definition: ImageIO.cpp:317
bool ReadImageFromPNG(const std::string &filename, geometry::Image &image)
Definition: ImageIO.cpp:158
bool ReadPNGFromMemory(const unsigned char *image_data_ptr, size_t image_data_size, geometry::Image &image)
Definition: ImageIO.cpp:187
bool ReadImage(const std::string &filename, geometry::Image &image)
Definition: ImageIO.cpp:99
std::shared_ptr< geometry::Image > CreateImageFromMemory(const std::string &image_format, const unsigned char *image_data_ptr, size_t image_data_size)
Factory function to create an image from memory.
Definition: ImageIO.cpp:90
std::shared_ptr< geometry::Image > CreateImageFromFile(const std::string &filename)
Definition: ImageIO.cpp:83
bool WriteImageToPNG(const std::string &filename, const geometry::Image &image, int quality=kCloudViewerImageIODefaultQuality)
Definition: ImageIO.cpp:219
bool ReadImageFromMemory(const std::string &image_format, const unsigned char *image_data_ptr, size_t image_data_size, geometry::Image &image)
Definition: ImageIO.cpp:123
bool WriteImageToJPG(const std::string &filename, const geometry::Image &image, int quality=kCloudViewerImageIODefaultQuality)
Definition: ImageIO.cpp:377
static void SetPNGImageFromImage(const geometry::Image &image, int quality, png_image &pngimage)
Definition: FilePNG.cpp:18
std::string GetFileExtensionInLowerCase(const std::string &filename)
Definition: FileSystem.cpp:281
FILE * FOpen(const std::string &filename, const std::string &mode)
Definition: FileSystem.cpp:609
Generic file read and write utility for python interface.