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.

Files changed (50) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +229 -41
  3. shinestacker/algorithms/align_auto.py +15 -3
  4. shinestacker/algorithms/align_parallel.py +81 -25
  5. shinestacker/algorithms/balance.py +23 -13
  6. shinestacker/algorithms/base_stack_algo.py +14 -20
  7. shinestacker/algorithms/depth_map.py +9 -14
  8. shinestacker/algorithms/noise_detection.py +3 -1
  9. shinestacker/algorithms/pyramid.py +8 -22
  10. shinestacker/algorithms/pyramid_auto.py +5 -14
  11. shinestacker/algorithms/pyramid_tiles.py +18 -20
  12. shinestacker/algorithms/stack_framework.py +1 -1
  13. shinestacker/algorithms/utils.py +37 -10
  14. shinestacker/algorithms/vignetting.py +2 -0
  15. shinestacker/app/gui_utils.py +10 -0
  16. shinestacker/app/main.py +3 -1
  17. shinestacker/app/project.py +3 -1
  18. shinestacker/app/retouch.py +3 -1
  19. shinestacker/config/gui_constants.py +2 -2
  20. shinestacker/core/core_utils.py +10 -1
  21. shinestacker/gui/action_config.py +172 -7
  22. shinestacker/gui/action_config_dialog.py +443 -452
  23. shinestacker/gui/colors.py +1 -0
  24. shinestacker/gui/folder_file_selection.py +5 -0
  25. shinestacker/gui/gui_run.py +2 -2
  26. shinestacker/gui/main_window.py +18 -9
  27. shinestacker/gui/menu_manager.py +26 -2
  28. shinestacker/gui/new_project.py +5 -5
  29. shinestacker/gui/project_controller.py +4 -0
  30. shinestacker/gui/project_editor.py +6 -4
  31. shinestacker/gui/recent_file_manager.py +93 -0
  32. shinestacker/gui/sys_mon.py +24 -23
  33. shinestacker/retouch/base_filter.py +5 -5
  34. shinestacker/retouch/brush_preview.py +3 -0
  35. shinestacker/retouch/brush_tool.py +11 -11
  36. shinestacker/retouch/display_manager.py +21 -37
  37. shinestacker/retouch/image_editor_ui.py +129 -71
  38. shinestacker/retouch/image_view_status.py +61 -0
  39. shinestacker/retouch/image_viewer.py +89 -431
  40. shinestacker/retouch/io_gui_handler.py +12 -2
  41. shinestacker/retouch/overlaid_view.py +212 -0
  42. shinestacker/retouch/shortcuts_help.py +13 -3
  43. shinestacker/retouch/sidebyside_view.py +479 -0
  44. shinestacker/retouch/view_strategy.py +466 -0
  45. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/METADATA +1 -1
  46. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/RECORD +50 -45
  47. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/WHEEL +0 -0
  48. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/entry_points.txt +0 -0
  49. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/licenses/LICENSE +0 -0
  50. {shinestacker-1.3.0.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 + =": "actual size",
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(left_layout, "Keyboard Shortcuts")
81
+ self.add_bold_label(right_layout, "Keyboard Shortcuts")
72
82
  for k, v in shortcuts.items():
73
- left_layout.addRow(f"<b>{k}</b>", QLabel(v))
83
+ right_layout.addRow(f"<b>{k}</b>", QLabel(v))
74
84
 
75
85
  mouse_controls = {
76
86
  "Space + Drag": "pan",