Coverage Report

Created: 2025-01-28 06:33

/src/libvips/libvips/resample/mapim.c
Line
Count
Source (jump to first uncovered line)
1
/* resample with an index image
2
 *
3
 * 15/11/15
4
 *  - from affine.c
5
 * 12/8/18
6
 *  - prevent float->int overflow
7
 *  - a bit quicker
8
 * 17/12/18
9
 *  - we were not offsetting pixel fetches by window_offset
10
 * 30/1/21 afontenot
11
 *  - avoid NaN
12
 * 21/12/21
13
 *  - improve edge antialiasing with "background" and "extend"
14
 *  - add "premultiplied" param
15
 */
16
17
/*
18
19
  This file is part of VIPS.
20
21
  VIPS is free software; you can redistribute it and/or modify
22
  it under the terms of the GNU Lesser General Public License as published by
23
  the Free Software Foundation; either version 2 of the License, or
24
  (at your option) any later version.
25
26
  This program is distributed in the hope that it will be useful,
27
  but WITHOUT ANY WARRANTY; without even the implied warranty of
28
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29
  GNU Lesser General Public License for more details.
30
31
  You should have received a copy of the GNU Lesser General Public License
32
  along with this program; if not, write to the Free Software
33
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
34
  02110-1301  USA
35
36
 */
37
38
/*
39
40
  These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
41
42
 */
43
44
/*
45
#define DEBUG_VERBOSE
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 <string.h>
57
#include <math.h>
58
#include <limits.h>
59
60
#include <vips/vips.h>
61
#include <vips/debug.h>
62
#include <vips/internal.h>
63
#include <vips/transform.h>
64
65
#include "presample.h"
66
67
typedef struct _VipsMapim {
68
  VipsResample parent_instance;
69
70
  VipsImage *index;
71
  VipsInterpolate *interpolate;
72
73
  /* How to generate extra edge pixels.
74
   */
75
  VipsExtend extend;
76
77
  /* Background colour.
78
   */
79
  VipsArrayDouble *background;
80
81
  /* The [double] converted to the input image format.
82
   */
83
  VipsPel *ink;
84
85
  /* True if the input is already premultiplied (and we don't need to).
86
   */
87
  gboolean premultiplied;
88
89
  /* Need an image vector for start_many / stop_many
90
   */
91
  VipsImage *in_array[3];
92
93
} VipsMapim;
94
95
typedef VipsResampleClass VipsMapimClass;
96
97
G_DEFINE_TYPE(VipsMapim, vips_mapim, VIPS_TYPE_RESAMPLE);
98
99
/* Minmax of a line of pixels.
100
 */
101
#define MINMAX(TYPE) \
102
0
  { \
103
0
    TYPE *restrict p1 = (TYPE *) p; \
104
0
\
105
0
    TYPE t_max_x = max_x; \
106
0
    TYPE t_min_x = min_x; \
107
0
    TYPE t_max_y = max_y; \
108
0
    TYPE t_min_y = min_y; \
109
0
\
110
0
    for (x = 0; x < r->width; x++) { \
111
0
      TYPE px = p1[0]; \
112
0
      TYPE py = p1[1]; \
113
0
\
114
0
      if (first) { \
115
0
        t_min_x = px; \
116
0
        t_max_x = px; \
117
0
        t_min_y = py; \
118
0
        t_max_y = py; \
119
0
\
120
0
        first = FALSE; \
121
0
      } \
122
0
      else { \
123
0
        if (px > t_max_x) \
124
0
          t_max_x = px; \
125
0
        else if (px < t_min_x) \
126
0
          t_min_x = px; \
127
0
\
128
0
        if (py > t_max_y) \
129
0
          t_max_y = py; \
130
0
        else if (py < t_min_y) \
131
0
          t_min_y = py; \
132
0
      } \
133
0
\
134
0
      p1 += 2; \
135
0
    } \
136
0
\
137
0
    min_x = t_min_x; \
138
0
    max_x = t_max_x; \
139
0
    min_y = t_min_y; \
140
0
    max_y = t_max_y; \
141
0
  }
