textureImage.cxx

00001 // Filename: textureImage.cxx
00002 // Created by:  drose (29Nov00)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "textureImage.h"
00016 #include "sourceTextureImage.h"
00017 #include "destTextureImage.h"
00018 #include "eggFile.h"
00019 #include "paletteGroup.h"
00020 #include "paletteImage.h"
00021 #include "texturePlacement.h"
00022 #include "filenameUnifier.h"
00023 #include "string_utils.h"
00024 #include "indent.h"
00025 #include "datagram.h"
00026 #include "datagramIterator.h"
00027 #include "bamReader.h"
00028 #include "bamWriter.h"
00029 #include "pnmFileType.h"
00030 #include "indirectCompareNames.h"
00031 #include "pvector.h"
00032 
00033 #include <iterator>
00034 
00035 TypeHandle TextureImage::_type_handle;
00036 
00037 ////////////////////////////////////////////////////////////////////
00038 //     Function: TextureImage::Constructor
00039 //       Access: Public
00040 //  Description:
00041 ////////////////////////////////////////////////////////////////////
00042 TextureImage::
00043 TextureImage() {
00044   _preferred_source = (SourceTextureImage *)NULL;
00045   _read_source_image = false;
00046   _allow_release_source_image = true;
00047   _is_surprise = true;
00048   _ever_read_image = false;
00049   _forced_grayscale = false;
00050   _alpha_bits = 0;
00051   _mid_pixel_ratio = 0.0;
00052   _is_cutout = false;
00053   _alpha_mode = EggRenderMode::AM_unspecified;
00054   _txa_wrap_u = EggTexture::WM_unspecified;
00055   _txa_wrap_v = EggTexture::WM_unspecified;
00056   _texture_named = false;
00057   _got_txa_file = false;
00058 }
00059 
00060 ////////////////////////////////////////////////////////////////////
00061 //     Function: TextureImage::note_egg_file
00062 //       Access: Public
00063 //  Description: Records that a particular egg file references this
00064 //               texture.  This is essential to know when deciding how
00065 //               to assign the TextureImage to the various
00066 //               PaletteGroups.
00067 ////////////////////////////////////////////////////////////////////
00068 void TextureImage::
00069 note_egg_file(EggFile *egg_file) {
00070   nassertv(!egg_file->get_complete_groups().empty());
00071   _egg_files.insert(egg_file);
00072 }
00073 
00074 ////////////////////////////////////////////////////////////////////
00075 //     Function: TextureImage::assign_groups
00076 //       Access: Public
00077 //  Description: Assigns the texture to all of the PaletteGroups the
00078 //               various egg files that use it need.  Attempts to
00079 //               choose the minimum set of PaletteGroups that
00080 //               satisfies all of the egg files.
00081 ////////////////////////////////////////////////////////////////////
00082 void TextureImage::
00083 assign_groups() {
00084   if (_egg_files.empty()) {
00085     // If we're not referenced by any egg files any more, assign us to
00086     // no groups.
00087     PaletteGroups empty;
00088     assign_to_groups(empty);
00089     return;
00090   }
00091 
00092   PaletteGroups definitely_in;
00093 
00094   // First, we need to eliminate from consideration all the egg files
00095   // that are already taken care of by the user's explicit group
00096   // assignments for this texture.
00097   WorkingEggs needed_eggs;
00098 
00099   if (_explicitly_assigned_groups.empty()) {
00100     // If we have no explicit group assignments, we must consider all
00101     // the egg files.
00102     copy(_egg_files.begin(), _egg_files.end(), back_inserter(needed_eggs));
00103 
00104   } else {
00105     // Otherwise, we only need to consider the egg files that don't
00106     // have any groups in common with our explicit assignments.
00107 
00108     EggFiles::const_iterator ei;
00109     for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
00110       PaletteGroups intersect;
00111       intersect.make_intersection(_explicitly_assigned_groups, (*ei)->get_complete_groups());
00112       if (!intersect.empty()) {
00113         // This egg file is satisfied by one of the texture's explicit
00114         // assignments.
00115 
00116         // We must use at least one of the explicitly-assigned groups
00117         // that satisfied the egg file.  We don't need to use all of
00118         // them, however, and we choose the first one arbitrarily.
00119         definitely_in.insert(*intersect.begin());
00120 
00121       } else {
00122         // This egg file was not satisfied by any of the texture's
00123         // explicit assignments.  Therefore, we'll need to choose some
00124         // additional group to assign the texture to, to make the egg
00125         // file happy.  Defer this a bit.
00126         needed_eggs.push_back(*ei);
00127       }
00128     }
00129   }
00130 
00131   while (!needed_eggs.empty()) {
00132     // We need to know the complete set of groups that we need to
00133     // consider adding the texture to.  This is the union of all the egg
00134     // files' requested groups.
00135     PaletteGroups total;
00136     WorkingEggs::const_iterator ei;
00137     for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) {
00138       total.make_union(total, (*ei)->get_complete_groups());
00139     }
00140 
00141     // We don't count the "null" group for texture assignment.
00142     total.remove_null();
00143     if (total.empty()) {
00144       break;
00145     }
00146 
00147     // Now, find the group that will satisfy the most egg files.  If
00148     // two groups satisfy the same number of egg files, choose (a) the
00149     // most specific one, i.e. with the lowest dirname_level, or the
00150     // lowest dependency_level if the dirname_levels are equal, and
00151     // (b) the one that has the fewest egg files sharing it.
00152     PaletteGroups::iterator gi = total.begin();
00153     PaletteGroup *best = (*gi);
00154     int best_egg_count = compute_egg_count(best, needed_eggs);
00155     ++gi;
00156     while (gi != total.end()) {
00157       PaletteGroup *group = (*gi);
00158 
00159       // Do we prefer this group to our current 'best'?
00160       bool prefer_group = false;
00161       int group_egg_count = compute_egg_count(group, needed_eggs);
00162       if (group_egg_count != best_egg_count) {
00163         prefer_group = (group_egg_count > best_egg_count);
00164 
00165       } else {
00166         prefer_group = group->is_preferred_over(*best);
00167       }
00168 
00169       if (prefer_group) {
00170         best = group;
00171         best_egg_count = group_egg_count;
00172       }
00173       ++gi;
00174     }
00175 
00176     // Okay, now we've picked the best group.  Eliminate all the eggs
00177     // from consideration that are satisfied by this group, and repeat.
00178     definitely_in.insert(best);
00179 
00180     WorkingEggs next_needed_eggs;
00181     for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) {
00182       if ((*ei)->get_complete_groups().count(best) == 0) {
00183         // This one wasn't eliminated.
00184         next_needed_eggs.push_back(*ei);
00185       }
00186     }
00187     needed_eggs.swap(next_needed_eggs);
00188   }
00189 
00190   // Finally, now that we've computed the set of groups we need to
00191   // assign the texture to, we need to reconcile this with the set of
00192   // groups we've assigned the texture to previously.
00193   assign_to_groups(definitely_in);
00194 }
00195 
00196 ////////////////////////////////////////////////////////////////////
00197 //     Function: TextureImage::get_groups
00198 //       Access: Public
00199 //  Description: Once assign_groups() has been called, this returns
00200 //               the actual set of groups the TextureImage has been
00201 //               assigned to.
00202 ////////////////////////////////////////////////////////////////////
00203 const PaletteGroups &TextureImage::
00204 get_groups() const {
00205   return _actual_assigned_groups;
00206 }
00207 
00208 ////////////////////////////////////////////////////////////////////
00209 //     Function: TextureImage::get_placement
00210 //       Access: Public
00211 //  Description: Gets the TexturePlacement object which represents the
00212 //               assignment of this texture to the indicated group.
00213 //               If the texture has not been assigned to the indicated
00214 //               group, returns NULL.
00215 ////////////////////////////////////////////////////////////////////
00216 TexturePlacement *TextureImage::
00217 get_placement(PaletteGroup *group) const {
00218   Placement::const_iterator pi;
00219   pi = _placement.find(group);
00220   if (pi == _placement.end()) {
00221     return (TexturePlacement *)NULL;
00222   }
00223 
00224   return (*pi).second;
00225 }
00226 
00227 ////////////////////////////////////////////////////////////////////
00228 //     Function: TextureImage::force_replace
00229 //       Access: Public
00230 //  Description: Removes the texture from any PaletteImages it is
00231 //               assigned to, but does not remove it from the groups.
00232 //               It will be re-placed within each group when
00233 //               PaletteGroup::place_all() is called.
00234 ////////////////////////////////////////////////////////////////////
00235 void TextureImage::
00236 force_replace() {
00237   Placement::iterator pi;
00238   for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
00239     (*pi).second->force_replace();
00240   }
00241 }
00242 
00243 ////////////////////////////////////////////////////////////////////
00244 //     Function: TextureImage::mark_eggs_stale
00245 //       Access: Public
00246 //  Description: Marks all the egg files that reference this texture
00247 //               stale.  Should be called only when the texture
00248 //               properties change in some catastrophic way that will
00249 //               require every egg file referencing it to be
00250 //               regenerated, even if it is not palettized.
00251 ////////////////////////////////////////////////////////////////////
00252 void TextureImage::
00253 mark_eggs_stale() {
00254   Placement::iterator pi;
00255   for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
00256     (*pi).second->mark_eggs_stale();
00257   }
00258 }
00259 
00260 ////////////////////////////////////////////////////////////////////
00261 //     Function: TextureImage::mark_texture_named
00262 //       Access: Public
00263 //  Description: Indicates that this particular texture has been named
00264 //               by the user for processing this session, normally by
00265 //               listing an egg file on the command line that
00266 //               references it.
00267 ////////////////////////////////////////////////////////////////////
00268 void TextureImage::
00269 mark_texture_named() {
00270   _texture_named = true;
00271 }
00272 
00273 ////////////////////////////////////////////////////////////////////
00274 //     Function: TextureImage::is_texture_named
00275 //       Access: Public
00276 //  Description: Returns true if this particular texture has been
00277 //               named by the user for procession this session, for
00278 //               instance by listing an egg file on the command line
00279 //               that references it.
00280 ////////////////////////////////////////////////////////////////////
00281 bool TextureImage::
00282 is_texture_named() const {
00283   return _texture_named;
00284 }
00285 
00286 ////////////////////////////////////////////////////////////////////
00287 //     Function: TextureImage::pre_txa_file
00288 //       Access: Public
00289 //  Description: Updates any internal state prior to reading the .txa
00290 //               file.
00291 ////////////////////////////////////////////////////////////////////
00292 void TextureImage::
00293 pre_txa_file() {
00294   // Save our current properties, so we can note if they change.
00295   _pre_txa_properties = _properties;
00296 
00297   // Get our properties from the actual image for this texture.  It's
00298   // possible the .txa file will update them further.
00299   SourceTextureImage *source = get_preferred_source();
00300   if (source != (SourceTextureImage *)NULL) {
00301     _properties = source->get_properties();
00302   }
00303 
00304   _pre_txa_alpha_mode = _alpha_mode;
00305   _alpha_mode = EggRenderMode::AM_unspecified;
00306 
00307   _request.pre_txa_file();
00308   _is_surprise = true;
00309 }
00310 
00311 ////////////////////////////////////////////////////////////////////
00312 //     Function: TextureImage::post_txa_file
00313 //       Access: Public
00314 //  Description: Once the .txa file has been read and the TextureImage
00315 //               matched against it, considers applying the requested
00316 //               size change.  Updates the TextureImage's size with
00317 //               the size the texture ought to be, if this can be
00318 //               determined.
00319 ////////////////////////////////////////////////////////////////////
00320 void TextureImage::
00321 post_txa_file() {
00322   _got_txa_file = true;
00323 
00324   // First, get the actual size of the texture.
00325   SourceTextureImage *source = get_preferred_source();
00326   if (source != (SourceTextureImage *)NULL) {
00327     if (source->get_size()) {
00328       _size_known = true;
00329       _x_size = source->get_x_size();
00330       _y_size = source->get_y_size();
00331       _properties.set_num_channels(source->get_num_channels());
00332     }
00333   }
00334 
00335   // Now update this with a particularly requested size.
00336   if (_request._got_size) {
00337     _size_known = true;
00338     _x_size = _request._x_size;
00339     _y_size = _request._y_size;
00340   }
00341 
00342   if (_txa_wrap_u != _request._wrap_u ||
00343       _txa_wrap_v != _request._wrap_v) {
00344     _txa_wrap_u = _request._wrap_u;
00345     _txa_wrap_v = _request._wrap_v;
00346 
00347     // If the explicit wrap mode changes, we may need to regenerate
00348     // the egg files, and/or refill the palettes.
00349     mark_eggs_stale();
00350 
00351     Placement::iterator pi;
00352     for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
00353       TexturePlacement *placement = (*pi).second;
00354       placement->mark_unfilled();
00355     }
00356   }
00357 
00358   if (_properties.has_num_channels() && !_request._keep_format) {
00359     int num_channels = _properties.get_num_channels();
00360     // Examine the image to determine if we can downgrade the number
00361     // of channels, for instance from color to grayscale.
00362     if (num_channels == 3 || num_channels == 4) {
00363       consider_grayscale();
00364     }
00365     
00366     // Also consider the alpha properties, and whether we should
00367     // downgrade from alpha to non-alpha.
00368     if (num_channels == 2 || num_channels == 4) {
00369       consider_alpha();
00370     }
00371   }
00372 
00373   // However, if we got an explicit request for channels, honor that.
00374   if (_request._got_num_channels) {
00375     _properties.set_num_channels(_request._num_channels);
00376   }
00377 
00378   _properties._generic_format = _request._generic_format;
00379   _properties._keep_format = _request._keep_format;
00380 
00381   if (_request._format != EggTexture::F_unspecified) {
00382     _properties._format = _request._format;
00383     _properties._force_format = _request._force_format;
00384   }
00385 
00386   if (_request._minfilter != EggTexture::FT_unspecified) {
00387     _properties._minfilter = _request._minfilter;
00388   }
00389   if (_request._magfilter != EggTexture::FT_unspecified) {
00390     _properties._magfilter = _request._magfilter;
00391   }
00392 
00393   _properties._anisotropic_degree = _request._anisotropic_degree;
00394 
00395   if (_properties._color_type == (PNMFileType *)NULL) {
00396     _properties._color_type = _request._properties._color_type;
00397     _properties._alpha_type = _request._properties._alpha_type;
00398   }
00399 
00400   // Finally, make sure our properties are fully defined.
00401   _properties.fully_define();
00402 
00403   // Now, if our properties have changed in all that from our previous
00404   // session, we need to re-place ourself in all palette groups.
00405   if (_properties != _pre_txa_properties) {
00406     force_replace();
00407 
00408     // The above will mark the egg files stale when the texture is
00409     // palettized (since the UV's will certainly need to be
00410     // recomputed), but sometimes we need to mark the egg files stale
00411     // even when the texture is not palettized (if a critical property
00412     // has changed).  The following accomplishes this:
00413     if (!_properties.egg_properties_match(_pre_txa_properties)) {
00414       mark_eggs_stale();
00415     }
00416   }
00417 
00418   // The alpha mode isn't stored in the properties, because it doesn't
00419   // affect which textures may be associated into a common palette.
00420   if (_request._alpha_mode != EggRenderMode::AM_unspecified) {
00421     _alpha_mode = _request._alpha_mode;
00422   }
00423 
00424   // On the other hand, if we don't have an alpha channel, we
00425   // shouldn't have an alpha mode.
00426   if (_properties.has_num_channels()) {
00427     int num_channels = _properties.get_num_channels();
00428     if (num_channels == 1 || num_channels == 3) {
00429       _alpha_mode = EggRenderMode::AM_unspecified;
00430     }
00431   }
00432 
00433   // If we've changed the alpha mode, we should also mark the eggs
00434   // stale.
00435   if (_pre_txa_alpha_mode != _alpha_mode) {
00436     mark_eggs_stale();
00437   }
00438 }
00439 
00440 ////////////////////////////////////////////////////////////////////
00441 //     Function: TextureImage::got_txa_file
00442 //       Access: Public
00443 //  Description: Returns true if this TextureImage has been looked up
00444 //               in the .txa file this session, false otherwise.
00445 ////////////////////////////////////////////////////////////////////
00446 bool TextureImage::
00447 got_txa_file() const {
00448   return _got_txa_file;
00449 }
00450 
00451 ////////////////////////////////////////////////////////////////////
00452 //     Function: TextureImage::determine_placement_size
00453 //       Access: Public
00454 //  Description: Calls determine_size() on each TexturePlacement for
00455 //               the texture, to ensure that each TexturePlacement is
00456 //               still requesting the best possible size for the
00457 //               texture.
00458 ////////////////////////////////////////////////////////////////////
00459 void TextureImage::
00460 determine_placement_size() {
00461   Placement::iterator pi;
00462   for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
00463     TexturePlacement *placement = (*pi).second;
00464     placement->determine_size();
00465   }
00466 }
00467 
00468 ////////////////////////////////////////////////////////////////////
00469 //     Function: TextureImage::get_omit
00470 //       Access: Public
00471 //  Description: Returns true if the user specifically requested to
00472 //               omit this texture via the "omit" keyword in the .txa
00473 //               file, or false otherwise.
00474 ////////////////////////////////////////////////////////////////////
00475 bool TextureImage::
00476 get_omit() const {
00477   return _request._omit;
00478 }
00479 
00480 ////////////////////////////////////////////////////////////////////
00481 //     Function: TextureImage::get_coverage_threshold
00482 //       Access: Public
00483 //  Description: Returns the appropriate coverage threshold for this
00484 //               texture.  This is either the
00485 //               Palettizer::_coverage_threshold parameter, given
00486 //               globally via -r, or a particular value for this
00487 //               texture as supplied by the "coverage" keyword in the
00488 //               .txa file.
00489 ////////////////////////////////////////////////////////////////////
00490 double TextureImage::
00491 get_coverage_threshold() const {
00492   return _request._coverage_threshold;
00493 }
00494 
00495 ////////////////////////////////////////////////////////////////////
00496 //     Function: TextureImage::get_margin
00497 //       Access: Public
00498 //  Description: Returns the appropriate margin for this texture.
00499 //               This is either the Palettizer::_margin parameter, or
00500 //               a particular value for this texture as supplied by
00501 //               the "margin" keyword in the .txa file.
00502 ////////////////////////////////////////////////////////////////////
00503 int TextureImage::
00504 get_margin() const {
00505   return _request._margin;
00506 }
00507 
00508 ////////////////////////////////////////////////////////////////////
00509 //     Function: TextureImage::is_surprise
00510 //       Access: Public
00511 //  Description: Returns true if this particular texture is a
00512 //               'surprise', i.e. it wasn't matched by a line in the
00513 //               .txa file that didn't include the keyword 'cont'.
00514 ////////////////////////////////////////////////////////////////////
00515 bool TextureImage::
00516 is_surprise() const {
00517   if (_placement.empty()) {
00518     // A texture that is not actually placed anywhere is not
00519     // considered a surprise.
00520     return false;
00521   }
00522 
00523   return _is_surprise;
00524 }
00525 
00526 ////////////////////////////////////////////////////////////////////
00527 //     Function: TextureImage::is_used
00528 //       Access: Public
00529 //  Description: Returns true if this particular texture has been
00530 //               placed somewhere, anywhere, or false if it is not
00531 //               used.
00532 ////////////////////////////////////////////////////////////////////
00533 bool TextureImage::
00534 is_used() const {
00535   return !_placement.empty();
00536 }
00537 
00538 ////////////////////////////////////////////////////////////////////
00539 //     Function: TextureImage::get_alpha_mode
00540 //       Access: Public
00541 //  Description: Returns the alpha mode that should be used to render
00542 //               objects with this texture, as specified by the user
00543 //               or as determined from examining the texture's alpha
00544 //               channel.
00545 ////////////////////////////////////////////////////////////////////
00546 EggRenderMode::AlphaMode TextureImage::
00547 get_alpha_mode() const {
00548   return _alpha_mode;
00549 }
00550 
00551 ////////////////////////////////////////////////////////////////////
00552 //     Function: TextureImage::get_txa_wrap_u
00553 //       Access: Public
00554 //  Description: Returns the wrap mode specified in the u direction in
00555 //               the txa file, or WM_unspecified.
00556 ////////////////////////////////////////////////////////////////////
00557 EggTexture::WrapMode TextureImage::
00558 get_txa_wrap_u() const {
00559   return _txa_wrap_u;
00560 }
00561 
00562 ////////////////////////////////////////////////////////////////////
00563 //     Function: TextureImage::get_txa_wrap_v
00564 //       Access: Public
00565 //  Description: Returns the wrap mode specified in the v direction in
00566 //               the txa file, or WM_unspecified.
00567 ////////////////////////////////////////////////////////////////////
00568 EggTexture::WrapMode TextureImage::
00569 get_txa_wrap_v() const {
00570   return _txa_wrap_v;
00571 }
00572 
00573 
00574 ////////////////////////////////////////////////////////////////////
00575 //     Function: TextureImage::get_source
00576 //       Access: Public
00577 //  Description: Returns the SourceTextureImage corresponding to the
00578 //               given filename(s).  If the given filename has never
00579 //               been used as a SourceTexture for this particular
00580 //               texture, creates a new SourceTextureImage and returns
00581 //               that.
00582 ////////////////////////////////////////////////////////////////////
00583 SourceTextureImage *TextureImage::
00584 get_source(const Filename &filename, const Filename &alpha_filename,
00585            int alpha_file_channel) {
00586   string key = get_source_key(filename, alpha_filename, alpha_file_channel);
00587 
00588   Sources::iterator si;
00589   si = _sources.find(key);
00590   if (si != _sources.end()) {
00591     return (*si).second;
00592   }
00593 
00594   SourceTextureImage *source =
00595     new SourceTextureImage(this, filename, alpha_filename, alpha_file_channel);
00596   _sources.insert(Sources::value_type(key, source));
00597 
00598   // Clear out the preferred source image to force us to rederive this
00599   // next time someone asks.
00600   _preferred_source = (SourceTextureImage *)NULL;
00601   _read_source_image = false;
00602 
00603   return source;
00604 }
00605 
00606 ////////////////////////////////////////////////////////////////////
00607 //     Function: TextureImage::get_preferred_source
00608 //       Access: Public
00609 //  Description: Determines the preferred source image for examining
00610 //               size and reading pixels, etc.  This is the largest
00611 //               and most recent of all the available source images.
00612 ////////////////////////////////////////////////////////////////////
00613 SourceTextureImage *TextureImage::
00614 get_preferred_source() {
00615   if (_preferred_source != (SourceTextureImage *)NULL) {
00616     return _preferred_source;
00617   }
00618 
00619   // Now examine all of the various source images available to us and
00620   // pick the most suitable.  We base this on the following criteria:
00621 
00622   // (1) A suitable source image must be referenced by at least one
00623   // egg file, unless no source images are referenced by any egg file.
00624 
00625   // (2) A larger source image is preferable to a smaller one.
00626 
00627   // (3) Given two source images of the same size, the more recent one
00628   // is preferable.
00629 
00630   // Are any source images referenced by an egg file?
00631 
00632   bool any_referenced = false;
00633   Sources::iterator si;
00634   for (si = _sources.begin(); si != _sources.end() && !any_referenced; ++si) {
00635     SourceTextureImage *source = (*si).second;
00636     if (source->get_egg_count() > 0) {
00637       any_referenced = true;
00638     }
00639   }
00640 
00641   SourceTextureImage *best = (SourceTextureImage *)NULL;
00642   int best_size = 0;
00643 
00644   for (si = _sources.begin(); si != _sources.end(); ++si) {
00645     SourceTextureImage *source = (*si).second;
00646 
00647     if (source->get_egg_count() > 0 || !any_referenced) {
00648       // Rule (1) passes.
00649 
00650       if (source->exists() && source->get_size()) {
00651         int source_size = source->get_x_size() * source->get_y_size();
00652         if (best == (SourceTextureImage *)NULL) {
00653           best = source;
00654           best_size = source_size;
00655 
00656         } else if (source_size > best_size) {
00657           // Rule (2) passes.
00658           best = source;
00659           best_size = source_size;
00660 
00661         } else if (source_size == best_size &&
00662                    source->get_filename().compare_timestamps(best->get_filename()) > 0) {
00663           // Rule (3) passes.
00664           best = source;
00665           best_size = source_size;
00666         }
00667       }
00668     }
00669   }
00670 
00671   if (best == (SourceTextureImage *)NULL && !_sources.empty()) {
00672     // If we didn't pick any that pass, it must be that all of them
00673     // are unreadable.  In this case, it really doesn't matter which
00674     // one we pick, but we should at least pick one that has an egg
00675     // reference, if any of them do.
00676     if (any_referenced) {
00677       for (si = _sources.begin();
00678            si != _sources.end() && best == (SourceTextureImage *)NULL;
00679            ++si) {
00680         SourceTextureImage *source = (*si).second;
00681         if (source->get_egg_count() > 0) {
00682           best = source;
00683         }
00684       }
00685     } else {
00686       best = (*_sources.begin()).second;
00687     }
00688   }
00689 
00690   _preferred_source = best;
00691   return _preferred_source;
00692 }
00693 
00694 ////////////////////////////////////////////////////////////////////
00695 //     Function: TextureImage::clear_source_basic_properties
00696 //       Access: Public
00697 //  Description: Calls clear_basic_properties() on each source texture
00698 //               image used by this texture, to reset the properties
00699 //               in preparation for re-applying them from the set of
00700 //               all known egg files.
00701 ////////////////////////////////////////////////////////////////////
00702 void TextureImage::
00703 clear_source_basic_properties() {
00704   Sources::iterator si;
00705   for (si = _sources.begin(); si != _sources.end(); ++si) {
00706     SourceTextureImage *source = (*si).second;
00707     source->clear_basic_properties();
00708   }
00709 }
00710 
00711 ////////////////////////////////////////////////////////////////////
00712 //     Function: TextureImage::copy_unplaced
00713 //       Access: Public
00714 //  Description: Copies the texture to whichever destination
00715 //               directories are appropriate for the groups in which
00716 //               it has been unplaced.  Also removes the old filenames
00717 //               for previous sessions where it was unplaced, but is
00718 //               no longer.
00719 //
00720 //               If redo_all is true, this recopies the texture
00721 //               whether it needed to or not.
00722 ////////////////////////////////////////////////////////////////////
00723 void TextureImage::
00724 copy_unplaced(bool redo_all) {
00725   // First, we need to build up the set of DestTextureImages that
00726   // represents the files we need to generate.
00727   Dests generate;
00728 
00729   // Go through all the TexturePlacements and note the ones for which
00730   // we're unplaced.  We check get_omit_reason() and not is_placed(),
00731   // because we want to consider solitary images to be unplaced in
00732   // this case.
00733   Placement::iterator pi;
00734   for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
00735     TexturePlacement *placement = (*pi).second;
00736     if (placement->get_omit_reason() != OR_none &&
00737         placement->get_omit_reason() != OR_unknown) {
00738       DestTextureImage *dest = new DestTextureImage(placement);
00739       Filename filename = dest->get_filename();
00740       FilenameUnifier::make_canonical(filename);
00741 
00742       pair<Dests::iterator, bool> insert_result = generate.insert
00743         (Dests::value_type(filename, dest));
00744       if (!insert_result.second) {
00745         // At least two DestTextureImages map to the same filename, no
00746         // sweat.
00747         delete dest;
00748         dest = (*insert_result.first).second;
00749       }
00750 
00751       placement->set_dest(dest);
00752 
00753     } else {
00754       placement->set_dest((DestTextureImage *)NULL);
00755     }
00756   }
00757 
00758   if (redo_all) {
00759     // If we're redoing everything, we remove everything first and
00760     // then recopy it again.
00761     Dests empty;
00762     remove_old_dests(empty, _dests);
00763     copy_new_dests(generate, empty);
00764 
00765   } else {
00766     // Otherwise, we only remove and recopy the things that changed
00767     // between this time and last time.
00768     remove_old_dests(generate, _dests);
00769     copy_new_dests(generate, _dests);
00770   }
00771 
00772   // Clean up the old set.
00773   Dests::iterator di;
00774   for (di = _dests.begin(); di != _dests.end(); ++di) {
00775     delete (*di).second;
00776   }
00777 
00778   _dests.swap(generate);
00779 }
00780 
00781 ////////////////////////////////////////////////////////////////////
00782 //     Function: TextureImage::read_source_image
00783 //       Access: Public
00784 //  Description: Reads in the original image, if it has not already
00785 //               been read, and returns it.
00786 ////////////////////////////////////////////////////////////////////
00787 const PNMImage &TextureImage::
00788 read_source_image() {
00789   if (!_read_source_image) {
00790     SourceTextureImage *source = get_preferred_source();
00791     if (source != (SourceTextureImage *)NULL) {
00792       source->read(_source_image);
00793     }
00794     _read_source_image = true;
00795     _allow_release_source_image = true;
00796     _ever_read_image = true;
00797   }
00798 
00799   return _source_image;
00800 }
00801 
00802 ////////////////////////////////////////////////////////////////////
00803 //     Function: TextureImage::release_source_image
00804 //       Access: Public
00805 //  Description: Frees the memory that was allocated by a previous
00806 //               call to read_source_image().  The next time
00807 //               read_source_image() is called, it will have to read
00808 //               the disk again.
00809 ////////////////////////////////////////////////////////////////////
00810 void TextureImage::
00811 release_source_image() {
00812   if (_read_source_image && _allow_release_source_image) {
00813     _source_image.clear();
00814     _read_source_image = false;
00815   }
00816 }
00817 
00818 ////////////////////////////////////////////////////////////////////
00819 //     Function: TextureImage::set_source_image
00820 //       Access: Public
00821 //  Description: Accepts the indicated source image as if it had been
00822 //               read from disk.  This image is copied into the
00823 //               structure, and will be returned by future calls to
00824 //               read_source_image().
00825 ////////////////////////////////////////////////////////////////////
00826 void TextureImage::
00827 set_source_image(const PNMImage &image) {
00828   _source_image = image;
00829   _allow_release_source_image = false;
00830   _read_source_image = true;
00831   _ever_read_image = true;
00832 }
00833 
00834 ////////////////////////////////////////////////////////////////////
00835 //     Function: TextureImage::read_header
00836 //       Access: Public
00837 //  Description: Causes the header part of the image to be reread,
00838 //               usually to confirm that its image properties (size,
00839 //               number of channels, etc.) haven't changed.
00840 ////////////////////////////////////////////////////////////////////
00841 void TextureImage::
00842 read_header() {
00843   if (!_read_source_image) {
00844     SourceTextureImage *source = get_preferred_source();
00845     if (source != (SourceTextureImage *)NULL) {
00846       source->read_header();
00847     }
00848   }
00849 }
00850 
00851 ////////////////////////////////////////////////////////////////////
00852 //     Function: TextureImage::is_newer_than
00853 //       Access: Public
00854 //  Description: Returns true if the source image is newer than the
00855 //               indicated file, false otherwise.  If the image has
00856 //               already been read, this always returns false.
00857 ////////////////////////////////////////////////////////////////////
00858 bool TextureImage::
00859 is_newer_than(const Filename &reference_filename) {
00860   if (!_read_source_image) {
00861     SourceTextureImage *source = get_preferred_source();
00862     if (source != (SourceTextureImage *)NULL) {
00863       const Filename &source_filename = source->get_filename();
00864       return source_filename.compare_timestamps(reference_filename) >= 0;
00865     }
00866   }
00867 
00868   return false;
00869 }
00870 
00871 ////////////////////////////////////////////////////////////////////
00872 //     Function: TextureImage::write_source_pathnames
00873 //       Access: Public
00874 //  Description: Writes the list of source pathnames that might
00875 //               contribute to this texture to the indicated output
00876 //               stream, one per line.
00877 ////////////////////////////////////////////////////////////////////
00878 void TextureImage::
00879 write_source_pathnames(ostream &out, int indent_level) const {
00880   Sources::const_iterator si;
00881   for (si = _sources.begin(); si != _sources.end(); ++si) {
00882     SourceTextureImage *source = (*si).second;
00883 
00884     if (source->get_egg_count() > 0) {
00885       indent(out, indent_level);
00886       source->output_filename(out);
00887       if (!source->is_size_known()) {
00888         out << " (unknown size)";
00889 
00890       } else {
00891         out << " " << source->get_x_size() << " "
00892             << source->get_y_size();
00893 
00894         if (source->get_properties().has_num_channels()) {
00895           out << " " << source->get_properties().get_num_channels();
00896         }
00897       }
00898       out << "\n";
00899     }
00900   }
00901 
00902   if (_is_cutout) {
00903     indent(out, indent_level)
00904       << "Cutout image (ratio " << (PN_stdfloat)_mid_pixel_ratio << ")\n";
00905   }
00906 
00907   // Now write out the group assignments.
00908   if (!_egg_files.empty()) {
00909     // Sort the egg files into order by name for output.
00910     pvector<EggFile *> egg_vector;
00911     egg_vector.reserve(_egg_files.size());
00912     EggFiles::const_iterator ei;
00913     for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
00914       egg_vector.push_back(*ei);
00915     }
00916     sort(egg_vector.begin(), egg_vector.end(),
00917          IndirectCompareNames<EggFile>());
00918 
00919     indent(out, indent_level)
00920       << "Used by:\n";
00921     pvector<EggFile *>::const_iterator evi;
00922     for (evi = egg_vector.begin(); evi != egg_vector.end(); ++evi) {
00923       EggFile *egg = (*evi);
00924       indent(out, indent_level + 2)
00925         << egg->get_name() << " (";
00926       if (egg->get_explicit_groups().empty()) {
00927         out << *egg->get_default_group();
00928       } else {
00929         out << egg->get_explicit_groups();
00930       }
00931       out << ")\n";
00932     }
00933   }
00934   if (!_explicitly_assigned_groups.empty()) {
00935     indent(out, indent_level)
00936       << "Explicitly assigned to " << _explicitly_assigned_groups << " in .txa\n";
00937   }
00938 
00939   if (_placement.empty()) {
00940     indent(out, indent_level)
00941       << "Not used.\n";
00942   } else {
00943     indent(out, indent_level)
00944       << "Assigned to " << _actual_assigned_groups << "\n";
00945   }
00946 }
00947 
00948 ////////////////////////////////////////////////////////////////////
00949 //     Function: TextureImage::write_scale_info
00950 //       Access: Public
00951 //  Description: Writes the information about the texture's size and
00952 //               placement.
00953 ////////////////////////////////////////////////////////////////////
00954 void TextureImage::
00955 write_scale_info(ostream &out, int indent_level) {
00956   SourceTextureImage *source = get_preferred_source();
00957   indent(out, indent_level) << get_name();
00958 
00959   // Write the list of groups we're placed in.
00960   if (_placement.empty()) {
00961     out << " (not used)";
00962   } else {
00963     Placement::const_iterator pi;
00964     pi = _placement.begin();
00965     out << " (" << (*pi).second->get_group()->get_name();
00966     ++pi;
00967     while (pi != _placement.end()) {
00968       out << " " << (*pi).second->get_group()->get_name();
00969       ++pi;
00970     }
00971     out << ")";
00972   }
00973 
00974   out << " orig ";
00975 
00976   if (source == (SourceTextureImage *)NULL ||
00977       !source->is_size_known()) {
00978     out << "unknown";
00979   } else {
00980     out << source->get_x_size() << " " << source->get_y_size()
00981         << " " << source->get_num_channels();
00982   }
00983 
00984   if (!_placement.empty() && is_size_known()) {
00985     out << " new " << get_x_size() << " " << get_y_size()
00986         << " " << get_num_channels();
00987 
00988     if (source != (SourceTextureImage *)NULL &&
00989         source->is_size_known()) {
00990       double scale =
00991         100.0 * (((double)get_x_size() / (double)source->get_x_size()) +
00992                  ((double)get_y_size() / (double)source->get_y_size())) / 2.0;
00993       out << " scale " << scale << "%";
00994     }
00995   }
00996   out << "\n";
00997 
00998   // Also cross-reference the placed and unplaced information.
00999   Placement::iterator pi;
01000   for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
01001     TexturePlacement *placement = (*pi).second;
01002     if (placement->get_omit_reason() == OR_none) {
01003       PaletteImage *image = placement->get_image();
01004       nassertv(image != (PaletteImage *)NULL);
01005       indent(out, indent_level + 2)
01006         << "placed on "
01007         << FilenameUnifier::make_user_filename(image->get_filename())
01008         << "\n";
01009 
01010     } else if (placement->get_omit_reason() == OR_unknown) {
01011       indent(out, indent_level + 2)
01012         << "not placed because unknown.\n";
01013 
01014     } else {
01015       DestTextureImage *image = placement->get_dest();
01016       nassertv(image != (DestTextureImage *)NULL);
01017       indent(out, indent_level + 2)
01018         << "copied to "
01019         << FilenameUnifier::make_user_filename(image->get_filename());
01020       if (image->is_size_known() && is_size_known() &&
01021           (image->get_x_size() != get_x_size() ||
01022            image->get_y_size() != get_y_size())) {
01023         out << " at size " << image->get_x_size() << " "
01024             << image->get_y_size();
01025         if (source != (SourceTextureImage *)NULL &&
01026             source->is_size_known()) {
01027           double scale =
01028             100.0 * (((double)image->get_x_size() / (double)source->get_x_size()) +
01029                      ((double)image->get_y_size() / (double)source->get_y_size())) / 2.0;
01030           out << " scale " << scale << "%";
01031         }
01032       }
01033       out << "\n";
01034     }
01035   }
01036 }
01037 
01038 ////////////////////////////////////////////////////////////////////
01039 //     Function: TextureImage::compute_egg_count
01040 //       Access: Private
01041 //  Description: Counts the number of egg files in the indicated set
01042 //               that will be satisfied if a texture is assigned to
01043 //               the indicated group.
01044 ////////////////////////////////////////////////////////////////////
01045 int TextureImage::
01046 compute_egg_count(PaletteGroup *group,
01047                   const TextureImage::WorkingEggs &egg_files) {
01048   int count = 0;
01049 
01050   WorkingEggs::const_iterator ei;
01051   for (ei = egg_files.begin(); ei != egg_files.end(); ++ei) {
01052     if ((*ei)->get_complete_groups().count(group) != 0) {
01053       count++;
01054     }
01055   }
01056 
01057   return count;
01058 }
01059 
01060 ////////////////////////////////////////////////////////////////////
01061 //     Function: TextureImage::assign_to_groups
01062 //       Access: Private
01063 //  Description: Assigns the texture to the indicated set of groups.
01064 //               If the texture was previously assigned to any of
01065 //               these groups, keeps the same TexturePlacement object
01066 //               for the assignment; at the same time, deletes any
01067 //               TexturePlacement objects that represent groups we are
01068 //               no longer assigned to.
01069 ////////////////////////////////////////////////////////////////////
01070 void TextureImage::
01071 assign_to_groups(const PaletteGroups &groups) {
01072   PaletteGroups::const_iterator gi;
01073   Placement::const_iterator pi;
01074 
01075   Placement new_placement;
01076 
01077   gi = groups.begin();
01078   pi = _placement.begin();
01079 
01080   while (gi != groups.end() && pi != _placement.end()) {
01081     PaletteGroup *a = (*gi);
01082     PaletteGroup *b = (*pi).first;
01083 
01084     if (a < b) {
01085       // Here's a group we're now assigned to that we weren't assigned
01086       // to previously.
01087       TexturePlacement *place = a->prepare(this);
01088       new_placement.insert
01089         (new_placement.end(), Placement::value_type(a, place));
01090       ++gi;
01091 
01092     } else if (b < a) {
01093       // Here's a group we're no longer assigned to.
01094       TexturePlacement *place = (*pi).second;
01095       delete place;
01096       ++pi;
01097 
01098     } else { // b == a
01099       // Here's a group we're still assigned to.
01100       TexturePlacement *place = (*pi).second;
01101       new_placement.insert
01102         (new_placement.end(), Placement::value_type(a, place));
01103       ++gi;
01104       ++pi;
01105     }
01106   }
01107 
01108   while (gi != groups.end()) {
01109     // Here's a group we're now assigned to that we weren't assigned
01110     // to previously.
01111     PaletteGroup *a = (*gi);
01112     TexturePlacement *place = a->prepare(this);
01113     new_placement.insert
01114       (new_placement.end(), Placement::value_type(a, place));
01115     ++gi;
01116   }
01117 
01118   while (pi != _placement.end()) {
01119     // Here's a group we're no longer assigned to.
01120     TexturePlacement *place = (*pi).second;
01121     delete place;
01122     ++pi;
01123   }
01124 
01125   _placement.swap(new_placement);
01126   _actual_assigned_groups = groups;
01127 }
01128 
01129 ////////////////////////////////////////////////////////////////////
01130 //     Function: TextureImage::consider_grayscale
01131 //       Access: Private
01132 //  Description: Examines the actual contents of the image to
01133 //               determine if it should maybe be considered a
01134 //               grayscale image (even though it has separate rgb
01135 //               components).
01136 ////////////////////////////////////////////////////////////////////
01137 void TextureImage::
01138 consider_grayscale() {
01139   // Since this isn't likely to change for a particular texture after
01140   // its creation, we save a bit of time by not performing this check
01141   // unless this is the first time we've ever seen this texture.  This
01142   // will save us from having to load the texture images each time we
01143   // look at them.  On the other hand, if we've already loaded up the
01144   // image, then go ahead.
01145   if (!_read_source_image && _ever_read_image) {
01146     if (_forced_grayscale) {
01147       _properties.force_grayscale();
01148     }
01149     return;
01150   }
01151 
01152   const PNMImage &source = read_source_image();
01153   if (!source.is_valid()) {
01154     return;
01155   }
01156 
01157   for (int y = 0; y < source.get_y_size(); y++) {
01158     for (int x = 0; x < source.get_x_size(); x++) {
01159       const xel &v = source.get_xel_val(x, y);
01160       if (PPM_GETR(v) != PPM_GETG(v) || PPM_GETR(v) != PPM_GETB(v)) {
01161         // Here's a colored pixel.  We can't go grayscale.
01162         _forced_grayscale = false;
01163         return;
01164       }
01165     }
01166   }
01167 
01168   // All pixels in the image were grayscale!
01169   _properties.force_grayscale();
01170   _forced_grayscale = true;
01171 }
01172 
01173 ////////////////////////////////////////////////////////////////////
01174 //     Function: TextureImage::consider_alpha
01175 //       Access: Private
01176 //  Description: Examines the actual contents of the image to
01177 //               determine what alpha properties it has.
01178 ////////////////////////////////////////////////////////////////////
01179 void TextureImage::
01180 consider_alpha() {
01181   // As above, we don't bother doing this if we've already done this
01182   // in a previous session.
01183 
01184   // _alpha_bits == -1 indicates we have read an older textures.boo
01185   // file that didn't define these bits.
01186   if (_read_source_image || !_ever_read_image || _alpha_bits == -1) {
01187     _alpha_bits = 0;
01188     int num_mid_pixels = 0;
01189 
01190     const PNMImage &source = read_source_image();
01191     if (source.is_valid() && source.has_alpha()) {
01192       xelval maxval = source.get_maxval();
01193       for (int y = 0; y < source.get_y_size(); y++) {
01194         for (int x = 0; x < source.get_x_size(); x++) {
01195           xelval alpha_val = source.get_alpha_val(x, y);
01196           if (alpha_val == 0) {
01197             _alpha_bits |= AB_zero;
01198           } else if (alpha_val == maxval) {
01199             _alpha_bits |= AB_one;
01200           } else {
01201             _alpha_bits |= AB_mid;
01202             ++num_mid_pixels;
01203           }
01204         }
01205       }
01206     }
01207 
01208     int num_pixels = source.get_x_size() * source.get_y_size();
01209     _mid_pixel_ratio = 0.0;
01210     if (num_pixels != 0) {
01211       _mid_pixel_ratio = (double)num_mid_pixels / (double)num_pixels;
01212     }
01213   }
01214 
01215   _is_cutout = false;
01216 
01217   if (_alpha_bits != 0) {
01218     if (_alpha_bits == AB_one) {
01219       // All alpha pixels are white; drop the alpha channel.
01220       _properties.force_nonalpha();
01221 
01222     } else if (_alpha_bits == AB_zero) {
01223       // All alpha pixels are invisible; this is probably a mistake.
01224       // Drop the alpha channel and complain.
01225       _properties.force_nonalpha();
01226       if (_read_source_image) {
01227         nout << *this << " has an all-zero alpha channel; dropping alpha.\n";
01228       }
01229 
01230     } else if (_alpha_mode == EggRenderMode::AM_unspecified) {
01231       // Consider fiddling with the alpha mode, if the user hasn't
01232       // specified a particular alpha mode in the txa file.
01233       if ((_alpha_bits & AB_mid) == 0) {
01234         // No middle range bits: a binary alpha image.
01235         _alpha_mode = EggRenderMode::AM_binary;
01236 
01237       } else if ((_alpha_bits & AB_one) != 0 && _mid_pixel_ratio < pal->_cutout_ratio) {
01238         // At least some opaque bits, and relatively few middle range
01239         // bits: a cutout image.
01240         _alpha_mode = pal->_cutout_mode;
01241         _is_cutout = true;
01242 
01243       } else {
01244         // No opaque bits; just use regular alpha blending.
01245         _alpha_mode = EggRenderMode::AM_blend;
01246       }
01247     }
01248   }
01249 }
01250 
01251 ////////////////////////////////////////////////////////////////////
01252 //     Function: TextureImage::remove_old_dests
01253 //       Access: Private
01254 //  Description: Removes all of the filenames named in b that are not
01255 //               also named in a.
01256 ////////////////////////////////////////////////////////////////////
01257 void TextureImage::
01258 remove_old_dests(const TextureImage::Dests &a, const TextureImage::Dests &b) {
01259   Dests::const_iterator ai = a.begin();
01260   Dests::const_iterator bi = b.begin();
01261 
01262   while (ai != a.end() && bi != b.end()) {
01263     const string &astr = (*ai).first;
01264     const string &bstr = (*bi).first;
01265 
01266     if (astr < bstr) {
01267       // Here's a filename in a, not in b.
01268       ++ai;
01269 
01270     } else if (bstr < astr) {
01271       // Here's a filename in b, not in a.
01272       (*bi).second->unlink();
01273       ++bi;
01274 
01275     } else { // bstr == astr
01276       // Here's a filename in both a and b.
01277       ++ai;
01278       ++bi;
01279     }
01280   }
01281 
01282   while (bi != b.end()) {
01283     // Here's a filename in b, not in a.
01284     (*bi).second->unlink();
01285     ++bi;
01286   }
01287 
01288   while (ai != a.end()) {
01289     ++ai;
01290   }
01291 }
01292 
01293 ////////////////////////////////////////////////////////////////////
01294 //     Function: TextureImage::copy_new_dests
01295 //       Access: Private
01296 //  Description: Copies a resized texture into each filename named in
01297 //               a that is not also listed in b, or whose
01298 //               corresponding listing in b is out of date.
01299 ////////////////////////////////////////////////////////////////////
01300 void TextureImage::
01301 copy_new_dests(const TextureImage::Dests &a, const TextureImage::Dests &b) {
01302   Dests::const_iterator ai = a.begin();
01303   Dests::const_iterator bi = b.begin();
01304 
01305   while (ai != a.end() && bi != b.end()) {
01306     const string &astr = (*ai).first;
01307     const string &bstr = (*bi).first;
01308 
01309     if (astr < bstr) {
01310       // Here's a filename in a, not in b.
01311       (*ai).second->copy(this);
01312       ++ai;
01313 
01314     } else if (bstr < astr) {
01315       // Here's a filename in b, not in a.
01316       ++bi;
01317 
01318     } else { // bstr == astr
01319       // Here's a filename in both a and b.
01320       (*ai).second->copy_if_stale((*bi).second, this);
01321       ++ai;
01322       ++bi;
01323     }
01324   }
01325 
01326   while (ai != a.end()) {
01327     // Here's a filename in a, not in b.
01328     (*ai).second->copy(this);
01329     ++ai;
01330   }
01331 }
01332 
01333 ////////////////////////////////////////////////////////////////////
01334 //     Function: TextureImage::get_source_key
01335 //       Access: Private
01336 //  Description: Returns the key that a SourceTextureImage should be
01337 //               stored in, given its one or two filenames.
01338 ////////////////////////////////////////////////////////////////////
01339 string TextureImage::
01340 get_source_key(const Filename &filename, const Filename &alpha_filename,
01341                int alpha_file_channel) {
01342   Filename f = FilenameUnifier::make_bam_filename(filename);
01343   Filename a = FilenameUnifier::make_bam_filename(alpha_filename);
01344 
01345   return f.get_fullpath() + ":" + a.get_fullpath() + ":" +
01346     format_string(alpha_file_channel);
01347 }
01348 
01349 ////////////////////////////////////////////////////////////////////
01350 //     Function: TextureImage::register_with_read_factory
01351 //       Access: Public, Static
01352 //  Description: Registers the current object as something that can be
01353 //               read from a Bam file.
01354 ////////////////////////////////////////////////////////////////////
01355 void TextureImage::
01356 register_with_read_factory() {
01357   BamReader::get_factory()->
01358     register_factory(get_class_type(), make_TextureImage);
01359 }
01360 
01361 ////////////////////////////////////////////////////////////////////
01362 //     Function: TextureImage::write_datagram
01363 //       Access: Public, Virtual
01364 //  Description: Fills the indicated datagram up with a binary
01365 //               representation of the current object, in preparation
01366 //               for writing to a Bam file.
01367 ////////////////////////////////////////////////////////////////////
01368 void TextureImage::
01369 write_datagram(BamWriter *writer, Datagram &datagram) {
01370   ImageFile::write_datagram(writer, datagram);
01371   datagram.add_string(get_name());
01372 
01373   // We don't write out _request; this is re-read from the .txa file
01374   // each time.
01375 
01376   // We don't write out _pre_txa_properties; this is transitional.
01377 
01378   // We don't write out _preferred_source; this is redetermined each
01379   // session.
01380 
01381   datagram.add_bool(_is_surprise);
01382   datagram.add_bool(_ever_read_image);
01383   datagram.add_bool(_forced_grayscale);
01384   datagram.add_uint8(_alpha_bits);
01385   datagram.add_int16((int)_alpha_mode);
01386   datagram.add_float64(_mid_pixel_ratio);
01387   datagram.add_bool(_is_cutout);
01388   datagram.add_uint8((int)_txa_wrap_u);
01389   datagram.add_uint8((int)_txa_wrap_v);
01390 
01391   // We don't write out _explicitly_assigned_groups; this is re-read
01392   // from the .txa file each time.
01393 
01394   _actual_assigned_groups.write_datagram(writer, datagram);
01395 
01396   // We don't write out _egg_files; this is redetermined each session.
01397 
01398   datagram.add_uint32(_placement.size());
01399   Placement::const_iterator pi;
01400   for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
01401     writer->write_pointer(datagram, (*pi).first);
01402     writer->write_pointer(datagram, (*pi).second);
01403   }
01404 
01405   datagram.add_uint32(_sources.size());
01406   Sources::const_iterator si;
01407   for (si = _sources.begin(); si != _sources.end(); ++si) {
01408     writer->write_pointer(datagram, (*si).second);
01409   }
01410 
01411   datagram.add_uint32(_dests.size());
01412   Dests::const_iterator di;
01413   for (di = _dests.begin(); di != _dests.end(); ++di) {
01414     writer->write_pointer(datagram, (*di).second);
01415   }
01416 }
01417 
01418 ////////////////////////////////////////////////////////////////////
01419 //     Function: TextureImage::complete_pointers
01420 //       Access: Public, Virtual
01421 //  Description: Called after the object is otherwise completely read
01422 //               from a Bam file, this function's job is to store the
01423 //               pointers that were retrieved from the Bam file for
01424 //               each pointer object written.  The return value is the
01425 //               number of pointers processed from the list.
01426 ////////////////////////////////////////////////////////////////////
01427 int TextureImage::
01428 complete_pointers(TypedWritable **p_list, BamReader *manager) {
01429   int pi = ImageFile::complete_pointers(p_list, manager);
01430 
01431   pi += _actual_assigned_groups.complete_pointers(p_list + pi, manager);
01432 
01433   int i;
01434   for (i = 0; i < _num_placement; i++) {
01435     PaletteGroup *group;
01436     TexturePlacement *placement;
01437     DCAST_INTO_R(group, p_list[pi++], pi);
01438     DCAST_INTO_R(placement, p_list[pi++], pi);
01439     _placement.insert(Placement::value_type(group, placement));
01440   }
01441 
01442   for (i = 0; i < _num_sources; i++) {
01443     SourceTextureImage *source;
01444     DCAST_INTO_R(source, p_list[pi++], pi);
01445     string key = get_source_key(source->get_filename(),
01446                                 source->get_alpha_filename(),
01447                                 source->get_alpha_file_channel());
01448 
01449     bool inserted = _sources.insert(Sources::value_type(key, source)).second;
01450     if (!inserted) {
01451       nout << "Warning: texture key " << key
01452            << " is nonunique; texture lost.\n";
01453     }
01454   }
01455 
01456   for (i = 0; i < _num_dests; i++) {
01457     DestTextureImage *dest;
01458     DCAST_INTO_R(dest, p_list[pi++], pi);
01459     bool inserted = _dests.insert(Dests::value_type(dest->get_filename(), dest)).second;
01460     if (!inserted) {
01461       nout << "Warning: dest filename " << dest->get_filename()
01462            << " is nonunique; texture lost.\n";
01463     }
01464   }
01465 
01466   return pi;
01467 }
01468 
01469 ////////////////////////////////////////////////////////////////////
01470 //     Function: TextureImage::make_TextureImage
01471 //       Access: Protected
01472 //  Description: This method is called by the BamReader when an object
01473 //               of this type is encountered in a Bam file; it should
01474 //               allocate and return a new object with all the data
01475 //               read.
01476 ////////////////////////////////////////////////////////////////////
01477 TypedWritable *TextureImage::
01478 make_TextureImage(const FactoryParams &params) {
01479   TextureImage *me = new TextureImage;
01480   DatagramIterator scan;
01481   BamReader *manager;
01482 
01483   parse_params(params, scan, manager);
01484   me->fillin(scan, manager);
01485   return me;
01486 }
01487 
01488 ////////////////////////////////////////////////////////////////////
01489 //     Function: TextureImage::fillin
01490 //       Access: Protected
01491 //  Description: Reads the binary data from the given datagram
01492 //               iterator, which was written by a previous call to
01493 //               write_datagram().
01494 ////////////////////////////////////////////////////////////////////
01495 void TextureImage::
01496 fillin(DatagramIterator &scan, BamReader *manager) {
01497   ImageFile::fillin(scan, manager);
01498   set_name(scan.get_string());
01499 
01500   _is_surprise = scan.get_bool();
01501   _ever_read_image = scan.get_bool();
01502   _forced_grayscale = scan.get_bool();
01503   _alpha_bits = scan.get_uint8();
01504   _alpha_mode = (EggRenderMode::AlphaMode)scan.get_int16();
01505   if (pal->_read_pi_version >= 16) {
01506     _mid_pixel_ratio = scan.get_float64();
01507     _is_cutout = scan.get_bool();
01508   } else {
01509     // Force a re-read of the image if we are upgrading to pi version 16.
01510     _ever_read_image = false;
01511     _mid_pixel_ratio = 0.0;
01512     _is_cutout = false;
01513   }
01514   if (pal->_read_pi_version >= 17) {
01515     _txa_wrap_u = (EggTexture::WrapMode)scan.get_uint8();
01516     _txa_wrap_v = (EggTexture::WrapMode)scan.get_uint8();
01517   }
01518 
01519   _actual_assigned_groups.fillin(scan, manager);
01520 
01521   _num_placement = scan.get_uint32();
01522   manager->read_pointers(scan, _num_placement * 2);
01523 
01524   _num_sources = scan.get_uint32();
01525   manager->read_pointers(scan, _num_sources);
01526   _num_dests = scan.get_uint32();
01527   manager->read_pointers(scan, _num_dests);
01528 }