Panda3D
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 
18 #include "config_pnmimagetypes.h"
19 #include "sgi.h"
20 
21 #include "pnmImage.h"
22 #include "pnmReader.h"
23 
24 #include "pnotify.h"
25 
26 using std::istream;
27 using 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 */
50 typedef PNMFileTypeSGI::Reader::TabEntry TabEntry;
51 
52 typedef short ScanElem;
53 typedef ScanElem * ScanLine;
54 
55 /* prototypes */
56 static unsigned char get_byte ( istream* f );
57 static long get_big_long (istream *f);
58 static short get_big_short (istream *f);
59 static short get_byte_as_short (istream *f);
60 static int readerr (istream *f);
61 static void * xmalloc (int bytes);
62 #define MALLOC(n, type) (type *)xmalloc((n) * sizeof(type))
63 static const char * compression_name (char compr);
64 static void read_bytes (istream *ifp, int n, char *buf);
65 static bool read_header(istream *ifp, Header *head, const string &magic_number);
66 static TabEntry * read_table (istream *ifp, int tablen);
67 static 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);
71 static 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.
81 static bool eof_err = false;
82 
83 
84 /**
85  *
86  */
87 PNMFileTypeSGI::Reader::
88 Reader(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  */
150 PNMFileTypeSGI::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  */
163 bool PNMFileTypeSGI::Reader::
164 supports_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  */
174 bool PNMFileTypeSGI::Reader::
175 read_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 
222 static bool
223 read_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 
314 static TabEntry *
315 read_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 
333 static void
334 read_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 
376 static void
377 rle_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 
416 static short
417 get_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 
426 static long
427 get_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 
436 static unsigned char
437 get_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 
448 static int
449 readerr(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 
465 static void
466 read_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 
481 static short
482 get_byte_as_short(istream *ifp) {
483  return (short)get_byte(ifp);
484 }
485 
486 
487 static void *
488 xmalloc(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 
500 static const char *
501 compression_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
Header
Definition: sgi.h:15
PNMImageHeader::read_header
bool read_header(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Opens up the image file and tries to read its header information to determine its size,...
Definition: pnmImageHeader.cxx:33
pnmImage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnmReader.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Thread::consider_yield
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
pnotify.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pm_error
void pm_error(const char *format,...)
Outputs the given printf-style message to the user and terminates messily.
Definition: pnmimage_base.cxx:54
pnmFileTypeSGI.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PNMReader
This is an abstract base class that defines the interface for reading image files of various types.
Definition: pnmReader.h:27
sgi.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
config_pnmimagetypes.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pixel
Definition: pnmimage_base.h:41
PNMImageHeader::read_magic_number
static bool read_magic_number(std::istream *file, std::string &magic_number, int num_bytes)
Ensures that the first n bytes of the file are read into magic_number.
Definition: pnmImageHeader.cxx:365
PNMFileType
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32