Panda3D
paletteImage.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file paletteImage.cxx
10  * @author drose
11  * @date 2000-12-01
12  */
13 
14 #include "paletteImage.h"
15 #include "palettePage.h"
16 #include "paletteGroup.h"
17 #include "texturePlacement.h"
18 #include "palettizer.h"
19 #include "textureImage.h"
20 #include "sourceTextureImage.h"
21 #include "filenameUnifier.h"
22 
23 #include "indent.h"
24 #include "datagram.h"
25 #include "datagramIterator.h"
26 #include "bamReader.h"
27 #include "bamWriter.h"
28 #include "string_utils.h"
29 
30 #include <algorithm>
31 
32 TypeHandle PaletteImage::_type_handle;
33 
34 /**
35  * The default constructor is only for the convenience of the bam reader.
36  */
37 PaletteImage::ClearedRegion::
38 ClearedRegion() {
39  _x = 0;
40  _y = 0;
41  _x_size = 0;
42  _y_size = 0;
43 }
44 
45 /**
46  *
47  */
48 PaletteImage::ClearedRegion::
49 ClearedRegion(TexturePlacement *placement) {
50  _x = placement->get_placed_x();
51  _y = placement->get_placed_y();
52  _x_size = placement->get_placed_x_size();
53  _y_size = placement->get_placed_y_size();
54 }
55 
56 /**
57  *
58  */
59 PaletteImage::ClearedRegion::
60 ClearedRegion(const PaletteImage::ClearedRegion &copy) :
61  _x(copy._x),
62  _y(copy._y),
63  _x_size(copy._x_size),
64  _y_size(copy._y_size)
65 {
66 }
67 
68 /**
69  *
70  */
71 void PaletteImage::ClearedRegion::
72 operator = (const PaletteImage::ClearedRegion &copy) {
73  _x = copy._x;
74  _y = copy._y;
75  _x_size = copy._x_size;
76  _y_size = copy._y_size;
77 }
78 
79 /**
80  * Sets the appropriate region of the image to black.
81  */
82 void PaletteImage::ClearedRegion::
83 clear(PNMImage &image) {
84  LRGBColorf rgb(pal->_background[0], pal->_background[1], pal->_background[2]);
85  float alpha = pal->_background[3];
86 
87  for (int y = _y; y < _y + _y_size; y++) {
88  for (int x = _x; x < _x + _x_size; x++) {
89  image.set_xel(x, y, rgb);
90  }
91  }
92  if (image.has_alpha()) {
93  for (int y = _y; y < _y + _y_size; y++) {
94  for (int x = _x; x < _x + _x_size; x++) {
95  image.set_alpha(x, y, alpha);
96  }
97  }
98  }
99 }
100 
101 /**
102  * Writes the contents of the ClearedRegion to the indicated datagram.
103  */
104 void PaletteImage::ClearedRegion::
105 write_datagram(Datagram &datagram) const {
106  datagram.add_int32(_x);
107  datagram.add_int32(_y);
108  datagram.add_int32(_x_size);
109  datagram.add_int32(_y_size);
110 }
111 
112 /**
113  * Extracts the contents of the ClearedRegion from the indicated datagram.
114  */
115 void PaletteImage::ClearedRegion::
116 fillin(DatagramIterator &scan) {
117  _x = scan.get_int32();
118  _y = scan.get_int32();
119  _x_size = scan.get_int32();
120  _y_size = scan.get_int32();
121 }
122 
123 
124 
125 
126 
127 
128 /**
129  * The default constructor is only for the convenience of the Bam reader.
130  */
131 PaletteImage::
132 PaletteImage() {
133  _page = nullptr;
134  _index = 0;
135  _new_image = false;
136  _got_image = false;
137 
138  _swapped_image = 0;
139 }
140 
141 /**
142  *
143  */
144 PaletteImage::
145 PaletteImage(PalettePage *page, int index) :
146  _page(page),
147  _index(index)
148 {
149  _properties = page->get_properties();
150  _size_known = true;
151  _x_size = pal->_pal_x_size;
152  _y_size = pal->_pal_y_size;
153  _new_image = true;
154  _got_image = false;
155  _swapped_image = 0;
156 
157  setup_filename();
158 }
159 
160 /**
161  *
162  */
163 PaletteImage::
164 PaletteImage(PalettePage *page, int index, unsigned swapIndex) :
165  _page(page),
166  _index(index),
167  _swapped_image(swapIndex)
168 {
169  _properties = page->get_properties();
170  _size_known = true;
171  _x_size = pal->_pal_x_size;
172  _y_size = pal->_pal_y_size;
173  _new_image = true;
174  _got_image = false;
175 
176  setup_filename();
177 }
178 
179 
180 /**
181  * Returns the particular PalettePage this image is associated with.
182  */
184 get_page() const {
185  return _page;
186 }
187 
188 /**
189  * Returns true if there are no textures, or only one "solitary" texture,
190  * placed on the image. In either case, the PaletteImage need not be
191  * generated.
192  */
193 bool PaletteImage::
194 is_empty() const {
195  if (_placements.empty()) {
196  // The image is genuinely empty.
197  return true;
198 
199  } else if (_placements.size() == 1) {
200  // If the image has exactly one texture, we consider the image empty only
201  // if the texture is actually flagged as 'solitary'.
202  return (_placements[0]->get_omit_reason() == OR_solitary);
203 
204  } else {
205  // The image has more than one texture, so it's definitely not empty.
206  return false;
207  }
208 }
209 
210 /**
211  * Returns the fraction of the PaletteImage that is actually used by any
212  * textures. This is 1.0 if every pixel in the PaletteImage is used, or 0.0
213  * if none are. Normally it will be somewhere in between.
214  */
215 double PaletteImage::
217  int used_pixels = 0;
218 
219  Placements::const_iterator pi;
220  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
221  TexturePlacement *placement = (*pi);
222 
223  int texture_pixels =
224  placement->get_placed_x_size() *
225  placement->get_placed_y_size();
226  used_pixels += texture_pixels;
227  }
228 
229  int total_pixels = get_x_size() * get_y_size();
230 
231  return (double)used_pixels / (double)total_pixels;
232 }
233 
234 /**
235  * Returns the a weighted average of the fraction of coverage represented by
236  * all of the textures placed on the palette. This number represents the
237  * fraction of wasted pixels in the palette image consumed by copying the same
238  * pixels multiple times into the palette, or if the number is negative, it
239  * represents the fraction of pixels saved by not having to copy the entire
240  * texture into the palette.
241  */
242 double PaletteImage::
243 count_coverage() const {
244  int coverage_pixels = 0;
245 
246  Placements::const_iterator pi;
247  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
248  TexturePlacement *placement = (*pi);
249  TextureImage *texture = placement->get_texture();
250  nassertr(texture != nullptr, 0.0);
251 
252  int orig_pixels =
253  texture->get_x_size() *
254  texture->get_y_size();
255  int placed_pixels =
256  placement->get_placed_x_size() *
257  placement->get_placed_y_size();
258 
259  coverage_pixels += placed_pixels - orig_pixels;
260  }
261 
262  int total_pixels = get_x_size() * get_y_size();
263 
264  return (double)coverage_pixels / (double)total_pixels;
265 }
266 
267 /**
268  * Attempts to place the indicated texture on the image. Returns true if
269  * successful, or false if there was no available space.
270  */
271 bool PaletteImage::
272 place(TexturePlacement *placement) {
273  nassertr(placement->is_size_known(), true);
274  nassertr(!placement->is_placed(), true);
275 
276  int x, y;
277  if (find_hole(x, y, placement->get_x_size(), placement->get_y_size())) {
278  placement->place_at(this, x, y);
279  _placements.push_back(placement);
280 
281  // [gjeon] create swappedImages
282  TexturePlacement::TextureSwaps::iterator tsi;
283  for (tsi = placement->_textureSwaps.begin(); tsi != placement->_textureSwaps.end(); ++tsi) {
284  if ((tsi - placement->_textureSwaps.begin()) >= (int)_swappedImages.size()) {
285  PaletteImage *swappedImage = new PaletteImage(_page, _swappedImages.size(), tsi - placement->_textureSwaps.begin() + 1);
286  swappedImage->_masterPlacements = &_placements;
287  _swappedImages.push_back(swappedImage);
288  }
289  }
290 
291  return true;
292  }
293 
294  return false;
295 }
296 
297 /**
298  * Removes the texture from the image.
299  */
300 void PaletteImage::
302  nassertv(placement->is_placed() && placement->get_image() == this);
303 
304  Placements::iterator pi;
305  pi = find(_placements.begin(), _placements.end(), placement);
306  while (pi != _placements.end()) {
307  _placements.erase(pi);
308  pi = find(_placements.begin(), _placements.end(), placement);
309  }
310  _cleared_regions.push_back(ClearedRegion(placement));
311 }
312 
313 /**
314  * To be called after all textures have been placed on the image, this checks
315  * to see if there is only one texture on the image. If there is, it is
316  * flagged as 'solitary' so that the egg files will not needlessly reference
317  * the palettized image.
318  *
319  * However, if pal->_omit_solitary is false, we generally don't change
320  * textures to solitary state.
321  */
322 void PaletteImage::
324  if (_placements.size() == 1) {
325  // How sad, only one.
326  TexturePlacement *placement = *_placements.begin();
327  nassertv(placement->get_omit_reason() == OR_none ||
328  placement->get_omit_reason() == OR_solitary);
329 
330  if (pal->_omit_solitary || placement->get_omit_reason() == OR_solitary) {
331  // We only omit the solitary texture if (a) we have omit_solitary in
332  // effect, or (b) we don't have omit_solitary in effect now, but we did
333  // before, and the texture is still flagged as solitary from that
334  // previous pass.
335  placement->omit_solitary();
336  }
337 
338  } else {
339  // Zero or multiple.
340  Placements::const_iterator pi;
341  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
342  TexturePlacement *placement = (*pi);
343  /*
344  if (!(placement->get_omit_reason() == OR_none ||
345  placement->get_omit_reason() == OR_solitary)) {
346  nout << "texture " << *placement->get_texture() << " is omitted for "
347  << placement->get_omit_reason() << "\n";
348  }
349  */
350  nassertv(placement->get_omit_reason() == OR_none ||
351  placement->get_omit_reason() == OR_solitary);
352  placement->not_solitary();
353  }
354  }
355 }
356 
357 /**
358  * Attempts to resize the palette image to as small as it can go.
359  */
360 void PaletteImage::
362  if (is_empty()) { // && (_swapped_image == 0)) {
363  return;
364  }
365 
366  bool resized_any = false;
367  bool success;
368  do {
369  success = false;
370  nassertv(_x_size > 0 && _y_size > 0);
371 
372  // Try to cut it in half in both dimensions, one at a time.
373  if (resize_image(_x_size, _y_size / 2)) {
374  success = true;
375  resized_any = true;
376  }
377  if (resize_image(_x_size / 2, _y_size)) {
378  success = true;
379  resized_any = true;
380  }
381 
382  } while (success);
383 
384  if (resized_any) {
385  nout << "Resizing "
387  << _x_size << " " << _y_size << "\n";
388 
389  // [gjeon] resize swapped images, also
390  SwappedImages::iterator si;
391  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
392  PaletteImage *swappedImage = (*si);
393  swappedImage->resize_swapped_image(_x_size, _y_size);
394  }
395  }
396 }
397 
398 /**
399  * Attempts to resize the palette image, and repack all of the textures within
400  * the new size. Returns true if successful, false otherwise. If this fails,
401  * it will still result in repacking all the textures in the original size.
402  */
403 bool PaletteImage::
404 resize_image(int x_size, int y_size) {
405  // We already know we're going to be generating a new image from scratch
406  // after this.
407  _cleared_regions.clear();
408  remove_image();
409 
410  // First, Save the current placement list, while simultaneously clearing it.
411  Placements saved;
412  saved.swap(_placements);
413 
414  // Also save our current size.
415  int saved_x_size = _x_size;
416  int saved_y_size = _y_size;
417 
418  // Then, sort the textures to in order from biggest to smallest, as an aid
419  // to optimal packing.
420  sort(saved.begin(), saved.end(), SortPlacementBySize());
421 
422  // And while we're at it, we need to officially unplace each of these.
423  Placements::iterator pi;
424  for (pi = saved.begin(); pi != saved.end(); ++pi) {
425  (*pi)->force_replace();
426  }
427 
428  // Finally, apply the new size and try to fit all the textures.
429  _x_size = x_size;
430  _y_size = y_size;
431 
432  bool packed = true;
433  for (pi = saved.begin(); pi != saved.end() && packed; ++pi) {
434  if (!place(*pi)) {
435  packed = false;
436  }
437  }
438 
439  if (!packed) {
440  // If it didn't work, phooey. Put 'em all back.
441  _x_size = saved_x_size;
442  _y_size = saved_y_size;
443 
444  Placements remove;
445  remove.swap(_placements);
446  for (pi = remove.begin(); pi != remove.end(); ++pi) {
447  (*pi)->force_replace();
448  }
449 
450  bool all_packed = true;
451  for (pi = saved.begin(); pi != saved.end(); ++pi) {
452  if (!place(*pi)) {
453  all_packed = false;
454  }
455  }
456  nassertr(all_packed, false);
457  }
458 
459  return packed;
460 }
461 
462 /**
463  * Attempts to resize the palette image, and repack all of the textures within
464  * the new size. Returns true if successful, false otherwise. If this fails,
465  * it will still result in repacking all the textures in the original size.
466  */
467 void PaletteImage::
468 resize_swapped_image(int x_size, int y_size) {
469  // Finally, apply the new size and try to fit all the textures.
470  _x_size = x_size;
471  _y_size = y_size;
472 }
473 
474 /**
475  * Writes a list of the textures that have been placed on this image to the
476  * indicated output stream, one per line.
477  */
478 void PaletteImage::
479 write_placements(std::ostream &out, int indent_level) const {
480  Placements::const_iterator pi;
481  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
482  TexturePlacement *placement = (*pi);
483  placement->write_placed(out, indent_level);
484  }
485 }
486 
487 /**
488  * Unpacks each texture that has been placed on this image, resetting the
489  * image to empty.
490  */
491 void PaletteImage::
493  // We need a copy so we can modify this list as we traverse it.
494  Placements copy_placements = _placements;
495  Placements::const_iterator pi;
496  for (pi = copy_placements.begin(); pi != copy_placements.end(); ++pi) {
497  TexturePlacement *placement = (*pi);
498  placement->force_replace();
499  }
500 
501  _placements.clear();
502  _cleared_regions.clear();
503  remove_image();
504 }
505 
506 /**
507  * Ensures the _shadow_image has the correct filename and image types, based
508  * on what was supplied on the command line and in the .txa file.
509  */
510 void PaletteImage::
512  _shadow_image.make_shadow_image(_basename);
513 
514  // [gjeon] setup shadoe_image of swappedImages
515  SwappedImages::iterator si;
516  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
517  PaletteImage *swappedImage = (*si);
518  swappedImage->setup_shadow_image();
519  }
520 }
521 
522 /**
523  * If the palette has changed since it was last written out, updates the image
524  * and writes out a new one. If redo_all is true, regenerates the image from
525  * scratch and writes it out again, whether it needed it or not.
526  */
527 void PaletteImage::
528 update_image(bool redo_all) {
529  if (is_empty() && pal->_aggressively_clean_mapdir) {
530  // If the palette image is 'empty', ensure that it doesn't exist. No need
531  // to clutter up the map directory.
532  remove_image();
533  return;
534  }
535 
536  if (redo_all) {
537  // If we're redoing everything, throw out the old image anyway.
538  remove_image();
539  }
540 
541  // Check the filename too.
542  update_filename();
543 
544  // Do we need to update?
545  bool needs_update =
546  _new_image || !exists() ||
547  !_cleared_regions.empty();
548 
549  Placements::iterator pi;
550  // We must continue to walk through all of the textures on the palette, even
551  // after we discover the palette requires an update, so we can determine
552  // which source images need to be recopied.
553  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
554  TexturePlacement *placement = (*pi);
555 
556  if (!placement->is_filled()) {
557  needs_update = true;
558 
559  } else {
560  TextureImage *texture = placement->get_texture();
561 
562  // Only check the timestamps on textures that are named (indirectly) on
563  // the command line.
564  if (texture->is_texture_named()) {
565  SourceTextureImage *source = texture->get_preferred_source();
566 
567  if (source != nullptr &&
568  source->get_filename().compare_timestamps(get_filename()) > 0) {
569  // The source image is newer than the palette image; we need to
570  // regenerate.
571  placement->mark_unfilled();
572  needs_update = true;
573  }
574  }
575 
576  // [gjeon] to find out all of the swappable textures is up to date
577  TexturePlacement::TextureSwaps::iterator tsi;
578  for (tsi = placement->_textureSwaps.begin(); tsi != placement->_textureSwaps.end(); ++tsi) {
579  TextureImage *swapTexture = (*tsi);
580 
581  if (swapTexture->is_texture_named()) {
582  SourceTextureImage *sourceSwapTexture = swapTexture->get_preferred_source();
583 
584  if (sourceSwapTexture != nullptr &&
585  sourceSwapTexture->get_filename().compare_timestamps(get_filename()) > 0) {
586  // The source image is newer than the palette image; we need to
587  // regenerate.
588  placement->mark_unfilled();
589  needs_update = true;
590  }
591  }
592  }
593 
594  }
595  }
596 
597  if (!needs_update) {
598  // No sweat; nothing has changed.
599  return;
600  }
601 
602  get_image();
603  // [gjeon] get swapped images, too
604  get_swapped_images();
605 
606  // Set to black any parts of the image that we recently unplaced.
607  ClearedRegions::iterator ci;
608  for (ci = _cleared_regions.begin(); ci != _cleared_regions.end(); ++ci) {
609  ClearedRegion &region = (*ci);
610  region.clear(_image);
611 
612  // [gjeon] clear swapped images also
613  SwappedImages::iterator si;
614  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
615  PaletteImage *swappedImage = (*si);
616  region.clear(swappedImage->_image);
617  }
618  }
619  _cleared_regions.clear();
620 
621  // Now add the recent additions to the image.
622  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
623  TexturePlacement *placement = (*pi);
624  if (!placement->is_filled()) {
625  placement->fill_image(_image);
626 
627  // [gjeon] fill swapped images
628  SwappedImages::iterator si;
629  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
630  PaletteImage *swappedImage = (*si);
631  swappedImage->update_filename();
632  placement->fill_swapped_image(swappedImage->_image, si - _swappedImages.begin());
633  }
634  }
635  }
636 
637  write(_image);
638 
639  if (pal->_shadow_color_type != nullptr) {
640  _shadow_image.write(_image);
641  }
642 
643  release_image();
644 
645  // [gjeon] write and release swapped images
646  SwappedImages::iterator si;
647  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
648  PaletteImage *swappedImage = (*si);
649  swappedImage->write(swappedImage->_image);
650  if (pal->_shadow_color_type != nullptr) {
651  swappedImage->_shadow_image.write(swappedImage->_image);
652  }
653  swappedImage->release_image();
654  }
655 }
656 
657 /**
658  * Changes the image filename to match the current naming scheme, assuming
659  * something has changed since the image was created. Returns true if the
660  * image filename changes (which means update_image() should be called).
661  */
662 bool PaletteImage::
664  Filename orig_filename = _filename;
665  Filename orig_alpha_filename = _alpha_filename;
666  Filename orig_shadow_filename = _shadow_image.get_filename();
667 
668  if (setup_filename()) {
669  nout << "Renaming " << FilenameUnifier::make_user_filename(orig_filename)
670  << " to " << FilenameUnifier::make_user_filename(_filename) << "\n";
671 
672  if (!orig_filename.empty() && orig_filename.exists()) {
673  nout << "Deleting " << FilenameUnifier::make_user_filename(orig_filename) << "\n";
674  orig_filename.unlink();
675  }
676  if (!orig_alpha_filename.empty() && orig_alpha_filename.exists()) {
677  nout << "Deleting " << FilenameUnifier::make_user_filename(orig_alpha_filename) << "\n";
678  orig_alpha_filename.unlink();
679  }
680  if (!orig_shadow_filename.empty() && orig_shadow_filename.exists()) {
681  nout << "Deleting " << FilenameUnifier::make_user_filename(orig_shadow_filename) << "\n";
682  orig_shadow_filename.unlink();
683  }
684  _new_image = true;
685 
686  // Since the palette filename has changed, we need to mark all of the egg
687  // files that referenced the old filename as stale.
688 
689  // Marking egg files stale at this late point can cause minor problems;
690  // because we might do this, it's necessary for eggPalettize.cxx to call
691  // read_stale_eggs() twice.
692  Placements::iterator pi;
693  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
694  TexturePlacement *placement = (*pi);
695  placement->mark_eggs_stale();
696  }
697 
698  return true;
699  }
700 
701  return false;
702 }
703 
704 /**
705  * Sets up the image's filename (and that of the _shadow_pal) according to the
706  * specified properties.
707  *
708  * Returns true if the filename changes from what it was previously, false
709  * otherwise.
710  */
711 bool PaletteImage::
712 setup_filename() {
713  // Build up the basename for the palette image, based on the supplied image
714  // pattern.
715  _basename = std::string();
716 
717  std::string::iterator si = pal->_generated_image_pattern.begin();
718  while (si != pal->_generated_image_pattern.end()) {
719  if ((*si) == '%') {
720  // Some keycode.
721  ++si;
722  if (si != pal->_generated_image_pattern.end()) {
723  switch (*si) {
724  case '%':
725  _basename += '%';
726  break;
727 
728  case 'g':
729  _basename += _page->get_group()->get_name();
730  break;
731 
732  case 'p':
733  _basename += _page->get_name();
734  break;
735 
736  case 'i':
737  _basename += format_string(_index + 1);
738  break;
739 
740  default:
741  _basename += '%';
742  _basename += (*si);
743  }
744  ++si;
745  }
746  } else {
747  // A literal character.
748  _basename += (*si);
749  ++si;
750  }
751  }
752 
753  if (_swapped_image > 0) {
754  _basename += "_swp_";
755  _basename += format_string(_swapped_image);
756  }
757 
758  // We must end the basename with a dot, so that it does not appear to have a
759  // filename extension. Otherwise, an embedded dot in the group's name would
760  // make everything following appear to be an extension, which would get lost
761  // in the set_filename() call.
762  if (_basename.empty() || _basename[_basename.length() - 1] != '.') {
763  _basename += '.';
764  }
765 
766  bool any_changed = false;
767 
768  if (set_filename(_page->get_group(), _basename)) {
769  any_changed = true;
770  }
771 
772  if (_shadow_image.make_shadow_image(_basename)) {
773  any_changed = true;
774  }
775 
776  return any_changed;
777 }
778 
779 /**
780  * Searches for a hole of at least x_size by y_size pixels somewhere within
781  * the PaletteImage. If a suitable hole is found, sets x and y to the top
782  * left corner and returns true; otherwise, returns false.
783  */
784 bool PaletteImage::
785 find_hole(int &x, int &y, int x_size, int y_size) const {
786  y = 0;
787  while (y + y_size <= _y_size) {
788  int next_y = _y_size;
789  // Scan along the row at 'y'.
790  x = 0;
791  while (x + x_size <= _x_size) {
792  int next_x = x;
793 
794  // Consider the spot at x, y.
795  TexturePlacement *overlap = find_overlap(x, y, x_size, y_size);
796 
797  if (overlap == nullptr) {
798  // Hooray!
799  return true;
800  }
801 
802  next_x = overlap->get_placed_x() + overlap->get_placed_x_size();
803  next_y = std::min(next_y, overlap->get_placed_y() + overlap->get_placed_y_size());
804  nassertr(next_x > x, false);
805  x = next_x;
806  }
807 
808  nassertr(next_y > y, false);
809  y = next_y;
810  }
811 
812  // Nope, wouldn't fit anywhere.
813  return false;
814 }
815 
816 /**
817  * If the rectangle whose top left corner is x, y and whose size is x_size,
818  * y_size describes an empty hole that does not overlap any placed images,
819  * returns NULL; otherwise, returns the first placed texture that the image
820  * does overlap. It is assumed the rectangle lies completely within the
821  * boundaries of the image itself.
822  */
823 TexturePlacement *PaletteImage::
824 find_overlap(int x, int y, int x_size, int y_size) const {
825  Placements::const_iterator pi;
826  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
827  TexturePlacement *placement = (*pi);
828  if (placement->is_placed() &&
829  placement->intersects(x, y, x_size, y_size)) {
830  return placement;
831  }
832  }
833 
834  return nullptr;
835 }
836 
837 /**
838  * Reads or generates the PNMImage that corresponds to the palette as it is
839  * known so far.
840  */
841 void PaletteImage::
842 get_image() {
843  if (_got_image) {
844  return;
845  }
846 
847  if (!_new_image) {
848  if (pal->_shadow_color_type != nullptr) {
849  if (_shadow_image.get_filename().exists() && _shadow_image.read(_image)) {
850  _got_image = true;
851  return;
852  }
853  } else {
854  if (get_filename().exists() && read(_image)) {
855  _got_image = true;
856  return;
857  }
858  }
859  }
860 
861  nout << "Generating new "
863 
864  // We won't be using this any more.
865  _cleared_regions.clear();
866 
867  _image.clear(get_x_size(), get_y_size(), _properties.get_num_channels());
868  _image.fill(pal->_background[0], pal->_background[1], pal->_background[2]);
869  if (_image.has_alpha()) {
870  _image.alpha_fill(pal->_background[3]);
871  }
872 
873  _new_image = false;
874  _got_image = true;
875 
876  // Now fill up the image.
877  Placements::iterator pi;
878  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
879  TexturePlacement *placement = (*pi);
880  placement->fill_image(_image);
881  }
882 }
883 
884 /**
885  * Reads or generates the PNMImage for swapped textures
886  */
887 void PaletteImage::
888 get_swapped_image(int index) {
889  if (_got_image) {
890  return;
891  }
892 
893  if (!_new_image) {
894  if (pal->_shadow_color_type != nullptr) {
895  if (_shadow_image.get_filename().exists() && _shadow_image.read(_image)) {
896  _got_image = true;
897  return;
898  }
899  } else {
900  if (get_filename().exists() && read(_image)) {
901  _got_image = true;
902  return;
903  }
904  }
905  }
906 
907  nout << "Generating new "
909 
910  // We won't be using this any more.
911  _cleared_regions.clear();
912 
913  _image.clear(get_x_size(), get_y_size(), _properties.get_num_channels());
914  _image.fill(pal->_background[0], pal->_background[1], pal->_background[2]);
915  if (_image.has_alpha()) {
916  _image.alpha_fill(pal->_background[3]);
917  }
918 
919  _new_image = false;
920  _got_image = true;
921 
922  // Now fill up the image.
923  Placements::iterator pi;
924  for (pi = _masterPlacements->begin(); pi != _masterPlacements->end(); ++pi) {
925  TexturePlacement *placement = (*pi);
926  if ((int)placement->_textureSwaps.size() > index) {
927  placement->fill_swapped_image(_image, index);
928  } else {
929  placement->fill_image(_image);
930  }
931  }
932 }
933 
934 /**
935  * Reads or generates the PNMImage that corresponds to the palette as it is
936  * known so far.
937  */
938 void PaletteImage::
939 get_swapped_images() {
940  SwappedImages::iterator si;
941  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
942  PaletteImage *swappedImage = (*si);
943  swappedImage->get_swapped_image(si - _swappedImages.begin());
944  }
945 }
946 
947 /**
948  * Deallocates the memory allocated by a previous call to get_image().
949  */
950 void PaletteImage::
951 release_image() {
952  _image.clear();
953  _got_image = false;
954 }
955 
956 /**
957  * Deletes the image file.
958  */
959 void PaletteImage::
960 remove_image() {
961  unlink();
962  if (pal->_shadow_color_type != nullptr) {
963  _shadow_image.unlink();
964  }
965  _new_image = true;
966 }
967 
968 /**
969  * Registers the current object as something that can be read from a Bam file.
970  */
971 void PaletteImage::
974  register_factory(get_class_type(), make_PaletteImage);
975 }
976 
977 /**
978  * Fills the indicated datagram up with a binary representation of the current
979  * object, in preparation for writing to a Bam file.
980  */
981 void PaletteImage::
982 write_datagram(BamWriter *writer, Datagram &datagram) {
983  ImageFile::write_datagram(writer, datagram);
984 
985  datagram.add_uint32(_cleared_regions.size());
986  ClearedRegions::const_iterator ci;
987  for (ci = _cleared_regions.begin(); ci != _cleared_regions.end(); ++ci) {
988  (*ci).write_datagram(datagram);
989  }
990 
991  datagram.add_uint32(_placements.size());
992  Placements::const_iterator pi;
993  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
994  writer->write_pointer(datagram, (*pi));
995  }
996 
997  writer->write_pointer(datagram, _page);
998  datagram.add_uint32(_index);
999  datagram.add_string(_basename);
1000  datagram.add_bool(_new_image);
1001 
1002  // We don't write _got_image or _image. These are loaded per-session.
1003 
1004  // We don't write _shadow_image. This is just a runtime convenience for
1005  // specifying the name of the shadow file, and we redefine this per-session
1006  // (which allows us to pick up a new pal->_shadow_dirname if it changes).
1007 }
1008 
1009 /**
1010  * Called after the object is otherwise completely read from a Bam file, this
1011  * function's job is to store the pointers that were retrieved from the Bam
1012  * file for each pointer object written. The return value is the number of
1013  * pointers processed from the list.
1014  */
1015 int PaletteImage::
1017  int index = ImageFile::complete_pointers(p_list, manager);
1018 
1019  int i;
1020  _placements.reserve(_num_placements);
1021  for (i = 0; i < _num_placements; i++) {
1022  TexturePlacement *placement;
1023  DCAST_INTO_R(placement, p_list[index], index);
1024  _placements.push_back(placement);
1025  index++;
1026  }
1027 
1028  if (p_list[index] != nullptr) {
1029  DCAST_INTO_R(_page, p_list[index], index);
1030  }
1031  index++;
1032 
1033  return index;
1034 }
1035 
1036 /**
1037  * This method is called by the BamReader when an object of this type is
1038  * encountered in a Bam file; it should allocate and return a new object with
1039  * all the data read.
1040  */
1041 TypedWritable *PaletteImage::
1042 make_PaletteImage(const FactoryParams &params) {
1043  PaletteImage *me = new PaletteImage;
1044  DatagramIterator scan;
1045  BamReader *manager;
1046 
1047  parse_params(params, scan, manager);
1048  me->fillin(scan, manager);
1049  return me;
1050 }
1051 
1052 /**
1053  * Reads the binary data from the given datagram iterator, which was written
1054  * by a previous call to write_datagram().
1055  */
1056 void PaletteImage::
1057 fillin(DatagramIterator &scan, BamReader *manager) {
1058  ImageFile::fillin(scan, manager);
1059 
1060  int num_cleared_regions = scan.get_uint32();
1061  _cleared_regions.reserve(num_cleared_regions);
1062  for (int i = 0; i < num_cleared_regions; i++) {
1063  _cleared_regions.push_back(ClearedRegion());
1064  _cleared_regions.back().fillin(scan);
1065  }
1066 
1067  _num_placements = scan.get_uint32();
1068  manager->read_pointers(scan, _num_placements);
1069 
1070  manager->read_pointer(scan); // _page
1071 
1072  _index = scan.get_uint32();
1073  _basename = scan.get_string();
1074  _new_image = scan.get_bool();
1075 }
bool write(const PNMImage &image) const
Writes out the image in the indicated PNMImage to the _filename and/or _alpha_filename.
Definition: imageFile.cxx:339
bool is_filled() const
Returns true if the texture has been filled (i.e.
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:277
int get_y_size() const
Returns the size in the Y dimension, in pixels, of the texture image as it must appear in the palette...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void not_solitary()
Indicates that the texture, formerly indicated as solitary, is now no longer.
double count_utilization() const
Returns the fraction of the PaletteImage that is actually used by any textures.
static void register_with_read_factory()
Registers the current object as something that can be read from a Bam file.
bool get_bool()
Extracts a boolean value.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
bool exists() const
Returns true if the file or files named by the image file exist, false otherwise.
Definition: imageFile.cxx:256
bool is_empty() const
Returns true if there are no textures, or only one "solitary" texture, placed on the image.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
int get_placed_x_size() const
Returns the size in the X dimension, in pixels, of the texture image as it has been placed within the...
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Called after the object is otherwise completely read from a Bam file, this function's job is to store...
Definition: imageFile.cxx:454
void alpha_fill(float alpha=0.0)
Sets the entire alpha channel to the given level.
Definition: pnmImage.I:183
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double count_coverage() const
Returns the a weighted average of the fraction of coverage represented by all of the textures placed ...
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition: imageFile.cxx:92
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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:35
int get_placed_x() const
Returns the X pixel at which the texture has been placed within its PaletteImage.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
This is a particular collection of textures, within a PaletteGroup, that all share the same TexturePr...
Definition: palettePage.h:33
int32_t get_int32()
Extracts a signed 32-bit integer.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void place_at(PaletteImage *image, int x, int y)
Assigns the texture to a particular position within the indicated PaletteImage.
void omit_solitary()
Sets the omit reason (returned by get_omit()) to OR_solitary, indicating that the palettized version ...
std::string get_string()
Extracts a variable-length string.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_placed_y() const
Returns the Y pixel at which the texture has been placed within its PaletteImage.
void add_uint32(uint32_t value)
Adds an unsigned 32-bit integer to the datagram.
Definition: datagram.I:94
SourceTextureImage * get_preferred_source()
Determines the preferred source image for examining size and reading pixels, etc.
PalettePage * get_page() const
Returns the particular PalettePage this image is associated with.
bool place(TexturePlacement *placement)
Attempts to place the indicated texture on the image.
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
Definition: imageFile.cxx:436
bool set_filename(PaletteGroup *group, const std::string &basename)
Sets the filename, and if applicable, the alpha_filename, from the indicated basename.
Definition: imageFile.cxx:150
void optimal_resize()
Attempts to resize the palette image to as small as it can go.
void parse_params(const FactoryParams &params, DatagramIterator &scan, BamReader *&manager)
Takes in a FactoryParams, passed from a WritableFactory into any TypedWritable's make function,...
Definition: bamReader.I:275
void force_replace()
Removes the texture from its particular PaletteImage, but does not remove it from the PaletteGroup.
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:34
bool is_size_known() const
Returns true if the texture's size is known, false otherwise.
void check_solitary()
To be called after all textures have been placed on the image, this checks to see if there is only on...
static Filename make_user_filename(Filename filename)
Returns a new filename that's made relative to the current directory, suitable for reporting to the u...
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition: imageFile.cxx:82
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
void write_placements(std::ostream &out, int indent_level=0) const
Writes a list of the textures that have been placed on this image to the indicated output stream,...
void reset_image()
Unpacks each texture that has been placed on this image, resetting the image to empty.
PaletteGroup * get_group() const
Returns the group this particular PalettePage belongs to.
Definition: palettePage.cxx:53
void read_pointers(DatagramIterator &scan, int count)
A convenience function to read a contiguous list of pointers.
Definition: bamReader.cxx:653
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Called after the object is otherwise completely read from a Bam file, this function's job is to store...
void add_string(const std::string &str)
Adds a variable-length string to the datagram.
Definition: datagram.I:219
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
uint32_t get_uint32()
Extracts an unsigned 32-bit integer.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This corresponds to a particular assignment of a TextureImage with a PaletteGroup,...
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
Definition: filename.cxx:2319
This is a texture image reference as it appears in an egg file: the source image of the texture.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
OmitReason get_omit_reason() const
Returns the reason the texture has been omitted from a palette image, or OR_none if it has not.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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:1417
void unlink()
Deletes the image file or files.
Definition: imageFile.cxx:388
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition: pnmImage.cxx:48
TextureImage * get_texture() const
Returns the texture that this placement represents.
void add_int32(int32_t value)
Adds a signed 32-bit integer to the datagram.
Definition: datagram.I:67
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition: pnmImage.I:845
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
int get_x_size() const
Returns the size in the X dimension, in pixels, of the texture image as it must appear in the palette...
void write_placed(std::ostream &out, int indent_level=0)
Writes the placement position information on a line by itself.
bool read_pointer(DatagramIterator &scan)
The interface for reading a pointer to another object from a Bam file.
Definition: bamReader.cxx:610
void setup_shadow_image()
Ensures the _shadow_image has the correct filename and image types, based on what was supplied on the...
bool is_texture_named() const
Returns true if this particular texture has been named by the user for procession this session,...
void fill_swapped_image(PNMImage &image, int index)
Fills in the rectangle of the swapped palette image represented by the texture placement with the ima...
bool make_shadow_image(const std::string &basename)
Sets up the ImageFile as a "shadow image" of a particular PaletteImage.
Definition: imageFile.cxx:51
This is a single palette image, one of several within a PalettePage, which is in turn one of several ...
Definition: paletteImage.h:32
void unplace(TexturePlacement *placement)
Removes the texture from the image.
bool intersects(int x, int y, int x_size, int y_size)
Returns true if the particular position this texture has been assigned to overlaps the rectangle whos...
A class to retrieve the individual data elements previously stored in a Datagram.
bool resize_image(int x_size, int y_size)
Attempts to resize the palette image, and repack all of the textures within the new size.
This represents a single source texture that is referenced by one or more egg files.
Definition: textureImage.h:46
void update_image(bool redo_all)
If the palette has changed since it was last written out, updates the image and writes out a new one.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
void fill(float red, float green, float blue)
Sets the entire image (except the alpha channel) to the given color.
Definition: pnmImage.I:157
const Filename & get_filename() const
Returns the primary filename of the image file.
Definition: imageFile.cxx:225
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
void resize_swapped_image(int x_size, int y_size)
Attempts to resize the palette image, and repack all of the textures within the new size.
int get_placed_y_size() const
Returns the size in the Y dimension, in pixels, of the texture image as it has been placed within the...
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition: pnmImage.I:522
void mark_eggs_stale()
Marks all the egg files that reference this placement stale.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1267
bool update_filename()
Changes the image filename to match the current naming scheme, assuming something has changed since t...
void fill_image(PNMImage &image)
Fills in the rectangle of the palette image represented by the texture placement with the image pixel...
bool is_placed() const
Returns true if the texture has been placed on a palette image, false otherwise.
void write_pointer(Datagram &packet, const TypedWritable *dest)
The interface for writing a pointer to another object to a Bam file.
Definition: bamWriter.cxx:317
const TextureProperties & get_properties() const
Returns the texture grouping properties that all textures in this page share.
Definition: palettePage.cxx:62