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 }