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