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-2026 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 "common/film.h"
88#include "history/history.h"
89#include "database/database.h"
96#include "caches/image_cache.h"
99#include "caches/mipmap_cache.h"
100#include "metadata/notify.h"
101#include "metadata/ratings.h"
102#include "metadata/tags.h"
103#include "common/undo.h"
104#include "common/selection.h"
105#include "common/datetime.h"
106#include "common/conf.h"
107#include "control/control.h"
108#include "system/atomic.h"
109#include "develop/lightroom.h"
110#include "develop/develop.h"
111#include "views/view.h"
112#include "win/filepath.h"
113#include <assert.h>
114#include <ctype.h>
115#include <inttypes.h>
116#include <math.h>
117#include <sqlite3.h>
118#include <stdlib.h>
119#include <string.h>
120#include <strings.h>
121#ifndef _WIN32
122#include <glob.h>
123#endif
124#include <glib/gstdio.h>
125#include "common/utility.h"
126
128{
129 int32_t imgid;
130 gboolean before;
131 gboolean after;
133
140
147
154
155/* An image removed from the library holds nothing in memory worth keeping: the whole state
156 * is the rows database/removed_image_repository.c staged, and this is the ticket to them. */
157typedef struct dt_undo_remove_t
158{
159 int32_t imgid;
162
163static void _pop_undo_execute(const int32_t imgid, const gboolean before, const gboolean after);
164static int32_t _image_duplicate_with_version(const int32_t imgid, const int32_t newversion, const gboolean undo,
165 const gboolean reload);
166static void _image_remove(const int32_t imgid, const gboolean undo);
167static 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);
168
169static void _copy_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path);
170static void _move_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path, const gboolean overwrite);
171
172// NULL terminated list of supported non-RAW extensions
173// const char *dt_non_raw_extensions[]
174// = { ".jpeg", ".jpg", ".pfm", ".hdr", ".exr", ".pxn", ".tif", ".tiff", ".png",
175// ".j2c", ".j2k", ".jp2", ".jpc", ".gif", ".jpc", ".jp2", ".bmp", ".dcm",
176// ".jng", ".miff", ".mng", ".pbm", ".pnm", ".ppm", ".pgm", NULL };
177gboolean dt_image_is_raw(const dt_image_t *img)
178{
179 return (img->flags & DT_IMAGE_RAW) != 0;
180}
181
182// LDR / HDR are flag-only predicates now: no filename-extension sniffing. The flags are set by the
183// decoding codec from the real file content (the buffer datatype is not a dynamic-range signal:
184// every raster decodes into a TYPE_FLOAT working buffer regardless of LDR/HDR), so these report what
185// was really loaded, not what the file name suggested. They are kept as a public API so callers
186// don't open-code the bitmask test.
187gboolean dt_image_is_ldr(const dt_image_t *img)
188{
189 return (img->flags & DT_IMAGE_LDR) != 0;
190}
191
192gboolean dt_image_is_hdr(const dt_image_t *img)
193{
194 return (img->flags & DT_IMAGE_HDR) != 0;
195}
196
198{
200}
201
202static void _image_set_monochrome_flag(const int32_t imgid, gboolean monochrome, gboolean undo_on)
203{
204 const dt_image_t *rimg = dt_image_cache_get(imgid, 'r');
205 if(IS_NULL_PTR(rimg))
206 {
207 fprintf(stderr, "[_image_set_monochrome_flag] could not load image %i, its monochrome flag is unchanged\n",
208 imgid);
209 return;
210 }
211 const int mask_bw = dt_image_monochrome_flags(rimg);
213
214 // wanting it to be color and found preview
215 const gboolean to_color = !monochrome && (mask_bw & DT_IMAGE_MONOCHROME_PREVIEW);
216 // wanting monochrome and found color or just preview without workflow activation
217 const gboolean to_monochrome = monochrome && (mask_bw == 0 || mask_bw == DT_IMAGE_MONOCHROME_PREVIEW);
218 if(!to_color && !to_monochrome) return;
219
220 dt_image_t *img = dt_image_cache_get(imgid, 'w');
221 if(IS_NULL_PTR(img))
222 {
223 fprintf(stderr, "[_image_set_monochrome_flag] could not load image %i, its monochrome flag is unchanged\n",
224 imgid);
225 return;
226 }
227 if(to_monochrome)
229 else
231 const int mask = dt_image_monochrome_flags(img);
233
234 if(undo_on)
235 {
237 undomono->imgid = imgid;
238 undomono->before = mask_bw;
239 undomono->after = mask;
240 dt_undo_record(dt_undo_get_global(), NULL, DT_UNDO_FLAGS, undomono, _pop_undo, g_free);
241 }
242}
243
244void dt_image_set_monochrome_flag(const int32_t imgid, gboolean monochrome)
245{
246 _image_set_monochrome_flag(imgid, monochrome, TRUE);
247}
248
249static void _pop_undo_execute(const int32_t imgid, const gboolean before, const gboolean after)
250{
251 _image_set_monochrome_flag(imgid, after, FALSE);
252}
253
254static gboolean _image_matrix_has_data(const float *matrix, const int count)
255{
256 float sum = 0.0f;
257 for(int i = 0; i < count; i++)
258 {
259 if(!isfinite(matrix[i])) return FALSE;
260 sum += fabsf(matrix[i]);
261 }
262 return sum > 0.0f;
263}
264
266{
267 // Whether a camera input matrix (and the white balance / color calibration it feeds) applies
268 // depends only on the colorimetry axis, never on the mosaic axis: an already-demosaiced raw
269 // (sRAW / linear DNG, e.g. DxO PureRAW) still carries raw colorimetry and an embedded matrix
270 // and must get matrix correction. A previous `S_RAW && dsc.filters == 0 -> FALSE` short-circuit
271 // wrongly excluded exactly those files, leaving white balance and color calibration disabled
272 // (green renders, issue #729).
273 if(!(img->flags & (DT_IMAGE_RAW | DT_IMAGE_S_RAW))) return FALSE;
274 if(img->flags & DT_IMAGE_MONOCHROME) return FALSE;
275
276 const gboolean has_d65 = _image_matrix_has_data(img->d65_color_matrix, 9);
277 const gboolean has_adobe = _image_matrix_has_data(&img->adobe_XYZ_to_CAM[0][0], 9);
278
279 return (has_d65 || has_adobe) ? TRUE : FALSE;
280}
281
287
292
293/* ------------------------------------------------------------------------------------------
294 * Canonical image-type API. See image.h and src/doc/image-type-detection.md.
295 * Each predicate tests exactly one independent flag fact; no filename sniffing here.
296 * ------------------------------------------------------------------------------------------ */
297
299{
300 return (img->flags & DT_IMAGE_BUFFER_RESOLVED) == 0;
301}
302
303gboolean dt_image_is_sraw(const dt_image_t *img)
304{
305 return (img->flags & DT_IMAGE_S_RAW) != 0;
306}
307
309{
310 return (img->flags & DT_IMAGE_MOSAIC) != 0;
311}
312
314{
315 // both mosaiced raw and already-demosaiced sRAW/linear-DNG carry raw colorimetry
316 return (img->flags & (DT_IMAGE_RAW | DT_IMAGE_S_RAW)) != 0;
317}
318
320{
321 const gboolean resolved = (img->flags & DT_IMAGE_BUFFER_RESOLVED) != 0;
322 const gboolean raw_colorimetry = (img->flags & (DT_IMAGE_RAW | DT_IMAGE_S_RAW)) != 0;
323
324 // Raw colorimetry takes precedence over the LDR/HDR bit-depth flags: a float mosaiced raw
325 // is flagged both RAW and HDR but must still be treated as a mosaiced raw, never as RGB HDR.
326 if(raw_colorimetry)
327 {
328 // Once decoded, the mosaic bit is authoritative and tells a mosaiced raw apart from an
329 // already-demosaiced raw (sRAW / linear DNG).
330 if(resolved)
332
333 // Provisional (not decoded yet, or a database predating DT_IMAGE_BUFFER_RESOLVED):
334 // DT_IMAGE_S_RAW is only ever set by a codec on a real decode (the extension detector
335 // never sets it), so it reliably means an already-demosaiced raw even without the resolved
336 // bit. Otherwise assume the common case of a mosaiced sensor raw; dt_image_buffer_resolve_flags()
337 // corrects the guess on first decode.
338 if((img->flags & DT_IMAGE_S_RAW) && !(img->flags & DT_IMAGE_RAW))
341 }
342
345
347}
348
350{
352}
353
355{
356 switch(klass)
357 {
358 case DT_IMAGE_PIPE_MOSAIC_RAW: return "mosaic-raw";
359 case DT_IMAGE_PIPE_LINEAR_RAW: return "linear-raw";
360 case DT_IMAGE_PIPE_RGB_LDR: return "rgb-ldr";
361 case DT_IMAGE_PIPE_RGB_HDR: return "rgb-hdr";
363 default: return "unknown";
364 }
365}
366
368{
369 if(IS_NULL_PTR(img)) return;
370
371 // The decoded buffer descriptor is the authoritative source for the mosaic axis.
372 if(img->dsc.filters != 0u)
373 img->flags |= DT_IMAGE_MOSAIC;
374 else
375 img->flags &= ~DT_IMAGE_MOSAIC;
376
377 // The LDR/HDR axis is NOT derivable from the buffer datatype: every raster codec decodes into a
378 // TYPE_FLOAT working buffer in RAM regardless of the file's real dynamic range (sRGB JPEG/PNG/WebP
379 // all land as float just like Radiance/PFM/EXR). The decoding codec is the authoritative source
380 // and has already set DT_IMAGE_LDR / DT_IMAGE_HDR from the actual file content, so we must not
381 // override it here from the datatype — doing so re-flagged every display-referred raster as HDR.
382 //
383 // The only normalization we still own is the raw-sensor case: mosaiced / sRAW integer data is
384 // scene-linear sensor data, neither display LDR nor HDR. A float raw (e.g. HDRMerge, legacy float
385 // DNG) is left flagged HDR exactly as the raw codec set it.
387 img->flags &= ~(DT_IMAGE_LDR | DT_IMAGE_HDR);
388
390}
391
393{
394 if(IS_NULL_PTR(img)) return;
395 // Never clobber a descriptor that a codec has already produced.
396 if(img->flags & DT_IMAGE_BUFFER_RESOLVED) return;
397
398 switch(dt_image_pipe_class(img))
399 {
401 img->dsc.channels = 4; img->dsc.datatype = TYPE_UINT8; img->dsc.filters = 0u; img->dsc.cst = IOP_CS_RGB;
402 break;
404 img->dsc.channels = 4; img->dsc.datatype = TYPE_FLOAT; img->dsc.filters = 0u; img->dsc.cst = IOP_CS_RGB;
405 break;
407 // filters is unknown until decode; leave 0 as a placeholder (the class is carried by
408 // flags, not by this provisional filters value).
409 img->dsc.channels = 1; img->dsc.datatype = TYPE_UINT16; img->dsc.filters = 0u; img->dsc.cst = IOP_CS_RAW;
410 break;
412 img->dsc.channels = 4; img->dsc.datatype = TYPE_FLOAT; img->dsc.filters = 0u; img->dsc.cst = IOP_CS_RAW;
413 break;
415 default:
416 return; // nothing reliable to seed
417 }
419}
420
422{
423 switch(type)
424 {
425 case TYPE_FLOAT:
426 return "float";
427 case TYPE_UINT16:
428 return "uint16";
429 case TYPE_UINT8:
430 return "uint8";
431 case TYPE_UNKNOWN:
432 default:
433 return "unknown";
434 }
435}
436
437static const char *_image_colorspace_to_string(const dt_image_colorspace_t colorspace)
438{
439 switch(colorspace)
440 {
442 return "sRGB";
444 return "AdobeRGB";
446 default:
447 return "none";
448 }
449}
450
451void dt_image_print_debug_info(const dt_image_t *img, const char *context)
452{
453 if(IS_NULL_PTR(img)) return;
454
455 const char *ctx = context ? context : "image";
456 const dt_iop_buffer_dsc_t *dsc = &img->dsc;
457 const gboolean is_raw = dt_image_is_raw(img);
458 const gboolean is_ldr = dt_image_is_ldr(img);
459 const gboolean is_hdr = dt_image_is_hdr(img);
460 const gboolean is_mono = dt_image_is_monochrome(img);
461 const gboolean mono_workflow = dt_image_use_monochrome_workflow(img);
462 const int mono_flags = dt_image_monochrome_flags(img);
463 const gboolean mosaic = dsc->filters != 0u;
464 const gboolean xtrans = dsc->filters == 9u;
465 const gboolean bayer = mosaic && !xtrans;
466 const size_t bpp = dsc->bpp;
467 int bit_depth = 0;
468
469 switch(dsc->datatype)
470 {
471 case TYPE_FLOAT:
472 bit_depth = 32;
473 break;
474 case TYPE_UINT16:
475 bit_depth = 16;
476 break;
477 case TYPE_UINT8:
478 bit_depth = 8;
479 break;
480 case TYPE_UNKNOWN:
481 default:
482 if(dsc->channels != 0)
483 bit_depth = (int)(bpp * 8 / dsc->channels);
484 break;
485 }
486
487 const unsigned int flags = (unsigned int)img->flags;
488
490 "[image debug] %s id=%d filename='%s' fullpath='%s'\n",
491 ctx, img->id, img->filename, img->fullpath);
493 "[image debug] %s size=%dx%d crop=%dx%d+%d+%d orientation=%d psize=%dx%d pixel_aspect=%.6f\n",
494 ctx, img->width, img->height, img->crop_width, img->crop_height, img->crop_x, img->crop_y,
495 img->orientation, img->p_width, img->p_height, img->pixel_aspect_ratio);
497 "[image debug] %s flags=0x%08x raw=%d non_raw=%d ldr=%d hdr=%d sraw=%d 4bayer=%d mosaic=%d xtrans=%d "
498 "bayer=%d mono=%d mono_flags=0x%x mono_workflow=%d mono_preview=%d mono_bayer=%d bw=%d bw_flow=%d "
499 "local_copy=%d has_txt=%d has_wav=%d addl_dng=%d auto_presets=%d no_legacy_presets=%d rejected=%d remove=%d "
500 "has_localcopy=%d has_audio=%d is_hdr_field=%d\n",
501 ctx, flags, is_raw, !is_raw, is_ldr, is_hdr,
502 (flags & DT_IMAGE_S_RAW) != 0, (flags & DT_IMAGE_4BAYER) != 0, mosaic, xtrans, bayer, is_mono,
503 mono_flags, mono_workflow, (flags & DT_IMAGE_MONOCHROME_PREVIEW) != 0,
504 (flags & DT_IMAGE_MONOCHROME_BAYER) != 0, img->is_bw, img->is_bw_flow,
508 img->has_localcopy, img->has_audio, img->is_hdr);
510 "[image debug] %s buf: channels=%u datatype=%s bit_depth=%d bpp=%" G_GSIZE_FORMAT " filters=%u cst=%d colorspace=%s "
511 "processed_max=[%.4f %.4f %.4f %.4f]\n",
512 ctx, dsc->channels, _image_buf_type_to_string(dsc->datatype), bit_depth, bpp, dsc->filters, dsc->cst,
514 dsc->processed_maximum[2], dsc->processed_maximum[3]);
516 "[image debug] %s colorspace=enum:%s d65_color_matrix=[%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f]\n",
518 img->d65_color_matrix[2], img->d65_color_matrix[3], img->d65_color_matrix[4], img->d65_color_matrix[5],
519 img->d65_color_matrix[6], img->d65_color_matrix[7], img->d65_color_matrix[8]);
521 "[image debug] %s raw: black=%u separate=[%u %u %u %u] white=%u rawprepare=[%u %u]\n",
526 "[image debug] %s class=%s state=%s needs_rawprepare=%d needs_demosaic=%d is_mosaiced=%d is_sraw=%d has_matrix=%d\n",
528 dt_image_pipe_class_is_provisional(img) ? "provisional" : "resolved",
531}
532
533const char *dt_image_film_roll_name(const char *path)
534{
535 const char *folder = path + strlen(path);
536 const int numparts = CLAMPS(dt_conf_get_int("show_folder_levels"), 1, 5);
537 int count = 0;
538 while(folder > path)
539 {
540
541#ifdef _WIN32
542 // in Windows, both \ and / can be folder separator
543 if(*folder == G_DIR_SEPARATOR || *folder == '/')
544#else
545 if(*folder == G_DIR_SEPARATOR)
546#endif
547
548 if(++count >= numparts)
549 {
550 ++folder;
551 break;
552 }
553 --folder;
554 }
555 return folder;
556}
557
558void dt_image_film_roll_directory(const dt_image_t *img, char *pathname, size_t pathname_len)
559{
561 if(folder)
562 {
563 g_strlcpy(pathname, folder, pathname_len);
565 }
566 pathname[pathname_len - 1] = '\0';
567}
568
569void dt_image_film_roll(const dt_image_t *img, char *pathname, size_t pathname_len)
570{
571 if(img->film_id < 0)
572 {
573 g_strlcpy(pathname, _("orphaned image"), pathname_len);
574 return;
575 }
576
578 if(folder)
579 {
580 g_strlcpy(pathname, dt_image_film_roll_name(folder), pathname_len);
582 }
583 else
584 {
585 g_strlcpy(pathname, _("orphaned image"), pathname_len);
586 }
587 pathname[pathname_len - 1] = '\0';
588}
589
590// dt_image_get_xmp_mode() is on the hot path: called from many threads (image cache
591// write-release, export/save-xmp jobs...) for every single image. Re-parsing the conf string on
592// every call meant every reader raced every writer on the same key -- a torn read could
593// masquerade as an unrecognized/disabled value and silently skip writing a sidecar. The setting
594// itself changes only when the user acts (preferences dialog) or once at startup, so it is read
595// from conf exactly those two times and cached here the rest of the time -- same lifecycle as
596// dt_mipmap_cache_settings_t in darktable.c: "read at startup, re-read when the user changes it,
597// never anywhere else". dt_image_xmp_mode_refresh_from_conf() does the actual conf read+sanitize
598// and is wired to DT_SIGNAL_PREFERENCES_CHANGE in dt_init().
599static dt_atomic_int _xmp_mode_cache = 1; // matches the confgen default (TRUE) until first refresh
600
602{
604}
605
607{
608 // Write sidecars when the setting is absent, consistently with the default exposed in the preferences.
609 gboolean res = TRUE;
610 char *config = dt_conf_get_string("write_sidecar_files");
611 if(!IS_NULL_PTR(config))
612 {
613 res = FALSE;
614
615 // Darktable > 3.6
616 if(!strcmp(config, "after edit") || !strcmp(config, "on import") || !strcmp(config, "always")) res = TRUE;
617
618 // Darktable < 3.8 and Ansel
619 else if(!strcmp(config, "TRUE"))
620 res = TRUE;
621 }
622
623 // sanitize keys, but only if they actually need it -- this now only ever runs at startup and
624 // on user-initiated preference changes, so there is no concurrent reader/writer race left to
625 // worry about; the guard is just to avoid rewriting an already-canonical value.
626 const char *sanitized = res ? "TRUE" : "FALSE";
627 if(IS_NULL_PTR(config) || strcmp(config, sanitized)) dt_conf_set_string("write_sidecar_files", sanitized);
628
629 dt_free(config);
630
632}
633
634gboolean dt_image_safe_remove(const int32_t imgid)
635{
636 // always safe to remove if we do not have .xmp
637 if(!dt_image_get_xmp_mode()) return TRUE;
638
639 // check whether the original file is accessible
640 char pathname[DT_PATH_MAX] = { 0 };
641 gboolean from_cache = TRUE;
642
643 dt_image_full_path(imgid, pathname, sizeof(pathname), &from_cache, __FUNCTION__);
644
645 if(!from_cache)
646 return TRUE;
647
648 else
649 {
650 // finally check if we have a .xmp for the local copy. If no modification done on the local copy it is safe
651 // to remove.
652 g_strlcat(pathname, ".xmp", sizeof(pathname));
653 return !g_file_test(pathname, G_FILE_TEST_EXISTS);
654 }
655}
656
658 size_t pathname_len, gboolean force_cache)
659{
660 if(IS_NULL_PTR(img) || IS_NULL_PTR(pathname)) return DT_IMAGE_PATH_NONE;
661 pathname[0] = '\0';
662
663 // Start with local copies as file I/O will be better if we have a choice
664 if(img->flags & DT_IMAGE_LOCAL_COPY || force_cache)
665 {
666 if(img->local_copy_path[0] && g_file_test(img->local_copy_path, G_FILE_TEST_EXISTS))
667 {
668 g_strlcpy(pathname, img->local_copy_path, pathname_len);
670 }
671
672 if(img->local_copy_legacy_path[0] && g_file_test(img->local_copy_legacy_path, G_FILE_TEST_EXISTS))
673 {
674 g_strlcpy(pathname, img->local_copy_legacy_path, pathname_len);
676 }
677
678 // Local copy flag might be stale (cache cleared, moved file, etc.). If local copy is missing and
679 // we are not explicitly forcing the cache, fall back to the original image.
680 if(!force_cache && img->fullpath[0] && g_file_test(img->fullpath, G_FILE_TEST_EXISTS))
681 {
682 g_strlcpy(pathname, img->fullpath, pathname_len);
684 }
685 }
686 // Forcing the cache should not consider the original image
687 else
688 {
689 if(img->fullpath[0] && g_file_test(img->fullpath, G_FILE_TEST_EXISTS))
690 {
691 g_strlcpy(pathname, img->fullpath, pathname_len);
693 }
694 }
695
696 // In case the local copy flag was not set properly, but the original file failed
697 // last-chance attempt at getting a possibly forgotten local copy to have some input
698 if(img->local_copy_path[0] && g_file_test(img->local_copy_path, G_FILE_TEST_EXISTS))
699 {
700 g_strlcpy(pathname, img->local_copy_path, pathname_len);
702 }
703
704 return DT_IMAGE_PATH_NONE;
705}
706
716void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
717{
718 if(imgid < 0) return;
720 const gboolean prefer_cache = (from_cache && *from_cache);
721
722 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
723 if(img)
724 {
725 // Preserve legacy semantics: `from_cache = TRUE` means "try local copy first, then fall back to original",
726 // not "only local copy".
727 if(prefer_cache)
728 {
729 source = dt_image_choose_input_path(img, pathname, pathname_len, TRUE);
730 if(source == DT_IMAGE_PATH_NONE)
731 source = dt_image_choose_input_path(img, pathname, pathname_len, FALSE);
732 }
733 else
734 {
735 source = dt_image_choose_input_path(img, pathname, pathname_len, FALSE);
736 }
738 }
739
740 if(from_cache)
741 *from_cache = (source == DT_IMAGE_PATH_LOCAL_COPY
743
744}
745
746void dt_image_local_copy_paths_from_fullpath(const char *fullpath, int32_t imgid, char *local_copy_path,
747 size_t local_copy_len, char *local_copy_legacy_path,
748 size_t local_copy_legacy_len)
749{
750 if(local_copy_path) local_copy_path[0] = '\0';
751 if(local_copy_legacy_path) local_copy_legacy_path[0] = '\0';
752 if(IS_NULL_PTR(fullpath) || !*fullpath || imgid <= 0 || IS_NULL_PTR(local_copy_path) || IS_NULL_PTR(local_copy_legacy_path)) return;
753
754 char *md5_filename = g_compute_checksum_for_string(G_CHECKSUM_MD5, fullpath, strlen(fullpath));
755 if(IS_NULL_PTR(md5_filename)) return;
756
757 char cachedir[DT_PATH_MAX] = { 0 };
758 dt_loc_get_user_cache_dir(cachedir, sizeof(cachedir));
759
760 const char *c = fullpath + strlen(fullpath);
761 while(*c != '.' && c > fullpath) c--;
762
763 // cache filename old format: <cachedir>/img-<id>-<MD5>.<ext>
764 // for upward compatibility we check for the old name, if found we return it
765 snprintf(local_copy_legacy_path, local_copy_legacy_len, "%s/img-%d-%s%s", cachedir, imgid, md5_filename, c);
766
767 // cache filename format: <cachedir>/img-<MD5>.<ext>
768 snprintf(local_copy_path, local_copy_len, "%s/img-%s%s", cachedir, md5_filename, c);
769
770 dt_free(md5_filename);
771}
772
773void dt_image_path_append_version_no_db(int version, char *pathname, size_t pathname_len)
774{
775 // the "first" instance (version zero) does not get a version suffix
776 if(version > 0)
777 {
778 // add version information:
779 char *filename = g_strdup(pathname);
780
781 char *c = pathname + strlen(pathname);
782 while(*c != '.' && c > pathname) c--;
783 snprintf(c, pathname + pathname_len - c, "_%02d", version);
784 c = pathname + strlen(pathname);
785 char *c2 = filename + strlen(filename);
786 while(*c2 != '.' && c2 > filename) c2--;
787 g_strlcpy(c, c2, pathname + pathname_len - c);
788 dt_free(filename);
789 }
790}
791
792void dt_image_path_append_version(const int32_t imgid, char *pathname, size_t pathname_len)
793{
794 // get duplicate suffix
795 const int version = dt_image_repository_get_version(imgid);
796
797 dt_image_path_append_version_no_db(version, pathname, pathname_len);
798}
799
800void dt_image_print_exif(const dt_image_t *img, char *line, size_t line_len)
801{
802 char *exposure_str = dt_util_format_exposure(img->exif_exposure);
803
804 snprintf(line, line_len, "%s f/%.1f %dmm ISO %d", exposure_str, img->exif_aperture, (int)img->exif_focal_length,
805 (int)img->exif_iso);
806
807 dt_free(exposure_str);
808}
809
811{
812 return (flags & DT_IMAGE_REJECTED)
813 ? -1 // rejected image = -1
814 : (flags & DT_VIEW_RATINGS_MASK); // others = 0 .. 5
815}
816
821
822void dt_image_set_xmp_rating(dt_image_t *img, const int rating)
823{
824 // clean flags stars and rejected
826
827 if(rating == -2) // assuming that value -2 cannot be found
828 {
829 img->flags |= (DT_VIEW_RATINGS_MASK & 0);
830 }
831 else if(rating == -1)
832 {
833 img->flags |= DT_IMAGE_REJECTED;
834 }
835 else
836 {
837 img->flags |= (DT_VIEW_RATINGS_MASK & rating);
838 }
839}
840
841void dt_image_get_location(const int32_t imgid, dt_image_geoloc_t *geoloc)
842{
843 *geoloc = (dt_image_geoloc_t){ NAN, NAN, NAN };
844 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
845 if(IS_NULL_PTR(img)) return;
846 geoloc->longitude = img->geoloc.longitude;
847 geoloc->latitude = img->geoloc.latitude;
848 geoloc->elevation = img->geoloc.elevation;
850}
851
852// 0 on success, 1 when the image cannot be read: nothing is written then
853static int _set_location(const int32_t imgid, const dt_image_geoloc_t *geoloc)
854{
855 /* fetch image from cache */
856 dt_image_t *image = dt_image_cache_get(imgid, 'w');
857 if(IS_NULL_PTR(image))
858 {
859 fprintf(stderr, "[_set_location] could not load image %i, its location is unchanged\n", imgid);
860 return 1;
861 }
862
863 memcpy(&image->geoloc, geoloc, sizeof(dt_image_geoloc_t));
864
866 return 0;
867}
868
869// 1 when the image cannot be read, as _set_location()
870static int _set_datetime(const int32_t imgid, const char *datetime)
871{
872 /* fetch image from cache */
873 dt_image_t *image = dt_image_cache_get(imgid, 'w');
874 if(IS_NULL_PTR(image))
875 {
876 fprintf(stderr, "[_set_datetime] could not load image %i, its date/time is unchanged\n", imgid);
877 return 1;
878 }
879
880 dt_datetime_exif_to_img(image, datetime);
881
883 return 0;
884}
885
886static 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)
887{
888 if(type == DT_UNDO_GEOTAG)
889 {
890 int i = 0;
891
892 for(GList *list = (GList *)data; list; list = g_list_next(list))
893 {
894 dt_undo_geotag_t *undogeotag = (dt_undo_geotag_t *)list->data;
895 const dt_image_geoloc_t *geoloc = (action == DT_ACTION_UNDO) ? &undogeotag->before : &undogeotag->after;
896
897 if(_set_location(undogeotag->imgid, geoloc)) continue;
898
899 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undogeotag->imgid));
900 i++;
901 }
902 if(i > 1) dt_control_log((action == DT_ACTION_UNDO)
903 ? _("geo-location undone for %d images")
904 : _("geo-location re-applied to %d images"), i);
905 // the handler copies the list; ours stays ours (the raise it replaces took ownership,
906 // which is why this site used to g_list_copy)
908 }
909 else if(type == DT_UNDO_DATETIME)
910 {
911 int i = 0;
912
913 for(GList *list = (GList *)data; list; list = g_list_next(list))
914 {
915 dt_undo_datetime_t *undodatetime = (dt_undo_datetime_t *)list->data;
916
917 if(_set_datetime(undodatetime->imgid, (action == DT_ACTION_UNDO)
918 ? undodatetime->before : undodatetime->after))
919 continue;
920
921 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undodatetime->imgid));
922 i++;
923 }
924 if(i > 1) dt_control_log((action == DT_ACTION_UNDO)
925 ? _("date/time undone for %d images")
926 : _("date/time re-applied to %d images"), i);
927 }
928 else if(type == DT_UNDO_DUPLICATE)
929 {
931
932 if(action == DT_ACTION_UNDO)
933 {
934 // remove image
936 }
937 else
938 {
939 // restore image, note that we record the new imgid created while
940 // restoring the duplicate.
942 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undo->new_imgid));
943 }
944 }
945 else if(type == DT_UNDO_REMOVE)
946 {
947 const dt_undo_remove_t *undo = (const dt_undo_remove_t *)data;
948
949 /* Undo puts the rows back, redo takes them out again -- and in both directions the
950 * snapshot stays: it is the undo record's, and the record can be popped either way any
951 * number of times. It is dropped only when the record itself is, by
952 * _remove_undo_data_free() below. */
953 GList *one = g_list_prepend(NULL, GINT_TO_POINTER(undo->imgid));
954
955 if(action == DT_ACTION_UNDO)
956 {
958 {
959 /* DT_IMAGE_REMOVE is written to the row BEFORE the removal runs, so it is part of
960 * what was staged -- and every collection query filters that flag out, which would
961 * bring the image back into the database and into no view. The image is not marked
962 * for deletion any more, so the flag goes. */
964
965 /* The removal emptied the image cache and the mipmap cache of this image, and neither
966 * refills itself: the mipmap cache regenerates only on explicit removal, and the image
967 * cache's history_items -- the "altered" flag deciding raw processing over the unedited
968 * embedded JPEG -- would be whatever the entry held before. Without this the image comes
969 * back looking undeveloped, or as a thumbnail that never renders at all. Reloading also
970 * re-reads the flags cleared just above, so no stale cache entry can write DT_IMAGE_REMOVE
971 * back over the restored row. TRUE: this is a lighttable operation, the filmstrip may
972 * refresh with the grid. */
974
975 /* The collection is the last one: nothing about a restored row reaches the grid until
976 * the query is re-run. */
978 DT_COLLECTION_PROP_UNDEF, g_list_copy(one));
979 /* Restoring the last image of a film roll brings the roll back with it, so the
980 * folder list has to be told; the notifier keeps that a layer-1 statement. */
982 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undo->imgid));
983 }
984 else
985 fprintf(stderr, "[dt_image_remove] fails to restore image %" PRId32 "\n", undo->imgid);
986 }
987 else
988 {
989 // not added to *imgs: dt_undo_do_undo() ends by writing the XMP of every image it
990 // names, and this one no longer has a row to write from.
991 _image_remove(undo->imgid, FALSE);
993 DT_COLLECTION_PROP_UNDEF, g_list_copy(one));
995 }
996
997 g_list_free(one);
998 }
999 else if(type == DT_UNDO_FLAGS)
1000 {
1001 dt_undo_monochrome_t *undomono = (dt_undo_monochrome_t *)data;
1002
1003 const gboolean before = (action == DT_ACTION_UNDO) ? undomono->after : undomono->before;
1004 const gboolean after = (action == DT_ACTION_UNDO) ? undomono->before : undomono->after;
1005 _pop_undo_execute(undomono->imgid, before, after);
1006 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undomono->imgid));
1007 }
1008}
1009
1010static void _geotag_undo_data_free(gpointer data)
1011{
1012 GList *l = (GList *)data;
1013 g_list_free_full(l, dt_free_gpointer);
1014 l = NULL;
1015}
1016
1017static void _image_set_location(GList *imgs, const dt_image_geoloc_t *geoloc, GList **undo, const gboolean undo_on)
1018{
1019 for(GList *images = imgs; images; images = g_list_next(images))
1020 {
1021 const int32_t imgid = GPOINTER_TO_INT(images->data);
1022
1023 dt_image_geoloc_t before = { NAN, NAN, NAN };
1024 if(undo_on) dt_image_get_location(imgid, &before);
1025
1026 if(_set_location(imgid, geoloc) || !undo_on) continue;
1027
1028 dt_undo_geotag_t *undogeotag = (dt_undo_geotag_t *)malloc(sizeof(dt_undo_geotag_t));
1029 undogeotag->imgid = imgid;
1030 undogeotag->before = before;
1031 memcpy(&undogeotag->after, geoloc, sizeof(dt_image_geoloc_t));
1032 *undo = g_list_append(*undo, undogeotag);
1033 }
1034}
1035
1036void dt_image_set_locations(const GList *imgs, const dt_image_geoloc_t *geoloc, const gboolean undo_on)
1037{
1038 if(imgs)
1039 {
1040 GList *undo = NULL;
1042
1043 _image_set_location((GList *)imgs, geoloc, &undo, undo_on);
1044
1045 if(undo_on)
1046 {
1049 }
1050 }
1051}
1052
1053void dt_image_set_location(const int32_t imgid, const dt_image_geoloc_t *geoloc, const gboolean undo_on, const gboolean group_on)
1054{
1055 GList *imgs = NULL;
1056 if(imgid == UNKNOWN_IMAGE)
1057 imgs = dt_act_on_get_images();
1058 else
1059 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
1060 if(group_on) dt_grouping_add_grouped_images(&imgs);
1061 dt_image_set_locations(imgs, geoloc, undo_on);
1062 g_list_free(imgs);
1063 imgs = NULL;
1064}
1065
1066static void _image_set_images_locations(const GList *img, const GArray *gloc,
1067 GList **undo, const gboolean undo_on)
1068{
1069 int i = 0;
1070 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
1071 {
1072 const int32_t imgid = GPOINTER_TO_INT(imgs->data);
1073 const dt_image_geoloc_t *geoloc = &g_array_index(gloc, dt_image_geoloc_t, i);
1074 i++;
1075
1076 dt_image_geoloc_t before = { NAN, NAN, NAN };
1077 if(undo_on) dt_image_get_location(imgid, &before);
1078
1079 if(_set_location(imgid, geoloc) || !undo_on) continue;
1080
1081 dt_undo_geotag_t *undogeotag = (dt_undo_geotag_t *)malloc(sizeof(dt_undo_geotag_t));
1082 undogeotag->imgid = imgid;
1083 undogeotag->before = before;
1084 memcpy(&undogeotag->after, geoloc, sizeof(dt_image_geoloc_t));
1085 *undo = g_list_prepend(*undo, undogeotag);
1086 }
1087}
1088
1089void dt_image_set_images_locations(const GList *imgs, const GArray *gloc, const gboolean undo_on)
1090{
1091 if(IS_NULL_PTR(imgs) || IS_NULL_PTR(gloc) || (g_list_length((GList *)imgs) != gloc->len))
1092 return;
1093 GList *undo = NULL;
1095
1096 _image_set_images_locations(imgs, gloc, &undo, undo_on);
1097
1098 if(undo_on)
1099 {
1102 }
1103}
1104
1105void dt_image_history_changed(const int32_t imgid, const gboolean refresh_filmstrip)
1106{
1107 if(imgid <= 0) return;
1108
1109 // Reload the cached image metadata from the DB. The caller has already persisted the new
1110 // history there; this refreshes history_items (the count of history entries) in the shared
1111 // image cache. history_items is the "altered" flag that the thumbnail regeneration uses to
1112 // pick raw processing over the (unedited) embedded JPEG, so a stale count makes edits and
1113 // rotations appear to have no effect on the thumbnail (issues #647, #861).
1114 dt_image_t *image = dt_image_cache_get_reload(imgid, 'w');
1115 if(image)
1117
1118 // Drop the stale rendered thumbnail. The mipmap cache regenerates purely on explicit removal,
1119 // never by comparing history hashes, so this is mandatory after any development change.
1121
1122 // The filmstrip is best-effort: refreshing it spawns an export thread that competes with the
1123 // realtime darkroom main preview. Darkroom write paths pass FALSE; lighttable ops pass TRUE.
1124 // No "is there a GUI?" guard needed: with no handler registered this is a no-op.
1125 dt_thumbnail_notify_image_changed(imgid, refresh_filmstrip);
1126}
1127
1128void dt_image_set_flip(const int32_t imgid, const dt_image_orientation_t orientation)
1129{
1130 // push new orientation to sql via additional history entry:
1131 const int iop_flip_MODVER = 2;
1132 const int num = dt_history_repository_get_next_num(imgid);
1133 dt_history_repository_write_item(imgid, num, "flip", &orientation, sizeof(int32_t), iop_flip_MODVER, 1,
1134 NULL, 0, 0, 0, "");
1135 dt_history_repository_set_end(imgid, num + 1);
1136 dt_control_save_xmp(imgid);
1137
1138 // Refresh the cached metadata and thumbnail. Without this the stale history_items keeps the
1139 // image flagged unedited, so the rotated raw keeps showing its (unrotated) embedded JPEG and
1140 // the rotation appears to do nothing unless "never use embedded JPEG" is forced (issue #647).
1142}
1143
1146{
1147 float top = in[0];
1148 float left = in[1];
1149 float bottom = in[2];
1150 float right = in[3];
1151
1152 /* Mirror, then swap, in the exact order iop/flip.c's distort_transform() applies to points:
1153 * x -> width - x, y -> height - y, then (x, y) -> (y, x). On normalized edge coordinates a
1154 * mirror exchanges the two edges of that axis, and the swap exchanges the two axes. */
1155 if(orientation & ORIENTATION_FLIP_X)
1156 {
1157 const float new_left = 1.0f - right;
1158 right = 1.0f - left;
1159 left = new_left;
1160 }
1161
1162 if(orientation & ORIENTATION_FLIP_Y)
1163 {
1164 const float new_top = 1.0f - bottom;
1165 bottom = 1.0f - top;
1166 top = new_top;
1167 }
1168
1169 if(orientation & ORIENTATION_SWAP_XY)
1170 {
1171 const float swap_top = left;
1172 const float swap_bottom = right;
1173 left = top;
1174 right = bottom;
1175 top = swap_top;
1176 bottom = swap_bottom;
1177 }
1178
1179 out[0] = top;
1180 out[1] = left;
1181 out[2] = bottom;
1182 out[3] = right;
1183}
1184
1186{
1187 box[0] = box[1] = 0.0f;
1188 box[2] = box[3] = 1.0f;
1189
1190 if(IS_NULL_PTR(img) || img->usercrop_status != DT_IMAGE_USERCROP_VALID) return FALSE;
1191
1192 for(int i = 0; i < 4; i++) box[i] = img->usercrop[i];
1193 return TRUE;
1194}
1195
1196gboolean dt_image_resolve_usercrop(const int32_t imgid, dt_boundingbox_t box)
1197{
1198 box[0] = box[1] = 0.0f;
1199 box[2] = box[3] = 1.0f;
1200
1201 if(imgid < 1 || !dt_image_cache_is_ready()) return FALSE;
1202
1203 char filename[DT_PATH_MAX] = { 0 };
1204 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1205 if(IS_NULL_PTR(img)) return FALSE;
1206
1208 {
1209 // Already answered for this image, in this session.
1210 const gboolean valid = dt_image_get_usercrop(img, box);
1212 return valid;
1213 }
1214
1215 /* DT_IMAGE_HAS_ADDITIONAL_DNG_TAGS is persisted and is set at import for every image that has
1216 * a usable DefaultUserCrop (it also covers DNG opcodes, so it is a superset -- which is what we
1217 * want here: no valid framing can hide behind a cleared bit). Images without it can be settled
1218 * without touching the file at all, which keeps this off the hot path for whole libraries. */
1220 {
1221 // Cheap enough to redo on every call -- deliberately not memoized, so that populating a whole
1222 // library's thumbnails does not take one image-cache write lock per image to record a "no".
1224 return FALSE;
1225 }
1226
1227 const gboolean has_file
1228 = (dt_image_choose_input_path(img, filename, sizeof(filename), FALSE) != DT_IMAGE_PATH_NONE);
1230 if(!has_file) return FALSE;
1231
1232 /* Parse into a throwaway image, holding no lock: the crop lives in runtime-only state, so a
1233 * thumbnail worker starting from a database-loaded cache entry has to read the file itself.
1234 * Two threads racing here compute the same answer from the same file, so the duplicated work
1235 * is wasteful at worst -- which is cheaper than serializing file I/O behind a write lock. */
1236 dt_image_t probe;
1237 dt_image_init(&probe);
1238 probe.id = imgid;
1239 dt_exif_read_usercrop(&probe, filename);
1240
1241 dt_image_t *write = dt_image_cache_get(imgid, 'w');
1242 if(!IS_NULL_PTR(write))
1243 {
1244 write->usercrop_status = probe.usercrop_status;
1245 for(int i = 0; i < 4; i++) write->usercrop[i] = probe.usercrop[i];
1247 }
1248
1249 return dt_image_get_usercrop(&probe, box);
1250}
1251
1253 dt_boundingbox_t box)
1254{
1255 if(!dt_image_get_usercrop(img, box)) return FALSE;
1256
1257 dt_image_orient_boundingbox(box, orientation, box);
1258 return TRUE;
1259}
1260
1267{
1268 // find the flip module -- the pointer stays valid until darktable shuts down
1269 static dt_iop_module_so_t *flip = NULL;
1270 if(IS_NULL_PTR(flip))
1271 {
1272 for(const GList *modules = dt_iop_get_modules_so(); modules; modules = g_list_next(modules))
1273 {
1274 dt_iop_module_so_t *module = (dt_iop_module_so_t *)(modules->data);
1275 if(!strcmp(module->op, "flip"))
1276 {
1277 flip = module;
1278 break;
1279 }
1280 }
1281 }
1282
1284
1285 // db lookup flip params
1286 if(flip && flip->have_introspection && flip->get_p)
1287 {
1288 void *params = NULL;
1289 int32_t params_len = 0;
1290 if(dt_history_repository_get_last_enabled_params(imgid, "flip", &params, &params_len))
1291 {
1292 // use introspection to get the orientation from the binary params blob
1293 orientation = *((dt_image_orientation_t *)flip->get_p(params, "orientation"));
1294 dt_free(params);
1295 }
1296 }
1297
1298 return orientation;
1299}
1300
1302{
1303 if(IS_NULL_PTR(img)) return ORIENTATION_NONE;
1304
1306 return (orientation != ORIENTATION_NULL) ? orientation : dt_image_orientation(img);
1307}
1308
1310{
1312
1313 if(orientation == ORIENTATION_NULL)
1314 {
1315 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1316 if(IS_NULL_PTR(img)) return ORIENTATION_NONE;
1317 orientation = dt_image_orientation(img);
1319 }
1320
1321 return orientation;
1322}
1323
1333{
1334 if(orientation & ORIENTATION_SWAP_XY)
1335 return orientation ^ ORIENTATION_FLIP_Y;
1336 else
1337 return orientation ^ ORIENTATION_FLIP_X;
1338}
1339
1342{
1343 if(orientation & ORIENTATION_SWAP_XY)
1344 return orientation ^ ORIENTATION_FLIP_X;
1345 else
1346 return orientation ^ ORIENTATION_FLIP_Y;
1347}
1348
1349void dt_image_flip(const int32_t imgid, const dt_image_transform_t transform)
1350{
1351 // this is light table only:
1353 if(dt_dev_get_global()->image_storage.id == imgid && cv->view((dt_view_t *)cv) == DT_VIEW_DARKROOM) return;
1354
1356 hist->imgid = imgid;
1358
1360
1361 switch(transform)
1362 {
1364 // mirror horizontally, then transpose
1365 orientation = _orientation_mirror_horizontally(orientation) ^ ORIENTATION_SWAP_XY;
1366 break;
1367
1369 // mirror vertically, then transpose
1370 orientation = _orientation_mirror_vertically(orientation) ^ ORIENTATION_SWAP_XY;
1371 break;
1372
1374 orientation = _orientation_mirror_horizontally(orientation);
1375 break;
1376
1378 orientation = _orientation_mirror_vertically(orientation);
1379 break;
1380
1382 // NULL, not NONE: hand the image back to whatever its EXIF says, rather than declaring it
1383 // upright. The previous code computed a quarter turn first and then threw it away.
1384 orientation = ORIENTATION_NULL;
1385 break;
1386 }
1387
1388 // dt_image_set_flip() writes the new orientation history entry and notifies the caches/GUI
1389 // (mipmap invalidation + thumbnail refresh) via dt_image_history_changed().
1390 dt_image_set_flip(imgid, orientation);
1391
1395}
1396
1397int32_t dt_image_duplicate(const int32_t imgid)
1398{
1399 return dt_image_duplicate_with_version(imgid, -1);
1400}
1401
1402
1403
1404static int32_t _image_duplicate_with_version(const int32_t imgid, const int32_t newversion, const gboolean undo,
1405 const gboolean reload)
1406{
1407 const int32_t newid = dt_image_repository_duplicate(imgid, newversion);
1408
1409 if(newid != -1)
1410 {
1411 if(undo)
1412 {
1413 dt_undo_duplicate_t *dupundo = (dt_undo_duplicate_t *)malloc(sizeof(dt_undo_duplicate_t));
1414 dupundo->orig_imgid = imgid;
1415 dupundo->version = newversion;
1416 dupundo->new_imgid = newid;
1418 }
1419
1420 // make sure that the duplicate doesn't have some magic darktable| tags
1421 if(dt_tag_detach_by_string("darktable|changed", newid, FALSE, FALSE)
1422 || dt_tag_detach_by_string("darktable|exported", newid, FALSE, FALSE))
1424
1425 // the duplicate's row is a copy of the original's, group_id included, so an original that
1426 // cannot be read leaves it in that group
1427 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1428 if(!IS_NULL_PTR(img))
1429 {
1430 const int grpid = img->group_id;
1432 dt_grouping_add_to_group(grpid, newid);
1433 }
1434
1435 // Reloading here exposes newid to the lighttable grid: it becomes eligible for thumbnail
1436 // generation immediately, against whatever history it has *right now*. Callers still about
1437 // to write history onto it (duplicate-with-history, style-on-duplicate) must skip this and
1438 // reload themselves once that is done -- see dt_image_duplicate_no_reload().
1439 if(reload)
1441 }
1442 return newid;
1443}
1444
1445int32_t dt_image_duplicate_with_version(const int32_t imgid, const int32_t newversion)
1446{
1447 return _image_duplicate_with_version(imgid, newversion, TRUE, TRUE);
1448}
1449
1450int32_t dt_image_duplicate_no_reload(const int32_t imgid)
1451{
1452 return _image_duplicate_with_version(imgid, -1, TRUE, FALSE);
1453}
1454
1455static void _remove_undo_data_free(gpointer data)
1456{
1457 dt_undo_remove_t *undo = (dt_undo_remove_t *)data;
1458
1459 /* The record is gone, so the removal it could have undone is now permanent. Nothing to
1460 * drop once the connection is closed, though: the staging tables are `memory.` ones and
1461 * went with it.
1462 *
1463 * No reachable path takes that branch today, and the check stays anyway. dt_undo_cleanup()
1464 * does run after dt_database_close() at shutdown, but it finds an empty list: the GUI's
1465 * teardown calls dt_ctl_switch_mode_to(""), and switching to no view begins by clearing
1466 * DT_UNDO_ALL -- while the database is still open. Without a GUI the order reverses, but
1467 * then nothing can have recorded a removal either, both callers of dt_control_remove_images()
1468 * being GUI ones. So this guards an ordering that nothing enforces, for the cost of one
1469 * call: dropping it would cost an abort on quit in a debug build (the assert inside
1470 * DT_DEBUG_SQLITE3_PREPARE_V2) the day either half of that changes. */
1473
1474 dt_free(undo);
1475}
1476
1477static void _image_remove(const int32_t imgid, const gboolean undo)
1478{
1479 // if a local copy exists, remove it
1480
1481 if(dt_image_local_copy_reset(imgid)) return;
1482
1483 /* Snapshot before anything below runs, dt_grouping_remove_from_group() included: that call
1484 * rewrites the group_id of the images STAYING behind, and putting that back is part of
1485 * undoing this removal. A snapshot that cannot be taken is not recorded -- removing the
1486 * image anyway is better than an undo entry that restores nothing. */
1487 if(undo)
1488 {
1489 dt_undo_remove_t *rm = (dt_undo_remove_t *)g_malloc0(sizeof(dt_undo_remove_t));
1490 rm->imgid = imgid;
1492
1493 if(rm->snap_id >= 0)
1495 else
1496 {
1497 fprintf(stderr, "[dt_image_remove] fails to snapshot image %" PRId32 ", removing it without undo\n", imgid);
1498 dt_free(rm);
1499 }
1500 }
1501
1502 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
1504
1505 // make sure we remove from the cache first, or else the cache will look for imgid in sql
1506 dt_image_cache_remove(imgid);
1507
1510
1511 /* Every buffer, not just the thumbnails: the decoded raw input survives dt_mipmap_cache_remove()
1512 * by design, and an image that has left the library has no business keeping one. It is what the
1513 * pipeline slices through basebuffer when the image comes back on Ctrl+Z, and a stale entry
1514 * there renders as a husk that no later pass replaces. */
1516}
1517
1518void dt_image_remove(const int32_t imgid)
1519{
1520 _image_remove(imgid, FALSE);
1521}
1522
1523void dt_image_remove_undoable(const int32_t imgid)
1524{
1525 _image_remove(imgid, TRUE);
1526}
1527
1528uint32_t dt_image_altered(const int32_t imgid)
1529{
1531}
1532
1533#ifndef _WIN32
1534static int _valid_glob_match(const char *const name, size_t offset)
1535{
1536 // verify that the name matched by glob() is a valid sidecar name by checking whether we have an underscore
1537 // followed by a sequence of digits followed by a period at the given offset in the name
1538 if(strlen(name) < offset || name[offset] != '_')
1539 return FALSE;
1540 size_t i;
1541 for(i = offset+1; name[i] && name[i] != '.'; i++)
1542 {
1543 if(!isdigit(name[i]))
1544 return FALSE;
1545 }
1546 return name[i] == '.';
1547}
1548#endif /* !_WIN32 */
1549
1550GList* dt_image_find_xmps(const char* filename)
1551{
1552 // find all duplicates of an image by looking for all possible sidecars for the file: file.ext.xmp, file_NN.ext.xmp,
1553 // file_NNN.ext.xmp, and file_NNNN.ext.xmp
1554 // because a glob() needs to scan the entire directory, we minimize work for large directories by doing a single
1555 // glob which might generate false matches (if the image name contains an underscore followed by a digit) and
1556 // filter out the false positives afterward
1557#ifndef _WIN32
1558 // start by locating the extension, which we'll be referencing multiple times
1559 const size_t fn_len = strlen(filename);
1560 const char* ext = strrchr(filename,'.'); // find last dot
1561 if(IS_NULL_PTR(ext)) ext = filename;
1562 const size_t ext_offset = ext - filename;
1563
1564 gchar pattern[DT_PATH_MAX] = { 0 };
1565 GList* files = NULL;
1566
1567 // check for file.ext.xmp
1568 static const char xmp[] = ".xmp";
1569 const size_t xmp_len = strlen(xmp);
1570 // concatenate filename and sidecar extension
1571 g_strlcpy(pattern, filename, sizeof(pattern));
1572 g_strlcpy(pattern + fn_len, xmp, sizeof(pattern) - fn_len);
1573 if(dt_util_test_image_file(pattern))
1574 {
1575 // the default sidecar exists, is readable and is a regular file with lenght > 0, so add it to the list
1576 files = g_list_prepend(files, g_strdup(pattern));
1577 }
1578
1579 // now collect all file_N*N.ext.xmp matches
1580 static const char glob_pattern[] = "_[0-9]*[0-9]";
1581 const size_t gp_len = strlen(glob_pattern);
1582 if(fn_len + gp_len + xmp_len < sizeof(pattern)) // enough space to build pattern?
1583 {
1584 // add GLOB.ext.xmp to the root of the basename
1585 g_strlcpy(pattern + ext_offset, glob_pattern, sizeof(pattern) - fn_len);
1586 g_strlcpy(pattern + ext_offset + gp_len, ext, sizeof(pattern) - ext_offset - gp_len);
1587 g_strlcpy(pattern + fn_len + gp_len, xmp, sizeof(pattern) - fn_len - gp_len);
1588 glob_t globbuf;
1589 if(!glob(pattern, 0, NULL, &globbuf))
1590 {
1591 // for each match of the pattern
1592 for(size_t i = 0; i < globbuf.gl_pathc; i++)
1593 {
1594 if(_valid_glob_match(globbuf.gl_pathv[i], ext_offset))
1595 {
1596 // it's not a false positive, so add it to the list of sidecars
1597 files = g_list_prepend(files, g_strdup(globbuf.gl_pathv[i]));
1598 }
1599 }
1600 globfree(&globbuf);
1601 }
1602 }
1603 // we built the list in reverse order for speed, so un-reverse it
1604 return g_list_reverse(files);
1605
1606#else
1607 return win_image_find_duplicates(filename);
1608#endif
1609}
1610
1611// Search for duplicate's sidecar files and import them if found and not in DB yet
1612int dt_image_read_duplicates(const uint32_t id, const char *filename, const gboolean clear_selection)
1613{
1614 int count_xmps_processed = 0;
1615 gchar pattern[DT_PATH_MAX] = { 0 };
1616
1617 GList *files = dt_image_find_xmps(filename);
1618
1619 // we store the xmp filename without version part in pattern to speed up string comparison later
1620 g_snprintf(pattern, sizeof(pattern), "%s.xmp", filename);
1621
1622 for(GList *file_iter = files; file_iter; file_iter = g_list_next(file_iter))
1623 {
1624 gchar *xmpfilename = file_iter->data;
1625 int version = -1;
1626
1627 // we need to get the version number of the sidecar filename
1628 if(!strncmp(xmpfilename, pattern, sizeof(pattern)))
1629 {
1630 // this is an xmp file without version number which corresponds to version 0
1631 version = 0;
1632 }
1633 else
1634 {
1635 // we need to derive the version number from the filename
1636
1637 gchar *c3 = xmpfilename + strlen(xmpfilename)
1638 - 5; // skip over .xmp extension; position c3 at character before the '.'
1639 while(*c3 != '.' && c3 > xmpfilename)
1640 c3--; // skip over filename extension; position c3 is at character '.'
1641 gchar *c4 = c3;
1642 while(*c4 != '_' && c4 > xmpfilename) c4--; // move to beginning of version number
1643 c4++;
1644
1645 gchar *idfield = g_strndup(c4, c3 - c4);
1646
1647 version = atoi(idfield);
1648 dt_free(idfield);
1649 }
1650
1651 int newid = id;
1652 int grpid = -1;
1653
1654 if(count_xmps_processed == 0)
1655 {
1656 // this is the first xmp processed, just update the passed-in id
1658 }
1659 else
1660 {
1661 // create a new duplicate based on the passed-in id. Note that we do not call
1662 // dt_image_duplicate_with_version() as this version also set the group which
1663 // is using DT_IMAGE_CACHE_SAFE and so will write the .XMP. But we must avoid
1664 // this has the xmp for the duplicate is read just below.
1665 newid = dt_image_repository_duplicate(id, version);
1666 const dt_image_t *img = dt_image_cache_get(id, 'r');
1667 if(!IS_NULL_PTR(img))
1668 {
1669 grpid = img->group_id;
1671 }
1672 }
1673 // make sure newid is not selected
1674 if(clear_selection) dt_selection_clear(dt_selection_get_global());
1675
1676 // dt_image_repository_duplicate() above returns -1 when it cannot create the duplicate,
1677 // and dt_image_cache_get() returns NULL for an id it cannot load. Both were dereferenced
1678 // unchecked, which is Sentry 140350855: SIGSEGV on `img->version = version` while
1679 // importing a film roll whose sidecars name versions the duplicate could not be created
1680 // for. Skipping this sidecar leaves the rest of the import to proceed.
1681 dt_image_t *img = (newid > 0) ? dt_image_cache_get(newid, 'w') : NULL;
1682 if(IS_NULL_PTR(img))
1683 {
1684 fprintf(stderr, "[dt_image_read_duplicates] could not load image %i for sidecar '%s', skipping it\n",
1685 newid, xmpfilename);
1686 continue;
1687 }
1688 (void)dt_exif_xmp_read(img, xmpfilename, 0);
1689 img->version = version;
1691
1692 if(grpid != -1)
1693 {
1694 // now it is safe to set the duplicate group-id
1695 dt_grouping_add_to_group(grpid, newid);
1697 }
1698
1699 count_xmps_processed++;
1700 }
1701
1702 g_list_free_full(files, dt_free_gpointer);
1703 files = NULL;
1704 return count_xmps_processed;
1705}
1706
1707static int32_t _image_import_internal(const int32_t film_id, const char *filename,
1708 gboolean lua_locking, gboolean raise_signals)
1709{
1710 char *normalized_filename = dt_util_normalize_path(filename);
1711 if(!normalized_filename || !dt_util_test_image_file(normalized_filename))
1712 {
1713 dt_free(normalized_filename);
1714 return 0;
1715 }
1716 const char *extension = dt_util_path_get_extension(normalized_filename);
1718 {
1719 dt_free(normalized_filename);
1720 return 0;
1721 }
1722 if(!strcasecmp(extension, "dt") || !strcasecmp(extension, "dttags") || !strcasecmp(extension, "xmp"))
1723 {
1724 dt_free(normalized_filename);
1725 return 0;
1726 }
1727 char *ext = g_ascii_strdown(extension, -1);
1729 {
1730 dt_free(normalized_filename);
1731 dt_free(ext);
1732 return 0;
1733 }
1734 // select from images; if found => return
1735 gchar *imgfname = g_path_get_basename(normalized_filename);
1736 int32_t id = dt_image_get_id(film_id, imgfname);
1737 if(id > UNKNOWN_IMAGE)
1738 {
1739 dt_control_log(_("Image %s is already in library and will not be re-imported.\n"), imgfname);
1740 dt_free(imgfname);
1741 dt_free(ext);
1742 dt_free(normalized_filename);
1743 return id;
1744 }
1745
1746 // also need to set the no-legacy bit, to make sure we get the right presets (new ones)
1747 uint32_t flags = 0;
1749 // and we set the type of image flag (from extension for now)
1750 const char *image_extension = dt_util_path_get_extension(imgfname);
1751 flags |= dt_image_flags_from_extension(image_extension - 1);
1752 // set the bits in flags that indicate if any of the extra files (.txt, .wav) are present
1753 char *extra_file = dt_image_get_audio_path_from_path(normalized_filename);
1754 if(extra_file)
1755 {
1757 dt_free(extra_file);
1758 }
1759 extra_file = dt_image_get_text_path_from_path(normalized_filename);
1760 if(extra_file)
1761 {
1763 dt_free(extra_file);
1764 }
1765
1767 return 0;
1768
1769 id = dt_image_get_id(film_id, imgfname);
1770
1771 // Try to find out if this should be grouped already.
1772 gchar *basename = g_strndup(imgfname, image_extension - imgfname - 1);
1773 gchar *sql_pattern = g_strconcat(basename, ".%", NULL);
1774 int group_id;
1775 // in case we are not a jpg check if we need to change group representative
1776 if(strcmp(ext, "jpg") != 0 && strcmp(ext, "jpeg") != 0)
1777 {
1778 const int32_t other_group = dt_image_repository_find_group_for_pattern(film_id, sql_pattern, TRUE, id);
1779 // if we have a group already, and its representative can be read
1780 dt_image_t *other_img = (other_group != -1) ? dt_image_cache_get(other_group, 'w') : NULL;
1781 if(!IS_NULL_PTR(other_img))
1782 {
1783 int other_id = other_group;
1784 const char *other_extension = dt_util_path_get_extension(other_img->filename);
1785 gchar *ext_lowercase = g_ascii_strdown(other_extension, -1);
1786 // if the group representative is a jpg, change group representative to this new imported image
1787 if(!strcmp(ext_lowercase, "jpg") || !strcmp(ext_lowercase, "jpeg"))
1788 {
1789 other_img->group_id = id;
1791 GList *members = dt_image_repository_get_group_members(other_id, other_id);
1792 for(GList *l = members; l; l = g_list_next(l))
1793 {
1794 other_id = GPOINTER_TO_INT(l->data);
1795 dt_image_t *group_img = dt_image_cache_get(other_id, 'w');
1796 if(IS_NULL_PTR(group_img)) continue;
1797 group_img->group_id = id;
1799 }
1800 g_list_free(members);
1801 group_id = id;
1802 }
1803 else
1804 {
1806 group_id = other_id;
1807 }
1808 dt_free(ext_lowercase);
1809 }
1810 else
1811 {
1812 group_id = id;
1813 }
1814 }
1815 else
1816 {
1817 const int32_t other_group = dt_image_repository_find_group_for_pattern(film_id, sql_pattern, FALSE, id);
1818 group_id = (other_group != -1) ? other_group : id;
1819 }
1820 dt_image_repository_set_group(id, group_id);
1821
1822 // printf("[image_import] importing `%s' to img id %d\n", imgfname, id);
1823
1824 // lock as shortly as possible:
1825 dt_image_t *img = dt_image_cache_get(id, 'w');
1826 if(IS_NULL_PTR(img))
1827 {
1828 // a row whose Exif was never read would stay in the library and block a new import of the file
1829 fprintf(stderr, "[image_import] could not load image %i for `%s', removing it\n", id, normalized_filename);
1830 if(id > 0) dt_image_remove(id);
1831 dt_free(ext);
1832 dt_free(imgfname);
1833 dt_free(basename);
1834 dt_free(sql_pattern);
1835 dt_free(normalized_filename);
1836 return 0;
1837 }
1838 img->group_id = group_id;
1839
1840 // read dttags and exif for database queries!
1841 (void)dt_exif_read(img, normalized_filename);
1842 char dtfilename[DT_PATH_MAX] = { 0 };
1843 g_strlcpy(dtfilename, normalized_filename, sizeof(dtfilename));
1844 // dt_image_path_append_version(id, dtfilename, sizeof(dtfilename));
1845 g_strlcat(dtfilename, ".xmp", sizeof(dtfilename));
1846
1847 const int res = dt_exif_xmp_read(img, dtfilename, 0);
1848
1849 // write through to db, but not to xmp.
1851
1852 // read all sidecar files
1853 const int nb_xmp = dt_image_read_duplicates(id, normalized_filename, raise_signals);
1854
1855 if((res != 0) && (nb_xmp == 0))
1856 {
1857 const gboolean lr_xmp = dt_lightroom_import(id, NULL, TRUE);
1858 if(lr_xmp) dt_control_save_xmp(id);
1859 }
1860
1861 // add a tag with the file extension
1862 guint tagid = 0;
1863 char tagname[512];
1864 snprintf(tagname, sizeof(tagname), "darktable|format|%s", ext);
1865 dt_free(ext);
1866 dt_tag_new(tagname, &tagid);
1867 dt_tag_attach(tagid, id, FALSE, FALSE);
1868
1869 // make sure that there are no stale thumbnails left
1871
1872 //synch database entries to xmp
1873 if(dt_image_get_xmp_mode()) dt_image_synch_all_xmp(normalized_filename);
1874
1875 dt_free(imgfname);
1876 dt_free(basename);
1877 dt_free(sql_pattern);
1878 dt_free(normalized_filename);
1879
1880 if(raise_signals)
1881 {
1883 // the old raise handed the freshly built list to the signal; the handler copies now,
1884 // so this site frees its own
1885 GList *imgs = g_list_prepend(NULL, GINT_TO_POINTER(id));
1887 g_list_free(imgs);
1888 }
1889
1890 // the following line would look logical with new_tags_set being the return value
1891 // from dt_tag_new above, but this could lead to too rapid signals, being able to lock up the
1892 // keywords side pane when trying to use it, which can lock up the whole dt GUI ..
1893 // if(new_tags_set) DT_DEBUG_CONTROL_SIGNAL_RAISE(dt_control_signal_get_global(),DT_SIGNAL_TAG_CHANGED);
1894 return id;
1895}
1896
1897int32_t dt_image_get_id_full_path(const gchar *filename)
1898{
1899 gchar *dir = g_path_get_dirname(filename);
1900 gchar *file = g_path_get_basename(filename);
1901 const int32_t id = dt_image_repository_find_by_folder_and_filename(dir, file);
1902 dt_free(dir);
1903 dt_free(file);
1904
1905 return id;
1906}
1907
1908int32_t dt_image_get_id(int32_t film_id, const gchar *filename)
1909{
1910 return dt_image_repository_find_by_film_and_filename(film_id, filename);
1911}
1912
1913/* Guess what kind of image a file holds from its extension alone.
1914 *
1915 * Lived in imageio/imageio_core.c, which put a six-line function over three predicates
1916 * from common/image_extensions.h and one enum from this file at layer 6 -- above every
1917 * one of its callers (common/image.c, common/exif.cc, caches/image_cache.c,
1918 * database/database.c, gui/import.c). Nothing in imageio/ or above ever called it. */
1920{
1921 const char *ext = g_str_has_prefix(extension, ".") ? extension + 1 : extension;
1922 if(dt_image_ext_is_ambiguous(ext)) return 0;
1923 if(dt_image_ext_is_raw(ext)) return DT_IMAGE_RAW;
1924 if(dt_image_ext_is_hdr(ext)) return DT_IMAGE_HDR;
1925 if(dt_image_ext_is_ldr(ext)) return DT_IMAGE_LDR;
1926 return 0;
1927}
1928
1929int32_t dt_image_import(const int32_t film_id, const char *filename, gboolean raise_signals)
1930{
1931 return _image_import_internal(film_id, filename, TRUE, raise_signals);
1932}
1933
1934int32_t dt_image_import_lua(const int32_t film_id, const char *filename)
1935{
1936 return _image_import_internal(film_id, filename, FALSE, TRUE);
1937}
1938
1940{
1941 memset(img, 0, sizeof(*img));
1942 img->width = img->height = 0;
1943 img->p_width = img->p_height = 0;
1944 img->crop_x = img->crop_y = img->crop_width = img->crop_height = 0;
1946
1948
1949 img->legacy_flip.legacy = 0;
1950 img->legacy_flip.user_flip = 0;
1951
1952 img->dsc = (dt_iop_buffer_dsc_t){ .channels = 0, .datatype = TYPE_UNKNOWN, .bpp = 0, .filters = 0u };
1953 img->film_id = UNKNOWN_IMAGE;
1954 img->group_id = UNKNOWN_IMAGE;
1955 img->group_members = 0;
1956 img->history_items = 0;
1957 img->history_hash = UINT64_MAX;
1958 img->mipmap_hash = 0; // don't default it to img->history_hash to not make it look like they are in sync
1959 img->self_hash = 0;
1960 img->flags = 0;
1961 img->id = UNKNOWN_IMAGE;
1962 img->version = -1;
1963 img->loader = LOADER_UNKNOWN;
1964 img->exif_inited = 0;
1966 dt_datetime_exif_to_img(img, "");
1967 memset(img->exif_maker, 0, sizeof(img->exif_maker));
1968 memset(img->exif_model, 0, sizeof(img->exif_model));
1969 memset(img->exif_lens, 0, sizeof(img->exif_lens));
1970 memset(img->camera_maker, 0, sizeof(img->camera_maker));
1971 memset(img->camera_model, 0, sizeof(img->camera_model));
1972 memset(img->camera_alias, 0, sizeof(img->camera_alias));
1973 memset(img->camera_makermodel, 0, sizeof(img->camera_makermodel));
1974 memset(img->camera_legacy_makermodel, 0, sizeof(img->camera_legacy_makermodel));
1975 memset(img->filename, 0, sizeof(img->filename));
1976 memset(img->fullpath, 0, sizeof(img->fullpath));
1977 memset(img->local_copy_path, 0, sizeof(img->local_copy_path));
1978 memset(img->local_copy_legacy_path, 0, sizeof(img->local_copy_legacy_path));
1979 memset(img->folder, 0, sizeof(img->folder));
1980 memset(img->filmroll, 0, sizeof(img->filmroll));
1981 memset(img->datetime, 0, sizeof(img->datetime));
1982 g_strlcpy(img->filename, "(unknown)", sizeof(img->filename));
1983 img->exif_crop = 1.0;
1984 img->exif_exposure = 0;
1985 img->exif_exposure_bias = NAN;
1986 img->exif_aperture = 0;
1987 img->exif_iso = 0;
1988 img->exif_focal_length = 0;
1989 img->exif_focus_distance = 0;
1990 img->geoloc.latitude = NAN;
1991 img->geoloc.longitude = NAN;
1992 img->geoloc.elevation = NAN;
1993 img->raw_black_level = 0;
1994 for(uint8_t i = 0; i < 4; i++) img->raw_black_level_separate[i] = 0;
1995 img->raw_white_point = 16384; // 2^14
1996 img->d65_color_matrix[0] = NAN;
1997 img->profile = NULL;
1998 img->profile_size = 0;
1999 img->embedded_profile = NULL;
2001 img->fuji_rotation_pos = 0;
2002 img->pixel_aspect_ratio = 1.0f;
2003 img->wb_coeffs[0] = NAN;
2004 img->wb_coeffs[1] = NAN;
2005 img->wb_coeffs[2] = NAN;
2006 img->wb_coeffs[3] = NAN;
2007 img->usercrop[0] = img->usercrop[1] = 0;
2008 img->usercrop[2] = img->usercrop[3] = 1;
2010 img->dng_gain_maps = NULL;
2011 img->cache_entry = 0;
2012 img->color_labels = 0;
2013 img->rating = 0;
2014 img->has_localcopy = FALSE;
2015 img->has_audio = FALSE;
2016 img->is_bw = FALSE;
2017 img->is_bw_flow = FALSE;
2018 img->is_hdr = FALSE;
2019
2020 for(int k=0; k<4; k++)
2021 for(int i=0; i<3; i++)
2022 img->adobe_XYZ_to_CAM[k][i] = NAN;
2023}
2024
2026{
2027 if(!img->camera_maker[0] || !img->camera_model[0] || !img->camera_alias[0])
2028 {
2029 // We need to use the exif values, so let's get rawspeed to munge them
2031 img->camera_maker, sizeof(img->camera_maker),
2032 img->camera_model, sizeof(img->camera_model),
2033 img->camera_alias, sizeof(img->camera_alias));
2034 }
2035
2036 // Now we just create a makermodel by concatenation
2037 g_strlcpy(img->camera_makermodel, img->camera_maker, sizeof(img->camera_makermodel));
2038 const int len = strlen(img->camera_maker);
2039 img->camera_makermodel[len] = ' ';
2040 g_strlcpy(img->camera_makermodel+len+1, img->camera_model, sizeof(img->camera_makermodel)-len-1);
2041}
2042
2043int32_t dt_image_rename(const int32_t imgid, const int32_t filmid, const gchar *newname)
2044{
2045 // TODO: several places where string truncation could occur unnoticed
2046 int32_t result = -1;
2047 gchar oldimg[DT_PATH_MAX] = { 0 };
2048 gchar newimg[DT_PATH_MAX] = { 0 };
2049 gboolean from_cache = FALSE;
2050 dt_image_full_path(imgid, oldimg, sizeof(oldimg), &from_cache, __FUNCTION__);
2051 gchar *newdir = NULL;
2052
2053 newdir = dt_film_repository_get_folder(filmid);
2054
2055 gchar copysrcpath[DT_PATH_MAX] = { 0 };
2056 gchar copydestpath[DT_PATH_MAX] = { 0 };
2057 GFile *old = NULL, *new = NULL;
2058 if(newdir)
2059 {
2060 old = g_file_new_for_path(oldimg);
2061
2062 if(newname)
2063 {
2064 g_snprintf(newimg, sizeof(newimg), "%s%c%s", newdir, G_DIR_SEPARATOR, newname);
2065 new = g_file_new_for_path(newimg);
2066 // 'newname' represents the file's new *basename* -- it must not
2067 // refer to a file outside of 'newdir'.
2068 gchar *newBasename = g_file_get_basename(new);
2069 if(g_strcmp0(newname, newBasename) != 0)
2070 {
2071 g_object_unref(old);
2072 old = NULL;
2073 g_object_unref(new);
2074 new = NULL;
2075 }
2076 dt_free(newBasename);
2077 }
2078 else
2079 {
2080 gchar *imgbname = g_path_get_basename(oldimg);
2081 g_snprintf(newimg, sizeof(newimg), "%s%c%s", newdir, G_DIR_SEPARATOR, imgbname);
2082 new = g_file_new_for_path(newimg);
2083 dt_free(imgbname);
2084 }
2085 dt_free(newdir);
2086 }
2087
2088 // no destination: the film roll has no folder, or the new name reached outside it
2089 if(IS_NULL_PTR(new)) return result;
2090
2091 // get current local copy if any
2092 dt_image_t *img = dt_image_cache_get(imgid, 'r');
2093 if(!IS_NULL_PTR(img))
2094 {
2095 dt_image_choose_input_path(img, copysrcpath, sizeof(copysrcpath), TRUE);
2097 }
2098
2099 // move image
2100 GError *moveError = NULL;
2101 gboolean moveStatus = g_file_move(old, new, 0, NULL, NULL, NULL, &moveError);
2102
2103 if(moveStatus)
2104 {
2105 _move_text_sidecar_if_present(oldimg, newimg, FALSE);
2106
2107 // first move xmp files of image and duplicates
2108 GList *dup_list = dt_image_repository_get_duplicate_ids(imgid);
2109 for(GList *l = dup_list; l; l = g_list_next(l))
2110 {
2111 const int32_t id = GPOINTER_TO_INT(l->data);
2112 gchar oldxmp[DT_PATH_MAX] = { 0 };
2113 gchar newxmp[DT_PATH_MAX] = { 0 };
2114 g_strlcpy(oldxmp, oldimg, sizeof(oldxmp));
2115 g_strlcpy(newxmp, newimg, sizeof(newxmp));
2116 dt_image_path_append_version(id, oldxmp, sizeof(oldxmp));
2117 dt_image_path_append_version(id, newxmp, sizeof(newxmp));
2118 g_strlcat(oldxmp, ".xmp", sizeof(oldxmp));
2119 g_strlcat(newxmp, ".xmp", sizeof(newxmp));
2120
2121 GFile *goldxmp = g_file_new_for_path(oldxmp);
2122 GFile *gnewxmp = g_file_new_for_path(newxmp);
2123
2124 g_file_move(goldxmp, gnewxmp, 0, NULL, NULL, NULL, NULL);
2125
2126 g_object_unref(goldxmp);
2127 g_object_unref(gnewxmp);
2128 }
2129
2130 // then update database and cache
2131 // if update was performed in above loop, dt_image_path_append_version()
2132 // would return wrong version!
2133 while(dup_list)
2134 {
2135 const int id = GPOINTER_TO_INT(dup_list->data);
2136 dup_list = g_list_delete_link(dup_list, dup_list);
2137 img = dt_image_cache_get(id, 'w');
2138 if(IS_NULL_PTR(img))
2139 {
2140 // the file and its sidecars have moved, the row of this duplicate has not
2141 dt_control_alert(_("File operation failed"),
2142 _("the file was moved, but the library could not record its new place"), newimg);
2143 continue;
2144 }
2145 img->film_id = filmid;
2146 if(newname) g_strlcpy(img->filename, newname, DT_MAX_FILENAME_LEN);
2147 // write through to db and queue xmp write
2149 }
2150 g_list_free(dup_list);
2151 dup_list = NULL;
2152
2153 // finally, rename local copy if any
2154 if(g_file_test(copysrcpath, G_FILE_TEST_EXISTS))
2155 {
2156 // get new name
2157 img = dt_image_cache_get(imgid, 'r');
2158 if(!IS_NULL_PTR(img))
2159 {
2160 dt_image_choose_input_path(img, copydestpath, sizeof(copydestpath), TRUE);
2162 }
2163
2164 GFile *cold = g_file_new_for_path(copysrcpath);
2165 GFile *cnew = g_file_new_for_path(copydestpath);
2166
2167 g_clear_error(&moveError);
2168 moveStatus = g_file_move(cold, cnew, 0, NULL, NULL, NULL, &moveError);
2169 if(!moveStatus)
2170 fprintf(stderr, "[dt_image_rename] error moving local copy `%s' -> `%s'\n", copysrcpath, copydestpath);
2171
2172 // moveError is set only when the move failed
2173 if(g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
2174 {
2175 gchar *oldBasename = g_path_get_basename(copysrcpath);
2176 dt_control_alert(_("File operation failed"), _("cannot access local copy"), oldBasename);
2177 dt_free(oldBasename);
2178 }
2179 else if(g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_EXISTS)
2180 || g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY))
2181 {
2182 gchar *newBasename = g_path_get_basename(copydestpath);
2183 dt_control_alert(_("File operation failed"), _("cannot write local copy"), newBasename);
2184 dt_free(newBasename);
2185 }
2186 else if(!moveStatus)
2187 {
2188 gchar *oldBasename = g_path_get_basename(copysrcpath);
2189 gchar *newBasename = g_path_get_basename(copydestpath);
2190 gchar *item = g_strdup_printf("%s -> %s", oldBasename, newBasename);
2191 dt_control_alert(_("File operation failed"), _("error moving local copy"), item);
2192 dt_free(item);
2193 dt_free(oldBasename);
2194 dt_free(newBasename);
2195 }
2196
2197 g_object_unref(cold);
2198 g_object_unref(cnew);
2199 }
2200
2201 result = 0;
2202 }
2203 else
2204 {
2205 if(g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
2206 {
2207 dt_control_alert(_("File operation failed"), _("error moving file: file not found"), oldimg);
2208 }
2209 // only display error message if newname is set (renaming and
2210 // not moving) as when moving it can be the case where a
2211 // duplicate is being moved, so only the .xmp are present but
2212 // the original file may already have been moved.
2213 else if(newname
2214 && (g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_EXISTS)
2215 || g_error_matches(moveError, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)))
2216 {
2217 gchar *item = g_strdup_printf("%s -> %s", oldimg, newimg);
2218 dt_control_alert(_("File operation failed"), _("error moving file: file exists"), item);
2219 dt_free(item);
2220 }
2221 else if(newname)
2222 {
2223 gchar *item = g_strdup_printf("%s -> %s", oldimg, newimg);
2224 dt_control_alert(_("File operation failed"), _("error moving file"), item);
2225 dt_free(item);
2226 }
2227 }
2228
2229 g_clear_error(&moveError);
2230 g_object_unref(old);
2231 g_object_unref(new);
2232
2233 return result;
2234}
2235
2236int32_t dt_image_move(const int32_t imgid, const int32_t filmid)
2237{
2238 return dt_image_rename(imgid, filmid, NULL);
2239}
2240
2241int32_t dt_image_copy_rename(const int32_t imgid, const int32_t filmid, const gchar *newname)
2242{
2243 int32_t newid = -1;
2244 gchar srcpath[DT_PATH_MAX] = { 0 };
2245 gchar *newdir = NULL;
2246 gboolean from_cache = FALSE;
2247 gchar *oldFilename = NULL;
2248 gchar *newFilename = NULL;
2249
2250 newdir = dt_film_repository_get_folder(filmid);
2251
2252 GFile *src = NULL, *dest = NULL;
2253 if(newdir)
2254 {
2255 dt_image_full_path(imgid, srcpath, sizeof(srcpath), &from_cache, __FUNCTION__);
2256 oldFilename = g_path_get_basename(srcpath);
2257 gchar *destpath;
2258 if(newname)
2259 {
2260 newFilename = g_strdup(newname);
2261 destpath = g_build_filename(newdir, newname, NULL);
2262 dest = g_file_new_for_path(destpath);
2263 // 'newname' represents the file's new *basename* -- it must not
2264 // refer to a file outside of 'newdir'.
2265 gchar *destBasename = g_file_get_basename(dest);
2266 if(g_strcmp0(newname, destBasename) != 0)
2267 {
2268 g_object_unref(dest);
2269 dest = NULL;
2270 }
2271 dt_free(destBasename);
2272 }
2273 else
2274 {
2275 newFilename = g_path_get_basename(srcpath);
2276 destpath = g_build_filename(newdir, newFilename, NULL);
2277 dest = g_file_new_for_path(destpath);
2278 }
2279 if(dest)
2280 {
2281 src = g_file_new_for_path(srcpath);
2282 }
2283 dt_free(newdir);
2284 dt_free(destpath);
2285 }
2286
2287 if(dest)
2288 {
2289 // copy image to new folder
2290 // if image file already exists, continue
2291 GError *gerror = NULL;
2292 gboolean copyStatus = g_file_copy(src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, &gerror);
2293
2294 if(copyStatus || g_error_matches(gerror, G_IO_ERROR, G_IO_ERROR_EXISTS))
2295 {
2296 gchar *dest_image_path = g_file_get_path(dest);
2297 if(dest_image_path)
2298 {
2299 _copy_text_sidecar_if_present(srcpath, dest_image_path);
2300 dt_free(dest_image_path);
2301 }
2302
2303 newid = dt_image_repository_copy_to_film(imgid, filmid, newFilename, oldFilename);
2304
2305 if(newid != -1)
2306 {
2307 // also copy over on-disk thumbnails, if any
2308 dt_mipmap_cache_copy_thumbnails(newid, imgid);
2309
2311
2313 NULL);
2314 }
2315 }
2316 else
2317 {
2318 fprintf(stderr, "Failed to copy image %s: %s\n", srcpath, gerror->message);
2319 }
2320 g_object_unref(dest);
2321 g_object_unref(src);
2322 g_clear_error(&gerror);
2323 }
2324 dt_free(oldFilename);
2325 dt_free(newFilename);
2326
2327 return newid;
2328}
2329
2330int32_t dt_image_copy(const int32_t imgid, const int32_t filmid)
2331{
2332 return dt_image_copy_rename(imgid, filmid, NULL);
2333}
2334
2335int dt_image_local_copy_set(const int32_t imgid)
2336{
2337 gchar srcpath[DT_PATH_MAX] = { 0 };
2338 gchar destpath[DT_PATH_MAX] = { 0 };
2340 char local_copy_path[DT_PATH_MAX] = { 0 };
2341 char local_copy_legacy_path[DT_PATH_MAX] = { 0 };
2342 dt_image_t *img = dt_image_cache_get(imgid, 'r');
2343 if(img)
2344 {
2345 g_strlcpy(srcpath, img->fullpath, DT_PATH_MAX);
2346 g_strlcpy(local_copy_path, img->local_copy_path, sizeof(local_copy_path));
2347 g_strlcpy(local_copy_legacy_path, img->local_copy_legacy_path, sizeof(local_copy_legacy_path));
2348 char existing[DT_PATH_MAX] = { 0 };
2349 source = dt_image_choose_input_path(img, existing, sizeof(existing), TRUE);
2351 g_strlcpy(destpath, existing, sizeof(destpath));
2352 else
2353 g_strlcpy(destpath, local_copy_path, sizeof(destpath));
2355 }
2356 else
2357 {
2358 return 1;
2359 }
2360
2361 // check that the src file is readable
2362 if(!g_file_test(srcpath, G_FILE_TEST_IS_REGULAR))
2363 {
2364 dt_control_alert(_("File operation failed"),
2365 _("cannot create local copy when the original file is not accessible."), srcpath);
2366 return 1;
2367 }
2368
2370 {
2371 GFile *src = g_file_new_for_path(srcpath);
2372 GFile *dest = g_file_new_for_path(destpath);
2373
2374 // copy image to cache directory
2375 GError *gerror = NULL;
2376
2377 if(!g_file_copy(src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, &gerror))
2378 {
2379 dt_control_alert(_("File operation failed"), _("cannot create local copy."), srcpath);
2380 g_object_unref(dest);
2381 g_object_unref(src);
2382 return 1;
2383 }
2384
2385 g_object_unref(dest);
2386 g_object_unref(src);
2387 }
2388
2389 _copy_text_sidecar_if_present(srcpath, destpath);
2390
2391 // update cache local copy flags, do this even if the local copy already exists as we need to set the flags
2392 // for duplicate
2393 img = dt_image_cache_get(imgid, 'w');
2394 if(IS_NULL_PTR(img))
2395 {
2396 // the copy is written but not flagged, so nothing reads it; asking again flags it
2397 dt_control_alert(_("File operation failed"), _("cannot create local copy."), srcpath);
2398 return 1;
2399 }
2400 img->flags |= DT_IMAGE_LOCAL_COPY;
2402
2404 return 0;
2405}
2406
2407static int _nb_other_local_copy_for(const int32_t imgid)
2408{
2410}
2411
2412int dt_image_local_copy_reset(const int32_t imgid)
2413{
2414 gchar destpath[DT_PATH_MAX] = { 0 };
2415 gchar locppath[DT_PATH_MAX] = { 0 };
2416 gchar cachedir[DT_PATH_MAX] = { 0 };
2417
2418 // check that a local copy exists, otherwise there is nothing to do
2419 dt_image_t *imgr = dt_image_cache_get(imgid, 'r');
2420 if(!imgr) return 0;
2421 const gboolean local_copy_exists = (imgr->flags & DT_IMAGE_LOCAL_COPY) == DT_IMAGE_LOCAL_COPY ? TRUE : FALSE;
2422 // The original's path even when it is gone, which dt_image_full_path() does not give: it names
2423 // the image in the alert below.
2424 char fullpath[DT_PATH_MAX] = { 0 };
2425 g_strlcpy(fullpath, imgr->fullpath, sizeof(fullpath));
2427
2428 if(!local_copy_exists)
2429 return 0;
2430
2431 // check that the original file is accessible
2432
2433 gboolean from_cache = FALSE;
2434 dt_image_full_path(imgid, destpath, sizeof(destpath), &from_cache, __FUNCTION__);
2435
2436 from_cache = TRUE;
2437 dt_image_full_path(imgid, locppath, sizeof(locppath), &from_cache, __FUNCTION__);
2438 dt_image_path_append_version(imgid, locppath, sizeof(locppath));
2439 g_strlcat(locppath, ".xmp", sizeof(locppath));
2440
2441 // a local copy exists, but the original is not accessible
2442
2443 if(g_file_test(locppath, G_FILE_TEST_EXISTS) && !g_file_test(destpath, G_FILE_TEST_EXISTS))
2444 {
2445 dt_control_alert(_("File operation failed"),
2446 _("cannot remove local copy when the original file is not accessible."), fullpath);
2447 return 1;
2448 }
2449
2450 // get name of local copy
2451
2452 locppath[0] = '\0';
2453 dt_image_t *img = dt_image_cache_get(imgid, 'r');
2454 if(img)
2455 {
2456 dt_image_choose_input_path(img, locppath, sizeof(locppath), TRUE);
2458 }
2459
2460 // remove cached file, but double check that this is really into the cache. We really want to avoid deleting
2461 // a user's original file.
2462
2463 dt_loc_get_user_cache_dir(cachedir, sizeof(cachedir));
2464
2465 if(g_file_test(locppath, G_FILE_TEST_EXISTS) && strstr(locppath, cachedir))
2466 {
2467 GFile *dest = g_file_new_for_path(locppath);
2468
2469 // first sync the xmp with the original picture
2470
2471 dt_control_save_xmp(imgid);
2472
2473 // delete image from cache directory only if there is no other local cache image referencing it
2474 // for example duplicates are all referencing the same base picture.
2475
2476 if(_nb_other_local_copy_for(imgid) == 0)
2477 {
2478 _move_text_sidecar_if_present(locppath, destpath, TRUE);
2479 g_file_delete(dest, NULL, NULL);
2480 }
2481
2482 g_object_unref(dest);
2483
2484 // delete xmp if any
2485 dt_image_path_append_version(imgid, locppath, sizeof(locppath));
2486 g_strlcat(locppath, ".xmp", sizeof(locppath));
2487 dest = g_file_new_for_path(locppath);
2488
2489 if(g_file_test(locppath, G_FILE_TEST_EXISTS)) g_file_delete(dest, NULL, NULL);
2490 g_object_unref(dest);
2491 }
2492
2493 // update cache, remove local copy flags, this is done in all cases here as when we
2494 // reach this point the local-copy flag is present and the file has been either removed
2495 // or is not present.
2496
2497 img = dt_image_cache_get(imgid, 'w');
2498 if(IS_NULL_PTR(img))
2499 {
2500 // the copy is gone already, and a stale flag falls back to the original (dt_image_choose_input_path())
2501 fprintf(stderr, "[dt_image_local_copy_reset] could not load image %i, its local copy flag stays\n", imgid);
2502 return 0;
2503 }
2504 img->flags &= ~DT_IMAGE_LOCAL_COPY;
2506
2508
2509 return 0;
2510}
2511
2512// *******************************************************
2513// xmp stuff
2514// *******************************************************
2515
2516static gboolean _sidecar_is_up_to_date(const dt_image_t *img)
2517{
2518 if(IS_NULL_PTR(img) || img->id <= 0) return FALSE;
2519 if(img->change_timestamp <= 0) return FALSE;
2520
2521 const int64_t write_timestamp = dt_image_repository_get_write_timestamp(img->id);
2522 if(write_timestamp <= 0) return FALSE;
2523
2525 if(IS_NULL_PTR(gdt)) return FALSE;
2526 const int64_t change_timestamp_unix = g_date_time_to_unix(gdt);
2527 g_date_time_unref(gdt);
2529 "[xmp] imgid %d change_ts=%lld write_ts=%lld\n",
2530 img->id, (long long)change_timestamp_unix, (long long)write_timestamp);
2531 return change_timestamp_unix <= write_timestamp;
2532}
2533
2535 const gboolean force_write)
2536{
2537 if(IS_NULL_PTR(img) || img->id <= 0) return DT_IMAGE_WRITE_SIDECAR_DISABLED;
2538
2539 char imgpath[DT_PATH_MAX] = { 0 };
2540 if(dt_image_choose_input_path(img, imgpath, sizeof(imgpath), FALSE) == DT_IMAGE_PATH_NONE)
2541 {
2542 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d no source path available\n", img->id);
2544 }
2545
2546 char filename[DT_PATH_MAX] = { 0 };
2547 g_strlcpy(filename, imgpath, sizeof(filename));
2548 dt_image_path_append_version(img->id, filename, sizeof(filename));
2549 g_strlcat(filename, ".xmp", sizeof(filename));
2550
2551 if(!force_write && g_file_test(filename, G_FILE_TEST_EXISTS))
2552 {
2553 if(_sidecar_is_up_to_date(img))
2554 {
2555 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d sidecar up-to-date, skip\n", img->id);
2557 }
2558 }
2559
2560 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d writing sidecar %s\n", img->id, filename);
2561 if(dt_exif_xmp_write_with_imgpath(img, filename, imgpath) != 0)
2563
2564 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d updating write_timestamp\n", img->id);
2567}
2568
2570{
2571 if(imgid <= 0 || !dt_image_get_xmp_mode()) return DT_IMAGE_WRITE_SIDECAR_DISABLED;
2572
2573 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d write start\n", imgid);
2574 dt_image_t *img = dt_image_cache_get(imgid, 'w');
2575 if(IS_NULL_PTR(img))
2576 {
2577 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock failed\n", imgid);
2579 }
2580 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock acquired (write)\n", imgid);
2581
2584 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock released (write minimal)\n", imgid);
2585 return res;
2586}
2587
2589{
2590 if(imgid <= 0) return DT_IMAGE_WRITE_SIDECAR_DISABLED;
2591
2592 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d forced write start\n", imgid);
2593 dt_image_t *img = dt_image_cache_get(imgid, 'w');
2594 if(IS_NULL_PTR(img))
2595 {
2596 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock failed\n", imgid);
2598 }
2599 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock acquired (write)\n", imgid);
2600
2603 dt_print(DT_DEBUG_CONTROL, "[xmp] imgid %d cache lock released (write minimal)\n", imgid);
2604 return res;
2605}
2606
2607void dt_image_synch_xmps(const GList *img)
2608{
2609 if(IS_NULL_PTR(img)) return;
2611 {
2613 }
2614}
2615
2617{
2618 if(selected > 0)
2619 {
2620 GList *imgs = g_list_append(NULL, GINT_TO_POINTER(selected));
2622 g_list_free(imgs);
2623 imgs = NULL;
2624 }
2625 else
2626 {
2627 GList *imgs = dt_act_on_get_images();
2628 dt_image_synch_xmps(imgs);
2629 g_list_free(imgs);
2630 imgs = NULL;
2631 }
2632}
2633
2634void dt_image_synch_all_xmp(const gchar *pathname)
2635{
2637 {
2638 const int32_t imgid = dt_image_get_id_full_path(pathname);
2639 if(imgid != UNKNOWN_IMAGE)
2640 {
2641 GList *imgs = g_list_append(NULL, GINT_TO_POINTER(imgid));
2643 g_list_free(imgs);
2644 imgs = NULL;
2645 }
2646 }
2647}
2648
2650{
2651 GList *imgs = NULL;
2652
2654 for(GList *l = candidates; l; l = g_list_next(l))
2655 {
2656 const int32_t imgid = GPOINTER_TO_INT(l->data);
2657 gboolean from_cache = FALSE;
2658 char filename[DT_PATH_MAX] = { 0 };
2659 dt_image_full_path(imgid, filename, sizeof(filename), &from_cache, __FUNCTION__);
2660
2661 if(g_file_test(filename, G_FILE_TEST_EXISTS))
2662 {
2663 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
2664 }
2665 }
2666 g_list_free(candidates);
2667
2668 const int count = g_list_length(imgs);
2669 if(count > 0)
2670 {
2672 dt_control_log(ngettext("%d local copy has been synchronized",
2673 "%d local copies have been synchronized", count),
2674 count);
2675 }
2676 g_list_free(imgs);
2677 imgs = NULL;
2678}
2679
2680void dt_image_get_datetime(const int32_t imgid, char *datetime)
2681{
2682 if(IS_NULL_PTR(datetime)) return;
2683 datetime[0] = '\0';
2684 const dt_image_t *cimg = dt_image_cache_get(imgid, 'r');
2685 if(IS_NULL_PTR(cimg)) return;
2688}
2689
2690static void _datetime_undo_data_free(gpointer data)
2691{
2692 GList *l = (GList *)data;
2693 g_list_free_full(l, dt_free_gpointer);
2694 l = NULL;
2695}
2696
2701
2702static void _image_set_datetimes(const GList *img, const GArray *dtime,
2703 GList **undo, const gboolean undo_on)
2704{
2705 int i = 0;
2706 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
2707 {
2708 const int32_t imgid = GPOINTER_TO_INT(imgs->data);
2709 // if char *datetime, the returned pointer is not correct => use of _datetime_t
2710 const _datetime_t *datetime = &g_array_index(dtime, _datetime_t, i);
2711 i++;
2712
2713 char before[DT_DATETIME_LENGTH] = { 0 };
2714 if(undo_on) dt_image_get_datetime(imgid, before);
2715
2716 if(_set_datetime(imgid, datetime->dt) || !undo_on) continue;
2717
2718 dt_undo_datetime_t *undodatetime = (dt_undo_datetime_t *)malloc(sizeof(dt_undo_datetime_t));
2719 undodatetime->imgid = imgid;
2720 memcpy(&undodatetime->before, before, DT_DATETIME_LENGTH);
2721 memcpy(&undodatetime->after, datetime->dt, DT_DATETIME_LENGTH);
2722 *undo = g_list_prepend(*undo, undodatetime);
2723 }
2724}
2725
2726void dt_image_set_datetimes(const GList *imgs, const GArray *dtime, const gboolean undo_on)
2727{
2728 if(IS_NULL_PTR(imgs) || IS_NULL_PTR(dtime) || (g_list_length((GList *)imgs) != dtime->len))
2729 return;
2730 GList *undo = NULL;
2732
2733 _image_set_datetimes(imgs, dtime, &undo, undo_on);
2734
2735 if(undo_on)
2736 {
2739 }
2740}
2741
2742static void _image_set_datetime(const GList *img, const char *datetime,
2743 GList **undo, const gboolean undo_on)
2744{
2745 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
2746 {
2747 const int32_t imgid = GPOINTER_TO_INT(imgs->data);
2748
2749 char before[DT_DATETIME_LENGTH] = { 0 };
2750 if(undo_on) dt_image_get_datetime(imgid, before);
2751
2752 if(_set_datetime(imgid, datetime) || !undo_on) continue;
2753
2754 dt_undo_datetime_t *undodatetime = (dt_undo_datetime_t *)malloc(sizeof(dt_undo_datetime_t));
2755 undodatetime->imgid = imgid;
2756 memcpy(&undodatetime->before, before, DT_DATETIME_LENGTH);
2757 memcpy(&undodatetime->after, datetime, DT_DATETIME_LENGTH);
2758 *undo = g_list_prepend(*undo, undodatetime);
2759 }
2760}
2761
2762void dt_image_set_datetime(const GList *imgs, const char *datetime, const gboolean undo_on)
2763{
2764 if(IS_NULL_PTR(imgs))
2765 return;
2766 GList *undo = NULL;
2768
2769 _image_set_datetime(imgs, datetime, &undo, undo_on);
2770
2771 if(undo_on)
2772 {
2775 }
2776}
2777
2778char *dt_image_get_audio_path_from_path(const char *image_path)
2779{
2780 size_t len = strlen(image_path);
2781 const char *c = image_path + len;
2782 while((c > image_path) && (*c != '.')) c--;
2783 len = c - image_path + 1;
2784
2785 char *result = g_strndup(image_path, len + 3);
2786
2787 result[len] = 'w';
2788 result[len + 1] = 'a';
2789 result[len + 2] = 'v';
2790 if(g_file_test(result, G_FILE_TEST_EXISTS)) return result;
2791
2792 result[len] = 'W';
2793 result[len + 1] = 'A';
2794 result[len + 2] = 'V';
2795 if(g_file_test(result, G_FILE_TEST_EXISTS)) return result;
2796
2797 dt_free(result);
2798 return NULL;
2799}
2800
2801char *dt_image_get_audio_path(const int32_t imgid)
2802{
2803 gboolean from_cache = FALSE;
2804 char image_path[DT_PATH_MAX] = { 0 };
2805 dt_image_full_path(imgid, image_path, sizeof(image_path), &from_cache, __FUNCTION__);
2806
2807 return dt_image_get_audio_path_from_path(image_path);
2808}
2809
2810static char *_text_path_legacy_if_exists(const char *image_path)
2811{
2812 size_t len = strlen(image_path);
2813 const char *c = image_path + len;
2814 while((c > image_path) && (*c != '.')) c--;
2815 len = c - image_path + 1;
2816
2817 char *result = g_strndup(image_path, len + 3);
2818
2819 result[len] = 't';
2820 result[len + 1] = 'x';
2821 result[len + 2] = 't';
2822 if(g_file_test(result, G_FILE_TEST_EXISTS)) return result;
2823
2824 result[len] = 'T';
2825 result[len + 1] = 'X';
2826 result[len + 2] = 'T';
2827 if(g_file_test(result, G_FILE_TEST_EXISTS)) return result;
2828
2829 dt_free(result);
2830 return NULL;
2831}
2832
2833static char *_text_path_legacy_build(const char *image_path)
2834{
2835 size_t len = strlen(image_path);
2836 const char *c = image_path + len;
2837 while((c > image_path) && (*c != '.')) c--;
2838 len = c - image_path + 1;
2839
2840 char *result = g_strndup(image_path, len + 3);
2841 result[len] = 't';
2842 result[len + 1] = 'x';
2843 result[len + 2] = 't';
2844 return result;
2845}
2846
2847char *dt_image_get_text_path_from_path(const char *image_path)
2848{
2849 if(IS_NULL_PTR(image_path)) return NULL;
2850
2851 return _text_path_legacy_if_exists(image_path);
2852}
2853
2854char *dt_image_build_text_path_from_path(const char *image_path)
2855{
2856 if(IS_NULL_PTR(image_path)) return NULL;
2857
2858 return _text_path_legacy_build(image_path);
2859}
2860
2861static void _copy_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path)
2862{
2863 if(IS_NULL_PTR(src_image_path) || IS_NULL_PTR(dest_image_path)) return;
2864
2865 char *src_txt = dt_image_get_text_path_from_path(src_image_path);
2866 if(IS_NULL_PTR(src_txt)) return;
2867
2868 char *dest_txt = _text_path_legacy_build(dest_image_path);
2869 if(dest_txt)
2870 {
2871 GFile *src = g_file_new_for_path(src_txt);
2872 GFile *dest = g_file_new_for_path(dest_txt);
2873 GError *gerror = NULL;
2874 gboolean copyStatus = g_file_copy(src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, &gerror);
2875
2876 if(!copyStatus && g_error_matches(gerror, G_IO_ERROR, G_IO_ERROR_EXISTS))
2877 copyStatus = TRUE;
2878
2879 if(!copyStatus && gerror)
2880 fprintf(stderr, "[dt_image] failed to copy text sidecar `%s' -> `%s': %s\n",
2881 src_txt, dest_txt, gerror->message);
2882
2883 g_clear_error(&gerror);
2884 g_object_unref(dest);
2885 g_object_unref(src);
2886 }
2887
2888 dt_free(dest_txt);
2889 dt_free(src_txt);
2890}
2891
2892static void _move_text_sidecar_if_present(const char *src_image_path, const char *dest_image_path, const gboolean overwrite)
2893{
2894 if(IS_NULL_PTR(src_image_path) || IS_NULL_PTR(dest_image_path)) return;
2895
2896 char *src_txt = dt_image_get_text_path_from_path(src_image_path);
2897 if(IS_NULL_PTR(src_txt)) return;
2898
2899 char *dest_txt = _text_path_legacy_build(dest_image_path);
2900 if(dest_txt)
2901 {
2902 GFile *src = g_file_new_for_path(src_txt);
2903 GFile *dest = g_file_new_for_path(dest_txt);
2904 GError *gerror = NULL;
2905 const GFileCopyFlags flags = overwrite ? G_FILE_COPY_OVERWRITE : G_FILE_COPY_NONE;
2906 gboolean moveStatus = g_file_move(src, dest, flags, NULL, NULL, NULL, &gerror);
2907
2908 if(!moveStatus && gerror)
2909 fprintf(stderr, "[dt_image] failed to move text sidecar `%s' -> `%s': %s\n",
2910 src_txt, dest_txt, gerror->message);
2911
2912 g_clear_error(&gerror);
2913 g_object_unref(dest);
2914 g_object_unref(src);
2915 }
2916
2917 dt_free(dest_txt);
2918 dt_free(src_txt);
2919}
2920
2921char *dt_image_get_text_path(const int32_t imgid)
2922{
2923 char image_path[DT_PATH_MAX] = { 0 };
2924
2925 gboolean from_cache = FALSE;
2926 dt_image_full_path(imgid, image_path, sizeof(image_path), &from_cache, __FUNCTION__);
2927
2928 if(image_path[0] != '\0' && g_file_test(image_path, G_FILE_TEST_EXISTS))
2929 return dt_image_get_text_path_from_path(image_path);
2930
2931 from_cache = TRUE;
2932 dt_image_full_path(imgid, image_path, sizeof(image_path), &from_cache, __FUNCTION__);
2933
2934 if(from_cache && image_path[0] != '\0' && g_file_test(image_path, G_FILE_TEST_EXISTS))
2935 return dt_image_get_text_path_from_path(image_path);
2936
2937 return NULL;
2938}
2939
2940float dt_image_get_exposure_bias(const struct dt_image_t *image_storage)
2941{
2942 // just check that pointers exist and are initialized
2943 if((image_storage) && (image_storage->exif_exposure_bias))
2944 {
2945 // sanity checks because I don't trust exif tags too much
2946 if(isnan(image_storage->exif_exposure_bias)
2947 || CLAMP(image_storage->exif_exposure_bias, -5.0f, 5.0f) != image_storage->exif_exposure_bias)
2948 return 0.0f; // isnan
2949 else
2950 return CLAMP(image_storage->exif_exposure_bias, -5.0f, 5.0f);
2951 }
2952 else
2953 return 0.0f;
2954}
2955
2956char *dt_image_camera_missing_sample_message(const struct dt_image_t *img, gboolean logmsg)
2957{
2958 const char *T1 = _("<b>WARNING</b>: camera is missing samples!");
2959 const char *T2 = _("You must provide samples in <a href='https://raw.pixls.us/'>https://raw.pixls.us/</a>");
2960 char *T3 = g_strdup_printf(_("for `%s' `%s'\n"
2961 "in as many format/compression/bit depths as possible"),
2962 img->camera_maker, img->camera_model);
2963 const char *T4 = _("or the <b>RAW won't be readable</b> in next version.");
2964
2965 char *NL = logmsg ? "\n\n" : "\n";
2966 char *PREFIX = logmsg ? "<big>" : "";
2967 char *SUFFIX = logmsg ? "</big>" : "";
2968
2969 char *msg = g_strconcat(PREFIX, T1, NL, T2, NL, T3, NL, T4, SUFFIX, NULL);
2970
2971 if(logmsg)
2972 {
2973 char *newmsg = dt_util_str_replace(msg, "<b>", "<span foreground='red'><b>");
2974 dt_free(msg);
2975 msg = dt_util_str_replace(newmsg, "</b>", "</b></span>");
2976 dt_free(newmsg);
2977 }
2978
2979 dt_free(T3);
2980 return msg;
2981}
2982
2984{
2985 if(img->camera_missing_sample)
2986 {
2988 dt_control_log(msg, (char *)NULL);
2989 dt_free(msg);
2990 }
2991}
2992
2993void dt_get_dirname_from_imgid(gchar *dir, const int32_t imgid)
2994{
2995 gchar path[DT_PATH_MAX] = { 0 };
2996 gboolean from_cache = FALSE;
2997 dt_image_full_path(imgid, path, sizeof(path), &from_cache, __FUNCTION__);
2998 gchar *folder = g_path_get_dirname(path);
2999 g_strlcpy(dir, folder, DT_PATH_MAX); // callers all provide a DT_PATH_MAX-sized dir buffer
3000 dt_free(folder);
3001}
3002
3003// clang-format off
3004// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3005// vim: shiftwidth=2 expandtab tabstop=2 cindent
3006// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3007// 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 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)
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)
void dt_image_remove_undoable(const int32_t imgid)
Same as dt_image_remove(), and records the removal on the undo stack.
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)
static void _remove_undo_data_free(gpointer data)
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)
static void _image_remove(const int32_t imgid, const gboolean undo)
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 int _set_datetime(const int32_t imgid, const char *datetime)
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 int _set_location(const int32_t imgid, const dt_image_geoloc_t *geoloc)
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_alert(const char *title, const char *message, const char *item)
Tell the user something that must not go unseen: what it says stays true after a toast would have fad...
Definition control.c:883
void dt_control_log(const char *msg,...)
Definition control.c:839
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
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:531
GList * dt_iop_get_modules_so(void)
Definition darktable.c:536
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:602
gboolean dt_database_is_open(void)
Definition database.c:3779
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
void dt_film_notify_rolls_changed(void)
State that the film-roll list changed.
Definition film.c:247
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:52
void dt_grouping_add_grouped_images(GList **images)
Definition grouping.c:149
void dt_grouping_add_to_group(const int32_t group_id, const int32_t image_id)
Definition grouping.c:40
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:200
@ DT_IMAGE_COLORSPACE_NONE
Definition image.h:201
@ DT_IMAGE_COLORSPACE_ADOBE_RGB
Definition image.h:203
@ DT_IMAGE_COLORSPACE_SRGB
Definition image.h:202
dt_image_path_source_t
Definition image.h:310
@ DT_IMAGE_PATH_NONE
Definition image.h:311
@ DT_IMAGE_PATH_LOCAL_COPY_LEGACY
Definition image.h:313
@ DT_IMAGE_PATH_ORIGINAL
Definition image.h:314
@ DT_IMAGE_PATH_LOCAL_COPY
Definition image.h:312
#define UNKNOWN_IMAGE
Definition image.h:78
float dt_boundingbox_t[4]
Definition image.h:83
dt_image_orientation_t
Definition image.h:225
@ ORIENTATION_SWAP_XY
Definition image.h:230
@ ORIENTATION_FLIP_Y
Definition image.h:228
@ ORIENTATION_NULL
Definition image.h:226
@ ORIENTATION_NONE
Definition image.h:227
@ ORIENTATION_FLIP_X
Definition image.h:229
dt_image_flags_t
Definition image.h:113
@ DT_IMAGE_NO_LEGACY_PRESETS
Definition image.h:141
@ DT_IMAGE_HAS_ADDITIONAL_DNG_TAGS
Definition image.h:154
@ DT_IMAGE_HAS_WAV
Definition image.h:147
@ DT_IMAGE_AUTO_PRESETS_APPLIED
Definition image.h:139
@ DT_IMAGE_S_RAW
Definition image.h:156
@ DT_IMAGE_REMOVE
Definition image.h:137
@ DT_IMAGE_MOSAIC
Definition image.h:168
@ DT_IMAGE_MONOCHROME_BAYER
Definition image.h:160
@ DT_IMAGE_LOCAL_COPY
Definition image.h:143
@ DT_IMAGE_BUFFER_RESOLVED
Definition image.h:173
@ DT_IMAGE_RAW
Definition image.h:133
@ DT_IMAGE_REJECTED
Definition image.h:122
@ DT_IMAGE_4BAYER
Definition image.h:149
@ DT_IMAGE_MONOCHROME_WORKFLOW
Definition image.h:162
@ DT_IMAGE_HAS_TXT
Definition image.h:145
@ DT_IMAGE_MONOCHROME
Definition image.h:151
@ DT_IMAGE_HDR
Definition image.h:135
@ DT_IMAGE_LDR
Definition image.h:131
@ DT_IMAGE_MONOCHROME_PREVIEW
Definition image.h:158
dt_image_write_sidecar_result_t
Definition image.h:761
@ DT_IMAGE_WRITE_SIDECAR_OK
Definition image.h:762
@ DT_IMAGE_WRITE_SIDECAR_IO_ERROR
Definition image.h:766
@ DT_IMAGE_WRITE_SIDECAR_CACHE_BUSY
Definition image.h:764
@ DT_IMAGE_WRITE_SIDECAR_NO_SOURCE_PATH
Definition image.h:765
@ DT_IMAGE_WRITE_SIDECAR_DISABLED
Definition image.h:763
dt_image_transform_t
An orientation change the user asks for, in the coordinates they are looking at.
Definition image.h:254
@ DT_IMAGE_TRANSFORM_FLIP_VERTICALLY
mirror top <-> bottom, as displayed
Definition image.h:259
@ DT_IMAGE_TRANSFORM_ROTATE_CW
quarter turn clockwise
Definition image.h:255
@ DT_IMAGE_TRANSFORM_RESET
back to ORIENTATION_NULL, i.e. what EXIF says
Definition image.h:257
@ DT_IMAGE_TRANSFORM_FLIP_HORIZONTALLY
mirror left <-> right, as displayed
Definition image.h:258
@ DT_IMAGE_TRANSFORM_ROTATE_CCW
quarter turn counter-clockwise
Definition image.h:256
dt_image_pipe_class_t
Mutually-exclusive classification of an image by the early-pipeline processing it requires....
Definition image.h:191
@ DT_IMAGE_PIPE_MOSAIC_RAW
Definition image.h:193
@ DT_IMAGE_PIPE_LINEAR_RAW
Definition image.h:194
@ DT_IMAGE_PIPE_RGB_LDR
Definition image.h:195
@ DT_IMAGE_PIPE_RGB_HDR
Definition image.h:196
@ DT_IMAGE_PIPE_UNKNOWN
Definition image.h:192
static dt_image_orientation_t dt_image_orientation(const dt_image_t *img)
Definition image.h:701
@ DT_IMAGE_USERCROP_UNKNOWN
Definition image.h:275
@ DT_IMAGE_USERCROP_VALID
Definition image.h:278
@ LOADER_UNKNOWN
Definition image.h:292
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:84
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.
gboolean dt_image_repository_clear_flag_among(GList *imgids, const int flag)
Clear flag on every image in imgids – the counterpart of dt_image_repository_set_flag_among(),...
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)
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
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_remove_all_sizes(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
void dt_removed_image_repository_clear(const int snap_id, const int32_t imgid)
Drop snapshot snap_id of imgid, making the removal permanent.
gboolean dt_removed_image_repository_restore(const int snap_id, const int32_t imgid)
Copy snapshot snap_id of imgid back into main, film roll included.
int dt_removed_image_repository_create(const int32_t imgid)
Copy every row imgid owns out of main, so the removal about to happen can be undone.
memory.removed_*: the staging area behind "undo remove from library".
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:341
double elevation
Definition image.h:341
double longitude
Definition image.h:341
gboolean has_audio
Definition image.h:485
gboolean is_hdr
Definition image.h:488
float exif_exposure
Definition image.h:376
float pixel_aspect_ratio
Definition image.h:452
int32_t height
Definition image.h:406
gboolean camera_missing_sample
Definition image.h:393
GTimeSpan export_timestamp
Definition image.h:424
uint64_t history_hash
Definition image.h:413
char camera_model[64]
Definition image.h:389
float exif_focus_distance
Definition image.h:381
dt_boundingbox_t usercrop
Definition image.h:469
GTimeSpan import_timestamp
Definition image.h:424
gboolean is_bw
Definition image.h:486
int32_t group_id
Definition image.h:410
char camera_makermodel[128]
Definition image.h:391
float exif_exposure_bias
Definition image.h:377
uint16_t raw_black_level
Definition image.h:446
float exif_iso
Definition image.h:379
GList * dng_gain_maps
Definition image.h:473
dt_image_loader_t loader
Definition image.h:426
char camera_maker[64]
Definition image.h:388
uint32_t raw_white_point
Definition image.h:448
int32_t exif_inited
Definition image.h:374
float exif_aperture
Definition image.h:378
char local_copy_path[DT_PATH_MAX]
Definition image.h:397
int32_t version
Definition image.h:410
int32_t crop_height
Definition image.h:407
uint64_t mipmap_hash
Definition image.h:420
dt_image_geoloc_t geoloc
Definition image.h:443
char camera_legacy_makermodel[128]
Definition image.h:392
dt_image_usercrop_status_t usercrop_status
Definition image.h:470
int32_t flags
Definition image.h:410
dt_image_orientation_t orientation
Definition image.h:375
int32_t width
Definition image.h:406
float exif_focal_length
Definition image.h:380
char exif_maker[64]
Definition image.h:383
GTimeSpan change_timestamp
Definition image.h:424
uint32_t profile_size
Definition image.h:432
int32_t crop_y
Definition image.h:407
uint32_t history_items
Definition image.h:412
int rating
Definition image.h:482
gboolean has_localcopy
Definition image.h:484
char camera_alias[64]
Definition image.h:390
char exif_lens[128]
Definition image.h:385
GTimeSpan print_timestamp
Definition image.h:424
int32_t film_id
Definition image.h:410
char folder[DT_PATH_MAX]
Definition image.h:399
int32_t crop_x
Definition image.h:407
int color_labels
Definition image.h:481
float d65_color_matrix[9]
Definition image.h:430
int32_t p_height
Definition image.h:406
dt_iop_buffer_dsc_t dsc
Definition image.h:428
int32_t p_width
Definition image.h:406
char filmroll[DT_PATH_MAX]
Definition image.h:400
char exif_model[64]
Definition image.h:384
dt_image_colorspace_t colorspace
Definition image.h:438
float adobe_XYZ_to_CAM[4][3]
Definition image.h:458
struct dt_colorspaces_color_profile_t * embedded_profile
Definition image.h:437
uint8_t * profile
Definition image.h:431
char datetime[200]
Definition image.h:401
struct dt_cache_entry_t * cache_entry
Definition image.h:491
int32_t crop_width
Definition image.h:407
uint32_t group_members
Definition image.h:411
char local_copy_legacy_path[DT_PATH_MAX]
Definition image.h:398
dt_image_raw_parameters_t legacy_flip
Definition image.h:440
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:395
int32_t id
Definition image.h:410
uint16_t raw_black_level_separate[4]
Definition image.h:447
uint64_t self_hash
Definition image.h:421
dt_aligned_pixel_t wb_coeffs
Definition image.h:455
float exif_crop
Definition image.h:382
char fullpath[DT_PATH_MAX]
Definition image.h:396
uint32_t fuji_rotation_pos
Definition image.h:451
gboolean is_bw_flow
Definition image.h:487
uint32_t filters
Definition format.h:89
uint16_t raw_black_level
Definition format.h:103
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
dt_aligned_pixel_t processed_maximum
Definition format.h:114
struct dt_iop_buffer_dsc_t::@58 rawprepare
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_REMOVE
Definition undo.h:53
@ 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:614
void * dt_undo_data_t
Definition undo.h:70
dt_undo_action_t
Definition undo.h:65
@ DT_ACTION_UNDO
Definition undo.h:66
gchar * dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
Definition utility.c:163
gboolean dt_util_test_image_file(const char *filename)
Definition utility.c:345
gchar * dt_util_normalize_path(const gchar *_input)
Definition utility.c:706
char * dt_util_format_exposure(const float exposuretime)
Definition utility.c:891
const char * dt_util_path_get_extension(const char *path)
Locate a normal filename extension in a path basename.
Definition utility.c:1033
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:144
@ 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,...