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.

Files changed (151) hide show
  1. {shinestacker-1.4.0 → shinestacker-1.5.1}/CHANGELOG.md +35 -7
  2. {shinestacker-1.4.0/src/shinestacker.egg-info → shinestacker-1.5.1}/PKG-INFO +7 -7
  3. {shinestacker-1.4.0 → shinestacker-1.5.1}/README.md +6 -6
  4. shinestacker-1.5.1/src/shinestacker/_version.py +1 -0
  5. shinestacker-1.5.1/src/shinestacker/app/args.py +23 -0
  6. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/main.py +18 -9
  7. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/project.py +2 -3
  8. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/retouch.py +8 -4
  9. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/config/gui_constants.py +7 -2
  10. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/new_project.py +17 -14
  11. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/base_filter.py +8 -10
  12. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_preview.py +28 -15
  13. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/display_manager.py +42 -42
  14. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_editor_ui.py +67 -53
  15. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_view_status.py +4 -0
  16. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/image_viewer.py +8 -4
  17. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/io_gui_handler.py +0 -3
  18. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/layer_collection.py +3 -0
  19. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/overlaid_view.py +99 -84
  20. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/shortcuts_help.py +35 -31
  21. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/sidebyside_view.py +141 -178
  22. shinestacker-1.5.1/src/shinestacker/retouch/transformation_manager.py +43 -0
  23. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/undo_manager.py +22 -3
  24. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/view_strategy.py +234 -64
  25. {shinestacker-1.4.0 → shinestacker-1.5.1/src/shinestacker.egg-info}/PKG-INFO +7 -7
  26. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/SOURCES.txt +2 -0
  27. shinestacker-1.4.0/src/shinestacker/_version.py +0 -1
  28. {shinestacker-1.4.0 → shinestacker-1.5.1}/.coveragerc +0 -0
  29. {shinestacker-1.4.0 → shinestacker-1.5.1}/.flake8 +0 -0
  30. {shinestacker-1.4.0 → shinestacker-1.5.1}/.github/workflows/ci-multiplatform.yml +0 -0
  31. {shinestacker-1.4.0 → shinestacker-1.5.1}/.github/workflows/pylint.yml +0 -0
  32. {shinestacker-1.4.0 → shinestacker-1.5.1}/.github/workflows/pypi-publish.yml +0 -0
  33. {shinestacker-1.4.0 → shinestacker-1.5.1}/.github/workflows/release.yml +0 -0
  34. {shinestacker-1.4.0 → shinestacker-1.5.1}/.gitignore +0 -0
  35. {shinestacker-1.4.0 → shinestacker-1.5.1}/.pylintrc +0 -0
  36. {shinestacker-1.4.0 → shinestacker-1.5.1}/.readthedocs.yaml +0 -0
  37. {shinestacker-1.4.0 → shinestacker-1.5.1}/LICENSE +0 -0
  38. {shinestacker-1.4.0 → shinestacker-1.5.1}/MANIFEST.in +0 -0
  39. {shinestacker-1.4.0 → shinestacker-1.5.1}/THIRD_PARTY_LICENSES.txt +0 -0
  40. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/alignment.md +0 -0
  41. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/api.md +0 -0
  42. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/balancing.md +0 -0
  43. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/conf.py +0 -0
  44. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/focus_stacking.md +0 -0
  45. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/gui.md +0 -0
  46. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/index.md +0 -0
  47. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/job.md +0 -0
  48. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/main.md +0 -0
  49. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/multilayer.md +0 -0
  50. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/noise.md +0 -0
  51. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/requirements.txt +0 -0
  52. {shinestacker-1.4.0 → shinestacker-1.5.1}/docs/vignetting.md +0 -0
  53. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/coffee.gif +0 -0
  54. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/coffee_stack.jpg +0 -0
  55. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/extreme-vignetting.jpg +0 -0
  56. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/flies.gif +0 -0
  57. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/flies_stack.jpg +0 -0
  58. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/flow-diagram.png +0 -0
  59. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/gui-finder.png +0 -0
  60. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/gui-project-new.png +0 -0
  61. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/gui-project-run.png +0 -0
  62. {shinestacker-1.4.0 → shinestacker-1.5.1}/img/gui-retouch.png +0 -0
  63. {shinestacker-1.4.0 → shinestacker-1.5.1}/index.html +0 -0
  64. {shinestacker-1.4.0 → shinestacker-1.5.1}/pyproject.toml +0 -0
  65. {shinestacker-1.4.0 → shinestacker-1.5.1}/requirements.txt +0 -0
  66. {shinestacker-1.4.0 → shinestacker-1.5.1}/scripts/build_release.py +0 -0
  67. {shinestacker-1.4.0 → shinestacker-1.5.1}/scripts/git-rev-list.sh +0 -0
  68. {shinestacker-1.4.0 → shinestacker-1.5.1}/scripts/validate-tomli.py +0 -0
  69. {shinestacker-1.4.0 → shinestacker-1.5.1}/setup.cfg +0 -0
  70. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/__init__.py +0 -0
  71. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/__init__.py +0 -0
  72. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align.py +0 -0
  73. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align_auto.py +0 -0
  74. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/align_parallel.py +0 -0
  75. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/balance.py +0 -0
  76. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
  77. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/denoise.py +0 -0
  78. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/depth_map.py +0 -0
  79. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/exif.py +0 -0
  80. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/multilayer.py +0 -0
  81. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/noise_detection.py +0 -0
  82. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid.py +0 -0
  83. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
  84. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
  85. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/sharpen.py +0 -0
  86. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/stack.py +0 -0
  87. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/stack_framework.py +0 -0
  88. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/utils.py +0 -0
  89. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/vignetting.py +0 -0
  90. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/algorithms/white_balance.py +0 -0
  91. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/__init__.py +0 -0
  92. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/about_dialog.py +0 -0
  93. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/gui_utils.py +0 -0
  94. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/help_menu.py +0 -0
  95. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/app/open_frames.py +0 -0
  96. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/config/__init__.py +0 -0
  97. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/config/config.py +0 -0
  98. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/config/constants.py +0 -0
  99. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/__init__.py +0 -0
  100. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/colors.py +0 -0
  101. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/core_utils.py +0 -0
  102. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/exceptions.py +0 -0
  103. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/framework.py +0 -0
  104. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/core/logging.py +0 -0
  105. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/__init__.py +0 -0
  106. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/action_config.py +0 -0
  107. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/action_config_dialog.py +0 -0
  108. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/base_form_dialog.py +0 -0
  109. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/colors.py +0 -0
  110. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/flow_layout.py +0 -0
  111. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/folder_file_selection.py +0 -0
  112. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_images.py +0 -0
  113. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_logging.py +0 -0
  114. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/gui_run.py +0 -0
  115. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  116. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  117. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  118. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  119. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
  120. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  121. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  122. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  123. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  124. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/main_window.py +0 -0
  125. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/menu_manager.py +0 -0
  126. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_controller.py +0 -0
  127. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_converter.py +0 -0
  128. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_editor.py +0 -0
  129. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/project_model.py +0 -0
  130. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/recent_file_manager.py +0 -0
  131. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/select_path_widget.py +0 -0
  132. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/sys_mon.py +0 -0
  133. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/tab_widget.py +0 -0
  134. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/gui/time_progress_bar.py +0 -0
  135. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/__init__.py +0 -0
  136. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush.py +0 -0
  137. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_gradient.py +0 -0
  138. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/brush_tool.py +0 -0
  139. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/denoise_filter.py +0 -0
  140. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/exif_data.py +0 -0
  141. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/file_loader.py +0 -0
  142. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/filter_manager.py +0 -0
  143. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/icon_container.py +0 -0
  144. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/io_manager.py +0 -0
  145. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
  146. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/vignetting_filter.py +0 -0
  147. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker/retouch/white_balance_filter.py +0 -0
  148. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  149. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/entry_points.txt +0 -0
  150. {shinestacker-1.4.0 → shinestacker-1.5.1}/src/shinestacker.egg-info/requires.txt +0 -0
  151. {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
- ## Fixed
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
- ## Fixed
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.4.0
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
- # Resources
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
- # Note for macOS users
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
- # Credits
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
- # Resources
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
- # License
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
- # Attribution request
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
- # Resources
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
- # Note for macOS users
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
- # Credits
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
- # Resources
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
- # License
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
- # Attribution request
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 From Current Project", self)
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.add_argument('-p', '--path', nargs='?', help='''
215
- import frames from one or more directories.
216
- Multiple directories can be specified separated by ';'.
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
- parser.add_argument('-r', '--retouch', action='store_true', help='''
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.add_argument('-x', '--expert', action='store_true', help='''
222
- expert options are visible by default.
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.add_argument('-x', '--expert', action='store_true', help='''
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.add_argument('-p', '--path', nargs='?', help='''
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, 150),
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': 50,
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 layers:", self.align_frames)
111
+ "Align frames:", self.align_frames)
112
112
  step2_layout.addRow(
113
113
  # f" {constants.ACTION_ICONS[constants.ACTION_BALANCEFRAMES]} "
114
- "Balance layers:", self.balance_frames)
114
+ "Balance frames:", self.balance_frames)
115
115
  step2_layout.addRow(
116
116
  # f" {constants.ACTION_ICONS[constants.ACTION_FOCUSSTACKBUNCH]} "
117
- "Bunch stack:", self.bunch_stack)
118
- step2_layout.addRow("Bunch frames:", self.bunch_frames)
119
- step2_layout.addRow("Bunch overlap:", self.bunch_overlap)
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
- "Save multi layer TIFF:", self.multi_layer)
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 confirm and prepare the job."))
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("You may consider to split the processing "
297
- " using a bunch stack to reduce memory usage.\n\n"
298
- '✅ Check the option "Bunch stack".\n\n'
299
- "➡️ Check expert options for the stacking algorithm."
300
- 'Go to "View" > "Expert Options".'
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
- if hasattr(self.editor, "undo_manager"):
147
- try:
148
- self.editor.undo_manager.extend_undo_area(0, 0, w, h)
149
- self.editor.undo_manager.save_undo_state(
150
- self.editor.master_layer_copy(),
151
- self.name
152
- )
153
- except Exception:
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
- x = int(scene_pos.x() - radius + 0.5)
76
- y = int(scene_pos.y() - radius)
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
- layer_area = self.get_layer_area(self.current_layer(), x, y, w, h)
82
- master_area = self.get_layer_area(self.master_layer(), x, y, w, h)
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) if x < 0 else 0
90
- mask_y_start = max(0, -y) if y < 0 else 0
91
- mask_x_end = size - (max(0, (x + w) - width)) if (x + w) > width else size
92
- mask_y_end = size - (max(0, (y + h) - height)) if (y + h) > height else size
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(w, h)
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
- painter.drawEllipse(0, 0, w, h)
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(w, h)
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
- x_start, y_start = max(0, x), max(0, y)
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.temp_view_individual = False
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.temp_view_individual = False
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
- self.cursor_preview_state_changed.emit(False)
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 not self.temp_view_individual and self.view_mode == 'master':
184
- self.temp_view_individual = True
185
- self.image_viewer.update_brush_cursor()
186
- self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
187
- self.highlight_thumbnail(self.current_layer_idx())
188
- self.image_viewer.show_current()
189
- self.refresh_current_view()
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.temp_view_individual:
194
- self.temp_view_individual = False
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
- self.cursor_preview_state_changed.emit(True)
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' and not self.temp_view_individual
204
+ return self.view_mode == 'master'