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"  |
44 | using namespace tSystem;  |
45 | namespace tImage  |
46 | {  |
47 |   |
48 |   |
49 | void 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 |   |
84 | void 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 |   |
95 | bool 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 |   |
114 | bool 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 |   |
148 | bool 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 |   |
220 | bool 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 |   |
269 | void 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 |   |
287 | bool 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 ((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 |   |
387 | bool 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 |   |
410 | bool 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 |   |
430 | tPixel* tImageHDR::StealPixels()  |
431 | {  |
432 | tPixel* pixels = Pixels;  |
433 | Pixels = nullptr;  |
434 | Width = 0;  |
435 | Height = 0;  |
436 | return pixels;  |
437 | }  |
438 |   |
439 |   |
440 | }  |
441 | |