shinestacker 1.3.1__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.

Files changed (38) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +198 -18
  3. shinestacker/algorithms/align_parallel.py +17 -1
  4. shinestacker/algorithms/balance.py +23 -13
  5. shinestacker/algorithms/noise_detection.py +3 -1
  6. shinestacker/algorithms/utils.py +21 -10
  7. shinestacker/algorithms/vignetting.py +2 -0
  8. shinestacker/app/main.py +1 -1
  9. shinestacker/config/gui_constants.py +7 -2
  10. shinestacker/core/core_utils.py +10 -1
  11. shinestacker/gui/action_config.py +172 -7
  12. shinestacker/gui/action_config_dialog.py +246 -285
  13. shinestacker/gui/gui_run.py +2 -2
  14. shinestacker/gui/main_window.py +14 -5
  15. shinestacker/gui/menu_manager.py +26 -2
  16. shinestacker/gui/project_controller.py +4 -0
  17. shinestacker/gui/recent_file_manager.py +93 -0
  18. shinestacker/retouch/base_filter.py +13 -15
  19. shinestacker/retouch/brush_preview.py +3 -1
  20. shinestacker/retouch/brush_tool.py +11 -11
  21. shinestacker/retouch/display_manager.py +43 -59
  22. shinestacker/retouch/image_editor_ui.py +161 -82
  23. shinestacker/retouch/image_view_status.py +65 -0
  24. shinestacker/retouch/image_viewer.py +95 -431
  25. shinestacker/retouch/io_gui_handler.py +12 -2
  26. shinestacker/retouch/layer_collection.py +3 -0
  27. shinestacker/retouch/overlaid_view.py +215 -0
  28. shinestacker/retouch/shortcuts_help.py +13 -3
  29. shinestacker/retouch/sidebyside_view.py +477 -0
  30. shinestacker/retouch/transformation_manager.py +43 -0
  31. shinestacker/retouch/undo_manager.py +22 -3
  32. shinestacker/retouch/view_strategy.py +557 -0
  33. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/METADATA +7 -7
  34. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/RECORD +38 -32
  35. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/WHEEL +0 -0
  36. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/entry_points.txt +0 -0
  37. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/licenses/LICENSE +0 -0
  38. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,477 @@
