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.
- shinestacker/__init__.py +6 -6
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/balance.py +6 -7
- shinestacker/algorithms/noise_detection.py +2 -0
- shinestacker/algorithms/utils.py +4 -0
- shinestacker/algorithms/white_balance.py +13 -0
- shinestacker/app/open_frames.py +6 -4
- shinestacker/config/__init__.py +2 -1
- shinestacker/config/config.py +1 -0
- shinestacker/config/constants.py +1 -0
- shinestacker/config/gui_constants.py +1 -0
- shinestacker/core/__init__.py +4 -3
- shinestacker/core/colors.py +1 -0
- shinestacker/core/core_utils.py +6 -6
- shinestacker/core/exceptions.py +1 -0
- shinestacker/core/framework.py +2 -1
- shinestacker/gui/action_config.py +47 -42
- shinestacker/gui/actions_window.py +8 -5
- shinestacker/gui/new_project.py +1 -0
- shinestacker/retouch/brush_gradient.py +20 -0
- shinestacker/retouch/brush_preview.py +10 -14
- shinestacker/retouch/brush_tool.py +164 -0
- shinestacker/retouch/denoise_filter.py +56 -0
- shinestacker/retouch/display_manager.py +177 -0
- shinestacker/retouch/exif_data.py +2 -1
- shinestacker/retouch/filter_base.py +114 -0
- shinestacker/retouch/filter_manager.py +14 -0
- shinestacker/retouch/image_editor.py +108 -543
- shinestacker/retouch/image_editor_ui.py +42 -75
- shinestacker/retouch/image_filters.py +27 -423
- shinestacker/retouch/image_viewer.py +31 -31
- shinestacker/retouch/io_gui_handler.py +208 -0
- shinestacker/retouch/io_manager.py +53 -0
- shinestacker/retouch/layer_collection.py +118 -0
- shinestacker/retouch/unsharp_mask_filter.py +84 -0
- shinestacker/retouch/white_balance_filter.py +111 -0
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/METADATA +3 -2
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/RECORD +42 -31
- shinestacker/retouch/brush_controller.py +0 -57
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/WHEEL +0 -0
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/entry_points.txt +0 -0
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {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,
|
|
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
|
|
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.
|
|
57
|
-
self.image_viewer.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
|
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.
|
|
369
|
+
self.brush_tool.decrease_brush_size()
|