See what's going on with flipcode!

This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.


  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 -

    Currently browsing [] (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 [] (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.


    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.