shinestacker 1.5.1__tar.gz → 1.5.3__tar.gz
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-1.5.1 → shinestacker-1.5.3}/CHANGELOG.md +26 -2
- {shinestacker-1.5.1/src/shinestacker.egg-info → shinestacker-1.5.3}/PKG-INFO +1 -1
- shinestacker-1.5.3/src/shinestacker/_version.py +1 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/main.py +1 -1
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/project.py +1 -1
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/retouch.py +1 -1
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/config/gui_constants.py +1 -1
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/base_filter.py +59 -35
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/denoise_filter.py +4 -3
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/display_manager.py +14 -19
- shinestacker-1.5.3/src/shinestacker/retouch/filter_manager.py +20 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/image_editor_ui.py +32 -73
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/image_viewer.py +31 -25
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/overlaid_view.py +15 -6
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/sidebyside_view.py +40 -6
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/unsharp_mask_filter.py +5 -4
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/view_strategy.py +147 -75
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/vignetting_filter.py +4 -3
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/white_balance_filter.py +62 -17
- {shinestacker-1.5.1 → shinestacker-1.5.3/src/shinestacker.egg-info}/PKG-INFO +1 -1
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/SOURCES.txt +1 -1
- shinestacker-1.5.1/src/shinestacker/_version.py +0 -1
- shinestacker-1.5.1/src/shinestacker/retouch/filter_manager.py +0 -12
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.coveragerc +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.flake8 +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.github/workflows/release.yml +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.gitignore +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.pylintrc +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/.readthedocs.yaml +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/LICENSE +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/MANIFEST.in +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/README.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/alignment.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/api.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/balancing.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/conf.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/focus_stacking.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/gui.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/index.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/job.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/main.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/multilayer.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/noise.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/requirements.txt +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/vignetting.md +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/coffee.gif +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/flies.gif +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/flies_stack.jpg +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/flow-diagram.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/gui-finder.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/gui-project-new.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/gui-project-run.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/img/gui-retouch.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/index.html +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/pyproject.toml +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/requirements.txt +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/scripts/build_release.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/setup.cfg +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/align_parallel.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/utils.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/about_dialog.py +0 -0
- /shinestacker-1.5.1/src/shinestacker/app/args.py → /shinestacker-1.5.3/src/shinestacker/app/args_parser_opts.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/action_config_dialog.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/gui_run.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/main_window.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/menu_manager.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/new_project.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/project_controller.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/recent_file_manager.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/brush_preview.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/image_view_status.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/io_gui_handler.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/shortcuts_help.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/transformation_manager.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -2,11 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [v1.5.3] - 2025-09-21
|
|
6
|
+
**Bug fixes**
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- fixed brush preview visiblity in cursor style transitions
|
|
10
|
+
- fixed relative import in main app scripts
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- implemenrted cursor dynamic color based on background image luminosity
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [v1.5.2] - 2025-09-21
|
|
18
|
+
**Bug fixes**
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- fixed white balance filter functionality
|
|
22
|
+
- fixed brush preview visiblity in view mode transitions
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- code refactoring and cleanup
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## [v1.5.1] - 2025-09-20
|
|
6
30
|
**Several bug fixes**
|
|
7
31
|
|
|
8
32
|
### Added
|
|
9
|
-
- new command-line
|
|
33
|
+
- new command-line arguments -v1, -v2, -v3, allow different view modes at startup
|
|
10
34
|
|
|
11
35
|
### Fixed
|
|
12
36
|
- consistent and restyled cursor for current layer view
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.5.3'
|
|
@@ -20,7 +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 .
|
|
23
|
+
from shinestacker.app.args_parser_opts import add_project_arguments, add_retouch_arguments
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class SelectionDialog(QDialog):
|
|
@@ -17,7 +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 .
|
|
20
|
+
from shinestacker.app.args_parser_opts import add_project_arguments
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class ProjectApp(MainWindow):
|
|
@@ -13,7 +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 .
|
|
16
|
+
from shinestacker.app.args_parser_opts import add_retouch_arguments
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class RetouchApp(ImageEditorUI):
|
|
@@ -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,
|
|
29
|
+
'pen': (255, 255, 255, 200),
|
|
30
30
|
'preview': (255, 180, 180),
|
|
31
31
|
'cursor_inner': (255, 0, 0, 120),
|
|
32
32
|
'preview_inner': (255, 255, 255, 150)
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, W0718, R0915, R0903, R0913, R0917, R0902, R0914
|
|
2
2
|
import traceback
|
|
3
|
-
from abc import
|
|
3
|
+
from abc import abstractmethod
|
|
4
4
|
import numpy as np
|
|
5
|
+
from PySide6.QtCore import Qt, QThread, QTimer, QObject, Signal
|
|
5
6
|
from PySide6.QtWidgets import (
|
|
6
7
|
QHBoxLayout, QLabel, QSlider, QDialog, QVBoxLayout, QCheckBox, QDialogButtonBox)
|
|
7
|
-
from
|
|
8
|
+
from .layer_collection import LayerCollectionHandler
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
class BaseFilter(
|
|
11
|
-
|
|
11
|
+
class BaseFilter(QObject, LayerCollectionHandler):
|
|
12
|
+
update_master_thumbnail_requested = Signal()
|
|
13
|
+
mark_as_modified_requested = Signal()
|
|
14
|
+
filter_gui_set_enabled_requested = Signal(bool)
|
|
15
|
+
|
|
16
|
+
def __init__(self, name, parent, image_viewer, layer_collection, undo_manager,
|
|
17
|
+
allow_partial_preview=True,
|
|
12
18
|
partial_preview_threshold=0.75, preview_at_startup=False):
|
|
13
|
-
self
|
|
19
|
+
QObject.__init__(self, parent)
|
|
20
|
+
LayerCollectionHandler.__init__(self, layer_collection)
|
|
21
|
+
self.image_viewer = image_viewer
|
|
22
|
+
self.undo_manager = undo_manager
|
|
14
23
|
self.name = name
|
|
15
24
|
self.allow_partial_preview = allow_partial_preview
|
|
16
25
|
self.partial_preview_threshold = partial_preview_threshold
|
|
@@ -31,11 +40,16 @@ class BaseFilter(ABC):
|
|
|
31
40
|
def apply(self, image, *params):
|
|
32
41
|
pass
|
|
33
42
|
|
|
43
|
+
def connect_signals(self, update_master_thumbnail, mark_as_modified, filter_gui_set_enabled):
|
|
44
|
+
self.update_master_thumbnail_requested.connect(update_master_thumbnail)
|
|
45
|
+
self.mark_as_modified_requested.connect(mark_as_modified)
|
|
46
|
+
self.filter_gui_set_enabled_requested.connect(filter_gui_set_enabled)
|
|
47
|
+
|
|
34
48
|
def run_with_preview(self, **kwargs):
|
|
35
|
-
if self.
|
|
49
|
+
if self.has_no_master_layer():
|
|
36
50
|
return
|
|
37
|
-
self.
|
|
38
|
-
dlg = QDialog(self.
|
|
51
|
+
self.copy_master_layer()
|
|
52
|
+
dlg = QDialog(self.parent())
|
|
39
53
|
layout = QVBoxLayout(dlg)
|
|
40
54
|
active_worker = None
|
|
41
55
|
last_request_id = 0
|
|
@@ -46,8 +60,8 @@ class BaseFilter(ABC):
|
|
|
46
60
|
def cleanup():
|
|
47
61
|
nonlocal active_worker, dialog_closed # noqa
|
|
48
62
|
dialog_closed = True
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
63
|
+
self.restore_master_layer()
|
|
64
|
+
self.image_viewer.update_master_display()
|
|
51
65
|
if active_worker and active_worker.isRunning():
|
|
52
66
|
active_worker.wait()
|
|
53
67
|
initial_timer.stop()
|
|
@@ -58,13 +72,13 @@ class BaseFilter(ABC):
|
|
|
58
72
|
if dialog_closed or request_id != expected_id:
|
|
59
73
|
return
|
|
60
74
|
if region:
|
|
61
|
-
current_region = self.
|
|
75
|
+
current_region = self.image_viewer.get_visible_image_portion()[1]
|
|
62
76
|
if current_region == region:
|
|
63
|
-
self.
|
|
64
|
-
self.
|
|
77
|
+
self.set_master_layer(img)
|
|
78
|
+
self.image_viewer.update_master_display()
|
|
65
79
|
else:
|
|
66
|
-
self.
|
|
67
|
-
self.
|
|
80
|
+
self.set_master_layer(img)
|
|
81
|
+
self.image_viewer.update_master_display()
|
|
68
82
|
try:
|
|
69
83
|
dlg.activateWindow()
|
|
70
84
|
except Exception:
|
|
@@ -84,10 +98,10 @@ class BaseFilter(ABC):
|
|
|
84
98
|
current_id = last_request_id
|
|
85
99
|
visible_region = None
|
|
86
100
|
if kwargs.get('partial_preview', self.allow_partial_preview):
|
|
87
|
-
visible_data = self.
|
|
101
|
+
visible_data = self.image_viewer.get_visible_image_portion()
|
|
88
102
|
if visible_data:
|
|
89
103
|
visible_img, visible_region = visible_data
|
|
90
|
-
master_img = self.
|
|
104
|
+
master_img = self.master_layer_copy()
|
|
91
105
|
if visible_img.size < master_img.size * self.partial_preview_threshold:
|
|
92
106
|
params = tuple(self.get_params() or ())
|
|
93
107
|
worker = self.PreviewWorker(
|
|
@@ -107,14 +121,14 @@ class BaseFilter(ABC):
|
|
|
107
121
|
params = tuple(self.get_params() or ())
|
|
108
122
|
worker = self.PreviewWorker(
|
|
109
123
|
self.apply,
|
|
110
|
-
args=(self.
|
|
124
|
+
args=(self.master_layer_copy(), *params),
|
|
111
125
|
request_id=current_id
|
|
112
126
|
)
|
|
113
127
|
else:
|
|
114
128
|
params = tuple(self.get_params() or ())
|
|
115
129
|
worker = self.PreviewWorker(
|
|
116
130
|
self.apply,
|
|
117
|
-
args=(self.
|
|
131
|
+
args=(self.master_layer_copy(), *params),
|
|
118
132
|
request_id=current_id
|
|
119
133
|
)
|
|
120
134
|
active_worker = worker
|
|
@@ -123,8 +137,8 @@ class BaseFilter(ABC):
|
|
|
123
137
|
active_worker.start()
|
|
124
138
|
|
|
125
139
|
def restore_original():
|
|
126
|
-
self.
|
|
127
|
-
self.
|
|
140
|
+
self.restore_master_layer()
|
|
141
|
+
self.image_viewer.update_master_display()
|
|
128
142
|
try:
|
|
129
143
|
dlg.activateWindow()
|
|
130
144
|
except Exception:
|
|
@@ -139,26 +153,34 @@ class BaseFilter(ABC):
|
|
|
139
153
|
if accepted:
|
|
140
154
|
params = tuple(self.get_params() or ())
|
|
141
155
|
try:
|
|
142
|
-
h, w = self.
|
|
156
|
+
h, w = self.master_layer().shape[:2]
|
|
143
157
|
except Exception:
|
|
144
|
-
h, w = self.
|
|
158
|
+
h, w = self.master_layer_copy().shape[:2]
|
|
145
159
|
try:
|
|
146
|
-
self.
|
|
147
|
-
self.
|
|
148
|
-
self.
|
|
160
|
+
self.undo_manager.extend_undo_area(0, 0, w, h)
|
|
161
|
+
self.undo_manager.save_undo_state(
|
|
162
|
+
self.master_layer_copy(),
|
|
149
163
|
self.name
|
|
150
164
|
)
|
|
151
165
|
except Exception:
|
|
152
166
|
pass
|
|
153
|
-
final_img = self.apply(self.
|
|
154
|
-
self.
|
|
155
|
-
self.
|
|
156
|
-
self.
|
|
157
|
-
self.
|
|
158
|
-
self.
|
|
167
|
+
final_img = self.apply(self.master_layer_copy(), *params)
|
|
168
|
+
self.set_master_layer(final_img)
|
|
169
|
+
self.copy_master_layer()
|
|
170
|
+
self.image_viewer.update_master_display()
|
|
171
|
+
self.update_master_thumbnail_requested.emit()
|
|
172
|
+
self.mark_as_modified_requested.emit()
|
|
159
173
|
else:
|
|
160
174
|
restore_original()
|
|
161
175
|
|
|
176
|
+
def connect_preview_toggle(self, preview_check, do_preview, restore_original):
|
|
177
|
+
def on_toggled(checked):
|
|
178
|
+
if checked:
|
|
179
|
+
do_preview()
|
|
180
|
+
else:
|
|
181
|
+
restore_original()
|
|
182
|
+
preview_check.toggled.connect(on_toggled)
|
|
183
|
+
|
|
162
184
|
def create_base_widgets(self, layout, buttons, preview_latency, parent):
|
|
163
185
|
self.preview_check = QCheckBox("Preview")
|
|
164
186
|
self.preview_check.setChecked(self.preview_at_startup)
|
|
@@ -199,10 +221,12 @@ class BaseFilter(ABC):
|
|
|
199
221
|
|
|
200
222
|
|
|
201
223
|
class OneSliderBaseFilter(BaseFilter):
|
|
202
|
-
def __init__(self, name,
|
|
224
|
+
def __init__(self, name, parent, image_viewer, layer_collection, undo_manager,
|
|
225
|
+
max_value, initial_value, title,
|
|
203
226
|
allow_partial_preview=True, partial_preview_threshold=0.5,
|
|
204
227
|
preview_at_startup=True):
|
|
205
|
-
super().__init__(name,
|
|
228
|
+
super().__init__(name, parent, image_viewer, layer_collection, undo_manager,
|
|
229
|
+
allow_partial_preview,
|
|
206
230
|
partial_preview_threshold, preview_at_startup)
|
|
207
231
|
self.max_range = 500
|
|
208
232
|
self.max_value = max_value
|
|
@@ -234,7 +258,7 @@ class OneSliderBaseFilter(BaseFilter):
|
|
|
234
258
|
self.preview_timer.timeout.connect(do_preview)
|
|
235
259
|
|
|
236
260
|
slider_local.valueChanged.connect(self.config_changed)
|
|
237
|
-
self.
|
|
261
|
+
self.connect_preview_toggle(
|
|
238
262
|
self.preview_check, self.do_preview_delayed, restore_original)
|
|
239
263
|
self.button_box.accepted.connect(dlg.accept)
|
|
240
264
|
self.button_box.rejected.connect(dlg.reject)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, W0221
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, W0221, R0913, R0917
|
|
2
2
|
from .base_filter import OneSliderBaseFilter
|
|
3
3
|
from .. algorithms.denoise import denoise
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class DenoiseFilter(OneSliderBaseFilter):
|
|
7
|
-
def __init__(self, name,
|
|
8
|
-
super().__init__(name,
|
|
7
|
+
def __init__(self, name, parent, image_viewer, layer_collection, undo_manager):
|
|
8
|
+
super().__init__(name, parent, image_viewer, layer_collection, undo_manager,
|
|
9
|
+
10.0, 2.5, "Denoise",
|
|
9
10
|
allow_partial_preview=True, preview_at_startup=False)
|
|
10
11
|
|
|
11
12
|
def apply(self, image, strength):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0903, R0913, R0917, E1121, R0902
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0903, R0913, R0917, E1121, R0902, R0914
|
|
2
2
|
import numpy as np
|
|
3
3
|
from PySide6.QtWidgets import (QWidget, QListWidgetItem, QVBoxLayout, QLabel, QInputDialog,
|
|
4
4
|
QAbstractItemView)
|
|
@@ -38,7 +38,6 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
38
38
|
self.update_timer = QTimer()
|
|
39
39
|
self.update_timer.setInterval(gui_constants.PAINT_REFRESH_TIMER)
|
|
40
40
|
self.update_timer.timeout.connect(self.process_pending_updates)
|
|
41
|
-
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
42
41
|
|
|
43
42
|
def process_pending_updates(self):
|
|
44
43
|
if self.needs_update:
|
|
@@ -110,14 +109,15 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
110
109
|
self.thumbnail_list, "Rename Label", "New label name:", text=old_label)
|
|
111
110
|
if ok and new_label and new_label != old_label:
|
|
112
111
|
label_widget.setText(new_label)
|
|
113
|
-
self.
|
|
112
|
+
self.set_layer_label(i, new_label)
|
|
113
|
+
self.status_message_requested.emit("Label renamed.")
|
|
114
114
|
|
|
115
115
|
label_widget.double_clicked.connect(lambda: rename_label(label_widget, label, i))
|
|
116
116
|
content_layout.addWidget(label_widget)
|
|
117
117
|
container_layout.addWidget(content_widget)
|
|
118
118
|
if is_current:
|
|
119
119
|
container.setStyleSheet(
|
|
120
|
-
f"#thumbnailContainer{{ border: 2px solid {
|
|
120
|
+
f"#thumbnailContainer{{ border: 2px solid {gui_constants.THUMB_HI_COLOR}; }}")
|
|
121
121
|
else:
|
|
122
122
|
container.setStyleSheet("#thumbnailContainer{ border: 2px solid transparent; }")
|
|
123
123
|
item = QListWidgetItem()
|
|
@@ -128,7 +128,7 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
128
128
|
if is_current:
|
|
129
129
|
self.thumbnail_list.setCurrentItem(item)
|
|
130
130
|
|
|
131
|
-
def highlight_thumbnail(self, index):
|
|
131
|
+
def highlight_thumbnail(self, index, color=gui_constants.THUMB_HI_COLOR):
|
|
132
132
|
for i in range(self.thumbnail_list.count()):
|
|
133
133
|
item = self.thumbnail_list.item(i)
|
|
134
134
|
widget = self.thumbnail_list.itemWidget(item)
|
|
@@ -139,7 +139,7 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
139
139
|
widget = self.thumbnail_list.itemWidget(current_item)
|
|
140
140
|
if widget:
|
|
141
141
|
widget.setStyleSheet(
|
|
142
|
-
f"#thumbnailContainer{{ border: 2px solid {
|
|
142
|
+
f"#thumbnailContainer{{ border: 2px solid {color}; }}")
|
|
143
143
|
self.thumbnail_list.setCurrentRow(index)
|
|
144
144
|
self.thumbnail_list.scrollToItem(
|
|
145
145
|
self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
|
|
@@ -147,28 +147,26 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
147
147
|
def _master_refresh_and_thumb(self):
|
|
148
148
|
self.image_viewer.show_master()
|
|
149
149
|
self.refresh_master_view()
|
|
150
|
-
self.
|
|
151
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
150
|
+
self.highlight_thumbnail(self.current_layer_idx(), gui_constants.THUMB_LO_COLOR)
|
|
152
151
|
|
|
153
152
|
def _current_refresh_and_thumb(self):
|
|
154
153
|
self.image_viewer.show_current()
|
|
155
154
|
self.refresh_current_view()
|
|
156
|
-
self.
|
|
157
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
155
|
+
self.highlight_thumbnail(self.current_layer_idx(), gui_constants.THUMB_HI_COLOR)
|
|
158
156
|
|
|
159
157
|
def set_view_master(self):
|
|
160
158
|
if self.has_no_master_layer():
|
|
161
159
|
return
|
|
162
160
|
self.view_mode = 'master'
|
|
163
161
|
self._master_refresh_and_thumb()
|
|
164
|
-
self.status_message_requested.emit("View
|
|
162
|
+
self.status_message_requested.emit("View: Master.")
|
|
165
163
|
|
|
166
164
|
def set_view_individual(self):
|
|
167
165
|
if self.has_no_master_layer():
|
|
168
166
|
return
|
|
169
167
|
self.view_mode = 'individual'
|
|
170
168
|
self._current_refresh_and_thumb()
|
|
171
|
-
self.status_message_requested.emit("View
|
|
169
|
+
self.status_message_requested.emit("View: Individual layers.")
|
|
172
170
|
|
|
173
171
|
def refresh_master_view(self):
|
|
174
172
|
if self.has_no_master_layer():
|
|
@@ -186,19 +184,16 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
186
184
|
def start_temp_view(self):
|
|
187
185
|
if self.view_mode == 'master':
|
|
188
186
|
self._current_refresh_and_thumb()
|
|
189
|
-
self.status_message_requested.emit("Temporary view: Individual layer")
|
|
187
|
+
self.status_message_requested.emit("Temporary view: Individual layer.")
|
|
190
188
|
else:
|
|
191
189
|
self._master_refresh_and_thumb()
|
|
192
190
|
self.image_viewer.strategy.brush_preview.hide()
|
|
193
|
-
self.status_message_requested.emit("Temporary view: Master")
|
|
191
|
+
self.status_message_requested.emit("Temporary view: Master.")
|
|
194
192
|
|
|
195
193
|
def end_temp_view(self):
|
|
196
194
|
if self.view_mode == 'master':
|
|
197
195
|
self._master_refresh_and_thumb()
|
|
198
|
-
self.status_message_requested.emit("View mode: Master")
|
|
196
|
+
self.status_message_requested.emit("View mode: Master.")
|
|
199
197
|
else:
|
|
200
198
|
self._current_refresh_and_thumb()
|
|
201
|
-
self.status_message_requested.emit("View: Individual layer")
|
|
202
|
-
|
|
203
|
-
def allow_cursor_preview(self):
|
|
204
|
-
return self.view_mode == 'master'
|
|
199
|
+
self.status_message_requested.emit("View: Individual layer.")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, R0913, R0917
|
|
2
|
+
class FilterManager:
|
|
3
|
+
def __init__(self, editor):
|
|
4
|
+
self.editor = editor
|
|
5
|
+
self.image_viewer = editor.image_viewer
|
|
6
|
+
self.layer_collection = editor.layer_collection
|
|
7
|
+
self.undo_manager = editor.undo_manager
|
|
8
|
+
self.filters = {}
|
|
9
|
+
|
|
10
|
+
def register_filter(self, name, filter_class,
|
|
11
|
+
update_master_thumbnail, mark_as_modified, filter_gui_set_enabled):
|
|
12
|
+
filter_obj = filter_class(
|
|
13
|
+
name, self.editor, self.image_viewer, self.layer_collection, self.undo_manager)
|
|
14
|
+
self.filters[name] = filter_obj
|
|
15
|
+
filter_obj.connect_signals(
|
|
16
|
+
update_master_thumbnail, mark_as_modified, filter_gui_set_enabled)
|
|
17
|
+
|
|
18
|
+
def apply(self, name, **kwargs):
|
|
19
|
+
if name in self.filters:
|
|
20
|
+
self.filters[name].run_with_preview(**kwargs)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904, W0108
|
|
2
2
|
from functools import partial
|
|
3
|
-
import numpy as np
|
|
4
3
|
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QMenu,
|
|
5
4
|
QListWidget, QSlider, QMainWindow, QMessageBox)
|
|
6
5
|
from PySide6.QtGui import QShortcut, QKeySequence, QAction, QActionGroup
|
|
@@ -31,9 +30,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
31
30
|
QMainWindow.__init__(self)
|
|
32
31
|
LayerCollectionHandler.__init__(self, LayerCollection())
|
|
33
32
|
self._recent_file_manager = RecentFileManager("shinestacker-recent-images-files.txt")
|
|
34
|
-
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
35
33
|
self.io_gui_handler = None
|
|
36
|
-
self.display_manager = None
|
|
37
34
|
self.brush = Brush()
|
|
38
35
|
self.brush_tool = BrushTool()
|
|
39
36
|
self.modified = False
|
|
@@ -43,11 +40,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
43
40
|
self.undo_action = None
|
|
44
41
|
self.redo_action = None
|
|
45
42
|
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
46
|
-
self.filter_manager = FilterManager(self)
|
|
47
|
-
self.filter_manager.register_filter("Denoise", DenoiseFilter)
|
|
48
|
-
self.filter_manager.register_filter("Unsharp Mask", UnsharpMaskFilter)
|
|
49
|
-
self.filter_manager.register_filter("White Balance", WhiteBalanceFilter)
|
|
50
|
-
self.filter_manager.register_filter("Vignetting Correction", VignettingFilter)
|
|
51
43
|
self.shortcuts_help_dialog = None
|
|
52
44
|
self.update_title()
|
|
53
45
|
self.resize(1400, 900)
|
|
@@ -144,7 +136,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
144
136
|
self.master_thumbnail_frame = QFrame()
|
|
145
137
|
self.master_thumbnail_frame.setObjectName("thumbnailContainer")
|
|
146
138
|
self.master_thumbnail_frame.setStyleSheet(
|
|
147
|
-
f"#thumbnailContainer{{ border: 2px solid {
|
|
139
|
+
f"#thumbnailContainer{{ border: 2px solid {gui_constants.THUMB_MASTER_HI_COLOR}; }}")
|
|
148
140
|
self.master_thumbnail_frame.setFrameShape(QFrame.StyledPanel)
|
|
149
141
|
master_thumbnail_layout = QVBoxLayout(self.master_thumbnail_frame)
|
|
150
142
|
master_thumbnail_layout.setContentsMargins(8, 8, 8, 8)
|
|
@@ -229,6 +221,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
229
221
|
self.display_manager = DisplayManager(
|
|
230
222
|
self.layer_collection, self.image_viewer,
|
|
231
223
|
self.master_thumbnail_label, self.thumbnail_list, parent=self)
|
|
224
|
+
self.filter_manager = FilterManager(self)
|
|
232
225
|
self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
|
|
233
226
|
self.display_manager.status_message_requested.connect(self.show_status_message)
|
|
234
227
|
self.io_gui_handler.status_message_requested.connect(self.show_status_message)
|
|
@@ -243,8 +236,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
243
236
|
self.image_viewer.set_preview_brush(self.brush_tool.brush)
|
|
244
237
|
self.brush_tool.update_brush_thumb()
|
|
245
238
|
self.io_gui_handler.setup_ui(self.display_manager, self.image_viewer)
|
|
246
|
-
self.image_viewer.set_display_manager(self.display_manager)
|
|
247
|
-
|
|
248
239
|
menubar = self.menuBar()
|
|
249
240
|
file_menu = menubar.addMenu("&File")
|
|
250
241
|
file_menu.addAction("&Open...", self.io_gui_handler.open_file, "Ctrl+O")
|
|
@@ -313,7 +304,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
313
304
|
|
|
314
305
|
view_menu.addSeparator()
|
|
315
306
|
|
|
316
|
-
view_strategy_menu = QMenu("View &Mode", view_menu)
|
|
307
|
+
self.view_strategy_menu = QMenu("View &Mode", view_menu)
|
|
317
308
|
|
|
318
309
|
self.view_mode_actions = {
|
|
319
310
|
'overlaid': QAction("Overlaid", self),
|
|
@@ -324,18 +315,32 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
324
315
|
overlaid_mode.setShortcut("Ctrl+1")
|
|
325
316
|
overlaid_mode.setCheckable(True)
|
|
326
317
|
overlaid_mode.triggered.connect(lambda: self.set_strategy('overlaid'))
|
|
327
|
-
view_strategy_menu.addAction(overlaid_mode)
|
|
318
|
+
self.view_strategy_menu.addAction(overlaid_mode)
|
|
328
319
|
side_by_side_mode = self.view_mode_actions['sidebyside']
|
|
329
320
|
side_by_side_mode.setShortcut("Ctrl+2")
|
|
330
321
|
side_by_side_mode.setCheckable(True)
|
|
331
322
|
side_by_side_mode.triggered.connect(lambda: self.set_strategy('sidebyside'))
|
|
332
|
-
view_strategy_menu.addAction(side_by_side_mode)
|
|
323
|
+
self.view_strategy_menu.addAction(side_by_side_mode)
|
|
333
324
|
side_by_side_mode = self.view_mode_actions['topbottom']
|
|
334
325
|
side_by_side_mode.setShortcut("Ctrl+3")
|
|
335
326
|
side_by_side_mode.setCheckable(True)
|
|
336
327
|
side_by_side_mode.triggered.connect(lambda: self.set_strategy('topbottom'))
|
|
337
|
-
view_strategy_menu.addAction(side_by_side_mode)
|
|
338
|
-
view_menu.addMenu(view_strategy_menu)
|
|
328
|
+
self.view_strategy_menu.addAction(side_by_side_mode)
|
|
329
|
+
view_menu.addMenu(self.view_strategy_menu)
|
|
330
|
+
|
|
331
|
+
filter_handles = (
|
|
332
|
+
self.display_manager.update_master_thumbnail,
|
|
333
|
+
self.mark_as_modified,
|
|
334
|
+
self.view_strategy_menu.setEnabled
|
|
335
|
+
)
|
|
336
|
+
self.filter_manager.register_filter(
|
|
337
|
+
"Denoise", DenoiseFilter, *filter_handles)
|
|
338
|
+
self.filter_manager.register_filter(
|
|
339
|
+
"Unsharp Mask", UnsharpMaskFilter, *filter_handles)
|
|
340
|
+
self.filter_manager.register_filter(
|
|
341
|
+
"White Balance", WhiteBalanceFilter, *filter_handles)
|
|
342
|
+
self.filter_manager.register_filter(
|
|
343
|
+
"Vignetting Correction", VignettingFilter, *filter_handles)
|
|
339
344
|
|
|
340
345
|
cursor_menu = view_menu.addMenu("Cursor Style")
|
|
341
346
|
|
|
@@ -462,6 +467,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
462
467
|
def set_strategy(self, strategy):
|
|
463
468
|
self.image_viewer.set_strategy(strategy)
|
|
464
469
|
enable_shortcuts = strategy == 'overlaid'
|
|
470
|
+
self.display_manager.view_mode = 'master'
|
|
471
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
|
|
465
472
|
self.view_master_action.setEnabled(enable_shortcuts)
|
|
466
473
|
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
467
474
|
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
@@ -584,7 +591,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
584
591
|
|
|
585
592
|
def copy_brush_area_to_master(self, view_pos):
|
|
586
593
|
if self.layer_stack() is None or self.number_of_layers() == 0 \
|
|
587
|
-
or
|
|
594
|
+
or self.display_manager.view_mode != 'master':
|
|
588
595
|
return
|
|
589
596
|
area = self.brush_tool.apply_brush_operation(
|
|
590
597
|
self.master_layer_copy(),
|
|
@@ -594,7 +601,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
594
601
|
self.undo_manager.extend_undo_area(*area)
|
|
595
602
|
|
|
596
603
|
def begin_copy_brush_area(self, pos):
|
|
597
|
-
if self.display_manager.
|
|
604
|
+
if self.display_manager.view_mode == 'master':
|
|
598
605
|
self.mask_layer = self.io_gui_handler.blank_layer.copy()
|
|
599
606
|
self.copy_master_layer()
|
|
600
607
|
self.undo_manager.reset_undo_area()
|
|
@@ -605,7 +612,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
605
612
|
self.mark_as_modified()
|
|
606
613
|
|
|
607
614
|
def continue_copy_brush_area(self, pos):
|
|
608
|
-
if self.display_manager.
|
|
615
|
+
if self.display_manager.view_mode == 'master':
|
|
609
616
|
self.copy_brush_area_to_master(pos)
|
|
610
617
|
self.display_manager.needs_update = True
|
|
611
618
|
if not self.display_manager.update_timer.isActive():
|
|
@@ -647,53 +654,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
647
654
|
def vignetting_correction(self):
|
|
648
655
|
self.filter_manager.apply("Vignetting Correction")
|
|
649
656
|
|
|
650
|
-
def
|
|
651
|
-
def on_toggled(checked):
|
|
652
|
-
if checked:
|
|
653
|
-
do_preview()
|
|
654
|
-
else:
|
|
655
|
-
restore_original()
|
|
656
|
-
preview_check.toggled.connect(on_toggled)
|
|
657
|
-
|
|
658
|
-
def get_pixel_color_at(self, pos, radius=None):
|
|
659
|
-
item_pos = self.image_viewer.position_on_image(pos)
|
|
660
|
-
x = int(item_pos.x())
|
|
661
|
-
y = int(item_pos.y())
|
|
662
|
-
master_layer = self.master_layer()
|
|
663
|
-
if (0 <= x < self.master_layer().shape[1]) and \
|
|
664
|
-
(0 <= y < self.master_layer().shape[0]):
|
|
665
|
-
if radius is None:
|
|
666
|
-
radius = int(self.brush.size)
|
|
667
|
-
if radius > 0:
|
|
668
|
-
y_indices, x_indices = np.ogrid[-radius:radius + 1, -radius:radius + 1]
|
|
669
|
-
mask = x_indices**2 + y_indices**2 <= radius**2
|
|
670
|
-
x0 = max(0, x - radius)
|
|
671
|
-
x1 = min(master_layer.shape[1], x + radius + 1)
|
|
672
|
-
y0 = max(0, y - radius)
|
|
673
|
-
y1 = min(master_layer.shape[0], y + radius + 1)
|
|
674
|
-
mask = mask[radius - (y - y0): radius + (y1 - y),
|
|
675
|
-
radius - (x - x0): radius + (x1 - x)]
|
|
676
|
-
region = master_layer[y0:y1, x0:x1]
|
|
677
|
-
if region.size == 0:
|
|
678
|
-
pixel = master_layer[y, x]
|
|
679
|
-
else:
|
|
680
|
-
if region.ndim == 3:
|
|
681
|
-
pixel = [region[:, :, c][mask].mean() for c in range(region.shape[2])]
|
|
682
|
-
else:
|
|
683
|
-
pixel = region[mask].mean()
|
|
684
|
-
else:
|
|
685
|
-
pixel = self.master_layer()[y, x]
|
|
686
|
-
if np.isscalar(pixel):
|
|
687
|
-
pixel = [pixel, pixel, pixel]
|
|
688
|
-
pixel = [np.float32(x) for x in pixel]
|
|
689
|
-
if master_layer.dtype == np.uint16:
|
|
690
|
-
pixel = [x / 256.0 for x in pixel]
|
|
691
|
-
return tuple(int(v) for v in pixel)
|
|
692
|
-
return (0, 0, 0)
|
|
693
|
-
|
|
694
|
-
def highlight_master_thumbnail(self):
|
|
657
|
+
def highlight_master_thumbnail(self, color):
|
|
695
658
|
self.master_thumbnail_frame.setStyleSheet(
|
|
696
|
-
f"#thumbnailContainer{{ border: 2px solid {
|
|
659
|
+
f"#thumbnailContainer{{ border: 2px solid {color}; }}")
|
|
697
660
|
|
|
698
661
|
def save_actions_set_enabled(self, enabled):
|
|
699
662
|
self.save_action.setEnabled(enabled)
|
|
@@ -709,13 +672,11 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
709
672
|
|
|
710
673
|
def set_view_master(self):
|
|
711
674
|
self.display_manager.set_view_master()
|
|
712
|
-
self.
|
|
713
|
-
self.highlight_master_thumbnail()
|
|
675
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
|
|
714
676
|
|
|
715
677
|
def set_view_individual(self):
|
|
716
678
|
self.display_manager.set_view_individual()
|
|
717
|
-
self.
|
|
718
|
-
self.highlight_master_thumbnail()
|
|
679
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_LO_COLOR)
|
|
719
680
|
|
|
720
681
|
def toggle_view_master_individual(self):
|
|
721
682
|
if self.display_manager.view_mode == 'master':
|
|
@@ -750,12 +711,10 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
750
711
|
def handle_temp_view(self, start):
|
|
751
712
|
if start:
|
|
752
713
|
self.display_manager.start_temp_view()
|
|
753
|
-
self.
|
|
754
|
-
self.highlight_master_thumbnail()
|
|
714
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_LO_COLOR)
|
|
755
715
|
else:
|
|
756
716
|
self.display_manager.end_temp_view()
|
|
757
|
-
self.
|
|
758
|
-
self.highlight_master_thumbnail()
|
|
717
|
+
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
|
|
759
718
|
|
|
760
719
|
def handle_brush_size_change(self, delta):
|
|
761
720
|
if delta > 0:
|