1
+ # pylint: disable=C0114, C0115, C0116, R0904, R0915, E0611, R0902, R0911, R0914, E1003
2
+ from PySide6.QtCore import Qt, Signal, QEvent, QRectF
3
+ from PySide6.QtGui import QPen, QColor, QCursor
4
+ from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame, QGraphicsEllipseItem
5
+ from .. config.gui_constants import gui_constants
6
+ from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
7
+
8
+
9
+ class ImageGraphicsView(ImageGraphicsViewBase):
10
+ mouse_pressed = Signal(QEvent)
11
+ mouse_moved = Signal(QEvent)
12
+ mouse_released = Signal(QEvent)
13
+ gesture_event = Signal(QEvent)
14
+ wheel_event = Signal(QEvent)
15
+
16
+ # pylint: disable=C0103
17
+ def event(self, event):
18
+ if event.type() == QEvent.Gesture:
19
+ self.gesture_event.emit(event)
20
+ return True
21
+ return super().event(event)
22
+
23
+ def mousePressEvent(self, event):
24
+ self.mouse_pressed.emit(event)
25
+ super().mousePressEvent(event)
26
+
27
+ def mouseMoveEvent(self, event):
28
+ self.mouse_moved.emit(event)
29
+
30
+ def mouseReleaseEvent(self, event):
31
+ self.mouse_released.emit(event)
32
+ super().mouseReleaseEvent(event)
33
+
34
+ def wheelEvent(self, event):
35
+ self.wheel_event.emit(event)
36
+ event.accept()
37
+ # pylint: enable=C0103
38
+
39
+
40
+ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
41
+ def __init__(self, layer_collection, status, parent):
42
+ ViewStrategy.__init__(self, layer_collection, status)
43
+ QWidget.__init__(self, parent)
44
+ self.current_view = ImageGraphicsView(parent)
45
+ self.master_view = ImageGraphicsView(parent)
46
+ self.current_scene = self.create_scene(self.current_view)
47
+ self.master_scene = self.create_scene(self.master_view)
48
+ self.create_pixmaps()
49
+ self.master_scene.addItem(self.brush_preview)
50
+ self.setup_layout()
51
+ self._connect_signals()
52
+ self.panning_current = False
53
+ self.brush_cursor = None
54
+ self.setFocusPolicy(Qt.StrongFocus)
55
+ self.pan_start = None
56
+ self.pinch_start_scale = None
57
+ self.current_view.installEventFilter(self)
58
+ self.master_view.installEventFilter(self)
59
+ self.current_view.setFocusPolicy(Qt.NoFocus)
60
+ self.master_view.setFocusPolicy(Qt.NoFocus)
61
+ self.current_brush_cursor = None
62
+ self.setup_current_brush_cursor()
63
+
64
+ def setup_layout(self):
65
+ raise NotImplementedError("Subclasses must implement setup_layout")
66
+
67
+ def create_pixmaps(self):
68
+ self.pixmap_item_current = self.create_pixmap(self.current_scene)
69
+ self.pixmap_item_master = self.create_pixmap(self.master_scene)
70
+
71
+ def _connect_signals(self):
72
+ self.current_view.mouse_pressed.connect(self.handle_current_mouse_press)
73
+ self.current_view.mouse_moved.connect(self.handle_current_mouse_move)
74
+ self.current_view.mouse_released.connect(self.handle_current_mouse_release)
75
+ self.current_view.gesture_event.connect(self.handle_gesture_event)
76
+ self.master_view.mouse_pressed.connect(self.handle_master_mouse_press)
77
+ self.master_view.mouse_moved.connect(self.handle_master_mouse_move)
78
+ self.master_view.mouse_released.connect(self.handle_master_mouse_release)
79
+ self.master_view.gesture_event.connect(self.handle_gesture_event)
80
+ self.current_view.horizontalScrollBar().valueChanged.connect(
81
+ self.master_view.horizontalScrollBar().setValue)
82
+ self.current_view.verticalScrollBar().valueChanged.connect(
83
+ self.master_view.verticalScrollBar().setValue)
84
+ self.master_view.horizontalScrollBar().valueChanged.connect(
85
+ self.current_view.horizontalScrollBar().setValue)
86
+ self.master_view.verticalScrollBar().valueChanged.connect(
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)
90
+ # pylint: disable=C0103, W0201
91
+ self.current_view.enterEvent = self.current_view_enter_event
92
+ self.current_view.leaveEvent = self.current_view_leave_event
93
+ self.master_view.enterEvent = self.master_view_enter_event
94
+ self.master_view.leaveEvent = self.master_view_leave_event
95
+ # pylint: enable=C0103, W0201
96
+
97
+ def current_view_enter_event(self, event):
98
+ self.activateWindow()
99
+ self.setFocus()
100
+ if not self.empty():
101
+ self.update_brush_cursor()
102
+ super(ImageGraphicsView, self.current_view).enterEvent(event)
103
+
104
+ def current_view_leave_event(self, event):
105
+ if not self.empty():
106
+ self.update_brush_cursor()
107
+ super(ImageGraphicsView, self.current_view).leaveEvent(event)
108
+
109
+ def master_view_enter_event(self, event):
110
+ self.activateWindow()
111
+ self.setFocus()
112
+ if not self.empty():
113
+ self.update_brush_cursor()
114
+ super(ImageGraphicsView, self.master_view).enterEvent(event)
115
+
116
+ def master_view_leave_event(self, event):
117
+ if not self.empty():
118
+ self.update_brush_cursor()
119
+ super(ImageGraphicsView, self.master_view).leaveEvent(event)
120
+
121
+ def get_master_view(self):
122
+ return self.master_view
123
+
124
+ def get_current_view(self):
125
+ return self.current_view
126
+
127
+ def get_master_scene(self):
128
+ return self.master_scene
129
+
130
+ def get_current_scene(self):
131
+ return self.current_scene
132
+
133
+ def get_master_pixmap(self):
134
+ return self.pixmap_item_master
135
+
136
+ def get_current_pixmap(self):
137
+ return self.pixmap_item_current
138
+
139
+ def get_views(self):
140
+ return [self.master_view, self.current_view]
141
+
142
+ def get_scenes(self):
143
+ return [self.master_scene, self.current_scene]
144
+
145
+ def get_pixmaps(self):
146
+ return {
147
+ self.pixmap_item_master: self.master_view,
148
+ self.pixmap_item_current: self.current_view
149
+ }
150
+
151
+ # pylint: disable=C0103
152
+ def focusInEvent(self, event):
153
+ super().focusInEvent(event)
154
+ self.activateWindow()
155
+ self.setFocus()
156
+
157
+ def eventFilter(self, obj, event):
158
+ if obj in [self.current_view, self.master_view]:
159
+ if event.type() == QEvent.KeyPress:
160
+ self.keyPressEvent(event)
161
+ return True
162
+ if event.type() == QEvent.KeyRelease:
163
+ self.keyReleaseEvent(event)
164
+ return True
165
+ return super().eventFilter(obj, event)
166
+
167
+ def showEvent(self, event):
168
+ super().showEvent(event)
169
+ self.update_brush_cursor()
170
+
171
+ def enterEvent(self, event):
172
+ self.activateWindow()
173
+ self.setFocus()
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:
179
+ if self.space_pressed:
180
+ self.master_view.setCursor(Qt.OpenHandCursor)
181
+ self.current_view.setCursor(Qt.OpenHandCursor)
182
+ else:
183
+ self.master_view.setCursor(Qt.BlankCursor)
184
+ self.current_view.setCursor(Qt.BlankCursor)
185
+ if self.brush_cursor:
186
+ self.brush_cursor.show()
187
+ super().enterEvent(event)
188
+
189
+ def leaveEvent(self, event):
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)
201
+ super().leaveEvent(event)
202
+
203
+ def keyPressEvent(self, event):
204
+ super().keyPressEvent(event)
205
+ if event.key() == Qt.Key_Space:
206
+ self.update_brush_cursor()
207
+
208
+ def keyReleaseEvent(self, event):
209
+ super().keyReleaseEvent(event)
210
+ if event.key() == Qt.Key_Space:
211
+ self.update_brush_cursor()
212
+ # pylint: enable=C0103
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
+
248
+ def setup_brush_cursor(self):
249
+ super().setup_brush_cursor()
250
+ self.setup_current_brush_cursor()
251
+ self.update_cursor_pen_width()
252
+
253
+ def setup_current_brush_cursor(self):
254
+ if not self.brush:
255
+ return
256
+ for item in self.current_scene.items():
257
+ if isinstance(item, QGraphicsEllipseItem) and item != self.brush_preview:
258
+ self.current_scene.removeItem(item)
259
+ pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
260
+ pen = QPen(QColor(255, 0, 0), pen_width, Qt.DotLine)
261
+ brush = Qt.NoBrush
262
+ self.current_brush_cursor = self.current_scene.addEllipse(
263
+ 0, 0, self.brush.size, self.brush.size, pen, brush)
264
+ self.current_brush_cursor.setZValue(1000)
265
+ self.current_brush_cursor.hide()
266
+
267
+ def update_current_brush_cursor(self, scene_pos):
268
+ if not self.current_brush_cursor or not self.isVisible():
269
+ return
270
+ size = self.brush.size
271
+ radius = size / 2
272
+ self.current_brush_cursor.setRect(
273
+ scene_pos.x() - radius, scene_pos.y() - radius, size, size)
274
+ if self.brush_cursor and self.brush_cursor.isVisible():
275
+ self.current_brush_cursor.show()
276
+ else:
277
+ self.current_brush_cursor.hide()
278
+
279
+ def update_cursor_pen_width(self):
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
286
+
287
+ def update_brush_cursor(self):
288
+ if self.empty():
289
+ return
290
+ self.update_cursor_pen_width()
291
+ mouse_pos_global = QCursor.pos()
292
+ mouse_pos_current = self.current_view.mapFromGlobal(mouse_pos_global)
293
+ mouse_pos_master = self.master_view.mapFromGlobal(mouse_pos_global)
294
+ current_has_mouse = self.current_view.rect().contains(mouse_pos_current)
295
+ master_has_mouse = self.master_view.rect().contains(mouse_pos_master)
296
+ if master_has_mouse:
297
+ self.brush_preview.show()
298
+ super().update_brush_cursor()
299
+ self.sync_current_cursor_with_master()
300
+ if self.space_pressed:
301
+ cursor_style = Qt.OpenHandCursor if not self.scrolling else Qt.ClosedHandCursor
302
+ self.master_view.setCursor(cursor_style)
303
+ self.current_view.setCursor(cursor_style)
304
+ else:
305
+ self.master_view.setCursor(Qt.BlankCursor)
306
+ self.current_view.setCursor(Qt.BlankCursor)
307
+ elif current_has_mouse:
308
+ self.brush_preview.hide()
309
+ scene_pos = self.current_view.mapToScene(mouse_pos_current)
310
+ size = self.brush.size
311
+ radius = size / 2
312
+ self.current_brush_cursor.setRect(
313
+ scene_pos.x() - radius, scene_pos.y() - radius, size, size)
314
+ self.current_brush_cursor.show()
315
+ if self.brush_cursor:
316
+ self.brush_cursor.setRect(
317
+ scene_pos.x() - radius, scene_pos.y() - radius, size, size)
318
+ self.brush_cursor.show()
319
+ if self.space_pressed:
320
+ cursor_style = Qt.OpenHandCursor \
321
+ if not self.panning_current else Qt.ClosedHandCursor
322
+ self.current_view.setCursor(cursor_style)
323
+ self.master_view.setCursor(cursor_style)
324
+ else:
325
+ self.current_view.setCursor(Qt.BlankCursor)
326
+ self.master_view.setCursor(Qt.BlankCursor)
327
+ else:
328
+ if self.brush_cursor:
329
+ self.brush_cursor.hide()
330
+ if self.current_brush_cursor:
331
+ self.current_brush_cursor.hide()
332
+ self.master_view.setCursor(Qt.ArrowCursor)
333
+ self.current_view.setCursor(Qt.ArrowCursor)
334
+
335
+ def handle_master_mouse_press(self, event):
336
+ self.setFocus()
337
+ self.mouse_press_event(event)
338
+
339
+ def handle_master_mouse_move(self, event):
340
+ self.mouse_move_event(event)
341
+ self.update_brush_cursor()
342
+
343
+ def handle_master_mouse_release(self, event):
344
+ self.mouse_release_event(event)
345
+
346
+ def handle_current_mouse_press(self, event):
347
+ position = event.position()
348
+ if self.space_pressed:
349
+ self.pan_start = position
350
+ self.panning_current = True
351
+ self.update_brush_cursor()
352
+
353
+ def handle_current_mouse_move(self, event):
354
+ position = event.position()
355
+ if self.panning_current and self.space_pressed:
356
+ delta = position - self.pan_start
357
+ self.pan_start = position
358
+ self.scroll_view(self.current_view, delta.x(), delta.y())
359
+ else:
360
+ self.update_brush_cursor()
361
+
362
+ def handle_current_mouse_release(self, _event):
363
+ if self.panning_current:
364
+ self.panning_current = False
365
+ self.update_brush_cursor()
366
+
367
+ def handle_gesture_event(self, event):
368
+ if self.empty():
369
+ return
370
+ pinch_gesture = event.gesture(Qt.PinchGesture)
371
+ if pinch_gesture:
372
+ self.handle_pinch_gesture(pinch_gesture)
373
+ event.accept()
374
+
375
+ def set_master_image(self, qimage):
376
+ self.status.set_master_image(qimage)
377
+ pixmap = self.status.pixmap_master
378
+ self.master_view.setSceneRect(QRectF(pixmap.rect()))
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)
382
+ self.set_zoom_factor(1.0)
383
+ self.master_view.fitInView(self.pixmap_item_master, Qt.KeepAspectRatio)
384
+ self.set_zoom_factor(self.get_current_scale())
385
+ self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
386
+ self.master_view.resetTransform()
387
+ self.master_scene.scale(self.zoom_factor(), self.zoom_factor())
388
+ self.master_view.centerOn(self.pixmap_item_master)
389
+ center = self.master_scene.sceneRect().center()
390
+ self.brush_preview.setPos(max(0, min(center.x(), img_width)),
391
+ max(0, min(center.y(), img_height)))
392
+ self.master_scene.setSceneRect(QRectF(self.pixmap_item_master.boundingRect()))
393
+ self.center_image(self.master_view)
394
+ self.update_cursor_pen_width()
395
+
396
+ def set_current_image(self, qimage):
397
+ self.status.set_current_image(qimage)
398
+ pixmap = self.status.pixmap_current
399
+ self.current_scene.setSceneRect(QRectF(pixmap.rect()))
400
+ self.pixmap_item_current.setPixmap(pixmap)
401
+ self.current_view.resetTransform()
402
+ self.current_scene.scale(self.zoom_factor(), self.zoom_factor())
403
+ self.current_scene.setSceneRect(QRectF(self.pixmap_item_current.boundingRect()))
404
+ self.center_image(self.current_view)
405
+ self.update_cursor_pen_width()
406
+
407
+ def arrange_images(self):
408
+ if self.status.empty():
409
+ return
410
+ if self.pixmap_item_master.pixmap().height() == 0:
411
+ self.update_master_display()
412
+ self.update_current_display()
413
+ self.reset_zoom()
414
+ else:
415
+ self.center_image(self.master_view)
416
+ self.apply_zoom()
417
+
418
+ def set_brush(self, brush):
419
+ super().set_brush(brush)
420
+ if self.brush_cursor:
421
+ self.master_scene.removeItem(self.brush_cursor)
422
+ self.setup_brush_cursor()
423
+ self.setup_current_brush_cursor()
424
+
425
+ def clear_image(self):
426
+ super().clear_image()
427
+ self.setCursor(Qt.ArrowCursor)
428
+ self.master_view.setCursor(Qt.ArrowCursor)
429
+ self.current_view.setCursor(Qt.ArrowCursor)
430
+ if self.current_brush_cursor:
431
+ self.current_scene.removeItem(self.current_brush_cursor)
432
+ self.current_brush_cursor = None
433
+
434
+ def sync_current_cursor_with_master(self):
435
+ if not self.brush_cursor or not self.current_brush_cursor:
436
+ return
437
+ master_rect = self.brush_cursor.rect()
438
+ scene_pos = master_rect.center()
439
+ size = self.brush.size
440
+ radius = size / 2
441
+ self.current_brush_cursor.setRect(
442
+ scene_pos.x() - radius,
443
+ scene_pos.y() - radius,
444
+ size, size
445
+ )
446
+ if self.brush_cursor.isVisible():
447
+ self.current_brush_cursor.show()
448
+ else:
449
+ self.current_brush_cursor.hide()
450
+
451
+
452
+ class SideBySideView(DoubleViewBase):
453
+ def setup_layout(self):
454
+ layout = QHBoxLayout(self)
455
+ layout.setContentsMargins(0, 0, 0, 0)
456
+ layout.setSpacing(0)
457
+ layout.addWidget(self.current_view, 1)
458
+ separator = QFrame()
459
+ separator.setFrameShape(QFrame.VLine)
460
+ separator.setFrameShadow(QFrame.Sunken)
461
+ separator.setLineWidth(2)
462
+ layout.addWidget(separator, 0)
463
+ layout.addWidget(self.master_view, 1)
464
+
465
+
466
+ class TopBottomView(DoubleViewBase):
467
+ def setup_layout(self):
468
+ layout = QVBoxLayout(self)
469
+ layout.setContentsMargins(0, 0, 0, 0)
470
+ layout.setSpacing(0)
471
+ layout.addWidget(self.current_view, 1)
472
+ separator = QFrame()
473
+ separator.setFrameShape(QFrame.HLine)
474
+ separator.setFrameShadow(QFrame.Sunken)
475
+ separator.setLineWidth(2)
476
+ layout.addWidget(separator, 0)
477
+ layout.addWidget(self.master_view, 1)
@@ -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
- layer[y_start:y_end, x_start:x_end] = undo_state['master']
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
- layer[y_start:y_end, x_start:x_end] = redo_state['master']
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)