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