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 import dplug.graphics.stb_image_resize; 14 15 16 version = STB_image_resize; 17 18 /// Image resizer. 19 /// To minimize CPU, it is advised to reuse that object for similar resize. 20 /// To minimize memory allocation, it is advised to reuse that object even across different resize. 21 struct ImageResizer 22 { 23 public: 24 nothrow: 25 @nogc: 26 27 @disable this(this); 28 29 /** 30 * Function resizes image. There are several other function for specialized treatment. 31 * 32 * Params: 33 * input Input image. 34 * output Output image. 35 */ 36 void resizeImageGeneric(ImageRef!RGBA input, ImageRef!RGBA output) 37 { 38 if (sameSizeResize(input, output)) 39 return; 40 41 stbir_filter filter = STBIR_FILTER_DEFAULT; 42 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 43 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 44 assert(res); 45 } 46 47 ///ditto 48 void resizeImageSmoother(ImageRef!RGBA input, ImageRef!RGBA output) 49 { 50 if (sameSizeResize(input, output)) 51 return; 52 53 // suitable when depth is encoded in a RGB8 triplet, such as in UIImageKnob 54 stbir_filter filter = STBIR_FILTER_CUBICBSPLINE; 55 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 56 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 57 assert(res); 58 } 59 60 ///ditto 61 void resizeImageNearest(ImageRef!RGBA input, ImageRef!RGBA output) // same but with nearest filter 62 { 63 if (sameSizeResize(input, output)) 64 return; 65 66 stbir_filter filter = STBIR_FILTER_BOX; 67 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 68 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 69 assert(res); 70 } 71 72 ///ditto 73 void resizeImageGeneric(ImageRef!L16 input, ImageRef!L16 output) 74 { 75 if (sameSizeResize(input, output)) 76 return; 77 78 stbir_filter filter = STBIR_FILTER_DEFAULT; 79 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 80 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, filter, &alloc_context); 81 assert(res); 82 } 83 84 ///ditto 85 void resizeImageDepth(ImageRef!L16 input, ImageRef!L16 output) 86 { 87 if (sameSizeResize(input, output)) 88 return; 89 90 stbir_filter filter = STBIR_FILTER_MKS_2021; 91 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 92 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, filter, &alloc_context); 93 assert(res); 94 } 95 96 ///ditto 97 void resizeImageCoverage(ImageRef!L8 input, ImageRef!L8 output) 98 { 99 if (sameSizeResize(input, output)) 100 return; 101 102 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 103 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, STBIR_FILTER_DEFAULT, &alloc_context); 104 assert(res); 105 } 106 107 ///ditto 108 void resizeImage_sRGBNoAlpha(ImageRef!RGBA input, ImageRef!RGBA output) 109 { 110 if (sameSizeResize(input, output)) 111 return; 112 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 113 int res = stbir_resize_uint8_srgb(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 114 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 115 4, STBIR_ALPHA_CHANNEL_NONE, 0, &alloc_context, filter); 116 assert(res); 117 } 118 119 ///ditto 120 void resizeImage_sRGBWithAlpha(ImageRef!RGBA input, ImageRef!RGBA output) 121 { 122 if (sameSizeResize(input, output)) 123 return; 124 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 125 int res = stbir_resize_uint8_srgb(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 126 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 127 4, 3, 0, &alloc_context, filter); 128 assert(res); 129 } 130 131 ///ditto 132 void resizeImageDiffuse(ImageRef!RGBA input, ImageRef!RGBA output) 133 { 134 if (sameSizeResize(input, output)) 135 return; 136 // Note: as the primary use case is downsampling, it was found it is helpful to have a relatively sharp filter 137 // since the diffuse map may contain text, and downsampling text is too blurry as of today. 138 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 139 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 140 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 141 assert(res); 142 } 143 144 // Note: no special treatment for material images 145 // No particular quality gain when using lanczos 3. 146 alias resizeImageMaterial = resizeImageGeneric; 147 148 private: 149 150 STBAllocatorContext alloc_context; 151 } 152 153 154 private: 155 156 157 bool sameSizeResize(COLOR)(ImageRef!COLOR input, ImageRef!COLOR output) nothrow @nogc 158 { 159 if (input.w == output.w && input.h == output.h) 160 { 161 // Just copy the pixels over 162 input.blitTo(output); 163 return true; 164 } 165 else 166 return false; 167 168 169 }