Coverage Report

Created: 2026-08-11 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libvips/libvips/resample/thumbnail.c
Line
Count
Source
1
/* make a thumbnail ... wraps up the process of thumbnailing, including
2
 * premultiply, colour management etc etc
3
 *
4
 * 2/11/16
5
 *  - from vipsthumbnail.c
6
 * 6/1/17
7
 *  - add @size parameter
8
 * 4/5/17
9
 *  - add FORCE
10
 * 29/5/17
11
 *  - don't cache (thanks tomasc)
12
 * 30/8/17
13
 *  - add intent option, thanks kleisauke
14
 * 31/10/18
15
 *  - deprecate auto_rotate, add no_rotate
16
 *  - implement shrink-on-load for openslide images
17
 * 16/11/18
18
 *  - implement shrink-on-load for tiff pyramid
19
 * 3/2/19 kleisauke
20
 *  - add option_string param to thumbnail_buffer
21
 * 23/4/19
22
 *  - don't force import CMYK, since colourspace knows about it now
23
 * 24/4/19
24
 *  - support multi-page (animated) images
25
 * 27/8/19 kleisauke
26
 *  - prevent over-pre-shrink in thumbnail
27
 * 30/9/19
28
 *  - smarter heif thumbnail selection
29
 * 12/10/19
30
 *  - add thumbnail_source
31
 * 2/6/20
32
 *  - add subifd pyr support
33
 * 27/2/21
34
 *  - simplify rules re. processing space, colour management and linear
35
 *    mode
36
 * 14/4/22
37
 *  - add a seq to thumbnail_image to stop cache thrashing
38
 * 28/4/22
39
 *  - add fail_on
40
 * 1/3/23 kleisauke
41
 *  - skip colourspace conversion when needed
42
 * 27/1/24
43
 *  - make icc profile transforms always write 8 bits
44
 * 22/8/25 kleisauke
45
 *  - remove seq line cache from thumbnail_image, use hint instead
46
 */
47
48
/*
49
50
  Copyright (C) 1991-2005 The National Gallery
51
52
  This library is free software; you can redistribute it and/or
53
  modify it under the terms of the GNU Lesser General Public
54
  License as published by the Free Software Foundation; either
55
  version 2.1 of the License, or (at your option) any later version.
56
57
  This library is distributed in the hope that it will be useful,
58
  but WITHOUT ANY WARRANTY; without even the implied warranty of
59
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
60
  Lesser General Public License for more details.
61
62
  You should have received a copy of the GNU Lesser General Public
63
  License along with this library; if not, write to the Free Software
64
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
65
  02110-1301  USA
66
67
 */
68
69
/*
70
71
  These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
72
73
 */
74
75
/*
76
#define DEBUG
77
 */
78
79
#ifdef HAVE_CONFIG_H
80
#include <config.h>
81
#endif /*HAVE_CONFIG_H*/
82
#include <glib/gi18n-lib.h>
83
84
#include <stdio.h>
85
#include <stdlib.h>
86
#include <math.h>
87
88
#include <vips/vips.h>
89
#include <vips/internal.h>
90
91
#define VIPS_TYPE_THUMBNAIL (vips_thumbnail_get_type())
92
#define VIPS_THUMBNAIL(obj) \
93
28.7k
  (G_TYPE_CHECK_INSTANCE_CAST((obj), VIPS_TYPE_THUMBNAIL, VipsThumbnail))
94
#define VIPS_THUMBNAIL_CLASS(klass) \
95
76
  (G_TYPE_CHECK_CLASS_CAST((klass), \
96
76
    VIPS_TYPE_THUMBNAIL, VipsThumbnailClass))
97
#define VIPS_IS_THUMBNAIL(obj) \
98
  (G_TYPE_CHECK_INSTANCE_TYPE((obj), VIPS_TYPE_THUMBNAIL))
99
#define VIPS_IS_THUMBNAIL_CLASS(klass) \
100
  (G_TYPE_CHECK_CLASS_TYPE((klass), VIPS_TYPE_THUMBNAIL))
101
#define VIPS_THUMBNAIL_GET_CLASS(obj) \
102
29.0k
  (G_TYPE_INSTANCE_GET_CLASS((obj), \
103
29.0k
    VIPS_TYPE_THUMBNAIL, VipsThumbnailClass))
104
105
/* Should be plenty.
106
 */
107
#define MAX_LEVELS (256)
108
109
typedef struct _VipsThumbnail {
110
  VipsOperation parent_instance;
111
112
  VipsImage *out;
113
  int width;
114
  int height;
115
  VipsSize size;
116
117
  gboolean auto_rotate;
118
  gboolean no_rotate;
119
  VipsInteresting crop;
120
  int interesting_x;
121
  int interesting_y;
122
  gboolean linear;
123
  char *output_profile;
124
  char *input_profile;
125
  VipsIntent intent;
126
  VipsFailOn fail_on;
127
128
  /* Bits of info we read from the input image when we get the header of
129
   * the original.
130
   */
131
  const char *loader; /* Eg. "VipsForeignLoadJpeg*" */
132
  int input_width;
133
  int input_height;
134
  int page_height;
135
  int orientation;  /* From vips_image_get_orientation() */
136
  gboolean swap;    /* If we must swap width / height */
137
  int n_pages;    /* Pages in file */
138
  int n_loaded_pages; /* Pages we've loaded from file */
139
  int n_subifds;    /* Number of subifds */
140
141
  /* For pyramidal formats, we need to read out the size of each level.
142
   */
143
  int level_count;
144
  int level_width[MAX_LEVELS];
145
  int level_height[MAX_LEVELS];
146
147
  /* For HEIF, try to fetch the size of the stored thumbnail.
148
   */
149
  int heif_thumbnail_width;
150
  int heif_thumbnail_height;
151
152
  /* Pyramids are stored in subifds.
153
   */
154
  gboolean subifd_pyramid;
155
156
  /* Pyramids are stored in pages.
157
   */
158
  gboolean page_pyramid;
159
160
} VipsThumbnail;
161
162
typedef struct _VipsThumbnailClass {
163
  VipsOperationClass parent_class;
164
165
  /* Fill out the info section of VipsThumbnail from the input object.
166
   */
167
  int (*get_info)(VipsThumbnail *thumbnail);
168
169
  /* Open with some kind of shrink or scale factor. Exactly what we pass
170
   * and to what param depends on the loader. It'll be an integer shrink
171
   * factor for vips_jpegload(), a double scale factor for vips_svgload().
172
   *
173
   * See VipsThumbnail::loader
174
   */
175
  VipsImage *(*open)(VipsThumbnail *thumbnail, double factor);
176
177
} VipsThumbnailClass;
178
179
156
G_DEFINE_ABSTRACT_TYPE(VipsThumbnail, vips_thumbnail, VIPS_TYPE_OPERATION);
180
156
181
156
static void
182
156
vips_thumbnail_dispose(GObject *gobject)
183
28.9k
{
184
#ifdef DEBUG
185
  printf("vips_thumbnail_dispose: ");
186
  vips_object_print_name(VIPS_OBJECT(gobject));
187
  printf("\n");
188
#endif /*DEBUG*/
189
190
28.9k
  G_OBJECT_CLASS(vips_thumbnail_parent_class)->dispose(gobject);
191
28.9k
}
192
193
static void
194
vips_thumbnail_finalize(GObject *gobject)
195
28.9k
{
196
#ifdef DEBUG
197
  printf("vips_thumbnail_finalize: ");
198
  vips_object_print_name(VIPS_OBJECT(gobject));
199
  printf("\n");
200
#endif /*DEBUG*/
201
202
28.9k
  G_OBJECT_CLASS(vips_thumbnail_parent_class)->finalize(gobject);
203
28.9k
}
204
205
/* Fetch an int openslide field from metadata. These are all represented as
206
 * strings. Return the default value if there's any problem.
207
 */
208
static int
209
get_int(VipsImage *image, const char *field, int default_value)
210
0
{
211
0
  const char *str;
212
213
0
  if (vips_image_get_typeof(image, field) &&
214
0
    !vips_image_get_string(image, field, &str))
215
0
    return atoi(str);
216
217
0
  return default_value;
218
0
}
219
220
static void
221
vips_thumbnail_read_header(VipsThumbnail *thumbnail, VipsImage *image)
222
28.7k
{
223
28.7k
  thumbnail->input_width = image->Xsize;
224
28.7k
  thumbnail->input_height = image->Ysize;
225
28.7k
  thumbnail->orientation = vips_image_get_orientation(image);
226
28.7k
  thumbnail->swap = vips_image_get_orientation_swap(image);
227
28.7k
  thumbnail->page_height = vips_image_get_page_height(image);
228
28.7k
  thumbnail->n_pages = vips_image_get_n_pages(image);
229
28.7k
  thumbnail->n_subifds = vips_image_get_n_subifds(image);
230
231
  /* VIPS_META_N_PAGES is the number of pages in the document,
232
   * not the number we've read out into this image. We calculate
233
   * ourselves from page_height.
234
   *
235
   * vips_image_get_page_height() has verified that Ysize is a simple
236
   * multiple of page_height.
237
   */
238
28.7k
  thumbnail->n_loaded_pages =
239
28.7k
    thumbnail->input_height / thumbnail->page_height;
240
241
  /* For openslide, read out the level structure too.
242
   */
243
28.7k
  if (vips_isprefix("VipsForeignLoadOpenslide", thumbnail->loader)) {
244
0
    int level_count;
245
0
    int level;
246
247
0
    level_count = get_int(image, "openslide.level-count", 1);
248
0
    level_count = VIPS_CLIP(1, level_count, MAX_LEVELS);
249
0
    thumbnail->level_count = level_count;
250
251
0
    for (level = 0; level < level_count; level++) {
252
0
      char name[256];
253
254
0
      g_snprintf(name, 256, "openslide.level[%d].width", level);
255
0
      thumbnail->level_width[level] = get_int(image, name, 0);
256
0
      g_snprintf(name, 256, "openslide.level[%d].height", level);
257
0
      thumbnail->level_height[level] = get_int(image, name, 0);
258
0
    }
259
0
  }
260
28.7k
}
261
262
/* Detect a pyramid made of pages following a roughly /2 shrink.
263
 *
264
 * This may not be a pyr tiff, so no error if we can't find the layers.
265
 */
