Coverage Report

Created: 2025-11-16 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libvips/libvips/foreign/heifsave.c
Line
Count
Source
1
/* save to heif
2
 *
3
 * 5/7/18
4
 *  - from niftisave.c
5
 * 3/7/19 [lovell]
6
 *  - add "compression" option
7
 * 1/9/19 [meyermarcel]
8
 *  - save alpha when necessary
9
 * 15/3/20
10
 *  - revise for new VipsTarget API
11
 * 14/2/21 kleisauke
12
 *  - move GObject part to vips2heif.c
13
 * 30/7/21
14
 *  - rename "speed" as "effort" for consistency with other savers
15
 * 22/12/21
16
 *  - add >8 bit support
17
 * 22/10/11
18
 *      - improve rules for 16-bit write [johntrunc]
19
 */
20
21
/*
22
23
  This file is part of VIPS.
24
25
  VIPS is free software; you can redistribute it and/or modify
26
  it under the terms of the GNU Lesser General Public License as published by
27
  the Free Software Foundation; either version 2 of the License, or
28
  (at your option) any later version.
29
30
  This program is distributed in the hope that it will be useful,
31
  but WITHOUT ANY WARRANTY; without even the implied warranty of
32
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33
  GNU Lesser General Public License for more details.
34
35
  You should have received a copy of the GNU Lesser General Public License
36
  along with this program; if not, write to the Free Software
37
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
38
  02110-1301  USA
39
40
 */
41
42
/*
43
44
  These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
45
46
 */
47
48
/*
49
#define DEBUG_VERBOSE
50
#define DEBUG
51
 */
52
53
#ifdef HAVE_CONFIG_H
54
#include <config.h>
55
#endif /*HAVE_CONFIG_H*/
56
#include <glib/gi18n-lib.h>
57
58
#ifdef HAVE_HEIF
59
60
#include <stdio.h>
61
#include <stdlib.h>
62
#include <string.h>
63
64
#include <vips/vips.h>
65
#include <vips/internal.h>
66
67
#include "pforeign.h"
68
69
#include <libheif/heif.h>
70
71
typedef struct _VipsForeignSaveHeif {
72
  VipsForeignSave parent_object;
73
74
  /* Where to write (set by subclasses).
75
   */
76
  VipsTarget *target;
77
78
  /* Coding quality factor (1 - 100).
79
   */
80
  int Q;
81
82
  /* bitdepth to save at for >8 bit images.
83
   */
84
  int bitdepth;
85
86
  /* Lossless compression.
87
   */
88
  gboolean lossless;
89
90
  /* Compression format
91
   */
92
  VipsForeignHeifCompression compression;
93
94
  /* CPU effort (0 - 9).
95
   */
96
  int effort;
97
98
  /* Chroma subsampling.
99
   */
100
  VipsForeignSubsample subsample_mode;
101
102
  /* Encoder to use. For instance: aom, svt etc.
103
   */
104
  VipsForeignHeifEncoder selected_encoder;
105
106
  int page_width;
107
  int page_height;
108
  int n_pages;
109
110
  struct heif_context *ctx;
111
  struct heif_encoder *encoder;
112
113
  /* The current page we are writing.
114
   */
115
  struct heif_image_handle *handle;
116
117
  /* The current page in memory which we build as we scan down the
118
   * image.
119
   */
120
  struct heif_image *img;
121
122
  /* The libheif memory area we fill with pixels from the libvips
123
   * pipe.
124
   */
125
  uint8_t *data;
126
  int stride;
127
128
  /* Deprecated ... this is now called effort for consistency with the
129
   * other encoders.
130
   */
131
  int speed;
132
133
} VipsForeignSaveHeif;
134
135
typedef VipsForeignSaveClass VipsForeignSaveHeifClass;
136
137
102
G_DEFINE_ABSTRACT_TYPE(VipsForeignSaveHeif, vips_foreign_save_heif,
138
102
  VIPS_TYPE_FOREIGN_SAVE);
139
102
140
102
static void
141
102
vips_foreign_save_heif_dispose(GObject *gobject)
142
19.4k
{
143
19.4k
  VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) gobject;
144
145
19.4k
  VIPS_UNREF(heif->target);
146
19.4k
  VIPS_FREEF(heif_image_release, heif->img);
147
19.4k
  VIPS_FREEF(heif_image_handle_release, heif->handle);
148
19.4k
  VIPS_FREEF(heif_encoder_release, heif->encoder);
149
19.4k
  VIPS_FREEF(heif_context_free, heif->ctx);
150
151
19.4k
  G_OBJECT_CLASS(vips_foreign_save_heif_parent_class)->dispose(gobject);
152
19.4k
}
153
154
typedef struct heif_error (*libheif_metadata_fn)(struct heif_context *,
155
  const struct heif_image_handle *,
156
  const void *, int);
157
158
/* String-based metadata fields we add.
159
 */