142
143
/* Scan a region and find min/max in the two axes.
144
 */
145
static void
146
vips_mapim_region_minmax(VipsRegion *region, VipsRect *r, VipsRect *bounds)
147
0
{
148
0
  double min_x;
149
0
  double max_x;
150
0
  double min_y;
151
0
  double max_y;
152
0
  gboolean first;
153
0
  int x, y;
154
155
0
  min_x = 0.0;
156
0
  max_x = 0.0;
157
0
  min_y = 0.0;
158
0
  max_y = 0.0;
159
0
  first = TRUE;
160
0
  for (y = 0; y < r->height; y++) {
161
0
    VipsPel *restrict p =
162
0
      VIPS_REGION_ADDR(region, r->left, r->top + y);
163
164
0
    switch (region->im->BandFmt) {
165
0
    case VIPS_FORMAT_UCHAR:
166
0
      MINMAX(unsigned char);
167
0
      break;
168
169
0
    case VIPS_FORMAT_CHAR:
170
0
      MINMAX(signed char);
171
0
      break;
172
173
0
    case VIPS_FORMAT_USHORT:
174
0
      MINMAX(unsigned short);
175
0
      break;
176
177
0
    case VIPS_FORMAT_SHORT:
178
0
      MINMAX(signed short);
179
0
      break;
180
181
0
    case VIPS_FORMAT_UINT:
182
0
      MINMAX(unsigned int);
183
0
      break;
184
185
0
    case VIPS_FORMAT_INT:
186
0
      MINMAX(signed int);
187
0
      break;
188
189
0
    case VIPS_FORMAT_FLOAT:
190
0
    case VIPS_FORMAT_COMPLEX:
191
0
      MINMAX(float);
192
0
      break;
193
194
0
    case VIPS_FORMAT_DOUBLE:
195
0
    case VIPS_FORMAT_DPCOMPLEX:
196
0
      MINMAX(double);
197
0
      break;
198
199
0
    default:
200
0
      g_assert_not_reached();
201
0
    }
202
0
  }
203
204
  /* bounds is the bounding box -- we must round left/top down and round
205
   * bottom/right up.
206
   */
207
0
  min_x = floor(min_x);
208
0
  min_y = floor(min_y);
209
0
  max_x = ceil(max_x);
210
0
  max_y = ceil(max_y);
211
212
  /* bounds uses ints, so we must clip the range down from double.
213
   * Coordinates can be negative for the antialias edges.
214
   */
215
0
  min_x = VIPS_CLIP(-1, min_x, VIPS_MAX_COORD);
216
0
  min_y = VIPS_CLIP(-1, min_y, VIPS_MAX_COORD);
217
0
  max_x = VIPS_CLIP(-1, max_x, VIPS_MAX_COORD);
218
0
  max_y = VIPS_CLIP(-1, max_y, VIPS_MAX_COORD);
219
220
0
  bounds->left = min_x;
221
0
  bounds->top = min_y;
222
0
  bounds->width = (max_x - min_x) + 1;
223
0
  bounds->height = (max_y - min_y) + 1;
224
0
}
225
226
/* Unsigned int types.
227
 */
228
#define ULOOKUP(TYPE) \
229
0
  { \
230
0
    TYPE *restrict p1 = (TYPE *) p; \
231
0
\
232
0
    for (x = 0; x < r->width; x++) { \
233
0
      TYPE px = p1[0]; \
234
0
      TYPE py = p1[1]; \
235
0
\
236
0
      if (px >= clip_width || \
237
0
        py >= clip_height) { \
238
0
        for (z = 0; z < ps; z++) \
239
0
          q[z] = mapim->ink[z]; \
240
0
      } \
241
0
      else \
242
0
        interpolate(mapim->interpolate, q, ir[0], \
243
0
          px + window_offset + 1, \
244
0
          py + window_offset + 1); \
245
0
\
246
0
      p1 += 2; \
247
0
      q += ps; \
248
0
    } \
249
0
  }
250
251
/* Signed int types. We allow -1 for x/y to get edge antialiasing.
252
 */
