src/libs/export.c
Rama master · réplica de lectura del repositorio original.
/*
This file is part of darktable,
Copyright (C) 2009-2026 darktable developers.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common/gdk_event_utils.h"
#include "bauhaus/bauhaus.h"
#include "common/collection.h"
#include "common/colorspaces.h"
#include "common/darktable.h"
#include "common/debug.h"
#include "common/presets.h"
#include "common/styles.h"
#include "control/conf.h"
#include "control/control.h"
#include "control/signal.h"
#include "develop/develop.h"
#include "dtgtk/button.h"
#include "dtgtk/stylemenu.h"
#include "gui/accelerators.h"
#include "gui/gtk.h"
#include "imageio/imageio_module.h"
#include "libs/lib.h"
#include "libs/lib_api.h"
#include "views/view.h"
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <ctype.h>
#include <glib.h>
DT_MODULE(9)
typedef enum dt_export_batch_cols_t
{
DT_EXPORT_BATCH_COL_ACTIVE = 0, // active preset for batch export
DT_EXPORT_BATCH_COL_NAME, // preset name
DT_EXPORT_BATCH_NUM_COLS
} dt_export_batch_cols_t;
#define EXPORT_MAX_IMAGE_SIZE UINT16_MAX
#define CONFIG_PREFIX "plugins/lighttable/export/"
typedef struct dt_lib_export_t
{
GtkWidget *dimensions_type, *print_dpi, *print_height, *print_width;
GtkWidget *unit_label;
GtkWidget *width, *height;
GtkWidget *px_size, *print_size, *scale, *size_in_px;
GtkWidget *storage, *format;
int format_lut[128];
uint32_t max_allowed_width , max_allowed_height;
GtkWidget *upscale, *profile, *pq_peak, *intent, *style, *style_mode;
dt_gui_collapsible_section_t cs;
GtkWidget *batch_treeview;
GtkButton *export_button, *batch_export_button;
GtkWidget *storage_extra_container, *format_extra_container;
GtkWidget *high_quality;
GtkWidget *export_masks;
char *metadata_export;
char *style_name;
dt_imageio_module_storage_t *storage_module;
} dt_lib_export_t;
typedef enum dt_dimensions_type_t
{
DT_DIMENSIONS_PIXELS = 0, // set dimensions exactly in pixels
DT_DIMENSIONS_CM = 1, // set dimensions from physical size in centimeters * DPI
DT_DIMENSIONS_INCH = 2, // set dimensions from physical size in inch
DT_DIMENSIONS_SCALE = 3 // set dimensions by scale
} dt_dimensions_type_t;
char *dt_lib_export_metadata_configuration_dialog(char *list,
const gboolean ondisk);
/** Updates the combo box and shows only the supported formats of
* current selected storage module */
static void _update_formats_combobox(dt_lib_export_t *d);
/** Sets the max dimensions based upon what storage and format supports */
static void _update_dimensions(dt_lib_export_t *d);
/** Update the combobox label for the style */
static void _update_style_label(dt_lib_export_t *d, const char *name);
/** get the max output dimension supported by combination of storage and format.. */
static void _get_max_output_dimension(dt_lib_export_t *d,
uint32_t *width,
uint32_t *height);
static void _resync_print_dimensions(dt_lib_export_t *self);
static void _resync_pixel_dimensions(dt_lib_export_t *self);
static inline gboolean _is_style_set(const char *name)
{
return name && name[0] && strcmp(name, _("none")) && dt_styles_exists(name);
}
#define INCH_TO_CM (2.54f)
static inline float pixels2cm(dt_lib_export_t *self, const uint32_t pix)
{
const int dpi = atoi(gtk_entry_get_text(GTK_ENTRY(self->print_dpi)));
return ((float)pix * INCH_TO_CM) / (float)dpi;
}
static inline float pixels2inch(dt_lib_export_t *self, const uint32_t pix)
{
const int dpi = atoi(gtk_entry_get_text(GTK_ENTRY(self->print_dpi)));
return (float)pix / (float)dpi;
}
static inline uint32_t cm2pixels(dt_lib_export_t *self, const float cm)
{
const int dpi = atoi(gtk_entry_get_text(GTK_ENTRY(self->print_dpi)));
return ceilf((cm * (float)dpi) / INCH_TO_CM);
}
static inline uint32_t inch2pixels(dt_lib_export_t *self, const float inch)
{
const int dpi = atoi(gtk_entry_get_text(GTK_ENTRY(self->print_dpi)));
return ceilf(inch * (float)dpi);
}
static inline uint32_t print2pixels(dt_lib_export_t *self, const float value)
{
const dt_dimensions_type_t d_type =
(dt_dimensions_type_t)dt_bauhaus_combobox_get(self->dimensions_type);
switch(d_type)
{
case(DT_DIMENSIONS_PIXELS):
return ceilf(value);
case(DT_DIMENSIONS_CM):
return cm2pixels(self, value);
case(DT_DIMENSIONS_INCH):
return inch2pixels(self, value);
case(DT_DIMENSIONS_SCALE):
;
}
// should never run this
return ceilf(value);
}
static inline float pixels2print(dt_lib_export_t *self,
const uint32_t pix)
{
const dt_dimensions_type_t d_type =
(dt_dimensions_type_t)dt_bauhaus_combobox_get(self->dimensions_type);
switch(d_type)
{
case(DT_DIMENSIONS_PIXELS):
return (float)pix;
case(DT_DIMENSIONS_CM):
return pixels2cm(self, pix);
case(DT_DIMENSIONS_INCH):
return pixels2inch(self, pix);
case(DT_DIMENSIONS_SCALE):
;
}
// should never run this
return (float)pix;
}
const char *name(dt_lib_module_t *self)
{
return _("export");
}
gboolean has_preset_label(dt_lib_module_t *self)
{
return TRUE;
}
const char *description(dt_lib_module_t *self)
{
return _("create new files for the\n"
"currently selected images\n"
"which apply your edits");
}
dt_view_type_flags_t views(dt_lib_module_t *self)
{
if(dt_conf_get_bool("plugins/darkroom/export/visible"))
return DT_VIEW_LIGHTTABLE | DT_VIEW_DARKROOM;
else
return DT_VIEW_LIGHTTABLE;
}
uint32_t container(dt_lib_module_t *self)
{
return DT_UI_CONTAINER_PANEL_RIGHT_CENTER;
}
void gui_update(dt_lib_module_t *self)
{
const dt_lib_export_t *d = self->data;
const gboolean has_act_on = (dt_act_on_get_images_nb(TRUE, FALSE) > 0);
const char *format_name = dt_conf_get_string_const(CONFIG_PREFIX "format_name");
const char *storage_name = dt_conf_get_string_const(CONFIG_PREFIX "storage_name");
const int format_index =
dt_imageio_get_index_of_format(dt_imageio_get_format_by_name(format_name));
const int storage_index =
dt_imageio_get_index_of_storage(dt_imageio_get_storage_by_name(storage_name));
dt_imageio_module_storage_t *storage = dt_imageio_get_storage();
gboolean export_enabled = TRUE;
if(storage->export_enabled)
export_enabled = storage->export_enabled(storage);
gtk_widget_set_sensitive(GTK_WIDGET(d->export_button),
has_act_on
&& format_index != -1
&& storage_index != -1
&& export_enabled);
}
static void _image_selection_changed_callback(gpointer instance,
dt_lib_module_t *self)
{
dt_lib_gui_queue_update(self);
}
static void _collection_updated_callback(gpointer instance,
const dt_collection_change_t query_change,
const dt_collection_properties_t changed_property,
gpointer imgs,
const int next,
dt_lib_module_t *self)
{
dt_lib_gui_queue_update(self);
}
static void _mouse_over_image_callback(gpointer instance, dt_lib_module_t *self)
{
dt_lib_gui_queue_update(self);
}
static void _export_enable_callback(gpointer instance, dt_lib_module_t *self)
{
dt_lib_gui_queue_update(self);
}
gboolean _is_int(double value)
{
return (value == (int)value);
}
static void _scale_optim()
{
double num = 1.0, denum = 1.0;
dt_imageio_resizing_factor_get_and_parsing(&num, &denum);
gchar *scale_str = dt_conf_get_string(CONFIG_PREFIX "resizing_factor");
gchar _str[6] = "";
gchar *pdiv = strchr(scale_str, '/');
gchar scale_buf[64] = "";
if(pdiv == NULL)
{
if(_is_int(num) && num > 0.0)
{
sprintf(_str, "%d", (int) num);
g_strlcat(scale_buf, _str, sizeof(scale_buf));
}
else
{
g_strlcat(scale_buf, scale_str, sizeof(scale_buf));
}
}
else if(pdiv-scale_str == 0)
{
if(_is_int(denum) && denum > 0.0)
{
sprintf(_str, "%d", (int) denum);
g_strlcat(scale_buf, _str, sizeof(scale_buf));
}
else
{
g_strlcat(scale_buf, "1/", sizeof(scale_buf));
g_strlcat(scale_buf, pdiv+1, sizeof(scale_buf));
}
}
else
{
if(_is_int(num) && num > 0.0)
{
sprintf(_str, "%d", (int) num);
g_strlcat(scale_buf, _str, sizeof(scale_buf));
}
else
{
g_strlcat(scale_buf, scale_str, sizeof(scale_buf));
}
g_strlcat(scale_buf, "/", sizeof(scale_buf));
if(_is_int(denum) && denum > 0.0)
{
sprintf(_str, "%d", (int) denum);
g_strlcat(scale_buf, _str, sizeof(scale_buf));
}
else
{
g_strlcat(scale_buf, pdiv+1, sizeof(scale_buf));
}
}
dt_conf_set_string(CONFIG_PREFIX "resizing_factor", scale_buf);
free(scale_str);
}
static void _export_with_current_settings(dt_lib_module_t *self)
{
dt_lib_export_t *d = self->data;
/* write current history changes so nothing gets lost, do that only
in the darkroom as there is nothing to be saved when in the
lighttable (and it would write over current history stack) */
if(dt_view_get_current() == DT_VIEW_DARKROOM) dt_dev_write_history(darktable.develop);
char style[128] = { 0 };
// get the format_name and storage_name settings which are plug-ins
// name and not necessary what is displayed on the combobox. note
// that we cannot take directly the combobox entry index as
// depending on the storage some format are not listed.
const char *format_name = dt_conf_get_string_const(CONFIG_PREFIX "format_name");
const char *storage_name = dt_conf_get_string_const(CONFIG_PREFIX "storage_name");
const int format_index =
dt_imageio_get_index_of_format(dt_imageio_get_format_by_name(format_name));
const int storage_index =
dt_imageio_get_index_of_storage(dt_imageio_get_storage_by_name(storage_name));
if(format_index == -1)
{
dt_control_log(_("invalid format for export selected"));
return;
}
if(storage_index == -1)
{
dt_control_log(_("invalid storage for export selected"));
return;
}
char *confirm_message = NULL;
dt_imageio_module_storage_t *mstorage = dt_imageio_get_storage();
if(mstorage->ask_user_confirmation)
confirm_message = mstorage->ask_user_confirmation(mstorage);
if(confirm_message)
{
gboolean res = dt_gui_show_yes_no_dialog
(_("export to disk"), "", "%s", confirm_message);
g_free(confirm_message);
confirm_message = NULL;
if(!res)
{
return;
}
}
// Let's get the max dimension restriction if any...
uint32_t max_width = dt_conf_get_int(CONFIG_PREFIX "width");
uint32_t max_height = dt_conf_get_int(CONFIG_PREFIX "height");
const gboolean upscale = dt_conf_get_bool(CONFIG_PREFIX "upscale");
const gboolean scaledimension =
dt_conf_get_int(CONFIG_PREFIX "dimensions_type") == DT_DIMENSIONS_SCALE;
const gboolean high_quality = dt_conf_get_bool(CONFIG_PREFIX "high_quality_processing");
const gboolean export_masks = dt_conf_get_bool(CONFIG_PREFIX "export_masks");
const gboolean style_append = dt_conf_get_bool(CONFIG_PREFIX "style_append");
const char *tmp = dt_conf_get_string_const(CONFIG_PREFIX "style");
if(tmp)
{
g_strlcpy(style, tmp, sizeof(style));
}
// scaling
const gboolean is_scaling =
dt_conf_is_equal("plugins/lighttable/export/resizing", "scaling");
double _num, _denum;
dt_imageio_resizing_factor_get_and_parsing(&_num, &_denum);
const double scale_factor = is_scaling? _num / _denum : 1.0;
// if upscale is activated and only one dimension is 0 we adjust it to ensure
// that the up-scaling will happen. The null dimension is set to MAX_ASPECT_RATIO
// time the other dimension, allowing for a ratio of max 1:100 exported images.
if(upscale)
{
const uint32_t MAX_ASPECT_RATIO = 100;
if(max_width == 0 && max_height != 0)
{
max_width = max_height * MAX_ASPECT_RATIO;
}
else if(max_height == 0 && max_width != 0)
{
max_height = max_width * MAX_ASPECT_RATIO;
}
}
const dt_colorspaces_color_profile_type_t icc_type =
dt_conf_get_int(CONFIG_PREFIX "icctype");
gchar *icc_filename = dt_conf_get_string(CONFIG_PREFIX "iccprofile");
const dt_iop_color_intent_t icc_intent = dt_conf_get_int(CONFIG_PREFIX "iccintent");
const int pq_peak_nits = dt_conf_get_int(CONFIG_PREFIX "pq_peak_nits");
GList *list = dt_act_on_get_images(TRUE, TRUE, TRUE);
dt_control_export(list, max_width, max_height, format_index, storage_index,
high_quality, upscale, scaledimension,
is_scaling, scale_factor,
export_masks, style, style_append,
icc_type, icc_filename, icc_intent, pq_peak_nits,
d->metadata_export);
g_free(icc_filename);
_scale_optim();
gtk_entry_set_text(GTK_ENTRY(d->scale),
dt_conf_get_string_const(CONFIG_PREFIX "resizing_factor"));
}
static void _export_with_preset(const gchar *preset_name, dt_lib_module_t *self)
{
dt_lib_export_t *d = self->data;
// get current settings from the GUI
int params_size;
void *params = get_params(self, ¶ms_size);
// not in params when the current profile cannot be PQ
const int pq_peak_nits = dt_conf_get_int(CONFIG_PREFIX "pq_peak_nits");
void *op_params = NULL;
int op_params_size = 0;
if(dt_presets_get_params_any_version("export", preset_name, &op_params, &op_params_size))
{
set_params(self, op_params, op_params_size);
gboolean login = TRUE;
if(d->storage_module->storage_login)
login = d->storage_module->storage_login(d->storage_module);
if(login)
_export_with_current_settings(self);
else
dt_control_log(_("could not login to storage `%s'!"), d->storage_module->name(d->storage_module));
}
g_free(op_params);
// restore previous settings
set_params(self, params, params_size);
dt_bauhaus_slider_set(d->pq_peak, pq_peak_nits);
}
static void _export_button_clicked(GtkWidget *widget, dt_lib_module_t *self)
{
_export_with_current_settings(self);
}
static void _batch_export_button_clicked(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_export_t *d = (dt_lib_export_t *)self->data;
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->batch_treeview));
GtkTreeIter iter;
gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
while(valid)
{
gboolean active;
char *preset_name;
gtk_tree_model_get(model, &iter,
DT_EXPORT_BATCH_COL_ACTIVE, &active,
DT_EXPORT_BATCH_COL_NAME, &preset_name,
-1);
if(active)
_export_with_preset(preset_name, self);
valid = gtk_tree_model_iter_next(model, &iter);
}
}
static void _scale_changed(GtkEntry *spin,
dt_lib_export_t *d)
{
const char *validSign = ",.0123456789";
const gchar *value = gtk_entry_get_text(spin);
const int len = sizeof(value);
int i, j = 0, idec = 0, idiv = 0, pdiv = 0;
char new_value[30] = "";
for(i = 0; i < len; i++)
{
char *val = strchr(validSign, value[i]);
if(val == NULL)
{
if(idiv==0)
{
if(i == 0)
{
new_value[j++] = '1';
}
else
{
if(atof(value) == 0.0)
{
new_value[0] = '1';
}
idec = 0;
idiv = 1;
new_value[j++] = '/';
pdiv = j;
}
}
}
else if((val[0] == '.') || (val[0] == ','))
{
if(idec == 0)
{
if((i == 0) || (i == pdiv))
{
new_value[j++] = '0';
}
else
{
idec = 1;
new_value[j++] = value[i];
}
}
}
else if(value[i] == '\0')
{
break;
}
else
{
new_value[j++] = value[i];
}
}
dt_conf_set_string(CONFIG_PREFIX "resizing_factor", new_value);
gtk_entry_set_text(spin, new_value);
}
static void _width_changed(GtkEditable *entry, gpointer user_data);
static void _height_changed(GtkEditable *entry, gpointer user_data);
static void _scale_mdlclick_cb(GtkGestureSingle *gesture, int n_press,
double x, double y,
dt_lib_export_t *d)
{
GtkWidget *spin = dt_gui_get_widget(gesture);
if(gtk_gesture_single_get_current_button(gesture) == GDK_BUTTON_MIDDLE)
{
dt_conf_set_string(CONFIG_PREFIX "resizing_factor", "1");
g_signal_handlers_block_by_func(spin, _scale_changed, d);
gtk_entry_set_text(GTK_ENTRY(spin), "1");
g_signal_handlers_unblock_by_func(spin, _scale_changed, d);
}
else
{
_scale_changed(GTK_ENTRY(spin), d);
}
}
static void _widht_mdlclick_cb(GtkGestureSingle *gesture, int n_press,
double x, double y,
dt_lib_export_t *d)
{
GtkWidget *spin = dt_gui_get_widget(gesture);
if(gtk_gesture_single_get_current_button(gesture) == GDK_BUTTON_MIDDLE)
{
dt_conf_set_int(CONFIG_PREFIX "width", 0);
g_signal_handlers_block_by_func(spin, _width_changed, d);
gtk_entry_set_text(GTK_ENTRY(spin), "0");
g_signal_handlers_unblock_by_func(spin, _width_changed, d);
}
else
{
_width_changed(GTK_EDITABLE(spin), d);
}
}
static void _height_mdlclick_cb(GtkGestureSingle *gesture, int n_press,
double x, double y,
dt_lib_export_t *d)
{
GtkWidget *spin = dt_gui_get_widget(gesture);
if(gtk_gesture_single_get_current_button(gesture) == GDK_BUTTON_MIDDLE)
{
dt_conf_set_int(CONFIG_PREFIX "height", 0);
g_signal_handlers_block_by_func(spin, _height_changed, d);
gtk_entry_set_text(GTK_ENTRY(spin), "0");
g_signal_handlers_unblock_by_func(spin, _height_changed, d);
}
else
{
_height_changed(GTK_EDITABLE(spin), d);
}
}
static void _size_in_px_update(dt_lib_export_t *d)
{
const dt_dimensions_type_t d_type =
(dt_dimensions_type_t)dt_bauhaus_combobox_get(d->dimensions_type);
if((d_type == DT_DIMENSIONS_SCALE) || (d_type == DT_DIMENSIONS_PIXELS))
{
gtk_widget_hide(d->size_in_px);
}
else
{
gtk_widget_show(d->size_in_px);
gchar size_in_px_txt[120];
snprintf(size_in_px_txt, sizeof(size_in_px_txt) / sizeof(size_in_px_txt[0]),
_("which is equal to %s × %s px"),
gtk_entry_get_text(GTK_ENTRY(d->width)),
gtk_entry_get_text(GTK_ENTRY(d->height)));
gtk_label_set_text(GTK_LABEL(d->size_in_px), size_in_px_txt);
}
}
static void _set_dimensions(dt_lib_export_t *d,
const uint32_t max_width,
const uint32_t max_height,
const uint32_t print_dpi,
const gchar *scale)
{
gchar *max_width_char = g_strdup_printf("%u", max_width);
gchar *max_height_char = g_strdup_printf("%u", max_height);
gchar *max_dpi_char = g_strdup_printf("%u", print_dpi);
DT_ENTER_GUI_UPDATE();
gtk_entry_set_text(GTK_ENTRY(d->width), max_width_char);
gtk_entry_set_text(GTK_ENTRY(d->height), max_height_char);
gtk_entry_set_text(GTK_ENTRY(d->print_dpi), max_dpi_char);
gtk_entry_set_text(GTK_ENTRY(d->scale), scale);
_size_in_px_update(d);
DT_LEAVE_GUI_UPDATE();
dt_conf_set_int(CONFIG_PREFIX "width", max_width);
dt_conf_set_int(CONFIG_PREFIX "height", max_height);
dt_conf_set_int(CONFIG_PREFIX "print_dpi", print_dpi);
dt_conf_set_string(CONFIG_PREFIX "resizing_factor", scale);
g_free(max_width_char);
g_free(max_height_char);
g_free(max_dpi_char);
_resync_print_dimensions(d);
}
static void _size_update_display(dt_lib_export_t *self)
{
const dt_dimensions_type_t d_type =
(dt_dimensions_type_t)dt_bauhaus_combobox_get(self->dimensions_type);
gtk_widget_set_visible(self->px_size, d_type == DT_DIMENSIONS_PIXELS);
gtk_widget_set_visible(self->print_size, d_type == DT_DIMENSIONS_CM || d_type == DT_DIMENSIONS_INCH);
gtk_widget_set_visible(self->scale, d_type == DT_DIMENSIONS_SCALE);
gtk_label_set_text(GTK_LABEL(self->unit_label),
d_type == DT_DIMENSIONS_CM ? _("cm") : C_("unit", "in"));
_size_in_px_update(self);
}
void gui_reset(dt_lib_module_t *self)
{
// make sure we don't do anything useless:
if(!dt_control_running()) return;
dt_lib_export_t *d = self->data;
gtk_entry_set_text(GTK_ENTRY(d->width),
dt_confgen_get(CONFIG_PREFIX "width", DT_DEFAULT));
gtk_entry_set_text(GTK_ENTRY(d->height),
dt_confgen_get(CONFIG_PREFIX "height", DT_DEFAULT));
dt_bauhaus_combobox_set(d->dimensions_type,
dt_confgen_get_int(CONFIG_PREFIX "dimensions_type", DT_DEFAULT));
_size_update_display(d);
// first reset the selected storage and format modules
dt_imageio_module_format_t *mformat = dt_imageio_get_format();
if(mformat) mformat->gui_reset(mformat);
dt_imageio_module_storage_t *mstorage = dt_imageio_get_storage();
if(mstorage) mstorage->gui_reset(mstorage);
// Set default storage
const int storage_index =
dt_imageio_get_index_of_storage(dt_imageio_get_storage_by_name
(dt_confgen_get(CONFIG_PREFIX "storage_name",
DT_DEFAULT)));
dt_bauhaus_combobox_set(d->storage, storage_index);
// Set default format
const int format_index =
dt_imageio_get_index_of_format(dt_imageio_get_format_by_name
(dt_confgen_get(CONFIG_PREFIX "format_name",
DT_DEFAULT)));
dt_bauhaus_combobox_set(d->format, format_index);
dt_bauhaus_combobox_set(d->upscale,
dt_confgen_get_bool(CONFIG_PREFIX "upscale",
DT_DEFAULT) ? 1 : 0);
dt_bauhaus_combobox_set(d->high_quality,
dt_confgen_get_bool(CONFIG_PREFIX "high_quality_processing",
DT_DEFAULT) ? 1 : 0);
dt_bauhaus_combobox_set(d->export_masks,
dt_confgen_get_bool(CONFIG_PREFIX "export_masks",
DT_DEFAULT) ? 1 : 0);
dt_bauhaus_combobox_set(d->intent,
dt_confgen_get_int(CONFIG_PREFIX "iccintent",
DT_DEFAULT) + 1);
dt_bauhaus_slider_set(d->pq_peak, dt_confgen_get_int(CONFIG_PREFIX "pq_peak_nits",
DT_DEFAULT));
// iccprofile
const int icctype = dt_confgen_get_int(CONFIG_PREFIX "icctype", DT_DEFAULT);
gchar *iccfilename = dt_conf_get_string(CONFIG_PREFIX "iccprofile");
dt_bauhaus_combobox_set(d->profile, 0);
if(icctype != DT_COLORSPACE_NONE)
{
for(GList *profiles = darktable.color_profiles->profiles;
profiles;
profiles = g_list_next(profiles))
{
const dt_colorspaces_color_profile_t *pp = profiles->data;
if(pp->out_pos > -1
&& icctype == pp->type
&& (icctype != DT_COLORSPACE_FILE || !strcmp(iccfilename, pp->filename)))
{
dt_bauhaus_combobox_set(d->profile, pp->out_pos + 1);
break;
}
}
}
g_free(iccfilename);
// set style to 'none'
_update_style_label(d, "");
// reset style mode
dt_bauhaus_combobox_set(d->style_mode,
dt_confgen_get_bool(CONFIG_PREFIX "style_append", DT_DEFAULT));
gtk_widget_set_visible(GTK_WIDGET(d->style_mode), _is_style_set(d->style_name));
// export metadata presets
g_free(d->metadata_export);
d->metadata_export = dt_lib_export_metadata_get_conf();
dt_lib_gui_queue_update(self);
}
static void set_format_by_name(dt_lib_export_t *d,
const char *name)
{
// Find the selected format plugin among all existing plugins
dt_imageio_module_format_t *module = NULL;
for(GList *it = darktable.imageio->plugins_format; it; it = g_list_next(it))
{
if(g_strcmp0(((dt_imageio_module_format_t *)it->data)->name(), name) == 0
|| g_strcmp0(((dt_imageio_module_format_t *)it->data)->plugin_name, name) == 0)
{
module = (dt_imageio_module_format_t *)it->data;
break;
}
}
if(!module)
{
gtk_widget_hide(d->format_extra_container);
return;
}
else if(module->widget)
{
gtk_widget_show_all(d->format_extra_container);
gtk_stack_set_visible_child(GTK_STACK(d->format_extra_container), module->widget);
}
else
{
gtk_widget_hide(d->format_extra_container);
}
// Store the new format
dt_conf_set_string(CONFIG_PREFIX "format_name", module->plugin_name);
if(dt_bauhaus_combobox_set_from_text(d->format, module->name()) == FALSE)
dt_bauhaus_combobox_set(d->format, 0);
// Let's also update combination of storage/format dimension restrictions
_update_dimensions(d);
// only some modules support export of masks set it to 0 when
// insensitive and restore when making it sensitive again. this
// doesn't survive dt restarts.
const gboolean support_layers =
(module->flags(NULL) & FORMAT_FLAGS_SUPPORT_LAYERS) == FORMAT_FLAGS_SUPPORT_LAYERS;
const gboolean is_enabled = gtk_widget_get_sensitive(d->export_masks);
if(support_layers && !is_enabled)
{
// combobox was disabled and shall be enabled. we want to restore
// the old setting.
const gboolean export_masks = dt_conf_get_bool(CONFIG_PREFIX "export_masks");
gtk_widget_set_sensitive(d->export_masks, TRUE);
dt_bauhaus_combobox_set(d->export_masks, export_masks ? 1 : 0);
}
else if(!support_layers && is_enabled)
{
// combobox was enabled but shall be disabled. we want to save the
// current setting.
const int export_masks = dt_bauhaus_combobox_get(d->export_masks);
dt_bauhaus_combobox_set(d->export_masks, 0);
dt_conf_set_bool(CONFIG_PREFIX "export_masks", export_masks == 1);
gtk_widget_set_sensitive(d->export_masks, FALSE);
}
}
static void _format_changed(GtkWidget *widget,
dt_lib_export_t *d)
{
const gchar *name = dt_bauhaus_combobox_get_text(d->format);
g_signal_handlers_block_by_func(widget, _format_changed, d);
set_format_by_name(d, name);
g_signal_handlers_unblock_by_func(widget, _format_changed, d);
}
static void _get_max_output_dimension(dt_lib_export_t *d,
uint32_t *width,
uint32_t *height)
{
const char *storage_name = dt_conf_get_string_const(CONFIG_PREFIX "storage_name");
dt_imageio_module_storage_t *storage = dt_imageio_get_storage_by_name(storage_name);
const char *format_name = dt_conf_get_string_const(CONFIG_PREFIX "format_name");
dt_imageio_module_format_t *format = dt_imageio_get_format_by_name(format_name);
if(storage && format)
{
uint32_t fw, fh, sw, sh;
fw = fh = sw = sh = 0; // We are all equals!!!
storage->dimension(storage, NULL, &sw, &sh);
format->dimension(format, NULL, &fw, &fh);
if(sw == 0 || fw == 0)
*width = sw > fw ? sw : fw;
else
*width = sw < fw ? sw : fw;
if(sh == 0 || fh == 0)
*height = sh > fh ? sh : fh;
else
*height = sh < fh ? sh : fh;
}
}
static void _validate_dimensions(dt_lib_export_t *d)
{
//reset dimensions to previously stored value if they exceed the maximum
uint32_t width = atoi(gtk_entry_get_text(GTK_ENTRY(d->width)));
uint32_t height = atoi(gtk_entry_get_text(GTK_ENTRY(d->height)));
uint32_t print_dpi = atoi(gtk_entry_get_text(GTK_ENTRY(d->print_dpi)));
const gchar *scale = gtk_entry_get_text(GTK_ENTRY(d->scale));
if(width > d->max_allowed_width || height > d->max_allowed_height)
{
width = width > d->max_allowed_width
? dt_conf_get_int(CONFIG_PREFIX "width")
: width;
height = height > d->max_allowed_height
? dt_conf_get_int(CONFIG_PREFIX "height")
: height;
_set_dimensions(d, width, height, print_dpi, scale);
}
}
static void _update_dimensions(dt_lib_export_t *d)
{
uint32_t max_w = 0, max_h = 0;
_get_max_output_dimension(d, &max_w, &max_h);
d->max_allowed_width = max_w > 0 ? max_w : EXPORT_MAX_IMAGE_SIZE;
d->max_allowed_height = max_h > 0 ? max_h : EXPORT_MAX_IMAGE_SIZE;
_validate_dimensions(d);
}
static void set_storage_by_name(dt_lib_export_t *d,
const char *name)
{
int k = -1;
dt_imageio_module_storage_t *module = NULL;
d->storage_module = NULL;
for(const GList *it = darktable.imageio->plugins_storage; it; it = g_list_next(it))
{
dt_imageio_module_storage_t *storage = it->data;
k++;
if(strcmp(storage->name(storage), name) == 0
|| strcmp(storage->plugin_name, name) == 0)
{
module = storage;
break;
}
}
if(!module)
{
gtk_widget_hide(d->storage_extra_container);
return;
}
else if(module->widget)
{
gtk_widget_show_all(d->storage_extra_container);
gtk_stack_set_visible_child(GTK_STACK(d->storage_extra_container), module->widget);
}
else
{
gtk_widget_hide(d->storage_extra_container);
}
dt_bauhaus_combobox_set(d->storage, k);
dt_conf_set_string(CONFIG_PREFIX "storage_name", module->plugin_name);
d->storage_module = module;
// Check if plugin recommends a max dimension and set
// if not implemented the stored conf values are used..
uint32_t w = 0, h = 0;
module->recommended_dimension(module, NULL, &w, &h);
const uint32_t cw = dt_conf_get_int(CONFIG_PREFIX "width");
const uint32_t ch = dt_conf_get_int(CONFIG_PREFIX "height");
const uint32_t pd = dt_conf_get_int(CONFIG_PREFIX "print_dpi");
const gchar *scale = dt_conf_get_string_const(CONFIG_PREFIX "resizing_factor");
// If user's selected value is below the max, select it
if(w > cw || w == 0) w = cw;
if(h > ch || h == 0) h = ch;
// Set the recommended dimension
_set_dimensions(d, w, h, pd, scale);
// Let's update formats combobox with supported formats of selected storage module...
_update_formats_combobox(d);
// Lets try to set selected format if fail select first in list..
const char *format_name = dt_conf_get_string_const(CONFIG_PREFIX "format_name");
const dt_imageio_module_format_t *format = dt_imageio_get_format_by_name(format_name);
if(format == NULL
|| dt_bauhaus_combobox_set_from_text(d->format, format->name()) == FALSE)
dt_bauhaus_combobox_set(d->format, 0);
}
static void _storage_changed(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_export_t *d = self->data;
const gchar *name = dt_bauhaus_combobox_get_text(d->storage);
g_signal_handlers_block_by_func(widget, _storage_changed, self);
if(name) set_storage_by_name(d, name);
g_signal_handlers_unblock_by_func(widget, _storage_changed, self);
dt_lib_gui_queue_update(self);
}
// the PQ peak only matters when the export may be PQ
static gboolean _export_may_be_pq(const dt_colorspaces_color_profile_type_t icctype)
{
return icctype == DT_COLORSPACE_NONE || dt_colorspaces_hdr_transfer(icctype) == DT_HDR_PQ;
}
static void _update_pq_peak_visibility(dt_lib_export_t *d)
{
gtk_widget_set_visible(d->pq_peak, _export_may_be_pq(dt_conf_get_int(CONFIG_PREFIX "icctype")));
}
static void _set_profile_conf(GtkWidget *widget)
{
int pos = dt_bauhaus_combobox_get(widget);
if(pos > 0)
{
pos--;
for(GList *profiles = darktable.color_profiles->profiles;
profiles;
profiles = g_list_next(profiles))
{
const dt_colorspaces_color_profile_t *pp = profiles->data;
if(pp->out_pos == pos)
{
dt_conf_set_int(CONFIG_PREFIX "icctype", pp->type);
if(pp->type == DT_COLORSPACE_FILE)
dt_conf_set_string(CONFIG_PREFIX "iccprofile", pp->filename);
else
dt_conf_set_string(CONFIG_PREFIX "iccprofile", "");
return;
}
}
}
dt_conf_set_int(CONFIG_PREFIX "icctype", DT_COLORSPACE_NONE);
dt_conf_set_string(CONFIG_PREFIX "iccprofile", "");
}
static void _profile_changed(GtkWidget *widget, dt_lib_export_t *d)
{
_set_profile_conf(widget);
_update_pq_peak_visibility(d);
}
static void _pq_peak_changed(GtkWidget *widget, dt_lib_export_t *d)
{
dt_conf_set_int(CONFIG_PREFIX "pq_peak_nits", (int)roundf(dt_bauhaus_slider_get(widget)));
// colorout reads the peak when it commits, and a darkroom on a PQ
// display renders with it too
if(dt_view_get_current() == DT_VIEW_DARKROOM)
dt_dev_reprocess_all(darktable.develop);
}
static void _dimensions_type_changed(GtkWidget *widget,
dt_lib_export_t *d)
{
DT_GUARD_GUI_UPDATE();
const dt_dimensions_type_t d_type =
(dt_dimensions_type_t)dt_bauhaus_combobox_get(widget);
dt_conf_set_int(CONFIG_PREFIX "dimensions_type", d_type);
dt_conf_set_string(CONFIG_PREFIX "resizing",
d_type == DT_DIMENSIONS_SCALE ? "scaling" : "max_size");
if(d_type == DT_DIMENSIONS_CM || d_type == DT_DIMENSIONS_INCH)
{
// set dpi to user-set dpi
dt_conf_set_int("metadata/resolution",
dt_conf_get_int(CONFIG_PREFIX "print_dpi"));
_resync_print_dimensions(d);
}
else
{
// reset export dpi to default value for scale/pixel specific export
dt_conf_set_int("metadata/resolution",
dt_confgen_get_int("metadata/resolution", DT_DEFAULT));
}
_size_update_display(d);
}
static void _resync_print_dimensions(dt_lib_export_t *self)
{
DT_TRY_GUI_UPDATE();
const uint32_t width = dt_conf_get_int(CONFIG_PREFIX "width");
const uint32_t height = dt_conf_get_int(CONFIG_PREFIX "height");
const int dpi = atoi(gtk_entry_get_text(GTK_ENTRY(self->print_dpi)));
const float p_width = pixels2print(self, width);
const float p_height = pixels2print(self, height);
gchar *pwidth = g_strdup_printf("%.2f", p_width);
gchar *pheight = g_strdup_printf("%.2f", p_height);
gchar *pdpi = g_strdup_printf("%d", dpi);
gtk_entry_set_text(GTK_ENTRY(self->print_width), pwidth);
gtk_entry_set_text(GTK_ENTRY(self->print_height), pheight);
gtk_entry_set_text(GTK_ENTRY(self->print_dpi), pdpi);
g_free(pwidth);
g_free(pheight);
g_free(pdpi);
DT_LEAVE_GUI_UPDATE();
}
static void _resync_pixel_dimensions(dt_lib_export_t *self)
{
DT_TRY_GUI_UPDATE();
const float p_width = atof(gtk_entry_get_text(GTK_ENTRY(self->print_width)));
const float p_height = atof(gtk_entry_get_text(GTK_ENTRY(self->print_height)));
const uint32_t width = print2pixels(self, p_width);
const uint32_t height = print2pixels(self, p_height);
dt_conf_set_int(CONFIG_PREFIX "width", width);
dt_conf_set_int(CONFIG_PREFIX "height", height);
gchar *pwidth = g_strdup_printf("%u", width);
gchar *pheight = g_strdup_printf("%u", height);
gtk_entry_set_text(GTK_ENTRY(self->width), pwidth);
gtk_entry_set_text(GTK_ENTRY(self->height), pheight);
g_free(pwidth);
g_free(pheight);
DT_LEAVE_GUI_UPDATE();
}
static void _width_changed(GtkEditable *entry, gpointer user_data)
{
DT_GUARD_GUI_UPDATE();
const dt_lib_export_t *d = (dt_lib_export_t *)user_data;
const uint32_t width = atoi(gtk_entry_get_text(GTK_ENTRY(d->width)));
dt_conf_set_int(CONFIG_PREFIX "width", width);
}
static void _print_width_changed(GtkEditable *entry, gpointer user_data)
{
DT_TRY_GUI_UPDATE();
dt_lib_export_t *d = (dt_lib_export_t *)user_data;
const float p_width = atof(gtk_entry_get_text(GTK_ENTRY(d->print_width)));
const uint32_t width = print2pixels(d, p_width);
dt_conf_set_int(CONFIG_PREFIX "width", width);
gchar *pwidth = g_strdup_printf("%u", width);
gtk_entry_set_text(GTK_ENTRY(d->width), pwidth);
g_free(pwidth);
_size_in_px_update(d);
DT_LEAVE_GUI_UPDATE();
}
static void _height_changed(GtkEditable *entry, gpointer user_data)
{
DT_GUARD_GUI_UPDATE();
const dt_lib_export_t *d = (dt_lib_export_t *)user_data;
const uint32_t height = atoi(gtk_entry_get_text(GTK_ENTRY(d->height)));
dt_conf_set_int(CONFIG_PREFIX "height", height);
}
static void _print_height_changed(GtkEditable *entry, gpointer user_data)
{
DT_TRY_GUI_UPDATE();
dt_lib_export_t *d = (dt_lib_export_t *)user_data;
const float p_height = atof(gtk_entry_get_text(GTK_ENTRY(d->print_height)));
const uint32_t height = print2pixels(d, p_height);
dt_conf_set_int(CONFIG_PREFIX "height", height);
gchar *pheight = g_strdup_printf("%u", height);
gtk_entry_set_text(GTK_ENTRY(d->height), pheight);
g_free(pheight);
_size_in_px_update(d);
DT_LEAVE_GUI_UPDATE();
}
static void _print_dpi_changed(GtkWidget *widget, gpointer user_data)
{
DT_GUARD_GUI_UPDATE();
dt_lib_export_t *d = (dt_lib_export_t *)user_data;
const int dpi = atoi(gtk_entry_get_text(GTK_ENTRY(d->print_dpi)));
dt_conf_set_int(CONFIG_PREFIX "print_dpi", dpi);
dt_conf_set_int("metadata/resolution", dpi);
_resync_pixel_dimensions(d);
_size_in_px_update(d);
}
static void _callback_bool(GtkWidget *widget, gpointer user_data)
{
const char *key = (const char *)user_data;
dt_conf_set_bool(key, dt_bauhaus_combobox_get(widget) == 1);
}
static void _intent_changed(GtkWidget *widget, dt_lib_export_t *d)
{
const int pos = dt_bauhaus_combobox_get(widget);
dt_conf_set_int(CONFIG_PREFIX "iccintent", pos - 1);
}
static void _update_style_label(dt_lib_export_t *d, const char *name)
{
gtk_widget_set_visible(GTK_WIDGET(d->style_mode), _is_style_set(name));
// We use the string "none" to indicate that we don't apply any style to the export
char *localized_style = name[0]
? dt_util_localize_segmented_name(name, TRUE)
: g_strdup(_("none"));
// Use only the leaf part of the segmented style name in the tooltip
char *leaf = strrchr(localized_style, '|');
leaf = leaf ? leaf+1 : localized_style;
gtk_label_set_text(GTK_LABEL(d->style), leaf);
char *tooltip = g_strdup_printf(_("style to be applied on export:\n<b>%s</b>"),
localized_style);
g_free(localized_style);
gtk_widget_set_tooltip_markup(d->style, tooltip);
g_free(tooltip);
g_free(d->style_name);
d->style_name = g_strdup(name);
dt_conf_set_string(CONFIG_PREFIX "style", d->style_name);
}
static void _update_style(const dt_stylemenu_data_t *menu_data)
{
_update_style_label(menu_data->user_data,menu_data->name);
}
static void _apply_style_activate_callback(GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
const dt_stylemenu_data_t *menu_data = (dt_stylemenu_data_t *)g_variant_get_uint64(parameter);
_update_style(menu_data);
dtgtk_stylemenu_free_menu_data();
}
static void _style_popupmenu_callback(GtkWidget *w,
gpointer user_data)
{
GActionGroup *action_group = gtk_widget_get_action_group(w, "styles");
if(action_group == NULL)
{
GActionEntry action_entries[] =
{
{ "activate", _apply_style_activate_callback, "t", NULL },
};
action_group = G_ACTION_GROUP(g_simple_action_group_new());
g_action_map_add_action_entries(G_ACTION_MAP(action_group),
action_entries,
G_N_ELEMENTS(action_entries),
NULL);
gtk_widget_insert_action_group(w,
"styles",
G_ACTION_GROUP(action_group));
g_object_unref(action_group);
}
/* if we got any styles, lets popup menu for selection */
GtkWidget *popover_menu = dtgtk_build_style_popover_menu(w, TRUE, user_data);
if(popover_menu)
{
gtk_popover_popup(GTK_POPOVER(popover_menu));
}
else
dt_control_log(_("no styles have been created yet"));
}
int position(const dt_lib_module_t *self)
{
return 0;
}
static void _update_formats_combobox(dt_lib_export_t *d)
{
// Clear format combo box
dt_bauhaus_combobox_clear(d->format);
// Get current selected storage
const char *storage_name = dt_conf_get_string_const(CONFIG_PREFIX "storage_name");
dt_imageio_module_storage_t *storage = dt_imageio_get_storage_by_name(storage_name);
// Add supported formats to combobox
gboolean empty = TRUE;
for(const GList *it = darktable.imageio->plugins_format; it; it = g_list_next(it))
{
dt_imageio_module_format_t *format = it->data;
if(storage->supported(storage, format))
{
dt_bauhaus_combobox_add(d->format, format->name());
empty = FALSE;
}
}
gtk_widget_set_sensitive(d->format, !empty);
}
static void _on_storage_list_changed(gpointer instance,
dt_lib_module_t *self)
{
dt_lib_export_t *d = self->data;
dt_imageio_module_storage_t *storage = dt_imageio_get_storage();
dt_bauhaus_combobox_clear(d->storage);
dt_gui_container_remove_children(GTK_CONTAINER(d->storage_extra_container));
for(const GList *it = darktable.imageio->plugins_storage; it; it = g_list_next(it))
{
const dt_imageio_module_storage_t *module = it->data;
dt_bauhaus_combobox_add(d->storage, module->name(module));
if(module->widget)
{
gtk_container_add(GTK_CONTAINER(d->storage_extra_container), module->widget);
}
}
dt_bauhaus_combobox_set(d->storage, dt_imageio_get_index_of_storage(storage));
}
static void _menuitem_preferences(GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
dt_lib_module_t *self = (dt_lib_module_t *)user_data;
dt_lib_export_t *d = self->data;
const gchar *name = dt_bauhaus_combobox_get_text(d->storage);
const gboolean ondisk = name
&& !g_strcmp0(name, _("file on disk")); // FIXME: NO!!!!!one!
d->metadata_export =
dt_lib_export_metadata_configuration_dialog(d->metadata_export, ondisk);
}
static gboolean _batch_preset_active(dt_lib_module_t *self)
{
dt_lib_export_t *d = self->data;
GtkTreeIter iter;
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->batch_treeview));
gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
gboolean batch_preset_active = FALSE;
while(valid)
{
gboolean active;
gtk_tree_model_get(model, &iter,
DT_EXPORT_BATCH_COL_ACTIVE, &active,
-1);
if(active)
{
batch_preset_active = TRUE;
break;
}
valid = gtk_tree_model_iter_next(model, &iter);
}
return batch_preset_active;
}
static void _batch_export_toggled_callback(GtkCellRendererToggle *cell_renderer,
gchar *path_str,
gpointer user_data)
{
dt_lib_module_t *self = user_data;
dt_lib_export_t *d = self->data;
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->batch_treeview));
GtkTreeIter iter;
GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
gboolean toggle;
gchar *name;
gtk_tree_model_get_iter(model, &iter, path);
gtk_tree_model_get(model, &iter,
DT_EXPORT_BATCH_COL_ACTIVE, &toggle,
DT_EXPORT_BATCH_COL_NAME, &name,
-1);
toggle = !toggle;
gtk_list_store_set(GTK_LIST_STORE(model), &iter, DT_EXPORT_BATCH_COL_ACTIVE, toggle, -1);
gtk_tree_path_free(path);
gchar *setting = g_strdup_printf(CONFIG_PREFIX "batch_%s", name);
dt_conf_set_bool(setting, toggle);
g_free(setting);
gtk_widget_set_sensitive(GTK_WIDGET(d->batch_export_button), _batch_preset_active(self));
}
static void _fill_batch_export_list(dt_lib_module_t *self)
{
dt_lib_export_t *d = self->data;
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->batch_treeview));
GtkTreeIter iter;
// save all preset names from the store to check if presets were renamed/deleted
GList *nonexistent_presets = NULL;
gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
while(valid)
{
char *preset_name;
gtk_tree_model_get(model, &iter,
DT_EXPORT_BATCH_COL_NAME, &preset_name,
-1);
nonexistent_presets = g_list_prepend(nonexistent_presets, g_strdup(preset_name));
valid = gtk_tree_model_iter_next(model, &iter);
}
// clear and refill the store
gtk_list_store_clear(GTK_LIST_STORE(model));
gboolean has_active = FALSE;
GList *names = dt_presets_version_names("export", self->version(), FALSE);
for(const GList *l = names; l; l = g_list_next(l))
{
const char *name = l->data;
gchar *setting = g_strdup_printf(CONFIG_PREFIX "batch_%s", name);
gboolean active = dt_conf_get_bool(setting);
g_free(setting);
gtk_list_store_insert_with_values(GTK_LIST_STORE(model), NULL, -1,
DT_EXPORT_BATCH_COL_ACTIVE, active,
DT_EXPORT_BATCH_COL_NAME, name,
-1);
has_active |= active;
// remove unchanged preset names from the list so the list will
// contain only preset names which no longer exist
for(GList *preset_iter = nonexistent_presets; preset_iter; preset_iter = g_list_next(preset_iter))
{
const gchar *preset_name = (gchar *)preset_iter->data;
if(!g_strcmp0(preset_name, name))
{
nonexistent_presets = g_list_remove_link(nonexistent_presets, preset_iter);
g_list_free_full(preset_iter, g_free);
break;
}
}
}
g_list_free_full(names, g_free);
gtk_widget_set_sensitive(GTK_WIDGET(d->batch_export_button), has_active);
// remove non-existent entries from config
for(GList *preset_iter = nonexistent_presets; preset_iter; preset_iter = g_list_next(preset_iter))
{
const gchar *preset_name = (gchar *)preset_iter->data;
gchar *setting = g_strdup_printf(CONFIG_PREFIX "batch_%s", preset_name);
dt_conf_remove_key(setting);
g_free(setting);
}
g_list_free_full(nonexistent_presets, g_free);
}
static void _export_presets_changed_callback(gpointer instance, gpointer module, dt_lib_module_t *self)
{
if(!g_strcmp0(module, "export"))
_fill_batch_export_list(self);
}
void set_preferences(GMenu *menu, GActionGroup *action_group, dt_lib_module_t *self)
{
GSimpleAction *action = g_simple_action_new("preferences", NULL);
g_signal_connect(action, "activate", G_CALLBACK(_menuitem_preferences), self);
g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action));
g_menu_append(menu, _("preferences..."), "presets.preferences");
}
void gui_init(dt_lib_module_t *self)
{
dt_lib_export_t *d = malloc(sizeof(dt_lib_export_t));
self->data = (void *)d;
dt_action_insert_sorted(DT_ACTION(self), &darktable.control->actions_format);
dt_action_insert_sorted(DT_ACTION(self), &darktable.control->actions_storage);
d->storage = dt_bauhaus_combobox_new_action(DT_ACTION(self));
dt_bauhaus_widget_set_label(d->storage, NULL, N_("target storage"));
// add all storage widgets to the stack widget
d->storage_extra_container = gtk_stack_new();
gtk_stack_set_homogeneous(GTK_STACK(d->storage_extra_container),FALSE);
for(const GList *it = darktable.imageio->plugins_storage;
it;
it = g_list_next(it))
{
const dt_imageio_module_storage_t *module = it->data;
dt_bauhaus_combobox_add(d->storage, module->name(module));
if(module->widget)
{
gtk_container_add(GTK_CONTAINER(d->storage_extra_container), module->widget);
}
}
// postponed so we can do the two steps in one loop
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_IMAGEIO_STORAGE_CHANGE, _on_storage_list_changed);
g_signal_connect(G_OBJECT(d->storage), "value-changed",
G_CALLBACK(_storage_changed), self);
d->format = dt_bauhaus_combobox_new_action(DT_ACTION(self));
dt_bauhaus_widget_set_label(d->format, NULL, N_("file format"));
gtk_widget_set_tooltip_markup(GTK_WIDGET(d->format),
_("images will be exported according to the format specified here\n\n"
"when exporting to AVIF, EXR, HEIF, JPEG XL, or XCF, selecting\n"
"specific metadata is not currently possible\n\n"
"for these formats, no metadata fields will be included\n"
"unless the user selects <b>all</b> of the metadata checkboxes in\n"
"the export module preferences"));
g_signal_connect(G_OBJECT(d->format), "value-changed",
G_CALLBACK(_format_changed), (gpointer)d);
// add all format widgets to the stack widget
d->format_extra_container = gtk_stack_new();
gtk_stack_set_homogeneous(GTK_STACK(d->format_extra_container),FALSE);
for(const GList *it = darktable.imageio->plugins_format; it; it = g_list_next(it))
{
const dt_imageio_module_format_t *module = it->data;
if(module->widget)
{
gtk_container_add(GTK_CONTAINER(d->format_extra_container), module->widget);
}
}
DT_BAUHAUS_COMBOBOX_NEW_FULL(d->dimensions_type, self, NULL, N_("set size"),
_("choose a method for setting the output size"),
dt_conf_get_int(CONFIG_PREFIX "dimensions_type"),
_dimensions_type_changed, d,
N_("in pixels (for file)"),
N_("in cm (for print)"),
N_("in inch (for print)"),
N_("by scale (for file)"));
d->print_width = dt_action_entry_new(DT_ACTION(self),
N_("print width"),
G_CALLBACK(_print_width_changed), d,
_("maximum output width limit.\n"
"click middle mouse button to reset to 0."),
NULL);
d->print_height = dt_action_entry_new(DT_ACTION(self), N_("print height"),
G_CALLBACK(_print_height_changed), d,
_("maximum output height limit.\n"
"click middle mouse button to reset to 0."),
NULL);
d->print_dpi = dt_action_entry_new(DT_ACTION(self), N_("dpi"),
G_CALLBACK(_print_dpi_changed), d,
_("resolution in dot per inch"),
dt_conf_get_string_const(CONFIG_PREFIX "print_dpi"));
d->width = dt_action_entry_new(DT_ACTION(self), N_("width"),
G_CALLBACK(_width_changed), d,
_("maximum output width limit.\n"
"click middle mouse button to reset to 0."),
NULL);
d->height = dt_action_entry_new(DT_ACTION(self), N_("height"),
G_CALLBACK(_height_changed), d,
_("maximum output height limit.\n"
"click middle mouse button to reset to 0."),
NULL);
d->print_size = gtk_flow_box_new();
gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(d->print_size), 5);
gtk_flow_box_set_column_spacing (GTK_FLOW_BOX(d->print_size), 3);
gtk_container_add(GTK_CONTAINER(d->print_size), d->print_width);
gtk_container_add(GTK_CONTAINER(d->print_size), gtk_label_new(_("x")));
gtk_container_add(GTK_CONTAINER(d->print_size), d->print_height);
d->unit_label = gtk_label_new(_("cm"));
gtk_container_add(GTK_CONTAINER(d->print_size), d->unit_label);
GtkBox *dpi_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3));
gtk_box_pack_start(dpi_box, gtk_label_new(_("@")), FALSE, FALSE, 0);
gtk_box_pack_start(dpi_box, d->print_dpi, TRUE, TRUE, 0);
gtk_box_pack_start(dpi_box, gtk_label_new(_("dpi")), FALSE, FALSE, 0);
gtk_container_add(GTK_CONTAINER(d->print_size), GTK_WIDGET(dpi_box));
gtk_container_foreach(GTK_CONTAINER(d->print_size),
(GtkCallback)gtk_widget_set_can_focus, GINT_TO_POINTER(FALSE));
d->px_size = gtk_flow_box_new();
gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(d->px_size), 3);
gtk_flow_box_set_column_spacing (GTK_FLOW_BOX(d->px_size), 3);
gtk_container_add(GTK_CONTAINER(d->px_size), d->width);
gtk_container_add(GTK_CONTAINER(d->px_size), gtk_label_new(_("x")));
GtkBox *px_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3));
gtk_box_pack_start(px_box, d->height, TRUE, TRUE, 0);
gtk_box_pack_start(px_box, gtk_label_new(_("px")), FALSE, FALSE, 0);
gtk_container_add(GTK_CONTAINER(d->px_size), GTK_WIDGET(px_box));
gtk_container_foreach(GTK_CONTAINER(d->px_size),
(GtkCallback)gtk_widget_set_can_focus, GINT_TO_POINTER(FALSE));
d->scale = dt_action_entry_new
(DT_ACTION(self), N_("scale"),
G_CALLBACK(_scale_changed), d,
_("it can be an integer, decimal number or simple fraction.\n"
"zero or empty values are equal to 1.\n"
"click middle mouse button to reset to 1."),
dt_conf_get_string_const(CONFIG_PREFIX "resizing_factor"));
gtk_widget_set_halign(GTK_WIDGET(d->scale), GTK_ALIGN_END);
d->size_in_px = gtk_label_new("");
gtk_label_set_ellipsize(GTK_LABEL(d->size_in_px), PANGO_ELLIPSIZE_START);
gtk_widget_set_sensitive(GTK_WIDGET(d->size_in_px), FALSE);
gtk_widget_set_halign(GTK_WIDGET(d->scale), GTK_ALIGN_FILL);
gtk_widget_set_halign(GTK_WIDGET(d->size_in_px), GTK_ALIGN_END);
DT_BAUHAUS_COMBOBOX_NEW_FULL
(d->upscale, self, NULL,
N_("allow upscaling"), NULL,
dt_conf_get_bool(CONFIG_PREFIX "upscale") ? 1 : 0,
_callback_bool,
(gpointer)CONFIG_PREFIX "upscale",
N_("no"), N_("yes"));
DT_BAUHAUS_COMBOBOX_NEW_FULL
(d->high_quality, self, NULL,
N_("high quality resampling"),
_("do high quality resampling during export"),
dt_conf_get_bool(CONFIG_PREFIX "high_quality_processing") ? 1 : 0, _callback_bool,
(gpointer)CONFIG_PREFIX "high_quality_processing",
N_("no"), N_("yes"));
DT_BAUHAUS_COMBOBOX_NEW_FULL
(d->export_masks, self, NULL,
N_("store masks"),
_("store masks as layers in exported images. only works for some formats."),
dt_conf_get_bool(CONFIG_PREFIX "export_masks") ? 1 : 0, _callback_bool,
(gpointer)CONFIG_PREFIX "export_masks",
N_("no"), N_("yes"));
// Add profile combo
d->profile = dt_bauhaus_combobox_new_action(DT_ACTION(self));
dt_bauhaus_widget_set_label(d->profile, NULL, N_("profile"));
dt_bauhaus_combobox_add(d->profile, _("image settings"));
for(GList *l = darktable.color_profiles->profiles; l; l = g_list_next(l))
{
const dt_colorspaces_color_profile_t *prof = l->data;
if(prof->out_pos > -1)
dt_bauhaus_combobox_add(d->profile, prof->name);
}
dt_bauhaus_combobox_set(d->profile, 0);
char *tooltip = dt_ioppr_get_location_tooltip("out", _("output ICC profiles"));
gtk_widget_set_tooltip_markup(d->profile, tooltip);
g_free(tooltip);
// Add PQ peak slider
d->pq_peak = dt_bauhaus_slider_new_action(DT_ACTION(self), 203.f, 10000.f, 1.f,
dt_confgen_get_int(CONFIG_PREFIX "pq_peak_nits",
DT_DEFAULT), 0);
dt_bauhaus_widget_set_label(d->pq_peak, NULL, N_("PQ peak"));
dt_bauhaus_slider_set_log_curve(d->pq_peak);
dt_bauhaus_slider_set_format(d->pq_peak, _(" nits"));
dt_bauhaus_slider_set(d->pq_peak, dt_conf_get_int(CONFIG_PREFIX "pq_peak_nits"));
gtk_widget_set_tooltip_text(d->pq_peak,
_("the brightest highlight of a PQ (HDR) export, in nits.\n"
"tone mappers that follow the output map their white to it.\n"
"HLG exports always peak at 1000 nits."));
// Add intent combo
DT_BAUHAUS_COMBOBOX_NEW_FULL
(d->intent, self, NULL, N_("intent"),
_("• perceptual: smoothly moves out-of-gamut colors into gamut, preserving gradations,\n"
"but distorts in-gamut colors in the process.\n"
"note that perceptual is often a proprietary LUT that depends on the destination space."
"\n\n"
"• relative colorimetric: keeps luminance while reducing as little as possible\n"
"saturation until colors fit in gamut."
"\n\n"
"• saturation: designed to present eye-catching business graphics\n"
"by preserving the saturation. (not suited for photography)."
"\n\n"
"• absolute colorimetric: adapt white point of the image to the white point of the\n"
"destination medium and do nothing else. mainly used when proofing colors.\n"
"(not suited for photography)."),
0, _intent_changed, self,
N_("image settings"),
N_("perceptual"),
N_("relative colorimetric"),
NC_("rendering intent", "saturation"),
N_("absolute colorimetric"));
// Add check to control whether the style is to replace or append the current module
DT_BAUHAUS_COMBOBOX_NEW_FULL
(d->style_mode, self, NULL,
N_("mode"),
_("whether the style items are appended to the history or replacing the history"),
dt_conf_get_bool(CONFIG_PREFIX "style_append") ? 1 : 0, _callback_bool,
(gpointer)CONFIG_PREFIX "style_append",
N_("replace history"), N_("append history"));
// Add style combo
GtkWidget *styles_button = dtgtk_button_new_full(dtgtk_cairo_paint_styles, 0, NULL,
&(dtgtk_button_config_t){
.tooltip = _("select style to be applied on export"),
.clicked_cb = G_CALLBACK(_style_popupmenu_callback),
.clicked_data = (gpointer)d,
});
gtk_widget_set_halign(styles_button,GTK_ALIGN_END);
// dt_gui_add_help_link(styles, "bottom_panel_styles");
GtkBox *style_box = (GtkBox*)gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);
gtk_widget_set_tooltip_text(GTK_WIDGET(style_box), _("temporary style to use while exporting"));
GtkWidget *styles_label = gtk_label_new(_("style"));
gtk_box_pack_start(style_box, styles_label, FALSE, FALSE, 0);
GtkWidget *current_style = gtk_label_new("");
gtk_widget_set_halign(current_style,GTK_ALIGN_END);
gtk_label_set_justify(GTK_LABEL(current_style), GTK_JUSTIFY_RIGHT);
gtk_label_set_ellipsize(GTK_LABEL(current_style), PANGO_ELLIPSIZE_MIDDLE);
gtk_box_pack_start(style_box, current_style, TRUE, TRUE, 0);
gtk_box_pack_start(style_box, styles_button, FALSE, FALSE, 0);
d->style = GTK_WIDGET(current_style);
d->style_name = NULL;
const char *stored_style = dt_conf_get_string_const(CONFIG_PREFIX "style");
_update_style_label(d, stored_style ? stored_style : "");
// Set callback signals
g_signal_connect(G_OBJECT(d->profile), "value-changed",
G_CALLBACK(_profile_changed), (gpointer)d);
g_signal_connect(G_OBJECT(d->pq_peak), "value-changed",
G_CALLBACK(_pq_peak_changed), (gpointer)d);
// Export button
d->export_button = GTK_BUTTON(dt_action_button_new
(self, NC_("actionbutton", "start export"),
_export_button_clicked, self,
NULL,
GDK_KEY_e, GDK_CONTROL_MASK));
self->widget = dt_gui_vbox
(dt_ui_section_label_new(C_("section", "storage options")),
d->storage, d->storage_extra_container,
dt_ui_section_label_new(C_("section", "format options")),
d->format, d->format_extra_container,
dt_ui_section_label_new(C_("section", "global options")),
d->dimensions_type, d->px_size, d->print_size, d->scale, d->size_in_px,
d->upscale, d->high_quality, d->export_masks,
d->profile, d->pq_peak, d->intent,
style_box, d->style_mode,
d->export_button);
// multi-preset export
dt_gui_new_collapsible_section(&d->cs,
"plugins/lighttable/export/batch_export_expanded",
_("multi-preset export"),
GTK_BOX(self->widget),
DT_ACTION(self));
gtk_widget_set_tooltip_text(d->cs.expander, _("export the selected images with multiple presets"));
GtkListStore *store = gtk_list_store_new(DT_EXPORT_BATCH_NUM_COLS,
G_TYPE_BOOLEAN,
G_TYPE_STRING);
GtkTreeModel *model = GTK_TREE_MODEL(store);
GtkWidget *view = gtk_tree_view_new_with_model(model);
g_object_unref(model);
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
gtk_tree_selection_set_mode(gtk_tree_view_get_selection
(GTK_TREE_VIEW(view)), GTK_SELECTION_NONE);
GtkTreeViewColumn *column = gtk_tree_view_column_new();
gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new();
g_signal_connect(renderer, "toggled", G_CALLBACK(_batch_export_toggled_callback), self);
gtk_tree_view_column_pack_start(column, renderer, FALSE);
gtk_tree_view_column_add_attribute(column, renderer, "active", DT_EXPORT_BATCH_COL_ACTIVE);
column = gtk_tree_view_column_new();
gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_start(column, renderer, TRUE);
gtk_tree_view_column_add_attribute(column, renderer, "text", DT_EXPORT_BATCH_COL_NAME);
// multi-preset export button
d->batch_export_button = GTK_BUTTON(dt_action_button_new
(self, NC_("actionbutton", "start batch export"),
_batch_export_button_clicked, self,
NULL, 0, 0));
d->batch_treeview = view;
_fill_batch_export_list(self);
dt_gui_box_add(d->cs.container, view, d->batch_export_button);
dt_gui_connect_click_all(d->width, _widht_mdlclick_cb, NULL, d);
dt_gui_connect_click_all(d->height, _height_mdlclick_cb, NULL, d);
dt_gui_connect_click_all(d->print_width, _widht_mdlclick_cb, NULL, d);
dt_gui_connect_click_all(d->print_height, _height_mdlclick_cb, NULL, d);
dt_gui_connect_click_all(d->scale, _scale_mdlclick_cb, NULL, d);
// this takes care of keeping hidden widgets hidden
gtk_widget_show_all(self->widget);
gtk_widget_set_no_show_all(self->widget, TRUE);
const char* setting = dt_conf_get_string_const(CONFIG_PREFIX "width");
gtk_entry_set_text(GTK_ENTRY(d->width), setting);
setting = dt_conf_get_string_const(CONFIG_PREFIX "height");
gtk_entry_set_text(GTK_ENTRY(d->height), setting);
_size_update_display(d);
// Set storage
setting = dt_conf_get_string_const(CONFIG_PREFIX "storage_name");
const int storage_index =
dt_imageio_get_index_of_storage(dt_imageio_get_storage_by_name(setting));
dt_bauhaus_combobox_set(d->storage, storage_index);
dt_bauhaus_combobox_set(d->intent, dt_conf_get_int(CONFIG_PREFIX "iccintent") + 1);
// iccprofile
const int icctype = dt_conf_get_int(CONFIG_PREFIX "icctype");
gchar *iccfilename = dt_conf_get_string(CONFIG_PREFIX "iccprofile");
dt_bauhaus_combobox_set(d->profile, 0);
if(icctype != DT_COLORSPACE_NONE)
{
for(GList *profiles = darktable.color_profiles->profiles;
profiles;
profiles = g_list_next(profiles))
{
const dt_colorspaces_color_profile_t *pp = profiles->data;
if(pp->out_pos > -1
&& icctype == pp->type
&& (icctype != DT_COLORSPACE_FILE || !strcmp(iccfilename, pp->filename)))
{
dt_bauhaus_combobox_set(d->profile, pp->out_pos + 1);
break;
}
}
}
g_free(iccfilename);
_update_pq_peak_visibility(d);
// style
// set it to none if the var is not set or the style doesn't exist anymore
setting = dt_conf_get_string_const(CONFIG_PREFIX "style");
const gboolean is_style_set = _is_style_set(setting);
g_free(d->style_name);
d->style_name = g_strdup(is_style_set ? setting : "");
// style mode to overwrite as it was the initial behavior
gtk_widget_set_no_show_all(d->style_mode, TRUE);
gtk_widget_set_visible(d->style_mode, is_style_set);
// export metadata presets
d->metadata_export = dt_lib_export_metadata_get_conf();
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_SELECTION_CHANGED, _image_selection_changed_callback);
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE, _mouse_over_image_callback);
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_COLLECTION_CHANGED, _collection_updated_callback);
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_IMAGEIO_STORAGE_EXPORT_ENABLE, _export_enable_callback);
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_PRESETS_CHANGED, _export_presets_changed_callback);
}
void gui_cleanup(dt_lib_module_t *self)
{
dt_lib_export_t *d = self->data;
for(const GList *it = darktable.imageio->plugins_storage; it; it = g_list_next(it))
{
dt_imageio_module_storage_t *module = it->data;
if(module->widget)
gtk_container_remove(GTK_CONTAINER(d->storage_extra_container), module->widget);
}
for(const GList *it = darktable.imageio->plugins_format; it; it = g_list_next(it))
{
dt_imageio_module_format_t *module = it->data;
if(module->widget)
gtk_container_remove(GTK_CONTAINER(d->format_extra_container), module->widget);
}
g_free(d->style_name);
g_free(d->metadata_export);
free(self->data);
self->data = NULL;
}
// the params blob packs strings between its int32_t fields, so these sit at
// any byte offset and must not be dereferenced in place
static int32_t _read_int32(const char **buf)
{
int32_t v;
memcpy(&v, *buf, sizeof(v));
*buf += sizeof(v);
return v;
}
void init_presets(dt_lib_module_t *self)
{
// TODO: store presets in db: dt_lib_presets_add(const char *name,
// const char *plugin_name, const void *params, const int32_t
// params_size)
// I know that it is super ugly to have this inside a module, but
// then is export not your average module since it handles the
// params blobs of imageio libs. - get all existing presets for
// export from db, - extract the versions of the embedded
// format/storage blob - check if it's up to date - if older than
// the module -> call its legacy_params and update the preset - drop
// presets that cannot be updated
const int version = self->version();
// read before updating them: the loop rewrites the rows it reads
GList *presets = dt_presets_list_stored("export");
for(const GList *l = presets; l; l = g_list_next(l))
{
const dt_presets_params_t *preset = l->data;
const int rowid = preset->rowid;
const int op_version = preset->op_version;
const void *op_params = preset->op_params;
const size_t op_params_size = preset->op_params_size;
const char *name = preset->name;
if(op_version != version)
{
// shouldn't happen, we run legacy_params on the lib level
// before calling this
dt_print(DT_DEBUG_ALWAYS,
"[export_init_presets] found export preset '%s' with version %d,"
" version %d was expected. dropping preset",
name, op_version, version);
dt_presets_delete_at_rowid(rowid);
}
else
{
// extract the interesting parts from the blob
const char *buf = (const char *)op_params;
// skip 10*int32_t: max_width, max_height, upscale, high_quality,
// export_masks, iccintent, icctype, dimensions_type, print_dpi,
// pq_peak_nits
buf += 10 * sizeof(int32_t);
// skip scale string
buf += strlen(buf) + 1;
// skip metadata presets string
buf += strlen(buf) + 1;
// next skip iccfilename
buf += strlen(buf) + 1;
// parse both names to '\0'
const char *fname = buf;
buf += strlen(fname) + 1;
const char *sname = buf;
buf += strlen(sname) + 1;
// get module by name and skip if not there.
dt_imageio_module_format_t *fmod = dt_imageio_get_format_by_name(fname);
dt_imageio_module_storage_t *smod = dt_imageio_get_storage_by_name(sname);
if(!fmod || !smod) continue;
// next we have fversion, sversion, fsize, ssize, fdata, sdata
// which is the stuff that might change
size_t copy_over_part = (void *)buf - (void *)op_params;
const int fversion = _read_int32(&buf);
const int sversion = _read_int32(&buf);
const int fsize = _read_int32(&buf);
const int ssize = _read_int32(&buf);
const void *fdata = buf;
buf += fsize;
const void *sdata = buf;
void *new_fdata = NULL;
void *new_sdata = NULL;
size_t new_fsize = fsize;
size_t new_ssize = ssize;
const int32_t new_fversion = fmod->version();
const int32_t new_sversion = smod->version();
int32_t cversion = fversion;
int32_t new_version = new_fversion;
size_t new_size = 0;
void *data = (void *)malloc(fsize);
memcpy(data, fdata, fsize);
while(cversion < new_fversion)
{
if(fmod->legacy_params
&& (new_fdata = fmod->legacy_params(fmod, data, new_fsize,
cversion, &new_version, &new_size))
!= NULL)
{
free(data);
data = new_fdata;
new_fsize = new_size;
cversion = new_version;
}
else
{
goto delete_preset;
}
}
cversion = sversion;
new_version = new_sversion;
new_size = 0;
data = (void *)malloc(ssize);
memcpy(data, sdata, ssize);
while(cversion < new_sversion)
{
if(smod->legacy_params
&& (new_sdata = smod->legacy_params(smod, data, new_ssize,
cversion, &new_version, &new_size))
!= NULL)
{
free(data);
data = new_sdata;
new_ssize = new_size;
cversion = new_version;
}
else
{
goto delete_preset;
}
}
if(new_fdata || new_sdata)
{
// we got an updated blob -> reassemble the parts and update the preset
const size_t new_params_size =
op_params_size - (fsize + ssize) + (new_fsize + new_ssize);
void *new_params = malloc(new_params_size);
memcpy(new_params, op_params, copy_over_part);
// next we have fversion, sversion, fsize, ssize, fdata, sdata
// which is the stuff that might change
size_t pos = copy_over_part;
memcpy((uint8_t *)new_params + pos, &new_fversion, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy((uint8_t *)new_params + pos, &new_sversion, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy((uint8_t *)new_params + pos, &new_fsize, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy((uint8_t *)new_params + pos, &new_ssize, sizeof(int32_t));
pos += sizeof(int32_t);
if(new_fdata)
memcpy((uint8_t *)new_params + pos, new_fdata, new_fsize);
else
memcpy((uint8_t *)new_params + pos, fdata, fsize);
pos += new_fsize;
if(new_sdata)
memcpy((uint8_t *)new_params + pos, new_sdata, new_ssize);
else
memcpy((uint8_t *)new_params + pos, sdata, ssize);
// write the updated preset back to db
dt_print(DT_DEBUG_ALWAYS,
"[export_init_presets] updating export preset '%s'"
" from versions %d/%d to versions %d/%d",
name, fversion, sversion, new_fversion, new_sversion);
dt_presets_set_op_params_at_rowid(rowid, new_params, new_params_size);
free(new_fdata);
free(new_sdata);
free(new_params);
}
continue;
delete_preset:
free(new_fdata);
free(new_sdata);
dt_print(DT_DEBUG_ALWAYS,
"[export_init_presets] export preset '%s' can't be updated"
" from versions %d/%d to versions %d/%d. dropping preset",
name, fversion, sversion, new_fversion, new_sversion);
dt_presets_delete_at_rowid(rowid);
}
}
g_list_free_full(presets, dt_presets_params_free);
}
void *legacy_params(dt_lib_module_t *self,
const void *const old_params,
const size_t old_params_size,
const int old_version,
int *new_version,
size_t *new_size)
{
if(old_version == 1)
{
// add version of format & storage to params
const size_t new_params_size = old_params_size + 2 * sizeof(int32_t);
void *new_params = malloc(new_params_size);
const char *buf = (const char *)old_params;
// skip 3*int32_t: max_width, max_height and iccintent
buf += 3 * sizeof(int32_t);
// next skip iccprofile
buf += strlen(buf) + 1;
// parse both names to '\0'
const char *fname = buf;
buf += strlen(fname) + 1;
const char *sname = buf;
buf += strlen(sname) + 1;
// get module by name and fail if not there.
dt_imageio_module_format_t *fmod = dt_imageio_get_format_by_name(fname);
dt_imageio_module_storage_t *smod = dt_imageio_get_storage_by_name(sname);
if(!fmod || !smod)
{
free(new_params);
return NULL;
}
// now we are just behind the module/storage names and before
// their param sizes. this is the place where we want their
// versions copy everything until here to the new params
size_t first_half = (void *)buf - (void *)old_params;
memcpy(new_params, old_params, first_half);
// add the versions. at the time this code was added all modules
// were at version 1, except of picasa which was at 2. every
// newer version of the imageio modules should result in a preset
// that is not going through this code.
int32_t fversion = 1;
int32_t sversion = (strcmp(sname, "picasa") == 0 ? 2 : 1);
memcpy((uint8_t *)new_params + first_half, &fversion, sizeof(int32_t));
memcpy((uint8_t *)new_params + first_half + sizeof(int32_t),
&sversion, sizeof(int32_t));
// copy the rest of the old params over
memcpy((uint8_t *)new_params + first_half + sizeof(int32_t) * 2,
buf, old_params_size - first_half);
*new_size = new_params_size;
*new_version = 2;
return new_params;
}
else if(old_version == 2)
{
// add upscale to params
const size_t new_params_size = old_params_size + sizeof(int32_t);
void *new_params = calloc(1, new_params_size);
memcpy(new_params, old_params, sizeof(int32_t) * 2);
memcpy((uint8_t *)new_params + sizeof(int32_t) * 3,
(uint8_t *)old_params + sizeof(int32_t) * 2,
old_params_size - sizeof(int32_t) * 2);
*new_size = new_params_size;
*new_version = 3;
return new_params;
}
else if(old_version == 3)
{
// replace iccprofile by type + filename
// format of v3:
// - 4 x int32_t (max_width, max_height, upscale, iccintent)
// - char* (iccprofile)
// - rest
// format of v4:
// - 5 x int32_t (max_width, max_height, upscale, iccintent, icctype)
// - char* (iccfilename)
// - old rest
const char *buf = (const char *)old_params;
// first get the old iccprofile to find out how big our new blob has to be
const char *iccprofile = buf + 4 * sizeof(int32_t);
size_t new_params_size = old_params_size - strlen(iccprofile) + sizeof(int32_t);
int icctype;
const char *iccfilename = "";
if(!strcmp(iccprofile, "image"))
icctype = DT_COLORSPACE_NONE;
else if(!strcmp(iccprofile, "sRGB"))
icctype = DT_COLORSPACE_SRGB;
else if(!strcmp(iccprofile, "linear_rec709_rgb") || !strcmp(iccprofile, "linear_rgb"))
icctype = DT_COLORSPACE_LIN_REC709;
else if(!strcmp(iccprofile, "linear_rec2020_rgb"))
icctype = DT_COLORSPACE_LIN_REC2020;
else if(!strcmp(iccprofile, "adobergb"))
icctype = DT_COLORSPACE_ADOBERGB;
else
{
icctype = DT_COLORSPACE_FILE;
iccfilename = iccprofile;
new_params_size += strlen(iccfilename);
}
void *new_params = calloc(1, new_params_size);
size_t pos = 0;
memcpy(new_params, old_params, sizeof(int32_t) * 4);
pos += 4 * sizeof(int32_t);
memcpy((uint8_t *)new_params + pos, &icctype, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy((uint8_t *)new_params + pos, iccfilename, strlen(iccfilename) + 1);
pos += strlen(iccfilename) + 1;
size_t old_pos = 4 * sizeof(int32_t) + strlen(iccprofile) + 1;
memcpy((uint8_t *)new_params + pos,
(uint8_t *)old_params + old_pos,
old_params_size - old_pos);
*new_size = new_params_size;
*new_version = 4;
return new_params;
}
else if(old_version == 4)
{
// add high_quality to params
// format of v4:
// - 5 x int32_t (max_width, max_height, upscale, iccintent, icctype)
// - char* (iccfilename)
// - old rest
// format of v5:
// - 6 x int32_t (max_width, max_height, upscale, high_quality, iccintent, icctype)
// - char* (iccfilename)
// - old rest
const size_t new_params_size = old_params_size + sizeof(int32_t);
void *new_params = calloc(1, new_params_size);
size_t pos = 0;
memcpy(new_params, old_params, sizeof(int32_t) * 3);
pos += 4 * sizeof(int32_t);
memcpy((uint8_t *)new_params + pos,
(uint8_t *)old_params + pos - sizeof(int32_t),
old_params_size - sizeof(int32_t) * 3);
*new_size = new_params_size;
*new_version = 5;
return new_params;
}
else if(old_version == 5)
{
// add metadata preset string
// format of v5:
// - 6 x int32_t (max_width, max_height, upscale, high_quality, iccintent, icctype)
// - char* (iccfilename)
// - old rest
// format of v6:
// - 6 x int32_t (max_width, max_height, upscale, high_quality, iccintent, icctype)
// - char* (metadata_export)
// - char* (iccfilename)
// - old rest
const gboolean omit = dt_conf_get_bool("omit_tag_hierarchy");
gchar *flags = g_strdup_printf
("%x", dt_lib_export_metadata_default_flags() | (omit ? DT_META_OMIT_HIERARCHY : 0));
const int flags_size = strlen(flags) + 1;
const size_t new_params_size = old_params_size + flags_size;
void *new_params = calloc(1, new_params_size);
size_t pos = 0;
memcpy(new_params, old_params, sizeof(int32_t) * 6);
pos += 6 * sizeof(int32_t);
memcpy((uint8_t *)new_params + pos, flags, flags_size);
pos += flags_size;
memcpy((uint8_t *)new_params + pos,
(uint8_t *)old_params + pos - flags_size,
old_params_size - sizeof(int32_t) * 6);
g_free(flags);
*new_size = new_params_size;
*new_version = 6;
return new_params;
}
else if(old_version == 6)
{
// add export_masks
// format of v6:
// - 6 x int32_t (max_width, max_height, upscale, high_quality, iccintent, icctype)
// - old rest
// format of v7:
// - 7 x int32_t (max_width, max_height, upscale, high_quality,
// export_masks, iccintent, icctype)
// - old rest
const size_t new_params_size = old_params_size + sizeof(int32_t);
void *new_params = calloc(1, new_params_size);
size_t pos = 0;
memcpy(new_params, old_params, sizeof(int32_t) * 4);
pos += 5 * sizeof(int32_t);
memcpy((uint8_t *)new_params + pos,
(uint8_t *)old_params + pos - sizeof(int32_t),
old_params_size - sizeof(int32_t) * 4);
*new_size = new_params_size;
*new_version = 7;
return new_params;
}
else if(old_version == 7)
{
// add dimension_type, print_dpi and scale
// format of v7:
// - 7 x int32_t (max_width, max_height, upscale, high_quality,
// export_masks, iccintent, icctype)
// - old rest
// format of v8:
// - 9 x int32_t (max_width, max_height, upscale, high_quality,
// export_masks, iccintent, icctype, dimensions_type, print_dpi)
// - char* (scale)
// - old rest
const char *scale = "1.0";
const int scale_size = strlen(scale) + 1;
const int print_dpi = dt_confgen_get_int("plugins/lighttable/export/print_dpi", DT_DEFAULT);
const size_t new_params_size = old_params_size
+ sizeof(int32_t) * 2
+ scale_size;
void *new_params = calloc(1, new_params_size);
size_t pos = 0;
memcpy(new_params, old_params, sizeof(int32_t) * 7);
pos += 7 * sizeof(int32_t);
pos += sizeof(int32_t); // dimensions_type
memcpy((uint8_t *)new_params + pos, &print_dpi, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy((uint8_t *)new_params + pos, scale, scale_size);
pos += scale_size;
memcpy((uint8_t *)new_params + pos,
(uint8_t *)old_params + pos - sizeof(int32_t) * 2 - scale_size,
old_params_size - sizeof(int32_t) * 7);
*new_size = new_params_size;
*new_version = 8;
return new_params;
}
else if(old_version == 8)
{
// add pq_peak_nits
// format of v8:
// - 9 x int32_t (max_width, max_height, upscale, high_quality,
// export_masks, iccintent, icctype, dimensions_type, print_dpi)
// - old rest
// format of v9:
// - 10 x int32_t (max_width, max_height, upscale, high_quality,
// export_masks, iccintent, icctype, dimensions_type, print_dpi,
// pq_peak_nits)
// - old rest
if(old_params_size < sizeof(int32_t) * 9) return NULL;
// 0: applying the preset leaves the PQ peak as it is, as before
const int32_t pq_peak_nits = 0;
const size_t new_params_size = old_params_size + sizeof(int32_t);
void *new_params = calloc(1, new_params_size);
memcpy(new_params, old_params, sizeof(int32_t) * 9);
memcpy((uint8_t *)new_params + sizeof(int32_t) * 9, &pq_peak_nits, sizeof(int32_t));
memcpy((uint8_t *)new_params + sizeof(int32_t) * 10,
(uint8_t *)old_params + sizeof(int32_t) * 9,
old_params_size - sizeof(int32_t) * 9);
*new_size = new_params_size;
*new_version = 9;
return new_params;
}
return NULL;
}
void *get_params(dt_lib_module_t *self, int *size)
{
dt_lib_export_t *d = self->data;
// concat storage and format, size is max + header
dt_imageio_module_format_t *mformat = dt_imageio_get_format();
dt_imageio_module_storage_t *mstorage = dt_imageio_get_storage();
if(!mformat || !mstorage) return NULL;
// size will be only as large as needed to remove random pointers
// from params (stored at the end).
size_t fsize = mformat->params_size(mformat);
dt_imageio_module_data_t *fdata = mformat->get_params(mformat);
size_t ssize = mstorage->params_size(mstorage);
void *sdata = mstorage->get_params(mstorage);
const int32_t fversion = mformat->version();
const int32_t sversion = mstorage->version();
// we allow null pointers (plugin not ready for export in current
// state), and just don't copy back the settings later:
if(!sdata) ssize = 0;
if(!fdata) fsize = 0;
if(fdata)
{
// clean up format global params (need to set all bytes to
// reliably detect which preset is active). we happen to want to
// set it all to 0
memset(fdata, 0, sizeof(dt_imageio_module_data_t));
}
// FIXME: also the web preset has to be applied twice to be known as
// preset! (other dimension magic going on here?) TODO: get this
// stuff from gui and not from conf, so it will be sanity-checked
// (you can never delete an insane preset)? also store icc
// profile/intent here.
const int32_t iccintent = dt_conf_get_int(CONFIG_PREFIX "iccintent");
const int32_t icctype = dt_conf_get_int(CONFIG_PREFIX "icctype");
const int32_t max_width = dt_conf_get_int(CONFIG_PREFIX "width");
const int32_t max_height = dt_conf_get_int(CONFIG_PREFIX "height");
const int32_t upscale = dt_conf_get_bool(CONFIG_PREFIX "upscale") ? 1 : 0;
const int32_t dimensions_type = dt_conf_get_int(CONFIG_PREFIX "dimensions_type");
const int32_t print_dpi = dt_conf_get_int(CONFIG_PREFIX "print_dpi");
// 0 when the export cannot be PQ, so that applying the preset does not
// change a PQ peak that is hidden and unused for its profile
const int32_t pq_peak_nits =
_export_may_be_pq(icctype) ? dt_conf_get_int(CONFIG_PREFIX "pq_peak_nits") : 0;
gchar *scale = dt_conf_get_string(CONFIG_PREFIX "resizing_factor");
const int32_t high_quality =
dt_conf_get_bool(CONFIG_PREFIX "high_quality_processing") ? 1 : 0;
const int32_t export_masks = dt_conf_get_bool(CONFIG_PREFIX "export_masks") ? 1 : 0;
gchar *iccfilename = dt_conf_get_string(CONFIG_PREFIX "iccprofile");
gchar *style = dt_conf_get_string(CONFIG_PREFIX "style");
const gboolean style_append = dt_conf_get_bool(CONFIG_PREFIX "style_append");
const char *metadata_export = d->metadata_export ? d->metadata_export : "";
if(fdata)
{
g_strlcpy(fdata->style, style, sizeof(fdata->style));
fdata->style_append = style_append;
}
if(icctype != DT_COLORSPACE_FILE)
{
g_free(iccfilename);
iccfilename = NULL;
}
if(!iccfilename) iccfilename = g_strdup("");
const char *fname = mformat->plugin_name;
const char *sname = mstorage->plugin_name;
const int32_t fname_len = strlen(fname);
const int32_t sname_len = strlen(sname);
*size = fname_len + 1
+ sname_len + 1
+ 4 * sizeof(int32_t)
+ fsize
+ ssize
+ 10 * sizeof(int32_t)
+ strlen(scale) + 1
+ strlen(iccfilename) + 1
+ strlen(metadata_export) + 1;
//??? WARNING: Any change here must be also done on get_params AND init_presets
// if some parameters are added before fname & sname
char *params = (char *)calloc(1, *size);
int pos = 0;
memcpy(params + pos, &max_width, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &max_height, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &upscale, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &high_quality, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &export_masks, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &iccintent, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &icctype, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &dimensions_type, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &print_dpi, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &pq_peak_nits, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, scale, strlen(scale) + 1);
pos += strlen(scale) + 1;
memcpy(params + pos, metadata_export, strlen(metadata_export) + 1);
pos += strlen(metadata_export) + 1;
memcpy(params + pos, iccfilename, strlen(iccfilename) + 1);
pos += strlen(iccfilename) + 1;
memcpy(params + pos, fname, fname_len + 1);
pos += fname_len + 1;
memcpy(params + pos, sname, sname_len + 1);
pos += sname_len + 1;
memcpy(params + pos, &fversion, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &sversion, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &fsize, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params + pos, &ssize, sizeof(int32_t));
pos += sizeof(int32_t);
if(fdata != NULL) // otherwise fsize == 0, but clang doesn't like it ...
{
memcpy(params + pos, fdata, fsize);
pos += fsize;
}
if(sdata != NULL) // see above
{
memcpy(params + pos, sdata, ssize);
pos += ssize;
}
g_assert(pos == *size);
g_free(scale);
g_free(iccfilename);
g_free(style);
if(fdata) mformat->free_params(mformat, fdata);
if(sdata) mstorage->free_params(mstorage, sdata);
return params;
}
int set_params(dt_lib_module_t *self,
const void *params,
const int size)
{
dt_lib_export_t *d = self->data;
// apply these stored presets again (parse blob)
const char *buf = (const char *)params;
//??? WARNING: Any change here must be also done on get_params AND init_presets
// if some parameters are added before fname & sname
const int max_width = _read_int32(&buf);
const int max_height = _read_int32(&buf);
const int upscale = _read_int32(&buf);
const int high_quality = _read_int32(&buf);
const int export_masks = _read_int32(&buf);
const int iccintent = _read_int32(&buf);
const int icctype = _read_int32(&buf);
const int dimensions_type = _read_int32(&buf);
const int print_dpi = _read_int32(&buf);
const int pq_peak_nits = _read_int32(&buf);
const char *scale = buf;
buf += strlen(scale) + 1;
const char *metadata_export = buf;
buf += strlen(metadata_export) + 1;
g_free(d->metadata_export);
d->metadata_export = g_strdup(metadata_export);
dt_lib_export_metadata_set_conf(d->metadata_export);
const char *iccfilename = buf;
buf += strlen(iccfilename) + 1;
// reverse these by setting the gui, not the conf vars!
dt_bauhaus_combobox_set(d->intent, iccintent + 1);
dt_bauhaus_combobox_set(d->profile, 0);
if(icctype != DT_COLORSPACE_NONE)
{
for(GList *iter = darktable.color_profiles->profiles;
iter;
iter = g_list_next(iter))
{
const dt_colorspaces_color_profile_t *pp = iter->data;
if(pp->out_pos > -1
&& icctype == pp->type
&& (icctype != DT_COLORSPACE_FILE || !strcmp(iccfilename, pp->filename)))
{
dt_bauhaus_combobox_set(d->profile, pp->out_pos + 1);
break;
}
}
}
// parse both names to '\0'
const char *fname = buf;
buf += strlen(fname) + 1;
const char *sname = buf;
buf += strlen(sname) + 1;
// get module by name and fail if not there.
dt_imageio_module_format_t *fmod = dt_imageio_get_format_by_name(fname);
dt_imageio_module_storage_t *smod = dt_imageio_get_storage_by_name(sname);
if(!fmod || !smod) return 1;
const int32_t fversion = _read_int32(&buf);
const int32_t sversion = _read_int32(&buf);
const int fsize = _read_int32(&buf);
const int ssize = _read_int32(&buf);
if(size
!= strlen(fname) + 1
+ strlen(sname) + 1
+ 4 * sizeof(int32_t)
+ fsize
+ ssize
+ 10 * sizeof(int32_t)
+ strlen(scale) + 1
+ strlen(metadata_export) + 1
+ strlen(iccfilename) + 1)
return 1;
if(fversion != fmod->version() || sversion != smod->version()) return 1;
// the format and storage modules read their structs in place, so hand
// them aligned copies rather than pointers into the blob
dt_imageio_module_data_t *fdata = NULL;
if(fsize)
{
fdata = malloc(fsize);
memcpy(fdata, buf, fsize);
if(fsize >= (int)sizeof(dt_imageio_module_data_t))
{
_update_style_label(d, fdata->style);
dt_bauhaus_combobox_set(d->style_mode, fdata->style_append ? 1 : 0);
}
}
buf += fsize;
void *sdata = NULL;
if(ssize)
{
sdata = malloc(ssize);
memcpy(sdata, buf, ssize);
}
// switch modules
set_storage_by_name(d, sname);
set_format_by_name(d, fname);
// set dimensions after switching, to have new range ready.
_set_dimensions(d, max_width, max_height, print_dpi, scale);
dt_bauhaus_combobox_set(d->upscale, upscale ? 1 : 0);
dt_bauhaus_combobox_set(d->high_quality, high_quality ? 1 : 0);
dt_bauhaus_combobox_set(d->export_masks, export_masks ? 1 : 0);
dt_bauhaus_combobox_set(d->dimensions_type, dimensions_type);
if(pq_peak_nits > 0) dt_bauhaus_slider_set(d->pq_peak, pq_peak_nits);
_size_update_display(d);
// propagate to modules
int res = 0;
if(ssize)
res += smod->set_params(smod, sdata, ssize);
if(fsize)
res += fmod->set_params(fmod, fdata, fsize);
free(fdata);
free(sdata);
return res;
}
// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on