160
typedef struct _VipsForeignSaveHeifMetadata {
161
  const char *name;      /* as understood by libvips */
162
  libheif_metadata_fn saver; /* as understood by libheif */
163
} VipsForeignSaveHeifMetadata;
164
165
static VipsForeignSaveHeifMetadata libheif_metadata[] = {
166
  { VIPS_META_EXIF_NAME, heif_context_add_exif_metadata },
167
  { VIPS_META_XMP_NAME, heif_context_add_XMP_metadata }
168
};
169
170
static int
171
vips_foreign_save_heif_write_metadata(VipsForeignSaveHeif *heif)
172
11.3k
{
173
11.3k
  VipsForeignSave *save = (VipsForeignSave *) heif;
174
175
11.3k
  struct heif_error error;
176
177
33.9k
  for (int i = 0; i < VIPS_NUMBER(libheif_metadata); i++) {
178
22.6k
    const char *vips_name = libheif_metadata[i].name;
179
22.6k
    libheif_metadata_fn heif_saver = libheif_metadata[i].saver;
180
181
22.6k
    if (vips_image_get_typeof(save->ready, vips_name)) {
182
11.3k
      const void *data;
183
11.3k
      size_t length;
184
185
#ifdef DEBUG
186
      printf("attaching %s ..\n", vips_name);
187
#endif /*DEBUG*/
188
189
11.3k
      if (vips_image_get_blob(save->ready, vips_name, &data, &length))
190
0
        return -1;
191
192
11.3k
      error = heif_saver(heif->ctx, heif->handle, data, length);
193
11.3k
      if (error.code) {
194
0
        vips__heif_error(&error);
195
0
        return -1;
196
0
      }
197
11.3k
    }
198
22.6k
  }
199
200
11.3k
  return 0;
201
11.3k
}
202
203
static int
204
vips_foreign_save_heif_add_icc(VipsForeignSaveHeif *heif,
205
  const void *profile, size_t length)
206
6
{
207
#ifdef DEBUG
208
  printf("attaching profile ..\n");
209
#endif /*DEBUG*/
210
211
6
  struct heif_error error;
212
6
  error = heif_image_set_raw_color_profile(heif->img,
213
6
    "rICC", profile, length);
214
215
6
  if (error.code) {
216
0
    vips__heif_error(&error);
217
0
    return -1;
218
0
  }
219
220
6
  return 0;
221
6
}
222
223
static int
224
vips_foreign_save_heif_add_custom_icc(VipsForeignSaveHeif *heif,
225
  const char *profile)
226
0
{
227
0
  VipsBlob *blob;
228
229
0
  if (vips_profile_load(profile, &blob, NULL))
230
0
    return -1;
231
232
0
  if (blob) {
233
0
    size_t length;
234
0
    const void *data = vips_blob_get(blob, &length);
235
236
0
    if (vips_foreign_save_heif_add_icc(heif, data, length)) {
237
0
      vips_area_unref((VipsArea *) blob);
238
0
      return -1;
239
0
    }
240
241
0
    vips_area_unref((VipsArea *) blob);
242
0
  }
243
244
0
  return 0;
245
0
}
246
247
static int
248
vips_foreign_save_heif_add_orig_icc(VipsForeignSaveHeif *heif)
249
6
{
250
6
  VipsForeignSave *save = (VipsForeignSave *) heif;
251
252
6
  const void *data;
253
6
  size_t length;
254
255
6
  if (vips_image_get_blob(save->ready, VIPS_META_ICC_NAME, &data, &length))
256
0
    return -1;
257
258
6
  if (vips_foreign_save_heif_add_icc(heif, data, length))
259
0
    return -1;
260
261
6
  return 0;
262
6
}
263
264
static int
265
vips_foreign_save_heif_write_page(VipsForeignSaveHeif *heif, int page)
266
11.3k
{
267
11.3k
  VipsForeignSave *save = (VipsForeignSave *) heif;
268
269
11.3k
  struct heif_error error;
270
11.3k
  struct heif_encoding_options *options;
271
11.3k
#ifdef HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE
272
11.3k
  struct heif_color_profile_nclx *nclx = NULL;
273
11.3k
#endif
274
275
  /* A profile supplied as an argument overrides an embedded
276
   * profile.
277
   */
278
11.3k
  if (save->profile) {
279
0
    if (vips_foreign_save_heif_add_custom_icc(heif, save->profile))
280
0
      return -1;
281
0
  }
282
11.3k
  else if (vips_image_get_typeof(save->ready, VIPS_META_ICC_NAME)) {
283
6
    if (vips_foreign_save_heif_add_orig_icc(heif))
284
0
      return -1;
285
6
  }
286
287
11.3k
  options = heif_encoding_options_alloc();
288
11.3k
  options->save_alpha_channel = save->ready->Bands > 3;
289
290
11.3k
#ifdef HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE
291
  /* Matrix coefficients have to be identity (CICP x/y/0) in lossless
292
   * mode.
293
   */
294
11.3k
  if (heif->lossless) {
295
85
    if (!(nclx = heif_nclx_color_profile_alloc())) {
296
0
      heif_encoding_options_free(options);
297
0
      return -1;
298
0
    }
299
300
85
    nclx->matrix_coefficients = heif_matrix_coefficients_RGB_GBR;
301
85
    options->output_nclx_profile = nclx;
302
303
    /* Ensure nclx profile is actually written with libheif < v1.17.2.
304
     */
305
85
    options->macOS_compatibility_workaround_no_nclx_profile = 0;
306
85
  }
307
11.3k
#endif /*HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE*/
308
309
11.3k
#ifdef HAVE_HEIF_ENCODING_OPTIONS_IMAGE_ORIENTATION
310
  /* EXIF orientation is informational in the HEIF specification.
311
   * Orientation is defined using irot and imir transformations.
312
   */
313
11.3k
  options->image_orientation = vips_image_get_orientation(save->ready);
314
11.3k
  vips_autorot_remove_angle(save->ready);
315
11.3k
#endif
316
317
#ifdef DEBUG
318
  {
319
    GTimer *timer = g_timer_new();
320
321
    printf("calling heif_context_encode_image() ...\n");
322
#endif /*DEBUG*/
323
324
11.3k
    error = heif_context_encode_image(heif->ctx,
325
11.3k
      heif->img, heif->encoder, options, &heif->handle);
326
327
#ifdef DEBUG
328
    printf("... libheif took %.2g seconds\n", g_timer_elapsed(timer, NULL));
329
    g_timer_destroy(timer);
330
  }
331
#endif /*DEBUG*/
332
333
11.3k
  heif_encoding_options_free(options);
334
11.3k
#ifdef HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE
335
11.3k
  VIPS_FREEF(heif_nclx_color_profile_free, nclx);
336
11.3k
#endif
337
338
11.3k
  if (error.code) {
339
0
    vips__heif_error(&error);
340
0
    return -1;
341
0
  }
342
343
11.3k
  if (vips_image_get_typeof(save->ready, "heif-primary")) {
344
177
    int primary;
345
346
177
    if (vips_image_get_int(save->ready, "heif-primary", &primary))
347
0
      return -1;
348
349
177
    if (page == primary) {
350
176
      error = heif_context_set_primary_image(heif->ctx, heif->handle);
351
176
      if (error.code) {
352
0
        vips__heif_error(&error);
353
0
        return -1;
354
0
      }
355
176
    }
356
177
  }
357
358
11.3k
  if (vips_foreign_save_heif_write_metadata(heif))
359
0
    return -1;
360
361
11.3k
  VIPS_FREEF(heif_image_handle_release, heif->handle);
362
363
11.3k
  return 0;
364
11.3k
}
365
366
static int
367
vips_foreign_save_heif_pack(VipsForeignSaveHeif *heif,
368
  VipsPel *q, VipsPel *p, int ne)
