Coverage Report

Created: 2025-06-22 07:12

/src/libvips/libvips/foreign/webpsave.c
Line
Count
Source (jump to first uncovered line)
1
/* save to webp
2
 *
3
 * 24/11/11
4
 *  - wrap a class around the webp writer
5
 * 6/8/13
6
 *  - from vips2jpeg.c
7
 * 31/5/16
8
 *  - buffer write ignored lossless, thanks aaron42net
9
 * 2/5/16 Felix Bünemann
10
 *  - used advanced encoding API, expose controls
11
 * 8/11/16
12
 *  - add metadata write
13
 * 29/10/18
14
 *  - add animated webp support
15
 * 29/10/18
16
 *  - target libwebp 0.5+ and remove some ifdefs
17
 *  - add animated webp write
18
 *  - use libwebpmux instead of our own thing, phew
19
 * 15/1/19 lovell
20
 *  - add @effort
21
 * 6/7/19 [deftomat]
22
 *  - support array of delays
23
 * 8/7/19
24
 *  - set loop even if we strip
25
 * 14/10/19
26
 *  - revise for target IO
27
 * 18/7/20
28
 *  - add @profile param to match tiff, jpg, etc.
29
 * 30/7/21
30
 *  - rename "reduction_effort" as "effort"
31
 * 7/9/22 dloebl
32
 *  - switch to sink_disc
33
 */
34
35
/*
36
37
  This file is part of VIPS.
38
39
  VIPS is free software; you can redistribute it and/or modify
40
  it under the terms of the GNU Lesser General Public License as published by
41
  the Free Software Foundation; either version 2 of the License, or
42
  (at your option) any later version.
43
44
  This program is distributed in the hope that it will be useful,
45
  but WITHOUT ANY WARRANTY; without even the implied warranty of
46
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
47
  GNU Lesser General Public License for more details.
48
49
  You should have received a copy of the GNU Lesser General Public License
50
  along with this program; if not, write to the Free Software
51
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
52
  02110-1301  USA
53
54
 */
55
56
/*
57
58
  These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
59
60
 */
61
62
/*
63
#define DEBUG_VERBOSE
64
#define DEBUG
65
 */
66
67
#ifdef HAVE_CONFIG_H
68
#include <config.h>
69
#endif /*HAVE_CONFIG_H*/
70
#include <glib/gi18n-lib.h>
71
72
#include <stdio.h>
73
#include <stdlib.h>
74
#include <string.h>
75
76
#include <vips/vips.h>
77
#include <vips/internal.h>
78
79
#include "pforeign.h"
80
81
#ifdef HAVE_LIBWEBP
82
83
#include <webp/encode.h>
84
#include <webp/types.h>
85
#include <webp/mux.h>
86
87
typedef int (*webp_import)(WebPPicture *picture,
88
  const uint8_t *rgb, int stride);
89
90
typedef enum _VipsForeignSaveWebpMode {
91
  VIPS_FOREIGN_SAVE_WEBP_MODE_SINGLE,
92
  VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM
93
} VipsForeignSaveWebpMode;
94
95
typedef struct _VipsForeignSaveWebp {
96
  VipsForeignSave parent_object;
97
  VipsTarget *target;
98
99
  /* Animated or single image write mode?
100
   * Important, because we use a different API
101
   * for animated WebP write.
102
   */
103
  VipsForeignSaveWebpMode mode;
104
105
  int timestamp_ms;
106
107
  /* Quality factor.
108
   */
109
  int Q;
110
111
  /* Turn on lossless encode.
112
   */
113
  gboolean lossless;
114
115
  /* Lossy compression preset.
116
   */
117
  VipsForeignWebpPreset preset;
118
119
  /* Enable smart chroma subsampling.
120
   */
121
  gboolean smart_subsample;
122
123
  /* Enable smart deblock filter adjusting.
124
   */
125
  gboolean smart_deblock;
126
127
  /* Use preprocessing in lossless mode.
128
   */
129
  gboolean near_lossless;
130
131
  /* Alpha quality.
132
   */
133
  int alpha_q;
134
135
  /* Level of CPU effort to reduce file size.
136
   */
137
  int effort;
138
139
  /* If non-zero, set the desired target size in bytes.
140
   * Takes precedence over the 'Q' parameter.
141
   */
142
  int target_size;
143
144
  /* Number of entropy-analysis passes (in [1..10]).
145
   * The default value of 1 is appropriate for most cases.
146
   * If target_size is set, this must be set to a suitably large value.
147
   */
148
  int passes;
149
150
  /* Animated webp options.
151
   */
152
153
  int gif_delay;
154
  int *delay;
155
  int delay_length;
156
157
  /* Attempt to minimise size
158
   */
159
  gboolean min_size;
160
161
  /* Allow mixed encoding (might reduce file size)
162
   */
163
  gboolean mixed;
164
165
  /* Min between key frames.
166
   */
167
  int kmin;
168
169
  /* Max between keyframes.
170
   */
171
  int kmax;
172
173
  WebPConfig config;
174
175
  /* Output is written here. We can only support memory write, since we
176
   * handle metadata.
177
   */
178
  WebPMemoryWriter memory_writer;
179
180
  /* Write animated webp here.
181
   */
182
  WebPAnimEncoder *enc;
183
184
  /* Add metadata with this.
185
   */
186
  WebPMux *mux;
187
188
  /* The current y position in the frame and the current page index.
189
   */
190
  int write_y;
191
  int page_number;
192
193
  /* VipsRegion is not always contiguous, but we need contiguous RGB(A)
194
   * for libwebp. We need to copy each frame to a local buffer.
195
   */
196
  VipsPel *frame_bytes;
197
} VipsForeignSaveWebp;
198
199
typedef VipsForeignSaveClass VipsForeignSaveWebpClass;
200
201
G_DEFINE_ABSTRACT_TYPE(VipsForeignSaveWebp, vips_foreign_save_webp,
202
  VIPS_TYPE_FOREIGN_SAVE);
203
204
static int
205
vips_foreign_save_webp_progress_hook(int percent, const WebPPicture *picture)
206
423k
{
207
423k
  VipsImage *in = (VipsImage *) picture->user_data;
208
209
  /* Trigger any eval callbacks on the image and check if we need to abort
210
   * the WebP encoding.
211
   */
212
423k
  vips_image_eval(in, VIPS_IMAGE_N_PELS(in));
213
214
  /* Abort WebP encoding if requested.
215
   */
216
423k
  if (vips_image_iskilled(in))
217
0
    return 0;
218
219
423k
  return 1;
220
423k
}
221
222
static void
223
vips_foreign_save_webp_unset(VipsForeignSaveWebp *webp)
224
44.8k
{
225
44.8k
  WebPMemoryWriterClear(&webp->memory_writer);
226
44.8k
  VIPS_FREEF(WebPAnimEncoderDelete, webp->enc);
227
44.8k
  VIPS_FREEF(WebPMuxDelete, webp->mux);
228
44.8k
}
229
230
static void
231
vips_foreign_save_webp_dispose(GObject *gobject)
232
30.4k
{
233
30.4k
  VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) gobject;
