support greater variety of maxvals when writing png

This commit is contained in:
David Rose 2013-03-06 22:09:33 +00:00
parent e1fda64ad3
commit 1f309fede5
3 changed files with 183 additions and 113 deletions

View File

@ -86,6 +86,10 @@ ConfigVariableInt jpeg_quality
"significantly better quality, but do lead to significantly greater " "significantly better quality, but do lead to significantly greater "
"size).")); "size)."));
ConfigVariableBool png_palette
("png-palette", true,
PRC_DESC("Set this true to allow writing palette-based PNG images when possible."));
ConfigVariableInt bmp_bpp ConfigVariableInt bmp_bpp
("bmp-bpp", 0, ("bmp-bpp", 0,

View File

@ -57,6 +57,8 @@ extern ConfigVariableBool tga_grayscale;
extern ConfigVariableInt jpeg_quality; extern ConfigVariableInt jpeg_quality;
extern ConfigVariableBool png_palette;
extern ConfigVariableInt bmp_bpp; extern ConfigVariableInt bmp_bpp;
enum IMGHeaderType { enum IMGHeaderType {

View File

@ -585,75 +585,82 @@ write_data(xel *array, xelval *alpha_data) {
// otherwise. // otherwise.
Palette palette; Palette palette;
HistMap palette_lookup; HistMap palette_lookup;
png_color png_palette[png_max_palette]; png_color png_palette_table[png_max_palette];
png_byte png_trans[png_max_palette]; png_byte png_trans[png_max_palette];
if (png_bit_depth <= 8) { if (png_palette) {
if (compute_palette(palette, array, alpha_data, png_max_palette)) { if (png_bit_depth <= 8) {
pnmimage_png_cat.debug() if (compute_palette(palette, array, alpha_data, png_max_palette)) {
<< palette.size() << " colors found.\n";
int palette_bit_depth = make_png_bit_depth(pm_maxvaltobits(palette.size() - 1));
int total_bits = png_bit_depth;
if (!is_grayscale()) {
total_bits *= 3;
}
if (has_alpha()) {
total_bits += png_bit_depth;
}
if (palette_bit_depth < total_bits) {
pnmimage_png_cat.debug() pnmimage_png_cat.debug()
<< "palette bit depth of " << palette_bit_depth << palette.size() << " colors found.\n";
<< " improves on bit depth of " << total_bits
<< "; making a palette image.\n";
color_type = PNG_COLOR_TYPE_PALETTE; int palette_bit_depth = make_png_bit_depth(pm_maxvaltobits(palette.size() - 1));
// Re-sort the palette to put the semitransparent pixels at the int total_bits = png_bit_depth;
// beginning. if (!is_grayscale()) {
sort(palette.begin(), palette.end(), LowAlphaCompare()); total_bits *= 3;
}
if (has_alpha()) {
total_bits += png_bit_depth;
}
int num_alpha = 0; if (palette_bit_depth < total_bits ||
for (int i = 0; i < (int)palette.size(); i++) { _maxval != (1 << true_bit_depth) - 1) {
png_palette[i].red = palette[i]._red; pnmimage_png_cat.debug()
png_palette[i].green = palette[i]._green; << "palette bit depth of " << palette_bit_depth
png_palette[i].blue = palette[i]._blue; << " improves on bit depth of " << total_bits
png_trans[i] = palette[i]._alpha; << "; making a palette image.\n";
if (palette[i]._alpha != _maxval) {
num_alpha = i + 1; color_type = PNG_COLOR_TYPE_PALETTE;
// Re-sort the palette to put the semitransparent pixels at the
// beginning.
sort(palette.begin(), palette.end(), LowAlphaCompare());
double palette_scale = 255.0 / _maxval;
int num_alpha = 0;
for (int i = 0; i < (int)palette.size(); i++) {
png_palette_table[i].red = (int)(palette[i]._red * palette_scale + 0.5);
png_palette_table[i].green = (int)(palette[i]._green * palette_scale + 0.5);
png_palette_table[i].blue = (int)(palette[i]._blue * palette_scale + 0.5);
png_trans[i] = (int)(palette[i]._alpha * palette_scale + 0.5);
if (palette[i]._alpha != _maxval) {
num_alpha = i + 1;
}
// Also build a reverse-lookup from color to palette index in
// the "histogram" structure.
palette_lookup[palette[i]] = i;
} }
// Also build a reverse-lookup from color to palette index in png_set_PLTE(_png, _info, png_palette_table, palette.size());
// the "histogram" structure. if (has_alpha()) {
palette_lookup[palette[i]] = i; pnmimage_png_cat.debug()
<< "palette contains " << num_alpha << " transparent entries.\n";
png_set_tRNS(_png, _info, png_trans, num_alpha, NULL);
}
} else {
pnmimage_png_cat.debug()
<< "palette bit depth of " << palette_bit_depth
<< " does not improve on bit depth of " << total_bits
<< "; not making a palette image.\n";
} }
png_set_PLTE(_png, _info, png_palette, palette.size());
if (has_alpha()) {
pnmimage_png_cat.debug()
<< "palette contains " << num_alpha << " transparent entries.\n";
png_set_tRNS(_png, _info, png_trans, num_alpha, NULL);
}
} else { } else {
pnmimage_png_cat.debug() pnmimage_png_cat.debug()
<< "palette bit depth of " << palette_bit_depth << "more than " << png_max_palette
<< " does not improve on bit depth of " << total_bits << " colors found; not making a palette image.\n";
<< "; not making a palette image.\n";
} }
} else { } else {
pnmimage_png_cat.debug() pnmimage_png_cat.debug()
<< "more than " << png_max_palette << "maxval exceeds 255; not making a palette image.\n";
<< " colors found; not making a palette image.\n";
} }
} else { } else {
pnmimage_png_cat.debug() pnmimage_png_cat.debug()
<< "maxval exceeds 255; not making a palette image.\n"; << "palette images are not enabled.\n";
} }
pnmimage_png_cat.debug() pnmimage_png_cat.debug()
<< "width = " << _x_size << " height = " << _y_size << "width = " << _x_size << " height = " << _y_size
<< " maxval = " << _maxval << " bit_depth = " << " maxval = " << _maxval << " bit_depth = "
@ -676,11 +683,16 @@ write_data(xel *array, xelval *alpha_data) {
png_set_packing(_png); png_set_packing(_png);
} }
if (png_bit_depth != true_bit_depth && color_type != PNG_COLOR_TYPE_PALETTE) { double val_scale = 1.0;
// This does assume that _maxval is one less than a power of 2.
// If it is not, the PNG image will be written as if it were the if (color_type != PNG_COLOR_TYPE_PALETTE) {
// next-higher power of 2, darkening the image. if (png_bit_depth != true_bit_depth) {
png_set_shift(_png, &sig_bit); png_set_shift(_png, &sig_bit);
}
// Since this assumes that _maxval is one less than a power of 2,
// we set val_scale to the appropriate factor in case it is not.
int png_maxval = (1 << png_bit_depth) - 1;
val_scale = (double)png_maxval / (double)_maxval;
} }
int row_byte_length = _x_size * _num_channels; int row_byte_length = _x_size * _num_channels;
@ -706,75 +718,127 @@ write_data(xel *array, xelval *alpha_data) {
bool save_color = !is_grayscale(); bool save_color = !is_grayscale();
bool save_alpha = has_alpha(); bool save_alpha = has_alpha();
int pi = 0; if (val_scale == 1.0) {
for (int yi = 0; yi < num_rows; yi++) { // No scale needed; we're already a power of 2.
png_bytep dest = row; int pi = 0;
for (int yi = 0; yi < num_rows; yi++) {
png_bytep dest = row;
if (color_type == PNG_COLOR_TYPE_PALETTE) { if (color_type == PNG_COLOR_TYPE_PALETTE) {
for (int xi = 0; xi < _x_size; xi++) { for (int xi = 0; xi < _x_size; xi++) {
int index; int index;
if (save_color) { if (save_color) {
if (save_alpha) { if (save_alpha) {
index = palette_lookup[PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]), alpha_data[pi])]; index = palette_lookup[PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]), alpha_data[pi])];
} else {
index = palette_lookup[PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]))];
}
} else { } else {
index = palette_lookup[PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]))]; if (save_alpha) {
index = palette_lookup[PixelSpec(PPM_GETB(array[pi]), alpha_data[pi])];
} else {
index = palette_lookup[PixelSpec(PPM_GETB(array[pi]))];
}
} }
} else {
*dest++ = index;
pi++;
}
} else if (png_bit_depth > 8) {
for (int xi = 0; xi < _x_size; xi++) {
if (save_color) {
xelval red = PPM_GETR(array[pi]);
*dest++ = (red >> 8) & 0xff;
*dest++ = red & 0xff;
xelval green = PPM_GETG(array[pi]);
*dest++ = (green >> 8) & 0xff;
*dest++ = green & 0xff;
}
xelval blue = PPM_GETB(array[pi]);
*dest++ = (blue >> 8) & 0xff;
*dest++ = blue & 0xff;
if (save_alpha) { if (save_alpha) {
index = palette_lookup[PixelSpec(PPM_GETB(array[pi]), alpha_data[pi])]; xelval alpha = alpha_data[pi];
} else { *dest++ = (alpha >> 8) & 0xff;
index = palette_lookup[PixelSpec(PPM_GETB(array[pi]))]; *dest++ = alpha & 0xff;
} }
pi++;
} }
*dest++ = index; } else {
pi++; for (int xi = 0; xi < _x_size; xi++) {
if (save_color) {
*dest++ = PPM_GETR(array[pi]);
*dest++ = PPM_GETG(array[pi]);
}
*dest++ = PPM_GETB(array[pi]);
if (save_alpha) {
*dest++ = alpha_data[pi];
}
pi++;
}
} }
} else if (png_bit_depth > 8) { nassertr(dest <= row + row_byte_length, yi);
for (int xi = 0; xi < _x_size; xi++) { png_write_row(_png, row);
if (save_color) { Thread::consider_yield();
xelval red = PPM_GETR(array[pi]);
*dest++ = (red >> 8) & 0xff;
*dest++ = red & 0xff;
xelval green = PPM_GETG(array[pi]);
*dest++ = (green >> 8) & 0xff;
*dest++ = green & 0xff;
}
xelval blue = PPM_GETB(array[pi]);
*dest++ = (blue >> 8) & 0xff;
*dest++ = blue & 0xff;
if (save_alpha) {
xelval alpha = alpha_data[pi];
*dest++ = (alpha >> 8) & 0xff;
*dest++ = alpha & 0xff;
}
pi++;
}
} else {
for (int xi = 0; xi < _x_size; xi++) {
if (save_color) {
*dest++ = PPM_GETR(array[pi]);
*dest++ = PPM_GETG(array[pi]);
}
*dest++ = PPM_GETB(array[pi]);
if (save_alpha) {
*dest++ = alpha_data[pi];
}
pi++;
}
} }
} else {
// Here we might need to scale each component to match the png
// requirement.
nassertr(color_type != PNG_COLOR_TYPE_PALETTE, 0);
int pi = 0;
for (int yi = 0; yi < num_rows; yi++) {
png_bytep dest = row;
nassertr(dest <= row + row_byte_length, yi); if (png_bit_depth > 8) {
png_write_row(_png, row); for (int xi = 0; xi < _x_size; xi++) {
Thread::consider_yield(); if (save_color) {
xelval red = (xelval)(PPM_GETR(array[pi]) * val_scale + 0.5);
*dest++ = (red >> 8) & 0xff;
*dest++ = red & 0xff;
xelval green = (xelval)(PPM_GETG(array[pi]) * val_scale + 0.5);
*dest++ = (green >> 8) & 0xff;
*dest++ = green & 0xff;
}
xelval blue = (xelval)(PPM_GETB(array[pi]) * val_scale + 0.5);
*dest++ = (blue >> 8) & 0xff;
*dest++ = blue & 0xff;
if (save_alpha) {
xelval alpha = (xelval)(alpha_data[pi] * val_scale + 0.5);
*dest++ = (alpha >> 8) & 0xff;
*dest++ = alpha & 0xff;
}
pi++;
}
} else {
for (int xi = 0; xi < _x_size; xi++) {
if (save_color) {
*dest++ = (xelval)(PPM_GETR(array[pi]) * val_scale + 0.5);
*dest++ = (xelval)(PPM_GETG(array[pi]) * val_scale + 0.5);
}
*dest++ = (xelval)(PPM_GETB(array[pi]) * val_scale + 0.5);
if (save_alpha) {
*dest++ = (xelval)(alpha_data[pi] * val_scale + 0.5);
}
pi++;
}
}
nassertr(dest <= row + row_byte_length, yi);
png_write_row(_png, row);
Thread::consider_yield();
}
} }
PANDA_FREE_ARRAY(row); PANDA_FREE_ARRAY(row);
png_write_end(_png, NULL); png_write_end(_png, NULL);