Coverage Report

Created: 2025-01-28 06:34

/src/libvips/libvips/resample/reduceh.cpp
Line
Count
Source (jump to first uncovered line)
1
/* horizontal reduce by a float factor with a kernel
2
 *
3
 * 29/1/16
4
 *  - from shrinkh.c
5
 * 10/3/16
6
 *  - add other kernels
7
 * 15/8/16
8
 *  - rename xshrink as hshrink for consistency
9
 * 9/9/16
10
 *  - add @centre option
11
 * 6/6/20 kleisauke
12
 *  - deprecate @centre option, it's now always on
13
 *  - fix pixel shift
14
 * 22/4/22 kleisauke
15
 *  - add @gap option
16
 */
17
18
/*
19
20
  This file is part of VIPS.
21
22
  VIPS is free software; you can redistribute it and/or modify
23
  it under the terms of the GNU Lesser General Public License as published by
24
  the Free Software Foundation; either version 2 of the License, or
25
  (at your option) any later version.
26
27
  This program is distributed in the hope that it will be useful,
28
  but WITHOUT ANY WARRANTY; without even the implied warranty of
29
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30
  GNU Lesser General Public License for more details.
31
32
  You should have received a copy of the GNU Lesser General Public License
33
  along with this program; if not, write to the Free Software
34
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
35
  02110-1301  USA
36
37
 */
38
39
/*
40
41
  These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
42
43
 */
44
45
/*
46
#define DEBUG
47
 */
48
49
#ifdef HAVE_CONFIG_H
50
#include <config.h>
51
#endif /*HAVE_CONFIG_H*/
52
#include <glib/gi18n-lib.h>
53
54
#include <stdio.h>
55
#include <stdlib.h>
56
#include <stdint.h>
57
#include <math.h>
58
59
#include <vips/vips.h>
60
#include <vips/vector.h>
61
#include <vips/debug.h>
62
#include <vips/internal.h>
63
64
#include "presample.h"
65
#include "templates.h"
66
67
typedef struct _VipsReduceh {
68
  VipsResample parent_instance;
69
70
  double hshrink; /* Reduce factor */
71
  double gap;   /* Reduce gap */
72
73
  /* The thing we use to make the kernel.
74
   */
75
  VipsKernel kernel;
76
77
  /* Number of points in kernel.
78
   */
79
  int n_point;
80
81
  /* Horizontal displacement.
82
   */
83
  double hoffset;
84
85
  /* The hshrink we do after integer reduction.
86
   */
87
  double residual_hshrink;
88
89
  /* Precalculated interpolation matrices. short (used for pel
90
   * sizes up to int), and double (for all others). We go to
91
   * scale + 1 so we can round-to-nearest safely.
92
   */
93
  short *matrixs[VIPS_TRANSFORM_SCALE + 1];
94
  double *matrixf[VIPS_TRANSFORM_SCALE + 1];
95
96
  /* Deprecated.
97
   */
98
  gboolean centre;
99
100
} VipsReduceh;
101
102
typedef VipsResampleClass VipsReducehClass;
103
104
/* We need C linkage for this.
105
 */
106
extern "C" {
107
G_DEFINE_TYPE(VipsReduceh, vips_reduceh, VIPS_TYPE_RESAMPLE);
108
}
109
110
/* Get n points. @shrink is the shrink factor, so 2 for a 50% reduction.
111
 */
112
int
113
vips_reduce_get_points(VipsKernel kernel, double shrink)
114
0
{
115
0
  switch (kernel) {
116
0
  case VIPS_KERNEL_NEAREST:
117
0
    return 1;
118
119
0
  case VIPS_KERNEL_LINEAR:
120
0
    return 2 * rint(shrink) + 1;
121
122
0
  case VIPS_KERNEL_CUBIC:
123
0
  case VIPS_KERNEL_MITCHELL:
124
0
    return 2 * rint(2 * shrink) + 1;
125
126
0
  case VIPS_KERNEL_LANCZOS2:
127
0
    return 2 * rint(2 * shrink) + 1;
128
129
0
  case VIPS_KERNEL_LANCZOS3:
130
0
    return 2 * rint(3 * shrink) + 1;
131
132
0
  case VIPS_KERNEL_MKS2013:
133
0
    return 2 * rint(3 * shrink) + 1;
134
135
0
  case VIPS_KERNEL_MKS2021:
136
0
    return 2 * rint(5 * shrink) + 1;
137
138
0
  default:
139
0
    g_assert_not_reached();
140
0
    return 0;
141
0
  }
142
0
}
143
144
template <typename T, T max_value>
145
static void inline reduceh_unsigned_int_tab(VipsReduceh *reduceh,
146
  VipsPel *pout, const VipsPel *pin,
147
  const int bands, const short *restrict cx)
