Files
megasource/libs/DevIL-1.7.8/src-IL/src/il_hdr.c
T
2013-09-26 00:20:08 +02:00

660 lines
16 KiB
C

//-----------------------------------------------------------------------------
//
// ImageLib Sources
// Copyright (C) 2000-2008 by Denton Woods (this file by thakis / Denton)
// Last modified: 02/09/2009
//
// Filename: src-IL/src/il_hdr.c
//
// Description: Reads/writes a RADIANCE High Dynamic Range Image
//
//-----------------------------------------------------------------------------
#include "il_internal.h"
#ifndef IL_NO_HDR
#include "il_hdr.h"
#include "il_endian.h"
//! Checks if the file specified in FileName is a valid .hdr file.
ILboolean ilIsValidHdr(ILconst_string FileName)
{
ILHANDLE HdrFile;
ILboolean bHdr = IL_FALSE;
if (!iCheckExtension(FileName, IL_TEXT("hdr"))) {
ilSetError(IL_INVALID_EXTENSION);
return bHdr;
}
HdrFile = iopenr(FileName);
if (HdrFile == NULL) {
ilSetError(IL_COULD_NOT_OPEN_FILE);
return bHdr;
}
bHdr = ilIsValidHdrF(HdrFile);
icloser(HdrFile);
return bHdr;
}
//! Checks if the ILHANDLE contains a valid .hdr file at the current position.
ILboolean ilIsValidHdrF(ILHANDLE File)
{
ILuint FirstPos;
ILboolean bRet;
iSetInputFile(File);
FirstPos = itell();
bRet = iIsValidHdr();
iseek(FirstPos, IL_SEEK_SET);
return bRet;
}
//! Checks if Lump is a valid .hdr lump.
ILboolean ilIsValidHdrL(const void *Lump, ILuint Size)
{
iSetInputLump(Lump, Size);
return iIsValidHdr();
}
// Internal function used to get the .hdr header from the current file.
ILboolean iGetHdrHead(HDRHEADER *Header)
{
ILboolean done = IL_FALSE;
char a, b;
char x[3], y[3]; //changed 20050217: added space for the '\0' char
char buff[81]; // 01-19-2009: Added space for the '\0'.
ILuint count = 0;
iread(Header->Signature, 1, 10);
//skip lines until an empty line is found.
//this marks the end of header information,
//the next line contains the image's dimension.
//TODO: read header contents into variables
//(EXPOSURE, orientation, xyz correction, ...)
if (iread(&a, 1, 1) != 1)
return IL_FALSE;
while (!done) {
if (iread(&b, 1, 1) != 1)
return IL_FALSE;
if (b == '\n' && a == '\n')
done = IL_TRUE;
else
a = b;
}
//read dimensions (note that this assumes a somewhat valid image)
if (iread(&a, 1, 1) != 1)
return IL_FALSE;
while (a != '\n') {
if (count >= 80) { // Line shouldn't be this long at all.
ilSetError(IL_INVALID_FILE_HEADER);
return IL_FALSE;
}
buff[count] = a;
if (iread(&a, 1, 1) != 1)
return IL_FALSE;
++count;
}
buff[count] = '\0';
//note that this is not the 100% correct way to load hdr images:
//in a perfect world we would check if there's a +/- X/Y in front
//of width and heigth and mirror + rotate the image after decoding
//according to that. But HDRShop doesn't do that either (and that's
//my reference program :) ) and it's just a rotate and a mirror,
//nothing that really changes the appearance of the loaded image...
//(The code as it is now assumes that y contains "-Y" and x contains
//"+X" after the following line)
// The 2 has to be in the %s format specifier to prevent buffer overruns.
sscanf(buff, "%2s %d %2s %d", y, &Header->Height, x, &Header->Width);
return IL_TRUE;
}
// Internal function to get the header and check it.
ILboolean iIsValidHdr()
{
char Head[10];
ILint Read;
Read = iread(Head, 1, 10);
iseek(-Read, IL_SEEK_CUR); // Go ahead and restore to previous state
if (Read != 10)
return IL_FALSE;
return
strnicmp(Head, "#?RADIANCE", 10) == 0
|| strnicmp(Head, "#?RGBE", 6) == 0;
}
// Internal function used to check if the HEADER is a valid .hdr header.
ILboolean iCheckHdr(HDRHEADER *Header)
{
return
strnicmp(Header->Signature, "#?RADIANCE", 10) == 0
|| strnicmp(Header->Signature, "#?RGBE", 6) == 0;
}
//! Reads a .hdr file
ILboolean ilLoadHdr(ILconst_string FileName)
{
ILHANDLE HdrFile;
ILboolean bHdr = IL_FALSE;
HdrFile = iopenr(FileName);
if (HdrFile == NULL) {
ilSetError(IL_COULD_NOT_OPEN_FILE);
return bHdr;
}
bHdr = ilLoadHdrF(HdrFile);
icloser(HdrFile);
return bHdr;
}
//! Reads an already-opened .hdr file
ILboolean ilLoadHdrF(ILHANDLE File)
{
ILuint FirstPos;
ILboolean bRet;
iSetInputFile(File);
FirstPos = itell();
bRet = iLoadHdrInternal();
iseek(FirstPos, IL_SEEK_SET);
return bRet;
}
//! Reads from a memory "lump" that contains a .hdr
ILboolean ilLoadHdrL(const void *Lump, ILuint Size)
{
iSetInputLump(Lump, Size);
return iLoadHdrInternal();
}
// Internal function used to load the .hdr.
ILboolean iLoadHdrInternal()
{
HDRHEADER Header;
ILfloat *data;
ILubyte *scanline;
ILuint i, j, e, r, g, b;
if (iCurImage == NULL) {
ilSetError(IL_ILLEGAL_OPERATION);
return IL_FALSE;
}
if (!iGetHdrHead(&Header)) {
ilSetError(IL_INVALID_FILE_HEADER);
return IL_FALSE;
}
if (!iCheckHdr(&Header)) {
//iseek(-(ILint)sizeof(HDRHEAD), IL_SEEK_CUR);
ilSetError(IL_INVALID_FILE_HEADER);
return IL_FALSE;
}
// Update the current image with the new dimensions
if (!ilTexImage(Header.Width, Header.Height, 1, 3, IL_RGB, IL_FLOAT, NULL)) {
return IL_FALSE;
}
iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
//read image data
if (iGetHint(IL_MEM_SPEED_HINT) == IL_FASTEST)
iPreCache(iCurImage->Width / 8 * iCurImage->Height);
data = (ILfloat*)iCurImage->Data;
scanline = (ILubyte*)ialloc(Header.Width*4);
for (i = 0; i < Header.Height; ++i) {
ReadScanline(scanline, Header.Width);
//convert hdrs internal format to floats
for (j = 0; j < 4*Header.Width; j += 4) {
ILuint *ee;
ILfloat t, *ff;
e = scanline[j + 3];
r = scanline[j + 0];
g = scanline[j + 1];
b = scanline[j + 2];
//t = (float)pow(2.f, ((ILint)e) - 128);
if (e != 0)
e = (e - 1) << 23;
// All this just to avoid stric-aliasing warnings...
// was: t = *(ILfloat*)&e
ee = &e;
ff = (ILfloat*)ee;
t = *ff;
data[0] = (r/255.0f)*t;
data[1] = (g/255.0f)*t;
data[2] = (b/255.0f)*t;
data += 3;
}
}
iUnCache();
ifree(scanline);
return ilFixImage();
}
void ReadScanline(ILubyte *scanline, ILuint w) {
ILubyte *runner;
ILuint r, g, b, e, read, shift;
r = igetc();
g = igetc();
b = igetc();
e = igetc();
//check if the scanline is in the new format
//if so, e, r, g, g are stored separated and are
//rle-compressed independently.
if (r == 2 && g == 2) {
ILuint length = (b << 8) | e;
ILuint j, t, k;
if (length > w)
length = w; //fix broken files
for (k = 0; k < 4; ++k) {
runner = scanline + k;
j = 0;
while (j < length) {
t = igetc();
if (t > 128) { //Run?
ILubyte val = igetc();
t &= 127;
//copy current byte
while (t > 0 && j < length) {
*runner = val;
runner += 4;
--t;
++j;
}
}
else { //No Run.
//read new bytes
while (t > 0 && j < length) {
*runner = igetc();
runner += 4;
--t;
++j;
}
}
}
}
return; //done decoding a scanline in separated format
}
//if we come here, we are dealing with old-style scanlines
shift = 0;
read = 0;
runner = scanline;
while (read < w) {
if (read != 0) {
r = igetc();
g = igetc();
b = igetc();
e = igetc();
}
//if all three mantissas are 1, then this is a rle
//count dword
if (r == 1 && g == 1 && b == 1) {
ILuint length = e;
ILuint j;
for (j = length << shift; j > 0 && read < w; --j) {
memcpy(runner, runner - 4, 4);
runner += 4;
++read;
}
//if more than one rle count dword is read
//consecutively, they are higher order bytes
//of the first read value. shift keeps track of
//that.
shift += 8;
}
else {
runner[0] = r;
runner[1] = g;
runner[2] = b;
runner[3] = e;
shift = 0;
runner += 4;
++read;
}
}
}
//! Writes a Hdr file
ILboolean ilSaveHdr(const ILstring FileName)
{
ILHANDLE HdrFile;
ILuint HdrSize;
if (ilGetBoolean(IL_FILE_MODE) == IL_FALSE) {
if (iFileExists(FileName)) {
ilSetError(IL_FILE_ALREADY_EXISTS);
return IL_FALSE;
}
}
HdrFile = iopenw(FileName);
if (HdrFile == NULL) {
ilSetError(IL_COULD_NOT_OPEN_FILE);
return IL_FALSE;
}
HdrSize = ilSaveHdrF(HdrFile);
iclosew(HdrFile);
if (HdrSize == 0)
return IL_FALSE;
return IL_TRUE;
}
//! Writes a Hdr to an already-opened file
ILuint ilSaveHdrF(ILHANDLE File)
{
ILuint Pos;
iSetOutputFile(File);
Pos = itellw();
if (iSaveHdrInternal() == IL_FALSE)
return 0; // Error occurred
return itellw() - Pos; // Return the number of bytes written.
}
//! Writes a Hdr to a memory "lump"
ILuint ilSaveHdrL(void *Lump, ILuint Size)
{
ILuint Pos;
iSetOutputLump(Lump, Size);
Pos = itellw();
if (iSaveHdrInternal() == IL_FALSE)
return 0; // Error occurred
return itellw() - Pos; // Return the number of bytes written.
}
//
// Much of the saving code is based on the code by Bruce Walter,
// available at http://www.graphics.cornell.edu/online/formats/rgbe/.
//
// The actual source code file is
// http://www.graphics.cornell.edu/online/formats/rgbe/rgbe.c
//
/* standard conversion from float pixels to rgbe pixels */
/* note: you can remove the "inline"s if your compiler complains about it */
//static INLINE void
static void
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 = (float)(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);
}
}
typedef struct {
ILuint valid; /* indicate which fields are valid */
ILbyte programtype[16]; /* listed at beginning of file to identify it
* after "#?". defaults to "RGBE" */
ILfloat gamma; /* image has already been gamma corrected with
* given gamma. defaults to 1.0 (no correction) */
ILfloat exposure; /* a value of 1.0 in an image corresponds to
* <exposure> watts/steradian/m^2.
* defaults to 1.0 */
} rgbe_header_info;
/* flags indicating which fields in an rgbe_header_info are valid */
#define RGBE_VALID_PROGRAMTYPE 0x01
#define RGBE_VALID_GAMMA 0x02
#define RGBE_VALID_EXPOSURE 0x04
/* offsets to red, green, and blue components in a data (float) pixel */
#define RGBE_DATA_RED 0
#define RGBE_DATA_GREEN 1
#define RGBE_DATA_BLUE 2
/* number of floats per pixel */
#define RGBE_DATA_SIZE 3
/* default minimal header. modify if you want more information in header */
ILboolean RGBE_WriteHeader(ILuint width, ILuint height, rgbe_header_info *info)
{
char *programtype = "RGBE";
if (info && (info->valid & RGBE_VALID_PROGRAMTYPE))
programtype = info->programtype;
if (ilprintf("#?%s\n",programtype) < 0)
return IL_FALSE;
/* The #? is to identify file type, the programtype is optional. */
if (info && (info->valid & RGBE_VALID_GAMMA)) {
if (ilprintf("GAMMA=%g\n",info->gamma) < 0)
return IL_FALSE;
}
if (info && (info->valid & RGBE_VALID_EXPOSURE)) {
if (ilprintf("EXPOSURE=%g\n",info->exposure) < 0)
return IL_FALSE;
}
if (ilprintf("FORMAT=32-bit_rle_rgbe\n\n") < 0)
return IL_FALSE;
if (ilprintf("-Y %d +X %d\n", height, width) < 0)
return IL_FALSE;
return IL_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 iwrite-ing the data in larger chunks */
int RGBE_WritePixels(float *data, int numpixels)
{
unsigned char rgbe[4];
while (numpixels-- > 0) {
float2rgbe(rgbe,data[RGBE_DATA_RED],data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
data += RGBE_DATA_SIZE;
if (iwrite(rgbe, sizeof(rgbe), 1) < 1)
return IL_FALSE;
}
return IL_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. */
ILboolean RGBE_WriteBytes_RLE(ILubyte *data, ILuint numbytes)
{
#define MINRUNLENGTH 4
ILuint cur, beg_run, run_count, old_run_count, nonrun_count;
ILubyte 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;
// 01-25-2009: Moved test for beg_run + run_count first so that it is
// tested first. This keeps it from going out of bounds by 1.
while((beg_run + run_count < numbytes) && (run_count < 127) &&
(data[beg_run] == data[beg_run + run_count]))
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];
if (iwrite(buf,sizeof(buf[0])*2,1) < 1)
return IL_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;
if (iwrite(buf,sizeof(buf[0]),1) < 1)
return IL_FALSE;
if (iwrite(&data[cur],sizeof(data[0])*nonrun_count,1) < 1)
return IL_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];
if (iwrite(buf,sizeof(buf[0])*2,1) < 1)
return IL_FALSE;
cur += run_count;
}
}
return IL_TRUE;
#undef MINRUNLENGTH
}
// Internal function used to save the Hdr.
ILboolean iSaveHdrInternal()
{
ILimage *TempImage;
rgbe_header_info stHeader;
unsigned char rgbe[4];
ILubyte *buffer;
ILfloat *data;
ILuint i;
ILboolean bRet;
if (iCurImage == NULL) {
ilSetError(IL_ILLEGAL_OPERATION);
return IL_FALSE;
}
stHeader.exposure = 0;
stHeader.gamma = 0;
stHeader.programtype[0] = 0;
stHeader.valid = 0;
if (iCurImage->Format != IL_UNSIGNED_BYTE) {
TempImage = iConvertImage(iCurImage, IL_RGB, IL_FLOAT);
if (TempImage == NULL)
return IL_FALSE;
}
else
TempImage = iCurImage;
if (!RGBE_WriteHeader(TempImage->Width, TempImage->Height, &stHeader))
return IL_FALSE;
if (TempImage->Origin == IL_ORIGIN_LOWER_LEFT)
iFlipBuffer(TempImage->Data, TempImage->Depth, TempImage->Bps, TempImage->Height);
data = (ILfloat*)TempImage->Data;
if ((TempImage->Width < 8)||(TempImage->Width > 0x7fff)) {
/* run length encoding is not allowed so write flat*/
bRet = RGBE_WritePixels(data,TempImage->Width*TempImage->Height);
if (iCurImage != TempImage)
ilCloseImage(TempImage);
return bRet;
}
buffer = (ILubyte*)ialloc(sizeof(ILubyte)*4*TempImage->Width);
if (buffer == NULL) {
/* no buffer space so write flat */
bRet = RGBE_WritePixels(data,TempImage->Width*TempImage->Height);
if (iCurImage != TempImage)
ilCloseImage(TempImage);
return bRet;
}
while(TempImage->Height-- > 0) {
rgbe[0] = 2;
rgbe[1] = 2;
rgbe[2] = TempImage->Width >> 8;
rgbe[3] = TempImage->Width & 0xFF;
if (iwrite(rgbe, sizeof(rgbe), 1) < 1) {
free(buffer);
if (iCurImage != TempImage)
ilCloseImage(TempImage);
return IL_FALSE;
}
for(i=0;i<TempImage->Width;i++) {
float2rgbe(rgbe,data[RGBE_DATA_RED],data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
buffer[i] = rgbe[0];
buffer[i+TempImage->Width] = rgbe[1];
buffer[i+2*TempImage->Width] = rgbe[2];
buffer[i+3*TempImage->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(i=0;i<4;i++) {
if (RGBE_WriteBytes_RLE(&buffer[i*TempImage->Width],TempImage->Width) != IL_TRUE) {
ifree(buffer);
if (iCurImage != TempImage)
ilCloseImage(TempImage);
return IL_FALSE;
}
}
}
ifree(buffer);
if (iCurImage != TempImage)
ilCloseImage(TempImage);
return IL_TRUE;
}
#endif//IL_NO_HDR