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.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/align.py +198 -18
- shinestacker/algorithms/align_parallel.py +17 -1
- shinestacker/algorithms/balance.py +23 -13
- shinestacker/algorithms/noise_detection.py +3 -1
- shinestacker/algorithms/utils.py +21 -10
- shinestacker/algorithms/vignetting.py +2 -0
- shinestacker/app/main.py +1 -1
- shinestacker/config/gui_constants.py +7 -2
- shinestacker/core/core_utils.py +10 -1
- shinestacker/gui/action_config.py +172 -7
- shinestacker/gui/action_config_dialog.py +246 -285
- shinestacker/gui/gui_run.py +2 -2
- shinestacker/gui/main_window.py +14 -5
- shinestacker/gui/menu_manager.py +26 -2
- shinestacker/gui/project_controller.py +4 -0
- shinestacker/gui/recent_file_manager.py +93 -0
- shinestacker/retouch/base_filter.py +13 -15
- shinestacker/retouch/brush_preview.py +3 -1
- shinestacker/retouch/brush_tool.py +11 -11
- shinestacker/retouch/display_manager.py +43 -59
- shinestacker/retouch/image_editor_ui.py +161 -82
- shinestacker/retouch/image_view_status.py +65 -0
- shinestacker/retouch/image_viewer.py +95 -431
- shinestacker/retouch/io_gui_handler.py +12 -2
- shinestacker/retouch/layer_collection.py +3 -0
- shinestacker/retouch/overlaid_view.py +215 -0
- shinestacker/retouch/shortcuts_help.py +13 -3
- shinestacker/retouch/sidebyside_view.py +477 -0
- shinestacker/retouch/transformation_manager.py +43 -0
- shinestacker/retouch/undo_manager.py +22 -3
- shinestacker/retouch/view_strategy.py +557 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/METADATA +7 -7
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/RECORD +38 -32
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, E1101, R0904, R0912, R0914, R0902
|
|
2
|
+
from PySide6.QtCore import Qt, QPointF, QEvent, QRectF
|
|
3
|
+
from .. config.gui_constants import gui_constants
|
|
4
|
+
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
8
|
+
def __init__(self, layer_collection, status, parent):
|
|
9
|
+
ViewStrategy.__init__(self, layer_collection, status)
|
|
10
|
+
ImageGraphicsViewBase.__init__(self, parent)
|
|
11
|
+
self.scene = self.create_scene(self)
|
|
12
|
+
self.create_pixmaps()
|
|
13
|
+
self.scene.addItem(self.brush_preview)
|
|
14
|
+
self.brush_cursor = None
|
|
15
|
+
self.pinch_start_scale = 1.0
|
|
16
|
+
self.last_scroll_pos = QPointF()
|
|
17
|
+
|
|
18
|
+
def create_pixmaps(self):
|
|
19
|
+
self.pixmap_item_master = self.create_pixmap(self.scene)
|
|
20
|
+
self.pixmap_item_current = self.create_pixmap(self.scene)
|
|
21
|
+
|
|
22
|
+
def get_master_view(self):
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
def get_current_view(self):
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def get_master_scene(self):
|
|
29
|
+
return self.scene
|
|
30
|
+
|
|
31
|
+
def get_current_scene(self):
|
|
32
|
+
return self.scene
|
|
33
|
+
|
|
34
|
+
def get_master_pixmap(self):
|
|
35
|
+
return self.pixmap_item_master
|
|
36
|
+
|
|
37
|
+
def get_current_pixmap(self):
|
|
38
|
+
return self.pixmap_item_current
|
|
39
|
+
|
|
40
|
+
def get_views(self):
|
|
41
|
+
return [self]
|
|
42
|
+
|
|
43
|
+
def get_scenes(self):
|
|
44
|
+
return [self.scene]
|
|
45
|
+
|
|
46
|
+
def get_pixmaps(self):
|
|
47
|
+
return {
|
|
48
|
+
self.pixmap_item_master: self,
|
|
49
|
+
self.pixmap_item_current: self
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# pylint: disable=C0103
|
|
53
|
+
def mousePressEvent(self, event):
|
|
54
|
+
self.mouse_press_event(event)
|
|
55
|
+
super().mousePressEvent(event)
|
|
56
|
+
|
|
57
|
+
def mouseMoveEvent(self, event):
|
|
58
|
+
self.mouse_move_event(event)
|
|
59
|
+
|
|
60
|
+
def mouseReleaseEvent(self, event):
|
|
61
|
+
self.mouse_release_event(event)
|
|
62
|
+
super().mouseReleaseEvent(event)
|
|
63
|
+
|
|
64
|
+
# pylint: enable=R0801
|
|
65
|
+
def wheelEvent(self, event):
|
|
66
|
+
if self.empty() or self.gesture_active:
|
|
67
|
+
return
|
|
68
|
+
if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
|
|
69
|
+
if self.control_pressed:
|
|
70
|
+
self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
|
|
71
|
+
else:
|
|
72
|
+
zoom_in_factor = gui_constants.ZOOM_IN_FACTOR
|
|
73
|
+
zoom_out_factor = gui_constants.ZOOM_OUT_FACTOR
|
|
74
|
+
current_scale = self.get_current_scale()
|
|
75
|
+
if event.angleDelta().y() > 0: # Zoom in
|
|
76
|
+
new_scale = current_scale * zoom_in_factor
|
|
77
|
+
if new_scale <= self.max_scale():
|
|
78
|
+
self.scale(zoom_in_factor, zoom_in_factor)
|
|
79
|
+
self.set_zoom_factor(new_scale)
|
|
80
|
+
else: # Zoom out
|
|
81
|
+
new_scale = current_scale * zoom_out_factor
|
|
82
|
+
if new_scale >= self.min_scale():
|
|
83
|
+
self.scale(zoom_out_factor, zoom_out_factor)
|
|
84
|
+
self.set_zoom_factor(new_scale)
|
|
85
|
+
self.update_brush_cursor()
|
|
86
|
+
else: # Touchpad event - fallback for systems without gesture recognition
|
|
87
|
+
if not self.control_pressed:
|
|
88
|
+
delta = event.pixelDelta() or event.angleDelta() / 8
|
|
89
|
+
if delta:
|
|
90
|
+
self.scroll_view(self, delta.x(), delta.y())
|
|
91
|
+
else: # Control + touchpad scroll for zoom
|
|
92
|
+
zoom_in = event.angleDelta().y() > 0
|
|
93
|
+
if zoom_in:
|
|
94
|
+
self.zoom_in()
|
|
95
|
+
else:
|
|
96
|
+
self.zoom_out()
|
|
97
|
+
event.accept()
|
|
98
|
+
# pylint: disable=R0801
|
|
99
|
+
|
|
100
|
+
def enterEvent(self, event):
|
|
101
|
+
self.activateWindow()
|
|
102
|
+
self.setFocus()
|
|
103
|
+
if self.empty():
|
|
104
|
+
self.setCursor(Qt.ArrowCursor)
|
|
105
|
+
else:
|
|
106
|
+
self.setCursor(Qt.BlankCursor)
|
|
107
|
+
if self.brush_cursor:
|
|
108
|
+
self.brush_cursor.show()
|
|
109
|
+
super().enterEvent(event)
|
|
110
|
+
# pylint: enable=C0103
|
|
111
|
+
|
|
112
|
+
def event(self, event):
|
|
113
|
+
if event.type() == QEvent.Gesture:
|
|
114
|
+
return self.handle_gesture_event(event)
|
|
115
|
+
return super().event(event)
|
|
116
|
+
|
|
117
|
+
def set_master_image(self, qimage):
|
|
118
|
+
self.status.set_master_image(qimage)
|
|
119
|
+
pixmap = self.status.pixmap_master
|
|
120
|
+
self.setSceneRect(QRectF(pixmap.rect()))
|
|
121
|
+
img_width, img_height = pixmap.width(), pixmap.height()
|
|
122
|
+
self.set_max_min_scales(img_width, img_height)
|
|
123
|
+
self.set_zoom_factor(1.0)
|
|
124
|
+
self.resetTransform()
|
|
125
|
+
self.fitInView(self.pixmap_item_master, Qt.KeepAspectRatio)
|
|
126
|
+
self.set_zoom_factor(self.get_current_scale())
|
|
127
|
+
self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
|
|
128
|
+
self.scale(self.zoom_factor(), self.zoom_factor())
|
|
129
|
+
self.centerOn(self.pixmap_item_master)
|
|
130
|
+
self.center_image(self)
|
|
131
|
+
self.update_cursor_pen_width()
|
|
132
|
+
|
|
133
|
+
def set_current_image(self, qimage):
|
|
134
|
+
self.status.set_current_image(qimage)
|
|
135
|
+
if self.empty():
|
|
136
|
+
self.setSceneRect(QRectF(self.status.pixmap_current.rect()))
|
|
137
|
+
self.update_cursor_pen_width()
|
|
138
|
+
|
|
139
|
+
def setup_brush_cursor(self):
|
|
140
|
+
super().setup_brush_cursor()
|
|
141
|
+
self.update_cursor_pen_width()
|
|
142
|
+
|
|
143
|
+
def show_master(self):
|
|
144
|
+
self.pixmap_item_master.setVisible(True)
|
|
145
|
+
self.pixmap_item_current.setVisible(False)
|
|
146
|
+
self.brush_preview.show()
|
|
147
|
+
|
|
148
|
+
def show_current(self):
|
|
149
|
+
self.pixmap_item_master.setVisible(False)
|
|
150
|
+
self.pixmap_item_current.setVisible(True)
|
|
151
|
+
self.brush_preview.hide()
|
|
152
|
+
|
|
153
|
+
def arrange_images(self):
|
|
154
|
+
if self.empty():
|
|
155
|
+
return
|
|
156
|
+
if self.pixmap_item_master.isVisible():
|
|
157
|
+
pixmap = self.pixmap_item_master.pixmap()
|
|
158
|
+
if not pixmap.isNull():
|
|
159
|
+
self.setSceneRect(QRectF(pixmap.rect()))
|
|
160
|
+
self.centerOn(self.pixmap_item_master)
|
|
161
|
+
self.center_image(self)
|
|
162
|
+
elif self.pixmap_item_current.isVisible():
|
|
163
|
+
pixmap = self.pixmap_item_current.pixmap()
|
|
164
|
+
if not pixmap.isNull():
|
|
165
|
+
self.setSceneRect(QRectF(pixmap.rect()))
|
|
166
|
+
self.centerOn(self.pixmap_item_current)
|
|
167
|
+
self.center_image(self)
|
|
168
|
+
current_scale = self.get_current_scale()
|
|
169
|
+
scale_factor = self.zoom_factor() / current_scale
|
|
170
|
+
self.scale(scale_factor, scale_factor)
|
|
171
|
+
|
|
172
|
+
def handle_key_press_event(self, event):
|
|
173
|
+
if event.key() in [Qt.Key_Up, Qt.Key_Down]:
|
|
174
|
+
return False
|
|
175
|
+
if event.key() == Qt.Key_X:
|
|
176
|
+
self.temp_view_requested.emit(True)
|
|
177
|
+
return False
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
def handle_key_release_event(self, event):
|
|
181
|
+
if event.key() in [Qt.Key_Up, Qt.Key_Down]:
|
|
182
|
+
return False
|
|
183
|
+
if event.key() == Qt.Key_X:
|
|
184
|
+
self.temp_view_requested.emit(False)
|
|
185
|
+
return False
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
def handle_gesture_event(self, event):
|
|
189
|
+
if self.empty():
|
|
190
|
+
return False
|
|
191
|
+
handled = False
|
|
192
|
+
pan_gesture = event.gesture(Qt.PanGesture)
|
|
193
|
+
if pan_gesture:
|
|
194
|
+
self.handle_pan_gesture(pan_gesture)
|
|
195
|
+
handled = True
|
|
196
|
+
pinch_gesture = event.gesture(Qt.PinchGesture)
|
|
197
|
+
if pinch_gesture:
|
|
198
|
+
self.handle_pinch_gesture(pinch_gesture)
|
|
199
|
+
handled = True
|
|
200
|
+
if handled:
|
|
201
|
+
event.accept()
|
|
202
|
+
return True
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
def handle_pan_gesture(self, pan_gesture):
|
|
206
|
+
if pan_gesture.state() == Qt.GestureStarted:
|
|
207
|
+
self.last_scroll_pos = pan_gesture.delta()
|
|
208
|
+
self.gesture_active = True
|
|
209
|
+
elif pan_gesture.state() == Qt.GestureUpdated:
|
|
210
|
+
delta = pan_gesture.delta() - self.last_scroll_pos
|
|
211
|
+
self.last_scroll_pos = pan_gesture.delta()
|
|
212
|
+
scaled_delta = delta * (1.0 / self.get_current_scale())
|
|
213
|
+
self.scroll_view(self, int(scaled_delta.x()), int(scaled_delta.y()))
|
|
214
|
+
elif pan_gesture.state() == Qt.GestureFinished:
|
|
215
|
+
self.gesture_active = False
|
|
@@ -61,16 +61,26 @@ class ShortcutsHelp(QDialog):
|
|
|
61
61
|
"Ctrl + +": "zoom in",
|
|
62
62
|
"Ctrl + -": "zoom out",
|
|
63
63
|
"Ctrl + 0": "adapt to screen",
|
|
64
|
-
"Ctrl +
|
|
64
|
+
"Ctrl + R": "actual size"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
self.add_bold_label(left_layout, "Keyboard Shortcuts")
|
|
68
|
+
for k, v in shortcuts.items():
|
|
69
|
+
left_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
70
|
+
|
|
71
|
+
shortcuts = {
|
|
72
|
+
"Ctrl + 1": "view mode: overlaid",
|
|
73
|
+
"Ctrl + 2": "view mode: side by side",
|
|
74
|
+
"Ctrl + 3": "view mode: top-bottom",
|
|
65
75
|
"[": "increase brush size",
|
|
66
76
|
"]": "decrease brush size",
|
|
67
77
|
"{": "increase brush hardness",
|
|
68
78
|
"}": "decrease brush hardness"
|
|
69
79
|
}
|
|
70
80
|
|
|
71
|
-
self.add_bold_label(
|
|
81
|
+
self.add_bold_label(right_layout, "Keyboard Shortcuts")
|
|
72
82
|
for k, v in shortcuts.items():
|
|
73
|
-
|
|
83
|
+
right_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
74
84
|
|
|
75
85
|
mouse_controls = {
|
|
76
86
|
"Space + Drag": "pan",
|