Panda3D
pnmFileTypeSoftImage.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 pnmFileTypeSoftImage.cxx
10  * @author drose
11  * @date 2000-06-17
12  */
13 
14 #include "pnmFileTypeSoftImage.h"
15 
16 #ifdef HAVE_SOFTIMAGE_PIC
17 
18 #include "config_pnmimagetypes.h"
19 
20 #include "pnmFileTypeRegistry.h"
21 #include "bamReader.h"
22 
23 using std::istream;
24 using std::ostream;
25 using std::string;
26 
27 static const float imageVersionNumber = 3.0;
28 static const int imageCommentLength = 80;
29 static const char imageComment[imageCommentLength+1] =
30  "Written by pnmimage.";
31 
32 // Values to indicate compresseduncompressed types
33 #define UNCOMPRESSED 0x00
34 #define MIXED_RUN_LENGTH 0x02
35 
36 // Bits to indicate channel type
37 #define RGB_CHANNEL 0xe0
38 #define ALPHA_CHANNEL 0x10
39 
40 // SoftImage magic number: high word, low word
41 #define SOFTIMAGE_MAGIC1 0x5380
42 #define SOFTIMAGE_MAGIC2 0xf634
43 
44 static const char * const extensions_softimage[] = {
45  "pic", "soft"
46 };
47 static const int num_extensions_softimage = sizeof(extensions_softimage) / sizeof(const char *);
48 
49 TypeHandle PNMFileTypeSoftImage::_type_handle;
50 
51 inline float
52 read_float(istream *file) {
53  long l;
54 
55  if (pm_readbiglong(file, &l)==0) {
56  return *(float *)&l;
57  } else {
58  return 0.0;
59  }
60 }
61 
62 inline unsigned short
63 read_ushort_SI(istream *file) {
64  unsigned short x;
65  return pm_readbigshort(file, (short *)&x)==0 ? x : 0;
66 }
67 
68 inline unsigned char
69 read_uchar_SI(istream *file) {
70  int x;
71  x = file->get();
72  return (x!=EOF) ? (unsigned char)x : 0;
73 }
74 
75 inline void
76 write_ushort_SI(ostream *file, unsigned short x) {
77  pm_writebigshort(file, (short)x);
78 }
79 
80 inline void
81 write_uchar_SI(ostream *file, unsigned char x) {
82  file->put(x);
83 }
84 
85 inline void
86 write_float(ostream *file, float x) {
87  pm_writebiglong(file, *(long *)&x);
88 }
89 
90 static int
91 read_channel_pkt(istream *file,
92  int &chained, int &size, int &type, int &channel) {
93  chained = read_uchar_SI(file);
94  size = read_uchar_SI(file);
95  type = read_uchar_SI(file);
96  channel = read_uchar_SI(file);
97 
98  if (file->eof() || file->fail()) {
99  return false;
100  }
101 
102  if (size!=8) {
103  pnmimage_soft_cat.error()
104  << "Don't know how to interpret " << size << " bits per pixel!\n";
105  return false;
106  }
107 
108  return true;
109 }
110 
111 static void
112 read_rgb(xel *row_data, xelval *, istream *file, int x, int repeat) {
113  xelval red, grn, blu;
114  red = read_uchar_SI(file);
115  grn = read_uchar_SI(file);
116  blu = read_uchar_SI(file);
117 
118  while (repeat>0) {
119  PPM_ASSIGN(row_data[x], red, grn, blu);
120  x++;
121  repeat--;
122  }
123 }
124 
125 static void
126 read_alpha(xel *, xelval *alpha_data, istream *file, int x, int repeat) {
127  xelval alpha = read_uchar_SI(file);
128 
129  while (repeat>0) {
130  alpha_data[x] = alpha;
131  x++;
132  repeat--;
133  }
134 }
135 
136 static void
137 read_rgba(xel *row_data, xelval *alpha_data, istream *file, int x, int repeat) {
138  xelval red, grn, blu, alpha;
139  red = read_uchar_SI(file);
140  grn = read_uchar_SI(file);
141  blu = read_uchar_SI(file);
142  alpha = read_uchar_SI(file);
143 
144  while (repeat>0) {
145  PPM_ASSIGN(row_data[x], red, grn, blu);
146  alpha_data[x] = alpha;
147  x++;
148  repeat--;
149  }
150 }
151 
152 
153 static int
154 read_scanline(xel *row_data, xelval *alpha_data, int cols, istream *file,
155  void (*read_data)(xel *row_data, xelval *alpha_data, istream *file,
156  int x, int repeat),
157  int ctype) {
158  if (ctype==UNCOMPRESSED) {
159  for (int x = 0; x<cols; x++) {
160  read_data(row_data, alpha_data, file, x, 1);
161  }
162  return true;
163  } else {
164  int x;
165  int num;
166 
167  x = 0;
168  while (x < cols) {
169  num = read_uchar_SI(file);
170 
171  if (num<128) {
172  // Sequence of non-repeated values.
173  num++;
174  if (x+num > cols) {
175  return false;
176  }
177  while (num>0) {
178  read_data(row_data, alpha_data, file, x, 1);
179  if (file->eof() || file->fail()) {
180  return false;
181  }
182  x++;
183  num--;
184  }
185  } else {
186  // Sequence of repeated values.
187  if (num==128) {
188  num = read_ushort_SI(file);
189  } else {
190  num -= 127;
191  }
192  if (x+num > cols) {
193  return false;
194  }
195  read_data(row_data, alpha_data, file, x, num);
196  if (file->eof() || file->fail()) {
197  return false;
198  }
199  x += num;
200  }
201  }
202 
203  return (x==cols);
204  }
205 }
206 
207 /**
208  *
209  */
210 PNMFileTypeSoftImage::
211 PNMFileTypeSoftImage() {
212 }
213 
214 /**
215  * Returns a few words describing the file type.
216  */
217 string PNMFileTypeSoftImage::
218 get_name() const {
219  return "SoftImage";
220 }
221 
222 /**
223  * Returns the number of different possible filename extensions_softimage
224  * associated with this particular file type.
225  */
226 int PNMFileTypeSoftImage::
227 get_num_extensions() const {
228  return num_extensions_softimage;
229 }
230 
231 /**
232  * Returns the nth possible filename extension associated with this particular
233  * file type, without a leading dot.
234  */
235 string PNMFileTypeSoftImage::
236 get_extension(int n) const {
237  nassertr(n >= 0 && n < num_extensions_softimage, string());
238  return extensions_softimage[n];
239 }
240 
241 /**
242  * Returns a suitable filename extension (without a leading dot) to suggest
243  * for files of this type, or empty string if no suggestions are available.
244  */
245 string PNMFileTypeSoftImage::
246 get_suggested_extension() const {
247  return "pic";
248 }
249 
250 /**
251  * Returns true if this particular file type uses a magic number to identify
252  * it, false otherwise.
253  */
254 bool PNMFileTypeSoftImage::
255 has_magic_number() const {
256  return true;
257 }
258 
259 /**
260  * Returns true if the indicated "magic number" byte stream (the initial few
261  * bytes read from the file) matches this particular file type, false
262  * otherwise.
263  */
264 bool PNMFileTypeSoftImage::
265 matches_magic_number(const string &magic_number) const {
266  nassertr(magic_number.size() >= 2, false);
267  int mn =
268  ((unsigned char)magic_number[0] << 8) |
269  ((unsigned char)magic_number[1]);
270  return (mn == SOFTIMAGE_MAGIC1);
271 }
272 
273 /**
274  * Allocates and returns a new PNMReader suitable for reading from this file
275  * type, if possible. If reading from this file type is not supported,
276  * returns NULL.
277  */
278 PNMReader *PNMFileTypeSoftImage::
279 make_reader(istream *file, bool owns_file, const string &magic_number) {
280  init_pnm();
281  return new Reader(this, file, owns_file, magic_number);
282 }
283 
284 /**
285  * Allocates and returns a new PNMWriter suitable for reading from this file
286  * type, if possible. If writing files of this type is not supported, returns
287  * NULL.
288  */
289 PNMWriter *PNMFileTypeSoftImage::
290 make_writer(ostream *file, bool owns_file) {
291  init_pnm();
292  return new Writer(this, file, owns_file);
293 }
294 
295 
296 /**
297  *
298  */
299 PNMFileTypeSoftImage::Reader::
300 Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
301  PNMReader(type, file, owns_file)
302 {
303  if (!read_magic_number(_file, magic_number, 4)) {
304  // No magic number, no image.
305  if (pnmimage_soft_cat.is_debug()) {
306  pnmimage_soft_cat.debug()
307  << "SoftImage image file appears to be empty.\n";
308  }
309  _is_valid = false;
310  return;
311  }
312 
313  int magic1 =
314  ((unsigned char)magic_number[0] << 8) |
315  ((unsigned char)magic_number[1]);
316  int magic2 =
317  ((unsigned char)magic_number[2] << 8) |
318  ((unsigned char)magic_number[3]);
319 
320  if (magic1 != SOFTIMAGE_MAGIC1 || magic2 != SOFTIMAGE_MAGIC2) {
321  _is_valid = false;
322  return;
323  }
324 
325  // skip version number
326  read_float(_file);
327 
328  // Skip comment
329  _file->seekg(imageCommentLength, std::ios::cur);
330 
331  char pict_id[4];
332  _file->read(pict_id, 4);
333  if (_file->gcount() < 4) {
334  _is_valid = false;
335  return;
336  }
337 
338  if (memcmp(pict_id, "PICT", 4)!=0) {
339  _is_valid = false;
340  return;
341  }
342 
343  _x_size = read_ushort_SI(_file);
344  _y_size = read_ushort_SI(_file);
345 
346  /* float ratio = */ read_float(_file);
347  /* int fields = */ read_ushort_SI(_file);
348  read_ushort_SI(_file);
349 
350  int chained, size, channel;
351  if (!read_channel_pkt(_file, chained, size, rgb_ctype, channel)) {
352  _is_valid = false;
353  return;
354  }
355 
356  soft_color = unknown;
357 
358  if (channel == (RGB_CHANNEL | ALPHA_CHANNEL)) {
359  // Four components in the first part: RGBA.
360  soft_color = rgba;
361 
362  } else if (channel == RGB_CHANNEL) {
363  // Three components in the first part: RGB.
364  soft_color = rgb;
365 
366  if (chained) {
367  if (!read_channel_pkt(_file, chained, size, alpha_ctype, channel)) {
368  _is_valid = false;
369  return;
370  }
371 
372  if (channel == ALPHA_CHANNEL) {
373  // Alpha component in the second part: RGBA.
374  soft_color = rgb_a;
375  }
376  }
377  }
378 
379  switch (soft_color) {
380  case rgb:
381  _num_channels = 3;
382  break;
383 
384  case rgba:
385  case rgb_a:
386  _num_channels = 4;
387  break;
388 
389  default:
390  pnmimage_soft_cat.error()
391  << "Image is not RGB or RGBA!\n";
392  _is_valid = false;
393  return;
394  }
395 
396  if (chained) {
397  pnmimage_soft_cat.error()
398  << "Unexpected additional channels in image file.\n";
399  _is_valid = false;
400  return;
401  }
402 
403  _maxval = 255;
404 
405  if (pnmimage_soft_cat.is_debug()) {
406  pnmimage_soft_cat.debug()
407  << "Reading SoftImage " << *this << "\n";
408  }
409 }
410 
411 
412 /**
413  * Returns true if this particular PNMReader supports a streaming interface to
414  * reading the data: that is, it is capable of returning the data one row at a
415  * time, via repeated calls to read_row(). Returns false if the only way to
416  * read from this file is all at once, via read_data().
417  */
418 bool PNMFileTypeSoftImage::Reader::
419 supports_read_row() const {
420  return true;
421 }
422 
423 /**
424  * If supports_read_row(), above, returns true, this function may be called
425  * repeatedly to read the image, one horizontal row at a time, beginning from
426  * the top. Returns true if the row is successfully read, false if there is
427  * an error or end of file.
428  */
429 bool PNMFileTypeSoftImage::Reader::
430 read_row(xel *row_data, xelval *alpha_data, int x_size, int) {
431  if (!is_valid()) {
432  return false;
433  }
434  switch (soft_color) {
435  case rgb:
436  if (!read_scanline(row_data, alpha_data, x_size, _file,
437  read_rgb, rgb_ctype)) {
438  return false;
439  }
440  break;
441 
442  case rgba:
443  if (!read_scanline(row_data, alpha_data, x_size, _file,
444  read_rgba, rgb_ctype)) {
445  return false;
446  }
447  break;
448 
449  case rgb_a:
450  if (!read_scanline(row_data, alpha_data, x_size, _file,
451  read_rgb, rgb_ctype)) {
452  return false;
453  }
454  if (!read_scanline(row_data, alpha_data, x_size, _file,
455  read_alpha, alpha_ctype)) {
456  return false;
457  }
458  break;
459 
460  default:
461  break;
462  }
463 
464  return true;
465 }
466 
467 
468 static void
469 write_channel_pkt(ostream *file,
470  int chained, int size, int type, int channel) {
471  write_uchar_SI(file, chained);
472  write_uchar_SI(file, size);
473  write_uchar_SI(file, type);
474  write_uchar_SI(file, channel);
475 }
476 
477 static void
478 write_rgb(xel *row_data, xelval *, ostream *file, int x) {
479  write_uchar_SI(file, PPM_GETR(row_data[x]));
480  write_uchar_SI(file, PPM_GETG(row_data[x]));
481  write_uchar_SI(file, PPM_GETB(row_data[x]));
482 }
483 
484 static int
485 compare_rgb(xel *row_data, xelval *, int x1, int x2) {
486  return PPM_EQUAL(row_data[x1], row_data[x2]);
487 }
488 
489 static void
490 write_gray(xel *row_data, xelval *, ostream *file, int x) {
491  write_uchar_SI(file, PPM_GETB(row_data[x]));
492  write_uchar_SI(file, PPM_GETB(row_data[x]));
493  write_uchar_SI(file, PPM_GETB(row_data[x]));
494 }
495 
496 static int
497 compare_gray(xel *row_data, xelval *, int x1, int x2) {
498  return (PPM_GETB(row_data[x1])==PPM_GETB(row_data[x2]));
499 }
500 
501 static void
502 write_alpha(xel *, xelval *alpha_data, ostream *file, int x) {
503  write_uchar_SI(file, alpha_data[x]);
504 }
505 
506 static int
507 compare_alpha(xel *, xelval *alpha_data, int x1, int x2) {
508  return (alpha_data[x1]==alpha_data[x2]);
509 }
510 
511 static void
512 write_diff(xel *row_data, xelval *alpha_data, ostream *file,
513  void (*write_data)(xel *row_data, xelval *alpha_data, ostream *file,
514  int x),
515  int tox, int length) {
516  if (length>0) {
517  nassertv(length<=128);
518 
519  write_uchar_SI(file, length-1);
520  while (length>0) {
521  length--;
522  write_data(row_data, alpha_data, file, tox-length);
523  }
524  }
525 }
526 
527 static void
528 write_same(xel *row_data, xelval *alpha_data, ostream *file,
529  void (*write_data)(xel *row_data, xelval *alpha_data, ostream *file,
530  int x),
531  int tox, int length) {
532  if (length==1) {
533  write_diff(row_data, alpha_data, file, write_data, tox, length);
534 
535  } else if (length>0) {
536  if (length<128) {
537  write_uchar_SI(file, length+127);
538  } else {
539  write_uchar_SI(file, 128);
540  write_ushort_SI(file, length);
541  }
542  write_data(row_data, alpha_data, file, tox);
543  }
544 }
545 
546 
547 static void
548 write_scanline(xel *row_data, xelval *alpha_data, int cols, ostream *file,
549  int (*compare_data)(xel *row_data, xelval *alpha_data,
550  int x1, int x2),
551  void (*write_data)(xel *row_data, xelval *alpha_data,
552  ostream *file, int x)) {
553  int run_length = 0;
554 
555  int x = 0;
556  int same = true;
557 
558  // Go through each value in the scanline, from beginning to end, looking for
559  // runs of identical values.
560  while (x < cols) {
561 
562  if (same) {
563 
564  // We have been scanning past a run of identical values. In this case,
565  // the run is the sequence of values from x-run_length to x-1.
566 
567  if (!compare_data(row_data, alpha_data, x, x-run_length)) {
568  // Oops, the end of a run.
569 
570  if (run_length <= 1) {
571  // If run_length is only 1, no big deal--this is actually the
572  // beginning of a different-valued run.
573 
574  same = false;
575 
576  } else {
577  // Write out the old run and begin a new one. We'll be optimistic
578  // and hope the new run will also represent a sequence of identical
579  // values (until we find otherwise).
580 
581  write_same(row_data, alpha_data, file, write_data, x-1, run_length);
582  same = true;
583  run_length = 0;
584  }
585  }
586 
587  } else { // !same
588 
589  // We have been scanning past a run of different values. In this case,
590  // the run is the sequence of values from x-run_length to x-1.
591 
592  if (run_length>128) {
593  // We can't have different runs of more than 128 characters. Close
594  // off the old run.
595 
596  int excess = run_length - 128;
597  write_diff(row_data, alpha_data, file, write_data, x-excess-1, 128);
598  run_length = excess;
599 
600  } else if (run_length > 2 &&
601  compare_data(row_data, alpha_data, x, x-1) &&
602  compare_data(row_data, alpha_data, x, x-2)) {
603 
604  // If the last three values have been the same, then it's time to
605  // begin a new run of similar values. Close off the old run.
606 
607  write_diff(row_data, alpha_data, file, write_data, x-3, run_length-2);
608  same = true;
609  run_length = 2;
610  }
611  }
612 
613  x++;
614  run_length++;
615  }
616 
617  // We made it all the way to the end. Flush out the last run.
618 
619  if (run_length>0) {
620  if (same) {
621  write_same(row_data, alpha_data, file, write_data, cols-1, run_length);
622  } else {
623 
624  // Mighty unlikely, but we might have just run over the 128-pixel limit.
625  if (run_length>128) {
626  int excess = run_length - 128;
627  write_diff(row_data, alpha_data, file, write_data, cols-excess-1, 128);
628  run_length = excess;
629  }
630 
631  write_diff(row_data, alpha_data, file, write_data, cols-1, run_length);
632  }
633  }
634 }
635 
636 /**
637  *
638  */
639 PNMFileTypeSoftImage::Writer::
640 Writer(PNMFileType *type, ostream *file, bool owns_file) :
641  PNMWriter(type, file, owns_file)
642 {
643 }
644 
645 /**
646  * Returns true if this particular PNMWriter supports a streaming interface to
647  * writing the data: that is, it is capable of writing the image one row at a
648  * time, via repeated calls to write_row(). Returns false if the only way to
649  * write from this file is all at once, via write_data().
650  */
651 bool PNMFileTypeSoftImage::Writer::
652 supports_write_row() const {
653  return true;
654 }
655 
656 /**
657  * If supports_write_row(), above, returns true, this function may be called
658  * to write out the image header in preparation to writing out the image data
659  * one row at a time. Returns true if the header is successfully written,
660  * false if there is an error.
661  *
662  * It is the user's responsibility to fill in the header data via calls to
663  * set_x_size(), set_num_channels(), etc., or copy_header_from(), before
664  * calling write_header().
665  */
666 bool PNMFileTypeSoftImage::Writer::
667 write_header() {
668  write_ushort_SI(_file, SOFTIMAGE_MAGIC1);
669  write_ushort_SI(_file, SOFTIMAGE_MAGIC2);
670  write_float(_file, imageVersionNumber);
671 
672  _file->write(imageComment, imageCommentLength);
673  _file->write("PICT", 4);
674 
675  write_ushort_SI(_file, _x_size);
676  write_ushort_SI(_file, _y_size);
677 
678  write_float(_file, 1.0); // pixel aspect ratio; we don't know.
679  write_ushort_SI(_file, 3); // fields value; we don't really know either.
680  write_ushort_SI(_file, 0); // padding
681 
682  // There doesn't seem to be a variation on SoftImage image formats for
683  // grayscale images. We'll write out grayscale as a 3-channel image.
684 
685  if (has_alpha()) {
686  write_channel_pkt(_file, 1, 8, MIXED_RUN_LENGTH, RGB_CHANNEL);
687  write_channel_pkt(_file, 0, 8, MIXED_RUN_LENGTH, ALPHA_CHANNEL);
688  } else {
689  write_channel_pkt(_file, 0, 8, MIXED_RUN_LENGTH, RGB_CHANNEL);
690  }
691 
692  return true;
693 }
694 
695 /**
696  * If supports_write_row(), above, returns true, this function may be called
697  * repeatedly to write the image, one horizontal row at a time, beginning from
698  * the top. Returns true if the row is successfully written, false if there
699  * is an error.
700  *
701  * You must first call write_header() before writing the individual rows. It
702  * is also important to delete the PNMWriter class after successfully writing
703  * the last row. Failing to do this may result in some data not getting
704  * flushed!
705  */
706 bool PNMFileTypeSoftImage::Writer::
707 write_row(xel *row_data, xelval *alpha_data) {
708  if (is_grayscale()) {
709  write_scanline(row_data, alpha_data, _x_size, _file, compare_gray, write_gray);
710 
711  } else {
712  write_scanline(row_data, alpha_data, _x_size, _file, compare_rgb, write_rgb);
713  }
714 
715  if (has_alpha()) {
716  write_scanline(row_data, alpha_data, _x_size, _file, compare_alpha, write_alpha);
717  }
718 
719  return !_file->fail();
720 }
721 
722 
723 
724 /**
725  * Registers the current object as something that can be read from a Bam file.
726  */
727 void PNMFileTypeSoftImage::
728 register_with_read_factory() {
730  register_factory(get_class_type(), make_PNMFileTypeSoftImage);
731 }
732 
733 /**
734  * This method is called by the BamReader when an object of this type is
735  * encountered in a Bam file; it should allocate and return a new object with
736  * all the data read.
737  *
738  * In the case of the PNMFileType objects, since these objects are all shared,
739  * we just pull the object from the registry.
740  */
741 TypedWritable *PNMFileTypeSoftImage::
742 make_PNMFileTypeSoftImage(const FactoryParams &params) {
743  return PNMFileTypeRegistry::get_global_ptr()->get_type_by_handle(get_class_type());
744 }
745 
746 #endif // HAVE_SOFTIMAGE_PIC
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PNMFileType * get_type_by_handle(TypeHandle handle) const
Returns the PNMFileType instance stored in the registry for the given TypeHandle, e....
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is an abstract base class that defines the interface for reading image files of various types.
Definition: pnmReader.h:27
This is an abstract base class that defines the interface for writing image files of various types.
Definition: pnmWriter.h:27
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.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81