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 }