266
static void
267
vips_thumbnail_get_pyramid_page(VipsThumbnail *thumbnail)
268
132
{
269
132
  VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS(thumbnail);
270
132
  int i;
271
272
#ifdef DEBUG
273
  printf("vips_thumbnail_get_pyramid_page:\n");
274
#endif /*DEBUG*/
275
276
  /* Single-page docs can't be pyramids, more than 30 levels will int
277
   * overflow.
278
   */
279
132
  if (thumbnail->n_pages < 2 ||
280
25
    thumbnail->n_pages > 29)
281
107
    return;
282
283
81
  for (i = 0; i < thumbnail->n_pages; i++) {
284
59
    VipsImage *page;
285
59
    int level_width;
286
59
    int level_height;
287
59
    int expected_level_width;
288
59
    int expected_level_height;
289
290
59
    if (!(page = class->open(thumbnail, i)))
291
3
      return;
292
56
    level_width = page->Xsize;
293
56
    level_height = page->Ysize;
294
56
    VIPS_UNREF(page);
295
296
56
    expected_level_width = thumbnail->input_width / (1 << i);
297
56
    expected_level_height = thumbnail->input_height / (1 << i);
298
299
    /* This won't be exact due to rounding etc.
300
     */
301
56
    if (abs(level_width - expected_level_width) > 5 ||
302
56
      level_width < 2)
303
0
      return;
304
56
    if (abs(level_height - expected_level_height) > 5 ||
305
56
      level_height < 2)
306
0
      return;
307
308
56
    thumbnail->level_width[i] = level_width;
309
56
    thumbnail->level_height[i] = level_height;
310
56
  }
311
312
  /* Now set level_count. This signals that we've found a pyramid.
313
   */
314
#ifdef DEBUG
315
  printf("vips_thumbnail_get_pyramid_page: %d layer pyramid detected\n",
316
    thumbnail->n_pages);
317
  for (int i = 0; i < thumbnail->n_pages; i++)
318
    printf("  %d - %d x %d\n",
319
      i, thumbnail->level_width[i], thumbnail->level_height[i]);
320
#endif /*DEBUG*/
321
22
  thumbnail->level_count = thumbnail->n_pages;
322
22
}
323
324
/* Detect a TIFF pyramid made of subifds following a roughly /2 shrink.
325
 *
326
 * This may not be a pyr tiff, so no error if we can't find the layers.
327
 */
328
static void
329
vips_thumbnail_get_tiff_pyramid_subifd(VipsThumbnail *thumbnail)
330
132
{
331
132
  VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS(thumbnail);
332
132
  int i;
333
334
#ifdef DEBUG
335
  printf("vips_thumbnail_get_tiff_pyramid_subifd:\n");
336
#endif /*DEBUG*/
337
338
  /* Very small pyramids are useless, large ones int overflow.
339
   */
340
132
  if (thumbnail->n_subifds < 1 ||
341
0
    thumbnail->n_subifds > 28)
342
132
    return;
343
344
0
  for (i = 0; i < thumbnail->n_subifds; i++) {
345
0
    VipsImage *page;
346
0
    int level_width;
347
0
    int level_height;
348
0
    int expected_level_width;
349
0
    int expected_level_height;
350
351
0
    if (!(page = class->open(thumbnail, i)))
352
0
      return;
353
0
    level_width = page->Xsize;
354
0
    level_height = page->Ysize;
355
0
    VIPS_UNREF(page);
356
357
    /* The main image is size 1, subifd 0 is half that.
358
     */
359
0
    expected_level_width = thumbnail->input_width / (2 << i);
360
0
    expected_level_height = thumbnail->input_height / (2 << i);
361
362
    /* This won't be exact due to rounding etc.
363
     */
364
0
    if (abs(level_width - expected_level_width) > 5 ||
365
0
      level_width < 2)
366
0
      return;
367
0
    if (abs(level_height - expected_level_height) > 5 ||
368
0
      level_height < 2)
369
0
      return;
370
371
0
    thumbnail->level_width[i] = level_width;
372
0
    thumbnail->level_height[i] = level_height;
373
0
  }
374
375
  /* Now set level_count. This signals that we've found a pyramid.
376
   */
377
#ifdef DEBUG
378
  printf("vips_thumbnail_get_tiff_pyramid_subifd: "
379
       "%d layer pyramid detected\n",
380
    thumbnail->n_subifds);
381
#endif /*DEBUG*/
382
0
  thumbnail->level_count = thumbnail->n_subifds;
383
0
}
384
385
static int
386
vips_thumbnail_get_heif_thumb_info(VipsThumbnail *thumbnail)
387
34
{
388
34
  VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS(thumbnail);
389
390
34
  VipsImage *thumb;
391
392
34
  if (!(thumb = class->open(thumbnail, 1)))
393
0
    return -1;
394
395
34
  if (thumb->Xsize < thumbnail->input_width) {
396
0
    thumbnail->heif_thumbnail_width = thumb->Xsize;
397
0
    thumbnail->heif_thumbnail_height = thumb->Ysize;
398
0
  }
399
400
34
  VIPS_UNREF(thumb);
401
402
34
  return 0;
403
34
}
404
405
/* Calculate the shrink factor, taking into account auto-rotate, the fit mode,
406
 * and so on.
407
 *
408
 * The hshrink/vshrink are the amount to shrink the input image axes by in
409
 * order for the output axes (ie. after rotation) to match the required
410
 * thumbnail->width, thumbnail->height and fit mode.
411
 */
412
static void
413
vips_thumbnail_calculate_shrink(VipsThumbnail *thumbnail,
414
  int input_width, int input_height, double *hshrink, double *vshrink)
415
29.0k
{
416
  /* If we will be rotating, swap the target width and height.
417
   */
418
29.0k
  gboolean rotate = thumbnail->swap && thumbnail->auto_rotate;
419
29.0k
  int target_width = rotate ? thumbnail->height : thumbnail->width;
420
29.0k
  int target_height = rotate ? thumbnail->width : thumbnail->height;
421
422
29.0k
  VipsDirection direction;
423
424
  /* Calculate the horizontal and vertical shrink we'd need to fit the
425
   * image to the bounding box, and pick the biggest.
426
   *
427
   * In crop mode, we aim to fill the bounding box, so we must use the
428
   * smaller axis.
429
   */
430
29.0k
  *hshrink = (double) input_width / target_width;
431
29.0k
  *vshrink = (double) input_height / target_height;
432
433
29.0k
  if (thumbnail->crop != VIPS_INTERESTING_NONE) {
434
29
    if (*hshrink < *vshrink)
435
0
      direction = VIPS_DIRECTION_HORIZONTAL;
436
29
    else
437
29
      direction = VIPS_DIRECTION_VERTICAL;
438
29
  }
439
29.0k
  else {
440
29.0k
    if (*hshrink < *vshrink)
441
7.34k
      direction = VIPS_DIRECTION_VERTICAL;
442
21.6k
    else
443
21.6k
      direction = VIPS_DIRECTION_HORIZONTAL;
444
29.0k
  }
445
446
29.0k
  if (thumbnail->size != VIPS_SIZE_FORCE) {
447
29.0k
    if (direction == VIPS_DIRECTION_HORIZONTAL)
448
21.6k
      *vshrink = *hshrink;
449
7.37k
    else
450
7.37k
      *hshrink = *vshrink;
451
29.0k
  }
452
453
29.0k
  if (thumbnail->size == VIPS_SIZE_UP) {
454
2
    *hshrink = VIPS_MIN(1, *hshrink);
455
2
    *vshrink = VIPS_MIN(1, *vshrink);
456
2
  }
457
29.0k
  else if (thumbnail->size == VIPS_SIZE_DOWN) {
458
29
    *hshrink = VIPS_MAX(1, *hshrink);
459
29
    *vshrink = VIPS_MAX(1, *vshrink);
460
29
  }
461
462
  /* We don't want to shrink so much that we send an axis to 0.
463
   */
464
29.0k
  *hshrink = VIPS_MIN(*hshrink, input_width);
465
29.0k
  *vshrink = VIPS_MIN(*vshrink, input_height);
466
29.0k
}
467
468
/* Just the common part of the shrink: the bit by which both axes must be
469
 * shrunk.
470
 */
471
static double
472
vips_thumbnail_calculate_common_shrink(VipsThumbnail *thumbnail,
473
  int width, int height)
474
283
{
475
283
  double hshrink;
476
283
  double vshrink;
477
283
  double shrink;
478
479
283
  vips_thumbnail_calculate_shrink(thumbnail, width, height,
480
283
    &hshrink, &vshrink);
481
482
283
  shrink = VIPS_MIN(hshrink, vshrink);
483
484
283
  return shrink;
485
283
}
486
487
/* Find the best jpeg preload shrink.
488
 */
489
static int
490
vips_thumbnail_find_jpegshrink(VipsThumbnail *thumbnail,
491
  int width, int height)
492
153
{
493
153
  double shrink = vips_thumbnail_calculate_common_shrink(thumbnail,
494
153
    width, height);
495
496
  /* We can't use pre-shrunk images in linear mode. libjpeg shrinks in Y
497
   * (of YCbCR), not linear space.
498
   */
499
153
  if (thumbnail->linear)
500
0
    return 1;
501
502
  /* Shrink-on-load is a simple block shrink and will add quite a bit of
503
   * extra sharpness to the image. We want to block shrink to a
504
   * bit above our target, then vips_shrink / vips_reduce() to the
505
   * final size.
506
   *
507
   * Leave at least a factor of two for the final resize step.
508
   */
509
153
  if (shrink >= 16)
510
100
    return 8;
511
53
  else if (shrink >= 8)
512
15
    return 4;
513
38
  else if (shrink >= 4)
514
28
    return 2;
515
10
  else
516
10
    return 1;
517
153
}
518
519
/* Find the best pyramid (openslide, tiff, etc.) level.
520
 */
521
static int
522
vips_thumbnail_find_pyrlevel(VipsThumbnail *thumbnail,
523
  int width, int height)
524
22
{
525
22
  int level;
526
527
22
  g_assert(thumbnail->level_count > 0);
528
22
  g_assert(thumbnail->level_count <= MAX_LEVELS);
529
530
24
  for (level = thumbnail->level_count - 1; level >= 0; level--) {
531
23
    double shrink = vips_thumbnail_calculate_common_shrink(thumbnail,
532
23
        thumbnail->level_width[level],
533
23
        thumbnail->level_height[level]);
534
535
    // not >=, shrink can clip to 1.0
536
23
    if (shrink > 1.0)
537
21
      return level;
538
23
  }
539
540
1
  return 0;
541
22
}
542
543
/* Open the image, returning the best version for thumbnailing.
544
 *
545
 * For example, libjpeg supports fast shrink-on-read, so if we have a JPEG,
546
 * we can ask VIPS to load a lower resolution version.
547
 */