148
0
{
149
0
  T *restrict out = (T *) pout;
150
0
  const T *restrict in = (T *) pin;
151
0
  const int n = reduceh->n_point;
152
153
0
  for (int z = 0; z < bands; z++) {
154
0
    typename LongT<T>::type sum;
155
156
0
    sum = reduce_sum<T>(in + z, bands, cx, n);
157
0
    sum = unsigned_fixed_round(sum);
158
0
    out[z] = VIPS_CLIP(0, sum, max_value);
159
0
  }
160
0
}
Unexecuted instantiation: reduceh.cpp:_ZL24reduceh_unsigned_int_tabIhTnT_Lh255EEvP12_VipsReducehPhPKhiPKs
Unexecuted instantiation: reduceh.cpp:_ZL24reduceh_unsigned_int_tabItTnT_Lt65535EEvP12_VipsReducehPhPKhiPKs
Unexecuted instantiation: reduceh.cpp:_ZL24reduceh_unsigned_int_tabIjTnT_Lj4294967295EEvP12_VipsReducehPhPKhiPKs
161
162
template <typename T, int min_value, int max_value>
163
static void inline reduceh_signed_int_tab(VipsReduceh *reduceh,
164
  VipsPel *pout, const VipsPel *pin,
165
  const int bands, const short *restrict cx)
166
0
{
167
0
  T *restrict out = (T *) pout;
168
0
  const T *restrict in = (T *) pin;
169
0
  const int n = reduceh->n_point;
170
171
0
  for (int z = 0; z < bands; z++) {
172
0
    typename LongT<T>::type sum;
173
174
0
    sum = reduce_sum<T>(in + z, bands, cx, n);
175
0
    sum = signed_fixed_round(sum);
176
0
    out[z] = VIPS_CLIP(min_value, sum, max_value);
177
0
  }
178
0
}
Unexecuted instantiation: reduceh.cpp:void reduceh_signed_int_tab<signed char, -128, 127>(_VipsReduceh*, unsigned char*, unsigned char const*, int, short const*)
Unexecuted instantiation: reduceh.cpp:void reduceh_signed_int_tab<short, -32768, 32767>(_VipsReduceh*, unsigned char*, unsigned char const*, int, short const*)
Unexecuted instantiation: reduceh.cpp:void reduceh_signed_int_tab<int, -2147483648, 2147483647>(_VipsReduceh*, unsigned char*, unsigned char const*, int, short const*)
179
180
/* Floating-point version.
181
 */
182
template <typename T>
183
static void inline reduceh_float_tab(VipsReduceh *reduceh,
184
  VipsPel *pout, const VipsPel *pin,
185
  const int bands, const double *restrict cx)
186
0
{
187
0
  T *restrict out = (T *) pout;
188
0
  const T *restrict in = (T *) pin;
189
0
  const int n = reduceh->n_point;
190
191
0
  for (int z = 0; z < bands; z++)
192
0
    out[z] = reduce_sum<T>(in + z, bands, cx, n);
193
0
}
194
195
/* Ultra-high-quality version for double images.
196
 */
197
template <typename T>
198
static void inline reduceh_notab(VipsReduceh *reduceh,
199
  VipsPel *pout, const VipsPel *pin,
200
  const int bands, double x)
201
0
{
202
0
  T *restrict out = (T *) pout;
203
0
  const T *restrict in = (T *) pin;
204
0
  const int n = reduceh->n_point;
205
206
0
  typename LongT<T>::type cx[MAX_POINT];
207
208
0
  vips_reduce_make_mask(cx, reduceh->kernel, reduceh->n_point,
209
0
    reduceh->residual_hshrink, x);
210
211
0
  for (int z = 0; z < bands; z++)
212
0
    out[z] = reduce_sum<T>(in + z, bands, cx, n);
213
0
}
214
215
static int
216
vips_reduceh_gen(VipsRegion *out_region, void *seq,
217
  void *a, void *b, gboolean *stop)
