|
HDR Image Reader
Submitted by |
Standard images file formats hold RGB (and eventually A) as a set of integers.
In a term of brightness, for an image with 8 bits per component, 0 is black and 255 is the full luminosity of the component.
Mathematically speaking it always corresponds to a range from 0 to 1.0. That's also the only range your eyes can see
on the monitor since it's not possible to have whiter than white (except with OMO Machine).
You grab colors value inside popular file formats such BMP, JPG, TGA, etc...
However, there are several disadvantages :
human eyes are able to perceive a granularity up to 1/10000 on a colour whereas RGB888 is only about 1/256
it exists format RGB161616 but it costs 48 bits per color
input colors values are ranged from 0 to 1 as said before, in a modulate context it means you can only
darken your colours
in a general manner, we would want to be more flexible
The solution is simple, choose some arbitrary rules such as on a [0...255] range the floating value 1.0 is
biased at 127 (and not 255). However, a good things would be to represent the values using floats so that
we have no more any limitation of any kind (negative values would be
even possible).
Finally we reach the content of this COTD, the HDRI (High Dynamic Range Image) file format.
It's used in RADIANCE to store the colors value of a CGI. Each component can be of any value : 0.75 , 1.6,
89.6 , 12.69 , etc... It's not really a new thing since Pixar needed similar concept more than 15 years ago
for their productions but it's not yet so common in the video game industry.
Using a float32 storage, it would cost 32 * 3 = 96 bits per color
which is quite expensive.
Fortunately, the HDR file format represents each pixel as follow :
byte 0: mantissa of R
byte 1: mantissa of G
byte 2: mantissa of B
byte 3: common exponant
that gives an RGBE scheme, in other words we approximated 3 float32 (RGB) in only 32 bits.
As you probably already noticed it can be a lossy compression scheme but fortunately not visible.
The RGBE content is then usually packed using a standard 8 bits RLE. We can also use this
algorithm to compress 3D geometry : instead of working with RGB we merely take XYZ positions.
Note it exists other formats which allow to store floating values such as the SGi LogLuv or
Pixar's Log format but the principle is just the same.
To load a HDR image using this COTD, you just need to call :
HDRLoaderResult result;
bool ret = HDRLoader::load("my_pic.hdr", result); |
and you obtain in "result" the width, height and the content of the image a set of float32 triplet (RGB).
It's then up to your render engine to manage HDR's textures using MODULATE2X or MODULATE4x
tricks (and it could be an entire other topic) or, if you are patient, to wait for the next generation of
3D video card.
For the sake of clarity, this code doesn't implement complex error's management or anything. It just
simplified to the very essential. You are free to take this code, change it and use it for any of your project.
Please have in mind this code is not guaranteed to be bug free and is at your own risk.
Igor Kravtchenko
OBRAZ Studio - http://www.obrazstudio.com
|
Currently browsing [hdrloader.zip] (1,826 bytes) - [hdrloader.cpp] - (3,880 bytes)
/***********************************************************************************
Created: 17:9:2002
FileName: hdrloader.cpp
Author: Igor Kravtchenko
Info: Load HDR image and convert to a set of float32 RGB triplet.
************************************************************************************/
#include "hdrloader.h"
#include <math.h>
#include <memory.h>
#include <stdio.h>
typedef unsigned char RGBE[4];
#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
static void workOnRGBE(RGBE *scan, int len, float *cols);
static bool decrunch(RGBE *scanline, int len, FILE *file);
static bool oldDecrunch(RGBE *scanline, int len, FILE *file);
bool HDRLoader::load(const char *fileName, HDRLoaderResult &res)
{
int i;
char str[200];
FILE *file;
file = fopen(fileName, "rb");
if (!file)
return false;
fread(str, 10, 1, file);
if (memcmp(str, "#?RADIANCE", 10)) {
fclose(file);
return false;
}
fseek(file, 1, SEEK_CUR);
char cmd[200];
i = 0;
char c = 0, oldc;
while(true) {
oldc = c;
c = fgetc(file);
if (c == 0xa && oldc == 0xa)
break;
cmd[i++] = c;
}
char reso[200];
i = 0;
while(true) {
c = fgetc(file);
reso[i++] = c;
if (c == 0xa)
break;
}
int w, h;
if (!sscanf(reso, "-Y %ld +X %ld", &h, &w)) {
fclose(file);
return false;
}
res.width = w;
res.height = h;
float *cols = new float[w * h * 3];
res.cols = cols;
RGBE *scanline = new RGBE[w];
if (!scanline) {
fclose(file);
return false;
}
// convert image
for (int y = h - 1; y >= 0; y--) {
if (decrunch(scanline, w, file) == false)
break;
workOnRGBE(scanline, w, cols);
cols += w * 3;
}
delete [] scanline;
fclose(file);
return true;
}
float convertComponent(int expo, int val)
{
float v = val / 256.0f;
float d = (float) pow(2, expo);
return v * d;
}
void workOnRGBE(RGBE *scan, int len, float *cols)
{
while (len-- > 0) {
int expo = scan[0][E] - 128;
cols[0] = convertComponent(expo, scan[0][R]);
cols[1] = convertComponent(expo, scan[0][G]);
cols[2] = convertComponent(expo, scan[0][B]);
cols += 3;
scan++;
}
}
bool decrunch(RGBE *scanline, int len, FILE *file)
{
int i, j;
if (len < MINELEN || len > MAXELEN)
return oldDecrunch(scanline, len, file);
i = fgetc(file);
if (i != 2) {
fseek(file, -1, SEEK_CUR);
return oldDecrunch(scanline, len, file);
}
scanline[0][G] = fgetc(file);
scanline[0][B] = fgetc(file);
i = fgetc(file);
if (scanline[0][G] != 2 || scanline[0][B] & 128) {
scanline[0][R] = 2;
scanline[0][E] = i;
return oldDecrunch(scanline + 1, len - 1, file);
}
// read each component
for (i = 0; i < 4; i++) {
for (j = 0; j < len; ) {
unsigned char code = fgetc(file);
if (code > 128) { // run
code &= 127;
unsigned char val = fgetc(file);
while (code--)
scanline[j++][i] = val;
}
else { // non-run
while(code--)
scanline[j++][i] = fgetc(file);
}
}
}
return feof(file) ? false : true;
}
bool oldDecrunch(RGBE *scanline, int len, FILE *file)
{
int i;
int rshift = 0;
while (len > 0) {
scanline[0][R] = fgetc(file);
scanline[0][G] = fgetc(file);
scanline[0][B] = fgetc(file);
scanline[0][E] = fgetc(file);
if (feof(file))
return false;
if (scanline[0][R] == 1 &&
scanline[0][G] == 1 &&
scanline[0][B] == 1) {
for (i = scanline[0][E] << rshift; i > 0; i--) {
memcpy(&scanline[0][0], &scanline[-1][0], 4);
scanline++;
len--;
}
rshift += 8;
}
else {
scanline++;
len--;
rshift = 0;
}
}
return true;
}
|
|
Currently browsing [hdrloader.zip] (1,826 bytes) - [hdrloader.h] - (568 bytes)
/***********************************************************************************
Created: 17:9:2002
FileName: hdrloader.h
Author: Igor Kravtchenko
Info: Load HDR image and convert to a set of float32 RGB triplet.
************************************************************************************/
class HDRLoaderResult {
public:
int width, height;
// each pixel takes 3 float32, each component can be of any value...
float *cols;
};
class HDRLoader {
public:
static bool load(const char *fileName, HDRLoaderResult &res);
};
|
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|