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 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" 
44using namespace tSystem
45namespace tImage 
46
47 
48 
49void tImageHDR::SetupGammaTables(float gammaCorr
50
51 double gamma = double(gammaCorr); 
52 if (GammaTable
53 return
54 
55 double invGamma = 1.0 / gamma
56 
57 // This table is used to convert from Radiance format to 24-bit. 
58 GammaTable = (uint8(*)[256])malloc((MaxGammaShift+1)*256); 
59 double mult = 1.0/256.0
60 for (int i = 0; i <= MaxGammaShift; i++) 
61
62 for (int j = 0; j < 256; j++) 
63 GammaTable[i][j] = uint8(uint32( 256.0 * pow( (j+0.5)*mult, invGamma ) )); 
64 mult *= 0.5
65
66 
67 // These tables are used to go from 24-bit to a Radiance-encoded value. 
68 MantissaTable = (uint8*)malloc(256); 
69 ExponentTable = (uint8*)malloc(256); 
70 int i = 0
71 mult = 256.0
72 for (int j = 256; j--; ) 
73
74 while ((MantissaTable[j] = uint8(uint32(mult * pow((j+0.5)/256.0, gamma)))) < 128
75
76 i++; 
77 mult *= 2.0
78
79 ExponentTable[j] = i
80
81
82 
83 
84void tImageHDR::CleanupGammaTables() 
85
86 if (MantissaTable) free(MantissaTable); 
87 MantissaTable = nullptr
88 if (ExponentTable) free(ExponentTable); 
89 ExponentTable = nullptr
90 if (GammaTable) free(GammaTable); 
91 GammaTable = nullptr
92
93 
94 
95bool tImageHDR::Load(const tString& hdrFile, float gamma, int exposure
96
97 Clear(); 
98 
99 if (tSystem::tGetFileType(hdrFile) != tSystem::tFileType::HDR
100 return false
101 
102 if (!tFileExists(hdrFile)) 
103 return false
104 
105 int numBytes = 0
106 uint8* hdrFileInMemory = tLoadFile(hdrFile, nullptr, &numBytes, true); 
107 bool success = Set(hdrFileInMemory, numBytes, gamma, exposure); 
108 delete[] hdrFileInMemory
109 
110 return success
111
112 
113 
114bool tImageHDR::LegacyReadRadianceColours(tPixel* scanline, int len
115
116 int rshift = 0
117 int i
118  
119 while (len > 0
120
121 scanline[0].R = GetB(); 
122 scanline[0].G = GetB(); 
123 scanline[0].B = GetB(); 
124 scanline[0].A = i = GetB(); 
125 if (i == EOF
126 return false
127 if (scanline[0].R == 1 && scanline[0].G == 1 && scanline[0].B == 1
128
129 for (i = scanline[0].A << rshift; i > 0; i--) 
130
131 scanline[0] = scanline[-1]; 
132 scanline++; 
133 len--; 
134
135 rshift += 8
136
137 else 
138
139 scanline++; 
140 len--; 
141 rshift = 0
142
143
144 return true
145
146 
147 
148bool tImageHDR::ReadRadianceColours(tPixel* scanline, int len
149
150 int i, j
151 int code, val
152  
153 // Determine if scanline is legacy and needs to be processed the old way. 
154 if ((len < MinScanLen) | (len > MaxScanLen)) 
155 return LegacyReadRadianceColours(scanline, len); 
156 
157 i = GetB(); 
158 if (i == EOF
159 return false
160 if (i != 2
161
162 UngetB(i); 
163 return LegacyReadRadianceColours(scanline, len); 
164
165 scanline[0].G = GetB(); 
166 scanline[0].B = GetB(); 
167 i = GetB(); 
168 if (i == EOF
169 return false
170 
171 if (scanline[0].G != 2 || scanline[0].B & 128
172
173 scanline[0].R = 2
174 scanline[0].A = i
175 return LegacyReadRadianceColours(scanline+1, len-1); 
176
177 if ((scanline[0].B << 8 | i) != len
178 return false
179  
180 // Read each component. 
181 for (i = 0; i < 4; i++) 
182
183 for (j = 0; j < len; ) 
184
185 code = GetB(); 
186 if (code == EOF
187 return false
188 
189 if (code > 128
190
191 // RLE run. 
192 code &= 127
193 val = GetB(); 
194 if (val == EOF
195 return false
196 if (j + code > len
197 return false; // Overrun. 
198 while (code--) 
199 scanline[j++].E[i] = val
200
201 else 
202
203 // New non-RLE colour. 
204 if (j + code > len
205 return false; // Overrun. 
206 while (code--) 
207
208 val = GetB(); 
209 if (val == EOF
210 return false
211 scanline[j++].E[i] = val
212
213
214
215
216 return true
217
218 
219 
220bool tImageHDR::ConvertRadianceToGammaCorrected(tPixel* scan, int len
221
222 if (!GammaTable
223 return false
224 
225 while (len-- > 0
226
227 int expo = scan[0].A - ExpXS
228 if (expo < -MaxGammaShift
229
230 if (expo < -MaxGammaShift-8
231
232 scan[0].MakeBlack(); 
233
234 else 
235
236 int i = (-MaxGammaShift-1) - expo
237 scan[0].R = GammaTable[MaxGammaShift][ ((scan[0].R >> i) + 1) >> 1 ]; 
238 scan[0].G = GammaTable[MaxGammaShift][ ((scan[0].G >> i) + 1) >> 1 ]; 
239 scan[0].B = GammaTable[MaxGammaShift][ ((scan[0].B >> i) + 1) >> 1 ]; 
240
241
242 else if (expo > 0
243
244 if (expo > 8
245
246 scan[0].MakeWhite(); 
247
248 else 
249
250 int i
251 i = (scan[0].R<<1 | 1) << (expo-1); scan[0].R = i > 255 ? 255 : GammaTable[0][i]; 
252 i = (scan[0].G<<1 | 1) << (expo-1); scan[0].G = i > 255 ? 255 : GammaTable[0][i]; 
253 i = (scan[0].B<<1 | 1) << (expo-1); scan[0].B = i > 255 ? 255 : GammaTable[0][i]; 
254
255
256 else 
257
258 scan[0].R = GammaTable[-expo][scan[0].R]; 
259 scan[0].G = GammaTable[-expo][scan[0].G]; 
260 scan[0].B = GammaTable[-expo][scan[0].B]; 
261
262 scan[0].A = ExpXS
263 scan++; 
264
265 return true
266
267 
268 
269void tImageHDR::AdjustExposure(tPixel* scan, int len, int adjust
270
271 // Shift a scanline of colors by 2^adjust. 
272 if (adjust == 0
273 return
274 
275 int minexp = adjust < 0 ? -adjust : 0
276 while (len-- > 0
277
278 if (scan[0].A <= minexp
279 scan[0].MakeZero(); 
280 else 
281 scan[0].A += adjust
282 scan++; 
283
284
285 
286 
287bool tImageHDR::Set(uint8* hdrFileInMemory, int numBytes, float gammaCorr, int exposureAdj
288
289 Clear(); 
290 if ((numBytes <= 0) || !hdrFileInMemory
291 return false
292 
293 SetupGammaTables(gammaCorr); 
294 
295 // Search for the first double 0x0A (linefeed). 
296 // Note that hdrFileInMemory has an extra EOF at the end. The (numBytes+1)th character. 
297 int doubleLFIndex = -1
298 for (int c = 0; c < numBytes; c++) 
299
300 if ((hdrFileInMemory[c] == 0x0A) && (hdrFileInMemory[c+1] == 0x0A)) 
301
302 doubleLFIndex = c
303 break
304
305
306 if (doubleLFIndex == -1
307
308 CleanupGammaTables(); 
309 return false
310
311 
312 // We are not allowed any '\0' characters in the header. Some Mac-generated images have one! 
313 for (int c = 0; c < doubleLFIndex; c++) 
314
315 if (hdrFileInMemory[c] == '\0'
316 hdrFileInMemory[c] = '_'
317
318 
319 char* foundY = tStd::tStrstr((char*)hdrFileInMemory, "-Y"); 
320 char* foundX = tStd::tStrstr((char*)hdrFileInMemory, "+X"); 
321 char* eolY = tStd::tStrchr(foundY, '\n'); 
322 char* eolX = tStd::tStrchr(foundX, '\n'); 
323 if (!eolX || (eolX != eolY)) 
324
325 CleanupGammaTables(); 
326 return false
327
328 *eolX = '\0'
329 tString header((char*)hdrFileInMemory); 
330 *eolX = '\n'
331 ReadP = (uint8*)(eolX+1); 
332 
333 tList<tStringItem> lines
334 tStd::tExplode(lines, header, '\n'); 
335 
336 // Display the header lines. 
337 // for (tStringItem* headerLine = lines.First(); headerLine; headerLine = headerLine->Next()) 
338 // tPrintf("HDR Info: %s\n", headerLine->Chars()); 
339 
340 tStringItem* resLine = lines.Last(); 
341 
342 tList<tStringItem> comps
343 tStd::tExplode(comps, *resLine, ' '); 
344 Height = comps.First()->Next()->AsInt(); 
345 Width = comps.First()->Next()->Next()->Next()->AsInt(); 
346 
347 Pixels = new tPixel[Width*Height]; 
348 tPixel* scanin = new tPixel[Width]; 
349 
350 bool ok = true
351 for (int y = Height-1; y >= 0; y--) 
352
353 ok = ReadRadianceColours(scanin, Width); 
354 if (!ok
355 break
356 
357 AdjustExposure(scanin, Width, exposureAdj); 
358 
359 ok = ConvertRadianceToGammaCorrected(scanin, Width); 
360 if (!ok
361 break
362 
363 WriteP = (uint8*)&Pixels[y * Width]; 
364 for (int x = 0; x < Width; x++) 
365
366 PutB(scanin[x].R); 
367 PutB(scanin[x].G); 
368 PutB(scanin[x].B); 
369 PutB(255); 
370
371
372 
373 CleanupGammaTables(); 
374 delete[] scanin
375 if (!ok
376
377 delete[] Pixels
378 Pixels = nullptr
379 return false
380
381 
382 SrcPixelFormat = tPixelFormat::HDR_RAD
383 return true
384
385 
386 
387bool tImageHDR::Set(tPixel* pixels, int width, int height, bool steal
388
389 Clear(); 
390 if (!pixels || (width <= 0) || (height <= 0)) 
391 return false
392 
393 Width = width
394 Height = height
395 if (steal
396
397 Pixels = pixels
398
399 else 
400
401 Pixels = new tPixel[Width*Height]; 
402 tStd::tMemcpy(Pixels, pixels, Width*Height*sizeof(tPixel)); 
403
404 
405 SrcPixelFormat = tPixelFormat::R8G8B8A8
406 return true
407
408 
409 
410bool tImageHDR::Save(const tString& hdrFile) const 
411
412 tAssertMsg(false, "HDR Save not implemented."); 
413 if (!IsValid()) 
414 return false
415 
416 if (tSystem::tGetFileType(hdrFile) != tSystem::tFileType::HDR
417 return false
418 
419 tFileHandle file = tOpenFile(hdrFile.ConstText(), "wb"); 
420 if (!file
421 return false
422 
423 // Write the data.... 
424 
425 tCloseFile(file); 
426 return true
427
428 
429 
430tPixel* tImageHDR::StealPixels() 
431
432 tPixel* pixels = Pixels
433 Pixels = nullptr
434 Width = 0
435 Height = 0
436 return pixels
437
438 
439 
440
441