/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, ¶ms) == WEBP_MUX_OK) { |
427 | 712 | params.loop_count = loop_count; |
428 | 712 | WebPMuxSetAnimationParams(webp->mux, ¶ms); |
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 | } |