Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
control_jobs.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2012 Henrik Andersson.
4 Copyright (C) 2010-2014, 2016 johannes hanika.
5 Copyright (C) 2010 kibooz.
6 Copyright (C) 2010-2017, 2019-2020 Tobias Ellinghaus.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 José Carlos García Sogo.
9 Copyright (C) 2011 Karl Mikaelsson.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2012 Christian Tellefsen.
12 Copyright (C) 2012 Frédéric Grollier.
13 Copyright (C) 2012 James C. McPherson.
14 Copyright (C) 2012 Jens Fendler.
15 Copyright (C) 2012 Joao Trindade.
16 Copyright (C) 2012 marcel.
17 Copyright (C) 2012 Michal Babej.
18 Copyright (C) 2012 Pascal de Bruijn.
19 Copyright (C) 2012-2015, 2018-2022 Pascal Obry.
20 Copyright (C) 2012 Richard Wonka.
21 Copyright (C) 2012-2013 Simon Spannagel.
22 Copyright (C) 2013, 2015 Jérémy Rosen.
23 Copyright (C) 2013 Pierre Le Magourou.
24 Copyright (C) 2013-2016 Roman Lebedev.
25 Copyright (C) 2013 Thomas Pryds.
26 Copyright (C) 2013 Ulrich Pegelow.
27 Copyright (C) 2014 Dan Torop.
28 Copyright (C) 2015 Bruce Guenter.
29 Copyright (C) 2015 Guillaume Benny.
30 Copyright (C) 2015 Nikolai Ugelvik.
31 Copyright (C) 2016 Matthieu Volat.
32 Copyright (C) 2016-2018 parafin.
33 Copyright (C) 2017 pgkos.
34 Copyright (C) 2018 Peter Budai.
35 Copyright (C) 2019 Andreas Schneider.
36 Copyright (C) 2019 August Schwerdfeger.
37 Copyright (C) 2019 Denis Dyakov.
38 Copyright (C) 2019 Edgardo Hoszowski.
39 Copyright (C) 2019-2020 Heiko Bauke.
40 Copyright (C) 2019-2022 Philippe Weyland.
41 Copyright (C) 2019 Sam Smith.
42 Copyright (C) 2020-2022 Aldric Renaudin.
43 Copyright (C) 2020 Hanno Schwalm.
44 Copyright (C) 2020-2021 Hubert Kowalski.
45 Copyright (C) 2020 JP Verrue.
46 Copyright (C) 2020 Marco.
47 Copyright (C) 2020-2021 Ralf Brown.
48 Copyright (C) 2020 Robert Bridge.
49 Copyright (C) 2020 U-DESKTOP-HQME86J\marco.
50 Copyright (C) 2021 Bill Ferguson.
51 Copyright (C) 2021-2022 Chris Elston.
52 Copyright (C) 2021 Cobert0.
53 Copyright (C) 2021 Diederik Ter Rahe.
54 Copyright (C) 2021 Felipe Contreras.
55 Copyright (C) 2021 Marco Carrarini.
56 Copyright (C) 2021 quovadit.
57 Copyright (C) 2022-2025 Aurélien PIERRE.
58 Copyright (C) 2022 Martin Bařinka.
59 Copyright (C) 2022 Paolo Benvenuto.
60 Copyright (C) 2022 Victor Forsiuk.
61 Copyright (C) 2023-2024 Alynx Zhou.
62 Copyright (C) 2023 Ricky Moon.
63 Copyright (C) 2024-2025 Guillaume Stutin.
64 Copyright (C) 2024 Miguel Moquillon.
65
66 darktable is free software: you can redistribute it and/or modify
67 it under the terms of the GNU General Public License as published by
68 the Free Software Foundation, either version 3 of the License, or
69 (at your option) any later version.
70
71 darktable is distributed in the hope that it will be useful,
72 but WITHOUT ANY WARRANTY; without even the implied warranty of
73 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
74 GNU General Public License for more details.
75
76 You should have received a copy of the GNU General Public License
77 along with darktable. If not, see <http://www.gnu.org/licenses/>.
78*/
79
81#include "common/paths.h" // DT_PATH_MAX
82#include "control/signal.h"
85#include "common/act_on.h"
87#include "control/control.h"
88#include "common/collection.h"
89#include "common/xmp_sidecar.h"
90#include "metadata/exif.h"
91#include "common/film.h"
92#include "metadata/gpx.h"
93#include "history/history.h"
95#include "common/image.h"
96#include "caches/image_cache.h"
98#include "imageio/imageio_dng.h"
100#include "metadata/tags.h"
101#include "common/undo.h"
102#include "common/grouping.h"
103#include "common/utility.h"
104#include "common/datetime.h"
105#include "common/conf.h"
106#include "develop/imageop_math.h"
107
108
109#include "gui/application.h"
110
111#include <gio/gio.h>
112#include <glib.h>
113#include <glib/gstdio.h>
114#ifndef _WIN32
115#endif
116#ifdef __APPLE__
117#include "osx/osx.h"
118#endif
119#ifdef _WIN32
120#include "win/dtwin.h"
121#endif
122
123// Control of the collection updates during an import. Start with a short interval to feel responsive,
124// but use fairly infrequent updates for large imports to minimize overall time.
125#define INIT_UPDATE_INTERVAL 2 //seconds
126#define MAX_UPDATE_INTERVAL 3.0 //seconds
127// How long (in seconds) between updates of the "Importing N/M" progress indicator? Should be relatively
128// short to avoid the impression that the import has gotten stuck. Setting this too low will impact the
129// overall time for a large import.
130#define PROGRESS_UPDATE_INTERVAL 1
131
137
143
145{
147 dt_imageio_module_data_t *sdata; // needed since the gui thread resets things like overwrite once the export
148 // is dispatched, but we have to keep that information
149 gboolean export_masks;
150 char style[128];
156
157
158/* enumerator of images from filmroll */
160{
161 /* get a list of images in filmroll */
162 GList *ids = dt_film_repository_get_image_ids(filmid);
163 for(GList *l = ids; l; l = g_list_next(l))
164 t->index = g_list_append(t->index, l->data);
165 g_list_free(ids);
166}
167
169 int32_t (*fileop_callback)(const int32_t,
170 const int32_t),
171 const char *desc, const char *desc_pl)
172{
174 GList *t = params->index;
175 const guint total = g_list_length(t);
176 char message[512] = { 0 };
177 double fraction = 0;
178 gchar *newdir = (gchar *)params->data;
179
180 g_snprintf(message, sizeof(message), ngettext(desc, desc_pl, total), total);
182
183 // create new film roll for the destination directory
184 dt_film_t new_film;
185 const int32_t film_id = dt_film_new(&new_film, newdir);
186 dt_free(newdir);
187
188 if(film_id <= 0)
189 {
190 dt_control_log(_("failed to create film roll for destination directory, aborting move.."));
191 return -1;
192 }
193
194 gboolean completeSuccess = TRUE;
196 {
197 completeSuccess &= (fileop_callback(GPOINTER_TO_INT(t->data), film_id) != -1);
198 t = g_list_next(t);
199 fraction += 1.0 / total;
200 dt_control_job_set_progress(job, fraction);
201 }
202
203 if(completeSuccess)
204 {
205 char collect[1024];
206 snprintf(collect, sizeof(collect), "1:0:0:%s$", new_film.dirname);
208 }
212 g_list_copy(params->index));
214 return 0;
215}
216
218{
220 if(IS_NULL_PTR(params)) return NULL;
221 return params;
222}
223
225{
227
228 if(params->index)
229 {
230 g_list_free(params->index);
231 params->index = NULL;
232 }
233 params->index = NULL;
234
235 dt_free(params);
236}
237
239
241 int flag, gpointer data, progress_type_t progress_type,
242 gboolean only_visible)
243{
244 dt_job_t *job = dt_control_job_create(execute, "%s", message);
245 if(IS_NULL_PTR(job)) return NULL;
247 if(IS_NULL_PTR(params))
248 {
250 return NULL;
251 }
252 if(progress_type != PROGRESS_NONE)
253 dt_control_job_add_progress(job, _(message), progress_type == PROGRESS_CANCELLABLE);
254 params->index = dt_act_on_get_images();
255
257
258 params->flag = flag;
259 params->data = data;
260 return job;
261}
262
264 int flag, gpointer data, progress_type_t progress_type,
265 int32_t imgid)
266{
267 dt_job_t *job = dt_control_job_create(execute, "%s", message);
268 if(IS_NULL_PTR(job)) return NULL;
270 if(IS_NULL_PTR(params))
271 {
273 return NULL;
274 }
275 if(progress_type != PROGRESS_NONE)
276 dt_control_job_add_progress(job, _(message), progress_type == PROGRESS_CANCELLABLE);
277
278 params->index = g_list_append(NULL, GINT_TO_POINTER(imgid));
279
281
282 params->flag = flag;
283 params->data = data;
284 return job;
285}
286
287
289{
291 const gboolean forced = (params->flag == 1);
292
293 if(!forced && !dt_image_get_xmp_mode()) return 0;
294
295 for(GList *t = params->index; t; t = g_list_next(t))
296 {
297 const int32_t imgid = GPOINTER_TO_INT(t->data);
301 switch(res)
302 {
304 break;
306 // xmp writing is off, or the setting changed mid-batch: nothing was attempted, nothing to report
307 break;
309 dt_control_log(_("cannot write XMP file for image %i: the image cache entry is busy, try again."), imgid);
310 break;
312 dt_control_log(_("cannot write XMP file for image %i: the original file could not be found."), imgid);
313 break;
315 dt_control_log(_("cannot write XMP file for image %i: the target storage may be unavailable or read-only."),
316 imgid);
317 break;
318 }
319 }
320 return 0;
321}
322
324{
325 GList *imgs = dt_act_on_get_images();
326 if(IS_NULL_PTR(imgs)) return;
328 g_list_free(imgs);
329 imgs = NULL;
330}
331
333{
334 uint32_t first_imgid;
335 uint32_t first_filter;
336 uint8_t first_xtrans[6][6];
337
338 float *pixels, *weight;
339
340 int wd;
341 int ht;
343
345 float epsw;
347 float adobe_XYZ_to_CAM[4][3];
349
350 // 0 - ok; 1 - errors, abort
351 gboolean abort;
353
359
361{
362 return 32;
363}
364
369
371{
372 return "memory";
373}
374
375static float envelope(const float xx)
376{
377 const float x = CLAMPS(xx, 0.0f, 1.0f);
378 // const float alpha = 2.0f;
379 const float beta = 0.5f;
380 if(x < beta)
381 {
382 // return 1.0f-fabsf(x/beta-1.0f)^2
383 const float tmp = fabsf(x / beta - 1.0f);
384 return 1.0f - tmp * tmp;
385 }
386 else
387 {
388 const float tmp1 = (1.0f - x) / (1.0f - beta);
389 const float tmp2 = tmp1 * tmp1;
390 const float tmp3 = tmp2 * tmp1;
391 return 3.0f * tmp2 - 2.0f * tmp3;
392 }
393}
394
395static int dt_control_merge_hdr_process(dt_imageio_module_data_t *datai, const char *filename,
396 const void *const ivoid,
397 dt_colorspaces_color_profile_type_t over_type, const char *over_filename,
398 void *exif, int exif_len, int32_t imgid, int num, int total,
399 dt_dev_pixelpipe_t *pipe, const gboolean export_masks)
400{
402 dt_control_merge_hdr_t *d = data->d;
403
404 // just take a copy. also do it after blocking read, so filters will make sense.
405 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
406 const dt_image_t image = *img;
408
409 if(IS_NULL_PTR(d->pixels))
410 {
411 d->first_imgid = imgid;
412 d->first_filter = image.dsc.filters;
413 // sensor layout is just passed on to be written to dng.
414 // we offset it to the crop of the image here, so we don't
415 // need to load in the FCxtrans dependency into the dng writer.
416 // for some stupid reason the dng needs this layout wrt cropped
417 // offsets, not globally.
418 dt_iop_roi_t roi = {0};
419 roi.x = image.crop_x;
420 roi.y = image.crop_y;
421 for(int j=0;j<6;j++)
422 for(int i = 0; i < 6; i++) d->first_xtrans[j][i] = FCxtrans(j, i, &roi, image.dsc.xtrans);
423 d->pixels = calloc((size_t)datai->width * datai->height, sizeof(float));
424 d->weight = calloc((size_t)datai->width * datai->height, sizeof(float));
425 d->wd = datai->width;
426 d->ht = datai->height;
427 d->orientation = image.orientation;
428 for(int i = 0; i < 3; i++)
429 d->wb_coeffs[i] = image.wb_coeffs[i];
430 for(int k=0; k<4; k++)
431 for(int i=0; i<3; i++)
432 d->adobe_XYZ_to_CAM[k][i] = image.adobe_XYZ_to_CAM[k][i];
433 }
434
435 if(image.dsc.filters == 0u || image.dsc.channels != 1 || image.dsc.datatype != TYPE_UINT16)
436 {
437 dt_control_log(_("exposure bracketing only works on raw images."));
438 d->abort = TRUE;
439 return 1;
440 }
441 else if(datai->width != d->wd || datai->height != d->ht || d->first_filter != image.dsc.filters
442 || d->orientation != image.orientation)
443 {
444 dt_control_log(_("images have to be of same size and orientation!"));
445 d->abort = TRUE;
446 return 1;
447 }
448
449 // if no valid exif data can be found, assume peleng fisheye at f/16, 8mm, with half of the light lost in
450 // the system => f/22
451 const float eap = image.exif_aperture > 0.0f ? image.exif_aperture : 22.0f;
452 const float efl = image.exif_focal_length > 0.0f ? image.exif_focal_length : 8.0f;
453 const float rad = .5f * efl / eap;
454 const float aperture = M_PI * rad * rad;
455 const float iso = image.exif_iso > 0.0f ? image.exif_iso : 100.0f;
456 const float exp = image.exif_exposure > 0.0f ? image.exif_exposure : 1.0f;
457 const float cal = 100.0f / (aperture * exp * iso);
458 // about proportional to how many photons we can expect from this shot:
459 const float photoncnt = 100.0f * aperture * exp / iso;
460 float saturation = 1.0f;
461 d->whitelevel = fmaxf(d->whitelevel, saturation * cal);
462 __OMP_PARALLEL_FOR__(collapse(2))
463 for(int y = 0; y < d->ht; y++)
464 for(int x = 0; x < d->wd; x++)
465 {
466 // read unclamped raw value with subtracted black and rescaled to 1.0 saturation.
467 // this is the output of the rawprepare iop.
468 const float in = ((float *)ivoid)[x + d->wd * y];
469 // weights based on siggraph 12 poster
470 // zijian zhu, zhengguo li, susanto rahardja, pasi fraenti
471 // 2d denoising factor for high dynamic range imaging
472 float w = photoncnt;
473
474 // need some safety margin due to upsampling and 16-bit quantization + dithering?
475 float offset = 3000.0f / (float)UINT16_MAX;
476
477 // cannot do an envelope based on single pixel values here, need to get
478 // maximum value of all color channels. to find that, go through the
479 // pattern block (we conservatively do a 3x3 for bayer or xtrans):
480 int xx = x & ~1, yy = y & ~1;
481 float M = 0.0f, m = FLT_MAX;
482 if(xx < d->wd - 2 && yy < d->ht - 2)
483 {
484 for(int i = 0; i < 3; i++)
485 for(int j = 0; j < 3; j++)
486 {
487 M = MAX(M, ((float *)ivoid)[xx + i + d->wd * (yy + j)]);
488 m = MIN(m, ((float *)ivoid)[xx + i + d->wd * (yy + j)]);
489 }
490 // move envelope a little to allow non-zero weight even for clipped regions.
491 // this is because even if the 2x2 block is clipped somewhere, the other channels
492 // might still prove useful. we'll check for individual channel saturation below.
493 w *= d->epsw + envelope((M + offset) / saturation);
494 }
495
496 if(M + offset >= saturation)
497 {
498 if(d->weight[x + d->wd * y] <= 0.0f)
499 { // only consider saturated pixels in case we have nothing better:
500 if(d->weight[x + d->wd * y] == 0 || m < -d->weight[x + d->wd * y])
501 {
502 if(m + offset >= saturation)
503 d->pixels[x + d->wd * y] = 1.0f; // let's admit we were completely clipped, too
504 else
505 d->pixels[x + d->wd * y] = in * cal / d->whitelevel;
506 d->weight[x + d->wd * y]
507 = -m; // could use -cal here, but m is per pixel and safer for varying illumination conditions
508 }
509 }
510 // else silently ignore, others have filled in a better color here already
511 }
512 else
513 {
514 if(d->weight[x + d->wd * y] <= 0.0)
515 { // cleanup potentially blown highlights from earlier images
516 d->pixels[x + d->wd * y] = 0.0f;
517 d->weight[x + d->wd * y] = 0.0f;
518 }
519 d->pixels[x + d->wd * y] += w * in * cal;
520 d->weight[x + d->wd * y] += w;
521 }
522 }
523
524 return 0;
525}
526
528{
530 GList *t = params->index;
531 const guint total = g_list_length(t);
532 char message[512] = { 0 };
533 double fraction = 0;
534 snprintf(message, sizeof(message), ngettext("merging %d image", "merging %d images", total), total);
535
537
539
543 .write_image = dt_control_merge_hdr_process };
544
546
547 int num = 1;
548 while(t)
549 {
550 if(d.abort) goto end;
551
552 const int32_t imgid = GPOINTER_TO_INT(t->data);
553
554 const gboolean is_scaling =
555 dt_conf_is_equal("plugins/lighttable/export/resizing", "scaling");
556
557 dt_imageio_export_with_flags(imgid, "unused", &buf, (dt_imageio_module_data_t *)&dat, TRUE, FALSE, FALSE, is_scaling,
558 FALSE, "pre:rawprepare", FALSE, FALSE, DT_COLORSPACE_NONE, NULL, DT_INTENT_LAST, NULL,
559 NULL, num, total, NULL, NULL);
560
561 t = g_list_next(t);
562
563 /* update the progress bar */
564 fraction += 1.0 / (total + 1);
565 dt_control_job_set_progress(job, fraction);
566 num++;
567 }
568
569 if(d.abort) goto end;
570
571// normalize by white level to make clipping at 1.0 work as expected
573 for(size_t k = 0; k < (size_t)d.wd * d.ht; k++)
574 {
575 if(d.weight[k] > 0.0) d.pixels[k] = fmaxf(0.0f, d.pixels[k] / (d.whitelevel * d.weight[k]));
576 }
577
578 // output hdr as digital negative with exif data.
579 uint8_t *exif = NULL;
580 char pathname[DT_PATH_MAX] = { 0 };
581 gboolean from_cache = TRUE;
582 dt_image_full_path(d.first_imgid, pathname, sizeof(pathname), &from_cache, __FUNCTION__);
583
584 // last param is dng mode
585 const int exif_len = dt_exif_read_blob(&exif, pathname, d.first_imgid, 0, d.wd, d.ht, 1);
586 char *c = pathname + safe_strlen(pathname);
587 while(*c != '.' && c > pathname) c--;
588 g_strlcpy(c, "-hdr.dng", sizeof(pathname) - (c - pathname));
589 dt_imageio_write_dng(pathname,
590 d.pixels,
591 d.wd,
592 d.ht,
593 exif,
594 exif_len,
595 d.first_filter,
596 (const uint8_t (*)[6])d.first_xtrans,
597 1.0f,
598 (const float (*))d.wb_coeffs,
599 d.adobe_XYZ_to_CAM);
600 dt_free(exif);
601
603
604 while(*c != '/' && c > pathname) c--;
605 dt_control_log(_("wrote merged HDR `%s'"), c + 1);
606
607 // import new image
608 gchar *directory = g_path_get_dirname((const gchar *)pathname);
609 dt_film_t film;
610 const int filmid = dt_film_new(&film, directory);
611 const uint32_t imageid = dt_image_import(filmid, pathname, TRUE);
613
614 // refresh the thumbtable view
616 g_list_prepend(NULL, GINT_TO_POINTER(imageid)));
619
620end:
621 dt_free(d.pixels);
622 dt_free(d.weight);
623
624 return 0;
625}
626
628{
630 GList *t = params->index;
631 const guint total = g_list_length(t);
632 double fraction = 0.0f;
633 char message[512] = { 0 };
634
636
637 snprintf(message, sizeof(message), ngettext("duplicating %d image", "duplicating %d images", total), total);
639 while(t)
640 {
641 const int32_t imgid = GPOINTER_TO_INT(t->data);
642 // Defer exposing the duplicate to the collection/lighttable grid until its history is
643 // settled below -- otherwise the grid can generate and cache a thumbnail from the
644 // still-historyless row before the copy/delete below ever runs.
645 const int newimgid = dt_image_duplicate_no_reload(imgid);
646 if(newimgid != UNKNOWN_IMAGE)
647 {
648 if(GPOINTER_TO_INT(params->data))
650 else
652
654 }
655 t = g_list_next(t);
656 fraction += 1.0 / total;
657 dt_control_job_set_progress(job, fraction);
658 }
659
661
664 return 0;
665}
666
668{
671 GList *t = params->index;
672 const guint total = g_list_length(t);
673 double fraction = 0.0f;
674 char message[512] = { 0 };
675
677
678 snprintf(message, sizeof(message), ngettext("flipping %d image", "flipping %d images", total), total);
680 while(t)
681 {
682 const int32_t imgid = GPOINTER_TO_INT(t->data);
683 dt_image_flip(imgid, transform);
684 t = g_list_next(t);
685 fraction += 1.0 / total;
686 dt_control_job_set_progress(job, fraction);
687 }
688
691 return 0;
692}
694{
696 const int32_t mode = params->flag;
697 GList *t = params->index;
698 const guint total = g_list_length(t);
699 char message[512] = { 0 };
700 double fraction = 0.0f;
701
703
704 if(mode == 0)
705 snprintf(message, sizeof(message), ngettext("set %d color image", "setting %d color images", total), total);
706 else
707 snprintf(message, sizeof(message), ngettext("set %d monochrome image", "setting %d monochrome images", total), total);
708
710 while(t)
711 {
712 const int32_t imgid = GPOINTER_TO_INT(t->data);
713
714 if(imgid >= 0)
715 {
716 dt_image_set_monochrome_flag(imgid, mode == 2);
717 }
718 else
719 fprintf(stderr,"[dt_control_monochrome_images_job_run] got illegal imgid %i\n", imgid);
720
721 t = g_list_next(t);
722 fraction += 1.0 / total;
723 dt_control_job_set_progress(job, fraction);
724 }
725
727
729 g_list_copy(params->index));
731 return 0;
732}
733
734// Regenerate .xmp files for any surviving duplicate at each of `pathnames` (owned, g_strdup'd
735// full paths from _get_full_pathname(); this call takes ownership and frees them), batched into
736// a single dt_control_save_xmps() job. dt_image_synch_all_xmp() spawns one DT_JOB_QUEUE_USER_FG
737// job per path -- fine for the handful of images a normal removal/delete touches, but for a large
738// recursive folder removal it flooded that same queue (the one this job itself runs on) with one
739// tiny job per path, each spawning its own GUI progress bar: stalled the app for minutes, looking
740// like an infinite loop.
741static void _resync_xmp_duplicates(GList *pathnames)
742{
743 GList *resync_imgids = NULL;
745 {
746 for(GList *l = pathnames; l; l = g_list_next(l))
747 {
748 const int32_t dup_imgid = dt_image_get_id_full_path((const char *)l->data);
749 if(dup_imgid != UNKNOWN_IMAGE) resync_imgids = g_list_prepend(resync_imgids, GINT_TO_POINTER(dup_imgid));
750 }
751 }
752 g_list_free_full(pathnames, dt_free_gpointer);
753 if(!IS_NULL_PTR(resync_imgids)) dt_control_save_xmps(resync_imgids, FALSE);
754 g_list_free(resync_imgids);
755}
756
758{
760 GList *t = params->index;
761 const guint total = g_list_length(t);
762 char message[512] = { 0 };
763 snprintf(message, sizeof(message), ngettext("removing %d image", "removing %d images", total), total);
765
766 // check that we can safely remove the image
767 gboolean remove_ok = TRUE;
769 for(GList *l = local_copies; l; l = g_list_next(l))
770 {
771 if(!dt_image_safe_remove(GPOINTER_TO_INT(l->data)))
772 {
773 remove_ok = FALSE;
774 break;
775 }
776 }
777 g_list_free(local_copies);
778
779 if(!remove_ok)
780 {
781 dt_control_log(_("cannot remove local copy when the original file is not accessible."));
782 return 0;
783 }
784
785 // update remove status
787
789
790 // We need a list of files to regenerate .xmp files if there are duplicates
792
793 /* One undo record per image, grouped so Ctrl+Z takes the whole batch back at once. The
794 * files stay on disk, but the database rows do not, and neither the edit nor the tags nor
795 * the group membership they carry can be reconstructed from the file -- so they are staged
796 * before deletion rather than re-imported after. */
798
799 double fraction = 0.0f;
800 while(t)
801 {
802 int32_t imgid = GPOINTER_TO_INT(t->data);
804 t = g_list_next(t);
805 fraction += 1.0 / total;
806 dt_control_job_set_progress(job, fraction);
807 }
808
810
814 g_list_copy(params->index));
817
818 return 0;
819}
820
822{
824 const char *filename;
825 const char *error_message;
826
828
829 dt_pthread_mutex_t mutex;
830 pthread_cond_t cond;
832
840
849
850static gboolean _dt_delete_dialog_main_thread(gpointer user_data)
851{
852 _dt_delete_modal_dialog_t* modal_dialog = (_dt_delete_modal_dialog_t*)user_data;
853 dt_pthread_mutex_lock(&modal_dialog->mutex);
854
855 GtkWidget *dialog = gtk_message_dialog_new(
856 GTK_WINDOW(dt_gui_main_window()),
857 GTK_DIALOG_DESTROY_WITH_PARENT,
858 GTK_MESSAGE_QUESTION,
859 GTK_BUTTONS_NONE,
860 modal_dialog->send_to_trash
861 ? _("could not send %s to trash.%s%s")
862 : _("could not physically delete %s.%s%s"),
863 modal_dialog->filename,
864 !IS_NULL_PTR(modal_dialog->error_message) ? "\n" : "",
865 !IS_NULL_PTR(modal_dialog->error_message) ? modal_dialog->error_message : "");
866#ifdef GDK_WINDOWING_QUARTZ
868#endif
869
870 if(modal_dialog->send_to_trash)
871 {
872 gtk_dialog_add_button(GTK_DIALOG(dialog), _("physically delete"), _DT_DELETE_DIALOG_CHOICE_DELETE);
873 gtk_dialog_add_button(GTK_DIALOG(dialog), _("physically delete all files"), _DT_DELETE_DIALOG_CHOICE_DELETE_ALL);
874 }
875 gtk_dialog_add_button(GTK_DIALOG(dialog), _("only remove from the image library"), _DT_DELETE_DIALOG_CHOICE_REMOVE);
876 gtk_dialog_add_button(GTK_DIALOG(dialog), _("skip to next file"), _DT_DELETE_DIALOG_CHOICE_CONTINUE);
877 gtk_dialog_add_button(GTK_DIALOG(dialog), _("stop process"), _DT_DELETE_DIALOG_CHOICE_STOP);
878
879 gtk_window_set_title(
880 GTK_WINDOW(dialog),
881 modal_dialog->send_to_trash
882 ? _("trashing error")
883 : _("deletion error"));
884 modal_dialog->dialog_result = gtk_dialog_run(GTK_DIALOG(dialog));
885 gtk_widget_destroy(dialog);
886
887 pthread_cond_signal(&modal_dialog->cond);
888
889 dt_pthread_mutex_unlock(&modal_dialog->mutex);
890
891 // Don't call again on next idle time
892 return FALSE;
893}
894
895static gint _dt_delete_file_display_modal_dialog(int send_to_trash, const char *filename, const char *error_message)
896{
897 _dt_delete_modal_dialog_t modal_dialog;
898 modal_dialog.send_to_trash = send_to_trash;
899 modal_dialog.filename = filename;
900 modal_dialog.error_message = error_message;
901
902 modal_dialog.dialog_result = GTK_RESPONSE_NONE;
903
904 dt_pthread_mutex_init(&modal_dialog.mutex, NULL);
905 pthread_cond_init(&modal_dialog.cond, NULL);
906
907 dt_pthread_mutex_lock(&modal_dialog.mutex);
908
909 gdk_threads_add_idle(_dt_delete_dialog_main_thread, &modal_dialog);
910 while (modal_dialog.dialog_result == GTK_RESPONSE_NONE)
911 dt_pthread_cond_wait(&modal_dialog.cond, &modal_dialog.mutex);
912
913 dt_pthread_mutex_unlock(&modal_dialog.mutex);
914 dt_pthread_mutex_destroy(&modal_dialog.mutex);
915 pthread_cond_destroy(&modal_dialog.cond);
916
917 return modal_dialog.dialog_result;
918}
919
920static enum _dt_delete_status delete_file_from_disk(const char *filename, gboolean *delete_on_trash_error)
921{
922 enum _dt_delete_status delete_status = _DT_DELETE_STATUS_UNKNOWN;
923
924 GFile *gfile = g_file_new_for_path(filename);
925 int send_to_trash = dt_conf_get_bool("send_to_trash");
926
927 while (delete_status == _DT_DELETE_STATUS_UNKNOWN)
928 {
929 gboolean delete_success = FALSE;
930 GError *gerror = NULL;
931 if(send_to_trash)
932 {
933#ifdef __APPLE__
934 delete_success = dt_osx_file_trash(filename, &gerror);
935#elif defined(_WIN32)
936 delete_success = dt_win_file_trash(gfile, NULL /*cancellable*/, &gerror);
937#else
938 delete_success = g_file_trash(gfile, NULL /*cancellable*/, &gerror);
939#endif
940 }
941 else
942 {
943 delete_success = g_file_delete(gfile, NULL /*cancellable*/, &gerror);
944 }
945
946 // Delete is a success or the file does not exists: OK to remove from darktable
947 if(delete_success
948 || g_error_matches(gerror, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
949 {
950 delete_status = _DT_DELETE_STATUS_OK_TO_REMOVE;
951 }
952 else if(send_to_trash && *delete_on_trash_error)
953 {
954 // Loop again, this time delete instead of trashing
955 delete_status = _DT_DELETE_STATUS_UNKNOWN;
956 send_to_trash = FALSE;
957 }
958 else
959 {
960 const char *filename_display = NULL;
961 GFileInfo *gfileinfo = g_file_query_info(
962 gfile,
963 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
964 G_FILE_QUERY_INFO_NONE,
965 NULL /*cancellable*/,
966 NULL /*error*/);
967 if(!IS_NULL_PTR(gfileinfo))
968 filename_display = g_file_info_get_attribute_string(
969 gfileinfo,
970 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
971
973 send_to_trash,
974 IS_NULL_PTR(filename_display) ? filename : filename_display,
975 IS_NULL_PTR(gerror) ? NULL : gerror->message);
976 g_object_unref(gfileinfo);
977 if(send_to_trash && res == _DT_DELETE_DIALOG_CHOICE_DELETE)
978 {
979 // Loop again, this time delete instead of trashing
980 delete_status = _DT_DELETE_STATUS_UNKNOWN;
981 send_to_trash = FALSE;
982 }
983 else if(send_to_trash && res == _DT_DELETE_DIALOG_CHOICE_DELETE_ALL)
984 {
985 // Loop again, this time delete instead of trashing
986 delete_status = _DT_DELETE_STATUS_UNKNOWN;
987 send_to_trash = FALSE;
988 *delete_on_trash_error = TRUE;
989 }
991 {
992 delete_status = _DT_DELETE_STATUS_OK_TO_REMOVE;
993 }
995 {
996 delete_status = _DT_DELETE_STATUS_SKIP_FILE;
997 }
998 else
999 {
1000 delete_status = _DT_DELETE_STATUS_STOP_PROCESSING;
1001 }
1002 }
1003 if(!IS_NULL_PTR(gerror))
1004 g_error_free(gerror);
1005 }
1006
1007 if(!IS_NULL_PTR(gfile))
1008 g_object_unref(gfile);
1009
1010 return delete_status;
1011}
1012
1013
1015{
1017 GList *t = params->index;
1018
1019 const guint total = g_list_length(t);
1020 double fraction = 0.0f;
1021 char message[512] = { 0 };
1022 gboolean delete_on_trash_error = FALSE;
1023 if(dt_conf_get_bool("send_to_trash"))
1024 snprintf(message, sizeof(message), ngettext("trashing %d image", "trashing %d images", total), total);
1025 else
1026 snprintf(message, sizeof(message), ngettext("deleting %d image", "deleting %d images", total), total);
1028
1029
1031
1032 // We need a list of files to regenerate .xmp files if there are duplicates
1034 while(t)
1035 {
1036 enum _dt_delete_status delete_status = _DT_DELETE_STATUS_UNKNOWN;
1037 const int32_t imgid = GPOINTER_TO_INT(t->data);
1038 char filename[DT_PATH_MAX] = { 0 };
1039 gboolean from_cache = FALSE;
1040 dt_image_full_path(imgid, filename, sizeof(filename), &from_cache, __FUNCTION__);
1041
1042#ifdef _WIN32
1043 char *dirname = g_path_get_dirname(filename);
1044#endif
1045
1046 // same row set the duplicate list returns: every image sharing this one's roll and filename
1047 GList *dups = dt_image_repository_get_duplicate_ids(imgid);
1048 const int duplicates = g_list_length(dups);
1049 g_list_free(dups);
1050
1051 // remove from disk:
1052 if(duplicates == 1)
1053 {
1054 // first check for local copies, never delete a file whose original file is not accessible
1055 if(dt_image_local_copy_reset(imgid))
1056 goto delete_next_file;
1057
1058 GList *one = g_list_prepend(NULL, GINT_TO_POINTER(imgid));
1060 g_list_free(one);
1061 dt_image_remove(imgid);
1062
1063 // there are no further duplicates so we can remove the source data file
1064 delete_status = delete_file_from_disk(filename, &delete_on_trash_error);
1065 if(delete_status != _DT_DELETE_STATUS_OK_TO_REMOVE)
1066 goto delete_next_file;
1067
1068 // all sidecar files - including left-overs - can be deleted;
1069 // left-overs can result when previously duplicates have been REMOVED;
1070 // no need to keep them as the source data file is gone.
1071
1072 GList *files = dt_image_find_xmps(filename);
1073
1074 for(GList *file_iter = files; file_iter; file_iter = g_list_next(file_iter))
1075 {
1076 delete_status = delete_file_from_disk(file_iter->data, &delete_on_trash_error);
1077 if(delete_status != _DT_DELETE_STATUS_OK_TO_REMOVE)
1078 break;
1079 }
1080
1081 g_list_free_full(files, dt_free_gpointer);
1082 files = NULL;
1083 }
1084 else
1085 {
1086 // don't remove the actual source data if there are further duplicates using it;
1087 // just delete the xmp file of the duplicate selected.
1088
1089 dt_image_path_append_version(imgid, filename, sizeof(filename));
1090 g_strlcat(filename, ".xmp", sizeof(filename));
1091
1092 // remove image from db first ...
1093 GList *one = g_list_prepend(NULL, GINT_TO_POINTER(imgid));
1095 g_list_free(one);
1096 dt_image_remove(imgid);
1097
1098 // ... and delete afterwards because removing will re-write the XMP
1099 delete_status = delete_file_from_disk(filename, &delete_on_trash_error);
1100 }
1101
1102delete_next_file:
1103#ifdef _WIN32
1104 dt_free(dirname);
1105#endif
1106 t = g_list_next(t);
1107 fraction += 1.0 / total;
1108 dt_control_job_set_progress(job, fraction);
1109 if(delete_status == _DT_DELETE_STATUS_STOP_PROCESSING)
1110 break;
1111 }
1112
1113
1115 list = NULL;
1118 g_list_copy(params->index));
1121 return 0;
1122}
1123
1125{
1127 GList *t = params->index;
1128 struct dt_gpx_t *gpx = NULL;
1129 uint32_t cntr = 0;
1130 const dt_control_gpx_apply_t *d = params->data;
1131 const gchar *filename = d->filename;
1132 const gchar *tz = d->tz;
1133 /* do we have any selected images */
1134 if(IS_NULL_PTR(t)) goto bail_out;
1135
1136 /* try parse the gpx data */
1137 gpx = dt_gpx_new(filename);
1138 if(IS_NULL_PTR(gpx))
1139 {
1140 dt_control_log(_("failed to parse GPX file"));
1141 goto bail_out;
1142 }
1143
1144 GTimeZone *tz_camera = (IS_NULL_PTR(tz)) ? g_time_zone_new_utc() : g_time_zone_new(tz);
1145 if(IS_NULL_PTR(tz_camera)) goto bail_out;
1146
1147 GList *imgs = NULL;
1148 GArray *gloc = g_array_new(FALSE, FALSE, sizeof(dt_image_geoloc_t));
1149 /* go thru each selected image and lookup location in gpx */
1150 do
1151 {
1152 dt_image_geoloc_t geoloc;
1153 int32_t imgid = GPOINTER_TO_INT(t->data);
1154
1155 /* get image */
1156 const dt_image_t *cimg = dt_image_cache_get(imgid, 'r');
1157 if(IS_NULL_PTR(cimg)) continue;
1158
1159 GDateTime *exif_time = dt_datetime_img_to_gdatetime(cimg, tz_camera);
1160
1161 /* release the lock */
1163 if(IS_NULL_PTR(exif_time)) continue;
1164 GDateTime *utc_time = g_date_time_to_timezone(exif_time, dt_datetime_utc_tz());
1165 g_date_time_unref(exif_time);
1166 if(IS_NULL_PTR(utc_time)) continue;
1167
1168 /* only update image location if time is within gpx tack range */
1169 if(dt_gpx_get_location(gpx, utc_time, &geoloc))
1170 {
1171 // takes the option to include the grouped images
1172 GList *grps = dt_grouping_get_group_images(imgid);
1173 for(GList *grp = grps; grp; grp = g_list_next(grp))
1174 {
1175 imgs = g_list_prepend(imgs, grp->data);
1176 g_array_append_val(gloc, geoloc);
1177 cntr++;
1178 }
1179 g_list_free(grps);
1180 grps = NULL;
1181 }
1182 g_date_time_unref(utc_time);
1183 } while((t = g_list_next(t)) != NULL);
1184 imgs = g_list_reverse(imgs);
1185
1187
1188 dt_control_log(ngettext("applied matched GPX location onto %d image",
1189 "applied matched GPX location onto %d images", cntr), cntr);
1190
1191 g_time_zone_unref(tz_camera);
1192 dt_gpx_destroy(gpx);
1193 g_array_unref(gloc);
1195 return 0;
1196
1197bail_out:
1198 if(!IS_NULL_PTR(gpx)) dt_gpx_destroy(gpx);
1199
1200 return 1;
1201}
1202
1204{
1205 return _generic_dt_control_fileop_images_job_run(job, &dt_image_move, _("moving %d image"),
1206 _("moving %d images"));
1207}
1208
1210{
1211 return _generic_dt_control_fileop_images_job_run(job, &dt_image_copy, _("copying %d image"),
1212 _("copying %d images"));
1213}
1214
1216{
1218 GList *t = params->index;
1219 guint tagid = 0;
1220 const guint total = g_list_length(t);
1221 double fraction = 0;
1222 const gboolean is_copy = params->flag == 1;
1223 char message[512] = { 0 };
1224
1225 if(is_copy)
1226 snprintf(message, sizeof(message),
1227 ngettext("creating local copy of %d image", "creating local copies of %d images", total), total);
1228 else
1229 snprintf(message, sizeof(message),
1230 ngettext("removing local copy of %d image", "removing local copies of %d images", total), total);
1231
1232 dt_control_log("%s", message);
1234
1235 dt_tag_new("darktable|local-copy", &tagid);
1236
1237 gboolean tag_change = FALSE;
1239 {
1240 const int32_t imgid = GPOINTER_TO_INT(t->data);
1241 if(is_copy)
1242 {
1243 if(dt_image_local_copy_set(imgid) == 0)
1244 {
1245 if(dt_tag_attach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1246 }
1247 }
1248 else
1249 {
1250 if(dt_image_local_copy_reset(imgid) == 0)
1251 {
1252 if(dt_tag_detach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1253 }
1254 }
1255 t = g_list_next(t);
1256
1257 fraction += 1.0 / total;
1258 dt_control_job_set_progress(job, fraction);
1259 }
1260
1262 g_list_copy(params->index));
1266 return 0;
1267}
1268
1270{
1272 GList *t = params->index;
1273 const guint total = g_list_length(t);
1274 double fraction = 0.0f;
1275 char message[512] = { 0 };
1276 snprintf(message, sizeof(message), ngettext("refreshing info for %d image", "refreshing info for %d images", total), total);
1278 while(t)
1279 {
1280 const int32_t imgid = GPOINTER_TO_INT(t->data);
1281 if(imgid >= 0)
1282 {
1283 gboolean from_cache = TRUE;
1284 char sourcefile[DT_PATH_MAX];
1285 dt_image_full_path(imgid, sourcefile, sizeof(sourcefile), &from_cache, __FUNCTION__);
1286
1287 dt_image_t *img = dt_image_cache_get(imgid, 'w');
1288 if(!IS_NULL_PTR(img))
1289 {
1290 // Re-sync the file-derived classification flags (LDR/HDR/RAW/sRAW, mosaic, monochrome,
1291 // 4bayer, additional-DNG-tags, buffer-resolved) with the actual file, repairing entries
1292 // that were persisted with stale/corrupted flags in the DB. We must *clear* these bits
1293 // before dt_exif_read(), because it only re-seeds the LDR/HDR/RAW class when none is set
1294 // yet (see exif.cc): leaving a stale DT_IMAGE_HDR in place would suppress the re-read.
1295 // Everything else in img->flags is DB-only / user state that the file knows nothing about
1296 // (star rating & reject, local copy, audio/txt sidecar, preset bookkeeping, monochrome
1297 // workflow), so it is preserved verbatim.
1298 const uint32_t file_class_flags
1302 img->flags &= ~file_class_flags;
1303 dt_exif_read(img, sourcefile);
1305 }
1306 else
1307 fprintf(stderr,"[dt_control_refresh_exif_run] couldn't dt_image_cache_get for imgid %i\n", imgid);
1308
1310 }
1311 else
1312 fprintf(stderr,"[dt_control_refresh_exif_run] illegal imgid %i\n", imgid);
1313
1314 t = g_list_next(t);
1315 fraction += 1.0 / total;
1316 dt_control_job_set_progress(job, fraction);
1317 }
1319 g_list_copy(params->index));
1322 return 0;
1323}
1324
1325
1327{
1329 dt_control_export_t *settings = (dt_control_export_t *)params->data;
1330 GList *t = params->index;
1332 g_assert(mformat);
1334 g_assert(mstorage);
1335 dt_imageio_module_data_t *sdata = settings->sdata;
1336
1337 gboolean tag_change = FALSE;
1338
1339 // get a thread-safe fdata struct (one jpeg struct per thread etc):
1340 dt_imageio_module_data_t *fdata = mformat->get_params(mformat);
1341
1342 if(mstorage->initialize_store)
1343 {
1344 if(mstorage->initialize_store(mstorage, sdata, &mformat, &fdata, &t, TRUE))
1345 {
1346 // bail out, something went wrong
1347 goto end;
1348 }
1349 mformat->set_params(mformat, fdata, mformat->params_size(mformat));
1350 mstorage->set_params(mstorage, sdata, mstorage->params_size(mstorage));
1351 }
1352
1353 // Get max dimensions...
1354 uint32_t w, h, fw, fh, sw, sh;
1355 fw = fh = sw = sh = 0;
1356 mstorage->dimension(mstorage, sdata, &sw, &sh);
1357 mformat->dimension(mformat, fdata, &fw, &fh);
1358
1359 if(sw == 0 || fw == 0)
1360 w = sw > fw ? sw : fw;
1361 else
1362 w = sw < fw ? sw : fw;
1363
1364 if(sh == 0 || fh == 0)
1365 h = sh > fh ? sh : fh;
1366 else
1367 h = sh < fh ? sh : fh;
1368
1369 const guint total = g_list_length(t);
1370 if(total > 0)
1371 dt_control_log(ngettext("exporting %d image..", "exporting %d images..", total), total);
1372 else
1373 dt_control_log(_("no image to export"));
1374
1375 double fraction = 0;
1376
1377 // set up the fdata struct
1378 fdata->max_width = (settings->max_width != 0 && w != 0) ? MIN(w, settings->max_width) : MAX(w, settings->max_width);
1379 fdata->max_height = (settings->max_height != 0 && h != 0) ? MIN(h, settings->max_height) : MAX(h, settings->max_height);
1380 g_strlcpy(fdata->style, settings->style, sizeof(fdata->style));
1381 // Invariant: the tagid for 'darktable|changed' will not change while this function runs. Is this a
1382 // sensible assumption?
1383 guint tagid = 0, etagid = 0;
1384 dt_tag_new("darktable|exported", &etagid);
1385
1386 dt_export_metadata_t metadata;
1387 metadata.flags = 0;
1388 metadata.list = dt_util_str_to_glist("\1", settings->metadata_export);
1389 if(metadata.list)
1390 {
1391 metadata.flags = strtol(metadata.list->data, NULL, 16);
1392 metadata.list = g_list_remove(metadata.list, metadata.list->data);
1393 }
1394
1396 {
1397 const int32_t imgid = GPOINTER_TO_INT(t->data);
1398 t = g_list_next(t);
1399 const guint num = total - g_list_length(t);
1400
1401 // progress message
1402 char message[512] = { 0 };
1403 snprintf(message, sizeof(message), _("exporting %d / %d to %s"), num, total, mstorage->name(mstorage));
1404 // update the message. initialize_store() might have changed the number of images
1406
1407 // remove 'changed' tag from image
1408 if(dt_tag_detach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1409 // make sure the 'exported' tag is set on the image
1410 if(dt_tag_attach(etagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1411
1412 /* register export timestamp in cache */
1414
1415 // check if image still exists:
1416 const dt_image_t *image = dt_image_cache_get((int32_t)imgid, 'r');
1417 if(image)
1418 {
1419 char imgfilename[DT_PATH_MAX] = { 0 };
1420 gboolean from_cache = TRUE;
1421 dt_image_full_path(image->id, imgfilename, sizeof(imgfilename), &from_cache, __FUNCTION__);
1422 if(!g_file_test(imgfilename, G_FILE_TEST_IS_REGULAR))
1423 {
1424 dt_control_log(_("image `%s' is currently unavailable"), image->filename);
1425 fprintf(stderr, "image `%s' is currently unavailable\n", imgfilename);
1426 // dt_image_remove(imgid);
1428 }
1429 else
1430 {
1432 if(mstorage->store(mstorage, sdata, imgid, mformat, fdata, num, total, TRUE,
1433 settings->export_masks, settings->icc_type, settings->icc_filename, settings->icc_intent,
1434 &metadata) != 0)
1436 }
1437 }
1438
1439 fraction += 1.0 / total;
1440 if(fraction > 1.0) fraction = 1.0;
1441 dt_control_job_set_progress(job, fraction);
1442 }
1443 g_list_free_full(metadata.list, dt_free_gpointer);
1444 metadata.list = NULL;
1445
1446 if(mstorage->finalize_store) mstorage->finalize_store(mstorage, sdata);
1447
1448end:
1449 // all threads free their fdata
1450 mformat->free_params(mformat, fdata);
1451
1452 // notify the user via the window manager
1454
1456 return 0;
1457}
1458
1460{
1462 if(IS_NULL_PTR(params)) return NULL;
1463
1464 params->data = calloc(1, sizeof(dt_control_gpx_apply_t));
1465 if(IS_NULL_PTR(params->data))
1466 {
1468 return NULL;
1469 }
1470
1471 return params;
1472}
1473
1475{
1477
1478 dt_control_gpx_apply_t *data = params->data;
1479 params->data = NULL;
1480 dt_free(data->filename);
1481 dt_free(data->tz);
1482
1483 dt_free(data);
1484
1486}
1487
1488static dt_job_t *_control_gpx_apply_job_create(const gchar *filename, int32_t filmid,
1489 const gchar *tz, GList *imgs)
1490{
1492 if(IS_NULL_PTR(job)) return NULL;
1494 if(IS_NULL_PTR(params))
1495 {
1497 return NULL;
1498 }
1500
1501 if(filmid != -1)
1503 else if(!imgs)
1504 params->index = dt_act_on_get_images();
1505 else
1506 params->index = imgs;
1507 dt_control_gpx_apply_t *data = params->data;
1508 data->filename = g_strdup(filename);
1509 data->tz = g_strdup(tz);
1510
1511 return job;
1512}
1513
1514void dt_control_save_xmp(const int32_t imgid)
1515{
1518 N_("save history to XMP"),
1519 0, NULL, PROGRESS_NONE, imgid));
1520}
1521
1522void dt_control_save_xmps(const GList *imgids, const gboolean check_history)
1523{
1524 (void)check_history;
1525 if(IS_NULL_PTR(imgids)) return;
1526
1528 if(IS_NULL_PTR(job)) return;
1529
1531 if(IS_NULL_PTR(params))
1532 {
1534 return;
1535 }
1536
1537 params->index = g_list_copy((GList *)imgids);
1538 params->flag = 0;
1539
1542}
1543
1544void dt_control_save_xmps_forced(const GList *imgids)
1545{
1546 if(IS_NULL_PTR(imgids)) return;
1547
1549 if(IS_NULL_PTR(job)) return;
1550
1552 if(IS_NULL_PTR(params))
1553 {
1555 return;
1556 }
1557
1558 params->index = g_list_copy((GList *)imgids);
1559 params->flag = 1;
1560
1563}
1564
1571
1572void dt_control_gpx_apply(const gchar *filename, int32_t filmid, const gchar *tz, GList *imgs)
1573{
1575 _control_gpx_apply_job_create(filename, filmid, tz, imgs));
1576}
1577
1584
1591
1598
1600{
1602 if(cv->view(cv) != DT_VIEW_LIGHTTABLE)
1603 {
1604 dt_control_log(_("removing images from library is only possible in Lighttable view"));
1605 return FALSE;
1606 }
1607
1608 // get all selected images now, to avoid the set changing during ui interaction
1610 NULL, PROGRESS_SIMPLE, FALSE);
1611 if(dt_conf_get_bool("ask_before_remove"))
1612 {
1613 GtkWidget *dialog;
1615
1617 const int number = g_list_length(e->index);
1618 if(number == 0)
1619 {
1621 return TRUE;
1622 }
1623
1624 dialog = gtk_message_dialog_new(
1625 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1626 ngettext("Do you really want to remove %d image from Ansel library ?\nThe files will not be deleted on disk.",
1627 "Do you really want to remove %d images from Ansel library ?\nThe files will not be deletetd on disk.", number),
1628 number);
1629#ifdef GDK_WINDOWING_QUARTZ
1631#endif
1632
1633 gtk_window_set_title(GTK_WINDOW(dialog), ngettext(_("Remove image from library ?"), _("Remove images from library ?"), number));
1634 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1635 gtk_widget_destroy(dialog);
1636 if(res != GTK_RESPONSE_YES)
1637 {
1639 return FALSE;
1640 }
1641 }
1643 return TRUE;
1644}
1645
1647{
1649 if(cv->view(cv) != DT_VIEW_LIGHTTABLE)
1650 {
1651 dt_control_log(_("Deleting images from library is only possible in Lighttable view"));
1652 return;
1653 }
1654
1655 // first get all selected images, to avoid the set changing during ui interaction
1657 NULL, PROGRESS_SIMPLE, FALSE);
1658 if(dt_conf_get_bool("ask_before_delete"))
1659 {
1660 GtkWidget *dialog;
1662
1664 const int number = g_list_length(e->index);
1665
1666 // Do not show the dialog if no image is selected:
1667 if(number == 0)
1668 {
1670 return;
1671 }
1672
1673 dialog = gtk_message_dialog_new(
1674 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1675 ngettext("Do you really want to physically delete %d image ?\nThe system trash bin will be used if possible.",
1676 "Do you really want to physically delete %d images ?\nThe system trash bin will be used if possible.", number),
1677 number);
1678#ifdef GDK_WINDOWING_QUARTZ
1680#endif
1681
1682 gtk_window_set_title(GTK_WINDOW(dialog), ngettext(_("Remove image from disk ?"), _("Remove images from disk ?"), number));
1683 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1684 gtk_widget_destroy(dialog);
1685 if(res != GTK_RESPONSE_YES)
1686 {
1688 return;
1689 }
1690 }
1692}
1693
1694void dt_control_delete_image(int32_t imgid)
1695{
1697 if(cv->view(cv) != DT_VIEW_LIGHTTABLE)
1698 {
1699 dt_control_log(_("Deleting images from library is only possible in Lighttable view"));
1700 return;
1701 }
1702 // first get all selected images, to avoid the set changing during ui interaction
1704 NULL, PROGRESS_SIMPLE, imgid);
1705 int send_to_trash = dt_conf_get_bool("send_to_trash");
1706 if(dt_conf_get_bool("ask_before_delete"))
1707 {
1708 GtkWidget *dialog;
1710
1711 // Do not show the dialog if no valid image
1712 if(imgid < 1)
1713 {
1715 return;
1716 }
1717
1718 dialog = gtk_message_dialog_new(
1719 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1720 send_to_trash ? _("do you really want to physically delete selected image (using trash if possible)?")
1721 : _("do you really want to physically delete selected image from disk?"));
1722#ifdef GDK_WINDOWING_QUARTZ
1724#endif
1725
1726 gtk_window_set_title(GTK_WINDOW(dialog), _("delete image?"));
1727 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1728 gtk_widget_destroy(dialog);
1729 if(res != GTK_RESPONSE_YES)
1730 {
1732 return;
1733 }
1734 }
1736}
1737
1739{
1740 // Open file chooser dialog
1741 gchar *dir = NULL;
1743
1747 const int number = g_list_length(e->index);
1748 if(number == 0)
1749 {
1751 return;
1752 }
1753
1754 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1755 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1756 _("_select as destination"), _("_cancel"));
1757
1758 dt_conf_get_folder_to_file_chooser("ui_last/move_path", GTK_FILE_CHOOSER(filechooser));
1759 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1760 {
1761 dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1762 dt_conf_set_folder_from_file_chooser("ui_last/move_path", GTK_FILE_CHOOSER(filechooser));
1763 }
1764 g_object_unref(filechooser);
1765
1766 if(IS_NULL_PTR(dir) || !g_file_test(dir, G_FILE_TEST_IS_DIR)) goto abort;
1767
1768 // ugly, but we need to set this after constructing the job:
1770 // the job's cleanup function is responsible for freeing dir, so we don't do that here
1771
1772 if(dt_conf_get_bool("ask_before_move"))
1773 {
1774 GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1775 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1776 ngettext("do you really want to physically move %d image to %s?\n"
1777 "(all duplicates will be moved along)",
1778 "do you really want to physically move %d images to %s?\n"
1779 "(all duplicates will be moved along)",
1780 number),
1781 number, dir);
1782#ifdef GDK_WINDOWING_QUARTZ
1784#endif
1785 gtk_window_set_title(GTK_WINDOW(dialog), ngettext("move image?", "move images?", number));
1786
1787 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1788 gtk_widget_destroy(dialog);
1789
1790 if(res != GTK_RESPONSE_YES) goto abort;
1791 }
1792
1794 return;
1795
1796abort:
1797 dt_free(dir);
1799}
1800
1802{
1803 // Open file chooser dialog
1804 gchar *dir = NULL;
1809 const int number = g_list_length(e->index);
1810 if(number == 0)
1811 {
1813 return;
1814 }
1815
1816 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1817 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1818 _("_select as destination"), _("_cancel"));
1819
1820 dt_conf_get_folder_to_file_chooser("ui_last/copy_path", GTK_FILE_CHOOSER(filechooser));
1821 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1822 {
1823 dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1824 dt_conf_set_folder_from_file_chooser("ui_last/copy_path", GTK_FILE_CHOOSER(filechooser));
1825 }
1826 g_object_unref(filechooser);
1827
1828 if(IS_NULL_PTR(dir) || !g_file_test(dir, G_FILE_TEST_IS_DIR)) goto abort;
1829
1830 // ugly, but we need to set this after constructing the job:
1832 // the job's cleanup function is responsible for freeing dir, so we don't do that here
1833
1834 if(dt_conf_get_bool("ask_before_copy"))
1835 {
1836 GtkWidget *dialog = gtk_message_dialog_new(
1837 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1838 ngettext("do you really want to physically copy %d image to %s?",
1839 "do you really want to physically copy %d images to %s?", number),
1840 number, dir);
1841#ifdef GDK_WINDOWING_QUARTZ
1843#endif
1844 gtk_window_set_title(GTK_WINDOW(dialog), ngettext("copy image?", "copy images?", number));
1845
1846 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1847 gtk_widget_destroy(dialog);
1848
1849 if(res != GTK_RESPONSE_YES) goto abort;
1850 }
1851
1853 return;
1854
1855abort:
1856 dt_free(dir);
1858}
1859
1867
1875
1882
1884{
1886 if(IS_NULL_PTR(params)) return NULL;
1887
1888 params->data = calloc(1, sizeof(dt_control_export_t));
1889 if(IS_NULL_PTR(params->data))
1890 {
1892 return NULL;
1893 }
1894
1895 return params;
1896}
1897
1899{
1901
1902 dt_control_export_t *settings = (dt_control_export_t *)params->data;
1904 dt_imageio_module_data_t *sdata = settings->sdata;
1905
1906 mstorage->free_params(mstorage, sdata);
1907
1908 dt_free(settings->icc_filename);
1909 dt_free(settings->metadata_export);
1910 dt_free(params->data);
1911
1913}
1914
1915void dt_control_export(GList *imgid_list, int max_width, int max_height, int format_index, int storage_index,
1916 gboolean high_quality, gboolean export_masks, char *style,
1917 dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename,
1918 dt_iop_color_intent_t icc_intent, const gchar *metadata_export)
1919{
1921 if(IS_NULL_PTR(job)) return;
1923 if(IS_NULL_PTR(params))
1924 {
1926 return;
1927 }
1929
1930 params->index = imgid_list;
1931
1932 dt_control_export_t *data = params->data;
1933 data->max_width = max_width;
1934 data->max_height = max_height;
1935 data->format_index = format_index;
1936 data->storage_index = storage_index;
1938 g_assert(mstorage);
1939 // get shared storage param struct (global sequence counter, one picasa connection etc)
1940 dt_imageio_module_data_t *sdata = mstorage->get_params(mstorage);
1941 if(IS_NULL_PTR(sdata))
1942 {
1943 dt_control_log(_("failed to get parameters from storage module `%s', aborting export.."),
1944 mstorage->name(mstorage));
1946 return;
1947 }
1948 data->sdata = sdata;
1949 data->export_masks = export_masks;
1950 g_strlcpy(data->style, style, sizeof(data->style));
1951 data->icc_type = icc_type;
1952 data->icc_filename = g_strdup(icc_filename);
1953 data->icc_intent = icc_intent;
1954 data->metadata_export = g_strdup(metadata_export);
1955
1956 dt_control_job_add_progress(job, _("export images"), TRUE);
1958
1959 // tell the storage that we got its params for an export so it can reset itself to a safe state
1960 mstorage->export_dispatched(mstorage);
1961}
1962
1963static void _add_datetime_offset(const char *odt, const long int offset, char *ndt)
1964{
1965 // get the datetime_taken and calculate the new time
1966 GDateTime *datetime_original = dt_datetime_exif_to_gdatetime(odt, dt_datetime_utc_tz());
1967 if(IS_NULL_PTR(datetime_original))
1968 return;
1969
1970 // let's add our offset
1971 GDateTime *datetime_new = g_date_time_add(datetime_original, offset);
1972 g_date_time_unref(datetime_original);
1973
1974 if(IS_NULL_PTR(datetime_new))
1975 return;
1976 gchar *datetime = g_date_time_format(datetime_new, "%Y:%m:%d %H:%M:%S,%f");
1977 datetime[DT_DATETIME_LENGTH - 1] = '\0'; // limit to milliseconds
1978 g_date_time_unref(datetime_new);
1979
1980 if(datetime)
1981 g_strlcpy(ndt, datetime, DT_DATETIME_LENGTH);
1982 dt_free(datetime);
1983}
1984
1986{
1988 uint32_t cntr = 0;
1989 GList *t = params->index;
1990 const GTimeSpan offset = ((dt_control_datetime_t *)params->data)->offset;
1991 const char *datetime = ((dt_control_datetime_t *)params->data)->datetime;
1992 char message[512] = { 0 };
1993
1994 /* do we have any selected images and is offset != 0 */
1995 if(IS_NULL_PTR(t) || (offset == 0 && !datetime[0]))
1996 {
1997 return 1;
1998 }
1999
2000 const guint total = g_list_length(t);
2001
2002 const char *mes11 = offset ? N_("adding time offset to %d image") : N_("setting date/time of %d image");
2003 const char *mes12 = offset ? N_("adding time offset to %d images") : N_("setting date/time of %d images");
2004 snprintf(message, sizeof(message), ngettext(mes11, mes12, total), total);
2006
2007 GList *imgs = NULL;
2008 if(offset)
2009 {
2010 GArray *dtime = g_array_new(FALSE, TRUE, DT_DATETIME_LENGTH);
2011
2012 for(GList *img = t; img; img = g_list_next(img))
2013 {
2014 char odt[DT_DATETIME_LENGTH] = {0};
2015 dt_image_get_datetime(GPOINTER_TO_INT(img->data), odt);
2016 if(!odt[0]) continue;
2017
2018 char ndt[DT_DATETIME_LENGTH] = {0};
2019 _add_datetime_offset(odt, offset, ndt);
2020 if(!ndt[0]) continue;
2021
2022 // takes the option to include the grouped images
2023 GList *grps = dt_grouping_get_group_images(GPOINTER_TO_INT(img->data));
2024 for(GList *grp = grps; grp; grp = g_list_next(grp))
2025 {
2026 imgs = g_list_prepend(imgs, grp->data);
2027 g_array_append_val(dtime, ndt);
2028 cntr++;
2029 }
2030 g_list_free(grps);
2031 grps = NULL;
2032 }
2033 imgs = g_list_reverse(imgs);
2034 dt_image_set_datetimes(imgs, dtime, TRUE);
2035 }
2036 else
2037 {
2038 imgs = g_list_copy(t);
2039 // takes the option to include the grouped images
2041 cntr = g_list_length(imgs);
2042 dt_image_set_datetime(imgs, datetime, TRUE);
2043 }
2044
2045 const char *mes21 = offset ? N_("added time offset to %d image") : N_("set date/time of %d image");
2046 const char *mes22 = offset ? N_("added time offset to %d images") : N_("set date/time of %d images");
2047 dt_control_log(ngettext(mes21, mes22, cntr), cntr);
2048 return 0;
2049}
2050
2052{
2054 if(IS_NULL_PTR(params)) return NULL;
2055
2056 params->data = calloc(1, sizeof(dt_control_datetime_t));
2057 if(IS_NULL_PTR(params->data))
2058 {
2060 return NULL;
2061 }
2062
2063 return params;
2064}
2065
2067{
2069
2070 dt_free(params->data);
2071
2073}
2074
2075static dt_job_t *dt_control_datetime_job_create(const GTimeSpan offset, const char *datetime, GList *imgs)
2076{
2078 if(IS_NULL_PTR(job)) return NULL;
2080 if(IS_NULL_PTR(params))
2081 {
2083 return NULL;
2084 }
2085 dt_control_job_add_progress(job, _("time offset"), FALSE);
2087
2088 if(imgs)
2089 params->index = imgs;
2090 else
2091 params->index = dt_act_on_get_images();
2092
2093 dt_control_datetime_t *data = params->data;
2094 data->offset = offset;
2095 if(!IS_NULL_PTR(datetime))
2096 memcpy(data->datetime, datetime, sizeof(data->datetime));
2097 else
2098 data->datetime[0] = '\0';
2099 params->data = data;
2100 return job;
2101}
2102
2103void dt_control_datetime(const GTimeSpan offset, const char *datetime, GList *imgs)
2104{
2106 dt_control_datetime_job_create(offset, datetime, imgs));
2107}
2108
2109// clang-format off
2110// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2111// vim: shiftwidth=2 expandtab tabstop=2 cindent
2112// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2113// clang-format on
GList * dt_act_on_get_images()
Definition act_on.c:38
GtkWidget * dt_gui_main_window(void)
void dt_ui_notify_user()
draw user's attention
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
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
void dt_collection_deserialize(const char *buf)
Definition collection.c:763
dt_collection_t * dt_collection_get_global(void)
Definition collection.c:138
int dt_collection_update(const dt_collection_t *collection)
Definition collection.c:198
@ DT_COLLECTION_PROP_LOCAL_COPY
Definition collection.h:144
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:155
@ DT_COLLECTION_CHANGE_RELOAD
Definition collection.h:164
static const float x
const int t
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const dt_colormatrix_t M
int dt_conf_get_bool(const char *name)
void dt_conf_set_folder_from_file_chooser(const char *name, GtkFileChooser *chooser)
gboolean dt_conf_is_equal(const char *name, const char *value)
gboolean dt_conf_get_folder_to_file_chooser(const char *name, GtkFileChooser *chooser)
int32_t dt_image_move(const int32_t imgid, const int32_t filmid)
int32_t dt_image_import(const int32_t film_id, const char *filename, gboolean raise_signals)
void dt_image_path_append_version(const int32_t imgid, char *pathname, size_t pathname_len)
int32_t dt_image_copy(const int32_t imgid, const int32_t filmid)
int32_t dt_image_duplicate_no_reload(const int32_t imgid)
void dt_image_get_datetime(const int32_t imgid, char *datetime)
void dt_image_set_datetimes(const GList *imgs, const GArray *dtime, const gboolean undo_on)
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.
void dt_image_set_datetime(const GList *imgs, const char *datetime, const gboolean undo_on)
void dt_image_set_monochrome_flag(const int32_t imgid, gboolean monochrome)
void dt_image_set_images_locations(const GList *imgs, const GArray *gloc, const gboolean undo_on)
void dt_image_remove_undoable(const int32_t imgid)
Same as dt_image_remove(), and records the removal on the undo stack.
int dt_image_local_copy_set(const int32_t imgid)
dt_image_write_sidecar_result_t dt_image_write_sidecar_file(const int32_t imgid)
void dt_image_remove(const int32_t imgid)
int dt_image_local_copy_reset(const int32_t imgid)
gboolean dt_image_get_xmp_mode()
int32_t dt_image_get_id_full_path(const gchar *filename)
gboolean dt_image_safe_remove(const int32_t imgid)
dt_image_write_sidecar_result_t dt_image_write_sidecar_file_forced(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_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
static dt_job_t * _control_gpx_apply_job_create(const gchar *filename, int32_t filmid, const gchar *tz, GList *imgs)
static void _resync_xmp_duplicates(GList *pathnames)
void dt_control_move_images()
static int32_t dt_control_remove_images_job_run(dt_job_t *job)
void dt_control_refresh_exif()
static gboolean _dt_delete_dialog_main_thread(gpointer user_data)
void dt_control_duplicate_images(gboolean virgin)
void dt_control_export(GList *imgid_list, int max_width, int max_height, int format_index, int storage_index, gboolean high_quality, gboolean export_masks, char *style, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, const gchar *metadata_export)
void dt_control_flip_images(const dt_image_transform_t transform)
static int32_t dt_control_refresh_exif_run(dt_job_t *job)
static dt_job_t * dt_control_datetime_job_create(const GTimeSpan offset, const char *datetime, GList *imgs)
progress_type_t
@ PROGRESS_NONE
@ PROGRESS_SIMPLE
@ PROGRESS_CANCELLABLE
static dt_job_t * dt_control_generic_image_job_create(dt_job_execute_callback execute, const char *message, int flag, gpointer data, progress_type_t progress_type, int32_t imgid)
static dt_control_image_enumerator_t * dt_control_export_alloc()
void dt_control_gpx_apply(const gchar *filename, int32_t filmid, const gchar *tz, GList *imgs)
void dt_control_monochrome_images(const int32_t mode)
void * dt_control_image_enumerator_alloc()
void dt_control_write_sidecar_files()
static int dt_control_merge_hdr_bpp(dt_imageio_module_data_t *data)
static int32_t dt_control_export_job_run(dt_job_t *job)
void dt_control_delete_image(int32_t imgid)
static const char * dt_control_merge_hdr_mime(dt_imageio_module_data_t *data)
static int32_t dt_control_delete_images_job_run(dt_job_t *job)
void dt_control_reset_local_copy_images()
static int32_t dt_control_duplicate_images_job_run(dt_job_t *job)
static int32_t dt_control_datetime_job_run(dt_job_t *job)
void dt_control_image_enumerator_cleanup(void *p)
static int32_t dt_control_merge_hdr_job_run(dt_job_t *job)
static int32_t dt_control_local_copy_images_job_run(dt_job_t *job)
static int32_t dt_control_monochrome_images_job_run(dt_job_t *job)
static gint _dt_delete_file_display_modal_dialog(int send_to_trash, const char *filename, const char *error_message)
void dt_control_set_local_copy_images()
void dt_control_datetime(const GTimeSpan offset, const char *datetime, GList *imgs)
static void _add_datetime_offset(const char *odt, const long int offset, char *ndt)
void dt_control_save_xmp(const int32_t imgid)
static int32_t dt_control_copy_images_job_run(dt_job_t *job)
static dt_job_t * dt_control_generic_images_job_create(dt_job_execute_callback execute, const char *message, int flag, gpointer data, progress_type_t progress_type, gboolean only_visible)
static int32_t dt_control_save_xmps_job_run(dt_job_t *job)
static dt_control_image_enumerator_t * dt_control_gpx_apply_alloc()
static void dt_control_gpx_apply_job_cleanup(void *p)
void dt_control_delete_images()
static int32_t dt_control_move_images_job_run(dt_job_t *job)
gboolean dt_control_remove_images()
static int dt_control_merge_hdr_process(dt_imageio_module_data_t *datai, const char *filename, const void *const ivoid, dt_colorspaces_color_profile_type_t over_type, const char *over_filename, void *exif, int exif_len, int32_t imgid, int num, int total, dt_dev_pixelpipe_t *pipe, const gboolean export_masks)
void dt_control_merge_hdr()
static float envelope(const float xx)
static int32_t dt_control_flip_images_job_run(dt_job_t *job)
_dt_delete_status
@ _DT_DELETE_STATUS_SKIP_FILE
@ _DT_DELETE_STATUS_STOP_PROCESSING
@ _DT_DELETE_STATUS_OK_TO_REMOVE
@ _DT_DELETE_STATUS_UNKNOWN
static void * dt_control_datetime_alloc()
void dt_control_copy_images()
static void dt_control_datetime_job_cleanup(void *p)
void dt_control_save_xmps(const GList *imgids, const gboolean check_history)
static enum _dt_delete_status delete_file_from_disk(const char *filename, gboolean *delete_on_trash_error)
_dt_delete_dialog_choice
@ _DT_DELETE_DIALOG_CHOICE_REMOVE
@ _DT_DELETE_DIALOG_CHOICE_CONTINUE
@ _DT_DELETE_DIALOG_CHOICE_DELETE_ALL
@ _DT_DELETE_DIALOG_CHOICE_STOP
@ _DT_DELETE_DIALOG_CHOICE_DELETE
void dt_control_save_xmps_forced(const GList *imgids)
static void dt_control_image_enumerator_job_film_init(dt_control_image_enumerator_t *t, int32_t filmid)
static int32_t dt_control_gpx_apply_job_run(dt_job_t *job)
static int32_t _generic_dt_control_fileop_images_job_run(dt_job_t *job, int32_t(*fileop_callback)(const int32_t, const int32_t), const char *desc, const char *desc_pl)
static void dt_control_export_cleanup(void *p)
static int dt_control_merge_hdr_levels(dt_imageio_module_data_t *data)
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
static int FCxtrans(const int row, const int col, global const unsigned char(*const xtrans)[6])
GDateTime * dt_datetime_exif_to_gdatetime(const char *exif, const GTimeZone *tz)
Definition datetime.c:260
GTimeZone * dt_datetime_utc_tz(void)
Definition datetime.c:40
GDateTime * dt_datetime_img_to_gdatetime(const dt_image_t *img, const GTimeZone *tz)
Definition datetime.c:302
#define DT_DATETIME_LENGTH
Definition datetime.h:38
gchar * directory
documentation root given after –doc, or NULL
const int res
Definition dtpthread.h:351
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Wait on cond, releasing mutex for the duration.
Definition dtpthread.h:149
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
boolean dt_win_file_trash(GFile *file, GCancellable *cancellable, GError **error)
Definition dtwin.c:356
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
int dt_exif_read(dt_image_t *img, const char *path)
Definition exif.cc:1994
What a photograph says about itself: the EXIF, IPTC and XMP tags a camera and a cataloguer write,...
int dt_film_new(dt_film_t *film, const char *directory)
Definition film.c:129
void dt_film_remove_empty()
Definition film.c:291
GList * dt_film_repository_get_image_ids(const int32_t filmid)
@ TYPE_UINT16
Definition format.h:57
void dt_gpx_destroy(struct dt_gpx_t *gpx)
Definition gpx.c:153
gboolean dt_gpx_get_location(struct dt_gpx_t *gpx, GDateTime *timestamp, dt_image_geoloc_t *geoloc)
Definition gpx.c:171
dt_gpx_t * dt_gpx_new(const gchar *filename)
Definition gpx.c:89
void dt_grouping_add_grouped_images(GList **images)
Definition grouping.c:148
GList * dt_grouping_get_group_images(const int32_t imgid)
Definition grouping.c:130
void dt_history_delete_on_image(int32_t imgid)
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
#define UNKNOWN_IMAGE
Definition image.h:78
dt_image_orientation_t
Definition image.h:216
const char flag
Definition image.h:311
@ DT_IMAGE_HAS_ADDITIONAL_DNG_TAGS
Definition image.h:145
@ DT_IMAGE_S_RAW
Definition image.h:147
@ DT_IMAGE_REMOVE
Definition image.h:128
@ DT_IMAGE_MOSAIC
Definition image.h:159
@ DT_IMAGE_MONOCHROME_BAYER
Definition image.h:151
@ DT_IMAGE_LOCAL_COPY
Definition image.h:134
@ DT_IMAGE_BUFFER_RESOLVED
Definition image.h:164
@ DT_IMAGE_RAW
Definition image.h:124
@ DT_IMAGE_4BAYER
Definition image.h:140
@ DT_IMAGE_MONOCHROME
Definition image.h:142
@ DT_IMAGE_HDR
Definition image.h:126
@ DT_IMAGE_LDR
Definition image.h:122
dt_image_write_sidecar_result_t
Definition image.h:752
@ DT_IMAGE_WRITE_SIDECAR_OK
Definition image.h:753
@ DT_IMAGE_WRITE_SIDECAR_IO_ERROR
Definition image.h:757
@ DT_IMAGE_WRITE_SIDECAR_CACHE_BUSY
Definition image.h:755
@ DT_IMAGE_WRITE_SIDECAR_NO_SOURCE_PATH
Definition image.h:756
@ DT_IMAGE_WRITE_SIDECAR_DISABLED
Definition image.h:754
dt_image_transform_t
An orientation change the user asks for, in the coordinates they are looking at.
Definition image.h:245
void dt_image_cache_write_release(dt_image_t *img, dt_image_cache_write_mode_t mode)
void dt_image_cache_set_export_timestamp(const int32_t imgid)
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_read_release(const dt_image_t *img)
@ DT_IMAGE_CACHE_SAFE
Definition image_cache.h:48
GList * dt_image_repository_get_ids_with_flag_among(GList *imgids, const int flag)
Of the images in imgids, those whose flags carry flag, in row order.
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...
gboolean dt_image_repository_set_flag_among(GList *imgids, const int flag)
Set flag on every image in imgids.
GList * dt_image_repository_get_full_paths(GList *imgids)
"<folder>/<filename>" for every image in imgids, distinct, in row order. Caller owns the strings.
Reading and writing one dt_image_t to and from the library database. The SQL half of what used to be ...
int dt_imageio_export_with_flags(const int32_t imgid, const char *filename, dt_imageio_module_format_t *format, dt_imageio_module_data_t *format_params, const gboolean ignore_exif, const gboolean display_byteorder, const gboolean high_quality, gboolean is_scaling, const gboolean thumbnail_export, const char *filter, const gboolean copy_metadata, const gboolean export_masks, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, dt_imageio_module_storage_t *storage, dt_imageio_module_data_t *storage_params, int num, int total, dt_export_metadata_t *metadata, dt_atomic_int *shutdown)
@ IMAGEIO_RGB
@ IMAGEIO_FLOAT
static void dt_imageio_write_dng(const char *filename, const float *const pixel, const int wd, const int ht, void *exif, const int exif_len, const uint32_t filter, const uint8_t xtrans[6][6], const float whitelevel, const dt_aligned_pixel_t wb_coeffs, const float adobe_XYZ_to_CAM[4][3])
dt_imageio_module_storage_t * dt_imageio_get_storage_by_index(int index)
dt_imageio_module_format_t * dt_imageio_get_format_by_index(int index)
dt_job_state_t dt_control_job_get_state(_dt_job_t *job)
Definition jobs.c:105
void dt_control_job_cancel(_dt_job_t *job)
Definition jobs.c:182
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:137
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:407
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:131
void dt_control_job_set_progress(dt_job_t *job, double value)
Definition jobs.c:628
void dt_control_job_add_progress(dt_job_t *job, const char *message, gboolean cancellable)
Definition jobs.c:614
void dt_control_job_set_progress_message(dt_job_t *job, const char *message)
Definition jobs.c:622
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:114
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:155
@ DT_JOB_QUEUE_USER_FG
Definition jobs.h:54
@ DT_JOB_QUEUE_USER_EXPORT
Definition jobs.h:57
int32_t(* dt_job_execute_callback)(dt_job_t *)
Definition jobs.h:64
@ DT_JOB_STATE_CANCELLED
Definition jobs.h:47
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
#define M_PI
Definition math.h:47
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
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
gboolean dt_osx_file_trash(const char *filename, GError **error)
Definition osx.mm:115
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:105
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
dt_iop_color_intent_t
ICC rendering intent, as stored in iop params and in conf.
@ DT_INTENT_LAST
Count of real intents, and the codebase's "unset" marker – never a user choice.
dt_colorspaces_color_profile_type_t
@ DT_COLORSPACE_NONE
No profile / "take it from the image settings". Never matches a list entry.
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
Definition signal.h:224
@ DT_SIGNAL_FILMROLLS_CHANGED
This signal is raised when a filmroll is deleted/changed but not imported.
Definition signal.h:159
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
Definition signal.h:139
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
Definition signal.h:133
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
dt_pthread_mutex_t mutex
char datetime[DT_DATETIME_LENGTH]
dt_iop_color_intent_t icc_intent
dt_imageio_module_data_t * sdata
dt_colorspaces_color_profile_type_t icc_type
dt_control_merge_hdr_t * d
dt_imageio_module_data_t parent
uint8_t first_xtrans[6][6]
dt_aligned_pixel_t wb_coeffs
dt_image_orientation_t orientation
float adobe_XYZ_to_CAM[4][3]
char dirname[512]
Definition film.h:46
Definition gpx.c:49
float exif_exposure
Definition image.h:367
float exif_iso
Definition image.h:370
float exif_aperture
Definition image.h:369
int32_t flags
Definition image.h:401
dt_image_orientation_t orientation
Definition image.h:366
float exif_focal_length
Definition image.h:371
int32_t crop_y
Definition image.h:398
int32_t crop_x
Definition image.h:398
dt_iop_buffer_dsc_t dsc
Definition image.h:419
float adobe_XYZ_to_CAM[4][3]
Definition image.h:449
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:386
int32_t id
Definition image.h:401
dt_aligned_pixel_t wb_coeffs
Definition image.h:446
uint32_t filters
Definition format.h:89
unsigned int channels
Definition format.h:83
uint8_t xtrans[6][6]
Definition format.h:99
dt_iop_buffer_type_t datatype
Definition format.h:85
Region of interest passed through the pixelpipe.
Definition format.h:49
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(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:485
gboolean dt_tag_new(const char *name, guint *tagid)
Definition tags.c:164
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
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
@ DT_UNDO_REMOVE
Definition undo.h:53
@ 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:611
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
Definition utility.c:833
size_t safe_strlen(const char *str)
check if the string is empty or NULL before calling strlen()
Definition utility.c:94
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
@ DT_VIEW_LIGHTTABLE
Definition view.h:77
int dt_exif_read_blob(uint8_t **buf, const char *path, const int32_t imgid, const int sRGB, const int out_width, const int out_height, const int dng_mode)
The XMP document that carries an image's development: history stack, mask shapes, module order,...