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 ///ditto 47 void resizeImageGeneric(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 48 { 49 if (sameSizeResize(input, output)) 50 return; 51 52 stbir_filter filter = STBIR_FILTER_DEFAULT; 53 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 54 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 55 assert(res); 56 } 57 58 ///ditto 59 void resizeImageSmoother(ImageRef!RGBA input, ImageRef!RGBA output) 60 { 61 if (sameSizeResize(input, output)) 62 return; 63 64 // suitable when depth is encoded in a RGB8 triplet, such as in UIImageKnob 65 stbir_filter filter = STBIR_FILTER_CUBICBSPLINE; 66 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 67 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 68 assert(res); 69 } 70 71 ///ditto 72 void resizeImageNearest(ImageRef!RGBA input, ImageRef!RGBA output) // same but with nearest filter 73 { 74 if (sameSizeResize(input, output)) 75 return; 76 77 stbir_filter filter = STBIR_FILTER_BOX; 78 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 79 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 80 assert(res); 81 } 82 83 ///ditto 84 void resizeImageGeneric(ImageRef!L16 input, ImageRef!L16 output) 85 { 86 if (sameSizeResize(input, output)) 87 return; 88 89 stbir_filter filter = STBIR_FILTER_DEFAULT; 90 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 91 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, filter, &alloc_context); 92 assert(res); 93 } 94 95 ///ditto 96 void resizeImageDepth(ImageRef!L16 input, ImageRef!L16 output) 97 { 98 if (sameSizeResize(input, output)) 99 return; 100 101 stbir_filter filter = STBIR_FILTER_MKS_2021; 102 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 103 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, filter, &alloc_context); 104 assert(res); 105 } 106 107 ///ditto 108 void resizeImageDepth(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 109 { 110 // Note: this function is intended for those images that contain depth despite having 4 channels. 111 if (sameSizeResize(input, output)) 112 return; 113 114 stbir_filter filter = STBIR_FILTER_MKS_2021; 115 int res = stbir_resize_uint16(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 116 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 117 assert(res); 118 } 119 120 ///ditto 121 void resizeImageCoverage(ImageRef!L8 input, ImageRef!L8 output) 122 { 123 if (sameSizeResize(input, output)) 124 return; 125 126 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 127 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 1, STBIR_FILTER_DEFAULT, &alloc_context); 128 assert(res); 129 } 130 131 ///ditto 132 void resizeImage_sRGBNoAlpha(ImageRef!RGBA input, ImageRef!RGBA output) 133 { 134 if (sameSizeResize(input, output)) 135 return; 136 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 137 int res = stbir_resize_uint8_srgb(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, 139 4, STBIR_ALPHA_CHANNEL_NONE, 0, &alloc_context, filter); 140 assert(res); 141 } 142 143 ///ditto 144 void resizeImage_sRGBWithAlpha(ImageRef!RGBA input, ImageRef!RGBA output) 145 { 146 if (sameSizeResize(input, output)) 147 return; 148 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 149 int res = stbir_resize_uint8_srgb(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 150 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 151 4, 3, 0, &alloc_context, filter); 152 assert(res); 153 } 154 155 ///ditto 156 void resizeImage_sRGBWithAlphaPremul(ImageRef!RGBA input, ImageRef!RGBA output) 157 { 158 if (sameSizeResize(input, output)) 159 return; 160 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 161 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 162 int res = stbir_resize_uint8_srgb(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 163 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 164 4, 3, flags, &alloc_context, filter); 165 assert(res); 166 } 167 168 ///ditto 169 void resizeImageDiffuseWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 170 { 171 // Intended for 16-bit image in sRGB, with premultipled alpha. 172 if (sameSizeResize(input, output)) 173 return; 174 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 175 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 176 177 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 178 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 179 4, 3, flags, 180 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, // for some reason, STBIR_COLORSPACE_SRGB with uint16 creates artifacts 181 &alloc_context); 182 assert(res); 183 } 184 185 ///ditto 186 void resizeImageDiffuse(ImageRef!RGBA input, ImageRef!RGBA output) 187 { 188 if (sameSizeResize(input, output)) 189 return; 190 // Note: as the primary use case is downsampling, it was found it is helpful to have a relatively sharp filter 191 // since the diffuse map may contain text, and downsampling text is too blurry as of today. 192 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 193 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 194 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 195 assert(res); 196 } 197 198 // Note: no special treatment for material images 199 // No particular quality gain when using lanczos 3. 200 alias resizeImageMaterial = resizeImageGeneric; 201 202 void resizeImageMaterialWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 203 { 204 // Intended for 16-bit image that contains Material with premultipled alpha. 205 if (sameSizeResize(input, output)) 206 return; 207 stbir_filter filter = STBIR_FILTER_DEFAULT; 208 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 209 210 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 211 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 212 4, 3, flags, 213 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, // for some reason, STBIR_COLORSPACE_SRGB with uint16 creates artifacts 214 &alloc_context); 215 assert(res); 216 } 217 218 void resizeImageDepthWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 219 { 220 // Intended for 16-bit image that contains Depth in the RGB channels (or just one), with premultipled alpha. 221 // Note: this function is intended for those images that contain depth despite having 4 channels. 222 if (sameSizeResize(input, output)) 223 return; 224 225 stbir_filter filter = STBIR_FILTER_MKS_2021; 226 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 227 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 228 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 229 4, 3, flags, 230 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, 231 &alloc_context); 232 assert(res); 233 } 234 235 private: 236 237 STBAllocatorContext alloc_context; 238 } 239 240 241 private: 242 243 244 bool sameSizeResize(COLOR)(ImageRef!COLOR input, ImageRef!COLOR output) nothrow @nogc 245 { 246 if (input.w == output.w && input.h == output.h) 247 { 248 // Just copy the pixels over 249 input.blitTo(output); 250 return true; 251 } 252 else 253 return false; 254 255 256 }