1 /*
2     This file is part of darktable,
3     Copyright (C) 2010-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 #include "bauhaus/bauhaus.h"
20 #include "common/imagebuf.h"
21 #include "common/tags.h"
22 #include "common/variables.h"
23 #include "control/control.h"
24 #include "develop/develop.h"
25 #include "develop/imageop.h"
26 #include "develop/imageop_gui.h"
27 #include "dtgtk/button.h"
28 #include "dtgtk/resetlabel.h"
29 #include "dtgtk/togglebutton.h"
30 #include "gui/accelerators.h"
31 #include "gui/color_picker_proxy.h"
32 #include "gui/gtk.h"
33 #include "iop/iop_api.h"
34 #include <assert.h>
35 #include <gtk/gtk.h>
36 #include <inttypes.h>
37 #include <math.h>
38 #include <stdlib.h>
39 #include <string.h>
40 
41 #include <librsvg/rsvg.h>
42 // ugh, ugly hack. why do people break stuff all the time?
43 #ifndef RSVG_CAIRO_H
44 #include <librsvg/rsvg-cairo.h>
45 #endif
46 
47 #include "common/file_location.h"
48 #include "common/metadata.h"
49 #include "common/utility.h"
50 
51 DT_MODULE_INTROSPECTION(5, dt_iop_watermark_params_t)
52 
53 // gchar *checksum = g_compute_checksum_for_data(G_CHECKSUM_MD5,data,length);
54 
55 typedef enum dt_iop_watermark_base_scale_t
56 {
57   DT_SCALE_IMAGE = 0,         // $DESCRIPTION: "image"
58   DT_SCALE_LARGER_BORDER = 1, // $DESCRIPTION: "larger border"
59   DT_SCALE_SMALLER_BORDER = 2 // $DESCRIPTION: "smaller border"
60 } dt_iop_watermark_base_scale_t;
61 
62 typedef enum dt_iop_watermark_type_t
63 {
64   DT_WTM_SVG = 0,         // $DESCRIPTION: "vector .svg"
65   DT_WTM_PNG = 1          // $DESCRIPTION: "raster .png"
66 } dt_iop_watermark_type_t;
67 
68 typedef struct dt_iop_watermark_params_t
69 {
70   /** opacity value of rendering watermark */
71   float opacity; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 100.0
72   /** scale value of rendering watermark */
73   float scale;   // $MIN: 1.0 $MAX: 500.0 $DEFAULT: 100.0
74   /** Pixel independent xoffset, 0 to 1 */
75   float xoffset; // $MIN: -1.0 $MAX: 1.0, 0.001 $DEFAULT: 0.0 $DESCRIPTION: "x offset"
76   /** Pixel independent yoffset, 0 to 1 */
77   float yoffset; // $MIN: -1.0 $MAX: 1.0, 0.001 $DEFAULT: 0.0 $DESCRIPTION: "y offset"
78   /** Alignment value 0-8 3x3 */
79   int alignment; // $DEFAULT: 4
80   /** Rotation **/
81   float rotate;  // $MIN: -180.0 $MAX: 180.0 $DEFAULT: 0.0 $DESCRIPTION: "rotation"
82   dt_iop_watermark_base_scale_t sizeto; // $DEFAULT: DT_SCALE_IMAGE $DESCRIPTION: "scale on"
83   char filename[64];
84   /* simple text */
85   char text[512];
86   /* text color */
87   float color[3]; // $DEFAULT: 0.0
88   /* text font */
89   char font[64];
90 } dt_iop_watermark_params_t;
91 
92 typedef struct dt_iop_watermark_data_t
93 {
94   float opacity;
95   float scale;
96   float xoffset;
97   float yoffset;
98   int alignment;
99   float rotate;
100   dt_iop_watermark_base_scale_t sizeto;
101   char filename[64];
102   char text[512];
103   float color[3];
104   char font[64];
105 } dt_iop_watermark_data_t;
106 
107 typedef struct dt_iop_watermark_gui_data_t
108 {
109   GtkWidget *watermarks;                             // watermark
110   GList     *watermarks_filenames;                   // the actual filenames
111   GtkWidget *refresh;                                // refresh watermarks...
112   GtkWidget *align[9];                               // Alignment buttons
113   GtkWidget *opacity, *scale, *x_offset, *y_offset;  // opacity, scale, xoffs, yoffs
114   GtkWidget *sizeto;                                 // relative size to
115   GtkWidget *rotate;
116   GtkWidget *text;
117   GtkWidget *colorpick;
118   GtkWidget *fontsel;
119   GtkWidget *color_picker_button;
120 } dt_iop_watermark_gui_data_t;
121 
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)122 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
123                   void *new_params, const int new_version)
124 {
125   if(old_version == 1 && new_version == 5)
126   {
127     typedef struct dt_iop_watermark_params_v1_t
128     {
129       /** opacity value of rendering watermark */
130       float opacity;
131       /** scale value of rendering watermark */
132       float scale;
133       /** Pixel independent xoffset, 0 to 1 */
134       float xoffset;
135       /** Pixel independent yoffset, 0 to 1 */
136       float yoffset;
137       /** Alignment value 0-8 3x3 */
138       int alignment;
139       char filename[64];
140     } dt_iop_watermark_params_v1_t;
141 
142     dt_iop_watermark_params_v1_t *o = (dt_iop_watermark_params_v1_t *)old_params;
143     dt_iop_watermark_params_t *n = (dt_iop_watermark_params_t *)new_params;
144     dt_iop_watermark_params_t *d = (dt_iop_watermark_params_t *)self->default_params;
145 
146     *n = *d; // start with a fresh copy of default parameters
147 
148     n->opacity = o->opacity;
149     n->scale = o->scale;
150     n->xoffset = o->xoffset;
151     n->yoffset = o->yoffset;
152     n->alignment = o->alignment;
153     n->rotate = 0.0;
154     n->sizeto = DT_SCALE_IMAGE;
155     g_strlcpy(n->filename, o->filename, sizeof(n->filename));
156     g_strlcpy(n->text, "", sizeof(n->text));
157     g_strlcpy(n->font, "DejaVu Sans 10", sizeof(n->font));
158     n->color[0] = n->color[1] = n->color[2] = 0;
159     return 0;
160   }
161   else if(old_version == 2 && new_version == 5)
162   {
163     typedef struct dt_iop_watermark_params_v2_t
164     {
165       /** opacity value of rendering watermark */
166       float opacity;
167       /** scale value of rendering watermark */
168       float scale;
169       /** Pixel independent xoffset, 0 to 1 */
170       float xoffset;
171       /** Pixel independent yoffset, 0 to 1 */
172       float yoffset;
173       /** Alignment value 0-8 3x3 */
174       int alignment;
175       dt_iop_watermark_base_scale_t sizeto;
176       char filename[64];
177     } dt_iop_watermark_params_v2_t;
178 
179     dt_iop_watermark_params_v2_t *o = (dt_iop_watermark_params_v2_t *)old_params;
180     dt_iop_watermark_params_t *n = (dt_iop_watermark_params_t *)new_params;
181     dt_iop_watermark_params_t *d = (dt_iop_watermark_params_t *)self->default_params;
182 
183     *n = *d; // start with a fresh copy of default parameters
184 
185     n->opacity = o->opacity;
186     n->scale = o->scale;
187     n->xoffset = o->xoffset;
188     n->yoffset = o->yoffset;
189     n->alignment = o->alignment;
190     n->rotate = 0.0;
191     n->sizeto = DT_SCALE_IMAGE;
192     g_strlcpy(n->filename, o->filename, sizeof(n->filename));
193     g_strlcpy(n->text, "", sizeof(n->text));
194     g_strlcpy(n->font, "DejaVu Sans 10", sizeof(n->font));
195     n->color[0] = n->color[1] = n->color[2] = 0;
196     return 0;
197   }
198   else if(old_version == 3 && new_version == 5)
199   {
200     typedef struct dt_iop_watermark_params_v3_t
201     {
202       /** opacity value of rendering watermark */
203       float opacity;
204       /** scale value of rendering watermark */
205       float scale;
206       /** Pixel independent xoffset, 0 to 1 */
207       float xoffset;
208       /** Pixel independent yoffset, 0 to 1 */
209       float yoffset;
210       /** Alignment value 0-8 3x3 */
211       int alignment;
212       /** Rotation **/
213       float rotate;
214       dt_iop_watermark_base_scale_t sizeto;
215       char filename[64];
216     } dt_iop_watermark_params_v3_t;
217 
218     dt_iop_watermark_params_v3_t *o = (dt_iop_watermark_params_v3_t *)old_params;
219     dt_iop_watermark_params_t *n = (dt_iop_watermark_params_t *)new_params;
220     dt_iop_watermark_params_t *d = (dt_iop_watermark_params_t *)self->default_params;
221 
222     *n = *d; // start with a fresh copy of default parameters
223 
224     n->opacity = o->opacity;
225     n->scale = o->scale;
226     n->xoffset = o->xoffset;
227     n->yoffset = o->yoffset;
228     n->alignment = o->alignment;
229     n->rotate = o->rotate;
230     n->sizeto = o->sizeto;
231     g_strlcpy(n->filename, o->filename, sizeof(n->filename));
232     g_strlcpy(n->text, "", sizeof(n->text));
233     g_strlcpy(n->font, "DejaVu Sans 10", sizeof(n->font));
234     n->color[0] = n->color[1] = n->color[2] = 0;
235     return 0;
236   }
237   else if(old_version == 4 && new_version == 5)
238   {
239     typedef struct dt_iop_watermark_params_v4_t
240     {
241       /** opacity value of rendering watermark */
242       float opacity;
243       /** scale value of rendering watermark */
244       float scale;
245       /** Pixel independent xoffset, 0 to 1 */
246       float xoffset;
247       /** Pixel independent yoffset, 0 to 1 */
248       float yoffset;
249       /** Alignment value 0-8 3x3 */
250       int alignment;
251       /** Rotation **/
252       float rotate;
253       dt_iop_watermark_base_scale_t sizeto;
254       char filename[64];
255       /* simple text */
256       char text[64];
257       /* text color */
258       float color[3];
259       /* text font */
260       char font[64];
261     } dt_iop_watermark_params_v4_t;
262 
263     dt_iop_watermark_params_v4_t *o = (dt_iop_watermark_params_v4_t *)old_params;
264     dt_iop_watermark_params_t *n = (dt_iop_watermark_params_t *)new_params;
265     dt_iop_watermark_params_t *d = (dt_iop_watermark_params_t *)self->default_params;
266 
267     *n = *d; // start with a fresh copy of default parameters
268 
269     n->opacity = o->opacity;
270     n->scale = o->scale;
271     n->xoffset = o->xoffset;
272     n->yoffset = o->yoffset;
273     n->alignment = o->alignment;
274     n->rotate = o->rotate;
275     n->sizeto = o->sizeto;
276     g_strlcpy(n->filename, o->filename, sizeof(n->filename));
277     g_strlcpy(n->text, o->text, sizeof(n->text));
278     g_strlcpy(n->font, o->font, sizeof(n->font));
279     n->color[0] = o->color[0];
280     n->color[1] = o->color[1];
281     n->color[2] = o->color[2];
282     return 0;
283   }
284   return 1;
285 }
286 
287 
name()288 const char *name()
289 {
290   return _("watermark");
291 }
292 
description(struct dt_iop_module_t * self)293 const char *description(struct dt_iop_module_t *self)
294 {
295   return dt_iop_set_description(self, _("overlay an SVG watermark like a signature on the picture"),
296                                       _("creative"),
297                                       _("non-linear, RGB, display-referred"),
298                                       _("non-linear, RGB"),
299                                       _("non-linear, RGB, display-referred"));
300 }
301 
flags()302 int flags()
303 {
304   return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_SUPPORTS_BLENDING;
305 }
306 
default_group()307 int default_group()
308 {
309   return IOP_GROUP_EFFECT | IOP_GROUP_EFFECTS;
310 }
311 
operation_tags()312 int operation_tags()
313 {
314   return IOP_TAG_DECORATION;
315 }
316 
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)317 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
318 {
319   return iop_cs_rgb;
320 }
321 
322 // sets text / color / font widgets sensitive based on watermark file type
_text_color_font_set_sensitive(dt_iop_watermark_gui_data_t * g,gchar * filename)323 static void _text_color_font_set_sensitive(dt_iop_watermark_gui_data_t *g, gchar *filename)
324 {
325   const gchar *extension = strrchr(filename, '.');
326   if(extension)
327   {
328     const gboolean active = !g_ascii_strcasecmp(extension, ".svg");
329     gtk_widget_set_sensitive(GTK_WIDGET(g->colorpick), active);
330     gtk_widget_set_sensitive(GTK_WIDGET(g->color_picker_button), active);
331     gtk_widget_set_sensitive(GTK_WIDGET(g->text), active);
332     gtk_widget_set_sensitive(GTK_WIDGET(g->fontsel), active);
333   }
334 }
335 
_combo_box_set_active_text(dt_iop_watermark_gui_data_t * g,gchar * text)336 static void _combo_box_set_active_text(dt_iop_watermark_gui_data_t *g, gchar *text)
337 {
338   int i = 0;
339   for(const GList *iter = g->watermarks_filenames; iter; iter = g_list_next(iter))
340   {
341     if(!g_strcmp0((gchar *)iter->data, text))
342     {
343       dt_bauhaus_combobox_set(g->watermarks, i);
344       _text_color_font_set_sensitive(g, text);
345       return;
346     }
347     i++;
348   }
349 }
350 
351 // replace < and > with &lt; and &gt;. any more? Yes! & -> &amp;
_string_escape(const gchar * string)352 static gchar *_string_escape(const gchar *string)
353 {
354   gchar *result, *result_old;
355   result = dt_util_str_replace(string, "&", "&amp;");
356 
357   result_old = result;
358   result = dt_util_str_replace(result_old, "<", "&lt;");
359   g_free(result_old);
360 
361   result_old = result;
362   result = dt_util_str_replace(result_old, ">", "&gt;");
363   g_free(result_old);
364 
365   return result;
366 }
367 
_string_substitute(gchar * string,const gchar * search,const gchar * replace)368 static gchar *_string_substitute(gchar *string, const gchar *search, const gchar *replace)
369 {
370   gchar *_replace = _string_escape(replace);
371   gchar *result = dt_util_str_replace(string, search, _replace);
372   g_free(_replace);
373   g_free(string);  // dt_util_str_replace always returns a new string, and we don't need the original after this func
374   return result;
375 }
376 
_watermark_get_svgdoc(dt_iop_module_t * self,dt_iop_watermark_data_t * data,const dt_image_t * image,const gchar * filename)377 static gchar *_watermark_get_svgdoc(dt_iop_module_t *self, dt_iop_watermark_data_t *data,
378                                     const dt_image_t *image, const gchar *filename)
379 {
380   char datetime[200];
381 
382   // EXIF datetime
383   struct tm tt_exif = { 0 };
384   if(sscanf(image->exif_datetime_taken, "%d:%d:%d %d:%d:%d", &tt_exif.tm_year, &tt_exif.tm_mon,
385             &tt_exif.tm_mday, &tt_exif.tm_hour, &tt_exif.tm_min, &tt_exif.tm_sec) == 6)
386   {
387     tt_exif.tm_year -= 1900;
388     tt_exif.tm_mon--;
389   }
390 
391   // Current datetime
392   struct tm tt_cur = { 0 };
393   time_t t = time(NULL);
394   (void)localtime_r(&t, &tt_cur);
395 
396   gchar *svgdata = NULL;
397   gsize length = 0;
398   if(g_file_get_contents(filename, &svgdata, &length, NULL))
399   {
400     // File is loaded lets substitute strings if found...
401 
402     // Darktable internal
403     svgdata = _string_substitute(svgdata, "$(DARKTABLE.NAME)", PACKAGE_NAME);
404     svgdata = _string_substitute(svgdata, "$(DARKTABLE.VERSION)", darktable_package_version);
405 
406     // Simple text from watermark module
407     gchar buffer[1024];
408 
409     // substitute $(WATERMARK_TEXT)
410     if(data->text[0])
411     {
412       g_strlcpy(buffer, data->text, sizeof(buffer));
413       svgdata = _string_substitute(svgdata, "$(WATERMARK_TEXT)", buffer);
414     }
415     // apply font style substitutions
416     PangoFontDescription *font = pango_font_description_from_string(data->font);
417     const PangoStyle font_style = pango_font_description_get_style(font);
418     const int font_weight = (int)pango_font_description_get_weight(font);
419 
420     g_strlcpy(buffer, pango_font_description_get_family(font), sizeof(buffer));
421     svgdata = _string_substitute(svgdata, "$(WATERMARK_FONT_FAMILY)", buffer);
422 
423     switch(font_style)
424     {
425       case PANGO_STYLE_OBLIQUE:
426         g_strlcpy(buffer, "oblique", sizeof(buffer));
427         break;
428       case PANGO_STYLE_ITALIC:
429         g_strlcpy(buffer, "italic", sizeof(buffer));
430         break;
431       default:
432         g_strlcpy(buffer, "normal", sizeof(buffer));
433         break;
434     }
435     svgdata = _string_substitute(svgdata, "$(WATERMARK_FONT_STYLE)", buffer);
436 
437     g_snprintf(buffer, sizeof(buffer), "%d", font_weight);
438     svgdata = _string_substitute(svgdata, "$(WATERMARK_FONT_WEIGHT)", buffer);
439 
440     pango_font_description_free(font);
441 
442     // watermark color
443     GdkRGBA c = { data->color[0], data->color[1], data->color[2], 1.0f };
444     g_strlcpy(buffer, gdk_rgba_to_string(&c), sizeof(buffer));
445     svgdata = _string_substitute(svgdata, "$(WATERMARK_COLOR)", buffer);
446 
447     // Current image ID
448     g_snprintf(buffer, sizeof(buffer), "%d", image->id);
449     svgdata = _string_substitute(svgdata, "$(IMAGE.ID)", buffer);
450 
451     // Current image
452     dt_image_print_exif(image, buffer, sizeof(buffer));
453     svgdata = _string_substitute(svgdata, "$(IMAGE.EXIF)", buffer);
454 
455     // Image exif
456     // EXIF date
457     svgdata = _string_substitute(svgdata, "$(EXIF.DATE)", image->exif_datetime_taken);
458     // $(EXIF.DATE.SECOND) -- 00..60
459     strftime(datetime, sizeof(datetime), "%S", &tt_exif);
460     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.SECOND)", datetime);
461     // $(EXIF.DATE.MINUTE) -- 00..59
462     strftime(datetime, sizeof(datetime), "%M", &tt_exif);
463     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.MINUTE)", datetime);
464     // $(EXIF.DATE.HOUR) -- 00..23
465     strftime(datetime, sizeof(datetime), "%H", &tt_exif);
466     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.HOUR)", datetime);
467     // $(EXIF.DATE.HOUR_AMPM) -- 01..12
468     strftime(datetime, sizeof(datetime), "%I %p", &tt_exif);
469     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.HOUR_AMPM)", datetime);
470     // $(EXIF.DATE.DAY) -- 01..31
471     strftime(datetime, sizeof(datetime), "%d", &tt_exif);
472     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.DAY)", datetime);
473     // $(EXIF.DATE.MONTH) -- 01..12
474     strftime(datetime, sizeof(datetime), "%m", &tt_exif);
475     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.MONTH)", datetime);
476     // $(EXIF.DATE.SHORT_MONTH) -- Jan, Feb, .., Dec, localized
477     strftime(datetime, sizeof(datetime), "%b", &tt_exif);
478     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.SHORT_MONTH)", datetime);
479     // $(EXIF.DATE.LONG_MONTH) -- January, February, .., December, localized
480     strftime(datetime, sizeof(datetime), "%B", &tt_exif);
481     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.LONG_MONTH)", datetime);
482     // $(EXIF.DATE.SHORT_YEAR) -- 12
483     strftime(datetime, sizeof(datetime), "%y", &tt_exif);
484     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.SHORT_YEAR)", datetime);
485     // $(EXIF.DATE.LONG_YEAR) -- 2012
486     strftime(datetime, sizeof(datetime), "%Y", &tt_exif);
487     svgdata = _string_substitute(svgdata, "$(EXIF.DATE.LONG_YEAR)", datetime);
488 
489     // Current date
490     // $(DATE) -- YYYY:
491     dt_gettime_t(datetime, sizeof(datetime), t);
492     svgdata = _string_substitute(svgdata, "$(DATE)", datetime);
493     // $(DATE.SECOND) -- 00..60
494     strftime(datetime, sizeof(datetime), "%S", &tt_cur);
495     svgdata = _string_substitute(svgdata, "$(DATE.SECOND)", datetime);
496     // $(DATE.MINUTE) -- 00..59
497     strftime(datetime, sizeof(datetime), "%M", &tt_cur);
498     svgdata = _string_substitute(svgdata, "$(DATE.MINUTE)", datetime);
499     // $(DATE.HOUR) -- 00..23
500     strftime(datetime, sizeof(datetime), "%H", &tt_cur);
501     svgdata = _string_substitute(svgdata, "$(DATE.HOUR)", datetime);
502     // $(DATE.HOUR_AMPM) -- 01..12
503     strftime(datetime, sizeof(datetime), "%I %p", &tt_cur);
504     svgdata = _string_substitute(svgdata, "$(DATE.HOUR_AMPM)", datetime);
505     // $(DATE.DAY) -- 01..31
506     strftime(datetime, sizeof(datetime), "%d", &tt_cur);
507     svgdata = _string_substitute(svgdata, "$(DATE.DAY)", datetime);
508     // $(DATE.MONTH) -- 01..12
509     strftime(datetime, sizeof(datetime), "%m", &tt_cur);
510     svgdata = _string_substitute(svgdata, "$(DATE.MONTH)", datetime);
511     // $(DATE.SHORT_MONTH) -- Jan, Feb, .., Dec, localized
512     strftime(datetime, sizeof(datetime), "%b", &tt_cur);
513     svgdata = _string_substitute(svgdata, "$(DATE.SHORT_MONTH)", datetime);
514     // $(DATE.LONG_MONTH) -- January, February, .., December, localized
515     strftime(datetime, sizeof(datetime), "%B", &tt_cur);
516     svgdata = _string_substitute(svgdata, "$(DATE.LONG_MONTH)", datetime);
517     // $(DATE.SHORT_YEAR) -- 12
518     strftime(datetime, sizeof(datetime), "%y", &tt_cur);
519     svgdata = _string_substitute(svgdata, "$(DATE.SHORT_YEAR)", datetime);
520     // $(DATE.LONG_YEAR) -- 2012
521     strftime(datetime, sizeof(datetime), "%Y", &tt_cur);
522     svgdata = _string_substitute(svgdata, "$(DATE.LONG_YEAR)", datetime);
523     svgdata = _string_substitute(svgdata, "$(EXIF.MAKER)", image->camera_maker);
524     svgdata = _string_substitute(svgdata, "$(EXIF.MODEL)", image->camera_model);
525     svgdata = _string_substitute(svgdata, "$(EXIF.LENS)", image->exif_lens);
526     svgdata = _string_substitute(svgdata, "$(IMAGE.FILENAME)", image->filename);
527 
528     gchar *basename = g_path_get_basename(image->filename);
529     if(g_strrstr(basename, ".")) *(g_strrstr(basename, ".")) = '\0';
530     svgdata = _string_substitute(svgdata, "$(IMAGE.BASENAME)", basename);
531     g_free(basename);
532 
533     // TODO: auto generate that code?
534     GList *res;
535     res = dt_metadata_get(image->id, "Xmp.dc.creator", NULL);
536     svgdata = _string_substitute(svgdata, "$(Xmp.dc.creator)", (res ? res->data : ""));
537     g_list_free_full(res, &g_free);
538 
539     res = dt_metadata_get(image->id, "Xmp.dc.publisher", NULL);
540     svgdata = _string_substitute(svgdata, "$(Xmp.dc.publisher)", (res ? res->data : ""));
541     g_list_free_full(res, &g_free);
542 
543     res = dt_metadata_get(image->id, "Xmp.dc.title", NULL);
544     svgdata = _string_substitute(svgdata, "$(Xmp.dc.title)", (res ? res->data : ""));
545     g_list_free_full(res, &g_free);
546 
547     res = dt_metadata_get(image->id, "Xmp.dc.description", NULL);
548     svgdata = _string_substitute(svgdata, "$(Xmp.dc.description)", (res ? res->data : ""));
549     g_list_free_full(res, &g_free);
550 
551     res = dt_metadata_get(image->id, "Xmp.dc.rights", NULL);
552     svgdata = _string_substitute(svgdata, "$(Xmp.dc.rights)", (res ? res->data : ""));
553     g_list_free_full(res, &g_free);
554 
555     res = dt_tag_get_list(image->id);
556     gchar *keywords = dt_util_glist_to_str(", ", res);
557     svgdata = _string_substitute(svgdata, "$(IMAGE.TAGS)", (keywords ? keywords : ""));
558     g_free(keywords);
559     g_list_free_full(res, &g_free);
560 
561     const int stars = image->flags & 0x7;
562     const char *const rating_str[] = { "☆☆☆☆☆", "★☆☆☆☆", "★★☆☆☆", "★★★☆☆", "★★★★☆", "★★★★★", "❌", "" };
563     svgdata = _string_substitute(svgdata, "$(Xmp.xmp.Rating)", rating_str[stars]);
564 
565     // geolocation
566     gchar *latitude = NULL, *longitude = NULL, *elevation = NULL;
567     if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location"))
568     {
569       latitude = dt_util_latitude_str(image->geoloc.latitude);
570       longitude = dt_util_longitude_str(image->geoloc.longitude);
571       elevation = dt_util_elevation_str(image->geoloc.elevation);
572     }
573     else
574     {
575       const gchar NS = image->geoloc.latitude < 0 ? 'S' : 'N';
576       const gchar EW = image->geoloc.longitude < 0 ? 'W' : 'E';
577       if(image->geoloc.latitude) latitude = g_strdup_printf("%c %09.6f", NS, fabs(image->geoloc.latitude));
578       if(image->geoloc.longitude) longitude = g_strdup_printf("%c %010.6f", EW, fabs(image->geoloc.longitude));
579       if(image->geoloc.elevation) elevation = g_strdup_printf("%.2f %s", image->geoloc.elevation, _("m"));
580     }
581     gchar *parts[4] = { 0 };
582     int i = 0;
583     if(latitude) parts[i++] = latitude;
584     if(longitude) parts[i++] = longitude;
585     if(elevation) parts[i++] = elevation;
586     gchar *location = g_strjoinv(", ", parts);
587     svgdata = _string_substitute(svgdata, "$(GPS.LATITUDE)", (latitude ? latitude : "-"));
588     svgdata = _string_substitute(svgdata, "$(GPS.LONGITUDE)", (longitude ? longitude : "-"));
589     svgdata = _string_substitute(svgdata, "$(GPS.ELEVATION)", (elevation ? elevation : "-"));
590     svgdata = _string_substitute(svgdata, "$(GPS.LOCATION)", location);
591     g_free(latitude);
592     g_free(longitude);
593     g_free(elevation);
594     g_free(location);
595 
596     // standard calculation on the remaining variables
597     const int32_t flags = dt_lib_export_metadata_get_conf_flags();
598     dt_variables_params_t *params;
599     dt_variables_params_init(&params);
600     params->filename = image->filename;
601     params->jobcode = "infos";
602     params->sequence = 0;
603     params->imgid = image->id;
604     dt_variables_set_tags_flags(params, flags);
605     gchar *svgdoc = dt_variables_expand(params, svgdata, FALSE);  // returns a new string
606     g_free(svgdata);  // free the old one
607     svgdata = svgdoc; // and make the expanded string our result
608   }
609   return svgdata;
610 }
611 
process(struct dt_iop_module_t * self,dt_dev_pixelpipe_iop_t * piece,const void * const ivoid,void * const ovoid,const dt_iop_roi_t * const roi_in,const dt_iop_roi_t * const roi_out)612 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
613              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
614 {
615   dt_iop_watermark_data_t *data = (dt_iop_watermark_data_t *)piece->data;
616   float *in = (float *)ivoid;
617   float *out = (float *)ovoid;
618   const int ch = piece->colors;
619   const float angle = (M_PI / 180) * (-data->rotate);
620 
621   gchar configdir[PATH_MAX] = { 0 };
622   gchar datadir[PATH_MAX] = { 0 };
623   gchar *filename;
624   dt_loc_get_datadir(datadir, sizeof(datadir));
625   dt_loc_get_user_config_dir(configdir, sizeof(configdir));
626   g_strlcat(datadir, "/watermarks/", sizeof(datadir));
627   g_strlcat(configdir, "/watermarks/", sizeof(configdir));
628   g_strlcat(datadir, data->filename, sizeof(datadir));
629   g_strlcat(configdir, data->filename, sizeof(configdir));
630 
631   if(g_file_test(configdir, G_FILE_TEST_EXISTS))
632     filename = configdir;
633   else if(g_file_test(datadir, G_FILE_TEST_EXISTS))
634     filename = datadir;
635   else
636   {
637     dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
638     return;
639   }
640 
641   // find out the watermark type
642   dt_iop_watermark_type_t type;
643   const gchar *extension = strrchr(data->filename, '.');
644   if(extension)
645   {
646     if(!g_ascii_strcasecmp(extension, ".svg"))
647       type = DT_WTM_SVG;
648     else if(!g_ascii_strcasecmp(extension, ".png"))
649       type = DT_WTM_PNG;
650     else // this should not happen
651     {
652       dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
653       return;
654     }
655   }
656   else
657   {
658     dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
659     return;
660   }
661 
662   /* Load svg if not loaded */
663   gchar *svgdoc = NULL;
664   if(type == DT_WTM_SVG)
665   {
666     svgdoc = _watermark_get_svgdoc(self, data, &piece->pipe->image, filename);
667     if(!svgdoc)
668     {
669       dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
670       return;
671     }
672   }
673 
674   /* setup stride for performance */
675   const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, roi_out->width);
676   if(stride == -1)
677   {
678     fprintf(stderr, "[watermark] cairo stride error\n");
679     dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
680     return;
681   }
682 
683   /* create a cairo memory surface that is later used for reading watermark overlay data */
684   guint8 *image = (guint8 *)g_malloc0_n(roi_out->height, stride);
685   cairo_surface_t *surface = cairo_image_surface_create_for_data(image, CAIRO_FORMAT_ARGB32, roi_out->width,
686                                                                  roi_out->height, stride);
687   if((cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) || (image == NULL))
688   {
689     fprintf(stderr, "[watermark] cairo surface error: %s\n",
690             cairo_status_to_string(cairo_surface_status(surface)));
691     g_free(image);
692     dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
693     return;
694   }
695 
696   // rsvg (or some part of cairo which is used underneath) isn't thread safe, for example when handling fonts
697   dt_pthread_mutex_lock(&darktable.plugin_threadsafe);
698 
699   RsvgHandle *svg = NULL;
700   if(type == DT_WTM_SVG)
701   {
702     /* create the rsvghandle from parsed svg data */
703     GError *error = NULL;
704     svg = rsvg_handle_new_from_data((const guint8 *)svgdoc, strlen(svgdoc), &error);
705     g_free(svgdoc);
706     if(!svg || error)
707     {
708       cairo_surface_destroy(surface);
709       g_free(image);
710       dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
711       dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
712       fprintf(stderr, "[watermark] error processing svg file: %s\n", error->message);
713       g_error_free(error);
714       return;
715     }
716   }
717 
718   // we use a second surface
719   guint8 *image_two = NULL;
720   cairo_surface_t *surface_two = NULL;
721 
722   /* get the dimension of svg or png */
723   RsvgDimensionData dimension;
724   switch(type)
725   {
726     case DT_WTM_SVG:
727       dimension = dt_get_svg_dimension(svg);
728       break;
729     case DT_WTM_PNG:
730       // load png into surface 2
731       surface_two = cairo_image_surface_create_from_png(filename);
732       if((cairo_surface_status(surface_two) != CAIRO_STATUS_SUCCESS))
733       {
734         fprintf(stderr, "[watermark] cairo png surface 2 error: %s\n",
735                 cairo_status_to_string(cairo_surface_status(surface_two)));
736         cairo_surface_destroy(surface);
737         g_free(image);
738         dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
739         dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
740         return;
741       }
742       dimension.width = cairo_image_surface_get_width(surface_two);
743       dimension.height = cairo_image_surface_get_height(surface_two);
744       break;
745   }
746 
747   // if no text is given dimensions are null
748   if(!dimension.width) dimension.width = 1;
749   if(!dimension.height) dimension.height = 1;
750 
751   //  width/height of current (possibly cropped) image
752   const float iw = piece->buf_in.width;
753   const float ih = piece->buf_in.height;
754   const float uscale = data->scale / 100.0f; // user scale, from GUI in percent
755 
756   // wbase, hbase are the base width and height, this is the multiplicator used for the offset computing
757   // scale is the scale of the watermark itself and is used only to render it.
758 
759   float wbase, hbase, scale;
760 
761   if(data->sizeto == DT_SCALE_IMAGE)
762   {
763     // in image mode, the wbase and hbase are just the image width and height
764     wbase = iw;
765     hbase = ih;
766     if(dimension.width > dimension.height)
767       scale = (iw * roi_out->scale) / dimension.width;
768     else
769       scale = (ih * roi_out->scale) / dimension.height;
770   }
771   else
772   {
773     // in larger/smaller side mode, set wbase and hbase to the largest or smallest side of the image
774     const float larger = dimension.width > dimension.height
775       ? (float)dimension.width
776       : (float)dimension.height;
777 
778     if(iw > ih)
779     {
780       wbase = hbase = (data->sizeto == DT_SCALE_LARGER_BORDER) ? iw : ih;
781       scale = (data->sizeto == DT_SCALE_LARGER_BORDER) ? (iw / larger) : (ih / larger);
782     }
783     else
784     {
785       wbase = hbase = (data->sizeto == DT_SCALE_SMALLER_BORDER) ? iw : ih;
786       scale = (data->sizeto == DT_SCALE_SMALLER_BORDER) ? (iw / larger) : (ih / larger);
787     }
788     scale *= roi_out->scale;
789   }
790 
791   scale *= uscale;
792 
793   // compute the width and height of the SVG object in image dimension. This is only used to properly
794   // layout the watermark based on the alignment.
795 
796   float svg_width, svg_height;
797 
798   if(dimension.width > dimension.height)
799   {
800     if(data->sizeto == DT_SCALE_IMAGE || (iw > ih && data->sizeto == DT_SCALE_LARGER_BORDER)
801        || (iw < ih && data->sizeto == DT_SCALE_SMALLER_BORDER))
802     {
803       svg_width = iw * uscale;
804       svg_height = dimension.height * (svg_width / dimension.width);
805     }
806     else
807     {
808       svg_width = ih * uscale;
809       svg_height = dimension.height * (svg_width / dimension.width);
810     }
811   }
812   else
813   {
814     if(data->sizeto == DT_SCALE_IMAGE || (ih > iw && data->sizeto == DT_SCALE_LARGER_BORDER)
815        || (ih < iw && data->sizeto == DT_SCALE_SMALLER_BORDER))
816     {
817       svg_height = ih * uscale;
818       svg_width = dimension.width * (svg_height / dimension.height);
819     }
820     else
821     {
822       svg_height = iw * uscale;
823       svg_width = dimension.width * (svg_height / dimension.height);
824     }
825   }
826 
827   /* For the rotation we need an extra cairo image as rotations are buggy  via rsvg_handle_render_cairo.
828      distortions and blurred images are obvious but you also can easily have crashes.
829   */
830 
831   float svg_offset_x = 0;
832   float svg_offset_y = 0;
833   if(type == DT_WTM_SVG)
834   {
835     /* the svg_offsets allow safe text boxes as they might render out of the dimensions */
836     svg_offset_x = ceilf(3.0f * scale);
837     svg_offset_y = ceilf(3.0f * scale);
838 
839     const int watermark_width  = (int)((dimension.width  * scale) + 3* svg_offset_x);
840     const int watermark_height = (int)((dimension.height * scale) + 3* svg_offset_y) ;
841 
842     const int stride_two = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, watermark_width);
843     image_two = (guint8 *)g_malloc0_n(watermark_height, stride_two);
844     surface_two = cairo_image_surface_create_for_data(image_two, CAIRO_FORMAT_ARGB32, watermark_width,
845                                                                    watermark_height, stride_two);
846     if((cairo_surface_status(surface_two) != CAIRO_STATUS_SUCCESS) || (image_two == NULL))
847     {
848       fprintf(stderr, "[watermark] cairo surface 2 error: %s\n",
849               cairo_status_to_string(cairo_surface_status(surface_two)));
850       cairo_surface_destroy(surface);
851       g_object_unref(svg);
852       g_free(image);
853       g_free(image_two);
854       dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
855       dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
856       return;
857     }
858   }
859 
860   /* create cairo context and setup transformation/scale */
861   cairo_t *cr = cairo_create(surface);
862   /* create cairo context for the scaled watermark */
863   cairo_t *cr_two = cairo_create(surface_two);
864 
865   // compute bounding box of rotated watermark
866   const float bb_width = fabsf(svg_width * cosf(angle)) + fabsf(svg_height * sinf(angle));
867   const float bb_height = fabsf(svg_width * sinf(angle)) + fabsf(svg_height * cosf(angle));
868   const float bX = bb_width / 2.0f - svg_width / 2.0f;
869   const float bY = bb_height / 2.0f - svg_height / 2.0f;
870 
871   // compute translation for the given alignment in image dimension
872 
873   float ty = 0, tx = 0;
874   if(data->alignment >= 0 && data->alignment < 3) // Align to verttop
875     ty = bY;
876   else if(data->alignment >= 3 && data->alignment < 6) // Align to vertcenter
877     ty = (ih / 2.0f) - (svg_height / 2.0f);
878   else if(data->alignment >= 6 && data->alignment < 9) // Align to vertbottom
879     ty = ih - svg_height - bY;
880 
881   if(data->alignment == 0 || data->alignment == 3 || data->alignment == 6)
882     tx = bX;
883   else if(data->alignment == 1 || data->alignment == 4 || data->alignment == 7)
884     tx = (iw / 2.0f) - (svg_width / 2.0f);
885   else if(data->alignment == 2 || data->alignment == 5 || data->alignment == 8)
886     tx = iw - svg_width - bX;
887 
888   // translate to position
889   cairo_translate(cr, -roi_in->x, -roi_in->y);
890 
891   // add translation for the given value in GUI (xoffset,yoffset)
892   tx += data->xoffset * wbase;
893   ty += data->yoffset * hbase;
894 
895   cairo_translate(cr, tx * roi_out->scale, ty * roi_out->scale);
896 
897   // compute the center of the svg to rotate from the center
898   const float cX = svg_width / 2.0f * roi_out->scale;
899   const float cY = svg_height / 2.0f * roi_out->scale;
900 
901   cairo_translate(cr, cX, cY);
902   cairo_rotate(cr, angle);
903   cairo_translate(cr, -cX, -cY);
904 
905   // now set proper scale and translationfor the watermark itself
906   cairo_translate(cr_two, svg_offset_x, svg_offset_y);
907 
908   switch(type)
909   {
910     case DT_WTM_SVG:
911       cairo_scale(cr_two, scale, scale);
912       /* render svg into surface*/
913       dt_render_svg(svg, cr_two, dimension.width, dimension.height, 0, 0);
914       break;
915     case DT_WTM_PNG:
916       cairo_scale(cr, scale, scale);
917       break;
918   }
919   cairo_surface_flush(surface_two);
920 
921   // paint the watermark
922   cairo_set_source_surface(cr, surface_two, -svg_offset_x, -svg_offset_y);
923   cairo_paint(cr);
924 
925   // no more non-thread safe rsvg usage
926   dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
927 
928   cairo_destroy(cr);
929   cairo_destroy(cr_two);
930 
931   /* ensure that all operations on surface finishing up */
932   cairo_surface_flush(surface);
933 
934   /* render surface on output */
935   guint8 *sd = image;
936   const float opacity = data->opacity / 100.0f;
937 #ifdef _OPENMP
938 #pragma omp parallel for default(none) \
939   dt_omp_firstprivate(roi_out, in, out, sd, opacity, ch)   \
940   schedule(static)
941 #endif
942   for(int j = 0; j < roi_out->height * roi_out->width; j++)
943   {
944     float *const i = in + ch*j;
945     float *const o = out + ch*j;
946     guint8 *const s = sd + 4*j;
947     const float alpha = (s[3] / 255.0f) * opacity;
948     /* svg uses a premultiplied alpha, so only use opacity for the blending */
949     o[0] = ((1.0f - alpha) * i[0]) + (opacity * (s[2] / 255.0f));
950     o[1] = ((1.0f - alpha) * i[1]) + (opacity * (s[1] / 255.0f));
951     o[2] = ((1.0f - alpha) * i[2]) + (opacity * (s[0] / 255.0f));
952     o[3] = in[3];
953     }
954 
955   /* clean up */
956   cairo_surface_destroy(surface);
957   cairo_surface_destroy(surface_two);
958   g_free(image);
959   if(type == DT_WTM_SVG)
960   {
961     g_free(image_two);
962     g_object_unref(svg);
963   }
964 
965 }
966 
watermark_callback(GtkWidget * tb,gpointer user_data)967 static void watermark_callback(GtkWidget *tb, gpointer user_data)
968 {
969   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
970   dt_iop_watermark_gui_data_t *g = (dt_iop_watermark_gui_data_t *)self->gui_data;
971 
972   if(darktable.gui->reset) return;
973   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
974   memset(p->filename, 0, sizeof(p->filename));
975   int n = dt_bauhaus_combobox_get(g->watermarks);
976   g_strlcpy(p->filename, (char *)g_list_nth_data(g->watermarks_filenames, n), sizeof(p->filename));
977   _text_color_font_set_sensitive(g, p->filename);
978   dt_dev_add_history_item(darktable.develop, self, TRUE);
979 }
980 
color_picker_apply(dt_iop_module_t * self,GtkWidget * picker,dt_dev_pixelpipe_iop_t * piece)981 void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_iop_t *piece)
982 {
983   dt_iop_watermark_gui_data_t *g = (dt_iop_watermark_gui_data_t *)self->gui_data;
984   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
985 
986   if(fabsf(p->color[0] - self->picked_color[0]) < 0.0001f
987      && fabsf(p->color[1] - self->picked_color[1]) < 0.0001f
988      && fabsf(p->color[2] - self->picked_color[2]) < 0.0001f)
989   {
990     // interrupt infinite loops
991     return;
992   }
993 
994   GdkRGBA c = {.red   = self->picked_color[0],
995                .green = self->picked_color[1],
996                .blue  = self->picked_color[2],
997                .alpha = 1.0 };
998 
999   p->color[0] = self->picked_color[0];
1000   p->color[1] = self->picked_color[1];
1001   p->color[2] = self->picked_color[2];
1002   gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->colorpick), &c);
1003 
1004   dt_dev_add_history_item(darktable.develop, self, TRUE);
1005 }
1006 
load_watermarks(const char * basedir,dt_iop_watermark_gui_data_t * g)1007 static void load_watermarks(const char *basedir, dt_iop_watermark_gui_data_t *g)
1008 {
1009   GList *files = NULL;
1010   char *watermarks_dir = g_build_filename(basedir, "watermarks", NULL);
1011   GDir *dir = g_dir_open(watermarks_dir, 0, NULL);
1012   if(dir)
1013   {
1014     const gchar *d_name;
1015     while((d_name = g_dir_read_name(dir)))
1016       files = g_list_prepend(files, g_strdup(d_name));
1017     g_dir_close(dir);
1018   }
1019 
1020   files = g_list_sort(files, (GCompareFunc)g_strcmp0);
1021   for(GList *iter = files; iter; iter = g_list_next(iter))
1022   {
1023     char *filename = iter->data;
1024     gchar *extension = strrchr(filename, '.');
1025     if(extension)
1026     {
1027       // we add only supported file formats to the list
1028       if(!g_ascii_strcasecmp(extension, ".svg") || !g_ascii_strcasecmp(extension, ".png"))
1029       {
1030         // remember the whole filename for later
1031         g->watermarks_filenames = g_list_append(g->watermarks_filenames, g_strdup(filename));
1032         // ... and build string shown in the gui
1033         *extension = '\0';
1034         extension++;
1035         gchar *text = g_strdup_printf("%s (%s)", filename, extension);
1036         dt_bauhaus_combobox_add(g->watermarks, text);
1037         g_free(text);
1038       }
1039     }
1040   }
1041 
1042   g_list_free_full(files, g_free);
1043   g_free(watermarks_dir);
1044 }
1045 
refresh_watermarks(dt_iop_module_t * self)1046 static void refresh_watermarks(dt_iop_module_t *self)
1047 {
1048   dt_iop_watermark_gui_data_t *g = (dt_iop_watermark_gui_data_t *)self->gui_data;
1049   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
1050 
1051   g_signal_handlers_block_by_func(g->watermarks, watermark_callback, self);
1052 
1053   // Clear combobox...
1054   dt_bauhaus_combobox_clear(g->watermarks);
1055   g_list_free_full(g->watermarks_filenames, g_free);
1056   g->watermarks_filenames = NULL;
1057 
1058   // check watermarkdir and update combo with entries...
1059   gchar configdir[PATH_MAX] = { 0 };
1060   gchar datadir[PATH_MAX] = { 0 };
1061   dt_loc_get_datadir(datadir, sizeof(datadir));
1062   dt_loc_get_user_config_dir(configdir, sizeof(configdir));
1063 
1064   load_watermarks(datadir, g);
1065   load_watermarks(configdir, g);
1066 
1067   _combo_box_set_active_text(g, p->filename);
1068 
1069   g_signal_handlers_unblock_by_func(g->watermarks, watermark_callback, self);
1070 }
1071 
refresh_callback(GtkWidget * tb,gpointer user_data)1072 static void refresh_callback(GtkWidget *tb, gpointer user_data)
1073 {
1074   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1075   refresh_watermarks(self);
1076 }
1077 
alignment_callback(GtkWidget * tb,gpointer user_data)1078 static void alignment_callback(GtkWidget *tb, gpointer user_data)
1079 {
1080   int index = -1;
1081   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1082   dt_iop_watermark_gui_data_t *g = (dt_iop_watermark_gui_data_t *)self->gui_data;
1083 
1084   if(darktable.gui->reset) return;
1085   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
1086 
1087 
1088   for(int i = 0; i < 9; i++)
1089   {
1090     /* block signal handler */
1091     g_signal_handlers_block_by_func(g->align[i], alignment_callback, user_data);
1092 
1093     if(GTK_WIDGET(g->align[i]) == tb)
1094     {
1095       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->align[i]), TRUE);
1096       index = i;
1097     }
1098     else
1099       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->align[i]), FALSE);
1100 
1101     /* unblock signal handler */
1102     g_signal_handlers_unblock_by_func(g->align[i], alignment_callback, user_data);
1103   }
1104   p->alignment = index;
1105   dt_dev_add_history_item(darktable.develop, self, TRUE);
1106 }
1107 
text_callback(GtkWidget * entry,gpointer user_data)1108 static void text_callback(GtkWidget *entry, gpointer user_data)
1109 {
1110   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1111   if(darktable.gui->reset) return;
1112   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
1113   g_strlcpy(p->text, gtk_entry_get_text(GTK_ENTRY(entry)), sizeof(p->text));
1114   dt_conf_set_string("plugins/darkroom/watermark/text", p->text);
1115   dt_dev_add_history_item(darktable.develop, self, TRUE);
1116 }
1117 
colorpick_color_set(GtkColorButton * widget,gpointer user_data)1118 static void colorpick_color_set(GtkColorButton *widget, gpointer user_data)
1119 {
1120   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1121   if(darktable.gui->reset) return;
1122   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
1123 
1124   GdkRGBA c;
1125   gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &c);
1126   p->color[0] = c.red;
1127   p->color[1] = c.green;
1128   p->color[2] = c.blue;
1129 
1130   dt_conf_set_float("plugins/darkroom/watermark/color_red", p->color[0]);
1131   dt_conf_set_float("plugins/darkroom/watermark/color_green", p->color[1]);
1132   dt_conf_set_float("plugins/darkroom/watermark/color_blue", p->color[2]);
1133   dt_dev_add_history_item(darktable.develop, self, TRUE);
1134 }
1135 
fontsel_callback(GtkWidget * button,gpointer user_data)1136 static void fontsel_callback(GtkWidget *button, gpointer user_data)
1137 {
1138   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1139   if(darktable.gui->reset) return;
1140   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
1141 
1142   gchar *fontname = gtk_font_chooser_get_font(GTK_FONT_CHOOSER(button));
1143   g_strlcpy(p->font, fontname, sizeof(p->font));
1144   g_free(fontname);
1145   dt_conf_set_string("plugins/darkroom/watermark/font", p->font);
1146   dt_dev_add_history_item(darktable.develop, self, TRUE);
1147 }
1148 
commit_params(struct dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1149 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
1150                    dt_dev_pixelpipe_iop_t *piece)
1151 {
1152   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)p1;
1153   dt_iop_watermark_data_t *d = (dt_iop_watermark_data_t *)piece->data;
1154 
1155   d->opacity = p->opacity;
1156   d->scale = p->scale;
1157   d->rotate = p->rotate;
1158   d->xoffset = p->xoffset;
1159   d->yoffset = p->yoffset;
1160   d->alignment = p->alignment;
1161   d->sizeto = p->sizeto;
1162   memset(d->filename, 0, sizeof(d->filename));
1163   g_strlcpy(d->filename, p->filename, sizeof(d->filename));
1164   memset(d->text, 0, sizeof(d->text));
1165   g_strlcpy(d->text, p->text, sizeof(d->text));
1166   for (int k=0; k<3; k++)
1167     d->color[k] = p->color[k];
1168   memset(d->font, 0, sizeof(d->font));
1169   g_strlcpy(d->font, p->font, sizeof(d->font));
1170 
1171 // fprintf(stderr, "Commit params: %s...\n",d->filename);
1172 }
1173 
init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1174 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
1175 {
1176   piece->data = malloc(sizeof(dt_iop_watermark_data_t));
1177 }
1178 
cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1179 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
1180 {
1181   free(piece->data);
1182   piece->data = NULL;
1183 }
1184 
1185 
gui_update(struct dt_iop_module_t * self)1186 void gui_update(struct dt_iop_module_t *self)
1187 {
1188   dt_iop_watermark_gui_data_t *g = (dt_iop_watermark_gui_data_t *)self->gui_data;
1189   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
1190   dt_bauhaus_slider_set(g->opacity, p->opacity);
1191   dt_bauhaus_slider_set_soft(g->scale, p->scale);
1192   dt_bauhaus_slider_set(g->rotate, p->rotate);
1193   dt_bauhaus_slider_set(g->x_offset, p->xoffset);
1194   dt_bauhaus_slider_set(g->y_offset, p->yoffset);
1195   for(int i = 0; i < 9; i++)
1196   {
1197     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->align[i]), FALSE);
1198   }
1199   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->align[p->alignment]), TRUE);
1200   _combo_box_set_active_text(g, p->filename);
1201   dt_bauhaus_combobox_set(g->sizeto, p->sizeto);
1202   gtk_entry_set_text(GTK_ENTRY(g->text), p->text);
1203   GdkRGBA color = (GdkRGBA){.red = p->color[0], .green = p->color[1], .blue = p->color[2], .alpha = 1.0 };
1204   gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->colorpick), &color);
1205   gtk_font_chooser_set_font(GTK_FONT_CHOOSER(g->fontsel), p->font);
1206 }
1207 
init(dt_iop_module_t * module)1208 void init(dt_iop_module_t *module)
1209 {
1210   dt_iop_default_init(module);
1211 
1212   dt_iop_watermark_params_t *d = module->default_params;
1213 
1214   g_strlcpy(d->filename, "darktable.svg", sizeof(d->filename));
1215   g_strlcpy(d->font, "DejaVu Sans 10", sizeof(d->font));
1216 }
1217 
gui_init(struct dt_iop_module_t * self)1218 void gui_init(struct dt_iop_module_t *self)
1219 {
1220   dt_iop_watermark_gui_data_t *g = IOP_GUI_ALLOC(watermark);
1221   dt_iop_watermark_params_t *p = (dt_iop_watermark_params_t *)self->params;
1222 
1223   self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
1224 
1225   GtkWidget *label = dt_ui_section_label_new(_("content"));
1226   GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(label));
1227   gtk_style_context_add_class(context, "section_label_top");
1228   gtk_box_pack_start(GTK_BOX(self->widget), label, TRUE, TRUE, 0);
1229 
1230   GtkGrid *grid = GTK_GRID(gtk_grid_new());
1231   gtk_grid_set_row_spacing(grid, DT_BAUHAUS_SPACE);
1232   gtk_grid_set_column_spacing(grid, DT_PIXEL_APPLY_DPI(10));
1233   int line = 0;
1234 
1235   // Add the marker combobox
1236   gchar configdir[PATH_MAX] = { 0 };
1237   gchar datadir[PATH_MAX] = { 0 };
1238   dt_loc_get_datadir(datadir, sizeof(datadir));
1239   dt_loc_get_user_config_dir(configdir, sizeof(configdir));
1240 
1241   label = dtgtk_reset_label_new(_("marker"), self, &p->filename, sizeof(p->filename));
1242   g->watermarks = dt_bauhaus_combobox_new(self);
1243   gtk_widget_set_hexpand(GTK_WIDGET(g->watermarks), TRUE);
1244   char *tooltip = g_strdup_printf(_("SVG watermarks in %s/watermarks or %s/watermarks"), configdir, datadir);
1245   gtk_widget_set_tooltip_text(g->watermarks, tooltip);
1246   g_free(tooltip);
1247   g->refresh = dtgtk_button_new(dtgtk_cairo_paint_refresh, CPF_STYLE_FLAT, NULL);
1248 
1249   gtk_grid_attach(grid, label, 0, line++, 1, 1);
1250   gtk_grid_attach_next_to(grid, g->watermarks, label, GTK_POS_RIGHT, 1, 1);
1251   gtk_grid_attach_next_to(grid, g->refresh, g->watermarks, GTK_POS_RIGHT, 1, 1);
1252 
1253   // Watermark color
1254   float red = dt_conf_get_float("plugins/darkroom/watermark/color_red");
1255   float green = dt_conf_get_float("plugins/darkroom/watermark/color_green");
1256   float blue = dt_conf_get_float("plugins/darkroom/watermark/color_blue");
1257   GdkRGBA color = (GdkRGBA){.red = red, .green = green, .blue = blue, .alpha = 1.0 };
1258 
1259   label = dtgtk_reset_label_new(_("color"), self, &p->color, 3 * sizeof(float));
1260   g->colorpick = gtk_color_button_new_with_rgba(&color);
1261   gtk_widget_set_tooltip_text(g->colorpick, _("watermark color, tag:\n$(WATERMARK_COLOR)"));
1262   gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->colorpick), FALSE);
1263   gtk_color_button_set_title(GTK_COLOR_BUTTON(g->colorpick), _("select watermark color"));
1264   g->color_picker_button = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
1265   gtk_widget_set_tooltip_text(GTK_WIDGET(g->color_picker_button), _("pick color from image"));
1266 
1267   gtk_grid_attach(grid, label, 0, line++, 1, 1);
1268   gtk_grid_attach_next_to(grid, g->colorpick, label, GTK_POS_RIGHT, 1, 1);
1269   gtk_grid_attach_next_to(grid, g->color_picker_button, g->colorpick, GTK_POS_RIGHT, 1, 1);
1270 
1271   // Simple text
1272   label = dt_ui_label_new(_("text"));
1273   g->text = gtk_entry_new();
1274   gtk_entry_set_width_chars(GTK_ENTRY(g->text), 1);
1275   gtk_widget_set_tooltip_text(g->text, _("text string, tag:\n$(WATERMARK_TEXT)"));
1276   const char *str = dt_conf_get_string_const("plugins/darkroom/watermark/text");
1277   gtk_entry_set_text(GTK_ENTRY(g->text), str);
1278 
1279   gtk_grid_attach(grid, label, 0, line++, 1, 1);
1280   gtk_grid_attach_next_to(grid, g->text, label, GTK_POS_RIGHT, 2, 1);
1281 
1282   // Text font
1283   label = dtgtk_reset_label_new(_("font"), self, &p->font, sizeof(p->font));
1284   str = dt_conf_get_string_const("plugins/darkroom/watermark/font");
1285   g->fontsel = gtk_font_button_new_with_font(str==NULL?"DejaVu Sans 10":str);
1286   GtkWidget *child = dt_gui_container_first_child(GTK_CONTAINER(gtk_bin_get_child(GTK_BIN(g->fontsel))));
1287   gtk_label_set_ellipsize(GTK_LABEL(child), PANGO_ELLIPSIZE_MIDDLE);
1288   gtk_widget_set_tooltip_text(g->fontsel, _("text font, tags:\n$(WATERMARK_FONT_FAMILY)\n"
1289                                             "$(WATERMARK_FONT_STYLE)\n$(WATERMARK_FONT_WEIGHT)"));
1290   gtk_font_button_set_show_size (GTK_FONT_BUTTON(g->fontsel), FALSE);
1291 
1292   gtk_grid_attach(grid, label, 0, line++, 1, 1);
1293   gtk_grid_attach_next_to(grid, g->fontsel, label, GTK_POS_RIGHT, 2, 1);
1294 
1295   gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(grid), TRUE, TRUE, 0);
1296 
1297   gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_section_label_new(_("properties")), TRUE, TRUE, 0);
1298 
1299   // Add opacity/scale sliders to table
1300   g->opacity = dt_bauhaus_slider_from_params(self, N_("opacity"));
1301   dt_bauhaus_slider_set_format(g->opacity, "%.f%%");
1302   g->scale = dt_bauhaus_slider_from_params(self, N_("scale"));
1303   dt_bauhaus_slider_set_soft_max(g->scale, 100.0);
1304   dt_bauhaus_slider_set_format(g->scale, "%.f%%");
1305   g->rotate = dt_bauhaus_slider_from_params(self, "rotate");
1306   dt_bauhaus_slider_set_format(g->rotate, "%.02f°");
1307 
1308   g->sizeto = dt_bauhaus_combobox_from_params(self, "sizeto");
1309 //  dt_bauhaus_combobox_add(g->sizeto, C_("size", "image"));
1310   gtk_widget_set_tooltip_text(g->sizeto, _("size is relative to"));
1311 
1312   gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_section_label_new(_("position")), TRUE, TRUE, 0);
1313 
1314   // Create the 3x3 gtk table toggle button table...
1315   GtkWidget *bat = gtk_grid_new();
1316   label = dtgtk_reset_label_new(_("alignment"), self, &p->alignment, sizeof(p->alignment));
1317   gtk_grid_attach(GTK_GRID(bat), label, 0, 0, 1, 3);
1318   gtk_widget_set_hexpand(label, TRUE);
1319   gtk_grid_set_row_spacing(GTK_GRID(bat), DT_PIXEL_APPLY_DPI(3));
1320   gtk_grid_set_column_spacing(GTK_GRID(bat), DT_PIXEL_APPLY_DPI(3));
1321   for(int i = 0; i < 9; i++)
1322   {
1323     g->align[i] = dtgtk_togglebutton_new(dtgtk_cairo_paint_alignment, CPF_STYLE_FLAT | (CPF_SPECIAL_FLAG << i), NULL);
1324     gtk_grid_attach(GTK_GRID(bat), GTK_WIDGET(g->align[i]), 1 + i%3, i/3, 1, 1);
1325     g_signal_connect(G_OBJECT(g->align[i]), "toggled", G_CALLBACK(alignment_callback), self);
1326   }
1327 
1328   gtk_box_pack_start(GTK_BOX(self->widget), bat, FALSE, FALSE, 0);
1329 
1330   // x/y offset
1331   g->x_offset = dt_bauhaus_slider_from_params(self, "xoffset");
1332   dt_bauhaus_slider_set_digits(g->x_offset, 3);
1333   g->y_offset = dt_bauhaus_slider_from_params(self, "yoffset");
1334   dt_bauhaus_slider_set_digits(g->y_offset, 3);
1335 
1336   // Let's add some tooltips and hook up some signals...
1337   gtk_widget_set_tooltip_text(g->opacity, _("the opacity of the watermark"));
1338   gtk_widget_set_tooltip_text(g->scale, _("the scale of the watermark"));
1339   gtk_widget_set_tooltip_text(g->rotate, _("the rotation of the watermark"));
1340 
1341   refresh_watermarks(self);
1342 
1343   g_signal_connect(G_OBJECT(g->watermarks), "value-changed", G_CALLBACK(watermark_callback), self);
1344   g_signal_connect(G_OBJECT(g->refresh), "clicked", G_CALLBACK(refresh_callback), self);
1345   g_signal_connect(G_OBJECT(g->text), "changed", G_CALLBACK(text_callback), self);
1346   g_signal_connect(G_OBJECT(g->colorpick), "color-set", G_CALLBACK(colorpick_color_set), self);
1347   g_signal_connect(G_OBJECT(g->fontsel), "font-set", G_CALLBACK(fontsel_callback), self);
1348 }
1349 
gui_cleanup(struct dt_iop_module_t * self)1350 void gui_cleanup(struct dt_iop_module_t *self)
1351 {
1352   dt_iop_watermark_gui_data_t *g = (dt_iop_watermark_gui_data_t *)self->gui_data;
1353   g_list_free_full(g->watermarks_filenames, g_free);
1354   g->watermarks_filenames = NULL;
1355 
1356   IOP_GUI_FREE;
1357 }
1358 
1359 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1360 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1361 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1362