shinestacker 1.3.1__py3-none-any.whl → 1.5.0__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/algorithms/align.py +198 -18
- shinestacker/algorithms/align_parallel.py +17 -1
- shinestacker/algorithms/balance.py +23 -13
- shinestacker/algorithms/noise_detection.py +3 -1
- shinestacker/algorithms/utils.py +21 -10
- shinestacker/algorithms/vignetting.py +2 -0
- shinestacker/app/main.py +1 -1
- shinestacker/config/gui_constants.py +7 -2
- shinestacker/core/core_utils.py +10 -1
- shinestacker/gui/action_config.py +172 -7
- shinestacker/gui/action_config_dialog.py +246 -285
- shinestacker/gui/gui_run.py +2 -2
- shinestacker/gui/main_window.py +14 -5
- shinestacker/gui/menu_manager.py +26 -2
- shinestacker/gui/project_controller.py +4 -0
- shinestacker/gui/recent_file_manager.py +93 -0
- shinestacker/retouch/base_filter.py +13 -15
- shinestacker/retouch/brush_preview.py +3 -1
- shinestacker/retouch/brush_tool.py +11 -11
- shinestacker/retouch/display_manager.py +43 -59
- shinestacker/retouch/image_editor_ui.py +161 -82
- shinestacker/retouch/image_view_status.py +65 -0
- shinestacker/retouch/image_viewer.py +95 -431
- shinestacker/retouch/io_gui_handler.py +12 -2
- shinestacker/retouch/layer_collection.py +3 -0
- shinestacker/retouch/overlaid_view.py +215 -0
- shinestacker/retouch/shortcuts_help.py +13 -3
- shinestacker/retouch/sidebyside_view.py +477 -0
- shinestacker/retouch/transformation_manager.py +43 -0
- shinestacker/retouch/undo_manager.py +22 -3
- shinestacker/retouch/view_strategy.py +557 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/METADATA +7 -7
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/RECORD +38 -32
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904, W0108
|
|
2
|
+
from functools import partial
|
|
2
3
|
import numpy as np
|
|
3
|
-
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel,
|
|
4
|
+
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QMenu,
|
|
4
5
|
QListWidget, QSlider, QMainWindow, QMessageBox)
|
|
5
6
|
from PySide6.QtGui import QShortcut, QKeySequence, QAction, QActionGroup
|
|
6
7
|
from PySide6.QtCore import Qt
|
|
7
8
|
from PySide6.QtGui import QGuiApplication
|
|
8
9
|
from .. config.constants import constants
|
|
9
10
|
from .. config.gui_constants import gui_constants
|
|
11
|
+
from .. gui.recent_file_manager import RecentFileManager
|
|
10
12
|
from .image_viewer import ImageViewer
|
|
11
13
|
from .shortcuts_help import ShortcutsHelp
|
|
12
14
|
from .brush import Brush
|
|
@@ -21,23 +23,26 @@ from .denoise_filter import DenoiseFilter
|
|
|
21
23
|
from .unsharp_mask_filter import UnsharpMaskFilter
|
|
22
24
|
from .white_balance_filter import WhiteBalanceFilter
|
|
23
25
|
from .vignetting_filter import VignettingFilter
|
|
26
|
+
from .transformation_manager import TransfromationManager
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
27
30
|
def __init__(self):
|
|
28
31
|
QMainWindow.__init__(self)
|
|
29
32
|
LayerCollectionHandler.__init__(self, LayerCollection())
|
|
33
|
+
self._recent_file_manager = RecentFileManager("shinestacker-recent-images-files.txt")
|
|
30
34
|
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
31
|
-
self.undo_manager = UndoManager()
|
|
32
|
-
self.undo_action = None
|
|
33
|
-
self.redo_action = None
|
|
34
|
-
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
35
35
|
self.io_gui_handler = None
|
|
36
36
|
self.display_manager = None
|
|
37
37
|
self.brush = Brush()
|
|
38
38
|
self.brush_tool = BrushTool()
|
|
39
39
|
self.modified = False
|
|
40
40
|
self.mask_layer = None
|
|
41
|
+
self.transformation_manager = TransfromationManager(self)
|
|
42
|
+
self.undo_manager = UndoManager(self.transformation_manager)
|
|
43
|
+
self.undo_action = None
|
|
44
|
+
self.redo_action = None
|
|
45
|
+
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
41
46
|
self.filter_manager = FilterManager(self)
|
|
42
47
|
self.filter_manager.register_filter("Denoise", DenoiseFilter)
|
|
43
48
|
self.filter_manager.register_filter("Unsharp Mask", UnsharpMaskFilter)
|
|
@@ -53,12 +58,12 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
53
58
|
self.setCentralWidget(central_widget)
|
|
54
59
|
layout = QHBoxLayout(central_widget)
|
|
55
60
|
self.image_viewer = ImageViewer(self.layer_collection)
|
|
56
|
-
self.image_viewer.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
self.image_viewer.connect_signals(
|
|
62
|
+
self.handle_temp_view,
|
|
63
|
+
self.begin_copy_brush_area,
|
|
64
|
+
self.continue_copy_brush_area,
|
|
65
|
+
self.end_copy_brush_area,
|
|
66
|
+
self.handle_brush_size_change)
|
|
62
67
|
side_panel = QWidget()
|
|
63
68
|
side_layout = QVBoxLayout(side_panel)
|
|
64
69
|
side_layout.setContentsMargins(0, 0, 0, 0)
|
|
@@ -115,9 +120,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
115
120
|
brush_layout.addWidget(self.flow_slider)
|
|
116
121
|
|
|
117
122
|
side_layout.addWidget(brush_panel)
|
|
118
|
-
self.
|
|
119
|
-
self.
|
|
120
|
-
self.
|
|
123
|
+
self.brush_preview_widget = QLabel()
|
|
124
|
+
self.brush_preview_widget.setContentsMargins(0, 0, 0, 0)
|
|
125
|
+
self.brush_preview_widget.setStyleSheet("""
|
|
121
126
|
QLabel {
|
|
122
127
|
background-color: #f0f0f0;
|
|
123
128
|
border: 1px solid #ccc;
|
|
@@ -126,9 +131,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
126
131
|
margin: 0px;
|
|
127
132
|
}
|
|
128
133
|
""")
|
|
129
|
-
self.
|
|
130
|
-
self.
|
|
131
|
-
brush_layout.addWidget(self.
|
|
134
|
+
self.brush_preview_widget.setAlignment(Qt.AlignCenter)
|
|
135
|
+
self.brush_preview_widget.setFixedHeight(100)
|
|
136
|
+
brush_layout.addWidget(self.brush_preview_widget)
|
|
132
137
|
side_layout.addWidget(brush_panel)
|
|
133
138
|
|
|
134
139
|
master_label = QLabel("Master")
|
|
@@ -235,23 +240,26 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
235
240
|
self.master_thumbnail_label, self.thumbnail_list, parent=self)
|
|
236
241
|
self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
|
|
237
242
|
self.display_manager.status_message_requested.connect(self.show_status_message)
|
|
238
|
-
self.display_manager.cursor_preview_state_changed.connect(
|
|
239
|
-
lambda state: setattr(self.image_viewer, 'allow_cursor_preview', state))
|
|
240
243
|
self.io_gui_handler.status_message_requested.connect(self.show_status_message)
|
|
241
244
|
self.io_gui_handler.update_title_requested.connect(self.update_title)
|
|
242
245
|
self.io_gui_handler.mark_as_modified_requested.connect(self.mark_as_modified)
|
|
243
246
|
self.io_gui_handler.change_layer_requested.connect(self.change_layer)
|
|
244
|
-
self.
|
|
247
|
+
self.io_gui_handler.add_recent_file_requested.connect(self.add_recent_file)
|
|
248
|
+
self.brush_tool.setup_ui(self.brush, self.brush_preview_widget, self.image_viewer,
|
|
245
249
|
self.brush_size_slider, self.hardness_slider, self.opacity_slider,
|
|
246
250
|
self.flow_slider)
|
|
247
|
-
self.image_viewer.
|
|
251
|
+
self.image_viewer.set_brush(self.brush_tool.brush)
|
|
252
|
+
self.image_viewer.set_preview_brush(self.brush_tool.brush)
|
|
248
253
|
self.brush_tool.update_brush_thumb()
|
|
249
254
|
self.io_gui_handler.setup_ui(self.display_manager, self.image_viewer)
|
|
250
|
-
self.image_viewer.
|
|
255
|
+
self.image_viewer.set_display_manager(self.display_manager)
|
|
251
256
|
|
|
252
257
|
menubar = self.menuBar()
|
|
253
258
|
file_menu = menubar.addMenu("&File")
|
|
254
259
|
file_menu.addAction("&Open...", self.io_gui_handler.open_file, "Ctrl+O")
|
|
260
|
+
self.recent_files_menu = QMenu("Open &Recent", file_menu)
|
|
261
|
+
file_menu.addMenu(self.recent_files_menu)
|
|
262
|
+
self.update_recent_files()
|
|
255
263
|
self.save_action = QAction("&Save", self)
|
|
256
264
|
self.save_action.setShortcut("Ctrl+S")
|
|
257
265
|
self.save_action.triggered.connect(self.io_gui_handler.save_file)
|
|
@@ -269,8 +277,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
269
277
|
|
|
270
278
|
file_menu.addAction("&Close", self.close_file, "Ctrl+W")
|
|
271
279
|
file_menu.addSeparator()
|
|
272
|
-
file_menu.addAction("&Import
|
|
273
|
-
file_menu.addAction("Import &EXIF
|
|
280
|
+
file_menu.addAction("&Import Frames", self.io_gui_handler.import_frames)
|
|
281
|
+
file_menu.addAction("Import &EXIF Data", self.io_gui_handler.select_exif_path)
|
|
274
282
|
|
|
275
283
|
edit_menu = menubar.addMenu("&Edit")
|
|
276
284
|
self.undo_action = QAction("Undo", self)
|
|
@@ -285,7 +293,21 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
285
293
|
edit_menu.addAction(self.redo_action)
|
|
286
294
|
edit_menu.addSeparator()
|
|
287
295
|
|
|
288
|
-
|
|
296
|
+
transf_menu = QMenu("&Transform")
|
|
297
|
+
rotate_90_cw_action = QAction(gui_constants.ROTATE_90_CW_LABEL, self)
|
|
298
|
+
transf_menu.addAction(rotate_90_cw_action)
|
|
299
|
+
rotate_90_cw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_cw())
|
|
300
|
+
rotate_90_ccw_action = QAction(gui_constants.ROTATE_90_CCW_LABEL, self)
|
|
301
|
+
transf_menu.addAction(rotate_90_ccw_action)
|
|
302
|
+
rotate_90_ccw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_ccw())
|
|
303
|
+
rotate_180_action = QAction(gui_constants.ROTATE_180_LABEL, self)
|
|
304
|
+
rotate_180_action.triggered.connect(lambda: self.transformation_manager.rotate_180())
|
|
305
|
+
transf_menu.addAction(rotate_180_action)
|
|
306
|
+
edit_menu.addMenu(transf_menu)
|
|
307
|
+
|
|
308
|
+
edit_menu.addSeparator()
|
|
309
|
+
|
|
310
|
+
copy_action = QAction("Copy Current Layer to Master", self)
|
|
289
311
|
copy_action.setShortcut("Ctrl+M")
|
|
290
312
|
copy_action.triggered.connect(self.copy_layer_to_master)
|
|
291
313
|
edit_menu.addAction(copy_action)
|
|
@@ -300,6 +322,78 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
300
322
|
|
|
301
323
|
view_menu.addSeparator()
|
|
302
324
|
|
|
325
|
+
view_strategy_menu = QMenu("View &Mode", view_menu)
|
|
326
|
+
|
|
327
|
+
self.view_mode_actions = {
|
|
328
|
+
'overlaid': QAction("Overlaid", self),
|
|
329
|
+
'sidebyside': QAction("Side by Side", self),
|
|
330
|
+
'topbottom': QAction("Top-Bottom", self)
|
|
331
|
+
}
|
|
332
|
+
overlaid_mode = self.view_mode_actions['overlaid']
|
|
333
|
+
overlaid_mode.setShortcut("Ctrl+1")
|
|
334
|
+
overlaid_mode.setCheckable(True)
|
|
335
|
+
overlaid_mode.triggered.connect(lambda: set_strategy('overlaid'))
|
|
336
|
+
view_strategy_menu.addAction(overlaid_mode)
|
|
337
|
+
side_by_side_mode = self.view_mode_actions['sidebyside']
|
|
338
|
+
side_by_side_mode.setShortcut("Ctrl+2")
|
|
339
|
+
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)
|
|
342
|
+
side_by_side_mode = self.view_mode_actions['topbottom']
|
|
343
|
+
side_by_side_mode.setShortcut("Ctrl+3")
|
|
344
|
+
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
|
+
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
|
+
cursor_menu = view_menu.addMenu("Cursor Style")
|
|
360
|
+
|
|
361
|
+
self.cursor_style_actions = {
|
|
362
|
+
'brush': QAction("Simple Brush", self),
|
|
363
|
+
'preview': QAction("Brush Preview", self),
|
|
364
|
+
'outline': QAction("Outline Only", self)
|
|
365
|
+
}
|
|
366
|
+
brush_action = self.cursor_style_actions['brush']
|
|
367
|
+
brush_action.setCheckable(True)
|
|
368
|
+
brush_action.triggered.connect(lambda: set_cursor_style('brush'))
|
|
369
|
+
cursor_menu.addAction(brush_action)
|
|
370
|
+
|
|
371
|
+
preview_action = self.cursor_style_actions['preview']
|
|
372
|
+
preview_action.setCheckable(True)
|
|
373
|
+
preview_action.triggered.connect(lambda: set_cursor_style('preview'))
|
|
374
|
+
cursor_menu.addAction(preview_action)
|
|
375
|
+
|
|
376
|
+
outline_action = self.cursor_style_actions['outline']
|
|
377
|
+
outline_action.setCheckable(True)
|
|
378
|
+
outline_action.triggered.connect(lambda: set_cursor_style('outline'))
|
|
379
|
+
cursor_menu.addAction(outline_action)
|
|
380
|
+
|
|
381
|
+
def set_cursor_style(cursor_style):
|
|
382
|
+
self.image_viewer.set_cursor_style(cursor_style)
|
|
383
|
+
for label, style in self.cursor_style_actions.items():
|
|
384
|
+
style.setEnabled(label != cursor_style)
|
|
385
|
+
style.setChecked(label == cursor_style)
|
|
386
|
+
|
|
387
|
+
set_cursor_style(self.image_viewer.get_cursor_style())
|
|
388
|
+
|
|
389
|
+
cursor_group = QActionGroup(self)
|
|
390
|
+
cursor_group.addAction(preview_action)
|
|
391
|
+
cursor_group.addAction(outline_action)
|
|
392
|
+
cursor_group.addAction(brush_action)
|
|
393
|
+
cursor_group.setExclusive(True)
|
|
394
|
+
|
|
395
|
+
view_menu.addSeparator()
|
|
396
|
+
|
|
303
397
|
zoom_in_action = QAction("Zoom In", self)
|
|
304
398
|
zoom_in_action.setShortcut("Ctrl++")
|
|
305
399
|
zoom_in_action.triggered.connect(self.image_viewer.zoom_in)
|
|
@@ -316,27 +410,30 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
316
410
|
view_menu.addAction(adapt_action)
|
|
317
411
|
|
|
318
412
|
actual_size_action = QAction("Actual Size", self)
|
|
319
|
-
actual_size_action.setShortcut("Ctrl
|
|
413
|
+
actual_size_action.setShortcut("Ctrl+R")
|
|
320
414
|
actual_size_action.triggered.connect(self.image_viewer.actual_size)
|
|
321
415
|
view_menu.addAction(actual_size_action)
|
|
322
416
|
view_menu.addSeparator()
|
|
323
417
|
|
|
324
|
-
view_master_action = QAction("View Master", self)
|
|
325
|
-
view_master_action.setShortcut("M")
|
|
326
|
-
view_master_action.triggered.connect(self.set_view_master)
|
|
327
|
-
view_menu.addAction(view_master_action)
|
|
328
|
-
|
|
329
|
-
view_individual_action = QAction("View Individual", self)
|
|
330
|
-
view_individual_action.setShortcut("L")
|
|
331
|
-
view_individual_action.triggered.connect(self.set_view_individual)
|
|
332
|
-
view_menu.addAction(view_individual_action)
|
|
333
|
-
|
|
334
|
-
toggle_view_master_individual_action = QAction("Toggle Master/Individual", self)
|
|
335
|
-
toggle_view_master_individual_action.setShortcut("T")
|
|
336
|
-
toggle_view_master_individual_action.triggered.connect(
|
|
337
|
-
|
|
418
|
+
self.view_master_action = QAction("View Master", self)
|
|
419
|
+
self.view_master_action.setShortcut("M")
|
|
420
|
+
self.view_master_action.triggered.connect(self.set_view_master)
|
|
421
|
+
view_menu.addAction(self.view_master_action)
|
|
422
|
+
|
|
423
|
+
self.view_individual_action = QAction("View Individual", self)
|
|
424
|
+
self.view_individual_action.setShortcut("L")
|
|
425
|
+
self.view_individual_action.triggered.connect(self.set_view_individual)
|
|
426
|
+
view_menu.addAction(self.view_individual_action)
|
|
427
|
+
|
|
428
|
+
self.toggle_view_master_individual_action = QAction("Toggle Master/Individual", self)
|
|
429
|
+
self.toggle_view_master_individual_action.setShortcut("T")
|
|
430
|
+
self.toggle_view_master_individual_action.triggered.connect(
|
|
431
|
+
self.toggle_view_master_individual)
|
|
432
|
+
view_menu.addAction(self.toggle_view_master_individual_action)
|
|
338
433
|
view_menu.addSeparator()
|
|
339
434
|
|
|
435
|
+
set_strategy('overlaid')
|
|
436
|
+
|
|
340
437
|
sort_asc_action = QAction("Sort Layers A-Z", self)
|
|
341
438
|
sort_asc_action.triggered.connect(lambda: self.sort_layers('asc'))
|
|
342
439
|
view_menu.addAction(sort_asc_action)
|
|
@@ -347,32 +444,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
347
444
|
|
|
348
445
|
view_menu.addSeparator()
|
|
349
446
|
|
|
350
|
-
cursor_menu = view_menu.addMenu("Cursor Style")
|
|
351
|
-
|
|
352
|
-
brush_action = QAction("Simple Brush", self)
|
|
353
|
-
brush_action.setCheckable(True)
|
|
354
|
-
brush_action.setChecked(self.image_viewer.cursor_style == 'brush')
|
|
355
|
-
brush_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('brush'))
|
|
356
|
-
cursor_menu.addAction(brush_action)
|
|
357
|
-
|
|
358
|
-
preview_action = QAction("Brush Preview", self)
|
|
359
|
-
preview_action.setCheckable(True)
|
|
360
|
-
preview_action.setChecked(self.image_viewer.cursor_style == 'preview')
|
|
361
|
-
preview_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('preview'))
|
|
362
|
-
cursor_menu.addAction(preview_action)
|
|
363
|
-
|
|
364
|
-
outline_action = QAction("Outline Only", self)
|
|
365
|
-
outline_action.setCheckable(True)
|
|
366
|
-
outline_action.setChecked(self.image_viewer.cursor_style == 'outline')
|
|
367
|
-
outline_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('outline'))
|
|
368
|
-
cursor_menu.addAction(outline_action)
|
|
369
|
-
|
|
370
|
-
cursor_group = QActionGroup(self)
|
|
371
|
-
cursor_group.addAction(preview_action)
|
|
372
|
-
cursor_group.addAction(outline_action)
|
|
373
|
-
cursor_group.addAction(brush_action)
|
|
374
|
-
cursor_group.setExclusive(True)
|
|
375
|
-
|
|
376
447
|
filter_menu = menubar.addMenu("&Filter")
|
|
377
448
|
filter_menu.setObjectName("Filter")
|
|
378
449
|
denoise_action = QAction("Denoise", self)
|
|
@@ -384,13 +455,13 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
384
455
|
white_balance_action = QAction("White Balance", self)
|
|
385
456
|
white_balance_action.triggered.connect(self.white_balance)
|
|
386
457
|
filter_menu.addAction(white_balance_action)
|
|
387
|
-
vignetting_action = QAction("Vignetting
|
|
458
|
+
vignetting_action = QAction("Vignetting Correction", self)
|
|
388
459
|
vignetting_action.triggered.connect(self.vignetting_correction)
|
|
389
460
|
filter_menu.addAction(vignetting_action)
|
|
390
461
|
|
|
391
462
|
help_menu = menubar.addMenu("&Help")
|
|
392
463
|
help_menu.setObjectName("Help")
|
|
393
|
-
shortcuts_help_action = QAction("Shortcuts and
|
|
464
|
+
shortcuts_help_action = QAction("Shortcuts and Mouse", self)
|
|
394
465
|
|
|
395
466
|
def shortcuts_help():
|
|
396
467
|
self.shortcuts_help_dialog = ShortcutsHelp(self)
|
|
@@ -415,6 +486,19 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
415
486
|
title += " *"
|
|
416
487
|
self.window().setWindowTitle(title)
|
|
417
488
|
|
|
489
|
+
def update_recent_files(self):
|
|
490
|
+
self.recent_files_menu.clear()
|
|
491
|
+
recent_files = self._recent_file_manager.get_files_with_display_names()
|
|
492
|
+
for file_path, display_name in recent_files.items():
|
|
493
|
+
action = self.recent_files_menu.addAction(display_name)
|
|
494
|
+
action.setData(file_path)
|
|
495
|
+
action.triggered.connect(partial(self.io_gui_handler.open_file, file_path))
|
|
496
|
+
self.recent_files_menu.setEnabled(len(recent_files) > 0)
|
|
497
|
+
|
|
498
|
+
def add_recent_file(self, file_path):
|
|
499
|
+
self._recent_file_manager.add_file(file_path)
|
|
500
|
+
self.update_recent_files()
|
|
501
|
+
|
|
418
502
|
def show_status_message(self, message):
|
|
419
503
|
self.statusBar().showMessage(message)
|
|
420
504
|
|
|
@@ -440,7 +524,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
440
524
|
|
|
441
525
|
# pylint: disable=C0103
|
|
442
526
|
def keyPressEvent(self, event):
|
|
443
|
-
if self.image_viewer.empty:
|
|
527
|
+
if self.image_viewer.empty():
|
|
444
528
|
return
|
|
445
529
|
if event.text() == '[':
|
|
446
530
|
self.brush_tool.decrease_brush_size()
|
|
@@ -467,14 +551,12 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
467
551
|
|
|
468
552
|
def change_layer(self, layer_idx):
|
|
469
553
|
if 0 <= layer_idx < self.number_of_layers():
|
|
470
|
-
view_state = self.image_viewer.get_view_state()
|
|
471
554
|
self.set_current_layer_idx(layer_idx)
|
|
472
|
-
self.display_manager.
|
|
473
|
-
self.image_viewer.set_view_state(view_state)
|
|
555
|
+
self.display_manager.refresh_current_view()
|
|
474
556
|
self.thumbnail_list.setCurrentRow(layer_idx)
|
|
475
557
|
self.thumbnail_list.setFocus()
|
|
476
558
|
self.image_viewer.update_brush_cursor()
|
|
477
|
-
self.image_viewer.setFocus()
|
|
559
|
+
self.image_viewer.strategy.setFocus()
|
|
478
560
|
|
|
479
561
|
def prev_layer(self):
|
|
480
562
|
if self.layer_stack() is not None:
|
|
@@ -503,8 +585,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
503
585
|
if reply == QMessageBox.Yes:
|
|
504
586
|
self.set_master_layer(self.current_layer().copy())
|
|
505
587
|
self.master_layer().setflags(write=True)
|
|
506
|
-
self.display_manager.
|
|
507
|
-
self.display_manager.update_thumbnails()
|
|
588
|
+
self.display_manager.refresh_master_view()
|
|
508
589
|
self.mark_as_modified()
|
|
509
590
|
self.statusBar().showMessage(f"Copied layer {self.current_layer_idx() + 1} to master")
|
|
510
591
|
|
|
@@ -540,8 +621,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
540
621
|
|
|
541
622
|
def end_copy_brush_area(self):
|
|
542
623
|
if self.display_manager.update_timer.isActive():
|
|
543
|
-
self.display_manager.
|
|
544
|
-
self.display_manager.update_master_thumbnail()
|
|
624
|
+
self.display_manager.refresh_master_view()
|
|
545
625
|
self.undo_manager.save_undo_state(self.master_layer_copy(), 'Brush Stroke')
|
|
546
626
|
self.display_manager.update_timer.stop()
|
|
547
627
|
self.mark_as_modified()
|
|
@@ -629,6 +709,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
629
709
|
|
|
630
710
|
def close_file(self):
|
|
631
711
|
if self.check_unsaved_changes():
|
|
712
|
+
self.image_viewer.reset_zoom()
|
|
632
713
|
self.io_gui_handler.close_file()
|
|
633
714
|
self.set_master_layer(None)
|
|
634
715
|
self.mark_as_modified(False)
|
|
@@ -663,15 +744,13 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
663
744
|
|
|
664
745
|
def undo(self):
|
|
665
746
|
if self.undo_manager.undo(self.master_layer()):
|
|
666
|
-
self.display_manager.
|
|
667
|
-
self.display_manager.update_master_thumbnail()
|
|
747
|
+
self.display_manager.refresh_master_view()
|
|
668
748
|
self.mark_as_modified()
|
|
669
749
|
self.statusBar().showMessage("Undo applied", 2000)
|
|
670
750
|
|
|
671
751
|
def redo(self):
|
|
672
752
|
if self.undo_manager.redo(self.master_layer()):
|
|
673
|
-
self.display_manager.
|
|
674
|
-
self.display_manager.update_master_thumbnail()
|
|
753
|
+
self.display_manager.refresh_master_view()
|
|
675
754
|
self.mark_as_modified()
|
|
676
755
|
self.statusBar().showMessage("Redo applied", 2000)
|
|
677
756
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0902
|
|
2
|
+
from PySide6.QtCore import QObject, QRectF
|
|
3
|
+
from PySide6.QtGui import QPixmap
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ImageViewStatus(QObject):
|
|
7
|
+
def __init__(self, parent=None):
|
|
8
|
+
super().__init__(parent)
|
|
9
|
+
self.pixmap_master = QPixmap()
|
|
10
|
+
self.pixmap_current = QPixmap()
|
|
11
|
+
self.zoom_factor = 1.0
|
|
12
|
+
self.min_scale = 0.0
|
|
13
|
+
self.max_scale = 0.0
|
|
14
|
+
self.h_scroll = 0
|
|
15
|
+
self.v_scroll = 0
|
|
16
|
+
self.scene_rect = QRectF()
|
|
17
|
+
|
|
18
|
+
def empty(self):
|
|
19
|
+
return self.pixmap_master.isNull()
|
|
20
|
+
|
|
21
|
+
def set_master_image(self, qimage):
|
|
22
|
+
pixmap = QPixmap.fromImage(qimage)
|
|
23
|
+
self.pixmap_master = pixmap
|
|
24
|
+
if not self.empty():
|
|
25
|
+
self.scene_rect = QRectF(pixmap.rect())
|
|
26
|
+
|
|
27
|
+
def set_current_image(self, qimage):
|
|
28
|
+
pixmap = QPixmap.fromImage(qimage)
|
|
29
|
+
self.pixmap_current = pixmap
|
|
30
|
+
|
|
31
|
+
def clear(self):
|
|
32
|
+
self.pixmap_master = QPixmap()
|
|
33
|
+
self.pixmap_current = QPixmap()
|
|
34
|
+
self.zoom_factor = 1.0
|
|
35
|
+
self.min_scale = 0.0
|
|
36
|
+
self.max_scale = 0.0
|
|
37
|
+
self.h_scroll = 0
|
|
38
|
+
self.v_scroll = 0
|
|
39
|
+
self.scene_rect = QRectF()
|
|
40
|
+
|
|
41
|
+
def get_state(self):
|
|
42
|
+
return {
|
|
43
|
+
'zoom': self.zoom_factor,
|
|
44
|
+
'h_scroll': self.h_scroll,
|
|
45
|
+
'v_scroll': self.v_scroll
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def set_state(self, state):
|
|
49
|
+
if state:
|
|
50
|
+
self.zoom_factor = state['zoom']
|
|
51
|
+
self.h_scroll = state['h_scroll']
|
|
52
|
+
self.v_scroll = state['v_scroll']
|
|
53
|
+
|
|
54
|
+
def set_zoom_factor(self, zoom_factor):
|
|
55
|
+
self.zoom_factor = zoom_factor
|
|
56
|
+
|
|
57
|
+
def set_min_scale(self, min_scale):
|
|
58
|
+
self.min_scale = min_scale
|
|
59
|
+
|
|
60
|
+
def set_max_scale(self, min_scale):
|
|
61
|
+
self.max_scale = min_scale
|
|
62
|
+
|
|
63
|
+
def set_scroll(self, h_scroll, v_scroll):
|
|
64
|
+
self.h_scroll = h_scroll
|
|
65
|
+
self.v_scroll = v_scroll
|