548
static VipsImage *
549
vips_thumbnail_open(VipsThumbnail *thumbnail)
550
28.7k
{
551
28.7k
  VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS(thumbnail);
552
553
28.7k
  VipsImage *im;
554
28.7k
  double factor;
555
556
28.7k
  if (class->get_info(thumbnail))
557
2
    return NULL;
558
28.7k
  g_info("selected loader is %s", thumbnail->loader);
559
28.7k
  g_info("input size is %d x %d",
560
28.7k
    thumbnail->input_width, thumbnail->input_height);
561
562
  /* For tiff, scan the image and try to spot page-based and ifd-based
563
   * pyramids.
564
   */
565
28.7k
  if (vips_isprefix("VipsForeignLoadTiff", thumbnail->loader)) {
566
    /* Test for a subifd pyr first, since we can do that from just
567
     * one page.
568
     */
569
132
    thumbnail->subifd_pyramid = TRUE;
570
132
    vips_thumbnail_get_tiff_pyramid_subifd(thumbnail);
571
572
132
    if (thumbnail->level_count == 0) {
573
132
      thumbnail->subifd_pyramid = FALSE;
574
132
      thumbnail->page_pyramid = TRUE;
575
576
132
      vips_thumbnail_get_pyramid_page(thumbnail);
577
578
132
      if (thumbnail->level_count == 0)
579
110
        thumbnail->page_pyramid = FALSE;
580
132
    }
581
132
  }
582
583
  /* jp2k uses page-based pyramids.
584
   */
585
28.7k
  if (vips_isprefix("VipsForeignLoadJp2k", thumbnail->loader)) {
586
0
    if (thumbnail->level_count == 0) {
587
0
      thumbnail->subifd_pyramid = FALSE;
588
0
      thumbnail->page_pyramid = TRUE;
589
590
0
      vips_thumbnail_get_pyramid_page(thumbnail);
591
592
0
      if (thumbnail->level_count == 0)
593
0
        thumbnail->page_pyramid = FALSE;
594
0
    }
595
0
  }
596
597
  /* For heif, we need to fetch the thumbnail size, in case we can use
598
   * that as the source.
599
   */
600
28.7k
  if (vips_isprefix("VipsForeignLoadHeif", thumbnail->loader))
601
34
    vips_thumbnail_get_heif_thumb_info(thumbnail);
602
603
  /* We read the openslide level structure in
604
   * vips_thumbnail_read_header().
605
   */
606
607
28.7k
  factor = 1.0;
608
609
28.7k
  if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader) ||
610
28.6k
    vips_isprefix("VipsForeignLoadUhdr", thumbnail->loader)) {
611
153
    factor = vips_thumbnail_find_jpegshrink(thumbnail,
612
153
      thumbnail->input_width, thumbnail->input_height);
613
153
    g_info("loading with factor %g pre-shrink", factor);
614
153
  }
615
28.6k
  else if (vips_isprefix("VipsForeignLoadTiff", thumbnail->loader) ||
616
28.4k
    vips_isprefix("VipsForeignLoadJp2k", thumbnail->loader) ||
617
28.4k
    vips_isprefix("VipsForeignLoadOpenslide", thumbnail->loader)) {
618
132
    if (thumbnail->level_count > 0) {
619
22
      factor = vips_thumbnail_find_pyrlevel(thumbnail,
620
22
        thumbnail->input_width,
621
22
        thumbnail->input_height);
622
22
      g_info("loading pyramid page %g", factor);
623
22
    }
624
110
    else
625
110
      g_info("loading with factor %g pre-shrink", factor);
626
132
  }
627
28.4k
  else if (vips_isprefix("VipsForeignLoadWebp", thumbnail->loader)) {
628
73
    factor = vips_thumbnail_calculate_common_shrink(thumbnail,
629
73
      thumbnail->input_width,
630
73
      thumbnail->page_height);
631
632
    /* Avoid upsizing via libwebp.
633
     */
634
73
    factor = VIPS_MAX(1.0, factor);
635
636
73
    g_info("loading with factor %g pre-shrink", factor);
637
73
  }
638
28.4k
  else if (vips_isprefix("VipsForeignLoadPdf", thumbnail->loader) ||
639
28.4k
    vips_isprefix("VipsForeignLoadSvg", thumbnail->loader)) {
640
0
    factor = vips_thumbnail_calculate_common_shrink(thumbnail,
641
0
      thumbnail->input_width,
642
0
      thumbnail->page_height);
643
644
0
    g_info("loading with factor %g pre-shrink", factor);
645
0
  }
646
28.4k
  else if (vips_isprefix("VipsForeignLoadHeif", thumbnail->loader)) {
647
    /* 'factor' is a gboolean which enables thumbnail load instead
648
     * of image load.
649
     *
650
     * Use the thumbnail if, by using it, we could get a factor >
651
     * 1.0, ie. we would not need to expand the thumbnail.
652
     *
653
     * Don't use >= since factor can be clipped to 1.0 under some
654
     * resizing modes.
655
     */
656
34
    double shrink_factor = vips_thumbnail_calculate_common_shrink(
657
34
      thumbnail,
658
34
      thumbnail->heif_thumbnail_width,
659
34
      thumbnail->heif_thumbnail_height);
660
661
34
    factor = shrink_factor > 1.0 ? 1 : 0;
662
663
34
    if (factor == 1)
664
0
      g_info("selected HEIF thumbnail for shrinking");
665
34
    else
666
34
      g_info("selected main HEIF image for shrinking");
667
34
  }
668
669
28.7k
  if (!(im = class->open(thumbnail, factor)))
670
0
    return NULL;
671
672
28.7k
  g_info("pre-shrunk size is %d x %d", im->Xsize, im->Ysize);
673
674
28.7k
  return im;
675
28.7k
}
676
677
/* Crop the same window from every page of an image and rejoin.
678
 *
679
 * left/top are relative to a page, not to the whole stack.
680
 */
681
static VipsImage *
682
vips_thumbnail_crop_pages(VipsObject *object, VipsImage *in,
683
  int left, int top, int width, int height, int page_height, int n_pages)
