515 lines
17 KiB
C++
515 lines
17 KiB
C++
// Filename: pnmImageHeader.cxx
|
|
// Created by: drose (14Jun00)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d-general@lists.sourceforge.net .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "pnmImageHeader.h"
|
|
#include "pnmFileTypeRegistry.h"
|
|
#include "pnmFileType.h"
|
|
#include "pnmReader.h"
|
|
#include "config_pnmimage.h"
|
|
#include "virtualFileSystem.h"
|
|
#include "zStream.h"
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::read_header
|
|
// Access: Published
|
|
// Description: Opens up the image file and tries to read its header
|
|
// information to determine its size, number of
|
|
// channels, etc. If successful, updates the header
|
|
// information and returns true; otherwise, returns
|
|
// false.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PNMImageHeader::
|
|
read_header(const Filename &filename, PNMFileType *type) {
|
|
PNMReader *reader = make_reader(filename, type);
|
|
if (reader != (PNMReader *)NULL) {
|
|
(*this) = (*reader);
|
|
delete reader;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::make_reader
|
|
// Access: Published
|
|
// Description: Returns a newly-allocated PNMReader of the suitable
|
|
// type for reading from the indicated image filename,
|
|
// or NULL if the filename cannot be read for some
|
|
// reason. The filename "-" always stands for standard
|
|
// input. If type is specified, it is a suggestion for
|
|
// the file type to use.
|
|
//
|
|
// The PNMReader should be deleted when it is no longer
|
|
// needed.
|
|
////////////////////////////////////////////////////////////////////
|
|
PNMReader *PNMImageHeader::
|
|
make_reader(const Filename &filename, PNMFileType *type,
|
|
bool report_unknown_type) const {
|
|
if (pnmimage_cat.is_debug()) {
|
|
pnmimage_cat.debug()
|
|
<< "Reading image from " << filename << "\n";
|
|
}
|
|
bool owns_file = false;
|
|
istream *file = (istream *)NULL;
|
|
|
|
if (filename == "-") {
|
|
owns_file = false;
|
|
file = &cin;
|
|
|
|
if (pnmimage_cat.is_debug()) {
|
|
pnmimage_cat.debug()
|
|
<< "(reading standard input)\n";
|
|
}
|
|
} else {
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
owns_file = true;
|
|
file = vfs->open_read_file(filename, true);
|
|
}
|
|
|
|
if (file == (istream *)NULL) {
|
|
if (pnmimage_cat.is_debug()) {
|
|
pnmimage_cat.debug()
|
|
<< "Unable to open file.\n";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
return make_reader(file, owns_file, filename, string(), type,
|
|
report_unknown_type);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::make_reader
|
|
// Access: Published
|
|
// Description: Returns a newly-allocated PNMReader of the suitable
|
|
// type for reading from the already-opened image file,
|
|
// or NULL if the file cannot be read for some reason.
|
|
//
|
|
// owns_file should be set true if the PNMReader is to
|
|
// be considered the owner of the stream pointer (in
|
|
// which case the stream will be deleted on completion,
|
|
// whether successful or not), or false if it should not
|
|
// delete it.
|
|
//
|
|
// The filename parameter is optional here, since the
|
|
// file has already been opened; it is only used to
|
|
// examine the extension and attempt to guess the file
|
|
// type.
|
|
//
|
|
// If magic_number is nonempty, it is assumed to
|
|
// represent the first few bytes that have already been
|
|
// read from the file. Some file types may have
|
|
// difficulty if this is more than two bytes.
|
|
//
|
|
// If type is non-NULL, it is a suggestion for the file
|
|
// type to use.
|
|
//
|
|
// The PNMReader should be deleted when it is no longer
|
|
// needed.
|
|
////////////////////////////////////////////////////////////////////
|
|
PNMReader *PNMImageHeader::
|
|
make_reader(istream *file, bool owns_file, const Filename &filename,
|
|
string magic_number, PNMFileType *type,
|
|
bool report_unknown_type) const {
|
|
if (type == (PNMFileType *)NULL) {
|
|
if (!read_magic_number(file, magic_number, 2)) {
|
|
// No magic number. No image.
|
|
if (pnmimage_cat.is_debug()) {
|
|
pnmimage_cat.debug()
|
|
<< "Image file appears to be empty.\n";
|
|
}
|
|
if (owns_file) {
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
|
|
// We're assuming here that the file was opened via VFS. That
|
|
// may not necessarily be the case, but we don't make that
|
|
// distinction. However, at the moment at least, that
|
|
// distinction doesn't matter, since vfs->close_read_file()
|
|
// just deletes the file pointer anyway.
|
|
vfs->close_read_file(file);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
type = PNMFileTypeRegistry::get_global_ptr()->
|
|
get_type_from_magic_number(magic_number);
|
|
|
|
if (pnmimage_cat.is_debug()) {
|
|
if (type != (PNMFileType *)NULL) {
|
|
pnmimage_cat.debug()
|
|
<< "By magic number, image file appears to be type "
|
|
<< type->get_name() << ".\n";
|
|
} else {
|
|
pnmimage_cat.debug()
|
|
<< "Unable to determine image file type from magic number.\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == (PNMFileType *)NULL && !filename.empty()) {
|
|
// We still don't know the type; attempt to guess it from the
|
|
// filename extension.
|
|
type = PNMFileTypeRegistry::get_global_ptr()->get_type_from_extension(filename);
|
|
|
|
if (pnmimage_cat.is_debug()) {
|
|
if (type != (PNMFileType *)NULL) {
|
|
pnmimage_cat.debug()
|
|
<< "From its extension, image file is probably type "
|
|
<< type->get_name() << ".\n";
|
|
} else {
|
|
pnmimage_cat.debug()
|
|
<< "Unable to guess image file type from its extension.\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == (PNMFileType *)NULL) {
|
|
// No? How about the default type associated with this image header.
|
|
type = _type;
|
|
|
|
if (pnmimage_cat.is_debug() && type != (PNMFileType *)NULL) {
|
|
pnmimage_cat.debug()
|
|
<< "Assuming image file type is " << type->get_name() << ".\n";
|
|
}
|
|
}
|
|
|
|
if (type == (PNMFileType *)NULL) {
|
|
// We can't figure out what type the file is; give up.
|
|
if (report_unknown_type && pnmimage_cat.is_error()) {
|
|
pnmimage_cat.error()
|
|
<< "Cannot determine type of image file " << filename << ".\n"
|
|
<< "Currently supported image types:\n";
|
|
PNMFileTypeRegistry::get_global_ptr()->
|
|
write(pnmimage_cat.error(false), 2);
|
|
}
|
|
if (owns_file) {
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
|
|
// We're assuming here that the file was opened via VFS. That
|
|
// may not necessarily be the case, but we don't make that
|
|
// distinction. However, at the moment at least, that
|
|
// distinction doesn't matter, since vfs->close_read_file()
|
|
// just deletes the file pointer anyway.
|
|
vfs->close_read_file(file);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PNMReader *reader = type->make_reader(file, owns_file, magic_number);
|
|
if (reader == NULL && owns_file) {
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
vfs->close_read_file(file);
|
|
}
|
|
|
|
if (!reader->is_valid()) {
|
|
delete reader;
|
|
reader = NULL;
|
|
}
|
|
|
|
return reader;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::make_writer
|
|
// Access: Published
|
|
// Description: Returns a newly-allocated PNMWriter of the suitable
|
|
// type for writing an image to the indicated filename,
|
|
// or NULL if the filename cannot be written for some
|
|
// reason. The filename "-" always stands for standard
|
|
// output. If type is specified, it is a suggestion for
|
|
// the file type to use.
|
|
//
|
|
// The PNMWriter should be deleted when it is no longer
|
|
// needed.
|
|
////////////////////////////////////////////////////////////////////
|
|
PNMWriter *PNMImageHeader::
|
|
make_writer(const Filename &filename, PNMFileType *type) const {
|
|
if (pnmimage_cat.is_debug()) {
|
|
pnmimage_cat.debug()
|
|
<< "Writing image to " << filename << "\n";
|
|
}
|
|
bool owns_file = false;
|
|
ostream *file = (ostream *)NULL;
|
|
|
|
if (filename == "-") {
|
|
owns_file = false;
|
|
file = &cout;
|
|
|
|
if (pnmimage_cat.is_debug()) {
|
|
pnmimage_cat.debug()
|
|
<< "(writing to standard output)\n";
|
|
}
|
|
|
|
} else {
|
|
ofstream *new_ostream = new ofstream;
|
|
Filename actual_name = Filename::binary_filename(filename);
|
|
if (!actual_name.open_write(*new_ostream)) {
|
|
delete new_ostream;
|
|
|
|
} else {
|
|
owns_file = true;
|
|
file = new_ostream;
|
|
|
|
#ifdef HAVE_ZLIB
|
|
if (filename.get_extension() == "pz") {
|
|
// The filename ends in .pz, which means to automatically
|
|
// compress the image file that we write.
|
|
file = new OCompressStream(file, true);
|
|
}
|
|
#endif // HAVE_ZLIB
|
|
}
|
|
}
|
|
|
|
if (file == (ostream *)NULL) {
|
|
if (pnmimage_cat.is_debug()) {
|
|
pnmimage_cat.debug()
|
|
<< "Unable to write to file.\n";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
return make_writer(file, owns_file, filename, type);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::make_writer
|
|
// Access: Published
|
|
// Description: Returns a newly-allocated PNMWriter of the suitable
|
|
// type for writing to the already-opened image file, or
|
|
// NULL if the file cannot be written for some reason.
|
|
//
|
|
// owns_file should be set true if the PNMWriter is to
|
|
// be considered the owner of the stream pointer (in
|
|
// which case the stream will be deleted on completion,
|
|
// whether successful or not), or false if it should not
|
|
// delete it.
|
|
//
|
|
// The filename parameter is optional here, since the
|
|
// file has already been opened; it is only used to
|
|
// examine the extension and attempt to guess the
|
|
// intended file type.
|
|
//
|
|
// If type is non-NULL, it is a suggestion for the file
|
|
// type to use.
|
|
//
|
|
// The PNMWriter should be deleted when it is no longer
|
|
// needed.
|
|
////////////////////////////////////////////////////////////////////
|
|
PNMWriter *PNMImageHeader::
|
|
make_writer(ostream *file, bool owns_file, const Filename &filename,
|
|
PNMFileType *type) const {
|
|
if (type == (PNMFileType *)NULL && !filename.empty()) {
|
|
// We don't know the type; attempt to guess it from the filename
|
|
// extension.
|
|
type = PNMFileTypeRegistry::get_global_ptr()->get_type_from_extension(filename);
|
|
|
|
if (pnmimage_cat.is_debug()) {
|
|
if (type != (PNMFileType *)NULL) {
|
|
pnmimage_cat.debug()
|
|
<< "From its extension, image file is intended to be type "
|
|
<< type->get_name() << ".\n";
|
|
} else {
|
|
pnmimage_cat.debug()
|
|
<< "Unable to guess image file type from its extension.\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == (PNMFileType *)NULL) {
|
|
// No? How about the default type associated with this image header.
|
|
type = _type;
|
|
|
|
if (pnmimage_cat.is_debug() && type != (PNMFileType *)NULL) {
|
|
pnmimage_cat.debug()
|
|
<< "Assuming image file type is " << type->get_name() << ".\n";
|
|
}
|
|
}
|
|
|
|
if (type == (PNMFileType *)NULL) {
|
|
// We can't figure out what type the file is; give up.
|
|
if (pnmimage_cat.is_debug()) {
|
|
pnmimage_cat.debug()
|
|
<< "Cannot determine type of image file " << filename << ".\n";
|
|
}
|
|
if (owns_file) {
|
|
delete file;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PNMWriter *writer = type->make_writer(file, owns_file);
|
|
if (writer == NULL && owns_file) {
|
|
delete file;
|
|
}
|
|
|
|
if (!writer->is_valid()) {
|
|
delete writer;
|
|
writer = NULL;
|
|
}
|
|
|
|
return writer;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::read_magic_number
|
|
// Access: Published, Static
|
|
// Description: Ensures that the first n bytes of the file are read
|
|
// into magic_number. If magic_number is initially
|
|
// nonempty, assumes these represent the first few bytes
|
|
// already extracted. Returns true if successful, false
|
|
// if an end of file or error occurred before num_bytes
|
|
// could be read.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PNMImageHeader::
|
|
read_magic_number(istream *file, string &magic_number, int num_bytes) {
|
|
while ((int)magic_number.size() < num_bytes) {
|
|
int ch = file->get();
|
|
if (file->eof() || file->fail()) {
|
|
return false;
|
|
}
|
|
magic_number += (char)ch;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::output
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void PNMImageHeader::
|
|
output(ostream &out) const {
|
|
out << "image: " << _x_size << " by " << _y_size << " pixels, "
|
|
<< _num_channels << " channels, " << _maxval << " maxval.";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::compute_histogram
|
|
// Access: Protected
|
|
// Description: Computes a histogram of the colors used in the
|
|
// indicated rgb/grayscale array and/or alpha array.
|
|
// This is most likely to be useful in a PNMWriter
|
|
// class, but it is defined at this level in case it has
|
|
// general utilty for PNMImages.
|
|
//
|
|
// The max_colors parameter, if greater than zero,
|
|
// limits the maximum number of colors we are interested
|
|
// in. If we encounter more than this number of colors,
|
|
// the function aborts before completion and returns
|
|
// false; otherwise, it returns true.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PNMImageHeader::
|
|
compute_histogram(PNMImageHeader::Histogram &hist,
|
|
xel *array, xelval *alpha, int max_colors) {
|
|
int num_pixels = _x_size * _y_size;
|
|
int pi;
|
|
|
|
switch (get_color_type()) {
|
|
case CT_invalid:
|
|
return false;
|
|
|
|
case CT_grayscale:
|
|
for (pi = 0; pi < num_pixels; pi++) {
|
|
record_color(hist, PixelSpec(PPM_GETB(array[pi])));
|
|
if (max_colors > 0 && (int)hist.size() > max_colors) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case CT_two_channel:
|
|
for (pi = 0; pi < num_pixels; pi++) {
|
|
record_color(hist, PixelSpec(PPM_GETB(array[pi]), alpha[pi]));
|
|
if (max_colors > 0 && (int)hist.size() > max_colors) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case CT_color:
|
|
for (pi = 0; pi < num_pixels; pi++) {
|
|
record_color(hist, PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi])));
|
|
if (max_colors > 0 && (int)hist.size() > max_colors) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case CT_four_channel:
|
|
for (pi = 0; pi < num_pixels; pi++) {
|
|
record_color(hist, PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]), alpha[pi]));
|
|
if (max_colors > 0 && (int)hist.size() > max_colors) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::compute_palette
|
|
// Access: Protected
|
|
// Description: Returns a linear list of all of the colors in the
|
|
// image, similar to compute_histogram().
|
|
////////////////////////////////////////////////////////////////////
|
|
bool PNMImageHeader::
|
|
compute_palette(PNMImageHeader::Palette &palette,
|
|
xel *array, xelval *alpha, int max_colors) {
|
|
Histogram hist;
|
|
|
|
int num_pixels = _x_size * _y_size;
|
|
|
|
// In case there are already entries in the palette, preserve them.
|
|
Palette::const_iterator pi;
|
|
for (pi = palette.begin(); pi != palette.end(); ++pi) {
|
|
hist.insert(Histogram::value_type(*pi, num_pixels + 1));
|
|
}
|
|
|
|
if (!compute_histogram(hist, array, alpha, max_colors)) {
|
|
return false;
|
|
}
|
|
|
|
// Now append the new entries discovered in the histogram onto the
|
|
// end of the palette.
|
|
palette.reserve(hist.size());
|
|
Histogram::const_iterator hi;
|
|
for (hi = hist.begin(); hi != hist.end(); ++hi) {
|
|
if ((*hi).second <= num_pixels) {
|
|
palette.push_back((*hi).first);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: PNMImageHeader::PixelSpec::output
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void PNMImageHeader::PixelSpec::
|
|
output(ostream &out) const {
|
|
out << "(" << _red << ", " << _green << ", " << _blue << ", " << _alpha << ")";
|
|
}
|
|
|