shinestacker 1.3.1__py3-none-any.whl → 1.4.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/config/gui_constants.py +2 -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 +5 -5
- shinestacker/retouch/brush_preview.py +3 -0
- shinestacker/retouch/brush_tool.py +11 -11
- shinestacker/retouch/display_manager.py +21 -37
- shinestacker/retouch/image_editor_ui.py +129 -71
- shinestacker/retouch/image_view_status.py +61 -0
- shinestacker/retouch/image_viewer.py +89 -431
- shinestacker/retouch/io_gui_handler.py +12 -2
- shinestacker/retouch/overlaid_view.py +212 -0
- shinestacker/retouch/shortcuts_help.py +13 -3
- shinestacker/retouch/sidebyside_view.py +479 -0
- shinestacker/retouch/view_strategy.py +466 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/METADATA +1 -1
- {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/RECORD +34 -29
- {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, E1101, R0904, R0912, R0914, R0902
|
|
2
|
+
from PySide6.QtGui import QPixmap
|
|
3
|
+
from PySide6.QtCore import Qt, QPointF, QEvent, QRectF
|
|
4
|
+
from .. config.gui_constants import gui_constants
|
|
5
|
+
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
9
|
+
def __init__(self, layer_collection, status, parent):
|
|
10
|
+
ViewStrategy.__init__(self, layer_collection, status)
|
|
11
|
+
ImageGraphicsViewBase.__init__(self, parent)
|
|
12
|
+
self.scene = self.create_scene(self)
|
|
13
|
+
self.create_pixmaps()
|
|
14
|
+
self.scene.addItem(self.brush_preview)
|
|
15
|
+
self.brush_cursor = None
|
|
16
|
+
self.pinch_start_scale = 1.0
|
|
17
|
+
self.last_scroll_pos = QPointF()
|
|
18
|
+
|
|
19
|
+
def create_pixmaps(self):
|
|
20
|
+
self.pixmap_item_master = self.create_pixmap(self.scene)
|
|
21
|
+
self.pixmap_item_current = self.create_pixmap(self.scene)
|
|
22
|
+
|
|
23
|
+
def get_master_view(self):
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def get_master_scene(self):
|
|
27
|
+
return self.scene
|
|
28
|
+
|
|
29
|
+
def get_master_pixmap(self):
|
|
30
|
+
return self.pixmap_item_master
|
|
31
|
+
|
|
32
|
+
def get_views(self):
|
|
33
|
+
return [self]
|
|
34
|
+
|
|
35
|
+
def get_scenes(self):
|
|
36
|
+
return [self.scene]
|
|
37
|
+
|
|
38
|
+
def get_pixmaps(self):
|
|
39
|
+
return {
|
|
40
|
+
self.pixmap_item_master: self,
|
|
41
|
+
self.pixmap_item_current: self
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def set_master_image(self, qimage):
|
|
45
|
+
self.status.set_master_image(qimage)
|
|
46
|
+
pixmap = self.status.pixmap_master
|
|
47
|
+
self.setSceneRect(QRectF(pixmap.rect()))
|
|
48
|
+
|
|
49
|
+
img_width, img_height = pixmap.width(), pixmap.height()
|
|
50
|
+
self.set_min_scale(min(gui_constants.MIN_ZOOMED_IMG_WIDTH / img_width,
|
|
51
|
+
gui_constants.MIN_ZOOMED_IMG_HEIGHT / img_height))
|
|
52
|
+
self.set_max_scale(gui_constants.MAX_ZOOMED_IMG_PX_SIZE)
|
|
53
|
+
self.set_zoom_factor(1.0)
|
|
54
|
+
self.fitInView(self.pixmap_item_master, Qt.KeepAspectRatio)
|
|
55
|
+
self.set_zoom_factor(self.get_current_scale())
|
|
56
|
+
self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
|
|
57
|
+
self.scale(self.zoom_factor(), self.zoom_factor())
|
|
58
|
+
|
|
59
|
+
def set_current_image(self, qimage):
|
|
60
|
+
self.status.set_current_image(qimage)
|
|
61
|
+
if self.empty():
|
|
62
|
+
self.setSceneRect(QRectF(self.status.pixmap_current.rect()))
|
|
63
|
+
|
|
64
|
+
def show_master(self):
|
|
65
|
+
self.pixmap_item_master.setVisible(True)
|
|
66
|
+
self.pixmap_item_current.setVisible(False)
|
|
67
|
+
|
|
68
|
+
def show_current(self):
|
|
69
|
+
self.pixmap_item_master.setVisible(False)
|
|
70
|
+
self.pixmap_item_current.setVisible(True)
|
|
71
|
+
|
|
72
|
+
def update_master_display(self):
|
|
73
|
+
if not self.empty():
|
|
74
|
+
master_qimage = self.numpy_to_qimage(
|
|
75
|
+
self.master_layer())
|
|
76
|
+
if master_qimage:
|
|
77
|
+
self.pixmap_item_master.setPixmap(QPixmap.fromImage(master_qimage))
|
|
78
|
+
|
|
79
|
+
def update_current_display(self):
|
|
80
|
+
if not self.empty() and self.number_of_layers() > 0:
|
|
81
|
+
current_qimage = self.numpy_to_qimage(
|
|
82
|
+
self.current_layer())
|
|
83
|
+
if current_qimage:
|
|
84
|
+
self.pixmap_item_current.setPixmap(QPixmap.fromImage(current_qimage))
|
|
85
|
+
|
|
86
|
+
def set_view_state(self, state):
|
|
87
|
+
self.status.set_state(state)
|
|
88
|
+
if state:
|
|
89
|
+
self.resetTransform()
|
|
90
|
+
self.scale(state['zoom'], state['zoom'])
|
|
91
|
+
self.horizontalScrollBar().setValue(state['h_scroll'])
|
|
92
|
+
self.verticalScrollBar().setValue(state['v_scroll'])
|
|
93
|
+
self.set_zoom_factor(state['zoom'])
|
|
94
|
+
|
|
95
|
+
def handle_key_press_event(self, event):
|
|
96
|
+
if event.key() == Qt.Key_X:
|
|
97
|
+
self.temp_view_requested.emit(True)
|
|
98
|
+
self.update_brush_cursor()
|
|
99
|
+
|
|
100
|
+
def handle_key_release_event(self, event):
|
|
101
|
+
if event.key() == Qt.Key_X:
|
|
102
|
+
self.temp_view_requested.emit(False)
|
|
103
|
+
|
|
104
|
+
# pylint: disable=C0103
|
|
105
|
+
def mousePressEvent(self, event):
|
|
106
|
+
self.mouse_press_event(event)
|
|
107
|
+
super().mousePressEvent(event)
|
|
108
|
+
|
|
109
|
+
def mouseMoveEvent(self, event):
|
|
110
|
+
self.mouse_move_event(event)
|
|
111
|
+
|
|
112
|
+
def mouseReleaseEvent(self, event):
|
|
113
|
+
self.mouse_release_event(event)
|
|
114
|
+
super().mouseReleaseEvent(event)
|
|
115
|
+
|
|
116
|
+
def wheelEvent(self, event):
|
|
117
|
+
if self.empty() or self.gesture_active:
|
|
118
|
+
return
|
|
119
|
+
if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
|
|
120
|
+
if self.control_pressed:
|
|
121
|
+
self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
|
|
122
|
+
else:
|
|
123
|
+
zoom_in_factor = 1.10
|
|
124
|
+
zoom_out_factor = 1 / zoom_in_factor
|
|
125
|
+
current_scale = self.get_current_scale()
|
|
126
|
+
if event.angleDelta().y() > 0: # Zoom in
|
|
127
|
+
new_scale = current_scale * zoom_in_factor
|
|
128
|
+
if new_scale <= self.max_scale():
|
|
129
|
+
self.scale(zoom_in_factor, zoom_in_factor)
|
|
130
|
+
self.set_zoom_factor(new_scale)
|
|
131
|
+
else: # Zoom out
|
|
132
|
+
new_scale = current_scale * zoom_out_factor
|
|
133
|
+
if new_scale >= self.min_scale():
|
|
134
|
+
self.scale(zoom_out_factor, zoom_out_factor)
|
|
135
|
+
self.set_zoom_factor(new_scale)
|
|
136
|
+
self.update_brush_cursor()
|
|
137
|
+
else: # Touchpad event - fallback for systems without gesture recognition
|
|
138
|
+
if not self.control_pressed:
|
|
139
|
+
delta = event.pixelDelta() or event.angleDelta() / 8
|
|
140
|
+
if delta:
|
|
141
|
+
self.scroll_view(self, delta.x(), delta.y())
|
|
142
|
+
else: # Control + touchpad scroll for zoom
|
|
143
|
+
zoom_in = event.angleDelta().y() > 0
|
|
144
|
+
if zoom_in:
|
|
145
|
+
self.zoom_in()
|
|
146
|
+
else:
|
|
147
|
+
self.zoom_out()
|
|
148
|
+
event.accept()
|
|
149
|
+
|
|
150
|
+
def enterEvent(self, event):
|
|
151
|
+
self.activateWindow()
|
|
152
|
+
self.setFocus()
|
|
153
|
+
if not self.empty():
|
|
154
|
+
self.setCursor(Qt.BlankCursor)
|
|
155
|
+
if self.brush_cursor:
|
|
156
|
+
self.brush_cursor.show()
|
|
157
|
+
super().enterEvent(event)
|
|
158
|
+
# pylint: enable=C0103
|
|
159
|
+
|
|
160
|
+
def event(self, event):
|
|
161
|
+
if event.type() == QEvent.Gesture:
|
|
162
|
+
return self.handle_gesture_event(event)
|
|
163
|
+
return super().event(event)
|
|
164
|
+
|
|
165
|
+
def handle_gesture_event(self, event):
|
|
166
|
+
if self.empty():
|
|
167
|
+
return False
|
|
168
|
+
handled = False
|
|
169
|
+
pan_gesture = event.gesture(Qt.PanGesture)
|
|
170
|
+
if pan_gesture:
|
|
171
|
+
self.handle_pan_gesture(pan_gesture)
|
|
172
|
+
handled = True
|
|
173
|
+
pinch_gesture = event.gesture(Qt.PinchGesture)
|
|
174
|
+
if pinch_gesture:
|
|
175
|
+
self.handle_pinch_gesture(pinch_gesture)
|
|
176
|
+
handled = True
|
|
177
|
+
if handled:
|
|
178
|
+
event.accept()
|
|
179
|
+
return True
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
def handle_pan_gesture(self, pan_gesture):
|
|
183
|
+
if pan_gesture.state() == Qt.GestureStarted:
|
|
184
|
+
self.last_scroll_pos = pan_gesture.delta()
|
|
185
|
+
self.gesture_active = True
|
|
186
|
+
elif pan_gesture.state() == Qt.GestureUpdated:
|
|
187
|
+
delta = pan_gesture.delta() - self.last_scroll_pos
|
|
188
|
+
self.last_scroll_pos = pan_gesture.delta()
|
|
189
|
+
scaled_delta = delta * (1.0 / self.get_current_scale())
|
|
190
|
+
self.scroll_view(self, int(scaled_delta.x()), int(scaled_delta.y()))
|
|
191
|
+
elif pan_gesture.state() == Qt.GestureFinished:
|
|
192
|
+
self.gesture_active = False
|
|
193
|
+
|
|
194
|
+
def handle_pinch_gesture(self, pinch):
|
|
195
|
+
if pinch.state() == Qt.GestureStarted:
|
|
196
|
+
self.pinch_start_scale = self.get_current_scale()
|
|
197
|
+
self.pinch_center_view = pinch.centerPoint()
|
|
198
|
+
self.pinch_center_scene = self.mapToScene(self.pinch_center_view.toPoint())
|
|
199
|
+
self.gesture_active = True
|
|
200
|
+
elif pinch.state() == Qt.GestureUpdated:
|
|
201
|
+
new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
|
|
202
|
+
new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
|
|
203
|
+
if abs(new_scale - self.get_current_scale()) > 0.01:
|
|
204
|
+
self.resetTransform()
|
|
205
|
+
self.scale(new_scale, new_scale)
|
|
206
|
+
self.set_zoom_factor(new_scale)
|
|
207
|
+
new_center = self.mapToScene(self.pinch_center_view.toPoint())
|
|
208
|
+
delta = self.pinch_center_scene - new_center
|
|
209
|
+
self.translate(delta.x(), delta.y())
|
|
210
|
+
self.update_brush_cursor()
|
|
211
|
+
elif pinch.state() in (Qt.GestureFinished, Qt.GestureCanceled):
|
|
212
|
+
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",
|