Panda3D
Loading...
Searching...
No Matches
pnmFileTypeSGIReader.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 pnmFileTypeSGIReader.cxx
10 * @author drose
11 * @date 2000-06-17
12 */
13
14#include "pnmFileTypeSGI.h"
15
16#ifdef HAVE_SGI_RGB
17
19#include "sgi.h"
20
21#include "pnmImage.h"
22#include "pnmReader.h"
23
24#include "pnotify.h"
25
26using std::istream;
27using std::string;
28
29// Much code in this file is borrowed from Netpbm, specifically sgitopnm.c.
30
31/* sgitopnm.c - read an SGI image and and produce a portable anymap
32**
33** Copyright (C) 1994 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
34**
35** Based on the SGI image description v0.9 by Paul Haeberli (paul@sgi.comp)
36** Available via ftp from sgi.com:graphics/SGIIMAGESPEC
37**
38** Permission to use, copy, modify, and distribute this software and its
39** documentation for any purpose and without fee is hereby granted, provided
40** that the above copyright notice appear in all copies and that both that
41** copyright notice and this permission notice appear in supporting
42** documentation. This software is provided "as is" without express or
43** implied warranty.
44**
45** 29Jan94: first version
46** 08Feb94: minor bugfix
47*/
48
49/* entry in RLE offset table */
50typedef PNMFileTypeSGI::Reader::TabEntry TabEntry;
51
52typedef short ScanElem;
53typedef ScanElem * ScanLine;
54
55/* prototypes */
56static unsigned char get_byte ( istream* f );
57static long get_big_long (istream *f);
58static short get_big_short (istream *f);
59static short get_byte_as_short (istream *f);
60static int readerr (istream *f);
61static void * xmalloc (int bytes);
62#define MALLOC(n, type) (type *)xmalloc((n) * sizeof(type))
63static const char * compression_name (char compr);
64static void read_bytes (istream *ifp, int n, char *buf);
65static bool read_header(istream *ifp, Header *head, const string &magic_number);
66static TabEntry * read_table (istream *ifp, int tablen);
67static void read_channel (istream *ifp, int xsize, int ysize,
68 int zsize, int bpc, TabEntry *table,
69 ScanElem *channel_data, long table_start,
70 int channel, int row);
71static void rle_decompress (ScanElem *src, long srclen, ScanElem *dest, long destlen);
72
73#define WORSTCOMPR(x) (2*(x) + 2)
74
75#define MAXVAL_BYTE 255
76#define MAXVAL_WORD 65535
77
78// This flag shouldn't really be a static global, but it's a little tricky to
79// fix and it doesn't do any harm, since it only controls whether an error
80// message is repeated.
81static bool eof_err = false;
82
83
84/**
85 *
86 */
87PNMFileTypeSGI::Reader::
88Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
89 PNMReader(type, file, owns_file)
90{
91 eof_err = false;
92 table = nullptr;
93
94 if (!read_magic_number(_file, magic_number, 4)) {
95 // No magic number. No image.
96 if (pnmimage_sgi_cat.is_debug()) {
97 pnmimage_sgi_cat.debug()
98 << "RGB file appears to be empty.\n";
99 }
100 _is_valid = false;
101 return;
102 }
103
104 Header head;
105
106 if (!::read_header(file, &head, magic_number)) {
107 _is_valid = false;
108 }
109
110 long pixmax = (head.bpc == 1) ? MAXVAL_BYTE : MAXVAL_WORD;
111 if( pixmax > PNM_MAXMAXVAL ) {
112 pnmimage_sgi_cat.error()
113 << "Cannot read RGB image with maxval of " << pixmax
114 << "--largest allowable maxval is currently " << PNM_MAXMAXVAL << "\n";
115 _is_valid = false;
116 return;
117 }
118
119 _maxval = (xelval)pixmax;
120
121 table_start = file->tellg();
122 if( head.storage != STORAGE_VERBATIM )
123 table = read_table(file, head.ysize * head.zsize);
124
125 _x_size = head.xsize;
126 _y_size = head.ysize;
127 _num_channels = std::min((int)head.zsize, 4);
128 bpc = head.bpc;
129
130 current_row = _y_size - 1;
131
132 if (_is_valid && pnmimage_sgi_cat.is_debug()) {
133 head.name[79] = '\0'; /* just to be safe */
134 pnmimage_sgi_cat.debug()
135 << "Read RGB image:\n"
136 << " raster size " << head.xsize << " x " << head.ysize
137 << ", " << head.zsize << " channels\n"
138 << " compression: " << (int)head.storage << " = "
139 << compression_name(head.storage) << "\n"
140 << " image name: " << head.name << "\n"
141 << " bpc: " << (int)head.bpc << " dimension: " << head.dimension << "\n"
142 << " pixmin: " << head.pixmin << " pixmax: " << head.pixmax
143 << " colormap: " << head.colormap << "\n";
144 }
145}
146
147/**
148 *
149 */
150PNMFileTypeSGI::Reader::
151~Reader() {
152 if (table != nullptr) {
153 free(table);
154 }
155}
156
157/**
158 * Returns true if this particular PNMReader supports a streaming interface to
159 * reading the data: that is, it is capable of returning the data one row at a
160 * time, via repeated calls to read_row(). Returns false if the only way to
161 * read from this file is all at once, via read_data().
162 */
163bool PNMFileTypeSGI::Reader::
164supports_read_row() const {
165 return true;
166}
167
168/**
169 * If supports_read_row(), above, returns true, this function may be called
170 * repeatedly to read the image, one horizontal row at a time, beginning from
171 * the top. Returns true if the row is successfully read, false if there is
172 * an error or end of file.
173 */
174bool PNMFileTypeSGI::Reader::
175read_row(xel *row_data, xelval *alpha_data, int x_size, int y_size) {
176 if (!is_valid()) {
177 return false;
178 }
179 nassertr(current_row >= 0, false);
180
181 ScanElem *red = (ScanElem *)alloca(x_size * sizeof(ScanElem));
182 ScanElem *grn = (ScanElem *)alloca(x_size * sizeof(ScanElem));
183 ScanElem *blu = (ScanElem *)alloca(x_size * sizeof(ScanElem));
184 ScanElem *alpha = (ScanElem *)alloca(x_size * sizeof(ScanElem));
185
186 read_channel(_file, x_size, y_size, _num_channels, bpc, table, red,
187 table_start, 0, current_row);
188
189 if (!is_grayscale()) {
190 read_channel(_file, x_size, y_size, _num_channels, bpc, table, grn,
191 table_start, 1, current_row);
192 read_channel(_file, x_size, y_size, _num_channels, bpc, table, blu,
193 table_start, 2, current_row);
194 }
195
196 if (has_alpha()) {
197 read_channel(_file, x_size, y_size, _num_channels, bpc, table, alpha,
198 table_start, _num_channels - 1, current_row);
199 }
200
201 for (int x = 0; x < x_size; x++) {
202 if (is_grayscale()) {
203 PPM_PUTB(row_data[x], (xelval)red[x]);
204 } else {
205 xelval r, g, b;
206 r = (xelval)red[x];
207 g = (xelval)grn[x];
208 b = (xelval)blu[x];
209 PPM_ASSIGN(row_data[x], r, g, b);
210 }
211
212 if (has_alpha()) {
213 alpha_data[x] = (xelval)alpha[x];
214 }
215 }
216 current_row--;
217 return true;
218}
219
220
221
222static bool
223read_header(istream *ifp, Header *head, const string &magic_number) {
224 nassertr(magic_number.size() == 4, false);
225 head->magic =
226 ((unsigned char)magic_number[0] << 8) |
227 ((unsigned char)magic_number[1]);
228 head->storage = (unsigned char)magic_number[2];
229 head->bpc = (unsigned char)magic_number[3];
230 head->dimension = get_big_short(ifp);
231 head->xsize = get_big_short(ifp);
232 head->ysize = get_big_short(ifp);
233 head->zsize = get_big_short(ifp);
234 head->pixmin = get_big_long(ifp);
235 head->pixmax = get_big_long(ifp);
236 read_bytes(ifp, 4, head->dummy1);
237 read_bytes(ifp, 80, head->name);
238 head->colormap = get_big_long(ifp);
239 read_bytes(ifp, 404, head->dummy2);
240
241 if (head->magic != SGI_MAGIC) {
242 pnmimage_sgi_cat.error()
243 << "Invalid magic number: not an SGI image file.\n";
244 return false;
245 }
246
247 if (head->storage != 0 && head->storage != 1) {
248 pnmimage_sgi_cat.error()
249 << "Unknown compression type.\n";
250 return false;
251 }
252
253 if (head->bpc < 1 || head->bpc > 2) {
254 pnmimage_sgi_cat.error()
255 << "Illegal precision value " << head->bpc << " (only 1-2 allowed)\n";
256 return false;
257 }
258
259 // Actually, some old broken SGI image writers put garbage in this field,
260 // so just ignore it.
261 /*
262 if (head->colormap != CMAP_NORMAL) {
263 pnmimage_sgi_cat.error()
264 << "Unsupported non-normal pixel data (" << head->colormap << ")\n";
265 return false;
266 }
267 */
268
269 /* adjust ysize/zsize to dimension, just to be sure */
270
271 // On reflection, this is a bad idea. Ignore the number of dimensions,
272 // and take the xsizeysizezsize at face value. The table was written
273 // based on these numbers, after all; you can't just change them
274 // arbitrarily.
275
276 /*
277 switch( head->dimension ) {
278 case 1:
279 head->ysize = 1;
280 break;
281 case 2:
282 head->zsize = 1;
283 break;
284 case 3:
285 switch( head->zsize ) {
286 case 1:
287 case 2:
288 head->dimension = 2;
289 break;
290 case 3:
291 case 4:
292 break;
293
294 default:
295 pnmimage_sgi_cat.warning()
296 << "Using only first 4 channels of " << head->zsize
297 << "-channel image.\n";
298 head->zsize = 4;
299 break;
300 }
301 break;
302 default:
303 pnmimage_sgi_cat.error()
304 << "Illegal dimension value " << head->dimension
305 << " (only 1-3 allowed)\n";
306 return false;
307 }
308 */
309
310 return true;
311}
312
313
314static TabEntry *
315read_table(istream *ifp, int tablen) {
316 TabEntry *table;
317 int i;
318
319 table = MALLOC(tablen, TabEntry);
320
321 for( i = 0; i < tablen; i++ ) {
322 table[i].start = get_big_long(ifp);
323 }
324 for( i = 0; i < tablen; i++ ) {
325 table[i].length = get_big_long(ifp);
326 }
327
328 return table;
329}
330
331
332
333static void
334read_channel(istream *ifp,
335 int xsize, int ysize, int, int bpc,
336 TabEntry *table,
337 ScanElem *channel_data, long table_start,
338 int channel, int row) {
339 ScanElem *temp = nullptr;
340 int sgi_index, i;
341 long offset, length;
342
343 short (*func)(istream *);
344 func = (bpc==1) ? get_byte_as_short : get_big_short;
345
346 if ( table ) {
347 temp = (ScanElem *)alloca(WORSTCOMPR(xsize) * sizeof(ScanElem));
348 }
349
350 sgi_index = channel * ysize + row;
351 if( table ) {
352 offset = table[sgi_index].start;
353 length = table[sgi_index].length;
354 if( bpc == 2 )
355 length /= 2; /* doc says length is in bytes, we are reading words */
356 if(!ifp->seekg(offset))
357 pm_error("seek error for offset %ld", offset);
358
359 nassertv(length <= WORSTCOMPR(xsize));
360 for( i = 0; i < length; i++ )
361 temp[i] = (*func)(ifp);
362
363 rle_decompress(temp, length, channel_data, xsize);
364 }
365 else {
366 offset = sgi_index * xsize + table_start;
367 if(!ifp->seekg(offset))
368 pm_error("seek error for offset %ld", offset);
369 for( i = 0; i < xsize; i++ )
370 channel_data[i] = (*func)(ifp);
371 }
372}
373
374
375
376static void
377rle_decompress(ScanElem *src,
378 long srcleft,
379 ScanElem *dest,
380 long destleft) {
381 int count;
382 unsigned char el;
383
384 while( srcleft ) {
385 el = (unsigned char)(*src++ & 0xff);
386 --srcleft;
387 count = (int)(el & 0x7f);
388
389 if( count == 0 )
390 return;
391 if( destleft < count )
392 pm_error("RLE error: too much input data (space left %d, need %d)", destleft, count);
393 destleft -= count;
394 if( el & 0x80 ) {
395 if( srcleft < count )
396 pm_error("RLE error: not enough data for literal run (data left %d, need %d)", srcleft, count);
397 srcleft -= count;
398 while( count-- )
399 *dest++ = *src++;
400 }
401 else {
402 if( srcleft == 0 )
403 pm_error("RLE error: not enough data for replicate run");
404 while( count-- )
405 *dest++ = *src;
406 ++src;
407 --srcleft;
408 }
409 }
410 pm_error("RLE error: no terminating 0-byte");
411}
412
413
414/* basic I/O functions, taken from ilbmtoppm.c */
415
416static short
417get_big_short(istream *ifp) {
418 short s;
419
420 if( pm_readbigshort(ifp, &s) == -1 )
421 s = readerr(ifp);
422
423 return s;
424}
425
426static long
427get_big_long(istream *ifp) {
428 long l;
429
430 if( pm_readbiglong(ifp, &l) == -1 )
431 l = readerr(ifp);
432
433 return l;
434}
435
436static unsigned char
437get_byte(istream *ifp) {
438 int i;
439
440 i = ifp->get();
441 if( i == EOF )
442 i = readerr(ifp);
443
444 return (unsigned char) i;
445}
446
447
448static int
449readerr(istream *f) {
450 if (!eof_err) {
451 if (!f->eof()) {
452 pnmimage_sgi_cat.warning()
453 << "Read error on file.\n";
454 } else {
455 pnmimage_sgi_cat.warning()
456 << "Premature EOF on file.\n";
457 }
458 eof_err = true;
459 }
460
461 return 0;
462}
463
464
465static void
466read_bytes(istream *ifp,
467 int n,
468 char *buf) {
469 int r;
470
471 ifp->read(buf, n);
472 r = ifp->gcount();
473 if( r != n ) {
474 readerr(ifp);
475 memset(buf+r, 0, n-r);
476 }
478}
479
480
481static short
482get_byte_as_short(istream *ifp) {
483 return (short)get_byte(ifp);
484}
485
486
487static void *
488xmalloc(int bytes) {
489 void *mem;
490
491 if( bytes == 0 )
492 return nullptr;
493
494 mem = malloc(bytes);
495 if( mem == nullptr )
496 pm_error("out of memory allocating %d bytes", bytes);
497 return mem;
498}
499
500static const char *
501compression_name(char compr) {
502 switch( compr ) {
503 case STORAGE_VERBATIM:
504 return "none";
505 case STORAGE_RLE:
506 return "RLE";
507 default:
508 return "unknown";
509 }
510}
511
512#endif // HAVE_SGI_RGB
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
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition thread.I:212
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void pm_error(const char *format,...)
Outputs the given printf-style message to the user and terminates messily.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Definition sgi.h:15