Panda3D
Loading...
Searching...
No Matches
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...
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.