234
235
30.4k
  vips_foreign_save_webp_unset(webp);
236
30.4k
  VIPS_UNREF(webp->target);
237
30.4k
  VIPS_FREE(webp->frame_bytes);
238
239
30.4k
  G_OBJECT_CLASS(vips_foreign_save_webp_parent_class)->dispose(gobject);
240
30.4k
}
241
242
static gboolean
243
vips_foreign_save_webp_pic_init(VipsForeignSaveWebp *webp, WebPPicture *pic)
244
17.1k
{
245
17.1k
  VipsForeignSave *save = (VipsForeignSave *) webp;
246
247
17.1k
  if (!WebPPictureInit(pic)) {
248
0
    vips_error("webpsave", "%s", _("picture version error"));
249
0
    return FALSE;
250
0
  }
251
17.1k
  pic->writer = WebPMemoryWrite;
252
17.1k
  pic->custom_ptr = (void *) &webp->memory_writer;
253
17.1k
  pic->progress_hook = vips_foreign_save_webp_progress_hook;
254
17.1k
  pic->user_data = (void *) save->in;
255
256
  /* Smart subsampling needs use_argb because it is applied during
257
   * RGB to YUV conversion.
258
   */
259
17.1k
  pic->use_argb = webp->lossless ||
260
17.1k
    webp->near_lossless ||
261
17.1k
    webp->smart_subsample;
262
263
17.1k
  return TRUE;
264
17.1k
}
265
266
/* Write a VipsImage into an uninitialised pic.
267
 */
268
static int
269
vips_foreign_save_webp_write_webp_image(VipsForeignSaveWebp *webp,
270
  const VipsPel *imagedata, WebPPicture *pic)
271
17.1k
{
272
17.1k
  VipsForeignSave *save = (VipsForeignSave *) webp;
273
17.1k
  int page_height = vips_image_get_page_height(save->ready);
274
275
17.1k
  webp_import import;
276
277
17.1k
  if (!vips_foreign_save_webp_pic_init(webp, pic))
278
0
    return -1;
279
280
17.1k
  pic->width = save->ready->Xsize;
281
17.1k
  pic->height = page_height;
282
283
17.1k
  if (save->ready->Bands == 4)
284
9.63k
    import = WebPPictureImportRGBA;
285
7.51k
  else
286
7.51k
    import = WebPPictureImportRGB;
287
288
17.1k
  if (!import(pic, imagedata, save->ready->Xsize * save->ready->Bands)) {
289
0
    WebPPictureFree(pic);
290
0
    vips_error("webpsave", "%s", _("picture memory error"));
291
0
    return -1;
292
0
  }
293
294
17.1k
  return 0;
295
17.1k
}
296
297
static int
298
vips_foreign_save_webp_get_delay(VipsForeignSaveWebp *webp, int page_number)
299
4.02k
{
300
4.02k
  int delay;
301
302
4.02k
  if (webp->delay &&
303
4.02k
    page_number < webp->delay_length)
304
4.02k
    delay = webp->delay[page_number];
305
0
  else
306
    // the old gif delay field was in centiseconds, so convert to ms
307
0
    delay = webp->gif_delay * 10;
308
309
  /* Force frames with a small or no duration to 100ms for consistency
310
   * with web browsers and other transcoding tools.
311
   */
312
4.02k
  return delay <= 10 ? 100 : delay;
313
4.02k
}
314
315
/* We have a complete frame -- write!
316
 */
317
static int
318
vips_foreign_save_webp_write_frame(VipsForeignSaveWebp *webp)
319
17.1k
{
320
17.1k
  VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(webp);
321
322
17.1k
  WebPPicture pic;
323
324
17.1k
  if (vips_foreign_save_webp_write_webp_image(webp, webp->frame_bytes, &pic))
325
0
    return -1;
326
327
  /* Animated write
328
   */
329
17.1k
  if (webp->mode == VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM) {
330
4.02k
    if (!WebPAnimEncoderAdd(webp->enc,
331
4.02k
        &pic, webp->timestamp_ms, &webp->config)) {
332
0
      WebPPictureFree(&pic);
333
0
      vips_error(class->nickname, "%s", _("anim add error"));
334
0
      return -1;
335
0
    }
336
337
    /* Adjust current timestamp
338
     */
339
4.02k
    webp->timestamp_ms +=
340
4.02k
      vips_foreign_save_webp_get_delay(webp, webp->page_number);
341
4.02k
  }
342
13.1k
  else {
343
    /* Single image write
344
     */
345
13.1k
    if (!WebPEncode(&webp->config, &pic)) {
346
0
      WebPPictureFree(&pic);
347
0
      vips_error("webpsave", "%s", _("unable to encode"));
348
0
      return -1;
349
0
    }
350
13.1k
  }
351
352
17.1k
  WebPPictureFree(&pic);
353
354
17.1k
  return 0;
355
17.1k
}
356
357
/* Another chunk of pixels have arrived from the pipeline. Add to frame, and
358
 * if the frame completes, compress and write to the target.
359
 */
360
static int
361
vips_foreign_save_webp_sink_disc(VipsRegion *region, VipsRect *area, void *a)
362
14.4k
{
363
14.4k
  VipsForeignSave *save = (VipsForeignSave *) a;
364
14.4k
  VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) a;
365
14.4k
  int page_height = vips_image_get_page_height(save->ready);
366
367
  /* Write the new pixels into the frame.
368
   */
369
867k
  for (int i = 0; i < area->height; i++) {
370
853k
    memcpy(webp->frame_bytes +
371
853k
        area->width * webp->write_y * save->ready->Bands,
372
853k
      VIPS_REGION_ADDR(region, 0, area->top + i),
373
853k
      (size_t) area->width * save->ready->Bands);
374
375
853k
    webp->write_y += 1;
376
377
    /* If we've filled the frame, write and move it down.
378
     */
379
853k
    if (webp->write_y == page_height) {
380
17.1k
      if (vips_foreign_save_webp_write_frame(webp))
381
0
        return -1;
382
383
17.1k
      webp->write_y = 0;
384
17.1k
      webp->page_number += 1;
385
17.1k
    }
386
853k
  }
387
388
14.4k
  return 0;
