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 }