/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others * * This program 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. * * This program 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 this program. If not, see . */ #include "config.h" #include #include #include "libpikacolor/pikacolor.h" #include "libpikawidgets/pikawidgets.h" #include "tools-types.h" #include "config/pikadisplayconfig.h" #include "core/pika.h" #include "core/pikadata.h" #include "core/pikaimage.h" #include "core/pikaimage-pick-color.h" #include "core/pikaimage-pick-item.h" #include "core/pikaimage-sample-points.h" #include "core/pikamarshal.h" #include "core/pikasamplepoint.h" #include "widgets/pikacolormapeditor.h" #include "widgets/pikadialogfactory.h" #include "widgets/pikadockable.h" #include "widgets/pikadockcontainer.h" #include "widgets/pikapaletteeditor.h" #include "widgets/pikawidgets-utils.h" #include "widgets/pikawindowstrategy.h" #include "display/pikacanvasitem.h" #include "display/pikadisplay.h" #include "display/pikadisplayshell.h" #include "display/pikadisplayshell-appearance.h" #include "pikacoloroptions.h" #include "pikacolortool.h" #include "pikasamplepointtool.h" #include "pikatoolcontrol.h" #include "pika-intl.h" enum { PICKED, LAST_SIGNAL }; /* local function prototypes */ static void pika_color_tool_finalize (GObject *object); static void pika_color_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display); static void pika_color_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display); static void pika_color_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display); static void pika_color_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display); static void pika_color_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display); static void pika_color_tool_draw (PikaDrawTool *draw_tool); static gboolean pika_color_tool_real_can_pick (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display); static gboolean pika_color_tool_real_pick (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display, const Babl **sample_format, gpointer pixel, PikaRGB *color); static void pika_color_tool_real_picked (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display, PikaColorPickState pick_state, const Babl *sample_format, gpointer pixel, const PikaRGB *color); static gboolean pika_color_tool_can_pick (PikaColorTool *tool, const PikaCoords *coords, PikaDisplay *display); static void pika_color_tool_pick (PikaColorTool *tool, const PikaCoords *coords, PikaDisplay *display, PikaColorPickState pick_state); G_DEFINE_TYPE (PikaColorTool, pika_color_tool, PIKA_TYPE_DRAW_TOOL) #define parent_class pika_color_tool_parent_class static guint pika_color_tool_signals[LAST_SIGNAL] = { 0 }; static void pika_color_tool_class_init (PikaColorToolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass); PikaDrawToolClass *draw_class = PIKA_DRAW_TOOL_CLASS (klass); pika_color_tool_signals[PICKED] = g_signal_new ("picked", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaColorToolClass, picked), NULL, NULL, pika_marshal_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED, G_TYPE_NONE, 6, G_TYPE_POINTER, PIKA_TYPE_DISPLAY, PIKA_TYPE_COLOR_PICK_STATE, G_TYPE_POINTER, G_TYPE_POINTER, PIKA_TYPE_RGB | G_SIGNAL_TYPE_STATIC_SCOPE); object_class->finalize = pika_color_tool_finalize; tool_class->button_press = pika_color_tool_button_press; tool_class->button_release = pika_color_tool_button_release; tool_class->motion = pika_color_tool_motion; tool_class->oper_update = pika_color_tool_oper_update; tool_class->cursor_update = pika_color_tool_cursor_update; draw_class->draw = pika_color_tool_draw; klass->can_pick = pika_color_tool_real_can_pick; klass->pick = pika_color_tool_real_pick; klass->picked = pika_color_tool_real_picked; } static void pika_color_tool_init (PikaColorTool *color_tool) { PikaTool *tool = PIKA_TOOL (color_tool); pika_tool_control_set_action_size (tool->control, "tools-color-average-radius-set"); } static void pika_color_tool_finalize (GObject *object) { PikaColorTool *color_tool = PIKA_COLOR_TOOL (object); g_clear_object (&color_tool->options); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_color_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool); if (color_tool->enabled) { if (color_tool->sample_point) { pika_sample_point_tool_start_edit (tool, display, color_tool->sample_point); } else if (pika_color_tool_can_pick (color_tool, coords, display)) { pika_color_tool_pick (color_tool, coords, display, PIKA_COLOR_PICK_STATE_START); pika_tool_control_activate (tool->control); } } else { PIKA_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state, press_type, display); } } static void pika_color_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display) { PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool); if (color_tool->enabled) { pika_tool_control_halt (tool->control); if (! color_tool->sample_point && pika_color_tool_can_pick (color_tool, coords, display)) { pika_color_tool_pick (color_tool, coords, display, PIKA_COLOR_PICK_STATE_END); } } else { PIKA_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state, release_type, display); } } static void pika_color_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool); if (color_tool->enabled) { if (! color_tool->sample_point) { pika_draw_tool_pause (PIKA_DRAW_TOOL (tool)); color_tool->can_pick = pika_color_tool_can_pick (color_tool, coords, display); color_tool->center_x = coords->x; color_tool->center_y = coords->y; if (color_tool->can_pick) { pika_color_tool_pick (color_tool, coords, display, PIKA_COLOR_PICK_STATE_UPDATE); } pika_draw_tool_resume (PIKA_DRAW_TOOL (tool)); } } else { PIKA_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display); } } static void pika_color_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display) { PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool); if (color_tool->enabled) { PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaSamplePoint *sample_point = NULL; pika_draw_tool_pause (draw_tool); if (! draw_tool->widget && pika_draw_tool_is_active (draw_tool) && (draw_tool->display != display || ! proximity)) { pika_draw_tool_stop (draw_tool); } if (pika_display_shell_get_show_sample_points (shell) && proximity) { PikaImage *image = pika_display_get_image (display); gint snap_distance = display->config->snap_distance; sample_point = pika_image_pick_sample_point (image, coords->x, coords->y, FUNSCALEX (shell, snap_distance), FUNSCALEY (shell, snap_distance)); } color_tool->sample_point = sample_point; color_tool->can_pick = pika_color_tool_can_pick (color_tool, coords, display); color_tool->center_x = coords->x; color_tool->center_y = coords->y; if (! draw_tool->widget && ! pika_draw_tool_is_active (draw_tool) && proximity) { pika_draw_tool_start (draw_tool, display); } pika_draw_tool_resume (draw_tool); } else { PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity, display); } } static void pika_color_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool); if (color_tool->enabled) { if (color_tool->sample_point) { pika_tool_set_cursor (tool, display, PIKA_CURSOR_MOUSE, PIKA_TOOL_CURSOR_COLOR_PICKER, PIKA_CURSOR_MODIFIER_MOVE); } else { PikaCursorModifier modifier = PIKA_CURSOR_MODIFIER_BAD; if (pika_color_tool_can_pick (color_tool, coords, display)) { switch (color_tool->pick_target) { case PIKA_COLOR_PICK_TARGET_NONE: modifier = PIKA_CURSOR_MODIFIER_NONE; break; case PIKA_COLOR_PICK_TARGET_FOREGROUND: modifier = PIKA_CURSOR_MODIFIER_FOREGROUND; break; case PIKA_COLOR_PICK_TARGET_BACKGROUND: modifier = PIKA_CURSOR_MODIFIER_BACKGROUND; break; case PIKA_COLOR_PICK_TARGET_PALETTE: modifier = PIKA_CURSOR_MODIFIER_PLUS; break; } } pika_tool_set_cursor (tool, display, PIKA_CURSOR_COLOR_PICKER, PIKA_TOOL_CURSOR_COLOR_PICKER, modifier); } } else { PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display); } } static void pika_color_tool_draw (PikaDrawTool *draw_tool) { PikaColorTool *color_tool = PIKA_COLOR_TOOL (draw_tool); PIKA_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool); if (color_tool->enabled) { if (color_tool->sample_point) { PikaImage *image = pika_display_get_image (draw_tool->display); PikaCanvasItem *item; gint index; gint x; gint y; pika_sample_point_get_position (color_tool->sample_point, &x, &y); index = g_list_index (pika_image_get_sample_points (image), color_tool->sample_point) + 1; item = pika_draw_tool_add_sample_point (draw_tool, x, y, index); pika_canvas_item_set_highlight (item, TRUE); } else if (color_tool->can_pick && color_tool->options->sample_average) { gdouble radius = color_tool->options->average_radius; pika_draw_tool_add_rectangle (draw_tool, FALSE, color_tool->center_x - radius, color_tool->center_y - radius, 2 * radius + 1, 2 * radius + 1); } } } static gboolean pika_color_tool_real_can_pick (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display) { PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); return pika_image_coords_in_active_pickable (image, coords, shell->show_all, color_tool->options->sample_merged, FALSE); } static gboolean pika_color_tool_real_pick (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display, const Babl **sample_format, gpointer pixel, PikaRGB *color) { PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); GList *drawables = pika_image_get_selected_drawables (image); g_return_val_if_fail (drawables != NULL, FALSE); return pika_image_pick_color (image, drawables, coords->x, coords->y, shell->show_all, color_tool->options->sample_merged, color_tool->options->sample_average, color_tool->options->average_radius, sample_format, pixel, color); } static void pika_color_tool_real_picked (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display, PikaColorPickState pick_state, const Babl *sample_format, gpointer pixel, const PikaRGB *color) { PikaTool *tool = PIKA_TOOL (color_tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaImageWindow *image_window; PikaDialogFactory *dialog_factory; PikaContext *context; image_window = pika_display_shell_get_window (shell); dialog_factory = pika_dock_container_get_dialog_factory (PIKA_DOCK_CONTAINER (image_window)); /* use this tool's own options here (NOT color_tool->options) */ context = PIKA_CONTEXT (pika_tool_get_options (tool)); if (color_tool->pick_target == PIKA_COLOR_PICK_TARGET_FOREGROUND || color_tool->pick_target == PIKA_COLOR_PICK_TARGET_BACKGROUND) { GtkWidget *widget; widget = pika_dialog_factory_find_widget (dialog_factory, "pika-indexed-palette"); if (widget) { GtkWidget *editor = gtk_bin_get_child (GTK_BIN (widget)); PikaImage *image = pika_display_get_image (display); if (babl_format_is_palette (sample_format)) { guchar *index = pixel; pika_colormap_editor_set_index (PIKA_COLORMAP_EDITOR (editor), *index, NULL); } else if (pika_image_get_base_type (image) == PIKA_INDEXED) { /* When Sample merged is set, we don't have the index * information and it is possible to pick colors out of * the colormap (with compositing). In such a case, the * sample format will not be a palette format even though * the image is indexed. Still search if the color exists * in the colormap. * Note that even if it does, we might still pick the * wrong color, since several indexes may contain the same * color and we can't know for sure which is the right * one. */ gint index = pika_colormap_editor_get_index (PIKA_COLORMAP_EDITOR (editor), color); if (index > -1) pika_colormap_editor_set_index (PIKA_COLORMAP_EDITOR (editor), index, NULL); } } widget = pika_dialog_factory_find_widget (dialog_factory, "pika-palette-editor"); if (widget) { GtkWidget *editor = gtk_bin_get_child (GTK_BIN (widget)); gint index; index = pika_palette_editor_get_index (PIKA_PALETTE_EDITOR (editor), color); if (index != -1) pika_palette_editor_set_index (PIKA_PALETTE_EDITOR (editor), index, NULL); } } switch (color_tool->pick_target) { case PIKA_COLOR_PICK_TARGET_NONE: break; case PIKA_COLOR_PICK_TARGET_FOREGROUND: pika_context_set_foreground (context, color); break; case PIKA_COLOR_PICK_TARGET_BACKGROUND: pika_context_set_background (context, color); break; case PIKA_COLOR_PICK_TARGET_PALETTE: { GdkMonitor *monitor = pika_widget_get_monitor (GTK_WIDGET (shell)); GtkWidget *dockable; dockable = pika_window_strategy_show_dockable_dialog (PIKA_WINDOW_STRATEGY (pika_get_window_strategy (display->pika)), display->pika, dialog_factory, monitor, "pika-palette-editor"); if (dockable) { GtkWidget *palette_editor; PikaData *data; /* don't blink like mad when updating */ if (pick_state != PIKA_COLOR_PICK_STATE_START) pika_widget_blink_cancel (dockable); palette_editor = gtk_bin_get_child (GTK_BIN (dockable)); data = pika_data_editor_get_data (PIKA_DATA_EDITOR (palette_editor)); if (! data) { data = PIKA_DATA (pika_context_get_palette (context)); pika_data_editor_set_data (PIKA_DATA_EDITOR (palette_editor), data); } pika_palette_editor_pick_color (PIKA_PALETTE_EDITOR (palette_editor), color, pick_state); } } break; } } static gboolean pika_color_tool_can_pick (PikaColorTool *tool, const PikaCoords *coords, PikaDisplay *display) { PikaColorToolClass *klass; klass = PIKA_COLOR_TOOL_GET_CLASS (tool); return klass->can_pick && klass->can_pick (tool, coords, display); } static void pika_color_tool_pick (PikaColorTool *tool, const PikaCoords *coords, PikaDisplay *display, PikaColorPickState pick_state) { PikaColorToolClass *klass; const Babl *sample_format; gdouble pixel[4]; PikaRGB color; klass = PIKA_COLOR_TOOL_GET_CLASS (tool); if (klass->pick && klass->pick (tool, coords, display, &sample_format, pixel, &color)) { g_signal_emit (tool, pika_color_tool_signals[PICKED], 0, coords, display, pick_state, sample_format, pixel, &color); } } /* public functions */ void pika_color_tool_enable (PikaColorTool *color_tool, PikaColorOptions *options) { PikaTool *tool; g_return_if_fail (PIKA_IS_COLOR_TOOL (color_tool)); g_return_if_fail (PIKA_IS_COLOR_OPTIONS (options)); tool = PIKA_TOOL (color_tool); if (pika_tool_control_is_active (tool->control)) { g_warning ("Trying to enable PikaColorTool while it is active."); return; } g_set_object (&color_tool->options, options); /* color picking doesn't snap, see bug #768058 */ color_tool->saved_snap_to = pika_tool_control_get_snap_to (tool->control); pika_tool_control_set_snap_to (tool->control, FALSE); color_tool->enabled = TRUE; } void pika_color_tool_disable (PikaColorTool *color_tool) { PikaTool *tool; g_return_if_fail (PIKA_IS_COLOR_TOOL (color_tool)); tool = PIKA_TOOL (color_tool); if (pika_tool_control_is_active (tool->control)) { g_warning ("Trying to disable PikaColorTool while it is active."); return; } g_clear_object (&color_tool->options); pika_tool_control_set_snap_to (tool->control, color_tool->saved_snap_to); color_tool->saved_snap_to = FALSE; color_tool->enabled = FALSE; } gboolean pika_color_tool_is_enabled (PikaColorTool *color_tool) { return color_tool->enabled; }