389
14.4k
}
390
391
static WebPPreset
392
get_preset(VipsForeignWebpPreset preset)
393
29.8k
{
394
29.8k
  switch (preset) {
395
29.8k
  case VIPS_FOREIGN_WEBP_PRESET_DEFAULT:
396
29.8k
    return WEBP_PRESET_DEFAULT;
397
0
  case VIPS_FOREIGN_WEBP_PRESET_PICTURE:
398
0
    return WEBP_PRESET_PICTURE;
399
0
  case VIPS_FOREIGN_WEBP_PRESET_PHOTO:
400
0
    return WEBP_PRESET_PHOTO;
401
0
  case VIPS_FOREIGN_WEBP_PRESET_DRAWING:
402
0
    return WEBP_PRESET_DRAWING;
403
0
  case VIPS_FOREIGN_WEBP_PRESET_ICON:
404
0
    return WEBP_PRESET_ICON;
405
0
  case VIPS_FOREIGN_WEBP_PRESET_TEXT:
406
0
    return WEBP_PRESET_TEXT;
407
408
0
  default:
409
0
    g_assert_not_reached();
410
29.8k
  }
411
412
  /* Keep -Wall happy.
413
   */
414
0
  return -1;
415
29.8k
}
416
417
static void
418
vips_webp_set_count(VipsForeignSaveWebp *webp, int loop_count)
419
3.25k
{
420
3.25k
  uint32_t features;
421
422
3.25k
  if (WebPMuxGetFeatures(webp->mux, &features) == WEBP_MUX_OK &&
423
3.25k
    (features & ANIMATION_FLAG)) {
424
712
    WebPMuxAnimParams params;
425
426
712
    if (WebPMuxGetAnimationParams(webp->mux, &params) == WEBP_MUX_OK) {
427
712
      params.loop_count = loop_count;
428
712
      WebPMuxSetAnimationParams(webp->mux, &params);
429
712
    }
430
712
  }
431
3.25k
}
432
433
static int
434
vips_webp_set_chunk(VipsForeignSaveWebp *webp,
435
  const char *webp_name, const void *data, size_t length)
436
14.4k
{
437
14.4k
  WebPData chunk;
438
439
14.4k
  chunk.bytes = data;
440
14.4k
  chunk.size = length;
441
442
14.4k
  if (WebPMuxSetChunk(webp->mux, webp_name, &chunk, 1) != WEBP_MUX_OK) {
443
0
    vips_error("webpsave", "%s", _("chunk add error"));
444
0
    return -1;
445
0
  }
446
447
14.4k
  return 0;
448
14.4k
}
449
450
static int
451
vips_webp_add_original_meta(VipsForeignSaveWebp *webp)
452
14.4k
{
453
14.4k
  VipsForeignSave *save = (VipsForeignSave *) webp;
454
455
57.6k
  for (int i = 0; i < vips__n_webp_names; i++) {
456
43.2k
    const char *vips_name = vips__webp_names[i].vips;
457
43.2k
    const char *webp_name = vips__webp_names[i].webp;
458
459
43.2k
    if (g_str_equal(vips_name, VIPS_META_ICC_NAME))
460
14.4k
      continue;
461
462
28.8k
    if (vips_image_get_typeof(save->ready, vips_name)) {
463
14.4k
      const void *data;
464
14.4k
      size_t length;
465
466
14.4k
      if (vips_image_get_blob(save->ready, vips_name, &data, &length) ||
467
14.4k
        vips_webp_set_chunk(webp, webp_name, data, length))
468
0
        return -1;
469
14.4k
    }
470
28.8k
  }
471
472
14.4k
  return 0;
473
14.4k
}
474
475
static const char *
476
vips_webp_get_webp_name(const char *vips_name)
477
0
{
478
0
  for (int i = 0; i < vips__n_webp_names; i++)
479
0
    if (g_str_equal(vips_name, vips__webp_names[i].vips))
480
0
      return vips__webp_names[i].webp;
481
482
0
  return "";
483
0
}
484
485
static int
486
vips_webp_add_icc(VipsForeignSaveWebp *webp,
487
  const void *profile, size_t length)
