2
* * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3
* * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
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.
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.
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.
20
#define _XOPEN_SOURCE 500 /* strptime() and realpath() */
21
#include <rawstudio.h>
24
#include <glib/gstdio.h>
26
#include <pthread.h> /* MinGW WIN32 gmtime_r() */
29
#include <sys/types.h>
35
#include "conf_interface.h"
37
#define DOTDIR ".rawstudio"
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
46
rs_atof(const gchar *str)
48
gdouble result = 0.0f;
50
gboolean point_passed = FALSE;
52
gchar *ptr = (gchar *) str;
56
if (g_ascii_isdigit(*ptr))
58
result = result * 10.0f + g_ascii_digit_value(*ptr);
64
else if (g_ascii_ispunct(*ptr))
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
79
rs_exiftime_to_unixtime(const gchar *str)
81
struct tm *tm = g_new0(struct tm, 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);
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
100
rs_unixtime_to_exiftime(GTime timestamp)
102
struct tm *tm = g_new0(struct tm, 1);
103
time_t tt = (time_t) timestamp;
104
gchar *result = g_new0(gchar, 20);
108
if (strftime(result, 20, "%Y:%m:%d %H:%M:%S", tm) != 19)
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
127
rs_constrain_to_bounding_box(gint target_width, gint target_height, gint *width, gint *height)
129
gdouble target_aspect = ((gdouble)target_width) / ((gdouble)target_height);
130
gdouble input_aspect = ((gdouble)*width) / ((gdouble)*height);
133
if (target_aspect < input_aspect)
134
scale = ((gdouble) *width) / ((gdouble) target_width);
136
scale = ((gdouble) *height) / ((gdouble) target_height);
138
*width = MIN((gint) ((gdouble)*width) / scale, target_width);
139
*height = MIN((gint) ((gdouble)*height) / scale, target_height);
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
148
rs_get_number_of_processor_cores(void)
150
static GStaticMutex lock = G_STATIC_MUTEX_INIT;
152
/* We assume processors will not be added/removed during our lifetime */
158
g_static_mutex_lock (&lock);
161
/* Use a temporary for thread safety */
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 */
171
io = g_io_channel_new_file("/proc/cpuinfo", "r", NULL);
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))
178
if (g_str_has_prefix(line, "processor"))
182
g_io_channel_shutdown(io, FALSE, NULL);
183
g_io_channel_unref(io);
185
#elif defined(_WIN32)
186
/* Use pthread on windows */
187
temp_num = pthread_num_processors_np();
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);
194
g_static_mutex_unlock (&lock);
199
#if defined (__i386__) || defined (__x86_64__)
201
#define xgetbv(index,eax,edx) \
202
__asm__ (".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c" (index))
205
* Detect cpu features
206
* @return A bitmask of @RSCpuFlags
209
rs_detect_cpu_features(void)
211
#define cpuid(cmd, eax, ecx, edx) \
215
"push %%"REG_b"\n\t"\
217
"pop %%"REG_b"\n\t" \
218
: "=a" (eax), "=c" (ecx), "=d" (edx) \
225
static GStaticMutex lock = G_STATIC_MUTEX_INIT;
226
static guint stored_cpuflags = -1;
228
if (stored_cpuflags != -1)
229
return stored_cpuflags;
231
g_static_mutex_lock(&lock);
232
if (stored_cpuflags == -1)
235
/* Test cpuid presence comparing eflags */
240
"mov %%"REG_a", %%"REG_b"\n\t"
241
"xor $0x00200000, %%"REG_a"\n\t"
246
"cmp %%"REG_a", %%"REG_b"\n\t"
262
/* Get the standard level */
263
cpuid(0x00000000, std_dsc, ecx, edx);
267
/* Request for standard features */
268
cpuid(0x00000001, std_dsc, ecx, edx);
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;
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)
290
if ((eax & 0x6) == 0x6)
291
cpuflags |= RS_CPU_FLAG_AVX;
295
/* Is there extensions */
296
cpuid(0x80000000, ext_dsc, ecx, edx);
300
/* Request for extensions */
301
cpuid(0x80000001, eax, ecx, edx);
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;
311
/* ISSE is also implied in SSE */
312
if (cpuflags & RS_CPU_FLAG_SSE)
313
cpuflags |= RS_CPU_FLAG_AMD_ISSE;
315
stored_cpuflags = cpuflags;
317
g_static_mutex_unlock(&lock);
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);
334
return(stored_cpuflags);
340
rs_detect_cpu_features()
344
#endif /* __i386__ || __x86_64__ */
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
354
static gchar *dir = NULL;
355
static GStaticMutex lock = G_STATIC_MUTEX_INIT;
357
g_static_mutex_lock(&lock);
360
const gchar *home = g_get_home_dir();
361
dir = g_build_filename(home, ".rawstudio", NULL);
364
g_mkdir_with_parents(dir, 00755);
365
g_static_mutex_unlock(&lock);
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
376
rs_dotdir_get(const gchar *filename)
381
gboolean dotdir_is_local = FALSE;
383
rs_conf_get_boolean(CONF_CACHEDIR_IS_LOCAL, &dotdir_is_local);
385
if (g_file_test(filename, G_FILE_TEST_IS_DIR))
386
directory = g_strdup(filename);
388
directory = g_path_get_dirname(filename);
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);
400
dotdir = g_string_new(directory);
401
dotdir = g_string_append(dotdir, G_DIR_SEPARATOR_S);
402
dotdir = g_string_append(dotdir, DOTDIR);
405
if (!g_file_test(dotdir->str, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
407
if (g_mkdir_with_parents(dotdir->str, 0700) != 0)
412
else if (g_file_test(dotdir->str, G_FILE_TEST_IS_DIR))
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 */
422
g_string_free(dotdir, TRUE);
424
if (g_file_test(filename, G_FILE_TEST_IS_REGULAR))
426
gchar* md5 = rs_file_checksum(filename);
427
ret = g_strdup_printf("%s/read-only-cache/%s", rs_confdir_get(), md5);
429
if (!g_file_test(ret, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
431
if (g_mkdir_with_parents(ret, 0700) != 0)
438
g_string_free(dotdir, FALSE);
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)
448
rs_rect_normalize(RS_RECT *in, RS_RECT *out)
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
486
rs_rect_flip(RS_RECT *in, RS_RECT *out, gint w, gint h)
500
rs_rect_normalize(out, out);
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
511
rs_rect_mirror(RS_RECT *in, RS_RECT *out, gint w, gint h)
525
rs_rect_normalize(out, out);
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
537
rs_rect_rotate(RS_RECT *in, RS_RECT *out, gint w, gint h, gint quarterturns)
573
rs_rect_normalize(out, out);
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
582
rs_object_class_property_reset(GObject *object, const gchar *property_name)
584
GObjectClass *klass = G_OBJECT_GET_CLASS(object);
588
spec = g_object_class_find_property(klass, property_name);
589
g_assert(spec != NULL);
591
g_value_init(&value, spec->value_type);
593
g_param_value_set_default(spec, &value);
594
g_object_set_property(object, spec->name, &value);
596
g_value_unset(&value);
600
* Check (and complain if needed) the Rawstudio install
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
630
/* Rewritten from Exiftools - lib/Image/ExifTool/Canon.pm*/
637
/* temporarily make the number positive */
648
gint ifrac = val & 0x1f;
650
/* remove fraction */
653
/* Convert 1/3 and 2/3 codes */
655
frac = 32.0 / 3.0; /* 0x20 / 3 */
656
else if (ifrac == 0x14)
657
frac = 64.0 / 3.0; /* 0x40 / 3 */
659
frac = (gfloat) ifrac;
661
return sign * (((gfloat)val) + frac) / 32.0;
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().
671
rs_split_string(const gchar *str, const gchar *delimiters) {
672
gchar **temp = g_strsplit_set(str, delimiters, 0);
678
gchar* text = (gchar *) temp[i];
680
glist = g_list_append(glist, text);
690
rs_file_checksum(const gchar *filename)
692
gchar *checksum = NULL;
694
gint fd = open(filename, O_RDONLY);
701
gint length = st.st_size;
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)
706
offset = st.st_size/2;
710
guchar buffer[length];
712
lseek(fd, offset, SEEK_SET);
713
gint bytes_read = read(fd, buffer, length);
717
if (bytes_read == length)
718
checksum = g_compute_checksum_for_data(G_CHECKSUM_MD5, buffer, length);
725
rs_human_aperture(gdouble aperture)
730
ret = g_strdup_printf("f/%.1f", aperture);
732
ret = g_strdup_printf("f/%.0f", aperture);
738
rs_human_focal(gdouble min, gdouble max)
743
ret = g_strdup_printf("%.0fmm", max);
745
ret = g_strdup_printf("%.0f-%.0fmm", min, max);
750
rs_normalize_path(const gchar *path)
753
gint path_max = PATH_MAX;
755
gint path_max = pathconf(path, _PC_PATH_MAX);
759
gchar *buffer = g_new0(gchar, path_max);
763
int length = GetFullPathName(path, path_max, buffer, NULL);
765
g_error("Error normalizing path: %s\n", path);
769
ret = realpath(path, buffer);
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
785
rs_file_copy(const gchar *source, const gchar *destination)
787
gboolean ret = FALSE;
788
const gint buffer_size = 1024*1024;
789
gint source_fd, destination_fd;
790
gint bytes_read, bytes_written;
792
mode_t default_mode = 00666; /* We set this relaxed to respect the users umask */
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);
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);
802
source_fd = open(source, O_RDONLY);
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);
810
if (destination_fd > 0)
812
gpointer buffer = g_malloc(buffer_size);
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);
823
close(destination_fd);
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
838
rs_remove_tailing_spaces(gchar *str, gboolean inplace)
843
g_return_val_if_fail(str != NULL, NULL);
848
for(i = strlen(ret)-1; i > 0; i--)