Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
webp.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013 Dimitrios Psychogios.
4 Copyright (C) 2013-2014 Jérémy Rosen.
5 Copyright (C) 2013 Pascal de Bruijn.
6 Copyright (C) 2013-2017, 2019-2020 Tobias Ellinghaus.
7 Copyright (C) 2014, 2018, 2020-2021 Pascal Obry.
8 Copyright (C) 2014, 2016 Roman Lebedev.
9 Copyright (C) 2018 Edouard Gomez.
10 Copyright (C) 2019, 2022, 2025 Aurélien PIERRE.
11 Copyright (C) 2019 Sam Smith.
12 Copyright (C) 2020, 2022 Diederik Ter Rahe.
13 Copyright (C) 2020 Heiko Bauke.
14 Copyright (C) 2021 Hubert Kowalski.
15 Copyright (C) 2022 Martin Bařinka.
16 Copyright (C) 2022 Miloš Komarčević.
17 Copyright (C) 2023 Alynx Zhou.
18
19 darktable is free software: you can redistribute it and/or modify
20 it under the terms of the GNU General Public License as published by
21 the Free Software Foundation, either version 3 of the License, or
22 (at your option) any later version.
23
24 darktable is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 GNU General Public License for more details.
28
29 You should have received a copy of the GNU General Public License
30 along with darktable. If not, see <http://www.gnu.org/licenses/>.
31*/
32
33#ifdef HAVE_CONFIG_H
34#include "config.h"
35#endif
36#include "bauhaus/bauhaus.h"
37#include "common/darktable.h"
38#include "common/exif.h"
39#include "common/imageio.h"
41#include "control/conf.h"
43#include "gui/gtk.h"
44
45#include <stdio.h>
46#include <stdlib.h>
47
48#include <webp/encode.h>
49#include <webp/mux.h>
50
51DT_MODULE(2)
52
53typedef enum
54{
58
59
67
68
76
83
84#define _stringify(a) #a
85#define stringify(a) _stringify(a)
86
87static const char *const EncoderError[] = {
88 "ok",
89 "out_of_memory: out of memory allocating objects",
90 "bitstream_out_of_memory: out of memory re-allocating byte buffer",
91 "null_parameter: null parameter passed to function",
92 "invalid_configuration: configuration is invalid",
93 "bad_dimension: bad picture dimension. maximum width and height "
94 "allowed is " stringify(WEBP_MAX_DIMENSION) " pixels.",
95 "partition0_overflow: partition #0 is too big to fit 512k.\n"
96 "to reduce the size of this partition, try using less segments "
97 "with the -segments option, and eventually reduce the number of "
98 "header bits using -partition_limit. more details are available "
99 "in the manual (`man cwebp`)",
100 "partition_overflow: partition is too big to fit 16M",
101 "bad_write: picture writer returned an i/o error",
102 "file_too_big: file would be too big to fit in 4G",
103 "user_abort: encoding abort requested by user"
104};
105
106const char *get_error_str(int err)
107{
108 if (err < 0 || err >= sizeof(EncoderError)/sizeof(EncoderError[0]))
109 {
110 return "Unknown error. Consider open an issue to ansel to update the webp error list.";
111 }
112 return EncoderError[err];
113}
114
116{
117}
118
122
123int write_image(dt_imageio_module_data_t *webp, const char *filename, const void *in_tmp,
124 dt_colorspaces_color_profile_type_t over_type, const char *over_filename,
125 void *exif, int exif_len, int32_t imgid, int num, int total, struct dt_dev_pixelpipe_t *pipe,
126 const gboolean export_masks)
127{
128 int res = 1;
129 FILE *out = NULL;
130 uint8_t *buf = NULL;
131 WebPPicture pic;
132 WebPMemoryWriter writer;
133 WebPMemoryWriterInit(&writer);
134 WebPData icc_profile;
135 WebPDataInit(&icc_profile);
136 WebPData bitstream;
137 WebPDataInit(&bitstream);
138 WebPData assembled_data;
139 WebPDataInit(&assembled_data);
140 WebPMux *mux = WebPMuxNew();
141 WebPMuxError err;
142
143 dt_imageio_webp_t *webp_data = (dt_imageio_webp_t *)webp;
144
145 // Create, configure and validate a WebPConfig instance
146 WebPConfig config;
147 if(!WebPConfigPreset(&config, webp_data->hint, (float)webp_data->quality)) goto out;
148
149 // TODO(jinxos): expose more config options in the UI
150 config.lossless = webp_data->comp_type;
151 config.image_hint = webp_data->hint;
152 config.method = 6;
153
154 // these are to allow for large image export.
155 // TODO(jinxos): these values should be adjusted as needed and ideally determined at runtime.
156 config.segments = 4;
157 config.partition_limit = 70;
158 if(!WebPValidateConfig(&config))
159 {
160 fprintf(stderr, "[webp export] error validating encoder configuration\n");
161 goto out;
162 }
163
164 // embed ICC profile
165 cmsHPROFILE out_profile = dt_colorspaces_get_output_profile(imgid, &over_type, over_filename)->profile;
166 uint32_t len = 0;
167 cmsSaveProfileToMem(out_profile, NULL, &len);
168 if(len > 0)
169 {
170 buf = (uint8_t *)g_malloc(len);
171 if(buf)
172 {
173 cmsSaveProfileToMem(out_profile, buf, &len);
174 icc_profile.bytes = buf;
175 icc_profile.size = len;
176 err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, 0);
177 if(err != WEBP_MUX_OK)
178 {
179 fprintf(stderr, "[webp export] error adding ICC profile to WebP stream\n");
180 goto out;
181 }
182 }
183 else
184 {
185 fprintf(stderr, "[webp export] error allocating ICC profile buffer\n");
186 goto out;
187 }
188 }
189
190 // encode image data to memory and add to mux
191 if(!WebPPictureInit(&pic)) goto out;
192 pic.width = webp_data->global.width;
193 pic.height = webp_data->global.height;
194 pic.use_argb = !!(config.lossless);
195 pic.writer = WebPMemoryWrite;
196 pic.custom_ptr = &writer;
197
198 WebPPictureImportRGBX(&pic, (const uint8_t *)in_tmp, webp_data->global.width * 4);
199 if(!config.lossless)
200 {
201 // webp is more efficient at coding YUV images, as we go lossy
202 // let the encoder where best to spend its bits instead of forcing it
203 // to spend bits equally on RGB data that doesn't weight the same when
204 // considering the human visual system.
205 // use the slower, but better and sharper YUV conversion.
206 WebPPictureSharpARGBToYUVA(&pic);
207 }
208
209 if(!WebPEncode(&config, &pic))
210 {
211 fprintf(stderr, "[webp export] error during encoding (error: %d - %s)\n",
212 pic.error_code, get_error_str(pic.error_code));
213 goto out;
214 }
215
216 bitstream.bytes = writer.mem;
217 bitstream.size = writer.size;
218 err = WebPMuxSetImage(mux, &bitstream, 0);
219 if(err != WEBP_MUX_OK)
220 {
221 fprintf(stderr, "[webp export] error adding image to WebP stream\n");
222 goto out;
223 }
224
225 // finally write out assembled data to file
226 err = WebPMuxAssemble(mux, &assembled_data);
227 if(err != WEBP_MUX_OK)
228 {
229 fprintf(stderr, "[webp export] error assembling the WebP file\n");
230 goto out;
231 }
232
233 out = g_fopen(filename, "w+b");
234 if(IS_NULL_PTR(out))
235 {
236 fprintf(stderr, "[webp export] error creating file %s\n", filename);
237 goto out;
238 }
239 if(fwrite(assembled_data.bytes, assembled_data.size, 1, out) != 1)
240 {
241 fprintf(stderr, "[webp export] error writing %" G_GSIZE_FORMAT " bytes to file %s\n", assembled_data.size, filename);
242 goto out;
243 }
244
245 res = 0;
246
247out:
248 WebPPictureFree(&pic);
249 WebPMemoryWriterClear(&writer); // no need to WebPDataClear(&bitstream) as well
250 dt_free(buf); // instead of WebPDataClear(&icc_profile)
251 WebPDataClear(&assembled_data);
252 WebPMuxDelete(mux);
253 fclose(out);
254 if(!res && exif) dt_exif_write_blob(exif, exif_len, filename, 1);
255 return res;
256}
257
259{
260 return sizeof(dt_imageio_webp_t);
261}
262
263void *legacy_params(dt_imageio_module_format_t *self, const void *const old_params,
264 const size_t old_params_size, const int old_version, const int new_version,
265 size_t *new_size)
266{
267 if(old_version == 1 && new_version == 2)
268 {
269 typedef struct dt_imageio_webp_v1_t
270 {
271 int max_width, max_height;
272 int width, height;
273 char style[128];
274 int comp_type;
275 int quality;
276 int hint;
277 } dt_imageio_webp_v1_t;
278
279 dt_imageio_webp_v1_t *o = (dt_imageio_webp_v1_t *)old_params;
281
282 n->global.max_width = o->max_width;
283 n->global.max_height = o->max_height;
284 n->global.width = o->width;
285 n->global.height = o->height;
286 g_strlcpy(n->global.style, o->style, sizeof(o->style));
287 n->comp_type = o->comp_type;
288 n->quality = o->quality;
289 n->hint = o->hint;
290 *new_size = self->params_size(self);
291 return n;
292 }
293 return NULL;
294}
295
297{
299 d->comp_type = dt_conf_get_int("plugins/imageio/format/webp/comp_type");
300 if(d->comp_type == webp_lossy)
301 d->quality = dt_conf_get_int("plugins/imageio/format/webp/quality");
302 else
303 d->quality = 100;
304 d->hint = dt_conf_get_int("plugins/imageio/format/webp/hint");
305 return d;
306}
307
308int set_params(dt_imageio_module_format_t *self, const void *params, const int size)
309{
310 if(size != self->params_size(self)) return 1;
311 const dt_imageio_webp_t *d = (dt_imageio_webp_t *)params;
313 dt_bauhaus_combobox_set(g->compression, d->comp_type);
314 dt_bauhaus_slider_set(g->quality, d->quality);
315 dt_bauhaus_combobox_set(g->hint, d->hint);
316 return 0;
317}
318
320{
321 dt_free(params);
322}
323
325{
326 return 8;
327}
328
333
335{
336 // TODO: revisit this when IANA makes it official.
337 return "image/webp";
338}
339
341{
342 return "webp";
343}
344
345const char *name()
346{
347 return _("WebP (8-bit)");
348}
349
350static void compression_changed(GtkWidget *widget, gpointer user_data)
351{
352 const int comp_type = dt_bauhaus_combobox_get(widget);
353 dt_conf_set_int("plugins/imageio/format/webp/comp_type", comp_type);
354
355 if (comp_type == webp_lossless)
356 gtk_widget_set_sensitive(GTK_WIDGET(user_data), FALSE);
357 else
358 gtk_widget_set_sensitive(GTK_WIDGET(user_data), TRUE);
359}
360
361static void quality_changed(GtkWidget *slider, gpointer user_data)
362{
363 const int quality = (int)dt_bauhaus_slider_get(slider);
364 dt_conf_set_int("plugins/imageio/format/webp/quality", quality);
365}
366
367static void hint_combobox_changed(GtkWidget *widget, gpointer user_data)
368{
369 const int hint = dt_bauhaus_combobox_get(widget);
370 dt_conf_set_int("plugins/imageio/format/webp/hint", hint);
371}
372
374{
376 self->gui_data = (void *)gui;
377 const int comp_type = dt_conf_get_int("plugins/imageio/format/webp/comp_type");
378 const int quality = dt_conf_get_int("plugins/imageio/format/webp/quality");
379 const int hint = dt_conf_get_int("plugins/imageio/format/webp/hint");
380
381 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
382
384 dt_bauhaus_widget_set_label(gui->compression, N_("compression type"));
385 dt_bauhaus_combobox_add(gui->compression, _("lossy"));
386 dt_bauhaus_combobox_add(gui->compression, _("lossless"));
387 dt_bauhaus_combobox_set(gui->compression, comp_type);
388 gtk_box_pack_start(GTK_BOX(self->widget), gui->compression, TRUE, TRUE, 0);
389
391 dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_MIN),
392 dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_MAX),
393 1,
394 dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_DEFAULT),
395 0);
396 dt_bauhaus_widget_set_label(gui->quality, N_("quality"));
397 dt_bauhaus_slider_set_default(gui->quality, dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_DEFAULT));
399 gtk_widget_set_tooltip_text(gui->quality, _("applies only to lossy setting"));
400 if(quality > 0 && quality <= 100) dt_bauhaus_slider_set(gui->quality, quality);
401 gtk_box_pack_start(GTK_BOX(self->widget), gui->quality, TRUE, TRUE, 0);
402 g_signal_connect(G_OBJECT(gui->quality), "value-changed", G_CALLBACK(quality_changed), (gpointer)0);
403
404 g_signal_connect(G_OBJECT(gui->compression), "value-changed", G_CALLBACK(compression_changed), (gpointer)gui->quality);
405
406 if (comp_type == webp_lossless)
407 gtk_widget_set_sensitive(gui->quality, FALSE);
408
410 dt_bauhaus_widget_set_label(gui->hint, N_("image hint"));
411 gtk_widget_set_tooltip_text(gui->hint,
412 _("image characteristics hint for the underlying encoder.\n"
413 "picture : digital picture, like portrait, inner shot\n"
414 "photo : outdoor photograph, with natural lighting\n"
415 "graphic : discrete tone image (graph, map-tile etc)"));
416 dt_bauhaus_combobox_add(gui->hint, _("default"));
417 dt_bauhaus_combobox_add(gui->hint, _("picture"));
418 dt_bauhaus_combobox_add(gui->hint, _("photo"));
419 dt_bauhaus_combobox_add(gui->hint, _("graphic"));
420 dt_bauhaus_combobox_set(gui->hint, hint);
421 gtk_box_pack_start(GTK_BOX(self->widget), gui->hint, TRUE, TRUE, 0);
422 g_signal_connect(G_OBJECT(gui->hint), "value-changed", G_CALLBACK(hint_combobox_changed), NULL);
423}
424
426{
427 dt_free(self->gui_data);
428}
429
431{
433 const int comp_type = dt_confgen_get_int("plugins/imageio/format/webp/comp_type", DT_DEFAULT);
434 const int quality = dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_DEFAULT);
435 const int hint = dt_confgen_get_int("plugins/imageio/format/webp/hint", DT_DEFAULT);
436 dt_bauhaus_combobox_set(gui->compression, comp_type);
437 dt_bauhaus_slider_set(gui->quality, quality);
438 dt_bauhaus_combobox_set(gui->hint, hint);
439}
440
442{
443 // TODO(jinxos): support embedded ICC
445}
446
447// clang-format off
448// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
449// vim: shiftwidth=2 expandtab tabstop=2 cindent
450// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
451// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1640
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3483
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1780
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1842
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
const dt_colorspaces_color_profile_t * dt_colorspaces_get_output_profile(const int32_t imgid, dt_colorspaces_color_profile_type_t *over_type, const char *over_filename)
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
const dt_colormatrix_t dt_aligned_pixel_t out
@ DT_DEFAULT
Definition conf.h:96
@ DT_MAX
Definition conf.h:98
@ DT_MIN
Definition conf.h:97
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
int dt_confgen_get_int(const char *name, dt_confgen_value_kind_t kind)
darktable_t darktable
Definition darktable.c:181
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
int dt_exif_write_blob(uint8_t *blob, uint32_t size, const char *path, const int compressed)
Definition exif.cc:1816
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_GUI_MODULE(x)
int bpp
@ IMAGEIO_RGB
Definition imageio.h:70
@ IMAGEIO_INT8
Definition imageio.h:62
@ FORMAT_FLAGS_SUPPORT_XMP
size_t size
Definition mipmap_cache.c:3
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
GModule *GtkWidget * widget
GtkWidget * quality
Definition webp.c:80
GtkWidget * compression
Definition webp.c:79
dt_imageio_module_data_t global
Definition webp.c:71
const char * mime(dt_imageio_module_data_t *data)
Definition webp.c:334
size_t params_size(dt_imageio_module_format_t *self)
Definition webp.c:258
hint_t
Definition webp.c:61
@ hint_graphic
Definition webp.c:65
@ hint_photo
Definition webp.c:64
@ hint_default
Definition webp.c:62
@ hint_picture
Definition webp.c:63
void gui_reset(dt_imageio_module_format_t *self)
Definition webp.c:430
void gui_init(dt_imageio_module_format_t *self)
Definition webp.c:373
const char * extension(dt_imageio_module_data_t *data)
Definition webp.c:340
static const char *const EncoderError[]
Definition webp.c:87
int set_params(dt_imageio_module_format_t *self, const void *params, const int size)
Definition webp.c:308
#define stringify(a)
Definition webp.c:85
const char * name()
Definition webp.c:345
void cleanup(dt_imageio_module_format_t *self)
Definition webp.c:119
int levels(dt_imageio_module_data_t *p)
Definition webp.c:329
void * legacy_params(dt_imageio_module_format_t *self, const void *const old_params, const size_t old_params_size, const int old_version, const int new_version, size_t *new_size)
Definition webp.c:263
void free_params(dt_imageio_module_format_t *self, dt_imageio_module_data_t *params)
Definition webp.c:319
comp_type_t
Definition webp.c:54
@ webp_lossless
Definition webp.c:56
@ webp_lossy
Definition webp.c:55
const char * get_error_str(int err)
Definition webp.c:106
static void compression_changed(GtkWidget *widget, gpointer user_data)
Definition webp.c:350
void init(dt_imageio_module_format_t *self)
Definition webp.c:115
void * get_params(dt_imageio_module_format_t *self)
Definition webp.c:296
void gui_cleanup(dt_imageio_module_format_t *self)
Definition webp.c:425
static void hint_combobox_changed(GtkWidget *widget, gpointer user_data)
Definition webp.c:367
int write_image(dt_imageio_module_data_t *webp, const char *filename, const void *in_tmp, dt_colorspaces_color_profile_type_t over_type, const char *over_filename, void *exif, int exif_len, int32_t imgid, int num, int total, struct dt_dev_pixelpipe_t *pipe, const gboolean export_masks)
Definition webp.c:123
static void quality_changed(GtkWidget *slider, gpointer user_data)
Definition webp.c:361