488
0
{
489
0
  const char *webp_name = vips_webp_get_webp_name(VIPS_META_ICC_NAME);
490
491
0
  if (vips_webp_set_chunk(webp, webp_name, profile, length))
492
0
    return -1;
493
494
0
  return 0;
495
0
}
496
497
static int
498
vips_webp_add_custom_icc(VipsForeignSaveWebp *webp, const char *profile)
499
0
{
500
0
  VipsBlob *blob;
501
502
0
  if (vips_profile_load(profile, &blob, NULL))
503
0
    return -1;
504
505
0
  if (blob) {
506
0
    size_t length;
507
0
    const void *data = vips_blob_get(blob, &length);
508
509
0
    if (vips_webp_add_icc(webp, data, length)) {
510
0
      vips_area_unref((VipsArea *) blob);
511
0
      return -1;
512
0
    }
513
514
0
    vips_area_unref((VipsArea *) blob);
515
0
  }
516
517
0
  return 0;
518
0
}
519
520
static int
521
vips_webp_add_original_icc(VipsForeignSaveWebp *webp)
522
0
{
523
0
  VipsForeignSave *save = (VipsForeignSave *) webp;
524
525
0
  const void *data;
526
0
  size_t length;
527
528
0
  if (vips_image_get_blob(save->ready, VIPS_META_ICC_NAME, &data, &length))
529
0
    return -1;
530
531
0
  vips_webp_add_icc(webp, data, length);
532
533
0
  return 0;
534
0
}
535
536
static int
537
vips_webp_add_metadata(VipsForeignSaveWebp *webp)
538
14.4k
{
539
14.4k
  VipsForeignSave *save = (VipsForeignSave *) webp;
540
541
14.4k
  WebPData data;
542
543
14.4k
  data.bytes = webp->memory_writer.mem;
544
14.4k
  data.size = webp->memory_writer.size;
545
546
  /* Parse what we have.
547
   */
548
14.4k
  if (!(webp->mux = WebPMuxCreate(&data, 1))) {
549
0
    vips_error("webpsave", "%s", _("mux error"));
550
0
    return -1;
551
0
  }
552
553
14.4k
  if (vips_image_get_typeof(save->ready, "loop")) {
554
3.25k
    int loop;
555
556
3.25k
    if (vips_image_get_int(save->ready, "loop", &loop))
557
0
      return -1;
558
559
3.25k
    vips_webp_set_count(webp, loop);
560
3.25k
  }
561
11.1k
  else if (vips_image_get_typeof(save->ready, "gif-loop")) {
562
    /* DEPRECATED "gif-loop"
563
     */
564
0
    int gif_loop;
565
566
0
    if (vips_image_get_int(save->ready, "gif-loop", &gif_loop))
567
0
      return -1;
568
569
0
    vips_webp_set_count(webp, gif_loop == 0 ? 0 : gif_loop + 1);
570
0
  }
571
572
  /* Metadata
573
   */
574
14.4k
  if (vips_webp_add_original_meta(webp))
575
0
    return -1;
576
577
  /* A profile supplied as an argument overrides an embedded
578
   * profile.
579
   */
580
14.4k
  if (save->profile) {
581
0
    if (vips_webp_add_custom_icc(webp, save->profile))
582
0
      return -1;
583
0
  }
584
14.4k
  else if (vips_image_get_typeof(save->ready, VIPS_META_ICC_NAME)) {
585
0
    if (vips_webp_add_original_icc(webp))
586
0
      return -1;
587
0
  }
588
589
14.4k
  if (WebPMuxAssemble(webp->mux, &data) != WEBP_MUX_OK) {
590
0
    vips_error("webpsave", "%s", _("mux error"));
591
0
    return -1;
592
0
  }
593
594
  /* Free old stuff, reinit with new stuff.
595
   */
596
14.4k
  WebPMemoryWriterClear(&webp->memory_writer);
597
14.4k
  webp->memory_writer.mem = (uint8_t *) data.bytes;
598
14.4k
  webp->memory_writer.size = data.size;
599
600
14.4k
  return 0;
601
14.4k
}
602
603
static int
604
vips_foreign_save_webp_init_config(VipsForeignSaveWebp *webp)
605
30.3k
{
606
  /* Init WebP config.
607
   */
608
30.3k
  WebPMemoryWriterInit(&webp->memory_writer);
609
30.3k
  if (!WebPConfigInit(&webp->config)) {
610
0
    vips_error("webpsave", "%s", _("config version error"));
611
0
    return -1;
612
0
  }
613
614
  /* These presets are only for lossy compression. There seems to be
615
   * separate API for lossless or near-lossless, see
616
   * WebPConfigLosslessPreset().
617
   */
618
30.3k
  if (!(webp->lossless || webp->near_lossless) &&
619
30.3k
    !WebPConfigPreset(&webp->config, get_preset(webp->preset), webp->Q)) {
620
0
    vips_error("webpsave", "%s", _("config version error"));
621
0
    return -1;
622
0
  }
623
624
30.3k
  webp->config.lossless = webp->lossless || webp->near_lossless;
625
30.3k
  webp->config.alpha_quality = webp->alpha_q;
626
30.3k
  webp->config.method = webp->effort;
627
30.3k
  webp->config.target_size = webp->target_size;
628
30.3k
  webp->config.pass = webp->passes;
629
630
30.3k
  if (webp->lossless)
631
511
    webp->config.quality = webp->Q;
632
30.3k
  if (webp->near_lossless)
633
373
    webp->config.near_lossless = webp->Q;
634
30.3k
  if (webp->smart_subsample)
635
93
    webp->config.use_sharp_yuv = 1;
636
30.3k
  if (webp->smart_deblock)
637
0
    webp->config.autofilter = 1;
638
639
30.3k
  if (!WebPValidateConfig(&webp->config)) {
640
0
    vips_error("webpsave", "%s", _("invalid configuration"));
641
0
    return -1;
642
0
  }
643
644
30.3k
  return 0;
645
30.3k
}
646
647
static int
648
vips_foreign_save_webp_init_anim_enc(VipsForeignSaveWebp *webp)
649
1.31k
{
650
1.31k
  VipsForeignSave *save = (VipsForeignSave *) webp;
651
1.31k
  int page_height = vips_image_get_page_height(save->ready);
652
653
1.31k
  WebPAnimEncoderOptions anim_config;
654
655
  /* Init config for animated write
656
   */
657
1.31k
  if (!WebPAnimEncoderOptionsInit(&anim_config)) {
658
0
    vips_error("webpsave", "%s", _("config version error"));
659
0
    return -1;
660
0
  }
661
662
1.31k
  anim_config.minimize_size = webp->min_size;
663
1.31k
  anim_config.allow_mixed = webp->mixed;
664
1.31k
  anim_config.kmin = webp->kmin;
665
1.31k
  anim_config.kmax = webp->kmax;
666
1.31k
  webp->enc = WebPAnimEncoderNew(save->ready->Xsize, page_height,
667
1.31k
    &anim_config);
668
1.31k
  if (!webp->enc) {
669
0
    vips_error("webpsave", "%s", _("unable to init animation"));
670
0
    return -1;
671
0
  }
672
673
  /* Get delay array
674
   *
675
   * There might just be the old gif-delay field. This is centiseconds.
676
   * New images have an array of ints giving millisecond durations.
677
   */
678
1.31k
  webp->gif_delay = 10;
679
1.31k
  if (vips_image_get_typeof(save->ready, "gif-delay") &&
680
1.31k
    vips_image_get_int(save->ready, "gif-delay", &webp->gif_delay))
681
0
    return -1;
682
683
1.31k
  webp->delay = NULL;
684
1.31k
  if (vips_image_get_typeof(save->ready, "delay") &&
685
1.31k
    vips_image_get_array_int(save->ready, "delay",
686
1.30k
      &webp->delay, &webp->delay_length))
687
0
    return -1;
688
689
1.31k
  return 0;
690
1.31k
}
691
692
static int
693
vips_foreign_save_webp_finish_anim(VipsForeignSaveWebp *webp)
694
1.30k
{
695
1.30k
  WebPData webp_data;
696
697
  /* Closes animated encoder and adds last frame delay.
698
   */
699
1.30k
  if (!WebPAnimEncoderAdd(webp->enc, NULL, webp->timestamp_ms, NULL)) {
700
0
    vips_error("webpsave", "%s", _("anim close error"));
701
0
    return -1;
702
0
  }
703
704
1.30k
  if (!WebPAnimEncoderAssemble(webp->enc, &webp_data)) {
705
0
    vips_error("webpsave", "%s", _("anim build error"));
706
0
    return -1;
707
0
  }
708
709
  /* Terrible. This will only work if the output buffer is currently
710
   * empty.
711
   */
712
1.30k
  if (webp->memory_writer.mem != NULL) {
713
0
    vips_error("webpsave", "%s", _("internal error"));
714
0
    return -1;
715
0
  }
716
717
1.30k
  webp->memory_writer.mem = (uint8_t *) webp_data.bytes;
718
1.30k
  webp->memory_writer.size = webp_data.size;
719
720
1.30k
  return 0;
721
1.30k
}
722
723
static int
724
vips_foreign_save_webp_build(VipsObject *object)
725
30.3k
{
726
30.3k
  VipsForeignSave *save = (VipsForeignSave *) object;
727
30.3k
  VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object;
728
729
30.3k
  int page_height;
730
731
30.3k
  if (VIPS_OBJECT_CLASS(vips_foreign_save_webp_parent_class)->build(object))
732
2
    return -1;
733
734
30.3k
  page_height = vips_image_get_page_height(save->ready);
735
30.3k
  if (save->ready->Xsize > 16383 || page_height > 16383) {
736
0
    vips_error("webpsave", _("image too large"));
737
0
    return -1;
738
0
  }
739
740
  /* RGB(A) frame as a contiguous buffer.
741
   */
742
30.3k
  size_t frame_size =
743
30.3k
    (size_t) save->ready->Bands * save->ready->Xsize * page_height;
744
30.3k
  webp->frame_bytes = g_try_malloc(frame_size);
745
30.3k
  if (webp->frame_bytes == NULL) {
746
0
    vips_error("webpsave", _("failed to allocate %zu bytes"), frame_size);
747
0
    return -1;
748
0
  }
749
750
30.3k
  if (!vips_object_argument_isset(object, "passes") &&
751
30.3k
    vips_object_argument_isset(object, "target_size"))
752
0
    webp->passes = 3;
753
754
  /* Init generic WebP config
755
   */
756
30.3k
  if (vips_foreign_save_webp_init_config(webp))
757
0
    return -1;
758
759
  /* Determine the write mode (single image or animated write)
760
   */
761
30.3k
  webp->mode = VIPS_FOREIGN_SAVE_WEBP_MODE_SINGLE;
762
30.3k
  if (page_height != save->ready->Ysize)
763
1.31k
    webp->mode = VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM;
764
765
  /* Init config for animated write (if necessary)
766
   */
767
30.3k
  if (webp->mode == VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM)
768
1.31k
    if (vips_foreign_save_webp_init_anim_enc(webp))
769
0
      return -1;
770
771
30.3k
  if (vips_sink_disc(save->ready, vips_foreign_save_webp_sink_disc, webp))
772
15.9k
    return -1;
773
774
  /* Finish animated write
775
   */
776
14.4k
  if (webp->mode == VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM)
777
1.30k
    if (vips_foreign_save_webp_finish_anim(webp))
778
0
      return -1;
779
780
14.4k
  if (vips_webp_add_metadata(webp))
781
0
    return -1;
782
783
14.4k
  if (vips_target_write(webp->target,
784
14.4k
      webp->memory_writer.mem, webp->memory_writer.size))
785
0
    return -1;
786
787
14.4k
  if (vips_target_end(webp->target))
788
0
    return -1;
789
790
14.4k
  vips_foreign_save_webp_unset(webp);
791
792
14.4k
  return 0;
793
14.4k
}
794
795
static const char *vips__save_webp_suffs[] = { ".webp", NULL };
796
797
#define UC VIPS_FORMAT_UCHAR
798
799
/* Type promotion for save ... just always go to uchar.
800
 */
