轉自:http://blog.csdn.net/lqhbupt/article/details/7828827
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方面的技術下次再奉上。