Panda3D
pnmFileTypeStbImage.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file pnmFileTypeStbImage.cxx
10  * @author rdb
11  * @date 2016-03-31
12  */
13 
14 #include "pnmFileTypeStbImage.h"
15 
16 #ifdef HAVE_STB_IMAGE
17 
18 #include "config_pnmimagetypes.h"
19 #include "pnmFileTypeRegistry.h"
20 #include "bamReader.h"
21 
22 // We use the public domain stb_image library for loading images. Define the
23 // stb_image implementation. We only use it in this unit.
24 #define STB_IMAGE_STATIC
25 #define STB_IMAGE_IMPLEMENTATION
26 
27 // Disable the stb_image implementation of these formats if we already support
28 // it through different loaders.
29 #ifndef HAVE_JPEG
30 #define STBI_ONLY_JPEG
31 #endif
32 #ifndef HAVE_PNG
33 #define STBI_ONLY_PNG
34 #endif
35 #ifndef HAVE_BMP
36 #define STBI_ONLY_BMP
37 #endif
38 #ifndef HAVE_TGA
39 #define STBI_ONLY_TGA
40 #endif
41 #ifndef HAVE_SOFTIMAGE_PIC
42 #define STBI_ONLY_PIC
43 #endif
44 #ifndef HAVE_PNM
45 #define STBI_ONLY_PNM
46 #endif
47 
48 // These are always enabled because we don't support these via other means.
49 #define STBI_ONLY_PSD
50 #define STBI_ONLY_HDR
51 #define STBI_ONLY_GIF
52 
53 #ifndef NDEBUG
54 // Get friendlier error messages in development builds.
55 #define STBI_FAILURE_USERMSG
56 #endif
57 
58 // We read via callbacks, so no need for stbi_load_from_file.
59 #define STBI_NO_STDIO
60 
61 #include "stb_image.h"
62 
63 using std::ios;
64 using std::istream;
65 using std::string;
66 
67 static const char *const stb_extensions[] = {
68  // Expose the extensions that we don't already expose through other loaders.
69 #if !defined(HAVE_JPEG) && !defined(ANDROID)
70  "jpg", "jpeg",
71 #endif
72 #ifndef HAVE_PNG
73  "png",
74 #endif
75 #ifndef HAVE_BMP
76  "bmp",
77 #endif
78 #ifndef HAVE_TGA
79  "tga",
80 #endif
81 #ifndef HAVE_SOFTIMAGE_PIC
82  "pic",
83 #endif
84 #ifndef HAVE_PNM
85  "ppm", "pgm",
86 #endif
87 
88  // We don't have other loaders for these, so add them unconditionally.
89  "psd",
90  "hdr",
91  "gif",
92 };
93 static const int num_stb_extensions = sizeof(stb_extensions) / sizeof(const char *);
94 
95 // Callbacks to allow stb_image to read from VFS.
96 static int cb_read(void *user, char *data, int size) {
97  istream *in = (istream *)user;
98  nassertr(in != nullptr, 0);
99 
100  in->read(data, size);
101 
102  if (in->eof()) {
103  // Gracefully handle EOF.
104  in->clear(ios::eofbit);
105  }
106 
107  return (int)in->gcount();
108 }
109 
110 static void cb_skip(void *user, int n) {
111  istream *in = (istream *)user;
112  nassertv(in != nullptr);
113 
114  in->seekg(n, ios::cur);
115 
116  // If we can't seek, move forward by ignoring bytes instead.
117  if (in->fail() && n > 0) {
118  in->clear();
119  in->ignore(n);
120  }
121 }
122 
123 static int cb_eof(void *user) {
124  istream *in = (istream *)user;
125  nassertr(in != nullptr, 1);
126 
127  return in->eof();
128 }
129 
130 static stbi_io_callbacks io_callbacks = {cb_read, cb_skip, cb_eof};
131 
132 /**
133  * This is defined in the .cxx file so we have access to stbi_context.
134  */
135 class StbImageReader : public PNMReader {
136 public:
137  StbImageReader(PNMFileType *type, istream *file, bool owns_file, string magic_number);
138 
139  virtual bool is_floating_point();
140  virtual bool read_pfm(PfmFile &pfm);
141  virtual int read_data(xel *array, xelval *alpha);
142 
143 private:
144  bool _is_float;
145  stbi__context _context;
146  unsigned char _buffer[1024];
147 };
148 
149 TypeHandle PNMFileTypeStbImage::_type_handle;
150 
151 /**
152  *
153  */
154 PNMFileTypeStbImage::
155 PNMFileTypeStbImage() {
156 }
157 
158 /**
159  * Returns a few words describing the file type.
160  */
161 string PNMFileTypeStbImage::
162 get_name() const {
163  return "stb_image";
164 }
165 
166 /**
167  * Returns the number of different possible filename extensions associated
168  * with this particular file type.
169  */
170 int PNMFileTypeStbImage::
171 get_num_extensions() const {
172  return num_stb_extensions;
173 }
174 
175 /**
176  * Returns the nth possible filename extension associated with this particular
177  * file type, without a leading dot.
178  */
179 string PNMFileTypeStbImage::
180 get_extension(int n) const {
181  nassertr(n >= 0 && n < num_stb_extensions, string());
182  return stb_extensions[n];
183 }
184 
185 /**
186  * Returns true if this particular file type uses a magic number to identify
187  * it, false otherwise.
188  */
189 bool PNMFileTypeStbImage::
190 has_magic_number() const {
191  return false;
192 }
193 
194 /**
195  * Returns true if the indicated "magic number" byte stream (the initial few
196  * bytes read from the file) matches this particular file type, false
197  * otherwise.
198  */
199 bool PNMFileTypeStbImage::
200 matches_magic_number(const string &magic_number) const {
201  return false;
202 }
203 
204 /**
205  * Allocates and returns a new PNMReader suitable for reading from this file
206  * type, if possible. If reading from this file type is not supported,
207  * returns NULL.
208  */
209 PNMReader *PNMFileTypeStbImage::
210 make_reader(istream *file, bool owns_file, const string &magic_number) {
211  init_pnm();
212  return new StbImageReader(this, file, owns_file, magic_number);
213 }
214 
215 /**
216  *
217  */
218 StbImageReader::
219 StbImageReader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
220  PNMReader(type, file, owns_file),
221  _is_float(false)
222 {
223  // Prepare the stb_image context. See stbi__start_callbacks.
224  _context.io.read = cb_read;
225  _context.io.skip = cb_skip;
226  _context.io.eof = cb_eof;
227  _context.io_user_data = (void *)file;
228  _context.buflen = sizeof(_context.buffer_start);
229  _context.read_from_callbacks = 1;
230  _context.img_buffer = _buffer;
231  _context.img_buffer_original = _buffer;
232 
233  // Prepopulate it with the magic number we already read, then fill it up.
234  // We need a big enough buffer so that we can read the image header.
235  // If stb_image runs out, it will switch to its own 128-byte buffer.
236  memcpy(_buffer, magic_number.data(), magic_number.size());
237  file->read((char *)_buffer + magic_number.size(), sizeof(_buffer) - magic_number.size());
238 
239  if (file->eof()) {
240  file->clear(ios::eofbit);
241  }
242 
243  size_t length = file->gcount() + magic_number.size();
244  _context.img_buffer_end = _buffer + length;
245  _context.img_buffer_original_end = _context.img_buffer_end;
246 
247 #ifndef STBI_NO_PNG
248  stbi__png png;
249  png.s = &_context;
250 #endif
251 
252  // Invoke stbi_info to read the image size and channel count.
253  if (magic_number[0] == '#' && magic_number[1] == '?' &&
254  stbi__hdr_info(&_context, &_x_size, &_y_size, &_num_channels)) {
255  _is_valid = true;
256  _is_float = true;
257 
258 #ifndef STBI_NO_PNG
259  } else if (magic_number[0] == '\x89' && magic_number[1] == 'P' &&
260  stbi__png_info_raw(&png, &_x_size, &_y_size, &_num_channels)) {
261  // Detect the case of using PNGs so that we can determine whether to do a
262  // 16-bit load instead.
263  if (png.depth == 16) {
264  _maxval = 65535;
265  }
266  _is_valid = true;
267 #endif
268 
269  } else if (stbi__info_main(&_context, &_x_size, &_y_size, &_num_channels)) {
270  _is_valid = true;
271 
272  } else {
273  _is_valid = false;
274  pnmimage_cat.error()
275  << "stb_info failure: " << stbi_failure_reason() << "\n";
276  }
277 }
278 
279 /**
280  * Returns true if this PNMFileType represents a floating-point image type,
281  * false if it is a normal, integer type. If this returns true, read_pfm() is
282  * implemented instead of read_data().
283  */
284 bool StbImageReader::
285 is_floating_point() {
286  return _is_float;
287 }
288 
289 /**
290  * Reads floating-point data directly into the indicated PfmFile. Returns
291  * true on success, false on failure.
292  */
293 bool StbImageReader::
294 read_pfm(PfmFile &pfm) {
295  if (!is_valid()) {
296  return false;
297  }
298 
299  // Reposition the file at the beginning.
300  if (_context.img_buffer_end == _context.img_buffer_original_end) {
301  // All we need to do is rewind the buffer.
302  stbi__rewind(&_context);
303 
304  } else {
305  // We need to reinitialize the context.
306  _file->seekg(0, ios::beg);
307  if (_file->tellg() != (std::streampos)0) {
308  pnmimage_cat.error()
309  << "Could not reposition file pointer to the beginning.\n";
310  return false;
311  }
312 
313  stbi__start_callbacks(&_context, &io_callbacks, (void *)_file);
314  }
315 
316  nassertr(_num_channels == 3, false);
317 
318  // This next bit is copied and pasted from stbi__hdr_load so that we can
319  // avoid making an unnecessary extra copy of the data.
320  char buffer[STBI__HDR_BUFLEN];
321  char *token;
322  int valid = 0;
323  int width, height;
324  stbi_uc *scanline;
325  int len;
326  unsigned char count, value;
327  int i, j, k, c1, c2, z;
328  const char *headerToken;
329 
330  // Check identifier
331  headerToken = stbi__hdr_gettoken(&_context, buffer);
332  if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) {
333  pnmimage_cat.error()
334  << "Missing #?RADIANCE or #?RGBE header.\n";
335  return false;
336  }
337 
338  // Parse header
339  for(;;) {
340  token = stbi__hdr_gettoken(&_context, buffer);
341  if (token[0] == 0) break;
342  if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
343  }
344 
345  if (!valid) {
346  pnmimage_cat.error() << "Unsupported HDR format.\n";
347  return false;
348  }
349 
350  // Parse width and height
351  // can't use sscanf() if we're not using stdio!
352  token = stbi__hdr_gettoken(&_context, buffer);
353  if (strncmp(token, "-Y ", 3)) {
354  pnmimage_cat.error() << "Unsupported HDR data layout.\n";
355  return false;
356  }
357  token += 3;
358  height = (int) strtol(token, &token, 10);
359  while (*token == ' ') ++token;
360  if (strncmp(token, "+X ", 3)) {
361  pnmimage_cat.error() << "Unsupported HDR data layout.\n";
362  return false;
363  }
364  token += 3;
365  width = (int) strtol(token, nullptr, 10);
366 
367  // Read data
368  pfm.clear(width, height, 3);
369  vector_float table;
370  pfm.swap_table(table);
371  float *hdr_data = (float *)&table[0];
372 
373  // Load image data
374  // image data is stored as some number of sca
375  if (width < 8 || width >= 32768) {
376  // Read flat data
377  for (j = 0; j < height; ++j) {
378  for (i = 0; i < width; ++i) {
379  stbi_uc rgbe[4];
380 main_decode_loop:
381  stbi__getn(&_context, rgbe, 4);
382  stbi__hdr_convert(hdr_data + j * width * 3 + i * 3, rgbe, 3);
383  }
384  }
385  } else {
386  // Read RLE-encoded data
387  scanline = nullptr;
388 
389  for (j = 0; j < height; ++j) {
390  c1 = stbi__get8(&_context);
391  c2 = stbi__get8(&_context);
392  len = stbi__get8(&_context);
393  if (c1 != 2 || c2 != 2 || (len & 0x80)) {
394  // not run-length encoded, so we have to actually use THIS data as a decoded
395  // pixel (note this can't be a valid pixel--one of RGB must be >= 128)
396  stbi_uc rgbe[4];
397  rgbe[0] = (stbi_uc) c1;
398  rgbe[1] = (stbi_uc) c2;
399  rgbe[2] = (stbi_uc) len;
400  rgbe[3] = (stbi_uc) stbi__get8(&_context);
401  stbi__hdr_convert(hdr_data, rgbe, 3);
402  i = 1;
403  j = 0;
404  STBI_FREE(scanline);
405  goto main_decode_loop; // yes, this makes no sense
406  }
407  len <<= 8;
408  len |= stbi__get8(&_context);
409  if (len != width) {
410  pnmimage_cat.error() << "Corrupt HDR: invalid decoded scanline length.\n";
411  STBI_FREE(scanline);
412  return false;
413  }
414  if (scanline == nullptr) {
415  scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0);
416  if (!scanline) {
417  pnmimage_cat.error() << "Out of memory while reading HDR file.\n";
418  STBI_FREE(hdr_data);
419  return false;
420  }
421  }
422 
423  for (k = 0; k < 4; ++k) {
424  int nleft;
425  i = 0;
426  while ((nleft = width - i) > 0) {
427  count = stbi__get8(&_context);
428  if (count > 128) {
429  // Run
430  value = stbi__get8(&_context);
431  count -= 128;
432  if (count > nleft) {
433  pnmimage_cat.error() << "Bad RLE data in HDR file.\n";
434  STBI_FREE(scanline);
435  return false;
436  }
437  for (z = 0; z < count; ++z) {
438  scanline[i++ * 4 + k] = value;
439  }
440  } else {
441  // Dump
442  if (count > nleft) {
443  pnmimage_cat.error() << "Bad RLE data in HDR file.\n";
444  STBI_FREE(scanline);
445  return false;
446  }
447  for (z = 0; z < count; ++z) {
448  scanline[i++ * 4 + k] = stbi__get8(&_context);
449  }
450  }
451  }
452  }
453  for (i = 0; i < width; ++i) {
454  stbi__hdr_convert(hdr_data+(j*width + i)*3, scanline + i*4, 3);
455  }
456  }
457  if (scanline) {
458  STBI_FREE(scanline);
459  }
460  }
461 
462  pfm.swap_table(table);
463  return true;
464 }
465 
466 /**
467  * Reads in an entire image all at once, storing it in the pre-allocated
468  * _x_size * _y_size array and alpha pointers. (If the image type has no
469  * alpha channel, alpha is ignored.) Returns the number of rows correctly
470  * read.
471  *
472  * Derived classes need not override this if they instead provide
473  * supports_read_row() and read_row(), below.
474  */
475 int StbImageReader::
476 read_data(xel *array, xelval *alpha) {
477  if (!is_valid()) {
478  return 0;
479  }
480 
481  // Reposition the file at the beginning.
482  if (_context.img_buffer_end == _context.img_buffer_original_end) {
483  // All we need to do is rewind the buffer.
484  stbi__rewind(&_context);
485 
486  } else {
487  // We need to reinitialize the context.
488  _file->seekg(0, ios::beg);
489  if (_file->tellg() != (std::streampos)0) {
490  pnmimage_cat.error()
491  << "Could not reposition file pointer to the beginning.\n";
492  return false;
493  }
494 
495  stbi__start_callbacks(&_context, &io_callbacks, (void *)_file);
496  }
497 
498  int cols = 0;
499  int rows = 0;
500  int comp = _num_channels;
501  void *data;
502  if (_maxval != 65535) {
503  data = stbi__load_and_postprocess_8bit(&_context, &cols, &rows, &comp, _num_channels);
504  } else {
505  data = stbi__load_and_postprocess_16bit(&_context, &cols, &rows, &comp, _num_channels);
506  }
507 
508  if (data == nullptr) {
509  pnmimage_cat.error()
510  << "stbi_load failure: " << stbi_failure_reason() << "\n";
511  return 0;
512  }
513 
514  nassertr(cols == _x_size, 0);
515  nassertr(comp == _num_channels, 0);
516 
517  size_t pixels = (size_t)_x_size * (size_t)rows;
518  if (_maxval != 65535) {
519  uint8_t *ptr = (uint8_t *)data;
520  switch (_num_channels) {
521  case 1:
522  for (size_t i = 0; i < pixels; ++i) {
523  PPM_ASSIGN(array[i], ptr[i], ptr[i], ptr[i]);
524  }
525  break;
526 
527  case 2:
528  for (size_t i = 0; i < pixels; ++i) {
529  PPM_ASSIGN(array[i], ptr[0], ptr[0], ptr[0]);
530  alpha[i] = ptr[1];
531  ptr += 2;
532  }
533  break;
534 
535  case 3:
536  for (size_t i = 0; i < pixels; ++i) {
537  PPM_ASSIGN(array[i], ptr[0], ptr[1], ptr[2]);
538  ptr += 3;
539  }
540  break;
541 
542  case 4:
543  for (size_t i = 0; i < pixels; ++i) {
544  PPM_ASSIGN(array[i], ptr[0], ptr[1], ptr[2]);
545  alpha[i] = ptr[3];
546  ptr += 4;
547  }
548  break;
549  }
550  } else {
551  uint16_t *ptr = (uint16_t *)data;
552  switch (_num_channels) {
553  case 1:
554  for (size_t i = 0; i < pixels; ++i) {
555  PPM_ASSIGN(array[i], ptr[i], ptr[i], ptr[i]);
556  }
557  break;
558 
559  case 2:
560  for (size_t i = 0; i < pixels; ++i) {
561  PPM_ASSIGN(array[i], ptr[0], ptr[0], ptr[0]);
562  alpha[i] = ptr[1];
563  ptr += 2;
564  }
565  break;
566 
567  case 3:
568  memcpy(array, ptr, pixels * sizeof(uint16_t) * 3);
569  break;
570 
571  case 4:
572  for (size_t i = 0; i < pixels; ++i) {
573  PPM_ASSIGN(array[i], ptr[0], ptr[1], ptr[2]);
574  alpha[i] = ptr[3];
575  ptr += 4;
576  }
577  break;
578  }
579  }
580 
581  stbi_image_free(data);
582  return rows;
583 }
584 
585 /**
586  * Registers the current object as something that can be read from a Bam file.
587  */
588 void PNMFileTypeStbImage::
589 register_with_read_factory() {
591  register_factory(get_class_type(), make_PNMFileTypeStbImage);
592 }
593 
594 /**
595  * This method is called by the BamReader when an object of this type is
596  * encountered in a Bam file; it should allocate and return a new object with
597  * all the data read.
598  *
599  * In the case of the PNMFileType objects, since these objects are all shared,
600  * we just pull the object from the registry.
601  */
602 TypedWritable *PNMFileTypeStbImage::
603 make_PNMFileTypeStbImage(const FactoryParams &params) {
604  return PNMFileTypeRegistry::get_global_ptr()->get_type_by_handle(get_class_type());
605 }
606 
607 #endif // HAVE_STB_IMAGE
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual int read_data(xel *array, xelval *alpha)
Reads in an entire image all at once, storing it in the pre-allocated _x_size * _y_size array and alp...
Definition: pnmReader.cxx:94
virtual bool is_floating_point()
Returns true if this PNMFileType represents a floating-point image type, false if it is a normal,...
Definition: pnmReader.cxx:71
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
virtual bool read_pfm(PfmFile &pfm)
Reads floating-point data directly into the indicated PfmFile.
Definition: pnmReader.cxx:80
void swap_table(vector_float &table)
This is a very low-level function that completely exchanges the PfmFile's internal table of floating-...
Definition: pfmFile.I:549
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition: pfmFile.h:31
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PNMFileType * get_type_by_handle(TypeHandle handle) const
Returns the PNMFileType instance stored in the registry for the given TypeHandle, e....
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is an abstract base class that defines the interface for reading image files of various types.
Definition: pnmReader.h:27
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
void clear()
Eliminates all data in the file.
Definition: pfmFile.cxx:77