/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 | } |