369
613k
{
370
613k
  VipsForeignSave *save = (VipsForeignSave *) heif;
371
372
613k
  int i;
373
374
613k
  if (save->ready->BandFmt == VIPS_FORMAT_UCHAR &&
375
508k
    heif->bitdepth == 8)
376
    /* Most common case -- 8 bit to 8 bit.
377
     */
378
486k
    memcpy(q, p, ne);
379
126k
  else if (save->ready->BandFmt == VIPS_FORMAT_UCHAR &&
380
21.8k
    heif->bitdepth > 8) {
381
    /* 8-bit source, write a bigendian short, shifted up.
382
     */
383
21.8k
    int shift = heif->bitdepth - 8;
384
385
4.57M
    for (i = 0; i < ne; i++) {
386
4.54M
      guint16 v = p[i] << shift;
387
388
4.54M
      q[0] = v >> 8;
389
4.54M
      q[1] = v;
390
391
4.54M
      q += 2;
392
4.54M
    }
393
21.8k
  }
394
104k
  else if (save->ready->BandFmt == VIPS_FORMAT_USHORT &&
395
104k
    heif->bitdepth <= 8) {
396
    /* 16-bit native byte order source, 8 bit write.
397
     *
398
     * Pick the high or low bits of the source.
399
     */
400
14.9k
    int vips_bitdepth =
401
14.9k
      save->ready->Type == VIPS_INTERPRETATION_RGB16 ||
402
14.9k
        save->ready->Type == VIPS_INTERPRETATION_GREY16 ? 16 : 8;
403
14.9k
    int shift = vips_bitdepth - heif->bitdepth;
404
405
2.28M
    for (i = 0; i < ne; i++) {
406
2.27M
      guint16 v = *((gushort *) p) >> shift;
407
408
2.27M
      q[i] = VIPS_MIN(v, UCHAR_MAX);
409
410
2.27M
      p += 2;
411
2.27M
    }
412
14.9k
  }
413
89.9k
  else if (save->ready->BandFmt == VIPS_FORMAT_USHORT &&
414
89.9k
    heif->bitdepth > 8) {
415
    /* 16-bit native byte order source, 16 bit bigendian write.
416
     */
417
89.9k
    int vips_bitdepth =
418
89.9k
      save->ready->Type == VIPS_INTERPRETATION_RGB16 ||
419
89.9k
        save->ready->Type == VIPS_INTERPRETATION_GREY16 ? 16 : 8;
420
89.9k
    int shift = vips_bitdepth - heif->bitdepth;
421
422
23.2M
    for (i = 0; i < ne; i++) {
423
23.1M
      guint16 v = *((gushort *) p) >> shift;
424
425
23.1M
      q[0] = v >> 8;
426
23.1M
      q[1] = v;
427
428
23.1M
      p += 2;
429
23.1M
      q += 2;
430
23.1M
    }
431
89.9k
  }
432
0
  else {
433
0
    VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(heif);
434
435
0
    vips_error(class->nickname,
436
0
      "%s", _("unimplemented format conversion"));
437
0
    return -1;
438
0
  }
439
440
613k
  return 0;
441
613k
}
442
443
static int
444
vips_foreign_save_heif_write_block(VipsRegion *region, VipsRect *area,
445
  void *a)
