1 /*
2 Copyright (c) 2014-2021 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 /**
30  * High dynamic range images
31  *
32  * Copyright: Timur Gafarov 2013-2021.
33  * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).
34  * Authors: Timur Gafarov
35  */
36 module dlib.image.hdri;
37 
38 import core.stdc..string;
39 import std.math;
40 import dlib.core.memory;
41 import dlib.image.image;
42 import dlib.image.color;
43 import dlib.math.vector;
44 import dlib.math.utils;
45 
46 /// Linear floating-point pixel formats
47 enum FloatPixelFormat: uint
48 {
49     RGBAF32 = 8
50     //TODO:
51     //RGBAF64 = 9
52     //RGBAF16 = 10
53 }
54 
55 /**
56  * HDR image interface
57  */
58 abstract class SuperHDRImage: SuperImage
59 {
60     override @property uint pixelFormat()
61     {
62         return FloatPixelFormat.RGBAF32;
63     }
64 }
65 
66 /**
67  * Extension of standard Image that is based on FloatPixelFormat.RGBAF32
68  */
69 class HDRImage: SuperHDRImage
70 {
71     public:
72 
73     @property uint width()
74     {
75         return _width;
76     }
77 
78     @property uint height()
79     {
80         return _height;
81     }
82 
83     @property uint bitDepth()
84     {
85         return _bitDepth;
86     }
87 
88     @property uint channels()
89     {
90         return _channels;
91     }
92 
93     @property uint pixelSize()
94     {
95         return _pixelSize;
96     }
97 
98     @property ubyte[] data()
99     {
100         return _data;
101     }
102 
103     @property SuperImage dup()
104     {
105         auto res = new HDRImage(_width, _height);
106         res.data[] = data[];
107         return res;
108     }
109 
110     SuperImage createSameFormat(uint w, uint h)
111     {
112         return new HDRImage(w, h);
113     }
114 
115     this(uint w, uint h)
116     {
117         _width = w;
118         _height = h;
119         _bitDepth = 32;
120         _channels = 4;
121         _pixelSize = (_bitDepth / 8) * _channels;
122         allocateData();
123     }
124 
125     Color4f opIndex(int x, int y)
126     {
127         while(x >= _width) x = _width-1;
128         while(y >= _height) y = _height-1;
129         while(x < 0) x = 0;
130         while(y < 0) y = 0;
131 
132         float r, g, b, a;
133         auto dataptr = data.ptr + (y * _width + x) * _pixelSize;
134         memcpy(&r, dataptr, 4);
135         memcpy(&g, dataptr + 4, 4);
136         memcpy(&b, dataptr + 4 * 2, 4);
137         memcpy(&a, dataptr + 4 * 3, 4);
138         return Color4f(r, g, b, a);
139     }
140 
141     Color4f opIndexAssign(Color4f c, int x, int y)
142     {
143         while(x >= _width) x = _width-1;
144         while(y >= _height) y = _height-1;
145         while(x < 0) x = 0;
146         while(y < 0) y = 0;
147 
148         auto dataptr = data.ptr + (y * _width + x) * _pixelSize;
149         memcpy(dataptr, &c.arrayof[0], 4);
150         memcpy(dataptr + 4, &c.arrayof[1], 4);
151         memcpy(dataptr + 4 * 2, &c.arrayof[2], 4);
152         memcpy(dataptr + 4 * 3, &c.arrayof[3], 4);
153 
154         return c;
155     }
156 
157     protected void allocateData()
158     {
159         _data = new ubyte[_width * _height * _pixelSize];
160     }
161 
162     void free()
163     {
164         // Do nothing, let GC delete the object
165     }
166 
167     protected:
168 
169     uint _width;
170     uint _height;
171     uint _bitDepth;
172     uint _channels;
173     uint _pixelSize;
174     ubyte[] _data;
175 }
176 
177 /// Clamp pixels luminance to a specified range
178 SuperImage clamp(SuperImage img, float minv, float maxv)
179 {
180     foreach(x; 0..img.width)
181     foreach(y; 0..img.height)
182     {
183         img[x, y] = img[x, y].clamped(minv, maxv);
184     }
185 
186     return img;
187 }
188 
189 /**
190  * Factory interface for HDR images
191  */
192 interface SuperHDRImageFactory
193 {
194     SuperHDRImage createImage(uint w, uint h);
195 }
196 
197 /**
198  * Factory class for HDR images
199  */
200 class HDRImageFactory: SuperHDRImageFactory
201 {
202     SuperHDRImage createImage(uint w, uint h)
203     {
204         return new HDRImage(w, h);
205     }
206 }
207 
208 private SuperHDRImageFactory _defaultHDRImageFactory;
209 
210 /**
211  * Get default SuperHDRImageFactory singleton
212  */
213 SuperHDRImageFactory defaultHDRImageFactory()
214 {
215     if (!_defaultHDRImageFactory)
216         _defaultHDRImageFactory = new HDRImageFactory();
217     return _defaultHDRImageFactory;
218 }
219 
220 /**
221  * HDRImage that uses dlib.core.memory instead of GC
222  */
223 class UnmanagedHDRImage: HDRImage
224 {
225     override @property SuperImage dup()
226     {
227         auto res = New!(UnmanagedHDRImage)(_width, _height);
228         res.data[] = data[];
229         return res;
230     }
231 
232     override SuperImage createSameFormat(uint w, uint h)
233     {
234         return New!(UnmanagedHDRImage)(w, h);
235     }
236 
237     this(uint w, uint h)
238     {
239         super(w, h);
240     }
241 
242     ~this()
243     {
244         Delete(_data);
245     }
246 
247     protected override void allocateData()
248     {
249         _data = New!(ubyte[])(_width * _height * _pixelSize);
250     }
251 
252     override void free()
253     {
254         Delete(this);
255     }
256 }
257 
258 /**
259  * Factory class for UnmanagedHDRImageFactory
260  */
261 class UnmanagedHDRImageFactory: SuperHDRImageFactory
262 {
263     SuperHDRImage createImage(uint w, uint h)
264     {
265         return New!UnmanagedHDRImage(w, h);
266     }
267 }
268 
269 /// Simple exponentiation tonal compression
270 SuperImage hdrTonemapGamma(SuperHDRImage img, SuperImage output, float gamma)
271 {
272     SuperImage res;
273     if (output)
274         res = output;
275     else
276         res = image(img.width, img.height, img.channels);
277 
278     foreach(y; 0..img.height)
279     foreach(x; 0..img.width)
280     {
281         Color4f c = img[x, y];
282         float r = c.r ^^ gamma;
283         float g = c.g ^^ gamma;
284         float b = c.b ^^ gamma;
285         res[x, y] = Color4f(r, g, b, c.a);
286     }
287 
288     return res;
289 }
290 
291 /// ditto
292 SuperImage hdrTonemapGamma(SuperHDRImage img, float gamma)
293 {
294     return hdrTonemapGamma(img, null, gamma);
295 }
296 
297 /// Reinhard tonal compression
298 SuperImage hdrTonemapReinhard(SuperHDRImage img, SuperImage output, float exposure, float gamma)
299 {
300     SuperImage res;
301     if (output)
302         res = output;
303     else
304         res = image(img.width, img.height, img.channels);
305 
306     foreach(y; 0..img.height)
307     foreach(x; 0..img.width)
308     {
309         Color4f c = img[x, y];
310         Vector3f v = c * exposure;
311         v = v / (v + 1.0f);
312         float r = v.r ^^ gamma;
313         float g = v.g ^^ gamma;
314         float b = v.b ^^ gamma;
315         res[x, y] = Color4f(r, g, b, c.a);
316     }
317 
318     return res;
319 }
320 
321 /// ditto
322 SuperImage hdrTonemapReinhard(SuperHDRImage img, float exposure, float gamma)
323 {
324     return hdrTonemapReinhard(img, null, exposure, gamma);
325 }
326 
327 /// Hable (Uncharted 2) tonal compression
328 SuperImage hdrTonemapHable(SuperHDRImage img, SuperImage output, float exposure, float gamma)
329 {
330     SuperImage res;
331     if (output)
332         res = output;
333     else
334         res = image(img.width, img.height, img.channels);
335 
336     foreach(y; 0..img.height)
337     foreach(x; 0..img.width)
338     {
339         Color4f c = img[x, y];
340         Vector3f v = c * exposure;
341         Vector3f one = Vector3f(1.0f, 1.0f, 1.0f);
342         Vector3f W = Vector3f(11.2f, 11.2f, 11.2f);
343         v = hableFunc(v * 2.0f) * (one / hableFunc(W));
344         float r = v.r ^^ gamma;
345         float g = v.g ^^ gamma;
346         float b = v.b ^^ gamma;
347         res[x, y] = Color4f(r, g, b, c.a);
348     }
349 
350     return res;
351 }
352 
353 /// ditto
354 SuperImage hdrTonemapHable(SuperHDRImage img, float exposure, float gamma)
355 {
356     return hdrTonemapHable(img, null, exposure, gamma);
357 }
358 
359 Vector3f hableFunc(Vector3f x)
360 {
361    return ((x * (x * 0.15f + 0.1f * 0.5f) + 0.2f * 0.02f) / (x * (x * 0.15f + 0.5f) + 0.2f * 0.3f)) - 0.02f / 0.3f;
362 }
363 
364 /// ACES curve tonal compression
365 SuperImage hdrTonemapACES(SuperHDRImage img, SuperImage output, float exposure, float gamma)
366 {
367     SuperImage res;
368     if (output)
369         res = output;
370     else
371         res = image(img.width, img.height, img.channels);
372 
373     float a = 2.51;
374     float b = 0.03;
375     float c = 2.43;
376     float d = 0.59;
377     float e = 0.14;
378 
379     foreach(y; 0..img.height)
380     foreach(x; 0..img.width)
381     {
382         Color4f col = img[x, y];
383         Color4f v = col * exposure * 0.6;
384         v = ((v * (v * a + b)) / (v * (v * c + d) + e)).clamped(0.0, 1.0);
385         res[x, y] = Color4f(
386             v.r ^^ gamma, 
387             v.g ^^ gamma, 
388             v.b ^^ gamma, 
389             col.a);
390     }
391 
392     return res;
393 }
394 
395 /// ditto
396 SuperImage hdrTonemapACES(SuperHDRImage img, float exposure, float gamma)
397 {
398     return hdrTonemapACES(img, null, exposure, gamma);
399 }
400 
401 /// Average luminance tonal compression
402 SuperImage hdrTonemapAverageLuminance(SuperHDRImage img, SuperImage output, float a, float gamma)
403 {
404     SuperImage res;
405     if (output)
406         res = output;
407     else
408         res = image(img.width, img.height, img.channels);
409 
410     float lumAverage = averageLuminance(img);
411     float aOverLumAverage = a / lumAverage;
412 
413     foreach(y; 0..img.height)
414     foreach(x; 0..img.width)
415     {
416         auto col = img[x, y];
417         float Lw = col.luminance;
418         float L = Lw * aOverLumAverage;
419         float Ld = L / (1.0f + L);
420         Color4f nRGB = col / Lw;
421         Color4f dRGB = nRGB * Ld;
422         float r = dRGB.r ^^ gamma;
423         float g = dRGB.g ^^ gamma;
424         float b = dRGB.b ^^ gamma;
425         res[x, y] = Color4f(r, g, b, col.a);
426     }
427 
428     return res;
429 }
430 
431 /// ditto
432 SuperImage hdrTonemapAverageLuminance(SuperHDRImage img, float a, float gamma)
433 {
434     return hdrTonemapAverageLuminance(img, null, a, gamma);
435 }
436 
437 float averageLuminance(SuperHDRImage img)
438 {
439     float sumLuminance = 0.0f;
440 
441     foreach(y; 0..img.height)
442     foreach(x; 0..img.width)
443     {
444         sumLuminance += log(EPSILON + img[x, y].luminance);        
445     }
446 
447     float N = img.width * img.height;
448     float lumAverage = exp(sumLuminance / N); 
449     return lumAverage;
450 }