shinestacker 1.3.0__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 +229 -41
- shinestacker/algorithms/align_auto.py +15 -3
- shinestacker/algorithms/align_parallel.py +81 -25
- shinestacker/algorithms/balance.py +23 -13
- shinestacker/algorithms/base_stack_algo.py +14 -20
- shinestacker/algorithms/depth_map.py +9 -14
- shinestacker/algorithms/noise_detection.py +3 -1
- shinestacker/algorithms/pyramid.py +8 -22
- shinestacker/algorithms/pyramid_auto.py +5 -14
- shinestacker/algorithms/pyramid_tiles.py +18 -20
- shinestacker/algorithms/stack_framework.py +1 -1
- shinestacker/algorithms/utils.py +37 -10
- shinestacker/algorithms/vignetting.py +2 -0
- shinestacker/app/gui_utils.py +10 -0
- shinestacker/app/main.py +3 -1
- shinestacker/app/project.py +3 -1
- shinestacker/app/retouch.py +3 -1
- 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 +443 -452
- shinestacker/gui/colors.py +1 -0
- shinestacker/gui/folder_file_selection.py +5 -0
- shinestacker/gui/gui_run.py +2 -2
- shinestacker/gui/main_window.py +18 -9
- shinestacker/gui/menu_manager.py +26 -2
- shinestacker/gui/new_project.py +5 -5
- shinestacker/gui/project_controller.py +4 -0
- shinestacker/gui/project_editor.py +6 -4
- shinestacker/gui/recent_file_manager.py +93 -0
- shinestacker/gui/sys_mon.py +24 -23
- 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.0.dist-info → shinestacker-1.4.0.dist-info}/METADATA +1 -1
- {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/RECORD +50 -45
- {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,465 +1,123 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0904, R0902, R0914, R0912
|
|
2
|
-
import
|
|
3
|
-
from PySide6.QtWidgets import
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from .. config.gui_constants import gui_constants
|
|
8
|
-
from .brush_preview import BrushPreviewItem
|
|
9
|
-
from .brush_gradient import create_default_brush_gradient
|
|
10
|
-
from .layer_collection import LayerCollectionHandler
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0904, R0902, R0914, R0912, R0913, R0917
|
|
2
|
+
from PySide6.QtCore import Qt
|
|
3
|
+
from PySide6.QtWidgets import QWidget, QVBoxLayout
|
|
4
|
+
from .image_view_status import ImageViewStatus
|
|
5
|
+
from .overlaid_view import OverlaidView
|
|
6
|
+
from .sidebyside_view import SideBySideView, TopBottomView
|
|
11
7
|
|
|
12
8
|
|
|
13
|
-
class ImageViewer(
|
|
14
|
-
temp_view_requested = Signal(bool)
|
|
15
|
-
brush_operation_started = Signal(QPoint)
|
|
16
|
-
brush_operation_continued = Signal(QPoint)
|
|
17
|
-
brush_operation_ended = Signal()
|
|
18
|
-
brush_size_change_requested = Signal(int) # +1 or -1
|
|
19
|
-
|
|
9
|
+
class ImageViewer(QWidget):
|
|
20
10
|
def __init__(self, layer_collection, parent=None):
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
self.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
self.
|
|
29
|
-
|
|
30
|
-
self.
|
|
31
|
-
self.
|
|
32
|
-
self.
|
|
33
|
-
self.
|
|
34
|
-
self.
|
|
35
|
-
self.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
self.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
47
|
-
self.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
self.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
self.
|
|
56
|
-
self.grabGesture(Qt.PanGesture)
|
|
57
|
-
self.grabGesture(Qt.PinchGesture)
|
|
58
|
-
self.pinch_start_scale = 1.0
|
|
59
|
-
self.last_scroll_pos = QPointF()
|
|
60
|
-
self.gesture_active = False
|
|
61
|
-
self.pinch_center_view = None
|
|
62
|
-
self.pinch_center_scene = None
|
|
63
|
-
|
|
64
|
-
def set_image(self, qimage):
|
|
65
|
-
pixmap = QPixmap.fromImage(qimage)
|
|
66
|
-
self.pixmap_item.setPixmap(pixmap)
|
|
67
|
-
self.setSceneRect(QRectF(pixmap.rect()))
|
|
68
|
-
img_width, img_height = pixmap.width(), pixmap.height()
|
|
69
|
-
self.min_scale = min(gui_constants.MIN_ZOOMED_IMG_WIDTH / img_width,
|
|
70
|
-
gui_constants.MIN_ZOOMED_IMG_HEIGHT / img_height)
|
|
71
|
-
self.max_scale = gui_constants.MAX_ZOOMED_IMG_PX_SIZE
|
|
72
|
-
if self.zoom_factor == 1.0:
|
|
73
|
-
self.fitInView(self.pixmap_item, Qt.KeepAspectRatio)
|
|
74
|
-
self.zoom_factor = self.get_current_scale()
|
|
75
|
-
self.zoom_factor = max(self.min_scale, min(self.max_scale, self.zoom_factor))
|
|
76
|
-
self.resetTransform()
|
|
77
|
-
self.scale(self.zoom_factor, self.zoom_factor)
|
|
78
|
-
self.empty = False
|
|
79
|
-
self.setFocus()
|
|
80
|
-
self.activateWindow()
|
|
81
|
-
self.brush_preview.brush = self.brush
|
|
11
|
+
super().__init__(parent)
|
|
12
|
+
self.status = ImageViewStatus()
|
|
13
|
+
self._strategies = {
|
|
14
|
+
'overlaid': OverlaidView(layer_collection, self.status, self),
|
|
15
|
+
'sidebyside': SideBySideView(layer_collection, self.status, self),
|
|
16
|
+
'topbottom': TopBottomView(layer_collection, self.status, self)
|
|
17
|
+
}
|
|
18
|
+
for strategy in self._strategies.values():
|
|
19
|
+
strategy.hide()
|
|
20
|
+
self.strategy = self._strategies['overlaid']
|
|
21
|
+
self.layout = QVBoxLayout(self)
|
|
22
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
23
|
+
self.strategy = self._strategies['overlaid']
|
|
24
|
+
self.layout.addWidget(self.strategy)
|
|
25
|
+
self.strategy.show()
|
|
26
|
+
|
|
27
|
+
def set_strategy(self, label):
|
|
28
|
+
new_strategy = self._strategies.get(label, None)
|
|
29
|
+
if new_strategy is None:
|
|
30
|
+
raise RuntimeError(f"View strategy {label} is invalid.")
|
|
31
|
+
self.layout.removeWidget(self.strategy)
|
|
32
|
+
self.strategy.hide()
|
|
33
|
+
self.strategy = new_strategy
|
|
34
|
+
self.layout.addWidget(self.strategy)
|
|
35
|
+
self.strategy.show()
|
|
36
|
+
self.strategy.resize(self.size())
|
|
37
|
+
if not self.strategy.empty():
|
|
38
|
+
self.strategy.update_master_display()
|
|
39
|
+
self.strategy.update_current_display()
|
|
40
|
+
|
|
41
|
+
def empty(self):
|
|
42
|
+
return self.strategy.empty()
|
|
43
|
+
|
|
44
|
+
def set_master_image_np(self, img):
|
|
45
|
+
self.strategy.set_master_image_np(img)
|
|
82
46
|
|
|
83
47
|
def clear_image(self):
|
|
84
|
-
self.
|
|
85
|
-
|
|
86
|
-
self.scene.addItem(self.pixmap_item)
|
|
87
|
-
self.zoom_factor = 1.0
|
|
88
|
-
self.setup_brush_cursor()
|
|
89
|
-
self.brush_preview = BrushPreviewItem(self.layer_collection)
|
|
90
|
-
self.scene.addItem(self.brush_preview)
|
|
91
|
-
self.setCursor(Qt.ArrowCursor)
|
|
92
|
-
self.brush_cursor.hide()
|
|
93
|
-
self.empty = True
|
|
94
|
-
|
|
95
|
-
# pylint: disable=C0103
|
|
96
|
-
def keyPressEvent(self, event):
|
|
97
|
-
if self.empty:
|
|
98
|
-
return
|
|
99
|
-
if event.key() == Qt.Key_Space and not self.scrolling:
|
|
100
|
-
self.space_pressed = True
|
|
101
|
-
self.setCursor(Qt.OpenHandCursor)
|
|
102
|
-
if self.brush_cursor:
|
|
103
|
-
self.brush_cursor.hide()
|
|
104
|
-
elif event.key() == Qt.Key_X:
|
|
105
|
-
self.temp_view_requested.emit(True)
|
|
106
|
-
self.update_brush_cursor()
|
|
107
|
-
return
|
|
108
|
-
if event.key() == Qt.Key_Control and not self.scrolling:
|
|
109
|
-
self.control_pressed = True
|
|
110
|
-
super().keyPressEvent(event)
|
|
48
|
+
for st in self._strategies.values():
|
|
49
|
+
st.clear_image()
|
|
111
50
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
return
|
|
115
|
-
self.update_brush_cursor()
|
|
116
|
-
if event.key() == Qt.Key_Space:
|
|
117
|
-
self.space_pressed = False
|
|
118
|
-
if not self.scrolling:
|
|
119
|
-
self.setCursor(Qt.BlankCursor)
|
|
120
|
-
if self.brush_cursor:
|
|
121
|
-
self.brush_cursor.show()
|
|
122
|
-
elif event.key() == Qt.Key_X:
|
|
123
|
-
self.temp_view_requested.emit(False)
|
|
124
|
-
return
|
|
125
|
-
if event.key() == Qt.Key_Control:
|
|
126
|
-
self.control_pressed = False
|
|
127
|
-
super().keyReleaseEvent(event)
|
|
51
|
+
def show_master(self):
|
|
52
|
+
self.strategy.show_master()
|
|
128
53
|
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
return
|
|
132
|
-
if event.button() == Qt.LeftButton and self.layer_collection.has_master_layer():
|
|
133
|
-
if self.space_pressed:
|
|
134
|
-
self.scrolling = True
|
|
135
|
-
self.last_mouse_pos = event.position()
|
|
136
|
-
self.setCursor(Qt.ClosedHandCursor)
|
|
137
|
-
if self.brush_cursor:
|
|
138
|
-
self.brush_cursor.hide()
|
|
139
|
-
else:
|
|
140
|
-
self.last_brush_pos = event.position()
|
|
141
|
-
self.brush_operation_started.emit(event.position().toPoint())
|
|
142
|
-
self.dragging = True
|
|
143
|
-
if self.brush_cursor:
|
|
144
|
-
self.brush_cursor.show()
|
|
145
|
-
super().mousePressEvent(event)
|
|
54
|
+
def show_current(self):
|
|
55
|
+
self.strategy.show_current()
|
|
146
56
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
return
|
|
150
|
-
position = event.position()
|
|
151
|
-
brush_size = self.brush.size
|
|
152
|
-
if not self.space_pressed:
|
|
153
|
-
self.update_brush_cursor()
|
|
154
|
-
if self.dragging and event.buttons() & Qt.LeftButton:
|
|
155
|
-
current_time = QTime.currentTime()
|
|
156
|
-
if self.last_update_time.msecsTo(current_time) >= gui_constants.PAINT_REFRESH_TIMER:
|
|
157
|
-
min_step = brush_size * \
|
|
158
|
-
gui_constants.MIN_MOUSE_STEP_BRUSH_FRACTION * self.zoom_factor
|
|
159
|
-
x, y = position.x(), position.y()
|
|
160
|
-
xp, yp = self.last_brush_pos.x(), self.last_brush_pos.y()
|
|
161
|
-
distance = math.sqrt((x - xp)**2 + (y - yp)**2)
|
|
162
|
-
n_steps = int(float(distance) / min_step)
|
|
163
|
-
if n_steps > 0:
|
|
164
|
-
delta_x = (position.x() - self.last_brush_pos.x()) / n_steps
|
|
165
|
-
delta_y = (position.y() - self.last_brush_pos.y()) / n_steps
|
|
166
|
-
for i in range(0, n_steps + 1):
|
|
167
|
-
pos = QPoint(self.last_brush_pos.x() + i * delta_x,
|
|
168
|
-
self.last_brush_pos.y() + i * delta_y)
|
|
169
|
-
self.brush_operation_continued.emit(pos)
|
|
170
|
-
self.last_brush_pos = position
|
|
171
|
-
self.last_update_time = current_time
|
|
172
|
-
if self.scrolling and event.buttons() & Qt.LeftButton:
|
|
173
|
-
if self.space_pressed:
|
|
174
|
-
self.setCursor(Qt.ClosedHandCursor)
|
|
175
|
-
if self.brush_cursor:
|
|
176
|
-
self.brush_cursor.hide()
|
|
177
|
-
delta = position - self.last_mouse_pos
|
|
178
|
-
self.last_mouse_pos = position
|
|
179
|
-
self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x())
|
|
180
|
-
self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y())
|
|
181
|
-
else:
|
|
182
|
-
super().mouseMoveEvent(event)
|
|
57
|
+
def update_master_display(self):
|
|
58
|
+
self.strategy.update_master_display()
|
|
183
59
|
|
|
184
|
-
def
|
|
185
|
-
|
|
186
|
-
return
|
|
187
|
-
if self.space_pressed:
|
|
188
|
-
self.setCursor(Qt.OpenHandCursor)
|
|
189
|
-
if self.brush_cursor:
|
|
190
|
-
self.brush_cursor.hide()
|
|
191
|
-
else:
|
|
192
|
-
self.setCursor(Qt.BlankCursor)
|
|
193
|
-
if self.brush_cursor:
|
|
194
|
-
self.brush_cursor.show()
|
|
195
|
-
if event.button() == Qt.LeftButton:
|
|
196
|
-
if self.scrolling:
|
|
197
|
-
self.scrolling = False
|
|
198
|
-
self.last_mouse_pos = None
|
|
199
|
-
elif hasattr(self, 'dragging') and self.dragging:
|
|
200
|
-
self.dragging = False
|
|
201
|
-
self.brush_operation_ended.emit()
|
|
202
|
-
super().mouseReleaseEvent(event)
|
|
60
|
+
def update_current_display(self):
|
|
61
|
+
self.strategy.update_current_display()
|
|
203
62
|
|
|
204
|
-
def
|
|
205
|
-
|
|
206
|
-
return
|
|
207
|
-
if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
|
|
208
|
-
if self.control_pressed:
|
|
209
|
-
self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
|
|
210
|
-
else:
|
|
211
|
-
zoom_in_factor = 1.10
|
|
212
|
-
zoom_out_factor = 1 / zoom_in_factor
|
|
213
|
-
current_scale = self.get_current_scale()
|
|
214
|
-
if event.angleDelta().y() > 0: # Zoom in
|
|
215
|
-
new_scale = current_scale * zoom_in_factor
|
|
216
|
-
if new_scale <= self.max_scale:
|
|
217
|
-
self.scale(zoom_in_factor, zoom_in_factor)
|
|
218
|
-
self.zoom_factor = new_scale
|
|
219
|
-
else: # Zoom out
|
|
220
|
-
new_scale = current_scale * zoom_out_factor
|
|
221
|
-
if new_scale >= self.min_scale:
|
|
222
|
-
self.scale(zoom_out_factor, zoom_out_factor)
|
|
223
|
-
self.zoom_factor = new_scale
|
|
224
|
-
self.update_brush_cursor()
|
|
225
|
-
else: # Touchpad event - fallback for systems without gesture recognition
|
|
226
|
-
if not self.control_pressed:
|
|
227
|
-
delta = event.pixelDelta() or event.angleDelta() / 8
|
|
228
|
-
if delta:
|
|
229
|
-
self.horizontalScrollBar().setValue(
|
|
230
|
-
self.horizontalScrollBar().value() - delta.x()
|
|
231
|
-
)
|
|
232
|
-
self.verticalScrollBar().setValue(
|
|
233
|
-
self.verticalScrollBar().value() - delta.y()
|
|
234
|
-
)
|
|
235
|
-
else: # Control + touchpad scroll for zoom
|
|
236
|
-
zoom_in = event.angleDelta().y() > 0
|
|
237
|
-
if zoom_in:
|
|
238
|
-
self.zoom_in()
|
|
239
|
-
else:
|
|
240
|
-
self.zoom_out()
|
|
241
|
-
event.accept()
|
|
242
|
-
|
|
243
|
-
def enterEvent(self, event):
|
|
244
|
-
self.activateWindow()
|
|
245
|
-
self.setFocus()
|
|
246
|
-
if not self.empty:
|
|
247
|
-
self.setCursor(Qt.BlankCursor)
|
|
248
|
-
if self.brush_cursor:
|
|
249
|
-
self.brush_cursor.show()
|
|
250
|
-
super().enterEvent(event)
|
|
63
|
+
def update_brush_cursor(self):
|
|
64
|
+
self.strategy.update_brush_cursor()
|
|
251
65
|
|
|
252
|
-
def
|
|
253
|
-
|
|
254
|
-
self.setCursor(Qt.ArrowCursor)
|
|
255
|
-
if self.brush_cursor:
|
|
256
|
-
self.brush_cursor.hide()
|
|
257
|
-
super().leaveEvent(event)
|
|
258
|
-
# pylint: enable=C0103
|
|
66
|
+
def refresh_display(self):
|
|
67
|
+
self.strategy.refresh_display()
|
|
259
68
|
|
|
260
|
-
def
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return super().event(event)
|
|
69
|
+
def set_brush(self, brush):
|
|
70
|
+
for st in self._strategies.values():
|
|
71
|
+
st.set_brush(brush)
|
|
264
72
|
|
|
265
|
-
def
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if pan_gesture:
|
|
269
|
-
self.handle_pan_gesture(pan_gesture)
|
|
270
|
-
handled = True
|
|
271
|
-
pinch_gesture = event.gesture(Qt.PinchGesture)
|
|
272
|
-
if pinch_gesture:
|
|
273
|
-
self.handle_pinch_gesture(pinch_gesture)
|
|
274
|
-
handled = True
|
|
275
|
-
if handled:
|
|
276
|
-
event.accept()
|
|
277
|
-
return True
|
|
278
|
-
return False
|
|
73
|
+
def set_preview_brush(self, brush):
|
|
74
|
+
for st in self._strategies.values():
|
|
75
|
+
st.set_preview_brush(brush)
|
|
279
76
|
|
|
280
|
-
def
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
self.gesture_active = True
|
|
284
|
-
elif pan_gesture.state() == Qt.GestureUpdated:
|
|
285
|
-
delta = pan_gesture.delta() - self.last_scroll_pos
|
|
286
|
-
self.last_scroll_pos = pan_gesture.delta()
|
|
287
|
-
zoom_factor = self.get_current_scale()
|
|
288
|
-
scaled_delta = delta * (1.0 / zoom_factor)
|
|
289
|
-
self.horizontalScrollBar().setValue(
|
|
290
|
-
self.horizontalScrollBar().value() - int(scaled_delta.x())
|
|
291
|
-
)
|
|
292
|
-
self.verticalScrollBar().setValue(
|
|
293
|
-
self.verticalScrollBar().value() - int(scaled_delta.y())
|
|
294
|
-
)
|
|
295
|
-
elif pan_gesture.state() == Qt.GestureFinished:
|
|
296
|
-
self.gesture_active = False
|
|
77
|
+
def set_display_manager(self, dm):
|
|
78
|
+
for st in self._strategies.values():
|
|
79
|
+
st.set_display_manager(dm)
|
|
297
80
|
|
|
298
|
-
def
|
|
299
|
-
|
|
300
|
-
self.pinch_start_scale = self.get_current_scale()
|
|
301
|
-
self.pinch_center_view = pinch.centerPoint()
|
|
302
|
-
self.pinch_center_scene = self.mapToScene(self.pinch_center_view.toPoint())
|
|
303
|
-
self.gesture_active = True
|
|
304
|
-
elif pinch.state() == Qt.GestureUpdated:
|
|
305
|
-
new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
|
|
306
|
-
new_scale = max(self.min_scale, min(new_scale, self.max_scale))
|
|
307
|
-
if abs(new_scale - self.get_current_scale()) > 0.01:
|
|
308
|
-
self.resetTransform()
|
|
309
|
-
self.scale(new_scale, new_scale)
|
|
310
|
-
self.zoom_factor = new_scale
|
|
311
|
-
new_center = self.mapToScene(self.pinch_center_view.toPoint())
|
|
312
|
-
delta = self.pinch_center_scene - new_center
|
|
313
|
-
self.translate(delta.x(), delta.y())
|
|
314
|
-
self.update_brush_cursor()
|
|
315
|
-
elif pinch.state() in (Qt.GestureFinished, Qt.GestureCanceled):
|
|
316
|
-
self.gesture_active = False
|
|
81
|
+
def set_allow_cursor_preview(self, state):
|
|
82
|
+
self.strategy.set_allow_cursor_preview(state)
|
|
317
83
|
|
|
318
84
|
def setup_brush_cursor(self):
|
|
319
|
-
self.
|
|
320
|
-
pen = QPen(QColor(*gui_constants.BRUSH_COLORS['pen']), 1)
|
|
321
|
-
brush = QBrush(QColor(*gui_constants.BRUSH_COLORS['cursor_inner']))
|
|
322
|
-
for item in self.scene.items():
|
|
323
|
-
if isinstance(item, QGraphicsEllipseItem):
|
|
324
|
-
self.scene.removeItem(item)
|
|
325
|
-
self.brush_cursor = self.scene.addEllipse(
|
|
326
|
-
0, 0, self.brush.size, self.brush.size, pen, brush)
|
|
327
|
-
self.brush_cursor.setZValue(1000)
|
|
328
|
-
self.brush_cursor.hide()
|
|
329
|
-
|
|
330
|
-
def update_brush_cursor(self):
|
|
331
|
-
if self.empty:
|
|
332
|
-
return
|
|
333
|
-
if not self.brush_cursor or not self.isVisible():
|
|
334
|
-
return
|
|
335
|
-
size = self.brush.size
|
|
336
|
-
mouse_pos = self.mapFromGlobal(QCursor.pos())
|
|
337
|
-
if not self.rect().contains(mouse_pos):
|
|
338
|
-
self.brush_cursor.hide()
|
|
339
|
-
return
|
|
340
|
-
scene_pos = self.mapToScene(mouse_pos)
|
|
341
|
-
center_x = scene_pos.x()
|
|
342
|
-
center_y = scene_pos.y()
|
|
343
|
-
radius = size / 2
|
|
344
|
-
self.brush_cursor.setRect(center_x - radius, center_y - radius, size, size)
|
|
345
|
-
allow_cursor_preview = self.display_manager.allow_cursor_preview()
|
|
346
|
-
if self.cursor_style == 'preview' and allow_cursor_preview:
|
|
347
|
-
self._setup_outline_style()
|
|
348
|
-
self.brush_cursor.hide()
|
|
349
|
-
pos = QCursor.pos()
|
|
350
|
-
if isinstance(pos, QPointF):
|
|
351
|
-
scene_pos = pos
|
|
352
|
-
else:
|
|
353
|
-
cursor_pos = self.mapFromGlobal(pos)
|
|
354
|
-
scene_pos = self.mapToScene(cursor_pos)
|
|
355
|
-
self.brush_preview.update(scene_pos, int(size))
|
|
356
|
-
else:
|
|
357
|
-
self.brush_preview.hide()
|
|
358
|
-
if self.cursor_style == 'outline' or not allow_cursor_preview:
|
|
359
|
-
self._setup_outline_style()
|
|
360
|
-
else:
|
|
361
|
-
self._setup_simple_brush_style(center_x, center_y, radius)
|
|
362
|
-
if not self.brush_cursor.isVisible():
|
|
363
|
-
self.brush_cursor.show()
|
|
364
|
-
|
|
365
|
-
def _setup_outline_style(self):
|
|
366
|
-
self.brush_cursor.setPen(QPen(QColor(*gui_constants.BRUSH_COLORS['pen']),
|
|
367
|
-
gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor))
|
|
368
|
-
self.brush_cursor.setBrush(Qt.NoBrush)
|
|
369
|
-
|
|
370
|
-
def _setup_simple_brush_style(self, center_x, center_y, radius):
|
|
371
|
-
gradient = create_default_brush_gradient(center_x, center_y, radius, self.brush)
|
|
372
|
-
self.brush_cursor.setPen(QPen(QColor(*gui_constants.BRUSH_COLORS['pen']),
|
|
373
|
-
gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor))
|
|
374
|
-
self.brush_cursor.setBrush(QBrush(gradient))
|
|
85
|
+
self.strategy.setup_brush_cursor()
|
|
375
86
|
|
|
376
87
|
def zoom_in(self):
|
|
377
|
-
|
|
378
|
-
return
|
|
379
|
-
current_scale = self.get_current_scale()
|
|
380
|
-
new_scale = current_scale * gui_constants.ZOOM_IN_FACTOR
|
|
381
|
-
if new_scale <= self.max_scale:
|
|
382
|
-
self.scale(gui_constants.ZOOM_IN_FACTOR, gui_constants.ZOOM_IN_FACTOR)
|
|
383
|
-
self.zoom_factor = new_scale
|
|
384
|
-
self.update_brush_cursor()
|
|
88
|
+
self.strategy.zoom_in()
|
|
385
89
|
|
|
386
90
|
def zoom_out(self):
|
|
387
|
-
|
|
388
|
-
return
|
|
389
|
-
current_scale = self.get_current_scale()
|
|
390
|
-
new_scale = current_scale * gui_constants.ZOOM_OUT_FACTOR
|
|
391
|
-
if new_scale >= self.min_scale:
|
|
392
|
-
self.scale(gui_constants.ZOOM_OUT_FACTOR, gui_constants.ZOOM_OUT_FACTOR)
|
|
393
|
-
self.zoom_factor = new_scale
|
|
394
|
-
self.update_brush_cursor()
|
|
91
|
+
self.strategy.zoom_out()
|
|
395
92
|
|
|
396
93
|
def reset_zoom(self):
|
|
397
|
-
|
|
398
|
-
return
|
|
399
|
-
self.pinch_start_scale = 1.0
|
|
400
|
-
self.last_scroll_pos = QPointF()
|
|
401
|
-
self.gesture_active = False
|
|
402
|
-
self.pinch_center_view = None
|
|
403
|
-
self.pinch_center_scene = None
|
|
404
|
-
self.fitInView(self.pixmap_item, Qt.KeepAspectRatio)
|
|
405
|
-
self.zoom_factor = self.get_current_scale()
|
|
406
|
-
self.zoom_factor = max(self.min_scale, min(self.max_scale, self.zoom_factor))
|
|
407
|
-
self.resetTransform()
|
|
408
|
-
self.scale(self.zoom_factor, self.zoom_factor)
|
|
409
|
-
self.update_brush_cursor()
|
|
94
|
+
self.strategy.reset_zoom()
|
|
410
95
|
|
|
411
96
|
def actual_size(self):
|
|
412
|
-
|
|
413
|
-
return
|
|
414
|
-
self.zoom_factor = max(self.min_scale, min(self.max_scale, 1.0))
|
|
415
|
-
self.resetTransform()
|
|
416
|
-
self.scale(self.zoom_factor, self.zoom_factor)
|
|
417
|
-
self.update_brush_cursor()
|
|
97
|
+
self.strategy.actual_size()
|
|
418
98
|
|
|
419
99
|
def get_current_scale(self):
|
|
420
|
-
return self.
|
|
100
|
+
return self.strategy.get_current_scale()
|
|
421
101
|
|
|
422
|
-
def
|
|
423
|
-
return
|
|
424
|
-
'zoom': self.zoom_factor,
|
|
425
|
-
'h_scroll': self.horizontalScrollBar().value(),
|
|
426
|
-
'v_scroll': self.verticalScrollBar().value()
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
def set_view_state(self, state):
|
|
430
|
-
if state:
|
|
431
|
-
self.resetTransform()
|
|
432
|
-
self.scale(state['zoom'], state['zoom'])
|
|
433
|
-
self.horizontalScrollBar().setValue(state['h_scroll'])
|
|
434
|
-
self.verticalScrollBar().setValue(state['v_scroll'])
|
|
435
|
-
self.zoom_factor = state['zoom']
|
|
102
|
+
def get_cursor_style(self):
|
|
103
|
+
return self.strategy.get_cursor_style()
|
|
436
104
|
|
|
437
105
|
def set_cursor_style(self, style):
|
|
438
|
-
self.
|
|
439
|
-
if self.brush_cursor:
|
|
440
|
-
self.update_brush_cursor()
|
|
106
|
+
self.strategy.set_cursor_style(style)
|
|
441
107
|
|
|
442
108
|
def position_on_image(self, pos):
|
|
443
|
-
|
|
444
|
-
item_pos = self.pixmap_item.mapFromScene(scene_pos)
|
|
445
|
-
return item_pos
|
|
446
|
-
|
|
447
|
-
def get_visible_image_region(self):
|
|
448
|
-
if self.empty:
|
|
449
|
-
return None
|
|
450
|
-
view_rect = self.viewport().rect()
|
|
451
|
-
scene_rect = self.mapToScene(view_rect).boundingRect()
|
|
452
|
-
image_rect = self.pixmap_item.mapFromScene(scene_rect).boundingRect()
|
|
453
|
-
image_rect = image_rect.intersected(self.pixmap_item.boundingRect().toRect())
|
|
454
|
-
return image_rect
|
|
109
|
+
return self.strategy.position_on_image(pos)
|
|
455
110
|
|
|
456
111
|
def get_visible_image_portion(self):
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
112
|
+
return self.strategy.get_visible_image_portion()
|
|
113
|
+
|
|
114
|
+
def connect_signals(
|
|
115
|
+
self, handle_temp_view, begin_copy_brush_area, continue_copy_brush_area,
|
|
116
|
+
end_copy_brush_area, handle_brush_size_change):
|
|
117
|
+
for st in self._strategies.values():
|
|
118
|
+
st.temp_view_requested.connect(handle_temp_view)
|
|
119
|
+
st.brush_operation_started.connect(begin_copy_brush_area)
|
|
120
|
+
st.brush_operation_continued.connect(continue_copy_brush_area)
|
|
121
|
+
st.brush_operation_ended.connect(end_copy_brush_area)
|
|
122
|
+
st.brush_size_change_requested.connect(handle_brush_size_change)
|
|
123
|
+
st.setFocusPolicy(Qt.StrongFocus)
|
|
@@ -16,6 +16,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
16
16
|
update_title_requested = Signal()
|
|
17
17
|
mark_as_modified_requested = Signal(bool)
|
|
18
18
|
change_layer_requested = Signal(int)
|
|
19
|
+
add_recent_file_requested = Signal(str)
|
|
19
20
|
|
|
20
21
|
def __init__(self, layer_collection, undo_manager, parent):
|
|
21
22
|
QObject.__init__(self, parent)
|
|
@@ -54,9 +55,13 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
54
55
|
else:
|
|
55
56
|
self.set_layer_labels(labels)
|
|
56
57
|
self.set_master_layer(master_layer)
|
|
58
|
+
self.image_viewer.set_master_image_np(master_layer)
|
|
59
|
+
self.image_viewer.show_master()
|
|
60
|
+
self.image_viewer.update_master_display()
|
|
57
61
|
self.undo_manager.reset()
|
|
58
62
|
self.blank_layer = np.zeros(master_layer.shape[:2])
|
|
59
63
|
self.finish_loading_setup(f"Loaded: {self.current_file_path()}")
|
|
64
|
+
self.image_viewer.reset_zoom()
|
|
60
65
|
|
|
61
66
|
def on_file_error(self, error_msg):
|
|
62
67
|
QApplication.restoreOverrideCursor()
|
|
@@ -75,6 +80,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
75
80
|
self.saving_dialog.deleteLater()
|
|
76
81
|
self.mark_as_modified_requested.emit(False)
|
|
77
82
|
self.update_title_requested.emit()
|
|
83
|
+
self.add_recent_file_requested.emit(self.current_file_path_multi)
|
|
78
84
|
self.status_message_requested.emit(f"Saved multilayer to: {self.current_file_path_multi}")
|
|
79
85
|
|
|
80
86
|
def on_multilayer_save_error(self, error_msg):
|
|
@@ -126,6 +132,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
126
132
|
self.status_message_requested.emit("Imported selected frames")
|
|
127
133
|
|
|
128
134
|
def import_frames_from_files(self, file_paths):
|
|
135
|
+
empty_viewer = self.image_viewer.empty()
|
|
129
136
|
try:
|
|
130
137
|
stack, labels, master = self.io_manager.import_frames(file_paths)
|
|
131
138
|
except Exception as e:
|
|
@@ -135,6 +142,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
135
142
|
msg.setText(str(e))
|
|
136
143
|
msg.exec()
|
|
137
144
|
return
|
|
145
|
+
self.image_viewer.set_master_image_np(master)
|
|
138
146
|
if self.layer_stack() is None and len(stack) > 0:
|
|
139
147
|
self.set_layer_stack(np.array(stack))
|
|
140
148
|
if labels is None:
|
|
@@ -150,15 +158,17 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
150
158
|
self.add_layer_label(label)
|
|
151
159
|
self.add_layer(img)
|
|
152
160
|
self.finish_loading_setup("Selected frames imported")
|
|
161
|
+
if empty_viewer:
|
|
162
|
+
self.image_viewer.reset_zoom()
|
|
153
163
|
|
|
154
164
|
def finish_loading_setup(self, message):
|
|
155
165
|
self.display_manager.update_thumbnails()
|
|
156
166
|
self.mark_as_modified_requested.emit(True)
|
|
157
167
|
self.change_layer_requested.emit(0)
|
|
158
168
|
self.image_viewer.setup_brush_cursor()
|
|
159
|
-
self.image_viewer.reset_zoom()
|
|
160
169
|
self.status_message_requested.emit(message)
|
|
161
170
|
self.update_title_requested.emit()
|
|
171
|
+
self.add_recent_file_requested.emit(self.current_file_path_master)
|
|
162
172
|
|
|
163
173
|
def save_file(self):
|
|
164
174
|
if self.save_master_only.isChecked():
|
|
@@ -218,7 +228,6 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
218
228
|
self.saving_timer.timeout.connect(self.saving_dialog.show)
|
|
219
229
|
self.saving_timer.start(100)
|
|
220
230
|
self.saver_thread.start()
|
|
221
|
-
|
|
222
231
|
except Exception as e:
|
|
223
232
|
traceback.print_tb(e.__traceback__)
|
|
224
233
|
QMessageBox.critical(self.parent(), "Save Error", f"Could not save file: {str(e)}")
|
|
@@ -247,6 +256,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
247
256
|
self.mark_as_modified_requested.emit(False)
|
|
248
257
|
self.update_title_requested.emit()
|
|
249
258
|
self.status_message_requested.emit(f"Saved master layer to: {path}")
|
|
259
|
+
self.add_recent_file_requested.emit(self.current_file_path_master)
|
|
250
260
|
except Exception as e:
|
|
251
261
|
traceback.print_tb(e.__traceback__)
|
|
252
262
|
QMessageBox.critical(self.parent(), "Save Error", f"Could not save file: {str(e)}")
|