Panda3D
Loading...
Searching...
No Matches
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
32TypeHandle PaletteImage::_type_handle;
33
34/**
35 * The default constructor is only for the convenience of the bam reader.
36 */
37PaletteImage::ClearedRegion::
38ClearedRegion() {
39 _x = 0;
40 _y = 0;
41 _x_size = 0;
42 _y_size = 0;
43}
44
45/**
46 *
47 */
48PaletteImage::ClearedRegion::
49ClearedRegion(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 */
59PaletteImage::ClearedRegion::
60ClearedRegion(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 */
71void PaletteImage::ClearedRegion::
72operator = (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 */
82void PaletteImage::ClearedRegion::
83clear(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 */
104void PaletteImage::ClearedRegion::
105write_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 */
115void PaletteImage::ClearedRegion::
116fillin(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 */
131PaletteImage::
132PaletteImage() {
133 _page = nullptr;
134 _index = 0;
135 _new_image = false;
136 _got_image = false;
137
138 _swapped_image = 0;
139}
140
141/**
142 *
143 */
144PaletteImage::
145PaletteImage(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 */
163PaletteImage::
164PaletteImage(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 */
184get_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 */
194is_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 */
216count_utilization() const {
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 */
243count_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 */
272place(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 */
301unplace(TexturePlacement *placement) {
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 */
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 */
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 */
404resize_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 */
468resize_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 */
479write_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 */
492reset_image() {
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 */
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 */
528update_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.
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 &&
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 */
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 */
711bool PaletteImage::
712setup_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 */
784bool PaletteImage::
785find_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 */
823TexturePlacement *PaletteImage::
824find_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 */
841void PaletteImage::
842get_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 */
887void PaletteImage::
888get_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 */
938void PaletteImage::
939get_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 */
950void PaletteImage::
951release_image() {
952 _image.clear();
953 _got_image = false;
954}
955
956/**
957 * Deletes the image file.
958 */
959void PaletteImage::
960remove_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 */
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 */
982write_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 */
1016complete_pointers(TypedWritable **p_list, BamReader *manager) {
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 */
1041TypedWritable *PaletteImage::
1042make_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 */
1056void PaletteImage::
1057fillin(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}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition bamReader.h:110
void read_pointers(DatagramIterator &scan, int count)
A convenience function to read a contiguous list of pointers.
bool read_pointer(DatagramIterator &scan)
The interface for reading a pointer to another object from a Bam file.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition bamReader.I:177
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition bamWriter.h:63
void write_pointer(Datagram &packet, const TypedWritable *dest)
The interface for writing a pointer to another object to a Bam file.
A class to retrieve the individual data elements previously stored in a Datagram.
uint32_t get_uint32()
Extracts an unsigned 32-bit integer.
bool get_bool()
Extracts a boolean value.
std::string get_string()
Extracts a variable-length string.
int32_t get_int32()
Extracts a signed 32-bit integer.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition datagram.h:38
void add_uint32(uint32_t value)
Adds an unsigned 32-bit integer to the datagram.
Definition datagram.I:94
void add_int32(int32_t value)
Adds a signed 32-bit integer to the datagram.
Definition datagram.I:67
void add_bool(bool value)
Adds a boolean value to the datagram.
Definition datagram.I:34
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...
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:44
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,...
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
bool exists() const
Returns true if the filename exists on the physical disk, false otherwise.
bool write(const PNMImage &image) const
Writes out the image in the indicated PNMImage to the _filename and/or _alpha_filename.
bool set_filename(PaletteGroup *group, const std::string &basename)
Sets the filename, and if applicable, the alpha_filename, from the indicated basename.
void unlink()
Deletes the image file or files.
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition imageFile.cxx:82
bool exists() const
Returns true if the file or files named by the image file exist, false otherwise.
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...
bool make_shadow_image(const std::string &basename)
Sets up the ImageFile as a "shadow image" of a particular PaletteImage.
Definition imageFile.cxx:51
const Filename & get_filename() const
Returns the primary filename of the image file.
bool read(PNMImage &image) const
Reads in the image (or images, if the alpha_filename is separate) and stores it in the indicated PNMI...
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition imageFile.cxx:92
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
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
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition pnmImage.cxx:48
void alpha_fill(float alpha=0.0)
Sets the entire alpha channel to the given level.
Definition pnmImage.I:272
void fill(float red, float green, float blue)
Sets the entire image (except the alpha channel) to the given color.
Definition pnmImage.I:246
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition pnmImage.I:579
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition pnmImage.I:859
This is a single palette image, one of several within a PalettePage, which is in turn one of several ...
bool is_empty() const
Returns true if there are no textures, or only one "solitary" texture, placed on the image.
PalettePage * get_page() const
Returns the particular PalettePage this image is associated with.
void unplace(TexturePlacement *placement)
Removes the texture from the image.
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
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.
bool update_filename()
Changes the image filename to match the current naming scheme, assuming something has changed since t...
void reset_image()
Unpacks each texture that has been placed on this image, resetting the image to empty.
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.
void optimal_resize()
Attempts to resize the palette image to as small as it can go.
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,...
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.
bool place(TexturePlacement *placement)
Attempts to place the indicated texture on the image.
void setup_shadow_image()
Ensures the _shadow_image has the correct filename and image types, based on what was supplied on 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...
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.
void check_solitary()
To be called after all textures have been placed on the image, this checks to see if there is only on...
double count_coverage() const
Returns the a weighted average of the fraction of coverage represented by all of the textures placed ...
This is a particular collection of textures, within a PaletteGroup, that all share the same TexturePr...
Definition palettePage.h:33
const TextureProperties & get_properties() const
Returns the texture grouping properties that all textures in this page share.
PaletteGroup * get_group() const
Returns the group this particular PalettePage belongs to.
This is a texture image reference as it appears in an egg file: the source image of the texture.
This represents a single source texture that is referenced by one or more egg files.
SourceTextureImage * get_preferred_source()
Determines the preferred source image for examining size and reading pixels, etc.
bool is_texture_named() const
Returns true if this particular texture has been named by the user for procession this session,...
This corresponds to a particular assignment of a TextureImage with a PaletteGroup,...
void write_placed(std::ostream &out, int indent_level=0)
Writes the placement position information on a line by itself.
void place_at(PaletteImage *image, int x, int y)
Assigns the texture to a particular position within the indicated PaletteImage.
bool is_filled() const
Returns true if the texture has been filled (i.e.
OmitReason get_omit_reason() const
Returns the reason the texture has been omitted from a palette image, or OR_none if it has not.
int get_placed_x_size() const
Returns the size in the X dimension, in pixels, of the texture image as it has been placed within the...
void mark_eggs_stale()
Marks all the egg files that reference this placement stale.
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...
bool is_size_known() const
Returns true if the texture's size is known, false otherwise.
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...
PaletteImage * get_image() const
Returns the particular PaletteImage on which the texture has been placed.
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...
void mark_unfilled()
Marks the texture as unfilled, so that it will need to be copied into the palette image again.
int get_placed_x() const
Returns the X pixel at which the texture has been placed within its PaletteImage.
int get_placed_y() const
Returns the Y pixel at which the texture has been placed within its PaletteImage.
bool is_placed() const
Returns true if the texture has been placed on a palette image, false otherwise.
TextureImage * get_texture() const
Returns the texture that this placement represents.
void fill_image(PNMImage &image)
Fills in the rectangle of the palette image represented by the texture placement with the image pixel...
void omit_solitary()
Sets the omit reason (returned by get_omit()) to OR_solitary, indicating that the palettized version ...
void not_solitary()
Indicates that the texture, formerly indicated as solitary, is now no longer.
void force_replace()
Removes the texture from its particular PaletteImage, but does not remove it from the PaletteGroup.
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...
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...
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
Base class for objects that can be written to and read from Bam files.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.