shinestacker 1.4.0__py3-none-any.whl → 1.5.0__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/main.py +1 -1
- shinestacker/config/gui_constants.py +5 -0
- shinestacker/retouch/base_filter.py +8 -10
- shinestacker/retouch/brush_preview.py +0 -1
- shinestacker/retouch/display_manager.py +42 -42
- shinestacker/retouch/image_editor_ui.py +52 -31
- shinestacker/retouch/image_view_status.py +4 -0
- shinestacker/retouch/image_viewer.py +6 -0
- shinestacker/retouch/layer_collection.py +3 -0
- shinestacker/retouch/overlaid_view.py +87 -84
- shinestacker/retouch/sidebyside_view.py +113 -115
- shinestacker/retouch/transformation_manager.py +43 -0
- shinestacker/retouch/undo_manager.py +22 -3
- shinestacker/retouch/view_strategy.py +135 -44
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/METADATA +7 -7
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/RECORD +21 -20
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
3
|
+
from PySide6.QtGui import QPen, QColor, QCursor
|
|
4
4
|
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame, QGraphicsEllipseItem
|
|
5
5
|
from .. config.gui_constants import gui_constants
|
|
6
6
|
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
@@ -11,6 +11,7 @@ class ImageGraphicsView(ImageGraphicsViewBase):
|
|
|
11
11
|
mouse_moved = Signal(QEvent)
|
|
12
12
|
mouse_released = Signal(QEvent)
|
|
13
13
|
gesture_event = Signal(QEvent)
|
|
14
|
+
wheel_event = Signal(QEvent)
|
|
14
15
|
|
|
15
16
|
# pylint: disable=C0103
|
|
16
17
|
def event(self, event):
|
|
@@ -29,6 +30,10 @@ class ImageGraphicsView(ImageGraphicsViewBase):
|
|
|
29
30
|
def mouseReleaseEvent(self, event):
|
|
30
31
|
self.mouse_released.emit(event)
|
|
31
32
|
super().mouseReleaseEvent(event)
|
|
33
|
+
|
|
34
|
+
def wheelEvent(self, event):
|
|
35
|
+
self.wheel_event.emit(event)
|
|
36
|
+
event.accept()
|
|
32
37
|
# pylint: enable=C0103
|
|
33
38
|
|
|
34
39
|
|
|
@@ -46,7 +51,6 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
46
51
|
self._connect_signals()
|
|
47
52
|
self.panning_current = False
|
|
48
53
|
self.brush_cursor = None
|
|
49
|
-
self.setup_brush_cursor()
|
|
50
54
|
self.setFocusPolicy(Qt.StrongFocus)
|
|
51
55
|
self.pan_start = None
|
|
52
56
|
self.pinch_start_scale = None
|
|
@@ -58,12 +62,11 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
58
62
|
self.setup_current_brush_cursor()
|
|
59
63
|
|
|
60
64
|
def setup_layout(self):
|
|
61
|
-
"""To be implemented by subclasses - creates layout and adds widgets"""
|
|
62
65
|
raise NotImplementedError("Subclasses must implement setup_layout")
|
|
63
66
|
|
|
64
67
|
def create_pixmaps(self):
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
68
|
+
self.pixmap_item_current = self.create_pixmap(self.current_scene)
|
|
69
|
+
self.pixmap_item_master = self.create_pixmap(self.master_scene)
|
|
67
70
|
|
|
68
71
|
def _connect_signals(self):
|
|
69
72
|
self.current_view.mouse_pressed.connect(self.handle_current_mouse_press)
|
|
@@ -82,6 +85,8 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
82
85
|
self.current_view.horizontalScrollBar().setValue)
|
|
83
86
|
self.master_view.verticalScrollBar().valueChanged.connect(
|
|
84
87
|
self.current_view.verticalScrollBar().setValue)
|
|
88
|
+
self.current_view.wheel_event.connect(self.handle_wheel_event)
|
|
89
|
+
self.master_view.wheel_event.connect(self.handle_wheel_event)
|
|
85
90
|
# pylint: disable=C0103, W0201
|
|
86
91
|
self.current_view.enterEvent = self.current_view_enter_event
|
|
87
92
|
self.current_view.leaveEvent = self.current_view_leave_event
|
|
@@ -92,31 +97,44 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
92
97
|
def current_view_enter_event(self, event):
|
|
93
98
|
self.activateWindow()
|
|
94
99
|
self.setFocus()
|
|
95
|
-
self.
|
|
100
|
+
if not self.empty():
|
|
101
|
+
self.update_brush_cursor()
|
|
96
102
|
super(ImageGraphicsView, self.current_view).enterEvent(event)
|
|
97
103
|
|
|
98
104
|
def current_view_leave_event(self, event):
|
|
99
|
-
self.
|
|
105
|
+
if not self.empty():
|
|
106
|
+
self.update_brush_cursor()
|
|
100
107
|
super(ImageGraphicsView, self.current_view).leaveEvent(event)
|
|
101
108
|
|
|
102
109
|
def master_view_enter_event(self, event):
|
|
103
110
|
self.activateWindow()
|
|
104
111
|
self.setFocus()
|
|
105
|
-
self.
|
|
112
|
+
if not self.empty():
|
|
113
|
+
self.update_brush_cursor()
|
|
106
114
|
super(ImageGraphicsView, self.master_view).enterEvent(event)
|
|
107
115
|
|
|
108
116
|
def master_view_leave_event(self, event):
|
|
109
|
-
self.
|
|
117
|
+
if not self.empty():
|
|
118
|
+
self.update_brush_cursor()
|
|
110
119
|
super(ImageGraphicsView, self.master_view).leaveEvent(event)
|
|
111
120
|
|
|
112
121
|
def get_master_view(self):
|
|
113
122
|
return self.master_view
|
|
114
123
|
|
|
124
|
+
def get_current_view(self):
|
|
125
|
+
return self.current_view
|
|
126
|
+
|
|
115
127
|
def get_master_scene(self):
|
|
116
128
|
return self.master_scene
|
|
117
129
|
|
|
130
|
+
def get_current_scene(self):
|
|
131
|
+
return self.current_scene
|
|
132
|
+
|
|
118
133
|
def get_master_pixmap(self):
|
|
119
|
-
return self.
|
|
134
|
+
return self.pixmap_item_master
|
|
135
|
+
|
|
136
|
+
def get_current_pixmap(self):
|
|
137
|
+
return self.pixmap_item_current
|
|
120
138
|
|
|
121
139
|
def get_views(self):
|
|
122
140
|
return [self.master_view, self.current_view]
|
|
@@ -126,8 +144,8 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
126
144
|
|
|
127
145
|
def get_pixmaps(self):
|
|
128
146
|
return {
|
|
129
|
-
self.
|
|
130
|
-
self.
|
|
147
|
+
self.pixmap_item_master: self.master_view,
|
|
148
|
+
self.pixmap_item_current: self.current_view
|
|
131
149
|
}
|
|
132
150
|
|
|
133
151
|
# pylint: disable=C0103
|
|
@@ -153,22 +171,33 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
153
171
|
def enterEvent(self, event):
|
|
154
172
|
self.activateWindow()
|
|
155
173
|
self.setFocus()
|
|
156
|
-
if
|
|
174
|
+
if self.empty():
|
|
175
|
+
self.setCursor(Qt.ArrowCursor)
|
|
176
|
+
self.master_view.setCursor(Qt.ArrowCursor)
|
|
177
|
+
self.current_view.setCursor(Qt.ArrowCursor)
|
|
178
|
+
else:
|
|
157
179
|
if self.space_pressed:
|
|
158
180
|
self.master_view.setCursor(Qt.OpenHandCursor)
|
|
181
|
+
self.current_view.setCursor(Qt.OpenHandCursor)
|
|
159
182
|
else:
|
|
160
183
|
self.master_view.setCursor(Qt.BlankCursor)
|
|
184
|
+
self.current_view.setCursor(Qt.BlankCursor)
|
|
161
185
|
if self.brush_cursor:
|
|
162
186
|
self.brush_cursor.show()
|
|
163
187
|
super().enterEvent(event)
|
|
164
188
|
|
|
165
189
|
def leaveEvent(self, event):
|
|
166
|
-
if self.
|
|
167
|
-
self.
|
|
168
|
-
|
|
169
|
-
self.
|
|
170
|
-
|
|
171
|
-
|
|
190
|
+
if self.empty():
|
|
191
|
+
self.setCursor(Qt.ArrowCursor)
|
|
192
|
+
self.master_view.setCursor(Qt.ArrowCursor)
|
|
193
|
+
self.current_view.setCursor(Qt.ArrowCursor)
|
|
194
|
+
else:
|
|
195
|
+
if self.brush_cursor is not None:
|
|
196
|
+
self.brush_cursor.hide()
|
|
197
|
+
if self.current_brush_cursor is not None:
|
|
198
|
+
self.current_brush_cursor.hide()
|
|
199
|
+
self.master_view.setCursor(Qt.ArrowCursor)
|
|
200
|
+
self.current_view.setCursor(Qt.ArrowCursor)
|
|
172
201
|
super().leaveEvent(event)
|
|
173
202
|
|
|
174
203
|
def keyPressEvent(self, event):
|
|
@@ -182,6 +211,40 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
182
211
|
self.update_brush_cursor()
|
|
183
212
|
# pylint: enable=C0103
|
|
184
213
|
|
|
214
|
+
# pylint: enable=R0801
|
|
215
|
+
def handle_wheel_event(self, event):
|
|
216
|
+
if self.empty() or self.gesture_active:
|
|
217
|
+
return
|
|
218
|
+
if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
|
|
219
|
+
if self.control_pressed:
|
|
220
|
+
self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
|
|
221
|
+
else:
|
|
222
|
+
if event.angleDelta().y() > 0: # Zoom in
|
|
223
|
+
self.zoom_in()
|
|
224
|
+
else: # Zoom out
|
|
225
|
+
self.zoom_out()
|
|
226
|
+
else: # Touchpad event - handle scrolling
|
|
227
|
+
if not self.control_pressed:
|
|
228
|
+
delta = event.pixelDelta() or event.angleDelta() / 8
|
|
229
|
+
if delta:
|
|
230
|
+
self.scroll_view(self.master_view, delta.x(), delta.y())
|
|
231
|
+
self.scroll_view(self.current_view, delta.x(), delta.y())
|
|
232
|
+
# pylint: disable=R0801
|
|
233
|
+
|
|
234
|
+
def _apply_zoom_to_view(self, view, factor):
|
|
235
|
+
view.scale(factor, factor)
|
|
236
|
+
other_view = self.current_view if view == self.master_view else self.master_view
|
|
237
|
+
other_view.resetTransform()
|
|
238
|
+
other_view.scale(self.zoom_factor(), self.zoom_factor())
|
|
239
|
+
|
|
240
|
+
def show_master(self):
|
|
241
|
+
self.pixmap_item_master.setVisible(True)
|
|
242
|
+
self.pixmap_item_current.setVisible(True)
|
|
243
|
+
|
|
244
|
+
def show_current(self):
|
|
245
|
+
self.pixmap_item_master.setVisible(True)
|
|
246
|
+
self.pixmap_item_current.setVisible(True)
|
|
247
|
+
|
|
185
248
|
def setup_brush_cursor(self):
|
|
186
249
|
super().setup_brush_cursor()
|
|
187
250
|
self.setup_current_brush_cursor()
|
|
@@ -194,7 +257,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
194
257
|
if isinstance(item, QGraphicsEllipseItem) and item != self.brush_preview:
|
|
195
258
|
self.current_scene.removeItem(item)
|
|
196
259
|
pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
|
|
197
|
-
pen = QPen(QColor(255, 0, 0), pen_width)
|
|
260
|
+
pen = QPen(QColor(255, 0, 0), pen_width, Qt.DotLine)
|
|
198
261
|
brush = Qt.NoBrush
|
|
199
262
|
self.current_brush_cursor = self.current_scene.addEllipse(
|
|
200
263
|
0, 0, self.brush.size, self.brush.size, pen, brush)
|
|
@@ -214,15 +277,12 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
214
277
|
self.current_brush_cursor.hide()
|
|
215
278
|
|
|
216
279
|
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)
|
|
280
|
+
pen_width = super().update_cursor_pen_width()
|
|
281
|
+
if self.current_brush_cursor:
|
|
282
|
+
current_pen = self.current_brush_cursor.pen()
|
|
283
|
+
current_pen.setWidthF(pen_width)
|
|
284
|
+
self.current_brush_cursor.setPen(current_pen)
|
|
285
|
+
return pen_width
|
|
226
286
|
|
|
227
287
|
def update_brush_cursor(self):
|
|
228
288
|
if self.empty():
|
|
@@ -234,6 +294,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
234
294
|
current_has_mouse = self.current_view.rect().contains(mouse_pos_current)
|
|
235
295
|
master_has_mouse = self.master_view.rect().contains(mouse_pos_master)
|
|
236
296
|
if master_has_mouse:
|
|
297
|
+
self.brush_preview.show()
|
|
237
298
|
super().update_brush_cursor()
|
|
238
299
|
self.sync_current_cursor_with_master()
|
|
239
300
|
if self.space_pressed:
|
|
@@ -244,21 +305,16 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
244
305
|
self.master_view.setCursor(Qt.BlankCursor)
|
|
245
306
|
self.current_view.setCursor(Qt.BlankCursor)
|
|
246
307
|
elif current_has_mouse:
|
|
308
|
+
self.brush_preview.hide()
|
|
247
309
|
scene_pos = self.current_view.mapToScene(mouse_pos_current)
|
|
248
310
|
size = self.brush.size
|
|
249
311
|
radius = size / 2
|
|
250
312
|
self.current_brush_cursor.setRect(
|
|
251
|
-
scene_pos.x() - radius,
|
|
252
|
-
scene_pos.y() - radius,
|
|
253
|
-
size, size
|
|
254
|
-
)
|
|
313
|
+
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
255
314
|
self.current_brush_cursor.show()
|
|
256
315
|
if self.brush_cursor:
|
|
257
316
|
self.brush_cursor.setRect(
|
|
258
|
-
scene_pos.x() - radius,
|
|
259
|
-
scene_pos.y() - radius,
|
|
260
|
-
size, size
|
|
261
|
-
)
|
|
317
|
+
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
262
318
|
self.brush_cursor.show()
|
|
263
319
|
if self.space_pressed:
|
|
264
320
|
cursor_style = Qt.OpenHandCursor \
|
|
@@ -316,93 +372,48 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
316
372
|
self.handle_pinch_gesture(pinch_gesture)
|
|
317
373
|
event.accept()
|
|
318
374
|
|
|
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
375
|
def set_master_image(self, qimage):
|
|
340
376
|
self.status.set_master_image(qimage)
|
|
341
377
|
pixmap = self.status.pixmap_master
|
|
342
|
-
img_width, img_height = pixmap.width(), pixmap.height()
|
|
343
378
|
self.master_view.setSceneRect(QRectF(pixmap.rect()))
|
|
344
|
-
self.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
self.set_max_scale(gui_constants.MAX_ZOOMED_IMG_PX_SIZE)
|
|
379
|
+
self.pixmap_item_master.setPixmap(pixmap)
|
|
380
|
+
img_width, img_height = pixmap.width(), pixmap.height()
|
|
381
|
+
self.set_max_min_scales(img_width, img_height)
|
|
348
382
|
self.set_zoom_factor(1.0)
|
|
349
|
-
self.master_view.fitInView(self.
|
|
383
|
+
self.master_view.fitInView(self.pixmap_item_master, Qt.KeepAspectRatio)
|
|
350
384
|
self.set_zoom_factor(self.get_current_scale())
|
|
351
385
|
self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
|
|
352
386
|
self.master_view.resetTransform()
|
|
353
387
|
self.master_scene.scale(self.zoom_factor(), self.zoom_factor())
|
|
354
|
-
self.master_view.centerOn(self.
|
|
388
|
+
self.master_view.centerOn(self.pixmap_item_master)
|
|
355
389
|
center = self.master_scene.sceneRect().center()
|
|
356
390
|
self.brush_preview.setPos(max(0, min(center.x(), img_width)),
|
|
357
391
|
max(0, min(center.y(), img_height)))
|
|
358
|
-
self.master_scene.setSceneRect(QRectF(self.
|
|
392
|
+
self.master_scene.setSceneRect(QRectF(self.pixmap_item_master.boundingRect()))
|
|
393
|
+
self.center_image(self.master_view)
|
|
394
|
+
self.update_cursor_pen_width()
|
|
359
395
|
|
|
360
396
|
def set_current_image(self, qimage):
|
|
361
397
|
self.status.set_current_image(qimage)
|
|
362
398
|
pixmap = self.status.pixmap_current
|
|
363
399
|
self.current_scene.setSceneRect(QRectF(pixmap.rect()))
|
|
364
|
-
self.
|
|
400
|
+
self.pixmap_item_current.setPixmap(pixmap)
|
|
365
401
|
self.current_view.resetTransform()
|
|
366
402
|
self.current_scene.scale(self.zoom_factor(), self.zoom_factor())
|
|
367
|
-
|
|
368
|
-
self.
|
|
403
|
+
self.current_scene.setSceneRect(QRectF(self.pixmap_item_current.boundingRect()))
|
|
404
|
+
self.center_image(self.current_view)
|
|
405
|
+
self.update_cursor_pen_width()
|
|
369
406
|
|
|
370
|
-
def
|
|
407
|
+
def arrange_images(self):
|
|
371
408
|
if self.status.empty():
|
|
372
409
|
return
|
|
373
|
-
if self.
|
|
410
|
+
if self.pixmap_item_master.pixmap().height() == 0:
|
|
374
411
|
self.update_master_display()
|
|
375
412
|
self.update_current_display()
|
|
376
413
|
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)
|
|
414
|
+
else:
|
|
415
|
+
self.center_image(self.master_view)
|
|
416
|
+
self.apply_zoom()
|
|
406
417
|
|
|
407
418
|
def set_brush(self, brush):
|
|
408
419
|
super().set_brush(brush)
|
|
@@ -413,6 +424,9 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
413
424
|
|
|
414
425
|
def clear_image(self):
|
|
415
426
|
super().clear_image()
|
|
427
|
+
self.setCursor(Qt.ArrowCursor)
|
|
428
|
+
self.master_view.setCursor(Qt.ArrowCursor)
|
|
429
|
+
self.current_view.setCursor(Qt.ArrowCursor)
|
|
416
430
|
if self.current_brush_cursor:
|
|
417
431
|
self.current_scene.removeItem(self.current_brush_cursor)
|
|
418
432
|
self.current_brush_cursor = None
|
|
@@ -434,22 +448,6 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
434
448
|
else:
|
|
435
449
|
self.current_brush_cursor.hide()
|
|
436
450
|
|
|
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
451
|
|
|
454
452
|
class SideBySideView(DoubleViewBase):
|
|
455
453
|
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)
|
|
@@ -6,8 +6,9 @@ from .. config.gui_constants import gui_constants
|
|
|
6
6
|
class UndoManager(QObject):
|
|
7
7
|
stack_changed = Signal(bool, str, bool, str)
|
|
8
8
|
|
|
9
|
-
def __init__(self):
|
|
9
|
+
def __init__(self, transformation_manager):
|
|
10
10
|
super().__init__()
|
|
11
|
+
self.transformation_manager = transformation_manager
|
|
11
12
|
self.x_start = None
|
|
12
13
|
self.y_start = None
|
|
13
14
|
self.x_end = None
|
|
@@ -59,7 +60,16 @@ class UndoManager(QObject):
|
|
|
59
60
|
'description': undo_state['description']
|
|
60
61
|
}
|
|
61
62
|
self.redo_stack.append(redo_state)
|
|
62
|
-
|
|
63
|
+
descr = undo_state['description']
|
|
64
|
+
if descr.startswith(gui_constants.ROTATE_LABEL):
|
|
65
|
+
if descr == gui_constants.ROTATE_90_CW_LABEL:
|
|
66
|
+
self.transformation_manager.rotate_90_ccw(False)
|
|
67
|
+
elif descr == gui_constants.ROTATE_90_CCW_LABEL:
|
|
68
|
+
self.transformation_manager.rotate_90_cw(False)
|
|
69
|
+
elif descr == gui_constants.ROTATE_180_LABEL:
|
|
70
|
+
self.transformation_manager.rotate_180(False)
|
|
71
|
+
else:
|
|
72
|
+
layer[y_start:y_end, x_start:x_end] = undo_state['master']
|
|
63
73
|
undo_desc = self.undo_stack[-1]['description'] if self.undo_stack else ""
|
|
64
74
|
redo_desc = redo_state['description']
|
|
65
75
|
self.stack_changed.emit(bool(self.undo_stack), undo_desc, bool(self.redo_stack), redo_desc)
|
|
@@ -76,7 +86,16 @@ class UndoManager(QObject):
|
|
|
76
86
|
'description': redo_state['description']
|
|
77
87
|
}
|
|
78
88
|
self.undo_stack.append(undo_state)
|
|
79
|
-
|
|
89
|
+
descr = undo_state['description']
|
|
90
|
+
if descr.startswith(gui_constants.ROTATE_LABEL):
|
|
91
|
+
if descr == gui_constants.ROTATE_90_CW_LABEL:
|
|
92
|
+
self.transformation_manager.rotate_90_cw(False)
|
|
93
|
+
elif descr == gui_constants.ROTATE_90_CCW_LABEL:
|
|
94
|
+
self.transformation_manager.rotate_90_ccw(False)
|
|
95
|
+
elif descr == gui_constants.ROTATE_180_LABEL:
|
|
96
|
+
self.transformation_manager.rotate_180(False)
|
|
97
|
+
else:
|
|
98
|
+
layer[y_start:y_end, x_start:x_end] = redo_state['master']
|
|
80
99
|
undo_desc = undo_state['description']
|
|
81
100
|
redo_desc = self.redo_stack[-1]['description'] if self.redo_stack else ""
|
|
82
101
|
self.stack_changed.emit(bool(self.undo_stack), undo_desc, bool(self.redo_stack), redo_desc)
|