801
static VipsBandFormat bandfmt_webp[10] = {
802
  /* Band format:  UC  C   US  S   UI  I   F   X   D   DX */
803
  /* Promotion: */ UC, UC, UC, UC, UC, UC, UC, UC, UC, UC
804
};
805
806
static void
807
vips_foreign_save_webp_class_init(VipsForeignSaveWebpClass *class)
808
17
{
809
17
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
810
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
811
17
  VipsForeignClass *foreign_class = (VipsForeignClass *) class;
812
17
  VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class;
813
814
17
  gobject_class->dispose = vips_foreign_save_webp_dispose;
815
17
  gobject_class->set_property = vips_object_set_property;
816
17
  gobject_class->get_property = vips_object_get_property;
817
818
17
  object_class->nickname = "webpsave_base";
819
17
  object_class->description = _("save as WebP");
820
17
  object_class->build = vips_foreign_save_webp_build;
821
822
17
  foreign_class->suffs = vips__save_webp_suffs;
823
824
17
  save_class->saveable =
825
17
    VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA;
826
17
  save_class->format_table = bandfmt_webp;
827
828
17
  VIPS_ARG_INT(class, "Q", 10,
829
17
    _("Q"),
830
17
    _("Q factor"),
831
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
832
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, Q),
833
17
    0, 100, 75);
834
835
17
  VIPS_ARG_BOOL(class, "lossless", 11,
836
17
    _("Lossless"),
837
17
    _("Enable lossless compression"),
838
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
839
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, lossless),
840
17
    FALSE);
841
842
17
  VIPS_ARG_ENUM(class, "preset", 12,
843
17
    _("Preset"),
844
17
    _("Preset for lossy compression"),
845
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
846
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, preset),
847
17
    VIPS_TYPE_FOREIGN_WEBP_PRESET,
848
17
    VIPS_FOREIGN_WEBP_PRESET_DEFAULT);
849
850
17
  VIPS_ARG_BOOL(class, "smart_subsample", 13,
851
17
    _("Smart subsampling"),
852
17
    _("Enable high quality chroma subsampling"),
853
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
854
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, smart_subsample),
855
17
    FALSE);
856
857
17
  VIPS_ARG_BOOL(class, "near_lossless", 14,
858
17
    _("Near lossless"),
859
17
    _("Enable preprocessing in lossless mode (uses Q)"),
860
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
861
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, near_lossless),
862
17
    FALSE);
863
864
17
  VIPS_ARG_INT(class, "alpha_q", 15,
865
17
    _("Alpha quality"),
866
17
    _("Change alpha plane fidelity for lossy compression"),
867
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
868
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, alpha_q),
869
17
    0, 100, 100);
870
871
17
  VIPS_ARG_BOOL(class, "min_size", 16,
872
17
    _("Minimise size"),
873
17
    _("Optimise for minimum size"),
874
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
875
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, min_size),
876
17
    FALSE);
877
878
17
  VIPS_ARG_INT(class, "kmin", 17,
879
17
    _("Minimum keyframe spacing"),
880
17
    _("Minimum number of frames between key frames"),
881
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
882
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, kmin),
883
17
    0, INT_MAX, INT_MAX - 1);
884
885
17
  VIPS_ARG_INT(class, "kmax", 18,
886
17
    _("Maximum keyframe spacing"),
887
17
    _("Maximum number of frames between key frames"),
888
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
889
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, kmax),
890
17
    0, INT_MAX, INT_MAX);
891
892
17
  VIPS_ARG_INT(class, "effort", 19,
893
17
    _("Effort"),
894
17
    _("Level of CPU effort to reduce file size"),
895
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
896
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, effort),
897
17
    0, 6, 4);
898
899
17
  VIPS_ARG_INT(class, "target_size", 20,
900
17
    _("Target size"),
901
17
    _("Desired target size in bytes"),
902
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
903
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, target_size),
904
17
    0, INT_MAX, 0);
905
906
17
  VIPS_ARG_INT(class, "passes", 23,
907
17
    _("Passes"),
908
17
    _("Number of entropy-analysis passes (in [1..10])"),
909
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
910
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, passes),
911
17
    1, 10, 1);
912
913
17
  VIPS_ARG_INT(class, "reduction_effort", 21,
914
17
    _("Reduction effort"),
915
17
    _("Level of CPU effort to reduce file size"),
916
17
    VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
917
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, effort),
918
17
    0, 6, 4);
919
920
17
  VIPS_ARG_BOOL(class, "mixed", 22,
921
17
    _("Mixed encoding"),
922
17
    _("Allow mixed encoding (might reduce file size)"),
923
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
924
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, mixed),
925
17
    FALSE);