684
18
{
685
18
  VipsImage **pages = (VipsImage **) vips_object_local_array(object, n_pages);
686
687
18
  VipsImage *out;
688
689
36
  for (int i = 0; i < n_pages; i++)
690
18
    if (vips_crop(in, &pages[i],
691
18
        left, i * page_height + top, width, height, NULL))
692
0
      return NULL;
693
18
  if (vips_arrayjoin(pages, &out, n_pages, "across", 1, NULL))
694
0
    return NULL;
695
696
18
  if (n_pages > 1) {
697
0
    VipsImage *t;
698
699
0
    if (vips_copy(out, &t, NULL)) {
700
0
      g_object_unref(out);
701
0
      return NULL;
702
0
    }
703
0
    g_object_unref(out);
704
0
    out = t;
705
706
0
    vips_image_set_int(out, VIPS_META_PAGE_HEIGHT, height);
707
0
  }
708
709
18
  return out;
710
18
}
711
712
static int
713
vips_thumbnail_build(VipsObject *object)
714
28.7k
{
715
28.7k
  VipsThumbnail *thumbnail = VIPS_THUMBNAIL(object);
716
28.7k
  VipsImage **t = (VipsImage **) vips_object_local_array(object, 20);
717
718
28.7k
  VipsImage *in;
719
28.7k
  int preshrunk_page_height;
720
28.7k
  double hshrink;
721
28.7k
  double vshrink;
722
28.7k
  VipsImage *gainmap;
723
724
  /* TRUE if we've done the import of an ICC transform and still need to
725
   * export.
726
   */
727
28.7k
  gboolean have_imported;
728
729
  /* TRUE if the image needs to transformed with a pair of ICC profiles.
730
   */
731
28.7k
  gboolean needs_icc_transform;
732
733
  /* The format we need to revert to after unpremultiply.
734
   */
735
28.7k
  VipsBandFormat unpremultiplied_format;
736
737
#ifdef DEBUG
738
  printf("vips_thumbnail_build: ");
739
  vips_object_print_name(object);
740
  printf("\n");
741
#endif /*DEBUG*/
742
743
28.7k
  if (VIPS_OBJECT_CLASS(vips_thumbnail_parent_class)->build(object))
744
8
    return -1;
745
746
  /* We have to support both no_rotate and auto_rotate optional args,
747
   * with no_rotate being the new and not-deprecated one.
748
   *
749
   * If the new no_rotate flag has been set, that value overrides
750
   * auto_rotate.
751
   */
752
28.7k
  if (vips_object_argument_isset(object, "no_rotate"))
753
0
    thumbnail->auto_rotate = !thumbnail->no_rotate; // FIXME: Invalidates operation cache
754
755
28.7k
  if (!vips_object_argument_isset(object, "height"))
756
28.7k
    thumbnail->height = thumbnail->width; // FIXME: Invalidates operation cache
757
758
  /* Open and do any pre-shrinking.
759
   */
760
28.7k
  if (!(t[0] = vips_thumbnail_open(thumbnail)))
761
2
    return -1;
762
28.7k
  in = t[0];
763
764
  /* After pre-shrink, but before the main shrink stage.
765
   */
766
28.7k
  preshrunk_page_height = vips_image_get_page_height(in);
767
768
28.7k
  needs_icc_transform = thumbnail->output_profile &&
769
0
    (thumbnail->input_profile ||
770
0
      vips_image_get_typeof(in, VIPS_META_ICC_NAME));
771
772
  /* CICP-tagged images should not have their
773
   * colourspace converted - the pixel values are in a specific
774
   * signal encoding that would be destroyed. Resize in the
775
   * original encoding and let the saver preserve the metadata.
776
   */
777
28.7k
  gboolean has_cicp =
778
28.7k
    vips_image_get_typeof(in, "cicp-transfer-characteristics") != 0;
779
780
  /* RAD needs special unpacking.
781
   */
782
28.7k
  if (in->Coding == VIPS_CODING_RAD) {
783
185
    g_info("unpacking Rad to float");
784
785
    /* rad is scrgb.
786
     */
787
185
    if (vips_rad2float(in, &t[1], NULL))
788
0
      return -1;
789
185
    in = t[1];
790
185
  }
791
792
  /* In linear mode, we need to transform to a linear space before
793
   * vips_resize().
794
   */
795
28.7k
  have_imported = FALSE;
796
28.7k
  if (has_cicp) {
797
    /* Skip colourspace conversion for CICP images.
798
     */
799
5.98k
    g_info("CICP image, skipping colourspace conversion");
800
5.98k
  }
801
22.7k
  else if (thumbnail->linear) {
802
    /* If we are doing colour management (there's an input
803
     * profile), then we can use XYZ PCS as the resize space.
804
     */
805
0
    if (in->Coding == VIPS_CODING_NONE &&
806
0
      (in->BandFmt == VIPS_FORMAT_UCHAR ||
807
0
        in->BandFmt == VIPS_FORMAT_USHORT) &&
808
0
      (vips_image_get_typeof(in, VIPS_META_ICC_NAME) ||
809
0
        thumbnail->input_profile)) {
810
0
      g_info("importing to XYZ PCS");
811
0
      if (thumbnail->input_profile)
812
0
        g_info("fallback input profile %s", thumbnail->input_profile);
813
814
0
      if (vips_icc_import(in, &t[2],
815
0
          "input_profile", thumbnail->input_profile,
816
0
          "embedded", TRUE,
817
0
          "intent", thumbnail->intent,
818
0
          "pcs", VIPS_PCS_XYZ,
819
0
          NULL))
820
0
        return -1;
821
0
      in = t[2];
822
823
0
      have_imported = TRUE;
824
0
    }
825
0
    else {
826
      /* Otherwise, use scRGB or GREY16 for linear shrink.
827
       */
828
0
      VipsInterpretation interpretation;
829
830
0
      if (in->Bands < 3)
831
0
        interpretation = VIPS_INTERPRETATION_GREY16;
832
0
      else
833
0
        interpretation = VIPS_INTERPRETATION_scRGB;
834
835
0
      g_info("converting to processing space %s",
836
0
        vips_enum_nick(VIPS_TYPE_INTERPRETATION, interpretation));
837
0
      if (vips_colourspace(in, &t[2], interpretation, NULL))
838
0
        return -1;
839
0
      in = t[2];
840
0
    }
841
0
  }
842
22.7k
  else if (!needs_icc_transform) {
843
    /* In non-linear mode, use sRGB or B_W as the processing space
844
     * but only when not transforming with a pair of ICC profiles.
845
     */
846
22.7k
    VipsInterpretation interpretation;
847
848
22.7k
    if (in->Bands < 3)
849
5.15k
      interpretation = VIPS_INTERPRETATION_B_W;
850
17.6k
    else
851
17.6k
      interpretation = VIPS_INTERPRETATION_sRGB;
852
853
22.7k
    g_info("converting to processing space %s",
854
22.7k
      vips_enum_nick(VIPS_TYPE_INTERPRETATION, interpretation));
855
22.7k
    if (vips_colourspace(in, &t[2], interpretation, NULL))
856
1
      return -1;
857
22.7k
    in = t[2];
858
22.7k
  }
859
860
  /* Shrink to preshrunk_page_height, so we work for multi-page images.
861
   */
862
28.7k
  vips_thumbnail_calculate_shrink(thumbnail,
863
28.7k
    in->Xsize, preshrunk_page_height, &hshrink, &vshrink);
864
865
  /* In toilet-roll mode, we must adjust vshrink so that we exactly hit
866
   * page_height or we'll have pixels straddling page boundaries.
867
   */
868
28.7k
  if (in->Ysize > preshrunk_page_height) {
869
0
    int target_page_height = rint(preshrunk_page_height / vshrink);
870
0
    int target_image_height =
871
0
      target_page_height * thumbnail->n_loaded_pages;
872
873
0
    vshrink = (double) in->Ysize / target_image_height;
874
0
  }
875
876
  /* Use NOTSET to mean no pre/unmultiply.
877
   */
878
28.7k
  unpremultiplied_format = VIPS_FORMAT_NOTSET;
879
880
  /* If there's an alpha, we have to premultiply before shrinking. See
881
   * https://github.com/libvips/libvips/issues/291
882
   */
883
28.7k
  if (vips_image_hasalpha(in) &&
884
4.99k
    hshrink != 1.0 &&
885
4.86k
    vshrink != 1.0) {
886
4.76k
    g_info("premultiplying alpha");
887
4.76k
    unpremultiplied_format = in->BandFmt;
888
889
4.76k
    if (vips_premultiply(in, &t[3],
890
        /* Fast path: stay in uchar.
891
         */
892
4.76k
        "uchar", in->BandFmt == VIPS_FORMAT_UCHAR,
893
4.76k
        NULL))
894
0
      return -1;
895
4.76k
    in = t[3];
896
4.76k
  }
897
898
28.7k
  if (vips_resize(in, &t[5], 1.0 / hshrink, "vscale", 1.0 / vshrink, NULL))
899
1
    return -1;
900
28.7k
  in = t[5];
901
902
  /* Also resize the gainmap, if any.
903
   */
904
28.7k
  if ((gainmap = vips_image_get_gainmap(in))) {
905
183
    if (vips_resize(gainmap, &t[15], 1.0 / hshrink,
906
183
      "vscale", 1.0 / vshrink,
907
183
      "kernel", VIPS_KERNEL_LINEAR,
908
183
      NULL))
909
0
      return -1;
910
183
    g_object_unref(gainmap);
911
912
    /* Make sure we don't have a shared image.
913
     */
914
183
    if (vips_copy(in, &t[8], NULL))
915
0
      return -1;
916
183
    in = t[8];
917
918
183
    vips_image_set_image(in, "gainmap", t[15]);
919
183
  }
920
921
28.7k
  if (unpremultiplied_format != VIPS_FORMAT_NOTSET) {
922
4.76k
    g_info("unpremultiplying alpha");
923
924
4.76k
    if (unpremultiplied_format == VIPS_FORMAT_UCHAR) {
925
      /* Fast path: unpremultiply in UCHAR directly.
926
       */
927
4.29k
      if (vips_unpremultiply(in, &t[6], "uchar", TRUE, NULL))
928
0
        return -1;
929
4.29k
      in = t[6];
930
4.29k
    }
931
475
    else {
932
475
      if (vips_unpremultiply(in, &t[6], NULL) ||
933
475
        vips_cast(t[6], &t[7], unpremultiplied_format, NULL))
934
0
        return -1;
935
475
      in = t[7];
936
475
    }
937
4.76k
  }
938
939
  /* Only set page-height if we have more than one page, or this could
940
   * accidentally turn into an animated image later.
941
   */
942
28.7k
  if (thumbnail->n_loaded_pages > 1) {
943
0
    int output_page_height = rint(preshrunk_page_height / vshrink);
944
945
    /* Make sure we don't have a shared image.
946
     */
947
0
    if (vips_copy(in, &t[8], NULL))
948
0
      return -1;
949
0
    in = t[8];
950
951
0
    vips_image_set_int(in, VIPS_META_PAGE_HEIGHT, output_page_height);
952
0
  }
953
954
  /* Colour management.
955
   *
956
   * We always export as depth 8, to match the no profile case which
957
   * uses vips_colourspace(sRGB|B_W).
958
   *
959
   * Skip for CICP images - their pixel encoding must be preserved.
960
   */
961
28.7k
  if (has_cicp) {
962
5.98k
    g_info("CICP image, skipping colour management");
963
5.98k
  }
964
22.7k
  else if (have_imported) {
965
    /* We are in PCS. Export with the output profile, if any (this
966
     * will export with the embedded input profile if there's no
967
     * output profile).
968
     */
969
0
    g_info("exporting to device space with a profile");
970
0
    if (vips_icc_export(in, &t[9],
971
0
        "output_profile", thumbnail->output_profile,
972
0
        "intent", thumbnail->intent,
973
0
        "depth", 8,
974
0
        NULL))
975
0
      return -1;
976
0
    in = t[9];
977
0
  }
978
22.7k
  else if (needs_icc_transform) {
979
    /* We can transform to the output with a pair of ICC profiles.
980
     */
981
0
    g_info("transforming with supplied profiles");
982
0
    if (vips_icc_transform(in, &t[9], thumbnail->output_profile,
983
0
        "input_profile", thumbnail->input_profile,
984
0
        "intent", thumbnail->intent,
985
0
        "embedded", TRUE,
986
0
        "depth", 8,
987
0
        NULL))
988
0
      return -1;
989
990
0
    in = t[9];
991
0
  }
992
22.7k
  else if (thumbnail->output_profile) {
993
    /* We are in one of the resize space (sRGB, scRGB, B_W, GREY16, etc.)
994
     * and need to go to PCS, then export.
995
     */
996
0
    g_info("exporting with %s", thumbnail->output_profile);
997
0
    if (vips_colourspace(in, &t[9], VIPS_INTERPRETATION_XYZ, NULL) ||
998
0
      vips_icc_export(t[9], &t[10],
999
0
        "output_profile", thumbnail->output_profile,
1000
0
        "intent", thumbnail->intent,
1001
0
        "depth", 8,
1002
0
        NULL))
1003
0
      return -1;
1004
0
    in = t[10];
1005
0
  }
1006
22.7k
  else if (thumbnail->linear) {
1007
    /* We are in one of the scRGB or GREY16 spaces and there's
1008
     * no output profile. Output to sRGB or B_W.
1009
     */
1010
0
    VipsInterpretation interpretation;
1011
1012
0
    if (in->Bands < 3)
1013
0
      interpretation = VIPS_INTERPRETATION_B_W;
1014
0
    else
1015
0
      interpretation = VIPS_INTERPRETATION_sRGB;
1016
1017
0
    g_info("converting to output space %s",
1018
0
      vips_enum_nick(VIPS_TYPE_INTERPRETATION, interpretation));
1019
0
    if (vips_colourspace(in, &t[9], interpretation, NULL))
1020
0
      return -1;
1021
0
    in = t[9];
1022
0
  }
1023
1024
28.7k
  if (thumbnail->auto_rotate &&
1025
28.7k
    thumbnail->orientation != 1) {
1026
256
    g_info("rotating by EXIF orientation %d", thumbnail->orientation);
1027
    /* Need to copy to memory, we have to stay seq.
1028
     */
1029
256
    if (!(t[11] = vips_image_copy_memory(in)) ||
1030
223
      vips_autorot(t[11], &t[12], NULL))
1031
33
      return -1;
1032
223
    in = t[12];
1033
1034
    /* Also rotate the gainmap, if any.
1035
     */
1036
223
    if ((gainmap = vips_image_get_gainmap(in))) {
1037
0
      vips_image_set_int(gainmap,
1038
0
        VIPS_META_ORIENTATION, thumbnail->orientation);
1039
0
      if (vips_autorot(gainmap, &t[17], NULL))
1040
0
        return -1;
1041
0
      g_object_unref(gainmap);
1042
1043
0
      vips_image_set_image(in, "gainmap", t[17]);
1044
0
    }
1045
223
  }
1046
1047
  /* Crop after rotate so we don't need to rotate the crop box.
1048
   */
1049
28.7k
  if (thumbnail->crop != VIPS_INTERESTING_NONE) {
1050
20
    int page_height = vips_image_get_page_height(in);
1051
20
    int n_pages = thumbnail->n_loaded_pages;
1052
1053
    /* crop_height is relative to a single frame.
1054
     */
1055
20
    int crop_width = VIPS_MIN(thumbnail->width, in->Xsize);
1056
20
    int crop_height = VIPS_MIN(thumbnail->height,
1057
20
      n_pages > 1 ? page_height : in->Ysize);
1058
20
    int original_width = in->Xsize;
1059
1060
20
    g_info("cropping to %dx%d", crop_width, crop_height);
1061
1062
20
    gboolean rotated = thumbnail->swap && thumbnail->auto_rotate;
1063
20
    int rotated_input_width = rotated ? thumbnail->input_height : thumbnail->input_width;
1064
20
    int rotated_input_height = rotated ? thumbnail->input_width : thumbnail->input_height;
1065
20
    double overall_hshrink = (double) rotated_input_width / in->Xsize;
1066
20
    double overall_vshrink = (double) rotated_input_height / in->Ysize;
1067
1068
20
    int bbox_x, bbox_y;
1069
1070
    /* Need to copy to memory, we have to stay seq.
1071
     *
1072
     * FIXME ... could skip the copy if we've rotated.
1073
     */
1074
20
    if (!(t[13] = vips_image_copy_memory(in)))
1075
2
      return -1;
1076
1077
    /* Smartcrop the first page to find a shared crop window, then
1078
     * apply that same window to every page (and to the gainmap, if
1079
     * any) so the bbox does not drift frame-to-frame.
1080
     */
1081
18
    if (vips_crop(t[13], &t[18],
1082
18
        0, 0, in->Xsize, page_height, NULL))
1083
0
      return -1;
1084
1085
18
    if (vips_smartcrop(t[18], &t[19],
1086
18
        crop_width, crop_height,
1087
18
        "interesting", thumbnail->crop,
1088
18
        "interesting_x", VIPS_ROUND_UINT((double) thumbnail->interesting_x / overall_vshrink),
1089
18
        "interesting_y", VIPS_ROUND_UINT((double) thumbnail->interesting_y / overall_hshrink),
1090
18
        NULL))
1091
0
      return -1;
1092
1093
18
    bbox_x = -vips_image_get_xoffset(t[19]);
1094
18
    bbox_y = -vips_image_get_yoffset(t[19]);
1095
1096
18
    if (!(t[14] = vips_thumbnail_crop_pages(object, t[13],
1097
18
          bbox_x, bbox_y, crop_width, crop_height,
1098
18
          page_height, n_pages)))
1099
0
      return -1;
1100
18
    in = t[14];
1101
1102
    /* Also crop the gainmap, if any.
1103
     */
1104
18
    if ((gainmap = vips_image_get_gainmap(t[13]))) {
1105
0
      int gm_page_height = vips_image_get_page_height(gainmap);
1106
0
      int gm_n_pages = gainmap->Ysize / gm_page_height;
1107
0
      double xscale = (double) gainmap->Xsize / original_width;
1108
0
      double yscale = (double) gm_page_height / page_height;
1109
1110
0
      if (gm_n_pages != n_pages) {
1111
0
        g_object_unref(gainmap);
1112
0
        vips_error("thumbnail", "%s",
1113
0
          "gainmap page count does not match "
1114
0
          "image page count");
1115
0
        return -1;
1116
0
      }
1117
1118
0
      if (!(t[16] = vips_thumbnail_crop_pages(object, gainmap,
1119
0
            bbox_x * xscale, bbox_y * yscale,
1120
0
            crop_width * xscale, crop_height * yscale,
1121
0
            gm_page_height, gm_n_pages))) {
1122
0
        g_object_unref(gainmap);
1123
0
        return -1;
1124
0
      }
1125
0
      g_object_unref(gainmap);
1126
1127
0
      if (vips_copy(in, &t[8], NULL))
1128
0
        return -1;
1129
0
      in = t[8];
1130
1131
0
      vips_image_set_image(in, "gainmap", t[16]);
1132
0
    }
1133
18
  }
1134
1135
28.7k
  g_object_set(thumbnail, "out", vips_image_new(), NULL);
1136
1137
28.7k
  if (vips_image_write(in, thumbnail->out))
1138
0
    return -1;
1139
1140
28.7k
  return 0;
1141
28.7k
}
1142
1143
static void
1144
vips_thumbnail_class_init(VipsThumbnailClass *class)
1145
19
{
1146
19
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
1147
19
  VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);
