diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ed77c93cdad6..725148ed8d22 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -117,6 +117,7 @@ FILE(GLOB SOURCE_FILES "develop/masks/group.c" "develop/masks/masks.c" "develop/masks/path.c" + "develop/masks/raster.c" "develop/pixelpipe.c" "develop/tiling.c" "dtgtk/button.c" diff --git a/src/develop/blend.h b/src/develop/blend.h index 4579f20d822a..2473a8eaeb8e 100644 --- a/src/develop/blend.h +++ b/src/develop/blend.h @@ -97,7 +97,7 @@ typedef enum dt_develop_mask_mode_t DEVELOP_MASK_MASK = 1 << 1, // drawn mask DEVELOP_MASK_CONDITIONAL = 1 << 2, // parametric mask DEVELOP_MASK_RASTER = 1 << 3, // raster mask - DEVELOP_MASK_MASK_CONDITIONAL = (DEVELOP_MASK_MASK | DEVELOP_MASK_CONDITIONAL) // drawn & parametric + DEVELOP_MASK_MASK_CONDITIONAL = (DEVELOP_MASK_MASK | DEVELOP_MASK_CONDITIONAL) // drawn & parametric } dt_develop_mask_mode_t; typedef enum dt_develop_mask_combine_mode_t @@ -282,7 +282,7 @@ extern const dt_introspection_type_enum_tuple_t dt_develop_combine_masks_names[] extern const dt_introspection_type_enum_tuple_t dt_develop_feathering_guide_names[]; extern const dt_introspection_type_enum_tuple_t dt_develop_invert_mask_names[]; -#define DEVELOP_MASKS_NB_SHAPES 5 +#define DEVELOP_MASKS_NB_SHAPES 6 /** blend gui data */ typedef struct dt_iop_gui_blend_data_t diff --git a/src/develop/blend_gui.c b/src/develop/blend_gui.c index 6394bd0fc427..bcdc2f5079b4 100644 --- a/src/develop/blend_gui.c +++ b/src/develop/blend_gui.c @@ -2789,6 +2789,14 @@ void dt_iop_gui_init_masks(GtkWidget *blendw, dt_iop_module_t *module) FALSE, 0, 0, dtgtk_cairo_paint_masks_eye, abox); + bd->masks_type[5] = DT_MASKS_RASTER; + bd->masks_shapes[5] = dt_iop_togglebutton_new(module, "blend`shapes", + N_("add raster"), + N_("add multiple raster mask"), + G_CALLBACK(_blendop_masks_add_shape), + FALSE, 0, 0, + dtgtk_cairo_paint_masks_circle, abox); + bd->masks_type[0] = DT_MASKS_GRADIENT; bd->masks_shapes[0] = dt_iop_togglebutton_new(module, "blend`shapes", N_("add gradient"), diff --git a/src/develop/masks.h b/src/develop/masks.h index 6977cf900624..394fe0ce5c57 100644 --- a/src/develop/masks.h +++ b/src/develop/masks.h @@ -42,7 +42,8 @@ typedef enum dt_masks_type_t DT_MASKS_GRADIENT = 1 << 4, DT_MASKS_ELLIPSE = 1 << 5, DT_MASKS_BRUSH = 1 << 6, - DT_MASKS_NON_CLONE = 1 << 7 + DT_MASKS_NON_CLONE = 1 << 7, + DT_MASKS_RASTER = 1 << 8, } dt_masks_type_t; /**masts states */ @@ -196,6 +197,15 @@ typedef struct dt_masks_point_group_t float opacity; } dt_masks_point_group_t; +/** structure used to store information regarding raster mask */ +typedef struct dt_masks_point_raster_t +{ + int32_t sourceInstanceId; + dt_mask_id_t maskId; + int state; + float opacity; +} dt_masks_point_raster_t; + /** structure used to store pointers to the functions implementing operations on a mask shape */ /** plus a few per-class descriptive data items */ typedef struct dt_masks_functions_t @@ -440,6 +450,7 @@ extern const dt_masks_functions_t dt_masks_functions_brush; extern const dt_masks_functions_t dt_masks_functions_path; extern const dt_masks_functions_t dt_masks_functions_gradient; extern const dt_masks_functions_t dt_masks_functions_group; +extern const dt_masks_functions_t dt_masks_functions_raster; /** init dt_masks_form_gui_t struct with default values */ void dt_masks_init_form_gui(dt_masks_form_gui_t *gui); diff --git a/src/develop/masks/masks.c b/src/develop/masks/masks.c index fe0e72780d62..c6cabf5239a2 100644 --- a/src/develop/masks/masks.c +++ b/src/develop/masks/masks.c @@ -852,6 +852,8 @@ dt_masks_form_t *dt_masks_create(const dt_masks_type_t type) form->functions = &dt_masks_functions_gradient; else if(type & DT_MASKS_GROUP) form->functions = &dt_masks_functions_group; + else if(type & DT_MASKS_RASTER) + form->functions = &dt_masks_functions_raster; if(form->functions && form->functions->sanitize_config) form->functions->sanitize_config(type); diff --git a/src/develop/masks/raster.c b/src/develop/masks/raster.c new file mode 100644 index 000000000000..e855f4130180 --- /dev/null +++ b/src/develop/masks/raster.c @@ -0,0 +1,372 @@ +/* + This file is part of darktable, + Copyright (C) 2013-2025 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/undo.h" +#include "control/conf.h" +#include "control/control.h" +#include "develop/blend.h" +#include "develop/imageop.h" +#include "develop/masks.h" +#include "develop/openmp_maths.h" +#include "develop/masks.h" + +static void _raster_sanitize_config(dt_masks_type_t type) +{ + // Placeholder +} + +static GSList *_raster_setup_mouse_actions(const struct dt_masks_form_t *const form) +{ + GSList *lm = NULL; + lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_SCROLL, + GDK_SHIFT_MASK, _("[RASTER] change feather size")); + lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_SCROLL, + GDK_CONTROL_MASK, _("[RASTER] change opacity")); + return lm; +} + +static void _raster_set_form_name(dt_masks_form_t *const form, + const size_t nb) +{ + snprintf(form->name, sizeof(form->name), _("raster #%d"), (int)nb); +} + +static void _raster_set_hint_message(const dt_masks_form_gui_t *const gui, + const dt_masks_form_t *const form, + const int opacity, + char *const restrict msgbuf, + const size_t msgbuf_len) +{ + // circle has same controls on creation and on edit + g_snprintf(msgbuf, msgbuf_len, + _("feather size: shift+scroll\n" + "opacity: ctrl+scroll (%d%%)"), opacity); +} + +static void _raster_modify_property(dt_masks_form_t *const form, + const dt_masks_property_t prop, + const float old_val, + const float new_val, + float *sum, + int *count, + float *min, + float *max) +{ + +} + +static void _raster_duplicate_points(dt_develop_t *dev, + dt_masks_form_t *const base, + dt_masks_form_t *const dest) +{ + (void)dev; // unused arg, keep compiler from complaining + for(GList *pts = base->points; pts; pts = g_list_next(pts)) + { + dt_masks_point_raster_t *pt = pts->data; + dt_masks_point_raster_t *npt = malloc(sizeof(dt_masks_point_raster_t)); + memcpy(npt, pt, sizeof(dt_masks_point_raster_t)); + dest->points = g_list_append(dest->points, npt); + } +} + +static void _raster_initial_source_pos(const float iwd, + const float iht, + float *x, + float *y) +{ + *x = 0; + *y = 0; +} + +static void _raster_get_distance(const float x, + const float y, + const float as, + dt_masks_form_gui_t *gui, + const int index, + const int num_points, + gboolean *inside, + gboolean *inside_border, + int *near, + gboolean *inside_source, + float *dist) +{ + (void)num_points; // unused arg, keep compiler from complaining + // initialise returned values + *inside_source = FALSE; + *inside = FALSE; + *inside_border = FALSE; + *near = -1; + *dist = FLT_MAX; +} + +static int _raster_get_points(dt_develop_t *dev, + const float x, + const float y, + const float radius, + const float radius2, + const float rotation, + float **points, + int *points_count) +{ + (void)radius2; // keep compiler from complaining about unused arg + (void)rotation; + // float wd, ht; + + dt_free_align(*points); + *points = NULL; + *points_count = 0; + return 1; +} + +static int _raster_get_points_border(dt_develop_t *dev, + struct dt_masks_form_t *form, + float **points, + int *points_count, + float **border, + int *border_count, + const int source, + const dt_iop_module_t *module) +{ + dt_free_align(*points); + *points = NULL; + *points_count = 0; + return 1; +} + +static int _render_raster_mask(const dt_iop_module_t *const restrict module, + const dt_dev_pixelpipe_iop_t *const restrict piece, + dt_masks_form_t *const restrict form, + float *buffer, + int width, + int height) +{ + const size_t obuffsize = (size_t)width * height; + + // initialize output buffer with zero + memset(buffer, 0, sizeof(float) * width * height); + + dt_masks_point_raster_t *rasterPoint = form->points->data; + + gboolean free_mask; + float *raster_mask = dt_dev_get_raster_mask( + (dt_dev_pixelpipe_iop_t *)piece, + module->raster_mask.sink.source, + module->raster_mask.sink.id, + module, + &free_mask + ); + + if(!raster_mask) + return 0; + + // Copy content of mask to prevent modification of the actual mask data + float *const restrict mask = dt_alloc_align_float(obuffsize); + DT_OMP_FOR_SIMD(aligned(mask, raster_mask:64)) + for(size_t i = 0; i < obuffsize; i++) + mask[i] = raster_mask[i] * rasterPoint->opacity; + + // Forward raster mask + dt_iop_image_scaled_copy(buffer, mask, 1.0f, width, height, 1); + + if(free_mask) dt_free_align(raster_mask); + dt_free_align(mask); // Don't forget to free our local mask copy + + return 1; +} + +static int _raster_get_mask(const dt_iop_module_t *const restrict module, + const dt_dev_pixelpipe_iop_t *const restrict piece, + dt_masks_form_t *const restrict form, + float **buffer, + int *width, + int *height, + int *posx, + int *posy) +{ + // dt_iop_gui_blend_data_t *bd = module->blend_data; + return _render_raster_mask(module, piece, form, *buffer, *width, *height); +} + +static int _raster_get_mask_roi(const dt_iop_module_t *const restrict module, + const dt_dev_pixelpipe_iop_t *const restrict piece, + dt_masks_form_t *const form, + const dt_iop_roi_t *const roi, + float *const restrict buffer) +{ + const int width = roi->width; + const int height = roi->height; + return _render_raster_mask(module, piece, form, buffer, width, height); +} + +static int _raster_get_area(const dt_iop_module_t *const restrict module, + const dt_dev_pixelpipe_iop_t *const restrict piece, + dt_masks_form_t *const restrict form, + int *width, + int *height, + int *posx, + int *posy) +{ + *posx = 0; + *posy = 0; + *width = piece->pipe->iwidth; + *height = piece->pipe->iheight; + return 1; +} + +static int _raster_get_source_area(dt_iop_module_t *module, + dt_dev_pixelpipe_iop_t *piece, + dt_masks_form_t *form, + int *width, + int *height, + int *posx, + int *posy) +{ + *posx = 0; + *posy = 0; + *width = piece->pipe->iwidth; + *height = piece->pipe->iheight; + return 1; +} + +static int _raster_events_mouse_moved(dt_iop_module_t *module, + const float pzx, + const float pzy, + const double pressure, + const int which, + const float zoom_scale, + dt_masks_form_t *form, + const dt_mask_id_t parentid, + dt_masks_form_gui_t *gui, + const int index) +{ + if(!gui) return 0; + return 1; +} + +static int _raster_events_mouse_scrolled(dt_iop_module_t *module, + const float pzx, + const float pzy, + const int up, + const uint32_t state, + dt_masks_form_t *form, + const dt_mask_id_t parentid, + dt_masks_form_gui_t *gui, + const int index) +{ + if(!gui) return 0; + return 1; +} + +static int _raster_events_button_pressed(dt_iop_module_t *module, + float pzx, float pzy, + const double pressure, + const int which, + const int type, + const uint32_t state, + dt_masks_form_t *form, + const dt_mask_id_t parentid, + dt_masks_form_gui_t *gui, + const int index) +{ + if(!gui) return 0; + return 1; +} + +static int _raster_events_button_released(dt_iop_module_t *module, + const float pzx, + const float pzy, + const int which, + const uint32_t state, + dt_masks_form_t *form, + const dt_mask_id_t parentid, + dt_masks_form_gui_t *gui, + const int index) +{ + if(!gui) return 0; + + if (gui->creation) + { + // Create raster mask + dt_masks_point_raster_t *raster = malloc(sizeof(dt_masks_point_raster_t)); + raster->opacity = 1.0f; + + dt_iop_module_t *sourceMask = module->raster_mask.sink.source; + if (!sourceMask) { + // TODO: Print error + return 0; + } + raster->sourceInstanceId = sourceMask->instance; + raster->maskId = module->raster_mask.sink.id; + + gui->form_dragging = FALSE; + + form->points = g_list_append(form->points, raster); + + dt_iop_module_t *crea_module = gui->creation_module; + dt_masks_gui_form_save_creation(darktable.develop, crea_module, form, gui); + + if(crea_module) + { + // we save the move + dt_dev_add_history_item(darktable.develop, crea_module, TRUE); + // and we switch in edit mode to show all the forms + dt_masks_set_edit_mode(crea_module, DT_MASKS_EDIT_FULL); + dt_masks_iop_update(crea_module); + } + + dt_dev_masks_selection_change(darktable.develop, crea_module, form->formid); + gui->creation_module = NULL; + } + + return 1; +} + +static void _raster_events_post_expose(cairo_t *cr, + const float zoom_scale, + dt_masks_form_gui_t *gui, + const int index, + const int num_points) +{ + // TODO: Implement ME!!! + return; +} + +// The function table for circles. This must be public, i.e. no "static" keyword. +const dt_masks_functions_t dt_masks_functions_raster = { + .point_struct_size = sizeof(struct dt_masks_point_raster_t), + .sanitize_config = _raster_sanitize_config, + .setup_mouse_actions = _raster_setup_mouse_actions, + .set_form_name = _raster_set_form_name, + .set_hint_message = _raster_set_hint_message, + .modify_property = _raster_modify_property, + .duplicate_points = _raster_duplicate_points, + .initial_source_pos = _raster_initial_source_pos, + .get_distance = _raster_get_distance, + .get_points = _raster_get_points, + .get_points_border = _raster_get_points_border, + .get_mask = _raster_get_mask, + .get_mask_roi = _raster_get_mask_roi, + .get_area = _raster_get_area, + .get_source_area = _raster_get_source_area, + .mouse_moved = _raster_events_mouse_moved, + .mouse_scrolled = _raster_events_mouse_scrolled, + .button_pressed = _raster_events_button_pressed, + .button_released = _raster_events_button_released, + .post_expose = _raster_events_post_expose +};