From 3c0e3af2331914c168028166302138e5e7abbe8e Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Sun, 30 Sep 2007 18:42:20 +0000 Subject: [PATCH] From David Spilling, "Please find attached an extension of the Radiance HDR (RGBE) image reader to support writing. The options are intended to match the existing read options. By default it will only write RGB32F format; if the "RAW" option is selected, it will output 8 bit RGBA as "raw" RGBE. Note also that the writer inserts a flipVertical(); although the RGBE format, according to spec, should support top-to-bottom or bottom-to-top ordering, no software I've found, including that from the formats originator, actually respects this." --- src/osgPlugins/hdr/ReaderWriterHDR.cpp | 95 +++++++++- src/osgPlugins/hdr/hdrwriter.cpp | 238 +++++++++++++++++++++++++ src/osgPlugins/hdr/hdrwriter.h | 94 ++++++++++ 3 files changed, 425 insertions(+), 2 deletions(-) create mode 100644 src/osgPlugins/hdr/hdrwriter.cpp create mode 100644 src/osgPlugins/hdr/hdrwriter.h diff --git a/src/osgPlugins/hdr/ReaderWriterHDR.cpp b/src/osgPlugins/hdr/ReaderWriterHDR.cpp index 70296f66e..a8ad9bdd0 100644 --- a/src/osgPlugins/hdr/ReaderWriterHDR.cpp +++ b/src/osgPlugins/hdr/ReaderWriterHDR.cpp @@ -42,12 +42,13 @@ #include #include "hdrloader.h" +#include "hdrwriter.h" class ReaderWriterHDR : public osgDB::ReaderWriter { public: virtual const char* className() { return "HDR Image Reader"; } - virtual bool acceptsExtension(const std::string &extension) { return osgDB::equalCaseInsensitive(extension, "hdr"); } + virtual bool acceptsExtension(const std::string &extension) const { return osgDB::equalCaseInsensitive(extension, "hdr"); } virtual ReadResult readImage(const std::string &_file, const osgDB::ReaderWriter::Options *_opts) const { @@ -94,7 +95,7 @@ public: } else if(opt == "YFLIP") { - bYFlip = true; // TODO + bYFlip = true; // Image is flipped later if required } } } @@ -167,8 +168,98 @@ public: osg::Image::USE_NEW_DELETE); } + // Y flip + if(bYFlip==true) img->flipVertical(); + return img; } + + + // Additional write methods + virtual WriteResult writeImage(const osg::Image &image,const std::string& file, const osgDB::ReaderWriter::Options* options) const + { + std::string ext = osgDB::getFileExtension(file); + if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED; + + std::ofstream fout(file.c_str(), std::ios::out | std::ios::binary); + if(!fout) return WriteResult::ERROR_IN_WRITING_FILE; + + return writeImage(image,fout,options); + } + + virtual WriteResult writeImage(const osg::Image& image,std::ostream& fout,const Options* options) const + { + + bool bYFlip = true; // Whether to flip the vertical + bool rawRGBE = false; // Whether to write as raw RGBE + + if(options) + { + std::istringstream iss(options->getOptionString()); + std::string opt; + while (iss >> opt) + { + if (opt=="NO_YFLIP") + { + // We want to YFLIP because although the file format specification + // dictates that +Y M +X N is a valid resolution line, no software (including + // HDRShop!) actually recognises it; hence everything tends to be written upside down + // So we flip the image first... + bYFlip = false; + } + else if(opt=="RAW") + { + rawRGBE = true; + } + /* The following are left out for the moment as + we don't really do anything with them in OSG + else if(opt=="GAMMA") + { + iss >> gamma; + } + else if(opt=="EXPOSURE") + { + iss >> exposure; + } + */ + + } + } + + // Reject unhandled image formats + if(rawRGBE==false) + { + if(image.getInternalTextureFormat()!=GL_RGB32F_ARB) // We only handle RGB (no alpha) with 32F formats + { + return WriteResult::FILE_NOT_HANDLED; + } + } + else // Outputting raw RGBE (as interpreted by a shader, for example) + { + if(image.getInternalTextureFormat()!=GL_RGBA8) // We need 8 bit bytes including alpha (E) + { + return WriteResult::FILE_NOT_HANDLED; + } + } + + // Get a temporary copy to flip if we need to + osg::ref_ptr source = new osg::Image(image,osg::CopyOp::DEEP_COPY_ALL); + + if(bYFlip==true) source->flipVertical(); + + bool success; + success = HDRWriter::writeHeader(source.get(),fout); + if(!success) + { + return WriteResult::ERROR_IN_WRITING_FILE; // early out + source = 0; // delete the temporary image + } + + success = HDRWriter::writeRLE(source.get(), fout); + + source = 0; // delete the temporary image + return (success)? WriteResult::FILE_SAVED : WriteResult::ERROR_IN_WRITING_FILE; + } }; // now register with Registry to instantiate the above diff --git a/src/osgPlugins/hdr/hdrwriter.cpp b/src/osgPlugins/hdr/hdrwriter.cpp new file mode 100644 index 000000000..96652adeb --- /dev/null +++ b/src/osgPlugins/hdr/hdrwriter.cpp @@ -0,0 +1,238 @@ +/* + The following code was based on code from the following location: + http://www.graphics.cornell.edu/online/formats/rgbe/ + + It includes the following information : + + "This file contains code to read and write four byte rgbe file format + developed by Greg Ward. It handles the conversions between rgbe and + pixels consisting of floats. The data is assumed to be an array of floats. + By default there are three floats per pixel in the order red, green, blue. + (RGBE_DATA_??? values control this.) Only the mimimal header reading and + writing is implemented. Each routine does error checking and will return + a status value as defined below. This code is intended as a skeleton so + feel free to modify it to suit your needs. + + (Place notice here if you modified the code.) + posted to http://www.graphics.cornell.edu/~bjw/ + written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95 + based on code written by Greg Ward" + + Modified for OSG September 2007 david.spilling@gmail.com : + The file format is described fully in http://radsite.lbl.gov/radiance/refer/filefmts.pdf + For the moment, we don't output most of the header fields + + +*/ + + +#include "hdrwriter.h" + +#include +#include +#include +#include +#include + + +bool HDRWriter::writeRLE(const osg::Image *img, std::ostream& fout) +{ + return writePixelsRLE(fout,(float*) img->data(), img->s(), img->t()); +} + +bool HDRWriter::writeRAW(const osg::Image *img, std::ostream& fout) +{ + return writePixelsRAW(fout,(unsigned char*) img->data(), img->s() * img->t()); +} + + + + + +/* number of floats per pixel */ +#define RGBE_DATA_SIZE 3 + +/* offsets to red, green, and blue components in a data (float) pixel */ +#define R 0 +#define G 1 +#define B 2 +#define E 3 + +#define MINELEN 8 // minimum scanline length for encoding +#define MAXELEN 0x7fff // maximum scanline length for encoding + +/* default minimal header. modify if you want more information in header */ +bool HDRWriter::writeHeader(const osg::Image *img, std::ostream& fout) +{ + std::stringstream stream; // for conversion to strings + + stream << "#?RADIANCE\n"; // Could be RGBE, but some 3rd party software doesn't interpret the file correctly + // if it is. + stream << "FORMAT=32-bit_rle_rgbe\n\n"; + + // Our image data is usually arranged row major, with the origin at the bottom left of the image. + // Based on the Radiance file format, this would be "+Y blah +X blah". However, no software (including + // HDRShop v1!) seems to support this; they all expect -Y blah +X blah, and then flip the image. This + // is unfortunate, and is what provokes the default behaviour of OSG having to flip the image prior to + // write. + stream << "-Y "<s()<<" +X "<t()<<"\n"; + + fout.write(stream.str().c_str(), stream.str().length()); + + return true; +} + +/* simple write routine that does not use run length encoding */ +/* These routines can be made faster by allocating a larger buffer and + fread-ing and fwrite-ing the data in larger chunks */ +bool HDRWriter::writePixelsNoRLE( std::ostream& fout, float* data, int numpixels) +{ + unsigned char rgbe[4]; + + while (numpixels-- > 0) + { + float2rgbe( + rgbe, + data[R], + data[G], + data[B] + ); + data += RGBE_DATA_SIZE; + fout.write(reinterpret_cast(rgbe), sizeof(rgbe)); //img->getTotalSizeInBytesIncludingMipmaps() + } + return true; +} + +bool HDRWriter::writePixelsRAW( std::ostream& fout, unsigned char* data, int numpixels) +{ + unsigned char rgbe[4]; + + while (numpixels-- > 0) + { + rgbe[0] = (unsigned char) *(data+R); + rgbe[1] = (unsigned char) *(data+G); + rgbe[2] = (unsigned char) *(data+B); + rgbe[3] = (unsigned char) *(data+E); + data += RGBE_DATA_SIZE; + fout.write(reinterpret_cast(rgbe), sizeof(rgbe)); //img->getTotalSizeInBytesIncludingMipmaps() + } + return true; +} + +/* The code below is only needed for the run-length encoded files. */ +/* Run length encoding adds considerable complexity but does */ +/* save some space. For each scanline, each channel (r,g,b,e) is */ +/* encoded separately for better compression. */ +bool HDRWriter::writeBytesRLE(std::ostream& fout, unsigned char *data, int numbytes) +{ +#define MINRUNLENGTH 4 + int cur, beg_run, run_count, old_run_count, nonrun_count; + unsigned char buf[2]; + + cur = 0; + while(cur < numbytes) + { + beg_run = cur; + /* find next run of length at least 4 if one exists */ + run_count = old_run_count = 0; + while((run_count < MINRUNLENGTH) && (beg_run < numbytes)) + { + beg_run += run_count; + old_run_count = run_count; + run_count = 1; + while((data[beg_run] == data[beg_run + run_count]) + && (beg_run + run_count < numbytes) + && (run_count < 127)) + { + run_count++; + } + } + /* if data before next big run is a short run then write it as such */ + if ((old_run_count > 1)&&(old_run_count == beg_run - cur)) + { + buf[0] = 128 + old_run_count; /*write short run*/ + buf[1] = data[cur]; + fout.write(reinterpret_cast(buf), sizeof(buf[0])*2); + //if (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1) return false + cur = beg_run; + } + /* write out bytes until we reach the start of the next run */ + while(cur < beg_run) + { + nonrun_count = beg_run - cur; + if (nonrun_count > 128) nonrun_count = 128; + buf[0] = nonrun_count; + fout.write(reinterpret_cast(buf),sizeof(buf[0])); + //if (fwrite(buf,sizeof(buf[0]),1,fp) < 1) return false + fout.write(reinterpret_cast(&data[cur]),sizeof(data[0])*nonrun_count); + // if (fwrite(&data[cur],sizeof(data[0])*nonrun_count,1,fp) < 1) return false; + cur += nonrun_count; + } + /* write out next run if one was found */ + if (run_count >= MINRUNLENGTH) + { + buf[0] = 128 + run_count; + buf[1] = data[beg_run]; + fout.write(reinterpret_cast(buf),sizeof(buf[0])*2); + //if (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1) return false; + cur += run_count; + } + } + return true; +#undef MINRUNLENGTH +} + +bool HDRWriter::writePixelsRLE( std::ostream& fout, float* data, int scanline_width, int num_scanlines ) + +{ + unsigned char rgbe[4]; + unsigned char *buffer; + + if ((scanline_width < MINELEN)||(scanline_width > MAXELEN)) + // run length encoding is not allowed so write flat + return writePixelsNoRLE(fout,data,scanline_width*num_scanlines); + + buffer = (unsigned char *)malloc(sizeof(unsigned char)*4*scanline_width); + if (buffer == NULL) + // no buffer space so write flat + return writePixelsNoRLE(fout,data,scanline_width*num_scanlines); + + while(num_scanlines-- > 0) + { + rgbe[0] = 2; + rgbe[1] = 2; + rgbe[2] = scanline_width >> 8; + rgbe[3] = scanline_width & 0xFF; + + fout.write(reinterpret_cast(rgbe), sizeof(rgbe)); + /* + if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1) + { + free(buffer); + return rgbe_error(rgbe_write_error,NULL); + } + */ + for(int i=0;i + +#include + +class HDRWriter { +public: + // all return "true" for success, "false" for failure. + static bool writeRLE(const osg::Image *img, std::ostream& fout); + static bool writeRAW(const osg::Image *img, std::ostream& fout); + static bool writeHeader(const osg::Image *img, std::ostream& fout); + +protected: + +// can read or write pixels in chunks of any size including single pixels + static bool writePixelsNoRLE( std::ostream& fout, float* data, int numpixels); + static bool writePixelsRAW( std::ostream& fout, unsigned char* data, int numpixels); + +// read or write run length encoded files +// must be called to read or write whole scanlines + static bool writeBytesRLE(std::ostream& fout, unsigned char *data, int numbytes); + static bool writePixelsRLE( std::ostream& fout, float* data, int scanline_width, int num_scanlines ); + +// inline conversions + inline static void float2rgbe(unsigned char rgbe[4], float red, float green, float blue); + inline static void rgbe2float(float *red, float *green, float *blue, unsigned char rgbe[4]); +}; + + +/* standard conversion from float pixels to rgbe pixels */ +/* note: you can remove the "inline"s if your compiler complains about it */ +inline void HDRWriter::float2rgbe(unsigned char rgbe[4], float red, float green, float blue) +{ + float v; + int e; + + v = red; + if (green > v) v = green; + if (blue > v) v = blue; + if (v < 1e-32) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } + else { + v = frexp(v,&e) * 256.0/v; + rgbe[0] = (unsigned char) (red * v); + rgbe[1] = (unsigned char) (green * v); + rgbe[2] = (unsigned char) (blue * v); + rgbe[3] = (unsigned char) (e + 128); + } +} + +/* standard conversion from rgbe to float pixels */ +/* note: Ward uses ldexp(col+0.5,exp-(128+8)). However we wanted pixels */ +/* in the range [0,1] to map back into the range [0,1]. */ +inline void HDRWriter::rgbe2float(float *red, float *green, float *blue, unsigned char rgbe[4]) +{ + float f; + + if (rgbe[3]) { /*nonzero pixel*/ + f = ldexp(1.0,rgbe[3]-(int)(128+8)); + *red = rgbe[0] * f; + *green = rgbe[1] * f; + *blue = rgbe[2] * f; + } + else + *red = *green = *blue = 0.0; +} + +#endif