转载自:http://blog.csdn.net/zzzGoogle/article/details/1615691
1、HDR简介
HDR的全称是High-DynamicRange(高动态范围)。在此,我们先解释一下什么是DynamicRange(动态范围),动态范围是指图像中所包含的从“最亮”至“最暗”的比值,也就是图像从“最亮”到“最暗”之间灰度划分的等级数;动态范围越大,所能表示的层次越丰富,所包含的色彩空间也越广。那高动态范围(HDR)顾名思义就是从“最亮”到“最暗”可以达到非常高的比值。
在日常生活中我们经常遇到这样的情况:突然从黑暗的房间中走到阳光下,眼睛会无法睁开;清晨阳光会穿透窗帘像光柱般照射入房间;反光度较高的物体在强光下会在周围产生光晕。以上这些生活中随处可见的现象在有HDR以前无法在3D世界中呈现!最大的原因就在于我们使用8~16bit的整数数据,使用8~16bit的整数数据是整个图象处理失真的关键点,所以我们对以往的运算方法做了以下二方面的重大改进:
1、使用16bit、32bit的数据来提高像素数据的精度。既然继续使用8bit的数据来记录像素的特征不能满足HDR数据所需要的高精度运算的要求,在这种情况下,我们考虑使用16bit、32bit的数据记录来提高像素数据的精度都是可以的。使用了更多的数据来保存像素特征之后,无论是像素的对比度还是像素可以体现的色彩数目都有了巨大的提高。
2、图象数据采用浮点数据。HDR真正的巨大变革来自于浮点数据的引入。我们可以采用浮点方式来处理和存放亮度数据,抛弃不准确的整数数据;同时计算机在引入浮点数据来存储象素的各个参数并且在运算的全过程都使用浮点数据,这样就可以有效的提高据的精确度。
那么采用HDR后动态范围最大可以有多大呢?我们看如下的公式,就可以知道我们到底使用了HDR后动态值可以有多大,而动态值的大小直接表现了动态范围的大小:DynamicRange=log10(Max Intensity / Min Intensity)。公式中intensity是指强度,我们对最大亮度除以最低亮度的结果取对数,得到的结果就是动态范围的相对数值。根据公式计算,当我们在亮度通道使用8bit的的情况下,最高亮度255,最低亮度1。那么计算得出的动态范围就是数值约为2.4,加上单位就是24dB。同理可以计算得出16bit的亮度通道的动态范围是数值约是4.8,是使用8bit亮度通道的一倍。理论上在HDR模式下,动态范围的数值最高可以到达76.8。在NVIDIA所使用的OpenEXR中表现出来的HDR动态范围的数值最大值约有12.0,远远高出单纯使用16bit亮度通道的所带来的亮度体验,这是采用了优秀算法的结果。OpenEXR所能实现的最大动态范围已经超过了人眼的9,带来了更加真实的视觉体验。
2、HDRI文件格式介绍(OpenEXR、RadianceRGBE、FloatTIFF)
HDRI(High-DynamicRange Image)就是记录采用了HDR技术的图象数据文件。常用的HDRI文件有OpenEXR、RadianceRGBE、FloatTIFF三种格式。
2.1 OpenEXR文件格式
OpenEXR是由工业光魔(IndustrialLight & Magic)开发的一种HDR标准。OpenEXR文件的扩展名为.exr,常见的OpenEXR文件是FP16(16bitFloat Point,也被称为halfFloat Point)数据图像文件,每个通道的数据类型是FP16,一共四个通道64bpp,每个通道1个bit位用来标志“指数”,5个bit用来存放指数的值,10个bit存放色度坐标(u,v)的尾数,其动态范围从6.14× 10 ^ -5到6.41× 10 ^ 4。
在OpenEXR的算法里面共使用16bit来表示光照数据。虽然看起来和使用16bit亮度通道运算位数相同,但是OpenEXR巧妙的采用了1个bit位用来标志“指数”,5个bit用来存放指数的值,10个bit存放色度坐标的尾数。这样就轻易的解决了浮点数值由于位数少而精度不高的问题。大大的拓宽的在FP16下的动态范围。根据实际的计算结果:在正规化的情况下OpenEXR可以提供和人眼基本相同的动态范围,最暗到最亮是0.00006103515625(6.14 ×10 ^ -5)到65504(6.41 ×10 ^ 4),动态范围是9.03;非正规化条件下,OpenEXR可以提供从最暗到最亮的数值从0.000000059604644775390625(5.96 ×10 ^ -8 )到65504(6.41 ×10 ^ 4),化为动态范围表示就是12。
下面是Still写的OpenEXR读写代码,保存的.exr文件采用Zips压缩编码。
bool COpenExr::Load(const char fileName[], int& width, int& height, float** pixels)
{
std::vector<float> vecpixels;
if(!Load(fileName, width, height, vecpixels))
return false;
int num = width * height * 3;
*pixels = new float[num];
if(NULL == *pixels)
return false;
std::vector<float>::pointer ptr = &vecpixels[0];
memcpy(*pixels, ptr, num * 4);
return true;
}
bool COpenExr::Load(const char fileName[], int& width, int& height, std::vector<float> &pixels)
{
Imf::Array<Imf::Rgba> pixelsdata;
bool bLoad = loadImage(fileName, width, height, pixelsdata);
if(!bLoad) return false;
for(int y = 0; y < height; y++)
{
int i = y * width;
for(int x = 0; x < width; x++)
{
int j = i + x;
const Imf::Rgba &rp = pixelsdata[j];
pixels.push_back( float(rp.r));
pixels.push_back( float(rp.g));
pixels.push_back( float(rp.b));
}
}
return true;
}
bool COpenExr::loadImage (const char fileName[], int& width, int& height, Imf::Array<Imf::Rgba>& pixels)
{
Imf::RgbaInputFile in (fileName);
Imath::Box2i dataWindow = in.dataWindow();
int dw, dh, dx, dy;
width = dw = dataWindow.max.x - dataWindow.min.x + 1;
height = dh = dataWindow.max.y - dataWindow.min.y + 1;
dx = dataWindow.min.x;
dy = dataWindow.min.y;
pixels.resizeErase (dw * dh);
in.setFrameBuffer (pixels - dx - dy * dw, 1, dw);
try
{
in.readPixels (dataWindow.min.y, dataWindow.max.y);
}catch (const exception &e)
{
std::cerr << e.what() << std::endl;
return false;
}
return true;
}
bool COpenExr::Save(const char fileName[], int width, int height, const float* pixels)
{
std::vector<float> vecpixels(pixels, pixels + width * height * 3);
return Save(fileName, width, height, vecpixels);
}
bool COpenExr::Save(const char fileName[], int width, int height, const std::vector<float> pixels)
{
Imf::Array<Imf::Rgba> pixelsdata;
pixelsdata.resizeErase(width * height);
for(int y = 0; y < height; y++)
{
int i = y * width;
for(int x = 0; x < width; x++)
{
int j = i + x;
half r = pixels[j * 3 ];
half g = pixels[j * 3 + 1];
half b = pixels[j * 3 + 2];
pixelsdata[j] = Imf::Rgba(r, g, b);
}
}
return SaveImage(fileName, width, height, pixelsdata);
}
bool COpenExr::SaveImage(const char fileName[], int width, int height, const Imf::Array<Imf::Rgba> &pixels)
{
Imf::RgbaOutputFile file (fileName, width, height);
file.setFrameBuffer(pixels, 1, width);
try
{
file.writePixels(height);
}catch(const exception &e)
{
std::cerr<< e.what() <<std::endl;
return false;
}
return true;
}
官方库链接地址:http://www.openexr.com/
2.2 Radiance RGBE文件格式
RGBE文件的扩展名为.hdr,RGBE正式名称为RadianceRGBE格式。这个本来是BR、FR等作为radiance材质的一种格式,也叫做radiancemap,后来成为流行的一种HDR格式。所谓E,就是指数。RadianceRGBE文件每个通道为8bitBYTE数据类型,4个通道一共是32bit。RGBE可以使用RLE压缩编码压缩,也可以不压缩。由文件头、RGBE数据组成。
文件头如下:
类型输出格式
char programtype[16]; //#?Radiance/n#Generated by still/n
float gamma; //1.0
float exposure; //1.0
字符串常量//FORMAT=32-bit_rle_rgbe/n/n
int nWidth, int nHeight //-Y nHeight +X nWidth/n
RGBE数据与HDRFP32(RGB)相互转换公式如下:
1、rgbe->FP32(RGB)
如果e为0, R = G= B = 0.0,否则:
R = r * 2^(e – 128 - 8);
G = g * 2^(e – 128 - 8);
B = b * 2^(e – 128 - 8);
2、FP32(RGB)-> rgbe
v = max(R, G, B);
如果v< 1e-32, r = g = b = e = 0, 否则:
将v用科学计算法表示成v = m * 2 ^ n ( 0 < m < 1):
r = R * m * 256.0/v;
g = G * m * 256.0/v;
b = B * m * 256.0/v;
e = n + 128;
Still注:
1、我们一般说HDR采用FP32,指的是HDR图象运算时候的内存数据类型,而RadianceRGBE文件采用8bitBYTE类型存储HDR数据。也就是说打开RadianceRGBE文件,要使用上面的公式1将RadianceRGBE文件的8bitBYTE文件数据转换为FP32的HDR内存数据进行运算;保存为RadianceRGBE文件时,要使用上面的公式2将HDR的FP32内存数据转换为RadianceRGBE的8bitBYTE文件数据进行保存。同理,OpenEXR文件的读写也存在将其FP16的文件数据到HDR的 FP32图象数据的转换;而下面将要讲的FloatTiff是不需要进行数据转换,直接将HDR的FP 32图象数据保存到TIFF文件中即可。
2、Radiance有多种文件格式,其官方库包含内容比较复杂,所以,实际的读写没有使用其官方库,而是使用了网络上一个简单的C语言读写类,Still并对其进行了部分修改(在文件头写入“Generatedby Still”)。
读写类链接地址:http://www.graphics.cornell.edu/~bjw/rgbe.html
官方库链接地址:http://radsite.lbl.gov/radiance/
****************************************************************************************
rgbe.txt - description of interface
rgbe.h - header file
rgbe.c - C code
Where can I find more information?
See "Real Pixels" by Greg Ward in Graphics Gems II .
*****************************************************************************************
Here's the minimal code for using these files. sampleminimal writing code:
f = fopen(image_filename,"wb");
RGBE_WriteHeader(f,image_width,image_height,NULL);
RGBE_WritePixels(f,image,image_width*image_height);
fclose(f);
For run length encoding instead of RGBE_WritePixels, useRGBE_WritePixels_RLE(f,image,image_width,image_height). sample minimal readingcode:
f = fopen(image_filename,"rb");
RGBE_ReadHeader(f,&image_width,&image_height,NULL);
image = (float *)malloc(sizeof(float)*3*image_width*image_height);
RGBE_ReadPixels_RLE(f,image,image_width,image_height);
fclose(f);
******************************************************************************************
//rgbe.h--header file
#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 */
//rgbe.c -- C code
/* 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 <malloc.h>
#include <string.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 (bjw@graphics.cornell.edu) 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;
}
2.3 FloatTiff文件格式
Tiff文件的扩展名为.tif(.tiff),FloatTiff每个通道为FP32(32bit Float Point)类型,一共3个通道96bpp。用Tiff文件存储HDR数据,直接将HDR的FP32保存到TIFF文件中,有官方库可以利用。下面是Still写的代码样例,HDR数据我采用的是LZW压缩编码:
bool CFloatTiff::Load(const char fileName[], int& width, int& height, float** pixels)
{
TIFF* fp = NULL;
if((fp = TIFFOpen(fileName, "r")) == NULL)
return false;
//获取信息
uint16 bps, spp, datatype, photometric, compression, planarconfig, fillorder;
//每个通道占据的数据位数
if( (TIFFGetField(fp, TIFFTAG_BITSPERSAMPLE, &bps) == 0) || (bps != 32))
return false;
//每个象素的通道数目
if((TIFFGetField(fp, TIFFTAG_SAMPLESPERPIXEL, &spp) == 0) || (spp != 3))
return false;
//每个通道的数据类型
if((TIFFGetField(fp, TIFFTAG_SAMPLEFORMAT, &datatype) == 0) || (datatype != AMPLEFORMAT_IEEEFP))
return false;
//图像的数据采用的颜色模型
if((TIFFGetField(fp, TIFFTAG_PHOTOMETRIC, &photometric) == 0) || (photometric != PHOTOMETRIC_RGB))
return false;
TIFFGetField(fp, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(fp, TIFFTAG_IMAGELENGTH, &height);
int num = width * height * 3;
*pixels = new float[num];
if(NULL == *pixels)
return false;
if( TIFFReadEncodedStrip(fp, 0, *pixels, width * height * 3 * 4) == -1)
return false;
TIFFClose(fp);
return true;
}
bool CFloatTiff::Save(const char fileName[], int width, int height, const float* pixels)
{
if(NULL == pixels)
return false;
TIFF *fp = NULL;
if((fp = TIFFOpen(fileName, "w")) == NULL)
return false;
TIFFSetField(fp, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(fp, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(fp, TIFFTAG_COMPRESSION, COMPRESSION_LZW);//COMPRESSION_DEFLATE;
TIFFSetField(fp, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(fp, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField(fp, TIFFTAG_BITSPERSAMPLE, 32);
TIFFSetField(fp, TIFFTAG_SAMPLESPERPIXEL, 3);
TIFFSetField(fp, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP);
if(TIFFWriteEncodedStrip(fp, 0, const_cast<float*>(pixels), width * height * 3 * 4) == -1)
return false;
TIFFClose(fp);
return true;
}
官方库链接地址:http://www.remotesensing.org/libtiff/
Still注:
1、这篇文章的基础知识大部分来自:《光与影的魔术棒——HDR技术解析》http://www.cqumzh.cn/topic_show.php?tid=200271。
2、这段时间工作比较忙,关于OpenEXR文件格式的详细介绍需要翻译相关文档,而且这部分内容是05年接触的,重新总结需要一些时间;还有HDR合成、ToneMapping方面的技术下次再奉上。