446
10.8k
{
447
10.8k
  VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) a;
448
449
10.8k
  int y;
450
451
#ifdef DEBUG
452
  printf("vips_foreign_save_heif_write_block: y = %d\n", area->top);
453
#endif /*DEBUG*/
454
455
  /* Copy a line at a time into our output image, write each time the
456
   * image fills.
457
   */
458
624k
  for (y = 0; y < area->height; y++) {
459
    /* Y in page.
460
     */
461
613k
    int page = (area->top + y) / heif->page_height;
462
613k
    int line = (area->top + y) % heif->page_height;
463
613k
    VipsPel *p = VIPS_REGION_ADDR(region, 0, area->top + y);
464
613k
    VipsPel *q = heif->data + (size_t) heif->stride * line;
465
466
613k
    if (vips_foreign_save_heif_pack(heif,
467
613k
        q, p, VIPS_REGION_N_ELEMENTS(region)))
468
0
      return -1;
469
470
    /* Did we just write the final line? Write as a new page
471
     * into the output.
472
     */
473
613k
    if (line == heif->page_height - 1)
474
11.3k
      if (vips_foreign_save_heif_write_page(heif, page))
475
0
        return -1;
476
613k
  }
477
478
10.8k
  return 0;
479
10.8k
}
480
481
struct heif_error
482
vips_foreign_save_heif_write(struct heif_context *ctx,
483
  const void *data, size_t length, void *userdata)
484
10.8k
{
485
10.8k
  VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) userdata;
486
487
10.8k
  struct heif_error error;
488
489
10.8k
#ifdef HAVE_HEIF_ERROR_SUCCESS
490
10.8k
  error = heif_error_success;
491
#else
492
  error.code = heif_error_Ok;
493
#endif /*HAVE_HEIF_ERROR_SUCCESS*/
494
495
10.8k
  if (vips_target_write(heif->target, data, length)) {
496
0
    error.code = heif_error_Encoding_error;
497
0
    error.subcode = heif_suberror_Cannot_write_output_data;
498
0
    error.message = "Cannot write output data";
499
0
  }
500
501
10.8k
  return error;
