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 resizeImageDiffuseWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 157 { 158 // Intended for 16-bit image in sRGB, with premultipled alpha. 159 if (sameSizeResize(input, output)) 160 return; 161 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 162 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 163 164 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 165 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 166 4, 3, flags, 167 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, // for some reason, STBIR_COLORSPACE_SRGB with uint16 creates artifacts 168 &alloc_context); 169 assert(res); 170 } 171 172 ///ditto 173 void resizeImageDiffuse(ImageRef!RGBA input, ImageRef!RGBA output) 174 { 175 if (sameSizeResize(input, output)) 176 return; 177 // Note: as the primary use case is downsampling, it was found it is helpful to have a relatively sharp filter 178 // since the diffuse map may contain text, and downsampling text is too blurry as of today. 179 stbir_filter filter = STBIR_FILTER_MKS_2013_86; 180 int res = stbir_resize_uint8(cast(const(ubyte*))input.pixels, input.w, input.h, cast(int)input.pitch, 181 cast( ubyte* )output.pixels, output.w, output.h, cast(int)output.pitch, 4, filter, &alloc_context); 182 assert(res); 183 } 184 185 // Note: no special treatment for material images 186 // No particular quality gain when using lanczos 3. 187 alias resizeImageMaterial = resizeImageGeneric; 188 189 void resizeImageMaterialWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 190 { 191 // Intended for 16-bit image that contains Material with premultipled alpha. 192 if (sameSizeResize(input, output)) 193 return; 194 stbir_filter filter = STBIR_FILTER_DEFAULT; 195 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 196 197 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 198 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 199 4, 3, flags, 200 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, // for some reason, STBIR_COLORSPACE_SRGB with uint16 creates artifacts 201 &alloc_context); 202 assert(res); 203 } 204 205 void resizeImageDepthWithAlphaPremul(ImageRef!RGBA16 input, ImageRef!RGBA16 output) 206 { 207 // Intended for 16-bit image that contains Depth in the RGB channels (or just one), with premultipled alpha. 208 // Note: this function is intended for those images that contain depth despite having 4 channels. 209 if (sameSizeResize(input, output)) 210 return; 211 212 stbir_filter filter = STBIR_FILTER_MKS_2021; 213 int flags = STBIR_FLAG_ALPHA_PREMULTIPLIED; 214 int res = stbir_resize_uint16_generic(cast(const(ushort*))input.pixels, input.w, input.h, cast(int)input.pitch, 215 cast( ushort* )output.pixels, output.w, output.h, cast(int)output.pitch, 216 4, 3, flags, 217 STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, 218 &alloc_context); 219 assert(res); 220 } 221 222 private: 223 224 STBAllocatorContext alloc_context; 225 } 226 227 228 private: 229 230 231 bool sameSizeResize(COLOR)(ImageRef!COLOR input, ImageRef!COLOR output) nothrow @nogc 232 { 233 if (input.w == output.w && input.h == output.h) 234 { 235 // Just copy the pixels over 236 input.blitTo(output); 237 return true; 238 } 239 else 240 return false; 241 242 243 }