1148
19
  VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
1149
1150
19
  gobject_class->dispose = vips_thumbnail_dispose;
1151
19
  gobject_class->finalize = vips_thumbnail_finalize;
1152
19
  gobject_class->set_property = vips_object_set_property;
1153
19
  gobject_class->get_property = vips_object_get_property;
1154
1155
19
  vobject_class->nickname = "thumbnail_base";
1156
19
  vobject_class->description = _("thumbnail generation");
1157
19
  vobject_class->build = vips_thumbnail_build;
1158
1159
  /* We mustn't cache these calls, since we open the file or buffer in
1160
   * sequential mode.
1161
   */
1162
19
  operation_class->flags |= VIPS_OPERATION_NOCACHE;
1163
1164
19
  VIPS_ARG_IMAGE(class, "out", 2,
1165
19
    _("Output"),
1166
19
    _("Output image"),
1167
19
    VIPS_ARGUMENT_REQUIRED_OUTPUT,
1168
19
    G_STRUCT_OFFSET(VipsThumbnail, out));
1169
1170
19
  VIPS_ARG_INT(class, "width", 3,
1171
19
    _("Target width"),
1172
19
    _("Size to this width"),
1173
19
    VIPS_ARGUMENT_REQUIRED_INPUT,
1174
19
    G_STRUCT_OFFSET(VipsThumbnail, width),
1175
19
    1, VIPS_MAX_COORD, 1);
1176
1177
19
  VIPS_ARG_INT(class, "height", 113,
1178
19
    _("Target height"),
1179
19
    _("Size to this height"),
1180
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1181
19
    G_STRUCT_OFFSET(VipsThumbnail, height),
1182
19
    1, VIPS_MAX_COORD, 1);
1183
1184
19
  VIPS_ARG_ENUM(class, "size", 114,
1185
19
    _("Size"),
1186
19
    _("Only upsize, only downsize, or both"),
1187
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1188
19
    G_STRUCT_OFFSET(VipsThumbnail, size),
1189
19
    VIPS_TYPE_SIZE, VIPS_SIZE_BOTH);
1190
1191
19
  VIPS_ARG_BOOL(class, "no_rotate", 115,
1192
19
    _("No rotate"),
1193
19
    _("Don't use orientation tags to rotate image upright"),
1194
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1195
19
    G_STRUCT_OFFSET(VipsThumbnail, no_rotate),
1196
19
    FALSE);
1197
1198
19
  VIPS_ARG_ENUM(class, "crop", 116,
1199
19
    _("Crop"),
1200
19
    _("Reduce to fill target rectangle, then crop"),
1201
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1202
19
    G_STRUCT_OFFSET(VipsThumbnail, crop),
1203
19
    VIPS_TYPE_INTERESTING, VIPS_INTERESTING_NONE);
1204
1205
19
  VIPS_ARG_INT(class, "interesting_x", 117,
1206
19
    _("Interesting x"),
1207
19
    _("Horizontal position of the specific point of interest for cropping"),
1208
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1209
19
    G_STRUCT_OFFSET(VipsThumbnail, interesting_x),
1210
19
    0, VIPS_MAX_COORD, 0);
1211
1212
19
  VIPS_ARG_INT(class, "interesting_y", 118,
1213
19
    _("Interesting y"),
1214
19
    _("Vertical position of the specific point of interest for cropping"),
1215
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1216
19
    G_STRUCT_OFFSET(VipsThumbnail, interesting_y),
1217
19
    0, VIPS_MAX_COORD, 0);
1218
1219
19
  VIPS_ARG_BOOL(class, "linear", 119,
1220
19
    _("Linear"),
1221
19
    _("Reduce in linear light"),
1222
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1223
19
    G_STRUCT_OFFSET(VipsThumbnail, linear),
1224
19
    FALSE);
1225
1226
19
  VIPS_ARG_STRING(class, "input_profile", 120,
1227
19
    _("Input profile"),
1228
19
    _("Fallback input profile"),
1229
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1230
19
    G_STRUCT_OFFSET(VipsThumbnail, input_profile),
1231
19
    NULL);
1232
1233
19
  VIPS_ARG_STRING(class, "output_profile", 121,
1234
19
    _("Output profile"),
1235
19
    _("Fallback output profile"),
1236
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1237
19
    G_STRUCT_OFFSET(VipsThumbnail, output_profile),
1238
19
    NULL);
1239
1240
19
  VIPS_ARG_ENUM(class, "intent", 122,
1241
19
    _("Intent"),
1242
19
    _("Rendering intent"),
1243
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1244
19
    G_STRUCT_OFFSET(VipsThumbnail, intent),
1245
19
    VIPS_TYPE_INTENT, VIPS_INTENT_RELATIVE);
1246
1247
19
  VIPS_ARG_ENUM(class, "fail_on", 123,
1248
19
    _("Fail on"),
1249
19
    _("Error level to fail on"),
1250
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1251
19
    G_STRUCT_OFFSET(VipsThumbnail, fail_on),
1252
19
    VIPS_TYPE_FAIL_ON, VIPS_FAIL_ON_NONE);
1253
1254
  /* BOOL args which default TRUE arguments don't work with the
1255
   * command-line -- GOption does not allow --auto-rotate=false.
1256
   *
1257
   * This is now replaced (though still functional) with "no-rotate",
1258
   * see above.
1259
   */
1260
19
  VIPS_ARG_BOOL(class, "auto_rotate", 130,
1261
19
    _("Auto rotate"),
1262
19
    _("Use orientation tags to rotate image upright"),
1263
19
    VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
1264
19
    G_STRUCT_OFFSET(VipsThumbnail, auto_rotate),
1265
19
    TRUE);
1266
1267
  /* Renamed as input-profile and output-profile for consistency with the
1268
   * rest of the API.
1269
   */
1270
1271
19
  VIPS_ARG_STRING(class, "import_profile", 131,
1272
19
    _("Import profile"),
1273
19
    _("Fallback import profile"),
1274
19
    VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
1275
19
    G_STRUCT_OFFSET(VipsThumbnail, input_profile),
1276
19
    NULL);
1277
1278
19
  VIPS_ARG_STRING(class, "export_profile", 132,
1279
19
    _("Export profile"),
1280
19
    _("Fallback export profile"),
1281
19
    VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
1282
19
    G_STRUCT_OFFSET(VipsThumbnail, output_profile),
1283
19
    NULL);
1284
19
}
1285
1286
static void
1287
vips_thumbnail_init(VipsThumbnail *thumbnail)
1288
28.9k
{
1289
28.9k
  thumbnail->width = 1;
1290
28.9k
  thumbnail->height = 1;
1291
28.9k
  thumbnail->auto_rotate = TRUE;
1292
28.9k
  thumbnail->intent = VIPS_INTENT_RELATIVE;
1293
28.9k
  thumbnail->fail_on = VIPS_FAIL_ON_NONE;
1294
28.9k
}
1295
1296
typedef struct _VipsThumbnailFile {
1297
  VipsThumbnail parent_object;
1298
1299
  char *filename;
1300
} VipsThumbnailFile;
1301
1302
typedef VipsThumbnailClass VipsThumbnailFileClass;
1303
1304
39
G_DEFINE_TYPE(VipsThumbnailFile, vips_thumbnail_file,
1305
39
  vips_thumbnail_get_type());
1306
39
1307
39
/* Get the info from a file.
1308
39
 */
1309
39
static int
1310
39
vips_thumbnail_file_get_info(VipsThumbnail *thumbnail)
1311
39
{
1312
1
  VipsThumbnailFile *file = (VipsThumbnailFile *) thumbnail;
1313
1314
1
  VipsImage *image;
1315
1316
1
  g_info("thumbnailing %s", file->filename);
1317
1318
1
  if (!(thumbnail->loader = vips_foreign_find_load(file->filename)) ||
1319
0
    !(image = vips_image_new_from_file(file->filename, NULL)))
1320
1
    return -1;
1321
1322
0
  vips_thumbnail_read_header(thumbnail, image);
1323
1324
0
  g_object_unref(image);
1325
1326
0
  return 0;
1327
1
}
1328
1329
/* Open an image, pre-shrinking as appropriate.
1330
 */
1331
static VipsImage *
1332
vips_thumbnail_file_open(VipsThumbnail *thumbnail, double factor)
1333
0
{
1334
0
  VipsThumbnailFile *file = (VipsThumbnailFile *) thumbnail;
1335
1336
0
  if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader) ||
