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."
This commit is contained in:
Robert Osfield 2007-09-30 18:42:20 +00:00
parent 8907919fce
commit 3c0e3af233
3 changed files with 425 additions and 2 deletions

View File

@ -42,12 +42,13 @@
#include <sstream>
#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<osg::Image> 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

View File

@ -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 <sstream>
#include <string>
#include <math.h>
#include <malloc.h>
#include <ctype.h>
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 "<<img->s()<<" +X "<<img->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<const char*>(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<const char*>(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<const char*>(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<const char*>(buf),sizeof(buf[0]));
//if (fwrite(buf,sizeof(buf[0]),1,fp) < 1) return false
fout.write(reinterpret_cast<const char*>(&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<const char*>(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<const char*>(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<scanline_width;i++)
{
float2rgbe(rgbe,data[R], data[G],data[B]);
buffer[i] = rgbe[0];
buffer[i+scanline_width] = rgbe[1];
buffer[i+2*scanline_width] = rgbe[2];
buffer[i+3*scanline_width] = rgbe[3];
data += RGBE_DATA_SIZE;
}
/* write out each of the four channels separately run length encoded */
/* first red, then green, then blue, then exponent */
for(int i=0;i<4;i++)
{
if (writeBytesRLE(fout,&buffer[i*scanline_width],scanline_width) != true)
{
free(buffer);
return false;
}
}
}
free(buffer);
return true;
}

View File

@ -0,0 +1,94 @@
/*
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
*/
#ifndef HDRWRITER_H
#define HDRWRITER_H
#include <osg/Image>
#include <math.h>
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