/* 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 "control/conf.h" #include "control/control.h" #include "common/gdk_event_utils.h" #include "common/imagebuf.h" #include "develop/blend.h" #include "develop/imageop.h" #include "develop/imageop_gui.h" #include "develop/masks.h" #include "gui/accelerators.h" #include "gui/gtk.h" #include "iop/iop_api.h" #include #include // this is the version of the modules parameters, // and includes version information about compile-time dt DT_MODULE_INTROSPECTION(2, dt_iop_spots_params_t) typedef struct dt_iop_spots_params_t { int clone_id[64]; int clone_algo[64]; } dt_iop_spots_params_t; typedef struct dt_iop_spots_gui_data_t { GtkLabel *label; GtkWidget *bt_path, *bt_circle, *bt_ellipse, *bt_edit_masks; } dt_iop_spots_gui_data_t; typedef struct dt_iop_spots_params_t dt_iop_spots_data_t; // this returns a translatable name const char *name() { return _("spot removal"); } const char *deprecated_msg() { return _("this module is deprecated. please use the retouch module instead."); } const char **description(dt_iop_module_t *self) { return dt_iop_set_description(self, _("remove sensor dust spots"), _("corrective"), _("linear, RGB, scene-referred"), _("geometric, raw"), _("linear, RGB, scene-referred")); } int default_group() { return IOP_GROUP_CORRECT | IOP_GROUP_EFFECTS; } int flags() { return IOP_FLAGS_SUPPORTS_BLENDING | IOP_FLAGS_NO_MASKS | IOP_FLAGS_DEPRECATED; } 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; } 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_spots_params_v2_t { int clone_id[64]; int clone_algo[64]; } dt_iop_spots_params_v2_t; if(old_version == 1) { typedef struct dt_iop_spots_v1_t { float x, y; float xc, yc; float radius; } dt_iop_spots_v1_t; typedef struct dt_iop_spots_params_v1_t { int num_spots; dt_iop_spots_v1_t spot[32]; } dt_iop_spots_params_v1_t; const dt_iop_spots_params_v1_t *o = (dt_iop_spots_params_v1_t *)old_params; dt_iop_spots_params_v2_t *n = malloc(sizeof(dt_iop_spots_params_v2_t)); memset(n, 0, sizeof(dt_iop_spots_params_v2_t)); for(int i = 0; i < o->num_spots; i++) { // we have to register a new circle mask dt_masks_form_t *form = dt_masks_create(DT_MASKS_CIRCLE | DT_MASKS_CLONE); // spots v1 was before raw orientation changes form->version = 1; dt_masks_point_circle_t *circle = (malloc(sizeof(dt_masks_point_circle_t))); circle->center[0] = o->spot[i].x; circle->center[1] = o->spot[i].y; circle->radius = o->spot[i].radius; circle->border = 0.0f; form->points = g_list_append(form->points, circle); form->source[0] = o->spot[i].xc; form->source[1] = o->spot[i].yc; // adapt for raw orientation changes dt_masks_legacy_params(self->dev, form, form->version, dt_masks_version()); dt_masks_gui_form_save_creation(self->dev, self, form, NULL); // and add it to the module params n->clone_id[i] = form->formid; n->clone_algo[i] = 2; } // look for spot history num, if not found then it will be added // at the end of the list. int last_spot_num = 0; int count = 0; for(GList *l = self->dev->history; l; l = g_list_next(l)) { dt_dev_history_item_t *item = l->data; count++; if(!strcmp(item->op_name, "spots")) last_spot_num = item->num; } if(last_spot_num == 0) last_spot_num = count; // record all forms for this module & history num // also record the group in the blend params. dt_develop_blend_params_t *bp = self->blend_params; for(GList *l = self->dev->forms; l; l = g_list_next(l)) { dt_masks_form_t *form = l->data; if(form && (form->type & DT_MASKS_GROUP)) bp->mask_id = form->formid; if(form) dt_masks_write_masks_history_item(self->dev->image_storage.id, last_spot_num, form); } *new_params = n; *new_params_size = sizeof(dt_iop_spots_params_v2_t); *new_version = 2; return 0; } return 1; } static void _resynch_params(dt_iop_module_t *self) { dt_iop_spots_params_t *p = self->params; dt_develop_blend_params_t *bp = self->blend_params; // we create 2 new buffers int nid[64] = { 0 }; int nalgo[64] = { 2 }; // we go through all forms in blend params dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop, bp->mask_id); if(grp && (grp->type & DT_MASKS_GROUP)) { int i = 0; for(GList *forms = grp->points; (i < 64) && forms; forms = g_list_next(forms)) { dt_masks_point_group_t *grpt = forms->data; nid[i] = grpt->formid; for(int j = 0; j < 64; j++) { if(p->clone_id[j] == nid[i]) { nalgo[i] = p->clone_algo[j]; break; } } i++; } } // we reaffect params for(int i = 0; i < 64; i++) { p->clone_algo[i] = nalgo[i]; p->clone_id[i] = nid[i]; } } static gboolean _reset_form_creation(GtkWidget *widget, dt_iop_module_t *self) { dt_iop_spots_gui_data_t *g = self->gui_data; // we check the nb of shapes limit dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, self->blend_params->mask_id); guint nb = 0; if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points); if(nb >= 64) { dt_control_log(_("spot module is limited to 64 shapes. please add a new instance!")); } if(nb < 64 && (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_path)) || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_circle)) || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_ellipse)))) { // we unset the creation mode dt_masks_change_form_gui(NULL); } if(widget != g->bt_path || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE); if(widget != g->bt_circle || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE); if(widget != g->bt_ellipse || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE); return (nb < 64); } static int _shape_is_being_added(dt_iop_module_t *self, const int shape_type) { int being_added = 0; if(self->dev->form_gui && self->dev->form_visible && ((self->dev->form_gui->creation && self->dev->form_gui->creation_module == self) || (self->dev->form_gui->creation_continuous && self->dev->form_gui->creation_continuous_module == self))) { if(self->dev->form_visible->type & DT_MASKS_GROUP) { GList *forms = self->dev->form_visible->points; if(forms) { dt_masks_point_group_t *grpt = forms->data; if(grpt) { const dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop, grpt->formid); if(form) being_added = (form->type & shape_type); } } } else being_added = (self->dev->form_visible->type & shape_type); } return being_added; } static gboolean _add_shape(GtkWidget *widget, const int creation_continuous, dt_iop_module_t *self) { //turn module on (else shape creation won't work) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE); //switch mask edit mode off dt_iop_gui_blend_data_t *bd = self->blend_data; if(bd) bd->masks_shown = DT_MASKS_EDIT_OFF; if(!_reset_form_creation(widget, self)) return TRUE; if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) return FALSE; dt_iop_spots_gui_data_t *g = self->gui_data; // we want to be sure that the iop has focus dt_iop_request_focus(self); // we create the new form dt_masks_type_t type = DT_MASKS_CIRCLE; if(widget == g->bt_path) type = DT_MASKS_PATH; else if(widget == g->bt_circle) type = DT_MASKS_CIRCLE; else if(widget == g->bt_ellipse) type = DT_MASKS_ELLIPSE; dt_masks_form_t *form = dt_masks_create(type | DT_MASKS_CLONE); dt_masks_change_form_gui(form); darktable.develop->form_gui->creation_module = self; if(creation_continuous) { darktable.develop->form_gui->creation_continuous = TRUE; darktable.develop->form_gui->creation_continuous_module = self; } else { darktable.develop->form_gui->creation_continuous = FALSE; darktable.develop->form_gui->creation_continuous_module = NULL; } dt_control_queue_redraw_center(); return FALSE; } static void _add_shape_callback(GtkGestureSingle *gesture, int n_press, double x, double y, dt_iop_module_t *self) { if(dt_atomic_get_int(&darktable.gui->reset) != 0) return; GtkWidget *widget = dt_gui_get_widget(gesture); const dt_iop_spots_gui_data_t *g = self->gui_data; const gboolean creation_continuous = dt_modifier_is(dt_gui_current_state(gesture), GDK_CONTROL_MASK); _add_shape(widget, creation_continuous, self); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), _shape_is_being_added(self, DT_MASKS_CIRCLE)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), _shape_is_being_added(self, DT_MASKS_ELLIPSE)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), _shape_is_being_added(self, DT_MASKS_PATH)); } static void _edit_masks(GtkGestureSingle *gesture, int n_press, double x, double y, dt_iop_module_t *self) { if(dt_atomic_get_int(&darktable.gui->reset) != 0) return; // if we don't have the focus, request for it and quit, gui_focus() do the rest if(darktable.develop->gui_module != self) { dt_iop_request_focus(self); return; } dt_iop_gui_blend_data_t *bd = self->blend_data; dt_iop_spots_gui_data_t *g = self->gui_data; //hide all shapes and free if some are in creation if(darktable.develop->form_gui->creation && darktable.develop->form_gui->creation_module == self) dt_masks_change_form_gui(NULL); if(darktable.develop->form_gui->creation_continuous_module == self) { darktable.develop->form_gui->creation_continuous = FALSE; darktable.develop->form_gui->creation_continuous_module = NULL; } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE); DT_ENTER_GUI_UPDATE(); dt_iop_color_picker_reset(self, TRUE); dt_masks_set_edit_mode(self, self->dev->form_gui->edit_mode == DT_MASKS_EDIT_FULL ? DT_MASKS_EDIT_OFF : DT_MASKS_EDIT_FULL); // update edit shapes status dt_develop_blend_params_t *bp = self->blend_params; dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop, bp->mask_id); //only toggle shape show button if shapes exist if(grp && (grp->type & DT_MASKS_GROUP) && grp->points) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), (bd->masks_shown != DT_MASKS_EDIT_OFF) && (darktable.develop->gui_module == self)); } else { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE); } DT_LEAVE_GUI_UPDATE(); dt_control_queue_redraw_center(); } static gboolean masks_form_is_in_roi(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out) { // we get the area for the form int fl, ft, fw, fh; if(!dt_masks_get_area(self, piece, form, &fw, &fh, &fl, &ft)) return FALSE; // is the form outside of the roi? fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale; if(ft >= roi_out->y + roi_out->height || ft + fh <= roi_out->y || fl >= roi_out->x + roi_out->width || fl + fw <= roi_out->x) return FALSE; return TRUE; } // needed if mask dest is in roi and mask src is not 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) { *roi_in = *roi_out; int roir = roi_in->width + roi_in->x; int roib = roi_in->height + roi_in->y; int roix = roi_in->x; int roiy = roi_in->y; dt_develop_blend_params_t *bp = piece->blendop_data; // We iterate through all spots or polygons dt_masks_form_t *grp = dt_masks_get_from_id_ext(piece->pipe->forms, bp->mask_id); if(grp && (grp->type & DT_MASKS_GROUP)) { for(const GList *forms = grp->points; forms; forms = g_list_next(forms)) { dt_masks_point_group_t *grpt = forms->data; // we get the spot dt_masks_form_t *form = dt_masks_get_from_id_ext(piece->pipe->forms, grpt->formid); if(form) { // if the form is outside the roi, we just skip it if(!masks_form_is_in_roi(self, piece, form, roi_in, roi_out)) { continue; } // we get the area for the source int fl, ft, fw, fh; if(!dt_masks_get_source_area(self, piece, form, &fw, &fh, &fl, &ft)) { continue; } fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale; // we enlarge the roi if needed roiy = fminf(ft, roiy); roix = fminf(fl, roix); roir = fmaxf(fl + fw, roir); roib = fmaxf(ft + fh, roib); } } } // now we set the values const float scwidth = piece->buf_in.width * roi_in->scale; const float scheight = piece->buf_in.height * roi_in->scale; roi_in->x = CLAMP(roix, 0, scwidth - 1); roi_in->y = CLAMP(roiy, 0, scheight - 1); // we want to stay in the range of available data roi_in->width = CLAMP(roir - roi_in->x, 1, scwidth - roi_in->x); roi_in->height = CLAMP(roib - roi_in->y, 1, scheight - roi_in->y); } static void masks_point_denormalize(dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi, const float *points, size_t points_count, float *new) { const float scalex = piece->pipe->iwidth * roi->scale; const float scaley = piece->pipe->iheight * roi->scale; for(size_t i = 0; i < points_count * 2; i += 2) { new[i] = points[i] * scalex; new[i + 1] = points[i + 1] * scaley; } } static int masks_point_calc_delta(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi, const float *target, const float *source, int *dx, int *dy) { dt_boundingbox_t points; masks_point_denormalize(piece, roi, target, 1, points); masks_point_denormalize(piece, roi, source, 1, points + 2); const int res = dt_dev_distort_transform_plus(self->dev, piece->pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, 2); if(!res) return res; *dx = points[0] - points[2]; *dy = points[1] - points[3]; return res; } static int masks_get_delta(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi, dt_masks_form_t *form, int *dx, int *dy) { int res = 0; if(form->type & DT_MASKS_PATH) { dt_masks_point_path_t *pt = form->points->data; res = masks_point_calc_delta(self, piece, roi, pt->corner, form->source, dx, dy); } else if(form->type & DT_MASKS_CIRCLE) { dt_masks_point_circle_t *pt = form->points->data; res = masks_point_calc_delta(self, piece, roi, pt->center, form->source, dx, dy); } else if(form->type & DT_MASKS_ELLIPSE) { dt_masks_point_ellipse_t *pt = form->points->data; res = masks_point_calc_delta(self, piece, roi, pt->center, form->source, dx, dy); } return res; } static void _process(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, const int ch) { dt_iop_spots_params_t *d = piece->data; dt_develop_blend_params_t *bp = piece->blendop_data; // we don't modify most of the image: dt_iop_copy_image_roi(out, in, ch, roi_in, roi_out); // iterate through all forms dt_masks_form_t *grp = dt_masks_get_from_id_ext(piece->pipe->forms, bp->mask_id); int pos = 0; if(grp && (grp->type & DT_MASKS_GROUP)) { for(const GList *forms = grp->points; (pos < 64) && forms; pos++, forms = g_list_next(forms)) { dt_masks_point_group_t *grpt = forms->data; // we get the spot dt_masks_form_t *form = dt_masks_get_from_id_ext(piece->pipe->forms, grpt->formid); if(!form) { continue; } // if the form is outside the roi, we just skip it if(!masks_form_is_in_roi(self, piece, form, roi_in, roi_out)) { continue; } if(d->clone_algo[pos] == 1 && (form->type & DT_MASKS_CIRCLE)) { dt_masks_point_circle_t *circle = form->points->data; dt_boundingbox_t points; masks_point_denormalize(piece, roi_in, circle->center, 1, points); masks_point_denormalize(piece, roi_in, form->source, 1, points + 2); if(!dt_dev_distort_transform_plus(self->dev, piece->pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, 2)) { continue; } // convert from world space: float radius10[2] = { circle->radius, circle->radius }; float radf[2]; masks_point_denormalize(piece, roi_in, radius10, 1, radf); const int rad = MIN(radf[0], radf[1]); const int posx = points[0] - rad; const int posy = points[1] - rad; const int posx_source = points[2] - rad; const int posy_source = points[3] - rad; const int dx = posx - posx_source; const int dy = posy - posy_source; const int fw = 2 * rad, fh = 2 * rad; float *filter = malloc(sizeof(float) * (2 * rad + 1)); if(rad > 0) { for(int k = -rad; k <= rad; k++) { const float kk = 1.0f - fabsf(k / (float)rad); filter[rad + k] = kk * kk * (3.0f - 2.0f * kk); } } else { filter[0] = 1.0f; } for(int yy = posy; yy < posy + fh; yy++) { // we test if we are inside roi_out if(yy < roi_out->y || yy >= roi_out->y + roi_out->height) continue; // we test if the source point is inside roi_in if(yy - dy < roi_in->y || yy - dy >= roi_in->y + roi_in->height) continue; for(int xx = posx; xx < posx + fw; xx++) { // we test if we are inside roi_out if(xx < roi_out->x || xx >= roi_out->x + roi_out->width) continue; // we test if the source point is inside roi_in if(xx - dx < roi_in->x || xx - dx >= roi_in->x + roi_in->width) continue; const float f = filter[xx - posx + 1] * filter[yy - posy + 1]; for(int c = 0; c < ch; c++) out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] = out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] * (1.0f - f) + in[ch * ((size_t)roi_in->width * (yy - posy + posy_source - roi_in->y) + xx - posx + posx_source - roi_in->x) + c] * f; } } free(filter); } else { // we get the mask float *mask = NULL; int posx, posy, width, height; dt_masks_get_mask(self, piece, form, &mask, &width, &height, &posx, &posy); const int fts = posy * roi_in->scale; const int fhs = height * roi_in->scale; const int fls = posx * roi_in->scale; const int fws = width * roi_in->scale; int dx = 0, dy = 0; // now we search the delta with the source if(!masks_get_delta(self, piece, roi_in, form, &dx, &dy)) { dt_free_align(mask); continue; } if(dx != 0 || dy != 0) { // now we do the pixel clone for(int yy = fts + 1; yy < fts + fhs - 1; yy++) { // we test if we are inside roi_out if(yy < roi_out->y || yy >= roi_out->y + roi_out->height) continue; // we test if the source point is inside roi_in if(yy - dy < roi_in->y || yy - dy >= roi_in->y + roi_in->height) continue; for(int xx = fls + 1; xx < fls + fws - 1; xx++) { // we test if we are inside roi_out if(xx < roi_out->x || xx >= roi_out->x + roi_out->width) continue; // we test if the source point is inside roi_in if(xx - dx < roi_in->x || xx - dx >= roi_in->x + roi_in->width) continue; const float f = mask[((int)((yy - fts) / roi_in->scale)) * width + (int)((xx - fls) / roi_in->scale)] * grpt->opacity; for(int c = 0; c < ch; c++) out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] = out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] * (1.0f - f) + in[ch * ((size_t)roi_in->width * (yy - dy - roi_in->y) + xx - dx - roi_in->x) + c] * f; } } } dt_free_align(mask); } } } } void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) { const float *in = (float *)i; float *out = (float *)o; _process(self, piece, in, out, roi_in, roi_out, piece->colors); } 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) { _process(self, piece, in, out, roi_in, roi_out, 1); } /** init, cleanup, commit to pipeline */ void init(dt_iop_module_t *self) { // we don't need global data: self->global_data = NULL; // malloc(sizeof(dt_iop_spots_global_data_t)); self->params = calloc(1, sizeof(dt_iop_spots_params_t)); self->default_params = calloc(1, sizeof(dt_iop_spots_params_t)); // our module is disabled by default // by default: self->default_enabled = FALSE; self->params_size = sizeof(dt_iop_spots_params_t); self->gui_data = NULL; // init defaults: dt_iop_spots_params_t tmp = (dt_iop_spots_params_t){ { 0 }, { 2 } }; memcpy(self->default_params, &tmp, sizeof(dt_iop_spots_params_t)); } void gui_focus(dt_iop_module_t *self, gboolean in) { if(self->enabled && !darktable.develop->full.pipe->loading) { dt_iop_spots_gui_data_t *g = self->gui_data; if(in) { dt_iop_gui_blend_data_t *bd = self->blend_data; // update edit shapes status dt_develop_blend_params_t *bp = self->blend_params; dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop, bp->mask_id); //only toggle shape show button if shapes exist if(grp && (grp->type & DT_MASKS_GROUP) && grp->points) { if(bd->masks_shown == DT_MASKS_EDIT_OFF) dt_masks_set_edit_mode(self, DT_MASKS_EDIT_FULL); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), (bd->masks_shown != DT_MASKS_EDIT_OFF) && (darktable.develop->gui_module == self)); } else { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE); } } else { // lost focus, hide all shapes if(darktable.develop->form_gui->creation && darktable.develop->form_gui->creation_module == self) dt_masks_change_form_gui(NULL); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE); dt_masks_set_edit_mode(self, DT_MASKS_EDIT_OFF); } } } 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_spots_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; } /** gui callbacks, these are needed. */ void gui_update(dt_iop_module_t *self) { _resynch_params(self); dt_iop_spots_gui_data_t *g = self->gui_data; // update clones count dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, self->blend_params->mask_id); guint nb = 0; if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points); gchar *str = g_strdup_printf("%u", nb); gtk_label_set_text(g->label, str); g_free(str); // enable/disable shapes toolbar gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), _shape_is_being_added(self, DT_MASKS_CIRCLE)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), _shape_is_being_added(self, DT_MASKS_PATH)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), _shape_is_being_added(self, DT_MASKS_ELLIPSE)); // update edit shapes status dt_iop_gui_blend_data_t *bd = self->blend_data; if(darktable.develop->history_updating) bd->masks_shown = DT_MASKS_EDIT_OFF; //only toggle shape show button if shapes exist if(grp && (grp->type & DT_MASKS_GROUP) && grp->points) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(g->bt_edit_masks), (bd->masks_shown != DT_MASKS_EDIT_OFF) && (darktable.develop->gui_module == self)); } else { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE); } dt_control_queue_redraw_center(); } void gui_init(dt_iop_module_t *self) { dt_iop_spots_gui_data_t *g = IOP_GUI_ALLOC(spots); g->label = GTK_LABEL(dt_ui_label_new("-1")); self->widget = dt_gui_hbox(dt_ui_label_new(_("number of strokes:")), g->label); gtk_widget_set_tooltip_text(self->widget, _("click on a shape and drag on canvas.\nuse the mouse wheel " "to adjust size.\nright-click to remove a shape.")); g->bt_edit_masks = dt_iop_togglebutton_new(self, NULL, N_("show and edit shapes"), NULL, G_CALLBACK(_edit_masks), TRUE, 0, 0, dtgtk_cairo_paint_masks_eye, self->widget); g->bt_path = dt_iop_togglebutton_new(self, N_("shapes"), N_("add path"), N_("add multiple paths"), G_CALLBACK(_add_shape_callback), TRUE, 0, 0, dtgtk_cairo_paint_masks_path, self->widget); g->bt_ellipse = dt_iop_togglebutton_new(self, N_("shapes"), N_("add ellipse"), N_("add multiple ellipses"), G_CALLBACK(_add_shape_callback), TRUE, 0, 0, dtgtk_cairo_paint_masks_ellipse, self->widget); g->bt_circle = dt_iop_togglebutton_new(self, N_("shapes"), N_("add circle"), N_("add multiple circles"), G_CALLBACK(_add_shape_callback), TRUE, 0, 0, dtgtk_cairo_paint_masks_circle, self->widget); } void gui_reset(dt_iop_module_t *self) { // hide the previous masks dt_masks_reset_form_gui(); } // 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