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