34 _has_read_size =
false;
36 bool result =
read(filename, type);
39 <<
"Could not read image " << filename <<
"\n";
49 if (_array !=
nullptr) {
50 PANDA_FREE_ARRAY(_array);
53 if (_alpha !=
nullptr) {
54 PANDA_FREE_ARRAY(_alpha);
61 _inv_maxval = 1.0 / 255.0;
62 _color_space = CS_linear;
65 _has_read_size =
false;
66 _xel_encoding = XE_generic;
74 clear(
int x_size,
int y_size,
int num_channels,
75 xelval maxval,
PNMFileType *type, ColorSpace color_space) {
77 nassertv(num_channels >= 1 && num_channels <= 4);
78 nassertv(color_space != CS_unspecified);
82 _num_channels = num_channels;
84 _color_space = color_space;
87 _has_read_size =
false;
91 memset(_alpha, 0,
sizeof(xelval) * _y_size * _x_size);
95 memset(_array, 0,
sizeof(
xel) * _y_size * _x_size);
111 memcpy(_alpha, copy._alpha,
sizeof(xelval) * _y_size * _x_size);
113 memcpy(_array, copy._array,
sizeof(
xel) * _y_size * _x_size);
123 nassertv(src_channel >= 0 && src_channel <= 3);
124 nassertv(dest_channel >= 0 && dest_channel <= 3);
127 pnmimage_cat.error() <<
"One of the images is invalid!\n";
132 pnmimage_cat.error() <<
"Image size doesn't match!\n";
136 for (
int x = 0; x < _x_size; x++) {
137 for (
int y = 0; y < _y_size; y++) {
140 t.set_cell(dest_channel, o.get_cell(src_channel));
156 nassertv(src_channel >= 0 && src_channel <= 3);
157 nassertv(dest_channel >= 0 && dest_channel <= 3);
160 pnmimage_cat.error() <<
"One of the images is invalid!\n";
165 pnmimage_cat.error() <<
"Image size doesn't match!\n";
170 if (right_shift >= 0) {
171 xelval dest_mask = ~(src_mask >> right_shift);
172 for (
int x = 0; x < _x_size; x++) {
173 for (
int y = 0; y < _y_size; y++) {
176 dest = (dest & dest_mask) | ((src & src_mask) >> right_shift);
181 int left_shift = -right_shift;
182 xelval dest_mask = ~(src_mask << left_shift);
183 for (
int x = 0; x < _x_size; x++) {
184 for (
int y = 0; y < _y_size; y++) {
187 dest = (dest & dest_mask) | ((src & src_mask) << left_shift);
202 PNMImageHeader::operator = (header);
207 _inv_maxval = 1.0f / (float)_maxval;
226 PNMImageHeader::operator = (orig);
231 _alpha = orig._alpha;
232 orig._alpha =
nullptr;
234 _array = orig._array;
235 orig._array =
nullptr;
244 fill_val(xelval red, xelval green, xelval blue) {
280 if (reader ==
nullptr) {
301 read(std::istream &data,
const std::string &filename,
PNMFileType *type,
302 bool report_unknown_type) {
304 (&data,
false, filename, std::string(), type, report_unknown_type);
305 if (reader ==
nullptr) {
323 int read_x_size = _read_x_size;
324 int read_y_size = _read_y_size;
328 if (reader ==
nullptr) {
352 return pfm.
store(*
this);
357 _y_size = reader->
read_data(_array, _alpha);
368 if (
has_read_size && (_x_size != read_x_size || _y_size != read_y_size)) {
391 if (writer ==
nullptr) {
395 return write(writer);
408 write(std::ostream &data,
const std::string &filename,
PNMFileType *type)
const {
414 (&data,
false, filename, type);
415 if (writer ==
nullptr) {
419 return write(writer);
431 if (writer ==
nullptr) {
446 if (!pfm.
load(*
this)) {
464 int result = writer->
write_data(_array, _alpha);
467 return (result == _y_size);
479 nassertv((
int)color_type >= 1 && (
int)color_type <= 4);
503 if (_alpha !=
nullptr) {
504 PANDA_FREE_ARRAY(_alpha);
511 memset(_alpha, 0,
sizeof(xelval) * (_x_size * _y_size));
514 _num_channels = (int)color_type;
535 nassertv(color_space != CS_unspecified);
537 if (color_space == _color_space) {
541 if (_array !=
nullptr) {
542 size_t array_size = _x_size * _y_size;
545 switch (color_space) {
547 if (_maxval == 255 && _color_space == CS_sRGB) {
548 for (
size_t i = 0; i < array_size; ++i) {
549 xel &col = _array[i];
555 for (
int x = 0; x < _x_size; ++x) {
556 for (
int y = 0; y < _y_size; ++y) {
557 LRGBColorf scaled =
get_xel(x, y) * _maxval + 0.5f;
558 xel &col = row(y)[x];
568 if (_maxval == 255 && _color_space == CS_linear) {
569 for (
size_t i = 0; i < array_size; ++i) {
570 xel &col = _array[i];
576 for (
int x = 0; x < _x_size; ++x) {
577 for (
int y = 0; y < _y_size; ++y) {
578 xel &col = row(y)[x];
586 for (
int x = 0; x < _x_size; ++x) {
587 for (
int y = 0; y < _y_size; ++y) {
588 LRGBColorf scaled =
get_xel(x, y) * 8192.f + 4096.5f;
589 xel &col = row(y)[x];
590 col.r = min(max(0, (
int)scaled[0]), 65535);
591 col.g = min(max(0, (
int)scaled[1]), 65535);
592 col.b = min(max(0, (
int)scaled[2]), 65535);
599 nassert_raise(
"invalid color space");
604 _color_space = color_space;
647 float r =
get_red(x, y) * alpha;
672 float r =
get_red(x, y) / alpha;
686 if (_array !=
nullptr) {
687 xel *new_array = (
xel *)PANDA_MALLOC_ARRAY(_x_size * _y_size *
sizeof(
xel));
688 for (
int y = 0; y < _y_size; y++) {
689 int new_y = _y_size - 1 - y;
690 memcpy(new_array + new_y * _x_size, _array + y * _x_size, _x_size *
sizeof(
xel));
692 PANDA_FREE_ARRAY(_array);
696 if (_alpha !=
nullptr) {
697 xelval *new_alpha = (xelval *)PANDA_MALLOC_ARRAY(_x_size * _y_size *
sizeof(xelval));
698 for (
int y = 0; y < _y_size; y++) {
699 int new_y = _y_size - 1 - y;
700 memcpy(new_alpha + new_y * _x_size, _alpha + y * _x_size, _x_size *
sizeof(xelval));
702 PANDA_FREE_ARRAY(_alpha);
715 flip(
bool flip_x,
bool flip_y,
bool transpose) {
718 if (_array !=
nullptr) {
719 xel *new_array = (
xel *)PANDA_MALLOC_ARRAY(_x_size * _y_size *
sizeof(
xel));
720 for (
int xi = 0; xi < _x_size; ++xi) {
721 xel *row = new_array + xi * _y_size;
722 int source_xi = !flip_x ? xi : _x_size - 1 - xi;
723 for (
int yi = 0; yi < _y_size; ++yi) {
724 int source_yi = !flip_y ? yi : _y_size - 1 - yi;
725 xel *source_row = _array + source_yi * _x_size;
726 row[yi] = source_row[source_xi];
729 PANDA_FREE_ARRAY(_array);
733 if (_alpha !=
nullptr) {
734 xelval *new_alpha = (xelval *)PANDA_MALLOC_ARRAY(_x_size * _y_size *
sizeof(xelval));
735 for (
int xi = 0; xi < _x_size; ++xi) {
736 xelval *row = new_alpha + xi * _y_size;
737 int source_xi = !flip_x ? xi : _x_size - 1 - xi;
738 for (
int yi = 0; yi < _y_size; ++yi) {
739 int source_yi = !flip_y ? yi : _y_size - 1 - yi;
740 xelval *source_row = _alpha + source_yi * _x_size;
741 row[yi] = source_row[source_xi];
745 PANDA_FREE_ARRAY(_alpha);
755 if (_array !=
nullptr) {
756 xel *new_array = (
xel *)PANDA_MALLOC_ARRAY(_x_size * _y_size *
sizeof(
xel));
757 for (
int yi = 0; yi < _y_size; ++yi) {
758 xel *row = new_array + yi * _x_size;
759 int source_yi = !flip_y ? yi : _y_size - 1 - yi;
760 xel *source_row = _array + source_yi * _x_size;
762 for (
int xi = 0; xi < _x_size; ++xi) {
763 int source_xi = !flip_x ? xi : _x_size - 1 - xi;
764 row[xi] = source_row[source_xi];
767 PANDA_FREE_ARRAY(_array);
771 if (_alpha !=
nullptr) {
772 xelval *new_alpha = (xelval *)PANDA_MALLOC_ARRAY(_x_size * _y_size *
sizeof(xelval));
773 for (
int yi = 0; yi < _y_size; ++yi) {
774 xelval *row = new_alpha + yi * _x_size;
775 int source_yi = !flip_y ? yi : _y_size - 1 - yi;
776 xelval *source_row = _alpha + source_yi * _x_size;
778 for (
int xi = 0; xi < _x_size; ++xi) {
779 int source_xi = !flip_x ? xi : _x_size - 1 - xi;
780 row[xi] = source_row[source_xi];
784 PANDA_FREE_ARRAY(_alpha);
795 nassertv(maxval > 0);
797 if (maxval != _maxval) {
798 float ratio = (float)maxval / (
float)_maxval;
853 <<
"Invalid request for channel " << channel <<
" in "
855 nassert_raise(
"unexpected channel count");
875 if (_num_channels == 2) {
891 nassert_raise(
"unexpected channel count");
920 <<
"Invalid request for channel " << channel <<
" in "
922 nassert_raise(
"unexpected channel count");
935 set_channel(
int x,
int y,
int channel,
float value) {
942 if (_num_channels == 2) {
958 nassert_raise(
"unexpected channel count");
969 switch (_num_channels) {
992 if (_alpha !=
nullptr) {
1004 blend(
int x,
int y,
float r,
float g,
float b,
float alpha) {
1012 }
else if (alpha > 0.0f) {
1016 if (prev_alpha == 0.0f) {
1023 LRGBColorf prev_rgb =
get_xel(x, y);
1024 r = r + (1.0f - alpha) * (prev_rgb[0] - r);
1025 g = g + (1.0f - alpha) * (prev_rgb[1] - g);
1026 b = b + (1.0f - alpha) * (prev_rgb[2] - b);
1027 alpha = prev_alpha + alpha * (1.0f - prev_alpha);
1047 if (_array !=
nullptr) {
1048 PANDA_FREE_ARRAY(_array);
1063 if (_alpha !=
nullptr) {
1064 PANDA_FREE_ARRAY(_alpha);
1078 int xfrom,
int yfrom,
int x_size,
int y_size) {
1079 int xmin, ymin, xmax, ymax;
1080 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1081 xmin, ymin, xmax, ymax);
1087 for (y = ymin; y < ymax; y++) {
1088 for (x = xmin; x < xmax; x++) {
1094 for (y = ymin; y < ymax; y++) {
1095 for (x = xmin; x < xmax; x++) {
1104 for (y = ymin; y < ymax; y++) {
1105 for (x = xmin; x < xmax; x++) {
1111 for (y = ymin; y < ymax; y++) {
1112 for (x = xmin; x < xmax; x++) {
1133 int xfrom,
int yfrom,
int x_size,
int y_size,
1134 float pixel_scale) {
1135 if (!copy.
has_alpha() && pixel_scale == 1.0) {
1140 int xmin, ymin, xmax, ymax;
1141 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1142 xmin, ymin, xmax, ymax);
1146 for (y = ymin; y < ymax; y++) {
1147 for (x = xmin; x < xmax; x++) {
1148 blend(x, y, copy.
get_xel(x - xmin + xfrom, y - ymin + yfrom),
1149 copy.
get_alpha(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
1153 for (y = ymin; y < ymax; y++) {
1154 for (x = xmin; x < xmax; x++) {
1155 blend(x, y, copy.
get_xel(x - xmin + xfrom, y - ymin + yfrom),
1169 int xfrom,
int yfrom,
int x_size,
int y_size,
1170 float pixel_scale) {
1171 int xmin, ymin, xmax, ymax;
1172 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1173 xmin, ymin, xmax, ymax);
1177 for (y = ymin; y < ymax; y++) {
1178 for (x = xmin; x < xmax; x++) {
1184 for (y = ymin; y < ymax; y++) {
1185 for (x = xmin; x < xmax; x++) {
1186 LRGBColorf rgb1 =
get_xel(x, y);
1187 LRGBColorf rgb2 = copy.
get_xel(x - xmin + xfrom, y - ymin + yfrom);
1189 rgb1[0] + rgb2[0] * pixel_scale,
1190 rgb1[1] + rgb2[1] * pixel_scale,
1191 rgb1[2] + rgb2[2] * pixel_scale);
1203 int xfrom,
int yfrom,
int x_size,
int y_size,
1204 float pixel_scale) {
1205 int xmin, ymin, xmax, ymax;
1206 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1207 xmin, ymin, xmax, ymax);
1211 for (y = ymin; y < ymax; y++) {
1212 for (x = xmin; x < xmax; x++) {
1218 for (y = ymin; y < ymax; y++) {
1219 for (x = xmin; x < xmax; x++) {
1220 LRGBColorf rgb1 =
get_xel(x, y);
1221 LRGBColorf rgb2 = copy.
get_xel(x - xmin + xfrom, y - ymin + yfrom);
1223 rgb1[0] * rgb2[0] * pixel_scale,
1224 rgb1[1] * rgb2[1] * pixel_scale,
1225 rgb1[2] * rgb2[2] * pixel_scale);
1242 int xfrom,
int yfrom,
int x_size,
int y_size,
1243 float pixel_scale) {
1244 int xmin, ymin, xmax, ymax;
1245 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1246 xmin, ymin, xmax, ymax);
1252 for (y = ymin; y < ymax; y++) {
1253 for (x = xmin; x < xmax; x++) {
1257 PPM_ASSIGN(p, min(c.r, o.r), min(c.g, o.g), min(c.b, o.b));
1263 for (y = ymin; y < ymax; y++) {
1264 for (x = xmin; x < xmax; x++) {
1265 xelval c = copy.
get_alpha_val(x - xmin + xfrom, y - ymin + yfrom);
1275 for (y = ymin; y < ymax; y++) {
1276 for (x = xmin; x < xmax; x++) {
1277 LRGBColorf c = copy.
get_xel(x - xmin + xfrom, y - ymin + yfrom);
1280 p.set(min(1.0f - ((1.0f - c[0]) * pixel_scale), o[0]),
1281 min(1.0f - ((1.0f - c[1]) * pixel_scale), o[1]),
1282 min(1.0f - ((1.0f - c[2]) * pixel_scale), o[2]));
1288 for (y = ymin; y < ymax; y++) {
1289 for (x = xmin; x < xmax; x++) {
1290 float c = copy.
get_alpha(x - xmin + xfrom, y - ymin + yfrom);
1292 set_alpha(x, y, min(1.0f - ((1.0f - c) * pixel_scale), o));
1309 int xfrom,
int yfrom,
int x_size,
int y_size,
1310 float pixel_scale) {
1311 int xmin, ymin, xmax, ymax;
1312 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1313 xmin, ymin, xmax, ymax);
1319 for (y = ymin; y < ymax; y++) {
1320 for (x = xmin; x < xmax; x++) {
1324 PPM_ASSIGN(p, max(c.r, o.r), max(c.g, o.g), max(c.b, o.b));
1330 for (y = ymin; y < ymax; y++) {
1331 for (x = xmin; x < xmax; x++) {
1332 xelval c = copy.
get_alpha_val(x - xmin + xfrom, y - ymin + yfrom);
1342 for (y = ymin; y < ymax; y++) {
1343 for (x = xmin; x < xmax; x++) {
1344 LRGBColorf c = copy.
get_xel(x - xmin + xfrom, y - ymin + yfrom);
1347 p.set(max(c[0] * pixel_scale, o[0]),
1348 max(c[1] * pixel_scale, o[1]),
1349 max(c[2] * pixel_scale, o[2]));
1355 for (y = ymin; y < ymax; y++) {
1356 for (x = xmin; x < xmax; x++) {
1357 float c = copy.
get_alpha(x - xmin + xfrom, y - ymin + yfrom);
1359 set_alpha(x, y, max(c * pixel_scale, o));
1396 xelval lt_alpha = 0;
1403 (xelval)(PPM_GETR(lt_val) * scale + 0.5),
1404 (xelval)(PPM_GETG(lt_val) * scale + 0.5),
1405 (xelval)(PPM_GETB(lt_val) * scale + 0.5));
1406 lt_alpha = (xelval)(lt_alpha * scale + 0.5);
1410 xelval ge_alpha = 0;
1417 (xelval)(PPM_GETR(ge_val) * scale + 0.5),
1418 (xelval)(PPM_GETG(ge_val) * scale + 0.5),
1419 (xelval)(PPM_GETB(ge_val) * scale + 0.5));
1420 ge_alpha = (xelval)(ge_alpha * scale + 0.5);
1603 nassertv(radius <= PNM_MAXMAXVAL);
1609 for (
int yi = 0; yi < mask.
get_y_size(); ++yi) {
1610 for (
int xi = 0; xi < mask.
get_x_size(); ++xi) {
1617 if (shrink_from_border) {
1619 for (
int yi = 0; yi < mask.
get_y_size(); ++yi) {
1623 for (
int xi = 0; xi < mask.
get_x_size(); ++xi) {
1647 nassertv(radius <= PNM_MAXMAXVAL);
1653 for (
int yi = 0; yi < mask.
get_y_size(); ++yi) {
1654 for (
int xi = 0; xi < mask.
get_x_size(); ++xi) {
1687 nassertv(v >= 0 && v < pixel_values.
get_x_size());
1703 rescale(
float min_val,
float max_val) {
1704 float scale = max_val - min_val;
1706 if (_num_channels <= 2) {
1711 set_gray(x, y, (val - min_val) / scale);
1720 (
xel[0] - min_val) / scale,
1721 (
xel[1] - min_val) / scale,
1722 (
xel[2] - min_val) / scale);
1734 int xfrom,
int yfrom,
int cfrom,
1735 int x_size,
int y_size) {
1736 int xmin, ymin, xmax, ymax;
1737 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1738 xmin, ymin, xmax, ymax);
1744 for (y = ymin; y < ymax; y++) {
1745 if (cto == 3 && cfrom == 3) {
1747 for (x = xmin; x < xmax; x++) {
1750 }
else if (cto == 3 && cfrom == 0) {
1752 for (x = xmin; x < xmax; x++) {
1755 }
else if (cto == 0 && cfrom == 3) {
1757 for (x = xmin; x < xmax; x++) {
1762 for (x = xmin; x < xmax; x++) {
1771 for (y = ymin; y < ymax; y++) {
1772 for (x = xmin; x < xmax; x++) {
1790 float min_radius,
float max_radius) {
1791 if (_x_size == 0 || _y_size == 0) {
1795 float x_scale = 2.0 / _x_size;
1796 float y_scale = 2.0 / _y_size;
1800 int x_center0 = _x_size / 2;
1801 int y_center0 = _y_size / 2;
1802 int x_center1 = (_x_size + 1) / 2;
1803 int y_center1 = (_y_size + 1) / 2;
1805 float min_r2 = min_radius * min_radius;
1806 float max_r2 = max_radius * max_radius;
1808 for (
int yi = 0; yi < y_center1; ++yi) {
1809 float y = yi * y_scale;
1810 float y2_inner = y * y;
1811 float y2_outer = (y + y_scale) * (y + y_scale);
1812 for (
int xi = 0; xi < x_center1; ++xi) {
1813 float x = xi * x_scale;
1814 float d2_inner = (x * x + y2_inner);
1815 float d2_outer = ((x + x_scale) * (x + x_scale) + y2_outer);
1816 float d2_a = ((x + x_scale) * (x + x_scale) + y2_inner);
1817 float d2_b = (x * x + y2_outer);
1819 if ((d2_inner <= min_r2) &&
1820 (d2_outer <= min_r2) &&
1824 set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, fg);
1825 set_xel_a(x_center0 + xi, y_center1 - 1 - yi, fg);
1826 set_xel_a(x_center1 - 1 - xi, y_center0 + yi, fg);
1827 set_xel_a(x_center0 + xi, y_center0 + yi, fg);
1829 }
else if ((d2_inner > max_r2) &&
1830 (d2_outer > max_r2) &&
1834 set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, bg);
1835 set_xel_a(x_center0 + xi, y_center1 - 1 - yi, bg);
1836 set_xel_a(x_center1 - 1 - xi, y_center0 + yi, bg);
1837 set_xel_a(x_center0 + xi, y_center0 + yi, bg);
1841 LColorf c_outer, c_inner, c_a, c_b;
1842 compute_spot_pixel(c_outer, d2_outer, min_radius, max_radius, fg, bg);
1843 compute_spot_pixel(c_inner, d2_inner, min_radius, max_radius, fg, bg);
1844 compute_spot_pixel(c_a, d2_a, min_radius, max_radius, fg, bg);
1845 compute_spot_pixel(c_b, d2_b, min_radius, max_radius, fg, bg);
1849 c = (c_outer + c_inner + c_a + c_b) * 0.25;
1851 set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, c);
1852 set_xel_a(x_center0 + xi, y_center1 - 1 - yi, c);
1853 set_xel_a(x_center1 - 1 - xi, y_center0 + yi, c);
1854 set_xel_a(x_center0 + xi, y_center0 + yi, c);
1868 const LColorf &color) {
1873 new_image.
fill(color[0], color[1], color[2]);
1915 int num_pixels = _x_size * _y_size;
1917 compute_histogram(hist_map, _array, _alpha);
1919 pixels.reserve(hist_map.size());
1920 HistMap::const_iterator hi;
1921 for (hi = hist_map.begin(); hi != hist_map.end(); ++hi) {
1922 if ((*hi).second <= num_pixels) {
1926 std::sort(pixels.begin(), pixels.end());
1928 histogram.
swap(pixels, hist_map);
1942 nassertv(_array !=
nullptr);
1944 size_t array_size = _x_size * _y_size;
1948 for (
size_t i = 0; i < array_size; ++i) {
1949 color_map[_array[i]];
1952 size_t num_colors = color_map.size();
1953 if (num_colors <= max_colors) {
1959 xel *colors = (
xel *)alloca(num_colors *
sizeof(
xel));
1962 it != color_map.end(); ++it) {
1963 colors[i++] = it->first;
1965 nassertv(i == num_colors);
1968 r_quantize(color_map, max_colors, colors, num_colors);
1971 for (
size_t i = 0; i < array_size; ++i) {
1972 _array[i] = color_map[_array[i]];
1986 PerlinNoise2 perlin (sx * _x_size, sy * _y_size, table_size, seed);
1987 for (x = 0; x < _x_size; ++x) {
1988 for (y = 0; y < _y_size; ++y) {
1989 noise = perlin.
noise(x, y);
1990 set_xel(x, y, 0.5 * (noise + 1.0));
2003 for (x = 0; x < _x_size; ++x) {
2004 for (y = 0; y < _y_size; ++y) {
2005 noise = perlin.
noise(x / (
float) _x_size, y / (
float) _y_size);
2006 set_xel(x, y, 0.5 * (noise + 1.0));
2018 nassertv((nchannels >= 3) && (nchannels <= 4));
2022 LVector3 outv(conv.xform_point(inv));
2023 set_xel(x, y, outv[0], outv[1], outv[2]);
2035 apply_exponent(
float red_exponent,
float green_exponent,
float blue_exponent,
2036 float alpha_exponent) {
2037 int num_channels = _num_channels;
2038 if (
has_alpha() && alpha_exponent == 1.0f) {
2045 if (red_exponent == 1.0f && green_exponent == 1.0f && blue_exponent == 1.0f) {
2047 switch (num_channels) {
2054 for (y = 0; y < _y_size; ++y) {
2055 for (x = 0; x < _x_size; ++x) {
2057 alpha = cpow(alpha, blue_exponent);
2067 switch (num_channels) {
2069 for (y = 0; y < _y_size; ++y) {
2070 for (x = 0; x < _x_size; ++x) {
2072 gray = cpow(gray, blue_exponent);
2079 for (y = 0; y < _y_size; ++y) {
2080 for (x = 0; x < _x_size; ++x) {
2082 gray = cpow(gray, blue_exponent);
2086 alpha = cpow(alpha, blue_exponent);
2093 for (y = 0; y < _y_size; ++y) {
2094 for (x = 0; x < _x_size; ++x) {
2095 LRGBColorf color =
get_xel(x, y);
2096 color[0] = cpow(color[0], red_exponent);
2097 color[1] = cpow(color[1], green_exponent);
2098 color[2] = cpow(color[2], blue_exponent);
2105 for (y = 0; y < _y_size; ++y) {
2106 for (x = 0; x < _x_size; ++x) {
2108 color[0] = cpow(color[0], red_exponent);
2109 color[1] = cpow(color[1], green_exponent);
2110 color[2] = cpow(color[2], blue_exponent);
2111 color[3] = cpow(color[3], alpha_exponent);
2132 _default_rc = lumin_red;
2133 _default_gc = lumin_grn;
2134 _default_bc = lumin_blu;
2149 _inv_maxval = 1.0f / (float)_maxval;
2153 switch (_color_space) {
2155 _xel_encoding = XE_generic_alpha;
2160 if (has_sse2_sRGB_encode()) {
2161 _xel_encoding = XE_uchar_sRGB_alpha_sse2;
2163 _xel_encoding = XE_uchar_sRGB_alpha;
2166 _xel_encoding = XE_generic_sRGB_alpha;
2171 _xel_encoding = XE_scRGB_alpha;
2176 nassert_raise(
"invalid color space");
2180 switch (_color_space) {
2182 _xel_encoding = XE_generic;
2187 if (has_sse2_sRGB_encode()) {
2188 _xel_encoding = XE_uchar_sRGB_sse2;
2190 _xel_encoding = XE_uchar_sRGB;
2193 _xel_encoding = XE_generic_sRGB;
2198 _xel_encoding = XE_scRGB;
2203 nassert_raise(
"invalid color space");
2214 xel *colors,
size_t num_colors) {
2215 if (num_colors <= max_colors) {
2217 for (
size_t i = 0; i < num_colors; ++i) {
2218 const xel &col = colors[i];
2219 color_map[col] = col;
2223 else if (max_colors == 1) {
2226 for (
size_t i = 0; i < num_colors; ++i) {
2229 avg *= 1.0f / num_colors;
2233 for (
size_t i = 0; i < num_colors; ++i) {
2234 color_map[colors[i]] = avg_val;
2238 else if (max_colors == 0) {
2245 xelval min_r = _maxval;
2246 xelval min_g = _maxval;
2247 xelval min_b = _maxval;
2248 xelval max_r = 0, max_g = 0, max_b = 0;
2249 for (
size_t i = 0; i < num_colors; ++i) {
2250 const xel &col = colors[i];
2251 min_r = std::min(min_r, col.r);
2252 max_r = std::max(max_r, col.r);
2253 min_g = std::min(min_g, col.g);
2254 max_g = std::max(max_g, col.g);
2255 min_b = std::min(min_b, col.b);
2256 max_b = std::max(max_b, col.b);
2259 int diff_r = max_r - min_r;
2260 int diff_g = max_g - min_g;
2261 int diff_b = max_b - min_b;
2263 auto sort_by_red = [](
const xel &c1,
const xel &c2) {
2266 auto sort_by_green = [](
const xel &c1,
const xel &c2) {
2269 auto sort_by_blue = [](
const xel &c1,
const xel &c2) {
2274 if (diff_g >= diff_r) {
2275 if (diff_g >= diff_b) {
2276 std::sort(colors, colors + num_colors, sort_by_green);
2278 std::sort(colors, colors + num_colors, sort_by_blue);
2280 }
else if (diff_r >= diff_b) {
2281 std::sort(colors, colors + num_colors, sort_by_red);
2283 std::sort(colors, colors + num_colors, sort_by_blue);
2287 size_t max_colors_1 = max_colors / 2;
2288 size_t max_colors_2 = max_colors - max_colors_1;
2289 size_t num_colors_1 = num_colors / 2;
2290 size_t num_colors_2 = num_colors - num_colors_1;
2291 r_quantize(color_map, max_colors_1, colors, num_colors_1);
2292 r_quantize(color_map, max_colors_2, colors + num_colors_1, num_colors_2);
2321 LRGBColorf color (LRGBColorf::zero());
2322 if (_x_size == 0 || _y_size == 0) {
2326 float factor = 1.0f / (float)(_x_size * _y_size);
2329 for (x = 0; x < _x_size; ++x) {
2330 for (y = 0; y < _y_size; ++y) {
2331 color +=
get_xel(x, y) * factor;
2344 LColorf color (LColorf::zero());
2345 if (_x_size == 0 || _y_size == 0) {
2349 float factor = 1.0f / (float)(_x_size * _y_size);
2352 for (x = 0; x < _x_size; ++x) {
2353 for (y = 0; y < _y_size; ++y) {
2367 if (_x_size == 0 || _y_size == 0) {
2372 for (x = 0; x < _x_size; ++x) {
2373 for (y = 0; y < _y_size; ++y) {
2378 gray /= (float)(_x_size * _y_size);
2389 size_t array_size = _x_size * _y_size;
2391 if (_array !=
nullptr && _alpha !=
nullptr) {
2392 for (
size_t i = 0; i < array_size; ++i) {
2393 target._array[i].r = _maxval - _array[i].r;
2394 target._array[i].g = _maxval - _array[i].g;
2395 target._array[i].b = _maxval - _array[i].b;
2396 target._alpha[i] = _maxval - _alpha[i];
2398 }
else if (_array !=
nullptr) {
2399 for (
size_t i = 0; i < array_size; ++i) {
2400 target._array[i].r = _maxval - _array[i].r;
2401 target._array[i].g = _maxval - _array[i].g;
2402 target._array[i].b = _maxval - _array[i].b;
2404 }
else if (_alpha !=
nullptr) {
2405 for (
size_t i = 0; i < array_size; ++i) {
2406 target._alpha[i] = _maxval - _alpha[i];
2419 nassertv(_x_size == other._x_size && _y_size == other._y_size);
2424 size_t array_size = _x_size * _y_size;
2427 if (_alpha !=
nullptr && other._alpha !=
nullptr) {
2428 for (
size_t i = 0; i < array_size; ++i) {
2429 _array[i].r =
clamp_val((
int)_array[i].r + (
int)other._array[i].r);
2430 _array[i].g =
clamp_val((
int)_array[i].g + (
int)other._array[i].g);
2431 _array[i].b =
clamp_val((
int)_array[i].b + (
int)other._array[i].b);
2432 _alpha[i] =
clamp_val((
int)_alpha[i] + (
int)other._alpha[i]);
2435 for (
size_t i = 0; i < array_size; ++i) {
2436 _array[i].r =
clamp_val((
int)_array[i].r + (
int)other._array[i].r);
2437 _array[i].g =
clamp_val((
int)_array[i].g + (
int)other._array[i].g);
2438 _array[i].b =
clamp_val((
int)_array[i].b + (
int)other._array[i].b);
2444 for (x = 0; x < _x_size; ++x) {
2445 for (y = 0; y < _y_size; ++y) {
2460 size_t array_size = _x_size * _y_size;
2463 int add_r = (int)(other.get_x() *
get_maxval() + 0.5);
2464 int add_g = (int)(other.get_y() *
get_maxval() + 0.5);
2465 int add_b = (int)(other.get_z() *
get_maxval() + 0.5);
2466 int add_a = (int)(other.get_w() *
get_maxval() + 0.5);
2468 if (_alpha !=
nullptr) {
2469 for (
size_t i = 0; i < array_size; ++i) {
2470 _array[i].r =
clamp_val((
int)_array[i].r + add_r);
2471 _array[i].g =
clamp_val((
int)_array[i].g + add_g);
2472 _array[i].b =
clamp_val((
int)_array[i].b + add_b);
2473 _alpha[i] =
clamp_val((
int)_alpha[i] + add_a);
2477 for (
size_t i = 0; i < array_size; ++i) {
2478 _array[i].r =
clamp_val((
int)_array[i].r + add_r);
2479 _array[i].g =
clamp_val((
int)_array[i].g + add_g);
2480 _array[i].b =
clamp_val((
int)_array[i].b + add_b);
2486 for (x = 0; x < _x_size; ++x) {
2487 for (y = 0; y < _y_size; ++y) {
2501 nassertv(_x_size == other._x_size && _y_size == other._y_size);
2506 size_t array_size = _x_size * _y_size;
2509 if (_alpha !=
nullptr && other._alpha !=
nullptr) {
2510 for (
size_t i = 0; i < array_size; ++i) {
2511 _array[i].r =
clamp_val((
int)_array[i].r - (
int)other._array[i].r);
2512 _array[i].g =
clamp_val((
int)_array[i].g - (
int)other._array[i].g);
2513 _array[i].b =
clamp_val((
int)_array[i].b - (
int)other._array[i].b);
2514 _alpha[i] =
clamp_val((
int)_alpha[i] - (
int)other._alpha[i]);
2517 for (
size_t i = 0; i < array_size; ++i) {
2518 _array[i].r =
clamp_val((
int)_array[i].r - (
int)other._array[i].r);
2519 _array[i].g =
clamp_val((
int)_array[i].g - (
int)other._array[i].g);
2520 _array[i].b =
clamp_val((
int)_array[i].b - (
int)other._array[i].b);
2526 for (x = 0; x < _x_size; ++x) {
2527 for (y = 0; y < _y_size; ++y) {
2551 nassertv(_x_size == other._x_size && _y_size == other._y_size);
2554 for (x = 0; x < _x_size; ++x) {
2555 for (y = 0; y < _y_size; ++y) {
2570 size_t array_size = _x_size * _y_size;
2572 if (_alpha !=
nullptr) {
2573 for (
size_t i = 0; i < array_size; ++i) {
2574 _array[i].r =
clamp_val((
int)(_array[i].r * multiplier + 0.5f));
2575 _array[i].g =
clamp_val((
int)(_array[i].g * multiplier + 0.5f));
2576 _array[i].b =
clamp_val((
int)(_array[i].b * multiplier + 0.5f));
2577 _alpha[i] =
clamp_val((
int)(_alpha[i] * multiplier + 0.5f));
2581 for (
size_t i = 0; i < array_size; ++i) {
2582 _array[i].r =
clamp_val((
int)(_array[i].r * multiplier + 0.5f));
2583 _array[i].g =
clamp_val((
int)(_array[i].g * multiplier + 0.5f));
2584 _array[i].b =
clamp_val((
int)(_array[i].b * multiplier + 0.5f));
2590 for (x = 0; x < _x_size; ++x) {
2591 for (y = 0; y < _y_size; ++y) {
2607 size_t array_size = _x_size * _y_size;
2609 if (_alpha !=
nullptr) {
2610 for (
size_t i = 0; i < array_size; ++i) {
2611 _array[i].r =
clamp_val((
int)(_array[i].r * other[0] + 0.5f));
2612 _array[i].g =
clamp_val((
int)(_array[i].g * other[1] + 0.5f));
2613 _array[i].b =
clamp_val((
int)(_array[i].b * other[2] + 0.5f));
2614 _alpha[i] =
clamp_val((
int)(_alpha[i] * other[3] + 0.5f));
2618 for (
size_t i = 0; i < array_size; ++i) {
2619 _array[i].r =
clamp_val((
int)(_array[i].r * other[0] + 0.5f));
2620 _array[i].g =
clamp_val((
int)(_array[i].g * other[1] + 0.5f));
2621 _array[i].b =
clamp_val((
int)(_array[i].b * other[2] + 0.5f));
2627 for (x = 0; x < _x_size; ++x) {
2628 for (y = 0; y < _y_size; ++y) {
2630 color.componentwise_mult(other);
The name of a file, such as a texture file or an Egg file.
This is the base class of a family of classes that represent particular image file types that PNMImag...
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
xel & get_xel_val(int x, int y)
Returns the RGB color at the indicated pixel.
void set_xel_val(int x, int y, const xel &value)
Changes the RGB color at the indicated pixel.
void set_maxval(xelval maxval)
Rescales the image to the indicated maxval.
xelval get_channel_val(int x, int y, int channel) const
Returns the nth component color at the indicated pixel.
void indirect_1d_lookup(const PNMImage &index_image, int channel, const PNMImage &pixel_values)
index_image is a WxH grayscale image, while pixel_values is an Nx1 color (or grayscale) image.
void copy_sub_image(const PNMImage ©, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1)
Copies a rectangular area of another image into a rectangular area of this image.
void render_spot(const LColorf &fg, const LColorf &bg, float min_radius, float max_radius)
Renders a solid-color circle, with a fuzzy edge, into the center of the PNMImage.
void flip(bool flip_x, bool flip_y, bool transpose)
Reverses, transposes, and/or rotates the image in-place according to the specified parameters.
void set_blue(int x, int y, float b)
Sets the blue component color only at the indicated pixel.
void set_blue_val(int x, int y, xelval b)
Sets the blue component color only at the indicated pixel.
void premultiply_alpha()
Converts an image in-place to its "premultiplied" form, where, for every pixel in the image,...
void operator-=(const PNMImage &other)
Subtracts each pixel from the right image from each pixel value in this image.
void fill_val(xelval red, xelval green, xelval blue)
Sets the entire image (except the alpha channel) to the given color.
void operator*=(const PNMImage &other)
Multiples each pixel in this image by each pixel value from the right image.
void alpha_fill(float alpha=0.0)
Sets the entire alpha channel to the given level.
void set_alpha_val(int x, int y, xelval a)
Sets the alpha component color only at the indicated pixel.
xelval get_green_val(int x, int y) const
Returns the green component color at the indicated pixel.
float get_average_gray() const
Returns the average grayscale component of all of the pixels in the image.
void make_grayscale()
Converts the image from RGB to grayscale.
void blend_sub_image(const PNMImage ©, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1, float pixel_scale=1.0)
Behaves like copy_sub_image(), except the alpha channel of the copy is used to blend the copy into th...
void set_green(int x, int y, float g)
Sets the green component color only at the indicated pixel.
float get_blue(int x, int y) const
Returns the blue component color at the indicated pixel.
float get_alpha(int x, int y) const
Returns the alpha component color at the indicated pixel.
LRGBColorf get_xel(int x, int y) const
Returns the RGB color at the indicated pixel.
void set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
void expand_border(int left, int right, int bottom, int top, const LColorf &color)
Expands the image by the indicated number of pixels on each edge.
float get_channel(int x, int y, int channel) const
Returns the nth component color at the indicated pixel.
LRGBColorf from_val(const xel &input_value) const
A handy function to scale non-alpha values from [0..get_maxval()] to [0..1].
void set_color_space(ColorSpace color_space)
Converts the colors in the image to the indicated color space.
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
void threshold(const PNMImage &select_image, int channel, float threshold, const PNMImage <, const PNMImage &ge)
Selectively copies each pixel from either one source or another source, depending on the pixel value ...
void quick_filter_from(const PNMImage ©, int xborder=0, int yborder=0)
Resizes from the given image, with a fixed radius of 0.5.
void lighten_sub_image(const PNMImage ©, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1, float pixel_scale=1.0)
Behaves like copy_sub_image(), but the resulting color will be the lighter of the source and destinat...
xelval clamp_val(int input_value) const
A handy function to clamp values to [0..get_maxval()].
void fill(float red, float green, float blue)
Sets the entire image (except the alpha channel) to the given color.
void blend(int x, int y, const LRGBColorf &val, float alpha)
Smoothly blends the indicated pixel value in with whatever was already in the image,...
void set_array(xel *array)
Replaces the underlying PNMImage array with the indicated pointer.
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
LColorf get_average_xel_a() const
Returns the average color of all of the pixels in the image, including the alpha channel.
void set_pixel(int x, int y, const PixelSpec &pixel)
Sets the (r, g, b, a) pixel value at the indicated pixel, using a PixelSpec object.
void set_gray(int x, int y, float gray)
Sets the gray component color at the indicated pixel.
void set_color_type(ColorType color_type)
Translates the image to or from grayscale, color, or four-color mode.
void copy_channel_bits(const PNMImage ©, int src_channel, int dest_channel, xelval src_mask, int right_shift)
Copies some subset of the bits of the specified channel from one image into some subset of the bits o...
void set_gray_val(int x, int y, xelval gray)
Sets the gray component color at the indicated pixel.
xelval get_alpha_val(int x, int y) const
Returns the alpha component color at the indicated pixel.
void set_red(int x, int y, float r)
Sets the red component color only at the indicated pixel.
void copy_header_from(const PNMImageHeader &header)
Copies just the header information into this image.
PNMImage operator~() const
Returns a new PNMImage that is the complement of this PNMImage.
void take_from(PNMImage &orig)
Move the contents of the other image into this one, and empty the other image.
void reverse_rows()
Performs an in-place reversal of the row (y) data.
float get_bright(int x, int y) const
Returns the linear brightness of the given xel, as a linearized float in the range 0....
bool has_read_size() const
Returns true if set_read_size() has been called.
void quantize(size_t max_colors)
Reduces the number of unique colors in the image to (at most) the given count.
void fill_distance_inside(const PNMImage &mask, float threshold, int radius, bool shrink_from_border)
Replaces this image with a grayscale image whose gray channel represents the linear Manhattan distanc...
void add_sub_image(const PNMImage ©, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1, float pixel_scale=1.0)
Behaves like copy_sub_image(), except the copy pixels are added to the pixels of the destination,...
void set_channel_val(int x, int y, int channel, xelval value)
Sets the nth component color at the indicated pixel.
void set_green_val(int x, int y, xelval g)
Sets the green component color only at the indicated pixel.
void unfiltered_stretch_from(const PNMImage ©)
Resizes from the indicated image into this one by performing a nearest- point sample.
bool is_valid() const
Returns true if the image has been read in or correctly initialized with a height and width.
void remix_channels(const LMatrix4 &conv)
Transforms every pixel using the operation (Ro,Go,Bo) = conv.xform_point(Ri,Gi,Bi); Input must be a c...
void copy_from(const PNMImage ©)
Makes this image become a copy of the other image.
void make_histogram(Histogram &hist)
Computes a histogram of the colors used in the image.
void set_alpha_array(xelval *alpha)
Replaces the underlying PNMImage alpha array with the indicated pointer.
void operator+=(const PNMImage &other)
Sets each pixel value to the sum of the corresponding pixel values in the two given images.
xelval get_blue_val(int x, int y) const
Returns the blue component color at the indicated pixel.
float get_green(int x, int y) const
Returns the green component color at the indicated pixel.
void unpremultiply_alpha()
Converts an image in-place to its "straight alpha" form (presumably from a "premultiplied" form),...
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
LRGBColorf get_average_xel() const
Returns the average color of all of the pixels in the image.
void mult_sub_image(const PNMImage ©, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1, float pixel_scale=1.0)
Behaves like copy_sub_image(), except the copy pixels are multiplied to the pixels of the destination...
void fill_distance_outside(const PNMImage &mask, float threshold, int radius)
Replaces this image with a grayscale image whose gray channel represents the linear Manhattan distanc...
xelval get_red_val(int x, int y) const
Returns the red component color at the indicated pixel.
void rescale(float min_val, float max_val)
Rescales the RGB channel values so that any values in the original image between min_val and max_val ...
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
void set_red_val(int x, int y, xelval r)
Sets the red component color only at the indicated pixel.
void set_channel(int x, int y, int channel, float value)
Sets the nth component color at the indicated pixel.
xel to_val(const LRGBColorf &input_value) const
A handy function to scale non-alpha values from [0..1] to [0..get_maxval()].
void do_fill_distance(int xi, int yi, int d)
Recursively fills in the minimum distance measured from a certain set of points into the gray channel...
PixelSpec get_pixel(int x, int y) const
Returns the (r, g, b, a) pixel value at the indicated pixel, using a PixelSpec object.
xelval get_gray_val(int x, int y) const
Returns the gray component color at the indicated pixel.
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
ColorSpace get_color_space() const
Returns the color space in which the image is encoded.
void apply_exponent(float gray_exponent)
Adjusts each channel of the image by raising the corresponding component value to the indicated expon...
void add_alpha()
Adds an alpha channel to the image, if it does not already have one.
void darken_sub_image(const PNMImage ©, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1, float pixel_scale=1.0)
Behaves like copy_sub_image(), but the resulting color will be the darker of the source and destinati...
void perlin_noise_fill(float sx, float sy, int table_size=256, unsigned long seed=0)
Fills the image with a grayscale perlin noise pattern based on the indicated parameters.
bool write(const Filename &filename, PNMFileType *type=nullptr) const
Writes the image to the indicated filename.
void alpha_fill_val(xelval alpha=0)
Sets the entire alpha channel to the given level.
void copy_channel(const PNMImage ©, int src_channel, int dest_channel)
Copies a channel from one image into another.
float get_red(int x, int y) const
Returns the red component color at the indicated pixel.
This is an abstract base class that defines the interface for reading image files of various types.
virtual int read_data(xel *array, xelval *alpha)
Reads in an entire image all at once, storing it in the pre-allocated _x_size * _y_size array and alp...
virtual bool is_floating_point()
Returns true if this PNMFileType represents a floating-point image type, false if it is a normal,...
virtual void prepare_read()
This method will be called before read_data() or read_row() is called.
bool is_valid() const
Returns true if the PNMReader can be used to read data, false if something is wrong.
void set_read_size(int x_size, int y_size)
Instructs the reader to attempt to scale the image to the indicated size while reading it.
virtual bool read_pfm(PfmFile &pfm)
Reads floating-point data directly into the indicated PfmFile.
This is an abstract base class that defines the interface for writing image files of various types.
void copy_header_from(const PNMImageHeader &header)
Initializes all the data in the header (x_size, y_size, num_channels, etc.) to the same values indica...
virtual int write_data(xel *array, xelval *alpha)
Writes out an entire image all at once, including the header, based on the image data stored in the g...
virtual bool write_pfm(const PfmFile &pfm)
Writes floating-point data from the indicated PfmFile.
virtual bool supports_grayscale() const
Returns true if this particular PNMWriter understands grayscale images.
virtual bool supports_integer()
Returns true if this PNMFileType can accept an integer image type, false if it can only accept a floa...
This class provides an implementation of Perlin noise for 2 variables.
double noise(double x, double y) const
Returns the noise function of the three inputs.
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
bool store(PNMImage &pnmimage) const
Copies the data to the indicated PNMImage, converting to RGB values.
bool load(const PNMImage &pnmimage)
Fills the PfmFile with the data from the indicated PNMImage, converted to floating-point values.
Implements a multi-layer PerlinNoise, with one or more high-frequency noise functions added to a lowe...
double noise(double x, double y)
Returns the noise function of the three inputs.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EXPCL_PANDA_PNMIMAGE unsigned char encode_sRGB_uchar(unsigned char val)
Encodes the linearized unsigned char value to an sRGB-encoded unsigned char value.
EXPCL_PANDA_PNMIMAGE unsigned char decode_sRGB_uchar(unsigned char val)
Decodes the sRGB-encoded unsigned char value to a linearized unsigned char value.
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.