218
0
{
219
0
  VipsImage *in = (VipsImage *) a;
220
0
  VipsReduceh *reduceh = (VipsReduceh *) b;
221
0
  const int ps = VIPS_IMAGE_SIZEOF_PEL(in);
222
0
  VipsRegion *ir = (VipsRegion *) seq;
223
0
  VipsRect *r = &out_region->valid;
224
225
  /* Double bands for complex.
226
   */
227
0
  const int bands = in->Bands *
228
0
    (vips_band_format_iscomplex(in->BandFmt) ? 2 : 1);
229
230
0
  VipsRect s;
231
232
#ifdef DEBUG
233
  printf("vips_reduceh_gen: generating %d x %d at %d x %d\n",
234
    r->width, r->height, r->left, r->top);
235
#endif /*DEBUG*/
236
237
0
  s.left = r->left * reduceh->residual_hshrink - reduceh->hoffset;
238
0
  s.top = r->top;
239
0
  s.width = r->width * reduceh->residual_hshrink + reduceh->n_point;
240
0
  s.height = r->height;
241
0
  if (vips_region_prepare(ir, &s))
242
0
    return -1;
243
244
0
  VIPS_GATE_START("vips_reduceh_gen: work");
245
246
0
  for (int y = 0; y < r->height; y++) {
247
0
    VipsPel *p0;
248
0
    VipsPel *q;
249
250
0
    double X;
251
252
0
    q = VIPS_REGION_ADDR(out_region, r->left, r->top + y);
253
254
0
    X = (r->left + 0.5) * reduceh->residual_hshrink - 0.5 -
255
0
      reduceh->hoffset;
256
257
    /* We want p0 to be the start (ie. x == 0) of the input
258
     * scanline we are reading from. We can then calculate the p we
259
     * need for each pixel with a single mul and avoid calling ADDR
260
     * for each pixel.
261
     *
262
     * We can't get p0 directly with ADDR since it could be outside
263
     * valid, so get the leftmost pixel in valid and subtract a
264
     * bit.
265
     */
266
0
    p0 = VIPS_REGION_ADDR(ir, ir->valid.left, r->top + y) -
267
0
      ir->valid.left * ps;
268
269
0
    for (int x = 0; x < r->width; x++) {
270
0
      const int ix = (int) X;
271
0
      VipsPel *p = p0 + ix * ps;
272
0
      const int sx = X * VIPS_TRANSFORM_SCALE * 2;
273
0
      const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1);
274
0
      const int tx = (six + 1) >> 1;
275
0
      const short *cxs = reduceh->matrixs[tx];
276
0
      const double *cxf = reduceh->matrixf[tx];
277
278
0
      switch (in->BandFmt) {
279
0
      case VIPS_FORMAT_UCHAR:
280
0
        reduceh_unsigned_int_tab<unsigned char,
281
0
          UCHAR_MAX>(reduceh, q, p, bands, cxs);
282
0
        break;
283
284
0
      case VIPS_FORMAT_CHAR:
285
0
        reduceh_signed_int_tab<signed char,
286
0
          SCHAR_MIN, SCHAR_MAX>(reduceh, q, p, bands, cxs);
287
0
        break;
288
289
0
      case VIPS_FORMAT_USHORT:
290
0
        reduceh_unsigned_int_tab<unsigned short,
291
0
          USHRT_MAX>(reduceh, q, p, bands, cxs);
292
0
        break;
293
294
0
      case VIPS_FORMAT_SHORT:
295
0
        reduceh_signed_int_tab<signed short,
296
0
          SHRT_MIN, SHRT_MAX>(reduceh, q, p, bands, cxs);
297
0
        break;
298
299
0
      case VIPS_FORMAT_UINT:
300
0
        reduceh_unsigned_int_tab<unsigned int,
301
0
          UINT_MAX>(reduceh, q, p, bands, cxs);
302
0
        break;
303
304
0
      case VIPS_FORMAT_INT:
305
0
        reduceh_signed_int_tab<signed int,
306
0
          INT_MIN, INT_MAX>(reduceh, q, p, bands, cxs);
307
0
        break;
308
309
0
      case VIPS_FORMAT_FLOAT:
310
0
      case VIPS_FORMAT_COMPLEX:
311
0
        reduceh_float_tab<float>(reduceh,
312
0
          q, p, bands, cxf);
313
0
        break;
314
315
0
      case VIPS_FORMAT_DOUBLE:
316
0
      case VIPS_FORMAT_DPCOMPLEX:
317
0
        reduceh_notab<double>(reduceh,
318
0
          q, p, bands, X - ix);
319
0
        break;
320
321
0
      default:
322
0
        g_assert_not_reached();
323
0
        break;
324
0
      }
325
326
0
      X += reduceh->residual_hshrink;
327
0
      q += ps;
328
0
    }
329
0
  }