1337
0
    vips_isprefix("VipsForeignLoadUhdr", thumbnail->loader)) {
1338
0
    return vips_image_new_from_file(file->filename,
1339
0
      "access", VIPS_ACCESS_SEQUENTIAL,
1340
0
      "fail_on", thumbnail->fail_on,
1341
0
      "shrink", (int) factor,
1342
0
      NULL);
1343
0
  }
1344
0
  else if (vips_isprefix("VipsForeignLoadOpenslide", thumbnail->loader)) {
1345
0
    return vips_image_new_from_file(file->filename,
1346
0
      "access", VIPS_ACCESS_SEQUENTIAL,
1347
0
      "fail_on", thumbnail->fail_on,
1348
0
      "level", (int) factor,
1349
0
      NULL);
1350
0
  }
1351
0
  else if (vips_isprefix("VipsForeignLoadPdf", thumbnail->loader) ||
1352
0
    vips_isprefix("VipsForeignLoadSvg", thumbnail->loader) ||
1353
0
    vips_isprefix("VipsForeignLoadWebp", thumbnail->loader)) {
1354
0
    return vips_image_new_from_file(file->filename,
1355
0
      "access", VIPS_ACCESS_SEQUENTIAL,
1356
0
      "fail_on", thumbnail->fail_on,
1357
0
      "scale", 1.0 / factor,
1358
0
      NULL);
1359
0
  }
1360
0
  else if (vips_isprefix("VipsForeignLoadJp2k", thumbnail->loader)) {
1361
    /* jp2k optionally uses page-based pyramids.
1362
     */
1363
0
    if (thumbnail->page_pyramid)
1364
0
      return vips_image_new_from_file(file->filename,
1365
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1366
0
        "fail_on", thumbnail->fail_on,
1367
0
        "page", (int) factor,
1368
0
        NULL);
1369
0
    else
1370
0
      return vips_image_new_from_file(file->filename,
1371
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1372
0
        NULL);
1373
0
  }
1374
0
  else if (vips_isprefix("VipsForeignLoadTiff", thumbnail->loader)) {
1375
    /* We support three modes: subifd pyramids, page-based
1376
     * pyramids, and simple multi-page TIFFs (no pyramid).
1377
     */
1378
0
    if (thumbnail->subifd_pyramid)
1379
0
      return vips_image_new_from_file(file->filename,
1380
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1381
0
        "fail_on", thumbnail->fail_on,
1382
0
        "subifd", (int) factor,
1383
0
        NULL);
1384
0
    else if (thumbnail->page_pyramid)
1385
0
      return vips_image_new_from_file(file->filename,
1386
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1387
0
        "fail_on", thumbnail->fail_on,
1388
0
        "page", (int) factor,
1389
0
        NULL);
1390
0
    else
1391
0
      return vips_image_new_from_file(file->filename,
1392
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1393
0
        "fail_on", thumbnail->fail_on,
1394
0
        NULL);
1395
0
  }
1396
0
  else if (vips_isprefix("VipsForeignLoadHeif", thumbnail->loader)) {
1397
0
    return vips_image_new_from_file(file->filename,
1398
0
      "access", VIPS_ACCESS_SEQUENTIAL,
1399
0
      "fail_on", thumbnail->fail_on,
1400
0
      "thumbnail", (int) factor,
1401
0
      NULL);
1402
0
  }
1403
0
  else {
1404
0
    return vips_image_new_from_file(file->filename,
1405
0
      "access", VIPS_ACCESS_SEQUENTIAL,
1406
0
      "fail_on", thumbnail->fail_on,
1407
0
      NULL);
1408
0
  }
1409
0
}
1410
1411
static void
1412
vips_thumbnail_file_class_init(VipsThumbnailClass *class)
1413
19
{
1414
19
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
1415
19
  VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);
1416
19
  VipsThumbnailClass *thumbnail_class = VIPS_THUMBNAIL_CLASS(class);
1417
1418
19
  gobject_class->set_property = vips_object_set_property;
1419
19
  gobject_class->get_property = vips_object_get_property;
1420
1421
19
  vobject_class->nickname = "thumbnail";
1422
19
  vobject_class->description = _("generate thumbnail from file");
1423
1424
19
  thumbnail_class->get_info = vips_thumbnail_file_get_info;
1425
19
  thumbnail_class->open = vips_thumbnail_file_open;
1426
1427
19
  VIPS_ARG_STRING(class, "filename", 1,
1428
19
    _("Filename"),
1429
19
    _("Filename to read from"),
1430
19
    VIPS_ARGUMENT_REQUIRED_INPUT,
1431
19
    G_STRUCT_OFFSET(VipsThumbnailFile, filename),
1432
19
    NULL);
1433
19
}
1434
1435
static void
1436
vips_thumbnail_file_init(VipsThumbnailFile *file)
1437
2
{
1438
2
}
1439
1440
/**
1441
 * vips_thumbnail:
1442
 * @filename: file to read from
1443
 * @out: (out): output image
1444
 * @width: target width in pixels
1445
 * @...: `NULL`-terminated list of optional named arguments
1446
 *
1447
 * Make a thumbnail from a file.
1448
 *
1449
 * Shrinking is done in three stages: using any
1450
 * shrink-on-load features available in the image load library, using a block
1451
 * shrink, and using a lanczos3 shrink. At least the final 200% is done with
1452
 * lanczos3. The output should be high quality, and the operation should be
1453
 * quick.
1454
 *
1455
 * See [ctor@Image.thumbnail_buffer] to thumbnail from a memory buffer, or
1456
 * [ctor@Image.thumbnail_source] to thumbnail from an arbitrary byte source.
1457
 *
1458
 * By default, libvips will only use the first frame of animated or multipage
1459
 * images. To thumbnail all pages or frames, pass `n=-1` to the loader in
1460
 * @filename, for example `"x.gif[n=-1]"`.
1461
 *
1462
 * The output image will fit within a square of size @width x @width. You can
1463
 * specify a separate height with the @height option. Set either @width or
1464
 * @height to a very large number to ignore that dimension.
1465
 *
1466
 * If you set @crop, then the output image will fill the whole of the @width x
1467
 * @height rectangle, with any excess cropped away. See [method@Image.smartcrop] for
1468
 * details on the cropping strategy. To use a specific point of interest for
1469
 * cropping use [enum@Vips.Interesting.SPECIFIC] and set @interesting_x and
1470
 * @interesting_y.
1471
 *
1472
 * Normally the operation will upsize or downsize as required to fit the image
1473
 * inside or outside the target size. If @size is set to [enum@Vips.Size.UP],
1474
 * the operation will only upsize and will just copy if asked to downsize.
1475
 * If @size is set to [enum@Vips.Size.DOWN], the operation will only downsize
1476
 * and will just copy if asked to upsize.
1477
 * If @size is [enum@Vips.Size.FORCE], the image aspect ratio will be broken
1478
 * and the image will be forced to fit the target.
1479
 *
1480
 * Normally any orientation tags on the input image (such as EXIF tags) are
1481
 * interpreted to rotate the image upright. If you set @no_rotate to `TRUE`,
1482
 * these tags will not be interpreted.
1483
 *
1484
 * Shrinking is normally done in sRGB colourspace. Set @linear to shrink in
1485
 * linear light colourspace instead. This can give better results, but can
1486
 * also be far slower, since tricks like JPEG shrink-on-load cannot be used in
1487
 * linear space.
1488
 *
1489
 * If you set @output_profile to the filename of an ICC profile, the image
1490
 * will be transformed to the target colourspace before writing to the
1491
 * output. You can also give an @input_profile which will be used if the
1492
 * input image has no ICC profile, or if the profile embedded in the
1493
 * input image is broken.
1494
 *
1495
 * Use @intent to set the rendering intent for any ICC transform. The default
1496
 * is [enum@Vips.Intent.RELATIVE].
1497
 *
1498
 * Use @fail_on to control the types of error that will cause loading to fail.
1499
 * The default is [enum@Vips.FailOn.NONE], ie. thumbnail is permissive.
1500
 *
1501
 * ::: tip "Optional arguments"
1502
 *     * @height: `gint`, target height in pixels
1503
 *     * @size: [enum@Size], upsize, downsize, both or force
1504
 *     * @no_rotate: `gboolean`, don't rotate upright using orientation tag
1505
 *     * @crop: [enum@Interesting], shrink and crop to fill target
1506
 *     * @interesting_x: `gint`, horizontal position of the specific point of
1507
 *       interest when cropping with [enum@Vips.Interesting.SPECIFIC])
1508
 *     * @interesting_y: `gint`, vertical position of the specific point of
1509
 *       interest when cropping with [enum@Vips.Interesting.SPECIFIC])
1510
 *     * @linear: `gboolean`, perform shrink in linear light
1511
 *     * @input_profile: `gchararray`, fallback input ICC profile
1512
 *     * @output_profile: `gchararray`, output ICC profile
1513
 *     * @intent: [enum@Intent], rendering intent
1514
 *     * @fail_on: [enum@FailOn], load error types to fail on
1515
 *
1516
 * ::: seealso
1517
 *     [ctor@Image.thumbnail_buffer].
1518
 *
1519
 * Returns: 0 on success, -1 on error.
1520
 */
1521
int
1522
vips_thumbnail(const char *filename, VipsImage **out, int width, ...)
1523
0
{
1524
0
  va_list ap;
1525
0
  int result;
1526
1527
0
  va_start(ap, width);
1528
0
  result = vips_call_split("thumbnail", ap, filename, out, width);
1529
0
  va_end(ap);
1530
1531
0
  return result;
1532
0
}
1533
1534
typedef struct _VipsThumbnailBuffer {
1535
  VipsThumbnail parent_object;
1536
1537
  VipsArea *buf;
1538
  char *option_string;
1539
} VipsThumbnailBuffer;
1540
1541
typedef VipsThumbnailClass VipsThumbnailBufferClass;
1542
1543
39
G_DEFINE_TYPE(VipsThumbnailBuffer, vips_thumbnail_buffer,
1544
39
  vips_thumbnail_get_type());
1545
39
1546
39
/* Get the info from a buffer.
1547
39
 */
1548
39
static int
1549
39
vips_thumbnail_buffer_get_info(VipsThumbnail *thumbnail)
1550
201
{
1551
201
  VipsThumbnailBuffer *buffer = (VipsThumbnailBuffer *) thumbnail;
1552
1553
201
  VipsImage *image;
1554
1555
201
  g_info("thumbnailing %zd bytes of data", buffer->buf->length);
1556
1557
201
  if (!(thumbnail->loader = vips_foreign_find_load_buffer(
1558
201
        buffer->buf->data, buffer->buf->length)) ||
1559
200
    !(image = vips_image_new_from_buffer(
1560
200
        buffer->buf->data, buffer->buf->length,
1561
200
        buffer->option_string, NULL)))
1562
1
    return -1;
1563
1564
200
  vips_thumbnail_read_header(thumbnail, image);
1565
1566
200
  g_object_unref(image);
1567
1568
200
  return 0;
1569
201
}
1570
1571
/* Open an image, scaling as appropriate.
1572
 */
1573
static VipsImage *
1574
vips_thumbnail_buffer_open(VipsThumbnail *thumbnail, double factor)
1575
208
{
1576
208
  VipsThumbnailBuffer *buffer = (VipsThumbnailBuffer *) thumbnail;
1577
1578
208
  if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader) ||
