shinestacker 1.5.0__py3-none-any.whl → 1.5.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/_version.py +1 -1
- shinestacker/app/args.py +23 -0
- shinestacker/app/main.py +17 -8
- shinestacker/app/project.py +2 -3
- shinestacker/app/retouch.py +8 -4
- shinestacker/config/gui_constants.py +2 -2
- shinestacker/gui/new_project.py +17 -14
- shinestacker/retouch/base_filter.py +59 -35
- shinestacker/retouch/brush_preview.py +29 -15
- shinestacker/retouch/denoise_filter.py +4 -3
- shinestacker/retouch/display_manager.py +14 -19
- shinestacker/retouch/filter_manager.py +11 -3
- shinestacker/retouch/image_editor_ui.py +48 -96
- shinestacker/retouch/image_viewer.py +31 -27
- shinestacker/retouch/io_gui_handler.py +0 -3
- shinestacker/retouch/overlaid_view.py +38 -17
- shinestacker/retouch/shortcuts_help.py +35 -31
- shinestacker/retouch/sidebyside_view.py +57 -76
- shinestacker/retouch/unsharp_mask_filter.py +5 -4
- shinestacker/retouch/view_strategy.py +176 -89
- shinestacker/retouch/vignetting_filter.py +4 -3
- shinestacker/retouch/white_balance_filter.py +62 -17
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.2.dist-info}/METADATA +1 -1
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.2.dist-info}/RECORD +28 -27
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.2.dist-info}/WHEEL +0 -0
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.2.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.2.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.2.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904, W0108
|
|
2
2
|
from functools import partial
|
|
3
|
-
import numpy as np
|
|
4
3
|
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QMenu,
|
|
5
4
|
QListWidget, QSlider, QMainWindow, QMessageBox)
|
|
6
5
|
from PySide6.QtGui import QShortcut, QKeySequence, QAction, QActionGroup
|
|
@@ -31,9 +30,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
31
30
|
QMainWindow.__init__(self)
|
|
32
31
|
LayerCollectionHandler.__init__(self, LayerCollection())
|
|
33
32
|
self._recent_file_manager = RecentFileManager("shinestacker-recent-images-files.txt")
|
|
34
|
-
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
35
33
|
self.io_gui_handler = None
|
|
36
|
-
self.display_manager = None
|
|
37
34
|
self.brush = Brush()
|
|
38
35
|
self.brush_tool = BrushTool()
|
|
39
36
|
self.modified = False
|
|
@@ -43,13 +40,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
43
40
|
self.undo_action = None
|
|
44
41
|
self.redo_action = None
|
|
45
42
|
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
46
|
-
self.filter_manager = FilterManager(self)
|
|
47
|
-
self.filter_manager.register_filter("Denoise", DenoiseFilter)
|
|
48
|
-
self.filter_manager.register_filter("Unsharp Mask", UnsharpMaskFilter)
|
|
49
|
-
self.filter_manager.register_filter("White Balance", WhiteBalanceFilter)
|
|
50
|
-
self.filter_manager.register_filter("Vignetting Correction", VignettingFilter)
|
|
51
43
|
self.shortcuts_help_dialog = None
|
|
52
|
-
|
|
53
44
|
self.update_title()
|
|
54
45
|
self.resize(1400, 900)
|
|
55
46
|
center = QGuiApplication.primaryScreen().geometry().center()
|
|
@@ -68,18 +59,15 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
68
59
|
side_layout = QVBoxLayout(side_panel)
|
|
69
60
|
side_layout.setContentsMargins(0, 0, 0, 0)
|
|
70
61
|
side_layout.setSpacing(2)
|
|
71
|
-
|
|
72
62
|
brush_panel = QFrame()
|
|
73
63
|
brush_panel.setFrameShape(QFrame.StyledPanel)
|
|
74
64
|
brush_panel.setContentsMargins(0, 0, 0, 0)
|
|
75
65
|
brush_layout = QVBoxLayout(brush_panel)
|
|
76
66
|
brush_layout.setContentsMargins(0, 0, 0, 0)
|
|
77
67
|
brush_layout.setSpacing(2)
|
|
78
|
-
|
|
79
68
|
brush_label = QLabel("Brush Size")
|
|
80
69
|
brush_label.setAlignment(Qt.AlignCenter)
|
|
81
70
|
brush_layout.addWidget(brush_label)
|
|
82
|
-
|
|
83
71
|
self.brush_size_slider = QSlider(Qt.Horizontal)
|
|
84
72
|
self.brush_size_slider.setRange(0, gui_constants.BRUSH_SIZE_SLIDER_MAX)
|
|
85
73
|
|
|
@@ -94,7 +82,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
94
82
|
|
|
95
83
|
self.brush_size_slider.setValue(brush_size_to_slider(self.brush.size))
|
|
96
84
|
brush_layout.addWidget(self.brush_size_slider)
|
|
97
|
-
|
|
98
85
|
hardness_label = QLabel("Brush Hardness")
|
|
99
86
|
hardness_label.setAlignment(Qt.AlignCenter)
|
|
100
87
|
brush_layout.addWidget(hardness_label)
|
|
@@ -102,7 +89,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
102
89
|
self.hardness_slider.setRange(0, 100)
|
|
103
90
|
self.hardness_slider.setValue(self.brush.hardness)
|
|
104
91
|
brush_layout.addWidget(self.hardness_slider)
|
|
105
|
-
|
|
106
92
|
opacity_label = QLabel("Brush Opacity")
|
|
107
93
|
opacity_label.setAlignment(Qt.AlignCenter)
|
|
108
94
|
brush_layout.addWidget(opacity_label)
|
|
@@ -110,7 +96,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
110
96
|
self.opacity_slider.setRange(0, 100)
|
|
111
97
|
self.opacity_slider.setValue(self.brush.opacity)
|
|
112
98
|
brush_layout.addWidget(self.opacity_slider)
|
|
113
|
-
|
|
114
99
|
flow_label = QLabel("Brush Flow")
|
|
115
100
|
flow_label.setAlignment(Qt.AlignCenter)
|
|
116
101
|
brush_layout.addWidget(flow_label)
|
|
@@ -118,7 +103,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
118
103
|
self.flow_slider.setRange(1, 100)
|
|
119
104
|
self.flow_slider.setValue(self.brush.flow)
|
|
120
105
|
brush_layout.addWidget(self.flow_slider)
|
|
121
|
-
|
|
122
106
|
side_layout.addWidget(brush_panel)
|
|
123
107
|
self.brush_preview_widget = QLabel()
|
|
124
108
|
self.brush_preview_widget.setContentsMargins(0, 0, 0, 0)
|
|
@@ -135,7 +119,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
135
119
|
self.brush_preview_widget.setFixedHeight(100)
|
|
136
120
|
brush_layout.addWidget(self.brush_preview_widget)
|
|
137
121
|
side_layout.addWidget(brush_panel)
|
|
138
|
-
|
|
139
122
|
master_label = QLabel("Master")
|
|
140
123
|
master_label.setStyleSheet("""
|
|
141
124
|
QLabel {
|
|
@@ -153,7 +136,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
153
136
|
self.master_thumbnail_frame = QFrame()
|
|
154
137
|
self.master_thumbnail_frame.setObjectName("thumbnailContainer")
|
|
155
138
|
self.master_thumbnail_frame.setStyleSheet(
|
|
156
|
-
f"#thumbnailContainer{{ border: 2px solid {
|
|
139
|
+
f"#thumbnailContainer{{ border: 2px solid {gui_constants.THUMB_MASTER_HI_COLOR}; }}")
|
|
157
140
|
self.master_thumbnail_frame.setFrameShape(QFrame.StyledPanel)
|
|
158
141
|
master_thumbnail_layout = QVBoxLayout(self.master_thumbnail_frame)
|
|
159
142
|
master_thumbnail_layout.setContentsMargins(8, 8, 8, 8)
|
|
@@ -238,6 +221,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
238
221
|
self.display_manager = DisplayManager(
|
|
239
222
|
self.layer_collection, self.image_viewer,
|
|
240
223
|
self.master_thumbnail_label, self.thumbnail_list, parent=self)
|
|
224
|
+
self.filter_manager = FilterManager(self)
|
|
241
225
|
self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
|
|
242
226
|
self.display_manager.status_message_requested.connect(self.show_status_message)
|
|
243
227
|
self.io_gui_handler.status_message_requested.connect(self.show_status_message)
|
|
@@ -252,8 +236,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
252
236
|
self.image_viewer.set_preview_brush(self.brush_tool.brush)
|
|
253
237
|
self.brush_tool.update_brush_thumb()
|
|
254
238
|
self.io_gui_handler.setup_ui(self.display_manager, self.image_viewer)
|
|
255
|
-
self.image_viewer.set_display_manager(self.display_manager)
|
|
256
|
-
|
|
257
239
|
menubar = self.menuBar()
|
|
258
240
|
file_menu = menubar.addMenu("&File")
|
|
259
241
|
file_menu.addAction("&Open...", self.io_gui_handler.open_file, "Ctrl+O")
|
|
@@ -322,7 +304,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
322
304
|
|
|
323
305
|
view_menu.addSeparator()
|
|
324
306
|
|
|
325
|
-
view_strategy_menu = QMenu("View &Mode", view_menu)
|
|
307
|
+
self.view_strategy_menu = QMenu("View &Mode", view_menu)
|
|
326
308
|
|
|
327
309
|
self.view_mode_actions = {
|
|
328
310
|
'overlaid': QAction("Overlaid", self),
|
|
@@ -332,29 +314,33 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
332
314
|
overlaid_mode = self.view_mode_actions['overlaid']
|
|
333
315
|
overlaid_mode.setShortcut("Ctrl+1")
|
|
334
316
|
overlaid_mode.setCheckable(True)
|
|
335
|
-
overlaid_mode.triggered.connect(lambda: set_strategy('overlaid'))
|
|
336
|
-
view_strategy_menu.addAction(overlaid_mode)
|
|
317
|
+
overlaid_mode.triggered.connect(lambda: self.set_strategy('overlaid'))
|
|
318
|
+
self.view_strategy_menu.addAction(overlaid_mode)
|
|
337
319
|
side_by_side_mode = self.view_mode_actions['sidebyside']
|
|
338
320
|
side_by_side_mode.setShortcut("Ctrl+2")
|
|
339
321
|
side_by_side_mode.setCheckable(True)
|
|
340
|
-
side_by_side_mode.triggered.connect(lambda: set_strategy('sidebyside'))
|
|
341
|
-
view_strategy_menu.addAction(side_by_side_mode)
|
|
322
|
+
side_by_side_mode.triggered.connect(lambda: self.set_strategy('sidebyside'))
|
|
323
|
+
self.view_strategy_menu.addAction(side_by_side_mode)
|
|
342
324
|
side_by_side_mode = self.view_mode_actions['topbottom']
|
|
343
325
|
side_by_side_mode.setShortcut("Ctrl+3")
|
|
344
326
|
side_by_side_mode.setCheckable(True)
|
|
345
|
-
side_by_side_mode.triggered.connect(lambda: set_strategy('topbottom'))
|
|
346
|
-
view_strategy_menu.addAction(side_by_side_mode)
|
|
347
|
-
view_menu.addMenu(view_strategy_menu)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
self.
|
|
351
|
-
|
|
352
|
-
self.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
327
|
+
side_by_side_mode.triggered.connect(lambda: self.set_strategy('topbottom'))
|
|
328
|
+
self.view_strategy_menu.addAction(side_by_side_mode)
|
|
329
|
+
view_menu.addMenu(self.view_strategy_menu)
|
|
330
|
+
|
|
331
|
+
filter_handles = (
|
|
332
|
+
self.display_manager.update_master_thumbnail,
|
|
333
|
+
self.mark_as_modified,
|
|
334
|
+
self.view_strategy_menu.setEnabled
|
|
335
|
+
)
|
|
336
|
+
self.filter_manager.register_filter(
|
|
337
|
+
"Denoise", DenoiseFilter, *filter_handles)
|
|
338
|
+
self.filter_manager.register_filter(
|
|
339
|
+
"Unsharp Mask", UnsharpMaskFilter, *filter_handles)
|
|
340
|
+
self.filter_manager.register_filter(
|
|
341
|
+
"White Balance", WhiteBalanceFilter, *filter_handles)
|
|
342
|
+
self.filter_manager.register_filter(
|
|
343
|
+
"Vignetting Correction", VignettingFilter, *filter_handles)
|
|
358
344
|
|
|
359
345
|
cursor_menu = view_menu.addMenu("Cursor Style")
|
|
360
346
|
|
|
@@ -432,7 +418,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
432
418
|
view_menu.addAction(self.toggle_view_master_individual_action)
|
|
433
419
|
view_menu.addSeparator()
|
|
434
420
|
|
|
435
|
-
set_strategy('overlaid')
|
|
421
|
+
self.set_strategy('overlaid')
|
|
436
422
|
|
|
437
423
|
sort_asc_action = QAction("Sort Layers A-Z", self)
|
|
438
424
|
sort_asc_action.triggered.connect(lambda: self.sort_layers('asc'))
|
|
@@ -463,6 +449,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
463
449
|
help_menu.setObjectName("Help")
|
|
464
450
|
shortcuts_help_action = QAction("Shortcuts and Mouse", self)
|
|
465
451
|
|
|
452
|
+
self.statusBar().showMessage("Shine Stacker ready.", 2000)
|
|
453
|
+
|
|
466
454
|
def shortcuts_help():
|
|
467
455
|
self.shortcuts_help_dialog = ShortcutsHelp(self)
|
|
468
456
|
self.shortcuts_help_dialog.exec()
|
|
@@ -476,6 +464,18 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
476
464
|
next_layer.activated.connect(self.next_layer)
|
|
477
465
|
self.installEventFilter(self)
|
|
478
466
|
|
|
467
|
+
def set_strategy(self, strategy):
|
|
468
|
+
self.image_viewer.set_strategy(strategy)
|
|
469
|
+
enable_shortcuts = strategy == 'overlaid'
|
|
470
|
+
self.display_manager.view_mode = 'master'
|
|
471
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
|
|
472
|
+
self.view_master_action.setEnabled(enable_shortcuts)
|
|
473
|
+
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
474
|
+
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
475
|
+
for label, mode in self.view_mode_actions.items():
|
|
476
|
+
mode.setEnabled(label != strategy)
|
|
477
|
+
mode.setChecked(label == strategy)
|
|
478
|
+
|
|
479
479
|
def update_title(self):
|
|
480
480
|
title = constants.APP_TITLE
|
|
481
481
|
if self.io_gui_handler is not None:
|
|
@@ -591,7 +591,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
591
591
|
|
|
592
592
|
def copy_brush_area_to_master(self, view_pos):
|
|
593
593
|
if self.layer_stack() is None or self.number_of_layers() == 0 \
|
|
594
|
-
or
|
|
594
|
+
or self.display_manager.view_mode != 'master':
|
|
595
595
|
return
|
|
596
596
|
area = self.brush_tool.apply_brush_operation(
|
|
597
597
|
self.master_layer_copy(),
|
|
@@ -601,7 +601,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
601
601
|
self.undo_manager.extend_undo_area(*area)
|
|
602
602
|
|
|
603
603
|
def begin_copy_brush_area(self, pos):
|
|
604
|
-
if self.display_manager.
|
|
604
|
+
if self.display_manager.view_mode == 'master':
|
|
605
605
|
self.mask_layer = self.io_gui_handler.blank_layer.copy()
|
|
606
606
|
self.copy_master_layer()
|
|
607
607
|
self.undo_manager.reset_undo_area()
|
|
@@ -612,7 +612,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
612
612
|
self.mark_as_modified()
|
|
613
613
|
|
|
614
614
|
def continue_copy_brush_area(self, pos):
|
|
615
|
-
if self.display_manager.
|
|
615
|
+
if self.display_manager.view_mode == 'master':
|
|
616
616
|
self.copy_brush_area_to_master(pos)
|
|
617
617
|
self.display_manager.needs_update = True
|
|
618
618
|
if not self.display_manager.update_timer.isActive():
|
|
@@ -654,53 +654,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
654
654
|
def vignetting_correction(self):
|
|
655
655
|
self.filter_manager.apply("Vignetting Correction")
|
|
656
656
|
|
|
657
|
-
def
|
|
658
|
-
def on_toggled(checked):
|
|
659
|
-
if checked:
|
|
660
|
-
do_preview()
|
|
661
|
-
else:
|
|
662
|
-
restore_original()
|
|
663
|
-
preview_check.toggled.connect(on_toggled)
|
|
664
|
-
|
|
665
|
-
def get_pixel_color_at(self, pos, radius=None):
|
|
666
|
-
item_pos = self.image_viewer.position_on_image(pos)
|
|
667
|
-
x = int(item_pos.x())
|
|
668
|
-
y = int(item_pos.y())
|
|
669
|
-
master_layer = self.master_layer()
|
|
670
|
-
if (0 <= x < self.master_layer().shape[1]) and \
|
|
671
|
-
(0 <= y < self.master_layer().shape[0]):
|
|
672
|
-
if radius is None:
|
|
673
|
-
radius = int(self.brush.size)
|
|
674
|
-
if radius > 0:
|
|
675
|
-
y_indices, x_indices = np.ogrid[-radius:radius + 1, -radius:radius + 1]
|
|
676
|
-
mask = x_indices**2 + y_indices**2 <= radius**2
|
|
677
|
-
x0 = max(0, x - radius)
|
|
678
|
-
x1 = min(master_layer.shape[1], x + radius + 1)
|
|
679
|
-
y0 = max(0, y - radius)
|
|
680
|
-
y1 = min(master_layer.shape[0], y + radius + 1)
|
|
681
|
-
mask = mask[radius - (y - y0): radius + (y1 - y),
|
|
682
|
-
radius - (x - x0): radius + (x1 - x)]
|
|
683
|
-
region = master_layer[y0:y1, x0:x1]
|
|
684
|
-
if region.size == 0:
|
|
685
|
-
pixel = master_layer[y, x]
|
|
686
|
-
else:
|
|
687
|
-
if region.ndim == 3:
|
|
688
|
-
pixel = [region[:, :, c][mask].mean() for c in range(region.shape[2])]
|
|
689
|
-
else:
|
|
690
|
-
pixel = region[mask].mean()
|
|
691
|
-
else:
|
|
692
|
-
pixel = self.master_layer()[y, x]
|
|
693
|
-
if np.isscalar(pixel):
|
|
694
|
-
pixel = [pixel, pixel, pixel]
|
|
695
|
-
pixel = [np.float32(x) for x in pixel]
|
|
696
|
-
if master_layer.dtype == np.uint16:
|
|
697
|
-
pixel = [x / 256.0 for x in pixel]
|
|
698
|
-
return tuple(int(v) for v in pixel)
|
|
699
|
-
return (0, 0, 0)
|
|
700
|
-
|
|
701
|
-
def highlight_master_thumbnail(self):
|
|
657
|
+
def highlight_master_thumbnail(self, color):
|
|
702
658
|
self.master_thumbnail_frame.setStyleSheet(
|
|
703
|
-
f"#thumbnailContainer{{ border: 2px solid {
|
|
659
|
+
f"#thumbnailContainer{{ border: 2px solid {color}; }}")
|
|
704
660
|
|
|
705
661
|
def save_actions_set_enabled(self, enabled):
|
|
706
662
|
self.save_action.setEnabled(enabled)
|
|
@@ -716,13 +672,11 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
716
672
|
|
|
717
673
|
def set_view_master(self):
|
|
718
674
|
self.display_manager.set_view_master()
|
|
719
|
-
self.
|
|
720
|
-
self.highlight_master_thumbnail()
|
|
675
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
|
|
721
676
|
|
|
722
677
|
def set_view_individual(self):
|
|
723
678
|
self.display_manager.set_view_individual()
|
|
724
|
-
self.
|
|
725
|
-
self.highlight_master_thumbnail()
|
|
679
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_LO_COLOR)
|
|
726
680
|
|
|
727
681
|
def toggle_view_master_individual(self):
|
|
728
682
|
if self.display_manager.view_mode == 'master':
|
|
@@ -757,12 +711,10 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
757
711
|
def handle_temp_view(self, start):
|
|
758
712
|
if start:
|
|
759
713
|
self.display_manager.start_temp_view()
|
|
760
|
-
self.
|
|
761
|
-
self.highlight_master_thumbnail()
|
|
714
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_LO_COLOR)
|
|
762
715
|
else:
|
|
763
716
|
self.display_manager.end_temp_view()
|
|
764
|
-
self.
|
|
765
|
-
self.highlight_master_thumbnail()
|
|
717
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
|
|
766
718
|
|
|
767
719
|
def handle_brush_size_change(self, delta):
|
|
768
720
|
if delta > 0:
|
|
@@ -20,7 +20,6 @@ class ImageViewer(QWidget):
|
|
|
20
20
|
self.strategy = self._strategies['overlaid']
|
|
21
21
|
self.layout = QVBoxLayout(self)
|
|
22
22
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
23
|
-
self.strategy = self._strategies['overlaid']
|
|
24
23
|
self.layout.addWidget(self.strategy)
|
|
25
24
|
self.strategy.show()
|
|
26
25
|
|
|
@@ -35,7 +34,6 @@ class ImageViewer(QWidget):
|
|
|
35
34
|
self.strategy.show()
|
|
36
35
|
self.strategy.resize(self.size())
|
|
37
36
|
if not self.strategy.empty():
|
|
38
|
-
self.strategy.cleanup_brush_preview()
|
|
39
37
|
self.strategy.update_master_display()
|
|
40
38
|
self.strategy.update_current_display()
|
|
41
39
|
self.strategy.setup_brush_cursor()
|
|
@@ -50,10 +48,6 @@ class ImageViewer(QWidget):
|
|
|
50
48
|
def set_master_image_np(self, img):
|
|
51
49
|
self.strategy.set_master_image_np(img)
|
|
52
50
|
|
|
53
|
-
def clear_image(self):
|
|
54
|
-
for st in self._strategies.values():
|
|
55
|
-
st.clear_image()
|
|
56
|
-
|
|
57
51
|
def show_master(self):
|
|
58
52
|
self.strategy.show_master()
|
|
59
53
|
|
|
@@ -72,24 +66,6 @@ class ImageViewer(QWidget):
|
|
|
72
66
|
def refresh_display(self):
|
|
73
67
|
self.strategy.refresh_display()
|
|
74
68
|
|
|
75
|
-
def set_brush(self, brush):
|
|
76
|
-
for st in self._strategies.values():
|
|
77
|
-
st.set_brush(brush)
|
|
78
|
-
|
|
79
|
-
def set_preview_brush(self, brush):
|
|
80
|
-
for st in self._strategies.values():
|
|
81
|
-
st.set_preview_brush(brush)
|
|
82
|
-
|
|
83
|
-
def set_display_manager(self, dm):
|
|
84
|
-
for st in self._strategies.values():
|
|
85
|
-
st.set_display_manager(dm)
|
|
86
|
-
|
|
87
|
-
def set_allow_cursor_preview(self, state):
|
|
88
|
-
self.strategy.set_allow_cursor_preview(state)
|
|
89
|
-
|
|
90
|
-
def setup_brush_cursor(self):
|
|
91
|
-
self.strategy.setup_brush_cursor()
|
|
92
|
-
|
|
93
69
|
def zoom_in(self):
|
|
94
70
|
self.strategy.zoom_in()
|
|
95
71
|
|
|
@@ -102,21 +78,49 @@ class ImageViewer(QWidget):
|
|
|
102
78
|
def actual_size(self):
|
|
103
79
|
self.strategy.actual_size()
|
|
104
80
|
|
|
81
|
+
def get_brush(self):
|
|
82
|
+
return self.strategy.brush
|
|
83
|
+
|
|
105
84
|
def get_current_scale(self):
|
|
106
85
|
return self.strategy.get_current_scale()
|
|
107
86
|
|
|
108
87
|
def get_cursor_style(self):
|
|
109
88
|
return self.strategy.get_cursor_style()
|
|
110
89
|
|
|
111
|
-
def set_cursor_style(self, style):
|
|
112
|
-
self.strategy.set_cursor_style(style)
|
|
113
|
-
|
|
114
90
|
def position_on_image(self, pos):
|
|
115
91
|
return self.strategy.position_on_image(pos)
|
|
116
92
|
|
|
117
93
|
def get_visible_image_portion(self):
|
|
118
94
|
return self.strategy.get_visible_image_portion()
|
|
119
95
|
|
|
96
|
+
def hide_brush_cursor(self):
|
|
97
|
+
self.strategy.hide_brush_cursor()
|
|
98
|
+
|
|
99
|
+
def show_brush_cursor(self):
|
|
100
|
+
self.strategy.show_brush_cursor()
|
|
101
|
+
|
|
102
|
+
def hide_brush_preview(self):
|
|
103
|
+
self.strategy.hide_brush_preview()
|
|
104
|
+
|
|
105
|
+
def show_brush_preview(self):
|
|
106
|
+
self.strategy.show_brush_preview()
|
|
107
|
+
|
|
108
|
+
def clear_image(self):
|
|
109
|
+
for st in self._strategies.values():
|
|
110
|
+
st.clear_image()
|
|
111
|
+
|
|
112
|
+
def set_brush(self, brush):
|
|
113
|
+
for st in self._strategies.values():
|
|
114
|
+
st.set_brush(brush)
|
|
115
|
+
|
|
116
|
+
def set_preview_brush(self, brush):
|
|
117
|
+
for st in self._strategies.values():
|
|
118
|
+
st.set_preview_brush(brush)
|
|
119
|
+
|
|
120
|
+
def set_cursor_style(self, style):
|
|
121
|
+
for st in self._strategies.values():
|
|
122
|
+
st.set_cursor_style(style)
|
|
123
|
+
|
|
120
124
|
def connect_signals(
|
|
121
125
|
self, handle_temp_view, begin_copy_brush_area, continue_copy_brush_area,
|
|
122
126
|
end_copy_brush_area, handle_brush_size_change):
|
|
@@ -56,8 +56,6 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
56
56
|
self.set_layer_labels(labels)
|
|
57
57
|
self.set_master_layer(master_layer)
|
|
58
58
|
self.image_viewer.set_master_image_np(master_layer)
|
|
59
|
-
self.image_viewer.show_master()
|
|
60
|
-
self.image_viewer.update_master_display()
|
|
61
59
|
self.undo_manager.reset()
|
|
62
60
|
self.blank_layer = np.zeros(master_layer.shape[:2])
|
|
63
61
|
self.finish_loading_setup(f"Loaded: {self.current_file_path()}")
|
|
@@ -165,7 +163,6 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
165
163
|
self.display_manager.update_thumbnails()
|
|
166
164
|
self.mark_as_modified_requested.emit(True)
|
|
167
165
|
self.change_layer_requested.emit(0)
|
|
168
|
-
self.image_viewer.setup_brush_cursor()
|
|
169
166
|
self.status_message_requested.emit(message)
|
|
170
167
|
self.update_title_requested.emit()
|
|
171
168
|
self.add_recent_file_requested.emit(self.current_file_path_master)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, E1101, R0904, R0912, R0914, R0902
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, E1101, R0904, R0912, R0914, R0902, E0202
|
|
2
2
|
from PySide6.QtCore import Qt, QPointF, QEvent, QRectF
|
|
3
3
|
from .. config.gui_constants import gui_constants
|
|
4
4
|
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
@@ -107,6 +107,16 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
107
107
|
if self.brush_cursor:
|
|
108
108
|
self.brush_cursor.show()
|
|
109
109
|
super().enterEvent(event)
|
|
110
|
+
|
|
111
|
+
def get_mouse_callbacks(self):
|
|
112
|
+
return self.mousePressEvent
|
|
113
|
+
|
|
114
|
+
def set_mouse_callbacks(self, callbacks):
|
|
115
|
+
self.mousePressEvent = callbacks
|
|
116
|
+
|
|
117
|
+
def show(self):
|
|
118
|
+
self.show_master()
|
|
119
|
+
super().show()
|
|
110
120
|
# pylint: enable=C0103
|
|
111
121
|
|
|
112
122
|
def event(self, event):
|
|
@@ -114,27 +124,31 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
114
124
|
return self.handle_gesture_event(event)
|
|
115
125
|
return super().event(event)
|
|
116
126
|
|
|
117
|
-
def
|
|
118
|
-
self.status.set_master_image(qimage)
|
|
119
|
-
pixmap = self.status.pixmap_master
|
|
127
|
+
def setup_scene_image(self, pixmap, pixmap_item):
|
|
120
128
|
self.setSceneRect(QRectF(pixmap.rect()))
|
|
121
129
|
img_width, img_height = pixmap.width(), pixmap.height()
|
|
122
130
|
self.set_max_min_scales(img_width, img_height)
|
|
123
|
-
self.
|
|
131
|
+
view_rect = self.viewport().rect()
|
|
132
|
+
scale_x = view_rect.width() / img_width
|
|
133
|
+
scale_y = view_rect.height() / img_height
|
|
134
|
+
scale_factor = min(scale_x, scale_y)
|
|
135
|
+
scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
|
|
136
|
+
self.set_zoom_factor(scale_factor)
|
|
124
137
|
self.resetTransform()
|
|
125
|
-
self.
|
|
126
|
-
self.
|
|
127
|
-
self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
|
|
128
|
-
self.scale(self.zoom_factor(), self.zoom_factor())
|
|
129
|
-
self.centerOn(self.pixmap_item_master)
|
|
138
|
+
self.scale(scale_factor, scale_factor)
|
|
139
|
+
self.centerOn(pixmap_item)
|
|
130
140
|
self.center_image(self)
|
|
131
141
|
self.update_cursor_pen_width()
|
|
132
142
|
|
|
143
|
+
def set_master_image(self, qimage):
|
|
144
|
+
self.status.set_master_image(qimage)
|
|
145
|
+
self.setup_scene_image(self.status.pixmap_master, self.pixmap_item_master)
|
|
146
|
+
self.update_master_display()
|
|
147
|
+
|
|
133
148
|
def set_current_image(self, qimage):
|
|
134
149
|
self.status.set_current_image(qimage)
|
|
135
150
|
if self.empty():
|
|
136
|
-
self.
|
|
137
|
-
self.update_cursor_pen_width()
|
|
151
|
+
self.setup_scene_image(self.status.pixmap_current, self.pixmap_item_current)
|
|
138
152
|
|
|
139
153
|
def setup_brush_cursor(self):
|
|
140
154
|
super().setup_brush_cursor()
|
|
@@ -143,12 +157,20 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
143
157
|
def show_master(self):
|
|
144
158
|
self.pixmap_item_master.setVisible(True)
|
|
145
159
|
self.pixmap_item_current.setVisible(False)
|
|
146
|
-
self.
|
|
160
|
+
self.show_brush_preview()
|
|
161
|
+
if self.brush_cursor:
|
|
162
|
+
self.scene.removeItem(self.brush_cursor)
|
|
163
|
+
self.brush_cursor = self.create_circle(self.scene)
|
|
164
|
+
self.update_brush_cursor()
|
|
147
165
|
|
|
148
166
|
def show_current(self):
|
|
149
167
|
self.pixmap_item_master.setVisible(False)
|
|
150
168
|
self.pixmap_item_current.setVisible(True)
|
|
151
|
-
self.
|
|
169
|
+
self.hide_brush_preview()
|
|
170
|
+
if self.brush_cursor:
|
|
171
|
+
self.scene.removeItem(self.brush_cursor)
|
|
172
|
+
self.brush_cursor = self.create_alt_circle(self.scene)
|
|
173
|
+
self.update_brush_cursor()
|
|
152
174
|
|
|
153
175
|
def arrange_images(self):
|
|
154
176
|
if self.empty():
|
|
@@ -199,8 +221,7 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
199
221
|
handled = True
|
|
200
222
|
if handled:
|
|
201
223
|
event.accept()
|
|
202
|
-
|
|
203
|
-
return False
|
|
224
|
+
return handled
|
|
204
225
|
|
|
205
226
|
def handle_pan_gesture(self, pan_gesture):
|
|
206
227
|
if pan_gesture.state() == Qt.GestureStarted:
|
|
@@ -209,7 +230,7 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
209
230
|
elif pan_gesture.state() == Qt.GestureUpdated:
|
|
210
231
|
delta = pan_gesture.delta() - self.last_scroll_pos
|
|
211
232
|
self.last_scroll_pos = pan_gesture.delta()
|
|
212
|
-
scaled_delta = delta
|
|
233
|
+
scaled_delta = delta / self.get_current_scale()
|
|
213
234
|
self.scroll_view(self, int(scaled_delta.x()), int(scaled_delta.y()))
|
|
214
235
|
elif pan_gesture.state() == Qt.GestureFinished:
|
|
215
236
|
self.gesture_active = False
|
|
@@ -39,7 +39,7 @@ class ShortcutsHelp(QDialog):
|
|
|
39
39
|
ok_button.clicked.connect(self.accept)
|
|
40
40
|
|
|
41
41
|
def add_bold_label(self, layout, label):
|
|
42
|
-
label = QLabel(label)
|
|
42
|
+
label = QLabel(f"{label}:")
|
|
43
43
|
label.setStyleSheet("font-weight: bold")
|
|
44
44
|
layout.addRow(label)
|
|
45
45
|
|
|
@@ -47,21 +47,21 @@ class ShortcutsHelp(QDialog):
|
|
|
47
47
|
self.main_layout.insertWidget(0, icon_container())
|
|
48
48
|
|
|
49
49
|
shortcuts = {
|
|
50
|
-
"M": "
|
|
51
|
-
"L": "
|
|
52
|
-
"T": "
|
|
53
|
-
"X": "
|
|
54
|
-
"↑": "
|
|
55
|
-
"↓": "
|
|
56
|
-
"Ctrl + O": "
|
|
57
|
-
"Ctrl + S": "
|
|
58
|
-
"
|
|
59
|
-
"Ctrl + M": "
|
|
60
|
-
"Ctrl + Cmd + F": "
|
|
61
|
-
"Ctrl + +": "
|
|
62
|
-
"Ctrl + -": "
|
|
63
|
-
"Ctrl + 0": "
|
|
64
|
-
"Ctrl + R": "
|
|
50
|
+
"M": "Show master layer",
|
|
51
|
+
"L": "Show selected layer",
|
|
52
|
+
"T": "Toggle master/selected layer",
|
|
53
|
+
"X": "Temporarily toggle between master and source layer",
|
|
54
|
+
"↑": "Select one layer up",
|
|
55
|
+
"↓": "Select one layer down",
|
|
56
|
+
"Ctrl + O": "Open file",
|
|
57
|
+
"Ctrl + S": "Save multilayer tiff",
|
|
58
|
+
"Ctrl + Z": "Undo brush draw",
|
|
59
|
+
"Ctrl + M": "Copy selected layer to master",
|
|
60
|
+
"Ctrl + Cmd + F": "Full screen mode",
|
|
61
|
+
"Ctrl + +": "Zoom in",
|
|
62
|
+
"Ctrl + -": "Zoom out",
|
|
63
|
+
"Ctrl + 0": "Fit to screen",
|
|
64
|
+
"Ctrl + R": "Actual size"
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
self.add_bold_label(left_layout, "Keyboard Shortcuts")
|
|
@@ -69,13 +69,13 @@ class ShortcutsHelp(QDialog):
|
|
|
69
69
|
left_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
70
70
|
|
|
71
71
|
shortcuts = {
|
|
72
|
-
"Ctrl + 1": "
|
|
73
|
-
"Ctrl + 2": "
|
|
74
|
-
"Ctrl + 3": "
|
|
75
|
-
"[": "
|
|
76
|
-
"]": "
|
|
77
|
-
"{": "
|
|
78
|
-
"}": "
|
|
72
|
+
"Ctrl + 1": "View: overlaid",
|
|
73
|
+
"Ctrl + 2": "View: side by side",
|
|
74
|
+
"Ctrl + 3": "View: top-bottom",
|
|
75
|
+
"[": "Increase brush size",
|
|
76
|
+
"]": "Decrease brush size",
|
|
77
|
+
"{": "Increase brush hardness",
|
|
78
|
+
"}": "Decrease brush hardness"
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
self.add_bold_label(right_layout, "Keyboard Shortcuts")
|
|
@@ -83,22 +83,26 @@ class ShortcutsHelp(QDialog):
|
|
|
83
83
|
right_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
84
84
|
|
|
85
85
|
mouse_controls = {
|
|
86
|
-
"Space + Drag": "
|
|
87
|
-
"Wheel": "
|
|
88
|
-
"Ctrl + Wheel": "
|
|
89
|
-
"Left Click": "brush
|
|
86
|
+
"Space + Drag": "Move",
|
|
87
|
+
"Wheel": "Zoom in/out",
|
|
88
|
+
"Ctrl + Wheel": "Adjust brush size",
|
|
89
|
+
"Left Click": "Use brush to copy from selected layer to master",
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
spacer = QLabel("")
|
|
93
|
+
spacer.setFixedHeight(10)
|
|
94
|
+
right_layout.addWidget(spacer)
|
|
92
95
|
self.add_bold_label(right_layout, "Mouse Controls")
|
|
93
96
|
for k, v in mouse_controls.items():
|
|
94
97
|
right_layout.addRow(f"<b>{k}</b>", QLabel(v))
|
|
95
98
|
|
|
96
99
|
touchpad_controls = {
|
|
97
|
-
"Two
|
|
98
|
-
"Pinch": "
|
|
99
|
-
"Ctrl + two fingers": "zoom in/out",
|
|
100
|
+
"Two-finger drag": "Move",
|
|
101
|
+
"Pinch two fingers": "Zoom in/out"
|
|
100
102
|
}
|
|
101
|
-
|
|
103
|
+
spacer = QLabel("")
|
|
104
|
+
spacer.setFixedHeight(10)
|
|
105
|
+
right_layout.addWidget(spacer)
|
|
102
106
|
self.add_bold_label(right_layout, "Touchpad Controls")
|
|
103
107
|
for k, v in touchpad_controls.items():
|
|
104
108
|
right_layout.addRow(f"<b>{k}</b>", QLabel(v))
|