shinestacker 1.5.0__tar.gz → 1.5.1__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.1}/CHANGELOG.md +19 -4
- {shinestacker-1.5.0/src/shinestacker.egg-info → shinestacker-1.5.1}/PKG-INFO +1 -1
- shinestacker-1.5.1/src/shinestacker/_version.py +1 -0
- shinestacker-1.5.1/src/shinestacker/app/args.py +23 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/app/main.py +17 -8
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/app/project.py +2 -3
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/app/retouch.py +8 -4
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/config/gui_constants.py +2 -2
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/new_project.py +17 -14
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_preview.py +29 -15
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_editor_ui.py +16 -23
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_viewer.py +2 -4
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/io_gui_handler.py +0 -3
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/overlaid_view.py +23 -11
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/shortcuts_help.py +35 -31
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/sidebyside_view.py +40 -75
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/view_strategy.py +107 -28
- {shinestacker-1.5.0 → shinestacker-1.5.1/src/shinestacker.egg-info}/PKG-INFO +1 -1
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/SOURCES.txt +1 -0
- shinestacker-1.5.0/src/shinestacker/_version.py +0 -1
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.coveragerc +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.flake8 +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.github/workflows/release.yml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.gitignore +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.pylintrc +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/.readthedocs.yaml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/LICENSE +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/MANIFEST.in +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/README.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/alignment.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/api.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/balancing.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/conf.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/focus_stacking.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/gui.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/index.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/job.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/main.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/multilayer.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/noise.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/requirements.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/docs/vignetting.md +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/coffee.gif +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/flies.gif +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/flies_stack.jpg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/flow-diagram.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/gui-finder.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/gui-project-new.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/gui-project-run.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/img/gui-retouch.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/index.html +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/pyproject.toml +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/requirements.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/scripts/build_release.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/setup.cfg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align_parallel.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/utils.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/action_config_dialog.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_run.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/main_window.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/menu_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_controller.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/recent_file_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/base_filter.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/display_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_view_status.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/transformation_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/vignetting_filter.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.5.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -2,20 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
+
## [V1.5.1] - 2025-09-20
|
|
6
|
+
**Several bug fixes**
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- new command-line parameters -v1, -v2, -v3, allow different view modes at startup
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- consistent and restyled cursor for current layer view
|
|
13
|
+
- fixed ghost cursors in side-by-side views
|
|
14
|
+
- fixed cursor shift at startup
|
|
15
|
+
- fixed brush preview at image borders
|
|
16
|
+
- fixed lower/upper case GUI labels
|
|
17
|
+
- improved help and description text
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
5
21
|
## [v1.5.0] - 2025-09-16
|
|
6
|
-
**GUI
|
|
22
|
+
**GUI improvements and fixes**
|
|
7
23
|
|
|
8
24
|
### Added
|
|
9
25
|
- implemented image rotation
|
|
10
|
-
- dotted cursor in secondary two-image view
|
|
11
26
|
|
|
12
27
|
### Fixed
|
|
13
|
-
- fixed zoom in wheel events for side-by-side
|
|
28
|
+
- fixed zoom in wheel events for side-by-side views
|
|
14
29
|
- restored standard cursor in empty retouch views
|
|
15
30
|
- lower/upper case GUI labels
|
|
16
31
|
|
|
17
32
|
### Changed
|
|
18
33
|
- code refactoring and cleanup
|
|
34
|
+
- dotted cursor in secondary two-image view
|
|
19
35
|
|
|
20
36
|
---
|
|
21
37
|
|
|
@@ -369,5 +385,4 @@ This release is equivalent to v0.3.2, but resolves a problem for PyPI distributi
|
|
|
369
385
|
- several stability improvements
|
|
370
386
|
- several bug fixes
|
|
371
387
|
|
|
372
|
-
|
|
373
388
|
---
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.5.1'
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0914, W0718
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0914, W0718, R0915
|
|
2
2
|
import traceback
|
|
3
3
|
import numpy as np
|
|
4
4
|
from PySide6.QtWidgets import QGraphicsPixmapItem
|
|
@@ -72,38 +72,52 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
|
|
|
72
72
|
self.hide()
|
|
73
73
|
return
|
|
74
74
|
radius = size // 2
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
x_center = int(scene_pos.x() + 0.5)
|
|
76
|
+
y_center = int(scene_pos.y() + 0.5)
|
|
77
|
+
x = x_center - radius
|
|
78
|
+
y = y_center - radius
|
|
77
79
|
w = h = size
|
|
78
80
|
if not self.valid_current_layer_idx():
|
|
79
81
|
self.hide()
|
|
80
82
|
return
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
height, width = self.current_layer().shape[:2]
|
|
84
|
+
visible_x = max(0, x)
|
|
85
|
+
visible_y = max(0, y)
|
|
86
|
+
visible_w = min(width, x + w) - visible_x
|
|
87
|
+
visible_h = min(height, y + h) - visible_y
|
|
88
|
+
if visible_w <= 0 or visible_h <= 0:
|
|
89
|
+
self.hide()
|
|
90
|
+
return
|
|
91
|
+
layer_area = self.get_layer_area(
|
|
92
|
+
self.current_layer(), visible_x, visible_y, visible_w, visible_h)
|
|
93
|
+
master_area = self.get_layer_area(
|
|
94
|
+
self.master_layer(), visible_x, visible_y, visible_w, visible_h)
|
|
83
95
|
if layer_area is None or master_area is None:
|
|
84
96
|
self.hide()
|
|
85
97
|
return
|
|
86
|
-
height, width = self.current_layer().shape[:2]
|
|
87
98
|
full_mask = create_brush_mask(size=size, hardness_percent=self.brush.hardness,
|
|
88
99
|
opacity_percent=self.brush.opacity)[:, :, np.newaxis]
|
|
89
|
-
mask_x_start = max(0, -x)
|
|
90
|
-
mask_y_start = max(0, -y)
|
|
91
|
-
mask_x_end =
|
|
92
|
-
mask_y_end =
|
|
100
|
+
mask_x_start = max(0, -x)
|
|
101
|
+
mask_y_start = max(0, -y)
|
|
102
|
+
mask_x_end = mask_x_start + visible_w
|
|
103
|
+
mask_y_end = mask_y_start + visible_h
|
|
93
104
|
mask_area = full_mask[mask_y_start:mask_y_end, mask_x_start:mask_x_end]
|
|
94
105
|
area = (layer_area * mask_area + master_area * (1 - mask_area)) * 255.0
|
|
95
106
|
area = area.astype(np.uint8)
|
|
96
107
|
qimage = QImage(area.data, area.shape[1], area.shape[0],
|
|
97
108
|
area.strides[0], QImage.Format_RGB888)
|
|
98
|
-
mask = QPixmap(
|
|
109
|
+
mask = QPixmap(visible_w, visible_h)
|
|
99
110
|
mask.fill(Qt.transparent)
|
|
100
111
|
painter = QPainter(mask)
|
|
101
112
|
painter.setPen(Qt.NoPen)
|
|
102
113
|
painter.setBrush(Qt.black)
|
|
103
|
-
|
|
114
|
+
center_x_in_visible = x_center - visible_x
|
|
115
|
+
center_y_in_visible = y_center - visible_y
|
|
116
|
+
painter.drawEllipse(
|
|
117
|
+
center_x_in_visible - radius, center_y_in_visible - radius, size, size)
|
|
104
118
|
painter.end()
|
|
105
119
|
pixmap = QPixmap.fromImage(qimage)
|
|
106
|
-
final_pixmap = QPixmap(
|
|
120
|
+
final_pixmap = QPixmap(visible_w, visible_h)
|
|
107
121
|
final_pixmap.fill(Qt.transparent)
|
|
108
122
|
painter = QPainter(final_pixmap)
|
|
109
123
|
painter.drawPixmap(0, 0, pixmap)
|
|
@@ -111,8 +125,8 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
|
|
|
111
125
|
painter.drawPixmap(0, 0, mask)
|
|
112
126
|
painter.end()
|
|
113
127
|
self.setPixmap(final_pixmap)
|
|
114
|
-
|
|
115
|
-
self.
|
|
128
|
+
self.setPos(visible_x, visible_y)
|
|
129
|
+
self.show()
|
|
116
130
|
except Exception:
|
|
117
131
|
traceback.print_exc()
|
|
118
132
|
self.hide()
|
|
@@ -49,7 +49,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
49
49
|
self.filter_manager.register_filter("White Balance", WhiteBalanceFilter)
|
|
50
50
|
self.filter_manager.register_filter("Vignetting Correction", VignettingFilter)
|
|
51
51
|
self.shortcuts_help_dialog = None
|
|
52
|
-
|
|
53
52
|
self.update_title()
|
|
54
53
|
self.resize(1400, 900)
|
|
55
54
|
center = QGuiApplication.primaryScreen().geometry().center()
|
|
@@ -68,18 +67,15 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
68
67
|
side_layout = QVBoxLayout(side_panel)
|
|
69
68
|
side_layout.setContentsMargins(0, 0, 0, 0)
|
|
70
69
|
side_layout.setSpacing(2)
|
|
71
|
-
|
|
72
70
|
brush_panel = QFrame()
|
|
73
71
|
brush_panel.setFrameShape(QFrame.StyledPanel)
|
|
74
72
|
brush_panel.setContentsMargins(0, 0, 0, 0)
|
|
75
73
|
brush_layout = QVBoxLayout(brush_panel)
|
|
76
74
|
brush_layout.setContentsMargins(0, 0, 0, 0)
|
|
77
75
|
brush_layout.setSpacing(2)
|
|
78
|
-
|
|
79
76
|
brush_label = QLabel("Brush Size")
|
|
80
77
|
brush_label.setAlignment(Qt.AlignCenter)
|
|
81
78
|
brush_layout.addWidget(brush_label)
|
|
82
|
-
|
|
83
79
|
self.brush_size_slider = QSlider(Qt.Horizontal)
|
|
84
80
|
self.brush_size_slider.setRange(0, gui_constants.BRUSH_SIZE_SLIDER_MAX)
|
|
85
81
|
|
|
@@ -94,7 +90,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
94
90
|
|
|
95
91
|
self.brush_size_slider.setValue(brush_size_to_slider(self.brush.size))
|
|
96
92
|
brush_layout.addWidget(self.brush_size_slider)
|
|
97
|
-
|
|
98
93
|
hardness_label = QLabel("Brush Hardness")
|
|
99
94
|
hardness_label.setAlignment(Qt.AlignCenter)
|
|
100
95
|
brush_layout.addWidget(hardness_label)
|
|
@@ -102,7 +97,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
102
97
|
self.hardness_slider.setRange(0, 100)
|
|
103
98
|
self.hardness_slider.setValue(self.brush.hardness)
|
|
104
99
|
brush_layout.addWidget(self.hardness_slider)
|
|
105
|
-
|
|
106
100
|
opacity_label = QLabel("Brush Opacity")
|
|
107
101
|
opacity_label.setAlignment(Qt.AlignCenter)
|
|
108
102
|
brush_layout.addWidget(opacity_label)
|
|
@@ -110,7 +104,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
110
104
|
self.opacity_slider.setRange(0, 100)
|
|
111
105
|
self.opacity_slider.setValue(self.brush.opacity)
|
|
112
106
|
brush_layout.addWidget(self.opacity_slider)
|
|
113
|
-
|
|
114
107
|
flow_label = QLabel("Brush Flow")
|
|
115
108
|
flow_label.setAlignment(Qt.AlignCenter)
|
|
116
109
|
brush_layout.addWidget(flow_label)
|
|
@@ -118,7 +111,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
118
111
|
self.flow_slider.setRange(1, 100)
|
|
119
112
|
self.flow_slider.setValue(self.brush.flow)
|
|
120
113
|
brush_layout.addWidget(self.flow_slider)
|
|
121
|
-
|
|
122
114
|
side_layout.addWidget(brush_panel)
|
|
123
115
|
self.brush_preview_widget = QLabel()
|
|
124
116
|
self.brush_preview_widget.setContentsMargins(0, 0, 0, 0)
|
|
@@ -135,7 +127,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
135
127
|
self.brush_preview_widget.setFixedHeight(100)
|
|
136
128
|
brush_layout.addWidget(self.brush_preview_widget)
|
|
137
129
|
side_layout.addWidget(brush_panel)
|
|
138
|
-
|
|
139
130
|
master_label = QLabel("Master")
|
|
140
131
|
master_label.setStyleSheet("""
|
|
141
132
|
QLabel {
|
|
@@ -332,30 +323,20 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
332
323
|
overlaid_mode = self.view_mode_actions['overlaid']
|
|
333
324
|
overlaid_mode.setShortcut("Ctrl+1")
|
|
334
325
|
overlaid_mode.setCheckable(True)
|
|
335
|
-
overlaid_mode.triggered.connect(lambda: set_strategy('overlaid'))
|
|
326
|
+
overlaid_mode.triggered.connect(lambda: self.set_strategy('overlaid'))
|
|
336
327
|
view_strategy_menu.addAction(overlaid_mode)
|
|
337
328
|
side_by_side_mode = self.view_mode_actions['sidebyside']
|
|
338
329
|
side_by_side_mode.setShortcut("Ctrl+2")
|
|
339
330
|
side_by_side_mode.setCheckable(True)
|
|
340
|
-
side_by_side_mode.triggered.connect(lambda: set_strategy('sidebyside'))
|
|
331
|
+
side_by_side_mode.triggered.connect(lambda: self.set_strategy('sidebyside'))
|
|
341
332
|
view_strategy_menu.addAction(side_by_side_mode)
|
|
342
333
|
side_by_side_mode = self.view_mode_actions['topbottom']
|
|
343
334
|
side_by_side_mode.setShortcut("Ctrl+3")
|
|
344
335
|
side_by_side_mode.setCheckable(True)
|
|
345
|
-
side_by_side_mode.triggered.connect(lambda: set_strategy('topbottom'))
|
|
336
|
+
side_by_side_mode.triggered.connect(lambda: self.set_strategy('topbottom'))
|
|
346
337
|
view_strategy_menu.addAction(side_by_side_mode)
|
|
347
338
|
view_menu.addMenu(view_strategy_menu)
|
|
348
339
|
|
|
349
|
-
def set_strategy(strategy):
|
|
350
|
-
self.image_viewer.set_strategy(strategy)
|
|
351
|
-
enable_shortcuts = strategy == 'overlaid'
|
|
352
|
-
self.view_master_action.setEnabled(enable_shortcuts)
|
|
353
|
-
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
354
|
-
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
355
|
-
for label, mode in self.view_mode_actions.items():
|
|
356
|
-
mode.setEnabled(label != strategy)
|
|
357
|
-
mode.setChecked(label == strategy)
|
|
358
|
-
|
|
359
340
|
cursor_menu = view_menu.addMenu("Cursor Style")
|
|
360
341
|
|
|
361
342
|
self.cursor_style_actions = {
|
|
@@ -432,7 +413,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
432
413
|
view_menu.addAction(self.toggle_view_master_individual_action)
|
|
433
414
|
view_menu.addSeparator()
|
|
434
415
|
|
|
435
|
-
set_strategy('overlaid')
|
|
416
|
+
self.set_strategy('overlaid')
|
|
436
417
|
|
|
437
418
|
sort_asc_action = QAction("Sort Layers A-Z", self)
|
|
438
419
|
sort_asc_action.triggered.connect(lambda: self.sort_layers('asc'))
|
|
@@ -463,6 +444,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
463
444
|
help_menu.setObjectName("Help")
|
|
464
445
|
shortcuts_help_action = QAction("Shortcuts and Mouse", self)
|
|
465
446
|
|
|
447
|
+
self.statusBar().showMessage("Shine Stacker ready.", 2000)
|
|
448
|
+
|
|
466
449
|
def shortcuts_help():
|
|
467
450
|
self.shortcuts_help_dialog = ShortcutsHelp(self)
|
|
468
451
|
self.shortcuts_help_dialog.exec()
|
|
@@ -476,6 +459,16 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
476
459
|
next_layer.activated.connect(self.next_layer)
|
|
477
460
|
self.installEventFilter(self)
|
|
478
461
|
|
|
462
|
+
def set_strategy(self, strategy):
|
|
463
|
+
self.image_viewer.set_strategy(strategy)
|
|
464
|
+
enable_shortcuts = strategy == 'overlaid'
|
|
465
|
+
self.view_master_action.setEnabled(enable_shortcuts)
|
|
466
|
+
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
467
|
+
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
468
|
+
for label, mode in self.view_mode_actions.items():
|
|
469
|
+
mode.setEnabled(label != strategy)
|
|
470
|
+
mode.setChecked(label == strategy)
|
|
471
|
+
|
|
479
472
|
def update_title(self):
|
|
480
473
|
title = constants.APP_TITLE
|
|
481
474
|
if self.io_gui_handler is not None:
|
|
@@ -87,9 +87,6 @@ class ImageViewer(QWidget):
|
|
|
87
87
|
def set_allow_cursor_preview(self, state):
|
|
88
88
|
self.strategy.set_allow_cursor_preview(state)
|
|
89
89
|
|
|
90
|
-
def setup_brush_cursor(self):
|
|
91
|
-
self.strategy.setup_brush_cursor()
|
|
92
|
-
|
|
93
90
|
def zoom_in(self):
|
|
94
91
|
self.strategy.zoom_in()
|
|
95
92
|
|
|
@@ -109,7 +106,8 @@ class ImageViewer(QWidget):
|
|
|
109
106
|
return self.strategy.get_cursor_style()
|
|
110
107
|
|
|
111
108
|
def set_cursor_style(self, style):
|
|
112
|
-
self.
|
|
109
|
+
for st in self._strategies.values():
|
|
110
|
+
st.set_cursor_style(style)
|
|
113
111
|
|
|
114
112
|
def position_on_image(self, pos):
|
|
115
113
|
return self.strategy.position_on_image(pos)
|
|
@@ -56,8 +56,6 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
56
56
|
self.set_layer_labels(labels)
|
|
57
57
|
self.set_master_layer(master_layer)
|
|
58
58
|
self.image_viewer.set_master_image_np(master_layer)
|
|
59
|
-
self.image_viewer.show_master()
|
|
60
|
-
self.image_viewer.update_master_display()
|
|
61
59
|
self.undo_manager.reset()
|
|
62
60
|
self.blank_layer = np.zeros(master_layer.shape[:2])
|
|
63
61
|
self.finish_loading_setup(f"Loaded: {self.current_file_path()}")
|
|
@@ -165,7 +163,6 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
165
163
|
self.display_manager.update_thumbnails()
|
|
166
164
|
self.mark_as_modified_requested.emit(True)
|
|
167
165
|
self.change_layer_requested.emit(0)
|
|
168
|
-
self.image_viewer.setup_brush_cursor()
|
|
169
166
|
self.status_message_requested.emit(message)
|
|
170
167
|
self.update_title_requested.emit()
|
|
171
168
|
self.add_recent_file_requested.emit(self.current_file_path_master)
|
|
@@ -114,27 +114,31 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
114
114
|
return self.handle_gesture_event(event)
|
|
115
115
|
return super().event(event)
|
|
116
116
|
|
|
117
|
-
def
|
|
118
|
-
self.status.set_master_image(qimage)
|
|
119
|
-
pixmap = self.status.pixmap_master
|
|
117
|
+
def setup_scene_image(self, pixmap, pixmap_item):
|
|
120
118
|
self.setSceneRect(QRectF(pixmap.rect()))
|
|
121
119
|
img_width, img_height = pixmap.width(), pixmap.height()
|
|
122
120
|
self.set_max_min_scales(img_width, img_height)
|
|
123
|
-
self.
|
|
121
|
+
view_rect = self.viewport().rect()
|
|
122
|
+
scale_x = view_rect.width() / img_width
|
|
123
|
+
scale_y = view_rect.height() / img_height
|
|
124
|
+
scale_factor = min(scale_x, scale_y)
|
|
125
|
+
scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
|
|
126
|
+
self.set_zoom_factor(scale_factor)
|
|
124
127
|
self.resetTransform()
|
|
125
|
-
self.
|
|
126
|
-
self.
|
|
127
|
-
self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
|
|
128
|
-
self.scale(self.zoom_factor(), self.zoom_factor())
|
|
129
|
-
self.centerOn(self.pixmap_item_master)
|
|
128
|
+
self.scale(scale_factor, scale_factor)
|
|
129
|
+
self.centerOn(pixmap_item)
|
|
130
130
|
self.center_image(self)
|
|
131
131
|
self.update_cursor_pen_width()
|
|
132
132
|
|
|
133
|
+
def set_master_image(self, qimage):
|
|
134
|
+
self.status.set_master_image(qimage)
|
|
135
|
+
self.setup_scene_image(self.status.pixmap_master, self.pixmap_item_master)
|
|
136
|
+
self.update_master_display()
|
|
137
|
+
|
|
133
138
|
def set_current_image(self, qimage):
|
|
134
139
|
self.status.set_current_image(qimage)
|
|
135
140
|
if self.empty():
|
|
136
|
-
self.
|
|
137
|
-
self.update_cursor_pen_width()
|
|
141
|
+
self.setup_scene_image(self.status.pixmap_current, self.pixmap_item_current)
|
|
138
142
|
|
|
139
143
|
def setup_brush_cursor(self):
|
|
140
144
|
super().setup_brush_cursor()
|
|
@@ -144,11 +148,19 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
144
148
|
self.pixmap_item_master.setVisible(True)
|
|
145
149
|
self.pixmap_item_current.setVisible(False)
|
|
146
150
|
self.brush_preview.show()
|
|
151
|
+
if self.brush_cursor:
|
|
152
|
+
self.scene.removeItem(self.brush_cursor)
|
|
153
|
+
self.brush_cursor = self.create_circle(self.scene)
|
|
154
|
+
self.update_brush_cursor()
|
|
147
155
|
|
|
148
156
|
def show_current(self):
|
|
149
157
|
self.pixmap_item_master.setVisible(False)
|
|
150
158
|
self.pixmap_item_current.setVisible(True)
|
|
151
159
|
self.brush_preview.hide()
|
|
160
|
+
if self.brush_cursor:
|
|
161
|
+
self.scene.removeItem(self.brush_cursor)
|
|
162
|
+
self.brush_cursor = self.create_alt_circle(self.scene)
|
|
163
|
+
self.update_brush_cursor()
|
|
152
164
|
|
|
153
165
|
def arrange_images(self):
|
|
154
166
|
if self.empty():
|