shinestacker 0.3.0__py3-none-any.whl → 0.3.2__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 (43) hide show
  1. shinestacker/__init__.py +6 -6
  2. shinestacker/_version.py +1 -1
  3. shinestacker/algorithms/balance.py +6 -7
  4. shinestacker/algorithms/noise_detection.py +2 -0
  5. shinestacker/algorithms/utils.py +4 -0
  6. shinestacker/algorithms/white_balance.py +13 -0
  7. shinestacker/app/open_frames.py +6 -4
  8. shinestacker/config/__init__.py +2 -1
  9. shinestacker/config/config.py +1 -0
  10. shinestacker/config/constants.py +1 -0
  11. shinestacker/config/gui_constants.py +1 -0
  12. shinestacker/core/__init__.py +4 -3
  13. shinestacker/core/colors.py +1 -0
  14. shinestacker/core/core_utils.py +6 -6
  15. shinestacker/core/exceptions.py +1 -0
  16. shinestacker/core/framework.py +2 -1
  17. shinestacker/gui/action_config.py +47 -42
  18. shinestacker/gui/actions_window.py +8 -5
  19. shinestacker/gui/new_project.py +1 -0
  20. shinestacker/retouch/brush_gradient.py +20 -0
  21. shinestacker/retouch/brush_preview.py +10 -14
  22. shinestacker/retouch/brush_tool.py +164 -0
  23. shinestacker/retouch/denoise_filter.py +56 -0
  24. shinestacker/retouch/display_manager.py +177 -0
  25. shinestacker/retouch/exif_data.py +2 -1
  26. shinestacker/retouch/filter_base.py +114 -0
  27. shinestacker/retouch/filter_manager.py +14 -0
  28. shinestacker/retouch/image_editor.py +108 -543
  29. shinestacker/retouch/image_editor_ui.py +42 -75
  30. shinestacker/retouch/image_filters.py +27 -423
  31. shinestacker/retouch/image_viewer.py +31 -31
  32. shinestacker/retouch/io_gui_handler.py +208 -0
  33. shinestacker/retouch/io_manager.py +53 -0
  34. shinestacker/retouch/layer_collection.py +118 -0
  35. shinestacker/retouch/unsharp_mask_filter.py +84 -0
  36. shinestacker/retouch/white_balance_filter.py +111 -0
  37. {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/METADATA +3 -2
  38. {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/RECORD +42 -31
  39. shinestacker/retouch/brush_controller.py +0 -57
  40. {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/WHEEL +0 -0
  41. {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/entry_points.txt +0 -0
  42. {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/licenses/LICENSE +0 -0
  43. {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,12 @@
1
- from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QListWidget, QListWidgetItem, QSlider, QInputDialog
1
+ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QListWidget, QSlider
2
2
  from PySide6.QtGui import QShortcut, QKeySequence, QAction, QActionGroup
3
- from PySide6.QtCore import Qt, QSize, Signal
3
+ from PySide6.QtCore import Qt
4
4
  from PySide6.QtGui import QGuiApplication
5
5
  from .. config.gui_constants import gui_constants
6
6
  from .image_filters import ImageFilters
7
7
  from .image_viewer import ImageViewer
8
8
  from .shortcuts_help import ShortcutsHelp
9
+ from .brush import Brush
9
10
 
10
11
 
11
12
  def brush_size_to_slider(size):
@@ -17,22 +18,10 @@ def brush_size_to_slider(size):
17
18
  return int(normalized * gui_constants.BRUSH_SIZE_SLIDER_MAX)
18
19
 
19
20
 
20
- class ClickableLabel(QLabel):
21
- doubleClicked = Signal()
22
-
23
- def __init__(self, text, parent=None):
24
- super().__init__(text, parent)
25
- self.setMouseTracking(True)
26
-
27
- def mouseDoubleClickEvent(self, event):
28
- if event.button() == Qt.LeftButton:
29
- self.doubleClicked.emit()
30
- super().mouseDoubleClickEvent(event)
31
-
32
-
33
21
  class ImageEditorUI(ImageFilters):
34
22
  def __init__(self):
35
23
  super().__init__()
24
+ self.brush = Brush()
36
25
  self.setup_ui()
37
26
  self.setup_menu()
38
27
  self.setup_shortcuts()
@@ -51,10 +40,12 @@ class ImageEditorUI(ImageFilters):
51
40
  central_widget = QWidget()
52
41
  self.setCentralWidget(central_widget)
53
42
  layout = QHBoxLayout(central_widget)
54
- self.image_viewer = ImageViewer()
43
+ self.image_viewer = ImageViewer(self.layer_collection)
55
44
  self.image_viewer.temp_view_requested.connect(self.handle_temp_view)
56
- self.image_viewer.image_editor = self
57
- self.image_viewer.brush = self.brush_controller.brush
45
+ self.image_viewer.brush_operation_started.connect(self.begin_copy_brush_area)
46
+ self.image_viewer.brush_operation_continued.connect(self.continue_copy_brush_area)
47
+ self.image_viewer.brush_operation_ended.connect(self.end_copy_brush_area)
48
+ self.image_viewer.brush_size_change_requested.connect(self.handle_brush_size_change)
58
49
  self.image_viewer.setFocusPolicy(Qt.StrongFocus)
59
50
  side_panel = QWidget()
60
51
  side_layout = QVBoxLayout(side_panel)
@@ -75,7 +66,6 @@ class ImageEditorUI(ImageFilters):
75
66
  self.brush_size_slider = QSlider(Qt.Horizontal)
76
67
  self.brush_size_slider.setRange(0, gui_constants.BRUSH_SIZE_SLIDER_MAX)
77
68
  self.brush_size_slider.setValue(brush_size_to_slider(self.brush.size))
78
- self.brush_size_slider.valueChanged.connect(self.update_brush_size)
79
69
  brush_layout.addWidget(self.brush_size_slider)
80
70
 
81
71
  hardness_label = QLabel("Brush Hardness")
@@ -84,7 +74,6 @@ class ImageEditorUI(ImageFilters):
84
74
  self.hardness_slider = QSlider(Qt.Horizontal)
85
75
  self.hardness_slider.setRange(0, 100)
86
76
  self.hardness_slider.setValue(self.brush.hardness)
87
- self.hardness_slider.valueChanged.connect(self.update_brush_hardness)
88
77
  brush_layout.addWidget(self.hardness_slider)
89
78
 
90
79
  opacity_label = QLabel("Brush Opacity")
@@ -93,7 +82,6 @@ class ImageEditorUI(ImageFilters):
93
82
  self.opacity_slider = QSlider(Qt.Horizontal)
94
83
  self.opacity_slider.setRange(0, 100)
95
84
  self.opacity_slider.setValue(self.brush.opacity)
96
- self.opacity_slider.valueChanged.connect(self.update_brush_opacity)
97
85
  brush_layout.addWidget(self.opacity_slider)
98
86
 
99
87
  flow_label = QLabel("Brush Flow")
@@ -102,7 +90,6 @@ class ImageEditorUI(ImageFilters):
102
90
  self.flow_slider = QSlider(Qt.Horizontal)
103
91
  self.flow_slider.setRange(1, 100)
104
92
  self.flow_slider.setValue(self.brush.flow)
105
- self.flow_slider.valueChanged.connect(self.update_brush_flow)
106
93
  brush_layout.addWidget(self.flow_slider)
107
94
 
108
95
  side_layout.addWidget(brush_panel)
@@ -119,7 +106,6 @@ class ImageEditorUI(ImageFilters):
119
106
  """)
120
107
  self.brush_preview.setAlignment(Qt.AlignCenter)
121
108
  self.brush_preview.setFixedHeight(100)
122
- self.update_brush_thumb()
123
109
  brush_layout.addWidget(self.brush_preview)
124
110
  side_layout.addWidget(brush_panel)
125
111
 
@@ -172,7 +158,12 @@ class ImageEditorUI(ImageFilters):
172
158
  self.thumbnail_list.setFixedWidth(gui_constants.THUMB_WIDTH)
173
159
  self.thumbnail_list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
174
160
  self.thumbnail_list.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
175
- self.thumbnail_list.itemClicked.connect(self.change_layer_item)
161
+
162
+ def change_layer_item(item):
163
+ layer_idx = self.thumbnail_list.row(item)
164
+ self.change_layer(layer_idx)
165
+
166
+ self.thumbnail_list.itemClicked.connect(change_layer_item)
176
167
  self.thumbnail_list.setStyleSheet("""
177
168
  QListWidget {
178
169
  background-color: #f5f5f5;
@@ -205,33 +196,34 @@ class ImageEditorUI(ImageFilters):
205
196
  layout.addWidget(control_panel, 0)
206
197
  layout.setContentsMargins(0, 0, 0, 0)
207
198
  layout.setSpacing(2)
199
+ super().setup_ui()
208
200
 
209
201
  def setup_menu(self):
210
202
  menubar = self.menuBar()
211
203
  file_menu = menubar.addMenu("&File")
212
- file_menu.addAction("&Open...", self.open_file, "Ctrl+O")
213
- file_menu.addAction("&Save", self.save_file, "Ctrl+S")
214
- file_menu.addAction("Save &As...", self.save_file_as, "Ctrl+Shift+S")
204
+ file_menu.addAction("&Open...", self.io_gui_handler.open_file, "Ctrl+O")
205
+ file_menu.addAction("&Save", self.io_gui_handler.save_file, "Ctrl+S")
206
+ file_menu.addAction("Save &As...", self.io_gui_handler.save_file_as, "Ctrl+Shift+S")
215
207
  self.save_master_only = QAction("Save Master &Only", self)
216
208
  self.save_master_only.setCheckable(True)
217
209
  self.save_master_only.setChecked(True)
218
210
  file_menu.addAction(self.save_master_only)
219
211
 
220
- file_menu.addAction("&Close", self.close_file, "Ctrl+W")
212
+ file_menu.addAction("&Close", self.io_gui_handler.close_file, "Ctrl+W")
221
213
  file_menu.addSeparator()
222
- file_menu.addAction("&Import frames", self.import_frames)
223
- file_menu.addAction("Import &EXIF data", self.select_exif_path)
214
+ file_menu.addAction("&Import frames", self.io_gui_handler.import_frames)
215
+ file_menu.addAction("Import &EXIF data", self.io_gui_handler.select_exif_path)
224
216
 
225
217
  edit_menu = menubar.addMenu("&Edit")
226
218
  self.undo_action = QAction("Undo", self)
227
219
  self.undo_action.setEnabled(False)
228
220
  self.undo_action.setShortcut("Ctrl+Z")
229
- self.undo_action.triggered.connect(self.undo_last_brush)
221
+ self.undo_action.triggered.connect(self.undo)
230
222
  edit_menu.addAction(self.undo_action)
231
223
  self.redo_action = QAction("Redo", self)
232
224
  self.redo_action.setEnabled(False)
233
225
  self.redo_action.setShortcut("Ctrl+Y")
234
- self.redo_action.triggered.connect(self.redo_last_brush)
226
+ self.redo_action.triggered.connect(self.redo)
235
227
  edit_menu.addAction(self.redo_action)
236
228
  edit_menu.addSeparator()
237
229
 
@@ -273,12 +265,12 @@ class ImageEditorUI(ImageFilters):
273
265
 
274
266
  view_master_action = QAction("View Master", self)
275
267
  view_master_action.setShortcut("M")
276
- view_master_action.triggered.connect(self.set_view_master)
268
+ view_master_action.triggered.connect(self.display_manager.set_view_master)
277
269
  view_menu.addAction(view_master_action)
278
270
 
279
271
  view_individual_action = QAction("View Individual", self)
280
272
  view_individual_action.setShortcut("L")
281
- view_individual_action.triggered.connect(self.set_view_individual)
273
+ view_individual_action.triggered.connect(self.display_manager.set_view_individual)
282
274
  view_menu.addAction(view_individual_action)
283
275
  view_menu.addSeparator()
284
276
 
@@ -350,53 +342,28 @@ class ImageEditorUI(ImageFilters):
350
342
  if self._check_unsaved_changes():
351
343
  self.close()
352
344
 
353
- def _add_thumbnail_item(self, thumbnail, label, i, is_current):
354
- item_widget = QWidget()
355
- layout = QVBoxLayout(item_widget)
356
- layout.setContentsMargins(0, 0, 0, 0)
357
- layout.setSpacing(0)
358
-
359
- thumbnail_label = QLabel()
360
- thumbnail_label.setPixmap(thumbnail)
361
- thumbnail_label.setAlignment(Qt.AlignCenter)
362
- layout.addWidget(thumbnail_label)
363
-
364
- label_widget = ClickableLabel(label)
365
- label_widget.setAlignment(Qt.AlignCenter)
366
- label_widget.doubleClicked.connect(lambda: self._rename_label(label_widget, label, i))
367
- layout.addWidget(label_widget)
368
-
369
- item = QListWidgetItem()
370
- item.setSizeHint(QSize(gui_constants.IMG_WIDTH, gui_constants.IMG_HEIGHT))
371
- self.thumbnail_list.addItem(item)
372
- self.thumbnail_list.setItemWidget(item, item_widget)
373
-
374
- if is_current:
375
- self.thumbnail_list.setCurrentItem(item)
376
-
377
- def _rename_label(self, label_widget, old_label, i):
378
- new_label, ok = QInputDialog.getText(self.thumbnail_list, "Rename Label", "New label name:", text=old_label)
379
- if ok and new_label and new_label != old_label:
380
- label_widget.setText(new_label)
381
- self._update_label_in_data(old_label, new_label, i)
382
-
383
- def _update_label_in_data(self, old_label, new_label, i):
384
- self.current_labels[i] = new_label
385
-
386
- def undo_last_brush(self):
387
- if self.undo_manager.undo(self.master_layer):
388
- self.display_current_view()
345
+ def undo(self):
346
+ if self.undo_manager.undo(self.master_layer()):
347
+ self.display_manager.display_current_view()
348
+ self.display_manager.update_master_thumbnail()
389
349
  self.mark_as_modified()
390
350
  self.statusBar().showMessage("Undo applied", 2000)
391
351
 
392
- def redo_last_brush(self):
393
- if self.undo_manager.redo(self.master_layer):
394
- self.display_current_view()
352
+ def redo(self):
353
+ if self.undo_manager.redo(self.master_layer()):
354
+ self.display_manager.display_current_view()
355
+ self.display_manager.update_master_thumbnail()
395
356
  self.mark_as_modified()
396
357
  self.statusBar().showMessage("Redo applied", 2000)
397
358
 
398
359
  def handle_temp_view(self, start):
399
360
  if start:
400
- self.start_temp_view()
361
+ self.display_manager.start_temp_view()
362
+ else:
363
+ self.display_manager.end_temp_view()
364
+
365
+ def handle_brush_size_change(self, delta):
366
+ if delta > 0:
367
+ self.brush_tool.increase_brush_size()
401
368
  else:
402
- self.end_temp_view()
369
+ self.brush_tool.decrease_brush_size()