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