Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
common/image.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2014, 2016 johannes hanika.
4 Copyright (C) 2010, 2015 Bruce Guenter.
5 Copyright (C) 2010-2012 Henrik Andersson.
6 Copyright (C) 2010-2012 Jérémy Rosen.
7 Copyright (C) 2010 Stuart Henderson.
8 Copyright (C) 2010-2018, 2020 Tobias Ellinghaus.
9 Copyright (C) 2010 Zeus V Panchenko.
10 Copyright (C) 2011 Brian Teague.
11 Copyright (C) 2011, 2013 José Carlos García Sogo.
12 Copyright (C) 2011 Robert Bieber.
13 Copyright (C) 2012 Alexander Clausen.
14 Copyright (C) 2012 Christian Tellefsen.
15 Copyright (C) 2012-2013 Dennis Gnad.
16 Copyright (C) 2012 James C. McPherson.
17 Copyright (C) 2012 marcel.
18 Copyright (C) 2012 Richard Wonka.
19 Copyright (C) 2012-2014 Ulrich Pegelow.
20 Copyright (C) 2013-2016, 2018-2022 Pascal Obry.
21 Copyright (C) 2013-2016 Roman Lebedev.
22 Copyright (C) 2014 Pascal de Bruijn.
23 Copyright (C) 2014-2016 Pedro Côrte-Real.
24 Copyright (C) 2016 Mark Oteiza.
25 Copyright (C) 2016-2018 Peter Budai.
26 Copyright (C) 2017, 2019 luzpaz.
27 Copyright (C) 2017 Žilvinas Žaltiena.
28 Copyright (C) 2018-2019 Edgardo Hoszowski.
29 Copyright (C) 2018 Kelvie Wong.
30 Copyright (C) 2018 Mario Lueder.
31 Copyright (C) 2019-2022 Aldric Renaudin.
32 Copyright (C) 2019 August Schwerdfeger.
33 Copyright (C) 2019 Bill Ferguson.
34 Copyright (C) 2019 fvollmer.
35 Copyright (C) 2019-2022 Hanno Schwalm.
36 Copyright (C) 2019 jakubfi.
37 Copyright (C) 2019-2022 Philippe Weyland.
38 Copyright (C) 2020-2026 Aurélien PIERRE.
39 Copyright (C) 2020 Chris Elston.
40 Copyright (C) 2020 Dan Torop.
41 Copyright (C) 2020 GrahamByrnes.
42 Copyright (C) 2020 hatsunearu.
43 Copyright (C) 2020 Hubert Kowalski.
44 Copyright (C) 2020 JP Verrue.
45 Copyright (C) 2020 Marco.
46 Copyright (C) 2020 U-DESKTOP-HQME86J\marco.
47 Copyright (C) 2021 Daniel Vogelbacher.
48 Copyright (C) 2021-2022 HansBull.
49 Copyright (C) 2021 Marco Carrarini.
50 Copyright (C) 2021-2022 Miloš Komarčević.
51 Copyright (C) 2021 Ralf Brown.
52 Copyright (C) 2021 wpferguson.
53 Copyright (C) 2022 Martin Bařinka.
54 Copyright (C) 2022 Nicolas Auffray.
55 Copyright (C) 2022 paolodepetrillo.
56 Copyright (C) 2022 Victor Forsiuk.
57 Copyright (C) 2023 Ricky Moon.
58 Copyright (C) 2024-2025 Guillaume Stutin.
59
60 darktable is free software: you can redistribute it and/or modify
61 it under the terms of the GNU General Public License as published by
62 the Free Software Foundation, either version 3 of the License, or
63 (at your option) any later version.
64
65 darktable is distributed in the hope that it will be useful,
66 but WITHOUT ANY WARRANTY; without even the implied warranty of
67 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
68 GNU General Public License for more details.
69
70 You should have received a copy of the GNU General Public License
71 along with darktable. If not, see <http://www.gnu.org/licenses/>.
72*/
73
74#include "common/image.h"
75#include "common/paths.h" // DT_PATH_MAX
76#include "common/image_notify.h"
78#include "common/act_on.h"
80#include "develop/imageop.h"
81#include "common/collection.h"
82#include "system/dtpthread.h"
83#include "common/xmp_sidecar.h"
84#include "metadata/exif.h"
86#include "common/grouping.h"
87#include "history/history.h"
93#include "caches/image_cache.h"
96#include "caches/mipmap_cache.h"
97#include "metadata/notify.h"
98#include "metadata/ratings.h"
99#include "metadata/tags.h"
100#include "common/undo.h"
101#include "common/selection.h"
102#include "common/datetime.h"
103#include "common/conf.h"
104#include "control/control.h"
105#include "system/atomic.h"
106#include "develop/lightroom.h"
107#include "develop/develop.h"
108#include "views/view.h"
109#include "win/filepath.h"
110#include <assert.h>
111#include <ctype.h>
112#include <math.h>
113#include <sqlite3.h>
114#include <stdlib.h>
115#include <string.h>
116#include <strings.h>
117#ifndef _WIN32
118#include <glob.h>
119#endif
120#include <glib/gstdio.h>
121#include "common/utility.h"
122
124{
125 int32_t imgid;
126 gboolean before;
127 gboolean after;
129
136
143
150
151static void _pop_undo_execute(const int32_t imgid, const gboolean before, const gboolean after);
152static int32_t _image_duplicate_with_version(const int32_t imgid, const int32_t newversion, const gboolean undo,
153 const gboolean reload);
154static void _pop_undo(gpointer user_data, const dt_undo_type_t type, dt_undo_data_t data, const dt_undo_action_t action, GList **imgs);
155
156static void _copy_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path);
157static void _move_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path, const gboolean overwrite);
158
159// NULL terminated list of supported non-RAW extensions
160// const char *dt_non_raw_extensions[]
161// = { ".jpeg", ".jpg", ".pfm", ".hdr", ".exr", ".pxn", ".tif", ".tiff", ".png",
162// ".j2c", ".j2k", ".jp2", ".jpc", ".gif", ".jpc", ".jp2", ".bmp", ".dcm",
163// ".jng", ".miff", ".mng", ".pbm", ".pnm", ".ppm", ".pgm", NULL };
164gboolean dt_image_is_raw(const dt_image_t *img)
165{
166 return (img->flags & DT_IMAGE_RAW) != 0;
167}
168
169// LDR / HDR are flag-only predicates now: no filename-extension sniffing. The flags are set by the
170// decoding codec from the real file content (the buffer datatype is not a dynamic-range signal:
171// every raster decodes into a TYPE_FLOAT working buffer regardless of LDR/HDR), so these report what
172// was really loaded, not what the file name suggested. They are kept as a public API so callers
173// don't open-code the bitmask test.
174gboolean dt_image_is_ldr(const dt_image_t *img)
175{
176 return (img->flags & DT_IMAGE_LDR) != 0;
177}
178
179gboolean dt_image_is_hdr(const dt_image_t *img)
180{
181 return (img->flags & DT_IMAGE_HDR) != 0;
182}
183
185{
187}
188
189static void _image_set_monochrome_flag(const int32_t imgid, gboolean monochrome, gboolean undo_on)
190{
191 dt_image_t *img = NULL;
192 gboolean changed = FALSE;
193
194 img = dt_image_cache_get(imgid, 'r');
195 if(img)
196 {
197 const int mask_bw = dt_image_monochrome_flags(img);
199
200 if((!monochrome) && (mask_bw & DT_IMAGE_MONOCHROME_PREVIEW))
201 {
202 // wanting it to be color found preview
203 img = dt_image_cache_get(imgid, 'w');
205 changed = TRUE;
206 }
207 if(monochrome && ((mask_bw == 0) || (mask_bw == DT_IMAGE_MONOCHROME_PREVIEW)))
208 {
209 // wanting monochrome and found color or just preview without workflow activation
210 img = dt_image_cache_get(imgid, 'w');
212 changed = TRUE;
213 }
214 if(changed)
215 {
216 const int mask = dt_image_monochrome_flags(img);
218
219 if(undo_on)
220 {
222 undomono->imgid = imgid;
223 undomono->before = mask_bw;
224 undomono->after = mask;
225 dt_undo_record(dt_undo_get_global(), NULL, DT_UNDO_FLAGS, undomono, _pop_undo, g_free);
226 }
227 }
228 }
229 else
230 fprintf(stderr,"[image] could not dt_image_cache_get imgid %i\n", imgid);
231}
232
233void dt_image_set_monochrome_flag(const int32_t imgid, gboolean monochrome)
234{
235 _image_set_monochrome_flag(imgid, monochrome, TRUE);
236}
237
238static void _pop_undo_execute(const int32_t imgid, const gboolean before, const gboolean after)
239{
240 _image_set_monochrome_flag(imgid, after, FALSE);
241}
242
243static gboolean _image_matrix_has_data(const float *matrix, const int count)
244{
245 float sum = 0.0f;
246 for(int i = 0; i < count; i++)
247 {
248 if(!isfinite(matrix[i])) return FALSE;
249 sum += fabsf(matrix[i]);
250 }
251 return sum > 0.0f;
252}
253
255{
256 // Whether a camera input matrix (and the white balance / color calibration it feeds) applies
257 // depends only on the colorimetry axis, never on the mosaic axis: an already-demosaiced raw
258 // (sRAW / linear DNG, e.g. DxO PureRAW) still carries raw colorimetry and an embedded matrix
259 // and must get matrix correction. A previous `S_RAW && dsc.filters == 0 -> FALSE` short-circuit
260 // wrongly excluded exactly those files, leaving white balance and color calibration disabled
261 // (green renders, issue #729).
262 if(!(img->flags & (DT_IMAGE_RAW | DT_IMAGE_S_RAW))) return FALSE;
263 if(img->flags & DT_IMAGE_MONOCHROME) return FALSE;
264
265 const gboolean has_d65 = _image_matrix_has_data(img->d65_color_matrix, 9);
266 const gboolean has_adobe = _image_matrix_has_data(&img->adobe_XYZ_to_CAM[0][0], 9);
267
268 return (has_d65 || has_adobe) ? TRUE : FALSE;
269}
270
276
281
282/* ------------------------------------------------------------------------------------------
283 * Canonical image-type API. See image.h and src/doc/image-type-detection.md.
284 * Each predicate tests exactly one independent flag fact; no filename sniffing here.
285 * ------------------------------------------------------------------------------------------ */
286
288{
289 return (img->flags & DT_IMAGE_BUFFER_RESOLVED) == 0;
290}
291
292gboolean dt_image_is_sraw(const dt_image_t *img)
293{
294 return (img->flags & DT_IMAGE_S_RAW) != 0;
295}
296
298{
299 return (img->flags & DT_IMAGE_MOSAIC) != 0;
300}
301
303{
304 // both mosaiced raw and already-demosaiced sRAW/linear-DNG carry raw colorimetry
305 return (img->flags & (DT_IMAGE_RAW | DT_IMAGE_S_RAW)) != 0;
306}
307
309{
310 const gboolean resolved = (img->flags & DT_IMAGE_BUFFER_RESOLVED) != 0;
311 const gboolean raw_colorimetry = (img->flags & (DT_IMAGE_RAW | DT_IMAGE_S_RAW)) != 0;
312
313 // Raw colorimetry takes precedence over the LDR/HDR bit-depth flags: a float mosaiced raw
314 // is flagged both RAW and HDR but must still be treated as a mosaiced raw, never as RGB HDR.
315 if(raw_colorimetry)
316 {
317 // Once decoded, the mosaic bit is authoritative and tells a mosaiced raw apart from an
318 // already-demosaiced raw (sRAW / linear DNG).
319 if(resolved)
321
322 // Provisional (not decoded yet, or a database predating DT_IMAGE_BUFFER_RESOLVED):
323 // DT_IMAGE_S_RAW is only ever set by a codec on a real decode (the extension detector
324 // never sets it), so it reliably means an already-demosaiced raw even without the resolved
325 // bit. Otherwise assume the common case of a mosaiced sensor raw; dt_image_buffer_resolve_flags()
326 // corrects the guess on first decode.
327 if((img->flags & DT_IMAGE_S_RAW) && !(img->flags & DT_IMAGE_RAW))
330 }
331
334
336}
337
339{
341}
342
344{
345 switch(klass)
346 {
347 case DT_IMAGE_PIPE_MOSAIC_RAW: return "mosaic-raw";
348 case DT_IMAGE_PIPE_LINEAR_RAW: return "linear-raw";
349 case DT_IMAGE_PIPE_RGB_LDR: return "rgb-ldr";
350 case DT_IMAGE_PIPE_RGB_HDR: return "rgb-hdr";
352 default: return "unknown";
353 }
354}
355
357{
358 if(IS_NULL_PTR(img)) return;
359
360 // The decoded buffer descriptor is the authoritative source for the mosaic axis.
361 if(img->dsc.filters != 0u)
362 img->flags |= DT_IMAGE_MOSAIC;
363 else
364 img->flags &= ~DT_IMAGE_MOSAIC;
365
366 // The LDR/HDR axis is NOT derivable from the buffer datatype: every raster codec decodes into a
367 // TYPE_FLOAT working buffer in RAM regardless of the file's real dynamic range (sRGB JPEG/PNG/WebP
368 // all land as float just like Radiance/PFM/EXR). The decoding codec is the authoritative source
369 // and has already set DT_IMAGE_LDR / DT_IMAGE_HDR from the actual file content, so we must not
370 // override it here from the datatype — doing so re-flagged every display-referred raster as HDR.
371 //
372 // The only normalization we still own is the raw-sensor case: mosaiced / sRAW integer data is
373 // scene-linear sensor data, neither display LDR nor HDR. A float raw (e.g. HDRMerge, legacy float
374 // DNG) is left flagged HDR exactly as the raw codec set it.
376 img->flags &= ~(DT_IMAGE_LDR | DT_IMAGE_HDR);
377
379}
380
382{
383 if(IS_NULL_PTR(img)) return;
384 // Never clobber a descriptor that a codec has already produced.
385 if(img->flags & DT_IMAGE_BUFFER_RESOLVED) return;
386
387 switch(dt_image_pipe_class(img))
388 {
390 img->dsc.channels = 4; img->dsc.datatype = TYPE_UINT8; img->dsc.filters = 0u; img->dsc.cst = IOP_CS_RGB;
391 break;
393 img->dsc.channels = 4; img->dsc.datatype = TYPE_FLOAT; img->dsc.filters = 0u; img->dsc.cst = IOP_CS_RGB;
394 break;
396 // filters is unknown until decode; leave 0 as a placeholder (the class is carried by
397 // flags, not by this provisional filters value).
398 img->dsc.channels = 1; img->dsc.datatype = TYPE_UINT16; img->dsc.filters = 0u; img->dsc.cst = IOP_CS_RAW;
399 break;
401 img->dsc.channels = 4; img->dsc.datatype = TYPE_FLOAT; img->dsc.filters = 0u; img->dsc.cst = IOP_CS_RAW;
402 break;
404 default:
405 return; // nothing reliable to seed
406 }
408}
409
411{
412 switch(type)
413 {
414 case TYPE_FLOAT:
415 return "float";
416 case TYPE_UINT16:
417 return "uint16";
418 case TYPE_UINT8:
419 return "uint8";
420 case TYPE_UNKNOWN:
421 default:
422 return "unknown";
423 }
424}
425
426static const char *_image_colorspace_to_string(const dt_image_colorspace_t colorspace)
427{
428 switch(colorspace)
429 {
431 return "sRGB";
433 return "AdobeRGB";
435 default:
436 return "none";
437 }
438}
439
440void dt_image_print_debug_info(const dt_image_t *img, const char *context)
441{
442 if(IS_NULL_PTR(img)) return;
443
444 const char *ctx = context ? context : "image";
445 const dt_iop_buffer_dsc_t *dsc = &img->dsc;
446 const gboolean is_raw = dt_image_is_raw(img);
447 const gboolean is_ldr = dt_image_is_ldr(img);
448 const gboolean is_hdr = dt_image_is_hdr(img);
449 const gboolean is_mono = dt_image_is_monochrome(img);
450 const gboolean mono_workflow = dt_image_use_monochrome_workflow(img);
451 const int mono_flags = dt_image_monochrome_flags(img);
452 const gboolean mosaic = dsc->filters != 0u;
453 const gboolean xtrans = dsc->filters == 9u;
454 const gboolean bayer = mosaic && !xtrans;
455 const size_t bpp = dsc->bpp;
456 int bit_depth = 0;
457
458 switch(dsc->datatype)
459 {
460 case TYPE_FLOAT:
461 bit_depth = 32;
462 break;
463 case TYPE_UINT16:
464 bit_depth = 16;
465 break;
466 case TYPE_UINT8:
467 bit_depth = 8;
468 break;
469 case TYPE_UNKNOWN:
470 default:
471 if(dsc->channels != 0)
472 bit_depth = (int)(bpp * 8 / dsc->channels);
473 break;
474 }
475
476 const unsigned int flags = (unsigned int)img->flags;
477
479 "[image debug] %s id=%d filename='%s' fullpath='%s'\n",
480 ctx, img->id, img->filename, img->fullpath);
482 "[image debug] %s size=%dx%d crop=%dx%d+%d+%d orientation=%d psize=%dx%d pixel_aspect=%.6f\n",
483 ctx, img->width, img->height, img->crop_width, img->crop_height, img->crop_x, img->crop_y,
484 img->orientation, img->p_width, img->p_height, img->pixel_aspect_ratio);
486 "[image debug] %s flags=0x%08x raw=%d non_raw=%d ldr=%d hdr=%d sraw=%d 4bayer=%d mosaic=%d xtrans=%d "
487 "bayer=%d mono=%d mono_flags=0x%x mono_workflow=%d mono_preview=%d mono_bayer=%d bw=%d bw_flow=%d "
488 "local_copy=%d has_txt=%d has_wav=%d addl_dng=%d auto_presets=%d no_legacy_presets=%d rejected=%d remove=%d "
489 "has_localcopy=%d has_audio=%d is_hdr_field=%d\n",
490 ctx, flags, is_raw, !is_raw, is_ldr, is_hdr,
491 (flags & DT_IMAGE_S_RAW) != 0, (flags & DT_IMAGE_4BAYER) != 0, mosaic, xtrans, bayer, is_mono,
492 mono_flags, mono_workflow, (flags & DT_IMAGE_MONOCHROME_PREVIEW) != 0,
493 (flags & DT_IMAGE_MONOCHROME_BAYER) != 0, img->is_bw, img->is_bw_flow,
497 img->has_localcopy, img->has_audio, img->is_hdr);
499 "[image debug] %s buf: channels=%u datatype=%s bit_depth=%d bpp=%" G_GSIZE_FORMAT " filters=%u cst=%d colorspace=%s "
500 "processed_max=[%.4f %.4f %.4f %.4f]\n",
501 ctx, dsc->channels, _image_buf_type_to_string(dsc->datatype), bit_depth, bpp, dsc->filters, dsc->cst,
503 dsc->processed_maximum[2], dsc->processed_maximum[3]);
505 "[image debug] %s colorspace=enum:%s d65_color_matrix=[%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f]\n",
507 img->d65_color_matrix[2], img->d65_color_matrix[3], img->d65_color_matrix[4], img->d65_color_matrix[5],
508 img->d65_color_matrix[6], img->d65_color_matrix[7], img->d65_color_matrix[8]);
510 "[image debug] %s raw: black=%u separate=[%u %u %u %u] white=%u rawprepare=[%u %u]\n",
515 "[image debug] %s class=%s state=%s needs_rawprepare=%d needs_demosaic=%d is_mosaiced=%d is_sraw=%d has_matrix=%d\n",
517 dt_image_pipe_class_is_provisional(img) ? "provisional" : "resolved",
520}
521
522const char *dt_image_film_roll_name(const char *path)
523{
524 const char *folder = path + strlen(path);
525 const int numparts = CLAMPS(dt_conf_get_int("show_folder_levels"), 1, 5);
526 int count = 0;
527 while(folder > path)
528 {
529
530#ifdef _WIN32
531 // in Windows, both \ and / can be folder separator
532 if(*folder == G_DIR_SEPARATOR || *folder == '/')
533#else
534 if(*folder == G_DIR_SEPARATOR)
535#endif
536
537 if(++count >= numparts)
538 {
539 ++folder;
540 break;
541 }
542 --folder;
543 }
544 return folder;
545}
546
547void dt_image_film_roll_directory(const dt_image_t *img, char *pathname, size_t pathname_len)
548{
550 if(folder)
551 {
552 g_strlcpy(pathname, folder, pathname_len);
554 }
555 pathname[pathname_len - 1] = '\0';
556}
557
558void dt_image_film_roll(const dt_image_t *img, char *pathname, size_t pathname_len)
559{
560 if(img->film_id < 0)
561 {
562 g_strlcpy(pathname, _("orphaned image"), pathname_len);
563 return;
564 }
565
567 if(folder)
568 {
569 g_strlcpy(pathname, dt_image_film_roll_name(folder), pathname_len);
571 }
572 else
573 {
574 g_strlcpy(pathname, _("orphaned image"), pathname_len);
575 }
576 pathname[pathname_len - 1] = '\0';
577}
578
579// dt_image_get_xmp_mode() is on the hot path: called from many threads (image cache
580// write-release, export/save-xmp jobs...) for every single image. Re-parsing the conf string on
581// every call meant every reader raced every writer on the same key -- a torn read could
582// masquerade as an unrecognized/disabled value and silently skip writing a sidecar. The setting
583// itself changes only when the user acts (preferences dialog) or once at startup, so it is read
584// from conf exactly those two times and cached here the rest of the time -- same lifecycle as
585// dt_mipmap_cache_settings_t in darktable.c: "read at startup, re-read when the user changes it,
586// never anywhere else". dt_image_xmp_mode_refresh_from_conf() does the actual conf read+sanitize
587// and is wired to DT_SIGNAL_PREFERENCES_CHANGE in dt_init().
588static dt_atomic_int _xmp_mode_cache = 1; // matches the confgen default (TRUE) until first refresh
589
591{
593}
594
596{
597 // Write sidecars when the setting is absent, consistently with the default exposed in the preferences.
598 gboolean res = TRUE;
599 char *config = dt_conf_get_string("write_sidecar_files");
600 if(!IS_NULL_PTR(config))
601 {
602 res = FALSE;
603
604 // Darktable > 3.6
605 if(!strcmp(config, "after edit") || !strcmp(config, "on import") || !strcmp(config, "always")) res = TRUE;
606
607 // Darktable < 3.8 and Ansel
608 else if(!strcmp(config, "TRUE"))
609 res = TRUE;
610 }
611
612 // sanitize keys, but only if they actually need it -- this now only ever runs at startup and
613 // on user-initiated preference changes, so there is no concurrent reader/writer race left to
614 // worry about; the guard is just to avoid rewriting an already-canonical value.
615 const char *sanitized = res ? "TRUE" : "FALSE";
616 if(IS_NULL_PTR(config) || strcmp(config, sanitized)) dt_conf_set_string("write_sidecar_files", sanitized);
617
618 dt_free(config);
619
621}
622
623gboolean dt_image_safe_remove(const int32_t imgid)
624{
625 // always safe to remove if we do not have .xmp
626 if(!dt_image_get_xmp_mode()) return TRUE;
627
628 // check whether the original file is accessible
629 char pathname[DT_PATH_MAX] = { 0 };
630 gboolean from_cache = TRUE;
631
632 dt_image_full_path(imgid, pathname, sizeof(pathname), &from_cache, __FUNCTION__);
633
634 if(!from_cache)
635 return TRUE;
636
637 else
638 {
639 // finally check if we have a .xmp for the local copy. If no modification done on the local copy it is safe
640 // to remove.
641 g_strlcat(pathname, ".xmp", sizeof(pathname));
642 return !g_file_test(pathname, G_FILE_TEST_EXISTS);
643 }
644}
645
647 size_t pathname_len, gboolean force_cache)
648{
649 if(IS_NULL_PTR(img) || IS_NULL_PTR(pathname)) return DT_IMAGE_PATH_NONE;
650 pathname[0] = '\0';
651
652 // Start with local copies as file I/O will be better if we have a choice
653 if(img->flags & DT_IMAGE_LOCAL_COPY || force_cache)
654 {
655 if(img->local_copy_path[0] && g_file_test(img->local_copy_path, G_FILE_TEST_EXISTS))
656 {
657 g_strlcpy(pathname, img->local_copy_path, pathname_len);
659 }
660
661 if(img->local_copy_legacy_path[0] && g_file_test(img->local_copy_legacy_path, G_FILE_TEST_EXISTS))
662 {
663 g_strlcpy(pathname, img->local_copy_legacy_path, pathname_len);
665 }
666
667 // Local copy flag might be stale (cache cleared, moved file, etc.). If local copy is missing and
668 // we are not explicitly forcing the cache, fall back to the original image.
669 if(!force_cache && img->fullpath[0] && g_file_test(img->fullpath, G_FILE_TEST_EXISTS))
670 {
671 g_strlcpy(pathname, img->fullpath, pathname_len);
673 }
674 }
675 // Forcing the cache should not consider the original image
676 else
677 {
678 if(img->fullpath[0] && g_file_test(img->fullpath, G_FILE_TEST_EXISTS))
679 {
680 g_strlcpy(pathname, img->fullpath, pathname_len);
682 }
683 }
684
685 // In case the local copy flag was not set properly, but the original file failed
686 // last-chance attempt at getting a possibly forgotten local copy to have some input
687 if(img->local_copy_path[0] && g_file_test(img->local_copy_path, G_FILE_TEST_EXISTS))
688 {
689 g_strlcpy(pathname, img->local_copy_path, pathname_len);
691 }
692
693 return DT_IMAGE_PATH_NONE;
694}
695
705void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
706{
707 if(imgid < 0) return;
709 const gboolean prefer_cache = (from_cache && *from_cache);
710
711 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
712 if(img)
713 {
714 // Preserve legacy semantics: `from_cache = TRUE` means "try local copy first, then fall back to original",
715 // not "only local copy".
716 if(prefer_cache)
717 {
718 source = dt_image_choose_input_path(img, pathname, pathname_len, TRUE);
719 if(source == DT_IMAGE_PATH_NONE)
720 source = dt_image_choose_input_path(img, pathname, pathname_len, FALSE);
721 }
722 else
723 {
724 source = dt_image_choose_input_path(img, pathname, pathname_len, FALSE);
725 }
727 }
728
729 if(from_cache)
730 *from_cache = (source == DT_IMAGE_PATH_LOCAL_COPY
732
733}
734
735void dt_image_local_copy_paths_from_fullpath(const char *fullpath, int32_t imgid, char *local_copy_path,
736 size_t local_copy_len, char *local_copy_legacy_path,
737 size_t local_copy_legacy_len)
738{
739 if(local_copy_path) local_copy_path[0] = '\0';
740 if(local_copy_legacy_path) local_copy_legacy_path[0] = '\0';
741 if(IS_NULL_PTR(fullpath) || !*fullpath || imgid <= 0 || IS_NULL_PTR(local_copy_path) || IS_NULL_PTR(local_copy_legacy_path)) return;
742
743 char *md5_filename = g_compute_checksum_for_string(G_CHECKSUM_MD5, fullpath, strlen(fullpath));
744 if(IS_NULL_PTR(md5_filename)) return;
745
746 char cachedir[DT_PATH_MAX] = { 0 };
747 dt_loc_get_user_cache_dir(cachedir, sizeof(cachedir));
748
749 const char *c = fullpath + strlen(fullpath);
750 while(*c != '.' && c > fullpath) c--;
751
752 // cache filename old format: <cachedir>/img-<id>-<MD5>.<ext>
753 // for upward compatibility we check for the old name, if found we return it
754 snprintf(local_copy_legacy_path, local_copy_legacy_len, "%s/img-%d-%s%s", cachedir, imgid, md5_filename, c);
755
756 // cache filename format: <cachedir>/img-<MD5>.<ext>
757 snprintf(local_copy_path, local_copy_len, "%s/img-%s%s", cachedir, md5_filename, c);
758
759 dt_free(md5_filename);
760}
761
762void dt_image_path_append_version_no_db(int version, char *pathname, size_t pathname_len)
763{
764 // the "first" instance (version zero) does not get a version suffix
765 if(version > 0)
766 {
767 // add version information:
768 char *filename = g_strdup(pathname);
769
770 char *c = pathname + strlen(pathname);
771 while(*c != '.' && c > pathname) c--;
772 snprintf(c, pathname + pathname_len - c, "_%02d", version);
773 c = pathname + strlen(pathname);
774 char *c2 = filename + strlen(filename);
775 while(*c2 != '.' && c2 > filename) c2--;
776 g_strlcpy(c, c2, pathname + pathname_len - c);
777 dt_free(filename);
778 }
779}
780
781void dt_image_path_append_version(const int32_t imgid, char *pathname, size_t pathname_len)
782{
783 // get duplicate suffix
784 const int version = dt_image_repository_get_version(imgid);
785
786 dt_image_path_append_version_no_db(version, pathname, pathname_len);
787}
788
789void dt_image_print_exif(const dt_image_t *img, char *line, size_t line_len)
790{
791 char *exposure_str = dt_util_format_exposure(img->exif_exposure);
792
793 snprintf(line, line_len, "%s f/%.1f %dmm ISO %d", exposure_str, img->exif_aperture, (int)img->exif_focal_length,
794 (int)img->exif_iso);
795
796 dt_free(exposure_str);
797}
798
800{
801 return (flags & DT_IMAGE_REJECTED)
802 ? -1 // rejected image = -1
803 : (flags & DT_VIEW_RATINGS_MASK); // others = 0 .. 5
804}
805
810
811void dt_image_set_xmp_rating(dt_image_t *img, const int rating)
812{
813 // clean flags stars and rejected
815
816 if(rating == -2) // assuming that value -2 cannot be found
817 {
818 img->flags |= (DT_VIEW_RATINGS_MASK & 0);
819 }
820 else if(rating == -1)
821 {
822 img->flags |= DT_IMAGE_REJECTED;
823 }
824 else
825 {
826 img->flags |= (DT_VIEW_RATINGS_MASK & rating);
827 }
828}
829
830void dt_image_get_location(const int32_t imgid, dt_image_geoloc_t *geoloc)
831{
832 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
833 geoloc->longitude = img->geoloc.longitude;
834 geoloc->latitude = img->geoloc.latitude;
835 geoloc->elevation = img->geoloc.elevation;
837}
838
839static void _set_location(const int32_t imgid, const dt_image_geoloc_t *geoloc)
840{
841 /* fetch image from cache */
842 dt_image_t *image = dt_image_cache_get(imgid, 'w');
843
844 memcpy(&image->geoloc, geoloc, sizeof(dt_image_geoloc_t));
845
847}
848
849static void _set_datetime(const int32_t imgid, const char *datetime)
850{
851 /* fetch image from cache */
852 dt_image_t *image = dt_image_cache_get(imgid, 'w');
853
854 dt_datetime_exif_to_img(image, datetime);
855
857}
858
859static void _pop_undo(gpointer user_data, const dt_undo_type_t type, dt_undo_data_t data, const dt_undo_action_t action, GList **imgs)
860{
861 if(type == DT_UNDO_GEOTAG)
862 {
863 int i = 0;
864
865 for(GList *list = (GList *)data; list; list = g_list_next(list))
866 {
867 dt_undo_geotag_t *undogeotag = (dt_undo_geotag_t *)list->data;
868 const dt_image_geoloc_t *geoloc = (action == DT_ACTION_UNDO) ? &undogeotag->before : &undogeotag->after;
869
870 _set_location(undogeotag->imgid, geoloc);
871
872 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undogeotag->imgid));
873 i++;
874 }
875 if(i > 1) dt_control_log((action == DT_ACTION_UNDO)
876 ? _("geo-location undone for %d images")
877 : _("geo-location re-applied to %d images"), i);
878 // the handler copies the list; ours stays ours (the raise it replaces took ownership,
879 // which is why this site used to g_list_copy)
881 }
882 else if(type == DT_UNDO_DATETIME)
883 {
884 int i = 0;
885
886 for(GList *list = (GList *)data; list; list = g_list_next(list))
887 {
888 dt_undo_datetime_t *undodatetime = (dt_undo_datetime_t *)list->data;
889
890 _set_datetime(undodatetime->imgid, (action == DT_ACTION_UNDO)
891 ? undodatetime->before : undodatetime->after);
892
893 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undodatetime->imgid));
894 i++;
895 }
896 if(i > 1) dt_control_log((action == DT_ACTION_UNDO)
897 ? _("date/time undone for %d images")
898 : _("date/time re-applied to %d images"), i);
899 }
900 else if(type == DT_UNDO_DUPLICATE)
901 {
903
904 if(action == DT_ACTION_UNDO)
905 {
906 // remove image
908 }
909 else
910 {
911 // restore image, note that we record the new imgid created while
912 // restoring the duplicate.
914 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undo->new_imgid));
915 }
916 }
917 else if(type == DT_UNDO_FLAGS)
918 {
919 dt_undo_monochrome_t *undomono = (dt_undo_monochrome_t *)data;
920
921 const gboolean before = (action == DT_ACTION_UNDO) ? undomono->after : undomono->before;
922 const gboolean after = (action == DT_ACTION_UNDO) ? undomono->before : undomono->after;
923 _pop_undo_execute(undomono->imgid, before, after);
924 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undomono->imgid));
925 }
926}
927
928static void _geotag_undo_data_free(gpointer data)
929{
930 GList *l = (GList *)data;
931 g_list_free_full(l, dt_free_gpointer);
932 l = NULL;
933}
934
935static void _image_set_location(GList *imgs, const dt_image_geoloc_t *geoloc, GList **undo, const gboolean undo_on)
936{
937 for(GList *images = imgs; images; images = g_list_next(images))
938 {
939 const int32_t imgid = GPOINTER_TO_INT(images->data);
940
941 if(undo_on)
942 {
943 dt_undo_geotag_t *undogeotag = (dt_undo_geotag_t *)malloc(sizeof(dt_undo_geotag_t));
944 undogeotag->imgid = imgid;
945 dt_image_get_location(imgid, &undogeotag->before);
946
947 memcpy(&undogeotag->after, geoloc, sizeof(dt_image_geoloc_t));
948
949 *undo = g_list_append(*undo, undogeotag);
950 }
951
952 _set_location(imgid, geoloc);
953 }
954}
955
956void dt_image_set_locations(const GList *imgs, const dt_image_geoloc_t *geoloc, const gboolean undo_on)
957{
958 if(imgs)
959 {
960 GList *undo = NULL;
962
963 _image_set_location((GList *)imgs, geoloc, &undo, undo_on);
964
965 if(undo_on)
966 {
969 }
970 }
971}
972
973void dt_image_set_location(const int32_t imgid, const dt_image_geoloc_t *geoloc, const gboolean undo_on, const gboolean group_on)
974{
975 GList *imgs = NULL;
976 if(imgid == UNKNOWN_IMAGE)
977 imgs = dt_act_on_get_images();
978 else
979 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
980 if(group_on) dt_grouping_add_grouped_images(&imgs);
981 dt_image_set_locations(imgs, geoloc, undo_on);
982 g_list_free(imgs);
983 imgs = NULL;
984}
985
986static void _image_set_images_locations(const GList *img, const GArray *gloc,
987 GList **undo, const gboolean undo_on)
988{
989 int i = 0;
990 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
991 {
992 const int32_t imgid = GPOINTER_TO_INT(imgs->data);
993 const dt_image_geoloc_t *geoloc = &g_array_index(gloc, dt_image_geoloc_t, i);
994 if(undo_on)
995 {
996 dt_undo_geotag_t *undogeotag = (dt_undo_geotag_t *)malloc(sizeof(dt_undo_geotag_t));
997 undogeotag->imgid = imgid;
998 dt_image_get_location(imgid, &undogeotag->before);
999
1000 memcpy(&undogeotag->after, geoloc, sizeof(dt_image_geoloc_t));
1001
1002 *undo = g_list_prepend(*undo, undogeotag);
1003 }
1004
1005 _set_location(imgid, geoloc);
1006 i++;
1007 }
1008}
1009
1010void dt_image_set_images_locations(const GList *imgs, const GArray *gloc, const gboolean undo_on)
1011{
1012 if(IS_NULL_PTR(imgs) || IS_NULL_PTR(gloc) || (g_list_length((GList *)imgs) != gloc->len))
1013 return;
1014 GList *undo = NULL;
1016
1017 _image_set_images_locations(imgs, gloc, &undo, undo_on);
1018
1019 if(undo_on)
1020 {
1023 }
1024}
1025
1026void dt_image_history_changed(const int32_t imgid, const gboolean refresh_filmstrip)
1027{
1028 if(imgid <= 0) return;
1029
1030 // Reload the cached image metadata from the DB. The caller has already persisted the new
1031 // history there; this refreshes history_items (the count of history entries) in the shared
1032 // image cache. history_items is the "altered" flag that the thumbnail regeneration uses to
1033 // pick raw processing over the (unedited) embedded JPEG, so a stale count makes edits and
1034 // rotations appear to have no effect on the thumbnail (issues #647, #861).
1035 dt_image_t *image = dt_image_cache_get_reload(imgid, 'w');
1036 if(image)
1038
1039 // Drop the stale rendered thumbnail. The mipmap cache regenerates purely on explicit removal,
1040 // never by comparing history hashes, so this is mandatory after any development change.
1042
1043 // The filmstrip is best-effort: refreshing it spawns an export thread that competes with the
1044 // realtime darkroom main preview. Darkroom write paths pass FALSE; lighttable ops pass TRUE.
1045 // No "is there a GUI?" guard needed: with no handler registered this is a no-op.
1046 dt_thumbnail_notify_image_changed(imgid, refresh_filmstrip);
1047}
1048
1049void dt_image_set_flip(const int32_t imgid, const dt_image_orientation_t orientation)
1050{
1051 // push new orientation to sql via additional history entry:
1052 const int iop_flip_MODVER = 2;
1053 const int num = dt_history_repository_get_next_num(imgid);
1054 dt_history_repository_write_item(imgid, num, "flip", &orientation, sizeof(int32_t), iop_flip_MODVER, 1,
1055 NULL, 0, 0, 0, "");
1056 dt_history_repository_set_end(imgid, num + 1);
1057 dt_control_save_xmp(imgid);
1058
1059 // Refresh the cached metadata and thumbnail. Without this the stale history_items keeps the
1060 // image flagged unedited, so the rotated raw keeps showing its (unrotated) embedded JPEG and
1061 // the rotation appears to do nothing unless "never use embedded JPEG" is forced (issue #647).
1063}
1064
1067{
1068 float top = in[0];
1069 float left = in[1];
1070 float bottom = in[2];
1071 float right = in[3];
1072
1073 /* Mirror, then swap, in the exact order iop/flip.c's distort_transform() applies to points:
1074 * x -> width - x, y -> height - y, then (x, y) -> (y, x). On normalized edge coordinates a
1075 * mirror exchanges the two edges of that axis, and the swap exchanges the two axes. */
1076 if(orientation & ORIENTATION_FLIP_X)
1077 {
1078 const float new_left = 1.0f - right;
1079 right = 1.0f - left;
1080 left = new_left;
1081 }
1082
1083 if(orientation & ORIENTATION_FLIP_Y)
1084 {
1085 const float new_top = 1.0f - bottom;
1086 bottom = 1.0f - top;
1087 top = new_top;
1088 }
1089
1090 if(orientation & ORIENTATION_SWAP_XY)
1091 {
1092 const float swap_top = left;
1093 const float swap_bottom = right;
1094 left = top;
1095 right = bottom;
1096 top = swap_top;
1097 bottom = swap_bottom;
1098 }
1099
1100 out[0] = top;
1101 out[1] = left;
1102 out[2] = bottom;
1103 out[3] = right;
1104}
1105
1107{
1108 box[0] = box[1] = 0.0f;
1109 box[2] = box[3] = 1.0f;
1110
1111 if(IS_NULL_PTR(img) || img->usercrop_status != DT_IMAGE_USERCROP_VALID) return FALSE;
1112
1113 for(int i = 0; i < 4; i++) box[i] = img->usercrop[i];
1114 return TRUE;
1115}
1116
1117gboolean dt_image_resolve_usercrop(const int32_t imgid, dt_boundingbox_t box)
1118{
1119 box[0] = box[1] = 0.0f;
1120 box[2] = box[3] = 1.0f;
1121
1122 if(imgid < 1 || !dt_image_cache_is_ready()) return FALSE;
1123
1124 char filename[DT_PATH_MAX] = { 0 };
1125 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1126 if(IS_NULL_PTR(img)) return FALSE;
1127
1129 {
1130 // Already answered for this image, in this session.
1131 const gboolean valid = dt_image_get_usercrop(img, box);
1133 return valid;
1134 }
1135
1136 /* DT_IMAGE_HAS_ADDITIONAL_DNG_TAGS is persisted and is set at import for every image that has
1137 * a usable DefaultUserCrop (it also covers DNG opcodes, so it is a superset -- which is what we
1138 * want here: no valid framing can hide behind a cleared bit). Images without it can be settled
1139 * without touching the file at all, which keeps this off the hot path for whole libraries. */
1141 {
1142 // Cheap enough to redo on every call -- deliberately not memoized, so that populating a whole
1143 // library's thumbnails does not take one image-cache write lock per image to record a "no".
1145 return FALSE;
1146 }
1147
1148 const gboolean has_file
1149 = (dt_image_choose_input_path(img, filename, sizeof(filename), FALSE) != DT_IMAGE_PATH_NONE);
1151 if(!has_file) return FALSE;
1152
1153 /* Parse into a throwaway image, holding no lock: the crop lives in runtime-only state, so a
1154 * thumbnail worker starting from a database-loaded cache entry has to read the file itself.
1155 * Two threads racing here compute the same answer from the same file, so the duplicated work
1156 * is wasteful at worst -- which is cheaper than serializing file I/O behind a write lock. */
1157 dt_image_t probe;
1158 dt_image_init(&probe);
1159 probe.id = imgid;
1160 dt_exif_read_usercrop(&probe, filename);
1161
1162 dt_image_t *write = dt_image_cache_get(imgid, 'w');
1163 if(!IS_NULL_PTR(write))
1164 {
1165 write->usercrop_status = probe.usercrop_status;
1166 for(int i = 0; i < 4; i++) write->usercrop[i] = probe.usercrop[i];
1168 }
1169
1170 return dt_image_get_usercrop(&probe, box);
1171}
1172
1174 dt_boundingbox_t box)
1175{
1176 if(!dt_image_get_usercrop(img, box)) return FALSE;
1177
1178 dt_image_orient_boundingbox(box, orientation, box);
1179 return TRUE;
1180}
1181
1188{
1189 // find the flip module -- the pointer stays valid until darktable shuts down
1190 static dt_iop_module_so_t *flip = NULL;
1191 if(IS_NULL_PTR(flip))
1192 {
1193 for(const GList *modules = dt_iop_get_modules_so(); modules; modules = g_list_next(modules))
1194 {
1195 dt_iop_module_so_t *module = (dt_iop_module_so_t *)(modules->data);
1196 if(!strcmp(module->op, "flip"))
1197 {
1198 flip = module;
1199 break;
1200 }
1201 }
1202 }
1203
1205
1206 // db lookup flip params
1207 if(flip && flip->have_introspection && flip->get_p)
1208 {
1209 void *params = NULL;
1210 int32_t params_len = 0;
1211 if(dt_history_repository_get_last_enabled_params(imgid, "flip", &params, &params_len))
1212 {
1213 // use introspection to get the orientation from the binary params blob
1214 orientation = *((dt_image_orientation_t *)flip->get_p(params, "orientation"));
1215 dt_free(params);
1216 }
1217 }
1218
1219 return orientation;
1220}
1221
1223{
1224 if(IS_NULL_PTR(img)) return ORIENTATION_NONE;
1225
1227 return (orientation != ORIENTATION_NULL) ? orientation : dt_image_orientation(img);
1228}
1229
1231{
1233
1234 if(orientation == ORIENTATION_NULL)
1235 {
1236 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1237 if(IS_NULL_PTR(img)) return ORIENTATION_NONE;
1238 orientation = dt_image_orientation(img);
1240 }
1241
1242 return orientation;
1243}
1244
1254{
1255 if(orientation & ORIENTATION_SWAP_XY)
1256 return orientation ^ ORIENTATION_FLIP_Y;
1257 else
1258 return orientation ^ ORIENTATION_FLIP_X;
1259}
1260
1263{
1264 if(orientation & ORIENTATION_SWAP_XY)
1265 return orientation ^ ORIENTATION_FLIP_X;
1266 else
1267 return orientation ^ ORIENTATION_FLIP_Y;
1268}
1269
1270void dt_image_flip(const int32_t imgid, const dt_image_transform_t transform)
1271{
1272 // this is light table only:
1274 if(dt_dev_get_global()->image_storage.id == imgid && cv->view((dt_view_t *)cv) == DT_VIEW_DARKROOM) return;
1275
1277 hist->imgid = imgid;
1279
1281
1282 switch(transform)
1283 {
1285 // mirror horizontally, then transpose
1286 orientation = _orientation_mirror_horizontally(orientation) ^ ORIENTATION_SWAP_XY;
1287 break;
1288
1290 // mirror vertically, then transpose
1291 orientation = _orientation_mirror_vertically(orientation) ^ ORIENTATION_SWAP_XY;
1292 break;
1293
1295 orientation = _orientation_mirror_horizontally(orientation);
1296 break;
1297
1299 orientation = _orientation_mirror_vertically(orientation);
1300 break;
1301
1303 // NULL, not NONE: hand the image back to whatever its EXIF says, rather than declaring it
1304 // upright. The previous code computed a quarter turn first and then threw it away.
1305 orientation = ORIENTATION_NULL;
1306 break;
1307 }
1308
1309 // dt_image_set_flip() writes the new orientation history entry and notifies the caches/GUI
1310 // (mipmap invalidation + thumbnail refresh) via dt_image_history_changed().
1311 dt_image_set_flip(imgid, orientation);
1312
1316}
1317
1318int32_t dt_image_duplicate(const int32_t imgid)
1319{
1320 return dt_image_duplicate_with_version(imgid, -1);
1321}
1322
1323
1324
1325static int32_t _image_duplicate_with_version(const int32_t imgid, const int32_t newversion, const gboolean undo,
1326 const gboolean reload)
1327{
1328 const int32_t newid = dt_image_repository_duplicate(imgid, newversion);
1329
1330 if(newid != -1)
1331 {
1332 if(undo)
1333 {
1334 dt_undo_duplicate_t *dupundo = (dt_undo_duplicate_t *)malloc(sizeof(dt_undo_duplicate_t));
1335 dupundo->orig_imgid = imgid;
1336 dupundo->version = newversion;
1337 dupundo->new_imgid = newid;
1339 }
1340
1341 // make sure that the duplicate doesn't have some magic darktable| tags
1342 if(dt_tag_detach_by_string("darktable|changed", newid, FALSE, FALSE)
1343 || dt_tag_detach_by_string("darktable|exported", newid, FALSE, FALSE))
1345
1346 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1347 const int grpid = img->group_id;
1349 dt_grouping_add_to_group(grpid, newid);
1350
1351 // Reloading here exposes newid to the lighttable grid: it becomes eligible for thumbnail
1352 // generation immediately, against whatever history it has *right now*. Callers still about
1353 // to write history onto it (duplicate-with-history, style-on-duplicate) must skip this and
1354 // reload themselves once that is done -- see dt_image_duplicate_no_reload().
1355 if(reload)
1357 }
1358 return newid;
1359}
1360
1361int32_t dt_image_duplicate_with_version(const int32_t imgid, const int32_t newversion)
1362{
1363 return _image_duplicate_with_version(imgid, newversion, TRUE, TRUE);
1364}
1365
1366int32_t dt_image_duplicate_no_reload(const int32_t imgid)
1367{
1368 return _image_duplicate_with_version(imgid, -1, TRUE, FALSE);
1369}
1370
1371void dt_image_remove(const int32_t imgid)
1372{
1373 // if a local copy exists, remove it
1374
1375 if(dt_image_local_copy_reset(imgid)) return;
1376
1377 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1379
1380 // make sure we remove from the cache first, or else the cache will look for imgid in sql
1381 dt_image_cache_remove(imgid);
1382
1385
1386 // also clear all thumbnails in mipmap_cache.
1388}
1389
1390uint32_t dt_image_altered(const int32_t imgid)
1391{
1393}
1394
1395#ifndef _WIN32
1396static int _valid_glob_match(const char *const name, size_t offset)
1397{
1398 // verify that the name matched by glob() is a valid sidecar name by checking whether we have an underscore
1399 // followed by a sequence of digits followed by a period at the given offset in the name
1400 if(strlen(name) < offset || name[offset] != '_')
1401 return FALSE;
1402 size_t i;
1403 for(i = offset+1; name[i] && name[i] != '.'; i++)
1404 {
1405 if(!isdigit(name[i]))
1406 return FALSE;
1407 }
1408 return name[i] == '.';
1409}
1410#endif /* !_WIN32 */
1411
1412GList* dt_image_find_xmps(const char* filename)
1413{
1414 // find all duplicates of an image by looking for all possible sidecars for the file: file.ext.xmp, file_NN.ext.xmp,
1415 // file_NNN.ext.xmp, and file_NNNN.ext.xmp
1416 // because a glob() needs to scan the entire directory, we minimize work for large directories by doing a single
1417 // glob which might generate false matches (if the image name contains an underscore followed by a digit) and
1418 // filter out the false positives afterward
1419#ifndef _WIN32
1420 // start by locating the extension, which we'll be referencing multiple times
1421 const size_t fn_len = strlen(filename);
1422 const char* ext = strrchr(filename,'.'); // find last dot
1423 if(IS_NULL_PTR(ext)) ext = filename;
1424 const size_t ext_offset = ext - filename;
1425
1426 gchar pattern[DT_PATH_MAX] = { 0 };
1427 GList* files = NULL;
1428
1429 // check for file.ext.xmp
1430 static const char xmp[] = ".xmp";
1431 const size_t xmp_len = strlen(xmp);
1432 // concatenate filename and sidecar extension
1433 g_strlcpy(pattern, filename, sizeof(pattern));
1434 g_strlcpy(pattern + fn_len, xmp, sizeof(pattern) - fn_len);
1435 if(dt_util_test_image_file(pattern))
1436 {
1437 // the default sidecar exists, is readable and is a regular file with lenght > 0, so add it to the list
1438 files = g_list_prepend(files, g_strdup(pattern));
1439 }
1440
1441 // now collect all file_N*N.ext.xmp matches
1442 static const char glob_pattern[] = "_[0-9]*[0-9]";
1443 const size_t gp_len = strlen(glob_pattern);
1444 if(fn_len + gp_len + xmp_len < sizeof(pattern)) // enough space to build pattern?
1445 {
1446 // add GLOB.ext.xmp to the root of the basename
1447 g_strlcpy(pattern + ext_offset, glob_pattern, sizeof(pattern) - fn_len);
1448 g_strlcpy(pattern + ext_offset + gp_len, ext, sizeof(pattern) - ext_offset - gp_len);
1449 g_strlcpy(pattern + fn_len + gp_len, xmp, sizeof(pattern) - fn_len - gp_len);
1450 glob_t globbuf;
1451 if(!glob(pattern, 0, NULL, &globbuf))
1452 {
1453 // for each match of the pattern
1454 for(size_t i = 0; i < globbuf.gl_pathc; i++)
1455 {
1456 if(_valid_glob_match(globbuf.gl_pathv[i], ext_offset))
1457 {
1458 // it's not a false positive, so add it to the list of sidecars
1459 files = g_list_prepend(files, g_strdup(globbuf.gl_pathv[i]));
1460 }
1461 }
1462 globfree(&globbuf);
1463 }
1464 }
1465 // we built the list in reverse order for speed, so un-reverse it
1466 return g_list_reverse(files);
1467
1468#else
1469 return win_image_find_duplicates(filename);
1470#endif
1471}
1472
1473// Search for duplicate's sidecar files and import them if found and not in DB yet
1474int dt_image_read_duplicates(const uint32_t id, const char *filename, const gboolean clear_selection)
1475{
1476 int count_xmps_processed = 0;
1477 gchar pattern[DT_PATH_MAX] = { 0 };
1478
1479 GList *files = dt_image_find_xmps(filename);
1480
1481 // we store the xmp filename without version part in pattern to speed up string comparison later
1482 g_snprintf(pattern, sizeof(pattern), "%s.xmp", filename);
1483
1484 for(GList *file_iter = files; file_iter; file_iter = g_list_next(file_iter))
1485 {
1486 gchar *xmpfilename = file_iter->data;
1487 int version = -1;
1488
1489 // we need to get the version number of the sidecar filename
1490 if(!strncmp(xmpfilename, pattern, sizeof(pattern)))
1491 {
1492 // this is an xmp file without version number which corresponds to version 0
1493 version = 0;
1494 }
1495 else
1496 {
1497 // we need to derive the version number from the filename
1498
1499 gchar *c3 = xmpfilename + strlen(xmpfilename)
1500 - 5; // skip over .xmp extension; position c3 at character before the '.'
1501 while(*c3 != '.' && c3 > xmpfilename)
1502 c3--; // skip over filename extension; position c3 is at character '.'
1503 gchar *c4 = c3;
1504 while(*c4 != '_' && c4 > xmpfilename) c4--; // move to beginning of version number
1505 c4++;
1506
1507 gchar *idfield = g_strndup(c4, c3 - c4);
1508
1509 version = atoi(idfield);
1510 dt_free(idfield);
1511 }
1512
1513 int newid = id;
1514 int grpid = -1;
1515
1516 if(count_xmps_processed == 0)
1517 {
1518 // this is the first xmp processed, just update the passed-in id
1520 }
1521 else
1522 {
1523 // create a new duplicate based on the passed-in id. Note that we do not call
1524 // dt_image_duplicate_with_version() as this version also set the group which
1525 // is using DT_IMAGE_CACHE_SAFE and so will write the .XMP. But we must avoid
1526 // this has the xmp for the duplicate is read just below.
1527 newid = dt_image_repository_duplicate(id, version);
1528 const dt_image_t *img = dt_image_cache_get(id, 'r');
1529 if(!IS_NULL_PTR(img))
1530 {
1531 grpid = img->group_id;
1533 }
1534 }
1535 // make sure newid is not selected
1536 if(clear_selection) dt_selection_clear(dt_selection_get_global());
1537
1538 // dt_image_repository_duplicate() above returns -1 when it cannot create the duplicate,
1539 // and dt_image_cache_get() returns NULL for an id it cannot load. Both were dereferenced
1540 // unchecked, which is Sentry 140350855: SIGSEGV on `img->version = version` while
1541 // importing a film roll whose sidecars name versions the duplicate could not be created
1542 // for. Skipping this sidecar leaves the rest of the import to proceed.
1543 dt_image_t *img = (newid > 0) ? dt_image_cache_get(newid, 'w') : NULL;
1544 if(IS_NULL_PTR(img))
1545 {
1546 fprintf(stderr, "[dt_image_read_duplicates] could not load image %i for sidecar '%s', skipping it\n",
1547 newid, xmpfilename);
1548 continue;
1549 }
1550 (void)dt_exif_xmp_read(img, xmpfilename, 0);
1551 img->version = version;
1553
1554 if(grpid != -1)
1555 {
1556 // now it is safe to set the duplicate group-id
1557 dt_grouping_add_to_group(grpid, newid);
1559 }
1560
1561 count_xmps_processed++;
1562 }
1563
1564 g_list_free_full(files, dt_free_gpointer);
1565 files = NULL;
1566 return count_xmps_processed;
1567}
1568
1569static int32_t _image_import_internal(const int32_t film_id, const char *filename,
1570 gboolean lua_locking, gboolean raise_signals)
1571{
1572 char *normalized_filename = dt_util_normalize_path(filename);
1573 if(!normalized_filename || !dt_util_test_image_file(normalized_filename))
1574 {
1575 dt_free(normalized_filename);
1576 return 0;
1577 }
1578 const char *cc = normalized_filename + strlen(normalized_filename);
1579 for(; *cc != '.' && cc > normalized_filename; cc--)
1580 ;
1581 if(!strcasecmp(cc, ".dt") || !strcasecmp(cc, ".dttags") || !strcasecmp(cc, ".xmp"))
1582 {
1583 dt_free(normalized_filename);
1584 return 0;
1585 }
1586 char *ext = g_ascii_strdown(cc + 1, -1);
1588 {
1589 dt_free(normalized_filename);
1590 dt_free(ext);
1591 return 0;
1592 }
1593 // select from images; if found => return
1594 gchar *imgfname = g_path_get_basename(normalized_filename);
1595 int32_t id = dt_image_get_id(film_id, imgfname);
1596 if(id > UNKNOWN_IMAGE)
1597 {
1598 dt_control_log(_("Image %s is already in library and will not be re-imported.\n"), imgfname);
1599 dt_free(imgfname);
1600 dt_free(ext);
1601 dt_free(normalized_filename);
1602 return id;
1603 }
1604
1605 // also need to set the no-legacy bit, to make sure we get the right presets (new ones)
1606 uint32_t flags = 0;
1608 // and we set the type of image flag (from extension for now)
1609 gchar *extension = g_strrstr(imgfname, ".");
1611 // set the bits in flags that indicate if any of the extra files (.txt, .wav) are present
1612 char *extra_file = dt_image_get_audio_path_from_path(normalized_filename);
1613 if(extra_file)
1614 {
1616 dt_free(extra_file);
1617 }
1618 extra_file = dt_image_get_text_path_from_path(normalized_filename);
1619 if(extra_file)
1620 {
1622 dt_free(extra_file);
1623 }
1624
1626 return 0;
1627
1628 id = dt_image_get_id(film_id, imgfname);
1629
1630 // Try to find out if this should be grouped already.
1631 gchar *basename = g_strdup(imgfname);
1632 gchar *cc2 = basename + strlen(basename);
1633 for(; *cc2 != '.' && cc2 > basename; cc2--)
1634 ;
1635 *cc2 = '\0';
1636 gchar *sql_pattern = g_strconcat(basename, ".%", NULL);
1637 int group_id;
1638 // in case we are not a jpg check if we need to change group representative
1639 if(strcmp(ext, "jpg") != 0 && strcmp(ext, "jpeg") != 0)
1640 {
1641 const int32_t other_group = dt_image_repository_find_group_for_pattern(film_id, sql_pattern, TRUE, id);
1642 // if we have a group already
1643 if(other_group != -1)
1644 {
1645 int other_id = other_group;
1646 dt_image_t *other_img = dt_image_cache_get(other_id, 'w');
1647 gchar *other_basename = g_strdup(other_img->filename);
1648 gchar *cc3 = other_basename + strlen(other_img->filename);
1649 for(; *cc3 != '.' && cc3 > other_basename; cc3--)
1650 ;
1651 ++cc3;
1652 gchar *ext_lowercase = g_ascii_strdown(cc3, -1);
1653 // if the group representative is a jpg, change group representative to this new imported image
1654 if(!strcmp(ext_lowercase, "jpg") || !strcmp(ext_lowercase, "jpeg"))
1655 {
1656 other_img->group_id = id;
1658 GList *members = dt_image_repository_get_group_members(other_id, other_id);
1659 for(GList *l = members; l; l = g_list_next(l))
1660 {
1661 other_id = GPOINTER_TO_INT(l->data);
1662 dt_image_t *group_img = dt_image_cache_get(other_id, 'w');
1663 group_img->group_id = id;
1665 }
1666 g_list_free(members);
1667 group_id = id;
1668 }
1669 else
1670 {
1672 group_id = other_id;
1673 }
1674 dt_free(ext_lowercase);
1675 dt_free(other_basename);
1676 }
1677 else
1678 {
1679 group_id = id;
1680 }
1681 }
1682 else
1683 {
1684 const int32_t other_group = dt_image_repository_find_group_for_pattern(film_id, sql_pattern, FALSE, id);
1685 group_id = (other_group != -1) ? other_group : id;
1686 }
1687 dt_image_repository_set_group(id, group_id);
1688
1689 // printf("[image_import] importing `%s' to img id %d\n", imgfname, id);
1690
1691 // lock as shortly as possible:
1692 dt_image_t *img = dt_image_cache_get(id, 'w');
1693 img->group_id = group_id;
1694
1695 // read dttags and exif for database queries!
1696 (void)dt_exif_read(img, normalized_filename);
1697 char dtfilename[DT_PATH_MAX] = { 0 };
1698 g_strlcpy(dtfilename, normalized_filename, sizeof(dtfilename));
1699 // dt_image_path_append_version(id, dtfilename, sizeof(dtfilename));
1700 g_strlcat(dtfilename, ".xmp", sizeof(dtfilename));
1701
1702 const int res = dt_exif_xmp_read(img, dtfilename, 0);
1703
1704 // write through to db, but not to xmp.
1706
1707 // read all sidecar files
1708 const int nb_xmp = dt_image_read_duplicates(id, normalized_filename, raise_signals);
1709
1710 if((res != 0) && (nb_xmp == 0))
1711 {
1712 const gboolean lr_xmp = dt_lightroom_import(id, NULL, TRUE);
1713 if(lr_xmp) dt_control_save_xmp(id);
1714 }
1715
1716 // add a tag with the file extension
1717 guint tagid = 0;
1718 char tagname[512];
1719 snprintf(tagname, sizeof(tagname), "darktable|format|%s", ext);
1720 dt_free(ext);
1721 dt_tag_new(tagname, &tagid);
1722 dt_tag_attach(tagid, id, FALSE, FALSE);
1723
1724 // make sure that there are no stale thumbnails left
1726
1727 //synch database entries to xmp
1728 if(dt_image_get_xmp_mode()) dt_image_synch_all_xmp(normalized_filename);
1729
1730 dt_free(imgfname);
1731 dt_free(basename);
1732 dt_free(sql_pattern);
1733 dt_free(normalized_filename);
1734
1735 if(raise_signals)
1736 {
1738 // the old raise handed the freshly built list to the signal; the handler copies now,
1739 // so this site frees its own
1740 GList *imgs = g_list_prepend(NULL, GINT_TO_POINTER(id));
1742 g_list_free(imgs);
1743 }
1744
1745 // the following line would look logical with new_tags_set being the return value
1746 // from dt_tag_new above, but this could lead to too rapid signals, being able to lock up the
1747 // keywords side pane when trying to use it, which can lock up the whole dt GUI ..
1748 // if(new_tags_set) DT_DEBUG_CONTROL_SIGNAL_RAISE(dt_control_signal_get_global(),DT_SIGNAL_TAG_CHANGED);
1749 return id;
1750}
1751
1752int32_t dt_image_get_id_full_path(const gchar *filename)
1753{
1754 gchar *dir = g_path_get_dirname(filename);
1755 gchar *file = g_path_get_basename(filename);
1756 const int32_t id = dt_image_repository_find_by_folder_and_filename(dir, file);
1757 dt_free(dir);
1758 dt_free(file);
1759
1760 return id;
1761}
1762
1763int32_t dt_image_get_id(int32_t film_id, const gchar *filename)
1764{
1765 return dt_image_repository_find_by_film_and_filename(film_id, filename);
1766}
1767
1768/* Guess what kind of image a file holds from its extension alone.
1769 *
1770 * Lived in imageio/imageio_core.c, which put a six-line function over three predicates
1771 * from common/image_extensions.h and one enum from this file at layer 6 -- above every
1772 * one of its callers (common/image.c, common/exif.cc, caches/image_cache.c,
1773 * database/database.c, gui/import.c). Nothing in imageio/ or above ever called it. */
1775{
1776 const char *ext = g_str_has_prefix(extension, ".") ? extension + 1 : extension;
1777 if(dt_image_ext_is_ambiguous(ext)) return 0;
1778 if(dt_image_ext_is_raw(ext)) return DT_IMAGE_RAW;
1779 if(dt_image_ext_is_hdr(ext)) return DT_IMAGE_HDR;
1780 if(dt_image_ext_is_ldr(ext)) return DT_IMAGE_LDR;
1781 return 0;
1782}
1783
1784int32_t dt_image_import(const int32_t film_id, const char *filename, gboolean raise_signals)
1785{
1786 return _image_import_internal(film_id, filename, TRUE, raise_signals);
1787}
1788
1789int32_t dt_image_import_lua(const int32_t film_id, const char *filename)
1790{
1791 return _image_import_internal(film_id, filename, FALSE, TRUE);
1792}
1793
1795{
1796 memset(img, 0, sizeof(*img));
1797 img->width = img->height = 0;
1798 img->p_width = img->p_height = 0;
1799 img->crop_x = img->crop_y = img->crop_width = img->crop_height = 0;
1801
1803
1804 img->legacy_flip.legacy = 0;
1805 img->legacy_flip.user_flip = 0;
1806
1807 img->dsc = (dt_iop_buffer_dsc_t){ .channels = 0, .datatype = TYPE_UNKNOWN, .bpp = 0, .filters = 0u };
1808 img->film_id = UNKNOWN_IMAGE;
1809 img->group_id = UNKNOWN_IMAGE;
1810 img->group_members = 0;
1811 img->history_items = 0;
1812 img->history_hash = UINT64_MAX;
1813 img->mipmap_hash = 0; // don't default it to img->history_hash to not make it look like they are in sync
1814 img->self_hash = 0;
1815 img->flags = 0;
1816 img->id = UNKNOWN_IMAGE;
1817 img->version = -1;
1818 img->loader = LOADER_UNKNOWN;
1819 img->exif_inited = 0;
1821 dt_datetime_exif_to_img(img, "");
1822 memset(img->exif_maker, 0, sizeof(img->exif_maker));
1823 memset(img->exif_model, 0, sizeof(img->exif_model));
1824 memset(img->exif_lens, 0, sizeof(img->exif_lens));
1825 memset(img->camera_maker, 0, sizeof(img->camera_maker));
1826 memset(img->camera_model, 0, sizeof(img->camera_model));
1827 memset(img->camera_alias, 0, sizeof(img->camera_alias));
1828 memset(img->camera_makermodel, 0, sizeof(img->camera_makermodel));
1829 memset(img->camera_legacy_makermodel, 0, sizeof(img->camera_legacy_makermodel));
1830 memset(img->filename, 0, sizeof(img->filename));
1831 memset(img->fullpath, 0, sizeof(img->fullpath));
1832 memset(img->local_copy_path, 0, sizeof(img->local_copy_path));
1833 memset(img->local_copy_legacy_path, 0, sizeof(img->local_copy_legacy_path));
1834 memset(img->folder, 0, sizeof(img->folder));
1835 memset(img->filmroll, 0, sizeof(img->filmroll));
1836 memset(img->datetime, 0, sizeof(img->datetime));
1837 g_strlcpy(img->filename, "(unknown)", sizeof(img->filename));
1838 img->exif_crop = 1.0;
1839 img->exif_exposure = 0;
1840 img->exif_exposure_bias = NAN;
1841 img->exif_aperture = 0;
1842 img->exif_iso = 0;
1843 img->exif_focal_length = 0;
1844 img->exif_focus_distance = 0;
1845 img->geoloc.latitude = NAN;
1846 img->geoloc.longitude = NAN;
1847 img->geoloc.elevation = NAN;
1848 img->raw_black_level = 0;
1849 for(uint8_t i = 0; i < 4; i++) img->raw_black_level_separate[i] = 0;
1850 img->raw_white_point = 16384; // 2^14
1851 img->d65_color_matrix[0] = NAN;
1852 img->profile = NULL;
1853 img->profile_size = 0;
1854 img->embedded_profile = NULL;
1856 img->fuji_rotation_pos = 0;
1857 img->pixel_aspect_ratio = 1.0f;
1858 img->wb_coeffs[0] = NAN;
1859 img->wb_coeffs[1] = NAN;
1860 img->wb_coeffs[2] = NAN;
1861 img->wb_coeffs[3] = NAN;
1862 img->usercrop[0] = img->usercrop[1] = 0;
1863 img->usercrop[2] = img->usercrop[3] = 1;
1865 img->dng_gain_maps = NULL;
1866 img->cache_entry = 0;
1867 img->color_labels = 0;
1868 img->rating = 0;
1869 img->has_localcopy = FALSE;
1870 img->has_audio = FALSE;
1871 img->is_bw = FALSE;
1872 img->is_bw_flow = FALSE;
1873 img->is_hdr = FALSE;
1874
1875 for(int k=0; k<4; k++)
1876 for(int i=0; i<3; i++)
1877 img->adobe_XYZ_to_CAM[k][i] = NAN;
1878}
1879
1881{
1882 if(!img->camera_maker[0] || !img->camera_model[0] || !img->camera_alias[0])
1883 {
1884 // We need to use the exif values, so let's get rawspeed to munge them
1886 img->camera_maker, sizeof(img->camera_maker),
1887 img->camera_model, sizeof(img->camera_model),
1888 img->camera_alias, sizeof(img->camera_alias));
1889 }
1890
1891 // Now we just create a makermodel by concatenation
1892 g_strlcpy(img->camera_makermodel, img->camera_maker, sizeof(img->camera_makermodel));
1893 const int len = strlen(img->camera_maker);
1894 img->camera_makermodel[len] = ' ';
1895 g_strlcpy(img->camera_makermodel+len+1, img->camera_model, sizeof(img->camera_makermodel)-len-1);
1896}
1897
1898int32_t dt_image_rename(const int32_t imgid, const int32_t filmid, const gchar *newname)
1899{
1900 // TODO: several places where string truncation could occur unnoticed
1901 int32_t result = -1;
1902 gchar oldimg[DT_PATH_MAX] = { 0 };
1903 gchar newimg[DT_PATH_MAX] = { 0 };
1904 gboolean from_cache = FALSE;
1905 dt_image_full_path(imgid, oldimg, sizeof(oldimg), &from_cache, __FUNCTION__);
1906 gchar *newdir = NULL;
1907
1908 newdir = dt_film_repository_get_folder(filmid);
1909
1910 gchar copysrcpath[DT_PATH_MAX] = { 0 };
1911 gchar copydestpath[DT_PATH_MAX] = { 0 };
1912 GFile *old = NULL, *new = NULL;
1913 if(newdir)
1914 {
1915 old = g_file_new_for_path(oldimg);
1916
1917 if(newname)
1918 {
1919 g_snprintf(newimg, sizeof(newimg), "%s%c%s", newdir, G_DIR_SEPARATOR, newname);
1920 new = g_file_new_for_path(newimg);
1921 // 'newname' represents the file's new *basename* -- it must not
1922 // refer to a file outside of 'newdir'.
1923 gchar *newBasename = g_file_get_basename(new);
1924 if(g_strcmp0(newname, newBasename) != 0)
1925 {
1926 g_object_unref(old);
1927 old = NULL;
1928 g_object_unref(new);
1929 new = NULL;
1930 }
1931 dt_free(newBasename);
1932 }
1933 else
1934 {
1935 gchar *imgbname = g_path_get_basename(oldimg);
1936 g_snprintf(newimg, sizeof(newimg), "%s%c%s", newdir, G_DIR_SEPARATOR, imgbname);
1937 new = g_file_new_for_path(newimg);
1938 dt_free(imgbname);
1939 }
1940 dt_free(newdir);
1941 }
1942
1943 if(new)
1944 {
1945 // get current local copy if any
1946 dt_image_t *img = dt_image_cache_get(imgid, 'r');
1947 if(img)
1948 {
1949 dt_image_choose_input_path(img, copysrcpath, sizeof(copysrcpath), TRUE);
1951 }
1952
1953 // move image
1954 GError *moveError = NULL;
1955 gboolean moveStatus = g_file_move(old, new, 0, NULL, NULL, NULL, &moveError);
1956
1957 if(moveStatus)
1958 {
1959 _move_text_sidecar_if_present(oldimg, newimg, FALSE);
1960
1961 // first move xmp files of image and duplicates
1962 GList *dup_list = dt_image_repository_get_duplicate_ids(imgid);
1963 for(GList *l = dup_list; l; l = g_list_next(l))
1964 {
1965 const int32_t id = GPOINTER_TO_INT(l->data);
1966 gchar oldxmp[DT_PATH_MAX] = { 0 }, newxmp[DT_PATH_MAX] = { 0 };
1967 g_strlcpy(oldxmp, oldimg, sizeof(oldxmp));
1968 g_strlcpy(newxmp, newimg, sizeof(newxmp));
1969 dt_image_path_append_version(id, oldxmp, sizeof(oldxmp));
1970 dt_image_path_append_version(id, newxmp, sizeof(newxmp));
1971 g_strlcat(oldxmp, ".xmp", sizeof(oldxmp));
1972 g_strlcat(newxmp, ".xmp", sizeof(newxmp));
1973
1974 GFile *goldxmp = g_file_new_for_path(oldxmp);
1975 GFile *gnewxmp = g_file_new_for_path(newxmp);
1976
1977 g_file_move(goldxmp, gnewxmp, 0, NULL, NULL, NULL, NULL);
1978
1979 g_object_unref(goldxmp);
1980 g_object_unref(gnewxmp);
1981 }
1982
1983 // then update database and cache
1984 // if update was performed in above loop, dt_image_path_append_version()
1985 // would return wrong version!
1986 while(dup_list)
1987 {
1988 const int id = GPOINTER_TO_INT(dup_list->data);
1989 img = dt_image_cache_get(id, 'w');
1990 img->film_id = filmid;
1991 if(newname) g_strlcpy(img->filename, newname, DT_MAX_FILENAME_LEN);
1992 // write through to db and queue xmp write
1994 dup_list = g_list_delete_link(dup_list, dup_list);
1995 }
1996 g_list_free(dup_list);
1997 dup_list = NULL;
1998
1999 // finally, rename local copy if any
2000 if(g_file_test(copysrcpath, G_FILE_TEST_EXISTS))
2001 {
2002 // get new name
2003 img = dt_image_cache_get(imgid, 'r');
2004 if(img)
2005 {
2006 dt_image_choose_input_path(img, copydestpath, sizeof(copydestpath), TRUE);
2008 }
2009
2010 GFile *cold = g_file_new_for_path(copysrcpath);
2011 GFile *cnew = g_file_new_for_path(copydestpath);
2012
2013 g_clear_error(&moveError);
2014 moveStatus = g_file_move(cold, cnew, 0, NULL, NULL, NULL, &moveError);
2015 if(!moveStatus)
2016 {
2017 fprintf(stderr, "[dt_image_rename] error moving local copy `%s' -> `%s'\n", copysrcpath, copydestpath);
2018
2019 if(g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
2020 {
2021 gchar *oldBasename = g_path_get_basename(copysrcpath);
2022 dt_control_log(_("cannot access local copy `%s'"), oldBasename);
2023 dt_free(oldBasename);
2024 }
2025 else if(g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_EXISTS)
2026 || g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY))
2027 {
2028 gchar *newBasename = g_path_get_basename(copydestpath);
2029 dt_control_log(_("cannot write local copy `%s'"), newBasename);
2030 dt_free(newBasename);
2031 }
2032 else
2033 {
2034 gchar *oldBasename = g_path_get_basename(copysrcpath);
2035 gchar *newBasename = g_path_get_basename(copydestpath);
2036 dt_control_log(_("error moving local copy `%s' -> `%s'"), oldBasename, newBasename);
2037 dt_free(oldBasename);
2038 dt_free(newBasename);
2039 }
2040 }
2041
2042 g_object_unref(cold);
2043 g_object_unref(cnew);
2044 }
2045
2046 result = 0;
2047 }
2048 else
2049 {
2050 if(g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
2051 {
2052 dt_control_log(_("error moving `%s': file not found"), oldimg);
2053 }
2054 // only display error message if newname is set (renaming and
2055 // not moving) as when moving it can be the case where a
2056 // duplicate is being moved, so only the .xmp are present but
2057 // the original file may already have been moved.
2058 else if(newname
2059 && (g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_EXISTS)
2060 || g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)))
2061 {
2062 dt_control_log(_("error moving `%s' -> `%s': file exists"), oldimg, newimg);
2063 }
2064 else if(newname)
2065 {
2066 dt_control_log(_("error moving `%s' -> `%s'"), oldimg, newimg);
2067 }
2068 }
2069
2070 g_clear_error(&moveError);
2071 g_object_unref(old);
2072 g_object_unref(new);
2073 }
2074
2075 return result;
2076}
2077
2078int32_t dt_image_move(const int32_t imgid, const int32_t filmid)
2079{
2080 return dt_image_rename(imgid, filmid, NULL);
2081}
2082
2083int32_t dt_image_copy_rename(const int32_t imgid, const int32_t filmid, const gchar *newname)
2084{
2085 int32_t newid = -1;
2086 gchar srcpath[DT_PATH_MAX] = { 0 };
2087 gchar *newdir = NULL;
2088 gboolean from_cache = FALSE;
2089 gchar *oldFilename = NULL;
2090 gchar *newFilename = NULL;
2091
2092 newdir = dt_film_repository_get_folder(filmid);
2093
2094 GFile *src = NULL, *dest = NULL;
2095 if(newdir)
2096 {
2097 dt_image_full_path(imgid, srcpath, sizeof(srcpath), &from_cache, __FUNCTION__);
2098 oldFilename = g_path_get_basename(srcpath);
2099 gchar *destpath;
2100 if(newname)
2101 {
2102 newFilename = g_strdup(newname);
2103 destpath = g_build_filename(newdir, newname, NULL);
2104 dest = g_file_new_for_path(destpath);
2105 // 'newname' represents the file's new *basename* -- it must not
2106 // refer to a file outside of 'newdir'.
2107 gchar *destBasename = g_file_get_basename(dest);
2108 if(g_strcmp0(newname, destBasename) != 0)
2109 {
2110 g_object_unref(dest);
2111 dest = NULL;
2112 }
2113 dt_free(destBasename);
2114 }
2115 else
2116 {
2117 newFilename = g_path_get_basename(srcpath);
2118 destpath = g_build_filename(newdir, newFilename, NULL);
2119 dest = g_file_new_for_path(destpath);
2120 }
2121 if(dest)
2122 {
2123 src = g_file_new_for_path(srcpath);
2124 }
2125 dt_free(newdir);
2126 dt_free(destpath);
2127 }
2128
2129 if(dest)
2130 {
2131 // copy image to new folder
2132 // if image file already exists, continue
2133 GError *gerror = NULL;
2134 gboolean copyStatus = g_file_copy(src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, &gerror);
2135
2136 if(copyStatus || g_error_matches(gerror, G_IO_ERROR, G_IO_ERROR_EXISTS))
2137 {
2138 gchar *dest_image_path = g_file_get_path(dest);
2139 if(dest_image_path)
2140 {
2141 _copy_text_sidecar_if_present(srcpath, dest_image_path);
2142 dt_free(dest_image_path);
2143 }
2144
2145 newid = dt_image_repository_copy_to_film(imgid, filmid, newFilename, oldFilename);
2146
2147 if(newid != -1)
2148 {
2149 // also copy over on-disk thumbnails, if any
2150 dt_mipmap_cache_copy_thumbnails(newid, imgid);
2151
2153
2155 NULL);
2156 }
2157 }
2158 else
2159 {
2160 fprintf(stderr, "Failed to copy image %s: %s\n", srcpath, gerror->message);
2161 }
2162 g_object_unref(dest);
2163 g_object_unref(src);
2164 g_clear_error(&gerror);
2165 }
2166 dt_free(oldFilename);
2167 dt_free(newFilename);
2168
2169 return newid;
2170}
2171
2172int32_t dt_image_copy(const int32_t imgid, const int32_t filmid)
2173{
2174 return dt_image_copy_rename(imgid, filmid, NULL);
2175}
2176
2177int dt_image_local_copy_set(const int32_t imgid)
2178{
2179 gchar srcpath[DT_PATH_MAX] = { 0 };
2180 gchar destpath[DT_PATH_MAX] = { 0 };
2182 char local_copy_path[DT_PATH_MAX] = { 0 };
2183 char local_copy_legacy_path[DT_PATH_MAX] = { 0 };
2184 dt_image_t *img = dt_image_cache_get(imgid, 'r');
2185 if(img)
2186 {
2187 g_strlcpy(srcpath, img->fullpath, DT_PATH_MAX);
2188 g_strlcpy(local_copy_path, img->local_copy_path, sizeof(local_copy_path));
2189 g_strlcpy(local_copy_legacy_path, img->local_copy_legacy_path, sizeof(local_copy_legacy_path));
2190 char existing[DT_PATH_MAX] = { 0 };
2191 source = dt_image_choose_input_path(img, existing, sizeof(existing), TRUE);
2193 g_strlcpy(destpath, existing, sizeof(destpath));
2194 else
2195 g_strlcpy(destpath, local_copy_path, sizeof(destpath));
2197 }
2198 else
2199 {
2200 return 1;
2201 }
2202
2203 // check that the src file is readable
2204 if(!g_file_test(srcpath, G_FILE_TEST_IS_REGULAR))
2205 {
2206 dt_control_log(_("cannot create local copy when the original file is not accessible."));
2207 return 1;
2208 }
2209
2211 {
2212 GFile *src = g_file_new_for_path(srcpath);
2213 GFile *dest = g_file_new_for_path(destpath);
2214
2215 // copy image to cache directory
2216 GError *gerror = NULL;
2217
2218 if(!g_file_copy(src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, &gerror))
2219 {
2220 dt_control_log(_("cannot create local copy."));
2221 g_object_unref(dest);
2222 g_object_unref(src);
2223 return 1;
2224 }
2225
2226 g_object_unref(dest);
2227 g_object_unref(src);
2228 }
2229
2230 _copy_text_sidecar_if_present(srcpath, destpath);
2231
2232 // update cache local copy flags, do this even if the local copy already exists as we need to set the flags
2233 // for duplicate
2234 img = dt_image_cache_get(imgid, 'w');
2235 img->flags |= DT_IMAGE_LOCAL_COPY;
2237
2239 return 0;
2240}
2241
2242static int _nb_other_local_copy_for(const int32_t imgid)
2243{
2245}
2246
2247int dt_image_local_copy_reset(const int32_t imgid)
2248{
2249 gchar destpath[DT_PATH_MAX] = { 0 };
2250 gchar locppath[DT_PATH_MAX] = { 0 };
2251 gchar cachedir[DT_PATH_MAX] = { 0 };
2252
2253 // check that a local copy exists, otherwise there is nothing to do
2254 dt_image_t *imgr = dt_image_cache_get(imgid, 'r');
2255 if(!imgr) return 0;
2256 const gboolean local_copy_exists = (imgr->flags & DT_IMAGE_LOCAL_COPY) == DT_IMAGE_LOCAL_COPY ? TRUE : FALSE;
2258
2259 if(!local_copy_exists)
2260 return 0;
2261
2262 // check that the original file is accessible
2263
2264 gboolean from_cache = FALSE;
2265 dt_image_full_path(imgid, destpath, sizeof(destpath), &from_cache, __FUNCTION__);
2266
2267 from_cache = TRUE;
2268 dt_image_full_path(imgid, locppath, sizeof(locppath), &from_cache, __FUNCTION__);
2269 dt_image_path_append_version(imgid, locppath, sizeof(locppath));
2270 g_strlcat(locppath, ".xmp", sizeof(locppath));
2271
2272 // a local copy exists, but the original is not accessible
2273
2274 if(g_file_test(locppath, G_FILE_TEST_EXISTS) && !g_file_test(destpath, G_FILE_TEST_EXISTS))
2275 {
2276 dt_control_log(_("cannot remove local copy when the original file is not accessible."));
2277 return 1;
2278 }
2279
2280 // get name of local copy
2281
2282 locppath[0] = '\0';
2283 dt_image_t *img = dt_image_cache_get(imgid, 'r');
2284 if(img)
2285 {
2286 dt_image_choose_input_path(img, locppath, sizeof(locppath), TRUE);
2288 }
2289
2290 // remove cached file, but double check that this is really into the cache. We really want to avoid deleting
2291 // a user's original file.
2292
2293 dt_loc_get_user_cache_dir(cachedir, sizeof(cachedir));
2294
2295 if(g_file_test(locppath, G_FILE_TEST_EXISTS) && strstr(locppath, cachedir))
2296 {
2297 GFile *dest = g_file_new_for_path(locppath);
2298
2299 // first sync the xmp with the original picture
2300
2301 dt_control_save_xmp(imgid);
2302
2303 // delete image from cache directory only if there is no other local cache image referencing it
2304 // for example duplicates are all referencing the same base picture.
2305
2306 if(_nb_other_local_copy_for(imgid) == 0)
2307 {
2308 _move_text_sidecar_if_present(locppath, destpath, TRUE);
2309 g_file_delete(dest, NULL, NULL);
2310 }
2311
2312 g_object_unref(dest);
2313
2314 // delete xmp if any
2315 dt_image_path_append_version(imgid, locppath, sizeof(locppath));
2316 g_strlcat(locppath, ".xmp", sizeof(locppath));
2317 dest = g_file_new_for_path(locppath);
2318
2319 if(g_file_test(locppath, G_FILE_TEST_EXISTS)) g_file_delete(dest, NULL, NULL);
2320 g_object_unref(dest);
2321 }
2322
2323 // update cache, remove local copy flags, this is done in all cases here as when we
2324 // reach this point the local-copy flag is present and the file has been either removed
2325 // or is not present.
2326
2327 img = dt_image_cache_get(imgid, 'w');
2328 img->flags &= ~DT_IMAGE_LOCAL_COPY;
2330
2332
2333 return 0;
2334}
2335
2336// *******************************************************
2337// xmp stuff
2338// *******************************************************
2339
2340static gboolean _sidecar_is_up_to_date(const dt_image_t *img)
2341{
2342 if(IS_NULL_PTR(img) || img->id <= 0) return FALSE;
2343 if(img->change_timestamp <= 0) return FALSE;
2344
2345 const int64_t write_timestamp = dt_image_repository_get_write_timestamp(img->id);
2346 if(write_timestamp <= 0) return FALSE;
2347
2349 if(IS_NULL_PTR(gdt)) return FALSE;
2350 const int64_t change_timestamp_unix = g_date_time_to_unix(gdt);
2351 g_date_time_unref(gdt);
2353 "[xmp] imgid %d change_ts=%lld write_ts=%lld\n",
2354 img->id, (long long)change_timestamp_unix, (long long)write_timestamp);
2355 return change_timestamp_unix <= write_timestamp;
2356}
2357
2359 const gboolean force_write)
2360{
2361 if(IS_NULL_PTR(img) || img->id <= 0) return DT_IMAGE_WRITE_SIDECAR_DISABLED;
2362
2363 char imgpath[DT_PATH_MAX] = { 0 };
2364 if(dt_image_choose_input_path(img, imgpath, sizeof(imgpath), FALSE) == DT_IMAGE_PATH_NONE)
2365 {
2366 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d no source path available\n", img->id);
2368 }
2369
2370 char filename[DT_PATH_MAX] = { 0 };
2371 g_strlcpy(filename, imgpath, sizeof(filename));
2372 dt_image_path_append_version(img->id, filename, sizeof(filename));
2373 g_strlcat(filename, ".xmp", sizeof(filename));
2374
2375 if(!force_write && g_file_test(filename, G_FILE_TEST_EXISTS))
2376 {
2377 if(_sidecar_is_up_to_date(img))
2378 {
2379 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d sidecar up-to-date, skip\n", img->id);
2381 }
2382 }
2383
2384 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d writing sidecar %s\n", img->id, filename);
2385 if(dt_exif_xmp_write_with_imgpath(img, filename, imgpath) != 0)
2387
2388 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d updating write_timestamp\n", img->id);
2391}
2392
2394{
2395 if(imgid <= 0 || !dt_image_get_xmp_mode()) return DT_IMAGE_WRITE_SIDECAR_DISABLED;
2396
2397 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d write start\n", imgid);
2398 dt_image_t *img = dt_image_cache_get(imgid, 'w');
2399 if(IS_NULL_PTR(img))
2400 {
2401 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock failed\n", imgid);
2403 }
2404 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock acquired (write)\n", imgid);
2405
2408 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock released (write minimal)\n", imgid);
2409 return res;
2410}
2411
2413{
2414 if(imgid <= 0) return DT_IMAGE_WRITE_SIDECAR_DISABLED;
2415
2416 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d forced write start\n", imgid);
2417 dt_image_t *img = dt_image_cache_get(imgid, 'w');
2418 if(IS_NULL_PTR(img))
2419 {
2420 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock failed\n", imgid);
2422 }
2423 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock acquired (write)\n", imgid);
2424
2427 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock released (write minimal)\n", imgid);
2428 return res;
2429}
2430
2431void dt_image_synch_xmps(const GList *img)
2432{
2433 if(IS_NULL_PTR(img)) return;
2435 {
2437 }
2438}
2439
2441{
2442 if(selected > 0)
2443 {
2444 GList *imgs = g_list_append(NULL, GINT_TO_POINTER(selected));
2446 g_list_free(imgs);
2447 imgs = NULL;
2448 }
2449 else
2450 {
2451 GList *imgs = dt_act_on_get_images();
2452 dt_image_synch_xmps(imgs);
2453 g_list_free(imgs);
2454 imgs = NULL;
2455 }
2456}
2457
2458void dt_image_synch_all_xmp(const gchar *pathname)
2459{
2461 {
2462 const int32_t imgid = dt_image_get_id_full_path(pathname);
2463 if(imgid != UNKNOWN_IMAGE)
2464 {
2465 GList *imgs = g_list_append(NULL, GINT_TO_POINTER(imgid));
2467 g_list_free(imgs);
2468 imgs = NULL;
2469 }
2470 }
2471}
2472
2474{
2475 GList *imgs = NULL;
2476
2478 for(GList *l = candidates; l; l = g_list_next(l))
2479 {
2480 const int32_t imgid = GPOINTER_TO_INT(l->data);
2481 gboolean from_cache = FALSE;
2482 char filename[DT_PATH_MAX] = { 0 };
2483 dt_image_full_path(imgid, filename, sizeof(filename), &from_cache, __FUNCTION__);
2484
2485 if(g_file_test(filename, G_FILE_TEST_EXISTS))
2486 {
2487 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
2488 }
2489 }
2490 g_list_free(candidates);
2491
2492 const int count = g_list_length(imgs);
2493 if(count > 0)
2494 {
2496 dt_control_log(ngettext("%d local copy has been synchronized",
2497 "%d local copies have been synchronized", count),
2498 count);
2499 }
2500 g_list_free(imgs);
2501 imgs = NULL;
2502}
2503
2504void dt_image_get_datetime(const int32_t imgid, char *datetime)
2505{
2506 if(IS_NULL_PTR(datetime)) return;
2507 datetime[0] = '\0';
2508 const dt_image_t *cimg = dt_image_cache_get(imgid, 'r');
2509 if(IS_NULL_PTR(cimg)) return;
2512}
2513
2514static void _datetime_undo_data_free(gpointer data)
2515{
2516 GList *l = (GList *)data;
2517 g_list_free_full(l, dt_free_gpointer);
2518 l = NULL;
2519}
2520
2525
2526static void _image_set_datetimes(const GList *img, const GArray *dtime,
2527 GList **undo, const gboolean undo_on)
2528{
2529 int i = 0;
2530 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
2531 {
2532 const int32_t imgid = GPOINTER_TO_INT(imgs->data);
2533 // if char *datetime, the returned pointer is not correct => use of _datetime_t
2534 const _datetime_t *datetime = &g_array_index(dtime, _datetime_t, i);
2535 if(undo_on)
2536 {
2537 dt_undo_datetime_t *undodatetime = (dt_undo_datetime_t *)malloc(sizeof(dt_undo_datetime_t));
2538 undodatetime->imgid = imgid;
2539 dt_image_get_datetime(imgid, undodatetime->before);
2540
2541 memcpy(&undodatetime->after, datetime->dt, DT_DATETIME_LENGTH);
2542
2543 *undo = g_list_prepend(*undo, undodatetime);
2544 }
2545
2546 _set_datetime(imgid, datetime->dt);
2547 i++;
2548 }
2549}
2550
2551void dt_image_set_datetimes(const GList *imgs, const GArray *dtime, const gboolean undo_on)
2552{
2553 if(IS_NULL_PTR(imgs) || IS_NULL_PTR(dtime) || (g_list_length((GList *)imgs) != dtime->len))
2554 return;
2555 GList *undo = NULL;
2557
2558 _image_set_datetimes(imgs, dtime, &undo, undo_on);
2559
2560 if(undo_on)
2561 {
2564 }
2565}
2566
2567static void _image_set_datetime(const GList *img, const char *datetime,
2568 GList **undo, const gboolean undo_on)
2569{
2570 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
2571 {
2572 const int32_t imgid = GPOINTER_TO_INT(imgs->data);
2573 if(undo_on)
2574 {
2575 dt_undo_datetime_t *undodatetime = (dt_undo_datetime_t *)malloc(sizeof(dt_undo_datetime_t));
2576 undodatetime->imgid = imgid;
2577 dt_image_get_datetime(imgid, undodatetime->before);
2578
2579 memcpy(&undodatetime->after, datetime, DT_DATETIME_LENGTH);
2580
2581 *undo = g_list_prepend(*undo, undodatetime);
2582 }
2583
2584 _set_datetime(imgid, datetime);
2585 }
2586}
2587
2588void dt_image_set_datetime(const GList *imgs, const char *datetime, const gboolean undo_on)
2589{
2590 if(IS_NULL_PTR(imgs))
2591 return;
2592 GList *undo = NULL;
2594
2595 _image_set_datetime(imgs, datetime, &undo, undo_on);
2596
2597 if(undo_on)
2598 {
2601 }
2602}
2603
2604char *dt_image_get_audio_path_from_path(const char *image_path)
2605{
2606 size_t len = strlen(image_path);
2607 const char *c = image_path + len;
2608 while((c > image_path) && (*c != '.')) c--;
2609 len = c - image_path + 1;
2610
2611 char *result = g_strndup(image_path, len + 3);
2612
2613 result[len] = 'w';
2614 result[len + 1] = 'a';
2615 result[len + 2] = 'v';
2616 if(g_file_test(result, G_FILE_TEST_EXISTS)) return result;
2617
2618 result[len] = 'W';
2619 result[len + 1] = 'A';
2620 result[len + 2] = 'V';
2621 if(g_file_test(result, G_FILE_TEST_EXISTS)) return result;
2622
2623 dt_free(result);
2624 return NULL;
2625}
2626
2627char *dt_image_get_audio_path(const int32_t imgid)
2628{
2629 gboolean from_cache = FALSE;
2630 char image_path[DT_PATH_MAX] = { 0 };
2631 dt_image_full_path(imgid, image_path, sizeof(image_path), &from_cache, __FUNCTION__);
2632
2633 return dt_image_get_audio_path_from_path(image_path);
2634}
2635
2636static char *_text_path_legacy_if_exists(const char *image_path)
2637{
2638 size_t len = strlen(image_path);
2639 const char *c = image_path + len;
2640 while((c > image_path) && (*c != '.')) c--;
2641 len = c - image_path + 1;
2642
2643 char *result = g_strndup(image_path, len + 3);
2644
2645 result[len] = 't';
2646 result[len + 1] = 'x';
2647 result[len + 2] = 't';
2648 if(g_file_test(result, G_FILE_TEST_EXISTS)) return result;
2649
2650 result[len] = 'T';
2651 result[len + 1] = 'X';
2652 result[len + 2] = 'T';
2653 if(g_file_test(result, G_FILE_TEST_EXISTS)) return result;
2654
2655 dt_free(result);
2656 return NULL;
2657}
2658
2659static char *_text_path_legacy_build(const char *image_path)
2660{
2661 size_t len = strlen(image_path);
2662 const char *c = image_path + len;
2663 while((c > image_path) && (*c != '.')) c--;
2664 len = c - image_path + 1;
2665
2666 char *result = g_strndup(image_path, len + 3);
2667 result[len] = 't';
2668 result[len + 1] = 'x';
2669 result[len + 2] = 't';
2670 return result;
2671}
2672
2673char *dt_image_get_text_path_from_path(const char *image_path)
2674{
2675 if(IS_NULL_PTR(image_path)) return NULL;
2676
2677 return _text_path_legacy_if_exists(image_path);
2678}
2679
2680char *dt_image_build_text_path_from_path(const char *image_path)
2681{
2682 if(IS_NULL_PTR(image_path)) return NULL;
2683
2684 return _text_path_legacy_build(image_path);
2685}
2686
2687static void _copy_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path)
2688{
2689 if(IS_NULL_PTR(src_image_path) || IS_NULL_PTR(dest_image_path)) return;
2690
2691 char *src_txt = dt_image_get_text_path_from_path(src_image_path);
2692 if(IS_NULL_PTR(src_txt)) return;
2693
2694 char *dest_txt = _text_path_legacy_build(dest_image_path);
2695 if(dest_txt)
2696 {
2697 GFile *src = g_file_new_for_path(src_txt);
2698 GFile *dest = g_file_new_for_path(dest_txt);
2699 GError *gerror = NULL;
2700 gboolean copyStatus = g_file_copy(src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, &gerror);
2701
2702 if(!copyStatus && g_error_matches(gerror, G_IO_ERROR, G_IO_ERROR_EXISTS))
2703 copyStatus = TRUE;
2704
2705 if(!copyStatus && gerror)
2706 fprintf(stderr, "[dt_image] failed to copy text sidecar `%s' -> `%s': %s\n",
2707 src_txt, dest_txt, gerror->message);
2708
2709 g_clear_error(&gerror);
2710 g_object_unref(dest);
2711 g_object_unref(src);
2712 }
2713
2714 dt_free(dest_txt);
2715 dt_free(src_txt);
2716}
2717
2718static void _move_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path, const gboolean overwrite)
2719{
2720 if(IS_NULL_PTR(src_image_path) || IS_NULL_PTR(dest_image_path)) return;
2721
2722 char *src_txt = dt_image_get_text_path_from_path(src_image_path);
2723 if(IS_NULL_PTR(src_txt)) return;
2724
2725 char *dest_txt = _text_path_legacy_build(dest_image_path);
2726 if(dest_txt)
2727 {
2728 GFile *src = g_file_new_for_path(src_txt);
2729 GFile *dest = g_file_new_for_path(dest_txt);
2730 GError *gerror = NULL;
2731 const GFileCopyFlags flags = overwrite ? G_FILE_COPY_OVERWRITE : G_FILE_COPY_NONE;
2732 gboolean moveStatus = g_file_move(src, dest, flags, NULL, NULL, NULL, &gerror);
2733
2734 if(!moveStatus && gerror)
2735 fprintf(stderr, "[dt_image] failed to move text sidecar `%s' -> `%s': %s\n",
2736 src_txt, dest_txt, gerror->message);
2737
2738 g_clear_error(&gerror);
2739 g_object_unref(dest);
2740 g_object_unref(src);
2741 }
2742
2743 dt_free(dest_txt);
2744 dt_free(src_txt);
2745}
2746
2747char *dt_image_get_text_path(const int32_t imgid)
2748{
2749 char image_path[DT_PATH_MAX] = { 0 };
2750
2751 gboolean from_cache = FALSE;
2752 dt_image_full_path(imgid, image_path, sizeof(image_path), &from_cache, __FUNCTION__);
2753
2754 if(image_path[0] != '\0' && g_file_test(image_path, G_FILE_TEST_EXISTS))
2755 return dt_image_get_text_path_from_path(image_path);
2756
2757 from_cache = TRUE;
2758 dt_image_full_path(imgid, image_path, sizeof(image_path), &from_cache, __FUNCTION__);
2759
2760 if(from_cache && image_path[0] != '\0' && g_file_test(image_path, G_FILE_TEST_EXISTS))
2761 return dt_image_get_text_path_from_path(image_path);
2762
2763 return NULL;
2764}
2765
2766float dt_image_get_exposure_bias(const struct dt_image_t *image_storage)
2767{
2768 // just check that pointers exist and are initialized
2769 if((image_storage) && (image_storage->exif_exposure_bias))
2770 {
2771 // sanity checks because I don't trust exif tags too much
2772 if(isnan(image_storage->exif_exposure_bias)
2773 || CLAMP(image_storage->exif_exposure_bias, -5.0f, 5.0f) != image_storage->exif_exposure_bias)
2774 return 0.0f; // isnan
2775 else
2776 return CLAMP(image_storage->exif_exposure_bias, -5.0f, 5.0f);
2777 }
2778 else
2779 return 0.0f;
2780}
2781
2782char *dt_image_camera_missing_sample_message(const struct dt_image_t *img, gboolean logmsg)
2783{
2784 const char *T1 = _("<b>WARNING</b>: camera is missing samples!");
2785 const char *T2 = _("You must provide samples in <a href='https://raw.pixls.us/'>https://raw.pixls.us/</a>");
2786 char *T3 = g_strdup_printf(_("for `%s' `%s'\n"
2787 "in as many format/compression/bit depths as possible"),
2788 img->camera_maker, img->camera_model);
2789 const char *T4 = _("or the <b>RAW won't be readable</b> in next version.");
2790
2791 char *NL = logmsg ? "\n\n" : "\n";
2792 char *PREFIX = logmsg ? "<big>" : "";
2793 char *SUFFIX = logmsg ? "</big>" : "";
2794
2795 char *msg = g_strconcat(PREFIX, T1, NL, T2, NL, T3, NL, T4, SUFFIX, NULL);
2796
2797 if(logmsg)
2798 {
2799 char *newmsg = dt_util_str_replace(msg, "<b>", "<span foreground='red'><b>");
2800 dt_free(msg);
2801 msg = dt_util_str_replace(newmsg, "</b>", "</b></span>");
2802 dt_free(newmsg);
2803 }
2804
2805 dt_free(T3);
2806 return msg;
2807}
2808
2810{
2811 if(img->camera_missing_sample)
2812 {
2814 dt_control_log(msg, (char *)NULL);
2815 dt_free(msg);
2816 }
2817}
2818
2819void dt_get_dirname_from_imgid(gchar *dir, const int32_t imgid)
2820{
2821 gchar path[DT_PATH_MAX] = { 0 };
2822 gboolean from_cache = FALSE;
2823 dt_image_full_path(imgid, path, sizeof(path), &from_cache, __FUNCTION__);
2824 gchar *folder = g_path_get_dirname(path);
2825 g_strlcpy(dir, folder, DT_PATH_MAX); // callers all provide a DT_PATH_MAX-sized dir buffer
2826 dt_free(folder);
2827}
2828
2829// clang-format off
2830// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2831// vim: shiftwidth=2 expandtab tabstop=2 cindent
2832// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2833// clang-format on
GList * dt_act_on_get_images()
Definition act_on.c:38
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
int dt_atomic_get_int(dt_atomic_int *var)
atomic_int dt_atomic_int
Definition atomic.h:68
const char * extension(dt_imageio_module_data_t *data)
Definition avif.c:651
uint32_t bit_depth
Definition avif.c:103
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
Definition clipping.c:487
void dt_collection_update_query(const dt_collection_t *collection, dt_collection_change_t query_change, dt_collection_properties_t changed_property, GList *list)
Definition collection.c:837
dt_collection_t * dt_collection_get_global(void)
Definition collection.c:138
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:155
@ DT_COLLECTION_CHANGE_RELOAD
Definition collection.h:164
@ IOP_CS_RAW
@ IOP_CS_RGB
const dt_colormatrix_t dt_aligned_pixel_t out
const float top
const dt_colormatrix_t matrix
gchar * dt_conf_get_string(const char *name)
Read the stored string for name as a private copy.
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_conf_set_string(const char *name, const char *val)
static dt_atomic_int _xmp_mode_cache
gboolean dt_image_is_matrix_correction_supported(const dt_image_t *img)
int32_t dt_image_copy_rename(const int32_t imgid, const int32_t filmid, const gchar *newname)
int32_t dt_image_move(const int32_t imgid, const int32_t filmid)
void dt_image_synch_xmps(const GList *img)
static void _pop_undo(gpointer user_data, const dt_undo_type_t type, dt_undo_data_t data, const dt_undo_action_t action, GList **imgs)
char * dt_image_get_audio_path(const int32_t imgid)
gboolean dt_image_is_raw(const dt_image_t *img)
dt_image_pipe_class_t dt_image_pipe_class(const dt_image_t *img)
int dt_image_get_xmp_rating(const dt_image_t *img)
int32_t dt_image_import(const int32_t film_id, const char *filename, gboolean raise_signals)
void dt_image_check_camera_missing_sample(const struct dt_image_t *img)
dt_image_orientation_t dt_image_get_orientation(const int32_t imgid)
static void _image_set_monochrome_flag(const int32_t imgid, gboolean monochrome, gboolean undo_on)
dt_image_flags_t dt_image_flags_from_extension(const char *extension)
void dt_image_path_append_version(const int32_t imgid, char *pathname, size_t pathname_len)
static void _set_location(const int32_t imgid, const dt_image_geoloc_t *geoloc)
static gboolean _image_matrix_has_data(const float *matrix, const int count)
int32_t dt_image_get_id(int32_t film_id, const gchar *filename)
int32_t dt_image_copy(const int32_t imgid, const int32_t filmid)
void dt_image_refresh_makermodel(dt_image_t *img)
static dt_image_orientation_t _orientation_mirror_horizontally(dt_image_orientation_t orientation)
Toggle the stored bit that mirrors the image along the axis the USER calls horizontal.
int32_t dt_image_duplicate_no_reload(const int32_t imgid)
static const char * _image_buf_type_to_string(const dt_iop_buffer_type_t type)
int dt_image_monochrome_flags(const dt_image_t *img)
uint32_t dt_image_altered(const int32_t imgid)
int32_t dt_image_import_lua(const int32_t film_id, const char *filename)
void dt_image_get_datetime(const int32_t imgid, char *datetime)
const char * dt_image_pipe_class_name(const dt_image_pipe_class_t klass)
static void _geotag_undo_data_free(gpointer data)
char * dt_image_get_text_path_from_path(const char *image_path)
void dt_image_set_location(const int32_t imgid, const dt_image_geoloc_t *geoloc, const gboolean undo_on, const gboolean group_on)
void dt_image_film_roll(const dt_image_t *img, char *pathname, size_t pathname_len)
void dt_image_print_exif(const dt_image_t *img, char *line, size_t line_len)
void dt_image_synch_all_xmp(const gchar *pathname)
void dt_image_get_location(const int32_t imgid, dt_image_geoloc_t *geoloc)
static void _datetime_undo_data_free(gpointer data)
gboolean dt_image_is_hdr(const dt_image_t *img)
int32_t dt_image_duplicate_with_version(const int32_t imgid, const int32_t newversion)
gboolean dt_image_is_monochrome(const dt_image_t *img)
void dt_image_print_debug_info(const dt_image_t *img, const char *context)
static dt_image_orientation_t _orientation_mirror_vertically(dt_image_orientation_t orientation)
The same, along the axis the user calls vertical.
void dt_image_init(dt_image_t *img)
void dt_image_buffer_resolve_flags(dt_image_t *img)
static void _move_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path, const gboolean overwrite)
void dt_image_set_datetimes(const GList *imgs, const GArray *dtime, const gboolean undo_on)
static int32_t _image_duplicate_with_version(const int32_t imgid, const int32_t newversion, const gboolean undo, const gboolean reload)
static void _image_set_datetimes(const GList *img, const GArray *dtime, GList **undo, const gboolean undo_on)
const char * dt_image_film_roll_name(const char *path)
float dt_image_get_exposure_bias(const struct dt_image_t *image_storage)
GList * dt_image_find_xmps(const char *filename)
void dt_image_flip(const int32_t imgid, const dt_image_transform_t transform)
Apply an orientation change to one image, on top of the orientation it already has.
static dt_image_write_sidecar_result_t _write_sidecar_file_from_image_locked(const dt_image_t *img, const gboolean force_write)
void dt_image_set_datetime(const GList *imgs, const char *datetime, const gboolean undo_on)
static void _pop_undo_execute(const int32_t imgid, const gboolean before, const gboolean after)
gboolean dt_image_is_ldr(const dt_image_t *img)
static int _nb_other_local_copy_for(const int32_t imgid)
gboolean dt_image_pipe_class_is_provisional(const dt_image_t *img)
void dt_image_path_append_version_no_db(int version, char *pathname, size_t pathname_len)
gboolean dt_image_needs_rawprepare(const dt_image_t *img)
static dt_image_orientation_t _image_get_history_orientation(const int32_t imgid)
void dt_image_set_monochrome_flag(const int32_t imgid, gboolean monochrome)
static void _image_set_datetime(const GList *img, const char *datetime, GList **undo, const gboolean undo_on)
static void _set_datetime(const int32_t imgid, const char *datetime)
void dt_image_set_images_locations(const GList *imgs, const GArray *gloc, const gboolean undo_on)
char * dt_image_get_audio_path_from_path(const char *image_path)
gboolean dt_image_use_monochrome_workflow(const dt_image_t *img)
int dt_image_local_copy_set(const int32_t imgid)
static void _image_set_location(GList *imgs, const dt_image_geoloc_t *geoloc, GList **undo, const gboolean undo_on)
void dt_image_film_roll_directory(const dt_image_t *img, char *pathname, size_t pathname_len)
gboolean dt_image_get_usercrop_oriented(const dt_image_t *img, const dt_image_orientation_t orientation, dt_boundingbox_t box)
dt_image_path_source_t dt_image_choose_input_path(const dt_image_t *img, char *pathname, size_t pathname_len, gboolean force_cache)
dt_image_write_sidecar_result_t dt_image_write_sidecar_file(const int32_t imgid)
void dt_image_xmp_mode_refresh_from_conf(void)
dt_image_orientation_t dt_image_get_effective_orientation(const dt_image_t *img)
void dt_image_remove(const int32_t imgid)
gboolean dt_image_is_sraw(const dt_image_t *img)
void dt_image_set_flip(const int32_t imgid, const dt_image_orientation_t orientation)
gboolean dt_image_is_mosaiced(const dt_image_t *img)
char * dt_image_camera_missing_sample_message(const struct dt_image_t *img, gboolean logmsg)
int dt_image_get_xmp_rating_from_flags(const int flags)
void dt_image_set_locations(const GList *imgs, const dt_image_geoloc_t *geoloc, const gboolean undo_on)
static void _copy_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path)
int dt_image_read_duplicates(const uint32_t id, const char *filename, const gboolean clear_selection)
void dt_image_orient_boundingbox(const dt_boundingbox_t in, const dt_image_orientation_t orientation, dt_boundingbox_t out)
gboolean dt_image_resolve_usercrop(const int32_t imgid, dt_boundingbox_t box)
static void _image_set_images_locations(const GList *img, const GArray *gloc, GList **undo, const gboolean undo_on)
int dt_image_local_copy_reset(const int32_t imgid)
static int32_t _image_import_internal(const int32_t film_id, const char *filename, gboolean lua_locking, gboolean raise_signals)
gboolean dt_image_get_xmp_mode()
static char * _text_path_legacy_if_exists(const char *image_path)
char * dt_image_get_text_path(const int32_t imgid)
int32_t dt_image_get_id_full_path(const gchar *filename)
gboolean dt_image_safe_remove(const int32_t imgid)
void dt_image_set_xmp_rating(dt_image_t *img, const int rating)
char * dt_image_build_text_path_from_path(const char *image_path)
static const char * _image_colorspace_to_string(const dt_image_colorspace_t colorspace)
static char * _text_path_legacy_build(const char *image_path)
static gboolean _sidecar_is_up_to_date(const dt_image_t *img)
dt_image_write_sidecar_result_t dt_image_write_sidecar_file_forced(const int32_t imgid)
int32_t dt_image_duplicate(const int32_t imgid)
gboolean dt_image_get_usercrop(const dt_image_t *img, dt_boundingbox_t box)
void dt_image_local_copy_synch()
int32_t dt_image_rename(const int32_t imgid, const int32_t filmid, const gchar *newname)
void dt_image_synch_xmp(const int selected)
void dt_get_dirname_from_imgid(gchar *dir, const int32_t imgid)
void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
Get the full path of an image out of the database.
void dt_image_history_changed(const int32_t imgid, const gboolean refresh_filmstrip)
gboolean dt_image_needs_demosaic(const dt_image_t *img)
void dt_image_set_provisional_dsc(dt_image_t *img)
void dt_image_local_copy_paths_from_fullpath(const char *fullpath, int32_t imgid, char *local_copy_path, size_t local_copy_len, char *local_copy_legacy_path, size_t local_copy_legacy_len)
void dt_control_log(const char *msg,...)
Definition control.c:828
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:928
void dt_control_save_xmp(const int32_t imgid)
void dt_control_save_xmps(const GList *imgids, const gboolean check_history)
void dt_control_save_xmps_forced(const GList *imgids)
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:527
GList * dt_iop_get_modules_so(void)
Definition darktable.c:532
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:598
gboolean dt_datetime_img_to_exif(char *exif, const size_t exif_size, const dt_image_t *img)
Definition datetime.c:255
GDateTime * dt_datetime_gtimespan_to_gdatetime(const GTimeSpan gts)
Definition datetime.c:445
GTimeSpan dt_datetime_now_to_gtimespan()
Definition datetime.c:237
void dt_datetime_exif_to_img(dt_image_t *img, const char *exif)
Definition datetime.c:243
#define DT_DATETIME_LENGTH
Definition datetime.h:38
GHashTable * selected
set of checked row labels, mirrored to conf on every change
GtkWidget * folder
destination folder chooser
const int res
Definition dtpthread.h:351
int dt_exif_read(dt_image_t *img, const char *path)
Definition exif.cc:1994
void dt_exif_read_usercrop(dt_image_t *img, const char *filename)
Definition exif.cc:1031
What a photograph says about itself: the EXIF, IPTC and XMP tags a camera and a cataloguer write,...
void dt_loc_get_user_cache_dir(char *cachedir, size_t bufsize)
GList * win_image_find_duplicates(const char *filename)
Definition filepath.c:30
char * dt_film_repository_get_folder(const int32_t id)
dt_iop_buffer_type_t
Definition format.h:54
@ TYPE_FLOAT
Definition format.h:56
@ TYPE_UNKNOWN
Definition format.h:55
@ TYPE_UINT8
Definition format.h:58
@ TYPE_UINT16
Definition format.h:57
int dt_grouping_remove_from_group(const int32_t image_id)
Definition grouping.c:58
void dt_grouping_add_grouped_images(GList **images)
Definition grouping.c:148
void dt_grouping_add_to_group(const int32_t group_id, const int32_t image_id)
Definition grouping.c:47
gboolean dt_history_copy_and_paste_on_image(const int32_t imgid, const int32_t dest_imgid, GList *ops, const gboolean copy_full, const dt_history_merge_strategy_t mode, const gboolean copy_iop_order, dt_hm_batch_state_t *batch)
@ DT_HISTORY_MERGE_REPLACE
gboolean dt_history_repository_get_last_enabled_params(const int32_t imgid, const char *operation, void **params, int32_t *params_len)
gboolean dt_history_repository_write_item(const int32_t imgid, const int num, const char *operation, const void *op_params, const int op_params_size, const int module_version, const gboolean enabled, const void *blendop_params, const int blendop_params_size, const int blendop_version, const int multi_priority, const char *multi_name)
int dt_history_repository_count_items(const int32_t imgid)
gboolean dt_history_repository_set_end(const int32_t imgid, const int32_t history_end)
int32_t dt_history_repository_get_next_num(const int32_t imgid)
void dt_history_snapshot_undo_create(const int32_t imgid, int *snap_id, int *history_end)
void dt_history_snapshot_undo_lt_history_data_free(gpointer data)
dt_undo_lt_history_t * dt_history_snapshot_item_init(void)
void dt_history_snapshot_undo_pop(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
dt_image_colorspace_t
Definition image.h:191
@ DT_IMAGE_COLORSPACE_NONE
Definition image.h:192
@ DT_IMAGE_COLORSPACE_ADOBE_RGB
Definition image.h:194
@ DT_IMAGE_COLORSPACE_SRGB
Definition image.h:193
dt_image_path_source_t
Definition image.h:301
@ DT_IMAGE_PATH_NONE
Definition image.h:302
@ DT_IMAGE_PATH_LOCAL_COPY_LEGACY
Definition image.h:304
@ DT_IMAGE_PATH_ORIGINAL
Definition image.h:305
@ DT_IMAGE_PATH_LOCAL_COPY
Definition image.h:303
#define UNKNOWN_IMAGE
Definition image.h:78
float dt_boundingbox_t[4]
Definition image.h:83
dt_image_orientation_t
Definition image.h:216
@ ORIENTATION_SWAP_XY
Definition image.h:221
@ ORIENTATION_FLIP_Y
Definition image.h:219
@ ORIENTATION_NULL
Definition image.h:217
@ ORIENTATION_NONE
Definition image.h:218
@ ORIENTATION_FLIP_X
Definition image.h:220
dt_image_flags_t
Definition image.h:104
@ DT_IMAGE_NO_LEGACY_PRESETS
Definition image.h:132
@ DT_IMAGE_HAS_ADDITIONAL_DNG_TAGS
Definition image.h:145
@ DT_IMAGE_HAS_WAV
Definition image.h:138
@ DT_IMAGE_AUTO_PRESETS_APPLIED
Definition image.h:130
@ DT_IMAGE_S_RAW
Definition image.h:147
@ DT_IMAGE_REMOVE
Definition image.h:128
@ DT_IMAGE_MOSAIC
Definition image.h:159
@ DT_IMAGE_MONOCHROME_BAYER
Definition image.h:151
@ DT_IMAGE_LOCAL_COPY
Definition image.h:134
@ DT_IMAGE_BUFFER_RESOLVED
Definition image.h:164
@ DT_IMAGE_RAW
Definition image.h:124
@ DT_IMAGE_REJECTED
Definition image.h:113
@ DT_IMAGE_4BAYER
Definition image.h:140
@ DT_IMAGE_MONOCHROME_WORKFLOW
Definition image.h:153
@ DT_IMAGE_HAS_TXT
Definition image.h:136
@ DT_IMAGE_MONOCHROME
Definition image.h:142
@ DT_IMAGE_HDR
Definition image.h:126
@ DT_IMAGE_LDR
Definition image.h:122
@ DT_IMAGE_MONOCHROME_PREVIEW
Definition image.h:149
dt_image_write_sidecar_result_t
Definition image.h:743
@ DT_IMAGE_WRITE_SIDECAR_OK
Definition image.h:744
@ DT_IMAGE_WRITE_SIDECAR_IO_ERROR
Definition image.h:748
@ DT_IMAGE_WRITE_SIDECAR_CACHE_BUSY
Definition image.h:746
@ DT_IMAGE_WRITE_SIDECAR_NO_SOURCE_PATH
Definition image.h:747
@ DT_IMAGE_WRITE_SIDECAR_DISABLED
Definition image.h:745
dt_image_transform_t
An orientation change the user asks for, in the coordinates they are looking at.
Definition image.h:245
@ DT_IMAGE_TRANSFORM_FLIP_VERTICALLY
mirror top <-> bottom, as displayed
Definition image.h:250
@ DT_IMAGE_TRANSFORM_ROTATE_CW
quarter turn clockwise
Definition image.h:246
@ DT_IMAGE_TRANSFORM_RESET
back to ORIENTATION_NULL, i.e. what EXIF says
Definition image.h:248
@ DT_IMAGE_TRANSFORM_FLIP_HORIZONTALLY
mirror left <-> right, as displayed
Definition image.h:249
@ DT_IMAGE_TRANSFORM_ROTATE_CCW
quarter turn counter-clockwise
Definition image.h:247
dt_image_pipe_class_t
Mutually-exclusive classification of an image by the early-pipeline processing it requires....
Definition image.h:182
@ DT_IMAGE_PIPE_MOSAIC_RAW
Definition image.h:184
@ DT_IMAGE_PIPE_LINEAR_RAW
Definition image.h:185
@ DT_IMAGE_PIPE_RGB_LDR
Definition image.h:186
@ DT_IMAGE_PIPE_RGB_HDR
Definition image.h:187
@ DT_IMAGE_PIPE_UNKNOWN
Definition image.h:183
static dt_image_orientation_t dt_image_orientation(const dt_image_t *img)
Definition image.h:683
@ DT_IMAGE_USERCROP_UNKNOWN
Definition image.h:266
@ DT_IMAGE_USERCROP_VALID
Definition image.h:269
@ LOADER_UNKNOWN
Definition image.h:283
void dt_image_cache_write_release(dt_image_t *img, dt_image_cache_write_mode_t mode)
gboolean dt_image_cache_is_ready(void)
Has the image cache been initialised? Callers that run before dt_image_cache_init() or after its clea...
Definition image_cache.c:83
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
dt_image_t * dt_image_cache_get_reload(const int32_t imgid, char mode)
void dt_image_cache_remove(const int32_t imgid)
void dt_image_cache_read_release(const dt_image_t *img)
@ DT_IMAGE_CACHE_RELAXED
Definition image_cache.h:50
@ DT_IMAGE_CACHE_MINIMAL
Definition image_cache.h:53
@ DT_IMAGE_CACHE_SAFE
Definition image_cache.h:48
gboolean dt_image_ext_is_ambiguous(const char *ext)
gboolean dt_image_ext_is_supported(const char *ext)
gboolean dt_image_ext_is_ldr(const char *ext)
gboolean dt_image_ext_is_raw(const char *ext)
gboolean dt_image_ext_is_hdr(const char *ext)
void dt_image_notify_imported(const int32_t imgid)
Raise it. Internal to common/image.c.
common/image.c states that an image entered the library; whoever is showing the collection decides wh...
int dt_image_repository_get_version(const int32_t imgid)
The duplicate version number of imgid, or 0.
void dt_image_repository_touch_write_timestamp(const int32_t imgid)
Set write_timestamp of imgid to now.
int dt_image_repository_count_others_with_flag(const int32_t imgid, const int flag)
How many OTHER images share this image's film roll and filename and carry flag.
int64_t dt_image_repository_get_write_timestamp(const int32_t imgid)
main.images.write_timestamp for imgid, or 0.
gboolean dt_image_repository_set_group(const int32_t imgid, const int32_t group_id)
Set imgid's group_id.
int32_t dt_image_repository_find_group_for_pattern(const int32_t film_id, const char *filename_pattern, const gboolean leader_only, const int32_t exclude_imgid)
The group leader of an existing image in film_id matching filename_pattern (a LIKE pattern),...
gboolean dt_image_repository_delete(const int32_t imgid)
Delete imgid from main.images and main.meta_data.
int32_t dt_image_repository_copy_to_film(const int32_t imgid, const int32_t filmid, const char *new_filename, const char *old_filename)
Copy imgid into film roll filmid under new_filename, returning the new id.
GList * dt_image_repository_get_duplicate_ids(const int32_t imgid)
Every image id sharing imgid's film roll and filename, itself included, in row order – an image and i...
GList * dt_image_repository_get_ids_with_flag(const int flag)
Every image id whose flags carry flag, in row order.
int32_t dt_image_repository_duplicate(const int32_t imgid, const int32_t newversion)
Duplicate imgid as a new version, returning the new image id (-1 on failure).
int32_t dt_image_repository_find_by_film_and_filename(const int32_t film_id, const char *filename)
The image id for filename inside film roll film_id, or -1.
gboolean dt_image_repository_set_version(const int32_t imgid, const int version)
Set both version and max_version of imgid to version.
int32_t dt_image_repository_find_by_folder_and_filename(const char *folder, const char *filename)
The image id for a full path, split into its folder and filename, or -1.
GList * dt_image_repository_get_group_members(const int32_t group_id, const int32_t exclude_imgid)
Image ids in group group_id.
gboolean dt_image_repository_insert_import(const int32_t film_id, const char *filename, const int flags, const int64_t import_timestamp)
Insert a bare row for a file being imported, returning TRUE on success.
Reading and writing one dt_image_t to and from the library database. The SQL half of what used to be ...
int bpp
gboolean dt_imageio_lookup_makermodel(const char *maker, const char *model, char *mk, int mk_len, char *md, int md_len, char *al, int al_len)
gboolean dt_lightroom_import(int32_t imgid, dt_develop_t *dev, gboolean iauto)
Definition lightroom.c:1053
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_CONTROL
Definition logging.h:52
@ DT_DEBUG_IMAGEIO
Definition logging.h:68
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define CLAMPS(A, L, H)
Definition math.h:78
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
void dt_metadata_geotags_changed(const GList *imgs)
void dt_metadata_tags_changed(void)
Raise it. Internal to the module.
Everything this module says to the outside world: messages for the user, and the fact that the tag vo...
void dt_mipmap_cache_remove(const int32_t imgid, const gboolean flush_disk)
void dt_mipmap_cache_copy_thumbnails(const uint32_t dst_imgid, const uint32_t src_imgid)
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
#define DT_MAX_FILENAME_LEN
Definition paths.h:35
const char * name
Definition pdf.h:90
void dt_iop_buffer_dsc_update_bpp(dt_iop_buffer_dsc_t *dsc)
#define DT_VIEW_RATINGS_MASK
Definition ratings.h:42
struct dt_selection_t * dt_selection_get_global(void)
Definition selection.c:80
void dt_selection_clear(dt_selection_t *selection)
Definition selection.c:271
const float const int flip
char dt[DT_DATETIME_LENGTH]
double latitude
Definition image.h:332
double elevation
Definition image.h:332
double longitude
Definition image.h:332
gboolean has_audio
Definition image.h:476
gboolean is_hdr
Definition image.h:479
float exif_exposure
Definition image.h:367
float pixel_aspect_ratio
Definition image.h:443
int32_t height
Definition image.h:397
gboolean camera_missing_sample
Definition image.h:384
GTimeSpan export_timestamp
Definition image.h:415
uint64_t history_hash
Definition image.h:404
char camera_model[64]
Definition image.h:380
float exif_focus_distance
Definition image.h:372
dt_boundingbox_t usercrop
Definition image.h:460
GTimeSpan import_timestamp
Definition image.h:415
gboolean is_bw
Definition image.h:477
int32_t group_id
Definition image.h:401
char camera_makermodel[128]
Definition image.h:382
float exif_exposure_bias
Definition image.h:368
uint16_t raw_black_level
Definition image.h:437
float exif_iso
Definition image.h:370
GList * dng_gain_maps
Definition image.h:464
dt_image_loader_t loader
Definition image.h:417
char camera_maker[64]
Definition image.h:379
uint32_t raw_white_point
Definition image.h:439
int32_t exif_inited
Definition image.h:365
float exif_aperture
Definition image.h:369
char local_copy_path[DT_PATH_MAX]
Definition image.h:388
int32_t version
Definition image.h:401
int32_t crop_height
Definition image.h:398
uint64_t mipmap_hash
Definition image.h:411
dt_image_geoloc_t geoloc
Definition image.h:434
char camera_legacy_makermodel[128]
Definition image.h:383
dt_image_usercrop_status_t usercrop_status
Definition image.h:461
int32_t flags
Definition image.h:401
dt_image_orientation_t orientation
Definition image.h:366
int32_t width
Definition image.h:397
float exif_focal_length
Definition image.h:371
char exif_maker[64]
Definition image.h:374
GTimeSpan change_timestamp
Definition image.h:415
uint32_t profile_size
Definition image.h:423
int32_t crop_y
Definition image.h:398
uint32_t history_items
Definition image.h:403
int rating
Definition image.h:473
gboolean has_localcopy
Definition image.h:475
char camera_alias[64]
Definition image.h:381
char exif_lens[128]
Definition image.h:376
GTimeSpan print_timestamp
Definition image.h:415
int32_t film_id
Definition image.h:401
char folder[DT_PATH_MAX]
Definition image.h:390
int32_t crop_x
Definition image.h:398
int color_labels
Definition image.h:472
float d65_color_matrix[9]
Definition image.h:421
int32_t p_height
Definition image.h:397
dt_iop_buffer_dsc_t dsc
Definition image.h:419
int32_t p_width
Definition image.h:397
char filmroll[DT_PATH_MAX]
Definition image.h:391
char exif_model[64]
Definition image.h:375
dt_image_colorspace_t colorspace
Definition image.h:429
float adobe_XYZ_to_CAM[4][3]
Definition image.h:449
struct dt_colorspaces_color_profile_t * embedded_profile
Definition image.h:428
uint8_t * profile
Definition image.h:422
char datetime[200]
Definition image.h:392
struct dt_cache_entry_t * cache_entry
Definition image.h:482
int32_t crop_width
Definition image.h:398
uint32_t group_members
Definition image.h:402
char local_copy_legacy_path[DT_PATH_MAX]
Definition image.h:389
dt_image_raw_parameters_t legacy_flip
Definition image.h:431
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:386
int32_t id
Definition image.h:401
uint16_t raw_black_level_separate[4]
Definition image.h:438
uint64_t self_hash
Definition image.h:412
dt_aligned_pixel_t wb_coeffs
Definition image.h:446
float exif_crop
Definition image.h:373
char fullpath[DT_PATH_MAX]
Definition image.h:387
uint32_t fuji_rotation_pos
Definition image.h:442
gboolean is_bw_flow
Definition image.h:478
uint32_t filters
Definition format.h:89
uint16_t raw_black_level
Definition format.h:103
unsigned int channels
Definition format.h:83
struct dt_iop_buffer_dsc_t::@55 rawprepare
dt_iop_buffer_type_t datatype
Definition format.h:85
dt_aligned_pixel_t processed_maximum
Definition format.h:114
uint16_t raw_white_point
Definition format.h:104
char after[DT_DATETIME_LENGTH]
char before[DT_DATETIME_LENGTH]
dt_image_geoloc_t after
dt_image_geoloc_t before
gboolean dt_tag_attach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:377
gboolean dt_tag_detach_by_string(const char *name, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:500
gboolean dt_tag_new(const char *name, guint *tagid)
Definition tags.c:164
void dt_thumbnail_notify_image_changed(int32_t imgid, gboolean refresh_filmstrip)
void dt_undo_end_group(dt_undo_t *self)
Definition undo.c:149
void dt_undo_start_group(dt_undo_t *self, dt_undo_type_t type)
Definition undo.c:134
void dt_undo_record(dt_undo_t *self, gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, void(*undo)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item, dt_undo_action_t action, GList **imgs), void(*free_data)(gpointer data))
Definition undo.c:163
dt_undo_type_t
Definition undo.h:40
@ DT_UNDO_GEOTAG
Definition undo.h:42
@ DT_UNDO_DATETIME
Definition undo.h:51
@ DT_UNDO_LT_HISTORY
Definition undo.h:49
@ DT_UNDO_FLAGS
Definition undo.h:50
@ DT_UNDO_DUPLICATE
Definition undo.h:52
dt_undo_t * dt_undo_get_global(void)
Definition darktable.c:610
void * dt_undo_data_t
Definition undo.h:68
dt_undo_action_t
Definition undo.h:63
@ DT_ACTION_UNDO
Definition undo.h:64
gchar * dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
Definition utility.c:140
gboolean dt_util_test_image_file(const char *filename)
Definition utility.c:322
gchar * dt_util_normalize_path(const gchar *_input)
Definition utility.c:683
char * dt_util_format_exposure(const float exposuretime)
Definition utility.c:868
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
@ DT_VIEW_DARKROOM
Definition view.h:79
int dt_exif_xmp_write_with_imgpath(const dt_image_t *image, const char *filename, const char *imgpath)
int dt_exif_xmp_read(dt_image_t *img, const char *filename, const int history_only)
The XMP document that carries an image's development: history stack, mask shapes, module order,...