1 /*
2 This file is part of darktable,
3 Copyright (C) 2013-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 #include "bauhaus/bauhaus.h"
23 #include "common/darktable.h"
24 #include "common/exif.h"
25 #include "common/imageio.h"
26 #include "common/imageio_module.h"
27 #include "control/conf.h"
28 #include "imageio/format/imageio_format_api.h"
29 #include <stdio.h>
30 #include <stdlib.h>
31
32 #include <webp/encode.h>
33
34 DT_MODULE(2)
35
36 typedef enum
37 {
38 webp_lossy = 0,
39 webp_lossless = 1
40 } comp_type_t;
41
42
43 typedef enum
44 {
45 hint_default,
46 hint_picture,
47 hint_photo,
48 hint_graphic
49 } hint_t;
50
51
52 typedef struct dt_imageio_webp_t
53 {
54 dt_imageio_module_data_t global;
55 int comp_type;
56 int quality;
57 int hint;
58 } dt_imageio_webp_t;
59
60 typedef struct dt_imageio_webp_gui_data_t
61 {
62 GtkWidget *compression;
63 GtkWidget *quality;
64 GtkWidget *hint;
65 } dt_imageio_webp_gui_data_t;
66
67 #define _stringify(a) #a
68 #define stringify(a) _stringify(a)
69
70 static const char *const EncoderError[] = {
71 "ok",
72 "out_of_memory: out of memory allocating objects",
73 "bitstream_out_of_memory: out of memory re-allocating byte buffer",
74 "null_parameter: null parameter passed to function",
75 "invalid_configuration: configuration is invalid",
76 "bad_dimension: bad picture dimension. maximum width and height "
77 "allowed is " stringify(WEBP_MAX_DIMENSION) " pixels.",
78 "partition0_overflow: partition #0 is too big to fit 512k.\n"
79 "to reduce the size of this partition, try using less segments "
80 "with the -segments option, and eventually reduce the number of "
81 "header bits using -partition_limit. more details are available "
82 "in the manual (`man cwebp`)",
83 "partition_overflow: partition is too big to fit 16M",
84 "bad_write: picture writer returned an i/o error",
85 "file_too_big: file would be too big to fit in 4G",
86 "user_abort: encoding abort requested by user"
87 };
88
get_error_str(int err)89 const char *get_error_str(int err)
90 {
91 if (err < 0 || err >= sizeof(EncoderError)/sizeof(EncoderError[0]))
92 {
93 return "unknown error (err=%d). consider filling a bug to DT to update the webp error list";
94 }
95 return EncoderError[err];
96 }
97
init(dt_imageio_module_format_t * self)98 void init(dt_imageio_module_format_t *self)
99 {
100 #ifdef USE_LUA
101 luaA_enum(darktable.lua_state.state, comp_type_t);
102 luaA_enum_value(darktable.lua_state.state, comp_type_t, webp_lossy);
103 luaA_enum_value(darktable.lua_state.state, comp_type_t, webp_lossless);
104 dt_lua_register_module_member(darktable.lua_state.state, self, dt_imageio_webp_t, comp_type, comp_type_t);
105 dt_lua_register_module_member(darktable.lua_state.state, self, dt_imageio_webp_t, quality, int);
106 luaA_enum(darktable.lua_state.state, hint_t);
107 luaA_enum_value(darktable.lua_state.state, hint_t, hint_default);
108 luaA_enum_value(darktable.lua_state.state, hint_t, hint_picture);
109 luaA_enum_value(darktable.lua_state.state, hint_t, hint_photo);
110 luaA_enum_value(darktable.lua_state.state, hint_t, hint_graphic);
111 dt_lua_register_module_member(darktable.lua_state.state, self, dt_imageio_webp_t, hint, hint_t);
112 #endif
113 }
cleanup(dt_imageio_module_format_t * self)114 void cleanup(dt_imageio_module_format_t *self)
115 {
116 }
117
FileWriter(const uint8_t * data,size_t data_size,const WebPPicture * const pic)118 static int FileWriter(const uint8_t *data, size_t data_size, const WebPPicture *const pic)
119 {
120 FILE *const out = (FILE *)pic->custom_ptr;
121 return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
122 }
123
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,int imgid,int num,int total,struct dt_dev_pixelpipe_t * pipe,const gboolean export_masks)124 int write_image(dt_imageio_module_data_t *webp, const char *filename, const void *in_tmp,
125 dt_colorspaces_color_profile_type_t over_type, const char *over_filename,
126 void *exif, int exif_len, int imgid, int num, int total, struct dt_dev_pixelpipe_t *pipe,
127 const gboolean export_masks)
128 {
129 FILE *out = NULL;
130 WebPPicture pic;
131 int pic_init = 0;
132
133 dt_imageio_webp_t *webp_data = (dt_imageio_webp_t *)webp;
134 out = g_fopen(filename, "w+b");
135 if (!out)
136 {
137 fprintf(stderr, "[webp export] error saving to %s\n", filename);
138 goto error;
139 }
140
141 // Create, configure and validate a WebPConfig instance
142 WebPConfig config;
143 if(!WebPConfigPreset(&config, webp_data->hint, (float)webp_data->quality)) goto error;
144
145 // TODO(jinxos): expose more config options in the UI
146 config.lossless = webp_data->comp_type;
147 config.image_hint = webp_data->hint;
148 config.method = 6;
149
150 // these are to allow for large image export.
151 // TODO(jinxos): these values should be adjusted as needed and ideally determined at runtime.
152 config.segments = 4;
153 config.partition_limit = 70;
154 if(!WebPValidateConfig(&config))
155 {
156 fprintf(stderr, "[webp export] error validating encoder configuration\n");
157 goto error;
158 }
159
160 if(!WebPPictureInit(&pic)) goto error;
161 pic_init = 1;
162 pic.width = webp_data->global.width;
163 pic.height = webp_data->global.height;
164 pic.use_argb = !!(config.lossless);
165 pic.writer = FileWriter;
166 pic.custom_ptr = out;
167
168 WebPPictureImportRGBX(&pic, (const uint8_t *)in_tmp, webp_data->global.width * 4);
169 if(!config.lossless)
170 {
171 // webp is more efficient at coding YUV images, as we go lossy
172 // let the encoder where best to spend its bits instead of forcing it
173 // to spend bits equally on RGB data that doesn't weight the same when
174 // considering the human visual system.
175 WebPPictureARGBToYUVA(&pic, WEBP_YUV420A);
176 }
177
178 if(!WebPEncode(&config, &pic))
179 {
180 fprintf(stderr, "[webp export] error during encoding (err:%d - %s)\n",
181 pic.error_code, get_error_str(pic.error_code));
182 goto error;
183 }
184
185 WebPPictureFree(&pic);
186 fclose(out);
187
188 dt_exif_write_blob(exif, exif_len, filename, 1);
189
190 return 0;
191
192 error:
193 if (pic_init) WebPPictureFree(&pic);
194 if(out) fclose(out);
195 return 1;
196 }
197
params_size(dt_imageio_module_format_t * self)198 size_t params_size(dt_imageio_module_format_t *self)
199 {
200 return sizeof(dt_imageio_webp_t);
201 }
202
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)203 void *legacy_params(dt_imageio_module_format_t *self, const void *const old_params,
204 const size_t old_params_size, const int old_version, const int new_version,
205 size_t *new_size)
206 {
207 if(old_version == 1 && new_version == 2)
208 {
209 typedef struct dt_imageio_webp_v1_t
210 {
211 int max_width, max_height;
212 int width, height;
213 char style[128];
214 int comp_type;
215 int quality;
216 int hint;
217 } dt_imageio_webp_v1_t;
218
219 dt_imageio_webp_v1_t *o = (dt_imageio_webp_v1_t *)old_params;
220 dt_imageio_webp_t *n = (dt_imageio_webp_t *)malloc(sizeof(dt_imageio_webp_t));
221
222 n->global.max_width = o->max_width;
223 n->global.max_height = o->max_height;
224 n->global.width = o->width;
225 n->global.height = o->height;
226 g_strlcpy(n->global.style, o->style, sizeof(o->style));
227 n->global.style_append = FALSE;
228 n->comp_type = o->comp_type;
229 n->quality = o->quality;
230 n->hint = o->hint;
231 *new_size = self->params_size(self);
232 return n;
233 }
234 return NULL;
235 }
236
get_params(dt_imageio_module_format_t * self)237 void *get_params(dt_imageio_module_format_t *self)
238 {
239 dt_imageio_webp_t *d = (dt_imageio_webp_t *)calloc(1, sizeof(dt_imageio_webp_t));
240 d->comp_type = dt_conf_get_int("plugins/imageio/format/webp/comp_type");
241 if(d->comp_type == webp_lossy)
242 d->quality = dt_conf_get_int("plugins/imageio/format/webp/quality");
243 else
244 d->quality = 100;
245 d->hint = dt_conf_get_int("plugins/imageio/format/webp/hint");
246 return d;
247 }
248
set_params(dt_imageio_module_format_t * self,const void * params,const int size)249 int set_params(dt_imageio_module_format_t *self, const void *params, const int size)
250 {
251 if(size != self->params_size(self)) return 1;
252 const dt_imageio_webp_t *d = (dt_imageio_webp_t *)params;
253 dt_imageio_webp_gui_data_t *g = (dt_imageio_webp_gui_data_t *)self->gui_data;
254 dt_bauhaus_combobox_set(g->compression, d->comp_type);
255 dt_bauhaus_slider_set(g->quality, d->quality);
256 dt_bauhaus_combobox_set(g->hint, d->hint);
257 return 0;
258 }
259
free_params(dt_imageio_module_format_t * self,dt_imageio_module_data_t * params)260 void free_params(dt_imageio_module_format_t *self, dt_imageio_module_data_t *params)
261 {
262 free(params);
263 }
264
bpp(dt_imageio_module_data_t * p)265 int bpp(dt_imageio_module_data_t *p)
266 {
267 return 8;
268 }
269
levels(dt_imageio_module_data_t * p)270 int levels(dt_imageio_module_data_t *p)
271 {
272 return IMAGEIO_RGB | IMAGEIO_INT8;
273 }
274
mime(dt_imageio_module_data_t * data)275 const char *mime(dt_imageio_module_data_t *data)
276 {
277 // TODO: revisit this when IANA makes it official.
278 return "image/webp";
279 }
280
extension(dt_imageio_module_data_t * data)281 const char *extension(dt_imageio_module_data_t *data)
282 {
283 return "webp";
284 }
285
name()286 const char *name()
287 {
288 return _("WebP (8-bit)");
289 }
290
compression_changed(GtkWidget * widget,gpointer user_data)291 static void compression_changed(GtkWidget *widget, gpointer user_data)
292 {
293 const int comp_type = dt_bauhaus_combobox_get(widget);
294 dt_conf_set_int("plugins/imageio/format/webp/comp_type", comp_type);
295
296 if (comp_type == webp_lossless)
297 gtk_widget_set_sensitive(GTK_WIDGET(user_data), FALSE);
298 else
299 gtk_widget_set_sensitive(GTK_WIDGET(user_data), TRUE);
300 }
301
quality_changed(GtkWidget * slider,gpointer user_data)302 static void quality_changed(GtkWidget *slider, gpointer user_data)
303 {
304 const int quality = (int)dt_bauhaus_slider_get(slider);
305 dt_conf_set_int("plugins/imageio/format/webp/quality", quality);
306 }
307
hint_combobox_changed(GtkWidget * widget,gpointer user_data)308 static void hint_combobox_changed(GtkWidget *widget, gpointer user_data)
309 {
310 const int hint = dt_bauhaus_combobox_get(widget);
311 dt_conf_set_int("plugins/imageio/format/webp/hint", hint);
312 }
313
gui_init(dt_imageio_module_format_t * self)314 void gui_init(dt_imageio_module_format_t *self)
315 {
316 dt_imageio_webp_gui_data_t *gui = (dt_imageio_webp_gui_data_t *)malloc(sizeof(dt_imageio_webp_gui_data_t));
317 self->gui_data = (void *)gui;
318 const int comp_type = dt_conf_get_int("plugins/imageio/format/webp/comp_type");
319 const int quality = dt_conf_get_int("plugins/imageio/format/webp/quality");
320 const int hint = dt_conf_get_int("plugins/imageio/format/webp/hint");
321
322 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
323
324 gui->compression = dt_bauhaus_combobox_new(NULL);
325 dt_bauhaus_widget_set_label(gui->compression, NULL, N_("compression type"));
326 dt_bauhaus_combobox_add(gui->compression, _("lossy"));
327 dt_bauhaus_combobox_add(gui->compression, _("lossless"));
328 dt_bauhaus_combobox_set(gui->compression, comp_type);
329 gtk_box_pack_start(GTK_BOX(self->widget), gui->compression, TRUE, TRUE, 0);
330
331 gui->quality = dt_bauhaus_slider_new_with_range(NULL,
332 dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_MIN),
333 dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_MAX),
334 1,
335 dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_DEFAULT),
336 0);
337 dt_bauhaus_widget_set_label(gui->quality, NULL, N_("quality"));
338 dt_bauhaus_slider_set_default(gui->quality, dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_DEFAULT));
339 dt_bauhaus_slider_set_format(gui->quality, "%.2f%%");
340 gtk_widget_set_tooltip_text(gui->quality, _("applies only to lossy setting"));
341 if(quality > 0 && quality <= 100) dt_bauhaus_slider_set(gui->quality, quality);
342 gtk_box_pack_start(GTK_BOX(self->widget), gui->quality, TRUE, TRUE, 0);
343 g_signal_connect(G_OBJECT(gui->quality), "value-changed", G_CALLBACK(quality_changed), (gpointer)0);
344
345 g_signal_connect(G_OBJECT(gui->compression), "value-changed", G_CALLBACK(compression_changed), (gpointer)gui->quality);
346
347 if (comp_type == webp_lossless)
348 gtk_widget_set_sensitive(gui->quality, FALSE);
349
350 gui->hint = dt_bauhaus_combobox_new(NULL);
351 dt_bauhaus_widget_set_label(gui->hint, NULL, N_("image hint"));
352 gtk_widget_set_tooltip_text(gui->hint,
353 _("image characteristics hint for the underlying encoder.\n"
354 "picture : digital picture, like portrait, inner shot\n"
355 "photo : outdoor photograph, with natural lighting\n"
356 "graphic : discrete tone image (graph, map-tile etc)"));
357 dt_bauhaus_combobox_add(gui->hint, _("default"));
358 dt_bauhaus_combobox_add(gui->hint, _("picture"));
359 dt_bauhaus_combobox_add(gui->hint, _("photo"));
360 dt_bauhaus_combobox_add(gui->hint, _("graphic"));
361 dt_bauhaus_combobox_set(gui->hint, hint);
362 gtk_box_pack_start(GTK_BOX(self->widget), gui->hint, TRUE, TRUE, 0);
363 g_signal_connect(G_OBJECT(gui->hint), "value-changed", G_CALLBACK(hint_combobox_changed), NULL);
364 }
365
gui_cleanup(dt_imageio_module_format_t * self)366 void gui_cleanup(dt_imageio_module_format_t *self)
367 {
368 free(self->gui_data);
369 }
370
gui_reset(dt_imageio_module_format_t * self)371 void gui_reset(dt_imageio_module_format_t *self)
372 {
373 dt_imageio_webp_gui_data_t *gui = (dt_imageio_webp_gui_data_t *)self->gui_data;
374 const int comp_type = dt_confgen_get_int("plugins/imageio/format/webp/comp_type", DT_DEFAULT);
375 const int quality = dt_confgen_get_int("plugins/imageio/format/webp/quality", DT_DEFAULT);
376 const int hint = dt_confgen_get_int("plugins/imageio/format/webp/hint", DT_DEFAULT);
377 dt_bauhaus_combobox_set(gui->compression, comp_type);
378 dt_bauhaus_slider_set(gui->quality, quality);
379 dt_bauhaus_combobox_set(gui->hint, hint);
380 }
381
flags(dt_imageio_module_data_t * data)382 int flags(dt_imageio_module_data_t *data)
383 {
384 // TODO(jinxos): support embedded XMP/ICC
385 return 0;
386 }
387
388 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
389 // vim: shiftwidth=2 expandtab tabstop=2 cindent
390 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
391