Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
utility.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2017 Tobias Ellinghaus.
4 Copyright (C) 2011 Henrik Andersson.
5 Copyright (C) 2011 johannes hanika.
6 Copyright (C) 2011 Kanstantsin Shautsou.
7 Copyright (C) 2011 Moritz Lipp.
8 Copyright (C) 2012 Jérémy Rosen.
9 Copyright (C) 2012 Richard Wonka.
10 Copyright (C) 2013 Antony Dovgal.
11 Copyright (C) 2013 Jean-Sébastien Pédron.
12 Copyright (C) 2013, 2020-2021 Pascal Obry.
13 Copyright (C) 2013 Stuart Henderson.
14 Copyright (C) 2014-2016 Roman Lebedev.
15 Copyright (C) 2015 Steven Fosdick.
16 Copyright (C) 2016-2018 Peter Budai.
17 Copyright (C) 2017, 2022 luzpaz.
18 Copyright (C) 2019, 2021 Philippe Weyland.
19 Copyright (C) 2020 Alexis Mousset.
20 Copyright (C) 2020 hatsunearu.
21 Copyright (C) 2020 Heiko Bauke.
22 Copyright (C) 2020 Hubert Kowalski.
23 Copyright (C) 2020-2021 Ralf Brown.
24 Copyright (C) 2021 Benjamin Grimm-Lebsanft.
25 Copyright (C) 2021 Christian Birzer.
26 Copyright (C) 2021 Hanno Schwalm.
27 Copyright (C) 2021 Marco Carrarini.
28 Copyright (C) 2021 parafin.
29 Copyright (C) 2021 RSL.
30 Copyright (C) 2021 wpferguson.
31 Copyright (C) 2022 Martin Bařinka.
32 Copyright (C) 2022 Nicolas Auffray.
33 Copyright (C) 2023 Luca Zulberti.
34 Copyright (C) 2024 Aurélien PIERRE.
35 Copyright (C) 2024-2025 Guillaume Stutin.
36
37 darktable is free software: you can redistribute it and/or modify
38 it under the terms of the GNU General Public License as published by
39 the Free Software Foundation, either version 3 of the License, or
40 (at your option) any later version.
41
42 darktable is distributed in the hope that it will be useful,
43 but WITHOUT ANY WARRANTY; without even the implied warranty of
44 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45 GNU General Public License for more details.
46
47 You should have received a copy of the GNU General Public License
48 along with darktable. If not, see <http://www.gnu.org/licenses/>.
49*/
50
51#include <locale.h>
52
53#include "common/darktable.h"
55#include "common/grealpath.h"
56#include "common/utility.h"
57#include "gui/gtk.h"
58
59/* getpwnam_r availability check */
60#if defined __APPLE__ || defined _POSIX_C_SOURCE >= 1 || defined _XOPEN_SOURCE || defined _BSD_SOURCE \
61 || defined _SVID_SOURCE || defined _POSIX_SOURCE || defined __DragonFly__ || defined __FreeBSD__ \
62 || defined __NetBSD__ || defined __OpenBSD__
63 #include <pwd.h>
64 #include <sys/types.h>
65 #include <unistd.h>
66#endif
67
68#ifdef _WIN32
69 #include <Windows.h>
70 #include <WinBase.h>
71 #include <FileAPI.h>
72#endif
73
74#include <math.h>
75#include <glib/gi18n.h>
76
77#include <sys/stat.h>
78#include <ctype.h>
79
80#ifdef HAVE_CONFIG_H
81 #include <config.h>
82#endif
83
84#include <librsvg/rsvg.h>
85// ugh, ugly hack. why do people break stuff all the time?
86#ifndef RSVG_CAIRO_H
87#include <librsvg/rsvg-cairo.h>
88#endif
89
90size_t safe_strlen(const char *str)
91{
92 return str ? strlen(str) : 0;
93}
94
95gchar *dt_util_dstrcat(gchar *str, const gchar *format, ...)
96{
97 va_list args;
98 gchar *ns;
99 va_start(args, format);
100 const size_t clen = str ? strlen(str) : 0;
101 const int alen = g_vsnprintf(NULL, 0, format, args);
102 const int nsize = alen + clen + 1;
103
104 /* realloc for new string */
105 ns = g_realloc(str, nsize);
106 if(IS_NULL_PTR(str)) ns[0] = '\0';
107 va_end(args);
108
109 /* append string */
110 va_start(args, format);
111 g_vsnprintf(ns + clen, alen + 1, format, args);
112 va_end(args);
113
114 ns[nsize - 1] = '\0';
115
116 return ns;
117}
118
119guint dt_util_str_occurence(const gchar *haystack, const gchar *needle)
120{
121 guint o = 0;
122 if(haystack && needle)
123 {
124 const gchar *p = haystack;
125 if((p = g_strstr_len(p, strlen(p), needle)) != NULL)
126 {
127 do
128 {
129 o++;
130 } while((p = g_strstr_len((p + 1), strlen(p + 1), needle)) != NULL);
131 }
132 }
133 return o;
134}
135
136gchar *dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
137{
138 const gint occurrences = dt_util_str_occurence(string, pattern);
139 gchar *nstring = NULL;
140
141 if(occurrences)
142 {
143 nstring = g_malloc_n(strlen(string) + (occurrences * strlen(substitute)) + 1, sizeof(gchar));
144 const gchar *pend = string + strlen(string);
145 const gchar *s = string, *p = string;
146 gchar *np = nstring;
147 if((s = g_strstr_len(s, strlen(s), pattern)) != NULL)
148 {
149 do
150 {
151 memcpy(np, p, s - p);
152 np += (s - p);
153 memcpy(np, substitute, strlen(substitute));
154 np += strlen(substitute);
155 p = s + strlen(pattern);
156 } while((s = g_strstr_len((s + 1), strlen(s + 1), pattern)) != NULL);
157 }
158 memcpy(np, p, pend - p);
159 np[pend - p] = '\0';
160 }
161 else
162 nstring = g_strdup(string); // otherwise it's a hell to decide whether to free this string later.
163 return nstring;
164}
165
166gchar *dt_util_glist_to_str(const gchar *separator, GList *items)
167{
168 if(IS_NULL_PTR(items)) return NULL;
169
170 const unsigned int count = g_list_length(items);
171 gchar *result = NULL;
172
173 // add the entries to an char* array
174 gchar **strings = g_malloc0_n(count + 1, sizeof(gchar *));
175 if(!IS_NULL_PTR(items))
176 {
177 int i = 0;
178 for(; items; items = g_list_next(items))
179 {
180 strings[i++] = items->data;
181 }
182 }
183
184 // join them into a single string
185 result = g_strjoinv(separator, strings);
186
187 // free the array
188 dt_free(strings);
189
190 return result;
191}
192
194{
195 if(IS_NULL_PTR(items)) return NULL;
196
197 gchar *last = NULL;
198 GList *last_item = NULL;
199
200 items = g_list_sort(items, (GCompareFunc)g_strcmp0);
201 GList *iter = items;
202 while(iter)
203 {
204 gchar *value = (gchar *)iter->data;
205 if(!g_strcmp0(last, value))
206 {
207 dt_free(value);
208 items = g_list_delete_link(items, iter);
209 iter = last_item;
210 }
211 else
212 {
213 last = value;
214 last_item = iter;
215 }
216 iter = g_list_next(iter);
217 }
218 return items;
219}
220
221
222gchar *dt_util_fix_path(const gchar *path)
223{
224 if(IS_NULL_PTR(path) || *path == '\0')
225 {
226 return NULL;
227 }
228
229 gchar *rpath = NULL;
230
231 /* check if path has a prepended tilde */
232 if(path[0] == '~')
233 {
234 const size_t len = strlen(path);
235 char *user = NULL;
236 int off = 1;
237
238 /* if the character after the tilde is not a slash we parse
239 * the path until the next slash to extend this part with the
240 * home directory of the specified user
241 *
242 * e.g.: ~foo will be evaluated as the home directory of the
243 * user foo */
244
245 if(len > 1 && path[1] != '/')
246 {
247 while(path[off] != '\0' && path[off] != '/')
248 {
249 ++off;
250 }
251
252 user = g_strndup(path + 1, off - 1);
253 }
254
255 gchar *home_path = dt_loc_get_home_dir(user);
256 dt_free(user);
257
258 if(IS_NULL_PTR(home_path))
259 {
260 return g_strdup(path);
261 }
262
263 rpath = g_build_filename(home_path, path + off, NULL);
264 dt_free(home_path);
265 }
266 else
267 {
268 rpath = g_strdup(path);
269 }
270
271 return rpath;
272}
273
289size_t dt_utf8_strlcpy(char *dest, const char *src, size_t n)
290{
291 register const gchar *s = src;
292 while(s - src < n && *s)
293 {
294 s = g_utf8_next_char(s);
295 }
296
297 if(s - src >= n)
298 {
299 /* We need to truncate; back up one. */
300 s = g_utf8_prev_char(s);
301 strncpy(dest, src, s - src);
302 dest[s - src] = '\0';
303 /* Find the full length for return value. */
304 while(*s)
305 {
306 s = g_utf8_next_char(s);
307 }
308 }
309 else
310 {
311 /* Plenty of room, just copy */
312 strncpy(dest, src, s - src);
313 dest[s - src] = '\0';
314 }
315 return s - src;
316}
317
318gboolean dt_util_test_image_file(const char *filename)
319{
320 if(g_access(filename, R_OK)) return FALSE;
321#ifdef _WIN32
322 struct _stati64 stats;
323
324 // the code this replaced used utf8 paths with no problem
325 // utf8 paths will not work in this context for no reason
326 // that I can figure out, but converting utf8 to utf16 works
327 // fine.
328
329 wchar_t *wfilename = g_utf8_to_utf16(filename, -1, NULL, NULL, NULL);
330 const int result = _wstati64(wfilename, &stats);
331 dt_free(wfilename);
332 if(result) return FALSE; // there was an error
333 #else
334 struct stat stats;
335 if(stat(filename, &stats)) return FALSE;
336#endif
337
338 const gboolean regular = (S_ISREG(stats.st_mode)) != 0;
339 const gboolean size_ok = stats.st_size > 0;
340 //fprintf(stderr, "ERR: regular %i, size_ok %i.\n\tfor file: %s\n", regular, size_ok, filename);
341 return regular && size_ok;
342}
343
344gboolean dt_util_test_writable_dir(const char *path)
345{
346 if(IS_NULL_PTR(path)) return FALSE;
347#ifdef _WIN32
348 struct _stati64 stats;
349
350 wchar_t *wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
351 const int result = _wstati64(wpath, &stats);
352 dt_free(wpath);
353
354 if(result)
355 { // error while testing path:
356 return FALSE;
357 }
358#else
359 struct stat stats;
360 if(stat(path, &stats)) return FALSE;
361#endif
362 if(S_ISDIR(stats.st_mode) == 0) return FALSE;
363 if(g_access(path, W_OK | X_OK) != 0) return FALSE;
364 return TRUE;
365}
366
367gboolean dt_util_dir_exist(const char *dir)
368{
369 if(IS_NULL_PTR(dir))
370 return 1;
371
372 return g_file_test(dir, G_FILE_TEST_IS_DIR);
373}
374
375gboolean dt_util_is_dir_empty(const char *dirname)
376{
377 int n = 0;
378 GDir *dir = g_dir_open(dirname, 0, NULL);
379 if(IS_NULL_PTR(dir)) // Not a directory or doesn't exist
380 return TRUE;
381 while(g_dir_read_name(dir) != NULL)
382 {
383 if(++n > 1) break;
384 }
385 g_dir_close(dir);
386 if(n == 0) // Directory Empty
387 return TRUE;
388 else
389 return FALSE;
390}
391
392gchar *dt_util_foo_to_utf8(const char *string)
393{
394 gchar *tag = NULL;
395
396 if(g_utf8_validate(string, -1, NULL)) // first check if it's utf8 already
397 tag = g_strdup(string);
398 else
399 tag = g_convert(string, -1, "UTF-8", "LATIN1", NULL, NULL, NULL); // let's try latin1
400
401 if(IS_NULL_PTR(tag)) // hmm, neither utf8 nor latin1, let's fall back to ascii and just remove everything that isn't
402 {
403 tag = g_strdup(string);
404 char *c = tag;
405 while(*c)
406 {
407 if((*c < 0x20) || (*c >= 0x7f)) *c = '?';
408 c++;
409 }
410 }
411 return tag;
412}
413
414// get easter sunday (in the western world)
415static void easter(int Y, int* month, int *day)
416{
417 const int a = Y % 19;
418 const int b = Y / 100;
419 const int c = Y % 100;
420 const int d = b / 4;
421 const int e = b % 4;
422 const int f = (b + 8) / 25;
423 const int g = (b - f + 1) / 3;
424 const int h = (19*a + b - d - g + 15) % 30;
425 const int i = c / 4;
426 const int k = c % 4;
427 const int L = (32 + 2*e + 2*i - h - k) % 7;
428 const int m = (a + 11*h + 22*L) / 451;
429 *month = (h + L - 7*m + 114) / 31;
430 *day = ((h + L - 7*m + 114) % 31) + 1;
431}
432
433// days are in [1..31], months are in [0..11], see "man localtime"
435{
436 time_t now;
437 time(&now);
438 struct tm lt;
439 localtime_r(&now, &lt);
440
441 // Halloween is active on 31.10. and 01.11.
442 if((lt.tm_mon == 9 && lt.tm_mday == 31) || (lt.tm_mon == 10 && lt.tm_mday == 1))
444
445 // Xmas is active from 24.12. until the end of the year
446 if(lt.tm_mon == 11 && lt.tm_mday >= 24) return DT_LOGO_SEASON_XMAS;
447
448 // Easter is active from 2 days before Easter Sunday until 1 day after
449 {
450 struct tm easter_sunday = lt;
451 easter(lt.tm_year+1900, &easter_sunday.tm_mon, &easter_sunday.tm_mday);
452 easter_sunday.tm_mon--;
453 easter_sunday.tm_hour = easter_sunday.tm_min = easter_sunday.tm_sec = 0;
454 easter_sunday.tm_isdst = -1;
455 time_t easter_sunday_sec = mktime(&easter_sunday);
456 // we start at midnight, so it's basically +- 2 days
457 if(llabs(easter_sunday_sec - now) <= 2 * 24 * 60 * 60) return DT_LOGO_SEASON_EASTER;
458 }
459
460 return DT_LOGO_SEASON_NONE;
461}
462
463static cairo_surface_t *_util_get_svg_img(gchar *logo, const float size)
464{
465 GError *error = NULL;
466 cairo_surface_t *surface = NULL;
467 char datadir[PATH_MAX] = { 0 };
468
469 dt_loc_get_datadir(datadir, sizeof(datadir));
470 char *dtlogo = g_build_filename(datadir, "pixmaps", logo, NULL);
471 RsvgHandle *svg = rsvg_handle_new_from_file(dtlogo, &error);
472 if(svg)
473 {
474 RsvgDimensionData dimension;
476
477 const float ppd = darktable.gui ? darktable.gui->ppd : 1.0;
478
479 const float svg_size = MAX(dimension.width, dimension.height);
480 const float factor = size > 0.0 ? size / svg_size : -1.0 * size;
481 const float final_width = dimension.width * factor * ppd,
482 final_height = dimension.height * factor * ppd;
483 const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, final_width);
484
485 guint8 *image_buffer = (guint8 *)calloc(stride * final_height, sizeof(guint8));
486 if(darktable.gui)
487 surface = dt_cairo_image_surface_create_for_data(image_buffer, CAIRO_FORMAT_ARGB32, final_width,
488 final_height, stride);
489 else // during startup we don't know ppd yet and darktable.gui isn't initialized yet.
490 surface = cairo_image_surface_create_for_data(image_buffer, CAIRO_FORMAT_ARGB32, final_width,
491 final_height, stride);
492 if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS)
493 {
494 fprintf(stderr, "warning: can't load darktable logo from SVG file `%s'\n", dtlogo);
495 cairo_surface_destroy(surface);
496 dt_free(image_buffer);
497 surface = NULL;
498 }
499 else
500 {
501 cairo_t *cr = cairo_create(surface);
502 cairo_scale(cr, factor, factor);
503 dt_render_svg(svg, cr, dimension.width, dimension.height, 0, 0);
504 cairo_destroy(cr);
505 cairo_surface_flush(surface);
506 }
507 g_object_unref(svg);
508 }
509 else
510 {
511 fprintf(stderr, "warning: can't load darktable logo from SVG file `%s'\n%s\n", dtlogo, error->message);
512 g_error_free(error);
513 }
514
515 dt_free(logo);
516 dt_free(dtlogo);
517
518 return surface;
519}
520
521cairo_surface_t *dt_util_get_logo(const float size)
522{
523 char *logo;
525 if(season != DT_LOGO_SEASON_NONE)
526 logo = g_strdup_printf("idbutton-%d.svg", (int)season);
527 else
528 logo = g_strdup("idbutton.svg");
529
530 return _util_get_svg_img(logo, size);
531}
532
533cairo_surface_t *dt_util_get_logo_text(const float size)
534{
535 return _util_get_svg_img(g_strdup("dt_text.svg"), size);
536}
537
538// the following two functions (dt_util_latitude_str and dt_util_longitude_str) were taken from libosmgpsmap
539// Copyright (C) 2013 John Stowers <john.stowers@gmail.com>
540/* these can be overwritten with versions that support
541 * localization */
542#define OSD_COORDINATES_CHR_N "N"
543#define OSD_COORDINATES_CHR_S "S"
544#define OSD_COORDINATES_CHR_E "E"
545#define OSD_COORDINATES_CHR_W "W"
546
547static const char *OSD_ELEVATION_ASL = N_("above sea level");
548static const char *OSD_ELEVATION_BSL = N_("below sea level");
549
550/* this is the classic geocaching notation */
551gchar *dt_util_latitude_str(float latitude)
552{
553 gchar *c = OSD_COORDINATES_CHR_N;
554 float integral, fractional;
555
556 if(isnan(latitude)) return NULL;
557
558 if(latitude < 0)
559 {
560 latitude = fabsf(latitude);
562 }
563
564 fractional = modff(latitude, &integral);
565
566 return g_strdup_printf("%s %02d\302\260 %06.3f'", c, (int)integral, fractional*60.0);
567}
568
569gchar *dt_util_longitude_str(float longitude)
570{
571 gchar *c = OSD_COORDINATES_CHR_E;
572 float integral, fractional;
573
574 if(isnan(longitude)) return NULL;
575
576 if(longitude < 0)
577 {
578 longitude = fabsf(longitude);
580 }
581
582 fractional = modff(longitude, &integral);
583
584 return g_strdup_printf("%s %03d\302\260 %06.3f'", c, (int)integral, fractional*60.0);
585}
586
587gchar *dt_util_elevation_str(float elevation)
588{
589 const gchar *c = OSD_ELEVATION_ASL;
590
591 if(isnan(elevation)) return NULL;
592
593 if(elevation < 0)
594 {
595 elevation = fabsf(elevation);
597 }
598
599 return g_strdup_printf("%.2f %s %s", elevation, _("m"), _(c));
600}
601
602/* a few helper functions inspired by
603 * https://projects.kde.org/projects/kde/kdegraphics/libs/libkexiv2/repository/revisions/master/entry/libkexiv2/kexiv2gps.cpp
604 */
605
606double dt_util_gps_string_to_number(const gchar *input)
607{
608 double res = NAN;
609 gchar dir = toupper(input[strlen(input) - 1]);
610 gchar **list = g_strsplit(input, ",", 0);
611 if(list)
612 {
613 if(list[2] == NULL) // format DDD,MM.mm{N|S}
614 res = g_ascii_strtoll(list[0], NULL, 10) + (g_ascii_strtod(list[1], NULL) / 60.0);
615 else if(list[3] == NULL) // format DDD,MM,SS{N|S}
616 res = g_ascii_strtoll(list[0], NULL, 10) + (g_ascii_strtoll(list[1], NULL, 10) / 60.0)
617 + (g_ascii_strtoll(list[2], NULL, 10) / 3600.0);
618 if(dir == 'S' || dir == 'W') res *= -1.0;
619 }
620 g_strfreev(list);
621 return res;
622}
623
624gboolean dt_util_gps_rationale_to_number(const double r0_1, const double r0_2, const double r1_1,
625 const double r1_2, const double r2_1, const double r2_2, char sign,
626 double *result)
627{
628 if(IS_NULL_PTR(result)) return FALSE;
629 double res = 0.0;
630 // Latitude decoding from Exif.
631 double num, den, min, sec;
632 num = r0_1;
633 den = r0_2;
634 if(den == 0) return FALSE;
635 res = num / den;
636
637 num = r1_1;
638 den = r1_2;
639 if(den == 0) return FALSE;
640 min = num / den;
641 if(min != -1.0) res += min / 60.0;
642
643 num = r2_1;
644 den = r2_2;
645 if(den == 0)
646 {
647 // be relaxed and accept 0/0 seconds. See #246077.
648 if(num == 0)
649 den = 1;
650 else
651 return FALSE;
652 }
653 sec = num / den;
654 if(sec != -1.0) res += sec / 3600.0;
655
656 if(sign == 'S' || sign == 'W') res *= -1.0;
657
658 *result = res;
659 return TRUE;
660}
661
662gboolean dt_util_gps_elevation_to_number(const double r_1, const double r_2, char sign, double *result)
663{
664 if(IS_NULL_PTR(result)) return FALSE;
665 double res = 0.0;
666 // Altitude decoding from Exif.
667 const double num = r_1;
668 const double den = r_2;
669 if(den == 0) return FALSE;
670 res = num / den;
671
672 if(sign != '0') res *= -1.0;
673
674 *result = res;
675 return TRUE;
676}
677
678
679// make paths absolute and try to normalize on Windows. also deal with character encoding on Windows.
680gchar *dt_util_normalize_path(const gchar *_input)
681{
682#ifdef _WIN32
683 gchar *input;
684 if(g_utf8_validate(_input, -1, NULL))
685 input = g_strdup(_input);
686 else
687 {
688 input = g_locale_to_utf8(_input, -1, NULL, NULL, NULL);
689 if(IS_NULL_PTR(input)) return NULL;
690 }
691#else
692 const gchar *input = _input;
693#endif
694
695 gchar *filename = g_filename_from_uri(input, NULL, NULL);
696
697 if(!filename)
698 {
699 if(g_str_has_prefix(input, "file://")) // in this case we should take care of %XX encodings in the string
700 // (for example %20 = ' ')
701 {
702 input += strlen("file://");
703 filename = g_uri_unescape_string(input, NULL);
704 }
705 else
706 filename = g_strdup(input);
707 }
708
709#ifdef _WIN32
710 dt_free(input);
711#endif
712
713 if(g_path_is_absolute(filename) == FALSE)
714 {
715 char *current_dir = g_get_current_dir();
716 char *tmp_filename = g_build_filename(current_dir, filename, NULL);
717 dt_free(filename);
718 filename = g_realpath(tmp_filename);
719 if(IS_NULL_PTR(filename))
720 {
721 dt_free(current_dir);
722 dt_free(tmp_filename);
723 dt_free(filename);
724 return NULL;
725 }
726 dt_free(current_dir);
727 dt_free(tmp_filename);
728 }
729
730#ifdef _WIN32
731 // on Windows filenames are case insensitive, so we can end up with an arbitrary number of different spellings for the same file.
732 // another problem is that path separators can either be / or \ leading to even more problems.
733
734 // TODO:
735 // this handles filenames in the formats <drive letter>:\path\to\file or \\host-name\share-name\file
736 // some other formats like \Device\... are not supported
737
738 GFile *gfile = g_file_new_for_path(filename);
739 dt_free(filename);
740 if(IS_NULL_PTR(gfile))
741 return NULL;
742 filename = g_file_get_path(gfile);
743 g_object_unref(gfile);
744 if(!filename)
745 return NULL;
746
747 const char first = g_ascii_toupper(filename[0]);
748 if(first >= 'A' && first <= 'Z' && filename[1] == ':') // path format is <drive letter>:\path\to\file
749 {
750 filename[0] = first;
751 return filename;
752 }
753 else if(first == '\\' && filename[1] == '\\') // path format is \\host-name\share-name\file
754 return filename;
755 else
756 {
757 dt_free(filename);
758 return NULL;
759 }
760#endif
761
762 return filename;
763}
764
765#ifdef WIN32
766// returns TRUE if the path is a Windows UNC (\\server\share\...\file)
767const gboolean dt_util_path_is_UNC(const gchar *filename)
768{
769 return filename[0] == G_DIR_SEPARATOR && filename[1] == G_DIR_SEPARATOR;
770}
771#endif
772
773// gets the directory components of a file name, like g_path_get_dirname(), but works also with Windows networks paths (\\hostname\share\file)
774gchar *dt_util_path_get_dirname(const gchar *filename)
775{
776 gchar *dirname = g_path_get_dirname(filename);
777
778 /* Remove trailing slash, as g_path_get_dirname() leaves it for Windows UNC and this messes up film roll name */
779 if(dirname[0])
780 {
781 int last = strlen(dirname) - 1;
782 if(G_IS_DIR_SEPARATOR(dirname[last]))
783 dirname[last] = '\0';
784 }
785 return dirname;
786}
787
788
789GDateTime *dt_util_get_file_datetime(const char *const path)
790{
791 if(IS_NULL_PTR(path)) return NULL;
792
793 GFile *file = g_file_new_for_path(path);
794 GError *error = NULL;
795 GFileInfo *info = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_TIME_MODIFIED,
796 G_FILE_QUERY_INFO_NONE, NULL, &error);
797 if(IS_NULL_PTR(info))
798 {
799 if(error) g_error_free(error);
800 g_object_unref(file);
801 return NULL;
802 }
803
804 const guint64 datetime = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
805 g_object_unref(file);
806 g_object_unref(info);
807 return g_date_time_new_from_unix_local(datetime);
808}
809
810
811guint dt_util_string_count_char(const char *text, const char needle)
812{
813 guint count = 0;
814 while(text[0])
815 {
816 if(text[0] == needle) count ++;
817 text ++;
818 }
819 return count;
820}
821
823{
824 const struct lconv *currentLocalConv = localeconv();
825 const gchar loc_decimal_point = currentLocalConv->decimal_point[0];
826 const gchar *en_decimal_point = ".";
827 g_strdelimit(data, en_decimal_point, loc_decimal_point);
828}
829
830GList *dt_util_str_to_glist(const gchar *separator, const gchar *text)
831{
832 if(IS_NULL_PTR(text)) return NULL;
833 GList *list = NULL;
834 gchar *item = NULL;
835 gchar *entry = g_strdup(text);
836 gchar *prev = entry;
837 int len = strlen(prev);
838 while (len)
839 {
840 gchar *next = g_strstr_len(prev, -1, separator);
841 if (next)
842 {
843 const gchar c = next[0];
844 next[0] = '\0';
845 item = g_strdup(prev);
846 next[0] = c;
847 prev = next + strlen(separator);
848 len = strlen(prev);
849 list = g_list_prepend(list, item);
850 if(!len) list = g_list_prepend(list, g_strdup(""));
851 }
852 else
853 {
854 item = g_strdup(prev);
855 len = 0;
856 list = g_list_prepend(list, item);
857 }
858 }
859 list = g_list_reverse(list);
860 dt_free(entry);
861 return list;
862}
863
864// format exposure time given in seconds to a string in a unified way
865char *dt_util_format_exposure(const float exposuretime)
866{
867 char *result = NULL;
868 if(exposuretime >= 1.0f)
869 {
870 if(nearbyintf(exposuretime) == exposuretime)
871 result = g_strdup_printf("%.0f\"", exposuretime);
872 else
873 result = g_strdup_printf("%.1f\"", exposuretime);
874 }
875 /* want to catch everything below 0.3 seconds */
876 else if(exposuretime < 0.29f)
877 result = g_strdup_printf("1/%.0f", 1.0 / exposuretime);
878
879 /* catch 1/2, 1/3 */
880 else if(nearbyintf(1.0f / exposuretime) == 1.0f / exposuretime)
881 result = g_strdup_printf("1/%.0f", 1.0 / exposuretime);
882
883 /* catch 1/1.3, 1/1.6, etc. */
884 else if(10 * nearbyintf(10.0f / exposuretime) == nearbyintf(100.0f / exposuretime))
885 result = g_strdup_printf("1/%.1f", 1.0 / exposuretime);
886
887 else
888 result = g_strdup_printf("%.1f\"", exposuretime);
889
890 return result;
891}
892
893char *dt_read_file(const char *const filename, size_t *filesize)
894{
895 if (filesize) *filesize = 0;
896 FILE *fd = g_fopen(filename, "rb");
897 if(IS_NULL_PTR(fd)) return NULL;
898
899 fseek(fd, 0, SEEK_END);
900 const size_t end = ftell(fd);
901 rewind(fd);
902
903 char *content = (char *)malloc(sizeof(char) * end);
904 if(IS_NULL_PTR(content)) return NULL;
905
906 const size_t count = fread(content, sizeof(char), end, fd);
907 fclose(fd);
908 if (count == end)
909 {
910 if (filesize) *filesize = end;
911 return content;
912 }
913 dt_free(content);
914 return NULL;
915}
916
917void dt_copy_file(const char *const sourcefile, const char *dst)
918{
919 char *content = NULL;
920 FILE *fin = g_fopen(sourcefile, "rb");
921 FILE *fout = g_fopen(dst, "wb");
922
923 if(fin && fout)
924 {
925 fseek(fin, 0, SEEK_END);
926 const size_t end = ftell(fin);
927 rewind(fin);
928 content = (char *)g_malloc_n(end, sizeof(char));
929 if(IS_NULL_PTR(content)) goto END;
930 if(fread(content, sizeof(char), end, fin) != end) goto END;
931 if(fwrite(content, sizeof(char), end, fout) != end) goto END;
932 }
933
934END:
935 if(!IS_NULL_PTR(fout)) fclose(fout);
936 if(!IS_NULL_PTR(fin)) fclose(fin);
937
938 dt_free(content);
939}
940
941void dt_copy_resource_file(const char *src, const char *dst)
942{
943 char share[PATH_MAX] = { 0 };
944 dt_loc_get_datadir(share, sizeof(share));
945 gchar *sourcefile = g_build_filename(share, src, NULL);
946 dt_copy_file(sourcefile, dst);
947 dt_free(sourcefile);
948}
949
950RsvgDimensionData dt_get_svg_dimension(RsvgHandle *svg)
951{
952 RsvgDimensionData dimension;
953 // rsvg_handle_get_dimensions has been deprecated in librsvg 2.52
954 #if LIBRSVG_CHECK_VERSION(2,52,0)
955 double width;
956 double height;
957 if(rsvg_handle_get_intrinsic_size_in_pixels(svg, &width, &height)) //only works if SVG document has size specified
958 {
959 dimension.width = lround(width);
960 dimension.height = lround(height);
961 }
962 else
963 {
964#define VIEWPORT_SIZE 32767 //use maximum cairo surface size to have enough precision when size is converted to int
965 const RsvgRectangle viewport = {
966 .x = 0,
967 .y = 0,
968 .width = VIEWPORT_SIZE,
969 .height = VIEWPORT_SIZE,
970 };
971#undef VIEWPORT_SIZE
972 RsvgRectangle rectangle;
973 rsvg_handle_get_geometry_for_layer(svg, NULL, &viewport, NULL, &rectangle, NULL);
974 dimension.width = lround(rectangle.width);
975 dimension.height = lround(rectangle.height);
976 }
977 #else
978 rsvg_handle_get_dimensions(svg, &dimension);
979 #endif
980 return dimension;
981}
982
983void dt_render_svg(RsvgHandle *svg, cairo_t *cr, double width, double height, double offset_x, double offset_y)
984{
985 // rsvg_handle_render_cairo has been deprecated in librsvg 2.52
986 #if LIBRSVG_CHECK_VERSION(2,52,0)
987 RsvgRectangle viewport = {
988 .x = offset_x,
989 .y = offset_y,
990 .width = width,
991 .height = height,
992 };
993 rsvg_handle_render_document(svg, cr, &viewport, NULL);
994 #else
995 rsvg_handle_render_cairo(svg, cr);
996 #endif
997}
998
999// check if the path + basenames are the same (<=> only differ by the extension)
1000gboolean dt_has_same_path_basename(const char *filename1, const char *filename2)
1001{
1002 // assume both filenames have an extension
1003 if(!filename1 || !filename2) return FALSE;
1004 const char *dot1 = strrchr(filename1, '.');
1005 if(IS_NULL_PTR(dot1)) return FALSE;
1006 const char *dot2 = strrchr(filename2, '.');
1007 if(IS_NULL_PTR(dot2)) return FALSE;
1008 const int length1 = dot1 - filename1;
1009 const int length2 = dot2 - filename2;
1010 if(length1 != length2)
1011 return FALSE;
1012 for(int i = length1 - 1; i > 0; i--)
1013 if(filename1[i] != filename2[i])
1014 return FALSE;
1015 return TRUE;
1016}
1017
1018// set the filename2 extension to filename1 - return NULL if fails - result should be freed
1019char *dt_copy_filename_extension(const char *filename1, const char *filename2)
1020{
1021 // assume both filenames have an extension
1022 if(!filename1 || !filename2) return NULL;
1023 const char *dot1 = strrchr(filename1, '.');
1024 if(IS_NULL_PTR(dot1)) return NULL;
1025 const char *dot2 = strrchr(filename2, '.');
1026 if(IS_NULL_PTR(dot2)) return NULL;
1027 const int name_lgth = dot1 - filename1;
1028 const int ext_lgth = strlen(dot2);
1029 char *output = g_malloc(name_lgth + ext_lgth + 1);
1030 if(output)
1031 {
1032 memcpy(output, filename1, name_lgth);
1033 memcpy(&output[name_lgth], &filename2[strlen(filename2) - ext_lgth], ext_lgth + 1);
1034 }
1035 return output;
1036}
1037
1038// replaces all occurences of a substring in a string
1039gchar *dt_str_replace(const char *string, const char *search, const char *replace)
1040{
1041 gchar **split = g_strsplit(string, search, -1);
1042 gchar *res = g_strjoinv(replace, split);
1043 g_strfreev(split);
1044 return res;
1045}
1046
1047// Checks for the opposite separator in a string and replace it by the needed one by the current OS
1048gchar *dt_cleanup_separators(gchar *string)
1049{
1050#ifdef WIN32
1051 string = dt_str_replace(string, "/", G_DIR_SEPARATOR_S);
1052#else
1053 string = dt_str_replace(string, "\\", G_DIR_SEPARATOR_S);
1054#endif
1055return string;
1056}
1057
1058// remove trail and lead space of each folders and file name. Result should be freed.
1059gchar *dt_util_remove_whitespace(const gchar *path)
1060{
1061 gchar **split = g_strsplit(path, G_DIR_SEPARATOR_S, -1);
1062 for(int i = 0; i < g_strv_length(split); i++)
1063 if(g_strdup(split[i]) != NULL ) g_strstrip(split[i]);
1064
1065 char* result = g_strjoinv(G_DIR_SEPARATOR_S, split);
1066 g_strfreev(split);
1067
1068 return result;
1069}
1070// clang-format off
1071// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1072// vim: shiftwidth=2 expandtab tabstop=2 cindent
1073// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1074// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:278
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_aligned_pixel_t f
static const float const float const float min
darktable_t darktable
Definition darktable.c:181
static const dt_aligned_pixel_simd_t sign
Definition darktable.h:551
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define PATH_MAX
Definition darktable.h:1062
#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
void dt_loc_get_datadir(char *datadir, size_t bufsize)
gchar * dt_loc_get_home_dir(const gchar *user)
const dt_collection_sort_t items[]
Definition filter.c:95
static gchar * g_realpath(const char *path)
Definition grealpath.h:46
static cairo_surface_t * dt_cairo_image_surface_create_for_data(unsigned char *data, cairo_format_t format, int width, int height, int stride)
Definition gtk.h:322
int dimension(struct dt_imageio_module_format_t *self, dt_imageio_module_data_t *data, uint32_t *width, uint32_t *height)
float *const restrict const size_t k
size_t size
Definition mipmap_cache.c:3
const float factor
Definition pdf.h:90
static const char *const day[7]
Definition strptime.c:97
struct dt_gui_gtk_t * gui
Definition darktable.h:775
double ppd
Definition gtk.h:200
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_util_gps_elevation_to_number(const double r_1, const double r_2, char sign, double *result)
Definition utility.c:662
guint dt_util_string_count_char(const char *text, const char needle)
Definition utility.c:811
gchar * dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
Definition utility.c:136
gchar * dt_util_longitude_str(float longitude)
Definition utility.c:569
gchar * dt_str_replace(const char *string, const char *search, const char *replace)
Definition utility.c:1039
gboolean dt_has_same_path_basename(const char *filename1, const char *filename2)
Definition utility.c:1000
void dt_util_str_to_loc_numbers_format(char *data)
Definition utility.c:822
#define OSD_COORDINATES_CHR_N
Definition utility.c:542
gchar * dt_cleanup_separators(gchar *string)
Definition utility.c:1048
gchar * dt_util_elevation_str(float elevation)
Definition utility.c:587
gboolean dt_util_dir_exist(const char *dir)
Definition utility.c:367
dt_logo_season_t dt_util_get_logo_season(void)
Definition utility.c:434
RsvgDimensionData dt_get_svg_dimension(RsvgHandle *svg)
Definition utility.c:950
char * dt_read_file(const char *const filename, size_t *filesize)
Definition utility.c:893
static const char * OSD_ELEVATION_BSL
Definition utility.c:548
#define OSD_COORDINATES_CHR_S
Definition utility.c:543
cairo_surface_t * dt_util_get_logo_text(const float size)
Definition utility.c:533
static void easter(int Y, int *month, int *day)
Definition utility.c:415
GDateTime * dt_util_get_file_datetime(const char *const path)
Definition utility.c:789
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
Definition utility.c:830
gboolean dt_util_test_image_file(const char *filename)
Definition utility.c:318
#define OSD_COORDINATES_CHR_W
Definition utility.c:545
void dt_copy_file(const char *const sourcefile, const char *dst)
Definition utility.c:917
void dt_copy_resource_file(const char *src, const char *dst)
Definition utility.c:941
guint dt_util_str_occurence(const gchar *haystack, const gchar *needle)
Definition utility.c:119
double dt_util_gps_string_to_number(const gchar *input)
Definition utility.c:606
gchar * dt_util_path_get_dirname(const gchar *filename)
Definition utility.c:774
gboolean dt_util_is_dir_empty(const char *dirname)
Definition utility.c:375
#define OSD_COORDINATES_CHR_E
Definition utility.c:544
gchar * dt_util_normalize_path(const gchar *_input)
Definition utility.c:680
size_t dt_utf8_strlcpy(char *dest, const char *src, size_t n)
Definition utility.c:289
size_t safe_strlen(const char *str)
check if the string is empty or NULL before calling strlen()
Definition utility.c:90
char * dt_util_format_exposure(const float exposuretime)
Definition utility.c:865
gboolean dt_util_test_writable_dir(const char *path)
Definition utility.c:344
gboolean dt_util_gps_rationale_to_number(const double r0_1, const double r0_2, const double r1_1, const double r1_2, const double r2_1, const double r2_2, char sign, double *result)
Definition utility.c:624
gchar * dt_util_fix_path(const gchar *path)
Definition utility.c:222
gchar * dt_util_foo_to_utf8(const char *string)
Definition utility.c:392
gchar * dt_util_latitude_str(float latitude)
Definition utility.c:551
gchar * dt_util_remove_whitespace(const gchar *path)
Definition utility.c:1059
void dt_render_svg(RsvgHandle *svg, cairo_t *cr, double width, double height, double offset_x, double offset_y)
Definition utility.c:983
static const char * OSD_ELEVATION_ASL
Definition utility.c:547
gchar * dt_util_glist_to_str(const gchar *separator, GList *items)
Definition utility.c:166
char * dt_copy_filename_extension(const char *filename1, const char *filename2)
Definition utility.c:1019
static cairo_surface_t * _util_get_svg_img(gchar *logo, const float size)
Definition utility.c:463
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:95
GList * dt_util_glist_uniq(GList *items)
Definition utility.c:193
cairo_surface_t * dt_util_get_logo(const float size)
Definition utility.c:521
dt_logo_season_t
Definition utility.h:92
@ DT_LOGO_SEASON_HALLOWEEN
Definition utility.h:94
@ DT_LOGO_SEASON_XMAS
Definition utility.h:95
@ DT_LOGO_SEASON_EASTER
Definition utility.h:96
@ DT_LOGO_SEASON_NONE
Definition utility.h:93