1579
105
    vips_isprefix("VipsForeignLoadUhdr", thumbnail->loader)) {
1580
105
    return vips_image_new_from_buffer(
1581
105
      buffer->buf->data, buffer->buf->length,
1582
105
      buffer->option_string,
1583
105
      "access", VIPS_ACCESS_SEQUENTIAL,
1584
105
      "shrink", (int) factor,
1585
105
      NULL);
1586
105
  }
1587
103
  else if (vips_isprefix("VipsForeignLoadOpenslide",
1588
103
         thumbnail->loader)) {
1589
0
    return vips_image_new_from_buffer(
1590
0
      buffer->buf->data, buffer->buf->length,
1591
0
      buffer->option_string,
1592
0
      "access", VIPS_ACCESS_SEQUENTIAL,
1593
0
      "level", (int) factor,
1594
0
      NULL);
1595
0
  }
1596
103
  else if (vips_isprefix("VipsForeignLoadPdf", thumbnail->loader) ||
1597
103
    vips_isprefix("VipsForeignLoadSvg", thumbnail->loader) ||
1598
103
    vips_isprefix("VipsForeignLoadWebp", thumbnail->loader)) {
1599
41
    return vips_image_new_from_buffer(
1600
41
      buffer->buf->data, buffer->buf->length,
1601
41
      buffer->option_string,
1602
41
      "access", VIPS_ACCESS_SEQUENTIAL,
1603
41
      "scale", 1.0 / factor,
1604
41
      NULL);
1605
41
  }
1606
62
  else if (vips_isprefix("VipsForeignLoadJp2k", thumbnail->loader)) {
1607
    /* Optional page-based pyramids.
1608
     */
1609
0
    if (thumbnail->page_pyramid)
1610
0
      return vips_image_new_from_buffer(
1611
0
        buffer->buf->data, buffer->buf->length,
1612
0
        buffer->option_string,
1613
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1614
0
        "page", (int) factor,
1615
0
        NULL);
1616
0
    else
1617
0
      return vips_image_new_from_buffer(
1618
0
        buffer->buf->data, buffer->buf->length,
1619
0
        buffer->option_string,
1620
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1621
0
        NULL);
1622
0
  }
1623
62
  else if (vips_isprefix("VipsForeignLoadTiff", thumbnail->loader)) {
1624
    /* We support three modes: subifd pyramids, page-based
1625
     * pyramids, and simple multi-page TIFFs (no pyramid).
1626
     */
1627
13
    if (thumbnail->subifd_pyramid)
1628
0
      return vips_image_new_from_buffer(
1629
0
        buffer->buf->data, buffer->buf->length,
1630
0
        buffer->option_string,
1631
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1632
0
        "subifd", (int) factor,
1633
0
        NULL);
1634
13
    else if (thumbnail->page_pyramid)
1635
7
      return vips_image_new_from_buffer(
1636
7
        buffer->buf->data, buffer->buf->length,
1637
7
        buffer->option_string,
1638
7
        "access", VIPS_ACCESS_SEQUENTIAL,
1639
7
        "page", (int) factor,
1640
7
        NULL);
1641
6
    else
1642
6
      return vips_image_new_from_buffer(
1643
6
        buffer->buf->data, buffer->buf->length,
1644
6
        buffer->option_string,
1645
6
        "access", VIPS_ACCESS_SEQUENTIAL,
1646
6
        NULL);
1647
13
  }
1648
49
  else if (vips_isprefix("VipsForeignLoadHeif", thumbnail->loader)) {
1649
6
    return vips_image_new_from_buffer(
1650
6
      buffer->buf->data, buffer->buf->length,
1651
6
      buffer->option_string,
1652
6
      "access", VIPS_ACCESS_SEQUENTIAL,
1653
6
      "thumbnail", (int) factor,
1654
6
      NULL);
1655
6
  }
1656
43
  else {
1657
43
    return vips_image_new_from_buffer(
1658
43
      buffer->buf->data, buffer->buf->length,
1659
43
      buffer->option_string,
1660
43
      "access", VIPS_ACCESS_SEQUENTIAL,
1661
43
      NULL);
1662
43
  }
1663
208
}
1664
1665
static void
1666
vips_thumbnail_buffer_class_init(VipsThumbnailClass *class)
1667
19
{
1668
19
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
1669
19
  VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);
1670
19
  VipsThumbnailClass *thumbnail_class = VIPS_THUMBNAIL_CLASS(class);
1671
1672
19
  gobject_class->set_property = vips_object_set_property;
1673
19
  gobject_class->get_property = vips_object_get_property;
1674
1675
19
  vobject_class->nickname = "thumbnail_buffer";
1676
19
  vobject_class->description = _("generate thumbnail from buffer");
1677
1678
19
  thumbnail_class->get_info = vips_thumbnail_buffer_get_info;
1679
19
  thumbnail_class->open = vips_thumbnail_buffer_open;
1680
1681
19
  VIPS_ARG_BOXED(class, "buffer", 1,
1682
19
    _("Buffer"),
1683
19
    _("Buffer to load from"),
1684
19
    VIPS_ARGUMENT_REQUIRED_INPUT,
1685
19
    G_STRUCT_OFFSET(VipsThumbnailBuffer, buf),
1686
19
    VIPS_TYPE_BLOB);
1687
1688
19
  VIPS_ARG_STRING(class, "option_string", 20,
1689
19
    _("Extra options"),
1690
19
    _("Options that are passed on to the underlying loader"),
1691
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1692
19
    G_STRUCT_OFFSET(VipsThumbnailBuffer, option_string),
1693
19
    "");
1694
19
}
1695
1696
static void
1697
vips_thumbnail_buffer_init(VipsThumbnailBuffer *buffer)
1698
232
{
1699
232
}
1700
1701
/**
1702
 * vips_thumbnail_buffer:
1703
 * @buf: (array length=len) (element-type guint8): memory area to load
1704
 * @len: (type gsize): size of memory area
1705
 * @out: (out): output image
1706
 * @width: target width in pixels
1707
 * @...: `NULL`-terminated list of optional named arguments
1708
 *
1709
 * Exactly as [ctor@Image.thumbnail], but read from a memory buffer.
1710
 *
1711
 * One extra optional argument, @option_string, lets you pass options to the
1712
 * underlying loader.
1713
 *
1714
 * ::: tip "Optional arguments"
1715
 *     * @height: `gint`, target height in pixels
1716
 *     * @size: [enum@Size], upsize, downsize, both or force
1717
 *     * @no_rotate: `gboolean`, don't rotate upright using orientation tag
1718
 *     * @crop: [enum@Interesting], shrink and crop to fill target
1719
 *     * @interesting_x: `gint`, horizontal position of the specific point of
1720
 *       interest when cropping with [enum@Vips.Interesting.SPECIFIC])
1721
 *     * @interesting_y: `gint`, vertical position of the specific point of
1722
 *       interest when cropping with [enum@Vips.Interesting.SPECIFIC])
1723
 *     * @linear: `gboolean`, perform shrink in linear light
1724
 *     * @input_profile: `gchararray`, fallback input ICC profile
1725
 *     * @output_profile: `gchararray`, output ICC profile
1726
 *     * @intent: [enum@Intent], rendering intent
1727
 *     * @fail_on: [enum@FailOn], load error types to fail on
1728
 *     * @option_string: `gchararray`, extra loader options
1729
 *
1730
 * ::: seealso
1731
 *     [ctor@Image.thumbnail].
1732
 *
1733
 * Returns: 0 on success, -1 on error.
1734
 */
1735
int
1736
vips_thumbnail_buffer(void *buf, size_t len, VipsImage **out, int width, ...)
1737
0
{
1738
0
  va_list ap;
1739
0
  VipsBlob *blob;
1740
0
  int result;
1741
1742
  /* We don't take a copy of the data or free it.
1743
   */
1744
0
  blob = vips_blob_new(NULL, buf, len);
1745
1746
0
  va_start(ap, width);
1747
0
  result = vips_call_split("thumbnail_buffer", ap, blob, out, width);
1748
0
  va_end(ap);
1749
1750
0
  vips_area_unref(VIPS_AREA(blob));
1751
1752
0
  return result;
1753
0
}
1754
1755
typedef struct _VipsThumbnailSource {
1756
  VipsThumbnail parent_object;
1757
1758
  VipsSource *source;
1759
  char *option_string;
1760
} VipsThumbnailSource;
1761
1762
typedef VipsThumbnailClass VipsThumbnailSourceClass;
1763
1764
39
G_DEFINE_TYPE(VipsThumbnailSource, vips_thumbnail_source,
1765
39
  vips_thumbnail_get_type());
1766
39
1767
39
/* Get the info from a source.
1768
39
 */
1769
39
static int
1770
39
vips_thumbnail_source_get_info(VipsThumbnail *thumbnail)
1771
421
{
1772
421
  VipsThumbnailSource *source = (VipsThumbnailSource *) thumbnail;
1773
1774
421
  VipsImage *image;
1775
1776
421
  g_info("thumbnailing source");
1777
1778
421
  if (!(thumbnail->loader = vips_foreign_find_load_source(source->source)) ||
1779
421
    !(image = vips_image_new_from_source(source->source,
1780
421
        source->option_string, NULL)))
1781
0
    return -1;
1782
1783
421
  vips_thumbnail_read_header(thumbnail, image);
1784
1785
421
  g_object_unref(image);
1786
1787
421
  return 0;
1788
421
}
1789
1790
/* Open an image, scaling as appropriate.
1791
 */
1792
static VipsImage *
1793
vips_thumbnail_source_open(VipsThumbnail *thumbnail, double factor)
1794
506
{
1795
506
  VipsThumbnailSource *source = (VipsThumbnailSource *) thumbnail;
1796
1797
506
  if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader) ||
1798
458
    vips_isprefix("VipsForeignLoadUhdr", thumbnail->loader)) {
1799
48
    return vips_image_new_from_source(
1800
48
      source->source,
1801
48
      source->option_string,
1802
48
      "access", VIPS_ACCESS_SEQUENTIAL,
1803
48
      "shrink", (int) factor,
1804
48
      NULL);
1805
48
  }
1806
458
  else if (vips_isprefix("VipsForeignLoadOpenslide", thumbnail->loader)) {
1807
0
    return vips_image_new_from_source(
1808
0
      source->source,
1809
0
      source->option_string,
1810
0
      "access", VIPS_ACCESS_SEQUENTIAL,
1811
0
      "level", (int) factor,
1812
0
      NULL);
1813
0
  }
1814
458
  else if (vips_isprefix("VipsForeignLoadPdf", thumbnail->loader) ||
1815
458
    vips_isprefix("VipsForeignLoadSvg", thumbnail->loader) ||
1816
458
    vips_isprefix("VipsForeignLoadWebp", thumbnail->loader)) {
1817
32
    return vips_image_new_from_source(
1818
32
      source->source,
1819
32
      source->option_string,
1820
32
      "access", VIPS_ACCESS_SEQUENTIAL,
1821
32
      "scale", 1.0 / factor,
1822
32
      NULL);
1823
32
  }
