/* 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 Spencer Kimball and Peter Mattis * * pikafiltertool-widgets.c * * 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 #include "libpikamath/pikamath.h" #include "tools-types.h" #include "core/pikacontainer.h" #include "core/pikaitem.h" #include "display/pikadisplay.h" #include "display/pikatoolfocus.h" #include "display/pikatoolgyroscope.h" #include "display/pikatoolline.h" #include "display/pikatooltransformgrid.h" #include "display/pikatoolwidgetgroup.h" #include "pikafilteroptions.h" #include "pikafiltertool.h" #include "pikafiltertool-widgets.h" typedef struct _Controller Controller; struct _Controller { PikaFilterTool *filter_tool; PikaControllerType controller_type; PikaToolWidget *widget; GCallback creator_callback; gpointer creator_data; }; /* local function prototypes */ static Controller * pika_filter_tool_controller_new (void); static void pika_filter_tool_controller_free (Controller *controller); static void pika_filter_tool_set_line (Controller *controller, GeglRectangle *area, gdouble x1, gdouble y1, gdouble x2, gdouble y2); static void pika_filter_tool_line_changed (PikaToolWidget *widget, Controller *controller); static void pika_filter_tool_set_slider_line (Controller *controller, GeglRectangle *area, gdouble x1, gdouble y1, gdouble x2, gdouble y2, const PikaControllerSlider *sliders, gint n_sliders); static void pika_filter_tool_slider_line_changed (PikaToolWidget *widget, Controller *controller); static void pika_filter_tool_set_transform_grid (Controller *controller, GeglRectangle *area, const PikaMatrix3 *transform); static void pika_filter_tool_transform_grid_changed (PikaToolWidget *widget, Controller *controller); static void pika_filter_tool_set_transform_grids (Controller *controller, GeglRectangle *area, const PikaMatrix3 *transforms, gint n_transforms); static void pika_filter_tool_transform_grids_changed (PikaToolWidget *widget, Controller *controller); static void pika_filter_tool_set_gyroscope (Controller *controller, GeglRectangle *area, gdouble yaw, gdouble pitch, gdouble roll, gdouble zoom, gboolean invert); static void pika_filter_tool_gyroscope_changed (PikaToolWidget *widget, Controller *controller); static void pika_filter_tool_set_focus (Controller *controller, GeglRectangle *area, PikaLimitType type, gdouble x, gdouble y, gdouble radius, gdouble aspect_ratio, gdouble angle, gdouble inner_limit, gdouble midpoint); static void pika_filter_tool_focus_changed (PikaToolWidget *widget, Controller *controller); /* public functions */ PikaToolWidget * pika_filter_tool_create_widget (PikaFilterTool *filter_tool, PikaControllerType controller_type, const gchar *status_title, GCallback callback, gpointer callback_data, GCallback *set_func, gpointer *set_func_data) { PikaTool *tool; PikaDisplayShell *shell; Controller *controller; g_return_val_if_fail (PIKA_IS_FILTER_TOOL (filter_tool), NULL); g_return_val_if_fail (filter_tool->config != NULL, NULL); tool = PIKA_TOOL (filter_tool); g_return_val_if_fail (tool->display != NULL, NULL); shell = pika_display_get_shell (tool->display); controller = pika_filter_tool_controller_new (); controller->filter_tool = filter_tool; controller->controller_type = controller_type; controller->creator_callback = callback; controller->creator_data = callback_data; switch (controller_type) { case PIKA_CONTROLLER_TYPE_LINE: controller->widget = pika_tool_line_new (shell, 100, 100, 500, 500); g_object_set (controller->widget, "status-title", status_title, NULL); g_signal_connect (controller->widget, "changed", G_CALLBACK (pika_filter_tool_line_changed), controller); *set_func = (GCallback) pika_filter_tool_set_line; *set_func_data = controller; break; case PIKA_CONTROLLER_TYPE_SLIDER_LINE: controller->widget = pika_tool_line_new (shell, 100, 100, 500, 500); g_object_set (controller->widget, "status-title", status_title, NULL); g_signal_connect (controller->widget, "changed", G_CALLBACK (pika_filter_tool_slider_line_changed), controller); *set_func = (GCallback) pika_filter_tool_set_slider_line; *set_func_data = controller; break; case PIKA_CONTROLLER_TYPE_TRANSFORM_GRID: { PikaMatrix3 transform; gint off_x, off_y; GeglRectangle area; gdouble x1, y1; gdouble x2, y2; pika_matrix3_identity (&transform); pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); x1 = off_x + area.x; y1 = off_y + area.y; x2 = x1 + area.width; y2 = y1 + area.height; controller->widget = pika_tool_transform_grid_new (shell, &transform, x1, y1, x2, y2); g_object_set (controller->widget, "pivot-x", (x1 + x2) / 2.0, "pivot-y", (y1 + y2) / 2.0, "inside-function", PIKA_TRANSFORM_FUNCTION_MOVE, "outside-function", PIKA_TRANSFORM_FUNCTION_ROTATE, "use-corner-handles", TRUE, "use-perspective-handles", TRUE, "use-side-handles", TRUE, "use-shear-handles", TRUE, "use-pivot-handle", TRUE, NULL); g_signal_connect (controller->widget, "changed", G_CALLBACK (pika_filter_tool_transform_grid_changed), controller); *set_func = (GCallback) pika_filter_tool_set_transform_grid; *set_func_data = controller; } break; case PIKA_CONTROLLER_TYPE_TRANSFORM_GRIDS: { controller->widget = pika_tool_widget_group_new (shell); pika_tool_widget_group_set_auto_raise ( PIKA_TOOL_WIDGET_GROUP (controller->widget), TRUE); g_signal_connect (controller->widget, "changed", G_CALLBACK (pika_filter_tool_transform_grids_changed), controller); *set_func = (GCallback) pika_filter_tool_set_transform_grids; *set_func_data = controller; } break; case PIKA_CONTROLLER_TYPE_GYROSCOPE: { GeglRectangle area; gint off_x, off_y; pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); controller->widget = pika_tool_gyroscope_new (shell); g_object_set (controller->widget, "speed", 1.0 / MAX (area.width, area.height), "pivot-x", off_x + area.x + area.width / 2.0, "pivot-y", off_y + area.y + area.height / 2.0, NULL); g_signal_connect (controller->widget, "changed", G_CALLBACK (pika_filter_tool_gyroscope_changed), controller); *set_func = (GCallback) pika_filter_tool_set_gyroscope; *set_func_data = controller; } break; case PIKA_CONTROLLER_TYPE_FOCUS: controller->widget = pika_tool_focus_new (shell); g_signal_connect (controller->widget, "changed", G_CALLBACK (pika_filter_tool_focus_changed), controller); *set_func = (GCallback) pika_filter_tool_set_focus; *set_func_data = controller; break; } g_object_add_weak_pointer (G_OBJECT (controller->widget), (gpointer) &controller->widget); g_object_set_data_full (filter_tool->config, "pika-filter-tool-controller", controller, (GDestroyNotify) pika_filter_tool_controller_free); return controller->widget; } static void pika_filter_tool_reset_transform_grid (PikaToolWidget *widget, PikaFilterTool *filter_tool) { PikaMatrix3 *transform; gint off_x, off_y; GeglRectangle area; gdouble x1, y1; gdouble x2, y2; gdouble pivot_x, pivot_y; g_object_get (widget, "transform", &transform, NULL); pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); x1 = off_x + area.x; y1 = off_y + area.y; x2 = x1 + area.width; y2 = y1 + area.height; pika_matrix3_transform_point (transform, (x1 + x2) / 2.0, (y1 + y2) / 2.0, &pivot_x, &pivot_y); g_object_set (widget, "pivot-x", pivot_x, "pivot-y", pivot_y, NULL); g_free (transform); } void pika_filter_tool_reset_widget (PikaFilterTool *filter_tool, PikaToolWidget *widget) { Controller *controller; g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool)); g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget)); g_return_if_fail (filter_tool->config != NULL); controller = g_object_get_data (filter_tool->config, "pika-filter-tool-controller"); g_return_if_fail (controller != NULL); switch (controller->controller_type) { case PIKA_CONTROLLER_TYPE_TRANSFORM_GRID: { g_signal_handlers_block_by_func (controller->widget, pika_filter_tool_transform_grid_changed, controller); pika_filter_tool_reset_transform_grid (controller->widget, filter_tool); g_signal_handlers_unblock_by_func (controller->widget, pika_filter_tool_transform_grid_changed, controller); } break; case PIKA_CONTROLLER_TYPE_TRANSFORM_GRIDS: { g_signal_handlers_block_by_func (controller->widget, pika_filter_tool_transform_grids_changed, controller); pika_container_foreach ( pika_tool_widget_group_get_children ( PIKA_TOOL_WIDGET_GROUP (controller->widget)), (GFunc) pika_filter_tool_reset_transform_grid, filter_tool); g_signal_handlers_unblock_by_func (controller->widget, pika_filter_tool_transform_grids_changed, controller); } break; default: break; } } /* private functions */ static Controller * pika_filter_tool_controller_new (void) { return g_slice_new0 (Controller); } static void pika_filter_tool_controller_free (Controller *controller) { if (controller->widget) { g_signal_handlers_disconnect_by_data (controller->widget, controller); g_clear_weak_pointer (&controller->widget); } g_slice_free (Controller, controller); } static void pika_filter_tool_set_line (Controller *controller, GeglRectangle *area, gdouble x1, gdouble y1, gdouble x2, gdouble y2) { PikaTool *tool; PikaDrawable *drawable; if (! controller->widget) return; tool = PIKA_TOOL (controller->filter_tool); drawable = tool->drawables->data; if (drawable) { gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); x1 += off_x + area->x; y1 += off_y + area->y; x2 += off_x + area->x; y2 += off_y + area->y; } g_signal_handlers_block_by_func (controller->widget, pika_filter_tool_line_changed, controller); g_object_set (controller->widget, "x1", x1, "y1", y1, "x2", x2, "y2", y2, NULL); g_signal_handlers_unblock_by_func (controller->widget, pika_filter_tool_line_changed, controller); } static void pika_filter_tool_line_changed (PikaToolWidget *widget, Controller *controller) { PikaFilterTool *filter_tool = controller->filter_tool; PikaControllerLineCallback line_callback; gdouble x1, y1, x2, y2; gint off_x, off_y; GeglRectangle area; line_callback = (PikaControllerLineCallback) controller->creator_callback; g_object_get (widget, "x1", &x1, "y1", &y1, "x2", &x2, "y2", &y2, NULL); pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); x1 -= off_x + area.x; y1 -= off_y + area.y; x2 -= off_x + area.x; y2 -= off_y + area.y; line_callback (controller->creator_data, &area, x1, y1, x2, y2); } static void pika_filter_tool_set_slider_line (Controller *controller, GeglRectangle *area, gdouble x1, gdouble y1, gdouble x2, gdouble y2, const PikaControllerSlider *sliders, gint n_sliders) { PikaTool *tool; PikaDrawable *drawable; if (! controller->widget) return; tool = PIKA_TOOL (controller->filter_tool); drawable = tool->drawables->data; if (drawable) { gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); x1 += off_x + area->x; y1 += off_y + area->y; x2 += off_x + area->x; y2 += off_y + area->y; } g_signal_handlers_block_by_func (controller->widget, pika_filter_tool_slider_line_changed, controller); g_object_set (controller->widget, "x1", x1, "y1", y1, "x2", x2, "y2", y2, NULL); pika_tool_line_set_sliders (PIKA_TOOL_LINE (controller->widget), sliders, n_sliders); g_signal_handlers_unblock_by_func (controller->widget, pika_filter_tool_slider_line_changed, controller); } static void pika_filter_tool_slider_line_changed (PikaToolWidget *widget, Controller *controller) { PikaFilterTool *filter_tool = controller->filter_tool; PikaControllerSliderLineCallback slider_line_callback; gdouble x1, y1, x2, y2; const PikaControllerSlider *sliders; gint n_sliders; gint off_x, off_y; GeglRectangle area; slider_line_callback = (PikaControllerSliderLineCallback) controller->creator_callback; g_object_get (widget, "x1", &x1, "y1", &y1, "x2", &x2, "y2", &y2, NULL); sliders = pika_tool_line_get_sliders (PIKA_TOOL_LINE (controller->widget), &n_sliders); pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); x1 -= off_x + area.x; y1 -= off_y + area.y; x2 -= off_x + area.x; y2 -= off_y + area.y; slider_line_callback (controller->creator_data, &area, x1, y1, x2, y2, sliders, n_sliders); } static void pika_filter_tool_set_transform_grid (Controller *controller, GeglRectangle *area, const PikaMatrix3 *transform) { PikaTool *tool; PikaDrawable *drawable; gdouble x1 = area->x; gdouble y1 = area->y; gdouble x2 = area->x + area->width; gdouble y2 = area->y + area->height; PikaMatrix3 matrix; if (! controller->widget) return; tool = PIKA_TOOL (controller->filter_tool); drawable = tool->drawables->data; if (drawable) { gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); x1 += off_x; y1 += off_y; x2 += off_x; y2 += off_y; } pika_matrix3_identity (&matrix); pika_matrix3_translate (&matrix, -x1, -y1); pika_matrix3_mult (transform, &matrix); pika_matrix3_translate (&matrix, +x1, +y1); g_signal_handlers_block_by_func (controller->widget, pika_filter_tool_transform_grid_changed, controller); g_object_set (controller->widget, "transform", &matrix, "x1", x1, "y1", y1, "x2", x2, "y2", y2, NULL); g_signal_handlers_unblock_by_func (controller->widget, pika_filter_tool_transform_grid_changed, controller); } static void pika_filter_tool_transform_grid_changed (PikaToolWidget *widget, Controller *controller) { PikaFilterTool *filter_tool = controller->filter_tool; PikaControllerTransformGridCallback transform_grid_callback; gint off_x, off_y; GeglRectangle area; PikaMatrix3 *transform; PikaMatrix3 matrix; transform_grid_callback = (PikaControllerTransformGridCallback) controller->creator_callback; g_object_get (widget, "transform", &transform, NULL); pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); pika_matrix3_identity (&matrix); pika_matrix3_translate (&matrix, +(off_x + area.x), +(off_y + area.y)); pika_matrix3_mult (transform, &matrix); pika_matrix3_translate (&matrix, -(off_x + area.x), -(off_y + area.y)); transform_grid_callback (controller->creator_data, &area, &matrix); g_free (transform); } static void pika_filter_tool_set_transform_grids (Controller *controller, GeglRectangle *area, const PikaMatrix3 *transforms, gint n_transforms) { PikaTool *tool; PikaDisplayShell *shell; PikaDrawable *drawable; PikaContainer *grids; PikaToolWidget *grid = NULL; gdouble x1 = area->x; gdouble y1 = area->y; gdouble x2 = area->x + area->width; gdouble y2 = area->y + area->height; PikaMatrix3 matrix; gint i; if (! controller->widget) return; tool = PIKA_TOOL (controller->filter_tool); shell = pika_display_get_shell (tool->display); drawable = tool->drawables->data; g_signal_handlers_block_by_func (controller->widget, pika_filter_tool_transform_grids_changed, controller); if (drawable) { gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); x1 += off_x; y1 += off_y; x2 += off_x; y2 += off_y; } grids = pika_tool_widget_group_get_children ( PIKA_TOOL_WIDGET_GROUP (controller->widget)); if (n_transforms > pika_container_get_n_children (grids)) { pika_matrix3_identity (&matrix); for (i = pika_container_get_n_children (grids); i < n_transforms; i++) { gdouble pivot_x; gdouble pivot_y; grid = pika_tool_transform_grid_new (shell, &matrix, x1, y1, x2, y2); if (i > 0 && ! memcmp (&transforms[i], &transforms[i - 1], sizeof (PikaMatrix3))) { g_object_get (pika_container_get_last_child (grids), "pivot-x", &pivot_x, "pivot-y", &pivot_y, NULL); } else { pivot_x = (x1 + x2) / 2.0; pivot_y = (y1 + y2) / 2.0; pika_matrix3_transform_point (&transforms[i], pivot_x, pivot_y, &pivot_x, &pivot_y); } g_object_set (grid, "pivot-x", pivot_x, "pivot-y", pivot_y, "inside-function", PIKA_TRANSFORM_FUNCTION_MOVE, "outside-function", PIKA_TRANSFORM_FUNCTION_ROTATE, "use-corner-handles", TRUE, "use-perspective-handles", TRUE, "use-side-handles", TRUE, "use-shear-handles", TRUE, "use-pivot-handle", TRUE, NULL); pika_container_add (grids, PIKA_OBJECT (grid)); g_object_unref (grid); } pika_tool_widget_set_focus (grid, TRUE); } else { while (pika_container_get_n_children (grids) > n_transforms) pika_container_remove (grids, pika_container_get_last_child (grids)); } for (i = 0; i < n_transforms; i++) { pika_matrix3_identity (&matrix); pika_matrix3_translate (&matrix, -x1, -y1); pika_matrix3_mult (&transforms[i], &matrix); pika_matrix3_translate (&matrix, +x1, +y1); g_object_set (pika_container_get_child_by_index (grids, i), "transform", &matrix, "x1", x1, "y1", y1, "x2", x2, "y2", y2, NULL); } g_signal_handlers_unblock_by_func (controller->widget, pika_filter_tool_transform_grids_changed, controller); } static void pika_filter_tool_transform_grids_changed (PikaToolWidget *widget, Controller *controller) { PikaFilterTool *filter_tool = controller->filter_tool; PikaControllerTransformGridsCallback transform_grids_callback; PikaContainer *grids; gint off_x, off_y; GeglRectangle area; PikaMatrix3 *transforms; gint n_transforms; gint i; transform_grids_callback = (PikaControllerTransformGridsCallback) controller->creator_callback; grids = pika_tool_widget_group_get_children ( PIKA_TOOL_WIDGET_GROUP (controller->widget)); pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); n_transforms = pika_container_get_n_children (grids); transforms = g_new (PikaMatrix3, n_transforms); for (i = 0; i < n_transforms; i++) { PikaMatrix3 *transform; g_object_get (pika_container_get_child_by_index (grids, i), "transform", &transform, NULL); pika_matrix3_identity (&transforms[i]); pika_matrix3_translate (&transforms[i], +(off_x + area.x), +(off_y + area.y)); pika_matrix3_mult (transform, &transforms[i]); pika_matrix3_translate (&transforms[i], -(off_x + area.x), -(off_y + area.y)); g_free (transform); } transform_grids_callback (controller->creator_data, &area, transforms, n_transforms); g_free (transforms); } static void pika_filter_tool_set_gyroscope (Controller *controller, GeglRectangle *area, gdouble yaw, gdouble pitch, gdouble roll, gdouble zoom, gboolean invert) { if (! controller->widget) return; g_signal_handlers_block_by_func (controller->widget, pika_filter_tool_gyroscope_changed, controller); g_object_set (controller->widget, "yaw", yaw, "pitch", pitch, "roll", roll, "zoom", zoom, "invert", invert, NULL); g_signal_handlers_unblock_by_func (controller->widget, pika_filter_tool_gyroscope_changed, controller); } static void pika_filter_tool_gyroscope_changed (PikaToolWidget *widget, Controller *controller) { PikaFilterTool *filter_tool = controller->filter_tool; PikaControllerGyroscopeCallback gyroscope_callback; gint off_x, off_y; GeglRectangle area; gdouble yaw; gdouble pitch; gdouble roll; gdouble zoom; gboolean invert; gyroscope_callback = (PikaControllerGyroscopeCallback) controller->creator_callback; pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); g_object_get (widget, "yaw", &yaw, "pitch", &pitch, "roll", &roll, "zoom", &zoom, "invert", &invert, NULL); gyroscope_callback (controller->creator_data, &area, yaw, pitch, roll, zoom, invert); } static void pika_filter_tool_set_focus (Controller *controller, GeglRectangle *area, PikaLimitType type, gdouble x, gdouble y, gdouble radius, gdouble aspect_ratio, gdouble angle, gdouble inner_limit, gdouble midpoint) { PikaTool *tool; PikaDrawable *drawable; if (! controller->widget) return; tool = PIKA_TOOL (controller->filter_tool); drawable = tool->drawables->data; if (drawable) { gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); x += off_x + area->x; y += off_y + area->y; } g_signal_handlers_block_by_func (controller->widget, pika_filter_tool_focus_changed, controller); g_object_set (controller->widget, "type", type, "x", x, "y", y, "radius", radius, "aspect-ratio", aspect_ratio, "angle", angle, "inner-limit", inner_limit, "midpoint", midpoint, NULL); g_signal_handlers_unblock_by_func (controller->widget, pika_filter_tool_focus_changed, controller); } static void pika_filter_tool_focus_changed (PikaToolWidget *widget, Controller *controller) { PikaFilterTool *filter_tool = controller->filter_tool; PikaControllerFocusCallback focus_callback; PikaLimitType type; gdouble x, y; gdouble radius; gdouble aspect_ratio; gdouble angle; gdouble inner_limit; gdouble midpoint; gint off_x, off_y; GeglRectangle area; focus_callback = (PikaControllerFocusCallback) controller->creator_callback; g_object_get (widget, "type", &type, "x", &x, "y", &y, "radius", &radius, "aspect-ratio", &aspect_ratio, "angle", &angle, "inner-limit", &inner_limit, "midpoint", &midpoint, NULL); pika_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area); x -= off_x + area.x; y -= off_y + area.y; focus_callback (controller->creator_data, &area, type, x, y, radius, aspect_ratio, angle, inner_limit, midpoint); }