shinestacker 1.4.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.4.0 → shinestacker-1.5.1}/CHANGELOG.md +35 -7
- {shinestacker-1.4.0/src/shinestacker.egg-info → shinestacker-1.5.1}/PKG-INFO +7 -7
- {shinestacker-1.4.0 → shinestacker-1.5.1}/README.md +6 -6
- shinestacker-1.5.1/src/shinestacker/_version.py +1 -0
- shinestacker-1.5.1/src/shinestacker/app/args.py +23 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/main.py +18 -9
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/project.py +2 -3
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/retouch.py +8 -4
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/config/gui_constants.py +7 -2
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/new_project.py +17 -14
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/base_filter.py +8 -10
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_preview.py +28 -15
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/display_manager.py +42 -42
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_editor_ui.py +67 -53
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_view_status.py +4 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_viewer.py +8 -4
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/io_gui_handler.py +0 -3
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/layer_collection.py +3 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/overlaid_view.py +99 -84
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/shortcuts_help.py +35 -31
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/sidebyside_view.py +141 -178
- shinestacker-1.5.1/src/shinestacker/retouch/transformation_manager.py +43 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/undo_manager.py +22 -3
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/view_strategy.py +234 -64
- {shinestacker-1.4.0 → shinestacker-1.5.1/src/shinestacker.egg-info}/PKG-INFO +7 -7
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/SOURCES.txt +2 -0
- shinestacker-1.4.0/src/shinestacker/_version.py +0 -1
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.coveragerc +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.flake8 +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.github/workflows/release.yml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.gitignore +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.pylintrc +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/.readthedocs.yaml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/LICENSE +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/MANIFEST.in +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/alignment.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/api.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/balancing.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/conf.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/focus_stacking.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/gui.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/index.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/job.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/main.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/multilayer.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/noise.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/requirements.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/vignetting.md +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/coffee.gif +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/flies.gif +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/flies_stack.jpg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/flow-diagram.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/gui-finder.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/gui-project-new.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/gui-project-run.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/img/gui-retouch.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/index.html +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/pyproject.toml +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/requirements.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/scripts/build_release.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/setup.cfg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align_parallel.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/utils.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/action_config_dialog.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_run.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/main_window.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/menu_manager.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_controller.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/recent_file_manager.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/vignetting_filter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,39 @@
|
|
|
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
|
+
|
|
21
|
+
## [v1.5.0] - 2025-09-16
|
|
22
|
+
**GUI improvements and fixes**
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- implemented image rotation
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- fixed zoom in wheel events for side-by-side views
|
|
29
|
+
- restored standard cursor in empty retouch views
|
|
30
|
+
- lower/upper case GUI labels
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- code refactoring and cleanup
|
|
34
|
+
- dotted cursor in secondary two-image view
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
5
38
|
## [v1.4.0] - 2025-09-14
|
|
6
39
|
**GUI improvements**
|
|
7
40
|
|
|
@@ -11,7 +44,7 @@ This page reports the main releases only and the main changes therein.
|
|
|
11
44
|
- expert options can be shown with a checkbox in each dialog
|
|
12
45
|
- optional summary plots for alignment transformation parameters
|
|
13
46
|
|
|
14
|
-
|
|
47
|
+
### Fixed
|
|
15
48
|
- fixed bug in plot generation
|
|
16
49
|
- fixes warning due to missing glyph in PDF generation on macOS
|
|
17
50
|
- safer parallel plot generation using a thread locks
|
|
@@ -19,15 +52,11 @@ This page reports the main releases only and the main changes therein.
|
|
|
19
52
|
### Changed
|
|
20
53
|
- code refactoring in various areas
|
|
21
54
|
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### Changed
|
|
25
|
-
- code cleanup
|
|
26
55
|
|
|
27
56
|
## [v1.3.1] - 2025-09-08
|
|
28
57
|
**Fixes and optimizations**
|
|
29
58
|
|
|
30
|
-
|
|
59
|
+
### Fixed
|
|
31
60
|
- fixed input folder widget in job configuration
|
|
32
61
|
- better management of patological alignments
|
|
33
62
|
- restored alignment match plots
|
|
@@ -356,5 +385,4 @@ This release is equivalent to v0.3.2, but resolves a problem for PyPI distributi
|
|
|
356
385
|
- several stability improvements
|
|
357
386
|
- several bug fixes
|
|
358
387
|
|
|
359
|
-
|
|
360
388
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shinestacker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
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.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):
|
|
@@ -102,7 +103,7 @@ class MainApp(QMainWindow):
|
|
|
102
103
|
file_menu = action.menu()
|
|
103
104
|
break
|
|
104
105
|
if file_menu is not None:
|
|
105
|
-
import_action = QAction("Import
|
|
106
|
+
import_action = QAction("Import from Current Project", self)
|
|
106
107
|
import_action.triggered.connect(self.import_from_project)
|
|
107
108
|
file_menu.addAction(import_action)
|
|
108
109
|
else:
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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:
|
|
@@ -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()
|
|
@@ -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,7 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
|
|
|
111
125
|
painter.drawPixmap(0, 0, mask)
|
|
112
126
|
painter.end()
|
|
113
127
|
self.setPixmap(final_pixmap)
|
|
114
|
-
|
|
115
|
-
self.setPos(x_start, y_start)
|
|
128
|
+
self.setPos(visible_x, visible_y)
|
|
116
129
|
self.show()
|
|
117
130
|
except Exception:
|
|
118
131
|
traceback.print_exc()
|
|
@@ -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'
|