Coverage Report

Created: 2026-04-01 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}