1 //-----------------------------------------------------------------------------
2 //
3 // ImageLib Sources
4 // Copyright (C) 2000-2008 by Denton Woods (this file by thakis / Denton)
5 // Last modified: 02/09/2009
6 //
7 // Filename: src-IL/src/il_hdr.c
8 //
9 // Description: Reads/writes a RADIANCE High Dynamic Range Image
10 //
11 //-----------------------------------------------------------------------------
12
13
14 #include "il_internal.h"
15 #ifndef IL_NO_HDR
16 #include "il_hdr.h"
17 #include "il_endian.h"
18
19
20 //! Checks if the file specified in FileName is a valid .hdr file.
ilIsValidHdr(ILconst_string FileName)21 ILboolean ilIsValidHdr(ILconst_string FileName)
22 {
23 ILHANDLE HdrFile;
24 ILboolean bHdr = IL_FALSE;
25
26 if (!iCheckExtension(FileName, IL_TEXT("hdr"))) {
27 ilSetError(IL_INVALID_EXTENSION);
28 return bHdr;
29 }
30
31 HdrFile = iopenr(FileName);
32 if (HdrFile == NULL) {
33 ilSetError(IL_COULD_NOT_OPEN_FILE);
34 return bHdr;
35 }
36
37 bHdr = ilIsValidHdrF(HdrFile);
38 icloser(HdrFile);
39
40 return bHdr;
41 }
42
43
44 //! Checks if the ILHANDLE contains a valid .hdr file at the current position.
ilIsValidHdrF(ILHANDLE File)45 ILboolean ilIsValidHdrF(ILHANDLE File)
46 {
47 ILuint FirstPos;
48 ILboolean bRet;
49
50 iSetInputFile(File);
51 FirstPos = itell();
52 bRet = iIsValidHdr();
53 iseek(FirstPos, IL_SEEK_SET);
54
55 return bRet;
56 }
57
58
59 //! Checks if Lump is a valid .hdr lump.
ilIsValidHdrL(const void * Lump,ILuint Size)60 ILboolean ilIsValidHdrL(const void *Lump, ILuint Size)
61 {
62 iSetInputLump(Lump, Size);
63 return iIsValidHdr();
64 }
65
66
67 // Internal function used to get the .hdr header from the current file.
iGetHdrHead(HDRHEADER * Header)68 ILboolean iGetHdrHead(HDRHEADER *Header)
69 {
70 ILboolean done = IL_FALSE;
71 char a, b;
72 char x[3], y[3]; //changed 20050217: added space for the '\0' char
73 char buff[81]; // 01-19-2009: Added space for the '\0'.
74 ILuint count = 0;
75
76 iread(Header->Signature, 1, 10);
77
78 //skip lines until an empty line is found.
79 //this marks the end of header information,
80 //the next line contains the image's dimension.
81
82 //TODO: read header contents into variables
83 //(EXPOSURE, orientation, xyz correction, ...)
84
85 if (iread(&a, 1, 1) != 1)
86 return IL_FALSE;
87
88 while (!done) {
89 if (iread(&b, 1, 1) != 1)
90 return IL_FALSE;
91 if (b == '\n' && a == '\n')
92 done = IL_TRUE;
93 else
94 a = b;
95 }
96
97 //read dimensions (note that this assumes a somewhat valid image)
98 if (iread(&a, 1, 1) != 1)
99 return IL_FALSE;
100 while (a != '\n') {
101 if (count >= 80) { // Line shouldn't be this long at all.
102 ilSetError(IL_INVALID_FILE_HEADER);
103 return IL_FALSE;
104 }
105 buff[count] = a;
106 if (iread(&a, 1, 1) != 1)
107 return IL_FALSE;
108 ++count;
109 }
110 buff[count] = '\0';
111
112 //note that this is not the 100% correct way to load hdr images:
113 //in a perfect world we would check if there's a +/- X/Y in front
114 //of width and heigth and mirror + rotate the image after decoding
115 //according to that. But HDRShop doesn't do that either (and that's
116 //my reference program :) ) and it's just a rotate and a mirror,
117 //nothing that really changes the appearance of the loaded image...
118 //(The code as it is now assumes that y contains "-Y" and x contains
119 //"+X" after the following line)
120
121 // The 2 has to be in the %s format specifier to prevent buffer overruns.
122 sscanf(buff, "%2s %d %2s %d", y, &Header->Height, x, &Header->Width);
123
124 return IL_TRUE;
125 }
126
127
128 // Internal function to get the header and check it.
iIsValidHdr()129 ILboolean iIsValidHdr()
130 {
131 char Head[10];
132 ILint Read;
133
134 Read = iread(Head, 1, 10);
135 iseek(-Read, IL_SEEK_CUR); // Go ahead and restore to previous state
136 if (Read != 10)
137 return IL_FALSE;
138
139 return
140 strnicmp(Head, "#?RADIANCE", 10) == 0
141 || strnicmp(Head, "#?RGBE", 6) == 0;
142 }
143
144
145 // Internal function used to check if the HEADER is a valid .hdr header.
iCheckHdr(HDRHEADER * Header)146 ILboolean iCheckHdr(HDRHEADER *Header)
147 {
148 return
149 strnicmp(Header->Signature, "#?RADIANCE", 10) == 0
150 || strnicmp(Header->Signature, "#?RGBE", 6) == 0;
151 }
152
153
154 //! Reads a .hdr file
ilLoadHdr(ILconst_string FileName)155 ILboolean ilLoadHdr(ILconst_string FileName)
156 {
157 ILHANDLE HdrFile;
158 ILboolean bHdr = IL_FALSE;
159
160 HdrFile = iopenr(FileName);
161 if (HdrFile == NULL) {
162 ilSetError(IL_COULD_NOT_OPEN_FILE);
163 return bHdr;
164 }
165
166 bHdr = ilLoadHdrF(HdrFile);
167 icloser(HdrFile);
168
169 return bHdr;
170 }
171
172
173 //! Reads an already-opened .hdr file
ilLoadHdrF(ILHANDLE File)174 ILboolean ilLoadHdrF(ILHANDLE File)
175 {
176 ILuint FirstPos;
177 ILboolean bRet;
178
179 iSetInputFile(File);
180 FirstPos = itell();
181 bRet = iLoadHdrInternal();
182 iseek(FirstPos, IL_SEEK_SET);
183
184 return bRet;
185 }
186
187
188 //! Reads from a memory "lump" that contains a .hdr
ilLoadHdrL(const void * Lump,ILuint Size)189 ILboolean ilLoadHdrL(const void *Lump, ILuint Size)
190 {
191 iSetInputLump(Lump, Size);
192 return iLoadHdrInternal();
193 }
194
195
196 // Internal function used to load the .hdr.
iLoadHdrInternal()197 ILboolean iLoadHdrInternal()
198 {
199 HDRHEADER Header;
200 ILfloat *data;
201 ILubyte *scanline;
202 ILuint i, j, e, r, g, b;
203
204 if (iCurImage == NULL) {
205 ilSetError(IL_ILLEGAL_OPERATION);
206 return IL_FALSE;
207 }
208
209 if (!iGetHdrHead(&Header)) {
210 ilSetError(IL_INVALID_FILE_HEADER);
211 return IL_FALSE;
212 }
213 if (!iCheckHdr(&Header)) {
214 //iseek(-(ILint)sizeof(HDRHEAD), IL_SEEK_CUR);
215 ilSetError(IL_INVALID_FILE_HEADER);
216 return IL_FALSE;
217 }
218
219 // Update the current image with the new dimensions
220 if (!ilTexImage(Header.Width, Header.Height, 1, 3, IL_RGB, IL_FLOAT, NULL)) {
221 return IL_FALSE;
222 }
223 iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
224
225 //read image data
226 if (iGetHint(IL_MEM_SPEED_HINT) == IL_FASTEST)
227 iPreCache(iCurImage->Width / 8 * iCurImage->Height);
228
229 data = (ILfloat*)iCurImage->Data;
230 scanline = (ILubyte*)ialloc(Header.Width*4);
231 for (i = 0; i < Header.Height; ++i) {
232 ReadScanline(scanline, Header.Width);
233
234 //convert hdrs internal format to floats
235 for (j = 0; j < 4*Header.Width; j += 4) {
236 ILuint *ee;
237 ILfloat t, *ff;
238 e = scanline[j + 3];
239 r = scanline[j + 0];
240 g = scanline[j + 1];
241 b = scanline[j + 2];
242
243 //t = (float)pow(2.f, ((ILint)e) - 128);
244 if (e != 0)
245 e = (e - 1) << 23;
246
247 // All this just to avoid stric-aliasing warnings...
248 // was: t = *(ILfloat*)&e
249 ee = &e;
250 ff = (ILfloat*)ee;
251 t = *ff;
252
253 data[0] = (r/255.0f)*t;
254 data[1] = (g/255.0f)*t;
255 data[2] = (b/255.0f)*t;
256 data += 3;
257 }
258 }
259 iUnCache();
260 ifree(scanline);
261
262 return ilFixImage();
263 }
264
ReadScanline(ILubyte * scanline,ILuint w)265 void ReadScanline(ILubyte *scanline, ILuint w) {
266 ILubyte *runner;
267 ILuint r, g, b, e, read, shift;
268
269 r = igetc();
270 g = igetc();
271 b = igetc();
272 e = igetc();
273
274 //check if the scanline is in the new format
275 //if so, e, r, g, g are stored separated and are
276 //rle-compressed independently.
277 if (r == 2 && g == 2) {
278 ILuint length = (b << 8) | e;
279 ILuint j, t, k;
280 if (length > w)
281 length = w; //fix broken files
282 for (k = 0; k < 4; ++k) {
283 runner = scanline + k;
284 j = 0;
285 while (j < length) {
286 t = igetc();
287 if (t > 128) { //Run?
288 ILubyte val = igetc();
289 t &= 127;
290 //copy current byte
291 while (t > 0 && j < length) {
292 *runner = val;
293 runner += 4;
294 --t;
295 ++j;
296 }
297 }
298 else { //No Run.
299 //read new bytes
300 while (t > 0 && j < length) {
301 *runner = igetc();
302 runner += 4;
303 --t;
304 ++j;
305 }
306 }
307 }
308 }
309 return; //done decoding a scanline in separated format
310 }
311
312 //if we come here, we are dealing with old-style scanlines
313 shift = 0;
314 read = 0;
315 runner = scanline;
316 while (read < w) {
317 if (read != 0) {
318 r = igetc();
319 g = igetc();
320 b = igetc();
321 e = igetc();
322 }
323
324 //if all three mantissas are 1, then this is a rle
325 //count dword
326 if (r == 1 && g == 1 && b == 1) {
327 ILuint length = e;
328 ILuint j;
329 for (j = length << shift; j > 0 && read < w; --j) {
330 memcpy(runner, runner - 4, 4);
331 runner += 4;
332 ++read;
333 }
334 //if more than one rle count dword is read
335 //consecutively, they are higher order bytes
336 //of the first read value. shift keeps track of
337 //that.
338 shift += 8;
339 }
340 else {
341 runner[0] = r;
342 runner[1] = g;
343 runner[2] = b;
344 runner[3] = e;
345
346 shift = 0;
347 runner += 4;
348 ++read;
349 }
350 }
351 }
352
353
354
355 //! Writes a Hdr file
ilSaveHdr(const ILstring FileName)356 ILboolean ilSaveHdr(const ILstring FileName)
357 {
358 ILHANDLE HdrFile;
359 ILuint HdrSize;
360
361 if (ilGetBoolean(IL_FILE_MODE) == IL_FALSE) {
362 if (iFileExists(FileName)) {
363 ilSetError(IL_FILE_ALREADY_EXISTS);
364 return IL_FALSE;
365 }
366 }
367
368 HdrFile = iopenw(FileName);
369 if (HdrFile == NULL) {
370 ilSetError(IL_COULD_NOT_OPEN_FILE);
371 return IL_FALSE;
372 }
373
374 HdrSize = ilSaveHdrF(HdrFile);
375 iclosew(HdrFile);
376
377 if (HdrSize == 0)
378 return IL_FALSE;
379 return IL_TRUE;
380 }
381
382
383 //! Writes a Hdr to an already-opened file
ilSaveHdrF(ILHANDLE File)384 ILuint ilSaveHdrF(ILHANDLE File)
385 {
386 ILuint Pos;
387 iSetOutputFile(File);
388 Pos = itellw();
389 if (iSaveHdrInternal() == IL_FALSE)
390 return 0; // Error occurred
391 return itellw() - Pos; // Return the number of bytes written.
392 }
393
394
395 //! Writes a Hdr to a memory "lump"
ilSaveHdrL(void * Lump,ILuint Size)396 ILuint ilSaveHdrL(void *Lump, ILuint Size)
397 {
398 ILuint Pos;
399 iSetOutputLump(Lump, Size);
400 Pos = itellw();
401 if (iSaveHdrInternal() == IL_FALSE)
402 return 0; // Error occurred
403 return itellw() - Pos; // Return the number of bytes written.
404 }
405
406
407 //
408 // Much of the saving code is based on the code by Bruce Walter,
409 // available at http://www.graphics.cornell.edu/online/formats/rgbe/.
410 //
411 // The actual source code file is
412 // http://www.graphics.cornell.edu/online/formats/rgbe/rgbe.c
413 //
414
415
416 /* standard conversion from float pixels to rgbe pixels */
417 /* note: you can remove the "inline"s if your compiler complains about it */
418 //static INLINE void
419 static void
float2rgbe(unsigned char rgbe[4],float red,float green,float blue)420 float2rgbe(unsigned char rgbe[4], float red, float green, float blue)
421 {
422 float v;
423 int e;
424
425 v = red;
426 if (green > v) v = green;
427 if (blue > v) v = blue;
428 if (v < 1e-32) {
429 rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
430 }
431 else {
432 v = (float)(frexp(v,&e) * 256.0/v);
433 rgbe[0] = (unsigned char) (red * v);
434 rgbe[1] = (unsigned char) (green * v);
435 rgbe[2] = (unsigned char) (blue * v);
436 rgbe[3] = (unsigned char) (e + 128);
437 }
438 }
439
440
441 typedef struct {
442 ILuint valid; /* indicate which fields are valid */
443 ILbyte programtype[16]; /* listed at beginning of file to identify it
444 * after "#?". defaults to "RGBE" */
445 ILfloat gamma; /* image has already been gamma corrected with
446 * given gamma. defaults to 1.0 (no correction) */
447 ILfloat exposure; /* a value of 1.0 in an image corresponds to
448 * <exposure> watts/steradian/m^2.
449 * defaults to 1.0 */
450 } rgbe_header_info;
451
452 /* flags indicating which fields in an rgbe_header_info are valid */
453 #define RGBE_VALID_PROGRAMTYPE 0x01
454 #define RGBE_VALID_GAMMA 0x02
455 #define RGBE_VALID_EXPOSURE 0x04
456
457 /* offsets to red, green, and blue components in a data (float) pixel */
458 #define RGBE_DATA_RED 0
459 #define RGBE_DATA_GREEN 1
460 #define RGBE_DATA_BLUE 2
461 /* number of floats per pixel */
462 #define RGBE_DATA_SIZE 3
463
464
465 /* default minimal header. modify if you want more information in header */
RGBE_WriteHeader(ILuint width,ILuint height,rgbe_header_info * info)466 ILboolean RGBE_WriteHeader(ILuint width, ILuint height, rgbe_header_info *info)
467 {
468 char *programtype = "RGBE";
469
470 if (info && (info->valid & RGBE_VALID_PROGRAMTYPE))
471 programtype = info->programtype;
472 if (ilprintf("#?%s\n",programtype) < 0)
473 return IL_FALSE;
474 /* The #? is to identify file type, the programtype is optional. */
475 if (info && (info->valid & RGBE_VALID_GAMMA)) {
476 if (ilprintf("GAMMA=%g\n",info->gamma) < 0)
477 return IL_FALSE;
478 }
479 if (info && (info->valid & RGBE_VALID_EXPOSURE)) {
480 if (ilprintf("EXPOSURE=%g\n",info->exposure) < 0)
481 return IL_FALSE;
482 }
483 if (ilprintf("FORMAT=32-bit_rle_rgbe\n\n") < 0)
484 return IL_FALSE;
485 if (ilprintf("-Y %d +X %d\n", height, width) < 0)
486 return IL_FALSE;
487 return IL_TRUE;
488 }
489
490
491 /* simple write routine that does not use run length encoding */
492 /* These routines can be made faster by allocating a larger buffer and
493 fread-ing and iwrite-ing the data in larger chunks */
RGBE_WritePixels(float * data,int numpixels)494 int RGBE_WritePixels(float *data, int numpixels)
495 {
496 unsigned char rgbe[4];
497
498 while (numpixels-- > 0) {
499 float2rgbe(rgbe,data[RGBE_DATA_RED],data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
500 data += RGBE_DATA_SIZE;
501 if (iwrite(rgbe, sizeof(rgbe), 1) < 1)
502 return IL_FALSE;
503 }
504 return IL_TRUE;
505 }
506
507
508 /* The code below is only needed for the run-length encoded files. */
509 /* Run length encoding adds considerable complexity but does */
510 /* save some space. For each scanline, each channel (r,g,b,e) is */
511 /* encoded separately for better compression. */
512
RGBE_WriteBytes_RLE(ILubyte * data,ILuint numbytes)513 ILboolean RGBE_WriteBytes_RLE(ILubyte *data, ILuint numbytes)
514 {
515 #define MINRUNLENGTH 4
516 ILuint cur, beg_run, run_count, old_run_count, nonrun_count;
517 ILubyte buf[2];
518
519 cur = 0;
520 while (cur < numbytes) {
521 beg_run = cur;
522 /* find next run of length at least 4 if one exists */
523 run_count = old_run_count = 0;
524 while((run_count < MINRUNLENGTH) && (beg_run < numbytes)) {
525 beg_run += run_count;
526 old_run_count = run_count;
527 run_count = 1;
528 // 01-25-2009: Moved test for beg_run + run_count first so that it is
529 // tested first. This keeps it from going out of bounds by 1.
530 while((beg_run + run_count < numbytes) && (run_count < 127) &&
531 (data[beg_run] == data[beg_run + run_count]))
532 run_count++;
533 }
534 /* if data before next big run is a short run then write it as such */
535 if ((old_run_count > 1)&&(old_run_count == beg_run - cur)) {
536 buf[0] = 128 + old_run_count; /*write short run*/
537 buf[1] = data[cur];
538 if (iwrite(buf,sizeof(buf[0])*2,1) < 1)
539 return IL_FALSE;
540 cur = beg_run;
541 }
542 /* write out bytes until we reach the start of the next run */
543 while(cur < beg_run) {
544 nonrun_count = beg_run - cur;
545 if (nonrun_count > 128)
546 nonrun_count = 128;
547 buf[0] = nonrun_count;
548 if (iwrite(buf,sizeof(buf[0]),1) < 1)
549 return IL_FALSE;
550 if (iwrite(&data[cur],sizeof(data[0])*nonrun_count,1) < 1)
551 return IL_FALSE;
552 cur += nonrun_count;
553 }
554 /* write out next run if one was found */
555 if (run_count >= MINRUNLENGTH) {
556 buf[0] = 128 + run_count;
557 buf[1] = data[beg_run];
558 if (iwrite(buf,sizeof(buf[0])*2,1) < 1)
559 return IL_FALSE;
560 cur += run_count;
561 }
562 }
563 return IL_TRUE;
564 #undef MINRUNLENGTH
565 }
566
567
568 // Internal function used to save the Hdr.
iSaveHdrInternal()569 ILboolean iSaveHdrInternal()
570 {
571 ILimage *TempImage;
572 rgbe_header_info stHeader;
573 unsigned char rgbe[4];
574 ILubyte *buffer;
575 ILfloat *data;
576 ILuint i;
577 ILboolean bRet;
578
579 if (iCurImage == NULL) {
580 ilSetError(IL_ILLEGAL_OPERATION);
581 return IL_FALSE;
582 }
583
584 stHeader.exposure = 0;
585 stHeader.gamma = 0;
586 stHeader.programtype[0] = 0;
587 stHeader.valid = 0;
588
589 if (iCurImage->Format != IL_UNSIGNED_BYTE) {
590 TempImage = iConvertImage(iCurImage, IL_RGB, IL_FLOAT);
591 if (TempImage == NULL)
592 return IL_FALSE;
593 }
594 else
595 TempImage = iCurImage;
596
597 if (!RGBE_WriteHeader(TempImage->Width, TempImage->Height, &stHeader))
598 return IL_FALSE;
599
600 if (TempImage->Origin == IL_ORIGIN_LOWER_LEFT)
601 iFlipBuffer(TempImage->Data, TempImage->Depth, TempImage->Bps, TempImage->Height);
602 data = (ILfloat*)TempImage->Data;
603
604 if ((TempImage->Width < 8)||(TempImage->Width > 0x7fff)) {
605 /* run length encoding is not allowed so write flat*/
606 bRet = RGBE_WritePixels(data,TempImage->Width*TempImage->Height);
607 if (iCurImage != TempImage)
608 ilCloseImage(TempImage);
609 return bRet;
610 }
611 buffer = (ILubyte*)ialloc(sizeof(ILubyte)*4*TempImage->Width);
612 if (buffer == NULL) {
613 /* no buffer space so write flat */
614 bRet = RGBE_WritePixels(data,TempImage->Width*TempImage->Height);
615 if (iCurImage != TempImage)
616 ilCloseImage(TempImage);
617 return bRet;
618 }
619
620 while(TempImage->Height-- > 0) {
621 rgbe[0] = 2;
622 rgbe[1] = 2;
623 rgbe[2] = TempImage->Width >> 8;
624 rgbe[3] = TempImage->Width & 0xFF;
625 if (iwrite(rgbe, sizeof(rgbe), 1) < 1) {
626 free(buffer);
627 if (iCurImage != TempImage)
628 ilCloseImage(TempImage);
629 return IL_FALSE;
630 }
631
632 for(i=0;i<TempImage->Width;i++) {
633 float2rgbe(rgbe,data[RGBE_DATA_RED],data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
634 buffer[i] = rgbe[0];
635 buffer[i+TempImage->Width] = rgbe[1];
636 buffer[i+2*TempImage->Width] = rgbe[2];
637 buffer[i+3*TempImage->Width] = rgbe[3];
638 data += RGBE_DATA_SIZE;
639 }
640 /* write out each of the four channels separately run length encoded */
641 /* first red, then green, then blue, then exponent */
642 for(i=0;i<4;i++) {
643 if (RGBE_WriteBytes_RLE(&buffer[i*TempImage->Width],TempImage->Width) != IL_TRUE) {
644 ifree(buffer);
645 if (iCurImage != TempImage)
646 ilCloseImage(TempImage);
647 return IL_FALSE;
648 }
649 }
650 }
651 ifree(buffer);
652
653 if (iCurImage != TempImage)
654 ilCloseImage(TempImage);
655 return IL_TRUE;
656 }
657
658
659 #endif//IL_NO_HDR
660