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
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
63using std::ios;
64using std::istream;
65using std::string;
66
67static 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};
93static const int num_stb_extensions = sizeof(stb_extensions) / sizeof(const char *);
94
95// Callbacks to allow stb_image to read from VFS.
96static 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
110static 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
123static int cb_eof(void *user) {
124 istream *in = (istream *)user;
125 nassertr(in != nullptr, 1);
126
127 return in->eof();
128}
129
130static 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 */
135class StbImageReader : public PNMReader {
136public:
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
143private:
144 bool _is_float;
145 stbi__context _context;
146 unsigned char _buffer[1024];
147};
148
149TypeHandle PNMFileTypeStbImage::_type_handle;
150
151/**
152 *
153 */
154PNMFileTypeStbImage::
155PNMFileTypeStbImage() {
156}
157
158/**
159 * Returns a few words describing the file type.
160 */
161string PNMFileTypeStbImage::
162get_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 */
170int PNMFileTypeStbImage::
171get_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 */
179string PNMFileTypeStbImage::
180get_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 */
189bool PNMFileTypeStbImage::
190has_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 */
199bool PNMFileTypeStbImage::
200matches_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 */
209PNMReader *PNMFileTypeStbImage::
210make_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 */
218StbImageReader::
219StbImageReader(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 */
284bool StbImageReader::
285is_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 */
293bool StbImageReader::
294read_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];
380main_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 */
475int StbImageReader::
476read_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 */
588void PNMFileTypeStbImage::
589register_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 */
602TypedWritable *PNMFileTypeStbImage::
603make_PNMFileTypeStbImage(const FactoryParams &params) {
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.