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