926
927
17
  VIPS_ARG_BOOL(class, "smart_deblock", 23,
928
17
    _("Smart deblocking"),
929
17
    _("Enable auto-adjusting of the deblocking filter"),
930
17
    VIPS_ARGUMENT_OPTIONAL_INPUT,
931
17
    G_STRUCT_OFFSET(VipsForeignSaveWebp, smart_deblock),
932
17
    FALSE);
933
17
}
934
935
static void
936
vips_foreign_save_webp_init(VipsForeignSaveWebp *webp)
937
30.4k
{
938
30.4k
  webp->Q = 75;
939
30.4k
  webp->alpha_q = 100;
940
30.4k
  webp->effort = 4;
941
30.4k
  webp->passes = 1;
942
943
  /* ie. keyframes disabled by default.
944
   */
945
30.4k
  webp->kmin = INT_MAX - 1;
946
30.4k
  webp->kmax = INT_MAX;
947
30.4k
}
948
949
typedef struct _VipsForeignSaveWebpTarget {
950
  VipsForeignSaveWebp parent_object;
951
952
  VipsTarget *target;
953
} VipsForeignSaveWebpTarget;
954
955
typedef VipsForeignSaveWebpClass VipsForeignSaveWebpTargetClass;
956
957
G_DEFINE_TYPE(VipsForeignSaveWebpTarget, vips_foreign_save_webp_target,
958
  vips_foreign_save_webp_get_type());
959
960
static int
961
vips_foreign_save_webp_target_build(VipsObject *object)
962
30.3k
{
963
30.3k
  VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object;
964
30.3k
  VipsForeignSaveWebpTarget *target = (VipsForeignSaveWebpTarget *) object;
965
966
30.3k
  webp->target = target->target;
967
30.3k
  g_object_ref(webp->target);
968
969
30.3k
  return VIPS_OBJECT_CLASS(vips_foreign_save_webp_target_parent_class)
970
30.3k
    ->build(object);
971
30.3k
}
972
973
static void
974
vips_foreign_save_webp_target_class_init(
975
  VipsForeignSaveWebpTargetClass *class)
976
17
{
977
17
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
978
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
979
980
17
  gobject_class->set_property = vips_object_set_property;
981
17
  gobject_class->get_property = vips_object_get_property;
982
983
17
  object_class->nickname = "webpsave_target";
984
17
  object_class->build = vips_foreign_save_webp_target_build;
985
986
17
  VIPS_ARG_OBJECT(class, "target", 1,
987
17
    _("Target"),
988
17
    _("Target to save to"),
989
17
    VIPS_ARGUMENT_REQUIRED_INPUT,
990
17
    G_STRUCT_OFFSET(VipsForeignSaveWebpTarget, target),
991
17
    VIPS_TYPE_TARGET);
992
17
}
993
994
static void
995
vips_foreign_save_webp_target_init(VipsForeignSaveWebpTarget *target)
996
30.4k
{
997
30.4k
}
998
999
typedef struct _VipsForeignSaveWebpFile {
1000
  VipsForeignSaveWebp parent_object;
1001
  char *filename;
1002
} VipsForeignSaveWebpFile;
1003
1004
typedef VipsForeignSaveWebpClass VipsForeignSaveWebpFileClass;
1005
1006
G_DEFINE_TYPE(VipsForeignSaveWebpFile, vips_foreign_save_webp_file,
1007
  vips_foreign_save_webp_get_type());
1008
1009
static int
1010
vips_foreign_save_webp_file_build(VipsObject *object)
1011
0
{
1012
0
  VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object;
1013
0
  VipsForeignSaveWebpFile *file = (VipsForeignSaveWebpFile *) object;
1014
1015
0
  if (!(webp->target = vips_target_new_to_file(file->filename)))
1016
0
    return -1;
1017
1018
0
  return VIPS_OBJECT_CLASS(vips_foreign_save_webp_file_parent_class)
1019
0
    ->build(object);
1020
0
}
1021
1022
static void
1023
vips_foreign_save_webp_file_class_init(VipsForeignSaveWebpFileClass *class)
1024
17
{
1025
17
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
1026
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
1027
1028
17
  gobject_class->set_property = vips_object_set_property;
1029
17
  gobject_class->get_property = vips_object_get_property;
1030
1031
17
  object_class->nickname = "webpsave";
1032
17
  object_class->build = vips_foreign_save_webp_file_build;
1033
1034
17
  VIPS_ARG_STRING(class, "filename", 1,
1035
17
    _("Filename"),
1036
17
    _("Filename to save to"),
1037
17
    VIPS_ARGUMENT_REQUIRED_INPUT,
1038
17
    G_STRUCT_OFFSET(VipsForeignSaveWebpFile, filename),
1039
17
    NULL);
1040
17
}
1041
1042
static void
1043
vips_foreign_save_webp_file_init(VipsForeignSaveWebpFile *file)
1044
0
{
1045
0
}
1046
1047
typedef struct _VipsForeignSaveWebpBuffer {
1048
  VipsForeignSaveWebp parent_object;
1049
  VipsArea *buf;
1050
} VipsForeignSaveWebpBuffer;
1051
1052
typedef VipsForeignSaveWebpClass VipsForeignSaveWebpBufferClass;
1053
1054
G_DEFINE_TYPE(VipsForeignSaveWebpBuffer, vips_foreign_save_webp_buffer,
1055
  vips_foreign_save_webp_get_type());
1056
1057
static int
1058
vips_foreign_save_webp_buffer_build(VipsObject *object)
1059
0
{
1060
0
  VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object;
1061
0
  VipsForeignSaveWebpBuffer *buffer = (VipsForeignSaveWebpBuffer *) object;
1062
1063
0
  VipsBlob *blob;
1064
1065
0
  if (!(webp->target = vips_target_new_to_memory()))
1066
0
    return -1;
1067
1068
0
  if (VIPS_OBJECT_CLASS(vips_foreign_save_webp_buffer_parent_class)
1069
0
      ->build(object))
1070
0
    return -1;
1071
1072
0
  g_object_get(webp->target, "blob", &blob, NULL);
1073
0
  g_object_set(buffer, "buffer", blob, NULL);
1074
0
  vips_area_unref(VIPS_AREA(blob));
1075
1076
0
  return 0;
1077
0
}
1078
1079
static void
1080
vips_foreign_save_webp_buffer_class_init(
1081
  VipsForeignSaveWebpBufferClass *class)
