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 }