253
#define LOOKUP(TYPE) \
254
0
  { \
255
0
    TYPE *restrict p1 = (TYPE *) p; \
256
0
\
257
0
    for (x = 0; x < r->width; x++) { \
258
0
      TYPE px = p1[0]; \
259
0
      TYPE py = p1[1]; \
260
0
\
261
0
      if (px < -1 || \
262
0
        px >= clip_width || \
263
0
        py < -1 || \
264
0
        py >= clip_height) { \
265
0
        for (z = 0; z < ps; z++) \
266
0
          q[z] = mapim->ink[z]; \
267
0
      } \
268
0
      else \
269
0
        interpolate(mapim->interpolate, q, ir[0], \
270
0
          px + window_offset + 1, \
271
0
          py + window_offset + 1); \
272
0
\
273
0
      p1 += 2; \
274
0
      q += ps; \
275
0
    } \
276
0
  }
277
278
/* Float types. We allow -1 for x/y to get edge antialiasing.
279
 */
280
#define FLOOKUP(TYPE) \
281
0
  { \
282
0
    TYPE *restrict p1 = (TYPE *) p; \
283
0
\
284
0
    for (x = 0; x < r->width; x++) { \
285
0
      TYPE px = p1[0]; \
286
0
      TYPE py = p1[1]; \
287
0
\
288
0
      if (VIPS_ISNAN(px) || \
289
0
        VIPS_ISNAN(py) || \
290
0
        px < -1 || \
291
0
        px >= clip_width || \
292
0
        py < -1 || \
293
0
        py >= clip_height) { \
294
0
        for (z = 0; z < ps; z++) \
295
0
          q[z] = mapim->ink[z]; \
296
0
      } \
297
0
      else \
298
0
        interpolate(mapim->interpolate, q, ir[0], \
299
0
          px + window_offset + 1, \
300
0
          py + window_offset + 1); \
301
0
\
302
0
      p1 += 2; \
303
0
      q += ps; \
304
0
    } \
305
0
  }
306
307
static int
308
vips_mapim_gen(VipsRegion *out_region,
309
  void *seq, void *a, void *b, gboolean *stop)