502
10.8k
}
503
504
static int
505
vips_foreign_save_heif_build(VipsObject *object)
506
19.4k
{
507
19.4k
  VipsForeignSave *save = (VipsForeignSave *) object;
508
19.4k
  VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
509
510
19.4k
  struct heif_error error;
511
19.4k
  struct heif_writer writer;
512
19.4k
  char *chroma;
513
19.4k
  const struct heif_encoder_descriptor *out_encoder;
514
19.4k
#ifdef HAVE_HEIF_ENCODER_PARAMETER_GET_VALID_INTEGER_VALUES
515
19.4k
  const struct heif_encoder_parameter *const *param;
516
19.4k
#endif
517
19.4k
  gboolean has_alpha;
518
519
19.4k
  if (VIPS_OBJECT_CLASS(vips_foreign_save_heif_parent_class)-> build(object))
520
4
    return -1;
521
522
  /* If the old, deprecated "speed" param is being used and the new
523
   * "effort" param is not, use speed to init effort.
524
   */
525
19.4k
  if (vips_object_argument_isset(object, "speed") &&
526
0
    !vips_object_argument_isset(object, "effort"))
527
0
    heif->effort = 9 - heif->speed;
528
529
  /* The "lossless" param implies no chroma subsampling.
530
   */
531
19.4k
  if (heif->lossless)
532
382
    heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_OFF;
533
534
  /* Default 12 bit save for 16-bit images.
535
   */
536
19.4k
  if (!vips_object_argument_isset(object, "bitdepth"))
537
18.9k
    heif->bitdepth =
538
18.9k
      save->ready->Type == VIPS_INTERPRETATION_RGB16 ||
539
16.0k
        save->ready->Type == VIPS_INTERPRETATION_GREY16
540
18.9k
      ? 12
541
18.9k
      : 8;
542
543
  /* HEIC and AVIF only implements 8 / 10 / 12 bit depth.
544
   */
545
19.4k
  if (heif->bitdepth != 12 &&
546
16.4k
    heif->bitdepth != 10 &&
547
16.0k
    heif->bitdepth != 8) {
548
1
    vips_error("heifsave", _("%d-bit colour depth not supported"),
549
1
      heif->bitdepth);
550
1
    return -1;
551
1
  }
552
553
  /* Try to find the selected encoder.
554
   */
555
19.4k
  if (heif->selected_encoder != VIPS_FOREIGN_HEIF_ENCODER_AUTO) {
556
1.55k
    const int count = heif_context_get_encoder_descriptors(
557
1.55k
      heif->ctx,
558
1.55k
      (enum heif_compression_format) heif->compression,
559
1.55k
      vips_enum_nick(VIPS_TYPE_FOREIGN_HEIF_ENCODER,
560
1.55k
        heif->selected_encoder),
561
1.55k
      &out_encoder, 1);
562
563
1.55k
    if (count > 0)
564
1.55k
      error = heif_context_get_encoder(heif->ctx,
565
1.55k
        out_encoder, &heif->encoder);
566
1
    else
567
1
      g_warning("heifsave: could not find %s",
568
1.55k
        vips_enum_nick(VIPS_TYPE_FOREIGN_HEIF_ENCODER,
569
1.55k
          heif->selected_encoder));
570
1.55k
  }
571
572
  /* Fallback to default encoder.
573
   */
574
19.4k
  if (!heif->encoder)
575
17.8k
    error = heif_context_get_encoder_for_format(heif->ctx,
576
17.8k
      (enum heif_compression_format) heif->compression,
577
17.8k
      &heif->encoder);
578
579
19.4k
  if (error.code) {
580
2
    if (error.code == heif_error_Unsupported_filetype)
581
2
      vips_error("heifsave", "%s", _("Unsupported compression"));
582
0
    else
583
0
      vips__heif_error(&error);
584
585
2
    return -1;
586
2
  }
587
588
19.4k
  error = heif_encoder_set_lossy_quality(heif->encoder, heif->Q);
589
19.4k
  if (error.code) {
590
0
    vips__heif_error(&error);
591
0
    return -1;
592
0
  }
593
594
19.4k
  error = heif_encoder_set_lossless(heif->encoder, heif->lossless);
595
19.4k
  if (error.code) {
596
0
    vips__heif_error(&error);
597
0
    return -1;
598
0
  }
599
600
19.4k
  error = heif_encoder_set_parameter_integer(heif->encoder,
601
19.4k
    "speed", 9 - heif->effort);
602
19.4k
  if (error.code &&
603
0
    error.subcode != heif_suberror_Unsupported_parameter) {
604
0
    vips__heif_error(&error);
605
0
    return -1;
606
0
  }
607
608
19.4k
  chroma = heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF ||
609
19.0k
      (heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO &&
610
17.8k
        heif->Q >= 90)
611
19.4k
    ? "444"
612
19.4k
    : "420";
613
19.4k
  error = heif_encoder_set_parameter_string(heif->encoder,
614
19.4k
    "chroma", chroma);
615
19.4k
  if (error.code &&
616
0
    error.subcode != heif_suberror_Unsupported_parameter) {
617
0
    vips__heif_error(&error);
618
0
    return -1;
619
0
  }
620
621
19.4k
#ifdef HAVE_HEIF_ENCODER_PARAMETER_GET_VALID_INTEGER_VALUES
622
19.4k
  for (param = heif_encoder_list_parameters(heif->encoder);
623
311k
    *param; param++) {
624
291k
    int have_minimum;
625
291k
    int have_maximum;
626
291k
    int minimum;
627
291k
    int maximum;
628
629
291k
    if (strcmp(heif_encoder_parameter_get_name(*param), "threads") != 0)
630
272k
      continue;
631
632
19.4k
    error = heif_encoder_parameter_get_valid_integer_values(*param,
633
19.4k
      &have_minimum, &have_maximum, &minimum, &maximum, NULL, NULL);
634
19.4k
    if (error.code) {
635
0
      vips__heif_error(&error);
636
0
      return -1;
637
0
    }
638
639
19.4k
    error = heif_encoder_set_parameter_integer(heif->encoder,
640
19.4k
      "threads", VIPS_CLIP(minimum, vips_concurrency_get(), maximum));
641
19.4k
    if (error.code &&
642
0
      error.subcode != heif_suberror_Unsupported_parameter) {
643
0
      vips__heif_error(&error);
644
0
      return -1;
645
0
    }
646
19.4k
  }
647
19.4k
#endif /*HAVE_HEIF_ENCODER_PARAMETER_GET_VALID_INTEGER_VALUES*/
648
649
  /* Try to enable auto_tiles. This can make AVIF encoding a lot faster,
650
   * with only a very small increase in file size.
651
   */
652
19.4k
  error = heif_encoder_set_parameter_boolean(heif->encoder,
653
19.4k
    "auto-tiles", TRUE);
654
19.4k
  if (error.code &&
655
0
    error.subcode != heif_suberror_Unsupported_parameter) {
656
0
    vips__heif_error(&error);
657
0
    return -1;
658
0
  }
659
660
  /* Try to prevent the AVIF encoder from using intra block copy,
661
   * helps ensure encoding time is more predictable.
662
   */
663
19.4k
  error = heif_encoder_set_parameter_boolean(heif->encoder,
664
19.4k
    "enable-intrabc", FALSE);
665
19.4k
  if (error.code &&
666
0
    error.subcode != heif_suberror_Unsupported_parameter) {
667
0
    vips__heif_error(&error);
668
0
    return -1;
669
0
  }
670
671
  /* TODO .. support extra per-encoder params with
672
   * heif_encoder_list_parameters().
673
   */
674
675
19.4k
  heif->page_width = save->ready->Xsize;
676
19.4k
  heif->page_height = vips_image_get_page_height(save->ready);
677
19.4k
  heif->n_pages = save->ready->Ysize / heif->page_height;
678
19.4k
  has_alpha = save->ready->Bands > 3;
679
680
19.4k
  if (heif->page_width > 16384 || heif->page_height > 16384) {
681
0
    vips_error("heifsave", _("image too large"));
682
0
    return -1;
683
0
  }
684
685
  /* Make a heif image the size of a page. We send sink_disc() output
686
   * here and write a frame each time it fills.
687
   */
688
#ifdef DEBUG
689
  printf("vips_foreign_save_heif_build:\n");
690
  printf("\twidth = %d\n", heif->page_width);
691
  printf("\theight = %d\n", heif->page_height);
692
  printf("\talpha = %d\n", has_alpha);
693
#endif /*DEBUG*/
694
19.4k
  error = heif_image_create(heif->page_width, heif->page_height,
695
19.4k
    heif_colorspace_RGB,
696
19.4k
    vips__heif_chroma(heif->bitdepth, has_alpha),
697
19.4k
    &heif->img);
698
19.4k
  if (error.code) {
699
0
    vips__heif_error(&error);
700
0
    return -1;
701
0
  }
702
703
19.4k
  error = heif_image_add_plane(heif->img, heif_channel_interleaved,
704
19.4k
    heif->page_width, heif->page_height,
705
19.4k
    heif->bitdepth);
706
19.4k
  if (error.code) {
707
0
    vips__heif_error(&error);
708
0
    return -1;
709
0
  }
710
711
#ifdef DEBUG
712
  vips__heif_image_print(heif->img);
713
#endif /*DEBUG*/
714
715
19.4k
  heif->data = heif_image_get_plane(heif->img,
716
19.4k
    heif_channel_interleaved, &heif->stride);
717
718
  /* Write data.
719
   */
720
19.4k
  if (vips_sink_disc(save->ready, vips_foreign_save_heif_write_block, heif))
721
8.59k
    return -1;
722
723
  /* This has to come right at the end :-( so there's no support for
724
   * incremental writes.
725
   */
726
10.8k
  writer.writer_api_version = 1;
727
10.8k
  writer.write = vips_foreign_save_heif_write;
728
10.8k
  error = heif_context_write(heif->ctx, &writer, heif);
729
10.8k
  if (error.code) {
730
0
    vips__heif_error(&error);
731
0
    return -1;
732
0
  }
733
734
10.8k
  if (vips_target_end(heif->target))
735
0
    return -1;
736
737
10.8k
  return 0;
738
10.8k
}
739
740
#define UC VIPS_FORMAT_UCHAR
741
#define US VIPS_FORMAT_USHORT
742
743
/* Except for 8-bit inputs, we send everything else to 16. We decide on 8-bit
744
 * vs. 12 bit save based on Type in_build(), see above.
745
 */
