support writing png images

This commit is contained in:
David Rose 2004-03-19 01:50:00 +00:00
parent d49685c9a9
commit a2772d745a
2 changed files with 438 additions and 9 deletions

View File

@ -24,7 +24,6 @@
#include "pnmFileTypeRegistry.h"
#include "bamReader.h"
#include "ppmcmap.h"
static const char * const extensions_png[] = {
"png"
@ -33,6 +32,21 @@ static const int num_extensions_png = sizeof(extensions_png) / sizeof(const char
TypeHandle PNMFileTypePNG::_type_handle;
static const int png_max_palette = 256;
// This STL comparison functor is used in write_data(), below. It
// sorts the non-maxval alpha pixels to the front of the list.
class LowAlphaCompare {
public:
bool operator() (const PNMImageHeader::PixelSpec &a,
const PNMImageHeader::PixelSpec &b) {
if (a._alpha != b._alpha) {
return a._alpha < b._alpha;
}
return a < b;
}
};
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Constructor
// Access: Public
@ -126,6 +140,7 @@ matches_magic_number(const string &magic_number) const {
////////////////////////////////////////////////////////////////////
PNMReader *PNMFileTypePNG::
make_reader(istream *file, bool owns_file, const string &magic_number) {
init_pnm();
return new Reader(this, file, owns_file, magic_number);
}
@ -138,8 +153,8 @@ make_reader(istream *file, bool owns_file, const string &magic_number) {
////////////////////////////////////////////////////////////////////
PNMWriter *PNMFileTypePNG::
make_writer(ostream *file, bool owns_file) {
// return new Writer(this, file, owns_file);
return NULL;
init_pnm();
return new Writer(this, file, owns_file);
}
////////////////////////////////////////////////////////////////////
@ -358,18 +373,18 @@ read_data(xel *array, xelval *alpha_data) {
if (_maxval > 255) {
if (get_color) {
red = (source[0] << 16) | source[1];
red = (source[0] << 8) | source[1];
source += 2;
green = (source[0] << 16) | source[1];
green = (source[0] << 8) | source[1];
source += 2;
}
blue = (source[0] << 16) | source[1];
blue = (source[0] << 8) | source[1];
source += 2;
if (get_alpha) {
alpha = (source[0] << 16) | source[1];
alpha = (source[0] << 8) | source[1];
source += 2;
}
@ -477,5 +492,407 @@ png_error(png_structp png_ptr, png_const_charp error_msg) {
longjmp(self->_jmpbuf, true);
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
PNMFileTypePNG::Writer::
Writer(PNMFileType *type, ostream *file, bool owns_file) :
PNMWriter(type, file, owns_file)
{
_png = NULL;
_info = NULL;
_is_valid = false;
_png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
png_error, png_warning);
if (_png == NULL) {
return;
}
_info = png_create_info_struct(_png);
if (_info == NULL) {
png_destroy_write_struct(&_png, NULL);
return;
}
_is_valid = true;
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
PNMFileTypePNG::Writer::
~Writer() {
free_png();
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::write_data
// Access: Public, Virtual
// Description: Writes in an entire image all at once, storing it in
// the pre-allocated _x_size * _y_size array and alpha
// pointers. (If the image type has no alpha channel,
// alpha is ignored.) Returns the number of rows
// correctly write.
//
// Derived classes need not override this if they
// instead provide supports_write_row() and write_row(),
// below.
////////////////////////////////////////////////////////////////////
int PNMFileTypePNG::Writer::
write_data(xel *array, xelval *alpha_data) {
if (!is_valid()) {
return 0;
}
if (setjmp(_jmpbuf)) {
// This is the ANSI C way to handle exceptions. If setjmp(),
// above, returns true, it means that libpng detected an exception
// while executing the code that writes the image, below.
free_png();
return 0;
}
png_set_write_fn(_png, (void *)this, png_write_data, png_flush_data);
// First, write the header.
int true_bit_depth = pm_maxvaltobits(_maxval);
int png_bit_depth = make_png_bit_depth(true_bit_depth);
png_color_8 sig_bit;
sig_bit.red = true_bit_depth;
sig_bit.green = true_bit_depth;
sig_bit.blue = true_bit_depth;
sig_bit.gray = true_bit_depth;
sig_bit.alpha = true_bit_depth;
int color_type = 0;
if (!is_grayscale()) {
color_type |= PNG_COLOR_MASK_COLOR;
}
if (has_alpha()) {
color_type |= PNG_COLOR_MASK_ALPHA;
}
// Determine if we should make a palettized image out of this. In
// order for this to be possible and effective, we must have no more
// than 256 unique color/alpha combinations for a color image, and
// the resulting bitdepth should be smaller than what we would have
// otherwise.
Palette palette;
Histogram palette_lookup;
png_color png_palette[png_max_palette];
png_byte png_trans[png_max_palette];
if (png_bit_depth <= 8) {
if (compute_palette(palette, array, alpha_data, png_max_palette)) {
pnmimage_png_cat.debug()
<< 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()
<< "palette bit depth of " << palette_bit_depth
<< " improves on bit depth of " << total_bits
<< "; making a palette image.\n";
color_type = PNG_COLOR_TYPE_PALETTE;
// Re-sort the palette to put the semitransparent pixels at the
// beginning.
sort(palette.begin(), palette.end(), LowAlphaCompare());
int num_alpha = 0;
for (int i = 0; i < (int)palette.size(); i++) {
png_palette[i].red = palette[i]._red;
png_palette[i].green = palette[i]._green;
png_palette[i].blue = palette[i]._blue;
png_trans[i] = palette[i]._alpha;
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;
}
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 {
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";
}
} else {
pnmimage_png_cat.debug()
<< "more than " << png_max_palette
<< " colors found; not making a palette image.\n";
}
} else {
pnmimage_png_cat.debug()
<< "maxval exceeds 255; not making a palette image.\n";
}
pnmimage_png_cat.debug()
<< "width = " << _x_size << " height = " << _y_size
<< " maxval = " << _maxval << " bit_depth = "
<< png_bit_depth << " color_type = " << color_type << "\n";
png_set_IHDR(_png, _info, _x_size, _y_size, png_bit_depth,
color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
// Set the true bit depth of the image data.
if (png_bit_depth != true_bit_depth || color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_sBIT(_png, _info, &sig_bit);
}
png_write_info(_png, _info);
// Now set up the transformations to write the image data.
if (png_bit_depth < 8) {
png_set_packing(_png);
}
if (png_bit_depth != true_bit_depth && color_type != PNG_COLOR_TYPE_PALETTE) {
// 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
// next-higher power of 2, darkening the image.
png_set_shift(_png, &sig_bit);
}
int row_byte_length = _x_size * _num_channels;
if (png_bit_depth > 8) {
row_byte_length *= 2;
}
int num_rows = _y_size;
if (pnmimage_png_cat.is_debug()) {
pnmimage_png_cat.debug()
<< "Allocating one row of " << row_byte_length
<< " bytes.\n";
}
// When writing, we only need to copy the image out one row at a
// time, because we don't mess around with writing interlaced files.
// If we were writing an interlaced file, we'd have to copy the
// whole image first.
png_bytep row = new png_byte[row_byte_length];
bool save_color = !is_grayscale();
bool save_alpha = has_alpha();
int pi = 0;
for (int yi = 0; yi < num_rows; yi++) {
png_bytep dest = row;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
for (int xi = 0; xi < _x_size; xi++) {
int index;
if (save_color) {
if (save_alpha) {
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 {
if (save_alpha) {
index = palette_lookup[PixelSpec(PPM_GETB(array[pi]), alpha_data[pi])];
} else {
index = palette_lookup[PixelSpec(PPM_GETB(array[pi]))];
}
}
*dest++ = index;
pi++;
}
} else if (png_bit_depth > 255) {
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) {
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++;
}
}
nassertr(dest <= row + row_byte_length, yi);
png_write_row(_png, row);
}
delete[] row;
png_write_end(_png, NULL);
return _y_size;
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::free_png
// Access: Private
// Description: Releases the internal PNG structures and marks the
// writer invalid.
////////////////////////////////////////////////////////////////////
void PNMFileTypePNG::Writer::
free_png() {
if (_is_valid) {
png_destroy_write_struct(&_png, &_info);
_is_valid = false;
}
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::make_png_bit_depth
// Access: Private, Static
// Description: Elevates the indicated bit depth to one of the legal
// PNG bit depths: 1, 2, 4, 8, or 16.
////////////////////////////////////////////////////////////////////
int PNMFileTypePNG::Writer::
make_png_bit_depth(int bit_depth) {
switch (bit_depth) {
case 0:
case 1:
return 1;
case 2:
return 2;
case 3:
case 4:
return 4;
case 5:
case 6:
case 7:
case 8:
return 8;
default:
return 16;
}
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::png_write_data
// Access: Private, Static
// Description: A callback handler that PNG uses to write data from
// the iostream.
////////////////////////////////////////////////////////////////////
void PNMFileTypePNG::Writer::
png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
Writer *self = (Writer *)png_get_io_ptr(png_ptr);
self->_file->write((char *)data, length);
if (self->_file->fail()) {
pnmimage_png_cat.error()
<< "Unable to write to the iostream.\n";
// Is there no way to indicate a write failure to libpng?
}
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::png_flush_data
// Access: Private, Static
// Description: A callback handler that PNG uses to write data from
// the iostream.
////////////////////////////////////////////////////////////////////
void PNMFileTypePNG::Writer::
png_flush_data(png_structp png_ptr) {
Writer *self = (Writer *)png_get_io_ptr(png_ptr);
self->_file->flush();
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::png_warning
// Access: Private, Static
// Description: This is our own warning handler. It is called by the
// png library to issue a warning message.
////////////////////////////////////////////////////////////////////
void PNMFileTypePNG::Writer::
png_warning(png_structp, png_const_charp warning_msg) {
pnmimage_png_cat.warning()
<< warning_msg << "\n";
}
////////////////////////////////////////////////////////////////////
// Function: PNMFileTypePNG::Writer::png_error
// Access: Private, Static
// Description: This is our own error handler. It is called by the
// png library to issue a fatal error message.
////////////////////////////////////////////////////////////////////
void PNMFileTypePNG::Writer::
png_error(png_structp png_ptr, png_const_charp error_msg) {
pnmimage_png_cat.error()
<< error_msg << "\n";
// The PNG library insists we should not return, so instead of
// returning, we will do a longjmp out of the png code.
Writer *self = (Writer *)png_get_io_ptr(png_ptr);
if (self == (Writer *)NULL) {
// Oops, we haven't got a self pointer yet. Return anyway and
// hope we'll be ok.
pnmimage_png_cat.error()
<< "Returning before opening file.\n";
return;
}
longjmp(self->_jmpbuf, true);
}
#endif // HAVE_PNG

View File

@ -75,19 +75,31 @@ public:
jmp_buf _jmpbuf;
};
/*
class Writer : public PNMWriter {
public:
Writer(PNMFileType *type, ostream *file, bool owns_file);
virtual ~Writer();
virtual int write_data(xel *array, xelval *alpha);
private:
void free_png();
static int make_png_bit_depth(int bit_depth);
static void png_write_data(png_structp png_ptr, png_bytep data,
png_size_t length);
static void png_flush_data(png_structp png_ptr);
static void png_error(png_structp png_ptr, png_const_charp error_msg);
static void png_warning(png_structp png_ptr, png_const_charp warning_msg);
png_structp _png;
png_infop _info;
// We need a jmp_buf to support libpng's fatal error handling, in
// which the error handler must not immediately leave libpng code,
// but must return to the caller in Panda.
jmp_buf _jmpbuf;
};
*/
// The TypedWritable interface follows.
public: