1 /*
2 * This file is part of Siril, an astronomy image processor.
3 * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
4 * Copyright (C) 2012-2021 team free-astro (see more in AUTHORS file)
5 * Reference site is https://free-astro.org/index.php/Siril
6 *
7 * Siril is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Siril is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Siril. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /**
22 *
23 * \file utils.c
24 * \brief Misc. function utilities.
25 *
26 */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <fcntl.h>
31 #include <string.h>
32 #include <assert.h>
33 #include <math.h>
34 #ifdef _WIN32
35 #include <windows.h>
36 #endif
37 #include <glib.h>
38
39 #include "core/siril.h"
40 #include "core/proto.h"
41 #include "core/siril_app_dirs.h"
42 #include "core/exif.h"
43 #include "io/conversion.h"
44 #include "io/ser.h"
45 #include "io/sequence.h"
46 #include "gui/utils.h"
47 #include "gui/progress_and_log.h"
48 #include "io/single_image.h"
49
50 #if GLIB_CHECK_VERSION(2,68,0)
51 #define g_memdup g_memdup2
52 #endif
53
54 /**
55 * Round double value to an integer
56 * @param x value to round
57 * @return an integer
58 */
round_to_int(double x)59 int round_to_int(double x) {
60 if (x <= INT_MIN + 0.5) return INT_MIN;
61 if (x >= INT_MAX - 0.5) return INT_MAX;
62 if (x >= 0.0)
63 return (int)(x + 0.5);
64 return (int)(x - 0.5);
65 }
66
67 /**
68 * Round float value to an integer
69 * @param x value to round
70 * @return an integer
71 */
roundf_to_int(float x)72 int roundf_to_int(float x) {
73 if (x <= INT_MIN + 0.5f) return INT_MIN;
74 if (x >= INT_MAX - 0.5f) return INT_MAX;
75 if (x >= 0.0f)
76 return (int)(x + 0.5f);
77 return (int)(x - 0.5f);
78 }
79
80 /**
81 * Round double value to a WORD
82 * @param x value to round
83 * @return a WORD
84 */
round_to_WORD(double x)85 WORD round_to_WORD(double x) {
86 if (x <= 0.0)
87 return (WORD)0;
88 if (x > USHRT_MAX_DOUBLE)
89 return USHRT_MAX;
90 return (WORD)(x + 0.5);
91 }
92
93 /**
94 * Round double value to a BYTE
95 * @param x value to round
96 * @return a BYTE
97 */
round_to_BYTE(double x)98 BYTE round_to_BYTE(double x) {
99 if (x <= 0.0)
100 return (BYTE)0;
101 if (x > UCHAR_MAX_DOUBLE)
102 return UCHAR_MAX;
103 return (BYTE)(x + 0.5);
104 }
105
106 /**
107 * Round float value to a BYTE
108 * @param f value to round
109 * @return a truncated and rounded BYTE
110 */
roundf_to_BYTE(float f)111 BYTE roundf_to_BYTE(float f) {
112 if (f < 0.5f) return 0;
113 if (f >= UCHAR_MAX - 0.5f) return UCHAR_MAX;
114 return (BYTE)(f + 0.5f);
115 }
116
117 /**
118 * Round float value to a WORD
119 * @param f value to round
120 * @return a truncated and rounded WORD
121 */
roundf_to_WORD(float f)122 WORD roundf_to_WORD(float f) {
123 if (f < 0.5f) return 0;
124 if (f >= USHRT_MAX - 0.5f) return USHRT_MAX;
125 return (WORD)(f + 0.5f);
126 }
127
128 /**
129 * Compute a ceiling factor
130 * @param x the number to test
131 * @param factor the factor
132 * @return x if it is a factor of factor or the next factor
133 */
round_to_ceiling_multiple(int x,int factor)134 int round_to_ceiling_multiple(int x, int factor) {
135 if (x % factor == 0)
136 return x;
137 return (x / factor + 1) * factor;
138 }
139
140 /**
141 * convert double value to a BYTE
142 * @param x value to convert
143 * @return a BYTE
144 */
conv_to_BYTE(double x)145 BYTE conv_to_BYTE(double x) {
146 if (x == 0.0)
147 return (BYTE)0;
148 if (x == USHRT_MAX_DOUBLE)
149 return UCHAR_MAX;
150 x = ((x / USHRT_MAX_DOUBLE) * UCHAR_MAX_DOUBLE);
151 return((BYTE)(x));
152 }
153
154 /**
155 * truncate a 64 bit unsigned int to a 32 bit signed int
156 * @param x value to truncate
157 * @return an int
158 */
truncate_to_int32(uint64_t x)159 int truncate_to_int32(uint64_t x) {
160 if (x > (uint64_t)INT_MAX)
161 return INT_MAX;
162 return (int)x;
163 }
164
truncate_to_WORD(int x)165 WORD truncate_to_WORD(int x) {
166 if (x < 0)
167 return 0;
168 if (x > USHRT_MAX)
169 return USHRT_MAX;
170 return (WORD)x;
171 }
172
173 /**
174 * Clamp an integer value in the interval given by [low, high]
175 * @param val value to be checked
176 * @param low low value of the interval
177 * @param high high value of the interval
178 * @return a new value set in the [low, high] interval
179 */
set_int_in_interval(int val,int low,int high)180 int set_int_in_interval(int val, int low, int high) {
181 return max(low, min(val, high));
182 }
183
184 /**
185 * Clamp a float value in the interval given by [low, high]
186 * @param val value to be checked
187 * @param low low value of the interval
188 * @param high high value of the interval
189 * @return a new value set in the [low, high] interval
190 */
set_float_in_interval(float val,float low,float high)191 float set_float_in_interval(float val, float low, float high) {
192 return max(low, min(val, high));
193 }
194
195 /**
196 * Clamp a double value in the interval given by [low, high]
197 * @param val value to be checked
198 * @param low low value of the interval
199 * @param high high value of the interval
200 * @return a new value set in the [low, high] interval
201 */
set_double_in_interval(double val,double low,double high)202 double set_double_in_interval(double val, double low, double high) {
203 return max(low, min(val, high));
204 }
205
206 /**
207 * convert an unsigned short value to siril's representation of float values [0, 1]
208 * @param w value to convert
209 * @return the float equivalent
210 */
ushort_to_float_range(WORD w)211 float ushort_to_float_range(WORD w) {
212 return (float)w * INV_USHRT_MAX_SINGLE;
213 }
214
215 /**
216 * convert an unsigned char value to siril's representation of float values [0, 1]
217 * @param w value to convert
218 * @return the float equivalent
219 */
uchar_to_float_range(BYTE w)220 float uchar_to_float_range(BYTE w) {
221 return (float)w * INV_UCHAR_MAX_SINGLE;
222 }
223
224 /**
225 * convert an double value from the unsigned short range to siril's representation
226 * of float values [0, 1]
227 * @param d value to convert
228 * @return the float equivalent
229 */
double_ushort_to_float_range(double d)230 float double_ushort_to_float_range(double d) {
231 return (float)d * INV_USHRT_MAX_SINGLE;
232 }
233
234 /**
235 * convert a siril float [0, 1] to an unsigned short
236 * @param f value to convert
237 * @return the unsigned short equivalent
238 */
float_to_ushort_range(float f)239 WORD float_to_ushort_range(float f) {
240 return roundf_to_WORD(f * USHRT_MAX_SINGLE);
241 }
242
243 /**
244 * convert a siril float [0, 1] to an unsigned char
245 * @param f value to convert
246 * @return the unsigned char equivalent
247 */
float_to_uchar_range(float f)248 BYTE float_to_uchar_range(float f) {
249 return roundf_to_BYTE(f * UCHAR_MAX_SINGLE);
250 }
251
252 /**
253 * convert the pixel value of an image to a float [0, 1] normalized using bitpix
254 * value depending on btpix
255 * @param fit the image the data is from
256 * @return a float [0, 1] value for the given integer value
257 */
ushort_to_float_bitpix(fits * fit,WORD value)258 float ushort_to_float_bitpix(fits *fit, WORD value) {
259 float fval = (float)value;
260 return fit->orig_bitpix == BYTE_IMG ?
261 fval * INV_UCHAR_MAX_SINGLE :
262 fval * INV_USHRT_MAX_SINGLE;
263 }
264
265 /**
266 * convert a float type buffer into a WORD buffer
267 * @param buffer in float
268 * @param ndata
269 * @return
270 */
float_buffer_to_ushort(float * buffer,size_t ndata)271 WORD *float_buffer_to_ushort(float *buffer, size_t ndata) {
272 WORD *buf = malloc(ndata * sizeof(WORD));
273 if (!buf) {
274 PRINT_ALLOC_ERR;
275 } else {
276 for (size_t i = 0; i < ndata; i++) {
277 buf[i] = float_to_ushort_range(buffer[i]);
278 }
279 }
280 return buf;
281 }
282
283 /**
284 * convert a BYTE type buffer into a float buffer
285 * @param buffer in BYTE
286 * @param ndata
287 * @return
288 */
uchar_buffer_to_float(BYTE * buffer,size_t ndata)289 float *uchar_buffer_to_float(BYTE *buffer, size_t ndata) {
290 float *buf = malloc(ndata * sizeof(float));
291 if (!buf) {
292 PRINT_ALLOC_ERR;
293 } else {
294 for (size_t i = 0; i < ndata; i++) {
295 buf[i] = uchar_to_float_range(buffer[i]);
296 }
297 }
298 return buf;
299 }
300
301 /**
302 * convert a WORD type buffer into a float buffer
303 * @param buffer in WORD
304 * @param ndata
305 * @return
306 */
ushort_buffer_to_float(WORD * buffer,size_t ndata)307 float *ushort_buffer_to_float(WORD *buffer, size_t ndata) {
308 float *buf = malloc(ndata * sizeof(float));
309 if (!buf) {
310 PRINT_ALLOC_ERR;
311 } else {
312 for (size_t i = 0; i < ndata; i++) {
313 buf[i] = ushort_to_float_range(buffer[i]);
314 }
315 }
316 return buf;
317 }
318
319 /**
320 * convert a WORD type buffer representing 8bit data into a float buffer
321 * @param buffer in WORD
322 * @param ndata
323 * @return
324 */
ushort8_buffer_to_float(WORD * buffer,size_t ndata)325 float *ushort8_buffer_to_float(WORD *buffer, size_t ndata) {
326 float *buf = malloc(ndata * sizeof(float));
327 if (!buf) {
328 PRINT_ALLOC_ERR;
329 } else {
330 for (size_t i = 0; i < ndata; i++) {
331 buf[i] = uchar_to_float_range((BYTE) buffer[i]);
332 }
333 }
334 return buf;
335 }
336
337 /**
338 * Test equality between two double number
339 * @param a
340 * @param b
341 * @param epsilon
342 * @return
343 */
test_double_eq(double a,double b,double epsilon)344 gboolean test_double_eq(double a, double b, double epsilon) {
345 return (fabs(a - b) <= epsilon);
346 }
347
348 /**
349 * change endianness of a 16 bit unsigned int
350 * @param x value to convert
351 * @return byte-swapped value
352 */
change_endianness16(uint16_t x)353 uint16_t change_endianness16(uint16_t x) {
354 return (x >> 8) | (x << 8);
355 }
356
357 /**
358 * convert a 16 bit unsigned int in CPU byte order to little endian
359 * @param x value to convert
360 * @return little endian value
361 */
cpu_to_le16(uint16_t x)362 uint16_t cpu_to_le16(uint16_t x) {
363 #ifdef __BIG_ENDIAN__
364 return change_endianness16(x);
365 #else
366 return x;
367 #endif
368 }
369
370 /**
371 * convert a 16 bit unsigned int in CPU byte order to big endian
372 * @param x value to convert
373 * @return big endian value
374 */
cpu_to_be16(uint16_t x)375 uint16_t cpu_to_be16(uint16_t x) {
376 #ifdef __BIG_ENDIAN__
377 return x;
378 #else
379 return change_endianness16(x);
380 #endif
381 }
382
383 /**
384 * convert a 16 bit unsigned int from little endian to CPU byte order
385 * @param x little endian value to convert
386 * @return value
387 */
le16_to_cpu(uint16_t x)388 uint16_t le16_to_cpu(uint16_t x) {
389 return cpu_to_le16(x);
390 }
391
392 /**
393 * convert a 16 bit unsigned int from big endian to CPU byte order
394 * @param x big endian value to convert
395 * @return value
396 */
be16_to_cpu(uint16_t x)397 uint16_t be16_to_cpu(uint16_t x) {
398 return cpu_to_be16(x);
399 }
400
401 /**
402 * change endianness of a 32 bit unsigned int
403 * @param x value to convert
404 * @return byte-swapped value
405 */
change_endianness32(uint32_t x)406 uint32_t change_endianness32(uint32_t x) {
407 return (x >> 24) | ((x & 0xFF0000) >> 8) | ((x & 0xFF00) << 8) | (x << 24);
408 }
409
410 /**
411 * convert a 32 bit unsigned int in CPU byte order to little endian
412 * @param x value to convert
413 * @return little endian value
414 */
cpu_to_le32(uint32_t x)415 uint32_t cpu_to_le32(uint32_t x) {
416 #ifdef __BIG_ENDIAN__
417 return change_endianness32(x);
418 #else
419 return x;
420 #endif
421 }
422
423 /**
424 * convert a 32 bit unsigned int in CPU byte order to big endian
425 * @param x value to convert
426 * @return big endian value
427 */
cpu_to_be32(uint32_t x)428 uint32_t cpu_to_be32(uint32_t x) {
429 #ifdef __BIG_ENDIAN__
430 return x;
431 #else
432 return change_endianness32(x);
433 #endif
434 }
435
436 /**
437 * convert a 32 bit unsigned int from little endian to CPU byte order
438 * @param x little endian value to convert
439 * @return value
440 */
le32_to_cpu(uint32_t x)441 uint32_t le32_to_cpu(uint32_t x) {
442 return cpu_to_le32(x);
443 }
444
445 /**
446 * convert a 32 bit unsigned int from big endian to CPU byte order
447 * @param x big endian value to convert
448 * @return value
449 */
be32_to_cpu(uint32_t x)450 uint32_t be32_to_cpu(uint32_t x) {
451 return cpu_to_be32(x);
452 }
453
454 /**
455 * change endianness of a 64 bit unsigned int
456 * @param x value to convert
457 * @return byte-swapped value
458 */
change_endianness64(uint64_t x)459 uint64_t change_endianness64(uint64_t x) {
460 return
461 (x >> 56)
462 | ((x & 0xFF000000000000) >> 40)
463 | ((x & 0xFF0000000000) >> 24)
464 | ((x & 0xFF00000000) >> 8)
465 | ((x & 0xFF000000) << 8)
466 | ((x & 0xFF0000) << 24)
467 | ((x & 0xFF00) << 40)
468 | (x << 56);
469 }
470
471 /**
472 * convert a 64 bit unsigned int in CPU byte order to little endian
473 * @param x value to convert
474 * @return little endian value
475 */
cpu_to_le64(uint64_t x)476 uint64_t cpu_to_le64(uint64_t x) {
477 #ifdef __BIG_ENDIAN__
478 return change_endianness64(x);
479 #else
480 return x;
481 #endif
482 }
483
484 /**
485 * convert a 64 bit unsigned int in CPU byte order to big endian
486 * @param x value to convert
487 * @return big endian value
488 */
cpu_to_be64(uint64_t x)489 uint64_t cpu_to_be64(uint64_t x) {
490 #ifdef __BIG_ENDIAN__
491 return x;
492 #else
493 return change_endianness64(x);
494 #endif
495 }
496
497 /**
498 * convert a 64 bit unsigned int from little endian to CPU byte order
499 * @param x little endian value to convert
500 * @return value
501 */
le64_to_cpu(uint64_t x)502 uint64_t le64_to_cpu(uint64_t x) {
503 return cpu_to_le64(x);
504 }
505
506 /**
507 * convert a 64 bit unsigned int from big endian to CPU byte order
508 * @param x big endian value to convert
509 * @return value
510 */
be64_to_cpu(uint64_t x)511 uint64_t be64_to_cpu(uint64_t x) {
512 return cpu_to_be64(x);
513 }
514
515 /**
516 * Test if fit has 3 channels
517 * @param fit input FITS image
518 * @return TRUE if fit image has 3 channels
519 */
isrgb(fits * fit)520 gboolean isrgb(fits *fit) {
521 return (fit->naxis == 3);
522 }
523
524 /**
525 * Searches for an extension '.something' in filename from the end
526 * @param filename input filename or path
527 * @return the index of the first '.' found
528 */
get_extension_index(const char * filename)529 int get_extension_index(const char *filename) {
530 if (filename == NULL || filename[0] == '\0')
531 return -1;
532 for (int i = strlen(filename) - 1; i > 0; i--) {
533 if (filename[i] == '\\' || filename[i] == '/')
534 break;
535 if (filename[i] == '.')
536 return i;
537 }
538 return -1;
539 }
540
541 /**
542 * Get the extension of a file, without the dot.
543 * @param filename input filename or path
544 * @return extension pointed from the filename itself or NULL
545 */
get_filename_ext(const char * filename)546 const char *get_filename_ext(const char *filename) {
547 gchar *basename;
548 int len;
549 const char *dot, *p;
550
551 basename = g_path_get_basename(filename);
552 len = strlen(filename) - strlen(basename);
553 g_free(basename);
554
555 p = filename + len;
556 dot = strrchr(p, '.');
557 if (!dot || dot == p) {
558 return NULL;
559 }
560 return dot + 1;
561 }
562
563 /**
564 *
565 * @param filename
566 * @return the type of the file from its filename
567 */
get_type_from_filename(const gchar * filename)568 image_type get_type_from_filename(const gchar *filename) {
569 const char *ext = get_filename_ext(filename);
570 if (!ext)
571 return TYPEUNDEF;
572 return get_type_for_extension(ext);
573 }
574
575 /**
576 * Removes extension of the filename or path
577 * @param filename file path with extension
578 * @return newly allocated filename without extension
579 */
remove_ext_from_filename(const char * filename)580 char *remove_ext_from_filename(const char *filename) {
581 char *file = NULL;
582 int ext_index = -1;
583
584 for (int i = strlen(filename) - 1; i > 0; i--) {
585 if (filename[i] == '\\' || filename[i] == '/')
586 break;
587 if (filename[i] == '.') {
588 ext_index = i;
589 break;
590 }
591 }
592 if (ext_index == -1)
593 return strdup(filename);
594
595 file = malloc(ext_index + 1);
596 strncpy(file, filename, ext_index);
597 file[ext_index] = '\0';
598 return file;
599 }
600
601 /**
602 * Replaces the extension of a file name or path
603 * @param path the original path
604 * @param new_ext the next extension to put
605 * @return a new string with the new extension
606 */
replace_ext(const char * path,const char * new_ext)607 gchar *replace_ext(const char *path, const char *new_ext) {
608 int idx = get_extension_index(path);
609 gchar *retval = g_strdup(path);
610 if (idx != -1)
611 retval[idx] = '\0';
612 return str_append(&retval, new_ext);
613 }
614
615 /**
616 * Check is a string contains directory separators and thus represent a path
617 * @param file the string to test
618 * @return true if it contains a separator
619 */
string_is_a_path(const char * file)620 gboolean string_is_a_path(const char *file) {
621 int len = strlen(file);
622 for (int i = 0; i < len; i++) {
623 if (file[i] == '\\' || file[i] == '/')
624 return TRUE;
625 }
626 return FALSE;
627 }
628
629 /**
630 * Tests whether the given file is either regular or a symlink
631 * @param filename input
632 * @return 1 if file is readable (not actually opened to verify)
633 */
is_readable_file(const char * filename)634 int is_readable_file(const char *filename) {
635 GStatBuf sts;
636 if (g_lstat(filename, &sts))
637 return 0;
638 if (S_ISREG (sts.st_mode)
639 #ifndef _WIN32
640 || S_ISLNK(sts.st_mode)
641 #else
642 || (GetFileAttributesA(filename) & FILE_ATTRIBUTE_REPARSE_POINT )
643 #endif
644 )
645 return 1;
646 return 0;
647 }
648
649 static gchar forbidden_char[] = { '/', '\\', '"', '\'' };
650
is_forbiden_in_filename(gchar c)651 gboolean is_forbiden_in_filename(gchar c) {
652 for (int i = 0; i < G_N_ELEMENTS(forbidden_char); i++) {
653 if (c == forbidden_char[i])
654 return TRUE;
655 }
656 return FALSE;
657 }
658
file_name_has_invalid_chars(const char * name)659 gboolean file_name_has_invalid_chars(const char *name) {
660 if (!name)
661 return TRUE; // NULL is kind of invalid
662 for (int i = 0; i < strlen(name); i++)
663 if (is_forbiden_in_filename(name[i]))
664 return TRUE;
665 return FALSE;
666 }
667
668 /** Tests if filename is the canonical name of a known file type
669 * If filename contains an extension, only this file name is tested, else all
670 * extensions are tested for the file name until one is found.
671 * @param[in] filename the filename to test for.
672 * @param[in] type is set according to the result of the test.
673 * @param[out] realname (optionnal) is set according to the found file name: it
674 * must be freed with when no longer needed.
675 * @return 0 if sucess, 1 if error
676 */
stat_file(const char * filename,image_type * type,char ** realname)677 int stat_file(const char *filename, image_type *type, char **realname) {
678 int k;
679 const char *ext;
680 *type = TYPEUNDEF; // default value
681
682 /* check for an extension in filename and isolate it, including the . */
683 if (filename[0] == '\0')
684 return 1;
685
686 ext = get_filename_ext(filename);
687 /* if filename has an extension, we only test for it */
688 if (ext) {
689 if (is_readable_file(filename)) {
690 if (realname)
691 *realname = strdup(filename);
692 *type = get_type_for_extension(ext);
693 return 0;
694 }
695 return 1;
696 }
697
698 /* else, we can test various file extensions */
699 /* first we test lowercase, then uppercase */
700 for (k = 0; k < 2; k++) {
701 int i = 0;
702 while (supported_extensions[i]) {
703 GString *testName = g_string_new(filename);
704 if (k == 0) {
705 testName = g_string_append(testName, supported_extensions[i]);
706 } else {
707 gchar *tmp = g_ascii_strup(supported_extensions[i],
708 strlen(supported_extensions[i]));
709 testName = g_string_append(testName, tmp);
710 g_free(tmp);
711 }
712 gchar *name = g_string_free(testName, FALSE);
713
714 if (is_readable_file(name)) {
715 *type = get_type_for_extension(supported_extensions[i] + 1);
716 assert(*type != TYPEUNDEF);
717 if (realname)
718 *realname = strdup(name);
719 g_free(name);
720 return 0;
721 }
722 i++;
723 g_free(name);
724 }
725 }
726 return 1;
727 }
728
siril_canonicalize_filename(const gchar * filename,const gchar * relative_to)729 static gchar* siril_canonicalize_filename(const gchar *filename,
730 const gchar *relative_to)
731 {
732 #if GLIB_CHECK_VERSION(2,58,0)
733 return g_canonicalize_filename(filename, relative_to);
734 }
735 #else
736 /**
737 * g_canonicalize_filename:
738 * @filename: (type filename): the name of the file
739 * @relative_to: (type filename) (nullable): the relative directory, or %NULL
740 * to use the current working directory
741 *
742 * Gets the canonical file name from @filename. All triple slashes are turned into
743 * single slashes, and all `..` and `.`s resolved against @relative_to.
744 *
745 * Symlinks are not followed, and the returned path is guaranteed to be absolute.
746 *
747 * If @filename is an absolute path, @relative_to is ignored. Otherwise,
748 * @relative_to will be prepended to @filename to make it absolute. @relative_to
749 * must be an absolute path, or %NULL. If @relative_to is %NULL, it'll fallback
750 * to g_get_current_dir().
751 *
752 * This function never fails, and will canonicalize file paths even if they don't
753 * exist.
754 *
755 * No file system I/O is done.
756 *
757 * Returns: (type filename) (transfer full): a newly allocated string with the
758 * canonical file path
759 * Since: 2.58
760 */
761 gchar *canon, *start, *p, *q;
762 guint i;
763
764 g_return_val_if_fail (relative_to == NULL || g_path_is_absolute (relative_to), NULL);
765
766 if (!g_path_is_absolute (filename))
767 {
768 gchar *cwd_allocated = NULL;
769 const gchar *cwd;
770
771 if (relative_to != NULL)
772 cwd = relative_to;
773 else
774 cwd = cwd_allocated = g_get_current_dir ();
775
776 canon = g_build_filename (cwd, filename, NULL);
777 g_free (cwd_allocated);
778 }
779 else
780 {
781 canon = g_strdup (filename);
782 }
783
784 start = (char *)g_path_skip_root (canon);
785
786 if (start == NULL)
787 {
788 /* This shouldn't really happen, as g_get_current_dir() should
789 return an absolute pathname, but bug 573843 shows this is
790 not always happening */
791 g_free (canon);
792 return g_build_filename (G_DIR_SEPARATOR_S, filename, NULL);
793 }
794
795 /* POSIX allows double slashes at the start to
796 * mean something special (as does windows too).
797 * So, "//" != "/", but more than two slashes
798 * is treated as "/".
799 */
800 i = 0;
801 for (p = start - 1;
802 (p >= canon) &&
803 G_IS_DIR_SEPARATOR (*p);
804 p--)
805 i++;
806 if (i > 2)
807 {
808 i -= 1;
809 start -= i;
810 memmove (start, start+i, strlen (start+i) + 1);
811 }
812
813 /* Make sure we're using the canonical dir separator */
814 p++;
815 while (p < start && G_IS_DIR_SEPARATOR (*p))
816 *p++ = G_DIR_SEPARATOR;
817
818 p = start;
819 while (*p != 0)
820 {
821 if (p[0] == '.' && (p[1] == 0 || G_IS_DIR_SEPARATOR (p[1])))
822 {
823 memmove (p, p+1, strlen (p+1)+1);
824 }
825 else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || G_IS_DIR_SEPARATOR (p[2])))
826 {
827 q = p + 2;
828 /* Skip previous separator */
829 p = p - 2;
830 if (p < start)
831 p = start;
832 while (p > start && !G_IS_DIR_SEPARATOR (*p))
833 p--;
834 if (G_IS_DIR_SEPARATOR (*p))
835 *p++ = G_DIR_SEPARATOR;
836 memmove (p, q, strlen (q)+1);
837 }
838 else
839 {
840 /* Skip until next separator */
841 while (*p != 0 && !G_IS_DIR_SEPARATOR (*p))
842 p++;
843
844 if (*p != 0)
845 {
846 /* Canonicalize one separator */
847 *p++ = G_DIR_SEPARATOR;
848 }
849 }
850
851 /* Remove additional separators */
852 q = p;
853 while (*q && G_IS_DIR_SEPARATOR (*q))
854 q++;
855
856 if (p != q)
857 memmove (p, q, strlen (q) + 1);
858 }
859
860 /* Remove trailing slashes */
861 if (p > start && G_IS_DIR_SEPARATOR (*(p-1)))
862 *(p-1) = 0;
863
864 return canon;
865 }
866 #endif
867
868 /** Try to change the CWD to the argument, absolute or relative.
869 * If success, the new CWD is written to com.wd
870 * @param[in] dir absolute or relative path we want to set as cwd
871 * @param[out] err error message when return value is different of 1. Can be NULL if message is not needed.
872 * @return 0 if success, any other values for error
873 */
siril_change_dir(const char * dir,gchar ** err)874 int siril_change_dir(const char *dir, gchar **err) {
875 gchar *error = NULL;
876 int retval = 0;
877 char *new_dir = NULL;
878
879 if (dir == NULL || dir[0] == '\0') {
880 error = siril_log_message(_("Unknown error\n"));
881 retval = -1;
882 } else if (!g_file_test(dir, G_FILE_TEST_EXISTS)) {
883 error = siril_log_message(_("'%s' No such file or directory\n"), dir);
884 retval = 2;
885 } else if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
886 error = siril_log_message(_("'%s' is not a directory\n"), dir);
887 retval = 3;
888 } else if (g_access(dir, W_OK)) {
889 error = siril_log_color_message(_("You don't have permission "
890 "to write in this directory: '%s'\n"), "red", dir);
891 retval = 4;
892 } else {
893 /* sequences are invalidate when cwd is changed */
894 close_sequence(FALSE);
895 if (!g_chdir(dir)) {
896 /* do we need to search for sequences in the directory now? We still need to
897 * press the check seq button to display the list, and this is also done there. */
898 /* check_seq();
899 update_sequence_list();*/
900 // Don't follow symbolic links
901 if (g_path_is_absolute(dir)) {
902 new_dir = g_memdup(dir, strlen(dir) + 1);
903 g_free(com.wd);
904 com.wd = new_dir;
905 } else {
906 new_dir = siril_canonicalize_filename(dir, com.wd);
907 g_free(com.wd);
908 com.wd = new_dir;
909 }
910
911 siril_log_message(_("Setting CWD (Current Working Directory) to '%s'\n"), com.wd);
912 retval = 0;
913 } else {
914 error = siril_log_message(_("Could not change directory to '%s'.\n"), dir);
915 retval = 1;
916 }
917 }
918 if (err) {
919 *err = error;
920 }
921 return retval;
922 }
923 /**
924 * If Windows OS, converts a filename from UTF-8 to the system codepage. Do nothing on other system
925 * @param path to convert
926 * @return converted filename
927 */
get_locale_filename(const gchar * path)928 gchar *get_locale_filename(const gchar *path) {
929 gchar *str;
930 #ifdef _WIN32
931 str = g_win32_locale_filename_from_utf8(path);
932 if (!str) {
933 siril_log_color_message("Conversion of the filename to system codepage failed. Please consider removing all wide chars.\n", "red");
934 return g_strdup(path);
935 }
936 #else // _WIN32
937 str = g_strdup(path);
938 #endif // _WIN32
939 return str;
940 }
941
942 #ifdef _WIN32
ListSequences(const gchar * sDir,const char * sequence_name_to_select,GtkComboBoxText * seqcombo,int * index_of_seq_to_load)943 static int ListSequences(const gchar *sDir, const char *sequence_name_to_select,
944 GtkComboBoxText *seqcombo, int *index_of_seq_to_load) {
945 WIN32_FIND_DATAW fdFile;
946 HANDLE hFind = NULL;
947 char sPath[2048];
948 char filename[256];
949 int number_of_loaded_sequences = 0;
950 wchar_t *wpath;
951
952 //Specify a file mask. *.seq = We want only seq file!
953 sprintf(sPath, "%s\\*.seq", sDir);
954
955 wpath = g_utf8_to_utf16(sPath, -1, NULL, NULL, NULL);
956 if (wpath == NULL)
957 return 1;
958
959 if ((hFind = FindFirstFileW(wpath, &fdFile)) == INVALID_HANDLE_VALUE) {
960 fprintf(stderr, "Path not found: [%s]\n", sDir);
961 g_free(wpath);
962 return 1;
963 }
964 g_free(wpath);
965
966 do {
967 //Is the entity a File or Folder?
968 if (!(fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
969 gchar *cFileName = g_utf16_to_utf8(fdFile.cFileName, -1, NULL, NULL, NULL);
970 if (cFileName == NULL) {
971 return 1;
972 }
973 sequence *seq = readseqfile(cFileName);
974 if (seq != NULL) {
975 strncpy(filename, cFileName, 255);
976 free_sequence(seq, TRUE);
977 gtk_combo_box_text_append_text(seqcombo, filename);
978 if (sequence_name_to_select
979 && !strncmp(filename, sequence_name_to_select,
980 strlen(filename))) {
981 *index_of_seq_to_load = number_of_loaded_sequences;
982 }
983 ++number_of_loaded_sequences;
984 }
985 g_free(cFileName);
986 }
987 } while (FindNextFileW(hFind, &fdFile)); //Find the next file.
988
989 FindClose(hFind);
990
991 return number_of_loaded_sequences;
992 }
993 #endif
994
995 /** This method populates the sequence combo box with the sequences found in the CWD.
996 * If only one sequence is found, or if a sequence whose name matches the
997 * possibly NULL argument is found, it is automatically selected, which triggers
998 * its loading
999 * @param sequence_name_to_select the name of the input sequence
1000 * @return 0 if success
1001 */
update_sequences_list(const char * sequence_name_to_select)1002 int update_sequences_list(const char *sequence_name_to_select) {
1003 GtkComboBoxText *seqcombo;
1004 int number_of_loaded_sequences = 0;
1005 int index_of_seq_to_load = -1;
1006 char *seqname = NULL;
1007
1008 // clear the previous list
1009 seqcombo = GTK_COMBO_BOX_TEXT(lookup_widget("sequence_list_combobox"));
1010 gtk_combo_box_text_remove_all(seqcombo);
1011
1012 if (sequence_name_to_select) {
1013 if (g_str_has_suffix(sequence_name_to_select, ".seq"))
1014 seqname = strdup(sequence_name_to_select);
1015 else {
1016 seqname = malloc(strlen(sequence_name_to_select) + 5);
1017 sprintf(seqname, "%s.seq", sequence_name_to_select);
1018 }
1019 }
1020
1021 #ifdef _WIN32
1022 number_of_loaded_sequences = ListSequences(com.wd, seqname, seqcombo, &index_of_seq_to_load);
1023 #else
1024 struct dirent **list;
1025 int i, n;
1026
1027 n = scandir(com.wd, &list, 0, alphasort);
1028 if (n < 0)
1029 perror("scandir");
1030
1031 for (i = 0; i < n; ++i) {
1032 char *suf;
1033
1034 if ((suf = strstr(list[i]->d_name, ".seq")) && strlen(suf) == 4) {
1035 sequence *seq = readseqfile(list[i]->d_name);
1036 if (seq != NULL) {
1037 free_sequence(seq, TRUE);
1038 char *filename = list[i]->d_name;
1039 gtk_combo_box_text_append_text(seqcombo, filename);
1040 if (seqname && !strcmp(filename, seqname))
1041 index_of_seq_to_load = number_of_loaded_sequences;
1042 ++number_of_loaded_sequences;
1043 }
1044 }
1045 }
1046 for (i = 0; i < n; i++)
1047 free(list[i]);
1048 free(list);
1049 #endif
1050
1051 if (seqname) free(seqname);
1052
1053 if (!number_of_loaded_sequences) {
1054 fprintf(stderr, "No valid sequence found in CWD.\n");
1055 return -1;
1056 } else {
1057 fprintf(stdout, "Loaded %d %s\n", number_of_loaded_sequences,
1058 ngettext("sequence", "sequences", number_of_loaded_sequences));
1059 }
1060
1061 if (number_of_loaded_sequences > 1 && index_of_seq_to_load < 0) {
1062 gtk_combo_box_popup(GTK_COMBO_BOX(seqcombo));
1063 } else if (index_of_seq_to_load >= 0)
1064 gtk_combo_box_set_active(GTK_COMBO_BOX(seqcombo), index_of_seq_to_load);
1065 else
1066 gtk_combo_box_set_active(GTK_COMBO_BOX(seqcombo), 0);
1067 return 0;
1068 }
1069
1070 /**
1071 * Expands the ~ in filenames
1072 * @param[in] filename input filename
1073 * @param[in] size maximum size of the filename
1074 */
expand_home_in_filename(char * filename,int size)1075 void expand_home_in_filename(char *filename, int size) {
1076 if (filename[0] == '~' && filename[1] == '\0')
1077 strcat(filename, G_DIR_SEPARATOR_S);
1078 int len = strlen(filename);
1079 if (len < 2)
1080 return; // not very necessary now with the first line
1081 if (filename[0] == '~' && filename[1] == G_DIR_SEPARATOR) {
1082 const gchar *homepath = g_get_home_dir();
1083 int j, homelen = strlen(homepath);
1084 if (len + homelen > size - 1) {
1085 siril_log_message(_("Filename is too long, not expanding it\n"));
1086 return;
1087 }
1088 for (j = len; j > 0; j--) // edit in place
1089 filename[j + homelen - 1] = filename[j];
1090 // the -1 above is tricky: it's the removal of the ~ character from
1091 // the original string
1092 memcpy(filename, homepath, homelen);
1093 }
1094 }
1095
1096 /**
1097 * Tries to get normalized value of a fit image. Make assumption that
1098 * an image with no values greater than 2^8 comes from 8-bit images
1099 * @param fit input FITS image
1100 * @return 255 or 65535 if 8- or 16-bit image
1101 */
get_normalized_value(fits * fit)1102 double get_normalized_value(fits *fit) {
1103 if (fit->type == DATA_USHORT) {
1104 image_find_minmax(fit);
1105 if (fit->maxi <= UCHAR_MAX_DOUBLE)
1106 return UCHAR_MAX_DOUBLE;
1107 return USHRT_MAX_DOUBLE;
1108 }
1109 if (fit->type == DATA_FLOAT) {
1110 return 1.0;
1111 }
1112 return -1.0;
1113 }
1114
1115 /**
1116 * append a string to the end of an existing string
1117 * @param data original string
1118 * @param newdata suffix to add
1119 * @return a new string that should be freed when no longer needed
1120 */
str_append(gchar ** data,const gchar * newdata)1121 gchar* str_append(gchar** data, const gchar* newdata) {
1122 gchar* p;
1123 int len = (*data ? strlen(*data) : 0);
1124 if ((p = g_try_realloc(*data, len + strlen(newdata) + 1)) == NULL) {
1125 g_free(p);
1126 PRINT_ALLOC_ERR;
1127 return NULL;
1128 }
1129 *data = p;
1130 g_strlcpy(*data + len, newdata, len + strlen(newdata));
1131 return *data;
1132 }
1133
1134 /**
1135 * Cut a base name to 120 characters and add a trailing underscore if needed.
1136 * WARNING: may return a newly allocated string and free the argument
1137 * @param root the original base name
1138 * @param can_free allow root to be freed in case a new string is allocated
1139 * @return a string ending with trailing underscore
1140 */
format_basename(char * root,gboolean can_free)1141 char *format_basename(char *root, gboolean can_free) {
1142 int len = strlen(root);
1143 if (len > 120) {
1144 root[120] = '\0';
1145 len = 120;
1146 }
1147 if (root[len - 1] == '-' || root[len - 1] == '_') {
1148 return root;
1149 }
1150
1151 char *appended = malloc(len + 2);
1152 sprintf(appended, "%s_", root);
1153 if (can_free)
1154 free(root);
1155 return appended;
1156 }
1157
1158 /**
1159 * Computes slope using low and high values
1160 * @param lo low value
1161 * @param hi high value
1162 * @return the computed slope
1163 */
compute_slope(WORD * lo,WORD * hi)1164 float compute_slope(WORD *lo, WORD *hi) {
1165 if (sequence_is_loaded() && !single_image_is_loaded()) {
1166 *hi = com.seq.layers[RLAYER].hi;
1167 *lo = com.seq.layers[RLAYER].lo;
1168 }
1169 else {
1170 *hi = com.uniq->layers[RLAYER].hi;
1171 *lo = com.uniq->layers[RLAYER].lo;
1172 }
1173 return UCHAR_MAX_SINGLE / (float) (*hi - *lo);
1174 }
1175
1176 /**
1177 * Try to get file info, i.e width and height
1178 * @param filename name of the file
1179 * @param pixbuf
1180 * @return a newly allocated and formated string containing dimension information or NULL
1181 */
siril_get_file_info(const gchar * filename,GdkPixbuf * pixbuf)1182 gchar* siril_get_file_info(const gchar *filename, GdkPixbuf *pixbuf) {
1183 int width, height;
1184 int n_channel = 0;
1185
1186 GdkPixbufFormat *pixbuf_file_info = gdk_pixbuf_get_file_info(filename,
1187 &width, &height);
1188
1189 if (pixbuf) {
1190 n_channel = gdk_pixbuf_get_n_channels(pixbuf);
1191 }
1192
1193 if (pixbuf_file_info != NULL) {
1194 /* Pixel size of image: width x height in pixel */
1195 return g_strdup_printf("%d x %d %s\n%d %s", width, height,
1196 ngettext("pixel", "pixels", height), n_channel,
1197 ngettext("channel", "channels", n_channel));
1198 }
1199 return NULL;
1200 }
1201
1202 /**
1203 * Truncate a string str to not exceed an length of size
1204 * @param str the string to be truncated
1205 * @param size maximum size of the string
1206 * @return the truncated size starting by "..." and followed by "/"
1207 * if possible
1208 */
siril_truncate_str(gchar * str,gint size)1209 gchar *siril_truncate_str(gchar *str, gint size) {
1210 GString *trunc_str = g_string_new(str);
1211 gint len = strlen(str);
1212
1213 if (len > size) {
1214 gint pos = len - size;
1215 /* locate first "/" */
1216 char *ptr = strchr(str + pos, G_DIR_SEPARATOR);
1217 if (ptr != NULL) {
1218 pos = ptr - str;
1219 }
1220 trunc_str = g_string_erase(trunc_str, 0, pos);
1221 trunc_str = g_string_prepend(trunc_str, "...");
1222 }
1223 return g_string_free(trunc_str, FALSE);
1224 }
1225
1226 /**
1227 *
1228 * @param list
1229 * @param arg_count
1230 * @return
1231 */
glist_to_array(GList * list,int * arg_count)1232 char **glist_to_array(GList *list, int *arg_count) {
1233 int count;
1234 if (arg_count && *arg_count > 0)
1235 count = *arg_count;
1236 else {
1237 count = g_list_length(list);
1238 if (arg_count)
1239 *arg_count = count;
1240 }
1241 char **array = malloc(count * sizeof(char *));
1242 if (!array) {
1243 PRINT_ALLOC_ERR;
1244 return NULL;
1245 }
1246 GList *orig_list = list;
1247 for (int i = 0; i < count && list; list = list->next, i++)
1248 array[i] = g_strdup(list->data);
1249 g_list_free_full(orig_list, g_free);
1250 return array;
1251 }
1252
1253 /**
1254 *
1255 * @param uri_string
1256 * @return a new allocated string
1257 */
url_cleanup(const gchar * uri_string)1258 gchar* url_cleanup(const gchar *uri_string) {
1259 GString *copy;
1260 const gchar *end;
1261
1262 /* Skip leading whitespace */
1263 while (g_ascii_isspace(*uri_string))
1264 uri_string++;
1265
1266 /* Ignore trailing whitespace */
1267 end = uri_string + strlen(uri_string);
1268 while (end > uri_string && g_ascii_isspace(*(end - 1)))
1269 end--;
1270
1271 /* Copy the rest, encoding unencoded spaces and stripping other whitespace */
1272 copy = g_string_sized_new(end - uri_string);
1273 while (uri_string < end) {
1274 if (*uri_string == ' ')
1275 g_string_append(copy, "%20");
1276 else if (g_ascii_isspace(*uri_string))
1277 ; // @suppress("Suspicious semicolon")
1278 else
1279 g_string_append_c(copy, *uri_string);
1280 uri_string++;
1281 }
1282
1283 return g_string_free(copy, FALSE);
1284 }
1285