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/collection.h"
82#include "common/darktable.h"
83#include "common/debug.h"
84#include "common/exif.h"
85#include "common/film.h"
86#include "common/gpx.h"
87#include "common/history.h"
89#include "common/image.h"
90#include "common/image_cache.h"
91#include "common/imageio.h"
92#include "common/imageio_dng.h"
94#include "common/mipmap_cache.h"
95#include "common/tags.h"
96#include "common/undo.h"
97#include "common/grouping.h"
98#include "common/utility.h"
99#include "common/datetime.h"
100#include "control/conf.h"
101#include "develop/imageop_math.h"
102#include "develop/develop.h"
103
104#include "common/selection.h"
105
106#include "gui/gtk.h"
107
108#include <gio/gio.h>
109#include <glib.h>
110#include <glib/gstdio.h>
111#ifndef _WIN32
112#include <glob.h>
113#endif
114#ifdef __APPLE__
115#include "osx/osx.h"
116#endif
117#ifdef _WIN32
118#include "win/dtwin.h"
119#endif
120
121// Control of the collection updates during an import. Start with a short interval to feel responsive,
122// but use fairly infrequent updates for large imports to minimize overall time.
123#define INIT_UPDATE_INTERVAL 2 //seconds
124#define MAX_UPDATE_INTERVAL 3.0 //seconds
125// How long (in seconds) between updates of the "Importing N/M" progress indicator? Should be relatively
126// short to avoid the impression that the import has gotten stuck. Setting this too low will impact the
127// overall time for a large import.
128#define PROGRESS_UPDATE_INTERVAL 1
129
135
141
143{
145 dt_imageio_module_data_t *sdata; // needed since the gui thread resets things like overwrite once the export
146 // is dispatched, but we have to keep that information
147 gboolean export_masks;
148 char style[128];
154
155
156/* enumerator of images from filmroll */
158{
159 sqlite3_stmt *stmt;
160 /* get a list of images in filmroll */
161 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT id FROM main.images WHERE film_id = ?1", -1,
162 &stmt, NULL);
163 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, filmid);
164
165 while(sqlite3_step(stmt) == SQLITE_ROW)
166 {
167 const int32_t imgid = sqlite3_column_int(stmt, 0);
168 t->index = g_list_append(t->index, GINT_TO_POINTER(imgid));
169 }
170 sqlite3_finalize(stmt);
171}
172
174 int32_t (*fileop_callback)(const int32_t,
175 const int32_t),
176 const char *desc, const char *desc_pl)
177{
179 GList *t = params->index;
180 const guint total = g_list_length(t);
181 char message[512] = { 0 };
182 double fraction = 0;
183 gchar *newdir = (gchar *)params->data;
184
185 g_snprintf(message, sizeof(message), ngettext(desc, desc_pl, total), total);
187
188 // create new film roll for the destination directory
189 dt_film_t new_film;
190 const int32_t film_id = dt_film_new(&new_film, newdir);
191 dt_free(newdir);
192
193 if(film_id <= 0)
194 {
195 dt_control_log(_("failed to create film roll for destination directory, aborting move.."));
196 return -1;
197 }
198
199 gboolean completeSuccess = TRUE;
201 {
202 completeSuccess &= (fileop_callback(GPOINTER_TO_INT(t->data), film_id) != -1);
203 t = g_list_next(t);
204 fraction += 1.0 / total;
205 dt_control_job_set_progress(job, fraction);
206 }
207
208 if(completeSuccess)
209 {
210 char collect[1024];
211 snprintf(collect, sizeof(collect), "1:0:0:%s$", new_film.dirname);
213 }
217 g_list_copy(params->index));
219 return 0;
220}
221
223{
225 if(IS_NULL_PTR(params)) return NULL;
226 return params;
227}
228
230{
232
233 if(params->index)
234 {
235 g_list_free(params->index);
236 params->index = NULL;
237 }
238 params->index = NULL;
239
240 dt_free(params);
241}
242
244
246 int flag, gpointer data, progress_type_t progress_type,
247 gboolean only_visible)
248{
249 dt_job_t *job = dt_control_job_create(execute, "%s", message);
250 if(IS_NULL_PTR(job)) return NULL;
252 if(IS_NULL_PTR(params))
253 {
255 return NULL;
256 }
257 if(progress_type != PROGRESS_NONE)
258 dt_control_job_add_progress(job, _(message), progress_type == PROGRESS_CANCELLABLE);
259 params->index = dt_act_on_get_images();
260
262
263 params->flag = flag;
264 params->data = data;
265 return job;
266}
267
269 int flag, gpointer data, progress_type_t progress_type,
270 int32_t imgid)
271{
272 dt_job_t *job = dt_control_job_create(execute, "%s", message);
273 if(IS_NULL_PTR(job)) return NULL;
275 if(IS_NULL_PTR(params))
276 {
278 return NULL;
279 }
280 if(progress_type != PROGRESS_NONE)
281 dt_control_job_add_progress(job, _(message), progress_type == PROGRESS_CANCELLABLE);
282
283 params->index = g_list_append(NULL, GINT_TO_POINTER(imgid));
284
286
287 params->flag = flag;
288 params->data = data;
289 return job;
290}
291
292
294{
295 if(!dt_image_get_xmp_mode()) return 0;
296
298
299 for(GList *t = params->index; t; t = g_list_next(t))
300 {
301 const int32_t imgid = GPOINTER_TO_INT(t->data);
303 fprintf(stdout,
304 "cannot write XMP file for image %i. The target storage may be unavailable or read-only.\n",
305 imgid);
306 }
307 return 0;
308}
309
311{
312 GList *imgs = dt_act_on_get_images();
313 if(IS_NULL_PTR(imgs)) return;
315 g_list_free(imgs);
316 imgs = NULL;
317}
318
320{
321 uint32_t first_imgid;
322 uint32_t first_filter;
323 uint8_t first_xtrans[6][6];
324
325 float *pixels, *weight;
326
327 int wd;
328 int ht;
330
332 float epsw;
334 float adobe_XYZ_to_CAM[4][3];
336
337 // 0 - ok; 1 - errors, abort
338 gboolean abort;
340
346
348{
349 return 32;
350}
351
356
358{
359 return "memory";
360}
361
362static float envelope(const float xx)
363{
364 const float x = CLAMPS(xx, 0.0f, 1.0f);
365 // const float alpha = 2.0f;
366 const float beta = 0.5f;
367 if(x < beta)
368 {
369 // return 1.0f-fabsf(x/beta-1.0f)^2
370 const float tmp = fabsf(x / beta - 1.0f);
371 return 1.0f - tmp * tmp;
372 }
373 else
374 {
375 const float tmp1 = (1.0f - x) / (1.0f - beta);
376 const float tmp2 = tmp1 * tmp1;
377 const float tmp3 = tmp2 * tmp1;
378 return 3.0f * tmp2 - 2.0f * tmp3;
379 }
380}
381
382static int dt_control_merge_hdr_process(dt_imageio_module_data_t *datai, const char *filename,
383 const void *const ivoid,
384 dt_colorspaces_color_profile_type_t over_type, const char *over_filename,
385 void *exif, int exif_len, int32_t imgid, int num, int total,
386 dt_dev_pixelpipe_t *pipe, const gboolean export_masks)
387{
389 dt_control_merge_hdr_t *d = data->d;
390
391 // just take a copy. also do it after blocking read, so filters will make sense.
392 const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
393 const dt_image_t image = *img;
395
396 if(IS_NULL_PTR(d->pixels))
397 {
398 d->first_imgid = imgid;
399 d->first_filter = image.dsc.filters;
400 // sensor layout is just passed on to be written to dng.
401 // we offset it to the crop of the image here, so we don't
402 // need to load in the FCxtrans dependency into the dng writer.
403 // for some stupid reason the dng needs this layout wrt cropped
404 // offsets, not globally.
405 dt_iop_roi_t roi = {0};
406 roi.x = image.crop_x;
407 roi.y = image.crop_y;
408 for(int j=0;j<6;j++)
409 for(int i = 0; i < 6; i++) d->first_xtrans[j][i] = FCxtrans(j, i, &roi, image.dsc.xtrans);
410 d->pixels = calloc((size_t)datai->width * datai->height, sizeof(float));
411 d->weight = calloc((size_t)datai->width * datai->height, sizeof(float));
412 d->wd = datai->width;
413 d->ht = datai->height;
414 d->orientation = image.orientation;
415 for(int i = 0; i < 3; i++)
416 d->wb_coeffs[i] = image.wb_coeffs[i];
417 for(int k=0; k<4; k++)
418 for(int i=0; i<3; i++)
419 d->adobe_XYZ_to_CAM[k][i] = image.adobe_XYZ_to_CAM[k][i];
420 }
421
422 if(image.dsc.filters == 0u || image.dsc.channels != 1 || image.dsc.datatype != TYPE_UINT16)
423 {
424 dt_control_log(_("exposure bracketing only works on raw images."));
425 d->abort = TRUE;
426 return 1;
427 }
428 else if(datai->width != d->wd || datai->height != d->ht || d->first_filter != image.dsc.filters
429 || d->orientation != image.orientation)
430 {
431 dt_control_log(_("images have to be of same size and orientation!"));
432 d->abort = TRUE;
433 return 1;
434 }
435
436 // if no valid exif data can be found, assume peleng fisheye at f/16, 8mm, with half of the light lost in
437 // the system => f/22
438 const float eap = image.exif_aperture > 0.0f ? image.exif_aperture : 22.0f;
439 const float efl = image.exif_focal_length > 0.0f ? image.exif_focal_length : 8.0f;
440 const float rad = .5f * efl / eap;
441 const float aperture = M_PI * rad * rad;
442 const float iso = image.exif_iso > 0.0f ? image.exif_iso : 100.0f;
443 const float exp = image.exif_exposure > 0.0f ? image.exif_exposure : 1.0f;
444 const float cal = 100.0f / (aperture * exp * iso);
445 // about proportional to how many photons we can expect from this shot:
446 const float photoncnt = 100.0f * aperture * exp / iso;
447 float saturation = 1.0f;
448 d->whitelevel = fmaxf(d->whitelevel, saturation * cal);
449 __OMP_PARALLEL_FOR__(collapse(2))
450 for(int y = 0; y < d->ht; y++)
451 for(int x = 0; x < d->wd; x++)
452 {
453 // read unclamped raw value with subtracted black and rescaled to 1.0 saturation.
454 // this is the output of the rawprepare iop.
455 const float in = ((float *)ivoid)[x + d->wd * y];
456 // weights based on siggraph 12 poster
457 // zijian zhu, zhengguo li, susanto rahardja, pasi fraenti
458 // 2d denoising factor for high dynamic range imaging
459 float w = photoncnt;
460
461 // need some safety margin due to upsampling and 16-bit quantization + dithering?
462 float offset = 3000.0f / (float)UINT16_MAX;
463
464 // cannot do an envelope based on single pixel values here, need to get
465 // maximum value of all color channels. to find that, go through the
466 // pattern block (we conservatively do a 3x3 for bayer or xtrans):
467 int xx = x & ~1, yy = y & ~1;
468 float M = 0.0f, m = FLT_MAX;
469 if(xx < d->wd - 2 && yy < d->ht - 2)
470 {
471 for(int i = 0; i < 3; i++)
472 for(int j = 0; j < 3; j++)
473 {
474 M = MAX(M, ((float *)ivoid)[xx + i + d->wd * (yy + j)]);
475 m = MIN(m, ((float *)ivoid)[xx + i + d->wd * (yy + j)]);
476 }
477 // move envelope a little to allow non-zero weight even for clipped regions.
478 // this is because even if the 2x2 block is clipped somewhere, the other channels
479 // might still prove useful. we'll check for individual channel saturation below.
480 w *= d->epsw + envelope((M + offset) / saturation);
481 }
482
483 if(M + offset >= saturation)
484 {
485 if(d->weight[x + d->wd * y] <= 0.0f)
486 { // only consider saturated pixels in case we have nothing better:
487 if(d->weight[x + d->wd * y] == 0 || m < -d->weight[x + d->wd * y])
488 {
489 if(m + offset >= saturation)
490 d->pixels[x + d->wd * y] = 1.0f; // let's admit we were completely clipped, too
491 else
492 d->pixels[x + d->wd * y] = in * cal / d->whitelevel;
493 d->weight[x + d->wd * y]
494 = -m; // could use -cal here, but m is per pixel and safer for varying illumination conditions
495 }
496 }
497 // else silently ignore, others have filled in a better color here already
498 }
499 else
500 {
501 if(d->weight[x + d->wd * y] <= 0.0)
502 { // cleanup potentially blown highlights from earlier images
503 d->pixels[x + d->wd * y] = 0.0f;
504 d->weight[x + d->wd * y] = 0.0f;
505 }
506 d->pixels[x + d->wd * y] += w * in * cal;
507 d->weight[x + d->wd * y] += w;
508 }
509 }
510
511 return 0;
512}
513
515{
517 GList *t = params->index;
518 const guint total = g_list_length(t);
519 char message[512] = { 0 };
520 double fraction = 0;
521 snprintf(message, sizeof(message), ngettext("merging %d image", "merging %d images", total), total);
522
524
526
530 .write_image = dt_control_merge_hdr_process };
531
533
534 int num = 1;
535 while(t)
536 {
537 if(d.abort) goto end;
538
539 const int32_t imgid = GPOINTER_TO_INT(t->data);
540
541 const gboolean is_scaling =
542 dt_conf_is_equal("plugins/lighttable/export/resizing", "scaling");
543
544 dt_imageio_export_with_flags(imgid, "unused", &buf, (dt_imageio_module_data_t *)&dat, TRUE, FALSE, FALSE, is_scaling,
545 FALSE, "pre:rawprepare", FALSE, FALSE, DT_COLORSPACE_NONE, NULL, DT_INTENT_LAST, NULL,
546 NULL, num, total, NULL, NULL);
547
548 t = g_list_next(t);
549
550 /* update the progress bar */
551 fraction += 1.0 / (total + 1);
552 dt_control_job_set_progress(job, fraction);
553 num++;
554 }
555
556 if(d.abort) goto end;
557
558// normalize by white level to make clipping at 1.0 work as expected
560 for(size_t k = 0; k < (size_t)d.wd * d.ht; k++)
561 {
562 if(d.weight[k] > 0.0) d.pixels[k] = fmaxf(0.0f, d.pixels[k] / (d.whitelevel * d.weight[k]));
563 }
564
565 // output hdr as digital negative with exif data.
566 uint8_t *exif = NULL;
567 char pathname[PATH_MAX] = { 0 };
568 gboolean from_cache = TRUE;
569 dt_image_full_path(d.first_imgid, pathname, sizeof(pathname), &from_cache, __FUNCTION__);
570
571 // last param is dng mode
572 const int exif_len = dt_exif_read_blob(&exif, pathname, d.first_imgid, 0, d.wd, d.ht, 1);
573 char *c = pathname + safe_strlen(pathname);
574 while(*c != '.' && c > pathname) c--;
575 g_strlcpy(c, "-hdr.dng", sizeof(pathname) - (c - pathname));
576 dt_imageio_write_dng(pathname,
577 d.pixels,
578 d.wd,
579 d.ht,
580 exif,
581 exif_len,
582 d.first_filter,
583 (const uint8_t (*)[6])d.first_xtrans,
584 1.0f,
585 (const float (*))d.wb_coeffs,
586 d.adobe_XYZ_to_CAM);
587 dt_free(exif);
588
590
591 while(*c != '/' && c > pathname) c--;
592 dt_control_log(_("wrote merged HDR `%s'"), c + 1);
593
594 // import new image
595 gchar *directory = g_path_get_dirname((const gchar *)pathname);
596 dt_film_t film;
597 const int filmid = dt_film_new(&film, directory);
598 const uint32_t imageid = dt_image_import(filmid, pathname, TRUE);
599 dt_free(directory);
600
601 // refresh the thumbtable view
603 g_list_prepend(NULL, GINT_TO_POINTER(imageid)));
606
607end:
608 dt_free(d.pixels);
609 dt_free(d.weight);
610
611 return 0;
612}
613
615{
617 GList *t = params->index;
618 const guint total = g_list_length(t);
619 double fraction = 0.0f;
620 char message[512] = { 0 };
621
623
624 snprintf(message, sizeof(message), ngettext("duplicating %d image", "duplicating %d images", total), total);
626 while(t)
627 {
628 const int32_t imgid = GPOINTER_TO_INT(t->data);
629 // Defer exposing the duplicate to the collection/lighttable grid until its history is
630 // settled below -- otherwise the grid can generate and cache a thumbnail from the
631 // still-historyless row before the copy/delete below ever runs.
632 const int newimgid = dt_image_duplicate_no_reload(imgid);
633 if(newimgid != UNKNOWN_IMAGE)
634 {
635 if(GPOINTER_TO_INT(params->data))
637 else
639
641 }
642 t = g_list_next(t);
643 fraction += 1.0 / total;
644 dt_control_job_set_progress(job, fraction);
645 }
646
648
651 return 0;
652}
653
655{
657 const int cw = params->flag;
658 GList *t = params->index;
659 const guint total = g_list_length(t);
660 double fraction = 0.0f;
661 char message[512] = { 0 };
662
664
665 snprintf(message, sizeof(message), ngettext("flipping %d image", "flipping %d images", total), total);
667 while(t)
668 {
669 const int32_t imgid = GPOINTER_TO_INT(t->data);
670 dt_image_flip(imgid, cw);
671 t = g_list_next(t);
672 fraction += 1.0 / total;
673 dt_control_job_set_progress(job, fraction);
674 }
675
678 return 0;
679}
681{
683 const int32_t mode = params->flag;
684 GList *t = params->index;
685 const guint total = g_list_length(t);
686 char message[512] = { 0 };
687 double fraction = 0.0f;
688
690
691 if(mode == 0)
692 snprintf(message, sizeof(message), ngettext("set %d color image", "setting %d color images", total), total);
693 else
694 snprintf(message, sizeof(message), ngettext("set %d monochrome image", "setting %d monochrome images", total), total);
695
697 while(t)
698 {
699 const int32_t imgid = GPOINTER_TO_INT(t->data);
700
701 if(imgid >= 0)
702 {
703 dt_image_set_monochrome_flag(imgid, mode == 2);
704 }
705 else
706 fprintf(stderr,"[dt_control_monochrome_images_job_run] got illegal imgid %i\n", imgid);
707
708 t = g_list_next(t);
709 fraction += 1.0 / total;
710 dt_control_job_set_progress(job, fraction);
711 }
712
714
716 g_list_copy(params->index));
718 return 0;
719}
720
721static char *_get_image_list(GList *l)
722{
723 const guint size = g_list_length(l);
724 char num[8];
725 char *buffer = calloc(size, sizeof(num));
726 gboolean first = TRUE;
727
728 buffer[0] = '\0';
729
730 while(l)
731 {
732 const int32_t imgid = GPOINTER_TO_INT(l->data);
733 snprintf(num, sizeof(num), "%s%6d", first ? "" : ",", imgid);
734 g_strlcat(buffer, num, size * sizeof(num));
735 l = g_list_next(l);
736 first = FALSE;
737 }
738 return buffer;
739}
740
741static void _set_remove_flag(char *imgs)
742{
743 sqlite3_stmt *stmt = NULL;
745 "UPDATE main.images SET flags = (flags|?1) WHERE id IN (?2)", -1, &stmt, NULL);
747 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, imgs, -1, SQLITE_STATIC);
748 sqlite3_step(stmt);
749 sqlite3_finalize(stmt);
750}
751
752static GList *_get_full_pathname(char *imgs)
753{
754 sqlite3_stmt *stmt = NULL;
755 GList *list = NULL;
756 // clang-format off
758 "SELECT DISTINCT folder || '" G_DIR_SEPARATOR_S "' || filename FROM "
759 "main.images i, main.film_rolls f "
760 "ON i.film_id = f.id WHERE i.id IN (?1)",
761 -1, &stmt, NULL);
762 // clang-format on
763 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, imgs, -1, SQLITE_STATIC);
764 while(sqlite3_step(stmt) == SQLITE_ROW)
765 {
766 list = g_list_prepend(list, g_strdup((const gchar *)sqlite3_column_text(stmt, 0)));
767 }
768 sqlite3_finalize(stmt);
769 return g_list_reverse(list); // list was built in reverse order, so un-reverse it
770}
771
772// Regenerate .xmp files for any surviving duplicate at each of `pathnames` (owned, g_strdup'd
773// full paths from _get_full_pathname(); this call takes ownership and frees them), batched into
774// a single dt_control_save_xmps() job. dt_image_synch_all_xmp() spawns one DT_JOB_QUEUE_USER_FG
775// job per path -- fine for the handful of images a normal removal/delete touches, but for a large
776// recursive folder removal it flooded that same queue (the one this job itself runs on) with one
777// tiny job per path, each spawning its own GUI progress bar: stalled the app for minutes, looking
778// like an infinite loop.
779static void _resync_xmp_duplicates(GList *pathnames)
780{
781 GList *resync_imgids = NULL;
783 {
784 for(GList *l = pathnames; l; l = g_list_next(l))
785 {
786 const int32_t dup_imgid = dt_image_get_id_full_path((const char *)l->data);
787 if(dup_imgid != UNKNOWN_IMAGE) resync_imgids = g_list_prepend(resync_imgids, GINT_TO_POINTER(dup_imgid));
788 }
789 }
790 g_list_free_full(pathnames, dt_free_gpointer);
791 if(!IS_NULL_PTR(resync_imgids)) dt_control_save_xmps(resync_imgids, FALSE);
792 g_list_free(resync_imgids);
793}
794
796{
798 GList *t = params->index;
799 char *imgs = _get_image_list(t);
800 const guint total = g_list_length(t);
801 char message[512] = { 0 };
802 snprintf(message, sizeof(message), ngettext("removing %d image", "removing %d images", total), total);
804 sqlite3_stmt *stmt = NULL;
805
806 // check that we can safely remove the image
807 gboolean remove_ok = TRUE;
809 "SELECT id FROM main.images WHERE id IN (?2) AND flags&?1=?1", -1, &stmt, NULL);
811 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, imgs, -1, SQLITE_STATIC);
812
813 while(sqlite3_step(stmt) == SQLITE_ROW)
814 {
815 const int32_t imgid = sqlite3_column_int(stmt, 0);
816 if(!dt_image_safe_remove(imgid))
817 {
818 remove_ok = FALSE;
819 break;
820 }
821 }
822 sqlite3_finalize(stmt);
823
824 if(!remove_ok)
825 {
826 dt_control_log(_("cannot remove local copy when the original file is not accessible."));
827 dt_free(imgs);
828 return 0;
829 }
830
831 // update remove status
832 _set_remove_flag(imgs);
833
835
836 // We need a list of files to regenerate .xmp files if there are duplicates
837 GList *list = _get_full_pathname(imgs);
838
839 dt_free(imgs);
840
841 double fraction = 0.0f;
842 while(t)
843 {
844 int32_t imgid = GPOINTER_TO_INT(t->data);
845 dt_image_remove(imgid);
846 t = g_list_next(t);
847 fraction += 1.0 / total;
848 dt_control_job_set_progress(job, fraction);
849 }
850
854 g_list_copy(params->index));
857
858 return 0;
859}
860
862{
864 const char *filename;
865 const char *error_message;
866
868
869 dt_pthread_mutex_t mutex;
870 pthread_cond_t cond;
872
880
889
890static gboolean _dt_delete_dialog_main_thread(gpointer user_data)
891{
892 _dt_delete_modal_dialog_t* modal_dialog = (_dt_delete_modal_dialog_t*)user_data;
893 dt_pthread_mutex_lock(&modal_dialog->mutex);
894
895 GtkWidget *dialog = gtk_message_dialog_new(
896 GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)),
897 GTK_DIALOG_DESTROY_WITH_PARENT,
898 GTK_MESSAGE_QUESTION,
899 GTK_BUTTONS_NONE,
900 modal_dialog->send_to_trash
901 ? _("could not send %s to trash.%s%s")
902 : _("could not physically delete %s.%s%s"),
903 modal_dialog->filename,
904 !IS_NULL_PTR(modal_dialog->error_message) ? "\n" : "",
905 !IS_NULL_PTR(modal_dialog->error_message) ? modal_dialog->error_message : "");
906#ifdef GDK_WINDOWING_QUARTZ
908#endif
909
910 if(modal_dialog->send_to_trash)
911 {
912 gtk_dialog_add_button(GTK_DIALOG(dialog), _("physically delete"), _DT_DELETE_DIALOG_CHOICE_DELETE);
913 gtk_dialog_add_button(GTK_DIALOG(dialog), _("physically delete all files"), _DT_DELETE_DIALOG_CHOICE_DELETE_ALL);
914 }
915 gtk_dialog_add_button(GTK_DIALOG(dialog), _("only remove from the image library"), _DT_DELETE_DIALOG_CHOICE_REMOVE);
916 gtk_dialog_add_button(GTK_DIALOG(dialog), _("skip to next file"), _DT_DELETE_DIALOG_CHOICE_CONTINUE);
917 gtk_dialog_add_button(GTK_DIALOG(dialog), _("stop process"), _DT_DELETE_DIALOG_CHOICE_STOP);
918
919 gtk_window_set_title(
920 GTK_WINDOW(dialog),
921 modal_dialog->send_to_trash
922 ? _("trashing error")
923 : _("deletion error"));
924 modal_dialog->dialog_result = gtk_dialog_run(GTK_DIALOG(dialog));
925 gtk_widget_destroy(dialog);
926
927 pthread_cond_signal(&modal_dialog->cond);
928
929 dt_pthread_mutex_unlock(&modal_dialog->mutex);
930
931 // Don't call again on next idle time
932 return FALSE;
933}
934
935static gint _dt_delete_file_display_modal_dialog(int send_to_trash, const char *filename, const char *error_message)
936{
937 _dt_delete_modal_dialog_t modal_dialog;
938 modal_dialog.send_to_trash = send_to_trash;
939 modal_dialog.filename = filename;
940 modal_dialog.error_message = error_message;
941
942 modal_dialog.dialog_result = GTK_RESPONSE_NONE;
943
944 dt_pthread_mutex_init(&modal_dialog.mutex, NULL);
945 pthread_cond_init(&modal_dialog.cond, NULL);
946
947 dt_pthread_mutex_lock(&modal_dialog.mutex);
948
949 gdk_threads_add_idle(_dt_delete_dialog_main_thread, &modal_dialog);
950 while (modal_dialog.dialog_result == GTK_RESPONSE_NONE)
951 dt_pthread_cond_wait(&modal_dialog.cond, &modal_dialog.mutex);
952
953 dt_pthread_mutex_unlock(&modal_dialog.mutex);
954 dt_pthread_mutex_destroy(&modal_dialog.mutex);
955 pthread_cond_destroy(&modal_dialog.cond);
956
957 return modal_dialog.dialog_result;
958}
959
960static enum _dt_delete_status delete_file_from_disk(const char *filename, gboolean *delete_on_trash_error)
961{
962 enum _dt_delete_status delete_status = _DT_DELETE_STATUS_UNKNOWN;
963
964 GFile *gfile = g_file_new_for_path(filename);
965 int send_to_trash = dt_conf_get_bool("send_to_trash");
966
967 while (delete_status == _DT_DELETE_STATUS_UNKNOWN)
968 {
969 gboolean delete_success = FALSE;
970 GError *gerror = NULL;
971 if(send_to_trash)
972 {
973#ifdef __APPLE__
974 delete_success = dt_osx_file_trash(filename, &gerror);
975#elif defined(_WIN32)
976 delete_success = dt_win_file_trash(gfile, NULL /*cancellable*/, &gerror);
977#else
978 delete_success = g_file_trash(gfile, NULL /*cancellable*/, &gerror);
979#endif
980 }
981 else
982 {
983 delete_success = g_file_delete(gfile, NULL /*cancellable*/, &gerror);
984 }
985
986 // Delete is a success or the file does not exists: OK to remove from darktable
987 if(delete_success
988 || g_error_matches(gerror, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
989 {
990 delete_status = _DT_DELETE_STATUS_OK_TO_REMOVE;
991 }
992 else if(send_to_trash && *delete_on_trash_error)
993 {
994 // Loop again, this time delete instead of trashing
995 delete_status = _DT_DELETE_STATUS_UNKNOWN;
996 send_to_trash = FALSE;
997 }
998 else
999 {
1000 const char *filename_display = NULL;
1001 GFileInfo *gfileinfo = g_file_query_info(
1002 gfile,
1003 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
1004 G_FILE_QUERY_INFO_NONE,
1005 NULL /*cancellable*/,
1006 NULL /*error*/);
1007 if(!IS_NULL_PTR(gfileinfo))
1008 filename_display = g_file_info_get_attribute_string(
1009 gfileinfo,
1010 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
1011
1013 send_to_trash,
1014 IS_NULL_PTR(filename_display) ? filename : filename_display,
1015 IS_NULL_PTR(gerror) ? NULL : gerror->message);
1016 g_object_unref(gfileinfo);
1017 if(send_to_trash && res == _DT_DELETE_DIALOG_CHOICE_DELETE)
1018 {
1019 // Loop again, this time delete instead of trashing
1020 delete_status = _DT_DELETE_STATUS_UNKNOWN;
1021 send_to_trash = FALSE;
1022 }
1023 else if(send_to_trash && res == _DT_DELETE_DIALOG_CHOICE_DELETE_ALL)
1024 {
1025 // Loop again, this time delete instead of trashing
1026 delete_status = _DT_DELETE_STATUS_UNKNOWN;
1027 send_to_trash = FALSE;
1028 *delete_on_trash_error = TRUE;
1029 }
1030 else if(res == _DT_DELETE_DIALOG_CHOICE_REMOVE)
1031 {
1032 delete_status = _DT_DELETE_STATUS_OK_TO_REMOVE;
1033 }
1034 else if(res == _DT_DELETE_DIALOG_CHOICE_CONTINUE)
1035 {
1036 delete_status = _DT_DELETE_STATUS_SKIP_FILE;
1037 }
1038 else
1039 {
1040 delete_status = _DT_DELETE_STATUS_STOP_PROCESSING;
1041 }
1042 }
1043 if(!IS_NULL_PTR(gerror))
1044 g_error_free(gerror);
1045 }
1046
1047 if(!IS_NULL_PTR(gfile))
1048 g_object_unref(gfile);
1049
1050 return delete_status;
1051}
1052
1053
1055{
1057 GList *t = params->index;
1058 char *imgs = _get_image_list(t);
1059 char imgidstr[25] = { 0 };
1060 const guint total = g_list_length(t);
1061 double fraction = 0.0f;
1062 char message[512] = { 0 };
1063 gboolean delete_on_trash_error = FALSE;
1064 if(dt_conf_get_bool("send_to_trash"))
1065 snprintf(message, sizeof(message), ngettext("trashing %d image", "trashing %d images", total), total);
1066 else
1067 snprintf(message, sizeof(message), ngettext("deleting %d image", "deleting %d images", total), total);
1069
1070 sqlite3_stmt *stmt;
1071
1073
1074 // We need a list of files to regenerate .xmp files if there are duplicates
1075 GList *list = _get_full_pathname(imgs);
1076
1077 dt_free(imgs);
1078
1080 "SELECT COUNT(*) FROM main.images WHERE filename IN (SELECT filename FROM "
1081 "main.images WHERE id = ?1) AND film_id IN (SELECT film_id FROM main.images WHERE "
1082 "id = ?1)", -1, &stmt, NULL);
1083 while(t)
1084 {
1085 enum _dt_delete_status delete_status = _DT_DELETE_STATUS_UNKNOWN;
1086 const int32_t imgid = GPOINTER_TO_INT(t->data);
1087 char filename[PATH_MAX] = { 0 };
1088 gboolean from_cache = FALSE;
1089 dt_image_full_path(imgid, filename, sizeof(filename), &from_cache, __FUNCTION__);
1090
1091#ifdef _WIN32
1092 char *dirname = g_path_get_dirname(filename);
1093#endif
1094
1095 int duplicates = 0;
1096 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
1097 if(sqlite3_step(stmt) == SQLITE_ROW) duplicates = sqlite3_column_int(stmt, 0);
1098 sqlite3_reset(stmt);
1099 sqlite3_clear_bindings(stmt);
1100
1101 // remove from disk:
1102 if(duplicates == 1)
1103 {
1104 // first check for local copies, never delete a file whose original file is not accessible
1105 if(dt_image_local_copy_reset(imgid))
1106 goto delete_next_file;
1107
1108 snprintf(imgidstr, sizeof(imgidstr), "%d", imgid);
1109 _set_remove_flag(imgidstr);
1110 dt_image_remove(imgid);
1111
1112 // there are no further duplicates so we can remove the source data file
1113 delete_status = delete_file_from_disk(filename, &delete_on_trash_error);
1114 if(delete_status != _DT_DELETE_STATUS_OK_TO_REMOVE)
1115 goto delete_next_file;
1116
1117 // all sidecar files - including left-overs - can be deleted;
1118 // left-overs can result when previously duplicates have been REMOVED;
1119 // no need to keep them as the source data file is gone.
1120
1121 GList *files = dt_image_find_xmps(filename);
1122
1123 for(GList *file_iter = files; file_iter; file_iter = g_list_next(file_iter))
1124 {
1125 delete_status = delete_file_from_disk(file_iter->data, &delete_on_trash_error);
1126 if(delete_status != _DT_DELETE_STATUS_OK_TO_REMOVE)
1127 break;
1128 }
1129
1130 g_list_free_full(files, dt_free_gpointer);
1131 files = NULL;
1132 }
1133 else
1134 {
1135 // don't remove the actual source data if there are further duplicates using it;
1136 // just delete the xmp file of the duplicate selected.
1137
1138 dt_image_path_append_version(imgid, filename, sizeof(filename));
1139 g_strlcat(filename, ".xmp", sizeof(filename));
1140
1141 // remove image from db first ...
1142 snprintf(imgidstr, sizeof(imgidstr), "%d", imgid);
1143 _set_remove_flag(imgidstr);
1144 dt_image_remove(imgid);
1145
1146 // ... and delete afterwards because removing will re-write the XMP
1147 delete_status = delete_file_from_disk(filename, &delete_on_trash_error);
1148 }
1149
1150delete_next_file:
1151#ifdef _WIN32
1152 dt_free(dirname);
1153#endif
1154 t = g_list_next(t);
1155 fraction += 1.0 / total;
1156 dt_control_job_set_progress(job, fraction);
1157 if(delete_status == _DT_DELETE_STATUS_STOP_PROCESSING)
1158 break;
1159 }
1160
1161 sqlite3_finalize(stmt);
1162
1164 list = NULL;
1167 g_list_copy(params->index));
1170 return 0;
1171}
1172
1174{
1176 GList *t = params->index;
1177 struct dt_gpx_t *gpx = NULL;
1178 uint32_t cntr = 0;
1179 const dt_control_gpx_apply_t *d = params->data;
1180 const gchar *filename = d->filename;
1181 const gchar *tz = d->tz;
1182 /* do we have any selected images */
1183 if(IS_NULL_PTR(t)) goto bail_out;
1184
1185 /* try parse the gpx data */
1186 gpx = dt_gpx_new(filename);
1187 if(IS_NULL_PTR(gpx))
1188 {
1189 dt_control_log(_("failed to parse GPX file"));
1190 goto bail_out;
1191 }
1192
1193 GTimeZone *tz_camera = (IS_NULL_PTR(tz)) ? g_time_zone_new_utc() : g_time_zone_new(tz);
1194 if(IS_NULL_PTR(tz_camera)) goto bail_out;
1195
1196 GList *imgs = NULL;
1197 GArray *gloc = g_array_new(FALSE, FALSE, sizeof(dt_image_geoloc_t));
1198 /* go thru each selected image and lookup location in gpx */
1199 do
1200 {
1201 dt_image_geoloc_t geoloc;
1202 int32_t imgid = GPOINTER_TO_INT(t->data);
1203
1204 /* get image */
1205 const dt_image_t *cimg = dt_image_cache_get(darktable.image_cache, imgid, 'r');
1206 if(IS_NULL_PTR(cimg)) continue;
1207
1208 GDateTime *exif_time = dt_datetime_img_to_gdatetime(cimg, tz_camera);
1209
1210 /* release the lock */
1212 if(IS_NULL_PTR(exif_time)) continue;
1213 GDateTime *utc_time = g_date_time_to_timezone(exif_time, darktable.utc_tz);
1214 g_date_time_unref(exif_time);
1215 if(IS_NULL_PTR(utc_time)) continue;
1216
1217 /* only update image location if time is within gpx tack range */
1218 if(dt_gpx_get_location(gpx, utc_time, &geoloc))
1219 {
1220 // takes the option to include the grouped images
1221 GList *grps = dt_grouping_get_group_images(imgid);
1222 for(GList *grp = grps; grp; grp = g_list_next(grp))
1223 {
1224 imgs = g_list_prepend(imgs, grp->data);
1225 g_array_append_val(gloc, geoloc);
1226 cntr++;
1227 }
1228 g_list_free(grps);
1229 grps = NULL;
1230 }
1231 g_date_time_unref(utc_time);
1232 } while((t = g_list_next(t)) != NULL);
1233 imgs = g_list_reverse(imgs);
1234
1236
1237 dt_control_log(ngettext("applied matched GPX location onto %d image",
1238 "applied matched GPX location onto %d images", cntr), cntr);
1239
1240 g_time_zone_unref(tz_camera);
1241 dt_gpx_destroy(gpx);
1242 g_array_unref(gloc);
1244 return 0;
1245
1246bail_out:
1247 if(!IS_NULL_PTR(gpx)) dt_gpx_destroy(gpx);
1248
1249 return 1;
1250}
1251
1253{
1254 return _generic_dt_control_fileop_images_job_run(job, &dt_image_move, _("moving %d image"),
1255 _("moving %d images"));
1256}
1257
1259{
1260 return _generic_dt_control_fileop_images_job_run(job, &dt_image_copy, _("copying %d image"),
1261 _("copying %d images"));
1262}
1263
1265{
1267 GList *t = params->index;
1268 guint tagid = 0;
1269 const guint total = g_list_length(t);
1270 double fraction = 0;
1271 const gboolean is_copy = params->flag == 1;
1272 char message[512] = { 0 };
1273
1274 if(is_copy)
1275 snprintf(message, sizeof(message),
1276 ngettext("creating local copy of %d image", "creating local copies of %d images", total), total);
1277 else
1278 snprintf(message, sizeof(message),
1279 ngettext("removing local copy of %d image", "removing local copies of %d images", total), total);
1280
1281 dt_control_log("%s", message);
1283
1284 dt_tag_new("darktable|local-copy", &tagid);
1285
1286 gboolean tag_change = FALSE;
1288 {
1289 const int32_t imgid = GPOINTER_TO_INT(t->data);
1290 if(is_copy)
1291 {
1292 if(dt_image_local_copy_set(imgid) == 0)
1293 {
1294 if(dt_tag_attach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1295 }
1296 }
1297 else
1298 {
1299 if(dt_image_local_copy_reset(imgid) == 0)
1300 {
1301 if(dt_tag_detach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1302 }
1303 }
1304 t = g_list_next(t);
1305
1306 fraction += 1.0 / total;
1307 dt_control_job_set_progress(job, fraction);
1308 }
1309
1311 g_list_copy(params->index));
1315 return 0;
1316}
1317
1319{
1321 GList *t = params->index;
1322 const guint total = g_list_length(t);
1323 double fraction = 0.0f;
1324 char message[512] = { 0 };
1325 snprintf(message, sizeof(message), ngettext("refreshing info for %d image", "refreshing info for %d images", total), total);
1327 while(t)
1328 {
1329 const int32_t imgid = GPOINTER_TO_INT(t->data);
1330 if(imgid >= 0)
1331 {
1332 gboolean from_cache = TRUE;
1333 char sourcefile[PATH_MAX];
1334 dt_image_full_path(imgid, sourcefile, sizeof(sourcefile), &from_cache, __FUNCTION__);
1335
1337 if(!IS_NULL_PTR(img))
1338 {
1339 // Re-sync the file-derived classification flags (LDR/HDR/RAW/sRAW, mosaic, monochrome,
1340 // 4bayer, additional-DNG-tags, buffer-resolved) with the actual file, repairing entries
1341 // that were persisted with stale/corrupted flags in the DB. We must *clear* these bits
1342 // before dt_exif_read(), because it only re-seeds the LDR/HDR/RAW class when none is set
1343 // yet (see exif.cc): leaving a stale DT_IMAGE_HDR in place would suppress the re-read.
1344 // Everything else in img->flags is DB-only / user state that the file knows nothing about
1345 // (star rating & reject, local copy, audio/txt sidecar, preset bookkeeping, monochrome
1346 // workflow), so it is preserved verbatim.
1347 const uint32_t file_class_flags
1351 img->flags &= ~file_class_flags;
1352 dt_exif_read(img, sourcefile);
1354 }
1355 else
1356 fprintf(stderr,"[dt_control_refresh_exif_run] couldn't dt_image_cache_get for imgid %i\n", imgid);
1357
1359 }
1360 else
1361 fprintf(stderr,"[dt_control_refresh_exif_run] illegal imgid %i\n", imgid);
1362
1363 t = g_list_next(t);
1364 fraction += 1.0 / total;
1365 dt_control_job_set_progress(job, fraction);
1366 }
1368 g_list_copy(params->index));
1371 return 0;
1372}
1373
1374
1376{
1378 dt_control_export_t *settings = (dt_control_export_t *)params->data;
1379 GList *t = params->index;
1381 g_assert(mformat);
1383 g_assert(mstorage);
1384 dt_imageio_module_data_t *sdata = settings->sdata;
1385
1386 gboolean tag_change = FALSE;
1387
1388 // get a thread-safe fdata struct (one jpeg struct per thread etc):
1389 dt_imageio_module_data_t *fdata = mformat->get_params(mformat);
1390
1391 if(mstorage->initialize_store)
1392 {
1393 if(mstorage->initialize_store(mstorage, sdata, &mformat, &fdata, &t, TRUE))
1394 {
1395 // bail out, something went wrong
1396 goto end;
1397 }
1398 mformat->set_params(mformat, fdata, mformat->params_size(mformat));
1399 mstorage->set_params(mstorage, sdata, mstorage->params_size(mstorage));
1400 }
1401
1402 // Get max dimensions...
1403 uint32_t w, h, fw, fh, sw, sh;
1404 fw = fh = sw = sh = 0;
1405 mstorage->dimension(mstorage, sdata, &sw, &sh);
1406 mformat->dimension(mformat, fdata, &fw, &fh);
1407
1408 if(sw == 0 || fw == 0)
1409 w = sw > fw ? sw : fw;
1410 else
1411 w = sw < fw ? sw : fw;
1412
1413 if(sh == 0 || fh == 0)
1414 h = sh > fh ? sh : fh;
1415 else
1416 h = sh < fh ? sh : fh;
1417
1418 const guint total = g_list_length(t);
1419 if(total > 0)
1420 dt_control_log(ngettext("exporting %d image..", "exporting %d images..", total), total);
1421 else
1422 dt_control_log(_("no image to export"));
1423
1424 double fraction = 0;
1425
1426 // set up the fdata struct
1427 fdata->max_width = (settings->max_width != 0 && w != 0) ? MIN(w, settings->max_width) : MAX(w, settings->max_width);
1428 fdata->max_height = (settings->max_height != 0 && h != 0) ? MIN(h, settings->max_height) : MAX(h, settings->max_height);
1429 g_strlcpy(fdata->style, settings->style, sizeof(fdata->style));
1430 // Invariant: the tagid for 'darktable|changed' will not change while this function runs. Is this a
1431 // sensible assumption?
1432 guint tagid = 0, etagid = 0;
1433 dt_tag_new("darktable|exported", &etagid);
1434
1435 dt_export_metadata_t metadata;
1436 metadata.flags = 0;
1437 metadata.list = dt_util_str_to_glist("\1", settings->metadata_export);
1438 if(metadata.list)
1439 {
1440 metadata.flags = strtol(metadata.list->data, NULL, 16);
1441 metadata.list = g_list_remove(metadata.list, metadata.list->data);
1442 }
1443
1445 {
1446 const int32_t imgid = GPOINTER_TO_INT(t->data);
1447 t = g_list_next(t);
1448 const guint num = total - g_list_length(t);
1449
1450 // progress message
1451 char message[512] = { 0 };
1452 snprintf(message, sizeof(message), _("exporting %d / %d to %s"), num, total, mstorage->name(mstorage));
1453 // update the message. initialize_store() might have changed the number of images
1455
1456 // remove 'changed' tag from image
1457 if(dt_tag_detach(tagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1458 // make sure the 'exported' tag is set on the image
1459 if(dt_tag_attach(etagid, imgid, FALSE, FALSE)) tag_change = TRUE;
1460
1461 /* register export timestamp in cache */
1463
1464 // check if image still exists:
1465 const dt_image_t *image = dt_image_cache_get(darktable.image_cache, (int32_t)imgid, 'r');
1466 if(image)
1467 {
1468 char imgfilename[PATH_MAX] = { 0 };
1469 gboolean from_cache = TRUE;
1470 dt_image_full_path(image->id, imgfilename, sizeof(imgfilename), &from_cache, __FUNCTION__);
1471 if(!g_file_test(imgfilename, G_FILE_TEST_IS_REGULAR))
1472 {
1473 dt_control_log(_("image `%s' is currently unavailable"), image->filename);
1474 fprintf(stderr, "image `%s' is currently unavailable\n", imgfilename);
1475 // dt_image_remove(imgid);
1477 }
1478 else
1479 {
1481 if(mstorage->store(mstorage, sdata, imgid, mformat, fdata, num, total, TRUE,
1482 settings->export_masks, settings->icc_type, settings->icc_filename, settings->icc_intent,
1483 &metadata) != 0)
1485 }
1486 }
1487
1488 fraction += 1.0 / total;
1489 if(fraction > 1.0) fraction = 1.0;
1490 dt_control_job_set_progress(job, fraction);
1491 }
1492 g_list_free_full(metadata.list, dt_free_gpointer);
1493 metadata.list = NULL;
1494
1495 if(mstorage->finalize_store) mstorage->finalize_store(mstorage, sdata);
1496
1497end:
1498 // all threads free their fdata
1499 mformat->free_params(mformat, fdata);
1500
1501 // notify the user via the window manager
1503
1505 return 0;
1506}
1507
1509{
1511 if(IS_NULL_PTR(params)) return NULL;
1512
1513 params->data = calloc(1, sizeof(dt_control_gpx_apply_t));
1514 if(IS_NULL_PTR(params->data))
1515 {
1517 return NULL;
1518 }
1519
1520 return params;
1521}
1522
1524{
1526
1527 dt_control_gpx_apply_t *data = params->data;
1528 params->data = NULL;
1529 dt_free(data->filename);
1530 dt_free(data->tz);
1531
1532 dt_free(data);
1533
1535}
1536
1537static dt_job_t *_control_gpx_apply_job_create(const gchar *filename, int32_t filmid,
1538 const gchar *tz, GList *imgs)
1539{
1541 if(IS_NULL_PTR(job)) return NULL;
1543 if(IS_NULL_PTR(params))
1544 {
1546 return NULL;
1547 }
1549
1550 if(filmid != -1)
1552 else if(!imgs)
1553 params->index = dt_act_on_get_images();
1554 else
1555 params->index = imgs;
1556 dt_control_gpx_apply_t *data = params->data;
1557 data->filename = g_strdup(filename);
1558 data->tz = g_strdup(tz);
1559
1560 return job;
1561}
1562
1563void dt_control_save_xmp(const int32_t imgid)
1564{
1567 N_("save history to XMP"),
1568 0, NULL, PROGRESS_NONE, imgid));
1569}
1570
1571void dt_control_save_xmps(const GList *imgids, const gboolean check_history)
1572{
1573 (void)check_history;
1574 if(IS_NULL_PTR(imgids)) return;
1575
1577 if(IS_NULL_PTR(job)) return;
1578
1580 if(IS_NULL_PTR(params))
1581 {
1583 return;
1584 }
1585
1586 params->index = g_list_copy((GList *)imgids);
1587 params->flag = 0;
1588
1591}
1592
1599
1600void dt_control_gpx_apply(const gchar *filename, int32_t filmid, const gchar *tz, GList *imgs)
1601{
1603 _control_gpx_apply_job_create(filename, filmid, tz, imgs));
1604}
1605
1606void dt_control_duplicate_images(gboolean virgin)
1607{
1610 N_("duplicate images"), 0, GINT_TO_POINTER(virgin), PROGRESS_SIMPLE, TRUE));
1611}
1612
1619
1626
1628{
1630 {
1631 dt_control_log(_("removing images from library is only possible in Lighttable view"));
1632 return FALSE;
1633 }
1634
1635 // get all selected images now, to avoid the set changing during ui interaction
1637 NULL, PROGRESS_SIMPLE, FALSE);
1638 if(dt_conf_get_bool("ask_before_remove"))
1639 {
1640 GtkWidget *dialog;
1642
1644 const int number = g_list_length(e->index);
1645 if(number == 0)
1646 {
1648 return TRUE;
1649 }
1650
1651 dialog = gtk_message_dialog_new(
1652 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1653 ngettext("Do you really want to remove %d image from Ansel library ?\nThe files will not be deleted on disk.",
1654 "Do you really want to remove %d images from Ansel library ?\nThe files will not be deletetd on disk.", number),
1655 number);
1656#ifdef GDK_WINDOWING_QUARTZ
1658#endif
1659
1660 gtk_window_set_title(GTK_WINDOW(dialog), ngettext(_("Remove image from library ?"), _("Remove images from library ?"), number));
1661 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1662 gtk_widget_destroy(dialog);
1663 if(res != GTK_RESPONSE_YES)
1664 {
1666 return FALSE;
1667 }
1668 }
1670 return TRUE;
1671}
1672
1674{
1676 {
1677 dt_control_log(_("Deleting images from library is only possible in Lighttable view"));
1678 return;
1679 }
1680
1681 // first get all selected images, to avoid the set changing during ui interaction
1683 NULL, PROGRESS_SIMPLE, FALSE);
1684 if(dt_conf_get_bool("ask_before_delete"))
1685 {
1686 GtkWidget *dialog;
1688
1690 const int number = g_list_length(e->index);
1691
1692 // Do not show the dialog if no image is selected:
1693 if(number == 0)
1694 {
1696 return;
1697 }
1698
1699 dialog = gtk_message_dialog_new(
1700 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1701 ngettext("Do you really want to physically delete %d image ?\nThe system trash bin will be used if possible.",
1702 "Do you really want to physically delete %d images ?\nThe system trash bin will be used if possible.", number),
1703 number);
1704#ifdef GDK_WINDOWING_QUARTZ
1706#endif
1707
1708 gtk_window_set_title(GTK_WINDOW(dialog), ngettext(_("Remove image from disk ?"), _("Remove images from disk ?"), number));
1709 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1710 gtk_widget_destroy(dialog);
1711 if(res != GTK_RESPONSE_YES)
1712 {
1714 return;
1715 }
1716 }
1718}
1719
1720void dt_control_delete_image(int32_t imgid)
1721{
1723 {
1724 dt_control_log(_("Deleting images from library is only possible in Lighttable view"));
1725 return;
1726 }
1727 // first get all selected images, to avoid the set changing during ui interaction
1729 NULL, PROGRESS_SIMPLE, imgid);
1730 int send_to_trash = dt_conf_get_bool("send_to_trash");
1731 if(dt_conf_get_bool("ask_before_delete"))
1732 {
1733 GtkWidget *dialog;
1735
1736 // Do not show the dialog if no valid image
1737 if(imgid < 1)
1738 {
1740 return;
1741 }
1742
1743 dialog = gtk_message_dialog_new(
1744 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1745 send_to_trash ? _("do you really want to physically delete selected image (using trash if possible)?")
1746 : _("do you really want to physically delete selected image from disk?"));
1747#ifdef GDK_WINDOWING_QUARTZ
1749#endif
1750
1751 gtk_window_set_title(GTK_WINDOW(dialog), _("delete image?"));
1752 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1753 gtk_widget_destroy(dialog);
1754 if(res != GTK_RESPONSE_YES)
1755 {
1757 return;
1758 }
1759 }
1761}
1762
1764{
1765 // Open file chooser dialog
1766 gchar *dir = NULL;
1768
1772 const int number = g_list_length(e->index);
1773 if(number == 0)
1774 {
1776 return;
1777 }
1778
1779 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1780 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1781 _("_select as destination"), _("_cancel"));
1782
1783 dt_conf_get_folder_to_file_chooser("ui_last/move_path", GTK_FILE_CHOOSER(filechooser));
1784 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1785 {
1786 dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1787 dt_conf_set_folder_from_file_chooser("ui_last/move_path", GTK_FILE_CHOOSER(filechooser));
1788 }
1789 g_object_unref(filechooser);
1790
1791 if(IS_NULL_PTR(dir) || !g_file_test(dir, G_FILE_TEST_IS_DIR)) goto abort;
1792
1793 // ugly, but we need to set this after constructing the job:
1795 // the job's cleanup function is responsible for freeing dir, so we don't do that here
1796
1797 if(dt_conf_get_bool("ask_before_move"))
1798 {
1799 GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1800 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1801 ngettext("do you really want to physically move %d image to %s?\n"
1802 "(all duplicates will be moved along)",
1803 "do you really want to physically move %d images to %s?\n"
1804 "(all duplicates will be moved along)",
1805 number),
1806 number, dir);
1807#ifdef GDK_WINDOWING_QUARTZ
1809#endif
1810 gtk_window_set_title(GTK_WINDOW(dialog), ngettext("move image?", "move images?", number));
1811
1812 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1813 gtk_widget_destroy(dialog);
1814
1815 if(res != GTK_RESPONSE_YES) goto abort;
1816 }
1817
1819 return;
1820
1821abort:
1822 dt_free(dir);
1824}
1825
1827{
1828 // Open file chooser dialog
1829 gchar *dir = NULL;
1834 const int number = g_list_length(e->index);
1835 if(number == 0)
1836 {
1838 return;
1839 }
1840
1841 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1842 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1843 _("_select as destination"), _("_cancel"));
1844
1845 dt_conf_get_folder_to_file_chooser("ui_last/copy_path", GTK_FILE_CHOOSER(filechooser));
1846 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1847 {
1848 dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1849 dt_conf_set_folder_from_file_chooser("ui_last/copy_path", GTK_FILE_CHOOSER(filechooser));
1850 }
1851 g_object_unref(filechooser);
1852
1853 if(IS_NULL_PTR(dir) || !g_file_test(dir, G_FILE_TEST_IS_DIR)) goto abort;
1854
1855 // ugly, but we need to set this after constructing the job:
1857 // the job's cleanup function is responsible for freeing dir, so we don't do that here
1858
1859 if(dt_conf_get_bool("ask_before_copy"))
1860 {
1861 GtkWidget *dialog = gtk_message_dialog_new(
1862 GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1863 ngettext("do you really want to physically copy %d image to %s?",
1864 "do you really want to physically copy %d images to %s?", number),
1865 number, dir);
1866#ifdef GDK_WINDOWING_QUARTZ
1868#endif
1869 gtk_window_set_title(GTK_WINDOW(dialog), ngettext("copy image?", "copy images?", number));
1870
1871 gint res = gtk_dialog_run(GTK_DIALOG(dialog));
1872 gtk_widget_destroy(dialog);
1873
1874 if(res != GTK_RESPONSE_YES) goto abort;
1875 }
1876
1878 return;
1879
1880abort:
1881 dt_free(dir);
1883}
1884
1892
1900
1907
1909{
1911 if(IS_NULL_PTR(params)) return NULL;
1912
1913 params->data = calloc(1, sizeof(dt_control_export_t));
1914 if(IS_NULL_PTR(params->data))
1915 {
1917 return NULL;
1918 }
1919
1920 return params;
1921}
1922
1924{
1926
1927 dt_control_export_t *settings = (dt_control_export_t *)params->data;
1929 dt_imageio_module_data_t *sdata = settings->sdata;
1930
1931 mstorage->free_params(mstorage, sdata);
1932
1933 dt_free(settings->icc_filename);
1934 dt_free(settings->metadata_export);
1935 dt_free(params->data);
1936
1938}
1939
1940void dt_control_export(GList *imgid_list, int max_width, int max_height, int format_index, int storage_index,
1941 gboolean high_quality, gboolean export_masks, char *style,
1942 dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename,
1943 dt_iop_color_intent_t icc_intent, const gchar *metadata_export)
1944{
1946 if(IS_NULL_PTR(job)) return;
1948 if(IS_NULL_PTR(params))
1949 {
1951 return;
1952 }
1954
1955 params->index = imgid_list;
1956
1957 dt_control_export_t *data = params->data;
1958 data->max_width = max_width;
1959 data->max_height = max_height;
1960 data->format_index = format_index;
1961 data->storage_index = storage_index;
1963 g_assert(mstorage);
1964 // get shared storage param struct (global sequence counter, one picasa connection etc)
1965 dt_imageio_module_data_t *sdata = mstorage->get_params(mstorage);
1966 if(IS_NULL_PTR(sdata))
1967 {
1968 dt_control_log(_("failed to get parameters from storage module `%s', aborting export.."),
1969 mstorage->name(mstorage));
1971 return;
1972 }
1973 data->sdata = sdata;
1974 data->export_masks = export_masks;
1975 g_strlcpy(data->style, style, sizeof(data->style));
1976 data->icc_type = icc_type;
1977 data->icc_filename = g_strdup(icc_filename);
1978 data->icc_intent = icc_intent;
1979 data->metadata_export = g_strdup(metadata_export);
1980
1981 dt_control_job_add_progress(job, _("export images"), TRUE);
1983
1984 // tell the storage that we got its params for an export so it can reset itself to a safe state
1985 mstorage->export_dispatched(mstorage);
1986}
1987
1988static void _add_datetime_offset(const char *odt, const long int offset, char *ndt)
1989{
1990 // get the datetime_taken and calculate the new time
1991 GDateTime *datetime_original = dt_datetime_exif_to_gdatetime(odt, darktable.utc_tz);
1992 if(IS_NULL_PTR(datetime_original))
1993 return;
1994
1995 // let's add our offset
1996 GDateTime *datetime_new = g_date_time_add(datetime_original, offset);
1997 g_date_time_unref(datetime_original);
1998
1999 if(IS_NULL_PTR(datetime_new))
2000 return;
2001 gchar *datetime = g_date_time_format(datetime_new, "%Y:%m:%d %H:%M:%S,%f");
2002 datetime[DT_DATETIME_LENGTH - 1] = '\0'; // limit to milliseconds
2003 g_date_time_unref(datetime_new);
2004
2005 if(datetime)
2006 g_strlcpy(ndt, datetime, DT_DATETIME_LENGTH);
2007 dt_free(datetime);
2008}
2009
2011{
2013 uint32_t cntr = 0;
2014 GList *t = params->index;
2015 const GTimeSpan offset = ((dt_control_datetime_t *)params->data)->offset;
2016 const char *datetime = ((dt_control_datetime_t *)params->data)->datetime;
2017 char message[512] = { 0 };
2018
2019 /* do we have any selected images and is offset != 0 */
2020 if(IS_NULL_PTR(t) || (offset == 0 && !datetime[0]))
2021 {
2022 return 1;
2023 }
2024
2025 const guint total = g_list_length(t);
2026
2027 const char *mes11 = offset ? N_("adding time offset to %d image") : N_("setting date/time of %d image");
2028 const char *mes12 = offset ? N_("adding time offset to %d images") : N_("setting date/time of %d images");
2029 snprintf(message, sizeof(message), ngettext(mes11, mes12, total), total);
2031
2032 GList *imgs = NULL;
2033 if(offset)
2034 {
2035 GArray *dtime = g_array_new(FALSE, TRUE, DT_DATETIME_LENGTH);
2036
2037 for(GList *img = t; img; img = g_list_next(img))
2038 {
2039 char odt[DT_DATETIME_LENGTH] = {0};
2040 dt_image_get_datetime(GPOINTER_TO_INT(img->data), odt);
2041 if(!odt[0]) continue;
2042
2043 char ndt[DT_DATETIME_LENGTH] = {0};
2044 _add_datetime_offset(odt, offset, ndt);
2045 if(!ndt[0]) continue;
2046
2047 // takes the option to include the grouped images
2048 GList *grps = dt_grouping_get_group_images(GPOINTER_TO_INT(img->data));
2049 for(GList *grp = grps; grp; grp = g_list_next(grp))
2050 {
2051 imgs = g_list_prepend(imgs, grp->data);
2052 g_array_append_val(dtime, ndt);
2053 cntr++;
2054 }
2055 g_list_free(grps);
2056 grps = NULL;
2057 }
2058 imgs = g_list_reverse(imgs);
2059 dt_image_set_datetimes(imgs, dtime, TRUE);
2060 }
2061 else
2062 {
2063 imgs = g_list_copy(t);
2064 // takes the option to include the grouped images
2066 cntr = g_list_length(imgs);
2067 dt_image_set_datetime(imgs, datetime, TRUE);
2068 }
2069
2070 const char *mes21 = offset ? N_("added time offset to %d image") : N_("set date/time of %d image");
2071 const char *mes22 = offset ? N_("added time offset to %d images") : N_("set date/time of %d images");
2072 dt_control_log(ngettext(mes21, mes22, cntr), cntr);
2073 return 0;
2074}
2075
2077{
2079 if(IS_NULL_PTR(params)) return NULL;
2080
2081 params->data = calloc(1, sizeof(dt_control_datetime_t));
2082 if(IS_NULL_PTR(params->data))
2083 {
2085 return NULL;
2086 }
2087
2088 return params;
2089}
2090
2092{
2094
2095 dt_free(params->data);
2096
2098}
2099
2100static dt_job_t *dt_control_datetime_job_create(const GTimeSpan offset, const char *datetime, GList *imgs)
2101{
2103 if(IS_NULL_PTR(job)) return NULL;
2105 if(IS_NULL_PTR(params))
2106 {
2108 return NULL;
2109 }
2110 dt_control_job_add_progress(job, _("time offset"), FALSE);
2112
2113 if(imgs)
2114 params->index = imgs;
2115 else
2116 params->index = dt_act_on_get_images();
2117
2118 dt_control_datetime_t *data = params->data;
2119 data->offset = offset;
2120 if(!IS_NULL_PTR(datetime))
2121 memcpy(data->datetime, datetime, sizeof(data->datetime));
2122 else
2123 data->datetime[0] = '\0';
2124 params->data = data;
2125 return job;
2126}
2127
2128void dt_control_datetime(const GTimeSpan offset, const char *datetime, GList *imgs)
2129{
2131 dt_control_datetime_job_create(offset, datetime, imgs));
2132}
2133
2134// clang-format off
2135// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2136// vim: shiftwidth=2 expandtab tabstop=2 cindent
2137// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2138// clang-format on
GList * dt_act_on_get_images()
Definition act_on.c:39
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:278
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void dt_collection_update_query(const dt_collection_t *collection, dt_collection_change_t query_change, dt_collection_properties_t changed_property, GList *list)
void dt_collection_deserialize(const char *buf)
int dt_collection_update(const dt_collection_t *collection)
Definition collection.c:268
@ DT_COLLECTION_PROP_LOCAL_COPY
Definition collection.h:131
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:142
@ DT_COLLECTION_CHANGE_RELOAD
Definition collection.h:151
dt_iop_color_intent_t
Definition colorspaces.h:63
@ DT_INTENT_LAST
Definition colorspaces.h:68
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
@ DT_COLORSPACE_NONE
Definition colorspaces.h:82
static const dt_colormatrix_t M
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_history_delete_on_image(int32_t imgid)
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)
int dt_image_write_sidecar_file(const int32_t imgid)
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_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)
int dt_image_local_copy_set(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)
void dt_image_flip(const int32_t imgid, const int32_t cw)
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.
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)
void dt_control_log(const char *msg,...)
Definition control.c:777
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:877
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)
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)
static char * _get_image_list(GList *l)
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 GList * _get_full_pathname(char *imgs)
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)
static void _set_remove_flag(char *imgs)
_dt_delete_status
@ _DT_DELETE_STATUS_SKIP_FILE
@ _DT_DELETE_STATUS_STOP_PROCESSING
@ _DT_DELETE_STATUS_OK_TO_REMOVE
@ _DT_DELETE_STATUS_UNKNOWN
void dt_control_flip_images(const int32_t cw)
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
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)
darktable_t darktable
Definition darktable.c:183
#define UNKNOWN_IMAGE
Definition darktable.h:194
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
#define PATH_MAX
Definition darktable.h:1189
#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 darktable.h:293
static int FCxtrans(const int row, const int col, global const unsigned char(*const xtrans)[6])
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3653
GDateTime * dt_datetime_exif_to_gdatetime(const char *exif, const GTimeZone *tz)
Definition datetime.c:246
GDateTime * dt_datetime_img_to_gdatetime(const dt_image_t *img, const GTimeZone *tz)
Definition datetime.c:288
#define DT_DATETIME_LENGTH
Definition datetime.h:37
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_TEXT(a, b, c, d, e)
Definition debug.h:118
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:384
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:369
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:389
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Definition dtpthread.h:394
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
boolean dt_win_file_trash(GFile *file, GCancellable *cancellable, GError **error)
Definition dtwin.c:355
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
int dt_exif_read(dt_image_t *img, const char *path)
Definition exif.cc:1752
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)
Definition exif.cc:1894
int dt_film_new(dt_film_t *film, const char *directory)
Definition film.c:161
void dt_film_remove_empty()
Definition film.c:342
@ TYPE_UINT16
Definition format.h:47
void dt_gpx_destroy(struct dt_gpx_t *gpx)
Definition gpx.c:152
gboolean dt_gpx_get_location(struct dt_gpx_t *gpx, GDateTime *timestamp, dt_image_geoloc_t *geoloc)
Definition gpx.c:170
dt_gpx_t * dt_gpx_new(const gchar *filename)
Definition gpx.c:88
void dt_grouping_add_grouped_images(GList **images)
Definition grouping.c:171
GList * dt_grouping_get_group_images(const int32_t imgid)
Definition grouping.c:145
void dt_ui_notify_user()
draw user's attention
Definition gtk.c:1825
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
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
dt_image_orientation_t
Definition image.h:203
const char flag
Definition image.h:252
@ DT_IMAGE_HAS_ADDITIONAL_DNG_TAGS
Definition image.h:132
@ DT_IMAGE_S_RAW
Definition image.h:134
@ DT_IMAGE_REMOVE
Definition image.h:115
@ DT_IMAGE_MOSAIC
Definition image.h:146
@ DT_IMAGE_MONOCHROME_BAYER
Definition image.h:138
@ DT_IMAGE_LOCAL_COPY
Definition image.h:121
@ DT_IMAGE_BUFFER_RESOLVED
Definition image.h:151
@ DT_IMAGE_RAW
Definition image.h:111
@ DT_IMAGE_4BAYER
Definition image.h:127
@ DT_IMAGE_MONOCHROME
Definition image.h:129
@ DT_IMAGE_HDR
Definition image.h:113
@ DT_IMAGE_LDR
Definition image.h:109
void dt_image_cache_read_release(dt_image_cache_t *cache, const dt_image_t *img)
dt_image_t * dt_image_cache_get(dt_image_cache_t *cache, const int32_t imgid, char mode)
void dt_image_cache_set_export_timestamp(dt_image_cache_t *cache, const int32_t imgid)
void dt_image_cache_write_release(dt_image_cache_t *cache, dt_image_t *img, dt_image_cache_write_mode_t mode)
@ DT_IMAGE_CACHE_SAFE
Definition image_cache.h:49
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)
Definition imageio.c:995
@ IMAGEIO_RGB
Definition imageio.h:70
@ IMAGEIO_FLOAT
Definition imageio.h:66
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)
static const float x
const int t
dt_job_state_t dt_control_job_get_state(_dt_job_t *job)
Definition jobs.c:103
void dt_control_job_cancel(_dt_job_t *job)
Definition jobs.c:180
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:135
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:405
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:129
void dt_control_job_set_progress(dt_job_t *job, double value)
Definition jobs.c:626
void dt_control_job_add_progress(dt_job_t *job, const char *message, gboolean cancellable)
Definition jobs.c:612
void dt_control_job_set_progress_message(dt_job_t *job, const char *message)
Definition jobs.c:620
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:112
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:153
@ DT_JOB_QUEUE_USER_FG
Definition jobs.h:53
@ DT_JOB_QUEUE_USER_EXPORT
Definition jobs.h:56
int32_t(* dt_job_execute_callback)(dt_job_t *)
Definition jobs.h:63
@ DT_JOB_STATE_CANCELLED
Definition jobs.h:46
float *const restrict const size_t k
#define CLAMPS(A, L, H)
Definition math.h:76
#define M_PI
Definition math.h:45
size_t size
Definition mipmap_cache.c:3
float dt_aligned_pixel_t[4]
gboolean dt_osx_file_trash(const char *filename, GError **error)
Definition osx.mm:114
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:366
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
Definition signal.h:221
@ DT_SIGNAL_FILMROLLS_CHANGED
This signal is raised when a filmroll is deleted/changed but not imported.
Definition signal.h:156
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
Definition signal.h:136
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
Definition signal.h:130
struct _GtkWidget GtkWidget
Definition splash.h:29
dt_pthread_mutex_t mutex
struct dt_undo_t * undo
Definition darktable.h:815
struct dt_gui_gtk_t * gui
Definition darktable.h:803
GTimeZone * utc_tz
Definition darktable.h:860
struct dt_collection_t * collection
Definition darktable.h:809
const struct dt_database_t * db
Definition darktable.h:807
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_image_cache_t * image_cache
Definition darktable.h:805
struct dt_view_manager_t * view_manager
Definition darktable.h:800
struct dt_control_t * control
Definition darktable.h:801
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:48
dt_ui_t * ui
Definition gtk.h:165
float exif_exposure
Definition image.h:285
float exif_iso
Definition image.h:288
float exif_aperture
Definition image.h:287
int32_t flags
Definition image.h:319
dt_image_orientation_t orientation
Definition image.h:284
float exif_focal_length
Definition image.h:289
int32_t crop_y
Definition image.h:316
int32_t crop_x
Definition image.h:316
dt_iop_buffer_dsc_t dsc
Definition image.h:337
float adobe_XYZ_to_CAM[4][3]
Definition image.h:362
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:304
int32_t id
Definition image.h:319
dt_aligned_pixel_t wb_coeffs
Definition image.h:359
uint32_t filters
Definition format.h:60
unsigned int channels
Definition format.h:54
uint8_t xtrans[6][6]
Definition format.h:70
dt_iop_buffer_type_t datatype
Definition format.h:56
Region of interest passed through the pixelpipe.
Definition imageop.h:72
dt_view_t * current_view
Definition view.h:202
gboolean dt_tag_attach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:485
gboolean dt_tag_detach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:593
gboolean dt_tag_new(const char *name, guint *tagid)
Definition tags.c:179
#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_LT_HISTORY
Definition undo.h:48
@ DT_UNDO_FLAGS
Definition undo.h:49
@ DT_UNDO_DUPLICATE
Definition undo.h:51
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
Definition utility.c:830
size_t safe_strlen(const char *str)
check if the string is empty or NULL before calling strlen()
Definition utility.c:90
@ DT_VIEW_LIGHTTABLE
Definition view.h:77