1824
426
  else if (vips_isprefix("VipsForeignLoadJp2k", thumbnail->loader)) {
1825
    /* Optional page-based pyramids.
1826
     */
1827
0
    if (thumbnail->page_pyramid)
1828
0
      return vips_image_new_from_source(
1829
0
        source->source,
1830
0
        source->option_string,
1831
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1832
0
        "page", (int) factor,
1833
0
        NULL);
1834
0
    else
1835
0
      return vips_image_new_from_source(
1836
0
        source->source,
1837
0
        source->option_string,
1838
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1839
0
        NULL);
1840
0
  }
1841
426
  else if (vips_isprefix("VipsForeignLoadTiff", thumbnail->loader)) {
1842
    /* We support three modes: subifd pyramids, page-based
1843
     * pyramids, and simple multi-page TIFFs (no pyramid).
1844
     */
1845
178
    if (thumbnail->subifd_pyramid)
1846
0
      return vips_image_new_from_source(
1847
0
        source->source,
1848
0
        source->option_string,
1849
0
        "access", VIPS_ACCESS_SEQUENTIAL,
1850
0
        "subifd", (int) factor,
1851
0
        NULL);
1852
178
    else if (thumbnail->page_pyramid)
1853
74
      return vips_image_new_from_source(
1854
74
        source->source,
1855
74
        source->option_string,
1856
74
        "access", VIPS_ACCESS_SEQUENTIAL,
1857
74
        "page", (int) factor,
1858
74
        NULL);
1859
104
    else
1860
104
      return vips_image_new_from_source(
1861
104
        source->source,
1862
104
        source->option_string,
1863
104
        "access", VIPS_ACCESS_SEQUENTIAL,
1864
104
        NULL);
1865
178
  }
1866
248
  else if (vips_isprefix("VipsForeignLoadHeif", thumbnail->loader)) {
1867
62
    return vips_image_new_from_source(
1868
62
      source->source,
1869
62
      source->option_string,
1870
62
      "access", VIPS_ACCESS_SEQUENTIAL,
1871
62
      "thumbnail", (int) factor,
1872
62
      NULL);
1873
62
  }
1874
186
  else {
1875
186
    return vips_image_new_from_source(
1876
186
      source->source,
1877
186
      source->option_string,
1878
186
      "access", VIPS_ACCESS_SEQUENTIAL,
1879
186
      NULL);
1880
186
  }
1881
506
}
1882
1883
static void
1884
vips_thumbnail_source_class_init(VipsThumbnailClass *class)
1885
19
{
1886
19
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
1887
19
  VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);
1888
19
  VipsThumbnailClass *thumbnail_class = VIPS_THUMBNAIL_CLASS(class);
1889
1890
19
  gobject_class->set_property = vips_object_set_property;
1891
19
  gobject_class->get_property = vips_object_get_property;
1892
1893
19
  vobject_class->nickname = "thumbnail_source";
1894
19
  vobject_class->description = _("generate thumbnail from source");
1895
1896
19
  thumbnail_class->get_info = vips_thumbnail_source_get_info;
1897
19
  thumbnail_class->open = vips_thumbnail_source_open;
1898
1899
19
  VIPS_ARG_OBJECT(class, "source", 1,
1900
19
    _("Source"),
1901
19
    _("Source to load from"),
1902
19
    VIPS_ARGUMENT_REQUIRED_INPUT,
1903
19
    G_STRUCT_OFFSET(VipsThumbnailSource, source),
1904
19
    VIPS_TYPE_SOURCE);
1905
1906
19
  VIPS_ARG_STRING(class, "option_string", 20,
1907
19
    _("Extra options"),
1908
19
    _("Options that are passed on to the underlying loader"),
1909
19
    VIPS_ARGUMENT_OPTIONAL_INPUT,
1910
19
    G_STRUCT_OFFSET(VipsThumbnailSource, option_string),
1911
19
    "");
1912
19
}
1913
1914
static void
1915
vips_thumbnail_source_init(VipsThumbnailSource *source)
1916
579
{
1917
579
}
1918
1919
/**
1920
 * vips_thumbnail_source:
1921
 * @source: source to thumbnail
1922
 * @out: (out): output image
1923
 * @width: target width in pixels
1924
 * @...: `NULL`-terminated list of optional named arguments
1925
 *
1926
 * Exactly as [ctor@Image.thumbnail], but read from a source.
1927
 *
1928
 * One extra
1929
 * optional argument, @option_string, lets you pass options to the underlying
1930
 * loader.
1931
 *
1932
 * ::: tip "Optional arguments"
1933
 *     * @height: `gint`, target height in pixels
1934
 *     * @size: [enum@Size], upsize, downsize, both or force
1935
 *     * @no_rotate: `gboolean`, don't rotate upright using orientation tag
1936
 *     * @crop: [enum@Interesting], shrink and crop to fill target
1937
 *     * @interesting_x: `gint`, horizontal position of the specific point of
1938
 *       interest when cropping with [enum@Vips.Interesting.SPECIFIC])
1939
 *     * @interesting_y: `gint`, vertical position of the specific point of
1940
 *       interest when cropping with [enum@Vips.Interesting.SPECIFIC])
1941
 *     * @linear: `gboolean`, perform shrink in linear light
1942
 *     * @input_profile: `gchararray`, fallback input ICC profile
1943
 *     * @output_profile: `gchararray`, output ICC profile
1944
 *     * @intent: [enum@Intent], rendering intent
1945
 *     * @fail_on: [enum@FailOn], load error types to fail on
1946
 *     * @option_string: `gchararray`, extra loader options
1947
 *
1948
 * ::: seealso
1949
 *     [ctor@Image.thumbnail].
1950
 *
1951
 * Returns: 0 on success, -1 on error.
1952
 */
1953
int
1954
vips_thumbnail_source(VipsSource *source, VipsImage **out, int width, ...)
1955
0
{
1956
0
  va_list ap;
1957
0
  int result;
1958
1959
0
  va_start(ap, width);
1960
0
  result = vips_call_split("thumbnail_source", ap, source, out, width);
1961
0
  va_end(ap);
1962
1963
0
  return result;
1964
0
}
1965
1966
typedef struct _VipsThumbnailImage {
1967
  VipsThumbnail parent_object;
1968
1969
  VipsImage *in;
1970
} VipsThumbnailImage;
1971
1972
typedef VipsThumbnailClass VipsThumbnailImageClass;
1973
1974
39
G_DEFINE_TYPE(VipsThumbnailImage, vips_thumbnail_image,
1975
39
  vips_thumbnail_get_type());
1976
39
1977
39
/* Get the info from a image.
1978
39
 */
1979
39
static int
1980
39
vips_thumbnail_image_get_info(VipsThumbnail *thumbnail)
1981
28.1k
{
1982
28.1k
  VipsThumbnailImage *image = (VipsThumbnailImage *) thumbnail;
1983
1984
  /* Doesn't really matter what we put here.
1985
   */
1986
28.1k
  thumbnail->loader = "image source";
1987
1988
28.1k
  vips_thumbnail_read_header(thumbnail, image->in);
1989
1990
28.1k
  return 0;
1991
28.1k
}
1992
1993
/* Open an image. We can't pre-shrink with an image source, sadly.
1994
 */
1995
static VipsImage *
1996
vips_thumbnail_image_open(VipsThumbnail *thumbnail, double factor)
1997
28.1k
{
1998
28.1k
  VipsThumbnailImage *image = (VipsThumbnailImage *) thumbnail;
1999
2000
28.1k
  g_object_ref(image->in);
2001
2002
28.1k
  return image->in;
2003
28.1k
}
2004
2005
static void
2006
vips_thumbnail_image_class_init(VipsThumbnailClass *class)
2007
19
{
2008
19
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
2009
19
  VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);
2010
19
  VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
2011
19
  VipsThumbnailClass *thumbnail_class = VIPS_THUMBNAIL_CLASS(class);
2012
2013
19
  gobject_class->set_property = vips_object_set_property;
2014
19
  gobject_class->get_property = vips_object_get_property;
2015
2016
19
  vobject_class->nickname = "thumbnail_image";
2017
19
  vobject_class->description = _("generate thumbnail from image");
2018
2019
19
  operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
2020
2021
19
  thumbnail_class->get_info = vips_thumbnail_image_get_info;
2022
19
  thumbnail_class->open = vips_thumbnail_image_open;
2023
2024
19
  VIPS_ARG_IMAGE(class, "in", 1,
2025
19
    _("Input"),
2026
19
    _("Input image argument"),
2027
19
    VIPS_ARGUMENT_REQUIRED_INPUT,
2028
19
    G_STRUCT_OFFSET(VipsThumbnailImage, in));
2029
19
}
2030
2031
static void
2032
vips_thumbnail_image_init(VipsThumbnailImage *image)
2033
28.1k
{
2034
28.1k
}
2035
2036
/**
2037
 * vips_thumbnail_image: (method)
2038
 * @in: input image
2039
 * @out: (out): output image
2040
 * @width: target width in pixels
2041
 * @...: `NULL`-terminated list of optional named arguments
2042
 *
2043
 * Exactly as [ctor@Image.thumbnail], but read from an existing image.
2044
 *
2045
 * This operation
2046
 * is not able to exploit shrink-on-load features of image load libraries, so
2047
 * it can be much slower than [ctor@Image.thumbnail] and produce poorer quality
2048
 * output. Only use this operation if you really have to.
2049
 *
2050
 * ::: tip "Optional arguments"
2051
 *     * @height: `gint`, target height in pixels
2052
 *     * @size: [enum@Size], upsize, downsize, both or force
2053
 *     * @no_rotate: `gboolean`, don't rotate upright using orientation tag
2054
 *     * @crop: [enum@Interesting], shrink and crop to fill target
2055
 *     * @interesting_x: `gint`, horizontal position of the specific point of
2056
 *       interest when cropping with [enum@Vips.Interesting.SPECIFIC])
2057
 *     * @interesting_y: `gint`, vertical position of the specific point of
2058
 *       interest when cropping with [enum@Vips.Interesting.SPECIFIC])
2059
 *     * @linear: `gboolean`, perform shrink in linear light
2060
 *     * @input_profile: `gchararray`, fallback input ICC profile
2061
 *     * @output_profile: `gchararray`, output ICC profile
2062
 *     * @intent: [enum@Intent], rendering intent
2063
 *     * @fail_on: [enum@FailOn], load error types to fail on
2064
 *
2065
 * ::: seealso
2066
 *     [ctor@Image.thumbnail].
2067
 *
2068
 * Returns: 0 on success, -1 on error.
2069
 */
2070
int
2071
vips_thumbnail_image(VipsImage *in, VipsImage **out, int width, ...)
2072
28.1k
{
2073
28.1k
  va_list ap;
2074
28.1k
  int result;
2075
2076
28.1k
  va_start(ap, width);
2077
28.1k
  result = vips_call_split("thumbnail_image", ap, in, out, width);
2078
28.1k
  va_end(ap);
2079
2080
28.1k
  return result;
2081
28.1k
}