Panda3D
 All Classes Functions Variables Enumerations
textureImage.cxx
1 // Filename: textureImage.cxx
2 // Created by: drose (29Nov00)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "textureImage.h"
16 #include "sourceTextureImage.h"
17 #include "destTextureImage.h"
18 #include "eggFile.h"
19 #include "paletteGroup.h"
20 #include "paletteImage.h"
21 #include "texturePlacement.h"
22 #include "filenameUnifier.h"
23 #include "string_utils.h"
24 #include "indent.h"
25 #include "datagram.h"
26 #include "datagramIterator.h"
27 #include "bamReader.h"
28 #include "bamWriter.h"
29 #include "pnmFileType.h"
30 #include "indirectCompareNames.h"
31 #include "pvector.h"
32 
33 #include <iterator>
34 
35 TypeHandle TextureImage::_type_handle;
36 
37 ////////////////////////////////////////////////////////////////////
38 // Function: TextureImage::Constructor
39 // Access: Public
40 // Description:
41 ////////////////////////////////////////////////////////////////////
42 TextureImage::
43 TextureImage() {
44  _preferred_source = (SourceTextureImage *)NULL;
45  _read_source_image = false;
46  _allow_release_source_image = true;
47  _is_surprise = true;
48  _ever_read_image = false;
49  _forced_grayscale = false;
50  _alpha_bits = 0;
51  _mid_pixel_ratio = 0.0;
52  _is_cutout = false;
53  _alpha_mode = EggRenderMode::AM_unspecified;
54  _txa_wrap_u = EggTexture::WM_unspecified;
55  _txa_wrap_v = EggTexture::WM_unspecified;
56  _texture_named = false;
57  _got_txa_file = false;
58 }
59 
60 ////////////////////////////////////////////////////////////////////
61 // Function: TextureImage::note_egg_file
62 // Access: Public
63 // Description: Records that a particular egg file references this
64 // texture. This is essential to know when deciding how
65 // to assign the TextureImage to the various
66 // PaletteGroups.
67 ////////////////////////////////////////////////////////////////////
68 void TextureImage::
69 note_egg_file(EggFile *egg_file) {
70  nassertv(!egg_file->get_complete_groups().empty());
71  _egg_files.insert(egg_file);
72 }
73 
74 ////////////////////////////////////////////////////////////////////
75 // Function: TextureImage::assign_groups
76 // Access: Public
77 // Description: Assigns the texture to all of the PaletteGroups the
78 // various egg files that use it need. Attempts to
79 // choose the minimum set of PaletteGroups that
80 // satisfies all of the egg files.
81 ////////////////////////////////////////////////////////////////////
82 void TextureImage::
84  if (_egg_files.empty()) {
85  // If we're not referenced by any egg files any more, assign us to
86  // no groups.
87  PaletteGroups empty;
88  assign_to_groups(empty);
89  return;
90  }
91 
92  PaletteGroups definitely_in;
93 
94  // First, we need to eliminate from consideration all the egg files
95  // that are already taken care of by the user's explicit group
96  // assignments for this texture.
97  WorkingEggs needed_eggs;
98 
99  if (_explicitly_assigned_groups.empty()) {
100  // If we have no explicit group assignments, we must consider all
101  // the egg files.
102  copy(_egg_files.begin(), _egg_files.end(), back_inserter(needed_eggs));
103 
104  } else {
105  // Otherwise, we only need to consider the egg files that don't
106  // have any groups in common with our explicit assignments.
107 
108  EggFiles::const_iterator ei;
109  for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
110  PaletteGroups intersect;
111  intersect.make_intersection(_explicitly_assigned_groups, (*ei)->get_complete_groups());
112  if (!intersect.empty()) {
113  // This egg file is satisfied by one of the texture's explicit
114  // assignments.
115 
116  // We must use at least one of the explicitly-assigned groups
117  // that satisfied the egg file. We don't need to use all of
118  // them, however, and we choose the first one arbitrarily.
119  definitely_in.insert(*intersect.begin());
120 
121  } else {
122  // This egg file was not satisfied by any of the texture's
123  // explicit assignments. Therefore, we'll need to choose some
124  // additional group to assign the texture to, to make the egg
125  // file happy. Defer this a bit.
126  needed_eggs.push_back(*ei);
127  }
128  }
129  }
130 
131  while (!needed_eggs.empty()) {
132  // We need to know the complete set of groups that we need to
133  // consider adding the texture to. This is the union of all the egg
134  // files' requested groups.
135  PaletteGroups total;
136  WorkingEggs::const_iterator ei;
137  for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) {
138  total.make_union(total, (*ei)->get_complete_groups());
139  }
140 
141  // We don't count the "null" group for texture assignment.
142  total.remove_null();
143  if (total.empty()) {
144  break;
145  }
146 
147  // Now, find the group that will satisfy the most egg files. If
148  // two groups satisfy the same number of egg files, choose (a) the
149  // most specific one, i.e. with the lowest dirname_level, or the
150  // lowest dependency_level if the dirname_levels are equal, and
151  // (b) the one that has the fewest egg files sharing it.
152  PaletteGroups::iterator gi = total.begin();
153  PaletteGroup *best = (*gi);
154  int best_egg_count = compute_egg_count(best, needed_eggs);
155  ++gi;
156  while (gi != total.end()) {
157  PaletteGroup *group = (*gi);
158 
159  // Do we prefer this group to our current 'best'?
160  bool prefer_group = false;
161  int group_egg_count = compute_egg_count(group, needed_eggs);
162  if (group_egg_count != best_egg_count) {
163  prefer_group = (group_egg_count > best_egg_count);
164 
165  } else {
166  prefer_group = group->is_preferred_over(*best);
167  }
168 
169  if (prefer_group) {
170  best = group;
171  best_egg_count = group_egg_count;
172  }
173  ++gi;
174  }
175 
176  // Okay, now we've picked the best group. Eliminate all the eggs
177  // from consideration that are satisfied by this group, and repeat.
178  definitely_in.insert(best);
179 
180  WorkingEggs next_needed_eggs;
181  for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) {
182  if ((*ei)->get_complete_groups().count(best) == 0) {
183  // This one wasn't eliminated.
184  next_needed_eggs.push_back(*ei);
185  }
186  }
187  needed_eggs.swap(next_needed_eggs);
188  }
189 
190  // Finally, now that we've computed the set of groups we need to
191  // assign the texture to, we need to reconcile this with the set of
192  // groups we've assigned the texture to previously.
193  assign_to_groups(definitely_in);
194 }
195 
196 ////////////////////////////////////////////////////////////////////
197 // Function: TextureImage::get_groups
198 // Access: Public
199 // Description: Once assign_groups() has been called, this returns
200 // the actual set of groups the TextureImage has been
201 // assigned to.
202 ////////////////////////////////////////////////////////////////////
204 get_groups() const {
205  return _actual_assigned_groups;
206 }
207 
208 ////////////////////////////////////////////////////////////////////
209 // Function: TextureImage::get_placement
210 // Access: Public
211 // Description: Gets the TexturePlacement object which represents the
212 // assignment of this texture to the indicated group.
213 // If the texture has not been assigned to the indicated
214 // group, returns NULL.
215 ////////////////////////////////////////////////////////////////////
218  Placement::const_iterator pi;
219  pi = _placement.find(group);
220  if (pi == _placement.end()) {
221  return (TexturePlacement *)NULL;
222  }
223 
224  return (*pi).second;
225 }
226 
227 ////////////////////////////////////////////////////////////////////
228 // Function: TextureImage::force_replace
229 // Access: Public
230 // Description: Removes the texture from any PaletteImages it is
231 // assigned to, but does not remove it from the groups.
232 // It will be re-placed within each group when
233 // PaletteGroup::place_all() is called.
234 ////////////////////////////////////////////////////////////////////
235 void TextureImage::
237  Placement::iterator pi;
238  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
239  (*pi).second->force_replace();
240  }
241 }
242 
243 ////////////////////////////////////////////////////////////////////
244 // Function: TextureImage::mark_eggs_stale
245 // Access: Public
246 // Description: Marks all the egg files that reference this texture
247 // stale. Should be called only when the texture
248 // properties change in some catastrophic way that will
249 // require every egg file referencing it to be
250 // regenerated, even if it is not palettized.
251 ////////////////////////////////////////////////////////////////////
252 void TextureImage::
254  Placement::iterator pi;
255  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
256  (*pi).second->mark_eggs_stale();
257  }
258 }
259 
260 ////////////////////////////////////////////////////////////////////
261 // Function: TextureImage::mark_texture_named
262 // Access: Public
263 // Description: Indicates that this particular texture has been named
264 // by the user for processing this session, normally by
265 // listing an egg file on the command line that
266 // references it.
267 ////////////////////////////////////////////////////////////////////
268 void TextureImage::
270  _texture_named = true;
271 }
272 
273 ////////////////////////////////////////////////////////////////////
274 // Function: TextureImage::is_texture_named
275 // Access: Public
276 // Description: Returns true if this particular texture has been
277 // named by the user for procession this session, for
278 // instance by listing an egg file on the command line
279 // that references it.
280 ////////////////////////////////////////////////////////////////////
281 bool TextureImage::
283  return _texture_named;
284 }
285 
286 ////////////////////////////////////////////////////////////////////
287 // Function: TextureImage::pre_txa_file
288 // Access: Public
289 // Description: Updates any internal state prior to reading the .txa
290 // file.
291 ////////////////////////////////////////////////////////////////////
292 void TextureImage::
294  // Save our current properties, so we can note if they change.
295  _pre_txa_properties = _properties;
296 
297  // Get our properties from the actual image for this texture. It's
298  // possible the .txa file will update them further.
300  if (source != (SourceTextureImage *)NULL) {
301  _properties = source->get_properties();
302  }
303 
304  _pre_txa_alpha_mode = _alpha_mode;
305  _alpha_mode = EggRenderMode::AM_unspecified;
306 
307  _request.pre_txa_file();
308  _is_surprise = true;
309 }
310 
311 ////////////////////////////////////////////////////////////////////
312 // Function: TextureImage::post_txa_file
313 // Access: Public
314 // Description: Once the .txa file has been read and the TextureImage
315 // matched against it, considers applying the requested
316 // size change. Updates the TextureImage's size with
317 // the size the texture ought to be, if this can be
318 // determined.
319 ////////////////////////////////////////////////////////////////////
320 void TextureImage::
322  _got_txa_file = true;
323 
324  // First, get the actual size of the texture.
326  if (source != (SourceTextureImage *)NULL) {
327  if (source->get_size()) {
328  _size_known = true;
329  _x_size = source->get_x_size();
330  _y_size = source->get_y_size();
331  _properties.set_num_channels(source->get_num_channels());
332  }
333  }
334 
335  // Now update this with a particularly requested size.
336  if (_request._got_size) {
337  _size_known = true;
338  _x_size = _request._x_size;
339  _y_size = _request._y_size;
340  }
341 
342  if (_txa_wrap_u != _request._wrap_u ||
343  _txa_wrap_v != _request._wrap_v) {
344  _txa_wrap_u = _request._wrap_u;
345  _txa_wrap_v = _request._wrap_v;
346 
347  // If the explicit wrap mode changes, we may need to regenerate
348  // the egg files, and/or refill the palettes.
349  mark_eggs_stale();
350 
351  Placement::iterator pi;
352  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
353  TexturePlacement *placement = (*pi).second;
354  placement->mark_unfilled();
355  }
356  }
357 
358  if (_properties.has_num_channels() && !_request._keep_format) {
359  int num_channels = _properties.get_num_channels();
360  // Examine the image to determine if we can downgrade the number
361  // of channels, for instance from color to grayscale.
362  if (num_channels == 3 || num_channels == 4) {
363  consider_grayscale();
364  }
365 
366  // Also consider the alpha properties, and whether we should
367  // downgrade from alpha to non-alpha.
368  if (num_channels == 2 || num_channels == 4) {
369  consider_alpha();
370  }
371  }
372 
373  // However, if we got an explicit request for channels, honor that.
374  if (_request._got_num_channels) {
375  _properties.set_num_channels(_request._num_channels);
376  }
377 
378  _properties._generic_format = _request._generic_format;
379  _properties._keep_format = _request._keep_format;
380 
381  if (_request._format != EggTexture::F_unspecified) {
382  _properties._format = _request._format;
383  _properties._force_format = _request._force_format;
384  }
385 
386  if (_request._minfilter != EggTexture::FT_unspecified) {
387  _properties._minfilter = _request._minfilter;
388  }
389  if (_request._magfilter != EggTexture::FT_unspecified) {
390  _properties._magfilter = _request._magfilter;
391  }
392 
393  _properties._anisotropic_degree = _request._anisotropic_degree;
394 
395  if (_properties._color_type == (PNMFileType *)NULL) {
396  _properties._color_type = _request._properties._color_type;
397  _properties._alpha_type = _request._properties._alpha_type;
398  }
399 
400  // Finally, make sure our properties are fully defined.
401  _properties.fully_define();
402 
403  // Now, if our properties have changed in all that from our previous
404  // session, we need to re-place ourself in all palette groups.
405  if (_properties != _pre_txa_properties) {
406  force_replace();
407 
408  // The above will mark the egg files stale when the texture is
409  // palettized (since the UV's will certainly need to be
410  // recomputed), but sometimes we need to mark the egg files stale
411  // even when the texture is not palettized (if a critical property
412  // has changed). The following accomplishes this:
413  if (!_properties.egg_properties_match(_pre_txa_properties)) {
414  mark_eggs_stale();
415  }
416  }
417 
418  // The alpha mode isn't stored in the properties, because it doesn't
419  // affect which textures may be associated into a common palette.
420  if (_request._alpha_mode != EggRenderMode::AM_unspecified) {
421  _alpha_mode = _request._alpha_mode;
422  }
423 
424  // On the other hand, if we don't have an alpha channel, we
425  // shouldn't have an alpha mode.
426  if (_properties.has_num_channels()) {
427  int num_channels = _properties.get_num_channels();
428  if (num_channels == 1 || num_channels == 3) {
429  _alpha_mode = EggRenderMode::AM_unspecified;
430  }
431  }
432 
433  // If we've changed the alpha mode, we should also mark the eggs
434  // stale.
435  if (_pre_txa_alpha_mode != _alpha_mode) {
436  mark_eggs_stale();
437  }
438 }
439 
440 ////////////////////////////////////////////////////////////////////
441 // Function: TextureImage::got_txa_file
442 // Access: Public
443 // Description: Returns true if this TextureImage has been looked up
444 // in the .txa file this session, false otherwise.
445 ////////////////////////////////////////////////////////////////////
446 bool TextureImage::
447 got_txa_file() const {
448  return _got_txa_file;
449 }
450 
451 ////////////////////////////////////////////////////////////////////
452 // Function: TextureImage::determine_placement_size
453 // Access: Public
454 // Description: Calls determine_size() on each TexturePlacement for
455 // the texture, to ensure that each TexturePlacement is
456 // still requesting the best possible size for the
457 // texture.
458 ////////////////////////////////////////////////////////////////////
459 void TextureImage::
461  Placement::iterator pi;
462  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
463  TexturePlacement *placement = (*pi).second;
464  placement->determine_size();
465  }
466 }
467 
468 ////////////////////////////////////////////////////////////////////
469 // Function: TextureImage::get_omit
470 // Access: Public
471 // Description: Returns true if the user specifically requested to
472 // omit this texture via the "omit" keyword in the .txa
473 // file, or false otherwise.
474 ////////////////////////////////////////////////////////////////////
475 bool TextureImage::
476 get_omit() const {
477  return _request._omit;
478 }
479 
480 ////////////////////////////////////////////////////////////////////
481 // Function: TextureImage::get_coverage_threshold
482 // Access: Public
483 // Description: Returns the appropriate coverage threshold for this
484 // texture. This is either the
485 // Palettizer::_coverage_threshold parameter, given
486 // globally via -r, or a particular value for this
487 // texture as supplied by the "coverage" keyword in the
488 // .txa file.
489 ////////////////////////////////////////////////////////////////////
490 double TextureImage::
492  return _request._coverage_threshold;
493 }
494 
495 ////////////////////////////////////////////////////////////////////
496 // Function: TextureImage::get_margin
497 // Access: Public
498 // Description: Returns the appropriate margin for this texture.
499 // This is either the Palettizer::_margin parameter, or
500 // a particular value for this texture as supplied by
501 // the "margin" keyword in the .txa file.
502 ////////////////////////////////////////////////////////////////////
503 int TextureImage::
504 get_margin() const {
505  return _request._margin;
506 }
507 
508 ////////////////////////////////////////////////////////////////////
509 // Function: TextureImage::is_surprise
510 // Access: Public
511 // Description: Returns true if this particular texture is a
512 // 'surprise', i.e. it wasn't matched by a line in the
513 // .txa file that didn't include the keyword 'cont'.
514 ////////////////////////////////////////////////////////////////////
515 bool TextureImage::
516 is_surprise() const {
517  if (_placement.empty()) {
518  // A texture that is not actually placed anywhere is not
519  // considered a surprise.
520  return false;
521  }
522 
523  return _is_surprise;
524 }
525 
526 ////////////////////////////////////////////////////////////////////
527 // Function: TextureImage::is_used
528 // Access: Public
529 // Description: Returns true if this particular texture has been
530 // placed somewhere, anywhere, or false if it is not
531 // used.
532 ////////////////////////////////////////////////////////////////////
533 bool TextureImage::
534 is_used() const {
535  return !_placement.empty();
536 }
537 
538 ////////////////////////////////////////////////////////////////////
539 // Function: TextureImage::get_alpha_mode
540 // Access: Public
541 // Description: Returns the alpha mode that should be used to render
542 // objects with this texture, as specified by the user
543 // or as determined from examining the texture's alpha
544 // channel.
545 ////////////////////////////////////////////////////////////////////
546 EggRenderMode::AlphaMode TextureImage::
547 get_alpha_mode() const {
548  return _alpha_mode;
549 }
550 
551 ////////////////////////////////////////////////////////////////////
552 // Function: TextureImage::get_txa_wrap_u
553 // Access: Public
554 // Description: Returns the wrap mode specified in the u direction in
555 // the txa file, or WM_unspecified.
556 ////////////////////////////////////////////////////////////////////
557 EggTexture::WrapMode TextureImage::
558 get_txa_wrap_u() const {
559  return _txa_wrap_u;
560 }
561 
562 ////////////////////////////////////////////////////////////////////
563 // Function: TextureImage::get_txa_wrap_v
564 // Access: Public
565 // Description: Returns the wrap mode specified in the v direction in
566 // the txa file, or WM_unspecified.
567 ////////////////////////////////////////////////////////////////////
568 EggTexture::WrapMode TextureImage::
569 get_txa_wrap_v() const {
570  return _txa_wrap_v;
571 }
572 
573 
574 ////////////////////////////////////////////////////////////////////
575 // Function: TextureImage::get_source
576 // Access: Public
577 // Description: Returns the SourceTextureImage corresponding to the
578 // given filename(s). If the given filename has never
579 // been used as a SourceTexture for this particular
580 // texture, creates a new SourceTextureImage and returns
581 // that.
582 ////////////////////////////////////////////////////////////////////
584 get_source(const Filename &filename, const Filename &alpha_filename,
585  int alpha_file_channel) {
586  string key = get_source_key(filename, alpha_filename, alpha_file_channel);
587 
588  Sources::iterator si;
589  si = _sources.find(key);
590  if (si != _sources.end()) {
591  return (*si).second;
592  }
593 
594  SourceTextureImage *source =
595  new SourceTextureImage(this, filename, alpha_filename, alpha_file_channel);
596  _sources.insert(Sources::value_type(key, source));
597 
598  // Clear out the preferred source image to force us to rederive this
599  // next time someone asks.
600  _preferred_source = (SourceTextureImage *)NULL;
601  _read_source_image = false;
602 
603  return source;
604 }
605 
606 ////////////////////////////////////////////////////////////////////
607 // Function: TextureImage::get_preferred_source
608 // Access: Public
609 // Description: Determines the preferred source image for examining
610 // size and reading pixels, etc. This is the largest
611 // and most recent of all the available source images.
612 ////////////////////////////////////////////////////////////////////
615  if (_preferred_source != (SourceTextureImage *)NULL) {
616  return _preferred_source;
617  }
618 
619  // Now examine all of the various source images available to us and
620  // pick the most suitable. We base this on the following criteria:
621 
622  // (1) A suitable source image must be referenced by at least one
623  // egg file, unless no source images are referenced by any egg file.
624 
625  // (2) A larger source image is preferable to a smaller one.
626 
627  // (3) Given two source images of the same size, the more recent one
628  // is preferable.
629 
630  // Are any source images referenced by an egg file?
631 
632  bool any_referenced = false;
633  Sources::iterator si;
634  for (si = _sources.begin(); si != _sources.end() && !any_referenced; ++si) {
635  SourceTextureImage *source = (*si).second;
636  if (source->get_egg_count() > 0) {
637  any_referenced = true;
638  }
639  }
640 
641  SourceTextureImage *best = (SourceTextureImage *)NULL;
642  int best_size = 0;
643 
644  for (si = _sources.begin(); si != _sources.end(); ++si) {
645  SourceTextureImage *source = (*si).second;
646 
647  if (source->get_egg_count() > 0 || !any_referenced) {
648  // Rule (1) passes.
649 
650  if (source->exists() && source->get_size()) {
651  int source_size = source->get_x_size() * source->get_y_size();
652  if (best == (SourceTextureImage *)NULL) {
653  best = source;
654  best_size = source_size;
655 
656  } else if (source_size > best_size) {
657  // Rule (2) passes.
658  best = source;
659  best_size = source_size;
660 
661  } else if (source_size == best_size &&
662  source->get_filename().compare_timestamps(best->get_filename()) > 0) {
663  // Rule (3) passes.
664  best = source;
665  best_size = source_size;
666  }
667  }
668  }
669  }
670 
671  if (best == (SourceTextureImage *)NULL && !_sources.empty()) {
672  // If we didn't pick any that pass, it must be that all of them
673  // are unreadable. In this case, it really doesn't matter which
674  // one we pick, but we should at least pick one that has an egg
675  // reference, if any of them do.
676  if (any_referenced) {
677  for (si = _sources.begin();
678  si != _sources.end() && best == (SourceTextureImage *)NULL;
679  ++si) {
680  SourceTextureImage *source = (*si).second;
681  if (source->get_egg_count() > 0) {
682  best = source;
683  }
684  }
685  } else {
686  best = (*_sources.begin()).second;
687  }
688  }
689 
690  _preferred_source = best;
691  return _preferred_source;
692 }
693 
694 ////////////////////////////////////////////////////////////////////
695 // Function: TextureImage::clear_source_basic_properties
696 // Access: Public
697 // Description: Calls clear_basic_properties() on each source texture
698 // image used by this texture, to reset the properties
699 // in preparation for re-applying them from the set of
700 // all known egg files.
701 ////////////////////////////////////////////////////////////////////
702 void TextureImage::
704  Sources::iterator si;
705  for (si = _sources.begin(); si != _sources.end(); ++si) {
706  SourceTextureImage *source = (*si).second;
707  source->clear_basic_properties();
708  }
709 }
710 
711 ////////////////////////////////////////////////////////////////////
712 // Function: TextureImage::copy_unplaced
713 // Access: Public
714 // Description: Copies the texture to whichever destination
715 // directories are appropriate for the groups in which
716 // it has been unplaced. Also removes the old filenames
717 // for previous sessions where it was unplaced, but is
718 // no longer.
719 //
720 // If redo_all is true, this recopies the texture
721 // whether it needed to or not.
722 ////////////////////////////////////////////////////////////////////
723 void TextureImage::
724 copy_unplaced(bool redo_all) {
725  // First, we need to build up the set of DestTextureImages that
726  // represents the files we need to generate.
727  Dests generate;
728 
729  // Go through all the TexturePlacements and note the ones for which
730  // we're unplaced. We check get_omit_reason() and not is_placed(),
731  // because we want to consider solitary images to be unplaced in
732  // this case.
733  Placement::iterator pi;
734  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
735  TexturePlacement *placement = (*pi).second;
736  if (placement->get_omit_reason() != OR_none &&
737  placement->get_omit_reason() != OR_unknown) {
738  DestTextureImage *dest = new DestTextureImage(placement);
739  Filename filename = dest->get_filename();
741 
742  pair<Dests::iterator, bool> insert_result = generate.insert
743  (Dests::value_type(filename, dest));
744  if (!insert_result.second) {
745  // At least two DestTextureImages map to the same filename, no
746  // sweat.
747  delete dest;
748  dest = (*insert_result.first).second;
749  }
750 
751  placement->set_dest(dest);
752 
753  } else {
754  placement->set_dest((DestTextureImage *)NULL);
755  }
756  }
757 
758  if (redo_all) {
759  // If we're redoing everything, we remove everything first and
760  // then recopy it again.
761  Dests empty;
762  remove_old_dests(empty, _dests);
763  copy_new_dests(generate, empty);
764 
765  } else {
766  // Otherwise, we only remove and recopy the things that changed
767  // between this time and last time.
768  remove_old_dests(generate, _dests);
769  copy_new_dests(generate, _dests);
770  }
771 
772  // Clean up the old set.
773  Dests::iterator di;
774  for (di = _dests.begin(); di != _dests.end(); ++di) {
775  delete (*di).second;
776  }
777 
778  _dests.swap(generate);
779 }
780 
781 ////////////////////////////////////////////////////////////////////
782 // Function: TextureImage::read_source_image
783 // Access: Public
784 // Description: Reads in the original image, if it has not already
785 // been read, and returns it.
786 ////////////////////////////////////////////////////////////////////
789  if (!_read_source_image) {
791  if (source != (SourceTextureImage *)NULL) {
792  source->read(_source_image);
793  }
794  _read_source_image = true;
795  _allow_release_source_image = true;
796  _ever_read_image = true;
797  }
798 
799  return _source_image;
800 }
801 
802 ////////////////////////////////////////////////////////////////////
803 // Function: TextureImage::release_source_image
804 // Access: Public
805 // Description: Frees the memory that was allocated by a previous
806 // call to read_source_image(). The next time
807 // read_source_image() is called, it will have to read
808 // the disk again.
809 ////////////////////////////////////////////////////////////////////
810 void TextureImage::
812  if (_read_source_image && _allow_release_source_image) {
813  _source_image.clear();
814  _read_source_image = false;
815  }
816 }
817 
818 ////////////////////////////////////////////////////////////////////
819 // Function: TextureImage::set_source_image
820 // Access: Public
821 // Description: Accepts the indicated source image as if it had been
822 // read from disk. This image is copied into the
823 // structure, and will be returned by future calls to
824 // read_source_image().
825 ////////////////////////////////////////////////////////////////////
826 void TextureImage::
827 set_source_image(const PNMImage &image) {
828  _source_image = image;
829  _allow_release_source_image = false;
830  _read_source_image = true;
831  _ever_read_image = true;
832 }
833 
834 ////////////////////////////////////////////////////////////////////
835 // Function: TextureImage::read_header
836 // Access: Public
837 // Description: Causes the header part of the image to be reread,
838 // usually to confirm that its image properties (size,
839 // number of channels, etc.) haven't changed.
840 ////////////////////////////////////////////////////////////////////
841 void TextureImage::
843  if (!_read_source_image) {
845  if (source != (SourceTextureImage *)NULL) {
846  source->read_header();
847  }
848  }
849 }
850 
851 ////////////////////////////////////////////////////////////////////
852 // Function: TextureImage::is_newer_than
853 // Access: Public
854 // Description: Returns true if the source image is newer than the
855 // indicated file, false otherwise. If the image has
856 // already been read, this always returns false.
857 ////////////////////////////////////////////////////////////////////
858 bool TextureImage::
859 is_newer_than(const Filename &reference_filename) {
860  if (!_read_source_image) {
862  if (source != (SourceTextureImage *)NULL) {
863  const Filename &source_filename = source->get_filename();
864  return source_filename.compare_timestamps(reference_filename) >= 0;
865  }
866  }
867 
868  return false;
869 }
870 
871 ////////////////////////////////////////////////////////////////////
872 // Function: TextureImage::write_source_pathnames
873 // Access: Public
874 // Description: Writes the list of source pathnames that might
875 // contribute to this texture to the indicated output
876 // stream, one per line.
877 ////////////////////////////////////////////////////////////////////
878 void TextureImage::
879 write_source_pathnames(ostream &out, int indent_level) const {
880  Sources::const_iterator si;
881  for (si = _sources.begin(); si != _sources.end(); ++si) {
882  SourceTextureImage *source = (*si).second;
883 
884  if (source->get_egg_count() > 0) {
885  indent(out, indent_level);
886  source->output_filename(out);
887  if (!source->is_size_known()) {
888  out << " (unknown size)";
889 
890  } else {
891  out << " " << source->get_x_size() << " "
892  << source->get_y_size();
893 
894  if (source->get_properties().has_num_channels()) {
895  out << " " << source->get_properties().get_num_channels();
896  }
897  }
898  out << "\n";
899  }
900  }
901 
902  if (_is_cutout) {
903  indent(out, indent_level)
904  << "Cutout image (ratio " << (PN_stdfloat)_mid_pixel_ratio << ")\n";
905  }
906 
907  // Now write out the group assignments.
908  if (!_egg_files.empty()) {
909  // Sort the egg files into order by name for output.
910  pvector<EggFile *> egg_vector;
911  egg_vector.reserve(_egg_files.size());
912  EggFiles::const_iterator ei;
913  for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
914  egg_vector.push_back(*ei);
915  }
916  sort(egg_vector.begin(), egg_vector.end(),
918 
919  indent(out, indent_level)
920  << "Used by:\n";
922  for (evi = egg_vector.begin(); evi != egg_vector.end(); ++evi) {
923  EggFile *egg = (*evi);
924  indent(out, indent_level + 2)
925  << egg->get_name() << " (";
926  if (egg->get_explicit_groups().empty()) {
927  out << *egg->get_default_group();
928  } else {
929  out << egg->get_explicit_groups();
930  }
931  out << ")\n";
932  }
933  }
934  if (!_explicitly_assigned_groups.empty()) {
935  indent(out, indent_level)
936  << "Explicitly assigned to " << _explicitly_assigned_groups << " in .txa\n";
937  }
938 
939  if (_placement.empty()) {
940  indent(out, indent_level)
941  << "Not used.\n";
942  } else {
943  indent(out, indent_level)
944  << "Assigned to " << _actual_assigned_groups << "\n";
945  }
946 }
947 
948 ////////////////////////////////////////////////////////////////////
949 // Function: TextureImage::write_scale_info
950 // Access: Public
951 // Description: Writes the information about the texture's size and
952 // placement.
953 ////////////////////////////////////////////////////////////////////
954 void TextureImage::
955 write_scale_info(ostream &out, int indent_level) {
957  indent(out, indent_level) << get_name();
958 
959  // Write the list of groups we're placed in.
960  if (_placement.empty()) {
961  out << " (not used)";
962  } else {
963  Placement::const_iterator pi;
964  pi = _placement.begin();
965  out << " (" << (*pi).second->get_group()->get_name();
966  ++pi;
967  while (pi != _placement.end()) {
968  out << " " << (*pi).second->get_group()->get_name();
969  ++pi;
970  }
971  out << ")";
972  }
973 
974  out << " orig ";
975 
976  if (source == (SourceTextureImage *)NULL ||
977  !source->is_size_known()) {
978  out << "unknown";
979  } else {
980  out << source->get_x_size() << " " << source->get_y_size()
981  << " " << source->get_num_channels();
982  }
983 
984  if (!_placement.empty() && is_size_known()) {
985  out << " new " << get_x_size() << " " << get_y_size()
986  << " " << get_num_channels();
987 
988  if (source != (SourceTextureImage *)NULL &&
989  source->is_size_known()) {
990  double scale =
991  100.0 * (((double)get_x_size() / (double)source->get_x_size()) +
992  ((double)get_y_size() / (double)source->get_y_size())) / 2.0;
993  out << " scale " << scale << "%";
994  }
995  }
996  out << "\n";
997 
998  // Also cross-reference the placed and unplaced information.
999  Placement::iterator pi;
1000  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
1001  TexturePlacement *placement = (*pi).second;
1002  if (placement->get_omit_reason() == OR_none) {
1003  PaletteImage *image = placement->get_image();
1004  nassertv(image != (PaletteImage *)NULL);
1005  indent(out, indent_level + 2)
1006  << "placed on "
1008  << "\n";
1009 
1010  } else if (placement->get_omit_reason() == OR_unknown) {
1011  indent(out, indent_level + 2)
1012  << "not placed because unknown.\n";
1013 
1014  } else {
1015  DestTextureImage *image = placement->get_dest();
1016  nassertv(image != (DestTextureImage *)NULL);
1017  indent(out, indent_level + 2)
1018  << "copied to "
1020  if (image->is_size_known() && is_size_known() &&
1021  (image->get_x_size() != get_x_size() ||
1022  image->get_y_size() != get_y_size())) {
1023  out << " at size " << image->get_x_size() << " "
1024  << image->get_y_size();
1025  if (source != (SourceTextureImage *)NULL &&
1026  source->is_size_known()) {
1027  double scale =
1028  100.0 * (((double)image->get_x_size() / (double)source->get_x_size()) +
1029  ((double)image->get_y_size() / (double)source->get_y_size())) / 2.0;
1030  out << " scale " << scale << "%";
1031  }
1032  }
1033  out << "\n";
1034  }
1035  }
1036 }
1037 
1038 ////////////////////////////////////////////////////////////////////
1039 // Function: TextureImage::compute_egg_count
1040 // Access: Private
1041 // Description: Counts the number of egg files in the indicated set
1042 // that will be satisfied if a texture is assigned to
1043 // the indicated group.
1044 ////////////////////////////////////////////////////////////////////
1045 int TextureImage::
1046 compute_egg_count(PaletteGroup *group,
1047  const TextureImage::WorkingEggs &egg_files) {
1048  int count = 0;
1049 
1050  WorkingEggs::const_iterator ei;
1051  for (ei = egg_files.begin(); ei != egg_files.end(); ++ei) {
1052  if ((*ei)->get_complete_groups().count(group) != 0) {
1053  count++;
1054  }
1055  }
1056 
1057  return count;
1058 }
1059 
1060 ////////////////////////////////////////////////////////////////////
1061 // Function: TextureImage::assign_to_groups
1062 // Access: Private
1063 // Description: Assigns the texture to the indicated set of groups.
1064 // If the texture was previously assigned to any of
1065 // these groups, keeps the same TexturePlacement object
1066 // for the assignment; at the same time, deletes any
1067 // TexturePlacement objects that represent groups we are
1068 // no longer assigned to.
1069 ////////////////////////////////////////////////////////////////////
1070 void TextureImage::
1071 assign_to_groups(const PaletteGroups &groups) {
1072  PaletteGroups::const_iterator gi;
1073  Placement::const_iterator pi;
1074 
1075  Placement new_placement;
1076 
1077  gi = groups.begin();
1078  pi = _placement.begin();
1079 
1080  while (gi != groups.end() && pi != _placement.end()) {
1081  PaletteGroup *a = (*gi);
1082  PaletteGroup *b = (*pi).first;
1083 
1084  if (a < b) {
1085  // Here's a group we're now assigned to that we weren't assigned
1086  // to previously.
1087  TexturePlacement *place = a->prepare(this);
1088  new_placement.insert
1089  (new_placement.end(), Placement::value_type(a, place));
1090  ++gi;
1091 
1092  } else if (b < a) {
1093  // Here's a group we're no longer assigned to.
1094  TexturePlacement *place = (*pi).second;
1095  delete place;
1096  ++pi;
1097 
1098  } else { // b == a
1099  // Here's a group we're still assigned to.
1100  TexturePlacement *place = (*pi).second;
1101  new_placement.insert
1102  (new_placement.end(), Placement::value_type(a, place));
1103  ++gi;
1104  ++pi;
1105  }
1106  }
1107 
1108  while (gi != groups.end()) {
1109  // Here's a group we're now assigned to that we weren't assigned
1110  // to previously.
1111  PaletteGroup *a = (*gi);
1112  TexturePlacement *place = a->prepare(this);
1113  new_placement.insert
1114  (new_placement.end(), Placement::value_type(a, place));
1115  ++gi;
1116  }
1117 
1118  while (pi != _placement.end()) {
1119  // Here's a group we're no longer assigned to.
1120  TexturePlacement *place = (*pi).second;
1121  delete place;
1122  ++pi;
1123  }
1124 
1125  _placement.swap(new_placement);
1126  _actual_assigned_groups = groups;
1127 }
1128 
1129 ////////////////////////////////////////////////////////////////////
1130 // Function: TextureImage::consider_grayscale
1131 // Access: Private
1132 // Description: Examines the actual contents of the image to
1133 // determine if it should maybe be considered a
1134 // grayscale image (even though it has separate rgb
1135 // components).
1136 ////////////////////////////////////////////////////////////////////
1137 void TextureImage::
1138 consider_grayscale() {
1139  // Since this isn't likely to change for a particular texture after
1140  // its creation, we save a bit of time by not performing this check
1141  // unless this is the first time we've ever seen this texture. This
1142  // will save us from having to load the texture images each time we
1143  // look at them. On the other hand, if we've already loaded up the
1144  // image, then go ahead.
1145  if (!_read_source_image && _ever_read_image) {
1146  if (_forced_grayscale) {
1147  _properties.force_grayscale();
1148  }
1149  return;
1150  }
1151 
1152  const PNMImage &source = read_source_image();
1153  if (!source.is_valid()) {
1154  return;
1155  }
1156 
1157  for (int y = 0; y < source.get_y_size(); y++) {
1158  for (int x = 0; x < source.get_x_size(); x++) {
1159  const xel &v = source.get_xel_val(x, y);
1160  if (PPM_GETR(v) != PPM_GETG(v) || PPM_GETR(v) != PPM_GETB(v)) {
1161  // Here's a colored pixel. We can't go grayscale.
1162  _forced_grayscale = false;
1163  return;
1164  }
1165  }
1166  }
1167 
1168  // All pixels in the image were grayscale!
1169  _properties.force_grayscale();
1170  _forced_grayscale = true;
1171 }
1172 
1173 ////////////////////////////////////////////////////////////////////
1174 // Function: TextureImage::consider_alpha
1175 // Access: Private
1176 // Description: Examines the actual contents of the image to
1177 // determine what alpha properties it has.
1178 ////////////////////////////////////////////////////////////////////
1179 void TextureImage::
1180 consider_alpha() {
1181  // As above, we don't bother doing this if we've already done this
1182  // in a previous session.
1183 
1184  // _alpha_bits == -1 indicates we have read an older textures.boo
1185  // file that didn't define these bits.
1186  if (_read_source_image || !_ever_read_image || _alpha_bits == -1) {
1187  _alpha_bits = 0;
1188  int num_mid_pixels = 0;
1189 
1190  const PNMImage &source = read_source_image();
1191  if (source.is_valid() && source.has_alpha()) {
1192  xelval maxval = source.get_maxval();
1193  for (int y = 0; y < source.get_y_size(); y++) {
1194  for (int x = 0; x < source.get_x_size(); x++) {
1195  xelval alpha_val = source.get_alpha_val(x, y);
1196  if (alpha_val == 0) {
1197  _alpha_bits |= AB_zero;
1198  } else if (alpha_val == maxval) {
1199  _alpha_bits |= AB_one;
1200  } else {
1201  _alpha_bits |= AB_mid;
1202  ++num_mid_pixels;
1203  }
1204  }
1205  }
1206  }
1207 
1208  int num_pixels = source.get_x_size() * source.get_y_size();
1209  _mid_pixel_ratio = 0.0;
1210  if (num_pixels != 0) {
1211  _mid_pixel_ratio = (double)num_mid_pixels / (double)num_pixels;
1212  }
1213  }
1214 
1215  _is_cutout = false;
1216 
1217  if (_alpha_bits != 0) {
1218  if (_alpha_bits == AB_one) {
1219  // All alpha pixels are white; drop the alpha channel.
1220  _properties.force_nonalpha();
1221 
1222  } else if (_alpha_bits == AB_zero) {
1223  // All alpha pixels are invisible; this is probably a mistake.
1224  // Drop the alpha channel and complain.
1225  _properties.force_nonalpha();
1226  if (_read_source_image) {
1227  nout << *this << " has an all-zero alpha channel; dropping alpha.\n";
1228  }
1229 
1230  } else if (_alpha_mode == EggRenderMode::AM_unspecified) {
1231  // Consider fiddling with the alpha mode, if the user hasn't
1232  // specified a particular alpha mode in the txa file.
1233  if ((_alpha_bits & AB_mid) == 0) {
1234  // No middle range bits: a binary alpha image.
1235  _alpha_mode = EggRenderMode::AM_binary;
1236 
1237  } else if ((_alpha_bits & AB_one) != 0 && _mid_pixel_ratio < pal->_cutout_ratio) {
1238  // At least some opaque bits, and relatively few middle range
1239  // bits: a cutout image.
1240  _alpha_mode = pal->_cutout_mode;
1241  _is_cutout = true;
1242 
1243  } else {
1244  // No opaque bits; just use regular alpha blending.
1245  _alpha_mode = EggRenderMode::AM_blend;
1246  }
1247  }
1248  }
1249 }
1250 
1251 ////////////////////////////////////////////////////////////////////
1252 // Function: TextureImage::remove_old_dests
1253 // Access: Private
1254 // Description: Removes all of the filenames named in b that are not
1255 // also named in a.
1256 ////////////////////////////////////////////////////////////////////
1257 void TextureImage::
1258 remove_old_dests(const TextureImage::Dests &a, const TextureImage::Dests &b) {
1259  Dests::const_iterator ai = a.begin();
1260  Dests::const_iterator bi = b.begin();
1261 
1262  while (ai != a.end() && bi != b.end()) {
1263  const string &astr = (*ai).first;
1264  const string &bstr = (*bi).first;
1265 
1266  if (astr < bstr) {
1267  // Here's a filename in a, not in b.
1268  ++ai;
1269 
1270  } else if (bstr < astr) {
1271  // Here's a filename in b, not in a.
1272  (*bi).second->unlink();
1273  ++bi;
1274 
1275  } else { // bstr == astr
1276  // Here's a filename in both a and b.
1277  ++ai;
1278  ++bi;
1279  }
1280  }
1281 
1282  while (bi != b.end()) {
1283  // Here's a filename in b, not in a.
1284  (*bi).second->unlink();
1285  ++bi;
1286  }
1287 
1288  while (ai != a.end()) {
1289  ++ai;
1290  }
1291 }
1292 
1293 ////////////////////////////////////////////////////////////////////
1294 // Function: TextureImage::copy_new_dests
1295 // Access: Private
1296 // Description: Copies a resized texture into each filename named in
1297 // a that is not also listed in b, or whose
1298 // corresponding listing in b is out of date.
1299 ////////////////////////////////////////////////////////////////////
1300 void TextureImage::
1301 copy_new_dests(const TextureImage::Dests &a, const TextureImage::Dests &b) {
1302  Dests::const_iterator ai = a.begin();
1303  Dests::const_iterator bi = b.begin();
1304 
1305  while (ai != a.end() && bi != b.end()) {
1306  const string &astr = (*ai).first;
1307  const string &bstr = (*bi).first;
1308 
1309  if (astr < bstr) {
1310  // Here's a filename in a, not in b.
1311  (*ai).second->copy(this);
1312  ++ai;
1313 
1314  } else if (bstr < astr) {
1315  // Here's a filename in b, not in a.
1316  ++bi;
1317 
1318  } else { // bstr == astr
1319  // Here's a filename in both a and b.
1320  (*ai).second->copy_if_stale((*bi).second, this);
1321  ++ai;
1322  ++bi;
1323  }
1324  }
1325 
1326  while (ai != a.end()) {
1327  // Here's a filename in a, not in b.
1328  (*ai).second->copy(this);
1329  ++ai;
1330  }
1331 }
1332 
1333 ////////////////////////////////////////////////////////////////////
1334 // Function: TextureImage::get_source_key
1335 // Access: Private
1336 // Description: Returns the key that a SourceTextureImage should be
1337 // stored in, given its one or two filenames.
1338 ////////////////////////////////////////////////////////////////////
1339 string TextureImage::
1340 get_source_key(const Filename &filename, const Filename &alpha_filename,
1341  int alpha_file_channel) {
1343  Filename a = FilenameUnifier::make_bam_filename(alpha_filename);
1344 
1345  return f.get_fullpath() + ":" + a.get_fullpath() + ":" +
1346  format_string(alpha_file_channel);
1347 }
1348 
1349 ////////////////////////////////////////////////////////////////////
1350 // Function: TextureImage::register_with_read_factory
1351 // Access: Public, Static
1352 // Description: Registers the current object as something that can be
1353 // read from a Bam file.
1354 ////////////////////////////////////////////////////////////////////
1355 void TextureImage::
1358  register_factory(get_class_type(), make_TextureImage);
1359 }
1360 
1361 ////////////////////////////////////////////////////////////////////
1362 // Function: TextureImage::write_datagram
1363 // Access: Public, Virtual
1364 // Description: Fills the indicated datagram up with a binary
1365 // representation of the current object, in preparation
1366 // for writing to a Bam file.
1367 ////////////////////////////////////////////////////////////////////
1368 void TextureImage::
1369 write_datagram(BamWriter *writer, Datagram &datagram) {
1370  ImageFile::write_datagram(writer, datagram);
1371  datagram.add_string(get_name());
1372 
1373  // We don't write out _request; this is re-read from the .txa file
1374  // each time.
1375 
1376  // We don't write out _pre_txa_properties; this is transitional.
1377 
1378  // We don't write out _preferred_source; this is redetermined each
1379  // session.
1380 
1381  datagram.add_bool(_is_surprise);
1382  datagram.add_bool(_ever_read_image);
1383  datagram.add_bool(_forced_grayscale);
1384  datagram.add_uint8(_alpha_bits);
1385  datagram.add_int16((int)_alpha_mode);
1386  datagram.add_float64(_mid_pixel_ratio);
1387  datagram.add_bool(_is_cutout);
1388  datagram.add_uint8((int)_txa_wrap_u);
1389  datagram.add_uint8((int)_txa_wrap_v);
1390 
1391  // We don't write out _explicitly_assigned_groups; this is re-read
1392  // from the .txa file each time.
1393 
1394  _actual_assigned_groups.write_datagram(writer, datagram);
1395 
1396  // We don't write out _egg_files; this is redetermined each session.
1397 
1398  datagram.add_uint32(_placement.size());
1399  Placement::const_iterator pi;
1400  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
1401  writer->write_pointer(datagram, (*pi).first);
1402  writer->write_pointer(datagram, (*pi).second);
1403  }
1404 
1405  datagram.add_uint32(_sources.size());
1406  Sources::const_iterator si;
1407  for (si = _sources.begin(); si != _sources.end(); ++si) {
1408  writer->write_pointer(datagram, (*si).second);
1409  }
1410 
1411  datagram.add_uint32(_dests.size());
1412  Dests::const_iterator di;
1413  for (di = _dests.begin(); di != _dests.end(); ++di) {
1414  writer->write_pointer(datagram, (*di).second);
1415  }
1416 }
1417 
1418 ////////////////////////////////////////////////////////////////////
1419 // Function: TextureImage::complete_pointers
1420 // Access: Public, Virtual
1421 // Description: Called after the object is otherwise completely read
1422 // from a Bam file, this function's job is to store the
1423 // pointers that were retrieved from the Bam file for
1424 // each pointer object written. The return value is the
1425 // number of pointers processed from the list.
1426 ////////////////////////////////////////////////////////////////////
1427 int TextureImage::
1429  int pi = ImageFile::complete_pointers(p_list, manager);
1430 
1431  pi += _actual_assigned_groups.complete_pointers(p_list + pi, manager);
1432 
1433  int i;
1434  for (i = 0; i < _num_placement; i++) {
1435  PaletteGroup *group;
1436  TexturePlacement *placement;
1437  DCAST_INTO_R(group, p_list[pi++], pi);
1438  DCAST_INTO_R(placement, p_list[pi++], pi);
1439  _placement.insert(Placement::value_type(group, placement));
1440  }
1441 
1442  for (i = 0; i < _num_sources; i++) {
1443  SourceTextureImage *source;
1444  DCAST_INTO_R(source, p_list[pi++], pi);
1445  string key = get_source_key(source->get_filename(),
1446  source->get_alpha_filename(),
1447  source->get_alpha_file_channel());
1448 
1449  bool inserted = _sources.insert(Sources::value_type(key, source)).second;
1450  if (!inserted) {
1451  nout << "Warning: texture key " << key
1452  << " is nonunique; texture lost.\n";
1453  }
1454  }
1455 
1456  for (i = 0; i < _num_dests; i++) {
1457  DestTextureImage *dest;
1458  DCAST_INTO_R(dest, p_list[pi++], pi);
1459  bool inserted = _dests.insert(Dests::value_type(dest->get_filename(), dest)).second;
1460  if (!inserted) {
1461  nout << "Warning: dest filename " << dest->get_filename()
1462  << " is nonunique; texture lost.\n";
1463  }
1464  }
1465 
1466  return pi;
1467 }
1468 
1469 ////////////////////////////////////////////////////////////////////
1470 // Function: TextureImage::make_TextureImage
1471 // Access: Protected
1472 // Description: This method is called by the BamReader when an object
1473 // of this type is encountered in a Bam file; it should
1474 // allocate and return a new object with all the data
1475 // read.
1476 ////////////////////////////////////////////////////////////////////
1477 TypedWritable *TextureImage::
1478 make_TextureImage(const FactoryParams &params) {
1479  TextureImage *me = new TextureImage;
1480  DatagramIterator scan;
1481  BamReader *manager;
1482 
1483  parse_params(params, scan, manager);
1484  me->fillin(scan, manager);
1485  return me;
1486 }
1487 
1488 ////////////////////////////////////////////////////////////////////
1489 // Function: TextureImage::fillin
1490 // Access: Protected
1491 // Description: Reads the binary data from the given datagram
1492 // iterator, which was written by a previous call to
1493 // write_datagram().
1494 ////////////////////////////////////////////////////////////////////
1495 void TextureImage::
1496 fillin(DatagramIterator &scan, BamReader *manager) {
1497  ImageFile::fillin(scan, manager);
1498  set_name(scan.get_string());
1499 
1500  _is_surprise = scan.get_bool();
1501  _ever_read_image = scan.get_bool();
1502  _forced_grayscale = scan.get_bool();
1503  _alpha_bits = scan.get_uint8();
1504  _alpha_mode = (EggRenderMode::AlphaMode)scan.get_int16();
1505  if (pal->_read_pi_version >= 16) {
1506  _mid_pixel_ratio = scan.get_float64();
1507  _is_cutout = scan.get_bool();
1508  } else {
1509  // Force a re-read of the image if we are upgrading to pi version 16.
1510  _ever_read_image = false;
1511  _mid_pixel_ratio = 0.0;
1512  _is_cutout = false;
1513  }
1514  if (pal->_read_pi_version >= 17) {
1515  _txa_wrap_u = (EggTexture::WrapMode)scan.get_uint8();
1516  _txa_wrap_v = (EggTexture::WrapMode)scan.get_uint8();
1517  }
1518 
1519  _actual_assigned_groups.fillin(scan, manager);
1520 
1521  _num_placement = scan.get_uint32();
1522  manager->read_pointers(scan, _num_placement * 2);
1523 
1524  _num_sources = scan.get_uint32();
1525  manager->read_pointers(scan, _num_sources);
1526  _num_dests = scan.get_uint32();
1527  manager->read_pointers(scan, _num_dests);
1528 }
void mark_eggs_stale()
Marks all the egg files that reference this texture stale.
void make_intersection(const PaletteGroups &a, const PaletteGroups &b)
Computes the intersection of PaletteGroups a and b, and stores the result in this object...
string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition: filename.I:398
const PaletteGroups & get_complete_groups() const
Returns the complete set of PaletteGroups that the egg file is assigned to.
Definition: eggFile.cxx:309
static Filename make_bam_filename(Filename filename)
Returns a new filename that&#39;s made relative to the bam file itself, suitable for writing to the bam f...
int get_margin() const
Returns the appropriate margin for this texture.
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Called after the object is otherwise completely read from a Bam file, this function&#39;s job is to store...
This represents a texture filename as it has been resized and copied to the map directory (e...
void add_uint8(PN_uint8 value)
Adds an unsigned 8-bit integer to the datagram.
Definition: datagram.I:138
bool determine_size()
Attempts to determine the appropriate size of the texture for the given placement.
bool get_bool()
Extracts a boolean value.
void add_string(const string &str)
Adds a variable-length string to the datagram.
Definition: datagram.I:351
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:68
void pre_txa_file()
Updates any internal state prior to reading the .txa file.
bool is_valid() const
Returns true if the image has been read in or correctly initialized with a height and width...
Definition: pnmImage.I:309
bool got_txa_file() const
Returns true if this TextureImage has been looked up in the .txa file this session, false otherwise.
DestTextureImage * get_dest() const
Returns the DestTextureImage that corresponds to this texture as it was copied to the install directo...
void add_float64(PN_float64 value)
Adds a 64-bit floating-point number to the datagram.
Definition: datagram.I:228
void remove_null()
Removes the special &quot;null&quot; group from the set.
bool is_newer_than(const Filename &reference_filename)
Returns true if the source image is newer than the indicated file, false otherwise.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:122
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Called after the object is otherwise completely read from a Bam file, this function&#39;s job is to store...
Definition: imageFile.cxx:519
void clear_source_basic_properties()
Calls clear_basic_properties() on each source texture image used by this texture, to reset the proper...
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object, in preparation for writing to a Bam file.
const Filename & get_alpha_filename() const
Returns the alpha filename of the image file.
Definition: imageFile.cxx:276
int get_num_channels() const
Returns the number of channels of the image.
Definition: imageFile.cxx:130
SourceTextureImage * get_source(const Filename &filename, const Filename &alpha_filename, int alpha_file_channel)
Returns the SourceTextureImage corresponding to the given filename(s).
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:37
void determine_placement_size()
Calls determine_size() on each TexturePlacement for the texture, to ensure that each TexturePlacement...
This is the highest level of grouping for TextureImages.
Definition: paletteGroup.h:47
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:35
iterator end() const
Returns an iterator suitable for traversing the set.
int get_alpha_file_channel() const
Returns the particular channel number of the alpha image file from which the alpha channel should be ...
Definition: imageFile.cxx:291
double get_coverage_threshold() const
Returns the appropriate coverage threshold for this texture.
static void register_with_read_factory()
Registers the current object as something that can be read from a Bam file.
void fillin(DatagramIterator &scan, BamReader *manager)
Reads the binary data from the given datagram iterator, which was written by a previous call to write...
bool empty() const
Returns true if the set is empty, false otherwise.
bool read_header()
Reads the actual image header to determine the image properties, like its size.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:73
PN_uint8 get_uint8()
Extracts an unsigned 8-bit integer.
void output_filename(ostream &out) const
Writes the filename (or pair of filenames) to the indicated output stream.
Definition: imageFile.cxx:483
int get_x_size() const
Returns the number of pixels in the X direction.
PN_uint32 get_uint32()
Extracts an unsigned 32-bit integer.
bool is_texture_named() const
Returns true if this particular texture has been named by the user for procession this session...
int get_y_size() const
Returns the number of pixels in the Y direction.
void pre_txa_file()
Sets some state up that must be set prior to reading the .txa file.
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition: imageFile.cxx:106
PN_int16 get_int16()
Extracts a signed 16-bit integer.
string get_string()
Extracts a variable-length string.
int get_egg_count() const
Returns the number of egg files that share this SourceTextureImage.
bool is_surprise() const
Returns true if this particular texture is a &#39;surprise&#39;, i.e.
void note_egg_file(EggFile *egg_file)
Records that a particular egg file references this texture.
SourceTextureImage * get_preferred_source()
Determines the preferred source image for examining size and reading pixels, etc. ...
xel & get_xel_val(int x, int y)
Returns the RGB color at the indicated pixel.
Definition: pnmImage.I:379
TexturePlacement * get_placement(PaletteGroup *group) const
Gets the TexturePlacement object which represents the assignment of this texture to the indicated gro...
void add_int16(PN_int16 value)
Adds a signed 16-bit integer to the datagram.
Definition: datagram.I:148
void force_replace()
Removes the texture from any PaletteImages it is assigned to, but does not remove it from the groups...
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object, in preparation for writing to a Bam file.
Definition: imageFile.cxx:498
bool is_preferred_over(const PaletteGroup &other) const
Returns true if this group should be preferred for adding textures over the other group...
int get_num_channels() const
Returns the number of channels (1 through 4) associated with the image.
bool exists() const
Returns true if the file or files named by the image file exist, false otherwise. ...
Definition: imageFile.cxx:303
void mark_unfilled()
Marks the texture as unfilled, so that it will need to be copied into the palette image again...
void add_bool(bool value)
Adds a boolean value to the datagram.
Definition: datagram.I:118
static Filename make_user_filename(Filename filename)
Returns a new filename that&#39;s made relative to the current directory, suitable for reporting to the u...
void read_header()
Causes the header part of the image to be reread, usually to confirm that its image properties (size...
OmitReason get_omit_reason() const
Returns the reason the texture has been omitted from a palette image, or OR_none if it has not...
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
bool is_used() const
Returns true if this particular texture has been placed somewhere, anywhere, or false if it is not us...
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
const Filename & get_filename() const
Returns the primary filename of the image file.
Definition: imageFile.cxx:263
EggTexture::WrapMode get_txa_wrap_u() const
Returns the wrap mode specified in the u direction in the txa file, or WM_unspecified.
void read_pointers(DatagramIterator &scan, int count)
A convenience function to read a contiguous list of pointers.
Definition: bamReader.cxx:694
An STL function object class, this is intended to be used on any ordered collection of pointers to cl...
static void make_canonical(Filename &filename)
Does the same thing as Filename::make_canonical()–it converts the filename to its canonical form–but ...
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:40
const PaletteGroups & get_explicit_groups() const
Returns the set of PaletteGroups that the egg file has been explicitly assigned to in the ...
Definition: eggFile.cxx:284
void copy_unplaced(bool redo_all)
Copies the texture to whichever destination directories are appropriate for the groups in which it ha...
PaletteGroup * get_default_group() const
Returns the PaletteGroup that was specified as the default group on the command line at the time the ...
Definition: eggFile.cxx:296
void clear_basic_properties()
Resets the properties to a neutral state, for instance in preparation for calling update_properties()...
Definition: imageFile.cxx:153
bool get_size()
Determines the size of the SourceTextureImage, if it is not already known.
This corresponds to a particular assignment of a TextureImage with a PaletteGroup, and specifically describes which PaletteImage (if any), and where on the PaletteImage, the TextureImage has been assigned to.
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object, in preparation for writing to a Bam file.
This is a texture image reference as it appears in an egg file: the source image of the texture...
const PaletteGroups & get_groups() const
Once assign_groups() has been called, this returns the actual set of groups the TextureImage has been...
EggRenderMode::AlphaMode get_alpha_mode() const
Returns the alpha mode that should be used to render objects with this texture, as specified by the u...
void assign_groups()
Assigns the texture to all of the PaletteGroups the various egg files that use it need...
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition: imageFile.cxx:93
int compare_timestamps(const Filename &other, bool this_missing_is_old=true, bool other_missing_is_old=true) const
Returns a number less than zero if the file named by this object is older than the given file...
Definition: filename.cxx:1521
xelval get_alpha_val(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition: pnmImage.I:503
bool has_num_channels() const
Returns true if the number of channels is known.
EggTexture::WrapMode get_txa_wrap_v() const
Returns the wrap mode specified in the v direction in the txa file, or WM_unspecified.
void mark_texture_named()
Indicates that this particular texture has been named by the user for processing this session...
void make_union(const PaletteGroups &a, const PaletteGroups &b)
Computes the union of PaletteGroups a and b, and stores the result in this object.
PN_float64 get_float64()
Extracts a 64-bit floating-point number.
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color, type, etc).
Definition: pnmImage.cxx:50
void insert(PaletteGroup *group)
Inserts a new group to the set, if it is not already there.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:213
void set_source_image(const PNMImage &image)
Accepts the indicated source image as if it had been read from disk.
const PNMImage & read_source_image()
Reads in the original image, if it has not already been read, and returns it.
PaletteImage * get_image() const
Returns the particular PaletteImage on which the texture has been placed.
void write_scale_info(ostream &out, int indent_level=0)
Writes the information about the texture&#39;s size and placement.
void add_uint32(PN_uint32 value)
Adds an unsigned 32-bit integer to the datagram.
Definition: datagram.I:192
A set of PaletteGroups.
Definition: paletteGroups.h:31
bool get_omit() const
Returns true if the user specifically requested to omit this texture via the &quot;omit&quot; keyword in the ...
xelval get_maxval() const
Returns the maximum channel value allowable for any pixel in this image; for instance, 255 for a typical 8-bit-per-channel image.
This is a single palette image, one of several within a PalettePage, which is in turn one of several ...
Definition: paletteImage.h:36
bool read(PNMImage &image) const
Reads in the image (or images, if the alpha_filename is separate) and stores it in the indicated PNMI...
Definition: imageFile.cxx:326
TexturePlacement * prepare(TextureImage *texture)
Marks the indicated Texture as ready for placing somewhere within this group, and returns a placehold...
void write_source_pathnames(ostream &out, int indent_level=0) const
Writes the list of source pathnames that might contribute to this texture to the indicated output str...
A class to retrieve the individual data elements previously stored in a Datagram. ...
This represents a single source texture that is referenced by one or more egg files.
Definition: textureImage.h:51
void set_dest(DestTextureImage *dest)
Sets the DestTextureImage that corresponds to this texture as it was copied to the install directory...
void post_txa_file()
Once the .txa file has been read and the TextureImage matched against it, considers applying the requ...
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
const TextureProperties & get_properties() const
Returns the grouping properties of the image.
Definition: imageFile.cxx:140
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:43
This represents a single egg file known to the palettizer.
Definition: eggFile.h:39
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Called after the object is otherwise completely read from a Bam file, this function&#39;s job is to store...
void write_pointer(Datagram &packet, const TypedWritable *dest)
The interface for writing a pointer to another object to a Bam file.
Definition: bamWriter.cxx:279
void release_source_image()
Frees the memory that was allocated by a previous call to read_source_image().
bool is_size_known() const
Returns true if the size of the image file is known, false otherwise.
Definition: imageFile.cxx:81
iterator begin() const
Returns an iterator suitable for traversing the set.