/* This file is part of darktable, Copyright (C) 2011-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 . */ #include "bauhaus/bauhaus.h" #include "common/debug.h" #include "common/imagebuf.h" #include "common/opencl.h" #include "control/conf.h" #include "control/control.h" #include "develop/develop.h" #include "develop/imageop.h" #include "develop/borders_helper.h" #include "develop/imageop_gui.h" #include "dtgtk/button.h" #include "dtgtk/resetlabel.h" #include "dtgtk/togglebutton.h" #include "gui/accelerators.h" #include "gui/color_picker_proxy.h" #include "gui/draw.h" #include "gui/gtk.h" #include "gui/presets.h" #include "iop/iop_api.h" #include #include #include #include #include #include DT_MODULE_INTROSPECTION(4, dt_iop_borders_params_t) // Module constants #define DT_IOP_BORDERS_ASPECT_IMAGE_VALUE 0.0f #define DT_IOP_BORDERS_ASPECT_CONSTANT_VALUE -1.0f typedef enum dt_iop_orientation_t { DT_IOP_BORDERS_ASPECT_ORIENTATION_AUTO = 0, // $DESCRIPTION: "auto" DT_IOP_BORDERS_ASPECT_ORIENTATION_PORTRAIT = 1, // $DESCRIPTION: "portrait" DT_IOP_BORDERS_ASPECT_ORIENTATION_LANDSCAPE = 2, // $DESCRIPTION: "landscape" } dt_iop_orientation_t; typedef enum dt_iop_basis_t { DT_IOP_BORDERS_BASIS_AUTO = 0, // $DESCRIPTION: "auto" DT_IOP_BORDERS_BASIS_WIDTH = 1, // $DESCRIPTION: "width" DT_IOP_BORDERS_BASIS_HEIGHT = 2, // $DESCRIPTION: "height" DT_IOP_BORDERS_BASIS_SHORTER = 3, // $DESCRIPTION: "shorter" DT_IOP_BORDERS_BASIS_LONGER = 4, // $DESCRIPTION: "longer" } dt_iop_basis_t; static const float _aspect_ratios[] = { DT_IOP_BORDERS_ASPECT_IMAGE_VALUE, 3.0f, 95.0f / 33.0f, 2.39f, 2.0f, 16.0f / 9.0f, 5.0f / 3.0f, 14.0f / 8.5f, PHI, 16.0f / 10.0f, 3.0f / 2.0f, 297.0f / 210.0f, M_SQRT2, 7.0f / 5.0f, 4.0f / 3.0f, 11.0f / 8.5f, 14.0f / 11.0f, 5.0f / 4.0f, 1.0f, DT_IOP_BORDERS_ASPECT_CONSTANT_VALUE }; static const float _pos_h_ratios[] = { 0.5f, 1.0f / 3.0f, 3.0f / 8.0f, 5.0f / 8.0f, 2.0f / 3.0f }; static const float _pos_v_ratios[] = { 0.5f, 1.0f / 3.0f, 3.0f / 8.0f, 5.0f / 8.0f, 2.0f / 3.0f }; #define DT_IOP_BORDERS_ASPECT_COUNT G_N_ELEMENTS(_aspect_ratios) #define DT_IOP_BORDERS_ASPECT_IMAGE_IDX 0 #define DT_IOP_BORDERS_ASPECT_CONSTANT_IDX (DT_IOP_BORDERS_ASPECT_COUNT - 1) #define DT_IOP_BORDERS_POSITION_H_COUNT G_N_ELEMENTS(_pos_h_ratios) #define DT_IOP_BORDERS_POSITION_V_COUNT G_N_ELEMENTS(_pos_v_ratios) typedef struct dt_iop_borders_params_t { float color[3]; // border color $DEFAULT: 1.0 $DESCRIPTION: "border color" float aspect; /* aspect ratio of the outer frame w/h $MIN: 1.0 $MAX: 3.0 $DEFAULT: DT_IOP_BORDERS_ASPECT_CONSTANT_VALUE $DESCRIPTION: "aspect ratio" */ char aspect_text[20]; /* UNUSED aspect ratio of the outer frame w/h (user string version) DEFAULT: "constant border" */ dt_iop_orientation_t aspect_orient; /* aspect ratio orientation $DEFAULT: 0 $DESCRIPTION: "orientation" */ float size; /* border width relative to the length of the chosen basis $MIN: 0.0 $MAX: 0.5 $DEFAULT: 0.1 $DESCRIPTION: "border size" */ float pos_h; /* picture horizontal position ratio into the final image $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "horizontal offset" */ char pos_h_text[20]; /* UNUSED picture horizontal position ratio into the final image (user string version) DEFAULT: "1/2" */ float pos_v; /* picture vertical position ratio into the final image $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "vertical offset"*/ char pos_v_text[20]; /* UNUSED picture vertical position ratio into the final image (user string version) DEFAULT: "1/2" */ float frame_size; /* frame line width relative to border width $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "frame line size" */ float frame_offset; /* frame offset from picture size relative to [border width - frame width] $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "frame line offset" */ float frame_color[3]; // frame line color $DEFAULT: 0.0 $DESCRIPTION: "frame line color" gboolean max_border_size; /* the way border size is computed $DEFAULT: TRUE */ dt_iop_basis_t basis; /* side of the photo to use as basis for the size calculation $DEFAULT: 0 $DESCRIPTION: "basis" */ } dt_iop_borders_params_t; typedef struct dt_iop_borders_gui_data_t { GtkWidget *basis; GtkWidget *size; GtkWidget *aspect; GtkWidget *aspect_slider; GtkWidget *aspect_orient; GtkWidget *pos_h; GtkWidget *pos_h_slider; GtkWidget *pos_v; GtkWidget *pos_v_slider; GtkWidget *colorpick; GtkWidget *border_picker; // the 1st button GtkWidget *frame_size; GtkWidget *frame_offset; GtkWidget *frame_colorpick; GtkWidget *frame_picker; // the 2nd button GtkWidget *frame_color_box; } dt_iop_borders_gui_data_t; // ******* Check and update legacy params...(esp. ver 4) int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void **new_params, int32_t *new_params_size, int *new_version) { typedef struct dt_iop_borders_params_v3_t { float color[3]; // border color $DEFAULT: 1.0 float aspect; /* aspect ratio of the outer frame w/h $MIN: 1.0 $MAX: 3.0 $DEFAULT: DT_IOP_BORDERS_ASPECT_CONSTANT_VALUE $DESCRIPTION: "aspect ratio" */ char aspect_text[20]; /* UNUSED aspect ratio of the outer frame w/h (user string version) DEFAULT: "constant border" */ dt_iop_orientation_t aspect_orient; /* aspect ratio orientation $DEFAULT: 0 $DESCRIPTION: "orientation" */ float size; /* border width relative to overall frame width $MIN: 0.0 $MAX: 0.5 $DEFAULT: 0.1 $DESCRIPTION: "border size" */ float pos_h; /* picture horizontal position ratio into the final image $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "horizontal offset" */ char pos_h_text[20]; /* UNUSED picture horizontal position ratio into the final image (user string version) DEFAULT: "1/2" */ float pos_v; /* picture vertical position ratio into the final image $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "vertical offset"*/ char pos_v_text[20]; /* UNUSED picture vertical position ratio into the final image (user string version) DEFAULT: "1/2" */ float frame_size; /* frame line width relative to border width $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "frame line size" */ float frame_offset; /* frame offset from picture size relative to [border width - frame width] $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "frame line offset" */ float frame_color[3]; // frame line color $DEFAULT: 0.0 gboolean max_border_size; /* the way border size is computed $DEFAULT: TRUE */ } dt_iop_borders_params_v3_t; dt_iop_borders_params_v3_t default_v3 = { { 1.0f, 1.0f, 1.0f }, DT_IOP_BORDERS_ASPECT_CONSTANT_VALUE, " ", 0, 0.1f, 0.5f, " ", 0.5f, " ", 0.0f, 0.5f, { 0.0f, 0.0f, 0.0f }, TRUE }; if(old_version == 1) { typedef struct dt_iop_borders_params_v1_t { float color[3]; // border color float aspect; // aspect ratio of the outer frame w/h float size; // border width relative to overall frame width } dt_iop_borders_params_v1_t; const dt_iop_borders_params_v1_t *o = (dt_iop_borders_params_v1_t *)old_params; dt_iop_borders_params_v3_t *n = calloc(1, sizeof(dt_iop_borders_params_v3_t)); *n = default_v3; // start with a fresh copy of default parameters memcpy(n->color, o->color, sizeof(o->color)); n->aspect = (o->aspect < 1) ? 1 / o->aspect : o->aspect; // no auto orientation in legacy param due to already convert aspect ratio n->aspect_orient = o->aspect > 1 ? DT_IOP_BORDERS_ASPECT_ORIENTATION_LANDSCAPE : DT_IOP_BORDERS_ASPECT_ORIENTATION_PORTRAIT; n->size = fabsf(o->size); // no negative size any more (was for "constant border" detect) n->max_border_size = FALSE; *new_params = n; *new_params_size = sizeof(dt_iop_borders_params_v3_t); *new_version = 3; return 0; } if(old_version == 2) { typedef struct dt_iop_borders_params_v2_t { float color[3]; // border color float aspect; // aspect ratio of the outer frame w/h char aspect_text[20]; // aspect ratio of the outer frame w/h (user string version) int aspect_orient; // aspect ratio orientation float size; // border width relative to overall frame width float pos_h; // picture horizontal position ratio into the final image char pos_h_text[20]; // picture horizontal position ratio into the final image (user string version) float pos_v; // picture vertical position ratio into the final image char pos_v_text[20]; // picture vertical position ratio into the final image (user string version) float frame_size; // frame line width relative to border width float frame_offset; // frame offset from picture size relative to [border width - frame width] float frame_color[3]; // frame line color } dt_iop_borders_params_v2_t; const dt_iop_borders_params_v2_t *o = (dt_iop_borders_params_v2_t *)old_params; dt_iop_borders_params_v3_t *n = calloc(1, sizeof(dt_iop_borders_params_v3_t)); memcpy(n, o, sizeof(struct dt_iop_borders_params_v2_t)); n->max_border_size = FALSE; *new_params = n; *new_params_size = sizeof(dt_iop_borders_params_v3_t); *new_version = 3; return 0; } if(old_version == 3) { typedef struct dt_iop_borders_params_v4_t { float color[3]; // border color $DEFAULT: 1.0 float aspect; /* aspect ratio of the outer frame w/h $MIN: 1.0 $MAX: 3.0 $DEFAULT: DT_IOP_BORDERS_ASPECT_CONSTANT_VALUE $DESCRIPTION: "aspect ratio" */ char aspect_text[20]; /* UNUSED aspect ratio of the outer frame w/h (user string version) DEFAULT: "constant border" */ dt_iop_orientation_t aspect_orient; /* aspect ratio orientation $DEFAULT: 0 $DESCRIPTION: "orientation" */ float size; /* border width relative to overall frame width $MIN: 0.0 $MAX: 0.5 $DEFAULT: 0.1 $DESCRIPTION: "border size" */ float pos_h; /* picture horizontal position ratio into the final image $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "horizontal offset" */ char pos_h_text[20]; /* UNUSED picture horizontal position ratio into the final image (user string version) DEFAULT: "1/2" */ float pos_v; /* picture vertical position ratio into the final image $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "vertical offset"*/ char pos_v_text[20]; /* UNUSED picture vertical position ratio into the final image (user string version) DEFAULT: "1/2" */ float frame_size; /* frame line width relative to border width $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "frame line size" */ float frame_offset; /* frame offset from picture size relative to [border width - frame width] $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.5 $DESCRIPTION: "frame line offset" */ float frame_color[3]; // frame line color $DEFAULT: 0.0 gboolean max_border_size; /* the way border size is computed $DEFAULT: TRUE */ dt_iop_basis_t basis; /* side of the photo to use as basis for the size calculation $DEFAULT: 0 $DESCRIPTION: "basis" */ } dt_iop_borders_params_v4_t; const dt_iop_borders_params_v3_t *o = (dt_iop_borders_params_v3_t *)old_params; dt_iop_borders_params_v4_t *n = calloc(1, sizeof(dt_iop_borders_params_v4_t)); memcpy(n, o, sizeof(struct dt_iop_borders_params_v3_t)); if(n->aspect == DT_IOP_BORDERS_ASPECT_CONSTANT_VALUE && !n->max_border_size) { // the legacy behaviour is, when a constant border is used and the // max_border_size flag is set, the width is always used as basis. n->basis = DT_IOP_BORDERS_BASIS_WIDTH; } else { n->basis = DT_IOP_BORDERS_BASIS_AUTO; } *new_params = n; *new_params_size = sizeof(dt_iop_borders_params_v4_t); *new_version = 4; return 0; } return 1; } typedef struct dt_iop_borders_global_data_t { int kernel_borders_fill; } dt_iop_borders_global_data_t; typedef struct dt_iop_borders_params_t dt_iop_borders_data_t; const char *name() { return _("framing"); } const char *aliases() { return _("borders|enlarge canvas|expand canvas"); } const char **description(dt_iop_module_t *self) { return dt_iop_set_description(self, _("add solid borders or margins around the image"), _("creative"), _("linear or non-linear, RGB, display-referred"), _("geometric, RGB"), _("linear or non-linear, RGB, display-referred")); } int default_group() { return IOP_GROUP_EFFECT | IOP_GROUP_EFFECTS; } int operation_tags() { return IOP_TAG_DISTORT | IOP_TAG_DECORATION; } int flags() { return IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_TILING_FULL_ROI | IOP_FLAGS_GUIDES_WIDGET | IOP_FLAGS_EXPAND_ROI_IN; } dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { return IOP_CS_RGB; } gboolean distort_transform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count) { dt_iop_borders_data_t *d = piece->data; const int border_tot_width = (piece->buf_out.width - piece->buf_in.width); const int border_tot_height = (piece->buf_out.height - piece->buf_in.height); const int border_size_t = border_tot_height * d->pos_v; const int border_size_l = border_tot_width * d->pos_h; // nothing to be done if parameters are set to neutral values (no top/left border) if(border_size_l == 0 && border_size_t == 0) return TRUE; float *const pts = DT_IS_ALIGNED(points); DT_OMP_FOR(if(points_count > 100)) for(size_t i = 0; i < points_count * 2; i += 2) { pts[i] += border_size_l; pts[i + 1] += border_size_t; } return TRUE; } gboolean distort_backtransform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count) { dt_iop_borders_data_t *d = piece->data; const int border_tot_width = (piece->buf_out.width - piece->buf_in.width); const int border_tot_height = (piece->buf_out.height - piece->buf_in.height); const int border_size_t = border_tot_height * d->pos_v; const int border_size_l = border_tot_width * d->pos_h; // nothing to be done if parameters are set to neutral values (no top/left border) if(border_size_l == 0 && border_size_t == 0) return TRUE; float *const pts = DT_IS_ALIGNED(points); DT_OMP_FOR(if(points_count > 100)) for(size_t i = 0; i < points_count * 2; i += 2) { pts[i] -= border_size_l; pts[i + 1] -= border_size_t; } return TRUE; } void distort_mask(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const float *const in, float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) { dt_iop_borders_data_t *d = piece->data; dt_iop_border_positions_t binfo; dt_iop_setup_binfo(piece, roi_in, roi_out, d->pos_v, d->pos_h, d->color, d->frame_color, d->frame_size, d->frame_offset, &binfo); const int border_in_x = binfo.border_in_x; const int border_in_y = binfo.border_in_y; // fill the image with 0 so that the added border isn't part of the mask dt_iop_image_fill(out, 0.0f, roi_out->width, roi_out->height, 1); // blit image inside border and fill the output with previous processed out DT_OMP_FOR() for(int j = 0; j < roi_in->height; j++) { float *outb = out + (size_t)(j + border_in_y) * roi_out->width + border_in_x; const float *inb = in + (size_t)j * roi_in->width; memcpy(outb, inb, sizeof(float) * roi_in->width); } } void modify_roi_out(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, const dt_iop_roi_t *roi_in) { *roi_out = *roi_in; dt_iop_borders_data_t *d = piece->data; const float size = fabsf(d->size); const gboolean is_constant_border = d->aspect == DT_IOP_BORDERS_ASPECT_CONSTANT_VALUE; dt_iop_basis_t basis = d->basis; if(basis == DT_IOP_BORDERS_BASIS_AUTO) { // automatic/legacy/default behaviour: // for a constant border be sure to base the computation on the // larger border, failing that the border will have a different // size depending on the orientation. // for all other borders use the width. basis = is_constant_border ? DT_IOP_BORDERS_BASIS_LONGER : DT_IOP_BORDERS_BASIS_WIDTH; } if(basis == DT_IOP_BORDERS_BASIS_LONGER) { basis = roi_in->width > roi_in->height ? DT_IOP_BORDERS_BASIS_WIDTH : DT_IOP_BORDERS_BASIS_HEIGHT; } else if(basis == DT_IOP_BORDERS_BASIS_SHORTER) { basis = roi_in->width < roi_in->height ? DT_IOP_BORDERS_BASIS_WIDTH : DT_IOP_BORDERS_BASIS_HEIGHT; } assert(basis == DT_IOP_BORDERS_BASIS_WIDTH || basis == DT_IOP_BORDERS_BASIS_HEIGHT); const int *basis_in = NULL, *other_in = NULL; int *basis_out = NULL, *other_out = NULL; #define DT_IOP_BORDERS_ASSIGN(b, b_uc, o) \ basis_in = &roi_in->b, basis_out = &roi_out->b, \ other_in = &roi_in->o, other_out = &roi_out->o, \ basis = DT_IOP_BORDERS_BASIS_ ## b_uc #define DT_IOP_BORDERS_ASSIGN_width DT_IOP_BORDERS_ASSIGN(width, WIDTH, height) #define DT_IOP_BORDERS_ASSIGN_height DT_IOP_BORDERS_ASSIGN(height, HEIGHT, width) #define DT_IOP_BORDERS_ASSIGN_BASIS(basis) DT_IOP_BORDERS_ASSIGN_ ## basis if(basis == DT_IOP_BORDERS_BASIS_WIDTH) DT_IOP_BORDERS_ASSIGN_BASIS(width); else if(basis == DT_IOP_BORDERS_BASIS_HEIGHT) DT_IOP_BORDERS_ASSIGN_BASIS(height); if(is_constant_border) { *basis_out = roundf((float)*basis_in / (1.0f - size)); *other_out = *other_in + *basis_out - *basis_in; } else { const float image_aspect = (float)roi_in->width / (float)(roi_in->height); float aspect = (d->aspect == DT_IOP_BORDERS_ASPECT_IMAGE_VALUE) ? image_aspect : d->aspect; if(d->aspect_orient == DT_IOP_BORDERS_ASPECT_ORIENTATION_AUTO) aspect = ((image_aspect < 1.0f && aspect > 1.0f) || (image_aspect > 1.0f && aspect < 1.0f)) ? 1.0f / aspect : aspect; else if(d->aspect_orient == DT_IOP_BORDERS_ASPECT_ORIENTATION_LANDSCAPE) aspect = (aspect < 1.0f) ? 1.0f / aspect : aspect; else if(d->aspect_orient == DT_IOP_BORDERS_ASPECT_ORIENTATION_PORTRAIT) aspect = (aspect > 1.0f) ? 1.0f / aspect : aspect; // first determine how large the border should be, float border_width = (float)*basis_in * (1.0f / (1.0f - size) - 1.0f); // then make sure we add that amount to the shorter side, if(basis == DT_IOP_BORDERS_BASIS_WIDTH && image_aspect < 1.0f) DT_IOP_BORDERS_ASSIGN_BASIS(height); else if(basis == DT_IOP_BORDERS_BASIS_HEIGHT && image_aspect > 1.0f) DT_IOP_BORDERS_ASSIGN_BASIS(width); // but add it to the longer side instead, // if the selected aspect ratio would cut off the image. if(basis == DT_IOP_BORDERS_BASIS_WIDTH && image_aspect < aspect) DT_IOP_BORDERS_ASSIGN_BASIS(height); else if(basis == DT_IOP_BORDERS_BASIS_HEIGHT && image_aspect > aspect) DT_IOP_BORDERS_ASSIGN_BASIS(width); if(basis == DT_IOP_BORDERS_BASIS_HEIGHT) aspect = 1.0f / aspect; *basis_out = roundf((float)*basis_in + border_width); *other_out = roundf((float)*basis_out / aspect); } // sanity check. const size_t max_dim = MAX(roi_in->width, roi_in->height); roi_out->width = CLAMP(roi_out->width, 1, 3 * max_dim); roi_out->height = CLAMP(roi_out->height, 1, 3 * max_dim); } void modify_roi_in(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in) { dt_iop_borders_data_t *d = piece->data; *roi_in = *roi_out; const float bw = (piece->buf_out.width - piece->buf_in.width) * roi_out->scale; const float bh = (piece->buf_out.height - piece->buf_in.height) * roi_out->scale; // don't request outside image (no px for borders) roi_in->x = MAX(roundf(roi_out->x - bw * d->pos_h), 0); roi_in->y = MAX(roundf(roi_out->y - bh * d->pos_v), 0); // subtract upper left border from dimensions roi_in->width -= MAX(roundf(bw * d->pos_h - roi_out->x), 0); roi_in->height -= MAX(roundf(bh * d->pos_v - roi_out->y), 0); // subtract lower right border from dimensions const float p_inw = (float)piece->buf_in.width * roi_out->scale; const float p_inh = (float)piece->buf_in.height * roi_out->scale; roi_in->width -= MAX(roundf((float)(roi_in->x + roi_in->width) - p_inw), 0); roi_in->height -= MAX(roundf((float)(roi_in->y + roi_in->height) - p_inh), 0); // sanity check: don't request nothing or outside roi roi_in->width = MIN(p_inw, MAX(1, roi_in->width)); roi_in->height = MIN(p_inh, MAX(1, roi_in->height)); // FIXME: clamping to 1 leads to a one-pixel visual glitch if the // right/bottom border completely fills the viewport, but // changing it to 0 breaks all of the tiling_callback functions with // a division by zero. } void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) { const dt_iop_borders_data_t *const d = piece->data; dt_iop_border_positions_t binfo; dt_iop_setup_binfo(piece, roi_in, roi_out, d->pos_v, d->pos_h, d->color, d->frame_color, d->frame_size, d->frame_offset, &binfo); dt_iop_copy_image_with_border((float*)ovoid, (const float*)ivoid, &binfo); } #ifdef HAVE_OPENCL int process_cl(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) { dt_iop_borders_data_t *d = piece->data; dt_iop_borders_global_data_t *gd = self->global_data; cl_int err = DT_OPENCL_DEFAULT_ERROR; const int devid = piece->pipe->devid; dt_iop_border_positions_t binfo; dt_iop_setup_binfo(piece, roi_in, roi_out, d->pos_v, d->pos_h, d->color, d->frame_color, d->frame_size, d->frame_offset, &binfo); const int width = roi_out->width; const int height = roi_out->height; // ----- Filling border const float col[4] = { d->color[0], d->color[1], d->color[2], 1.0f }; err = dt_opencl_enqueue_kernel_2d_args(devid, gd->kernel_borders_fill, width, height, CLARG(dev_out), CLARGINT(0), CLARGINT(0), CLARG(width), CLARG(height), CLARG(col)); if(err != CL_SUCCESS) goto error; if(binfo.frame_size != 0) { const float col_frame[4] = { d->frame_color[0], d->frame_color[1], d->frame_color[2], 1.0f }; const int roi_frame_in_width = binfo.frame_br_in_x - binfo.frame_tl_in_x; const int roi_frame_in_height = binfo.frame_br_in_y - binfo.frame_tl_in_y; const int roi_frame_out_width = binfo.frame_br_out_x - binfo.frame_tl_out_x; const int roi_frame_out_height = binfo.frame_br_out_y - binfo.frame_tl_out_y; err = dt_opencl_enqueue_kernel_2d_args(devid, gd->kernel_borders_fill, width, height, CLARG(dev_out), CLARG(binfo.frame_tl_out_x), CLARG(binfo.frame_tl_out_y), CLARG(roi_frame_out_width), CLARG(roi_frame_out_height), CLARG(col_frame)); if(err != CL_SUCCESS) goto error; err = dt_opencl_enqueue_kernel_2d_args(devid, gd->kernel_borders_fill, width, height, CLARG(dev_out), CLARG(binfo.frame_tl_in_x), CLARG(binfo.frame_tl_in_y), CLARG(roi_frame_in_width), CLARG(roi_frame_in_height), CLARG(col)); if(err != CL_SUCCESS) goto error; } const size_t oorigin[2] = { binfo.border_in_x, binfo.border_in_y }; const size_t region[2] = { roi_in->width, roi_in->height }; // copy original input from dev_in -> dev_out as starting point err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, CLIMG_ORIGIN, oorigin, region); error: return err; } #endif void init_global(dt_iop_module_so_t *self) { const int program = 2; // basic.cl from programs.conf dt_iop_borders_global_data_t *gd = malloc(sizeof(dt_iop_borders_global_data_t)); self->data = gd; gd->kernel_borders_fill = dt_opencl_create_kernel(program, "borders_fill"); } void cleanup_global(dt_iop_module_so_t *self) { dt_iop_borders_global_data_t *gd = self->data; dt_opencl_free_kernel(gd->kernel_borders_fill); free(self->data); self->data = NULL; } void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { dt_iop_borders_params_t *p = (dt_iop_borders_params_t *)p1; dt_iop_borders_data_t *d = piece->data; memcpy(d, p, sizeof(dt_iop_borders_params_t)); } void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { piece->data = malloc(sizeof(dt_iop_borders_data_t)); } void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { free(piece->data); piece->data = NULL; } void init_presets(dt_iop_module_so_t *self) { dt_iop_borders_params_t p = (dt_iop_borders_params_t){ { 1.0f, 1.0f, 1.0f }, 3.0f / 2.0f, "3:2", 0, 0.1f, 0.5f, "1/2", 0.5f, "1/2", 0.0f, 0.5f, { 0.0f, 0.0f, 0.0f }, TRUE, DT_IOP_BORDERS_BASIS_AUTO }; dt_gui_presets_add_generic(_("15:10 postcard white"), self->op, self->version(), &p, sizeof(p), TRUE, DEVELOP_BLEND_CS_NONE); p.color[0] = p.color[1] = p.color[2] = 0.0f; p.frame_color[0] = p.frame_color[1] = p.frame_color[2] = 1.0f; dt_gui_presets_add_generic(_("15:10 postcard black"), self->op, self->version(), &p, sizeof(p), TRUE, DEVELOP_BLEND_CS_NONE); } void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe) { dt_iop_borders_gui_data_t *g = self->gui_data; dt_iop_borders_params_t *p = self->params; if(fabsf(p->color[0] - self->picked_color[0]) < 0.0001f && fabsf(p->color[1] - self->picked_color[1]) < 0.0001f && fabsf(p->color[2] - self->picked_color[2]) < 0.0001f) { // interrupt infinite loops return; } if(fabsf(p->frame_color[0] - self->picked_color[0]) < 0.0001f && fabsf(p->frame_color[1] - self->picked_color[1]) < 0.0001f && fabsf(p->frame_color[2] - self->picked_color[2]) < 0.0001f) { // interrupt infinite loops return; } GdkRGBA c = (GdkRGBA){.red = self->picked_color[0], .green = self->picked_color[1], .blue = self->picked_color[2], .alpha = 1.0 }; if(picker == g->frame_picker) { p->frame_color[0] = self->picked_color[0]; p->frame_color[1] = self->picked_color[1]; p->frame_color[2] = self->picked_color[2]; gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->frame_colorpick), &c); } else if(picker == g->border_picker) { p->color[0] = self->picked_color[0]; p->color[1] = self->picked_color[1]; p->color[2] = self->picked_color[2]; gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->colorpick), &c); } dt_dev_add_history_item(darktable.develop, self, TRUE); } static void _aspect_changed(GtkWidget *combo, dt_iop_module_t *self) { dt_iop_borders_gui_data_t *g = self->gui_data; dt_iop_borders_params_t *p = self->params; const int which = dt_bauhaus_combobox_get(combo); if(which < DT_IOP_BORDERS_ASPECT_COUNT) { p->aspect = _aspect_ratios[which]; DT_ENTER_GUI_UPDATE(); dt_bauhaus_slider_set(g->aspect_slider,p->aspect); DT_LEAVE_GUI_UPDATE(); } dt_iop_color_picker_reset(self, TRUE); dt_dev_add_history_item(darktable.develop, self, TRUE); } static void _position_h_changed(GtkWidget *combo, dt_iop_module_t *self) { dt_iop_borders_gui_data_t *g = self->gui_data; dt_iop_borders_params_t *p = self->params; const int which = dt_bauhaus_combobox_get(combo); if(which < DT_IOP_BORDERS_POSITION_H_COUNT) { p->pos_h = _pos_h_ratios[which]; DT_ENTER_GUI_UPDATE(); dt_bauhaus_slider_set(g->pos_h_slider,p->pos_h); DT_LEAVE_GUI_UPDATE(); } dt_iop_color_picker_reset(self, TRUE); dt_dev_add_history_item(darktable.develop, self, TRUE); } static void _position_v_changed(GtkWidget *combo, dt_iop_module_t *self) { dt_iop_borders_gui_data_t *g = self->gui_data; dt_iop_borders_params_t *p = self->params; const int which = dt_bauhaus_combobox_get(combo); if(which < DT_IOP_BORDERS_POSITION_V_COUNT) { p->pos_v = _pos_v_ratios[which]; DT_ENTER_GUI_UPDATE(); dt_bauhaus_slider_set(g->pos_v_slider,p->pos_v); DT_LEAVE_GUI_UPDATE(); } dt_iop_color_picker_reset(self, TRUE); dt_dev_add_history_item(darktable.develop, self, TRUE); } void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous) { dt_iop_borders_gui_data_t *g = self->gui_data; dt_iop_borders_params_t *p = self->params; int k; if(!w || w == g->aspect_slider) { for(k = 0; k < DT_IOP_BORDERS_ASPECT_COUNT; k++) { if(fabsf(p->aspect - _aspect_ratios[k]) < 0.01f) break; } dt_bauhaus_combobox_set(g->aspect, k); } if(!w || w == g->pos_h_slider) { for(k = 0; k < DT_IOP_BORDERS_POSITION_H_COUNT; k++) { if(fabsf(p->pos_h - _pos_h_ratios[k]) < 0.01f) break; } dt_bauhaus_combobox_set(g->pos_h, k); } if(!w || w == g->pos_v_slider) { for(k = 0; k < DT_IOP_BORDERS_POSITION_V_COUNT; k++) { if(fabsf(p->pos_v - _pos_v_ratios[k]) < 0.01f) break; } dt_bauhaus_combobox_set(g->pos_v, k); } const char *reason = NULL; if(p->size <= 0.0f) reason = _("increase border size to enable the frame line"); else if(p->pos_h <= 0.0f) reason = _("increase horizontal offset to enable the frame line"); else if(p->pos_h >= 1.0f) reason = _("decrease horizontal offset to enable the frame line"); else if(p->pos_v <= 0.0f) reason = _("increase vertical offset to enable the frame line"); else if(p->pos_v >= 1.0f) reason = _("decrease vertical offset to enable the frame line"); const gboolean enable_frameline = (reason == NULL); gtk_widget_set_sensitive(g->frame_size, enable_frameline); gtk_widget_set_sensitive(g->frame_offset, enable_frameline); gtk_widget_set_sensitive(g->frame_color_box, enable_frameline); if(enable_frameline) { gtk_widget_set_tooltip_text(g->frame_size, _("size of the frame line in percent of min border width")); gtk_widget_set_tooltip_text(g->frame_offset, _("offset of the frame line beginning on image side")); gtk_widget_set_tooltip_text(g->frame_colorpick, _("select frame line color")); gtk_widget_set_tooltip_text(g->frame_picker, _("pick frame line color from image")); } else { gtk_widget_set_tooltip_text(g->frame_size, reason); gtk_widget_set_tooltip_text(g->frame_offset, reason); gtk_widget_set_tooltip_text(g->frame_colorpick, reason); gtk_widget_set_tooltip_text(g->frame_picker, reason); } } static void _colorpick_color_set(GtkColorButton *widget, dt_iop_module_t *self) { DT_GUARD_GUI_UPDATE(); dt_iop_borders_params_t *p = self->params; // turn off the other color picker so that this tool actually works ... dt_iop_color_picker_reset(self, TRUE); GdkRGBA c; gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &c); p->color[0] = c.red; p->color[1] = c.green; p->color[2] = c.blue; dt_dev_add_history_item(darktable.develop, self, TRUE); } static void _frame_colorpick_color_set(GtkColorButton *widget, dt_iop_module_t *self) { DT_GUARD_GUI_UPDATE(); dt_iop_borders_params_t *p = self->params; // turn off the other color picker so that this tool actually works ... dt_iop_color_picker_reset(self, TRUE); GdkRGBA c; gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &c); p->frame_color[0] = c.red; p->frame_color[1] = c.green; p->frame_color[2] = c.blue; dt_dev_add_history_item(darktable.develop, self, TRUE); } void gui_update(dt_iop_module_t *self) { dt_iop_borders_gui_data_t *g = self->gui_data; dt_iop_borders_params_t *p = self->params; gui_changed(self, NULL, NULL); // ----- Border Color GdkRGBA c = (GdkRGBA){.red = p->color[0], .green = p->color[1], .blue = p->color[2], .alpha = 1.0 }; gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->colorpick), &c); // ----- Frame Color GdkRGBA fc = (GdkRGBA){ .red = p->frame_color[0], .green = p->frame_color[1], .blue = p->frame_color[2], .alpha = 1.0 }; gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->frame_colorpick), &fc); } void gui_init(dt_iop_module_t *self) { dt_iop_borders_gui_data_t *g = IOP_GUI_ALLOC(borders); dt_iop_borders_params_t *p = self->params; dt_iop_borders_params_t *dp = self->default_params; g->basis = dt_bauhaus_combobox_from_params(self, "basis"); gtk_widget_set_tooltip_text(g->basis, _("which dimension to use for the size calculation")); g->size = dt_bauhaus_slider_from_params(self, "size"); dt_bauhaus_slider_set_digits(g->size, 4); dt_bauhaus_slider_set_format(g->size, "%"); gtk_widget_set_tooltip_text(g->size, _("size of the border in percent of the chosen basis")); DT_BAUHAUS_COMBOBOX_NEW_FULL(g->aspect, self, NULL, N_("aspect"), _("select the aspect ratio\n" "(right-click on slider below to type your own w:h)"), 0, _aspect_changed, self, N_("image"), N_("3:1"), N_("95:33"), N_("CinemaScope 2.39:1"), N_("2:1"), N_("16:9"), N_("5:3"), N_("US Legal 8.5x14"), N_("golden cut"), N_("16:10"), N_("3:2 (4x6, 10x15cm)"), N_("A4"), N_("DIN"), N_("7:5"), N_("4:3"), N_("US Letter 8.5x11"), N_("14:11"), N_("5:4 (8x10)"), N_("square"), N_("constant border"), N_("custom...")); dt_gui_box_add(self->widget, g->aspect); g->aspect_slider = dt_bauhaus_slider_from_params(self, "aspect"); gtk_widget_set_tooltip_text(g->aspect_slider, _("set the custom aspect ratio\n" "(right-click to enter number or w:h)")); g->aspect_orient = dt_bauhaus_combobox_from_params(self, "aspect_orient"); gtk_widget_set_tooltip_text(g->aspect_orient, _("aspect ratio orientation of the image with border")); DT_BAUHAUS_COMBOBOX_NEW_FULL(g->pos_h, self, NULL, N_("horizontal position"), _("select the horizontal position ratio relative to top\n" "(right-click on slider below to type your own x:w)"), 0, _position_h_changed, self, N_("center"), N_("1/3"), N_("3/8"), N_("5/8"), N_("2/3"), N_("custom...")); dt_gui_box_add(self->widget, g->pos_h); g->pos_h_slider = dt_bauhaus_slider_from_params(self, "pos_h"); gtk_widget_set_tooltip_text(g->pos_h_slider, _("custom horizontal position")); DT_BAUHAUS_COMBOBOX_NEW_FULL(g->pos_v, self, NULL, N_("vertical position"), _("select the vertical position ratio relative to left\n" "(right-click on slider below to type your own y:h)"), 0, _position_v_changed, self, N_("center"), N_("1/3"), N_("3/8"), N_("5/8"), N_("2/3"), N_("custom...")); dt_gui_box_add(self->widget, g->pos_v); g->pos_v_slider = dt_bauhaus_slider_from_params(self, "pos_v"); gtk_widget_set_tooltip_text(g->pos_v_slider, _("custom vertical position")); g->frame_size = dt_bauhaus_slider_from_params(self, "frame_size"); dt_bauhaus_slider_set_digits(g->frame_size, 4); dt_bauhaus_slider_set_format(g->frame_size, "%"); gtk_widget_set_tooltip_text(g->frame_size, _("size of the frame line in percent of min border width")); g->frame_offset = dt_bauhaus_slider_from_params(self, "frame_offset"); dt_bauhaus_slider_set_digits(g->frame_offset, 4); dt_bauhaus_slider_set_format(g->frame_offset, "%"); gtk_widget_set_tooltip_text(g->frame_offset, _("offset of the frame line beginning on image side")); GdkRGBA color = (GdkRGBA){.red = dp->color[0], .green = dp->color[1], .blue = dp->color[2], .alpha = 1.0 }; GdkRGBA frame_color = (GdkRGBA){.red = dp->frame_color[0], .green = dp->frame_color[1], .blue = dp->frame_color[2], .alpha = 1.0 }; GtkWidget *label = dtgtk_reset_label_new(_("border color"), self, &p->color, 3 * sizeof(float)); g->colorpick = gtk_color_button_new_with_rgba(&color); gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->colorpick), FALSE); gtk_color_button_set_title(GTK_COLOR_BUTTON(g->colorpick), _("select border color")); g_signal_connect(G_OBJECT(g->colorpick), "color-set", G_CALLBACK(_colorpick_color_set), self); g->border_picker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL); gtk_widget_set_tooltip_text(GTK_WIDGET(g->border_picker), _("pick border color from image")); dt_action_define_iop(self, N_("pickers"), N_("border color"), g->border_picker, &dt_action_def_color_picker); dt_gui_box_add(self->widget, dt_gui_hbox(dt_gui_expand(label), g->colorpick, g->border_picker)); label = dtgtk_reset_label_new(_("frame line color"), self, &p->frame_color, 3 * sizeof(float)); g->frame_colorpick = gtk_color_button_new_with_rgba(&frame_color); gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->frame_colorpick), FALSE); gtk_color_button_set_title(GTK_COLOR_BUTTON(g->frame_colorpick), _("select frame line color")); g_signal_connect(G_OBJECT(g->frame_colorpick), "color-set", G_CALLBACK(_frame_colorpick_color_set), self); g->frame_picker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL); gtk_widget_set_tooltip_text(GTK_WIDGET(g->frame_picker), _("pick frame line color from image")); dt_action_define_iop(self, N_("pickers"), N_("frame line color"), g->frame_picker, &dt_action_def_color_picker); g->frame_color_box = dt_gui_hbox(dt_gui_expand(label), g->frame_colorpick, g->frame_picker); dt_gui_box_add(self->widget, g->frame_color_box); } // 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