310
0
{
311
0
  VipsRect *r = &out_region->valid;
312
0
  VipsRegion **ir = (VipsRegion **) seq;
313
0
  const VipsImage **in_array = (const VipsImage **) a;
314
0
  const VipsMapim *mapim = (VipsMapim *) b;
315
0
  const VipsImage *in = in_array[0];
316
0
  const int window_size =
317
0
    vips_interpolate_get_window_size(mapim->interpolate);
318
0
  const int window_offset =
319
0
    vips_interpolate_get_window_offset(mapim->interpolate);
320
0
  const VipsInterpolateMethod interpolate =
321
0
    vips_interpolate_get_method(mapim->interpolate);
322
0
  const int ps = VIPS_IMAGE_SIZEOF_PEL(in);
323
0
  const int clip_width = in->Xsize - window_size;
324
0
  const int clip_height = in->Ysize - window_size;
325
326
0
  VipsRect bounds, need, image, clipped;
327
0
  int x, y, z;
328
329
#ifdef DEBUG_VERBOSE
330
  printf("vips_mapim_gen: generating left=%d, top=%d, width=%d, height=%d\n",
331
    r->left,
332
    r->top,
333
    r->width,
334
    r->height);
335
#endif /*DEBUG_VERBOSE*/
336
337
  /* Fetch the chunk of the index image we need, and find the max/min in
338
   * x and y.
339
   */
340
0
  if (vips_region_prepare(ir[1], r))
341
0
    return -1;
342
343
0
  VIPS_GATE_START("vips_mapim_gen: work");
344
345
0
  vips_mapim_region_minmax(ir[1], r, &bounds);
346
347
0
  VIPS_GATE_STOP("vips_mapim_gen: work");
348
349
  /* Enlarge by the stencil size.
350
   */
351
0
  need.width = bounds.width + window_size - 1;
352
0
  need.height = bounds.height + window_size - 1;
353
354
  /* Offset for the antialias edge we have top and left.
355
   */
356
0
  need.left = bounds.left + 1;
357
0
  need.top = bounds.top + 1;
358
359
  /* Clip against the expanded image.
360
   */
361
0
  image.left = 0;
362
0
  image.top = 0;
363
0
  image.width = in->Xsize;
364
0
  image.height = in->Ysize;
365
0
  vips_rect_intersectrect(&need, &image, &clipped);
366
367
#ifdef DEBUG_VERBOSE
368
  printf("vips_mapim_gen: preparing left=%d, top=%d, width=%d, height=%d\n",
369
    clipped.left,
370
    clipped.top,
371
    clipped.width,
372
    clipped.height);
373
#endif /*DEBUG_VERBOSE*/
374
375
0
  if (vips_rect_isempty(&clipped)) {
376
0
    vips_region_paint_pel(out_region, r, mapim->ink);
377
0
    return 0;
378
0
  }
379
0
  if (vips_region_prepare(ir[0], &clipped))
380
0
    return -1;
381
382
0
  VIPS_GATE_START("vips_mapim_gen: work");
383
384
  /* Resample! x/y loop over pixels in the output (and index) images.
385
   */
386
0
  for (y = 0; y < r->height; y++) {
387
0
    VipsPel *restrict p =
388
0
      VIPS_REGION_ADDR(ir[1], r->left, y + r->top);
389
0
    VipsPel *restrict q =
390
0
      VIPS_REGION_ADDR(out_region, r->left, y + r->top);
391
392
0
    switch (ir[1]->im->BandFmt) {
393
0
    case VIPS_FORMAT_UCHAR:
394
0
      ULOOKUP(unsigned char);
395
0
      break;
396
0
    case VIPS_FORMAT_CHAR:
397
0
      LOOKUP(signed char);
398
0
      break;
399
0
    case VIPS_FORMAT_USHORT:
400
0
      ULOOKUP(unsigned short);
401
0
      break;
402
0
    case VIPS_FORMAT_SHORT:
403
0
      LOOKUP(signed short);
404
0
      break;
405
0
    case VIPS_FORMAT_UINT:
406
0
      ULOOKUP(unsigned int);
407
0
      break;
408
0
    case VIPS_FORMAT_INT:
409
0
      LOOKUP(signed int);
410
0
      break;
411
0
    case VIPS_FORMAT_FLOAT:
412
0
    case VIPS_FORMAT_COMPLEX:
413
0
      FLOOKUP(float);
414
0
      break;
415
0
    case VIPS_FORMAT_DOUBLE:
416
0
    case VIPS_FORMAT_DPCOMPLEX:
417
0
      FLOOKUP(double);
418
0
      break;
419
420
0
    default:
421
0
      g_assert_not_reached();
422
0
    }
423
0
  }
424
425
0
  VIPS_GATE_STOP("vips_mapim_gen: work");
426
427
0
  return 0;
428
0
}
429
430
static int
431
vips_mapim_build(VipsObject *object)
432
0
{
433
0
  VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
434
0
  VipsResample *resample = VIPS_RESAMPLE(object);
435
0
  VipsMapim *mapim = (VipsMapim *) object;
436
0
  VipsImage **t = (VipsImage **) vips_object_local_array(object, 6);
437
438
0
  VipsImage *in;
439
0
  int window_size;
440
0
  int window_offset;
441
442
  /* TRUE if we've premultiplied and need to unpremultiply.
443
   */
444
0
  gboolean have_premultiplied;
445
0
  VipsBandFormat unpremultiplied_format;
446
447
0
  if (VIPS_OBJECT_CLASS(vips_mapim_parent_class)->build(object))
448
0
    return -1;
449
450
0
  if (vips_check_coding_known(class->nickname, resample->in) ||
451
0
    vips_check_twocomponents(class->nickname, mapim->index))
452
0
    return -1;
453
454
0
  in = resample->in;
455
456
0
  if (vips_image_decode(in, &t[0]))
457
0
    return -1;
458
0
  in = t[0];
459
460
0
  window_size = vips_interpolate_get_window_size(mapim->interpolate);
461
0
  window_offset =
462
0
    vips_interpolate_get_window_offset(mapim->interpolate);
463
464
  /* Add new pixels around the input so we can interpolate at the edges.
465
   *
466
   * We add the interpolate stencil, plus one extra pixel on all the
467
   * edges. This means when we clip in generate (above) we can be sure
468
   * we clip outside the real pixels and don't get jaggies on edges.
469
   *
470
   * We allow for the +1 in the adjustment to window_offset in generate.
471
   */
472
0
  if (vips_embed(in, &t[1],
473
0
      window_offset + 1, window_offset + 1,
474
0
      in->Xsize + window_size - 1 + 2,
475
0
      in->Ysize + window_size - 1 + 2,
476
0
      "extend", mapim->extend,
477
0
      "background", mapim->background,
478
0
      NULL))
479
0
    return -1;
480
0
  in = t[1];
481
482
  /* If there's an alpha and we've not premultiplied, we have to
483
   * premultiply before resampling.
484
   */
485
0
  have_premultiplied = FALSE;
486
0
  if (vips_image_hasalpha(in) &&
487
0
    !mapim->premultiplied) {
488
0
    if (vips_premultiply(in, &t[2], NULL))
489
0
      return -1;
490
0
    have_premultiplied = TRUE;
491
492
    /* vips_premultiply() makes a float image. When we
493
     * vips_unpremultiply() below, we need to cast back to the
494
     * pre-premultiply format.
495
     */
496
0
    unpremultiplied_format = in->BandFmt;
497
0
    in = t[2];
498
0
  }
499
500
  /* Convert the background to the image's format.
501
   */
502
0
  if (!(mapim->ink = vips__vector_to_ink(class->nickname,
503
0
        in,
504
0
        VIPS_AREA(mapim->background)->data, NULL,
505
0
        VIPS_AREA(mapim->background)->n)))
506
0
    return -1;
507
508
0
  t[3] = vips_image_new();
509
0
  if (vips_image_pipelinev(t[3], VIPS_DEMAND_STYLE_SMALLTILE,
510
0
      in, NULL))
511
0
    return -1;
512
513
0
  t[3]->Xsize = mapim->index->Xsize;
514
0
  t[3]->Ysize = mapim->index->Ysize;
515
516
0
  mapim->in_array[0] = in;
517
0
  mapim->in_array[1] = mapim->index;
518
0
  mapim->in_array[2] = NULL;
519
0
  if (vips_image_generate(t[3],
520
0
      vips_start_many, vips_mapim_gen, vips_stop_many,
521
0
      mapim->in_array, mapim))
522
0
    return -1;
523
524
0
  in = t[3];
525
526
0
  if (have_premultiplied) {
527
0
    if (vips_unpremultiply(in, &t[4], NULL) ||
528
0
      vips_cast(t[4], &t[5], unpremultiplied_format, NULL))
529
0
      return -1;
530
0
    in = t[5];
531
0
  }
532
533
0
  if (vips_image_write(in, resample->out))
534
0
    return -1;
535
536
0
  return 0;
537
0
}
538
539
static void
540
vips_mapim_class_init(VipsMapimClass *class)
541
1
{
542
1
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
543
1
  VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);