1082
17
{
1083
17
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
1084
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
1085
1086
17
  gobject_class->set_property = vips_object_set_property;
1087
17
  gobject_class->get_property = vips_object_get_property;
1088
1089
17
  object_class->nickname = "webpsave_buffer";
1090
17
  object_class->build = vips_foreign_save_webp_buffer_build;
1091
1092
17
  VIPS_ARG_BOXED(class, "buffer", 1,
1093
17
    _("Buffer"),
1094
17
    _("Buffer to save to"),
1095
17
    VIPS_ARGUMENT_REQUIRED_OUTPUT,
1096
17
    G_STRUCT_OFFSET(VipsForeignSaveWebpBuffer, buf),
1097
17
    VIPS_TYPE_BLOB);
1098
17
}
1099
1100
static void
1101
vips_foreign_save_webp_buffer_init(VipsForeignSaveWebpBuffer *buffer)
1102
0
{
1103
0
}
1104
1105
typedef struct _VipsForeignSaveWebpMime {
1106
  VipsForeignSaveWebp parent_object;
1107
1108
} VipsForeignSaveWebpMime;
1109
1110
typedef VipsForeignSaveWebpClass VipsForeignSaveWebpMimeClass;
1111
1112
G_DEFINE_TYPE(VipsForeignSaveWebpMime, vips_foreign_save_webp_mime,
1113
  vips_foreign_save_webp_get_type());
1114
1115
static int
1116
vips_foreign_save_webp_mime_build(VipsObject *object)
1117
0
{
1118
0
  VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object;
1119
1120
0
  VipsBlob *blob;
1121
0
  void *data;
1122
0
  size_t len;
1123
1124
0
  if (!(webp->target = vips_target_new_to_memory()))
1125
0
    return -1;
1126
1127
0
  if (VIPS_OBJECT_CLASS(vips_foreign_save_webp_mime_parent_class)
1128
0
      ->build(object))
1129
0
    return -1;
1130
1131
0
  g_object_get(webp->target, "blob", &blob, NULL);
1132
0
  data = VIPS_AREA(blob)->data;
1133
0
  len = VIPS_AREA(blob)->length;
1134
0
  vips_area_unref(VIPS_AREA(blob));
1135
1136
0
  printf("Content-length: %zu\r\n", len);
1137
0
  printf("Content-type: image/webp\r\n");
1138
0
  printf("\r\n");
1139
0
  (void) fwrite(data, sizeof(char), len, stdout);
1140
0
  fflush(stdout);
1141
1142
0
  VIPS_UNREF(webp->target);
1143
1144
0
  return 0;
1145
0
}
1146
1147
static void
1148
vips_foreign_save_webp_mime_class_init(VipsForeignSaveWebpMimeClass *class)
1149
17
{
1150
17
  VipsObjectClass *object_class = (VipsObjectClass *) class;
1151
1152
17
  object_class->nickname = "webpsave_mime";
1153
17
  object_class->description = _("save image to webp mime");
1154
17
  object_class->build = vips_foreign_save_webp_mime_build;
1155
17
}
1156
1157
static void
1158
vips_foreign_save_webp_mime_init(VipsForeignSaveWebpMime *mime)
1159
0
{
1160
0
}
1161
1162
#endif /*HAVE_LIBWEBP*/
1163
1164
/**
1165
 * vips_webpsave: (method)
1166
 * @in: image to save
1167
 * @filename: file to write to
1168
 * @...: `NULL`-terminated list of optional named arguments
1169
 *
1170
 * Write an image to a file in WebP format.
1171
 *
1172
 * By default, images are saved in lossy format, with
1173
 * @Q giving the WebP quality factor. It has the range 0 - 100, with the
1174
 * default 75.
1175
 *
1176
 * Use @preset to hint the image type to the lossy compressor. The default is
1177
 * [enum@Vips.ForeignWebpPreset.DEFAULT].
1178
 *
1179
 * Set @smart_subsample to enable high quality chroma subsampling.
1180
 *
1181
 * Set @smart_deblock to enable auto-adjusting of the deblocking filter. This
1182
 * can improve image quality, especially on low-contrast edges, but encoding
1183
 * can take significantly longer.
1184
 *
1185
 * Use @alpha_q to set the quality for the alpha channel in lossy mode. It has
1186
 * the range 1 - 100, with the default 100.
1187
 *
1188
 * Use @effort to control how much CPU time to spend attempting to
1189
 * reduce file size. A higher value means more effort and therefore CPU time
1190
 * should be spent. It has the range 0-6 and a default value of 4.
1191
 *
1192
 * Use @target_size to set the desired target size in bytes.
1193
 *
1194
 * Use @passes to set the number of entropy-analysis passes, by default 1,
1195
 * unless @target_size is set, in which case the default is 3. It is not
1196
 * recommended to set @passes unless you set @target_size. Doing so will
1197
 * result in longer encoding times for no benefit.
1198
 *
1199
 * Set @lossless to use lossless compression, or combine @near_lossless
1200
 * with @Q 80, 60, 40 or 20 to apply increasing amounts of preprocessing
1201
 * which improves the near-lossless compression ratio by up to 50%.
1202
 *
1203
 * For animated webp output, @min_size will try to optimize for minimum size.
1204
 *
1205
 * For animated webp output, @kmax sets the maximum number of frames between
1206
 * keyframes. Setting 0 means only keyframes. @kmin sets the minimum number of
1207
 * frames between frames. Setting 0 means no keyframes. By default, keyframes
1208
 * are disabled.
1209
 *
1210
 * For animated webp output, @mixed tries to improve the file size by mixing
1211
 * both lossy and lossless encoding.
1212
 *
1213
 * Use the metadata items `loop` and `delay` to set the number of
1214
 * loops for the animation and the frame delays.
1215
 *
1216
 * ::: tip "Optional arguments"
1217
 *     * @Q: `gint`, quality factor
1218
 *     * @lossless: `gboolean`, enables lossless compression
1219
 *     * @preset: [enum@ForeignWebpPreset], choose lossy compression preset
1220
 *     * @smart_subsample: `gboolean`, enables high quality chroma subsampling
1221
 *     * @smart_deblock: `gboolean`, enables auto-adjusting of the deblocking
1222
 *       filter
1223
 *     * @near_lossless: `gboolean`, preprocess in lossless mode (controlled
1224
 *       by Q)
1225
 *     * @alpha_q: `gint`, set alpha quality in lossless mode
1226
 *     * @effort: `gint`, level of CPU effort to reduce file size
1227
 *     * @target_size: `gint`, desired target size in bytes
1228
 *     * @passes: `gint`, number of entropy-analysis passes
1229
 *     * @min_size: `gboolean`, minimise size
1230
 *     * @mixed: `gboolean`, allow both lossy and lossless encoding
1231
 *     * @kmin: `gint`, minimum number of frames between keyframes
1232
 *     * @kmax: `gint`, maximum number of frames between keyframes
1233
 *
1234
 * ::: seealso
1235
 *     [ctor@Image.webpload], [method@Image.write_to_file].
1236
 *
1237
 * Returns: 0 on success, -1 on error.
1238
 */