746
static VipsBandFormat vips_heif_bandfmt[10] = {
747
  /* Band format:  UC  C   US  S   UI  I   F   X   D   DX */
748
  /* Promotion: */ UC, UC, US, US, US, US, US, US, US, US
749
};
750
751
static void
752
vips_foreign_save_heif_class_init(VipsForeignSaveHeifClass *class)
753
17
{
754
17
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
755
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
756
17
  VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class;
757
758
17
  vips__heif_init();
759
760
17
  gobject_class->dispose = vips_foreign_save_heif_dispose;
761
17
  gobject_class->set_property = vips_object_set_property;
762
17
  gobject_class->get_property = vips_object_get_property;
763
764
17
  object_class->nickname = "heifsave_base";
765
17
  object_class->description = _("save image in HEIF format");
766
17
  object_class->build = vips_foreign_save_heif_build;
767
768
17
  save_class->saveable =
769
17
    VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA;
770
17
  save_class->format_table = vips_heif_bandfmt;
771
772
17
  VIPS_ARG_INT(class, "Q", 10,
773
17
    _("Q"),
774
17
    _("Q factor"),
775
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
776
17
    G_STRUCT_OFFSET(VipsForeignSaveHeif, Q),
777
17
    1, 100, 50);
778
779
17
  VIPS_ARG_INT(class, "bitdepth", 11,
780
17
    _("Bit depth"),
781
17
    _("Number of bits per pixel"),
782
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
783
17
    G_STRUCT_OFFSET(VipsForeignSaveHeif, bitdepth),
784
17
    8, 12, 12);
785
786
17
  VIPS_ARG_BOOL(class, "lossless", 13,
787
17
    _("Lossless"),
788
17
    _("Enable lossless compression"),
789
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
790
17
    G_STRUCT_OFFSET(VipsForeignSaveHeif, lossless),
791
17
    FALSE);
792
793
17
  VIPS_ARG_ENUM(class, "compression", 14,
794
17
    _("Compression"),
795
17
    _("Compression format"),
796
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
797
17
    G_STRUCT_OFFSET(VipsForeignSaveHeif, compression),
798
17
    VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
799
17
    VIPS_FOREIGN_HEIF_COMPRESSION_HEVC);
800
801
17
  VIPS_ARG_INT(class, "effort", 15,
802
17
    _("Effort"),
803
17
    _("CPU effort"),
804
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
805
17
    G_STRUCT_OFFSET(VipsForeignSaveHeif, effort),
806
17
    0, 9, 4);
807
808
17
  VIPS_ARG_ENUM(class, "subsample_mode", 16,
809
17
    _("Subsample mode"),
810
17
    _("Select chroma subsample operation mode"),
811
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
812
17
    G_STRUCT_OFFSET(VipsForeignSaveHeif, subsample_mode),
813
17
    VIPS_TYPE_FOREIGN_SUBSAMPLE,
814
17
    VIPS_FOREIGN_SUBSAMPLE_AUTO);
815
816
17
  VIPS_ARG_INT(class, "speed", 17,
817
17
    _("Speed"),
818
17
    _("CPU effort"),
819
17
    VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
820
17
    G_STRUCT_OFFSET(VipsForeignSaveHeif, speed),
821
17
    0, 9, 5);
822
823
17
  VIPS_ARG_ENUM(class, "encoder", 18,
824
17
    _("Encoder"),
825
17
    _("Select encoder to use"),
826
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
827
17
    G_STRUCT_OFFSET(VipsForeignSaveHeif, selected_encoder),
828
17
    VIPS_TYPE_FOREIGN_HEIF_ENCODER,
829
17
    VIPS_FOREIGN_HEIF_ENCODER_AUTO);
830
17
}
831
832
static void
833
vips_foreign_save_heif_init(VipsForeignSaveHeif *heif)
834
19.4k
{
835
19.4k
  heif->ctx = heif_context_alloc();
836
19.4k
  heif->Q = 50;
837
19.4k
  heif->bitdepth = 12;
838
19.4k
  heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC;
839
19.4k
  heif->effort = 4;
840
19.4k
  heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO;
841
842
  /* Deprecated.
843
   */
844
19.4k
  heif->speed = 5;
845
19.4k
}
846
847
typedef struct _VipsForeignSaveHeifFile {
848
  VipsForeignSaveHeif parent_object;
849
850
  /* Filename for save.
851
   */
852
  char *filename;
853
854
} VipsForeignSaveHeifFile;
855
856
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifFileClass;
857
858
34
G_DEFINE_TYPE(VipsForeignSaveHeifFile, vips_foreign_save_heif_file,
859
34
  vips_foreign_save_heif_get_type());
