shinestacker 1.4.0__py3-none-any.whl → 1.5.1__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 +18 -9
- shinestacker/app/project.py +2 -3
- shinestacker/app/retouch.py +8 -4
- shinestacker/config/gui_constants.py +7 -2
- shinestacker/gui/new_project.py +17 -14
- shinestacker/retouch/base_filter.py +8 -10
- shinestacker/retouch/brush_preview.py +28 -15
- shinestacker/retouch/display_manager.py +42 -42
- shinestacker/retouch/image_editor_ui.py +67 -53
- shinestacker/retouch/image_view_status.py +4 -0
- shinestacker/retouch/image_viewer.py +8 -4
- shinestacker/retouch/io_gui_handler.py +0 -3
- shinestacker/retouch/layer_collection.py +3 -0
- shinestacker/retouch/overlaid_view.py +99 -84
- shinestacker/retouch/shortcuts_help.py +35 -31
- shinestacker/retouch/sidebyside_view.py +141 -178
- shinestacker/retouch/transformation_manager.py +43 -0
- shinestacker/retouch/undo_manager.py +22 -3
- shinestacker/retouch/view_strategy.py +234 -64
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/METADATA +7 -7
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/RECORD +27 -25
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.1.dist-info}/top_level.txt +0 -0
shinestacker/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '1.
|
|
1
|
+
__version__ = '1.5.1'
|
shinestacker/app/args.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0116
|
|
2
|
+
|
|
3
|
+
def add_project_arguments(parser):
|
|
4
|
+
parser.add_argument('-x', '--expert', action='store_true', help='''
|
|
5
|
+
expert options are visible by default.
|
|
6
|
+
''')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def add_retouch_arguments(parser):
|
|
10
|
+
parser.add_argument('-p', '--path', nargs='?', help='''
|
|
11
|
+
import frames from one or more directories.
|
|
12
|
+
Multiple directories can be specified separated by ';'.
|
|
13
|
+
''')
|
|
14
|
+
view_group = parser.add_mutually_exclusive_group()
|
|
15
|
+
view_group.add_argument('-v1', '--view-overlaid', action='store_true', help='''
|
|
16
|
+
set overlaid view.
|
|
17
|
+
''')
|
|
18
|
+
view_group.add_argument('-v2', '--view-side-by-side', action='store_true', help='''
|
|
19
|
+
set side-by-side view.
|
|
20
|
+
''')
|
|
21
|
+
view_group.add_argument('-v3', '--view-top-bottom', action='store_true', help='''
|
|
22
|
+
set top-bottom view.
|
|
23
|
+
''')
|
shinestacker/app/main.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, C0413, E0611, R0903, E1121, W0201
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, C0413, E0611, R0903, E1121, W0201, R0915, R0912
|
|
2
2
|
import sys
|
|
3
3
|
import os
|
|
4
4
|
import logging
|
|
@@ -20,6 +20,7 @@ from shinestacker.app.gui_utils import (
|
|
|
20
20
|
disable_macos_special_menu_items, fill_app_menu, set_css_style)
|
|
21
21
|
from shinestacker.app.help_menu import add_help_action
|
|
22
22
|
from shinestacker.app.open_frames import open_frames
|
|
23
|
+
from .args import add_project_arguments, add_retouch_arguments
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class SelectionDialog(QDialog):
|
|
@@ -102,7 +103,7 @@ class MainApp(QMainWindow):
|
|
|
102
103
|
file_menu = action.menu()
|
|
103
104
|
break
|
|
104
105
|
if file_menu is not None:
|
|
105
|
-
import_action = QAction("Import
|
|
106
|
+
import_action = QAction("Import from Current Project", self)
|
|
106
107
|
import_action.triggered.connect(self.import_from_project)
|
|
107
108
|
file_menu.addAction(import_action)
|
|
108
109
|
else:
|
|
@@ -211,19 +212,21 @@ if a single file is specified, it can be either a project or an image.
|
|
|
211
212
|
Multiple frames can be specified as a list of files.
|
|
212
213
|
Multiple files can be specified separated by ';'.
|
|
213
214
|
''')
|
|
214
|
-
parser.
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
app_group = parser.add_mutually_exclusive_group()
|
|
216
|
+
app_group.add_argument('-j', '--project', action='store_true', help='''
|
|
217
|
+
open project window at startup instead of project windows (default).
|
|
217
218
|
''')
|
|
218
|
-
|
|
219
|
+
app_group.add_argument('-r', '--retouch', action='store_true', help='''
|
|
219
220
|
open retouch window at startup instead of project windows.
|
|
220
221
|
''')
|
|
221
|
-
parser
|
|
222
|
-
|
|
223
|
-
''')
|
|
222
|
+
add_project_arguments(parser)
|
|
223
|
+
add_retouch_arguments(parser)
|
|
224
224
|
args = vars(parser.parse_args(sys.argv[1:]))
|
|
225
225
|
filename = args['filename']
|
|
226
226
|
path = args['path']
|
|
227
|
+
if filename and path:
|
|
228
|
+
print("can't specify both arguments --filename and --path", file=sys.stderr)
|
|
229
|
+
sys.exit(1)
|
|
227
230
|
setup_logging(console_level=logging.DEBUG, file_level=logging.DEBUG, disable_console=True)
|
|
228
231
|
app = Application(sys.argv)
|
|
229
232
|
if config.DONT_USE_NATIVE_MENU:
|
|
@@ -239,6 +242,12 @@ expert options are visible by default.
|
|
|
239
242
|
main_app.activateWindow()
|
|
240
243
|
if args['expert']:
|
|
241
244
|
main_app.project_window.set_expert_options()
|
|
245
|
+
if args['view_overlaid']:
|
|
246
|
+
main_app.retouch_window.set_strategy('overlaid')
|
|
247
|
+
elif args['view_side_by_side']:
|
|
248
|
+
main_app.retouch_window.set_strategy('sidebyside')
|
|
249
|
+
elif args['view_top_bottom']:
|
|
250
|
+
main_app.retouch_window.set_strategy('topbottom')
|
|
242
251
|
if filename:
|
|
243
252
|
filenames = filename.split(';')
|
|
244
253
|
filename = filenames[0]
|
shinestacker/app/project.py
CHANGED
|
@@ -17,6 +17,7 @@ from shinestacker.gui.main_window import MainWindow
|
|
|
17
17
|
from shinestacker.app.gui_utils import (
|
|
18
18
|
disable_macos_special_menu_items, fill_app_menu, set_css_style)
|
|
19
19
|
from shinestacker.app.help_menu import add_help_action
|
|
20
|
+
from .args import add_project_arguments
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class ProjectApp(MainWindow):
|
|
@@ -52,9 +53,7 @@ def main():
|
|
|
52
53
|
parser.add_argument('-f', '--filename', nargs='?', help='''
|
|
53
54
|
project filename.
|
|
54
55
|
''')
|
|
55
|
-
parser
|
|
56
|
-
expert options are visible by default.
|
|
57
|
-
''')
|
|
56
|
+
add_project_arguments(parser)
|
|
58
57
|
args = vars(parser.parse_args(sys.argv[1:]))
|
|
59
58
|
setup_logging(console_level=logging.DEBUG, file_level=logging.DEBUG, disable_console=True)
|
|
60
59
|
app = Application(sys.argv)
|
shinestacker/app/retouch.py
CHANGED
|
@@ -13,6 +13,7 @@ from shinestacker.app.gui_utils import (
|
|
|
13
13
|
disable_macos_special_menu_items, fill_app_menu, set_css_style)
|
|
14
14
|
from shinestacker.app.help_menu import add_help_action
|
|
15
15
|
from shinestacker.app.open_frames import open_frames
|
|
16
|
+
from .args import add_retouch_arguments
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class RetouchApp(ImageEditorUI):
|
|
@@ -44,10 +45,7 @@ def main():
|
|
|
44
45
|
import frames from files.
|
|
45
46
|
Multiple files can be specified separated by ';'.
|
|
46
47
|
''')
|
|
47
|
-
parser
|
|
48
|
-
import frames from one or more directories.
|
|
49
|
-
Multiple directories can be specified separated by ';'.
|
|
50
|
-
''')
|
|
48
|
+
add_retouch_arguments(parser)
|
|
51
49
|
args = vars(parser.parse_args(sys.argv[1:]))
|
|
52
50
|
filename = args['filename']
|
|
53
51
|
path = args['path']
|
|
@@ -65,6 +63,12 @@ Multiple directories can be specified separated by ';'.
|
|
|
65
63
|
editor = RetouchApp()
|
|
66
64
|
app.editor = editor
|
|
67
65
|
editor.show()
|
|
66
|
+
if args['view_overlaid']:
|
|
67
|
+
editor.set_strategy('overlaid')
|
|
68
|
+
elif args['view_side_by_side']:
|
|
69
|
+
editor.set_strategy('sidebyside')
|
|
70
|
+
elif args['view_top_bottom']:
|
|
71
|
+
editor.set_strategy('topbottom')
|
|
68
72
|
open_frames(editor, filename, path)
|
|
69
73
|
sys.exit(app.exec())
|
|
70
74
|
|
|
@@ -26,7 +26,7 @@ class _GuiConstants:
|
|
|
26
26
|
'outer': (255, 0, 0, 200),
|
|
27
27
|
'inner': (255, 0, 0, 150),
|
|
28
28
|
'gradient_end': (255, 0, 0, 0),
|
|
29
|
-
'pen': (255, 0, 0,
|
|
29
|
+
'pen': (255, 0, 0, 200),
|
|
30
30
|
'preview': (255, 180, 180),
|
|
31
31
|
'cursor_inner': (255, 0, 0, 120),
|
|
32
32
|
'preview_inner': (255, 255, 255, 150)
|
|
@@ -55,7 +55,7 @@ class _GuiConstants:
|
|
|
55
55
|
DEFAULT_BRUSH_OPACITY = 100
|
|
56
56
|
DEFAULT_BRUSH_FLOW = 100
|
|
57
57
|
BRUSH_SIZES = {
|
|
58
|
-
'default':
|
|
58
|
+
'default': 100,
|
|
59
59
|
'min': 5,
|
|
60
60
|
'mid': 50,
|
|
61
61
|
'max': 1000
|
|
@@ -66,6 +66,11 @@ class _GuiConstants:
|
|
|
66
66
|
ZOOM_IN_FACTOR = 1.10
|
|
67
67
|
ZOOM_OUT_FACTOR = 1 / ZOOM_IN_FACTOR
|
|
68
68
|
|
|
69
|
+
ROTATE_LABEL = "Rotate"
|
|
70
|
+
ROTATE_90_CW_LABEL = f"{ROTATE_LABEL} 90° Clockwise"
|
|
71
|
+
ROTATE_90_CCW_LABEL = f"{ROTATE_LABEL} 90° Anticlockwise"
|
|
72
|
+
ROTATE_180_LABEL = f"{ROTATE_LABEL} 180°"
|
|
73
|
+
|
|
69
74
|
def calculate_gamma(self):
|
|
70
75
|
if self.BRUSH_SIZES['mid'] <= self.BRUSH_SIZES['min'] or self.BRUSH_SIZES['max'] <= 0:
|
|
71
76
|
return 1.0
|
shinestacker/gui/new_project.py
CHANGED
|
@@ -108,17 +108,19 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
108
108
|
step2_layout.addRow("Vignetting correction:", self.vignetting_correction)
|
|
109
109
|
step2_layout.addRow(
|
|
110
110
|
# f" {constants.ACTION_ICONS[constants.ACTION_ALIGNFRAMES]} "
|
|
111
|
-
"Align
|
|
111
|
+
"Align frames:", self.align_frames)
|
|
112
112
|
step2_layout.addRow(
|
|
113
113
|
# f" {constants.ACTION_ICONS[constants.ACTION_BALANCEFRAMES]} "
|
|
114
|
-
"Balance
|
|
114
|
+
"Balance frames:", self.balance_frames)
|
|
115
115
|
step2_layout.addRow(
|
|
116
116
|
# f" {constants.ACTION_ICONS[constants.ACTION_FOCUSSTACKBUNCH]} "
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
"Create bunches:", self.bunch_stack)
|
|
118
|
+
self.bunch_stack.setToolTip("Combine multiple frames into fewer, high-quality "
|
|
119
|
+
"composite frames for easier retouching")
|
|
120
|
+
step2_layout.addRow("Frames per bunch:", self.bunch_frames)
|
|
121
|
+
step2_layout.addRow("Overlap between bunches:", self.bunch_overlap)
|
|
120
122
|
self.bunches_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
121
|
-
step2_layout.addRow("Number of bunches: ", self.bunches_label)
|
|
123
|
+
step2_layout.addRow("Number of resulting bunches: ", self.bunches_label)
|
|
122
124
|
if self.expert():
|
|
123
125
|
step2_layout.addRow(
|
|
124
126
|
f" {constants.ACTION_ICONS[constants.ACTION_FOCUSSTACK]} "
|
|
@@ -133,14 +135,14 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
133
135
|
if self.expert():
|
|
134
136
|
step2_layout.addRow(
|
|
135
137
|
f" {constants.ACTION_ICONS[constants.ACTION_MULTILAYER]} "
|
|
136
|
-
"
|
|
138
|
+
"Export as multilayer TIFF:", self.multi_layer)
|
|
137
139
|
step2_group.setLayout(step2_layout)
|
|
138
140
|
self.form_layout.addRow(step2_group)
|
|
139
141
|
step3_group = QGroupBox("3) Confirm")
|
|
140
142
|
step3_layout = QVBoxLayout()
|
|
141
143
|
step3_layout.setContentsMargins(15, 0, 15, 15)
|
|
142
144
|
step3_layout.addWidget(
|
|
143
|
-
QLabel("Click 🆗 to
|
|
145
|
+
QLabel("Click 🆗 to create project with these settings."))
|
|
144
146
|
step3_layout.addWidget(
|
|
145
147
|
QLabel("Select: <b>View</b> > <b>Expert options</b> for advanced configuration."))
|
|
146
148
|
step3_group.setLayout(step3_layout)
|
|
@@ -149,6 +151,7 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
149
151
|
step4_layout = QHBoxLayout()
|
|
150
152
|
step4_layout.setContentsMargins(15, 0, 15, 15)
|
|
151
153
|
step4_layout.addWidget(QLabel("Press ▶️ to run your job."))
|
|
154
|
+
step4_layout.addStretch()
|
|
152
155
|
icon_path = f"{os.path.dirname(__file__)}/ico/shinestacker.png"
|
|
153
156
|
app_icon = QIcon(icon_path)
|
|
154
157
|
icon_pixmap = app_icon.pixmap(80, 80)
|
|
@@ -293,12 +296,12 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
293
296
|
"Processing may require a significant amount "
|
|
294
297
|
"of memory or I/O buffering.\n\n"
|
|
295
298
|
"Continue anyway?")
|
|
296
|
-
msg.setInformativeText(
|
|
297
|
-
"
|
|
298
|
-
'✅ Check
|
|
299
|
-
"
|
|
300
|
-
|
|
301
|
-
)
|
|
299
|
+
msg.setInformativeText('You may consider creating "bunches" to reduce '
|
|
300
|
+
"the number of frames for retouching.\n\n"
|
|
301
|
+
'✅ Check "Create bunches" to combine frames '
|
|
302
|
+
"into manageable composites.\n\n"
|
|
303
|
+
"➡️ Check expert options for the stacking algorithm.\n\n"
|
|
304
|
+
'Go to "View" > "Expert Options".')
|
|
302
305
|
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
|
|
303
306
|
msg.setDefaultButton(QMessageBox.Cancel)
|
|
304
307
|
if msg.exec_() != QMessageBox.Ok:
|
|
@@ -34,7 +34,6 @@ class BaseFilter(ABC):
|
|
|
34
34
|
def run_with_preview(self, **kwargs):
|
|
35
35
|
if self.editor.has_no_master_layer():
|
|
36
36
|
return
|
|
37
|
-
|
|
38
37
|
self.editor.copy_master_layer()
|
|
39
38
|
dlg = QDialog(self.editor)
|
|
40
39
|
layout = QVBoxLayout(dlg)
|
|
@@ -143,15 +142,14 @@ class BaseFilter(ABC):
|
|
|
143
142
|
h, w = self.editor.master_layer().shape[:2]
|
|
144
143
|
except Exception:
|
|
145
144
|
h, w = self.editor.master_layer_copy().shape[:2]
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
self.editor.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
pass
|
|
145
|
+
try:
|
|
146
|
+
self.editor.undo_manager.extend_undo_area(0, 0, w, h)
|
|
147
|
+
self.editor.undo_manager.save_undo_state(
|
|
148
|
+
self.editor.master_layer_copy(),
|
|
149
|
+
self.name
|
|
150
|
+
)
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
155
153
|
final_img = self.apply(self.editor.master_layer_copy(), *params)
|
|
156
154
|
self.editor.set_master_layer(final_img)
|
|
157
155
|
self.editor.copy_master_layer()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0914, W0718
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0914, W0718, R0915
|
|
2
2
|
import traceback
|
|
3
3
|
import numpy as np
|
|
4
4
|
from PySide6.QtWidgets import QGraphicsPixmapItem
|
|
@@ -72,38 +72,52 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
|
|
|
72
72
|
self.hide()
|
|
73
73
|
return
|
|
74
74
|
radius = size // 2
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
x_center = int(scene_pos.x() + 0.5)
|
|
76
|
+
y_center = int(scene_pos.y() + 0.5)
|
|
77
|
+
x = x_center - radius
|
|
78
|
+
y = y_center - radius
|
|
77
79
|
w = h = size
|
|
78
80
|
if not self.valid_current_layer_idx():
|
|
79
81
|
self.hide()
|
|
80
82
|
return
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
height, width = self.current_layer().shape[:2]
|
|
84
|
+
visible_x = max(0, x)
|
|
85
|
+
visible_y = max(0, y)
|
|
86
|
+
visible_w = min(width, x + w) - visible_x
|
|
87
|
+
visible_h = min(height, y + h) - visible_y
|
|
88
|
+
if visible_w <= 0 or visible_h <= 0:
|
|
89
|
+
self.hide()
|
|
90
|
+
return
|
|
91
|
+
layer_area = self.get_layer_area(
|
|
92
|
+
self.current_layer(), visible_x, visible_y, visible_w, visible_h)
|
|
93
|
+
master_area = self.get_layer_area(
|
|
94
|
+
self.master_layer(), visible_x, visible_y, visible_w, visible_h)
|
|
83
95
|
if layer_area is None or master_area is None:
|
|
84
96
|
self.hide()
|
|
85
97
|
return
|
|
86
|
-
height, width = self.current_layer().shape[:2]
|
|
87
98
|
full_mask = create_brush_mask(size=size, hardness_percent=self.brush.hardness,
|
|
88
99
|
opacity_percent=self.brush.opacity)[:, :, np.newaxis]
|
|
89
|
-
mask_x_start = max(0, -x)
|
|
90
|
-
mask_y_start = max(0, -y)
|
|
91
|
-
mask_x_end =
|
|
92
|
-
mask_y_end =
|
|
100
|
+
mask_x_start = max(0, -x)
|
|
101
|
+
mask_y_start = max(0, -y)
|
|
102
|
+
mask_x_end = mask_x_start + visible_w
|
|
103
|
+
mask_y_end = mask_y_start + visible_h
|
|
93
104
|
mask_area = full_mask[mask_y_start:mask_y_end, mask_x_start:mask_x_end]
|
|
94
105
|
area = (layer_area * mask_area + master_area * (1 - mask_area)) * 255.0
|
|
95
106
|
area = area.astype(np.uint8)
|
|
96
107
|
qimage = QImage(area.data, area.shape[1], area.shape[0],
|
|
97
108
|
area.strides[0], QImage.Format_RGB888)
|
|
98
|
-
mask = QPixmap(
|
|
109
|
+
mask = QPixmap(visible_w, visible_h)
|
|
99
110
|
mask.fill(Qt.transparent)
|
|
100
111
|
painter = QPainter(mask)
|
|
101
112
|
painter.setPen(Qt.NoPen)
|
|
102
113
|
painter.setBrush(Qt.black)
|
|
103
|
-
|
|
114
|
+
center_x_in_visible = x_center - visible_x
|
|
115
|
+
center_y_in_visible = y_center - visible_y
|
|
116
|
+
painter.drawEllipse(
|
|
117
|
+
center_x_in_visible - radius, center_y_in_visible - radius, size, size)
|
|
104
118
|
painter.end()
|
|
105
119
|
pixmap = QPixmap.fromImage(qimage)
|
|
106
|
-
final_pixmap = QPixmap(
|
|
120
|
+
final_pixmap = QPixmap(visible_w, visible_h)
|
|
107
121
|
final_pixmap.fill(Qt.transparent)
|
|
108
122
|
painter = QPainter(final_pixmap)
|
|
109
123
|
painter.drawPixmap(0, 0, pixmap)
|
|
@@ -111,8 +125,7 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
|
|
|
111
125
|
painter.drawPixmap(0, 0, mask)
|
|
112
126
|
painter.end()
|
|
113
127
|
self.setPixmap(final_pixmap)
|
|
114
|
-
|
|
115
|
-
self.setPos(x_start, y_start)
|
|
128
|
+
self.setPos(visible_x, visible_y)
|
|
116
129
|
self.show()
|
|
117
130
|
except Exception:
|
|
118
131
|
traceback.print_exc()
|
|
@@ -25,7 +25,6 @@ class ClickableLabel(QLabel):
|
|
|
25
25
|
|
|
26
26
|
class DisplayManager(QObject, LayerCollectionHandler):
|
|
27
27
|
status_message_requested = Signal(str)
|
|
28
|
-
cursor_preview_state_changed = Signal(bool)
|
|
29
28
|
|
|
30
29
|
def __init__(self, layer_collection, image_viewer, master_thumbnail_label,
|
|
31
30
|
thumbnail_list, parent=None):
|
|
@@ -35,7 +34,6 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
35
34
|
self.master_thumbnail_label = master_thumbnail_label
|
|
36
35
|
self.thumbnail_list = thumbnail_list
|
|
37
36
|
self.view_mode = 'master'
|
|
38
|
-
self.temp_view_individual = False
|
|
39
37
|
self.needs_update = False
|
|
40
38
|
self.update_timer = QTimer()
|
|
41
39
|
self.update_timer.setInterval(gui_constants.PAINT_REFRESH_TIMER)
|
|
@@ -47,21 +45,10 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
47
45
|
self.refresh_master_view()
|
|
48
46
|
self.needs_update = False
|
|
49
47
|
|
|
50
|
-
def refresh_master_view(self):
|
|
51
|
-
if self.has_no_master_layer():
|
|
52
|
-
return
|
|
53
|
-
self.image_viewer.update_master_display()
|
|
54
|
-
self.update_master_thumbnail()
|
|
55
|
-
self.image_viewer.refresh_display()
|
|
56
|
-
|
|
57
|
-
def refresh_current_view(self):
|
|
58
|
-
if self.number_of_layers() == 0:
|
|
59
|
-
return
|
|
60
|
-
self.image_viewer.update_current_display()
|
|
61
|
-
self.image_viewer.refresh_display()
|
|
62
|
-
|
|
63
48
|
def create_thumbnail(self, layer):
|
|
64
49
|
source_layer = (layer // 256).astype(np.uint8) if layer.dtype == np.uint16 else layer
|
|
50
|
+
if not source_layer.flags.c_contiguous:
|
|
51
|
+
source_layer = np.ascontiguousarray(source_layer)
|
|
65
52
|
height, width = source_layer.shape[:2]
|
|
66
53
|
if layer.ndim == 3 and source_layer.shape[-1] == 3:
|
|
67
54
|
qimg = QImage(source_layer.data, width, height, 3 * width, QImage.Format_RGB888)
|
|
@@ -157,48 +144,61 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
157
144
|
self.thumbnail_list.scrollToItem(
|
|
158
145
|
self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
|
|
159
146
|
|
|
147
|
+
def _master_refresh_and_thumb(self):
|
|
148
|
+
self.image_viewer.show_master()
|
|
149
|
+
self.refresh_master_view()
|
|
150
|
+
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
151
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
152
|
+
|
|
153
|
+
def _current_refresh_and_thumb(self):
|
|
154
|
+
self.image_viewer.show_current()
|
|
155
|
+
self.refresh_current_view()
|
|
156
|
+
self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
|
|
157
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
158
|
+
|
|
160
159
|
def set_view_master(self):
|
|
161
160
|
if self.has_no_master_layer():
|
|
162
161
|
return
|
|
163
162
|
self.view_mode = 'master'
|
|
164
|
-
self.
|
|
165
|
-
self.refresh_master_view()
|
|
166
|
-
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
167
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
163
|
+
self._master_refresh_and_thumb()
|
|
168
164
|
self.status_message_requested.emit("View mode: Master")
|
|
169
|
-
self.cursor_preview_state_changed.emit(True)
|
|
170
165
|
|
|
171
166
|
def set_view_individual(self):
|
|
172
167
|
if self.has_no_master_layer():
|
|
173
168
|
return
|
|
174
169
|
self.view_mode = 'individual'
|
|
175
|
-
self.
|
|
176
|
-
self.refresh_current_view()
|
|
177
|
-
self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
|
|
178
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
170
|
+
self._current_refresh_and_thumb()
|
|
179
171
|
self.status_message_requested.emit("View mode: Individual layers")
|
|
180
|
-
|
|
172
|
+
|
|
173
|
+
def refresh_master_view(self):
|
|
174
|
+
if self.has_no_master_layer():
|
|
175
|
+
return
|
|
176
|
+
self.image_viewer.update_master_display()
|
|
177
|
+
self.image_viewer.refresh_display()
|
|
178
|
+
self.update_master_thumbnail()
|
|
179
|
+
|
|
180
|
+
def refresh_current_view(self):
|
|
181
|
+
if self.number_of_layers() == 0:
|
|
182
|
+
return
|
|
183
|
+
self.image_viewer.update_current_display()
|
|
184
|
+
self.image_viewer.refresh_display()
|
|
181
185
|
|
|
182
186
|
def start_temp_view(self):
|
|
183
|
-
if
|
|
184
|
-
self.
|
|
185
|
-
self.
|
|
186
|
-
|
|
187
|
-
self.
|
|
188
|
-
self.image_viewer.
|
|
189
|
-
self.
|
|
190
|
-
self.status_message_requested.emit("Temporary view: Individual layer (hold X)")
|
|
187
|
+
if self.view_mode == 'master':
|
|
188
|
+
self._current_refresh_and_thumb()
|
|
189
|
+
self.status_message_requested.emit("Temporary view: Individual layer")
|
|
190
|
+
else:
|
|
191
|
+
self._master_refresh_and_thumb()
|
|
192
|
+
self.image_viewer.strategy.brush_preview.hide()
|
|
193
|
+
self.status_message_requested.emit("Temporary view: Master")
|
|
191
194
|
|
|
192
195
|
def end_temp_view(self):
|
|
193
|
-
if self.
|
|
194
|
-
self.
|
|
195
|
-
self.image_viewer.update_brush_cursor()
|
|
196
|
-
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
197
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
198
|
-
self.image_viewer.show_master()
|
|
199
|
-
self.refresh_master_view()
|
|
196
|
+
if self.view_mode == 'master':
|
|
197
|
+
self._master_refresh_and_thumb()
|
|
200
198
|
self.status_message_requested.emit("View mode: Master")
|
|
201
|
-
|
|
199
|
+
else:
|
|
200
|
+
self._current_refresh_and_thumb()
|
|
201
|
+
self.status_message_requested.emit("View: Individual layer")
|
|
202
202
|
|
|
203
203
|
def allow_cursor_preview(self):
|
|
204
|
-
return self.view_mode == 'master'
|
|
204
|
+
return self.view_mode == 'master'
|