1239
int
1240
vips_webpsave(VipsImage *in, const char *filename, ...)
1241
0
{
1242
0
  va_list ap;
1243
0
  int result;
1244
1245
0
  va_start(ap, filename);
1246
0
  result = vips_call_split("webpsave", ap, in, filename);
1247
0
  va_end(ap);
1248
1249
0
  return result;
1250
0
}
1251
1252
/**
1253
 * vips_webpsave_buffer: (method)
1254
 * @in: image to save
1255
 * @buf: (out) (array length=len) (element-type guint8): return output buffer here
1256
 * @len: return output length here
1257
 * @...: `NULL`-terminated list of optional named arguments
1258
 *
1259
 * As [method@Image.webpsave], but save to a memory buffer.
1260
 *
1261
 * The address of the buffer is returned in @buf, the length of the buffer in
1262
 * @len. You are responsible for freeing the buffer with [func@GLib.free] when you
1263
 * are done with it.
1264
 *
1265
 * ::: tip "Optional arguments"
1266
 *     * @Q: `gint`, quality factor
1267
 *     * @lossless: `gboolean`, enables lossless compression
1268
 *     * @preset: [enum@ForeignWebpPreset], choose lossy compression preset
1269
 *     * @smart_subsample: `gboolean`, enables high quality chroma subsampling
1270
 *     * @smart_deblock: `gboolean`, enables auto-adjusting of the deblocking
1271
 *       filter
1272
 *     * @near_lossless: `gboolean`, preprocess in lossless mode (controlled
1273
 *       by Q)
1274
 *     * @alpha_q: `gint`, set alpha quality in lossless mode
1275
 *     * @effort: `gint`, level of CPU effort to reduce file size
1276
 *     * @target_size: `gint`, desired target size in bytes
1277
 *     * @passes: `gint`, number of entropy-analysis passes
1278
 *     * @min_size: `gboolean`, minimise size
1279
 *     * @mixed: `gboolean`, allow both lossy and lossless encoding
1280
 *     * @kmin: `gint`, minimum number of frames between keyframes
1281
 *     * @kmax: `gint`, maximum number of frames between keyframes
1282
 *
1283
 * ::: seealso
1284
 *     [method@Image.webpsave].
1285
 *
1286
 * Returns: 0 on success, -1 on error.
1287
 */
1288
int
1289
vips_webpsave_buffer(VipsImage *in, void **buf, size_t *len, ...)
1290
0
{
1291
0
  va_list ap;
1292
0
  VipsArea *area;
1293
0
  int result;
1294
1295
0
  area = NULL;
1296
1297
0
  va_start(ap, len);
1298
0
  result = vips_call_split("webpsave_buffer", ap, in, &area);
1299
0
  va_end(ap);
1300
1301
0
  if (!result &&
1302
0
    area) {
1303
0
    if (buf) {
1304
0
      *buf = area->data;
1305
0
      area->free_fn = NULL;
1306
0
    }
1307
0
    if (len)
1308
0
      *len = area->length;
1309
1310
0
    vips_area_unref(area);
1311
0
  }
1312
1313
0
  return result;
1314
0
}
1315
1316
/**
1317
 * vips_webpsave_mime: (method)
1318
 * @in: image to save
1319
 * @...: `NULL`-terminated list of optional named arguments
1320
 *
1321
 * As [method@Image.webpsave], but save as a mime webp on stdout.
1322
 *
1323
 * ::: tip "Optional arguments"
1324
 *     * @Q: `gint`, quality factor
1325
 *     * @lossless: `gboolean`, enables lossless compression
1326
 *     * @preset: [enum@ForeignWebpPreset], choose lossy compression preset
1327
 *     * @smart_subsample: `gboolean`, enables high quality chroma subsampling
1328
 *     * @smart_deblock: `gboolean`, enables auto-adjusting of the deblocking
1329
 *       filter
1330
 *     * @near_lossless: `gboolean`, preprocess in lossless mode (controlled
1331
 *       by Q)
1332
 *     * @alpha_q: `gint`, set alpha quality in lossless mode
1333
 *     * @effort: `gint`, level of CPU effort to reduce file size
1334
 *     * @target_size: `gint`, desired target size in bytes
1335
 *     * @passes: `gint`, number of entropy-analysis passes
1336
 *     * @min_size: `gboolean`, minimise size
1337
 *     * @mixed: `gboolean`, allow both lossy and lossless encoding
1338
 *     * @kmin: `gint`, minimum number of frames between keyframes
1339
 *     * @kmax: `gint`, maximum number of frames between keyframes
1340
 *
1341
 * ::: seealso
1342
 *     [method@Image.webpsave], [method@Image.write_to_file].
1343
 *
1344
 * Returns: 0 on success, -1 on error.
1345
 */
1346
int
1347
vips_webpsave_mime(VipsImage *in, ...)
1348
0
{
1349
0
  va_list ap;
1350
0
  int result;
1351
1352
0
  va_start(ap, in);
1353
0
  result = vips_call_split("webpsave_mime", ap, in);
1354
0
  va_end(ap);
1355
1356
0
  return result;
1357
0
}
1358
1359
/**
1360
 * vips_webpsave_target: (method)
1361
 * @in: image to save
1362
 * @target: save image to this target
1363
 * @...: `NULL`-terminated list of optional named arguments
1364
 *
1365
 * As [method@Image.webpsave], but save to a target.
1366
 *
1367
 * ::: tip "Optional arguments"
1368
 *     * @Q: `gint`, quality factor
1369
 *     * @lossless: `gboolean`, enables lossless compression
1370
 *     * @preset: [enum@ForeignWebpPreset], choose lossy compression preset
1371
 *     * @smart_subsample: `gboolean`, enables high quality chroma subsampling
1372
 *     * @smart_deblock: `gboolean`, enables auto-adjusting of the deblocking
1373
 *       filter
1374
 *     * @near_lossless: `gboolean`, preprocess in lossless mode (controlled
1375
 *       by Q)
1376
 *     * @alpha_q: `gint`, set alpha quality in lossless mode
1377
 *     * @effort: `gint`, level of CPU effort to reduce file size
1378
 *     * @target_size: `gint`, desired target size in bytes
1379
 *     * @passes: `gint`, number of entropy-analysis passes
1380
 *     * @min_size: `gboolean`, minimise size
1381
 *     * @mixed: `gboolean`, allow both lossy and lossless encoding
1382
 *     * @kmin: `gint`, minimum number of frames between keyframes
1383
 *     * @kmax: `gint`, maximum number of frames between keyframes
1384
 *
1385
 * ::: seealso
1386
 *     [method@Image.webpsave].
1387
 *
1388
 * Returns: 0 on success, -1 on error.
1389
 */
1390
int
1391
vips_webpsave_target(VipsImage *in, VipsTarget *target, ...)
1392
0
{
1393
0
  va_list ap;
1394
0
  int result;
1395
1396
0
  va_start(ap, target);
1397
0
  result = vips_call_split("webpsave_target", ap, in, target);
1398
0
  va_end(ap);
1399
1400
0
  return result;
1401
0
}