1# abstract_transform_tool.py 2# 3# Copyright 2018-2021 Romain F. T. 4# 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18import cairo 19from gi.repository import Gtk, Gdk, GdkPixbuf 20 21from .abstract_tool import AbstractAbstractTool 22 23class AbstractCanvasTool(AbstractAbstractTool): 24 __gtype_name__ = 'AbstractCanvasTool' 25 26 def __init__(self, tool_id, label, icon_name, window, **kwargs): 27 super().__init__(tool_id, label, icon_name, window) 28 self.menu_id = 1 29 self.centered_box = None 30 self.needed_width_for_long = 0 31 self.accept_selection = True 32 self.apply_to_selection = False 33 34 def on_tool_selected(self, *args): 35 super().on_tool_selected() 36 self.apply_to_selection = self.selection_is_active() 37 38 def update_actions_state(self, *args): 39 # Changing this in on_tool_selected would be overridden by image.py 40 super().update_actions_state() 41 42 self.set_action_sensitivity('selection_delete', False) 43 self.set_action_sensitivity('selection_cut', False) 44 self.set_action_sensitivity('unselect', False) 45 self.set_action_sensitivity('select_all', False) 46 47 def give_back_control(self, preserve_selection): 48 if not preserve_selection and self.selection_is_active(): 49 self.on_apply_temp_pixbuf_tool_operation() 50 self.window.get_selection_tool().unselect_and_apply() 51 super().give_back_control(preserve_selection) 52 53 ############################################################################ 54 55 def build_and_do_op(self): 56 operation = self.build_operation() 57 self.do_tool_operation(operation) 58 59 def temp_preview(self, is_selection, local_dx, local_dy): 60 """Part of the previewing methods shared by all transform tools.""" 61 pixbuf = self.get_image().temp_pixbuf 62 if is_selection: 63 cairo_context = self.get_context() 64 cairo_context.set_source_surface(self.get_surface(), 0, 0) 65 cairo_context.paint() 66 x = self.get_selection().selection_x + local_dx 67 y = self.get_selection().selection_y + local_dy 68 Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, x, y) 69 cairo_context.paint() 70 else: 71 cairo_context = self.get_context() 72 # widget_surface = cairo.ImageSurface(cairo.Format.ARGB32, w, h) 73 # cairo_context = cairo.Context(widget_surface) 74 # TODO concerning the scale(/crop)/rotate/skew preview ^ 75 cairo_context.set_operator(cairo.Operator.SOURCE) 76 Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, 0, 0) 77 cairo_context.paint() 78 cairo_context.set_operator(cairo.Operator.OVER) 79 self.get_image().update() 80 81 ############################################################################ 82 83 def on_apply_temp_pixbuf_tool_operation(self, *args): 84 self.restore_pixbuf() 85 operation = self.build_operation() 86 self.apply_operation(operation) 87 if self.apply_to_selection: 88 self.window.force_selection() 89 else: 90 self.window.back_to_previous() 91 92 def apply_operation(self, operation): 93 operation['is_preview'] = False 94 super().apply_operation(operation) 95 self.get_image().reset_temp() 96 97 def common_end_operation(self, op): 98 if op['is_preview']: 99 self.temp_preview(op['is_selection'], op['local_dx'], op['local_dy']) 100 else: 101 self.apply_temp(op['is_selection'], op['local_dx'], op['local_dy']) 102 103 def apply_temp(self, operation_is_selection, ldx, ldy): 104 new_pixbuf = self.get_image().temp_pixbuf.copy() 105 if operation_is_selection: 106 self.get_selection().update_from_transform_tool(new_pixbuf, ldx, ldy) 107 else: 108 self.get_image().set_main_pixbuf(new_pixbuf) 109 self.get_image().use_stable_pixbuf() 110 111 ############################################################################ 112 113 def get_handle_cursor_name(self, event_x, event_y): 114 """Return the name of the accurate cursor for tools such as `scale` or 115 `crop`, with or without an active selection, depending on the size and 116 position of the resized/cropped area.""" 117 w_left, w_right, h_top, h_bottom = self.get_image().get_nineths_sizes( \ 118 self.apply_to_selection, int(self._x), int(self._y)) 119 # if we're transforming the selection from its top and/or left, coords 120 # to decide the direction depend on local deltas (self._x and self._y) 121 122 # print("get_handle_cursor_name", w_left, w_right, h_top, h_bottom) 123 cursor_name = '' 124 if event_y < h_top: 125 cursor_name = cursor_name + 'n' 126 elif event_y > h_bottom: 127 cursor_name = cursor_name + 's' 128 if event_x < w_left: 129 cursor_name = cursor_name + 'w' 130 elif event_x > w_right: 131 cursor_name = cursor_name + 'e' 132 133 if cursor_name == '': 134 cursor_name = 'not-allowed' 135 else: 136 cursor_name = cursor_name + '-resize' 137 return cursor_name 138 139 def on_draw_above(self, area, cairo_context): 140 pass 141 142 def _draw_temp_pixbuf(self, cairo_context, x, y): 143 pixbuf = self.get_image().temp_pixbuf 144 Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, x, y) 145 cairo_context.paint() 146 147 def get_deformed_surface(self, source_surface, coefs): 148 """Use cairo.Matrix to apply a transformation to `source_surface` using 149 the coefficients in `coefs` and return a new surface with the result.""" 150 p_xx, p_yx, p_xy, p_yy, p_x0, p_y0 = coefs 151 152 source_w = source_surface.get_width() 153 source_h = source_surface.get_height() 154 w = p_xx * source_w + p_xy * source_h + p_x0 * 2 155 h = p_yx * source_w + p_yy * source_h + p_y0 * 2 156 157 new_surface = cairo.ImageSurface(cairo.Format.ARGB32, int(w), int(h)) 158 cairo_context = cairo.Context(new_surface) 159 # m = cairo.Matrix(xx=1.0, yx=0.0, xy=0.0, yy=1.0, x0=0.0, y0=0.0) 160 m = cairo.Matrix(xx=p_xx, yx=p_yx, xy=p_xy, yy=p_yy, x0=p_x0, y0=p_y0) 161 try: 162 cairo_context.transform(m) 163 except: 164 self.show_error(_("Error: invalid values")) 165 return source_surface 166 cairo_context.set_source_surface(source_surface, 0, 0) 167 cairo_context.paint() 168 return new_surface 169 170 ############################################################################ 171################################################################################ 172 173