1 /*
2  * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3  * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #define _XOPEN_SOURCE 500 /* strptime() and realpath() */
21 #include <rawstudio.h>
22 #include <config.h>
23 #include <glib.h>
24 #include <glib/gstdio.h>
25 #ifdef WIN32
26 #include <pthread.h> /* MinGW WIN32 gmtime_r() */
27 #endif
28 #include <time.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "conf_interface.h"
36 
37 #define DOTDIR ".rawstudio"
38 
39 /**
40  * A version of atof() that isn't locale specific
41  * @note This doesn't do any error checking!
42  * @param str A NULL terminated string representing a number
43  * @return The number represented by str or 0.0 if str is NULL
44  */
45 gdouble
rs_atof(const gchar * str)46 rs_atof(const gchar *str)
47 {
48 	gdouble result = 0.0f;
49 	gdouble div = 1.0f;
50 	gboolean point_passed = FALSE;
51 
52 	gchar *ptr = (gchar *) str;
53 
54 	while(str && *ptr)
55 	{
56 		if (g_ascii_isdigit(*ptr))
57 		{
58 			result = result * 10.0f + g_ascii_digit_value(*ptr);
59 			if (point_passed)
60 				div *= 10.0f;
61 		}
62 		else if (*ptr == '-')
63 			div *= -1.0f;
64 		else if (g_ascii_ispunct(*ptr))
65 			point_passed = TRUE;
66 		ptr++;
67 	}
68 
69 	return result / div;
70 }
71 
72 /**
73  * A convenience function to convert an EXIF timestamp to a unix timestamp.
74  * @note This will only work until 2038 unless glib fixes its GTime
75  * @param str A NULL terminated string containing a timestamp in the format "YYYY:MM:DD HH:MM:SS" (EXIF 2.2 section 4.6.4)
76  * @return A unix timestamp or -1 on error
77  */
78 GTime
rs_exiftime_to_unixtime(const gchar * str)79 rs_exiftime_to_unixtime(const gchar *str)
80 {
81 	struct tm *tm = g_new0(struct tm, 1);
82 	GTime timestamp = -1;
83 #ifndef WIN32 /* There is no strptime() in time.h in MinGW */
84 	if (strptime(str, "%Y:%m:%d %H:%M:%S", tm))
85 		timestamp = (GTime) mktime(tm);
86 #endif
87 
88 	g_free(tm);
89 
90 	return timestamp;
91 }
92 
93 /**
94  * A convenience function to convert an unix timestamp to an EXIF timestamp.
95  * @note This will only work until 2038 unless glib fixes its GTime
96  * @param timestamp A unix timestamp
97  * @return A string formatted as specified in EXIF 2.2 section 4.6.4
98  */
99 gchar *
rs_unixtime_to_exiftime(GTime timestamp)100 rs_unixtime_to_exiftime(GTime timestamp)
101 {
102 	struct tm *tm = g_new0(struct tm, 1);
103 	time_t tt = (time_t) timestamp;
104 	gchar *result = g_new0(gchar, 20);
105 
106 	gmtime_r(&tt, tm);
107 
108 	if (strftime(result, 20, "%Y:%m:%d %H:%M:%S", tm) != 19)
109 	{
110 		g_free(result);
111 		result = NULL;
112 	}
113 
114 	g_free(tm);
115 
116 	return result;
117 }
118 
119 /**
120  * Constrains a box to fill a bounding box without changing aspect
121  * @param target_width The width of the bounding box
122  * @param target_height The height of the bounding box
123  * @param width The input and output width
124  * @param height The input and output height
125  */
126 void
rs_constrain_to_bounding_box(gint target_width,gint target_height,gint * width,gint * height)127 rs_constrain_to_bounding_box(gint target_width, gint target_height, gint *width, gint *height)
128 {
129 	gdouble target_aspect = ((gdouble)target_width) / ((gdouble)target_height);
130 	gdouble input_aspect = ((gdouble)*width) / ((gdouble)*height);
131 	gdouble scale;
132 
133 	if (target_aspect < input_aspect)
134 		scale = ((gdouble) *width) / ((gdouble) target_width);
135 	else
136 		scale = ((gdouble) *height) / ((gdouble) target_height);
137 
138 	*width = MIN((gint) ((gdouble)*width) / scale, target_width);
139 	*height = MIN((gint) ((gdouble)*height) / scale, target_height);
140 }
141 
142 /**
143  * Try to count the number of processor cores in a system.
144  * @note This currently only works for systems with /proc/cpuinfo
145  * @return The numver of cores or 1 if the system is unsupported
146  */
147 gint
rs_get_number_of_processor_cores(void)148 rs_get_number_of_processor_cores(void)
149 {
150 	static GStaticMutex lock = G_STATIC_MUTEX_INIT;
151 
152 	/* We assume processors will not be added/removed during our lifetime */
153 	static gint num = 0;
154 
155 	if (num)
156 		return num;
157 
158 	g_static_mutex_lock (&lock);
159 	if (num == 0)
160 	{
161 		/* Use a temporary for thread safety */
162 		gint temp_num = 0;
163 #if defined(_SC_NPROCESSORS_ONLN)
164 		/* Use the POSIX way of getting the number of processors */
165 		temp_num = sysconf(_SC_NPROCESSORS_ONLN);
166 #elif defined (__linux__) && (defined (__i386__) || defined (__x86_64__))
167 		/* Parse the /proc/cpuinfo exposed by Linux i386/amd64 kernels */
168 		GIOChannel *io;
169 		gchar *line;
170 
171 		io = g_io_channel_new_file("/proc/cpuinfo", "r", NULL);
172 		if (io)
173 		{
174 			/* Count the "processor"-lines, there should be one for each processor/core */
175 			while (G_IO_STATUS_NORMAL == g_io_channel_read_line(io, &line, NULL, NULL, NULL))
176 				if (line)
177 				{
178 					if (g_str_has_prefix(line, "processor"))
179 						temp_num++;
180 					g_free(line);
181 				}
182 			g_io_channel_shutdown(io, FALSE, NULL);
183 			g_io_channel_unref(io);
184 		}
185 #elif defined(_WIN32)
186 		/* Use pthread on windows */
187 		temp_num = pthread_num_processors_np();
188 #endif
189 		/* Be sure we have at least 1 processor and as sanity check, clamp to no more than 127 */
190 		temp_num = (temp_num <= 0) ? 1 : MIN(temp_num, 127);
191 		RS_DEBUG(PERFORMANCE, "Detected %d CPU cores.", temp_num);
192 		num = temp_num;
193 	}
194 	g_static_mutex_unlock (&lock);
195 
196 	return num;
197 }
198 
199 #if defined (__i386__) || defined (__x86_64__)
200 
201 #define xgetbv(index,eax,edx)                                   \
202    __asm__ (".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c" (index))
203 
204 /**
205  * Detect cpu features
206  * @return A bitmask of @RSCpuFlags
207  */
208 guint
rs_detect_cpu_features(void)209 rs_detect_cpu_features(void)
210 {
211 #define cpuid(cmd, eax, ecx, edx) \
212   do { \
213      eax = edx = 0;	\
214      asm ( \
215        "push %%"REG_b"\n\t"\
216        "cpuid\n\t" \
217        "pop %%"REG_b"\n\t" \
218        : "=a" (eax), "=c" (ecx),  "=d" (edx) \
219        : "0" (cmd) \
220      ); \
221 } while(0)
222 	guint eax;
223 	guint edx;
224 	guint ecx;
225 	static GStaticMutex lock = G_STATIC_MUTEX_INIT;
226 	static guint stored_cpuflags = -1;
227 
228 	if (stored_cpuflags != -1)
229 		return stored_cpuflags;
230 
231 	g_static_mutex_lock(&lock);
232 	if (stored_cpuflags == -1)
233 	{
234 		guint cpuflags = 0;
235 		/* Test cpuid presence comparing eflags */
236 		asm (
237 			"push %%"REG_b"\n\t"
238 			"pushf\n\t"
239 			"pop %%"REG_a"\n\t"
240 			"mov %%"REG_a", %%"REG_b"\n\t"
241 			"xor $0x00200000, %%"REG_a"\n\t"
242 			"push %%"REG_a"\n\t"
243 			"popf\n\t"
244 			"pushf\n\t"
245 			"pop %%"REG_a"\n\t"
246 			"cmp %%"REG_a", %%"REG_b"\n\t"
247 			"je notfound\n\t"
248 			"mov $1, %0\n\t"
249 			"notfound:\n\t"
250 			"pop %%"REG_b"\n\t"
251 			: "=r" (eax)
252 			:
253 			: REG_a
254 
255 			);
256 
257 		if (eax)
258 		{
259 			guint std_dsc;
260 			guint ext_dsc;
261 
262 			/* Get the standard level */
263 			cpuid(0x00000000, std_dsc, ecx, edx);
264 
265 			if (std_dsc)
266 			{
267 				/* Request for standard features */
268 				cpuid(0x00000001, std_dsc, ecx, edx);
269 
270 				if (edx & 0x00800000)
271 					cpuflags |= RS_CPU_FLAG_MMX;
272 				if (edx & 0x02000000)
273 					cpuflags |= RS_CPU_FLAG_SSE;
274 				if (edx & 0x04000000)
275 					cpuflags |= RS_CPU_FLAG_SSE2;
276 				if (edx & 0x00008000)
277 					cpuflags |= RS_CPU_FLAG_CMOV;
278 
279 				if (ecx & 0x00000001)
280 					cpuflags |= RS_CPU_FLAG_SSE3;
281 				if (ecx & 0x00000200)
282 					cpuflags |= RS_CPU_FLAG_SSSE3;
283 				if (ecx & 0x00080000)
284 					cpuflags |= RS_CPU_FLAG_SSE4_1;
285 				if (ecx & 0x00100000)
286 					cpuflags |= RS_CPU_FLAG_SSE4_2;
287 				if ((ecx & 0x18000000) == 0x18000000)
288 				{
289 					xgetbv(0, eax, edx);
290 						if ((eax & 0x6) == 0x6)
291 							cpuflags |= RS_CPU_FLAG_AVX;
292 				}
293 			}
294 
295 			/* Is there extensions */
296 			cpuid(0x80000000, ext_dsc, ecx, edx);
297 
298 			if (ext_dsc)
299 			{
300 				/* Request for extensions */
301 				cpuid(0x80000001, eax, ecx, edx);
302 
303 				if (edx & 0x80000000)
304 					cpuflags |= RS_CPU_FLAG_3DNOW;
305 				if (edx & 0x40000000)
306 					cpuflags |= RS_CPU_FLAG_3DNOW_EXT;
307 				if (edx & 0x00400000)
308 					cpuflags |= RS_CPU_FLAG_AMD_ISSE;
309 			}
310 
311 			/* ISSE is also implied in SSE */
312 			if (cpuflags & RS_CPU_FLAG_SSE)
313 				cpuflags |= RS_CPU_FLAG_AMD_ISSE;
314 		}
315 		stored_cpuflags = cpuflags;
316 	}
317 	g_static_mutex_unlock(&lock);
318 
319 #define report(a, x) RS_DEBUG(PERFORMANCE, "CPU Feature: "a" = %d", !!(stored_cpuflags&x));
320 	report("MMX",RS_CPU_FLAG_MMX);
321 	report("SSE",RS_CPU_FLAG_SSE);
322 	report("CMOV",RS_CPU_FLAG_CMOV);
323 	report("3DNOW",RS_CPU_FLAG_3DNOW);
324 	report("3DNOW_EXT",RS_CPU_FLAG_3DNOW_EXT);
325 	report("Integer SSE",RS_CPU_FLAG_AMD_ISSE);
326 	report("SSE2",RS_CPU_FLAG_SSE2);
327 	report("SSE3",RS_CPU_FLAG_SSE3);
328 	report("SSSE3",RS_CPU_FLAG_SSSE3);
329 	report("SSE4.1",RS_CPU_FLAG_SSE4_1);
330 	report("SSE4.2",RS_CPU_FLAG_SSE4_2);
331 	report("AVX",RS_CPU_FLAG_AVX);
332 #undef report
333 
334 	return(stored_cpuflags);
335 #undef cpuid
336 }
337 
338 #else
339 guint
rs_detect_cpu_features()340 rs_detect_cpu_features()
341 {
342 	return 0;
343 }
344 #endif /* __i386__ || __x86_64__ */
345 
346 /**
347  * Return a path to the current config directory for Rawstudio - this is the
348  * .rawstudio direcotry in home
349  * @return A path to an existing directory
350  */
351 const gchar *
rs_confdir_get(void)352 rs_confdir_get(void)
353 {
354 	static gchar *dir = NULL;
355 	static GStaticMutex lock = G_STATIC_MUTEX_INIT;
356 
357 	g_static_mutex_lock(&lock);
358 	if (!dir)
359 	{
360 		const gchar *home = g_get_home_dir();
361 		dir = g_build_filename(home, ".rawstudio", NULL);
362 	}
363 
364 	g_mkdir_with_parents(dir, 00755);
365 	g_static_mutex_unlock(&lock);
366 
367 	return dir;
368 }
369 
370 /**
371  * Return a cache directory for filename
372  * @param filename A complete path to a photo
373  * @return A directory to hold the cache. This is guarenteed to exist
374  */
375 gchar *
rs_dotdir_get(const gchar * filename)376 rs_dotdir_get(const gchar *filename)
377 {
378 	gchar *ret = NULL;
379 	gchar *directory;
380 	GString *dotdir;
381 	gboolean dotdir_is_local = FALSE;
382 
383 	rs_conf_get_boolean(CONF_CACHEDIR_IS_LOCAL, &dotdir_is_local);
384 
385 	if (g_file_test(filename, G_FILE_TEST_IS_DIR))
386 		directory = g_strdup(filename);
387 	else
388 		directory = g_path_get_dirname(filename);
389 
390 	if (dotdir_is_local)
391 	{
392 		dotdir = g_string_new(g_get_home_dir());
393 		dotdir = g_string_append(dotdir, G_DIR_SEPARATOR_S);
394 		dotdir = g_string_append(dotdir, DOTDIR);
395 		dotdir = g_string_append(dotdir, G_DIR_SEPARATOR_S);
396 		dotdir = g_string_append(dotdir, directory);
397 	}
398 	else
399 	{
400 		dotdir = g_string_new(directory);
401 		dotdir = g_string_append(dotdir, G_DIR_SEPARATOR_S);
402 		dotdir = g_string_append(dotdir, DOTDIR);
403 	}
404 
405 	if (!g_file_test(dotdir->str, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
406 	{
407 		if (g_mkdir_with_parents(dotdir->str, 0700) != 0)
408 			ret = NULL;
409 		else
410 			ret = dotdir->str;
411 	}
412 	else if (g_file_test(dotdir->str, G_FILE_TEST_IS_DIR))
413 		ret = dotdir->str;
414 	else
415 		ret = NULL;
416 
417 	/* If we for some reason cannot write to the current directory, */
418 	/* we save it to a new folder named as the md5 of the file content, */
419 	/* not particularly fast, but ensures that the images can be moved */
420 	if (ret == NULL)
421 	{
422 		g_string_free(dotdir, TRUE);
423 		g_free(directory);
424 		if (g_file_test(filename, G_FILE_TEST_IS_REGULAR))
425 		{
426 			gchar* md5 = rs_file_checksum(filename);
427 			ret = g_strdup_printf("%s/read-only-cache/%s", rs_confdir_get(), md5);
428 			g_free(md5);
429 			if (!g_file_test(ret, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
430 			{
431 				if (g_mkdir_with_parents(ret, 0700) != 0)
432 					ret = NULL;
433 			}
434 		}
435 		return ret;
436 	}
437 	g_free(directory);
438 	g_string_free(dotdir, FALSE);
439 	return (ret);
440 }
441 
442 /**
443  * Normalize a RS_RECT, ie makes sure that x1 < x2 and y1<y2
444  * @param in A RS_RECT to read values from
445  * @param out A RS_RECT to write the values to (can be the same as in)
446  */
447 void
rs_rect_normalize(RS_RECT * in,RS_RECT * out)448 rs_rect_normalize(RS_RECT *in, RS_RECT *out)
449 {
450 	gint n;
451 	gint x1,y1;
452 	gint x2,y2;
453 
454 	x1 = in->x2;
455 	x2 = in->x1;
456 	y1 = in->y1;
457 	y2 = in->y2;
458 
459 	if (x1>x2)
460 	{
461 		n = x1;
462 		x1 = x2;
463 		x2 = n;
464 	}
465 	if (y1>y2)
466 	{
467 		n = y1;
468 		y1 = y2;
469 		y2 = n;
470 	}
471 
472 	out->x1 = x1;
473 	out->x2 = x2;
474 	out->y1 = y1;
475 	out->y2 = y2;
476 }
477 
478 /**
479  * Flip a RS_RECT
480  * @param in A RS_RECT to read values from
481  * @param out A RS_RECT to write the values to (can be the same as in)
482  * @param w The width of the data OUTSIDE the RS_RECT
483  * @param h The height of the data OUTSIDE the RS_RECT
484  */
485 void
rs_rect_flip(RS_RECT * in,RS_RECT * out,gint w,gint h)486 rs_rect_flip(RS_RECT *in, RS_RECT *out, gint w, gint h)
487 {
488 	gint x1,y1;
489 	gint x2,y2;
490 
491 	x1 = in->x1;
492 	x2 = in->x2;
493 	y1 = h - in->y2 - 1;
494 	y2 = h - in->y1 - 1;
495 
496 	out->x1 = x1;
497 	out->x2 = x2;
498 	out->y1 = y1;
499 	out->y2 = y2;
500 	rs_rect_normalize(out, out);
501 }
502 
503 /**
504  * Mirrors a RS_RECT
505  * @param in A RS_RECT to read values from
506  * @param out A RS_RECT to write the values to (can be the same as in)
507  * @param w The width of the data OUTSIDE the RS_RECT
508  * @param h The height of the data OUTSIDE the RS_RECT
509  */
510 void
rs_rect_mirror(RS_RECT * in,RS_RECT * out,gint w,gint h)511 rs_rect_mirror(RS_RECT *in, RS_RECT *out, gint w, gint h)
512 {
513 	gint x1,y1;
514 	gint x2,y2;
515 
516 	x1 = w - in->x2 - 1;
517 	x2 = w - in->x1 - 1;
518 	y1 = in->y1;
519 	y2 = in->y2;
520 
521 	out->x1 = x1;
522 	out->x2 = x2;
523 	out->y1 = y1;
524 	out->y2 = y2;
525 	rs_rect_normalize(out, out);
526 }
527 
528 /**
529  * Rotate a RS_RECT in 90 degrees steps
530  * @param in A RS_RECT to read values from
531  * @param out A RS_RECT to write the values to (can be the same as in)
532  * @param w The width of the data OUTSIDE the RS_RECT
533  * @param h The height of the data OUTSIDE the RS_RECT
534  * @param quarterturns How many times to turn the rect clockwise
535  */
536 void
rs_rect_rotate(RS_RECT * in,RS_RECT * out,gint w,gint h,gint quarterturns)537 rs_rect_rotate(RS_RECT *in, RS_RECT *out, gint w, gint h, gint quarterturns)
538 {
539 	gint x1,y1;
540 	gint x2,y2;
541 
542 	x1 = in->x2;
543 	x2 = in->x1;
544 	y1 = in->y1;
545 	y2 = in->y2;
546 
547 	switch(quarterturns)
548 	{
549 		case 1:
550 			x1 = h - in->y1-1;
551 			x2 = h - in->y2-1;
552 			y1 = in->x1;
553 			y2 = in->x2;
554 			break;
555 		case 2:
556 			x1 = w - in->x1 - 1;
557 			x2 = w - in->x2 - 1;
558 			y1 = h - in->y1 - 1;
559 			y2 = h - in->y2 - 1;
560 			break;
561 		case 3:
562 			x1 = in->y1;
563 			x2 = in->y2;
564 			y1 = w - in->x1 - 1;
565 			y2 = w - in->x2 - 1;
566 			break;
567 	}
568 
569 	out->x1 = x1;
570 	out->x2 = x2;
571 	out->y1 = y1;
572 	out->y2 = y2;
573 	rs_rect_normalize(out, out);
574 }
575 
576 /**
577  * Reset a property on a GObject to it's default
578  * @param object A GObject
579  * @param property_name A name of a property installed in object's class
580  */
581 void
rs_object_class_property_reset(GObject * object,const gchar * property_name)582 rs_object_class_property_reset(GObject *object, const gchar *property_name)
583 {
584 	GObjectClass *klass = G_OBJECT_GET_CLASS(object);
585 	GParamSpec *spec;
586 	GValue value = {0};
587 
588 	spec = g_object_class_find_property(klass, property_name);
589 	g_assert(spec != NULL);
590 
591 	g_value_init(&value, spec->value_type);
592 
593 	g_param_value_set_default(spec, &value);
594 	g_object_set_property(object, spec->name, &value);
595 
596 	g_value_unset(&value);
597 }
598 
599 /**
600  * Check (and complain if needed) the Rawstudio install
601  */
602 void
check_install(void)603 check_install(void)
604 {
605 #define TEST_FILE_ACCESS(path) do { if (g_access(path, R_OK)!=0) g_debug("Cannot access %s\n", path);} while (0)
606 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "icons" G_DIR_SEPARATOR_S PACKAGE ".png");
607 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "overlay_priority1.png");
608 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "overlay_priority2.png");
609 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "overlay_priority3.png");
610 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "overlay_deleted.png");
611 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "overlay_exported.png");
612 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "transform_flip.png");
613 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "transform_mirror.png");
614 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "transform_90.png");
615 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "transform_180.png");
616 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "transform_270.png");
617 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "cursor-color-picker.png");
618 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "cursor-crop.png");
619 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "cursor-rotate.png");
620 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "tool-color-picker.png");
621 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "tool-crop.png");
622 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "tool-rotate.png");
623 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "ui.xml");
624 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "rawstudio.gtkrc");
625 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "profiles" G_DIR_SEPARATOR_S "generic_camera_profile.icc");
626 	TEST_FILE_ACCESS(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "profiles" G_DIR_SEPARATOR_S "sRGB.icc");
627 #undef TEST_FILE_ACCESS
628 }
629 
630 /* Rewritten from Exiftools - lib/Image/ExifTool/Canon.pm*/
631 gfloat
CanonEv(gint val)632 CanonEv(gint val)
633 {
634 	gfloat sign;
635 	gfloat frac;
636 
637 	/* temporarily make the number positive */
638 	if (val < 0)
639 	{
640 		val = -val;
641 		sign = -1.0;
642 	}
643 	else
644 	{
645 		sign = 1.0;
646 	}
647 
648 	gint ifrac = val & 0x1f;
649 
650 	/* remove fraction */
651 	val -= ifrac;
652 
653 	/* Convert 1/3 and 2/3 codes */
654 	if (ifrac == 0x0c)
655 		frac = 32.0 / 3.0; /* 0x20 / 3 */
656 	else if (ifrac == 0x14)
657 		frac = 64.0 / 3.0; /* 0x40 / 3 */
658 	else
659 		frac = (gfloat) ifrac;
660 
661 	return sign * (((gfloat)val) + frac) / 32.0;
662 }
663 
664 /**
665  * Split a char * with a given delimiter
666  * @param str The gchar * to be splitted
667  * @param delimiters The gchar * to be used as delimiter (can be more than 1 char)
668  * @return A GList consisting of the different parts of the input string, must be freed using g_free() and g_list_free().
669  */
670 GList *
rs_split_string(const gchar * str,const gchar * delimiters)671 rs_split_string(const gchar *str, const gchar *delimiters) {
672 	gchar **temp = g_strsplit_set(str, delimiters, 0);
673 
674 	int i = 0;
675 	GList *glist = NULL;
676 	while (temp[i])
677 	{
678 		gchar* text = (gchar *) temp[i];
679 		if (text[0] != 0)
680 			glist = g_list_append(glist, text);
681 		else
682 			g_free(text);
683 		i++;
684 	}
685 	g_free(temp);
686 	return glist;
687 }
688 
689 gchar *
rs_file_checksum(const gchar * filename)690 rs_file_checksum(const gchar *filename)
691 {
692 	gchar *checksum = NULL;
693 	struct stat st;
694 	gint fd = open(filename, O_RDONLY);
695 
696 	if (fd > 0)
697 	{
698 		fstat(fd, &st);
699 
700 		gint offset = 0;
701 		gint length = st.st_size;
702 
703 		/* If the file is bigger than 2 KiB, we sample 1 KiB in the middle of the file */
704 		if (st.st_size > 2048)
705 		{
706 			offset = st.st_size/2;
707 			length = 1024;
708 		}
709 
710 		guchar buffer[length];
711 
712 		lseek(fd, offset, SEEK_SET);
713 		gint bytes_read = read(fd, buffer, length);
714 
715 		close(fd);
716 
717 		if (bytes_read == length)
718 			checksum = g_compute_checksum_for_data(G_CHECKSUM_MD5, buffer, length);
719 	}
720 
721 	return checksum;
722 }
723 
724 const gchar *
rs_human_aperture(gdouble aperture)725 rs_human_aperture(gdouble aperture)
726 {
727 	gchar *ret = NULL;
728 
729 	if (aperture < 8)
730 		ret = g_strdup_printf("f/%.1f", aperture);
731 	else
732 		ret = g_strdup_printf("f/%.0f", aperture);
733 
734 	return ret;
735 }
736 
737 const gchar *
rs_human_focal(gdouble min,gdouble max)738 rs_human_focal(gdouble min, gdouble max)
739 {
740 	gchar *ret = NULL;
741 
742 	if (min == max)
743 		ret = g_strdup_printf("%.0fmm", max);
744 	else
745 		ret = g_strdup_printf("%.0f-%.0fmm", min, max);
746 	return ret;
747 }
748 
749 gchar *
rs_normalize_path(const gchar * path)750 rs_normalize_path(const gchar *path)
751 {
752 #ifdef PATH_MAX
753 	gint path_max = PATH_MAX;
754 #else
755 	gint path_max = pathconf(path, _PC_PATH_MAX);
756 	if (path_max <= 0)
757 		path_max = 4096;
758 #endif
759 	gchar *buffer = g_new0(gchar, path_max);
760 
761 	gchar *ret = NULL;
762 #ifdef WIN32
763 	int length = GetFullPathName(path, path_max, buffer, NULL);
764 	if(length == 0){
765 	  g_error("Error normalizing path: %s\n", path);
766 	}
767 	ret = buffer;
768 #else
769 	ret = realpath(path, buffer);
770 #endif
771 
772 	if (ret == NULL)
773 		g_free(buffer);
774 
775 	return ret;
776 }
777 
778 /**
779  * Copy a file from one location to another
780  * @param source An absolute path to a source file
781  * @param deastination An absolute path to a destination file (not folder), will be overwritten if exists
782  * @return TRUE on success, FALSE on failure
783  */
784 gboolean
rs_file_copy(const gchar * source,const gchar * destination)785 rs_file_copy(const gchar *source, const gchar *destination)
786 {
787 	gboolean ret = FALSE;
788 	const gint buffer_size = 1024*1024;
789 	gint source_fd, destination_fd;
790 	gint bytes_read, bytes_written;
791 	struct stat st;
792 	mode_t default_mode = 00666; /* We set this relaxed to respect the users umask */
793 
794 	g_return_val_if_fail(source != NULL, FALSE);
795 	g_return_val_if_fail(source[0] != '\0', FALSE);
796 	g_return_val_if_fail(g_path_is_absolute(source), FALSE);
797 
798 	g_return_val_if_fail(destination != NULL, FALSE);
799 	g_return_val_if_fail(destination[0] != '\0', FALSE);
800 	g_return_val_if_fail(g_path_is_absolute(destination), FALSE);
801 
802 	source_fd = open(source, O_RDONLY);
803 	if (source_fd > 0)
804 	{
805 		/* Try to copy permissions too */
806 		if (fstat(source_fd, &st) == 0)
807 			default_mode = st.st_mode;
808 		destination_fd = creat(destination, default_mode);
809 
810 		if (destination_fd > 0)
811 		{
812 			gpointer buffer = g_malloc(buffer_size);
813 			do {
814 				bytes_read = read(source_fd, buffer, buffer_size);
815 				bytes_written = write(destination_fd, buffer, bytes_read);
816 				if (bytes_written != bytes_read)
817 					g_warning("%s was truncated", destination);
818 			} while(bytes_read > 0);
819 			g_free(buffer);
820 
821 			ret = TRUE;
822 
823 			close(destination_fd);
824 		}
825 		close(source_fd);
826 	}
827 
828 	return ret;
829 }
830 
831 /**
832  * Removes tailing spaces from a gchar *
833  * @param str A gchar * to have tailing spaces removed
834  * @param inplace Set to TRUE if string should be edited inplace
835  * @return A gchar * with tailing spaces removed
836  */
837 gchar *
rs_remove_tailing_spaces(gchar * str,gboolean inplace)838 rs_remove_tailing_spaces(gchar *str, gboolean inplace)
839 {
840 	gint i;
841 	gchar *ret = str;
842 
843 	g_return_val_if_fail(str != NULL, NULL);
844 
845 	if (!inplace)
846 		ret = g_strdup(str);
847 
848 	for(i = strlen(ret)-1; i > 0; i--)
849 	{
850 		if (ret[i] == 0x20)
851 			ret[i] = 0x00;
852 		else
853 			i = 0;
854 	}
855 
856 	return ret;
857 }
858