Просмотр исходного кода

Added HDR image loading support (still expiremental), fixed image type not being respected in texture creation

Ivan Safrin 11 лет назад
Родитель
Сommit
e2f3451384

+ 2 - 0
Core/Contents/CMakeLists.txt

@@ -83,6 +83,7 @@ SET(polycore_SRCS
     Source/PolyRay.cpp
     Source/PolySceneSprite.cpp
     Source/PolySceneEntityInstance.cpp
+    Source/rgbe.cpp
 )
 
 SET(polycore_HDRS
@@ -170,6 +171,7 @@ SET(polycore_HDRS
     Include/PolyRay.h
     Include/PolySceneSprite.h
     Include/PolySceneEntityInstance.h
+    Include/rgbe.h
 )
 
 SET(CMAKE_DEBUG_POSTFIX "_d")

+ 19 - 0
Core/Contents/Include/PolyImage.h

@@ -28,7 +28,19 @@ THE SOFTWARE.
 namespace Polycode {
 
 	class String;
+    
+    #define HALF_FLOAT_MIN_BIASED_EXP_AS_SINGLE_FP_EXP 0x38000000
+    #define HALF_FLOAT_MAX_BIASED_EXP_AS_SINGLE_FP_EXP 0x47800000
+    #define FLOAT_MAX_BIASED_EXP (0xFF << 23)
+    #define HALF_FLOAT_MAX_BIASED_EXP (0x1F << 10)
+    
+    typedef uint16_t hfloat;
 
+    typedef struct POLYIGNORE  {
+        int size;
+        char **tokens;
+    } TokenArray;
+    
 	/**
 	* An image in memory. Basic RGB or RGBA images stored in memory. Can be loaded from PNG files, created into textures and written to file.
 	*/
@@ -81,6 +93,10 @@ namespace Polycode {
 			*/ 			
 			bool loadImage(const String& fileName);
 			bool loadPNG(const String& fileName);
+        
+            static POLYIGNORE TokenArray readTokens(char *line, const char *tokens);
+            static POLYIGNORE void freeTokens(TokenArray tokens);
+            bool loadHDR(const String &fileName);
 			
 			/**
 			* Saves the image to a file. Currently only PNG files are supported.
@@ -259,6 +275,9 @@ namespace Polycode {
 		
 		protected:
 		
+        
+            static inline hfloat convertFloatToHFloat(float f);
+        
 			void setPixelType(int type);
 
 			// transform coordinates from external topleft position mode

+ 51 - 0
Core/Contents/Include/rgbe.h

@@ -0,0 +1,51 @@
+#ifndef _H_RGBE
+#define _H_RGBE
+/* THIS CODE CARRIES NO GUARANTEE OF USABILITY OR FITNESS FOR ANY PURPOSE.
+ * WHILE THE AUTHORS HAVE TRIED TO ENSURE THE PROGRAM WORKS CORRECTLY,
+ * IT IS STRICTLY USE AT YOUR OWN RISK.  */
+
+/* utility for reading and writing Ward's rgbe image format.
+   See rgbe.txt file for more details.
+*/
+
+#include <stdio.h>
+
+typedef struct {
+  int valid;            /* indicate which fields are valid */
+  char programtype[16]; /* listed at beginning of file to identify it 
+                         * after "#?".  defaults to "RGBE" */ 
+  float gamma;          /* image has already been gamma corrected with 
+                         * given gamma.  defaults to 1.0 (no correction) */
+  float 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
+
+/* return codes for rgbe routines */
+#define RGBE_RETURN_SUCCESS 0
+#define RGBE_RETURN_FAILURE -1
+
+/* read or write headers */
+/* you may set rgbe_header_info to null if you want to */
+int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info);
+int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info);
+
+/* read or write pixels */
+/* can read or write pixels in chunks of any size including single pixels*/
+int RGBE_WritePixels(FILE *fp, float *data, int numpixels);
+int RGBE_ReadPixels(FILE *fp, float *data, int numpixels);
+
+/* read or write run length encoded files */
+/* must be called to read or write whole scanlines */
+int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
+			 int num_scanlines);
+int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
+			int num_scanlines);
+
+#endif /* _H_RGBE */
+