544
545
1
  VIPS_DEBUG_MSG("vips_mapim_class_init\n");
546
547
1
  gobject_class->set_property = vips_object_set_property;
548
1
  gobject_class->get_property = vips_object_get_property;
549
550
1
  vobject_class->nickname = "mapim";
551
1
  vobject_class->description = _("resample with a map image");
552
1
  vobject_class->build = vips_mapim_build;
553
554
1
  VIPS_ARG_IMAGE(class, "index", 3,
555
1
    _("Index"),
556
1
    _("Index pixels with this"),
557
1
    VIPS_ARGUMENT_REQUIRED_INPUT,
558
1
    G_STRUCT_OFFSET(VipsMapim, index));
559
560
1
  VIPS_ARG_INTERPOLATE(class, "interpolate", 4,
561
1
    _("Interpolate"),
562
1
    _("Interpolate pixels with this"),
563
1
    VIPS_ARGUMENT_OPTIONAL_INPUT,
564
1
    G_STRUCT_OFFSET(VipsMapim, interpolate));
565
566
1
  VIPS_ARG_ENUM(class, "extend", 117,
567
1
    _("Extend"),
568
1
    _("How to generate the extra pixels"),
569
1
    VIPS_ARGUMENT_OPTIONAL_INPUT,
570
1
    G_STRUCT_OFFSET(VipsMapim, extend),
571
1
    VIPS_TYPE_EXTEND, VIPS_EXTEND_BACKGROUND);
