diff --git a/panda/src/grutil/Sources.pp b/panda/src/grutil/Sources.pp index 1252d1e70c..de532b43a7 100644 --- a/panda/src/grutil/Sources.pp +++ b/panda/src/grutil/Sources.pp @@ -26,6 +26,7 @@ multitexReducer.I multitexReducer.h multitexReducer.cxx \ nodeVertexTransform.I nodeVertexTransform.h \ pfmFile.I pfmFile.h \ + pnmFileTypePfm.h \ rigidBodyCombiner.I rigidBodyCombiner.h #define INCLUDED_SOURCES \ @@ -41,6 +42,7 @@ heightfieldTesselator.cxx \ nodeVertexTransform.cxx \ pfmFile.cxx \ + pnmFileTypePfm.cxx \ pipeOcclusionCullTraverser.cxx \ lineSegs.cxx \ rigidBodyCombiner.cxx @@ -59,6 +61,7 @@ multitexReducer.I multitexReducer.h \ nodeVertexTransform.I nodeVertexTransform.h \ pfmFile.I pfmFile.h \ + pnmFileTypePfm.h \ rigidBodyCombiner.I rigidBodyCombiner.h #define IGATESCAN all diff --git a/panda/src/grutil/config_grutil.cxx b/panda/src/grutil/config_grutil.cxx index 69ebc7fa41..4cdf89e957 100644 --- a/panda/src/grutil/config_grutil.cxx +++ b/panda/src/grutil/config_grutil.cxx @@ -24,6 +24,8 @@ #include "nodeVertexTransform.h" #include "rigidBodyCombiner.h" #include "pipeOcclusionCullTraverser.h" +#include "pnmFileTypePfm.h" +#include "pnmFileTypeRegistry.h" #include "dconfig.h" @@ -132,5 +134,10 @@ init_libgrutil() { TexturePool *ts = TexturePool::get_global_ptr(); ts->register_texture_type(MovieTexture::make_texture, "avi mov mpg mpeg mp4 wmv asf flv nut ogm mkv"); #endif // HAVE_FFMPEG + + PNMFileTypeRegistry *tr = PNMFileTypeRegistry::get_global_ptr(); + PNMFileTypePfm::init_type(); + PNMFileTypePfm::register_with_read_factory(); + tr->register_type(new PNMFileTypePfm); } diff --git a/panda/src/grutil/p3grutil_composite2.cxx b/panda/src/grutil/p3grutil_composite2.cxx index 3e904cb936..3a5bf4d0ff 100644 --- a/panda/src/grutil/p3grutil_composite2.cxx +++ b/panda/src/grutil/p3grutil_composite2.cxx @@ -5,3 +5,4 @@ #include "meshDrawer.cxx" #include "meshDrawer2D.cxx" #include "pfmFile.cxx" +#include "pnmFileTypePfm.cxx" diff --git a/panda/src/grutil/pfmFile.cxx b/panda/src/grutil/pfmFile.cxx index 7dfcccd0fe..f9b9c814b6 100755 --- a/panda/src/grutil/pfmFile.cxx +++ b/panda/src/grutil/pfmFile.cxx @@ -26,6 +26,9 @@ #include "geomPoints.h" #include "geomTriangles.h" #include "geomVertexWriter.h" +#include "pnmImage.h" +#include "pnmWriter.h" +#include "string_utils.h" #include "lens.h" #include "look_at.h" @@ -118,6 +121,10 @@ clear(int x_size, int y_size, int num_channels) { // Access: Published // Description: Reads the PFM data from the indicated file, returning // true on success, false on failure. +// +// This can also handle reading a standard image file +// supported by PNMImage; it will be quietly converted +// to a floating-point type. //////////////////////////////////////////////////////////////////// bool PfmFile:: read(const Filename &fullpath) { @@ -138,7 +145,7 @@ read(const Filename &fullpath) { } istream *in = file->open_read_file(true); - bool success = read(*in); + bool success = read(*in, fullpath); vfs->close_read_file(in); return success; @@ -149,22 +156,41 @@ read(const Filename &fullpath) { // Access: Published // Description: Reads the PFM data from the indicated stream, // returning true on success, false on failure. +// +// This can also handle reading a standard image file +// supported by PNMImage; it will be quietly converted +// to a floating-point type. //////////////////////////////////////////////////////////////////// bool PfmFile:: -read(istream &in) { +read(istream &in, const Filename &fullpath, const string &magic_number) { clear(); - string identifier; - in >> identifier; + string identifier = magic_number; + PNMImageHeader::read_magic_number(&in, identifier, 2); if (identifier == "PF") { _num_channels = 3; } else if (identifier == "Pf") { _num_channels = 1; } else { - grutil_cat.error() - << "Not a pfm file.\n"; - return false; + // Not a PFM file. Maybe it's a more conventional image file that + // we can read into a PFM. + PNMImage pnm; + PNMReader *reader = pnm.make_reader + (&in, false, fullpath, identifier, NULL, false); + if (reader == (PNMReader *)NULL) { + grutil_cat.error() + << "Not a PFM file or known image file type: " << fullpath << "\n"; + return false; + } + + if (!pnm.read(reader)) { + grutil_cat.error() + << "Invalid image file: " << fullpath << "\n"; + return false; + } + + return load(pnm); } int width, height; @@ -172,7 +198,7 @@ read(istream &in) { in >> width >> height >> scale; if (!in) { grutil_cat.error() - << "Error parsing pfm header.\n"; + << "Error parsing PFM header: " << fullpath << "\n"; return false; } @@ -247,9 +273,21 @@ read(istream &in) { // Access: Published // Description: Writes the PFM data to the indicated file, returning // true on success, false on failure. +// +// This can also handle writing a standard image file +// supported by PNMImage, if the filename extension is +// some image type's extension fother than "pfm"; it +// will be quietly converted to the appropriate integer +// type. //////////////////////////////////////////////////////////////////// bool PfmFile:: write(const Filename &fullpath) { + if (!is_valid()) { + grutil_cat.error() + << "PFM file is invalid.\n"; + return false; + } + Filename filename = Filename::binary_filename(fullpath); pofstream out; if (!filename.open_write(out)) { @@ -258,12 +296,27 @@ write(const Filename &fullpath) { return false; } + string extension = downcase(fullpath.get_extension()); + if (extension != "pfm") { + // Maybe we're trying to write a different kind of image file. + PNMImage pnm; + PNMWriter *writer = pnm.make_writer(&out, false, fullpath, NULL); + if (writer != (PNMWriter *)NULL) { + // Yep. + if (store(pnm)) { + return pnm.write(writer); + } + // Couldn't make an image. Carry on directly. + delete writer; + } + } + if (grutil_cat.is_debug()) { grutil_cat.debug() << "Writing PFM file " << filename << "\n"; } - return write(out); + return write(out, fullpath); } //////////////////////////////////////////////////////////////////// @@ -273,7 +326,7 @@ write(const Filename &fullpath) { // returning true on success, false on failure. //////////////////////////////////////////////////////////////////// bool PfmFile:: -write(ostream &out) { +write(ostream &out, const Filename &fullpath) { nassertr(is_valid(), false); if (_num_channels == 1) { @@ -310,6 +363,70 @@ write(ostream &out) { return true; } +//////////////////////////////////////////////////////////////////// +// Function: PfmFile::load +// Access: Published +// Description: Fills the PfmFile with the data from the indicated +// PNMImage, converted to floating-point values. +//////////////////////////////////////////////////////////////////// +bool PfmFile:: +load(const PNMImage &pnmimage) { + if (!pnmimage.is_valid()) { + clear(); + return false; + } + + // Get the number of channels, ignoring alpha. + int num_channels; + if (pnmimage.get_num_channels() >= 3) { + num_channels = 3; + } else { + num_channels = 1; + } + + clear(pnmimage.get_x_size(), pnmimage.get_y_size(), num_channels); + if (num_channels == 1) { + for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) { + for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) { + double gray = pnmimage.get_gray(xi, yi); + set_point(xi, yi, LVecBase3(gray, gray, gray)); + } + } + } else { + for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) { + for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) { + LRGBColord xel = pnmimage.get_xel(xi, yi); + set_point(xi, yi, LVecBase3(xel[0], xel[1], xel[2])); + } + } + } + return true; +} + + +//////////////////////////////////////////////////////////////////// +// Function: PfmFile::store +// Access: Published +// Description: Copies the data to the indicated PNMImage, converting +// to RGB values. +//////////////////////////////////////////////////////////////////// +bool PfmFile:: +store(PNMImage &pnmimage) const { + if (!is_valid()) { + pnmimage.clear(); + return false; + } + + pnmimage.clear(get_x_size(), get_y_size(), get_num_channels(), PGM_MAXMAXVAL); + for (int yi = 0; yi < get_y_size(); ++yi) { + for (int xi = 0; xi < get_x_size(); ++xi) { + LPoint3 point = get_point(xi, yi); + pnmimage.set_xel(xi, yi, point[0], point[1], point[2]); + } + } + return true; +} + //////////////////////////////////////////////////////////////////// // Function: PfmFile::calc_average_point // Access: Published diff --git a/panda/src/grutil/pfmFile.h b/panda/src/grutil/pfmFile.h index 591c1ec68b..a2a671a293 100755 --- a/panda/src/grutil/pfmFile.h +++ b/panda/src/grutil/pfmFile.h @@ -39,9 +39,13 @@ PUBLISHED: void clear(int x_size, int y_size, int num_channels); BLOCKING bool read(const Filename &fullpath); - BLOCKING bool read(istream &in); + BLOCKING bool read(istream &in, const Filename &fullpath = Filename(), + const string &magic_number = string()); BLOCKING bool write(const Filename &fullpath); - BLOCKING bool write(ostream &out); + BLOCKING bool write(ostream &out, const Filename &fullpath = Filename()); + + BLOCKING bool load(const PNMImage &pnmimage); + BLOCKING bool store(PNMImage &pnmimage) const; INLINE bool is_valid() const; diff --git a/panda/src/grutil/pnmFileTypePfm.cxx b/panda/src/grutil/pnmFileTypePfm.cxx new file mode 100644 index 0000000000..15f5eea889 --- /dev/null +++ b/panda/src/grutil/pnmFileTypePfm.cxx @@ -0,0 +1,258 @@ +// Filename: pnmFileTypePfm.cxx +// Created by: drose (04Apr98) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "pnmFileTypePfm.h" +#include "config_grutil.h" + +#include "pnmFileTypeRegistry.h" +#include "bamReader.h" + +TypeHandle PNMFileTypePfm::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PNMFileTypePfm:: +PNMFileTypePfm() { +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::get_name +// Access: Public, Virtual +// Description: Returns a few words describing the file type. +//////////////////////////////////////////////////////////////////// +string PNMFileTypePfm:: +get_name() const { + return "Portable Float Map"; +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::get_num_extensions +// Access: Public, Virtual +// Description: Returns the number of different possible filename +// extensions associated with this particular file type. +//////////////////////////////////////////////////////////////////// +int PNMFileTypePfm:: +get_num_extensions() const { + return 1; +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::get_extension +// Access: Public, Virtual +// Description: Returns the nth possible filename extension +// associated with this particular file type, without a +// leading dot. +//////////////////////////////////////////////////////////////////// +string PNMFileTypePfm:: +get_extension(int n) const { + return "pfm"; +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::get_suggested_extension +// Access: Public, Virtual +// Description: Returns a suitable filename extension (without a +// leading dot) to suggest for files of this type, or +// empty string if no suggestions are available. +//////////////////////////////////////////////////////////////////// +string PNMFileTypePfm:: +get_suggested_extension() const { + return "pfm"; +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::has_magic_number +// Access: Public, Virtual +// Description: Returns true if this particular file type uses a +// magic number to identify it, false otherwise. +//////////////////////////////////////////////////////////////////// +bool PNMFileTypePfm:: +has_magic_number() const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::matches_magic_number +// Access: Public, Virtual +// Description: Returns true if the indicated "magic number" byte +// stream (the initial few bytes read from the file) +// matches this particular file type, false otherwise. +//////////////////////////////////////////////////////////////////// +bool PNMFileTypePfm:: +matches_magic_number(const string &magic_number) const { + return (magic_number.size() >= 2) && + magic_number[0] == 'P' && + (magic_number[1] == 'F' || magic_number[1] == 'f'); +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::make_reader +// Access: Public, Virtual +// Description: Allocates and returns a new PNMReader suitable for +// reading from this file type, if possible. If reading +// from this file type is not supported, returns NULL. +//////////////////////////////////////////////////////////////////// +PNMReader *PNMFileTypePfm:: +make_reader(istream *file, bool owns_file, const string &magic_number) { + return new Reader(this, file, owns_file, magic_number); +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::make_writer +// Access: Public, Virtual +// Description: Allocates and returns a new PNMWriter suitable for +// reading from this file type, if possible. If writing +// files of this type is not supported, returns NULL. +//////////////////////////////////////////////////////////////////// +PNMWriter *PNMFileTypePfm:: +make_writer(ostream *file, bool owns_file) { + return new Writer(this, file, owns_file); +} + + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::Reader::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PNMFileTypePfm::Reader:: +Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) : + PNMReader(type, file, owns_file) +{ + PfmFile pfm; + if (!pfm.read(*file, Filename(), magic_number)) { + _is_valid = false; + return; + } + + pfm.store(_image); + PNMImageHeader::operator = (_image); +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::Reader::read_data +// Access: Public, Virtual +// Description: Reads 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 read. +// +// Derived classes need not override this if they +// instead provide supports_read_row() and read_row(), +// below. +//////////////////////////////////////////////////////////////////// +int PNMFileTypePfm::Reader:: +read_data(xel *array, xelval *alpha) { + if (!is_valid()) { + return 0; + } + + nassertr(_image.get_x_size() == get_x_size() && + _image.get_y_size() == get_y_size(), 0); + memcpy(array, _image[0], get_x_size() * get_y_size() * sizeof(xel)); + nassertr(!has_alpha(), 0); + return get_y_size(); +} + + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::Writer::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PNMFileTypePfm::Writer:: +Writer(PNMFileType *type, ostream *file, bool owns_file) : + PNMWriter(type, file, owns_file) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::write_data +// Access: Public, Virtual +// Description: Writes out an entire image all at once, including the +// header, based on the image data stored in the given +// _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 written. +// +// It is the user's responsibility to fill in the header +// data via calls to set_x_size(), set_num_channels(), +// etc., or copy_header_from(), before calling +// write_data(). +// +// It is important to delete the PNMWriter class after +// successfully writing the data. Failing to do this +// may result in some data not getting flushed! +// +// Derived classes need not override this if they +// instead provide supports_streaming() and write_row(), +// below. +//////////////////////////////////////////////////////////////////// +int PNMFileTypePfm::Writer:: +write_data(xel *array, xelval *alpha) { + if (_x_size <= 0 || _y_size <= 0) { + return 0; + } + + PNMImage image; + image.copy_header_from(*this); + nassertr(image.get_x_size() == get_x_size() && + image.get_y_size() == get_y_size(), 0); + memcpy(image[0], array, get_x_size() * get_y_size() * sizeof(xel)); + nassertr(!has_alpha(), 0); + + PfmFile pfm; + if (!pfm.load(image)) { + return 0; + } + + if (!pfm.write(*_file)) { + return 0; + } + + return get_y_size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::register_with_read_factory +// Access: Public, Static +// Description: Registers the current object as something that can be +// read from a Bam file. +//////////////////////////////////////////////////////////////////// +void PNMFileTypePfm:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_PNMFileTypePfm); +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePfm::make_PNMFileTypePfm +// Access: Protected, Static +// Description: This method is called by the BamReader when an object +// of this type is encountered in a Bam file; it should +// allocate and return a new object with all the data +// read. +// +// In the case of the PNMFileType objects, since these +// objects are all shared, we just pull the object from +// the registry. +//////////////////////////////////////////////////////////////////// +TypedWritable *PNMFileTypePfm:: +make_PNMFileTypePfm(const FactoryParams ¶ms) { + return PNMFileTypeRegistry::get_global_ptr()->get_type_by_handle(get_class_type()); +} diff --git a/panda/src/grutil/pnmFileTypePfm.h b/panda/src/grutil/pnmFileTypePfm.h new file mode 100644 index 0000000000..6f2c73eac1 --- /dev/null +++ b/panda/src/grutil/pnmFileTypePfm.h @@ -0,0 +1,92 @@ +// Filename: pnmFileTypePfm.h +// Created by: drose (17Jun00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef PNMFILETYPEPFM_H +#define PNMFILETYPEPFM_H + +#include "pandabase.h" + +#include "pnmFileType.h" +#include "pnmReader.h" +#include "pnmWriter.h" + +//////////////////////////////////////////////////////////////////// +// Class : PNMFileTypePfm +// Description : For reading and writing PFM files using the basic +// PNMImage interface, as if they were basic RGB files. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_GRUTIL PNMFileTypePfm : public PNMFileType { +public: + PNMFileTypePfm(); + + virtual string get_name() const; + + virtual int get_num_extensions() const; + virtual string get_extension(int n) const; + virtual string get_suggested_extension() const; + + virtual bool has_magic_number() const; + virtual bool matches_magic_number(const string &magic_number) const; + + virtual PNMReader *make_reader(istream *file, bool owns_file = true, + const string &magic_number = string()); + virtual PNMWriter *make_writer(ostream *file, bool owns_file = true); + +public: + class Reader : public PNMReader { + public: + Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number); + + virtual int read_data(xel *array, xelval *alpha); + + private: + PNMImage _image; + }; + + class Writer : public PNMWriter { + public: + Writer(PNMFileType *type, ostream *file, bool owns_file); + + virtual int write_data(xel *array, xelval *alpha); + }; + + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + +protected: + static TypedWritable *make_PNMFileTypePfm(const FactoryParams ¶ms); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + PNMFileType::init_type(); + register_type(_type_handle, "PNMFileTypePfm", + PNMFileType::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif + + diff --git a/pandatool/src/imageprogs/Sources.pp b/pandatool/src/imageprogs/Sources.pp index 94d01c8bdc..9cf99d0f91 100644 --- a/pandatool/src/imageprogs/Sources.pp +++ b/pandatool/src/imageprogs/Sources.pp @@ -2,7 +2,8 @@ p3imagebase p3progbase #define OTHER_LIBS \ - p3pipeline:c p3event:c p3pstatclient:c panda:m \ + p3pipeline:c p3event:c p3pstatclient:c p3grutil:c \ + panda:m \ p3pandabase:c p3pnmimage:c p3pnmimagetypes:c \ p3mathutil:c p3linmath:c p3putil:c p3express:c \ pandaexpress:m \