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