330
331
0
  VIPS_GATE_STOP("vips_reduceh_gen: work");
332
333
0
  VIPS_COUNT_PIXELS(out_region, "vips_reduceh_gen");
334
335
0
  return 0;
336
0
}
337
338
#ifdef HAVE_HWY
339
static int
340
vips_reduceh_uchar_vector_gen(VipsRegion *out_region, void *seq,
341
  void *a, void *b, gboolean *stop)
342
0
{
343
0
  VipsImage *in = (VipsImage *) a;
344
0
  VipsReduceh *reduceh = (VipsReduceh *) b;
345
0
  const int ps = VIPS_IMAGE_SIZEOF_PEL(in);
346
0
  VipsRegion *ir = (VipsRegion *) seq;
347
0
  VipsRect *r = &out_region->valid;
348
0
  const int bands = in->Bands;
349
350
0
  VipsRect s;
351
352
#ifdef DEBUG
353
  printf("vips_reduceh_uchar_vector_gen: generating %d x %d at %d x %d\n",
354
    r->width, r->height, r->left, r->top);
355
#endif /*DEBUG*/
356
357
0
  s.left = r->left * reduceh->residual_hshrink - reduceh->hoffset;
358
0
  s.top = r->top;
359
0
  s.width = r->width * reduceh->residual_hshrink + reduceh->n_point;
360
0
  s.height = r->height;
361
0
  if (vips_region_prepare(ir, &s))
362
0
    return -1;
363
364
0
  VIPS_GATE_START("vips_reduceh_uchar_vector_gen: work");
365
366
0
  for (int y = 0; y < r->height; y++) {
367
0
    VipsPel *p0;
368
0
    VipsPel *q;
369
370
0
    double X;
371
372
0
    q = VIPS_REGION_ADDR(out_region, r->left, r->top + y);
373
374
0
    X = (r->left + 0.5) * reduceh->residual_hshrink - 0.5 -
375
0
      reduceh->hoffset;
376
377
0
    p0 = VIPS_REGION_ADDR(ir, ir->valid.left, r->top + y) -
378
0
      ir->valid.left * ps;
379
380
0
    vips_reduceh_uchar_hwy(q, p0, reduceh->n_point, r->width,
381
0
      bands, reduceh->matrixs, X, reduceh->residual_hshrink);
382
0
  }
383
384
0
  VIPS_GATE_STOP("vips_reduceh_uchar_vector_gen: work");
385
386
0
  VIPS_COUNT_PIXELS(out_region, "vips_reduceh_uchar_vector_gen");
387
388
0
  return 0;
389
0
}
390
#endif /*HAVE_HWY*/
391
392
static int
393
vips_reduceh_build(VipsObject *object)
394
0
{
395
0
  VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS(object);
396
0
  VipsResample *resample = VIPS_RESAMPLE(object);
397
0
  VipsReduceh *reduceh = (VipsReduceh *) object;
398
0
  VipsImage **t = (VipsImage **)
399
0
    vips_object_local_array(object, 3);
400
401
0
  VipsImage *in;
402
0
  VipsGenerateFn generate;
403
0
  int width;
404
0
  int int_hshrink;
405
0
  double extra_pixels;
406
407
0
  if (VIPS_OBJECT_CLASS(vips_reduceh_parent_class)->build(object))
408
0
    return -1;
409
410
0
  in = resample->in;
411
412
0
  if (reduceh->hshrink < 1.0) {
413
0
    vips_error(object_class->nickname,
414
0
      "%s", _("reduce factor should be >= 1.0"));
415
0
    return -1;
416
0
  }
417
418
  /* Output size. We need to always round to nearest, so round(), not
419
   * rint().
420
   */
421
0
  width = VIPS_ROUND_UINT(
422
0
    (double) in->Xsize / reduceh->hshrink);
423
424
  /* How many pixels we are inventing in the input, -ve for
425
   * discarding.
426
   */
427
0
  extra_pixels = width * reduceh->hshrink - in->Xsize;
428
429
  /* The hshrink we do after integer reduction.
430
   */
431
0
  reduceh->residual_hshrink = reduceh->hshrink;
432
433
0
  if (reduceh->gap > 0.0 &&
434
0
    reduceh->kernel != VIPS_KERNEL_NEAREST) {
435
0
    if (reduceh->gap < 1.0) {
436
0
      vips_error(object_class->nickname,
437
0
        "%s", _("reduce gap should be >= 1.0"));
438
0
      return -1;
439
0
    }
440
441
    /* The int part of our reduce.
442
     */
443
0
    int_hshrink = VIPS_MAX(1,
444
0
      VIPS_FLOOR((double) in->Xsize / width / reduceh->gap));
445
446
0
    if (int_hshrink > 1) {
447
0
      g_info("shrinkh by %d", int_hshrink);
448
0
      if (vips_shrinkh(in, &t[0], int_hshrink,
449
0
          "ceil", TRUE,
450
0
          nullptr))
451
0
        return -1;
452
0
      in = t[0];
453
454
0
      reduceh->residual_hshrink /= int_hshrink;
455
0
      extra_pixels /= int_hshrink;
456
0
    }
457
0
  }
458
459
0
  if (reduceh->residual_hshrink == 1.0)
460
0
    return vips_image_write(in, resample->out);
461
462
0
  reduceh->n_point =
463
0
    vips_reduce_get_points(reduceh->kernel, reduceh->residual_hshrink);
464
0
  g_info("reduceh: %d point mask", reduceh->n_point);
465
0
  if (reduceh->n_point > MAX_POINT) {
466
0
    vips_error(object_class->nickname,
467
0
      "%s", _("reduce factor too large"));
468
0
    return -1;
469
0
  }
470
471
  /* If we are rounding down, we are not using some input
472
   * pixels. We need to move the origin *inside* the input image
473
   * by half that distance so that we discard pixels equally
474
   * from left and right.
475
   */
476
0
  reduceh->hoffset = (1 + extra_pixels) / 2.0 - 1;
477
478
  /* Build the tables of pre-computed coefficients.
479
   */
480
0
  for (int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++) {
481
0
    reduceh->matrixf[x] =
482
0
      VIPS_ARRAY(object, reduceh->n_point, double);
483
0
    reduceh->matrixs[x] =
484
0
      VIPS_ARRAY(object, reduceh->n_point, short);
485
0
    if (!reduceh->matrixf[x] ||
486
0
      !reduceh->matrixs[x])
487
0
      return -1;
488
489
0
    vips_reduce_make_mask(reduceh->matrixf[x], reduceh->kernel,
490
0
      reduceh->n_point, reduceh->residual_hshrink,
491
0
      (float) x / VIPS_TRANSFORM_SCALE);
492
493
0
    for (int i = 0; i < reduceh->n_point; i++)
494
0
      reduceh->matrixs[x][i] = (short) (reduceh->matrixf[x][i] *
495
0
        VIPS_INTERPOLATE_SCALE);
496
#ifdef DEBUG
497
    printf("vips_reduceh_build: mask %d\n    ", x);
498
    for (int i = 0; i < reduceh->n_point; i++)
499
      printf("%d ", reduceh->matrixs[x][i]);
500
    printf("\n");
501
#endif /*DEBUG*/
502
0
  }
503
504
  /* Unpack for processing.
505
   */
506
0
  if (vips_image_decode(in, &t[1]))
507
0
    return -1;
508
0
  in = t[1];
509
510
  /* Add new pixels around the input so we can interpolate at the edges.
511
   */
512
0
  if (vips_embed(in, &t[2],
513
0
      VIPS_CEIL(reduceh->n_point / 2.0) - 1, 0,
514
0
      in->Xsize + reduceh->n_point, in->Ysize,
515
0
      "extend", VIPS_EXTEND_COPY,
516
0
      nullptr))
517
0
    return -1;
518
0
  in = t[2];
519
520
  /* For uchar input, try to make a vector path.
521
   */
522
0
#ifdef HAVE_HWY
523
0
  if (in->BandFmt == VIPS_FORMAT_UCHAR &&
524
0
    (in->Bands == 4 || in->Bands == 3) &&
525
0
    vips_vector_isenabled()) {
526
0
    generate = vips_reduceh_uchar_vector_gen;
527
0
    g_info("reduceh: using vector path");
528
0
  }
529
0
  else
530
0
#endif /*HAVE_HWY*/
531
    /* Default to the C path.
532
     */
533
0
    generate = vips_reduceh_gen;
534
535
0
  if (vips_image_pipelinev(resample->out,
536
0
      VIPS_DEMAND_STYLE_FATSTRIP, in, nullptr))
537
0
    return -1;
538
539
  /* Don't change xres/yres, leave that to the application layer. For
540
   * example, vipsthumbnail knows the true reduce factor (including the
541
   * fractional part), we just see the integer part here.
542
   */
543
0
  resample->out->Xsize = width;
544
0
  if (resample->out->Xsize <= 0) {
545
0
    vips_error(object_class->nickname,
546
0
      "%s", _("image has shrunk to nothing"));
547
0
    return -1;
548
0
  }
549
550
#ifdef DEBUG
551
  printf("vips_reduceh_build: reducing %d x %d image to %d x %d\n",
552
    in->Xsize, in->Ysize,
553
    resample->out->Xsize, resample->out->Ysize);
554
#endif /*DEBUG*/
555
556
0
  if (vips_image_generate(resample->out,
557
0
      vips_start_one, generate, vips_stop_one,
558
0
      in, reduceh))
559
0
    return -1;
560
561
0
  vips_reorder_margin_hint(resample->out, reduceh->n_point);
562
563
0
  return 0;
564
0
}
565
566
static void
567
vips_reduceh_class_init(VipsReducehClass *reduceh_class)
568
1
{
569
1
  GObjectClass *gobject_class = G_OBJECT_CLASS(reduceh_class);
570
1
  VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(reduceh_class);
571
1
  VipsOperationClass *operation_class =
572
1
    VIPS_OPERATION_CLASS(reduceh_class);
573
574
1
  VIPS_DEBUG_MSG("vips_reduceh_class_init\n");
575
576
1
  gobject_class->set_property = vips_object_set_property;
577
1
  gobject_class->get_property = vips_object_get_property;
578
579
1
  vobject_class->nickname = "reduceh";
580
1
  vobject_class->description = _("shrink an image horizontally");
581
1
  vobject_class->build = vips_reduceh_build;
582
583
1
  operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
584
585
1
  VIPS_ARG_DOUBLE(reduceh_class, "hshrink", 3,
586
1
    _("Hshrink"),
587
1
    _("Horizontal shrink factor"),
588
1
    VIPS_ARGUMENT_REQUIRED_INPUT,
589
1
    G_STRUCT_OFFSET(VipsReduceh, hshrink),
590
1
    1.0, 1000000.0, 1.0);
591
592
1
  VIPS_ARG_ENUM(reduceh_class, "kernel", 4,
593
1
    _("Kernel"),
594
1
    _("Resampling kernel"),
595
1
    VIPS_ARGUMENT_OPTIONAL_INPUT,
596
1
    G_STRUCT_OFFSET(VipsReduceh, kernel),
597
1
    VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3);
598
599
1
  VIPS_ARG_DOUBLE(reduceh_class, "gap", 5,
600
1
    _("Gap"),
601
1
    _("Reducing gap"),
602
1
    VIPS_ARGUMENT_OPTIONAL_INPUT,
603
1
    G_STRUCT_OFFSET(VipsReduceh, gap),
604
1
    0.0, 1000000.0, 0.0);
605
606
  /* Old name.
607
   */
608
1
  VIPS_ARG_DOUBLE(reduceh_class, "xshrink", 3,
609
1
    _("Xshrink"),
610
1
    _("Horizontal shrink factor"),
611
1
    VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED,
612
1
    G_STRUCT_OFFSET(VipsReduceh, hshrink),
613
1
    1.0, 1000000.0, 1.0);
614
615
  /* We used to let people pick centre or corner, but it's automatic now.
616
   */
617
1
  VIPS_ARG_BOOL(reduceh_class, "centre", 7,
618
1
    _("Centre"),
619
1
    _("Use centre sampling convention"),
620
1
    VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
621
1
    G_STRUCT_OFFSET(VipsReduceh, centre),
622
1
    FALSE);
623
1
}
624
625
static void
626
vips_reduceh_init(VipsReduceh *reduceh)
627
0
{
628
0
  reduceh->gap = 0.0;
629
0
  reduceh->kernel = VIPS_KERNEL_LANCZOS3;
630
0
}
631
632
/* See reduce.c for the doc comment.
633
 */
634
635
int
636
vips_reduceh(VipsImage *in, VipsImage **out, double hshrink, ...)
637
0
{
638
0
  va_list ap;
639
0
  int result;
640
641
0
  va_start(ap, hshrink);
642
0
  result = vips_call_split("reduceh", ap, in, out, hshrink);
643
0
  va_end(ap);
644
645
0
  return result;
646
0
}