572
573
1
  VIPS_ARG_BOXED(class, "background", 116,
574
1
    _("Background"),
575
1
    _("Background value"),
576
1
    VIPS_ARGUMENT_OPTIONAL_INPUT,
577
1
    G_STRUCT_OFFSET(VipsMapim, background),
578
1
    VIPS_TYPE_ARRAY_DOUBLE);
579
580
1
  VIPS_ARG_BOOL(class, "premultiplied", 117,
581
1
    _("Premultiplied"),
582
1
    _("Images have premultiplied alpha"),
583
1
    VIPS_ARGUMENT_OPTIONAL_INPUT,
584
1
    G_STRUCT_OFFSET(VipsMapim, premultiplied),
585
1
    FALSE);
586
1
}
587
588
static void
589
vips_mapim_init(VipsMapim *mapim)
590
0
{
591
0
  mapim->interpolate = vips_interpolate_new("bilinear");
592
0
  mapim->extend = VIPS_EXTEND_BACKGROUND;
593
0
  mapim->background = vips_array_double_newv(1, 0.0);
594
0
}
595
596
/**
597
 * vips_mapim: (method)
598
 * @in: input image
599
 * @out: (out): output image
600
 * @index: index image
601
 * @...: %NULL-terminated list of optional named arguments
602
 *
603
 * Optional arguments:
604
 *
605
 * * @interpolate: interpolate pixels with this
606
 * * @extend: #VipsExtend how to generate new pixels
607
 * * @background: #VipsArrayDouble colour for new pixels
608
 * * @premultiplied: %gboolean, images are already premultiplied
609
 *
610
 * This operator resamples @in using @index to look up pixels. @out is
611
 * the same size as @index, with each pixel being fetched from that position in
612
 * @in. That is:
613
 *
614
 * |[
615
 * out[x, y] = in[index[x, y]]
616
 * ]|
617
 *
618
 * If @index has one band, that band must be complex. Otherwise, @index must
619
 * have two bands of any format.
620
 *
621
 * Coordinates in @index are in pixels, with (0, 0) being the top-left corner
622
 * of @in, and with y increasing down the image. Use vips_xyz() to build index
623
 * images.
624
 *
625
 * @interpolate defaults to bilinear.
626
 *
627
 * By default, new pixels are filled with @background. This defaults to
628
 * zero (black). You can set other extend types with @extend. #VIPS_EXTEND_COPY
629
 * is better for image upsizing.
630
 *
631
 * Image are normally treated as unpremultiplied, so this operation can be used
632
 * directly on PNG images. If your images have been through vips_premultiply(),
633
 * set @premultiplied.
634
 *
635
 * This operation does not change xres or yres. The image resolution needs to
636
 * be updated by the application.
637
 *
638
 * See vips_maplut() for a 1D equivalent of this operation.
639
 *
640
 * See also: vips_xyz(), vips_affine(), vips_resize(),
641
 * vips_maplut(), #VipsInterpolate.
642
 *
643
 * Returns: 0 on success, -1 on error
644
 */
645
int
646
vips_mapim(VipsImage *in, VipsImage **out, VipsImage *index, ...)
647
0
{
648
0
  va_list ap;
649
0
  int result;
650
651
0
  va_start(ap, index);
652
0
  result = vips_call_split("mapim", ap, in, out, index);
653
0
  va_end(ap);
654
655
0
  return result;
656
0
}