/src/libvips/libvips/resample/vsqbs.cpp
Line | Count | Source |
1 | | /* vertex-split subdivision followed by quadratic b-spline smoothing |
2 | | * |
3 | | * C. Racette 23-28/05/2010 based on code by N. Robidoux and J. Cupitt |
4 | | * |
5 | | * N. Robidoux 29-30/05/2010 |
6 | | */ |
7 | | |
8 | | /* |
9 | | |
10 | | This file is part of VIPS. |
11 | | |
12 | | VIPS is free software; you can redistribute it and/or modify it |
13 | | under the terms of the GNU Lesser General Public License as |
14 | | published by the Free Software Foundation; either version 2 of the |
15 | | License, or (at your option) any later version. |
16 | | |
17 | | This program is distributed in the hope that it will be useful, |
18 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 | | Lesser General Public License for more details. |
21 | | |
22 | | You should have received a copy of the GNU Lesser General Public |
23 | | License along with this program; if not, write to the Free Software |
24 | | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
25 | | 02110-1301 USA |
26 | | |
27 | | */ |
28 | | |
29 | | /* |
30 | | |
31 | | These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk |
32 | | |
33 | | */ |
34 | | |
35 | | /* |
36 | | * 2010 (c) Chantal Racette, Nicolas Robidoux, John Cupitt. |
37 | | * |
38 | | * Nicolas Robidoux thanks Adam Turcotte, Geert Jordaens, Ralf Meyer, |
39 | | * Øyvind Kolås, Minglun Gong and Eric Daoust for useful comments and |
40 | | * code. |
41 | | * |
42 | | * Chantal Racette's image resampling research and programming funded |
43 | | * in part by a NSERC Discovery Grant awarded to Julien Dompierre |
44 | | * (20-61098). |
45 | | */ |
46 | | |
47 | | /* |
48 | | * Vertex-Split Quadratic B-Splines (VSQBS) is a brand new method |
49 | | * which consists of vertex-split subdivision, a subdivision method |
50 | | * with the (as yet unknown?) property that data which is (locally) |
51 | | * constant on diagonals is subdivided into data which is (locally) |
52 | | * constant on diagonals, followed by quadratic B-Spline smoothing. |
53 | | * Because both methods are linear, their combination can be |
54 | | * implemented as if there is no subdivision. |
55 | | * |
56 | | * At high enlargement ratios, VSQBS is very effective at "masking" |
57 | | * that the original has pixels uniformly distributed on a grid. In |
58 | | * particular, VSQBS produces resamples with only very mild |
59 | | * staircasing. Like cubic B-Spline smoothing, however, VSQBS is not |
60 | | * an interpolatory method. For example, using VSQBS to perform the |
61 | | * identity geometric transformation (enlargement by a scaling factor |
62 | | * equal to 1) on an image does not return the original: VSQBS |
63 | | * effectively smooths out the image with the convolution mask |
64 | | * |
65 | | * 1/8 |
66 | | * 1/8 1/2 1/8 |
67 | | * 1/8 |
68 | | * |
69 | | * which is a fairly moderate blur (although the checkerboard mode is |
70 | | * in its nullspace). |
71 | | * |
72 | | * By blending VSQBS with an interpolatory method (bilinear, say) in a |
73 | | * transformation adaptive environment (current GEGL, for example), it |
74 | | * is quite easy to restore that resampling for identity geometric |
75 | | * transformation is equivalent to the identity, and rotations are not |
76 | | * affected by the above, implicit, blur. Contact N. Robidoux for |
77 | | * details. |
78 | | * |
79 | | * An article on VSQBS is forthcoming. |
80 | | */ |
81 | | |
82 | | #ifdef HAVE_CONFIG_H |
83 | | #include <config.h> |
84 | | #endif /*HAVE_CONFIG_H*/ |
85 | | #include <glib/gi18n-lib.h> |
86 | | |
87 | | #include <cstdio> |
88 | | #include <cstdlib> |
89 | | |
90 | | #include <vips/vips.h> |
91 | | #include <vips/internal.h> |
92 | | |
93 | | #include "templates.h" |
94 | | |
95 | | #define VIPS_TYPE_INTERPOLATE_VSQBS \ |
96 | | (vips_interpolate_vsqbs_get_type()) |
97 | | #define VIPS_INTERPOLATE_VSQBS(obj) \ |
98 | | (G_TYPE_CHECK_INSTANCE_CAST((obj), \ |
99 | | VIPS_TYPE_INTERPOLATE_VSQBS, VipsInterpolateVsqbs)) |
100 | | #define VIPS_INTERPOLATE_VSQBS_CLASS(klass) \ |
101 | | (G_TYPE_CHECK_CLASS_CAST((klass), \ |
102 | | VIPS_TYPE_INTERPOLATE_VSQBS, VipsInterpolateVsqbsClass)) |
103 | | #define VIPS_IS_INTERPOLATE_VSQBS(obj) \ |
104 | | (G_TYPE_CHECK_INSTANCE_TYPE((obj), VIPS_TYPE_INTERPOLATE_VSQBS)) |
105 | | #define VIPS_IS_INTERPOLATE_VSQBS_CLASS(klass) \ |
106 | | (G_TYPE_CHECK_CLASS_TYPE((klass), VIPS_TYPE_INTERPOLATE_VSQBS)) |
107 | | #define VIPS_INTERPOLATE_VSQBS_GET_CLASS(obj) \ |
108 | | (G_TYPE_INSTANCE_GET_CLASS((obj), \ |
109 | | VIPS_TYPE_INTERPOLATE_VSQBS, VipsInterpolateVsqbsClass)) |
110 | | |
111 | | typedef struct _VipsInterpolateVsqbs { |
112 | | VipsInterpolate parent_object; |
113 | | |
114 | | } VipsInterpolateVsqbs; |
115 | | |
116 | | typedef struct _VipsInterpolateVsqbsClass { |
117 | | VipsInterpolateClass parent_class; |
118 | | |
119 | | } VipsInterpolateVsqbsClass; |
120 | | |
121 | | /* |
122 | | * THE STENCIL OF INPUT VALUES: |
123 | | * |
124 | | * Pointer arithmetic is used to implicitly reflect the input stencil |
125 | | * about dos_two---assumed closer to the sampling location than other |
126 | | * pixels (ties are OK)---in such a way that after reflection the |
127 | | * sampling point is to the bottom right of dos_two. |
128 | | * |
129 | | * The following code and picture assumes that the stencil reflexion |
130 | | * has already been performed. (X is the sampling location.) |
131 | | * |
132 | | * |
133 | | * (ix,iy-1) (ix+1,iy-1) |
134 | | * = uno_two = uno_thr |
135 | | * |
136 | | * |
137 | | * |
138 | | * (ix-1,iy) (ix,iy) (ix+1,iy) |
139 | | * = dos_one = dos_two = dos_thr |
140 | | * X |
141 | | * |
142 | | * |
143 | | * (ix-1,iy+1) (ix,iy+1) (ix+1,iy+1) |
144 | | * = tre_one = tre_two = tre_thr |
145 | | * |
146 | | * |
147 | | * The above input pixel values are the ones needed in order to |
148 | | * IMPLICITLY make available the following values, needed by quadratic |
149 | | * B-Splines, which is performed on (shifted) double density data: |
150 | | * |
151 | | * |
152 | | * uno_one_1 = uno_two_1 = uno_thr_1 = |
153 | | * (ix-1/4,iy-1/4) (ix+1/4,iy-1/4) (ix+3/4,iy-1/4) |
154 | | * |
155 | | * |
156 | | * |
157 | | * X or X |
158 | | * dos_one_1 = dos_two_1 = dos_thr_1 = |
159 | | * (ix-1/4,iy+1/4) (ix+1/4,iy+1/4) (ix+3/4,iy+1/4) |
160 | | * or X or X |
161 | | * |
162 | | * |
163 | | * |
164 | | * tre_one_1 = tre_two_1 = tre_thr_1 = |
165 | | * (ix-1/4,iy+3/4) (ix+1/4,iy+3/4) (ix+3/4,iy+3/4) |
166 | | * |
167 | | * |
168 | | * In the coefficient computations, we fix things so that coordinates |
169 | | * are relative to dos_two_1, and so that distances are rescaled so |
170 | | * that double density pixel locations are at a distance of 1. |
171 | | */ |
172 | | |
173 | | /* |
174 | | * Call vertex-split + quadratic B-splines with a careful type |
175 | | * conversion as a parameter. (It would be nice to do this with |
176 | | * templates somehow---for one thing this would allow code |
177 | | * comments---but we can't figure a clean way to do it.) |
178 | | */ |
179 | | #define VSQBS_CONVERSION(conversion) \ |
180 | | template <typename T> \ |
181 | | static void inline vsqbs_##conversion(void *restrict pout, \ |
182 | | const VipsPel *restrict pin, \ |
183 | | const int bands, \ |
184 | | const int lskip, \ |
185 | | const double x_0, \ |
186 | | const double y_0) \ |
187 | 0 | { \ |
188 | 0 | T *restrict out = (T *) pout; \ |
189 | 0 | \ |
190 | 0 | const T *restrict in = (T *) pin; \ |
191 | 0 | \ |
192 | 0 | const int sign_of_x_0 = 2 * (x_0 >= 0.) - 1; \ |
193 | 0 | const int sign_of_y_0 = 2 * (y_0 >= 0.) - 1; \ |
194 | 0 | \ |
195 | 0 | const int shift_forw_1_pix = sign_of_x_0 * bands; \ |
196 | 0 | const int shift_forw_1_row = sign_of_y_0 * lskip; \ |
197 | 0 | \ |
198 | 0 | const int shift_back_1_pix = -shift_forw_1_pix; \ |
199 | 0 | const int shift_back_1_row = -shift_forw_1_row; \ |
200 | 0 | \ |
201 | 0 | const int uno_two_shift = shift_back_1_row; \ |
202 | 0 | const int uno_thr_shift = shift_forw_1_pix + shift_back_1_row; \ |
203 | 0 | \ |
204 | 0 | const int dos_one_shift = shift_back_1_pix; \ |
205 | 0 | const int dos_two_shift = 0; \ |
206 | 0 | const int dos_thr_shift = shift_forw_1_pix; \ |
207 | 0 | \ |
208 | 0 | const int tre_one_shift = shift_back_1_pix + shift_forw_1_row; \ |
209 | 0 | const int tre_two_shift = shift_forw_1_row; \ |
210 | 0 | const int tre_thr_shift = shift_forw_1_pix + shift_forw_1_row; \ |
211 | 0 | \ |
212 | 0 | const double twice_abs_x_0 = (2 * sign_of_x_0) * x_0; \ |
213 | 0 | const double twice_abs_y_0 = (2 * sign_of_y_0) * y_0; \ |
214 | 0 | const double x = twice_abs_x_0 + -0.5; \ |
215 | 0 | const double y = twice_abs_y_0 + -0.5; \ |
216 | 0 | const double cent = 0.75 - x * x; \ |
217 | 0 | const double mid = 0.75 - y * y; \ |
218 | 0 | const double left = -0.5 * (x + cent) + 0.5; \ |
219 | 0 | const double top = -0.5 * (y + mid) + 0.5; \ |
220 | 0 | const double left_p_cent = left + cent; \ |
221 | 0 | const double top_p_mid = top + mid; \ |
222 | 0 | const double cent_p_rite = 1.0 - left; \ |
223 | 0 | const double mid_p_bot = 1.0 - top; \ |
224 | 0 | const double rite = 1.0 - left_p_cent; \ |
225 | 0 | const double bot = 1.0 - top_p_mid; \ |
226 | 0 | \ |
227 | 0 | const double four_c_uno_two = left_p_cent * top; \ |
228 | 0 | const double four_c_dos_one = left * top_p_mid; \ |
229 | 0 | const double four_c_dos_two = left_p_cent + top_p_mid; \ |
230 | 0 | const double four_c_dos_thr = cent_p_rite * top_p_mid + rite; \ |
231 | 0 | const double four_c_tre_two = mid_p_bot * left_p_cent + bot; \ |
232 | 0 | const double four_c_tre_thr = mid_p_bot * rite + cent_p_rite * bot; \ |
233 | 0 | const double four_c_uno_thr = top - four_c_uno_two; \ |
234 | 0 | const double four_c_tre_one = left - four_c_dos_one; \ |
235 | 0 | \ |
236 | 0 | int band = bands; \ |
237 | 0 | \ |
238 | 0 | do { \ |
239 | 0 | const double double_result = \ |
240 | 0 | (((four_c_uno_two * in[uno_two_shift] + \ |
241 | 0 | four_c_dos_one * in[dos_one_shift]) + \ |
242 | 0 | (four_c_dos_two * in[dos_two_shift] + \ |
243 | 0 | four_c_dos_thr * in[dos_thr_shift])) + \ |
244 | 0 | ((four_c_tre_two * in[tre_two_shift] + \ |
245 | 0 | four_c_tre_thr * in[tre_thr_shift]) + \ |
246 | 0 | (four_c_uno_thr * in[uno_thr_shift] + \ |
247 | 0 | four_c_tre_one * in[tre_one_shift]))) * \ |
248 | 0 | 0.25; \ |
249 | 0 | \ |
250 | 0 | const T result = to_##conversion<T>(double_result); \ |
251 | 0 | in++; \ |
252 | 0 | *out++ = result; \ |
253 | 0 | \ |
254 | 0 | } while (--band); \ |
255 | 0 | } Unexecuted instantiation: vsqbs.cpp:void vsqbs_nosign<unsigned char>(void*, unsigned char const*, int, int, double, double) Unexecuted instantiation: vsqbs.cpp:void vsqbs_withsign<signed char>(void*, unsigned char const*, int, int, double, double) Unexecuted instantiation: vsqbs.cpp:void vsqbs_nosign<unsigned short>(void*, unsigned char const*, int, int, double, double) Unexecuted instantiation: vsqbs.cpp:void vsqbs_withsign<short>(void*, unsigned char const*, int, int, double, double) Unexecuted instantiation: vsqbs.cpp:void vsqbs_nosign<unsigned int>(void*, unsigned char const*, int, int, double, double) Unexecuted instantiation: vsqbs.cpp:void vsqbs_withsign<int>(void*, unsigned char const*, int, int, double, double) Unexecuted instantiation: vsqbs.cpp:void vsqbs_fptypes<float>(void*, unsigned char const*, int, int, double, double) Unexecuted instantiation: vsqbs.cpp:void vsqbs_fptypes<double>(void*, unsigned char const*, int, int, double, double) |
256 | | |
257 | | VSQBS_CONVERSION(fptypes) |
258 | | VSQBS_CONVERSION(withsign) |
259 | | VSQBS_CONVERSION(nosign) |
260 | | |
261 | | #define CALL(T, conversion) \ |
262 | 0 | vsqbs_##conversion<T>(out, \ |
263 | 0 | p, \ |
264 | 0 | bands, \ |
265 | 0 | lskip, \ |
266 | 0 | relative_x, \ |
267 | 0 | relative_y); |
268 | | |
269 | | /* |
270 | | * We need C linkage: |
271 | | */ |
272 | | extern "C" { |
273 | 38 | G_DEFINE_TYPE(VipsInterpolateVsqbs, vips_interpolate_vsqbs, |
274 | 38 | VIPS_TYPE_INTERPOLATE); |
275 | 38 | } |
276 | 38 | |
277 | 38 | static void |
278 | 38 | vips_interpolate_vsqbs_interpolate(VipsInterpolate *restrict interpolate, |
279 | 38 | void *restrict out, |
280 | 38 | VipsRegion *restrict in, |
281 | 38 | double absolute_x, |
282 | 38 | double absolute_y) |
283 | 38 | { |
284 | | /* absolute_x and absolute_y are always >= 1.0 (see double-check assert |
285 | | * below), so we don't need floor(). |
286 | | * |
287 | | * It's 1 not 0 since we ask for a window_offset of 1 at the bottom. |
288 | | */ |
289 | 0 | const int ix = (int) (absolute_x + 0.5); |
290 | 0 | const int iy = (int) (absolute_y + 0.5); |
291 | | |
292 | | /* |
293 | | * Move the pointer to (the first band of) the top/left pixel of the |
294 | | * 2x2 group of pixel centers which contains the sampling location |
295 | | * in its convex hull: |
296 | | */ |
297 | 0 | const VipsPel *restrict p = VIPS_REGION_ADDR(in, ix, iy); |
298 | |
|
299 | 0 | const double relative_x = absolute_x - ix; |
300 | 0 | const double relative_y = absolute_y - iy; |
301 | | |
302 | | /* |
303 | | * VIPS versions of Nicolas's pixel addressing values. |
304 | | */ |
305 | 0 | const int lskip = VIPS_REGION_LSKIP(in) / |
306 | 0 | VIPS_IMAGE_SIZEOF_ELEMENT(in->im); |
307 | | |
308 | | /* |
309 | | * Double the bands for complex images to account for the real and |
310 | | * imaginary parts being computed independently: |
311 | | */ |
312 | 0 | const int actual_bands = in->im->Bands; |
313 | 0 | const int bands = vips_band_format_iscomplex(in->im->BandFmt) |
314 | 0 | ? 2 * actual_bands |
315 | 0 | : actual_bands; |
316 | |
|
317 | 0 | g_assert(ix - 1 >= in->valid.left); |
318 | 0 | g_assert(iy - 1 >= in->valid.top); |
319 | 0 | g_assert(ix + 1 <= VIPS_RECT_RIGHT(&in->valid)); |
320 | 0 | g_assert(iy + 1 <= VIPS_RECT_BOTTOM(&in->valid)); |
321 | | |
322 | | /* Confirm that absolute_x and absolute_y are >= 1, see above. |
323 | | */ |
324 | 0 | g_assert(absolute_x >= 1.0); |
325 | 0 | g_assert(absolute_y >= 1.0); |
326 | |
|
327 | 0 | switch (in->im->BandFmt) { |
328 | 0 | case VIPS_FORMAT_UCHAR: |
329 | 0 | CALL(unsigned char, nosign); |
330 | 0 | break; |
331 | | |
332 | 0 | case VIPS_FORMAT_CHAR: |
333 | 0 | CALL(signed char, withsign); |
334 | 0 | break; |
335 | | |
336 | 0 | case VIPS_FORMAT_USHORT: |
337 | 0 | CALL(unsigned short, nosign); |
338 | 0 | break; |
339 | | |
340 | 0 | case VIPS_FORMAT_SHORT: |
341 | 0 | CALL(signed short, withsign); |
342 | 0 | break; |
343 | | |
344 | 0 | case VIPS_FORMAT_UINT: |
345 | 0 | CALL(unsigned int, nosign); |
346 | 0 | break; |
347 | | |
348 | 0 | case VIPS_FORMAT_INT: |
349 | 0 | CALL(signed int, withsign); |
350 | 0 | break; |
351 | | |
352 | | /* |
353 | | * Complex images are handled by doubling bands: |
354 | | */ |
355 | 0 | case VIPS_FORMAT_FLOAT: |
356 | 0 | case VIPS_FORMAT_COMPLEX: |
357 | 0 | CALL(float, fptypes); |
358 | 0 | break; |
359 | | |
360 | 0 | case VIPS_FORMAT_DOUBLE: |
361 | 0 | case VIPS_FORMAT_DPCOMPLEX: |
362 | 0 | CALL(double, fptypes); |
363 | 0 | break; |
364 | | |
365 | 0 | default: |
366 | 0 | g_assert(0); |
367 | 0 | break; |
368 | 0 | } |
369 | 0 | } |
370 | | |
371 | | static void |
372 | | vips_interpolate_vsqbs_class_init(VipsInterpolateVsqbsClass *klass) |
373 | 19 | { |
374 | 19 | VipsObjectClass *object_class = VIPS_OBJECT_CLASS(klass); |
375 | 19 | VipsInterpolateClass *interpolate_class = VIPS_INTERPOLATE_CLASS(klass); |
376 | | |
377 | 19 | object_class->nickname = "vsqbs"; |
378 | 19 | object_class->description = _("B-Splines with antialiasing smoothing"); |
379 | | |
380 | 19 | interpolate_class->interpolate = vips_interpolate_vsqbs_interpolate; |
381 | 19 | interpolate_class->window_size = 4; |
382 | 19 | interpolate_class->window_offset = 1; |
383 | 19 | } |
384 | | |
385 | | static void |
386 | | vips_interpolate_vsqbs_init(VipsInterpolateVsqbs *vsqbs) |
387 | 0 | { |
388 | 0 | } |