860
34
861
34
static int
862
34
vips_foreign_save_heif_file_build(VipsObject *object)
863
34
{
864
0
  VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
865
0
  VipsForeignSaveHeifFile *file = (VipsForeignSaveHeifFile *) object;
866
867
0
  if (file->filename &&
868
0
    !(heif->target = vips_target_new_to_file(file->filename)))
869
0
    return -1;
870
871
0
  if (vips_iscasepostfix(file->filename, ".avif"))
872
0
    heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
873
874
0
  return VIPS_OBJECT_CLASS(vips_foreign_save_heif_file_parent_class)
875
0
    ->build(object);
876
0
}
877
878
static void
879
vips_foreign_save_heif_file_class_init(VipsForeignSaveHeifFileClass *class)
880
17
{
881
17
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
882
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
883
17
  VipsForeignClass *foreign_class = (VipsForeignClass *) class;
884
885
17
  gobject_class->set_property = vips_object_set_property;
886
17
  gobject_class->get_property = vips_object_get_property;
887
888
17
  object_class->nickname = "heifsave";
889
17
  object_class->build = vips_foreign_save_heif_file_build;
890
891
17
  foreign_class->suffs = vips__heif_suffs;
892
893
17
  VIPS_ARG_STRING(class, "filename", 1,
894
17
    _("Filename"),
895
17
    _("Filename to save to"),
896
17
    VIPS_ARGUMENT_REQUIRED_INPUT,
897
17
    G_STRUCT_OFFSET(VipsForeignSaveHeifFile, filename),
898
17
    NULL);
899
17
}
900
901
static void
902
vips_foreign_save_heif_file_init(VipsForeignSaveHeifFile *file)
903
0
{
904
0
}
905
906
typedef struct _VipsForeignSaveHeifBuffer {
907
  VipsForeignSaveHeif parent_object;
908
909
  /* Save to a buffer.
910
   */
911
  VipsArea *buf;
912
913
} VipsForeignSaveHeifBuffer;
914
915
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifBufferClass;
916
917
34
G_DEFINE_TYPE(VipsForeignSaveHeifBuffer, vips_foreign_save_heif_buffer,
918
34
  vips_foreign_save_heif_get_type());
919
34
920
34
static int
921
34
vips_foreign_save_heif_buffer_build(VipsObject *object)
922
34
{
923
0
  VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
924
0
  VipsForeignSaveHeifBuffer *buffer = (VipsForeignSaveHeifBuffer *) object;
925
926
0
  VipsBlob *blob;
927
928
0
  if (!(heif->target = vips_target_new_to_memory()))
929
0
    return -1;
930
931
0
  if (VIPS_OBJECT_CLASS(vips_foreign_save_heif_buffer_parent_class)
932
0
      ->build(object))
933
0
    return -1;
934
935
0
  g_object_get(heif->target, "blob", &blob, NULL);
936
0
  g_object_set(buffer, "buffer", blob, NULL);
937
0
  vips_area_unref(VIPS_AREA(blob));
938
939
0
  return 0;
940
0
}
941
942
static void
943
vips_foreign_save_heif_buffer_class_init(
944
  VipsForeignSaveHeifBufferClass *class)
