shinestacker 1.5.0__tar.gz → 1.5.2__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.0 → shinestacker-1.5.2}/CHANGELOG.md +31 -4
- {shinestacker-1.5.0/src/shinestacker.egg-info → shinestacker-1.5.2}/PKG-INFO +1 -1
- shinestacker-1.5.2/src/shinestacker/_version.py +1 -0
- shinestacker-1.5.2/src/shinestacker/app/args.py +23 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/app/main.py +17 -8
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/app/project.py +2 -3
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/app/retouch.py +8 -4
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/config/gui_constants.py +2 -2
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/new_project.py +17 -14
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/base_filter.py +59 -35
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/brush_preview.py +29 -15
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/denoise_filter.py +4 -3
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/display_manager.py +14 -19
- shinestacker-1.5.2/src/shinestacker/retouch/filter_manager.py +20 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/image_editor_ui.py +48 -96
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/image_viewer.py +31 -27
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/io_gui_handler.py +0 -3
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/overlaid_view.py +38 -17
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/shortcuts_help.py +35 -31
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/sidebyside_view.py +57 -76
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/unsharp_mask_filter.py +5 -4
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/view_strategy.py +176 -89
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/vignetting_filter.py +4 -3
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/white_balance_filter.py +62 -17
- {shinestacker-1.5.0 → shinestacker-1.5.2/src/shinestacker.egg-info}/PKG-INFO +1 -1
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker.egg-info/SOURCES.txt +1 -0
- shinestacker-1.5.0/src/shinestacker/_version.py +0 -1
- shinestacker-1.5.0/src/shinestacker/retouch/filter_manager.py +0 -12
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.coveragerc +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.flake8 +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.github/workflows/release.yml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.gitignore +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.pylintrc +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/.readthedocs.yaml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/LICENSE +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/MANIFEST.in +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/README.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/alignment.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/api.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/balancing.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/conf.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/focus_stacking.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/gui.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/index.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/job.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/main.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/multilayer.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/noise.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/requirements.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/docs/vignetting.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/coffee.gif +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/flies.gif +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/flies_stack.jpg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/flow-diagram.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/gui-finder.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/gui-project-new.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/gui-project-run.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/img/gui-retouch.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/index.html +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/pyproject.toml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/requirements.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/scripts/build_release.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/setup.cfg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/align_parallel.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/utils.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/action_config_dialog.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/gui_run.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/main_window.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/menu_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/project_controller.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/recent_file_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/image_view_status.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/transformation_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.2}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -2,20 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
+
## [v1.5.2] - 2025-09-21
|
|
6
|
+
**Bug fixes**
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- fixed white balance filter functionality
|
|
10
|
+
- fixed brush preview visiblity in view mode transitions
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- code refactoring and cleanup
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [v1.5.1] - 2025-09-20
|
|
18
|
+
**Several bug fixes**
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- new command-line arguments -v1, -v2, -v3, allow different view modes at startup
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- consistent and restyled cursor for current layer view
|
|
25
|
+
- fixed ghost cursors in side-by-side views
|
|
26
|
+
- fixed cursor shift at startup
|
|
27
|
+
- fixed brush preview at image borders
|
|
28
|
+
- fixed lower/upper case GUI labels
|
|
29
|
+
- improved help and description text
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
5
33
|
## [v1.5.0] - 2025-09-16
|
|
6
|
-
**GUI
|
|
34
|
+
**GUI improvements and fixes**
|
|
7
35
|
|
|
8
36
|
### Added
|
|
9
37
|
- implemented image rotation
|
|
10
|
-
- dotted cursor in secondary two-image view
|
|
11
38
|
|
|
12
39
|
### Fixed
|
|
13
|
-
- fixed zoom in wheel events for side-by-side
|
|
40
|
+
- fixed zoom in wheel events for side-by-side views
|
|
14
41
|
- restored standard cursor in empty retouch views
|
|
15
42
|
- lower/upper case GUI labels
|
|
16
43
|
|
|
17
44
|
### Changed
|
|
18
45
|
- code refactoring and cleanup
|
|
46
|
+
- dotted cursor in secondary two-image view
|
|
19
47
|
|
|
20
48
|
---
|
|
21
49
|
|
|
@@ -369,5 +397,4 @@ This release is equivalent to v0.3.2, but resolves a problem for PyPI distributi
|
|
|
369
397
|
- several stability improvements
|
|
370
398
|
- several bug fixes
|
|
371
399
|
|
|
372
|
-
|
|
373
400
|
---
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.5.2'
|
|
@@ -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
|
+
''')
|
|
@@ -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]
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -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,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,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()
|
|
@@ -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):
|