1// tImageHDR.cpp 
2// 
3// This knows how to load and save a Radiance High Dynamic Range image (.hdr or .rgbe). It knows the details of the hdr 
4// file format and loads the data into a tPixel array. These tPixels may be 'stolen' by the tPicture's constructor if 
5// an HDR file is specified. After the array is stolen the tImageHDR is invalid. This is purely for performance. 
6// 
7// Copyright (c) 2020, 2022-2024 Tristan Grimmer. 
8// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
9// granted, provided that the above copyright notice and this permission notice appear in all copies. 
10// 
11// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
12// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
13// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
14// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
15// PERFORMANCE OF THIS SOFTWARE. 
16// 
17// The conversion code for hdr data is a modified version of code from Radiance. Here is the licence. 
18// 
19// The Radiance Software License, Version 1.0 
20// Copyright (c) 1990 - 2015 The Regents of the University of California, through Lawrence Berkeley National Laboratory. All rights reserved. 
21// 
22// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 
23// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
24// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the 
25// documentation and/or other materials provided with the distribution. 
26// 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: 
27// "This product includes Radiance software (http://radsite.lbl.gov/) developed by the Lawrence Berkeley National Laboratory (http://www.lbl.gov/)." 
28// Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear. 
29// 4. The names "Radiance," "Lawrence Berkeley National Laboratory" and "The Regents of the University of California" must not be used to endorse 
30// or promote products derived from this software without prior written permission. For written permission, please contact radiance@radsite.lbl.gov. 
31// 5. Products derived from this software may not be called "Radiance", nor may "Radiance" appear in their name, without prior written permission of 
32// Lawrence Berkeley National Laboratory. 
33// 
34// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
35// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Lawrence Berkeley National Laboratory OR ITS CONTRIBUTORS BE LIABLE FOR ANY 
36// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
37// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
38// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
39 
40#include <Foundation/tStandard.h> 
41#include <Foundation/tString.h> 
42#include <System/tFile.h> 
43#include "Image/tImageHDR.h" 
44#include "Image/tPicture.h" 
45using namespace tSystem
46namespace tImage 
47
48 
49 
50void tImageHDR::SetupGammaTables(float gammaCorr
51
52 double gamma = double(gammaCorr); 
53 if (GammaTable
54 return
55 
56 double invGamma = 1.0 / gamma
57 
58 // This table is used to convert from Radiance format to 24-bit. 
59 GammaTable = (uint8(*)[256])malloc(size: (MaxGammaShift+1)*256); 
60 double mult = 1.0/256.0
61 for (int i = 0; i <= MaxGammaShift; i++) 
62
63 for (int j = 0; j < 256; j++) 
64 GammaTable[i][j] = uint8(uint32( 256.0 * pow( x: (j+0.5)*mult, y: invGamma ) )); 
65 mult *= 0.5
66
67 
68 // These tables are used to go from 24-bit to a Radiance-encoded value. 
69 MantissaTable = (uint8*)malloc(size: 256); 
70 ExponentTable = (uint8*)malloc(size: 256); 
71 int i = 0
72 mult = 256.0
73 for (int j = 256; j--; ) 
74
75 while ((MantissaTable[j] = uint8(uint32(mult * pow(x: (j+0.5)/256.0, y: gamma)))) < 128
76
77 i++; 
78 mult *= 2.0
79
80 ExponentTable[j] = i
81
82
83 
84 
85void tImageHDR::CleanupGammaTables() 
86
87 if (MantissaTable) free(ptr: MantissaTable); 
88 MantissaTable = nullptr
89 if (ExponentTable) free(ptr: ExponentTable); 
90 ExponentTable = nullptr
91 if (GammaTable) free(ptr: GammaTable); 
92 GammaTable = nullptr
93
94 
95 
96bool tImageHDR::LegacyReadRadianceColours(tPixel4b* scanline, int len
97
98 int rshift = 0
99 int i
100  
101 while (len > 0
102
103 scanline[0].R = GetB(); 
104 scanline[0].G = GetB(); 
105 scanline[0].B = GetB(); 
106 scanline[0].A = i = GetB(); 
107 if (i == EOF
108 return false
109 if (scanline[0].R == 1 && scanline[0].G == 1 && scanline[0].B == 1
110
111 for (i = scanline[0].A << rshift; i > 0; i--) 
112
113 scanline[0] = scanline[-1]; 
114 scanline++; 
115 len--; 
116
117 rshift += 8
118
119 else 
120
121 scanline++; 
122 len--; 
123 rshift = 0
124
125
126 return true
127
128 
129 
130bool tImageHDR::ReadRadianceColours(tPixel4b* scanline, int len
131
132 int i, j
133 int code, val
134  
135 // Determine if scanline is legacy and needs to be processed the old way. 
136 if ((len < MinScanLen) | (len > MaxScanLen)) 
137 return LegacyReadRadianceColours(scanline, len); 
138 
139 i = GetB(); 
140 if (i == EOF
141 return false
142 if (i != 2
143
144 UngetB(v: i); 
145 return LegacyReadRadianceColours(scanline, len); 
146
147 scanline[0].G = GetB(); 
148 scanline[0].B = GetB(); 
149 i = GetB(); 
150 if (i == EOF
151 return false
152 
153 if (scanline[0].G != 2 || scanline[0].B & 128
154
155 scanline[0].R = 2
156 scanline[0].A = i
157 return LegacyReadRadianceColours(scanline: scanline+1, len: len-1); 
158
159 if ((scanline[0].B << 8 | i) != len
160 return false
161  
162 // Read each component. 
163 for (i = 0; i < 4; i++) 
164
165 for (j = 0; j < len; ) 
166
167 code = GetB(); 
168 if (code == EOF
169 return false
170 
171 if (code > 128
172
173 // RLE run. 
174 code &= 127
175 val = GetB(); 
176 if (val == EOF
177 return false
178 if (j + code > len
179 return false; // Overrun. 
180 while (code--) 
181 scanline[j++].E[i] = val
182
183 else 
184
185 // New non-RLE colour. 
186 if (j + code > len
187 return false; // Overrun. 
188 while (code--) 
189
190 val = GetB(); 
191 if (val == EOF
192 return false
193 scanline[j++].E[i] = val
194
195
196
197
198 return true
199
200 
201 
202bool tImageHDR::ConvertRadianceToGammaCorrected(tPixel4b* scan, int len
203
204 if (!GammaTable
205 return false
206 
207 while (len-- > 0
208
209 int expo = scan[0].A - ExpXS
210 if (expo < -MaxGammaShift
211
212 if (expo < -MaxGammaShift-8
213
214 scan[0].MakeBlack(); 
215
216 else 
217
218 int i = (-MaxGammaShift-1) - expo
219 scan[0].R = GammaTable[MaxGammaShift][ ((scan[0].R >> i) + 1) >> 1 ]; 
220 scan[0].G = GammaTable[MaxGammaShift][ ((scan[0].G >> i) + 1) >> 1 ]; 
221 scan[0].B = GammaTable[MaxGammaShift][ ((scan[0].B >> i) + 1) >> 1 ]; 
222
223
224 else if (expo > 0
225
226 if (expo > 8
227
228 scan[0].MakeWhite(); 
229
230 else 
231
232 int i
233 i = (scan[0].R<<1 | 1) << (expo-1); scan[0].R = i > 255 ? 255 : GammaTable[0][i]; 
234 i = (scan[0].G<<1 | 1) << (expo-1); scan[0].G = i > 255 ? 255 : GammaTable[0][i]; 
235 i = (scan[0].B<<1 | 1) << (expo-1); scan[0].B = i > 255 ? 255 : GammaTable[0][i]; 
236
237
238 else 
239
240 scan[0].R = GammaTable[-expo][scan[0].R]; 
241 scan[0].G = GammaTable[-expo][scan[0].G]; 
242 scan[0].B = GammaTable[-expo][scan[0].B]; 
243
244 scan[0].A = ExpXS
245 scan++; 
246
247 return true
248
249 
250 
251void tImageHDR::AdjustExposure(tPixel4b* scan, int len, int adjust
252
253 // Shift a scanline of colors by 2^adjust. 
254 if (adjust == 0
255 return
256 
257 int minexp = adjust < 0 ? -adjust : 0
258 while (len-- > 0
259
260 if (scan[0].A <= minexp
261 scan[0].MakeZero(); 
262 else 
263 scan[0].A += adjust
264 scan++; 
265
266
267 
268 
269bool tImageHDR::Load(const tString& hdrFile, const LoadParams& loadParams
270
271 Clear(); 
272 
273 if (tSystem::tGetFileType(file: hdrFile) != tSystem::tFileType::HDR
274 return false
275 
276 if (!tFileExists(file: hdrFile)) 
277 return false
278 
279 int numBytes = 0
280 uint8* hdrFileInMemory = tLoadFile(file: hdrFile, buffer: nullptr, fileSize: &numBytes, appendEOF: true); 
281 bool success = Load(hdrFileInMemory, numBytes, loadParams); 
282 delete[] hdrFileInMemory
283 
284 return success
285
286 
287 
288bool tImageHDR::Load(uint8* hdrFileInMemory, int numBytes, const LoadParams& loadParams
289
290 Clear(); 
291 if ((numBytes <= 0) || !hdrFileInMemory
292 return false
293 
294 float gammaCorr = loadParams.Gamma
295 int exposureAdj = loadParams.Exposure
296 SetupGammaTables(gammaCorr); 
297 
298 // Search for the first double 0x0A (linefeed). 
299 // Note that hdrFileInMemory has an extra EOF at the end. The (numBytes+1)th character. 
300 int doubleLFIndex = -1
301 for (int c = 0; c < numBytes; c++) 
302
303 if ((hdrFileInMemory[c] == 0x0A) && (hdrFileInMemory[c+1] == 0x0A)) 
304
305 doubleLFIndex = c
306 break
307
308
309 if (doubleLFIndex == -1
310
311 CleanupGammaTables(); 
312 return false
313
314 
315 // We are not allowed any '\0' characters in the header. Some Mac-generated images have one! 
316 for (int c = 0; c < doubleLFIndex; c++) 
317
318 if (hdrFileInMemory[c] == '\0'
319 hdrFileInMemory[c] = '_'
320
321 
322 char* foundY = tStd::tStrstr(s: (char*)hdrFileInMemory, r: "-Y"); 
323 char* foundX = tStd::tStrstr(s: (char*)hdrFileInMemory, r: "+X"); 
324 char* eolY = tStd::tStrchr(s: foundY, c: '\n'); 
325 char* eolX = tStd::tStrchr(s: foundX, c: '\n'); 
326 if (!eolX || (eolX != eolY)) 
327
328 CleanupGammaTables(); 
329 return false
330
331 *eolX = '\0'
332 tString header((char*)hdrFileInMemory); 
333 *eolX = '\n'
334 ReadP = (uint8*)(eolX+1); 
335 
336 tList<tStringItem> lines
337 tStd::tExplode(components&: lines, src: header, divider: '\n'); 
338 // Display the header lines. 
339 // for (tStringItem* headerLine = lines.First(); headerLine; headerLine = headerLine->Next()) 
340 // tPrintf("HDR Info: %s\n", headerLine->Chr()); 
341 
342 tStringItem* resLine = lines.Last(); 
343 
344 tList<tStringItem> comps
345 tStd::tExplode(components&: comps, src: *resLine, divider: ' '); 
346 Height = comps.First()->Next()->AsInt(); 
347 Width = comps.First()->Next()->Next()->Next()->AsInt(); 
348 
349 Pixels = new tPixel4b[Width*Height]; 
350 tPixel4b* scanin = new tPixel4b[Width]; 
351 
352 bool ok = true
353 for (int y = Height-1; y >= 0; y--) 
354
355 ok = ReadRadianceColours(scanline: scanin, len: Width); 
356 if (!ok
357 break
358 
359 AdjustExposure(scan: scanin, len: Width, adjust: exposureAdj); 
360 
361 ok = ConvertRadianceToGammaCorrected(scan: scanin, len: Width); 
362 if (!ok
363 break
364 
365 WriteP = (uint8*)&Pixels[y * Width]; 
366 for (int x = 0; x < Width; x++) 
367
368 PutB(v: scanin[x].R); 
369 PutB(v: scanin[x].G); 
370 PutB(v: scanin[x].B); 
371 PutB(v: 255); 
372
373
374 
375 CleanupGammaTables(); 
376 delete[] scanin
377 if (!ok
378
379 delete[] Pixels
380 Pixels = nullptr
381 return false
382
383 
384 PixelFormatSrc = tPixelFormat::RADIANCE
385 PixelFormat = tPixelFormat::R8G8B8A8
386 ColourProfileSrc = tColourProfile::HDRa; // The source pixels are HDR. 
387 ColourProfile = tColourProfile::sRGB; // The decoded pixels are in sRGB space. 
388 return true
389
390 
391 
392bool tImageHDR::Set(tPixel4b* pixels, int width, int height, bool steal
393
394 Clear(); 
395 if (!pixels || (width <= 0) || (height <= 0)) 
396 return false
397 
398 Width = width
399 Height = height
400 if (steal
401
402 Pixels = pixels
403
404 else 
405
406 Pixels = new tPixel4b[Width*Height]; 
407 tStd::tMemcpy(dest: Pixels, src: pixels, numBytes: Width*Height*sizeof(tPixel4b)); 
408
409 
410 PixelFormatSrc = tPixelFormat::R8G8B8A8
411 PixelFormat = tPixelFormat::R8G8B8A8
412 ColourProfileSrc = tColourProfile::sRGB; // We assume pixels must be sRGB. 
413 ColourProfile = tColourProfile::sRGB
414 
415 return true
416
417 
418 
419bool tImageHDR::Set(tFrame* frame, bool steal
420
421 Clear(); 
422 if (!frame || !frame->IsValid()) 
423 return false
424  
425 PixelFormatSrc = frame->PixelFormatSrc
426 PixelFormat = tPixelFormat::R8G8B8A8
427 ColourProfileSrc = tColourProfile::sRGB; // We assume frame must be sRGB. 
428 ColourProfile = tColourProfile::sRGB
429 
430 Set(pixels: frame->GetPixels(steal), width: frame->Width, height: frame->Height, steal); 
431 if (steal
432 delete frame
433 
434 return true
435
436 
437 
438bool tImageHDR::Set(tPicture& picture, bool steal
439
440 Clear(); 
441 if (!picture.IsValid()) 
442 return false
443 
444 PixelFormatSrc = picture.PixelFormatSrc
445 PixelFormat = tPixelFormat::R8G8B8A8
446 // We don't know colour profile of tPicture. 
447 
448 // This is worth some explanation. If steal is true the picture becomes invalid and the 
449 // 'set' call will steal the stolen pixels. If steal is false GetPixels is called and the 
450 // 'set' call will memcpy them out... which makes sure the picture is still valid after and 
451 // no-one is sharing the pixel buffer. We don't check the success of 'set' because it must 
452 // succeed if picture was valid. 
453 tPixel4b* pixels = steal ? picture.StealPixels() : picture.GetPixels(); 
454 bool success = Set(pixels, width: picture.GetWidth(), height: picture.GetHeight(), steal); 
455 tAssert(success); 
456 return true
457
458 
459 
460tFrame* tImageHDR::GetFrame(bool steal
461
462 if (!IsValid()) 
463 return nullptr
464 
465 tFrame* frame = new tFrame(); 
466 frame->PixelFormatSrc = PixelFormatSrc
467 
468 if (steal
469
470 frame->StealFrom(src: Pixels, width: Width, height: Height); 
471 Pixels = nullptr
472
473 else 
474
475 frame->Set(srcPixels: Pixels, width: Width, height: Height); 
476
477 
478 return frame
479
480 
481 
482tPixel4b* tImageHDR::StealPixels() 
483
484 tPixel4b* pixels = Pixels
485 Pixels = nullptr
486 Clear(); 
487 return pixels
488
489 
490 
491
492