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 }
TexturePlacement::is_size_known
bool is_size_known() const
Returns true if the texture's size is known, false otherwise.
Definition: texturePlacement.cxx:392
PaletteImage::unplace
void unplace(TexturePlacement *placement)
Removes the texture from the image.
Definition: paletteImage.cxx:301
ImageFile::complete_pointers
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
PaletteImage::optimal_resize
void optimal_resize()
Attempts to resize the palette image to as small as it can go.
Definition: paletteImage.cxx:361
DatagramIterator::get_string
std::string get_string()
Extracts a variable-length string.
Definition: datagramIterator.cxx:26
PalettePage::get_properties
const TextureProperties & get_properties() const
Returns the texture grouping properties that all textures in this page share.
Definition: palettePage.cxx:62
ImageFile::get_y_size
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition: imageFile.cxx:92
PaletteImage::complete_pointers
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: paletteImage.cxx:1016
palettePage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PaletteImage
This is a single palette image, one of several within a PalettePage, which is in turn one of several ...
Definition: paletteImage.h:32
DatagramIterator::get_int32
int32_t get_int32()
Extracts a signed 32-bit integer.
Definition: datagramIterator.I:107
ImageFile::make_shadow_image
bool make_shadow_image(const std::string &basename)
Sets up the ImageFile as a "shadow image" of a particular PaletteImage.
Definition: imageFile.cxx:51
ImageFile::write_datagram
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
pvector< TexturePlacement * >
PaletteImage::write_placements
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,...
Definition: paletteImage.cxx:479
string_utils.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PNMImage::clear
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition: pnmImage.cxx:48
PaletteImage::write_datagram
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
Definition: paletteImage.cxx:982
ImageFile::get_filename
const Filename & get_filename() const
Returns the primary filename of the image file.
Definition: imageFile.cxx:225
DatagramIterator
A class to retrieve the individual data elements previously stored in a Datagram.
Definition: datagramIterator.h:27
Filename::exists
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1267
BamReader
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
PaletteImage::count_utilization
double count_utilization() const
Returns the fraction of the PaletteImage that is actually used by any textures.
Definition: paletteImage.cxx:216
PNMImage::set_alpha
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition: pnmImage.I:859
BamWriter
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
TexturePlacement::fill_swapped_image
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...
Definition: texturePlacement.cxx:823
BamWriter::write_pointer
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
texturePlacement.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PaletteImage::get_page
PalettePage * get_page() const
Returns the particular PalettePage this image is associated with.
Definition: paletteImage.cxx:184
PaletteImage::update_image
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.
Definition: paletteImage.cxx:528
PaletteImage::is_empty
bool is_empty() const
Returns true if there are no textures, or only one "solitary" texture, placed on the image.
Definition: paletteImage.cxx:194
BamReader::get_factory
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
TexturePlacement::force_replace
void force_replace()
Removes the texture from its particular PaletteImage, but does not remove it from the PaletteGroup.
Definition: texturePlacement.cxx:545
PaletteImage::check_solitary
void check_solitary()
To be called after all textures have been placed on the image, this checks to see if there is only on...
Definition: paletteImage.cxx:323
bamReader.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
textureImage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PaletteImage::resize_swapped_image
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.
Definition: paletteImage.cxx:468
TypedWritable
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
PNMImage
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
Datagram
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
TexturePlacement::get_placed_x_size
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...
Definition: texturePlacement.cxx:495
SourceTextureImage
This is a texture image reference as it appears in an egg file: the source image of the texture.
Definition: sourceTextureImage.h:28
Datagram::add_string
void add_string(const std::string &str)
Adds a variable-length string to the datagram.
Definition: datagram.I:219
TypeHandle
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
ImageFile::exists
bool exists() const
Returns true if the file or files named by the image file exist, false otherwise.
Definition: imageFile.cxx:256
TexturePlacement::get_omit_reason
OmitReason get_omit_reason() const
Returns the reason the texture has been omitted from a palette image, or OR_none if it has not.
Definition: texturePlacement.cxx:401
paletteImage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PaletteImage::update_filename
bool update_filename()
Changes the image filename to match the current naming scheme, assuming something has changed since t...
Definition: paletteImage.cxx:663
BamReader::read_pointers
void read_pointers(DatagramIterator &scan, int count)
A convenience function to read a contiguous list of pointers.
Definition: bamReader.cxx:653
filenameUnifier.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
DatagramIterator::get_bool
bool get_bool()
Extracts a boolean value.
Definition: datagramIterator.I:48
FactoryParams
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
TexturePlacement::get_x_size
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...
Definition: texturePlacement.cxx:411
TexturePlacement::get_y_size
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...
Definition: texturePlacement.cxx:422
TexturePlacement::get_image
PaletteImage * get_image() const
Returns the particular PaletteImage on which the texture has been placed.
Definition: texturePlacement.cxx:456
palettizer.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ImageFile::read
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
PaletteImage::place
bool place(TexturePlacement *placement)
Attempts to place the indicated texture on the image.
Definition: paletteImage.cxx:272
PalettePage::get_group
PaletteGroup * get_group() const
Returns the group this particular PalettePage belongs to.
Definition: palettePage.cxx:53
datagram.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TexturePlacement::place_at
void place_at(PaletteImage *image, int x, int y)
Assigns the texture to a particular position within the indicated PaletteImage.
Definition: texturePlacement.cxx:527
TexturePlacement::intersects
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...
Definition: texturePlacement.cxx:590
TexturePlacement::mark_unfilled
void mark_unfilled()
Marks the texture as unfilled, so that it will need to be copied into the palette image again.
Definition: texturePlacement.cxx:685
PalettePage
This is a particular collection of textures, within a PaletteGroup, that all share the same TexturePr...
Definition: palettePage.h:33
PaletteImage::setup_shadow_image
void setup_shadow_image()
Ensures the _shadow_image has the correct filename and image types, based on what was supplied on the...
Definition: paletteImage.cxx:511
PaletteImage::resize_image
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.
Definition: paletteImage.cxx:404
sourceTextureImage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ImageFile::unlink
void unlink()
Deletes the image file or files.
Definition: imageFile.cxx:388
PNMImageHeader::has_alpha
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
Definition: pnmImageHeader.I:106
Datagram::add_int32
void add_int32(int32_t value)
Adds a signed 32-bit integer to the datagram.
Definition: datagram.I:67
TexturePlacement::get_placed_y_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...
Definition: texturePlacement.cxx:505
PaletteImage::reset_image
void reset_image()
Unpacks each texture that has been placed on this image, resetting the image to empty.
Definition: paletteImage.cxx:492
TexturePlacement::write_placed
void write_placed(std::ostream &out, int indent_level=0)
Writes the placement position information on a line by itself.
Definition: texturePlacement.cxx:646
TexturePlacement::get_texture
TextureImage * get_texture() const
Returns the texture that this placement represents.
Definition: texturePlacement.cxx:103
SortPlacementBySize
Definition: texturePlacement.h:152
TexturePlacement::get_placed_x
int get_placed_x() const
Returns the X pixel at which the texture has been placed within its PaletteImage.
Definition: texturePlacement.cxx:475
PNMImage::set_xel
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition: pnmImage.I:579
FilenameUnifier::make_user_filename
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...
Definition: filenameUnifier.cxx:97
PNMImage::alpha_fill
void alpha_fill(float alpha=0.0)
Sets the entire alpha channel to the given level.
Definition: pnmImage.I:272
Datagram::add_uint32
void add_uint32(uint32_t value)
Adds an unsigned 32-bit integer to the datagram.
Definition: datagram.I:94
ImageFile::write
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
BamReader::read_pointer
bool read_pointer(DatagramIterator &scan)
The interface for reading a pointer to another object from a Bam file.
Definition: bamReader.cxx:610
TexturePlacement::is_filled
bool is_filled() const
Returns true if the texture has been filled (i.e.
Definition: texturePlacement.cxx:676
TexturePlacement
This corresponds to a particular assignment of a TextureImage with a PaletteGroup,...
Definition: texturePlacement.h:41
PaletteImage::count_coverage
double count_coverage() const
Returns the a weighted average of the fraction of coverage represented by all of the textures placed ...
Definition: paletteImage.cxx:243
Datagram::add_bool
void add_bool(bool value)
Adds a boolean value to the datagram.
Definition: datagram.I:34
PNMImage::fill
void fill(float red, float green, float blue)
Sets the entire image (except the alpha channel) to the given color.
Definition: pnmImage.I:246
Filename::compare_timestamps
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
ImageFile::get_x_size
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition: imageFile.cxx:82
datagramIterator.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TexturePlacement::fill_image
void fill_image(PNMImage &image)
Fills in the rectangle of the palette image represented by the texture placement with the image pixel...
Definition: texturePlacement.cxx:694
indent.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TexturePlacement::is_placed
bool is_placed() const
Returns true if the texture has been placed on a palette image, false otherwise.
Definition: texturePlacement.cxx:448
DatagramIterator::get_uint32
uint32_t get_uint32()
Extracts an unsigned 32-bit integer.
Definition: datagramIterator.I:164
Filename::unlink
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
Definition: filename.cxx:2319
TexturePlacement::mark_eggs_stale
void mark_eggs_stale()
Marks all the egg files that reference this placement stale.
Definition: texturePlacement.cxx:159
bamWriter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TexturePlacement::omit_solitary
void omit_solitary()
Sets the omit reason (returned by get_omit()) to OR_solitary, indicating that the palettized version ...
Definition: texturePlacement.cxx:563
TextureImage::get_preferred_source
SourceTextureImage * get_preferred_source()
Determines the preferred source image for examining size and reading pixels, etc.
Definition: textureImage.cxx:547
TexturePlacement::get_placed_y
int get_placed_y() const
Returns the Y pixel at which the texture has been placed within its PaletteImage.
Definition: texturePlacement.cxx:485
TexturePlacement::not_solitary
void not_solitary()
Indicates that the texture, formerly indicated as solitary, is now no longer.
Definition: texturePlacement.cxx:576
PaletteImage::register_with_read_factory
static void register_with_read_factory()
Registers the current object as something that can be read from a Bam file.
Definition: paletteImage.cxx:972
ImageFile::set_filename
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
paletteGroup.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
parse_params
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
TextureImage::is_texture_named
bool is_texture_named() const
Returns true if this particular texture has been named by the user for procession this session,...
Definition: textureImage.cxx:257
TextureImage
This represents a single source texture that is referenced by one or more egg files.
Definition: textureImage.h:46
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39