shinestacker 0.2.0.post1.dev1__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 (67) hide show
  1. shinestacker/__init__.py +3 -0
  2. shinestacker/_version.py +1 -0
  3. shinestacker/algorithms/__init__.py +14 -0
  4. shinestacker/algorithms/align.py +307 -0
  5. shinestacker/algorithms/balance.py +367 -0
  6. shinestacker/algorithms/core_utils.py +22 -0
  7. shinestacker/algorithms/depth_map.py +164 -0
  8. shinestacker/algorithms/exif.py +238 -0
  9. shinestacker/algorithms/multilayer.py +187 -0
  10. shinestacker/algorithms/noise_detection.py +182 -0
  11. shinestacker/algorithms/pyramid.py +176 -0
  12. shinestacker/algorithms/stack.py +112 -0
  13. shinestacker/algorithms/stack_framework.py +248 -0
  14. shinestacker/algorithms/utils.py +71 -0
  15. shinestacker/algorithms/vignetting.py +137 -0
  16. shinestacker/app/__init__.py +0 -0
  17. shinestacker/app/about_dialog.py +24 -0
  18. shinestacker/app/app_config.py +39 -0
  19. shinestacker/app/gui_utils.py +35 -0
  20. shinestacker/app/help_menu.py +16 -0
  21. shinestacker/app/main.py +176 -0
  22. shinestacker/app/open_frames.py +39 -0
  23. shinestacker/app/project.py +91 -0
  24. shinestacker/app/retouch.py +82 -0
  25. shinestacker/config/__init__.py +4 -0
  26. shinestacker/config/config.py +53 -0
  27. shinestacker/config/constants.py +174 -0
  28. shinestacker/config/gui_constants.py +85 -0
  29. shinestacker/core/__init__.py +5 -0
  30. shinestacker/core/colors.py +60 -0
  31. shinestacker/core/core_utils.py +52 -0
  32. shinestacker/core/exceptions.py +50 -0
  33. shinestacker/core/framework.py +210 -0
  34. shinestacker/core/logging.py +89 -0
  35. shinestacker/gui/__init__.py +0 -0
  36. shinestacker/gui/action_config.py +879 -0
  37. shinestacker/gui/actions_window.py +283 -0
  38. shinestacker/gui/colors.py +57 -0
  39. shinestacker/gui/gui_images.py +152 -0
  40. shinestacker/gui/gui_logging.py +213 -0
  41. shinestacker/gui/gui_run.py +393 -0
  42. shinestacker/gui/img/close-round-line-icon.png +0 -0
  43. shinestacker/gui/img/forward-button-icon.png +0 -0
  44. shinestacker/gui/img/play-button-round-icon.png +0 -0
  45. shinestacker/gui/img/plus-round-line-icon.png +0 -0
  46. shinestacker/gui/main_window.py +599 -0
  47. shinestacker/gui/new_project.py +170 -0
  48. shinestacker/gui/project_converter.py +148 -0
  49. shinestacker/gui/project_editor.py +539 -0
  50. shinestacker/gui/project_model.py +138 -0
  51. shinestacker/retouch/__init__.py +0 -0
  52. shinestacker/retouch/brush.py +9 -0
  53. shinestacker/retouch/brush_controller.py +57 -0
  54. shinestacker/retouch/brush_preview.py +126 -0
  55. shinestacker/retouch/exif_data.py +65 -0
  56. shinestacker/retouch/file_loader.py +104 -0
  57. shinestacker/retouch/image_editor.py +651 -0
  58. shinestacker/retouch/image_editor_ui.py +380 -0
  59. shinestacker/retouch/image_viewer.py +356 -0
  60. shinestacker/retouch/shortcuts_help.py +98 -0
  61. shinestacker/retouch/undo_manager.py +38 -0
  62. shinestacker-0.2.0.post1.dev1.dist-info/METADATA +55 -0
  63. shinestacker-0.2.0.post1.dev1.dist-info/RECORD +67 -0
  64. shinestacker-0.2.0.post1.dev1.dist-info/WHEEL +5 -0
  65. shinestacker-0.2.0.post1.dev1.dist-info/entry_points.txt +4 -0
  66. shinestacker-0.2.0.post1.dev1.dist-info/licenses/LICENSE +1 -0
  67. shinestacker-0.2.0.post1.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,380 @@
