1 /** 2 Wrapper for stb_image_resize.h port. 3 Makes it trivial to use. 4 Copyright: (c) Guillaume Piolat (2021) 5 */ 6 module dplug.graphics.resizer; 7 8 import std.math: PI; 9 import dplug.core.math; 10 import dplug.core.vec; 11 import dplug.graphics.color; 12 import dplug.graphics.image; 13 14 import dplug.graphics.stb_image_resize; 15 16 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 17 { 18 import stb_image_resize2; 19 } 20 21 22 /// Image resizer. 23 /// To minimize CPU, it is advised to reuse that object for similar resize. 24 /// To minimize memory allocation, it is advised to reuse that object even across different resize. 25 struct ImageResizer 26 { 27 public: 28 nothrow: 29 @nogc: 30 31 @disable this(this); 32 33 /** 34 * Function resizes image. There are several other function for specialized treatment. 35 * 36 * Params: 37 * input Input image. 38 * output Output image. 39 */ 40 void resizeImageGeneric(ImageRef!RGBA input, ImageRef!RGBA output) 41 { 42 if (sameSizeResize(input, output)) 43 return; 44 45 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 46 { 47 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 48 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 49 STBIR_RGBA, 50 STBIR_TYPE_UINT8_SRGB, 51 STBIR_EDGE_CLAMP, 52 STBIR_FILTER_DEFAULT); 53 assert(res); 54 } 55 else 56 { 57 stbir_filter filter = STBIR_FILTER_DEFAULT; 58 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 59 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 60 61 62 4, filter, &alloc_context); 63 assert(res); 64 } 65 } 66 ///ditto 67 void resizeImageGeneric(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 68 { 69 if (sameSizeResize(input, output)) 70 return; 71 72 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 73 { 74 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 75 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 76 STBIR_RGBA, 77 STBIR_TYPE_UINT16, 78 STBIR_EDGE_CLAMP, 79 STBIR_FILTER_DEFAULT); 80 assert(res); 81 } 82 else 83 { 84 stbir_filter filter = STBIR_FILTER_DEFAULT; 85 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 86 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 87 assert(res); 88 } 89 } 90 91 ///ditto 92 void resizeImageSmoother(ImageRef!RGBA input, ImageRef!RGBA output) 93 { 94 if (sameSizeResize(input, output)) 95 return; 96 97 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 98 { 99 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 100 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 101 STBIR_RGBA, 102 STBIR_TYPE_UINT16, 103 STBIR_EDGE_CLAMP, 104 STBIR_FILTER_CUBICBSPLINE); 105 assert(res); 106 } 107 else 108 { 109 // suitable when depth is encoded in a RGB8 triplet, such as in UIImageKnob 110 stbir_filter filter = STBIR_FILTER_CUBICBSPLINE; 111 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 112 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 113 assert(res); 114 } 115 } 116 117 ///ditto 118 // FUTURE: deprecate, not nearest at all 119 void resizeImageNearest(ImageRef!RGBA input, ImageRef!RGBA output) // same but with nearest filter 120 { 121 if (sameSizeResize(input, output)) 122 return; 123 124 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 125 { 126 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 127 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 128 STBIR_RGBA, 129 STBIR_TYPE_UINT16, 130 STBIR_EDGE_CLAMP, 131 STBIR_FILTER_BOX); 132 assert(res); 133 } 134 else 135 { 136 stbir_filter filter = STBIR_FILTER_BOX; 137 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 138 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 139 assert(res); 140 } 141 } 142 143 ///ditto 144 void resizeImageGeneric(ImageRef!L16 input, ImageRef!L16 output) 145 { 146 if (sameSizeResize(input, output)) 147 return; 148 149 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 150 { 151 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 152 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 153 STBIR_1CHANNEL, 154 STBIR_TYPE_UINT16, 155 STBIR_EDGE_CLAMP, 156 STBIR_FILTER_DEFAULT); 157 assert(res); 158 } 159 else 160 { 161 stbir_filter filter = STBIR_FILTER_DEFAULT; 162 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 163 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, filter, &alloc_context); 164 assert(res); 165 } 166 } 167 168 ///ditto 169 void resizeImageDepth(ImageRef!L16 input, ImageRef!L16 output) 170 { 171 if (sameSizeResize(input, output)) 172 return; 173 174 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 175 { 176 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 177 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 178 STBIR_1CHANNEL, 179 STBIR_TYPE_UINT16, 180 STBIR_EDGE_CLAMP, 181 STBIR_FILTER_MKS_2021); 182 assert(res); 183 } 184 else 185 { 186 stbir_filter filter = STBIR_FILTER_MKS_2021; 187 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 188 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, filter, &alloc_context); 189 assert(res); 190 } 191 } 192 193 ///ditto 194 void resizeImageDepth(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 195 { 196 // Note: this function is intended for those images that contain depth despite having 4 channels. 197 if (sameSizeResize(input, output)) 198 return; 199 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 200 { 201 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 202 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 203 STBIR_4CHANNEL, 204 STBIR_TYPE_UINT16, 205 STBIR_EDGE_CLAMP, 206 STBIR_FILTER_MKS_2021); 207 assert(res); 208 } 209 else 210 { 211 stbir_filter filter = STBIR_FILTER_MKS_2021; 212 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 213 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 214 assert(res); 215 } 216 } 217 218 ///ditto 219 void resizeImageCoverage(ImageRef!L8 input, ImageRef!L8 output) 220 { 221 if (sameSizeResize(input, output)) 222 return; 223 224 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 225 { 226 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 227 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 228 STBIR_1CHANNEL, 229 STBIR_TYPE_UINT8, 230 STBIR_EDGE_CLAMP, 231 STBIR_FILTER_DEFAULT); 232 assert(res); 233 } 234 else 235 { 236 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 237 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, STBIR_FILTER_DEFAULT, &alloc_context); 238 assert(res); 239 } 240 } 241 242 ///ditto 243 void resizeImage_sRGBNoAlpha(ImageRef!RGBA input, ImageRef!RGBA output) 244 { 245 if (sameSizeResize(input, output)) 246 return; 247 248 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 249 { 250 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 251 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 252 STBIR_4CHANNEL, 253 STBIR_TYPE_UINT8, 254 STBIR_EDGE_CLAMP, 255 STBIR_FILTER_MKS_2013_86); 256 assert(res); 257 } 258 else 259 { 260 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 261 int res = stbir_resize_uint8_srgb(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 262 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 263 4, STBIR_ALPHA_CHANNEL_NONE, 0, &alloc_context, filter); 264 assert(res); 265 } 266 } 267 268 ///ditto 269 void resizeImage_sRGBWithAlpha(ImageRef!RGBA input, ImageRef!RGBA output) 270 { 271 if (sameSizeResize(input, output)) 272 return; 273 274 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 275 { 276 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 277 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 278 STBIR_RGBA, 279 STBIR_TYPE_UINT8_SRGB, 280 STBIR_EDGE_CLAMP, 281 STBIR_FILTER_MKS_2013_86); 282 assert(res); 283 } 284 else 285 { 286 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 287 int res = stbir_resize_uint8_srgb(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 288 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 289 4, 3, 0, &alloc_context, filter); 290 assert(res); 291 } 292 } 293 294 ///ditto 295 void resizeImage_sRGBWithAlphaPremul(ImageRef!RGBA input, ImageRef!RGBA output) 296 { 297 if (sameSizeResize(input, output)) 298 return; 299 300 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 301 { 302 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 303 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 304 STBIR_RGBA_PM, 305 STBIR_TYPE_UINT8_SRGB, 306 STBIR_EDGE_CLAMP, 307 STBIR_FILTER_MKS_2013_86); 308 assert(res); 309 } 310 else 311 { 312 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 313 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 314 int res = stbir_resize_uint8_srgb(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 315 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 316 4, 3, flags, &alloc_context, filter); 317 assert(res); 318 } 319 } 320 321 ///ditto 322 void resizeImageDiffuseWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 323 { 324 // Intended for 16-bit image in sRGB, with premultipled alpha. 325 if (sameSizeResize(input, output)) 326 return; 327 328 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 329 { 330 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 331 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 332 STBIR_RGBA_PM, 333 STBIR_TYPE_UINT16, 334 STBIR_EDGE_CLAMP, 335 STBIR_FILTER_MKS_2013_86); 336 assert(res); 337 } 338 else 339 { 340 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 341 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 342 343 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 344 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 345 4, 3, flags, 346 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, // for some reason, STBIR_COLORSPACE_SRGB with uint16 creates artifacts 347 &alloc_context); 348 assert(res); 349 } 350 } 351 352 ///ditto 353 void resizeImageDiffuse(ImageRef!RGBA input, ImageRef!RGBA output) 354 { 355 if (sameSizeResize(input, output)) 356 return; 357 358 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 359 { 360 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 361 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 362 STBIR_4CHANNEL, 363 STBIR_TYPE_UINT8, 364 STBIR_EDGE_CLAMP, 365 STBIR_FILTER_MKS_2013_86); 366 assert(res); 367 } 368 else 369 { 370 // Note: as the primary use case is downsampling, it was found it is helpful to have a relatively sharp filter 371 // since the diffuse map may contain text, and downsampling text is too blurry as of today. 372 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 373 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 374 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 375 assert(res); 376 } 377 } 378 379 // Note: no special treatment for material images 380 // No particular quality gain when using lanczos 3. 381 alias resizeImageMaterial = resizeImageGeneric; 382 383 void resizeImageMaterialWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 384 { 385 // Intended for 16-bit image that contains Material with premultipled alpha. 386 if (sameSizeResize(input, output)) 387 return; 388 389 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 390 { 391 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 392 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 393 STBIR_RGBA_PM, 394 STBIR_TYPE_UINT16, 395 STBIR_EDGE_CLAMP, 396 STBIR_FILTER_MKS_2013_86); 397 assert(res); 398 } 399 else 400 { 401 stbir_filter filter = STBIR_FILTER_DEFAULT; 402 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 403 404 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 405 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 406 4, 3, flags, 407 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, // for some reason, STBIR_COLORSPACE_SRGB with uint16 creates artifacts 408 &alloc_context); 409 assert(res); 410 } 411 } 412 413 void resizeImageDepthWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 414 { 415 // Intended for 16-bit image that contains Depth in the RGB channels (or just one), with premultipled alpha. 416 // Note: this function is intended for those images that contain depth despite having 4 channels. 417 if (sameSizeResize(input, output)) 418 return; 419 420 static if (DPLUG_USE_STB_IMAGE_RESIZE_V2) 421 { 422 void* res = stbir_resize(cast(const(void*))input.pixels, input.w, input.h, cast(int)input.pitch, 423 cast( void* )output.pixels, output.w, output.h, cast(int)output.pitch, 424 STBIR_RGBA_PM, 425 STBIR_TYPE_UINT16, 426 STBIR_EDGE_CLAMP, 427 STBIR_FILTER_MKS_2021); 428 assert(res); 429 } 430 else 431 { 432 stbir_filter filter = STBIR_FILTER_MKS_2021; 433 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 434 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 435 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 436 4, 3, flags, 437 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, 438 &alloc_context); 439 assert(res); 440 } 441 } 442 443 private: 444 445 static if (!DPLUG_USE_STB_IMAGE_RESIZE_V2) 446 STBAllocatorContext alloc_context; 447 } 448 449 450 private: 451 452 453 bool sameSizeResize(COLOR)(ImageRef!COLOR input, ImageRef!COLOR output) nothrow @nogc 454 { 455 if (input.w == output.w && input.h == output.h) 456 { 457 // Just copy the pixels over 458 input.blitTo(output); 459 return true; 460 } 461 else 462 return false; 463 464 465 }