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-2026 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 double scale_factor; // > 0: reduce by it, otherwise fit in max_width x max_height
148 dt_imageio_module_data_t *sdata; // needed since the gui thread resets things like overwrite once the export
149 // is dispatched, but we have to keep that information
150 gboolean export_masks;
151 char style[128];
157
158
159/* enumerator of images from filmroll */
161{
162 /* get a list of images in filmroll */
163 GList *ids = dt_film_repository_get_image_ids(filmid);
164 for(GList *l = ids; l; l = g_list_next(l))
165 t->index = g_list_append(t->index, l->data);
166 g_list_free(ids);
167}
168
170 int32_t (*fileop_callback)(const int32_t,
171 const int32_t),
172 const char *desc, const char *desc_pl)
173{
175 GList *t = params->index;
176 const guint total = g_list_length(t);
177 char message[512] = { 0 };
178 double fraction = 0;
179 gchar *newdir = (gchar *)params->data;
180
181 g_snprintf(message, sizeof(message), ngettext(desc, desc_pl, total), total);
183
184 // create new film roll for the destination directory
185 dt_film_t new_film;
186 const int32_t film_id = dt_film_new(&new_film, newdir);
187 dt_free(newdir);
188
189 if(film_id <= 0)
190 {
191 dt_control_alert(_("File operation failed"),
192 _("failed to create film roll for destination directory, aborting move.."), NULL);
193 return -1;
194 }
195
196 gboolean completeSuccess = TRUE;
198 {
199 completeSuccess &= (fileop_callback(GPOINTER_TO_INT(t->data), film_id) != -1);
200 t = g_list_next(t);
201 fraction += 1.0 / total;
202 dt_control_job_set_progress(job, fraction);
203 }
204
205 if(completeSuccess)
206 {
207 char collect[1024];
208 snprintf(collect, sizeof(collect), "1:0:0:%s$", new_film.dirname);
210 }
214 g_list_copy(params->index));
216 return 0;
217}
218
220{
222 if(IS_NULL_PTR(params)) return NULL;
223 return params;
224}
225
227{
229
230 if(params->index)
231 {
232 g_list_free(params->index);
233 params->index = NULL;
234 }
235 params->index = NULL;
236
238}
239
241
243 int flag, gpointer data, progress_type_t progress_type,
244 gboolean only_visible)
245{
246 dt_job_t *job = dt_control_job_create(execute, "%s", message);
247 if(IS_NULL_PTR(job)) return NULL;
250 {
252 return NULL;
253 }
254 if(progress_type != PROGRESS_NONE)
255 dt_control_job_add_progress(job, _(message), progress_type == PROGRESS_CANCELLABLE);
256 params->index = dt_act_on_get_images();
257
259
260 params->flag = flag;
261 params->data = data;
262 return job;
263}
264
266 int flag, gpointer data, progress_type_t progress_type,
267 int32_t imgid)
268{
269 dt_job_t *job = dt_control_job_create(execute, "%s", message);
270 if(IS_NULL_PTR(job)) return NULL;
273 {
275 return NULL;
276 }
277 if(progress_type != PROGRESS_NONE)
278 dt_control_job_add_progress(job, _(message), progress_type == PROGRESS_CANCELLABLE);
279
280 params->index = g_list_append(NULL, GINT_TO_POINTER(imgid));
281
283
284 params->flag = flag;
285 params->data = data;
286 return job;
287}
288
289
291{
293 const gboolean forced = (params->flag == 1);
294
295 if(!forced && !dt_image_get_xmp_mode()) return 0;
296
297 for(GList *t = params->index; t; t = g_list_next(t))
298 {
299 const int32_t imgid = GPOINTER_TO_INT(t->data);
303 const char *alert_msg = NULL;
304 switch(res)
305 {
307 break;
309 // xmp writing is off, or the setting changed mid-batch: nothing was attempted, nothing to report
310 break;
312 alert_msg = _("cannot write XMP file: the image cache entry is busy, try again.");
313 break;
315 alert_msg = _("cannot write XMP file: the original file could not be found.");
316 break;
318 alert_msg = _("cannot write XMP file: the target storage may be unavailable or read-only.");
319 break;
320 }
321 if(!IS_NULL_PTR(alert_msg))
322 {
323 // The image goes under the message, by the path it has in the library: an id means nothing to
324 // the user, and dt_image_full_path() gives no path for an original that is gone.
325 char fullpath[DT_PATH_MAX] = { 0 };
326 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
327 if(!IS_NULL_PTR(img))
328 {
329 g_strlcpy(fullpath, img->fullpath, sizeof(fullpath));
331 }
332 dt_control_alert(_("Sidecar file not written"), alert_msg, fullpath[0] ? fullpath : NULL);
333 }
334 }
335 return 0;
336}
337
339{
340 GList *imgs = dt_act_on_get_images();
341 if(IS_NULL_PTR(imgs)) return;
343 g_list_free(imgs);
344 imgs = NULL;
345}
346
348{
349 uint32_t first_imgid;
350 uint32_t first_filter;
351 uint8_t first_xtrans[6][6];
352
353 float *pixels, *weight;
354
355 int wd;
356 int ht;
358
360 float epsw;
362 float adobe_XYZ_to_CAM[4][3];
364
365 // 0 - ok; 1 - errors, abort
366 gboolean abort;
368
374
376{
377 return 32;
378}
379
384
386{
387 return "memory";
388}
389
390static float envelope(const float xx)
391{
392 const float x = CLAMPS(xx, 0.0f, 1.0f);
393 // const float alpha = 2.0f;
394 const float beta = 0.5f;
395 if(x < beta)
396 {
397 // return 1.0f-fabsf(x/beta-1.0f)^2
398 const float tmp = fabsf(x / beta - 1.0f);
399 return 1.0f - tmp * tmp;
400 }
401 else
402 {
403 const float tmp1 = (1.0f - x) / (1.0f - beta);
404 const float tmp2 = tmp1 * tmp1;
405 const float tmp3 = tmp2 * tmp1;
406 return 3.0f * tmp2 - 2.0f * tmp3;
407 }
408}
409
410static int dt_control_merge_hdr_process(dt_imageio_module_data_t *datai, const char *filename,
411 const void *const ivoid,
412 dt_colorspaces_color_profile_type_t over_type, const char *over_filename,
413 void *exif, int exif_len, int32_t imgid, int num, int total,
414 dt_dev_pixelpipe_t *pipe, const gboolean export_masks)
415{
417 dt_control_merge_hdr_t *d = data->d;
418
419 // just take a copy. also do it after blocking read, so filters will make sense.
420 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
421 if(IS_NULL_PTR(img))
422 {
423 // a merge missing one of its exposures is not the merge that was asked for
424 dt_control_alert(_("Image could not be opened"), _("Could not read image information from the database."),
425 NULL);
426 d->abort = TRUE;
427 return 1;
428 }
429 const dt_image_t image = *img;
431
432 if(IS_NULL_PTR(d->pixels))
433 {
434 d->first_imgid = imgid;
435 d->first_filter = image.dsc.filters;
436 // sensor layout is just passed on to be written to dng.
437 // we offset it to the crop of the image here, so we don't
438 // need to load in the FCxtrans dependency into the dng writer.
439 // for some stupid reason the dng needs this layout wrt cropped
440 // offsets, not globally.
441 dt_iop_roi_t roi = {0};
442 roi.x = image.crop_x;
443 roi.y = image.crop_y;
444 for(int j=0;j<6;j++)
445 for(int i = 0; i < 6; i++) d->first_xtrans[j][i] = FCxtrans(j, i, &roi, image.dsc.xtrans);
446 d->pixels = calloc((size_t)datai->width * datai->height, sizeof(float));
447 d->weight = calloc((size_t)datai->width * datai->height, sizeof(float));
448 d->wd = datai->width;
449 d->ht = datai->height;
450 d->orientation = image.orientation;
451 for(int i = 0; i < 3; i++)
452 d->wb_coeffs[i] = image.wb_coeffs[i];
453 for(int k=0; k<4; k++)
454 for(int i=0; i<3; i++)
455 d->adobe_XYZ_to_CAM[k][i] = image.adobe_XYZ_to_CAM[k][i];
456 }
457
458 if(image.dsc.filters == 0u || image.dsc.channels != 1 || image.dsc.datatype != TYPE_UINT16)
459 {
460 dt_control_log(_("exposure bracketing only works on raw images."));
461 d->abort = TRUE;
462 return 1;
463 }
464 else if(datai->width != d->wd || datai->height != d->ht || d->first_filter != image.dsc.filters
465 || d->orientation != image.orientation)
466 {
467 dt_control_log(_("images have to be of same size and orientation!"));
468 d->abort = TRUE;
469 return 1;
470 }
471
472 // if no valid exif data can be found, assume peleng fisheye at f/16, 8mm, with half of the light lost in
473 // the system => f/22
474 const float eap = image.exif_aperture > 0.0f ? image.exif_aperture : 22.0f;
475 const float efl = image.exif_focal_length > 0.0f ? image.exif_focal_length : 8.0f;
476 const float rad = .5f * efl / eap;
477 const float aperture = M_PI * rad * rad;
478 const float iso = image.exif_iso > 0.0f ? image.exif_iso : 100.0f;
479 const float exp = image.exif_exposure > 0.0f ? image.exif_exposure : 1.0f;
480 const float cal = 100.0f / (aperture * exp * iso);
481 // about proportional to how many photons we can expect from this shot:
482 const float photoncnt = 100.0f * aperture * exp / iso;
483 float saturation = 1.0f;
484 d->whitelevel = fmaxf(d->whitelevel, saturation * cal);
485 __OMP_PARALLEL_FOR__(collapse(2))
486 for(int y = 0; y < d->ht; y++)
487 for(int x = 0; x < d->wd; x++)
488 {
489 // read unclamped raw value with subtracted black and rescaled to 1.0 saturation.
490 // this is the output of the rawprepare iop.
491 const float in = ((float *)ivoid)[x + d->wd * y];
492 // weights based on siggraph 12 poster
493 // zijian zhu, zhengguo li, susanto rahardja, pasi fraenti
494 // 2d denoising factor for high dynamic range imaging
495 float w = photoncnt;
496
497 // need some safety margin due to upsampling and 16-bit quantization + dithering?
498 float offset = 3000.0f / (float)UINT16_MAX;
499
500 // cannot do an envelope based on single pixel values here, need to get
501 // maximum value of all color channels. to find that, go through the
502 // pattern block (we conservatively do a 3x3 for bayer or xtrans):
503 int xx = x & ~1, yy = y & ~1;
504 float M = 0.0f, m = FLT_MAX;
505 if(xx < d->wd - 2 && yy < d->ht - 2)
506 {
507 for(int i = 0; i < 3; i++)
508 for(int j = 0; j < 3; j++)
509 {
510 M = MAX(M, ((float *)ivoid)[xx + i + d->wd * (yy + j)]);
511 m = MIN(m, ((float *)ivoid)[xx + i + d->wd * (yy + j)]);
512 }
513 // move envelope a little to allow non-zero weight even for clipped regions.
514 // this is because even if the 2x2 block is clipped somewhere, the other channels
515 // might still prove useful. we'll check for individual channel saturation below.
516 w *= d->epsw + envelope((M + offset) / saturation);
517 }
518
519 if(M + offset >= saturation)
520 {
521 if(d->weight[x + d->wd * y] <= 0.0f)
522 { // only consider saturated pixels in case we have nothing better:
523 if(d->weight[x + d->wd * y] == 0 || m < -d->weight[x + d->wd * y])
524 {
525 if(m + offset >= saturation)
526 d->pixels[x + d->wd * y] = 1.0f; // let's admit we were completely clipped, too
527 else
528 d->pixels[x + d->wd * y] = in * cal / d->whitelevel;
529 d->weight[x + d->wd * y]
530 = -m; // could use -cal here, but m is per pixel and safer for varying illumination conditions
531 }
532 }
533 // else silently ignore, others have filled in a better color here already
534 }
535 else
536 {
537 if(d->weight[x + d->wd * y] <= 0.0)
538 { // cleanup potentially blown highlights from earlier images
539 d->pixels[x + d->wd * y] = 0.0f;
540 d->weight[x + d->wd * y] = 0.0f;
541 }
542 d->pixels[x + d->wd * y] += w * in * cal;
543 d->weight[x + d->wd * y] += w;
544 }
545 }
546
547 return 0;
548}
549
551{
553 GList *t = params->index;
554 const guint total = g_list_length(t);
555 char message[512] = { 0 };
556 double fraction = 0;
557 snprintf(message, sizeof(message), ngettext("merging %d image", "merging %d images", total), total);
558
560
562
566 .write_image = dt_control_merge_hdr_process };
567
569
570 int num = 1;
571 while(t)
572 {
573 if(d.abort) goto end;
574
575 const int32_t imgid = GPOINTER_TO_INT(t->data);
576
577 // the merge reads the images at full size: the export module's size settings are not its own
578 dt_imageio_export_with_flags(imgid, "unused", &buf, (dt_imageio_module_data_t *)&dat, TRUE, FALSE, FALSE, 0.0,
579 FALSE, FALSE, "pre:rawprepare", FALSE, FALSE, DT_COLORSPACE_NONE, NULL, DT_INTENT_LAST, NULL,
580 NULL, num, total, NULL, NULL);
581
582 t = g_list_next(t);
583
584 /* update the progress bar */
585 fraction += 1.0 / (total + 1);
586 dt_control_job_set_progress(job, fraction);
587 num++;
588 }
589
590 if(d.abort) goto end;
591
592// normalize by white level to make clipping at 1.0 work as expected
594 for(size_t k = 0; k < (size_t)d.wd * d.ht; k++)
595 {
596 if(d.weight[k] > 0.0) d.pixels[k] = fmaxf(0.0f, d.pixels[k] / (d.whitelevel * d.weight[k]));
597 }
598
599 // output hdr as digital negative with exif data.
600 uint8_t *exif = NULL;
601 char pathname[DT_PATH_MAX] = { 0 };
602 gboolean from_cache = TRUE;
603 dt_image_full_path(d.first_imgid, pathname, sizeof(pathname), &from_cache, __FUNCTION__);
604
605 // last param is dng mode
606 const int exif_len = dt_exif_read_blob(&exif, pathname, d.first_imgid, 0, d.wd, d.ht, 1);
607 char *c = pathname + safe_strlen(pathname);
608 while(*c != '.' && c > pathname) c--;
609 g_strlcpy(c, "-hdr.dng", sizeof(pathname) - (c - pathname));
610 dt_imageio_write_dng(pathname,
611 d.pixels,
612 d.wd,
613 d.ht,
614 exif,
615 exif_len,
616 d.first_filter,
617 (const uint8_t (*)[6])d.first_xtrans,
618 1.0f,
619 (const float (*))d.wb_coeffs,
620 d.adobe_XYZ_to_CAM);
621 dt_free(exif);
622
624
625 while(*c != '/' && c > pathname) c--;
626 dt_control_log(_("wrote merged HDR `%s'"), c + 1);
627
628 // import new image
629 gchar *directory = g_path_get_dirname((const gchar *)pathname);
630 dt_film_t film;
631 const int filmid = dt_film_new(&film, directory);
632 const uint32_t imageid = dt_image_import(filmid, pathname, TRUE);
634
635 // refresh the thumbtable view
637 g_list_prepend(NULL, GINT_TO_POINTER(imageid)));
640
641end:
642 dt_free(d.pixels);
643 dt_free(d.weight);
644
645 return 0;
646}
647
649{
651 GList *t = params->index;
652 const guint total = g_list_length(t);
653 double fraction = 0.0f;
654 char message[512] = { 0 };
655
657
658 snprintf(message, sizeof(message), ngettext("duplicating %d image", "duplicating %d images", total), total);
660 while(t)
661 {
662 const int32_t imgid = GPOINTER_TO_INT(t->data);
663 // Defer exposing the duplicate to the collection/lighttable grid until its history is
664 // settled below -- otherwise the grid can generate and cache a thumbnail from the
665 // still-historyless row before the copy/delete below ever runs.
666 const int newimgid = dt_image_duplicate_no_reload(imgid);
667 if(newimgid != UNKNOWN_IMAGE)
668 {
669 if(GPOINTER_TO_INT(params->data))
671 else
673
675 }
676 t = g_list_next(t);
677 fraction += 1.0 / total;
678 dt_control_job_set_progress(job, fraction);
679 }
680
682
685 return 0;
686}
687
689{
692 GList *t = params->index;
693 const guint total = g_list_length(t);
694 double fraction = 0.0f;
695 char message[512] = { 0 };
696
698
699 snprintf(message, sizeof(message), ngettext("flipping %d image", "flipping %d images", total), total);
701 while(t)
702 {
703 const int32_t imgid = GPOINTER_TO_INT(t->data);
704 dt_image_flip(imgid, transform);
705 t = g_list_next(t);
706 fraction += 1.0 / total;
707 dt_control_job_set_progress(job, fraction);
708 }
709
712 return 0;
713}
715{
717 const int32_t mode = params->flag;
718 GList *t = params->index;
719 const guint total = g_list_length(t);
720 char message[512] = { 0 };
721 double fraction = 0.0f;
722
724
725 if(mode == 0)
726 snprintf(message, sizeof(message), ngettext("set %d color image", "setting %d color images", total), total);
727 else
728 snprintf(message, sizeof(message), ngettext("set %d monochrome image", "setting %d monochrome images", total), total);
729
731 while(t)
732 {
733 const int32_t imgid = GPOINTER_TO_INT(t->data);
734
735 if(imgid >= 0)
736 {
737 dt_image_set_monochrome_flag(imgid, mode == 2);
738 }
739 else
740 fprintf(stderr,"[dt_control_monochrome_images_job_run] got illegal imgid %i\n", imgid);
741
742 t = g_list_next(t);
743 fraction += 1.0 / total;
744 dt_control_job_set_progress(job, fraction);
745 }
746
748
750 g_list_copy(params->index));
752 return 0;
753}
754
755// Regenerate .xmp files for any surviving duplicate at each of `pathnames` (owned, g_strdup'd
756// full paths from _get_full_pathname(); this call takes ownership and frees them), batched into
757// a single dt_control_save_xmps() job. dt_image_synch_all_xmp() spawns one DT_JOB_QUEUE_USER_FG
758// job per path -- fine for the handful of images a normal removal/delete touches, but for a large
759// recursive folder removal it flooded that same queue (the one this job itself runs on) with one
760// tiny job per path, each spawning its own GUI progress bar: stalled the app for minutes, looking
761// like an infinite loop.
762static void _resync_xmp_duplicates(GList *pathnames)
763{
764 GList *resync_imgids = NULL;
766 {
767 for(GList *l = pathnames; l; l = g_list_next(l))
768 {
769 const int32_t dup_imgid = dt_image_get_id_full_path((const char *)l->data);
770 if(dup_imgid != UNKNOWN_IMAGE) resync_imgids = g_list_prepend(resync_imgids, GINT_TO_POINTER(dup_imgid));
771 }
772 }
773 g_list_free_full(pathnames, dt_free_gpointer);
774 if(!IS_NULL_PTR(resync_imgids)) dt_control_save_xmps(resync_imgids, FALSE);
775 g_list_free(resync_imgids);
776}
777
779{
781 GList *t = params->index;
782 const guint total = g_list_length(t);
783 char message[512] = { 0 };
784 snprintf(message, sizeof(message), ngettext("removing %d image", "removing %d images", total), total);
786
787 // check that we can safely remove the image
788 gboolean remove_ok = TRUE;
790 for(GList *l = local_copies; l; l = g_list_next(l))
791 {
792 if(!dt_image_safe_remove(GPOINTER_TO_INT(l->data)))
793 {
794 remove_ok = FALSE;
795 break;
796 }
797 }
798 g_list_free(local_copies);
799
800 if(!remove_ok)
801 {
802 dt_control_alert(_("File operation failed"),
803 _("cannot remove local copy when the original file is not accessible."), NULL);
804 return 0;
805 }
806
807 // update remove status
809
811
812 // We need a list of files to regenerate .xmp files if there are duplicates
814
815 /* One undo record per image, grouped so Ctrl+Z takes the whole batch back at once. The
816 * files stay on disk, but the database rows do not, and neither the edit nor the tags nor
817 * the group membership they carry can be reconstructed from the file -- so they are staged
818 * before deletion rather than re-imported after. */
820
821 double fraction = 0.0f;
822 while(t)
823 {
824 int32_t imgid = GPOINTER_TO_INT(t->data);
826 t = g_list_next(t);
827 fraction += 1.0 / total;
828 dt_control_job_set_progress(job, fraction);
829 }
830
832
836 g_list_copy(params->index));
839
840 return 0;
841}
842
844{
846 const char *filename;
847 const char *error_message;
848
850
851 dt_pthread_mutex_t mutex;
852 pthread_cond_t cond;
854
862
871
872static gboolean _dt_delete_dialog_main_thread(gpointer user_data)
873{
874 _dt_delete_modal_dialog_t* modal_dialog = (_dt_delete_modal_dialog_t*)user_data;
875 dt_pthread_mutex_lock(&modal_dialog->mutex);
876
877 GtkWidget *dialog = gtk_message_dialog_new(
878 GTK_WINDOW(dt_gui_main_window()),
879 GTK_DIALOG_DESTROY_WITH_PARENT,
880 GTK_MESSAGE_QUESTION,
881 GTK_BUTTONS_NONE,
882 modal_dialog->send_to_trash
883 ? _("could not send %s to trash.%s%s")
884 : _("could not physically delete %s.%s%s"),
885 modal_dialog->filename,
886 !IS_NULL_PTR(modal_dialog->error_message) ? "\n" : "",
887 !IS_NULL_PTR(modal_dialog->error_message) ? modal_dialog->error_message : "");
888#ifdef GDK_WINDOWING_QUARTZ
890#endif
891
892 if(modal_dialog->send_to_trash)
893 {
894 gtk_dialog_add_button(GTK_DIALOG(dialog), _("physically delete"), _DT_DELETE_DIALOG_CHOICE_DELETE);
895 gtk_dialog_add_button(GTK_DIALOG(dialog), _("physically delete all files"), _DT_DELETE_DIALOG_CHOICE_DELETE_ALL);
896 }
897 gtk_dialog_add_button(GTK_DIALOG(dialog), _("only remove from the image library"), _DT_DELETE_DIALOG_CHOICE_REMOVE);
898 gtk_dialog_add_button(GTK_DIALOG(dialog), _("skip to next file"), _DT_DELETE_DIALOG_CHOICE_CONTINUE);
899 gtk_dialog_add_button(GTK_DIALOG(dialog), _("stop process"), _DT_DELETE_DIALOG_CHOICE_STOP);
900
901 gtk_window_set_title(
902 GTK_WINDOW(dialog),
903 modal_dialog->send_to_trash
904 ? _("trashing error")
905 : _("deletion error"));
906 modal_dialog->dialog_result = gtk_dialog_run(GTK_DIALOG(dialog));
907 gtk_widget_destroy(dialog);
908
909 pthread_cond_signal(&modal_dialog->cond);
910
911 dt_pthread_mutex_unlock(&modal_dialog->mutex);
912
913 // Don't call again on next idle time
914 return FALSE;
915}
916
917static gint _dt_delete_file_display_modal_dialog(int send_to_trash, const char *filename, const char *error_message)
918{
919 _dt_delete_modal_dialog_t modal_dialog;
920 modal_dialog.send_to_trash = send_to_trash;
921 modal_dialog.filename = filename;
922 modal_dialog.error_message = error_message;
923
924 modal_dialog.dialog_result = GTK_RESPONSE_NONE;
925
926 dt_pthread_mutex_init(&modal_dialog.mutex, NULL);
927 pthread_cond_init(&modal_dialog.cond, NULL);
928
929 dt_pthread_mutex_lock(&modal_dialog.mutex);
930
931 gdk_threads_add_idle(_dt_delete_dialog_main_thread, &modal_dialog);
932 while (modal_dialog.dialog_result == GTK_RESPONSE_NONE)
933 dt_pthread_cond_wait(&modal_dialog.cond, &modal_dialog.mutex);
934
935 dt_pthread_mutex_unlock(&modal_dialog.mutex);
936 dt_pthread_mutex_destroy(&modal_dialog.mutex);
937 pthread_cond_destroy(&modal_dialog.cond);
938
939 return modal_dialog.dialog_result;
940}
941
942static enum _dt_delete_status delete_file_from_disk(const char *filename, gboolean *delete_on_trash_error)
943{
944 enum _dt_delete_status delete_status = _DT_DELETE_STATUS_UNKNOWN;
945
946 GFile *gfile = g_file_new_for_path(filename);
947 int send_to_trash = dt_conf_get_bool("send_to_trash");
948
949 while (delete_status == _DT_DELETE_STATUS_UNKNOWN)
950 {
951 gboolean delete_success = FALSE;
952 GError *gerror = NULL;
953 if(send_to_trash)
954 {
955#ifdef __APPLE__
956 delete_success = dt_osx_file_trash(filename, &gerror);
957#elif defined(_WIN32)
958 delete_success = dt_win_file_trash(gfile, NULL /*cancellable*/, &gerror);
959#else
960 delete_success = g_file_trash(gfile, NULL /*cancellable*/, &gerror);
961#endif
962 }
963 else
964 {
965 delete_success = g_file_delete(gfile, NULL /*cancellable*/, &gerror);
966 }
967
968 // Delete is a success or the file does not exists: OK to remove from darktable
969 if(delete_success
970 || g_error_matches(gerror, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
971 {
972 delete_status = _DT_DELETE_STATUS_OK_TO_REMOVE;
973 }
974 else if(send_to_trash && *delete_on_trash_error)
975 {
976 // Loop again, this time delete instead of trashing
977 delete_status = _DT_DELETE_STATUS_UNKNOWN;
978 send_to_trash = FALSE;
979 }
980 else
981 {
982 const char *filename_display = NULL;
983 GFileInfo *gfileinfo = g_file_query_info(
984 gfile,
985 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
986 G_FILE_QUERY_INFO_NONE,
987 NULL /*cancellable*/,
988 NULL /*error*/);
989 if(!IS_NULL_PTR(gfileinfo))
990 filename_display = g_file_info_get_attribute_string(
991 gfileinfo,
992 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
993
995 send_to_trash,
996 IS_NULL_PTR(filename_display) ? filename : filename_display,
997 IS_NULL_PTR(gerror) ? NULL : gerror->message);
998 g_object_unref(gfileinfo);
999 if(send_to_trash && res == _DT_DELETE_DIALOG_CHOICE_DELETE)
1000 {
1001 // Loop again, this time delete instead of trashing
1002 delete_status = _DT_DELETE_STATUS_UNKNOWN;
1003 send_to_trash = FALSE;
1004 }
1005 else if(send_to_trash && res == _DT_DELETE_DIALOG_CHOICE_DELETE_ALL)
1006 {
1007 // Loop again, this time delete instead of trashing
1008 delete_status = _DT_DELETE_STATUS_UNKNOWN;
1009 send_to_trash = FALSE;
1010 *delete_on_trash_error = TRUE;
1011 }
1013 {
1014 delete_status = _DT_DELETE_STATUS_OK_TO_REMOVE;
1015 }
1017 {
1018 delete_status = _DT_DELETE_STATUS_SKIP_FILE;
1019 }
1020 else
1021 {
1022 delete_status = _DT_DELETE_STATUS_STOP_PROCESSING;
1023 }
1024 }
1025 if(!IS_NULL_PTR(gerror))
1026 g_error_free(gerror);
1027 }
1028
1029 if(!IS_NULL_PTR(gfile))
1030 g_object_unref(gfile);
1031
1032 return delete_status;
1033}
1034
1035
1037{
1039 GList *t = params->index;
1040
1041 const guint total = g_list_length(t);
1042 double fraction = 0.0f;
1043 char message[512] = { 0 };
1044 gboolean delete_on_trash_error = FALSE;
1045 if(dt_conf_get_bool("send_to_trash"))
1046 snprintf(message, sizeof(message), ngettext("trashing %d image", "trashing %d images", total), total);
1047 else
1048 snprintf(message, sizeof(message), ngettext("deleting %d image", "deleting %d images", total), total);
1050
1051
1053
1054 // We need a list of files to regenerate .xmp files if there are duplicates
1056 while(t)
1057 {
1058 enum _dt_delete_status delete_status = _DT_DELETE_STATUS_UNKNOWN;
1059 const int32_t imgid = GPOINTER_TO_INT(t->data);
1060 char filename[DT_PATH_MAX] = { 0 };
1061 gboolean from_cache = FALSE;
1062 dt_image_full_path(imgid, filename, sizeof(filename), &from_cache, __FUNCTION__);
1063
1064#ifdef _WIN32
1065 char *dirname = g_path_get_dirname(filename);
1066#endif
1067
1068 // same row set the duplicate list returns: every image sharing this one's roll and filename
1069 GList *dups = dt_image_repository_get_duplicate_ids(imgid);
1070 const int duplicates = g_list_length(dups);
1071 g_list_free(dups);
1072
1073 // remove from disk:
1074 if(duplicates == 1)
1075 {
1076 // first check for local copies, never delete a file whose original file is not accessible
1077 if(dt_image_local_copy_reset(imgid))
1078 goto delete_next_file;
1079
1080 GList *one = g_list_prepend(NULL, GINT_TO_POINTER(imgid));
1082 g_list_free(one);
1083 dt_image_remove(imgid);
1084
1085 // there are no further duplicates so we can remove the source data file
1086 delete_status = delete_file_from_disk(filename, &delete_on_trash_error);
1087 if(delete_status != _DT_DELETE_STATUS_OK_TO_REMOVE)
1088 goto delete_next_file;
1089
1090 // all sidecar files - including left-overs - can be deleted;
1091 // left-overs can result when previously duplicates have been REMOVED;
1092 // no need to keep them as the source data file is gone.
1093
1094 GList *files = dt_image_find_xmps(filename);
1095
1096 for(GList *file_iter = files; file_iter; file_iter = g_list_next(file_iter))
1097 {
1098 delete_status = delete_file_from_disk(file_iter->data, &delete_on_trash_error);
1099 if(delete_status != _DT_DELETE_STATUS_OK_TO_REMOVE)
1100 break;
1101 }
1102
1103 g_list_free_full(files, dt_free_gpointer);
1104 files = NULL;
1105 }
1106 else
1107 {
1108 // don't remove the actual source data if there are further duplicates using it;
1109 // just delete the xmp file of the duplicate selected.
1110
1111 dt_image_path_append_version(imgid, filename, sizeof(filename));
1112 g_strlcat(filename, ".xmp", sizeof(filename));
1113
1114 // remove image from db first ...
1115 GList *one = g_list_prepend(NULL, GINT_TO_POINTER(imgid));
1117 g_list_free(one);
1118 dt_image_remove(imgid);
1119
1120 // ... and delete afterwards because removing will re-write the XMP
1121 delete_status = delete_file_from_disk(filename, &delete_on_trash_error);
1122 }
1123
1124delete_next_file:
1125#ifdef _WIN32
1126 dt_free(dirname);
1127#endif
1128 t = g_list_next(t);
1129 fraction += 1.0 / total;
1130 dt_control_job_set_progress(job, fraction);
1131 if(delete_status == _DT_DELETE_STATUS_STOP_PROCESSING)
1132 break;
1133 }
1134
1135
1137 list = NULL;
1140 g_list_copy(params->index));
1143 return 0;
1144}
1145
1147{
1149 GList *t = params->index;
1150 struct dt_gpx_t *gpx = NULL;
1151 uint32_t cntr = 0;
1152 const dt_control_gpx_apply_t *d = params->data;
1153 const gchar *filename = d->filename;
1154 const gchar *tz = d->tz;
1155 /* do we have any selected images */
1156 if(IS_NULL_PTR(t)) goto bail_out;
1157
1158 /* try parse the gpx data */
1159 gpx = dt_gpx_new(filename);
1160 if(IS_NULL_PTR(gpx))
1161 {
1162 dt_control_log(_("failed to parse GPX file"));
1163 goto bail_out;
1164 }
1165
1166 GTimeZone *tz_camera = (IS_NULL_PTR(tz)) ? g_time_zone_new_utc() : g_time_zone_new(tz);
1167 if(IS_NULL_PTR(tz_camera)) goto bail_out;
1168
1169 GList *imgs = NULL;
1170 GArray *gloc = g_array_new(FALSE, FALSE, sizeof(dt_image_geoloc_t));
1171 /* go thru each selected image and lookup location in gpx */
1172 do
1173 {
1174 dt_image_geoloc_t geoloc;
1175 int32_t imgid = GPOINTER_TO_INT(t->data);
1176
1177 /* get image */
1178 const dt_image_t *cimg = dt_image_cache_get(imgid, 'r');
1179 if(IS_NULL_PTR(cimg)) continue;
1180
1181 GDateTime *exif_time = dt_datetime_img_to_gdatetime(cimg, tz_camera);
1182
1183 /* release the lock */
1185 if(IS_NULL_PTR(exif_time)) continue;
1186 GDateTime *utc_time = g_date_time_to_timezone(exif_time, dt_datetime_utc_tz());
1187 g_date_time_unref(exif_time);
1188 if(IS_NULL_PTR(utc_time)) continue;
1189
1190 /* only update image location if time is within gpx tack range */
1191 if(dt_gpx_get_location(gpx, utc_time, &geoloc))
1192 {
1193 // takes the option to include the grouped images
1194 GList *grps = dt_grouping_get_group_images(imgid);
1195 for(GList *grp = grps; grp; grp = g_list_next(grp))
1196 {
1197 imgs = g_list_prepend(imgs, grp->data);
1198 g_array_append_val(gloc, geoloc);
1199 cntr++;
1200 }
1201 g_list_free(grps);
1202 grps = NULL;
1203 }
1204 g_date_time_unref(utc_time);
1205 } while((t = g_list_next(t)) != NULL);
1206 imgs = g_list_reverse(imgs);
1207
1209
1210 dt_control_log(ngettext("applied matched GPX location onto %d image",
1211 "applied matched GPX location onto %d images", cntr), cntr);
1212
1213 g_time_zone_unref(tz_camera);
1214 dt_gpx_destroy(gpx);
1215 g_array_unref(gloc);
1217 return 0;
1218
1219bail_out:
1220 if(!IS_NULL_PTR(gpx)) dt_gpx_destroy(gpx);
1221
1222 return 1;
1223}
1224
1226{
1227 return _generic_dt_control_fileop_images_job_run(job, &dt_image_move, _("moving %d image"),
1228 _("moving %d images"));
1229}
1230
1232{
1233 return _generic_dt_control_fileop_images_job_run(job, &dt_image_copy, _("copying %d image"),
1234 _("copying %d images"));
1235}
1236
1238{
1240 GList *t = params->index;
1241 guint tagid = 0;
1242 const guint total = g_list_length(t);
1243 double fraction = 0;
1244 const gboolean is_copy = params->flag == 1;
1245 char message[512] = { 0 };
1246
1247 if(is_copy)
1248 snprintf(message, sizeof(message),
1249 ngettext("creating local copy of %d image", "creating local copies of %d images", total), total);
1250 else
1251 snprintf(message, sizeof(message),
1252 ngettext("removing local copy of %d image", "removing local copies of %d images", total), total);
1253
1254 dt_control_log("%s", message);
1256
1257 dt_tag_new("darktable|local-copy", &tagid);
1258
1259 gboolean tag_change = FALSE;
1261 {
1262 const int32_t imgid = GPOINTER_TO_INT(t->data);
1263 if(is_copy)
1264 {
1265 if(dt_image_local_copy_set(imgid) == 0)
1266 {
1267 if(dt_tag_attach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1268 }
1269 }
1270 else
1271 {
1272 if(dt_image_local_copy_reset(imgid) == 0)
1273 {
1274 if(dt_tag_detach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1275 }
1276 }
1277 t = g_list_next(t);
1278
1279 fraction += 1.0 / total;
1280 dt_control_job_set_progress(job, fraction);
1281 }
1282
1284 g_list_copy(params->index));
1288 return 0;
1289}
1290
1292{
1294 GList *t = params->index;
1295 const guint total = g_list_length(t);
1296 double fraction = 0.0f;
1297 char message[512] = { 0 };
1298 snprintf(message, sizeof(message), ngettext("refreshing info for %d image", "refreshing info for %d images", total), total);
1300 while(t)
1301 {
1302 const int32_t imgid = GPOINTER_TO_INT(t->data);
1303 if(imgid >= 0)
1304 {
1305 gboolean from_cache = TRUE;
1306 char sourcefile[DT_PATH_MAX];
1307 dt_image_full_path(imgid, sourcefile, sizeof(sourcefile), &from_cache, __FUNCTION__);
1308
1309 dt_image_t *img = dt_image_cache_get(imgid, 'w');
1310 if(!IS_NULL_PTR(img))
1311 {
1312 // Re-sync the file-derived classification flags (LDR/HDR/RAW/sRAW, mosaic, monochrome,
1313 // 4bayer, additional-DNG-tags, buffer-resolved) with the actual file, repairing entries
1314 // that were persisted with stale/corrupted flags in the DB. We must *clear* these bits
1315 // before dt_exif_read(), because it only re-seeds the LDR/HDR/RAW class when none is set
1316 // yet (see exif.cc): leaving a stale DT_IMAGE_HDR in place would suppress the re-read.
1317 // Everything else in img->flags is DB-only / user state that the file knows nothing about
1318 // (star rating & reject, local copy, audio/txt sidecar, preset bookkeeping, monochrome
1319 // workflow), so it is preserved verbatim.
1320 const uint32_t file_class_flags
1324 img->flags &= ~file_class_flags;
1325 dt_exif_read(img, sourcefile);
1327 }
1328 else
1329 fprintf(stderr,"[dt_control_refresh_exif_run] couldn't dt_image_cache_get for imgid %i\n", imgid);
1330
1332 }
1333 else
1334 fprintf(stderr,"[dt_control_refresh_exif_run] illegal imgid %i\n", imgid);
1335
1336 t = g_list_next(t);
1337 fraction += 1.0 / total;
1338 dt_control_job_set_progress(job, fraction);
1339 }
1341 g_list_copy(params->index));
1344 return 0;
1345}
1346
1347
1348/* Whether the image is still in the library and its file still on disk; tells the user when it is not. */
1349static gboolean _export_source_available(const int32_t imgid)
1350{
1351 const dt_image_t *image = dt_image_cache_get(imgid, 'r');
1352 if(IS_NULL_PTR(image)) return FALSE;
1353
1354 char imgfilename[DT_PATH_MAX] = { 0 };
1355 gboolean from_cache = TRUE;
1356 dt_image_full_path(image->id, imgfilename, sizeof(imgfilename), &from_cache, __FUNCTION__);
1357 const gboolean available = g_file_test(imgfilename, G_FILE_TEST_IS_REGULAR);
1358 if(!available)
1359 {
1360 dt_control_log(_("image `%s' is currently unavailable"), image->filename);
1361 fprintf(stderr, "image `%s' is currently unavailable\n", imgfilename);
1362 }
1364 return available;
1365}
1366
1368{
1370 dt_control_export_t *settings = (dt_control_export_t *)params->data;
1371 GList *t = params->index;
1373 g_assert(mformat);
1375 g_assert(mstorage);
1376 dt_imageio_module_data_t *sdata = settings->sdata;
1377
1378 gboolean tag_change = FALSE;
1379
1380 // get a thread-safe fdata struct (one jpeg struct per thread etc):
1381 dt_imageio_module_data_t *fdata = mformat->get_params(mformat);
1382
1383 if(mstorage->initialize_store)
1384 {
1385 if(mstorage->initialize_store(mstorage, sdata, &mformat, &fdata, &t, TRUE))
1386 {
1387 // bail out, something went wrong
1388 goto end;
1389 }
1390 mformat->set_params(mformat, fdata, mformat->params_size(mformat));
1391 mstorage->set_params(mstorage, sdata, mstorage->params_size(mstorage));
1392 }
1393
1394 // Get max dimensions...
1395 uint32_t w, h, fw, fh, sw, sh;
1396 fw = fh = sw = sh = 0;
1397 mstorage->dimension(mstorage, sdata, &sw, &sh);
1398 mformat->dimension(mformat, fdata, &fw, &fh);
1399
1400 if(sw == 0 || fw == 0)
1401 w = sw > fw ? sw : fw;
1402 else
1403 w = sw < fw ? sw : fw;
1404
1405 if(sh == 0 || fh == 0)
1406 h = sh > fh ? sh : fh;
1407 else
1408 h = sh < fh ? sh : fh;
1409
1410 const guint total = g_list_length(t);
1411 if(total > 0)
1412 dt_control_log(ngettext("exporting %d image..", "exporting %d images..", total), total);
1413 else
1414 dt_control_log(_("no image to export"));
1415
1416 double fraction = 0;
1417
1418 // set up the fdata struct
1419 fdata->max_width = (settings->max_width != 0 && w != 0) ? MIN(w, settings->max_width) : MAX(w, settings->max_width);
1420 fdata->max_height = (settings->max_height != 0 && h != 0) ? MIN(h, settings->max_height) : MAX(h, settings->max_height);
1421 g_strlcpy(fdata->style, settings->style, sizeof(fdata->style));
1422 // Invariant: the tagid for 'darktable|changed' will not change while this function runs. Is this a
1423 // sensible assumption?
1424 guint tagid = 0, etagid = 0;
1425 dt_tag_new("darktable|exported", &etagid);
1426
1427 dt_export_metadata_t metadata;
1428 metadata.flags = 0;
1429 metadata.list = dt_util_str_to_glist("\1", settings->metadata_export);
1430 if(metadata.list)
1431 {
1432 metadata.flags = strtol(metadata.list->data, NULL, 16);
1433 metadata.list = g_list_remove(metadata.list, metadata.list->data);
1434 }
1435
1437 {
1438 const int32_t imgid = GPOINTER_TO_INT(t->data);
1439 t = g_list_next(t);
1440 const guint num = total - g_list_length(t);
1441
1442 // progress message
1443 char message[512] = { 0 };
1444 snprintf(message, sizeof(message), _("exporting %d / %d to %s"), num, total, mstorage->name(mstorage));
1445 // update the message. initialize_store() might have changed the number of images
1447
1448 // remove 'changed' tag from image
1449 if(dt_tag_detach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1450 // make sure the 'exported' tag is set on the image
1451 if(dt_tag_attach(etagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1452
1453 /* register export timestamp in cache */
1455
1456 if(_export_source_available(imgid)
1457 && mstorage->store(mstorage, sdata, imgid, mformat, fdata, settings->scale_factor, num, total, TRUE,
1458 settings->export_masks, settings->icc_type, settings->icc_filename,
1459 settings->icc_intent, &metadata) != 0)
1461
1462 fraction += 1.0 / total;
1463 if(fraction > 1.0) fraction = 1.0;
1464 dt_control_job_set_progress(job, fraction);
1465 }
1466 g_list_free_full(metadata.list, dt_free_gpointer);
1467 metadata.list = NULL;
1468
1469 if(mstorage->finalize_store) mstorage->finalize_store(mstorage, sdata);
1470
1471end:
1472 // all threads free their fdata
1473 mformat->free_params(mformat, fdata);
1474
1475 // notify the user via the window manager
1477
1479 return 0;
1480}
1481
1483{
1485 if(IS_NULL_PTR(params)) return NULL;
1486
1487 params->data = calloc(1, sizeof(dt_control_gpx_apply_t));
1488 if(IS_NULL_PTR(params->data))
1489 {
1491 return NULL;
1492 }
1493
1494 return params;
1495}
1496
1498{
1500
1501 dt_control_gpx_apply_t *data = params->data;
1502 params->data = NULL;
1503 dt_free(data->filename);
1504 dt_free(data->tz);
1505
1506 dt_free(data);
1507
1509}
1510
1511static dt_job_t *_control_gpx_apply_job_create(const gchar *filename, int32_t filmid,
1512 const gchar *tz, GList *imgs)
1513{
1515 if(IS_NULL_PTR(job)) return NULL;
1517 if(IS_NULL_PTR(params))
1518 {
1520 return NULL;
1521 }
1523
1524 if(filmid != -1)
1526 else if(!imgs)
1527 params->index = dt_act_on_get_images();
1528 else
1529 params->index = imgs;
1530 dt_control_gpx_apply_t *data = params->data;
1531 data->filename = g_strdup(filename);
1532 data->tz = g_strdup(tz);
1533
1534 return job;
1535}
1536
1537void dt_control_save_xmp(const int32_t imgid)
1538{
1541 N_("save history to XMP"),
1542 0, NULL, PROGRESS_NONE, imgid));
1543}
1544
1545void dt_control_save_xmps(const GList *imgids, const gboolean check_history)
1546{
1547 (void)check_history;
1548 if(IS_NULL_PTR(imgids)) return;
1549
1551 if(IS_NULL_PTR(job)) return;
1552
1554 if(IS_NULL_PTR(params))
1555 {
1557 return;
1558 }
1559
1560 params->index = g_list_copy((GList *)imgids);
1561 params->flag = 0;
1562
1565}
1566
1567void dt_control_save_xmps_forced(const GList *imgids)
1568{
1569 if(IS_NULL_PTR(imgids)) return;
1570
1572 if(IS_NULL_PTR(job)) return;
1573
1575 if(IS_NULL_PTR(params))
1576 {
1578 return;
1579 }
1580
1581 params->index = g_list_copy((GList *)imgids);
1582 params->flag = 1;
1583
1586}
1587
1594
1595void dt_control_gpx_apply(const gchar *filename, int32_t filmid, const gchar *tz, GList *imgs)
1596{
1598 _control_gpx_apply_job_create(filename, filmid, tz, imgs));
1599}
1600
1607
1614
1621
1623{
1625 if(cv->view(cv) != DT_VIEW_LIGHTTABLE)
1626 {
1627 dt_control_log(_("removing images from library is only possible in Lighttable view"));
1628 return FALSE;
1629 }
1630
1631 // get all selected images now, to avoid the set changing during ui interaction
1633 NULL, PROGRESS_SIMPLE, FALSE);
1634 if(dt_conf_get_bool("ask_before_remove"))
1635 {
1636 GtkWidget *dialog;
1638
1640 const int number = g_list_length(e->index);
1641 if(number == 0)
1642 {
1644 return TRUE;
1645 }
1646
1647 dialog = gtk_message_dialog_new(
1648 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1649 ngettext("Do you really want to remove %d image from Ansel library ?\nThe files will not be deleted on disk.",
1650 "Do you really want to remove %d images from Ansel library ?\nThe files will not be deletetd on disk.", number),
1651 number);
1652#ifdef GDK_WINDOWING_QUARTZ
1654#endif
1655
1656 gtk_window_set_title(GTK_WINDOW(dialog), ngettext(_("Remove image from library ?"), _("Remove images from library ?"), number));
1657 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1658 gtk_widget_destroy(dialog);
1659 if(res != GTK_RESPONSE_YES)
1660 {
1662 return FALSE;
1663 }
1664 }
1666 return TRUE;
1667}
1668
1670{
1672 if(cv->view(cv) != DT_VIEW_LIGHTTABLE)
1673 {
1674 dt_control_log(_("Deleting images from library is only possible in Lighttable view"));
1675 return;
1676 }
1677
1678 // first get all selected images, to avoid the set changing during ui interaction
1680 NULL, PROGRESS_SIMPLE, FALSE);
1681 if(dt_conf_get_bool("ask_before_delete"))
1682 {
1683 GtkWidget *dialog;
1685
1687 const int number = g_list_length(e->index);
1688
1689 // Do not show the dialog if no image is selected:
1690 if(number == 0)
1691 {
1693 return;
1694 }
1695
1696 dialog = gtk_message_dialog_new(
1697 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1698 ngettext("Do you really want to physically delete %d image ?\nThe system trash bin will be used if possible.",
1699 "Do you really want to physically delete %d images ?\nThe system trash bin will be used if possible.", number),
1700 number);
1701#ifdef GDK_WINDOWING_QUARTZ
1703#endif
1704
1705 gtk_window_set_title(GTK_WINDOW(dialog), ngettext(_("Remove image from disk ?"), _("Remove images from disk ?"), number));
1706 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1707 gtk_widget_destroy(dialog);
1708 if(res != GTK_RESPONSE_YES)
1709 {
1711 return;
1712 }
1713 }
1715}
1716
1717void dt_control_delete_image(int32_t imgid)
1718{
1720 if(cv->view(cv) != DT_VIEW_LIGHTTABLE)
1721 {
1722 dt_control_log(_("Deleting images from library is only possible in Lighttable view"));
1723 return;
1724 }
1725 // first get all selected images, to avoid the set changing during ui interaction
1727 NULL, PROGRESS_SIMPLE, imgid);
1728 int send_to_trash = dt_conf_get_bool("send_to_trash");
1729 if(dt_conf_get_bool("ask_before_delete"))
1730 {
1731 GtkWidget *dialog;
1733
1734 // Do not show the dialog if no valid image
1735 if(imgid < 1)
1736 {
1738 return;
1739 }
1740
1741 dialog = gtk_message_dialog_new(
1742 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1743 send_to_trash ? _("do you really want to physically delete selected image (using trash if possible)?")
1744 : _("do you really want to physically delete selected image from disk?"));
1745#ifdef GDK_WINDOWING_QUARTZ
1747#endif
1748
1749 gtk_window_set_title(GTK_WINDOW(dialog), _("delete image?"));
1750 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1751 gtk_widget_destroy(dialog);
1752 if(res != GTK_RESPONSE_YES)
1753 {
1755 return;
1756 }
1757 }
1759}
1760
1762{
1763 // Open file chooser dialog
1764 gchar *dir = NULL;
1766
1770 const int number = g_list_length(e->index);
1771 if(number == 0)
1772 {
1774 return;
1775 }
1776
1777 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1778 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1779 _("_select as destination"), _("_cancel"));
1780
1781 dt_conf_get_folder_to_file_chooser("ui_last/move_path", GTK_FILE_CHOOSER(filechooser));
1782 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1783 {
1784 dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1785 dt_conf_set_folder_from_file_chooser("ui_last/move_path", GTK_FILE_CHOOSER(filechooser));
1786 }
1787 g_object_unref(filechooser);
1788
1789 if(IS_NULL_PTR(dir) || !g_file_test(dir, G_FILE_TEST_IS_DIR)) goto abort;
1790
1791 // ugly, but we need to set this after constructing the job:
1793 // the job's cleanup function is responsible for freeing dir, so we don't do that here
1794
1795 if(dt_conf_get_bool("ask_before_move"))
1796 {
1797 GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1798 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1799 ngettext("do you really want to physically move %d image to %s?\n"
1800 "(all duplicates will be moved along)",
1801 "do you really want to physically move %d images to %s?\n"
1802 "(all duplicates will be moved along)",
1803 number),
1804 number, dir);
1805#ifdef GDK_WINDOWING_QUARTZ
1807#endif
1808 gtk_window_set_title(GTK_WINDOW(dialog), ngettext("move image?", "move images?", number));
1809
1810 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1811 gtk_widget_destroy(dialog);
1812
1813 if(res != GTK_RESPONSE_YES) goto abort;
1814 }
1815
1817 return;
1818
1819abort:
1820 dt_free(dir);
1822}
1823
1825{
1826 // Open file chooser dialog
1827 gchar *dir = NULL;
1832 const int number = g_list_length(e->index);
1833 if(number == 0)
1834 {
1836 return;
1837 }
1838
1839 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1840 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1841 _("_select as destination"), _("_cancel"));
1842
1843 dt_conf_get_folder_to_file_chooser("ui_last/copy_path", GTK_FILE_CHOOSER(filechooser));
1844 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1845 {
1846 dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1847 dt_conf_set_folder_from_file_chooser("ui_last/copy_path", GTK_FILE_CHOOSER(filechooser));
1848 }
1849 g_object_unref(filechooser);
1850
1851 if(IS_NULL_PTR(dir) || !g_file_test(dir, G_FILE_TEST_IS_DIR)) goto abort;
1852
1853 // ugly, but we need to set this after constructing the job:
1855 // the job's cleanup function is responsible for freeing dir, so we don't do that here
1856
1857 if(dt_conf_get_bool("ask_before_copy"))
1858 {
1859 GtkWidget *dialog = gtk_message_dialog_new(
1860 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1861 ngettext("do you really want to physically copy %d image to %s?",
1862 "do you really want to physically copy %d images to %s?", number),
1863 number, dir);
1864#ifdef GDK_WINDOWING_QUARTZ
1866#endif
1867 gtk_window_set_title(GTK_WINDOW(dialog), ngettext("copy image?", "copy images?", number));
1868
1869 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1870 gtk_widget_destroy(dialog);
1871
1872 if(res != GTK_RESPONSE_YES) goto abort;
1873 }
1874
1876 return;
1877
1878abort:
1879 dt_free(dir);
1881}
1882
1890
1898
1905
1907{
1909 if(IS_NULL_PTR(params)) return NULL;
1910
1911 params->data = calloc(1, sizeof(dt_control_export_t));
1912 if(IS_NULL_PTR(params->data))
1913 {
1915 return NULL;
1916 }
1917
1918 return params;
1919}
1920
1922{
1924
1925 dt_control_export_t *settings = (dt_control_export_t *)params->data;
1927 dt_imageio_module_data_t *sdata = settings->sdata;
1928
1929 mstorage->free_params(mstorage, sdata);
1930
1931 dt_free(settings->icc_filename);
1932 dt_free(settings->metadata_export);
1933 dt_free(params->data);
1934
1936}
1937
1938void dt_control_export(GList *imgid_list, const dt_control_export_request_t *request)
1939{
1941 if(IS_NULL_PTR(job)) return;
1943 if(IS_NULL_PTR(params))
1944 {
1946 return;
1947 }
1949
1950 params->index = imgid_list;
1951
1952 dt_control_export_t *data = params->data;
1953 data->max_width = request->max_width;
1954 data->max_height = request->max_height;
1955 data->scale_factor = request->scale_factor;
1956 data->format_index = request->format_index;
1957 data->storage_index = request->storage_index;
1959 g_assert(mstorage);
1960 // get shared storage param struct (global sequence counter, one picasa connection etc)
1961 dt_imageio_module_data_t *sdata = mstorage->get_params(mstorage);
1962 if(IS_NULL_PTR(sdata))
1963 {
1964 dt_control_alert(_("Export failed"), _("failed to get parameters from storage module, aborting export.."),
1965 mstorage->name(mstorage));
1967 return;
1968 }
1969 data->sdata = sdata;
1970 data->export_masks = request->export_masks;
1971 g_strlcpy(data->style, request->style, sizeof(data->style));
1972 data->icc_type = request->icc_type;
1973 data->icc_filename = g_strdup(request->icc_filename);
1974 data->icc_intent = request->icc_intent;
1975 data->metadata_export = g_strdup(request->metadata_export);
1976
1977 dt_control_job_add_progress(job, _("export images"), TRUE);
1979
1980 // tell the storage that we got its params for an export so it can reset itself to a safe state
1981 mstorage->export_dispatched(mstorage);
1982}
1983
1984static void _add_datetime_offset(const char *odt, const long int offset, char *ndt)
1985{
1986 // get the datetime_taken and calculate the new time
1987 GDateTime *datetime_original = dt_datetime_exif_to_gdatetime(odt, dt_datetime_utc_tz());
1988 if(IS_NULL_PTR(datetime_original))
1989 return;
1990
1991 // let's add our offset
1992 GDateTime *datetime_new = g_date_time_add(datetime_original, offset);
1993 g_date_time_unref(datetime_original);
1994
1995 if(IS_NULL_PTR(datetime_new))
1996 return;
1997 gchar *datetime = g_date_time_format(datetime_new, "%Y:%m:%d %H:%M:%S,%f");
1998 datetime[DT_DATETIME_LENGTH - 1] = '\0'; // limit to milliseconds
1999 g_date_time_unref(datetime_new);
2000
2001 if(datetime)
2002 g_strlcpy(ndt, datetime, DT_DATETIME_LENGTH);
2003 dt_free(datetime);
2004}
2005
2007{
2009 uint32_t cntr = 0;
2010 GList *t = params->index;
2011 const GTimeSpan offset = ((dt_control_datetime_t *)params->data)->offset;
2012 const char *datetime = ((dt_control_datetime_t *)params->data)->datetime;
2013 char message[512] = { 0 };
2014
2015 /* do we have any selected images and is offset != 0 */
2016 if(IS_NULL_PTR(t) || (offset == 0 && !datetime[0]))
2017 {
2018 return 1;
2019 }
2020
2021 const guint total = g_list_length(t);
2022
2023 const char *mes11 = offset ? N_("adding time offset to %d image") : N_("setting date/time of %d image");
2024 const char *mes12 = offset ? N_("adding time offset to %d images") : N_("setting date/time of %d images");
2025 snprintf(message, sizeof(message), ngettext(mes11, mes12, total), total);
2027
2028 GList *imgs = NULL;
2029 if(offset)
2030 {
2031 GArray *dtime = g_array_new(FALSE, TRUE, DT_DATETIME_LENGTH);
2032
2033 for(GList *img = t; img; img = g_list_next(img))
2034 {
2035 char odt[DT_DATETIME_LENGTH] = {0};
2036 dt_image_get_datetime(GPOINTER_TO_INT(img->data), odt);
2037 if(!odt[0]) continue;
2038
2039 char ndt[DT_DATETIME_LENGTH] = {0};
2040 _add_datetime_offset(odt, offset, ndt);
2041 if(!ndt[0]) continue;
2042
2043 // takes the option to include the grouped images
2044 GList *grps = dt_grouping_get_group_images(GPOINTER_TO_INT(img->data));
2045 for(GList *grp = grps; grp; grp = g_list_next(grp))
2046 {
2047 imgs = g_list_prepend(imgs, grp->data);
2048 g_array_append_val(dtime, ndt);
2049 cntr++;
2050 }
2051 g_list_free(grps);
2052 grps = NULL;
2053 }
2054 imgs = g_list_reverse(imgs);
2055 dt_image_set_datetimes(imgs, dtime, TRUE);
2056 }
2057 else
2058 {
2059 imgs = g_list_copy(t);
2060 // takes the option to include the grouped images
2062 cntr = g_list_length(imgs);
2063 dt_image_set_datetime(imgs, datetime, TRUE);
2064 }
2065
2066 const char *mes21 = offset ? N_("added time offset to %d image") : N_("set date/time of %d image");
2067 const char *mes22 = offset ? N_("added time offset to %d images") : N_("set date/time of %d images");
2068 dt_control_log(ngettext(mes21, mes22, cntr), cntr);
2069 return 0;
2070}
2071
2073{
2075 if(IS_NULL_PTR(params)) return NULL;
2076
2077 params->data = calloc(1, sizeof(dt_control_datetime_t));
2078 if(IS_NULL_PTR(params->data))
2079 {
2081 return NULL;
2082 }
2083
2084 return params;
2085}
2086
2095
2096static dt_job_t *dt_control_datetime_job_create(const GTimeSpan offset, const char *datetime, GList *imgs)
2097{
2099 if(IS_NULL_PTR(job)) return NULL;
2101 if(IS_NULL_PTR(params))
2102 {
2104 return NULL;
2105 }
2106 dt_control_job_add_progress(job, _("time offset"), FALSE);
2108
2109 if(imgs)
2110 params->index = imgs;
2111 else
2112 params->index = dt_act_on_get_images();
2113
2114 dt_control_datetime_t *data = params->data;
2115 data->offset = offset;
2116 if(!IS_NULL_PTR(datetime))
2117 memcpy(data->datetime, datetime, sizeof(data->datetime));
2118 else
2119 data->datetime[0] = '\0';
2120 params->data = data;
2121 return job;
2122}
2123
2124void dt_control_datetime(const GTimeSpan offset, const char *datetime, GList *imgs)
2125{
2127 dt_control_datetime_job_create(offset, datetime, imgs));
2128}
2129
2130// clang-format off
2131// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2132// vim: shiftwidth=2 expandtab tabstop=2 cindent
2133// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2134// 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_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_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
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:654
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_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)
void dt_control_export(GList *imgid_list, const dt_control_export_request_t *request)
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 gboolean _export_source_available(const int32_t imgid)
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:602
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:149
GList * dt_grouping_get_group_images(const int32_t imgid)
Definition grouping.c:131
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:225
const char flag
Definition image.h:320
@ DT_IMAGE_HAS_ADDITIONAL_DNG_TAGS
Definition image.h:154
@ 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_4BAYER
Definition image.h:149
@ DT_IMAGE_MONOCHROME
Definition image.h:151
@ DT_IMAGE_HDR
Definition image.h:135
@ DT_IMAGE_LDR
Definition image.h:131
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
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 ...
dt_imageio_retval_t 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, const double scale_factor, const gboolean thumbnail_export, const gboolean ignore_history, 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:106
void dt_control_job_cancel(_dt_job_t *job)
Definition jobs.c:183
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:138
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:419
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:132
void dt_control_job_set_progress(dt_job_t *job, double value)
Definition jobs.c:701
void dt_control_job_add_progress(dt_job_t *job, const char *message, gboolean cancellable)
Definition jobs.c:687
void dt_control_job_set_progress_message(dt_job_t *job, const char *message)
Definition jobs.c:695
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:115
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:156
@ DT_JOB_QUEUE_USER_FG
Definition jobs.h:55
@ DT_JOB_QUEUE_USER_EXPORT
Definition jobs.h:58
int32_t(* dt_job_execute_callback)(dt_job_t *)
Definition jobs.h:65
@ DT_JOB_STATE_CANCELLED
Definition jobs.h:48
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
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:390
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
Definition signal.h:225
@ DT_SIGNAL_FILMROLLS_CHANGED
This signal is raised when a filmroll is deleted/changed but not imported.
Definition signal.h:160
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
Definition signal.h:140
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
Definition signal.h:134
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
dt_pthread_mutex_t mutex
char datetime[DT_DATETIME_LENGTH]
dt_colorspaces_color_profile_type_t icc_type
dt_iop_color_intent_t icc_intent
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:376
float exif_iso
Definition image.h:379
float exif_aperture
Definition image.h:378
int32_t flags
Definition image.h:410
dt_image_orientation_t orientation
Definition image.h:375
float exif_focal_length
Definition image.h:380
int32_t crop_y
Definition image.h:407
int32_t crop_x
Definition image.h:407
dt_iop_buffer_dsc_t dsc
Definition image.h:428
float adobe_XYZ_to_CAM[4][3]
Definition image.h:458
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:395
int32_t id
Definition image.h:410
dt_aligned_pixel_t wb_coeffs
Definition image.h:455
char fullpath[DT_PATH_MAX]
Definition image.h:396
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:614
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
Definition utility.c:856
size_t safe_strlen(const char *str)
check if the string is empty or NULL before calling strlen()
Definition utility.c:117
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:144
@ 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,...