shinestacker 1.5.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 +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/brush_preview.py +29 -15
- shinestacker/retouch/image_editor_ui.py +16 -23
- shinestacker/retouch/image_viewer.py +2 -4
- shinestacker/retouch/io_gui_handler.py +0 -3
- shinestacker/retouch/overlaid_view.py +23 -11
- shinestacker/retouch/shortcuts_help.py +35 -31
- shinestacker/retouch/sidebyside_view.py +40 -75
- shinestacker/retouch/view_strategy.py +107 -28
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.1.dist-info}/METADATA +1 -1
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.1.dist-info}/RECORD +21 -20
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.5.0.dist-info → shinestacker-1.5.1.dist-info}/top_level.txt +0 -0
shinestacker/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '1.5.
|
|
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):
|
|
@@ -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
|
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:
|
|
@@ -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,8 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
|
|
|
111
125
|
painter.drawPixmap(0, 0, mask)
|
|
112
126
|
painter.end()
|
|
113
127
|
self.setPixmap(final_pixmap)
|
|
114
|
-
|
|
115
|
-
self.
|
|
128
|
+
self.setPos(visible_x, visible_y)
|
|
129
|
+
self.show()
|
|
116
130
|
except Exception:
|
|
117
131
|
traceback.print_exc()
|
|
118
132
|
self.hide()
|
|
@@ -49,7 +49,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
49
49
|
self.filter_manager.register_filter("White Balance", WhiteBalanceFilter)
|
|
50
50
|
self.filter_manager.register_filter("Vignetting Correction", VignettingFilter)
|
|
51
51
|
self.shortcuts_help_dialog = None
|
|
52
|
-
|
|
53
52
|
self.update_title()
|
|
54
53
|
self.resize(1400, 900)
|
|
55
54
|
center = QGuiApplication.primaryScreen().geometry().center()
|
|
@@ -68,18 +67,15 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
68
67
|
side_layout = QVBoxLayout(side_panel)
|
|
69
68
|
side_layout.setContentsMargins(0, 0, 0, 0)
|
|
70
69
|
side_layout.setSpacing(2)
|
|
71
|
-
|
|
72
70
|
brush_panel = QFrame()
|
|
73
71
|
brush_panel.setFrameShape(QFrame.StyledPanel)
|
|
74
72
|
brush_panel.setContentsMargins(0, 0, 0, 0)
|
|
75
73
|
brush_layout = QVBoxLayout(brush_panel)
|
|
76
74
|
brush_layout.setContentsMargins(0, 0, 0, 0)
|
|
77
75
|
brush_layout.setSpacing(2)
|
|
78
|
-
|
|
79
76
|
brush_label = QLabel("Brush Size")
|
|
80
77
|
brush_label.setAlignment(Qt.AlignCenter)
|
|
81
78
|
brush_layout.addWidget(brush_label)
|
|
82
|
-
|
|
83
79
|
self.brush_size_slider = QSlider(Qt.Horizontal)
|
|
84
80
|
self.brush_size_slider.setRange(0, gui_constants.BRUSH_SIZE_SLIDER_MAX)
|
|
85
81
|
|
|
@@ -94,7 +90,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
94
90
|
|
|
95
91
|
self.brush_size_slider.setValue(brush_size_to_slider(self.brush.size))
|
|
96
92
|
brush_layout.addWidget(self.brush_size_slider)
|
|
97
|
-
|
|
98
93
|
hardness_label = QLabel("Brush Hardness")
|
|
99
94
|
hardness_label.setAlignment(Qt.AlignCenter)
|
|
100
95
|
brush_layout.addWidget(hardness_label)
|
|
@@ -102,7 +97,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
102
97
|
self.hardness_slider.setRange(0, 100)
|
|
103
98
|
self.hardness_slider.setValue(self.brush.hardness)
|
|
104
99
|
brush_layout.addWidget(self.hardness_slider)
|
|
105
|
-
|
|
106
100
|
opacity_label = QLabel("Brush Opacity")
|
|
107
101
|
opacity_label.setAlignment(Qt.AlignCenter)
|
|
108
102
|
brush_layout.addWidget(opacity_label)
|
|
@@ -110,7 +104,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
110
104
|
self.opacity_slider.setRange(0, 100)
|
|
111
105
|
self.opacity_slider.setValue(self.brush.opacity)
|
|
112
106
|
brush_layout.addWidget(self.opacity_slider)
|
|
113
|
-
|
|
114
107
|
flow_label = QLabel("Brush Flow")
|
|
115
108
|
flow_label.setAlignment(Qt.AlignCenter)
|
|
116
109
|
brush_layout.addWidget(flow_label)
|
|
@@ -118,7 +111,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
118
111
|
self.flow_slider.setRange(1, 100)
|
|
119
112
|
self.flow_slider.setValue(self.brush.flow)
|
|
120
113
|
brush_layout.addWidget(self.flow_slider)
|
|
121
|
-
|
|
122
114
|
side_layout.addWidget(brush_panel)
|
|
123
115
|
self.brush_preview_widget = QLabel()
|
|
124
116
|
self.brush_preview_widget.setContentsMargins(0, 0, 0, 0)
|
|
@@ -135,7 +127,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
135
127
|
self.brush_preview_widget.setFixedHeight(100)
|
|
136
128
|
brush_layout.addWidget(self.brush_preview_widget)
|
|
137
129
|
side_layout.addWidget(brush_panel)
|
|
138
|
-
|
|
139
130
|
master_label = QLabel("Master")
|
|
140
131
|
master_label.setStyleSheet("""
|
|
141
132
|
QLabel {
|
|
@@ -332,30 +323,20 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
332
323
|
overlaid_mode = self.view_mode_actions['overlaid']
|
|
333
324
|
overlaid_mode.setShortcut("Ctrl+1")
|
|
334
325
|
overlaid_mode.setCheckable(True)
|
|
335
|
-
overlaid_mode.triggered.connect(lambda: set_strategy('overlaid'))
|
|
326
|
+
overlaid_mode.triggered.connect(lambda: self.set_strategy('overlaid'))
|
|
336
327
|
view_strategy_menu.addAction(overlaid_mode)
|
|
337
328
|
side_by_side_mode = self.view_mode_actions['sidebyside']
|
|
338
329
|
side_by_side_mode.setShortcut("Ctrl+2")
|
|
339
330
|
side_by_side_mode.setCheckable(True)
|
|
340
|
-
side_by_side_mode.triggered.connect(lambda: set_strategy('sidebyside'))
|
|
331
|
+
side_by_side_mode.triggered.connect(lambda: self.set_strategy('sidebyside'))
|
|
341
332
|
view_strategy_menu.addAction(side_by_side_mode)
|
|
342
333
|
side_by_side_mode = self.view_mode_actions['topbottom']
|
|
343
334
|
side_by_side_mode.setShortcut("Ctrl+3")
|
|
344
335
|
side_by_side_mode.setCheckable(True)
|
|
345
|
-
side_by_side_mode.triggered.connect(lambda: set_strategy('topbottom'))
|
|
336
|
+
side_by_side_mode.triggered.connect(lambda: self.set_strategy('topbottom'))
|
|
346
337
|
view_strategy_menu.addAction(side_by_side_mode)
|
|
347
338
|
view_menu.addMenu(view_strategy_menu)
|
|
348
339
|
|
|
349
|
-
def set_strategy(strategy):
|
|
350
|
-
self.image_viewer.set_strategy(strategy)
|
|
351
|
-
enable_shortcuts = strategy == 'overlaid'
|
|
352
|
-
self.view_master_action.setEnabled(enable_shortcuts)
|
|
353
|
-
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
354
|
-
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
355
|
-
for label, mode in self.view_mode_actions.items():
|
|
356
|
-
mode.setEnabled(label != strategy)
|
|
357
|
-
mode.setChecked(label == strategy)
|
|
358
|
-
|
|
359
340
|
cursor_menu = view_menu.addMenu("Cursor Style")
|
|
360
341
|
|
|
361
342
|
self.cursor_style_actions = {
|
|
@@ -432,7 +413,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
432
413
|
view_menu.addAction(self.toggle_view_master_individual_action)
|
|
433
414
|
view_menu.addSeparator()
|
|
434
415
|
|
|
435
|
-
set_strategy('overlaid')
|
|
416
|
+
self.set_strategy('overlaid')
|
|
436
417
|
|
|
437
418
|
sort_asc_action = QAction("Sort Layers A-Z", self)
|
|
438
419
|
sort_asc_action.triggered.connect(lambda: self.sort_layers('asc'))
|
|
@@ -463,6 +444,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
463
444
|
help_menu.setObjectName("Help")
|
|
464
445
|
shortcuts_help_action = QAction("Shortcuts and Mouse", self)
|
|
465
446
|
|
|
447
|
+
self.statusBar().showMessage("Shine Stacker ready.", 2000)
|
|
448
|
+
|
|
466
449
|
def shortcuts_help():
|
|
467
450
|
self.shortcuts_help_dialog = ShortcutsHelp(self)
|
|
468
451
|
self.shortcuts_help_dialog.exec()
|
|
@@ -476,6 +459,16 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
476
459
|
next_layer.activated.connect(self.next_layer)
|
|
477
460
|
self.installEventFilter(self)
|
|
478
461
|
|
|
462
|
+
def set_strategy(self, strategy):
|
|
463
|
+
self.image_viewer.set_strategy(strategy)
|
|
464
|
+
enable_shortcuts = strategy == 'overlaid'
|
|
465
|
+
self.view_master_action.setEnabled(enable_shortcuts)
|
|
466
|
+
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
467
|
+
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
468
|
+
for label, mode in self.view_mode_actions.items():
|
|
469
|
+
mode.setEnabled(label != strategy)
|
|
470
|
+
mode.setChecked(label == strategy)
|
|
471
|
+
|
|
479
472
|
def update_title(self):
|
|
480
473
|
title = constants.APP_TITLE
|
|
481
474
|
if self.io_gui_handler is not None:
|
|
@@ -87,9 +87,6 @@ class ImageViewer(QWidget):
|
|
|
87
87
|
def set_allow_cursor_preview(self, state):
|
|
88
88
|
self.strategy.set_allow_cursor_preview(state)
|
|
89
89
|
|
|
90
|
-
def setup_brush_cursor(self):
|
|
91
|
-
self.strategy.setup_brush_cursor()
|
|
92
|
-
|
|
93
90
|
def zoom_in(self):
|
|
94
91
|
self.strategy.zoom_in()
|
|
95
92
|
|
|
@@ -109,7 +106,8 @@ class ImageViewer(QWidget):
|
|
|
109
106
|
return self.strategy.get_cursor_style()
|
|
110
107
|
|
|
111
108
|
def set_cursor_style(self, style):
|
|
112
|
-
self.
|
|
109
|
+
for st in self._strategies.values():
|
|
110
|
+
st.set_cursor_style(style)
|
|
113
111
|
|
|
114
112
|
def position_on_image(self, pos):
|
|
115
113
|
return self.strategy.position_on_image(pos)
|
|
@@ -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)
|
|
@@ -114,27 +114,31 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
114
114
|
return self.handle_gesture_event(event)
|
|
115
115
|
return super().event(event)
|
|
116
116
|
|
|
117
|
-
def
|
|
118
|
-
self.status.set_master_image(qimage)
|
|
119
|
-
pixmap = self.status.pixmap_master
|
|
117
|
+
def setup_scene_image(self, pixmap, pixmap_item):
|
|
120
118
|
self.setSceneRect(QRectF(pixmap.rect()))
|
|
121
119
|
img_width, img_height = pixmap.width(), pixmap.height()
|
|
122
120
|
self.set_max_min_scales(img_width, img_height)
|
|
123
|
-
self.
|
|
121
|
+
view_rect = self.viewport().rect()
|
|
122
|
+
scale_x = view_rect.width() / img_width
|
|
123
|
+
scale_y = view_rect.height() / img_height
|
|
124
|
+
scale_factor = min(scale_x, scale_y)
|
|
125
|
+
scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
|
|
126
|
+
self.set_zoom_factor(scale_factor)
|
|
124
127
|
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)
|
|
128
|
+
self.scale(scale_factor, scale_factor)
|
|
129
|
+
self.centerOn(pixmap_item)
|
|
130
130
|
self.center_image(self)
|
|
131
131
|
self.update_cursor_pen_width()
|
|
132
132
|
|
|
133
|
+
def set_master_image(self, qimage):
|
|
134
|
+
self.status.set_master_image(qimage)
|
|
135
|
+
self.setup_scene_image(self.status.pixmap_master, self.pixmap_item_master)
|
|
136
|
+
self.update_master_display()
|
|
137
|
+
|
|
133
138
|
def set_current_image(self, qimage):
|
|
134
139
|
self.status.set_current_image(qimage)
|
|
135
140
|
if self.empty():
|
|
136
|
-
self.
|
|
137
|
-
self.update_cursor_pen_width()
|
|
141
|
+
self.setup_scene_image(self.status.pixmap_current, self.pixmap_item_current)
|
|
138
142
|
|
|
139
143
|
def setup_brush_cursor(self):
|
|
140
144
|
super().setup_brush_cursor()
|
|
@@ -144,11 +148,19 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
144
148
|
self.pixmap_item_master.setVisible(True)
|
|
145
149
|
self.pixmap_item_current.setVisible(False)
|
|
146
150
|
self.brush_preview.show()
|
|
151
|
+
if self.brush_cursor:
|
|
152
|
+
self.scene.removeItem(self.brush_cursor)
|
|
153
|
+
self.brush_cursor = self.create_circle(self.scene)
|
|
154
|
+
self.update_brush_cursor()
|
|
147
155
|
|
|
148
156
|
def show_current(self):
|
|
149
157
|
self.pixmap_item_master.setVisible(False)
|
|
150
158
|
self.pixmap_item_current.setVisible(True)
|
|
151
159
|
self.brush_preview.hide()
|
|
160
|
+
if self.brush_cursor:
|
|
161
|
+
self.scene.removeItem(self.brush_cursor)
|
|
162
|
+
self.brush_cursor = self.create_alt_circle(self.scene)
|
|
163
|
+
self.update_brush_cursor()
|
|
152
164
|
|
|
153
165
|
def arrange_images(self):
|
|
154
166
|
if self.empty():
|
|
@@ -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))
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, R0904, R0915, E0611, R0902, R0911, R0914, E1003
|
|
2
2
|
from PySide6.QtCore import Qt, Signal, QEvent, QRectF
|
|
3
|
-
from PySide6.QtGui import
|
|
4
|
-
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame
|
|
5
|
-
from .. config.gui_constants import gui_constants
|
|
3
|
+
from PySide6.QtGui import QCursor
|
|
4
|
+
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame
|
|
6
5
|
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
7
6
|
|
|
8
7
|
|
|
@@ -59,7 +58,6 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
59
58
|
self.current_view.setFocusPolicy(Qt.NoFocus)
|
|
60
59
|
self.master_view.setFocusPolicy(Qt.NoFocus)
|
|
61
60
|
self.current_brush_cursor = None
|
|
62
|
-
self.setup_current_brush_cursor()
|
|
63
61
|
|
|
64
62
|
def setup_layout(self):
|
|
65
63
|
raise NotImplementedError("Subclasses must implement setup_layout")
|
|
@@ -180,10 +178,11 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
180
178
|
self.master_view.setCursor(Qt.OpenHandCursor)
|
|
181
179
|
self.current_view.setCursor(Qt.OpenHandCursor)
|
|
182
180
|
else:
|
|
181
|
+
if self.brush_cursor is None or self.current_brush_cursor is None:
|
|
182
|
+
self.setup_brush_cursor()
|
|
183
183
|
self.master_view.setCursor(Qt.BlankCursor)
|
|
184
184
|
self.current_view.setCursor(Qt.BlankCursor)
|
|
185
|
-
|
|
186
|
-
self.brush_cursor.show()
|
|
185
|
+
self.brush_cursor.show()
|
|
187
186
|
super().enterEvent(event)
|
|
188
187
|
|
|
189
188
|
def leaveEvent(self, event):
|
|
@@ -192,10 +191,10 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
192
191
|
self.master_view.setCursor(Qt.ArrowCursor)
|
|
193
192
|
self.current_view.setCursor(Qt.ArrowCursor)
|
|
194
193
|
else:
|
|
195
|
-
if self.brush_cursor is
|
|
196
|
-
self.
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
if self.brush_cursor is None or self.current_brush_cursor is None:
|
|
195
|
+
self.setup_brush_cursor()
|
|
196
|
+
self.brush_cursor.hide()
|
|
197
|
+
self.current_brush_cursor.hide()
|
|
199
198
|
self.master_view.setCursor(Qt.ArrowCursor)
|
|
200
199
|
self.current_view.setCursor(Qt.ArrowCursor)
|
|
201
200
|
super().leaveEvent(event)
|
|
@@ -248,33 +247,12 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
248
247
|
def setup_brush_cursor(self):
|
|
249
248
|
super().setup_brush_cursor()
|
|
250
249
|
self.setup_current_brush_cursor()
|
|
251
|
-
self.update_cursor_pen_width()
|
|
252
250
|
|
|
253
251
|
def setup_current_brush_cursor(self):
|
|
254
252
|
if not self.brush:
|
|
255
253
|
return
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
self.current_scene.removeItem(item)
|
|
259
|
-
pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
|
|
260
|
-
pen = QPen(QColor(255, 0, 0), pen_width, Qt.DotLine)
|
|
261
|
-
brush = Qt.NoBrush
|
|
262
|
-
self.current_brush_cursor = self.current_scene.addEllipse(
|
|
263
|
-
0, 0, self.brush.size, self.brush.size, pen, brush)
|
|
264
|
-
self.current_brush_cursor.setZValue(1000)
|
|
265
|
-
self.current_brush_cursor.hide()
|
|
266
|
-
|
|
267
|
-
def update_current_brush_cursor(self, scene_pos):
|
|
268
|
-
if not self.current_brush_cursor or not self.isVisible():
|
|
269
|
-
return
|
|
270
|
-
size = self.brush.size
|
|
271
|
-
radius = size / 2
|
|
272
|
-
self.current_brush_cursor.setRect(
|
|
273
|
-
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
274
|
-
if self.brush_cursor and self.brush_cursor.isVisible():
|
|
275
|
-
self.current_brush_cursor.show()
|
|
276
|
-
else:
|
|
277
|
-
self.current_brush_cursor.hide()
|
|
254
|
+
self.current_brush_cursor = self.create_alt_circle(
|
|
255
|
+
self.get_current_scene(), line_style=Qt.SolidLine)
|
|
278
256
|
|
|
279
257
|
def update_cursor_pen_width(self):
|
|
280
258
|
pen_width = super().update_cursor_pen_width()
|
|
@@ -287,23 +265,29 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
287
265
|
def update_brush_cursor(self):
|
|
288
266
|
if self.empty():
|
|
289
267
|
return
|
|
268
|
+
if self.brush_cursor is None or self.current_brush_cursor is None:
|
|
269
|
+
self.setup_brush_cursor()
|
|
290
270
|
self.update_cursor_pen_width()
|
|
271
|
+
if self.space_pressed:
|
|
272
|
+
cursor_style = Qt.OpenHandCursor if not self.scrolling else Qt.ClosedHandCursor
|
|
273
|
+
self.master_view.setCursor(cursor_style)
|
|
274
|
+
self.current_view.setCursor(cursor_style)
|
|
275
|
+
self.brush_cursor.hide()
|
|
276
|
+
self.current_brush_cursor.hide()
|
|
277
|
+
return
|
|
278
|
+
self.master_view.setCursor(Qt.BlankCursor)
|
|
279
|
+
self.current_view.setCursor(Qt.BlankCursor)
|
|
291
280
|
mouse_pos_global = QCursor.pos()
|
|
292
281
|
mouse_pos_current = self.current_view.mapFromGlobal(mouse_pos_global)
|
|
293
282
|
mouse_pos_master = self.master_view.mapFromGlobal(mouse_pos_global)
|
|
294
283
|
current_has_mouse = self.current_view.rect().contains(mouse_pos_current)
|
|
295
284
|
master_has_mouse = self.master_view.rect().contains(mouse_pos_master)
|
|
285
|
+
self.current_brush_cursor.hide()
|
|
296
286
|
if master_has_mouse:
|
|
297
|
-
self.
|
|
287
|
+
if self.cursor_style == 'preview':
|
|
288
|
+
self.brush_preview.show()
|
|
298
289
|
super().update_brush_cursor()
|
|
299
290
|
self.sync_current_cursor_with_master()
|
|
300
|
-
if self.space_pressed:
|
|
301
|
-
cursor_style = Qt.OpenHandCursor if not self.scrolling else Qt.ClosedHandCursor
|
|
302
|
-
self.master_view.setCursor(cursor_style)
|
|
303
|
-
self.current_view.setCursor(cursor_style)
|
|
304
|
-
else:
|
|
305
|
-
self.master_view.setCursor(Qt.BlankCursor)
|
|
306
|
-
self.current_view.setCursor(Qt.BlankCursor)
|
|
307
291
|
elif current_has_mouse:
|
|
308
292
|
self.brush_preview.hide()
|
|
309
293
|
scene_pos = self.current_view.mapToScene(mouse_pos_current)
|
|
@@ -312,23 +296,12 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
312
296
|
self.current_brush_cursor.setRect(
|
|
313
297
|
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
314
298
|
self.current_brush_cursor.show()
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
self.brush_cursor.show()
|
|
319
|
-
if self.space_pressed:
|
|
320
|
-
cursor_style = Qt.OpenHandCursor \
|
|
321
|
-
if not self.panning_current else Qt.ClosedHandCursor
|
|
322
|
-
self.current_view.setCursor(cursor_style)
|
|
323
|
-
self.master_view.setCursor(cursor_style)
|
|
324
|
-
else:
|
|
325
|
-
self.current_view.setCursor(Qt.BlankCursor)
|
|
326
|
-
self.master_view.setCursor(Qt.BlankCursor)
|
|
299
|
+
self.brush_cursor.setRect(
|
|
300
|
+
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
301
|
+
self.brush_cursor.show()
|
|
327
302
|
else:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if self.current_brush_cursor:
|
|
331
|
-
self.current_brush_cursor.hide()
|
|
303
|
+
self.brush_cursor.hide()
|
|
304
|
+
self.current_brush_cursor.hide()
|
|
332
305
|
self.master_view.setCursor(Qt.ArrowCursor)
|
|
333
306
|
self.current_view.setCursor(Qt.ArrowCursor)
|
|
334
307
|
|
|
@@ -379,12 +352,14 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
379
352
|
self.pixmap_item_master.setPixmap(pixmap)
|
|
380
353
|
img_width, img_height = pixmap.width(), pixmap.height()
|
|
381
354
|
self.set_max_min_scales(img_width, img_height)
|
|
382
|
-
self.
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
355
|
+
view_rect = self.master_view.viewport().rect()
|
|
356
|
+
scale_x = view_rect.width() / img_width
|
|
357
|
+
scale_y = view_rect.height() / img_height
|
|
358
|
+
scale_factor = min(scale_x, scale_y)
|
|
359
|
+
scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
|
|
360
|
+
self.set_zoom_factor(scale_factor)
|
|
386
361
|
self.master_view.resetTransform()
|
|
387
|
-
self.
|
|
362
|
+
self.master_view.scale(scale_factor, scale_factor)
|
|
388
363
|
self.master_view.centerOn(self.pixmap_item_master)
|
|
389
364
|
center = self.master_scene.sceneRect().center()
|
|
390
365
|
self.brush_preview.setPos(max(0, min(center.x(), img_width)),
|
|
@@ -399,10 +374,9 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
399
374
|
self.current_scene.setSceneRect(QRectF(pixmap.rect()))
|
|
400
375
|
self.pixmap_item_current.setPixmap(pixmap)
|
|
401
376
|
self.current_view.resetTransform()
|
|
402
|
-
self.
|
|
377
|
+
self.master_view.scale(self.zoom_factor(), self.zoom_factor())
|
|
403
378
|
self.current_scene.setSceneRect(QRectF(self.pixmap_item_current.boundingRect()))
|
|
404
379
|
self.center_image(self.current_view)
|
|
405
|
-
self.update_cursor_pen_width()
|
|
406
380
|
|
|
407
381
|
def arrange_images(self):
|
|
408
382
|
if self.status.empty():
|
|
@@ -415,13 +389,6 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
415
389
|
self.center_image(self.master_view)
|
|
416
390
|
self.apply_zoom()
|
|
417
391
|
|
|
418
|
-
def set_brush(self, brush):
|
|
419
|
-
super().set_brush(brush)
|
|
420
|
-
if self.brush_cursor:
|
|
421
|
-
self.master_scene.removeItem(self.brush_cursor)
|
|
422
|
-
self.setup_brush_cursor()
|
|
423
|
-
self.setup_current_brush_cursor()
|
|
424
|
-
|
|
425
392
|
def clear_image(self):
|
|
426
393
|
super().clear_image()
|
|
427
394
|
self.setCursor(Qt.ArrowCursor)
|
|
@@ -439,10 +406,8 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
439
406
|
size = self.brush.size
|
|
440
407
|
radius = size / 2
|
|
441
408
|
self.current_brush_cursor.setRect(
|
|
442
|
-
scene_pos.x() - radius,
|
|
443
|
-
|
|
444
|
-
size, size
|
|
445
|
-
)
|
|
409
|
+
scene_pos.x() - radius, scene_pos.y() - radius,
|
|
410
|
+
size, size)
|
|
446
411
|
if self.brush_cursor.isVisible():
|
|
447
412
|
self.current_brush_cursor.show()
|
|
448
413
|
else:
|
|
@@ -1,17 +1,79 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0904, R0903, R0902, E1101, R0914
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0904, R0903, R0902, E1101, R0914, R0913, R0917
|
|
2
2
|
import math
|
|
3
3
|
from abc import abstractmethod
|
|
4
4
|
import numpy as np
|
|
5
5
|
from PySide6.QtCore import Qt, QPointF, QTime, QPoint, Signal, QRectF
|
|
6
|
-
from PySide6.QtGui import QImage, QPainter, QColor, QBrush, QPen, QCursor, QPixmap
|
|
6
|
+
from PySide6.QtGui import QImage, QPainter, QColor, QBrush, QPen, QCursor, QPixmap, QPainterPath
|
|
7
7
|
from PySide6.QtWidgets import (
|
|
8
|
-
QGraphicsEllipseItem, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
|
|
8
|
+
QGraphicsEllipseItem, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem,
|
|
9
|
+
QGraphicsItemGroup, QGraphicsPathItem)
|
|
9
10
|
from .. config.gui_constants import gui_constants
|
|
10
11
|
from .layer_collection import LayerCollectionHandler
|
|
11
12
|
from .brush_gradient import create_default_brush_gradient
|
|
12
13
|
from .brush_preview import BrushPreviewItem
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
class BrushCursor(QGraphicsItemGroup):
|
|
17
|
+
def __init__(self, x0, y0, size, pen, brush):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self._pen = pen
|
|
20
|
+
self._radius = size / 2
|
|
21
|
+
self._brush = brush
|
|
22
|
+
self._rect = QRectF(x0 - self._radius, y0 - self._radius, size, size)
|
|
23
|
+
self._arc_items = []
|
|
24
|
+
self._create_arcs()
|
|
25
|
+
|
|
26
|
+
def _point_on_circle(self, phi_deg):
|
|
27
|
+
phi = phi_deg / 180.0 * math.pi
|
|
28
|
+
x0 = self._rect.x() + self._radius
|
|
29
|
+
y0 = self._rect.y() + self._radius
|
|
30
|
+
return x0 + self._radius * math.cos(phi), y0 - self._radius * math.sin(phi)
|
|
31
|
+
|
|
32
|
+
def _create_arcs(self):
|
|
33
|
+
for item in self._arc_items:
|
|
34
|
+
self.removeFromGroup(item)
|
|
35
|
+
if item.scene():
|
|
36
|
+
item.scene().removeItem(item)
|
|
37
|
+
self._arc_items = []
|
|
38
|
+
half_gap = 20
|
|
39
|
+
arcs = [half_gap, 90 + half_gap, 180 + half_gap, 270 + half_gap]
|
|
40
|
+
span_angle = 90 - 2 * half_gap
|
|
41
|
+
for start_angle in arcs:
|
|
42
|
+
path = QPainterPath()
|
|
43
|
+
path.moveTo(*self._point_on_circle(start_angle))
|
|
44
|
+
path.arcTo(self._rect, start_angle, span_angle)
|
|
45
|
+
arc_item = QGraphicsPathItem(path)
|
|
46
|
+
arc_item.setPen(self._pen)
|
|
47
|
+
arc_item.setBrush(Qt.NoBrush)
|
|
48
|
+
self.addToGroup(arc_item)
|
|
49
|
+
self._arc_items.append(arc_item)
|
|
50
|
+
|
|
51
|
+
# pylint: disable=C0103
|
|
52
|
+
def setPen(self, pen):
|
|
53
|
+
self._pen = pen
|
|
54
|
+
for item in self._arc_items:
|
|
55
|
+
item.setPen(pen)
|
|
56
|
+
|
|
57
|
+
def pen(self):
|
|
58
|
+
return self._pen
|
|
59
|
+
|
|
60
|
+
def setBrush(self, brush):
|
|
61
|
+
self._brush = brush
|
|
62
|
+
for item in self._arc_items:
|
|
63
|
+
item.setBrush(Qt.NoBrush)
|
|
64
|
+
|
|
65
|
+
def brush(self):
|
|
66
|
+
return self._brush
|
|
67
|
+
|
|
68
|
+
def setRect(self, x, y, w, h):
|
|
69
|
+
self._rect = QRectF(x, y, w, h)
|
|
70
|
+
self._create_arcs()
|
|
71
|
+
|
|
72
|
+
def rect(self):
|
|
73
|
+
return self._rect
|
|
74
|
+
# pylint: enable=C0103
|
|
75
|
+
|
|
76
|
+
|
|
15
77
|
class ViewSignals:
|
|
16
78
|
temp_view_requested = Signal(bool)
|
|
17
79
|
brush_operation_started = Signal(QPoint)
|
|
@@ -141,12 +203,12 @@ class ViewStrategy(LayerCollectionHandler):
|
|
|
141
203
|
self.arrange_images()
|
|
142
204
|
|
|
143
205
|
def update_cursor_pen_width(self):
|
|
144
|
-
|
|
206
|
+
width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
|
|
145
207
|
if self.brush_cursor is not None:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
self.brush_cursor.setPen(
|
|
149
|
-
return
|
|
208
|
+
pen = self.brush_cursor.pen()
|
|
209
|
+
pen.setWidthF(width)
|
|
210
|
+
self.brush_cursor.setPen(pen)
|
|
211
|
+
return width
|
|
150
212
|
|
|
151
213
|
def set_allow_cursor_preview(self, state):
|
|
152
214
|
self.allow_cursor_preview = state
|
|
@@ -217,6 +279,9 @@ class ViewStrategy(LayerCollectionHandler):
|
|
|
217
279
|
|
|
218
280
|
def set_master_image_np(self, img):
|
|
219
281
|
self.set_master_image(self.numpy_to_qimage(img))
|
|
282
|
+
if self.brush_cursor is None:
|
|
283
|
+
self.setup_brush_cursor()
|
|
284
|
+
self.show_master()
|
|
220
285
|
|
|
221
286
|
def numpy_to_qimage(self, array):
|
|
222
287
|
if array is None:
|
|
@@ -319,33 +384,51 @@ class ViewStrategy(LayerCollectionHandler):
|
|
|
319
384
|
self.update_brush_cursor()
|
|
320
385
|
self.update_cursor_pen_width()
|
|
321
386
|
|
|
322
|
-
def setup_outline_style(self):
|
|
323
|
-
self.brush_cursor.setPen(QPen(QColor(*gui_constants.BRUSH_COLORS['pen']),
|
|
324
|
-
gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()))
|
|
325
|
-
self.brush_cursor.setBrush(Qt.NoBrush)
|
|
326
|
-
|
|
327
387
|
def setup_simple_brush_style(self, center_x, center_y, radius):
|
|
328
388
|
gradient = create_default_brush_gradient(center_x, center_y, radius, self.brush)
|
|
329
389
|
self.brush_cursor.setPen(QPen(QColor(*gui_constants.BRUSH_COLORS['pen']),
|
|
330
390
|
gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()))
|
|
331
391
|
self.brush_cursor.setBrush(QBrush(gradient))
|
|
332
392
|
|
|
333
|
-
def
|
|
334
|
-
if not self.brush:
|
|
335
|
-
return
|
|
336
|
-
scene = self.get_master_scene()
|
|
393
|
+
def create_circle(self, scene, line_style=Qt.SolidLine):
|
|
337
394
|
for item in scene.items():
|
|
338
395
|
if isinstance(item, QGraphicsEllipseItem) and item != self.brush_preview:
|
|
339
396
|
scene.removeItem(item)
|
|
340
|
-
|
|
397
|
+
pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
|
|
398
|
+
pen = QPen(QColor(*gui_constants.BRUSH_COLORS['pen']), pen_width, line_style)
|
|
399
|
+
brush = Qt.NoBrush
|
|
400
|
+
scene_center = scene.sceneRect().center()
|
|
401
|
+
brush_cursor = scene.addEllipse(
|
|
402
|
+
scene_center.x(), scene_center.y(),
|
|
403
|
+
self.brush.size, self.brush.size, pen, brush)
|
|
404
|
+
brush_cursor.setZValue(1000)
|
|
405
|
+
brush_cursor.hide()
|
|
406
|
+
return brush_cursor
|
|
407
|
+
|
|
408
|
+
def create_alt_circle(self, scene, line_style=Qt.SolidLine):
|
|
409
|
+
for item in scene.items():
|
|
410
|
+
if isinstance(item, BrushCursor) and item != self.brush_preview:
|
|
411
|
+
scene.removeItem(item)
|
|
412
|
+
pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
|
|
413
|
+
pen = QPen(QColor(*gui_constants.BRUSH_COLORS['pen']), pen_width, line_style)
|
|
341
414
|
brush = Qt.NoBrush
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
415
|
+
scene_center = scene.sceneRect().center()
|
|
416
|
+
brush_cursor = BrushCursor(
|
|
417
|
+
scene_center.x(), scene_center.y(),
|
|
418
|
+
self.brush.size, pen, brush
|
|
419
|
+
)
|
|
420
|
+
brush_cursor.setZValue(1000)
|
|
421
|
+
brush_cursor.hide()
|
|
422
|
+
scene.addItem(brush_cursor)
|
|
423
|
+
return brush_cursor
|
|
424
|
+
|
|
425
|
+
def setup_brush_cursor(self):
|
|
426
|
+
if not self.brush:
|
|
427
|
+
return
|
|
428
|
+
self.brush_cursor = self.create_circle(self.get_master_scene())
|
|
346
429
|
|
|
347
430
|
def update_brush_cursor(self):
|
|
348
|
-
if self.empty() or
|
|
431
|
+
if self.empty() or self.brush_cursor is None or not self.isVisible():
|
|
349
432
|
return
|
|
350
433
|
self.update_cursor_pen_width()
|
|
351
434
|
master_view = self.get_master_view()
|
|
@@ -359,7 +442,6 @@ class ViewStrategy(LayerCollectionHandler):
|
|
|
359
442
|
self.brush_cursor.setRect(scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
360
443
|
allow_cursor_preview = self.display_manager.allow_cursor_preview()
|
|
361
444
|
if self.cursor_style == 'preview':
|
|
362
|
-
self.setup_outline_style()
|
|
363
445
|
if allow_cursor_preview:
|
|
364
446
|
self.brush_cursor.hide()
|
|
365
447
|
pos = QCursor.pos()
|
|
@@ -371,9 +453,7 @@ class ViewStrategy(LayerCollectionHandler):
|
|
|
371
453
|
self.brush_preview.update(scene_pos, int(size))
|
|
372
454
|
else:
|
|
373
455
|
self.brush_preview.hide()
|
|
374
|
-
if self.cursor_style
|
|
375
|
-
self.setup_outline_style()
|
|
376
|
-
else:
|
|
456
|
+
if self.cursor_style != 'outline':
|
|
377
457
|
self.setup_simple_brush_style(scene_pos.x(), scene_pos.y(), radius)
|
|
378
458
|
if not self.brush_cursor.isVisible():
|
|
379
459
|
self.brush_cursor.show()
|
|
@@ -427,7 +507,6 @@ class ViewStrategy(LayerCollectionHandler):
|
|
|
427
507
|
def keyReleaseEvent(self, event):
|
|
428
508
|
if self.empty():
|
|
429
509
|
return
|
|
430
|
-
self.update_brush_cursor()
|
|
431
510
|
if event.key() == Qt.Key_Space:
|
|
432
511
|
self.space_pressed = False
|
|
433
512
|
if not self.scrolling:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
|
|
2
|
-
shinestacker/_version.py,sha256=
|
|
2
|
+
shinestacker/_version.py,sha256=oU1lLCdhmPP8CUfgzMFfhCPjCfk4eDfd8WSwOfXNBOk,21
|
|
3
3
|
shinestacker/algorithms/__init__.py,sha256=1FwVJ3w9GGbFFkjYJRUedTvcdE4j0ieSgaH9RC9iCY4,877
|
|
4
4
|
shinestacker/algorithms/align.py,sha256=mb44u-YxZI1TTSHz81nRpX_2c8awlOhnGrK0LyfTQeQ,33543
|
|
5
5
|
shinestacker/algorithms/align_auto.py,sha256=pJetw6zZEWQLouzcelkI8gD4cPiOp887ePXzVbm0E6Q,3800
|
|
@@ -22,16 +22,17 @@ shinestacker/algorithms/vignetting.py,sha256=gJOv-FN3GnTgaVn70W_6d-qbw3WmqinDiO9
|
|
|
22
22
|
shinestacker/algorithms/white_balance.py,sha256=PMKsBtxOSn5aRr_Gkx1StHS4eN6kBN2EhNnhg4UG24g,501
|
|
23
23
|
shinestacker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
shinestacker/app/about_dialog.py,sha256=pkH7nnxUP8yc0D3vRGd1jRb5cwi1nDVbQRk_OC9yLk8,4144
|
|
25
|
+
shinestacker/app/args.py,sha256=TlfMR5GBd6Zz9wZNrN7CGJ1_hm1FnLZn5EoP5mkRHrk,776
|
|
25
26
|
shinestacker/app/gui_utils.py,sha256=fSpkwPXTON_l676UHdAnJNrGq7BPbSlPOiHpOF_LZaI,2519
|
|
26
27
|
shinestacker/app/help_menu.py,sha256=g8lKG_xZmXtNQaC3SIRzyROKVWva_PLEgZsQWh6zUcQ,499
|
|
27
|
-
shinestacker/app/main.py,sha256=
|
|
28
|
+
shinestacker/app/main.py,sha256=dcitc5vwIIyIXeDfZwQnC7KHRCdd3FaVJWyaU8mK86c,10935
|
|
28
29
|
shinestacker/app/open_frames.py,sha256=bsu32iJSYJQLe_tQQbvAU5DuMDVX6MRuNdE7B5lojZc,1488
|
|
29
|
-
shinestacker/app/project.py,sha256=
|
|
30
|
-
shinestacker/app/retouch.py,sha256=
|
|
30
|
+
shinestacker/app/project.py,sha256=X98pK_mMtE_NefTUZfebEaP1YCsVY97hcQD4bSxuNyY,2777
|
|
31
|
+
shinestacker/app/retouch.py,sha256=wlk-tHaei5YAFinGZWyzBopUhUqyxMT6jSH-4DMEwo8,2659
|
|
31
32
|
shinestacker/config/__init__.py,sha256=aXxi-LmAvXd0daIFrVnTHE5OCaYeK1uf1BKMr7oaXQs,197
|
|
32
33
|
shinestacker/config/config.py,sha256=eBko2D3ADhLTIm9X6hB_a_WsIjwgfE-qmBVkhP1XSvc,1636
|
|
33
34
|
shinestacker/config/constants.py,sha256=EEdr7pZg4JpbIjUWaP7kJQfTuBB85FN739myDNAfn8A,8301
|
|
34
|
-
shinestacker/config/gui_constants.py,sha256=
|
|
35
|
+
shinestacker/config/gui_constants.py,sha256=Aqan-AdUjtlARXpsefQvWW2uKVv1tvwc0gfKyo7xud4,2787
|
|
35
36
|
shinestacker/core/__init__.py,sha256=IUEIx6SQ3DygDEHN3_E6uKpHjHtUa4a_U_1dLd_8yEU,484
|
|
36
37
|
shinestacker/core/colors.py,sha256=kr_tJA1iRsdck2JaYDb2lS-codZ4Ty9gdu3kHfiWvuM,1340
|
|
37
38
|
shinestacker/core/core_utils.py,sha256=1LYj19Dfc9jZN9-4dlf1paximDH5WZYa7DXvKr7R7QY,1719
|
|
@@ -50,7 +51,7 @@ shinestacker/gui/gui_logging.py,sha256=kiZcrC2AFYCWgPZo0O5SKw-E5cFrezwf4anS3HjPu
|
|
|
50
51
|
shinestacker/gui/gui_run.py,sha256=zr7x4BVmM0n_ZRsSEaJVVKvHSWHuwhftgkUvgeg90gU,15767
|
|
51
52
|
shinestacker/gui/main_window.py,sha256=5k_9TiZT9idKCmovUFYpUTSEQQj-DMQrlyq9dAgY1MU,24800
|
|
52
53
|
shinestacker/gui/menu_manager.py,sha256=b5Cxh6uddOlio8i7fRISbGDJI-oe0ds6LIF5dWM7leI,11263
|
|
53
|
-
shinestacker/gui/new_project.py,sha256=
|
|
54
|
+
shinestacker/gui/new_project.py,sha256=z8e3EhRMB-KtoPwYQSiKLSOQ2dS0-Okm7zVw21B7zy8,16391
|
|
54
55
|
shinestacker/gui/project_controller.py,sha256=W4sbBGEPVtfF9F1rC-6Y0oKLq_y94HuFBvZRj87xNKQ,16272
|
|
55
56
|
shinestacker/gui/project_converter.py,sha256=Gmna0HwbvACcXiX74TaQYumif8ZV8sZ2APLTMM-L1mU,7436
|
|
56
57
|
shinestacker/gui/project_editor.py,sha256=lSgQ42IoaobHs-NQQWT88Qhg5l7nu5ejxAO5VgIupr8,25498
|
|
@@ -73,7 +74,7 @@ shinestacker/retouch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
73
74
|
shinestacker/retouch/base_filter.py,sha256=gvRGvhOhhWS05jKOXyNdMnia3b7rscQqR8-xnySIWFc,10260
|
|
74
75
|
shinestacker/retouch/brush.py,sha256=dzD2FzSpBIPdJRmTZobcrQ1FrVd3tF__ZPnUplNE72s,357
|
|
75
76
|
shinestacker/retouch/brush_gradient.py,sha256=F5SFhyzl8YTMqjJU3jK8BrIlLCYLUvITd5wz3cQE4xk,1453
|
|
76
|
-
shinestacker/retouch/brush_preview.py,sha256=
|
|
77
|
+
shinestacker/retouch/brush_preview.py,sha256=cOFVMCbEsgR_alzmr_-LLghtGU_unrE-hAjLHcvrZAY,5484
|
|
77
78
|
shinestacker/retouch/brush_tool.py,sha256=8uVncTA375uC3Nhp2YM0eZjpOR-nN47i2eGjN8tJzOU,8714
|
|
78
79
|
shinestacker/retouch/denoise_filter.py,sha256=TDUHzhRKlKvCa3D5SCYCZKTpjcl81kGwmONsgSDtO1k,440
|
|
79
80
|
shinestacker/retouch/display_manager.py,sha256=wwbX4n7ulg53fM3E5QL5YEb5IFV0jN8za6yfYc74anA,8624
|
|
@@ -81,24 +82,24 @@ shinestacker/retouch/exif_data.py,sha256=LF-fRXW-reMq-xJ_QRE5j8DC2LVGKIlC6MR3QbC
|
|
|
81
82
|
shinestacker/retouch/file_loader.py,sha256=z02-A8_uDZxayI1NFTxT2GVUvEBWStchX9hlN1o5-0U,4784
|
|
82
83
|
shinestacker/retouch/filter_manager.py,sha256=SdYIZkZBUvuB6wDG0moGWav5sfEvIcB9ioUJR5wJFts,388
|
|
83
84
|
shinestacker/retouch/icon_container.py,sha256=6gw1HO1bC2FrdB4dc_iH81DQuLjzuvRGksZ2hKLT9yA,585
|
|
84
|
-
shinestacker/retouch/image_editor_ui.py,sha256=
|
|
85
|
+
shinestacker/retouch/image_editor_ui.py,sha256=ifrlHafXdELwCXoVfUiSM7jcz3O6qH7z2WjNpNPEmUs,33505
|
|
85
86
|
shinestacker/retouch/image_view_status.py,sha256=bdIhsXiYXm7eyjkTGWkw5PRShzaF_by-g7daqgmhwjM,1858
|
|
86
|
-
shinestacker/retouch/image_viewer.py,sha256=
|
|
87
|
-
shinestacker/retouch/io_gui_handler.py,sha256=
|
|
87
|
+
shinestacker/retouch/image_viewer.py,sha256=D7feFlaZkR2nzJ9VcKcVg8lYPS7AMjA_uxJs9L099kQ,4412
|
|
88
|
+
shinestacker/retouch/io_gui_handler.py,sha256=BRQ5eSt1tCMDYtOqxfdYGhx2BLCrncfNrNLGuWIy5Rk,11873
|
|
88
89
|
shinestacker/retouch/io_manager.py,sha256=JUAA--AK0mVa1PTErJTnBFjaXIle5Qs7Ow0Wkd8at0o,2437
|
|
89
90
|
shinestacker/retouch/layer_collection.py,sha256=fZlGrkm9-Ycc7AOzFSpImhafiTieBeCZRk-UlvlFHbo,5819
|
|
90
|
-
shinestacker/retouch/overlaid_view.py,sha256=
|
|
91
|
-
shinestacker/retouch/shortcuts_help.py,sha256=
|
|
92
|
-
shinestacker/retouch/sidebyside_view.py,sha256=
|
|
91
|
+
shinestacker/retouch/overlaid_view.py,sha256=0V6Y0wVmf7vPzhB4O9BSF8UwBnw0RdBdYCt_tHD4vls,8478
|
|
92
|
+
shinestacker/retouch/shortcuts_help.py,sha256=BFWTT5QvodqMhqa_9LI25hZqjICfckgyWG4fGrGzvnM,4283
|
|
93
|
+
shinestacker/retouch/sidebyside_view.py,sha256=We4hY_ZGwINPVPHACPGYFPaiU3Khpax7Sf9Xur317FA,17358
|
|
93
94
|
shinestacker/retouch/transformation_manager.py,sha256=NSHGUF-JFv4Y81gSvizjQCTp49TLo1so7c0WoUElO08,1812
|
|
94
95
|
shinestacker/retouch/undo_manager.py,sha256=cKUkqnJtnJ-Hq-LQs5Bv49FC6qkG6XSw9oCVySJ8jS0,4312
|
|
95
96
|
shinestacker/retouch/unsharp_mask_filter.py,sha256=uFnth8fpZFGhdIgJCnS8x5v6lBQgJ3hX0CBke9pFXeM,3510
|
|
96
|
-
shinestacker/retouch/view_strategy.py,sha256=
|
|
97
|
+
shinestacker/retouch/view_strategy.py,sha256=5AiSYH_FkeIhaUMtQwZ3v1_8r6KckvOYPFoufEtw12Y,23877
|
|
97
98
|
shinestacker/retouch/vignetting_filter.py,sha256=MA97rQkSL0D-Nh-n2L4AiPR064RoTROkvza4tw84g9U,3658
|
|
98
99
|
shinestacker/retouch/white_balance_filter.py,sha256=glMBYlmrF-i_OrB3sGUpjZE6X4FQdyLC4GBy2bWtaFc,6056
|
|
99
|
-
shinestacker-1.5.
|
|
100
|
-
shinestacker-1.5.
|
|
101
|
-
shinestacker-1.5.
|
|
102
|
-
shinestacker-1.5.
|
|
103
|
-
shinestacker-1.5.
|
|
104
|
-
shinestacker-1.5.
|
|
100
|
+
shinestacker-1.5.1.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
|
|
101
|
+
shinestacker-1.5.1.dist-info/METADATA,sha256=FrhfVRsmH4sx7-cQxDajn-zZinLt-UIhhHf2xYZNzvM,6978
|
|
102
|
+
shinestacker-1.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
103
|
+
shinestacker-1.5.1.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
|
|
104
|
+
shinestacker-1.5.1.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
|
|
105
|
+
shinestacker-1.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|