shinestacker 1.4.0__tar.gz → 1.5.0__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.4.0 → shinestacker-1.5.0}/CHANGELOG.md +19 -6
- {shinestacker-1.4.0/src/shinestacker.egg-info → shinestacker-1.5.0}/PKG-INFO +7 -7
- {shinestacker-1.4.0 → shinestacker-1.5.0}/README.md +6 -6
- shinestacker-1.5.0/src/shinestacker/_version.py +1 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/app/main.py +1 -1
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/config/gui_constants.py +5 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/base_filter.py +8 -10
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/brush_preview.py +0 -1
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/display_manager.py +42 -42
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/image_editor_ui.py +52 -31
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/image_view_status.py +4 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/image_viewer.py +6 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/layer_collection.py +3 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/overlaid_view.py +87 -84
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/sidebyside_view.py +113 -115
- shinestacker-1.5.0/src/shinestacker/retouch/transformation_manager.py +43 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/undo_manager.py +22 -3
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/view_strategy.py +135 -44
- {shinestacker-1.4.0 → shinestacker-1.5.0/src/shinestacker.egg-info}/PKG-INFO +7 -7
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker.egg-info/SOURCES.txt +1 -0
- shinestacker-1.4.0/src/shinestacker/_version.py +0 -1
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.coveragerc +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.flake8 +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.github/workflows/release.yml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.gitignore +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.pylintrc +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/.readthedocs.yaml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/LICENSE +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/MANIFEST.in +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/alignment.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/api.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/balancing.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/conf.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/focus_stacking.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/gui.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/index.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/job.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/main.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/multilayer.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/noise.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/requirements.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/docs/vignetting.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/coffee.gif +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/flies.gif +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/flies_stack.jpg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/flow-diagram.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/gui-finder.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/gui-project-new.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/gui-project-run.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/img/gui-retouch.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/index.html +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/pyproject.toml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/requirements.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/scripts/build_release.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/setup.cfg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/align_parallel.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/utils.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/app/project.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/app/retouch.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/action_config_dialog.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/gui_run.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/main_window.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/menu_manager.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/new_project.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/project_controller.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/recent_file_manager.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/io_gui_handler.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/shortcuts_help.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/vignetting_filter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.0}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
+
## [v1.5.0] - 2025-09-16
|
|
6
|
+
**GUI updates and fixes**
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- implemented image rotation
|
|
10
|
+
- dotted cursor in secondary two-image view
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- fixed zoom in wheel events for side-by-side view
|
|
14
|
+
- restored standard cursor in empty retouch views
|
|
15
|
+
- lower/upper case GUI labels
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- code refactoring and cleanup
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
5
22
|
## [v1.4.0] - 2025-09-14
|
|
6
23
|
**GUI improvements**
|
|
7
24
|
|
|
@@ -11,7 +28,7 @@ This page reports the main releases only and the main changes therein.
|
|
|
11
28
|
- expert options can be shown with a checkbox in each dialog
|
|
12
29
|
- optional summary plots for alignment transformation parameters
|
|
13
30
|
|
|
14
|
-
|
|
31
|
+
### Fixed
|
|
15
32
|
- fixed bug in plot generation
|
|
16
33
|
- fixes warning due to missing glyph in PDF generation on macOS
|
|
17
34
|
- safer parallel plot generation using a thread locks
|
|
@@ -19,15 +36,11 @@ This page reports the main releases only and the main changes therein.
|
|
|
19
36
|
### Changed
|
|
20
37
|
- code refactoring in various areas
|
|
21
38
|
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### Changed
|
|
25
|
-
- code cleanup
|
|
26
39
|
|
|
27
40
|
## [v1.3.1] - 2025-09-08
|
|
28
41
|
**Fixes and optimizations**
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
### Fixed
|
|
31
44
|
- fixed input folder widget in job configuration
|
|
32
45
|
- better management of patological alignments
|
|
33
46
|
- restored alignment match plots
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shinestacker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: ShineStacker
|
|
5
5
|
Author-email: Luca Lista <luka.lista@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0
|
|
@@ -70,11 +70,11 @@ The GUI has two main working areas:
|
|
|
70
70
|
|
|
71
71
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/gui-retouch.png' width="600" referrerpolicy="no-referrer">
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
## Resources
|
|
74
74
|
|
|
75
75
|
🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
## Note for macOS users
|
|
78
78
|
|
|
79
79
|
**The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
|
|
80
80
|
|
|
@@ -93,17 +93,17 @@ xattr -cr ~/Downloads/shinestacker/shinestacker.app
|
|
|
93
93
|
|
|
94
94
|
macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
## Credits
|
|
97
97
|
|
|
98
98
|
The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
## Resources
|
|
101
101
|
|
|
102
102
|
* [Pyramid Methods in Image Processing](https://www.researchgate.net/publication/246727904_Pyramid_Methods_in_Image_Processing), E. H. Adelson, C. H. Anderson, J. R. Bergen, P. J. Burt, J. M. Ogden, RCA Engineer, 29-6, Nov/Dec 1984
|
|
103
103
|
Pyramid methods in image processing
|
|
104
104
|
* [A Multi-focus Image Fusion Method Based on Laplacian Pyramid](http://www.jcomputers.us/vol6/jcp0612-07.pdf), Wencheng Wang, Faliang Chang, Journal of Computers 6 (12), 2559, December 2011
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
## License
|
|
107
107
|
|
|
108
108
|
<img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
|
|
109
109
|
|
|
@@ -112,7 +112,7 @@ Pyramid methods in image processing
|
|
|
112
112
|
|
|
113
113
|
- **Logo**: The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista). Copyright © Alessandro Lista. All rights reserved. The logo is not covered by the LGPL-3.0 license of this project.
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
## Attribution request
|
|
116
116
|
📸 If you publish images created with Shine Stacker, please consider adding a note such as:
|
|
117
117
|
|
|
118
118
|
*Created with Shine Stacker – https://github.com/lucalista/shinestacker*
|
|
@@ -38,11 +38,11 @@ The GUI has two main working areas:
|
|
|
38
38
|
|
|
39
39
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/gui-retouch.png' width="600" referrerpolicy="no-referrer">
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
## Resources
|
|
42
42
|
|
|
43
43
|
🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
## Note for macOS users
|
|
46
46
|
|
|
47
47
|
**The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
|
|
48
48
|
|
|
@@ -61,17 +61,17 @@ xattr -cr ~/Downloads/shinestacker/shinestacker.app
|
|
|
61
61
|
|
|
62
62
|
macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
## Credits
|
|
65
65
|
|
|
66
66
|
The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
## Resources
|
|
69
69
|
|
|
70
70
|
* [Pyramid Methods in Image Processing](https://www.researchgate.net/publication/246727904_Pyramid_Methods_in_Image_Processing), E. H. Adelson, C. H. Anderson, J. R. Bergen, P. J. Burt, J. M. Ogden, RCA Engineer, 29-6, Nov/Dec 1984
|
|
71
71
|
Pyramid methods in image processing
|
|
72
72
|
* [A Multi-focus Image Fusion Method Based on Laplacian Pyramid](http://www.jcomputers.us/vol6/jcp0612-07.pdf), Wencheng Wang, Faliang Chang, Journal of Computers 6 (12), 2559, December 2011
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
## License
|
|
75
75
|
|
|
76
76
|
<img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
|
|
77
77
|
|
|
@@ -80,7 +80,7 @@ Pyramid methods in image processing
|
|
|
80
80
|
|
|
81
81
|
- **Logo**: The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista). Copyright © Alessandro Lista. All rights reserved. The logo is not covered by the LGPL-3.0 license of this project.
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
## Attribution request
|
|
84
84
|
📸 If you publish images created with Shine Stacker, please consider adding a note such as:
|
|
85
85
|
|
|
86
86
|
*Created with Shine Stacker – https://github.com/lucalista/shinestacker*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.5.0'
|
|
@@ -102,7 +102,7 @@ class MainApp(QMainWindow):
|
|
|
102
102
|
file_menu = action.menu()
|
|
103
103
|
break
|
|
104
104
|
if file_menu is not None:
|
|
105
|
-
import_action = QAction("Import
|
|
105
|
+
import_action = QAction("Import from Current Project", self)
|
|
106
106
|
import_action.triggered.connect(self.import_from_project)
|
|
107
107
|
file_menu.addAction(import_action)
|
|
108
108
|
else:
|
|
@@ -66,6 +66,11 @@ class _GuiConstants:
|
|
|
66
66
|
ZOOM_IN_FACTOR = 1.10
|
|
67
67
|
ZOOM_OUT_FACTOR = 1 / ZOOM_IN_FACTOR
|
|
68
68
|
|
|
69
|
+
ROTATE_LABEL = "Rotate"
|
|
70
|
+
ROTATE_90_CW_LABEL = f"{ROTATE_LABEL} 90° Clockwise"
|
|
71
|
+
ROTATE_90_CCW_LABEL = f"{ROTATE_LABEL} 90° Anticlockwise"
|
|
72
|
+
ROTATE_180_LABEL = f"{ROTATE_LABEL} 180°"
|
|
73
|
+
|
|
69
74
|
def calculate_gamma(self):
|
|
70
75
|
if self.BRUSH_SIZES['mid'] <= self.BRUSH_SIZES['min'] or self.BRUSH_SIZES['max'] <= 0:
|
|
71
76
|
return 1.0
|
|
@@ -34,7 +34,6 @@ class BaseFilter(ABC):
|
|
|
34
34
|
def run_with_preview(self, **kwargs):
|
|
35
35
|
if self.editor.has_no_master_layer():
|
|
36
36
|
return
|
|
37
|
-
|
|
38
37
|
self.editor.copy_master_layer()
|
|
39
38
|
dlg = QDialog(self.editor)
|
|
40
39
|
layout = QVBoxLayout(dlg)
|
|
@@ -143,15 +142,14 @@ class BaseFilter(ABC):
|
|
|
143
142
|
h, w = self.editor.master_layer().shape[:2]
|
|
144
143
|
except Exception:
|
|
145
144
|
h, w = self.editor.master_layer_copy().shape[:2]
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
self.editor.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
pass
|
|
145
|
+
try:
|
|
146
|
+
self.editor.undo_manager.extend_undo_area(0, 0, w, h)
|
|
147
|
+
self.editor.undo_manager.save_undo_state(
|
|
148
|
+
self.editor.master_layer_copy(),
|
|
149
|
+
self.name
|
|
150
|
+
)
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
155
153
|
final_img = self.apply(self.editor.master_layer_copy(), *params)
|
|
156
154
|
self.editor.set_master_layer(final_img)
|
|
157
155
|
self.editor.copy_master_layer()
|
|
@@ -113,7 +113,6 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
|
|
|
113
113
|
self.setPixmap(final_pixmap)
|
|
114
114
|
x_start, y_start = max(0, x), max(0, y)
|
|
115
115
|
self.setPos(x_start, y_start)
|
|
116
|
-
self.show()
|
|
117
116
|
except Exception:
|
|
118
117
|
traceback.print_exc()
|
|
119
118
|
self.hide()
|
|
@@ -25,7 +25,6 @@ class ClickableLabel(QLabel):
|
|
|
25
25
|
|
|
26
26
|
class DisplayManager(QObject, LayerCollectionHandler):
|
|
27
27
|
status_message_requested = Signal(str)
|
|
28
|
-
cursor_preview_state_changed = Signal(bool)
|
|
29
28
|
|
|
30
29
|
def __init__(self, layer_collection, image_viewer, master_thumbnail_label,
|
|
31
30
|
thumbnail_list, parent=None):
|
|
@@ -35,7 +34,6 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
35
34
|
self.master_thumbnail_label = master_thumbnail_label
|
|
36
35
|
self.thumbnail_list = thumbnail_list
|
|
37
36
|
self.view_mode = 'master'
|
|
38
|
-
self.temp_view_individual = False
|
|
39
37
|
self.needs_update = False
|
|
40
38
|
self.update_timer = QTimer()
|
|
41
39
|
self.update_timer.setInterval(gui_constants.PAINT_REFRESH_TIMER)
|
|
@@ -47,21 +45,10 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
47
45
|
self.refresh_master_view()
|
|
48
46
|
self.needs_update = False
|
|
49
47
|
|
|
50
|
-
def refresh_master_view(self):
|
|
51
|
-
if self.has_no_master_layer():
|
|
52
|
-
return
|
|
53
|
-
self.image_viewer.update_master_display()
|
|
54
|
-
self.update_master_thumbnail()
|
|
55
|
-
self.image_viewer.refresh_display()
|
|
56
|
-
|
|
57
|
-
def refresh_current_view(self):
|
|
58
|
-
if self.number_of_layers() == 0:
|
|
59
|
-
return
|
|
60
|
-
self.image_viewer.update_current_display()
|
|
61
|
-
self.image_viewer.refresh_display()
|
|
62
|
-
|
|
63
48
|
def create_thumbnail(self, layer):
|
|
64
49
|
source_layer = (layer // 256).astype(np.uint8) if layer.dtype == np.uint16 else layer
|
|
50
|
+
if not source_layer.flags.c_contiguous:
|
|
51
|
+
source_layer = np.ascontiguousarray(source_layer)
|
|
65
52
|
height, width = source_layer.shape[:2]
|
|
66
53
|
if layer.ndim == 3 and source_layer.shape[-1] == 3:
|
|
67
54
|
qimg = QImage(source_layer.data, width, height, 3 * width, QImage.Format_RGB888)
|
|
@@ -157,48 +144,61 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
157
144
|
self.thumbnail_list.scrollToItem(
|
|
158
145
|
self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
|
|
159
146
|
|
|
147
|
+
def _master_refresh_and_thumb(self):
|
|
148
|
+
self.image_viewer.show_master()
|
|
149
|
+
self.refresh_master_view()
|
|
150
|
+
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
151
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
152
|
+
|
|
153
|
+
def _current_refresh_and_thumb(self):
|
|
154
|
+
self.image_viewer.show_current()
|
|
155
|
+
self.refresh_current_view()
|
|
156
|
+
self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
|
|
157
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
158
|
+
|
|
160
159
|
def set_view_master(self):
|
|
161
160
|
if self.has_no_master_layer():
|
|
162
161
|
return
|
|
163
162
|
self.view_mode = 'master'
|
|
164
|
-
self.
|
|
165
|
-
self.refresh_master_view()
|
|
166
|
-
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
167
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
163
|
+
self._master_refresh_and_thumb()
|
|
168
164
|
self.status_message_requested.emit("View mode: Master")
|
|
169
|
-
self.cursor_preview_state_changed.emit(True)
|
|
170
165
|
|
|
171
166
|
def set_view_individual(self):
|
|
172
167
|
if self.has_no_master_layer():
|
|
173
168
|
return
|
|
174
169
|
self.view_mode = 'individual'
|
|
175
|
-
self.
|
|
176
|
-
self.refresh_current_view()
|
|
177
|
-
self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
|
|
178
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
170
|
+
self._current_refresh_and_thumb()
|
|
179
171
|
self.status_message_requested.emit("View mode: Individual layers")
|
|
180
|
-
|
|
172
|
+
|
|
173
|
+
def refresh_master_view(self):
|
|
174
|
+
if self.has_no_master_layer():
|
|
175
|
+
return
|
|
176
|
+
self.image_viewer.update_master_display()
|
|
177
|
+
self.image_viewer.refresh_display()
|
|
178
|
+
self.update_master_thumbnail()
|
|
179
|
+
|
|
180
|
+
def refresh_current_view(self):
|
|
181
|
+
if self.number_of_layers() == 0:
|
|
182
|
+
return
|
|
183
|
+
self.image_viewer.update_current_display()
|
|
184
|
+
self.image_viewer.refresh_display()
|
|
181
185
|
|
|
182
186
|
def start_temp_view(self):
|
|
183
|
-
if
|
|
184
|
-
self.
|
|
185
|
-
self.
|
|
186
|
-
|
|
187
|
-
self.
|
|
188
|
-
self.image_viewer.
|
|
189
|
-
self.
|
|
190
|
-
self.status_message_requested.emit("Temporary view: Individual layer (hold X)")
|
|
187
|
+
if self.view_mode == 'master':
|
|
188
|
+
self._current_refresh_and_thumb()
|
|
189
|
+
self.status_message_requested.emit("Temporary view: Individual layer")
|
|
190
|
+
else:
|
|
191
|
+
self._master_refresh_and_thumb()
|
|
192
|
+
self.image_viewer.strategy.brush_preview.hide()
|
|
193
|
+
self.status_message_requested.emit("Temporary view: Master")
|
|
191
194
|
|
|
192
195
|
def end_temp_view(self):
|
|
193
|
-
if self.
|
|
194
|
-
self.
|
|
195
|
-
self.image_viewer.update_brush_cursor()
|
|
196
|
-
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
197
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
198
|
-
self.image_viewer.show_master()
|
|
199
|
-
self.refresh_master_view()
|
|
196
|
+
if self.view_mode == 'master':
|
|
197
|
+
self._master_refresh_and_thumb()
|
|
200
198
|
self.status_message_requested.emit("View mode: Master")
|
|
201
|
-
|
|
199
|
+
else:
|
|
200
|
+
self._current_refresh_and_thumb()
|
|
201
|
+
self.status_message_requested.emit("View: Individual layer")
|
|
202
202
|
|
|
203
203
|
def allow_cursor_preview(self):
|
|
204
|
-
return self.view_mode == 'master'
|
|
204
|
+
return self.view_mode == 'master'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904, W0108
|
|
2
2
|
from functools import partial
|
|
3
3
|
import numpy as np
|
|
4
4
|
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QMenu,
|
|
@@ -8,6 +8,7 @@ from PySide6.QtCore import Qt
|
|
|
8
8
|
from PySide6.QtGui import QGuiApplication
|
|
9
9
|
from .. config.constants import constants
|
|
10
10
|
from .. config.gui_constants import gui_constants
|
|
11
|
+
from .. gui.recent_file_manager import RecentFileManager
|
|
11
12
|
from .image_viewer import ImageViewer
|
|
12
13
|
from .shortcuts_help import ShortcutsHelp
|
|
13
14
|
from .brush import Brush
|
|
@@ -22,7 +23,7 @@ from .denoise_filter import DenoiseFilter
|
|
|
22
23
|
from .unsharp_mask_filter import UnsharpMaskFilter
|
|
23
24
|
from .white_balance_filter import WhiteBalanceFilter
|
|
24
25
|
from .vignetting_filter import VignettingFilter
|
|
25
|
-
from
|
|
26
|
+
from .transformation_manager import TransfromationManager
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
@@ -31,16 +32,17 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
31
32
|
LayerCollectionHandler.__init__(self, LayerCollection())
|
|
32
33
|
self._recent_file_manager = RecentFileManager("shinestacker-recent-images-files.txt")
|
|
33
34
|
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
34
|
-
self.undo_manager = UndoManager()
|
|
35
|
-
self.undo_action = None
|
|
36
|
-
self.redo_action = None
|
|
37
|
-
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
38
35
|
self.io_gui_handler = None
|
|
39
36
|
self.display_manager = None
|
|
40
37
|
self.brush = Brush()
|
|
41
38
|
self.brush_tool = BrushTool()
|
|
42
39
|
self.modified = False
|
|
43
40
|
self.mask_layer = None
|
|
41
|
+
self.transformation_manager = TransfromationManager(self)
|
|
42
|
+
self.undo_manager = UndoManager(self.transformation_manager)
|
|
43
|
+
self.undo_action = None
|
|
44
|
+
self.redo_action = None
|
|
45
|
+
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
44
46
|
self.filter_manager = FilterManager(self)
|
|
45
47
|
self.filter_manager.register_filter("Denoise", DenoiseFilter)
|
|
46
48
|
self.filter_manager.register_filter("Unsharp Mask", UnsharpMaskFilter)
|
|
@@ -238,8 +240,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
238
240
|
self.master_thumbnail_label, self.thumbnail_list, parent=self)
|
|
239
241
|
self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
|
|
240
242
|
self.display_manager.status_message_requested.connect(self.show_status_message)
|
|
241
|
-
self.display_manager.cursor_preview_state_changed.connect(
|
|
242
|
-
self.image_viewer.set_allow_cursor_preview)
|
|
243
243
|
self.io_gui_handler.status_message_requested.connect(self.show_status_message)
|
|
244
244
|
self.io_gui_handler.update_title_requested.connect(self.update_title)
|
|
245
245
|
self.io_gui_handler.mark_as_modified_requested.connect(self.mark_as_modified)
|
|
@@ -277,8 +277,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
277
277
|
|
|
278
278
|
file_menu.addAction("&Close", self.close_file, "Ctrl+W")
|
|
279
279
|
file_menu.addSeparator()
|
|
280
|
-
file_menu.addAction("&Import
|
|
281
|
-
file_menu.addAction("Import &EXIF
|
|
280
|
+
file_menu.addAction("&Import Frames", self.io_gui_handler.import_frames)
|
|
281
|
+
file_menu.addAction("Import &EXIF Data", self.io_gui_handler.select_exif_path)
|
|
282
282
|
|
|
283
283
|
edit_menu = menubar.addMenu("&Edit")
|
|
284
284
|
self.undo_action = QAction("Undo", self)
|
|
@@ -293,7 +293,21 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
293
293
|
edit_menu.addAction(self.redo_action)
|
|
294
294
|
edit_menu.addSeparator()
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
transf_menu = QMenu("&Transform")
|
|
297
|
+
rotate_90_cw_action = QAction(gui_constants.ROTATE_90_CW_LABEL, self)
|
|
298
|
+
transf_menu.addAction(rotate_90_cw_action)
|
|
299
|
+
rotate_90_cw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_cw())
|
|
300
|
+
rotate_90_ccw_action = QAction(gui_constants.ROTATE_90_CCW_LABEL, self)
|
|
301
|
+
transf_menu.addAction(rotate_90_ccw_action)
|
|
302
|
+
rotate_90_ccw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_ccw())
|
|
303
|
+
rotate_180_action = QAction(gui_constants.ROTATE_180_LABEL, self)
|
|
304
|
+
rotate_180_action.triggered.connect(lambda: self.transformation_manager.rotate_180())
|
|
305
|
+
transf_menu.addAction(rotate_180_action)
|
|
306
|
+
edit_menu.addMenu(transf_menu)
|
|
307
|
+
|
|
308
|
+
edit_menu.addSeparator()
|
|
309
|
+
|
|
310
|
+
copy_action = QAction("Copy Current Layer to Master", self)
|
|
297
311
|
copy_action.setShortcut("Ctrl+M")
|
|
298
312
|
copy_action.triggered.connect(self.copy_layer_to_master)
|
|
299
313
|
edit_menu.addAction(copy_action)
|
|
@@ -310,22 +324,22 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
310
324
|
|
|
311
325
|
view_strategy_menu = QMenu("View &Mode", view_menu)
|
|
312
326
|
|
|
313
|
-
self.
|
|
327
|
+
self.view_mode_actions = {
|
|
314
328
|
'overlaid': QAction("Overlaid", self),
|
|
315
|
-
'sidebyside': QAction("Side
|
|
329
|
+
'sidebyside': QAction("Side by Side", self),
|
|
316
330
|
'topbottom': QAction("Top-Bottom", self)
|
|
317
331
|
}
|
|
318
|
-
overlaid_mode = self.
|
|
332
|
+
overlaid_mode = self.view_mode_actions['overlaid']
|
|
319
333
|
overlaid_mode.setShortcut("Ctrl+1")
|
|
320
334
|
overlaid_mode.setCheckable(True)
|
|
321
335
|
overlaid_mode.triggered.connect(lambda: set_strategy('overlaid'))
|
|
322
336
|
view_strategy_menu.addAction(overlaid_mode)
|
|
323
|
-
side_by_side_mode = self.
|
|
337
|
+
side_by_side_mode = self.view_mode_actions['sidebyside']
|
|
324
338
|
side_by_side_mode.setShortcut("Ctrl+2")
|
|
325
339
|
side_by_side_mode.setCheckable(True)
|
|
326
340
|
side_by_side_mode.triggered.connect(lambda: set_strategy('sidebyside'))
|
|
327
341
|
view_strategy_menu.addAction(side_by_side_mode)
|
|
328
|
-
side_by_side_mode = self.
|
|
342
|
+
side_by_side_mode = self.view_mode_actions['topbottom']
|
|
329
343
|
side_by_side_mode.setShortcut("Ctrl+3")
|
|
330
344
|
side_by_side_mode.setCheckable(True)
|
|
331
345
|
side_by_side_mode.triggered.connect(lambda: set_strategy('topbottom'))
|
|
@@ -338,31 +352,40 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
338
352
|
self.view_master_action.setEnabled(enable_shortcuts)
|
|
339
353
|
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
340
354
|
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
341
|
-
for label, mode in self.
|
|
355
|
+
for label, mode in self.view_mode_actions.items():
|
|
342
356
|
mode.setEnabled(label != strategy)
|
|
343
357
|
mode.setChecked(label == strategy)
|
|
344
358
|
|
|
345
359
|
cursor_menu = view_menu.addMenu("Cursor Style")
|
|
346
360
|
|
|
347
|
-
|
|
348
|
-
|
|
361
|
+
self.cursor_style_actions = {
|
|
362
|
+
'brush': QAction("Simple Brush", self),
|
|
363
|
+
'preview': QAction("Brush Preview", self),
|
|
364
|
+
'outline': QAction("Outline Only", self)
|
|
365
|
+
}
|
|
366
|
+
brush_action = self.cursor_style_actions['brush']
|
|
349
367
|
brush_action.setCheckable(True)
|
|
350
|
-
brush_action.
|
|
351
|
-
brush_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('brush'))
|
|
368
|
+
brush_action.triggered.connect(lambda: set_cursor_style('brush'))
|
|
352
369
|
cursor_menu.addAction(brush_action)
|
|
353
370
|
|
|
354
|
-
preview_action =
|
|
371
|
+
preview_action = self.cursor_style_actions['preview']
|
|
355
372
|
preview_action.setCheckable(True)
|
|
356
|
-
preview_action.
|
|
357
|
-
preview_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('preview'))
|
|
373
|
+
preview_action.triggered.connect(lambda: set_cursor_style('preview'))
|
|
358
374
|
cursor_menu.addAction(preview_action)
|
|
359
375
|
|
|
360
|
-
outline_action =
|
|
376
|
+
outline_action = self.cursor_style_actions['outline']
|
|
361
377
|
outline_action.setCheckable(True)
|
|
362
|
-
outline_action.
|
|
363
|
-
outline_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('outline'))
|
|
378
|
+
outline_action.triggered.connect(lambda: set_cursor_style('outline'))
|
|
364
379
|
cursor_menu.addAction(outline_action)
|
|
365
380
|
|
|
381
|
+
def set_cursor_style(cursor_style):
|
|
382
|
+
self.image_viewer.set_cursor_style(cursor_style)
|
|
383
|
+
for label, style in self.cursor_style_actions.items():
|
|
384
|
+
style.setEnabled(label != cursor_style)
|
|
385
|
+
style.setChecked(label == cursor_style)
|
|
386
|
+
|
|
387
|
+
set_cursor_style(self.image_viewer.get_cursor_style())
|
|
388
|
+
|
|
366
389
|
cursor_group = QActionGroup(self)
|
|
367
390
|
cursor_group.addAction(preview_action)
|
|
368
391
|
cursor_group.addAction(outline_action)
|
|
@@ -432,13 +455,13 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
432
455
|
white_balance_action = QAction("White Balance", self)
|
|
433
456
|
white_balance_action.triggered.connect(self.white_balance)
|
|
434
457
|
filter_menu.addAction(white_balance_action)
|
|
435
|
-
vignetting_action = QAction("Vignetting
|
|
458
|
+
vignetting_action = QAction("Vignetting Correction", self)
|
|
436
459
|
vignetting_action.triggered.connect(self.vignetting_correction)
|
|
437
460
|
filter_menu.addAction(vignetting_action)
|
|
438
461
|
|
|
439
462
|
help_menu = menubar.addMenu("&Help")
|
|
440
463
|
help_menu.setObjectName("Help")
|
|
441
|
-
shortcuts_help_action = QAction("Shortcuts and
|
|
464
|
+
shortcuts_help_action = QAction("Shortcuts and Mouse", self)
|
|
442
465
|
|
|
443
466
|
def shortcuts_help():
|
|
444
467
|
self.shortcuts_help_dialog = ShortcutsHelp(self)
|
|
@@ -693,13 +716,11 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
693
716
|
|
|
694
717
|
def set_view_master(self):
|
|
695
718
|
self.display_manager.set_view_master()
|
|
696
|
-
self.display_manager.refresh_master_view()
|
|
697
719
|
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
698
720
|
self.highlight_master_thumbnail()
|
|
699
721
|
|
|
700
722
|
def set_view_individual(self):
|
|
701
723
|
self.display_manager.set_view_individual()
|
|
702
|
-
self.display_manager.refresh_current_view()
|
|
703
724
|
self.thumbnail_highlight = gui_constants.THUMB_MASTER_LO_COLOR
|
|
704
725
|
self.highlight_master_thumbnail()
|
|
705
726
|
|
|
@@ -35,8 +35,14 @@ class ImageViewer(QWidget):
|
|
|
35
35
|
self.strategy.show()
|
|
36
36
|
self.strategy.resize(self.size())
|
|
37
37
|
if not self.strategy.empty():
|
|
38
|
+
self.strategy.cleanup_brush_preview()
|
|
38
39
|
self.strategy.update_master_display()
|
|
39
40
|
self.strategy.update_current_display()
|
|
41
|
+
self.strategy.setup_brush_cursor()
|
|
42
|
+
self.strategy.update_brush_cursor()
|
|
43
|
+
self.strategy.show_master()
|
|
44
|
+
self.strategy.setFocus()
|
|
45
|
+
self.strategy.activateWindow()
|
|
40
46
|
|
|
41
47
|
def empty(self):
|
|
42
48
|
return self.strategy.empty()
|
|
@@ -143,6 +143,9 @@ class LayerCollectionHandler:
|
|
|
143
143
|
def has_master_layer(self):
|
|
144
144
|
return self.layer_collection.has_master_layer()
|
|
145
145
|
|
|
146
|
+
def set_layer(self, idx, img):
|
|
147
|
+
self.layer_collection.layer_stack[idx] = img
|
|
148
|
+
|
|
146
149
|
def set_layer_stack(self, stk):
|
|
147
150
|
self.layer_collection.set_layer_stack(stk)
|
|
148
151
|
|