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