945
17
{
946
17
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
947
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
948
17
  VipsForeignClass *foreign_class = (VipsForeignClass *) class;
949
950
17
  gobject_class->set_property = vips_object_set_property;
951
17
  gobject_class->get_property = vips_object_get_property;
952
953
17
  object_class->nickname = "heifsave_buffer";
954
17
  object_class->build = vips_foreign_save_heif_buffer_build;
955
956
17
  foreign_class->suffs = vips__heic_suffs;
957
958
17
  VIPS_ARG_BOXED(class, "buffer", 1,
959
17
    _("Buffer"),
960
17
    _("Buffer to save to"),
961
17
    VIPS_ARGUMENT_REQUIRED_OUTPUT,
962
17
    G_STRUCT_OFFSET(VipsForeignSaveHeifBuffer, buf),
963
17
    VIPS_TYPE_BLOB);
964
17
}
965
966
static void
967
vips_foreign_save_heif_buffer_init(VipsForeignSaveHeifBuffer *buffer)
968
0
{
969
0
}
970
971
typedef struct _VipsForeignSaveHeifTarget {
972
  VipsForeignSaveHeif parent_object;
973
974
  VipsTarget *target;
975
} VipsForeignSaveHeifTarget;
976
977
typedef VipsForeignSaveHeifClass VipsForeignSaveHeifTargetClass;
978
979
68
G_DEFINE_TYPE(VipsForeignSaveHeifTarget, vips_foreign_save_heif_target,
980
68
  vips_foreign_save_heif_get_type());
981
68
982
68
static int
983
68
vips_foreign_save_heif_target_build(VipsObject *object)
984
19.4k
{
985
19.4k
  VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
986
19.4k
  VipsForeignSaveHeifTarget *target = (VipsForeignSaveHeifTarget *) object;
987
988
19.4k
  if (target->target) {
989
19.4k
    heif->target = target->target;
990
19.4k
    g_object_ref(heif->target);
991
19.4k
  }
992
993
19.4k
  return VIPS_OBJECT_CLASS(vips_foreign_save_heif_target_parent_class)
994
19.4k
    ->build(object);
995
19.4k
}
996
997
static void
998
vips_foreign_save_heif_target_class_init(
999
  VipsForeignSaveHeifTargetClass *class)
1000
17
{
1001
17
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
1002
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
1003
17
  VipsForeignClass *foreign_class = (VipsForeignClass *) class;
1004
1005
17
  gobject_class->set_property = vips_object_set_property;
1006
17
  gobject_class->get_property = vips_object_get_property;
1007
1008
17
  object_class->nickname = "heifsave_target";
1009
17
  object_class->build = vips_foreign_save_heif_target_build;
1010
1011
17
  foreign_class->suffs = vips__heic_suffs;
1012
1013
17
  VIPS_ARG_OBJECT(class, "target", 1,
1014
17
    _("Target"),
1015
17
    _("Target to save to"),
1016
17
    VIPS_ARGUMENT_REQUIRED_INPUT,
1017
17
    G_STRUCT_OFFSET(VipsForeignSaveHeifTarget, target),
1018
17
    VIPS_TYPE_TARGET);
1019
17
}
1020
1021
static void
1022
vips_foreign_save_heif_target_init(VipsForeignSaveHeifTarget *target)
1023
19.4k
{
1024
19.4k
}
1025
1026
typedef VipsForeignSaveHeifTarget VipsForeignSaveAvifTarget;
1027
typedef VipsForeignSaveHeifTargetClass VipsForeignSaveAvifTargetClass;
1028
1029
34
G_DEFINE_TYPE(VipsForeignSaveAvifTarget, vips_foreign_save_avif_target,
1030
34
  vips_foreign_save_heif_target_get_type());
1031
34
1032
34
static void
1033
34
vips_foreign_save_avif_target_class_init(
1034
34
  VipsForeignSaveAvifTargetClass *class)
1035
34
{
1036
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
1037
17
  VipsForeignClass *foreign_class = (VipsForeignClass *) class;
1038
17
  VipsOperationClass *operation_class = (VipsOperationClass *) class;
1039
1040
17
  object_class->nickname = "avifsave_target";
1041
17
  object_class->description = _("save image in AVIF format");
1042
1043
17
  foreign_class->suffs = vips__avif_suffs;
1044
1045
  /* Hide from UI.
1046
   */
1047
17
  operation_class->flags |= VIPS_OPERATION_DEPRECATED;
1048
17
}
1049
1050
static void
1051
vips_foreign_save_avif_target_init(VipsForeignSaveAvifTarget *target)
1052
19.4k
{
1053
19.4k
  VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) target;
1054
1055
19.4k
  heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
1056
19.4k
}
1057
1058
#endif /*HAVE_HEIF*/
1059
1060
/* The C API wrappers are defined in foreign.c.
1061
 */