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.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
PNMFileType * get_type_by_handle(TypeHandle handle) const
Returns the PNMFileType instance stored in the registry for the given TypeHandle, e....
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
This is an abstract base class that defines the interface for reading image files of various types.
Definition: pnmReader.h:27
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
virtual bool read_pfm(PfmFile &pfm)
Reads floating-point data directly into the indicated PfmFile.
Definition: pnmReader.cxx:80
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition: pfmFile.h:31
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
void clear()
Eliminates all data in the file.
Definition: pfmFile.cxx:77
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.