Panda3D
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...
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...
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
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
This is our own Panda specialization on the default STL map.
Definition: pmap.h:52
int get_num_channels() const
Returns the number of channels (1 through 4) associated with the image.
iterator end() const
Returns an iterator suitable for traversing the set.
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 exists() const
Returns true if the file or files named by the image file exist, false otherwise. ...
Definition: imageFile.cxx:303
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 "null" group from the set.
string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition: filename.I:398
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
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.
int get_egg_count() const
Returns the number of egg files that share this SourceTextureImage.
SourceTextureImage * get_source(const Filename &filename, const Filename &alpha_filename, int alpha_file_channel)
Returns the SourceTextureImage corresponding to the given filename(s).
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition: imageFile.cxx:106
PaletteImage * get_image() const
Returns the particular PaletteImage on which the texture has been placed.
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:37
iterator begin() const
Returns an iterator suitable for traversing the set.
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
void output_filename(ostream &out) const
Writes the filename (or pair of filenames) to the indicated output stream.
Definition: imageFile.cxx:483
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 read_header()
Reads the actual image header to determine the image properties, like its size.
TexturePlacement * get_placement(PaletteGroup *group) const
Gets the TexturePlacement object which represents the assignment of this texture to the indicated gro...
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:73
EggTexture::WrapMode get_txa_wrap_v() const
Returns the wrap mode specified in the v direction in the txa file, or WM_unspecified.
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...
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
PN_uint8 get_uint8()
Extracts an unsigned 8-bit integer.
PN_uint32 get_uint32()
Extracts an unsigned 32-bit integer.
const PaletteGroups & get_complete_groups() const
Returns the complete set of PaletteGroups that the egg file is assigned to.
Definition: eggFile.cxx:309
void pre_txa_file()
Sets some state up that must be set prior to reading the .txa file.
PN_int16 get_int16()
Extracts a signed 16-bit integer.
string get_string()
Extracts a variable-length string.
int get_y_size() const
Returns the number of pixels in the Y direction.
void note_egg_file(EggFile *egg_file)
Records that a particular egg file references this texture.
int get_x_size() const
Returns the number of pixels in the X direction.
SourceTextureImage * get_preferred_source()
Determines the preferred source image for examining size and reading pixels, etc. ...
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
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:39
xel & get_xel_val(int x, int y)
Returns the RGB color at the indicated pixel.
Definition: pnmImage.I:379
bool is_used() const
Returns true if this particular texture has been placed somewhere, anywhere, or false if it is not us...
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
const TextureProperties & get_properties() const
Returns the grouping properties of the image.
Definition: imageFile.cxx:140
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...
bool is_size_known() const
Returns true if the size of the image file is known, false otherwise.
Definition: imageFile.cxx:81
void read_header()
Causes the header part of the image to be reread, usually to confirm that its image properties (size...
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...
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition: imageFile.cxx:93
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
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.
bool get_omit() const
Returns true if the user specifically requested to omit this texture via the "omit" keyword in the ...
void read_pointers(DatagramIterator &scan, int count)
A convenience function to read a contiguous list of pointers.
Definition: bamReader.cxx:700
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–bu...
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:40
void copy_unplaced(bool redo_all)
Copies the texture to whichever destination directories are appropriate for the groups in which it ha...
int get_num_channels() const
Returns the number of channels of the image.
Definition: imageFile.cxx:130
bool empty() const
Returns true if the set is empty, false otherwise.
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...
void assign_groups()
Assigns the texture to all of the PaletteGroups the various egg files that use it need...
double get_coverage_threshold() const
Returns the appropriate coverage threshold for this texture.
bool has_num_channels() const
Returns true if the number of channels is known.
OmitReason get_omit_reason() const
Returns the reason the texture has been omitted from a palette image, or OR_none if it has not...
int get_margin() const
Returns the appropriate margin for this texture.
bool is_surprise() const
Returns true if this particular texture is a &#39;surprise&#39;, i.e.
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
const Filename & get_alpha_filename() const
Returns the alpha filename of the image file.
Definition: imageFile.cxx:276
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.
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
DestTextureImage * get_dest() const
Returns the DestTextureImage that corresponds to this texture as it was copied to the install directo...
bool is_texture_named() const
Returns true if this particular texture has been named by the user for procession this session...
This is a single palette image, one of several within a PalettePage, which is in turn one of several ...
Definition: paletteImage.h:36
bool is_preferred_over(const PaletteGroup &other) const
Returns true if this group should be preferred for adding textures over the other group...
TexturePlacement * prepare(TextureImage *texture)
Marks the indicated Texture as ready for placing somewhere within this group, and returns a placehold...
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...
bool got_txa_file() const
Returns true if this TextureImage has been looked up in the .txa file this session, false otherwise.
void post_txa_file()
Once the .txa file has been read and the TextureImage matched against it, considers applying the requ...
xelval get_alpha_val(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition: pnmImage.I:503
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
const Filename & get_filename() const
Returns the primary filename of the image file.
Definition: imageFile.cxx:263
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:43
EggTexture::WrapMode get_txa_wrap_u() const
Returns the wrap mode specified in the u direction in the txa file, or WM_unspecified.
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...
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
const PaletteGroups & get_groups() const
Once assign_groups() has been called, this returns the actual set of groups the TextureImage has been...
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().