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 }