1
+ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QListWidget, QListWidgetItem, QSlider, QInputDialog
2
+ from PySide6.QtGui import QShortcut, QKeySequence, QAction, QActionGroup
3
+ from PySide6.QtCore import Qt, QSize, Signal
4
+ from PySide6.QtGui import QGuiApplication
5
+ from .. config.gui_constants import gui_constants
6
+ from .image_editor import ImageEditor
7
+ from .image_viewer import ImageViewer
8
+ from .shortcuts_help import ShortcutsHelp
9
+
10
+
11
+ def brush_size_to_slider(size):
12
+ if size <= gui_constants.BRUSH_SIZES['min']:
13
+ return 0
14
+ if size >= gui_constants.BRUSH_SIZES['max']:
15
+ return gui_constants.BRUSH_SIZE_SLIDER_MAX
16
+ normalized = ((size - gui_constants.BRUSH_SIZES['min']) / gui_constants.BRUSH_SIZES['max']) ** (1 / gui_constants.BRUSH_GAMMA)
17
+ return int(normalized * gui_constants.BRUSH_SIZE_SLIDER_MAX)
18
+
19
+
20
+ class ClickableLabel(QLabel):
21
+ """QLabel personalizzata che emette un segnale quando viene doppio-clickata"""
22
+ doubleClicked = Signal() # PySide6 usa Signal invece di pyqtSignal
23
+
24
+ def __init__(self, text, parent=None):
25
+ super().__init__(text, parent)
26
+ self.setMouseTracking(True)
27
+
28
+ def mouseDoubleClickEvent(self, event):
29
+ """Override del doppio click"""
30
+ if event.button() == Qt.LeftButton:
31
+ self.doubleClicked.emit()
32
+ super().mouseDoubleClickEvent(event)
33
+
34
+
35
+ class ImageEditorUI(ImageEditor):
36
+ def __init__(self):
37
+ super().__init__()
38
+ self.setup_ui()
39
+ self.setup_menu()
40
+ self.setup_shortcuts()
41
+
42
+ def setup_shortcuts(self):
43
+ prev_layer = QShortcut(QKeySequence(Qt.Key_Up), self, context=Qt.ApplicationShortcut)
44
+ prev_layer.activated.connect(self.prev_layer)
45
+ next_layer = QShortcut(QKeySequence(Qt.Key_Down), self, context=Qt.ApplicationShortcut)
46
+ next_layer.activated.connect(self.next_layer)
47
+
48
+ def setup_ui(self):
49
+ self.update_title()
50
+ self.resize(1400, 900)
51
+ center = QGuiApplication.primaryScreen().geometry().center()
52
+ self.move(center - self.rect().center())
53
+ central_widget = QWidget()
54
+ self.setCentralWidget(central_widget)
55
+ layout = QHBoxLayout(central_widget)
56
+ self.image_viewer = ImageViewer()
57
+ self.image_viewer.temp_view_requested.connect(self.handle_temp_view)
58
+ self.image_viewer.image_editor = self
59
+ self.image_viewer.brush = self.brush_controller.brush
60
+ self.image_viewer.setFocusPolicy(Qt.StrongFocus)
61
+ side_panel = QWidget()
62
+ side_layout = QVBoxLayout(side_panel)
63
+ side_layout.setContentsMargins(0, 0, 0, 0)
64
+ side_layout.setSpacing(2)
65
+
66
+ brush_panel = QFrame()
67
+ brush_panel.setFrameShape(QFrame.StyledPanel)
68
+ brush_panel.setContentsMargins(0, 0, 0, 0)
69
+ brush_layout = QVBoxLayout(brush_panel)
70
+ brush_layout.setContentsMargins(0, 0, 0, 0)
71
+ brush_layout.setSpacing(2)
72
+
73
+ brush_label = QLabel("Brush Size")
74
+ brush_label.setAlignment(Qt.AlignCenter)
75
+ brush_layout.addWidget(brush_label)
76
+
77
+ self.brush_size_slider = QSlider(Qt.Horizontal)
78
+ self.brush_size_slider.setRange(0, gui_constants.BRUSH_SIZE_SLIDER_MAX)
79
+ self.brush_size_slider.setValue(brush_size_to_slider(self.brush.size))
80
+ self.brush_size_slider.valueChanged.connect(self.update_brush_size)
81
+ brush_layout.addWidget(self.brush_size_slider)
82
+
83
+ hardness_label = QLabel("Brush Hardness")
84
+ hardness_label.setAlignment(Qt.AlignCenter)
85
+ brush_layout.addWidget(hardness_label)
86
+ self.hardness_slider = QSlider(Qt.Horizontal)
87
+ self.hardness_slider.setRange(0, 100)
88
+ self.hardness_slider.setValue(self.brush.hardness)
89
+ self.hardness_slider.valueChanged.connect(self.update_brush_hardness)
90
+ brush_layout.addWidget(self.hardness_slider)
91
+
92
+ opacity_label = QLabel("Brush Opacity")
93
+ opacity_label.setAlignment(Qt.AlignCenter)
94
+ brush_layout.addWidget(opacity_label)
95
+ self.opacity_slider = QSlider(Qt.Horizontal)
96
+ self.opacity_slider.setRange(0, 100)
97
+ self.opacity_slider.setValue(self.brush.opacity)
98
+ self.opacity_slider.valueChanged.connect(self.update_brush_opacity)
99
+ brush_layout.addWidget(self.opacity_slider)
100
+
101
+ flow_label = QLabel("Brush Flow")
102
+ flow_label.setAlignment(Qt.AlignCenter)
103
+ brush_layout.addWidget(flow_label)
104
+ self.flow_slider = QSlider(Qt.Horizontal)
105
+ self.flow_slider.setRange(1, 100)
106
+ self.flow_slider.setValue(self.brush.flow)
107
+ self.flow_slider.valueChanged.connect(self.update_brush_flow)
108
+ brush_layout.addWidget(self.flow_slider)
109
+
110
+ side_layout.addWidget(brush_panel)
111
+ self.brush_preview = QLabel()
112
+ self.brush_preview.setContentsMargins(0, 0, 0, 0)
113
+ self.brush_preview.setStyleSheet("""
114
+ QLabel {
115
+ background-color: #f0f0f0;
116
+ border: 1px solid #ccc;
117
+ border-radius: 4px;
118
+ padding: 0px;
119
+ margin: 0px;
120
+ }
121
+ """)
122
+ self.brush_preview.setAlignment(Qt.AlignCenter)
123
+ self.brush_preview.setFixedHeight(100)
124
+ self.update_brush_thumb()
125
+ brush_layout.addWidget(self.brush_preview)
126
+ side_layout.addWidget(brush_panel)
127
+
128
+ master_label = QLabel("Master")
129
+ master_label.setStyleSheet("""
130
+ QLabel {
131
+ font-weight: bold;
132
+ font-size: 11px;
133
+ padding: 2px;
134
+ color: #444;
135
+ border-bottom: 1px solid #ddd;
136
+ background: #f5f5f5;
137
+ }
138
+ """)
139
+ master_label.setAlignment(Qt.AlignCenter)
140
+ master_label.setFixedHeight(gui_constants.LABEL_HEIGHT)
141
+ side_layout.addWidget(master_label)
142
+ self.master_thumbnail_frame = QFrame()
143
+ self.master_thumbnail_frame.setFrameShape(QFrame.StyledPanel)
144
+ master_thumbnail_layout = QVBoxLayout(self.master_thumbnail_frame)
145
+ master_thumbnail_layout.setContentsMargins(2, 2, 2, 2)
146
+ self.master_thumbnail_label = QLabel()
147
+ self.master_thumbnail_label.setAlignment(Qt.AlignCenter)
148
+ self.master_thumbnail_label.setFixedSize(gui_constants.THUMB_WIDTH, gui_constants.THUMB_HEIGHT)
149
+ self.master_thumbnail_label.mousePressEvent = lambda e: self.set_view_master()
150
+ master_thumbnail_layout.addWidget(self.master_thumbnail_label)
151
+ side_layout.addWidget(self.master_thumbnail_frame)
152
+ side_layout.addSpacing(10)
153
+ layers_label = QLabel("Layers")
154
+ layers_label.setStyleSheet("""
155
+ QLabel {
156
+ font-weight: bold;
157
+ font-size: 11px;
158
+ padding: 2px;
159
+ color: #444;
160
+ border-bottom: 1px solid #ddd;
161
+ background: #f5f5f5;
162
+ }
163
+ """)
164
+ layers_label.setAlignment(Qt.AlignCenter)
165
+ layers_label.setFixedHeight(gui_constants.LABEL_HEIGHT)
166
+ side_layout.addWidget(layers_label)
167
+ self.thumbnail_list = QListWidget()
168
+ self.thumbnail_list.setFocusPolicy(Qt.StrongFocus)
169
+ self.thumbnail_list.setViewMode(QListWidget.ListMode)
170
+ self.thumbnail_list.setUniformItemSizes(True)
171
+ self.thumbnail_list.setResizeMode(QListWidget.Adjust)
172
+ self.thumbnail_list.setFlow(QListWidget.TopToBottom)
173
+ self.thumbnail_list.setMovement(QListWidget.Static)
174
+ self.thumbnail_list.setFixedWidth(gui_constants.THUMB_WIDTH)
175
+ self.thumbnail_list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
176
+ self.thumbnail_list.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
177
+ self.thumbnail_list.itemClicked.connect(self.change_layer_item)
178
+ self.thumbnail_list.setStyleSheet("""
179
+ QListWidget {
180
+ background-color: #f5f5f5;
181
+ border: 1px solid #ddd;
182
+ }
183
+ QListWidget::item {
184
+ height: 130px;
185
+ width: 110px;
186
+ }
187
+ QListWidget::item:selected {
188
+ background-color: #e0e0e0;
189
+ border: 1px solid #aaa;
190
+ }
191
+ QScrollBar:vertical {
192
+ border: none;
193
+ background: #f5f5f5;
194
+ width: 10px;
195
+ margin: 0px 0px 0px 0px;
196
+ }
197
+ QScrollBar::handle:vertical {
198
+ background: #ccc;
199
+ min-height: 20px;
200
+ border-radius: 6px;
201
+ }
202
+ """)
203
+ side_layout.addWidget(self.thumbnail_list, 1)
204
+ control_panel = QWidget()
205
+ layout.addWidget(self.image_viewer, 1)
206
+ layout.addWidget(side_panel, 0)
207
+ layout.addWidget(control_panel, 0)
208
+ layout.setContentsMargins(0, 0, 0, 0)
209
+ layout.setSpacing(2)
210
+
211
+ def setup_menu(self):
212
+ menubar = self.menuBar()
213
+ file_menu = menubar.addMenu("&File")
214
+ file_menu.addAction("&Open...", self.open_file, "Ctrl+O")
215
+ file_menu.addAction("&Save", self.save_file, "Ctrl+S")
216
+ file_menu.addAction("Save &As...", self.save_file_as, "Ctrl+Shift+S")
217
+ self.save_master_only = QAction("Save Master &Only", self)
218
+ self.save_master_only.setCheckable(True)
219
+ self.save_master_only.setChecked(True)
220
+ file_menu.addAction(self.save_master_only)
221
+
222
+ file_menu.addAction("&Close", self.close_file, "Ctrl+W")
223
+ file_menu.addSeparator()
224
+ file_menu.addAction("&Import frames", self.import_frames)
225
+ file_menu.addAction("Import &EXIF data", self.select_exif_path)
226
+
227
+ edit_menu = menubar.addMenu("&Edit")
228
+ undo_action = QAction("Undo Brush", self)
229
+ undo_action.setShortcut("Ctrl+Z")
230
+ undo_action.triggered.connect(self.undo_last_brush)
231
+ edit_menu.addAction(undo_action)
232
+ edit_menu.addSeparator()
233
+
234
+ copy_action = QAction("Copy Layer to Master", self)
235
+ copy_action.setShortcut("Ctrl+M")
236
+ copy_action.triggered.connect(self.copy_layer_to_master)
237
+ edit_menu.addAction(copy_action)
238
+
239
+ view_menu = menubar.addMenu("&View")
240
+
241
+ fullscreen_action = QAction("Full Screen", self)
242
+ fullscreen_action.setShortcut("Ctrl+Cmd+F")
243
+ fullscreen_action.setCheckable(True)
244
+ fullscreen_action.triggered.connect(self.toggle_fullscreen)
245
+ view_menu.addAction(fullscreen_action)
246
+
247
+ view_menu.addSeparator()
248
+
249
+ zoom_in_action = QAction("Zoom In", self)
250
+ zoom_in_action.setShortcut("Ctrl++")
251
+ zoom_in_action.triggered.connect(self.image_viewer.zoom_in)
252
+ view_menu.addAction(zoom_in_action)
253
+
254
+ zoom_out_action = QAction("Zoom Out", self)
255
+ zoom_out_action.setShortcut("Ctrl+-")
256
+ zoom_out_action.triggered.connect(self.image_viewer.zoom_out)
257
+ view_menu.addAction(zoom_out_action)
258
+
259
+ adapt_action = QAction("Adapt to Screen", self)
260
+ adapt_action.setShortcut("Ctrl+0")
261
+ adapt_action.triggered.connect(self.image_viewer.reset_zoom)
262
+ view_menu.addAction(adapt_action)
263
+
264
+ actual_size_action = QAction("Actual Size", self)
265
+ actual_size_action.setShortcut("Ctrl+=")
266
+ actual_size_action.triggered.connect(self.image_viewer.actual_size)
267
+ view_menu.addAction(actual_size_action)
268
+ view_menu.addSeparator()
269
+
270
+ view_master_action = QAction("View Master", self)
271
+ view_master_action.setShortcut("M")
272
+ view_master_action.triggered.connect(self.set_view_master)
273
+ view_menu.addAction(view_master_action)
274
+
275
+ view_individual_action = QAction("View Individual", self)
276
+ view_individual_action.setShortcut("L")
277
+ view_individual_action.triggered.connect(self.set_view_individual)
278
+ view_menu.addAction(view_individual_action)
279
+ view_menu.addSeparator()
280
+
281
+ sort_asc_action = QAction("Sort Layers A-Z", self)
282
+ sort_asc_action.triggered.connect(lambda: self.sort_layers('asc'))
283
+ view_menu.addAction(sort_asc_action)
284
+
285
+ sort_desc_action = QAction("Sort Layers Z-A", self)
286
+ sort_desc_action.triggered.connect(lambda: self.sort_layers('desc'))
287
+ view_menu.addAction(sort_desc_action)
288
+
289
+ view_menu.addSeparator()
290
+
291
+ cursor_menu = view_menu.addMenu("Cursor Style")
292
+
293
+ brush_action = QAction("Simple Brush", self)
294
+ brush_action.setCheckable(True)
295
+ brush_action.setChecked(self.image_viewer.cursor_style == 'brush')
296
+ brush_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('brush'))
297
+ cursor_menu.addAction(brush_action)
298
+
299
+ preview_action = QAction("Brush Preview", self)
300
+ preview_action.setCheckable(True)
301
+ preview_action.setChecked(self.image_viewer.cursor_style == 'preview')
302
+ preview_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('preview'))
303
+ cursor_menu.addAction(preview_action)
304
+
305
+ outline_action = QAction("Outline Only", self)
306
+ outline_action.setCheckable(True)
307
+ outline_action.setChecked(self.image_viewer.cursor_style == 'outline')
308
+ outline_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('outline'))
309
+ cursor_menu.addAction(outline_action)
310
+
311
+ cursor_group = QActionGroup(self)
312
+ cursor_group.addAction(preview_action)
313
+ cursor_group.addAction(outline_action)
314
+ cursor_group.addAction(brush_action)
315
+ cursor_group.setExclusive(True)
316
+
317
+ help_menu = menubar.addMenu("&Help")
318
+ help_menu.setObjectName("Help")
319
+ shortcuts_help_action = QAction("Shortcuts and mouse", self)
320
+ shortcuts_help_action.triggered.connect(self.shortcuts_help)
321
+ help_menu.addAction(shortcuts_help_action)
322
+
323
+ def shortcuts_help(self):
324
+ self._dialog = ShortcutsHelp(self)
325
+ self._dialog.exec()
326
+
327
+ def toggle_fullscreen(self, checked):
328
+ if checked:
329
+ self.window().showFullScreen()
330
+ else:
331
+ self.window().showNormal()
332
+
333
+ def quit(self):
334
+ if self._check_unsaved_changes():
335
+ self.close()
336
+
337
+ def _add_thumbnail_item(self, thumbnail, label, i, is_current):
338
+ item_widget = QWidget()
339
+ layout = QVBoxLayout(item_widget)
340
+ layout.setContentsMargins(0, 0, 0, 0)
341
+ layout.setSpacing(0)
342
+
343
+ thumbnail_label = QLabel()
344
+ thumbnail_label.setPixmap(thumbnail)
345
+ thumbnail_label.setAlignment(Qt.AlignCenter)
346
+ layout.addWidget(thumbnail_label)
347
+
348
+ label_widget = ClickableLabel(label)
349
+ label_widget.setAlignment(Qt.AlignCenter)
350
+ label_widget.doubleClicked.connect(lambda: self._rename_label(label_widget, label, i))
351
+ layout.addWidget(label_widget)
352
+
353
+ item = QListWidgetItem()
354
+ item.setSizeHint(QSize(gui_constants.IMG_WIDTH, gui_constants.IMG_HEIGHT))
355
+ self.thumbnail_list.addItem(item)
356
+ self.thumbnail_list.setItemWidget(item, item_widget)
357
+
358
+ if is_current:
359
+ self.thumbnail_list.setCurrentItem(item)
360
+
361
+ def _rename_label(self, label_widget, old_label, i):
362
+ new_label, ok = QInputDialog.getText(self.thumbnail_list, "Rename Label", "New label name:", text=old_label)
363
+ if ok and new_label and new_label != old_label:
364
+ label_widget.setText(new_label)
365
+ self._update_label_in_data(old_label, new_label, i)
366
+
367
+ def _update_label_in_data(self, old_label, new_label, i):
368
+ self.current_labels[i] = new_label
369
+
370
+ def undo_last_brush(self):
371
+ if self.undo_manager.undo(self.master_layer):
372
+ self.display_current_view()
373
+ self.mark_as_modified()
374
+ self.statusBar().showMessage("Undo applied", 2000)
375
+
376
+ def handle_temp_view(self, start):
377
+ if start:
378
+ self.start_temp_view()
379
+ else:
380
+ self.end_temp_view()