+ 2 - 2
Core/Contents/Source/PolyGLTexture.cpp

@@ -44,8 +44,8 @@ OpenGLTexture::OpenGLTexture(unsigned int width, unsigned int height, char *text
 			pixelType = GL_UNSIGNED_BYTE;			
 		break;
 		case Image::IMAGE_FP16:
-			glTextureType = GL_RGBA;
-			glTextureFormat = GL_RGBA16F_ARB;				
+			glTextureType = GL_RGB;
+			glTextureFormat = GL_RGBA;
 			pixelType = GL_FLOAT;
 		break;		
 		default:

+ 158 - 2
Core/Contents/Source/PolyImage.cpp

@@ -29,6 +29,7 @@
 #include "PolyPerlin.h"
 #include <algorithm>
 #include <stdlib.h>
+#include "rgbe.h"
 
 using namespace Polycode;
 
@@ -63,7 +64,7 @@ void Image::setPixelType(int type) {
 			pixelSize = 4;						
 		break;
 		case IMAGE_FP16:		
-			pixelSize = 16;
+			pixelSize = 6;
 		break;
 		default:
 			pixelSize = 4;								
@@ -501,7 +502,162 @@ bool Image::savePNG(const String &fileName) {
 
 
 bool Image::loadImage(const String& fileName) {
-	return loadPNG(fileName);
+    
+	String extension;
+	size_t found;
+	found=fileName.rfind(".");
+	if (found != -1) {
+		extension = fileName.substr(found+1);
+	} else {
+		extension = "";
+	}
+
+    if(extension == "png") {
+        return loadPNG(fileName);
+    } else if(extension == "hdr") {
+        return loadHDR(fileName);
+    } else {
+        Logger::log("Error: Invalid image format.\n");
+        return false;
+    }
+}
+
+inline hfloat Image::convertFloatToHFloat(float f) {
+    float _f = f;
+    uint32_t x = *(uint32_t *)(&_f);
+    uint32_t sign = (uint32_t)(x >> 31);
+    uint32_t mantissa;
+    uint32_t exp;
+    hfloat          hf;
+    
+    // get mantissa
+    mantissa = x & ((1 << 23) - 1);
+    // get exponent bits
+    exp = x & FLOAT_MAX_BIASED_EXP;
+    if (exp >= HALF_FLOAT_MAX_BIASED_EXP_AS_SINGLE_FP_EXP)
+    {
+        // check if the original single precision float number is a NaN
+        if (mantissa && (exp == FLOAT_MAX_BIASED_EXP))
+        {
+            // we have a single precision NaN
+            mantissa = (1 << 23) - 1;
+        }
+        else
+        {
+            // 16-bit half-float representation stores number as Inf
+            mantissa = 0;
+        }
+        hf = (((hfloat)sign) << 15) | (hfloat)(HALF_FLOAT_MAX_BIASED_EXP) |
+        (hfloat)(mantissa >> 13);
+    }
+    // check if exponent is <= -15
+    else if (exp <= HALF_FLOAT_MIN_BIASED_EXP_AS_SINGLE_FP_EXP)
+    {
+        
+        // store a denorm half-float value or zero
+        exp = (HALF_FLOAT_MIN_BIASED_EXP_AS_SINGLE_FP_EXP - exp) >> 23;
+        mantissa >>= (14 + exp);
+        
+        hf = (((hfloat)sign) << 15) | (hfloat)(mantissa);
+    }
+    else
+    {
+        hf = (((hfloat)sign) << 15) |
+        (hfloat)((exp - HALF_FLOAT_MIN_BIASED_EXP_AS_SINGLE_FP_EXP) >> 13) |
+        (hfloat)(mantissa >> 13);
+    }
+    
+    return hf;
+}
+
+TokenArray Image::readTokens(char *line, const char *tokenString) {
+	char **tokens = (char**)malloc(sizeof(void*));
+	char *pch;
+	int numTokens = 0;
+	pch = strtok (line, tokenString);
+	while (pch != NULL) {
+		numTokens++;
+		tokens = (char**)realloc(tokens, sizeof(void*) *numTokens);
+		tokens[numTokens-1] = (char*) malloc(strlen(pch)+1);
+		memcpy(tokens[numTokens-1], pch, strlen(pch)+1);
+		pch = strtok (NULL, tokenString);
+	}
+    
+	TokenArray ta;
+	ta.size = numTokens;
+	ta.tokens = tokens;
+	return ta;
+}
+
+void Image::freeTokens(TokenArray tokens) {
+	int i;
+	for(i =0; i < tokens.size; i++) {
+		free(tokens.tokens[i]);
+	}
+	free(tokens.tokens);
+}
+
+bool Image::loadHDR(const String &fileName) {
+    
+    printf("Loading HDR image\n");
+    
+    setPixelType(IMAGE_FP16);
+
+    FILE *file = fopen (fileName.c_str(), "rb");
+	if(!file) {
+		return false;
+    }
+
+    if(RGBE_ReadHeader(file, &width, &height, NULL) != RGBE_RETURN_SUCCESS) {
+        
+        // if ReadHeader failed, try reading info manually.
+        
+        char line [ 128 ];
+        Number exposure;
+        
+        fseek(file, 0, SEEK_SET);
+        
+        while ( fgets ( line, sizeof(line), file ) != NULL ) {
+            TokenArray ta = readTokens(line, "=");
+            if(strcmp(ta.tokens[0], "EXPOSURE") == 0) {
+                exposure = atof(ta.tokens[1]);
+            }
+            if(strcmp(ta.tokens[0], "FORMAT") == 0) {
+                //printf("format is %s\n", ta.tokens[1]);
+            }
+            freeTokens(ta);
+            
+            ta = readTokens(line, " ");
+            if(strcmp(ta.tokens[0], "-Y") == 0) {
+                //printf("image size is %d x %d\n", atoi(ta.tokens[1]), atoi(ta.tokens[3]));
+                width = atoi(ta.tokens[1]);
+                height = atoi(ta.tokens[3]);
+                freeTokens(ta);
+                break;
+            }
+            freeTokens(ta);
+        }
+    }
+    
+    float *data = (float *)malloc(sizeof(float)*3*width*height);
+    
+    printf("Reading fp pixels (%d x %d)\n", width, height);
+    
+    RGBE_ReadPixels_RLE(file, data, width, height);
+
+    float *hFloatImageData = (float*) malloc(sizeof(float)*3*width*height);
+/*
+    for(int i=0; i < width*height; i++) {
+		hFloatImageData[i*3] = convertFloatToHFloat(data[i*3]);
+		hFloatImageData[(i*3)+1] = convertFloatToHFloat(data[(i*3)+1]);
+		hFloatImageData[(i*3)+2] = convertFloatToHFloat(data[(i*3)+2]);
+    }
+*/
+    imageData = (char*) data; //(char*)hFloatImageData;
+    
+ //   free(data);
+	fclose(file);
+    return true;
 }
 
 bool Image::loadPNG(const String& fileName) {

+ 1 - 1
Core/Contents/Source/PolyMaterialManager.cpp

@@ -148,7 +148,7 @@ Texture *MaterialManager::createTextureFromFile(const String& fileName, bool cla
 		if(premultiplyAlphaOnLoad) {
 			image->premultiplyAlpha();
 		}
-		newTexture = createTexture(image->getWidth(), image->getHeight(), image->getPixels(), clamp, createMipmaps);
+		newTexture = createTexture(image->getWidth(), image->getHeight(), image->getPixels(), clamp, createMipmaps, image->getType());
 		newTexture->setResourcePath(fileName);
         resourcePool->addResource(newTexture);
 	} else {

+ 414 - 0
Core/Contents/Source/rgbe.cpp

@@ -0,0 +1,414 @@
+/* THIS CODE CARRIES NO GUARANTEE OF USABILITY OR FITNESS FOR ANY PURPOSE.
+ * WHILE THE AUTHORS HAVE TRIED TO ENSURE THE PROGRAM WORKS CORRECTLY,
+ * IT IS STRICTLY USE AT YOUR OWN RISK.  */
+
+#include "rgbe.h"
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+/* 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  ([email protected])  5/26/95
+ based on code written by Greg Ward
+*/
+
+#ifdef _CPLUSPLUS
+/* define if your compiler understands inline commands */
+#define INLINE inline
+#else
+#define INLINE
+#endif
+
+/* 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
+
+enum rgbe_error_codes {
+  rgbe_read_error,
+  rgbe_write_error,
+  rgbe_format_error,
+  rgbe_memory_error,
+};
+
+/* default error routine.  change this to change error handling */
+static int rgbe_error(int rgbe_error_code, char *msg)
+{
+  switch (rgbe_error_code) {
+  case rgbe_read_error:
+    perror("RGBE read error");
+    break;
+  case rgbe_write_error:
+    perror("RGBE write error");
+    break;
+  case rgbe_format_error:
+    fprintf(stderr,"RGBE bad file format: %s\n",msg);
+    break;
+  default:
+  case rgbe_memory_error:
+    fprintf(stderr,"RGBE error: %s\n",msg);
+  }
+  return RGBE_RETURN_FAILURE;
+}
+
+/* standard conversion from float pixels to rgbe pixels */
+/* note: you can remove the "inline"s if your compiler complains about it */
+static INLINE 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 = 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].            */
+static INLINE void 
+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;
+}
+
+/* default minimal header. modify if you want more information in header */
+int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info)
+{
+  char *programtype = "RGBE";
+
+  if (info && (info->valid & RGBE_VALID_PROGRAMTYPE))
+    programtype = info->programtype;
+  if (fprintf(fp,"#?%s\n",programtype) < 0)
+    return rgbe_error(rgbe_write_error,NULL);
+  /* The #? is to identify file type, the programtype is optional. */
+  if (info && (info->valid & RGBE_VALID_GAMMA)) {
+    if (fprintf(fp,"GAMMA=%g\n",info->gamma) < 0)
+      return rgbe_error(rgbe_write_error,NULL);
+  }
+  if (info && (info->valid & RGBE_VALID_EXPOSURE)) {
+    if (fprintf(fp,"EXPOSURE=%g\n",info->exposure) < 0)
+      return rgbe_error(rgbe_write_error,NULL);
+  }
+  if (fprintf(fp,"FORMAT=32-bit_rle_rgbe\n\n") < 0)
+    return rgbe_error(rgbe_write_error,NULL);
+  if (fprintf(fp, "-Y %d +X %d\n", height, width) < 0)
+    return rgbe_error(rgbe_write_error,NULL);
+  return RGBE_RETURN_SUCCESS;
+}
+
+/* minimal header reading.  modify if you want to parse more information */
+int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info)
+{
+  char buf[128];
+  int found_format;
+  float tempf;
+  int i;
+
+  found_format = 0;
+  if (info) {
+    info->valid = 0;
+    info->programtype[0] = 0;
+    info->gamma = info->exposure = 1.0;
+  }
+  if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == NULL)
+    return rgbe_error(rgbe_read_error,NULL);
+  if ((buf[0] != '#')||(buf[1] != '?')) {
+    /* if you want to require the magic token then uncomment the next line */
+    /*return rgbe_error(rgbe_format_error,"bad initial token"); */
+  }
+  else if (info) {
+    info->valid |= RGBE_VALID_PROGRAMTYPE;
+    for(i=0;i<sizeof(info->programtype)-1;i++) {
+      if ((buf[i+2] == 0) || isspace(buf[i+2]))
+	break;
+      info->programtype[i] = buf[i+2];
+    }
+    info->programtype[i] = 0;
+    if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
+      return rgbe_error(rgbe_read_error,NULL);
+  }
+  for(;;) {
+    if ((buf[0] == 0)||(buf[0] == '\n'))
+      return rgbe_error(rgbe_format_error,"no FORMAT specifier found");
+    else if (strcmp(buf,"FORMAT=32-bit_rle_rgbe\n") == 0)
+      break;       /* format found so break out of loop */
+    else if (info && (sscanf(buf,"GAMMA=%g",&tempf) == 1)) {
+      info->gamma = tempf;
+      info->valid |= RGBE_VALID_GAMMA;
+    }
+    else if (info && (sscanf(buf,"EXPOSURE=%g",&tempf) == 1)) {
+      info->exposure = tempf;
+      info->valid |= RGBE_VALID_EXPOSURE;
+    }
+    if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
+      return rgbe_error(rgbe_read_error,NULL);
+  }
+  if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
+    return rgbe_error(rgbe_read_error,NULL);
+  if (strcmp(buf,"\n") != 0)
+    return rgbe_error(rgbe_format_error,
+		      "missing blank line after FORMAT specifier");
+  if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
+    return rgbe_error(rgbe_read_error,NULL);
+  if (sscanf(buf,"-Y %d +X %d",height,width) < 2)
+    return rgbe_error(rgbe_format_error,"missing image size specifier");
+  return RGBE_RETURN_SUCCESS;
+}
+
+/* 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 */
+int RGBE_WritePixels(FILE *fp, 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 (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1)
+      return rgbe_error(rgbe_write_error,NULL);
+  }
+  return RGBE_RETURN_SUCCESS;
+}
+
+/* simple read routine.  will not correctly handle run length encoding */
+int RGBE_ReadPixels(FILE *fp, float *data, int numpixels)
+{
+  unsigned char rgbe[4];
+
+  while(numpixels-- > 0) {
+    if (fread(rgbe, sizeof(rgbe), 1, fp) < 1)
+      return rgbe_error(rgbe_read_error,NULL);
+    rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],
+	       &data[RGBE_DATA_BLUE],rgbe);
+    data += RGBE_DATA_SIZE;
+  }
+  return RGBE_RETURN_SUCCESS;
+}
+
+/* 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. */
+
+static int RGBE_WriteBytes_RLE(FILE *fp, 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( (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 (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1)
+	return rgbe_error(rgbe_write_error,NULL);
+      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 (fwrite(buf,sizeof(buf[0]),1,fp) < 1)
+	return rgbe_error(rgbe_write_error,NULL);
+      if (fwrite(&data[cur],sizeof(data[0])*nonrun_count,1,fp) < 1)
+	return rgbe_error(rgbe_write_error,NULL);
+      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 (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1)
+	return rgbe_error(rgbe_write_error,NULL);
+      cur += run_count;
+    }
+  }
+  return RGBE_RETURN_SUCCESS;
+#undef MINRUNLENGTH
+}
+
+int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
+			 int num_scanlines)
+{
+  unsigned char rgbe[4];
+  unsigned char *buffer;
+  int i, err;
+
+  if ((scanline_width < 8)||(scanline_width > 0x7fff))
+    /* run length encoding is not allowed so write flat*/
+    return RGBE_WritePixels(fp,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 RGBE_WritePixels(fp,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;
+    if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1) {
+      free(buffer);
+      return rgbe_error(rgbe_write_error,NULL);
+    }
+    for(i=0;i<scanline_width;i++) {
+      float2rgbe(rgbe,data[RGBE_DATA_RED],
+		 data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
+      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(i=0;i<4;i++) {
+      if ((err = RGBE_WriteBytes_RLE(fp,&buffer[i*scanline_width],
+				     scanline_width)) != RGBE_RETURN_SUCCESS) {
+	free(buffer);
+	return err;
+      }
+    }
+  }
+  free(buffer);
+  return RGBE_RETURN_SUCCESS;
+}
+      
+int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
+			int num_scanlines)
+{
+  unsigned char rgbe[4], *scanline_buffer, *ptr, *ptr_end;
+  int i, count;
+  unsigned char buf[2];
+
+  if ((scanline_width < 8)||(scanline_width > 0x7fff))
+    /* run length encoding is not allowed so read flat*/
+    return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines);
+  scanline_buffer = NULL;
+  /* read in each successive scanline */
+  while(num_scanlines > 0) {
+    if (fread(rgbe,sizeof(rgbe),1,fp) < 1) {
+      free(scanline_buffer);
+      return rgbe_error(rgbe_read_error,NULL);
+    }
+    if ((rgbe[0] != 2)||(rgbe[1] != 2)||(rgbe[2] & 0x80)) {
+      /* this file is not run length encoded */
+      rgbe2float(&data[0],&data[1],&data[2],rgbe);
+      data += RGBE_DATA_SIZE;
+      free(scanline_buffer);
+      return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines-1);
+    }
+    if ((((int)rgbe[2])<<8 | rgbe[3]) != scanline_width) {
+      free(scanline_buffer);
+      return rgbe_error(rgbe_format_error,"wrong scanline width");
+    }
+    if (scanline_buffer == NULL)
+      scanline_buffer = (unsigned char *)
+	malloc(sizeof(unsigned char)*4*scanline_width);
+    if (scanline_buffer == NULL) 
+      return rgbe_error(rgbe_memory_error,"unable to allocate buffer space");
+    
+    ptr = &scanline_buffer[0];
+    /* read each of the four channels for the scanline into the buffer */
+    for(i=0;i<4;i++) {
+      ptr_end = &scanline_buffer[(i+1)*scanline_width];
+      while(ptr < ptr_end) {
+	if (fread(buf,sizeof(buf[0])*2,1,fp) < 1) {
+	  free(scanline_buffer);
+	  return rgbe_error(rgbe_read_error,NULL);
+	}
+	if (buf[0] > 128) {
+	  /* a run of the same value */
+	  count = buf[0]-128;
+	  if ((count == 0)||(count > ptr_end - ptr)) {
+	    free(scanline_buffer);
+	    return rgbe_error(rgbe_format_error,"bad scanline data");
+	  }
+	  while(count-- > 0)
+	    *ptr++ = buf[1];
+	}
+	else {
+	  /* a non-run */
+	  count = buf[0];
+	  if ((count == 0)||(count > ptr_end - ptr)) {
+	    free(scanline_buffer);
+	    return rgbe_error(rgbe_format_error,"bad scanline data");
+	  }
+	  *ptr++ = buf[1];
+	  if (--count > 0) {
+	    if (fread(ptr,sizeof(*ptr)*count,1,fp) < 1) {
+	      free(scanline_buffer);
+	      return rgbe_error(rgbe_read_error,NULL);
+	    }
+	    ptr += count;
+	  }
+	}
+      }
+    }
+    /* now convert data from buffer into floats */
+    for(i=0;i<scanline_width;i++) {
+      rgbe[0] = scanline_buffer[i];
+      rgbe[1] = scanline_buffer[i+scanline_width];
+      rgbe[2] = scanline_buffer[i+2*scanline_width];
+      rgbe[3] = scanline_buffer[i+3*scanline_width];
+      rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],
+		 &data[RGBE_DATA_BLUE],rgbe);
+      data += RGBE_DATA_SIZE;
+    }
+    num_scanlines--;
+  }
+  free(scanline_buffer);
+  return RGBE_RETURN_SUCCESS;
+}
+