shinestacker 1.4.0__py3-none-any.whl → 1.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shinestacker might be problematic. Click here for more details.
- shinestacker/_version.py +1 -1
- shinestacker/app/args.py +23 -0
- shinestacker/app/main.py +18 -9
- shinestacker/app/project.py +2 -3
- shinestacker/app/retouch.py +8 -4
- shinestacker/config/gui_constants.py +7 -2
- shinestacker/gui/new_project.py +17 -14
- shinestacker/retouch/base_filter.py +8 -10
- shinestacker/retouch/brush_preview.py +28 -15
- shinestacker/retouch/display_manager.py +42 -42
- shinestacker/retouch/image_editor_ui.py +67 -53
- shinestacker/retouch/image_view_status.py +4 -0
- shinestacker/retouch/image_viewer.py +8 -4
- shinestacker/retouch/io_gui_handler.py +0 -3
- shinestacker/retouch/layer_collection.py +3 -0
- shinestacker/retouch/overlaid_view.py +99 -84
- shinestacker/retouch/shortcuts_help.py +35 -31
- shinestacker/retouch/sidebyside_view.py +141 -178
- shinestacker/retouch/transformation_manager.py +43 -0
- shinestacker/retouch/undo_manager.py +22 -3
- shinestacker/retouch/view_strategy.py +234 -64
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/METADATA +7 -7
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/RECORD +27 -25
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/top_level.txt +0 -0
|
@@ -39,7 +39,7 @@ class ShortcutsHelp(QDialog):
|
|
|
39
39
|
ok_button.clicked.connect(self.accept)
|
|
40
40
|
|
|
41
41
|
def add_bold_label(self, layout, label):
|
|
42
|
-
label = QLabel(label)
|
|
42
|
+
label = QLabel(f"{label}:")
|
|
43
43
|
label.setStyleSheet("font-weight: bold")
|
|
44
44
|
layout.addRow(label)
|
|
45
45
|
|
|
@@ -47,21 +47,21 @@ class ShortcutsHelp(QDialog):
|
|
|
47
47
|
self.main_layout.insertWidget(0, icon_container())
|
|
48
48
|
|
|
49
49
|
shortcuts = {
|
|
50
|
-
"M": "
|
|
51
|
-
"L": "
|
|
52
|
-
"T": "
|
|
53
|
-
"X": "
|
|
54
|
-
"↑": "
|
|
55
|
-
"↓": "
|
|
56
|
-
"Ctrl + O": "
|
|
57
|
-
"Ctrl + S": "
|
|
58
|
-
"
|
|
59
|
-
"Ctrl + M": "
|
|
60
|
-
"Ctrl + Cmd + F": "
|
|
61
|
-
"Ctrl + +": "
|
|
62
|
-
"Ctrl + -": "
|
|
63
|
-
"Ctrl + 0": "
|
|
64
|
-
"Ctrl + R": "
|
|
50
|
+
"M": "Show master layer",
|
|
51
|
+
"L": "Show selected layer",
|
|
52
|
+
"T": "Toggle master/selected layer",
|
|
53
|
+
"X": "Temporarily toggle between master and source layer",
|
|
54
|
+
"↑": "Select one layer up",
|
|
55
|
+
"↓": "Select one layer down",
|
|
56
|
+
"Ctrl + O": "Open file",
|
|
57
|
+
"Ctrl + S": "Save multilayer tiff",
|
|
58
|
+
"Ctrl + Z": "Undo brush draw",
|
|
59
|
+
"Ctrl + M": "Copy selected layer to master",
|
|
60
|
+
"Ctrl + Cmd + F": "Full screen mode",
|
|
61
|
+
"Ctrl + +": "Zoom in",
|
|
62
|
+
"Ctrl + -": "Zoom out",
|
|
63
|
+
"Ctrl + 0": "Fit to screen",
|
|
64
|
+
"Ctrl + R": "Actual size"
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
self.add_bold_label(left_layout, "Keyboard Shortcuts")
|
|
@@ -69,13 +69,13 @@ class ShortcutsHelp(QDialog):
|
|
|
69
69
|
left_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
70
70
|
|
|
71
71
|
shortcuts = {
|
|
72
|
-
"Ctrl + 1": "
|
|
73
|
-
"Ctrl + 2": "
|
|
74
|
-
"Ctrl + 3": "
|
|
75
|
-
"[": "
|
|
76
|
-
"]": "
|
|
77
|
-
"{": "
|
|
78
|
-
"}": "
|
|
72
|
+
"Ctrl + 1": "View: overlaid",
|
|
73
|
+
"Ctrl + 2": "View: side by side",
|
|
74
|
+
"Ctrl + 3": "View: top-bottom",
|
|
75
|
+
"[": "Increase brush size",
|
|
76
|
+
"]": "Decrease brush size",
|
|
77
|
+
"{": "Increase brush hardness",
|
|
78
|
+
"}": "Decrease brush hardness"
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
self.add_bold_label(right_layout, "Keyboard Shortcuts")
|
|
@@ -83,22 +83,26 @@ class ShortcutsHelp(QDialog):
|
|
|
83
83
|
right_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
84
84
|
|
|
85
85
|
mouse_controls = {
|
|
86
|
-
"Space + Drag": "
|
|
87
|
-
"Wheel": "
|
|
88
|
-
"Ctrl + Wheel": "
|
|
89
|
-
"Left Click": "brush
|
|
86
|
+
"Space + Drag": "Move",
|
|
87
|
+
"Wheel": "Zoom in/out",
|
|
88
|
+
"Ctrl + Wheel": "Adjust brush size",
|
|
89
|
+
"Left Click": "Use brush to copy from selected layer to master",
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
spacer = QLabel("")
|
|
93
|
+
spacer.setFixedHeight(10)
|
|
94
|
+
right_layout.addWidget(spacer)
|
|
92
95
|
self.add_bold_label(right_layout, "Mouse Controls")
|
|
93
96
|
for k, v in mouse_controls.items():
|
|
94
97
|
right_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
95
98
|
|
|
96
99
|
touchpad_controls = {
|
|
97
|
-
"Two
|
|
98
|
-
"Pinch": "
|
|
99
|
-
"Ctrl + two fingers": "zoom in/out",
|
|
100
|
+
"Two-finger drag": "Move",
|
|
101
|
+
"Pinch two fingers": "Zoom in/out"
|
|
100
102
|
}
|
|
101
|
-
|
|
103
|
+
spacer = QLabel("")
|
|
104
|
+
spacer.setFixedHeight(10)
|
|
105
|
+
right_layout.addWidget(spacer)
|
|
102
106
|
self.add_bold_label(right_layout, "Touchpad Controls")
|
|
103
107
|
for k, v in touchpad_controls.items():
|
|
104
108
|
right_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, R0904, R0915, E0611, R0902, R0911, R0914, E1003
|
|
2
2
|
from PySide6.QtCore import Qt, Signal, QEvent, QRectF
|
|
3
|
-
from PySide6.QtGui import
|
|
4
|
-
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame
|
|
5
|
-
from .. config.gui_constants import gui_constants
|
|
3
|
+
from PySide6.QtGui import QCursor
|
|
4
|
+
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame
|
|
6
5
|
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
7
6
|
|
|
8
7
|
|
|
@@ -11,6 +10,7 @@ class ImageGraphicsView(ImageGraphicsViewBase):
|
|
|
11
10
|
mouse_moved = Signal(QEvent)
|
|
12
11
|
mouse_released = Signal(QEvent)
|
|
13
12
|
gesture_event = Signal(QEvent)
|
|
13
|
+
wheel_event = Signal(QEvent)
|
|
14
14
|
|
|
15
15
|
# pylint: disable=C0103
|
|
16
16
|
def event(self, event):
|
|
@@ -29,6 +29,10 @@ class ImageGraphicsView(ImageGraphicsViewBase):
|
|
|
29
29
|
def mouseReleaseEvent(self, event):
|
|
30
30
|
self.mouse_released.emit(event)
|
|
31
31
|
super().mouseReleaseEvent(event)
|
|
32
|
+
|
|
33
|
+
def wheelEvent(self, event):
|
|
34
|
+
self.wheel_event.emit(event)
|
|
35
|
+
event.accept()
|
|
32
36
|
# pylint: enable=C0103
|
|
33
37
|
|
|
34
38
|
|
|
@@ -46,7 +50,6 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
46
50
|
self._connect_signals()
|
|
47
51
|
self.panning_current = False
|
|
48
52
|
self.brush_cursor = None
|
|
49
|
-
self.setup_brush_cursor()
|
|
50
53
|
self.setFocusPolicy(Qt.StrongFocus)
|
|
51
54
|
self.pan_start = None
|
|
52
55
|
self.pinch_start_scale = None
|
|
@@ -55,15 +58,13 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
55
58
|
self.current_view.setFocusPolicy(Qt.NoFocus)
|
|
56
59
|
self.master_view.setFocusPolicy(Qt.NoFocus)
|
|
57
60
|
self.current_brush_cursor = None
|
|
58
|
-
self.setup_current_brush_cursor()
|
|
59
61
|
|
|
60
62
|
def setup_layout(self):
|
|
61
|
-
"""To be implemented by subclasses - creates layout and adds widgets"""
|
|
62
63
|
raise NotImplementedError("Subclasses must implement setup_layout")
|
|
63
64
|
|
|
64
65
|
def create_pixmaps(self):
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
66
|
+
self.pixmap_item_current = self.create_pixmap(self.current_scene)
|
|
67
|
+
self.pixmap_item_master = self.create_pixmap(self.master_scene)
|
|
67
68
|
|
|
68
69
|
def _connect_signals(self):
|
|
69
70
|
self.current_view.mouse_pressed.connect(self.handle_current_mouse_press)
|
|
@@ -82,6 +83,8 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
82
83
|
self.current_view.horizontalScrollBar().setValue)
|
|
83
84
|
self.master_view.verticalScrollBar().valueChanged.connect(
|
|
84
85
|
self.current_view.verticalScrollBar().setValue)
|
|
86
|
+
self.current_view.wheel_event.connect(self.handle_wheel_event)
|
|
87
|
+
self.master_view.wheel_event.connect(self.handle_wheel_event)
|
|
85
88
|
# pylint: disable=C0103, W0201
|
|
86
89
|
self.current_view.enterEvent = self.current_view_enter_event
|
|
87
90
|
self.current_view.leaveEvent = self.current_view_leave_event
|
|
@@ -92,31 +95,44 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
92
95
|
def current_view_enter_event(self, event):
|
|
93
96
|
self.activateWindow()
|
|
94
97
|
self.setFocus()
|
|
95
|
-
self.
|
|
98
|
+
if not self.empty():
|
|
99
|
+
self.update_brush_cursor()
|
|
96
100
|
super(ImageGraphicsView, self.current_view).enterEvent(event)
|
|
97
101
|
|
|
98
102
|
def current_view_leave_event(self, event):
|
|
99
|
-
self.
|
|
103
|
+
if not self.empty():
|
|
104
|
+
self.update_brush_cursor()
|
|
100
105
|
super(ImageGraphicsView, self.current_view).leaveEvent(event)
|
|
101
106
|
|
|
102
107
|
def master_view_enter_event(self, event):
|
|
103
108
|
self.activateWindow()
|
|
104
109
|
self.setFocus()
|
|
105
|
-
self.
|
|
110
|
+
if not self.empty():
|
|
111
|
+
self.update_brush_cursor()
|
|
106
112
|
super(ImageGraphicsView, self.master_view).enterEvent(event)
|
|
107
113
|
|
|
108
114
|
def master_view_leave_event(self, event):
|
|
109
|
-
self.
|
|
115
|
+
if not self.empty():
|
|
116
|
+
self.update_brush_cursor()
|
|
110
117
|
super(ImageGraphicsView, self.master_view).leaveEvent(event)
|
|
111
118
|
|
|
112
119
|
def get_master_view(self):
|
|
113
120
|
return self.master_view
|
|
114
121
|
|
|
122
|
+
def get_current_view(self):
|
|
123
|
+
return self.current_view
|
|
124
|
+
|
|
115
125
|
def get_master_scene(self):
|
|
116
126
|
return self.master_scene
|
|
117
127
|
|
|
128
|
+
def get_current_scene(self):
|
|
129
|
+
return self.current_scene
|
|
130
|
+
|
|
118
131
|
def get_master_pixmap(self):
|
|
119
|
-
return self.
|
|
132
|
+
return self.pixmap_item_master
|
|
133
|
+
|
|
134
|
+
def get_current_pixmap(self):
|
|
135
|
+
return self.pixmap_item_current
|
|
120
136
|
|
|
121
137
|
def get_views(self):
|
|
122
138
|
return [self.master_view, self.current_view]
|
|
@@ -126,8 +142,8 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
126
142
|
|
|
127
143
|
def get_pixmaps(self):
|
|
128
144
|
return {
|
|
129
|
-
self.
|
|
130
|
-
self.
|
|
145
|
+
self.pixmap_item_master: self.master_view,
|
|
146
|
+
self.pixmap_item_current: self.current_view
|
|
131
147
|
}
|
|
132
148
|
|
|
133
149
|
# pylint: disable=C0103
|
|
@@ -153,22 +169,34 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
153
169
|
def enterEvent(self, event):
|
|
154
170
|
self.activateWindow()
|
|
155
171
|
self.setFocus()
|
|
156
|
-
if
|
|
172
|
+
if self.empty():
|
|
173
|
+
self.setCursor(Qt.ArrowCursor)
|
|
174
|
+
self.master_view.setCursor(Qt.ArrowCursor)
|
|
175
|
+
self.current_view.setCursor(Qt.ArrowCursor)
|
|
176
|
+
else:
|
|
157
177
|
if self.space_pressed:
|
|
158
178
|
self.master_view.setCursor(Qt.OpenHandCursor)
|
|
179
|
+
self.current_view.setCursor(Qt.OpenHandCursor)
|
|
159
180
|
else:
|
|
181
|
+
if self.brush_cursor is None or self.current_brush_cursor is None:
|
|
182
|
+
self.setup_brush_cursor()
|
|
160
183
|
self.master_view.setCursor(Qt.BlankCursor)
|
|
161
|
-
|
|
162
|
-
|
|
184
|
+
self.current_view.setCursor(Qt.BlankCursor)
|
|
185
|
+
self.brush_cursor.show()
|
|
163
186
|
super().enterEvent(event)
|
|
164
187
|
|
|
165
188
|
def leaveEvent(self, event):
|
|
166
|
-
if self.
|
|
189
|
+
if self.empty():
|
|
190
|
+
self.setCursor(Qt.ArrowCursor)
|
|
191
|
+
self.master_view.setCursor(Qt.ArrowCursor)
|
|
192
|
+
self.current_view.setCursor(Qt.ArrowCursor)
|
|
193
|
+
else:
|
|
194
|
+
if self.brush_cursor is None or self.current_brush_cursor is None:
|
|
195
|
+
self.setup_brush_cursor()
|
|
167
196
|
self.brush_cursor.hide()
|
|
168
|
-
if self.current_brush_cursor:
|
|
169
197
|
self.current_brush_cursor.hide()
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
self.master_view.setCursor(Qt.ArrowCursor)
|
|
199
|
+
self.current_view.setCursor(Qt.ArrowCursor)
|
|
172
200
|
super().leaveEvent(event)
|
|
173
201
|
|
|
174
202
|
def keyPressEvent(self, event):
|
|
@@ -182,97 +210,98 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
182
210
|
self.update_brush_cursor()
|
|
183
211
|
# pylint: enable=C0103
|
|
184
212
|
|
|
213
|
+
# pylint: enable=R0801
|
|
214
|
+
def handle_wheel_event(self, event):
|
|
215
|
+
if self.empty() or self.gesture_active:
|
|
216
|
+
return
|
|
217
|
+
if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
|
|
218
|
+
if self.control_pressed:
|
|
219
|
+
self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
|
|
220
|
+
else:
|
|
221
|
+
if event.angleDelta().y() > 0: # Zoom in
|
|
222
|
+
self.zoom_in()
|
|
223
|
+
else: # Zoom out
|
|
224
|
+
self.zoom_out()
|
|
225
|
+
else: # Touchpad event - handle scrolling
|
|
226
|
+
if not self.control_pressed:
|
|
227
|
+
delta = event.pixelDelta() or event.angleDelta() / 8
|
|
228
|
+
if delta:
|
|
229
|
+
self.scroll_view(self.master_view, delta.x(), delta.y())
|
|
230
|
+
self.scroll_view(self.current_view, delta.x(), delta.y())
|
|
231
|
+
# pylint: disable=R0801
|
|
232
|
+
|
|
233
|
+
def _apply_zoom_to_view(self, view, factor):
|
|
234
|
+
view.scale(factor, factor)
|
|
235
|
+
other_view = self.current_view if view == self.master_view else self.master_view
|
|
236
|
+
other_view.resetTransform()
|
|
237
|
+
other_view.scale(self.zoom_factor(), self.zoom_factor())
|
|
238
|
+
|
|
239
|
+
def show_master(self):
|
|
240
|
+
self.pixmap_item_master.setVisible(True)
|
|
241
|
+
self.pixmap_item_current.setVisible(True)
|
|
242
|
+
|
|
243
|
+
def show_current(self):
|
|
244
|
+
self.pixmap_item_master.setVisible(True)
|
|
245
|
+
self.pixmap_item_current.setVisible(True)
|
|
246
|
+
|
|
185
247
|
def setup_brush_cursor(self):
|
|
186
248
|
super().setup_brush_cursor()
|
|
187
249
|
self.setup_current_brush_cursor()
|
|
188
|
-
self.update_cursor_pen_width()
|
|
189
250
|
|
|
190
251
|
def setup_current_brush_cursor(self):
|
|
191
252
|
if not self.brush:
|
|
192
253
|
return
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
self.current_scene.removeItem(item)
|
|
196
|
-
pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
|
|
197
|
-
pen = QPen(QColor(255, 0, 0), pen_width)
|
|
198
|
-
brush = Qt.NoBrush
|
|
199
|
-
self.current_brush_cursor = self.current_scene.addEllipse(
|
|
200
|
-
0, 0, self.brush.size, self.brush.size, pen, brush)
|
|
201
|
-
self.current_brush_cursor.setZValue(1000)
|
|
202
|
-
self.current_brush_cursor.hide()
|
|
203
|
-
|
|
204
|
-
def update_current_brush_cursor(self, scene_pos):
|
|
205
|
-
if not self.current_brush_cursor or not self.isVisible():
|
|
206
|
-
return
|
|
207
|
-
size = self.brush.size
|
|
208
|
-
radius = size / 2
|
|
209
|
-
self.current_brush_cursor.setRect(
|
|
210
|
-
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
211
|
-
if self.brush_cursor and self.brush_cursor.isVisible():
|
|
212
|
-
self.current_brush_cursor.show()
|
|
213
|
-
else:
|
|
214
|
-
self.current_brush_cursor.hide()
|
|
254
|
+
self.current_brush_cursor = self.create_alt_circle(
|
|
255
|
+
self.get_current_scene(), line_style=Qt.SolidLine)
|
|
215
256
|
|
|
216
257
|
def update_cursor_pen_width(self):
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
current_pen = self.current_brush_cursor.pen()
|
|
224
|
-
current_pen.setWidthF(pen_width)
|
|
225
|
-
self.current_brush_cursor.setPen(current_pen)
|
|
258
|
+
pen_width = super().update_cursor_pen_width()
|
|
259
|
+
if self.current_brush_cursor:
|
|
260
|
+
current_pen = self.current_brush_cursor.pen()
|
|
261
|
+
current_pen.setWidthF(pen_width)
|
|
262
|
+
self.current_brush_cursor.setPen(current_pen)
|
|
263
|
+
return pen_width
|
|
226
264
|
|
|
227
265
|
def update_brush_cursor(self):
|
|
228
266
|
if self.empty():
|
|
229
267
|
return
|
|
268
|
+
if self.brush_cursor is None or self.current_brush_cursor is None:
|
|
269
|
+
self.setup_brush_cursor()
|
|
230
270
|
self.update_cursor_pen_width()
|
|
271
|
+
if self.space_pressed:
|
|
272
|
+
cursor_style = Qt.OpenHandCursor if not self.scrolling else Qt.ClosedHandCursor
|
|
273
|
+
self.master_view.setCursor(cursor_style)
|
|
274
|
+
self.current_view.setCursor(cursor_style)
|
|
275
|
+
self.brush_cursor.hide()
|
|
276
|
+
self.current_brush_cursor.hide()
|
|
277
|
+
return
|
|
278
|
+
self.master_view.setCursor(Qt.BlankCursor)
|
|
279
|
+
self.current_view.setCursor(Qt.BlankCursor)
|
|
231
280
|
mouse_pos_global = QCursor.pos()
|
|
232
281
|
mouse_pos_current = self.current_view.mapFromGlobal(mouse_pos_global)
|
|
233
282
|
mouse_pos_master = self.master_view.mapFromGlobal(mouse_pos_global)
|
|
234
283
|
current_has_mouse = self.current_view.rect().contains(mouse_pos_current)
|
|
235
284
|
master_has_mouse = self.master_view.rect().contains(mouse_pos_master)
|
|
285
|
+
self.current_brush_cursor.hide()
|
|
236
286
|
if master_has_mouse:
|
|
287
|
+
if self.cursor_style == 'preview':
|
|
288
|
+
self.brush_preview.show()
|
|
237
289
|
super().update_brush_cursor()
|
|
238
290
|
self.sync_current_cursor_with_master()
|
|
239
|
-
if self.space_pressed:
|
|
240
|
-
cursor_style = Qt.OpenHandCursor if not self.scrolling else Qt.ClosedHandCursor
|
|
241
|
-
self.master_view.setCursor(cursor_style)
|
|
242
|
-
self.current_view.setCursor(cursor_style)
|
|
243
|
-
else:
|
|
244
|
-
self.master_view.setCursor(Qt.BlankCursor)
|
|
245
|
-
self.current_view.setCursor(Qt.BlankCursor)
|
|
246
291
|
elif current_has_mouse:
|
|
292
|
+
self.brush_preview.hide()
|
|
247
293
|
scene_pos = self.current_view.mapToScene(mouse_pos_current)
|
|
248
294
|
size = self.brush.size
|
|
249
295
|
radius = size / 2
|
|
250
296
|
self.current_brush_cursor.setRect(
|
|
251
|
-
scene_pos.x() - radius,
|
|
252
|
-
scene_pos.y() - radius,
|
|
253
|
-
size, size
|
|
254
|
-
)
|
|
297
|
+
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
255
298
|
self.current_brush_cursor.show()
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
scene_pos.y() - radius,
|
|
260
|
-
size, size
|
|
261
|
-
)
|
|
262
|
-
self.brush_cursor.show()
|
|
263
|
-
if self.space_pressed:
|
|
264
|
-
cursor_style = Qt.OpenHandCursor \
|
|
265
|
-
if not self.panning_current else Qt.ClosedHandCursor
|
|
266
|
-
self.current_view.setCursor(cursor_style)
|
|
267
|
-
self.master_view.setCursor(cursor_style)
|
|
268
|
-
else:
|
|
269
|
-
self.current_view.setCursor(Qt.BlankCursor)
|
|
270
|
-
self.master_view.setCursor(Qt.BlankCursor)
|
|
299
|
+
self.brush_cursor.setRect(
|
|
300
|
+
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
301
|
+
self.brush_cursor.show()
|
|
271
302
|
else:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if self.current_brush_cursor:
|
|
275
|
-
self.current_brush_cursor.hide()
|
|
303
|
+
self.brush_cursor.hide()
|
|
304
|
+
self.current_brush_cursor.hide()
|
|
276
305
|
self.master_view.setCursor(Qt.ArrowCursor)
|
|
277
306
|
self.current_view.setCursor(Qt.ArrowCursor)
|
|
278
307
|
|
|
@@ -316,103 +345,55 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
316
345
|
self.handle_pinch_gesture(pinch_gesture)
|
|
317
346
|
event.accept()
|
|
318
347
|
|
|
319
|
-
def handle_pinch_gesture(self, pinch):
|
|
320
|
-
if pinch.state() == Qt.GestureStarted:
|
|
321
|
-
self.pinch_start_scale = self.zoom_factor()
|
|
322
|
-
self.pinch_center_view = pinch.centerPoint()
|
|
323
|
-
self.pinch_center_scene = self.master_view.mapToScene(self.pinch_center_view.toPoint())
|
|
324
|
-
elif pinch.state() == Qt.GestureUpdated:
|
|
325
|
-
new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
|
|
326
|
-
new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
|
|
327
|
-
if abs(new_scale - self.zoom_factor()) > 0.01:
|
|
328
|
-
self.set_zoom_factor(new_scale)
|
|
329
|
-
self._apply_zoom()
|
|
330
|
-
new_center = self.master_view.mapToScene(self.pinch_center_view.toPoint())
|
|
331
|
-
delta = self.pinch_center_scene - new_center
|
|
332
|
-
h_scroll = self.master_view.horizontalScrollBar().value()
|
|
333
|
-
v_scroll = self.master_view.verticalScrollBar().value()
|
|
334
|
-
self.master_view.horizontalScrollBar().setValue(
|
|
335
|
-
h_scroll + int(delta.x() * self.zoom_factor()))
|
|
336
|
-
self.master_view.verticalScrollBar().setValue(
|
|
337
|
-
v_scroll + int(delta.y() * self.zoom_factor()))
|
|
338
|
-
|
|
339
348
|
def set_master_image(self, qimage):
|
|
340
349
|
self.status.set_master_image(qimage)
|
|
341
350
|
pixmap = self.status.pixmap_master
|
|
342
|
-
img_width, img_height = pixmap.width(), pixmap.height()
|
|
343
351
|
self.master_view.setSceneRect(QRectF(pixmap.rect()))
|
|
344
|
-
self.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
self.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
+
self.pixmap_item_master.setPixmap(pixmap)
|
|
353
|
+
img_width, img_height = pixmap.width(), pixmap.height()
|
|
354
|
+
self.set_max_min_scales(img_width, img_height)
|
|
355
|
+
view_rect = self.master_view.viewport().rect()
|
|
356
|
+
scale_x = view_rect.width() / img_width
|
|
357
|
+
scale_y = view_rect.height() / img_height
|
|
358
|
+
scale_factor = min(scale_x, scale_y)
|
|
359
|
+
scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
|
|
360
|
+
self.set_zoom_factor(scale_factor)
|
|
352
361
|
self.master_view.resetTransform()
|
|
353
|
-
self.
|
|
354
|
-
self.master_view.centerOn(self.
|
|
362
|
+
self.master_view.scale(scale_factor, scale_factor)
|
|
363
|
+
self.master_view.centerOn(self.pixmap_item_master)
|
|
355
364
|
center = self.master_scene.sceneRect().center()
|
|
356
365
|
self.brush_preview.setPos(max(0, min(center.x(), img_width)),
|
|
357
366
|
max(0, min(center.y(), img_height)))
|
|
358
|
-
self.master_scene.setSceneRect(QRectF(self.
|
|
367
|
+
self.master_scene.setSceneRect(QRectF(self.pixmap_item_master.boundingRect()))
|
|
368
|
+
self.center_image(self.master_view)
|
|
369
|
+
self.update_cursor_pen_width()
|
|
359
370
|
|
|
360
371
|
def set_current_image(self, qimage):
|
|
361
372
|
self.status.set_current_image(qimage)
|
|
362
373
|
pixmap = self.status.pixmap_current
|
|
363
374
|
self.current_scene.setSceneRect(QRectF(pixmap.rect()))
|
|
364
|
-
self.
|
|
375
|
+
self.pixmap_item_current.setPixmap(pixmap)
|
|
365
376
|
self.current_view.resetTransform()
|
|
366
|
-
self.
|
|
367
|
-
|
|
368
|
-
self.
|
|
377
|
+
self.master_view.scale(self.zoom_factor(), self.zoom_factor())
|
|
378
|
+
self.current_scene.setSceneRect(QRectF(self.pixmap_item_current.boundingRect()))
|
|
379
|
+
self.center_image(self.current_view)
|
|
369
380
|
|
|
370
|
-
def
|
|
381
|
+
def arrange_images(self):
|
|
371
382
|
if self.status.empty():
|
|
372
383
|
return
|
|
373
|
-
if self.
|
|
384
|
+
if self.pixmap_item_master.pixmap().height() == 0:
|
|
374
385
|
self.update_master_display()
|
|
375
386
|
self.update_current_display()
|
|
376
387
|
self.reset_zoom()
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if not self.status.empty():
|
|
381
|
-
master_qimage = self.numpy_to_qimage(self.master_layer())
|
|
382
|
-
if master_qimage:
|
|
383
|
-
pixmap = QPixmap.fromImage(master_qimage)
|
|
384
|
-
self.master_pixmap_item.setPixmap(pixmap)
|
|
385
|
-
self.master_scene.setSceneRect(QRectF(pixmap.rect()))
|
|
386
|
-
self._arrange_images()
|
|
387
|
-
|
|
388
|
-
def update_current_display(self):
|
|
389
|
-
if not self.status.empty() and self.number_of_layers() > 0:
|
|
390
|
-
current_qimage = self.numpy_to_qimage(self.current_layer())
|
|
391
|
-
if current_qimage:
|
|
392
|
-
pixmap = QPixmap.fromImage(current_qimage)
|
|
393
|
-
self.current_pixmap_item.setPixmap(pixmap)
|
|
394
|
-
self.current_scene.setSceneRect(QRectF(pixmap.rect()))
|
|
395
|
-
self._arrange_images()
|
|
396
|
-
|
|
397
|
-
def _apply_zoom(self):
|
|
398
|
-
if not self.current_pixmap_item.pixmap().isNull():
|
|
399
|
-
self.current_view.resetTransform()
|
|
400
|
-
self.current_view.scale(self.zoom_factor(), self.zoom_factor())
|
|
401
|
-
# self.current_view.centerOn(self.current_pixmap_item)
|
|
402
|
-
if not self.master_pixmap_item.pixmap().isNull():
|
|
403
|
-
self.master_view.resetTransform()
|
|
404
|
-
self.master_view.scale(self.zoom_factor(), self.zoom_factor())
|
|
405
|
-
# self.master_view.centerOn(self.master_pixmap_item)
|
|
406
|
-
|
|
407
|
-
def set_brush(self, brush):
|
|
408
|
-
super().set_brush(brush)
|
|
409
|
-
if self.brush_cursor:
|
|
410
|
-
self.master_scene.removeItem(self.brush_cursor)
|
|
411
|
-
self.setup_brush_cursor()
|
|
412
|
-
self.setup_current_brush_cursor()
|
|
388
|
+
else:
|
|
389
|
+
self.center_image(self.master_view)
|
|
390
|
+
self.apply_zoom()
|
|
413
391
|
|
|
414
392
|
def clear_image(self):
|
|
415
393
|
super().clear_image()
|
|
394
|
+
self.setCursor(Qt.ArrowCursor)
|
|
395
|
+
self.master_view.setCursor(Qt.ArrowCursor)
|
|
396
|
+
self.current_view.setCursor(Qt.ArrowCursor)
|
|
416
397
|
if self.current_brush_cursor:
|
|
417
398
|
self.current_scene.removeItem(self.current_brush_cursor)
|
|
418
399
|
self.current_brush_cursor = None
|
|
@@ -425,31 +406,13 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
425
406
|
size = self.brush.size
|
|
426
407
|
radius = size / 2
|
|
427
408
|
self.current_brush_cursor.setRect(
|
|
428
|
-
scene_pos.x() - radius,
|
|
429
|
-
|
|
430
|
-
size, size
|
|
431
|
-
)
|
|
409
|
+
scene_pos.x() - radius, scene_pos.y() - radius,
|
|
410
|
+
size, size)
|
|
432
411
|
if self.brush_cursor.isVisible():
|
|
433
412
|
self.current_brush_cursor.show()
|
|
434
413
|
else:
|
|
435
414
|
self.current_brush_cursor.hide()
|
|
436
415
|
|
|
437
|
-
def zoom_in(self):
|
|
438
|
-
super().zoom_in()
|
|
439
|
-
self.update_cursor_pen_width()
|
|
440
|
-
|
|
441
|
-
def zoom_out(self):
|
|
442
|
-
super().zoom_out()
|
|
443
|
-
self.update_cursor_pen_width()
|
|
444
|
-
|
|
445
|
-
def reset_zoom(self):
|
|
446
|
-
super().reset_zoom()
|
|
447
|
-
self.update_cursor_pen_width()
|
|
448
|
-
|
|
449
|
-
def actual_size(self):
|
|
450
|
-
super().actual_size()
|
|
451
|
-
self.update_cursor_pen_width()
|
|
452
|
-
|
|
453
416
|
|
|
454
417
|
class SideBySideView(DoubleViewBase):
|
|
455
418
|
def setup_layout(self):
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E1101, W0718
|
|
2
|
+
import traceback
|
|
3
|
+
import cv2
|
|
4
|
+
from .. config.gui_constants import gui_constants
|
|
5
|
+
from .layer_collection import LayerCollectionHandler
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TransfromationManager(LayerCollectionHandler):
|
|
9
|
+
def __init__(self, editor):
|
|
10
|
+
super().__init__(editor.layer_collection)
|
|
11
|
+
self.editor = editor
|
|
12
|
+
|
|
13
|
+
def transform(self, transf_func, label, undoable=True):
|
|
14
|
+
if self.has_no_master_layer():
|
|
15
|
+
return
|
|
16
|
+
if undoable:
|
|
17
|
+
try:
|
|
18
|
+
undo = self.editor.undo_manager
|
|
19
|
+
undo.x_start, undo.x_stop = 0, 1
|
|
20
|
+
undo.y_start, undo.y_stop = 0, 1
|
|
21
|
+
undo.save_undo_state(self.editor.master_layer(), label)
|
|
22
|
+
except Exception as e:
|
|
23
|
+
traceback.print_tb(e.__traceback__)
|
|
24
|
+
self.set_master_layer(transf_func(self.master_layer()))
|
|
25
|
+
self.set_layer_stack([transf_func(layer) for layer in self.layer_stack()])
|
|
26
|
+
self.copy_master_layer()
|
|
27
|
+
self.editor.image_viewer.update_master_display()
|
|
28
|
+
self.editor.image_viewer.update_current_display()
|
|
29
|
+
self.editor.image_viewer.refresh_display()
|
|
30
|
+
self.editor.display_manager.update_thumbnails()
|
|
31
|
+
self.editor.mark_as_modified()
|
|
32
|
+
|
|
33
|
+
def rotate_90_cw(self, undoable=True):
|
|
34
|
+
self.transform(lambda img: cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE),
|
|
35
|
+
gui_constants.ROTATE_90_CW_LABEL, undoable)
|
|
36
|
+
|
|
37
|
+
def rotate_90_ccw(self, undoable=True):
|
|
38
|
+
self.transform(lambda img: cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE),
|
|
39
|
+
gui_constants.ROTATE_90_CCW_LABEL, undoable)
|
|
40
|
+
|
|
41
|
+
def rotate_180(self, undoable=True):
|
|
42
|
+
self.transform(lambda img: cv2.rotate(img, cv2.ROTATE_180),
|
|
43
|
+
gui_constants.ROTATE_180_LABEL, undoable)
|