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