shinestacker 1.5.1__tar.gz → 1.5.3__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 (152) hide show
  1. {shinestacker-1.5.1 → shinestacker-1.5.3}/CHANGELOG.md +26 -2
  2. {shinestacker-1.5.1/src/shinestacker.egg-info → shinestacker-1.5.3}/PKG-INFO +1 -1
  3. shinestacker-1.5.3/src/shinestacker/_version.py +1 -0
  4. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/main.py +1 -1
  5. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/project.py +1 -1
  6. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/retouch.py +1 -1
  7. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/config/gui_constants.py +1 -1
  8. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/base_filter.py +59 -35
  9. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/denoise_filter.py +4 -3
  10. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/display_manager.py +14 -19
  11. shinestacker-1.5.3/src/shinestacker/retouch/filter_manager.py +20 -0
  12. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/image_editor_ui.py +32 -73
  13. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/image_viewer.py +31 -25
  14. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/overlaid_view.py +15 -6
  15. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/sidebyside_view.py +40 -6
  16. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/unsharp_mask_filter.py +5 -4
  17. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/view_strategy.py +147 -75
  18. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/vignetting_filter.py +4 -3
  19. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/white_balance_filter.py +62 -17
  20. {shinestacker-1.5.1 → shinestacker-1.5.3/src/shinestacker.egg-info}/PKG-INFO +1 -1
  21. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/SOURCES.txt +1 -1
  22. shinestacker-1.5.1/src/shinestacker/_version.py +0 -1
  23. shinestacker-1.5.1/src/shinestacker/retouch/filter_manager.py +0 -12
  24. {shinestacker-1.5.1 → shinestacker-1.5.3}/.coveragerc +0 -0
  25. {shinestacker-1.5.1 → shinestacker-1.5.3}/.flake8 +0 -0
  26. {shinestacker-1.5.1 → shinestacker-1.5.3}/.github/workflows/ci-multiplatform.yml +0 -0
  27. {shinestacker-1.5.1 → shinestacker-1.5.3}/.github/workflows/pylint.yml +0 -0
  28. {shinestacker-1.5.1 → shinestacker-1.5.3}/.github/workflows/pypi-publish.yml +0 -0
  29. {shinestacker-1.5.1 → shinestacker-1.5.3}/.github/workflows/release.yml +0 -0
  30. {shinestacker-1.5.1 → shinestacker-1.5.3}/.gitignore +0 -0
  31. {shinestacker-1.5.1 → shinestacker-1.5.3}/.pylintrc +0 -0
  32. {shinestacker-1.5.1 → shinestacker-1.5.3}/.readthedocs.yaml +0 -0
  33. {shinestacker-1.5.1 → shinestacker-1.5.3}/LICENSE +0 -0
  34. {shinestacker-1.5.1 → shinestacker-1.5.3}/MANIFEST.in +0 -0
  35. {shinestacker-1.5.1 → shinestacker-1.5.3}/README.md +0 -0
  36. {shinestacker-1.5.1 → shinestacker-1.5.3}/THIRD_PARTY_LICENSES.txt +0 -0
  37. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/alignment.md +0 -0
  38. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/api.md +0 -0
  39. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/balancing.md +0 -0
  40. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/conf.py +0 -0
  41. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/focus_stacking.md +0 -0
  42. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/gui.md +0 -0
  43. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/index.md +0 -0
  44. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/job.md +0 -0
  45. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/main.md +0 -0
  46. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/multilayer.md +0 -0
  47. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/noise.md +0 -0
  48. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/requirements.txt +0 -0
  49. {shinestacker-1.5.1 → shinestacker-1.5.3}/docs/vignetting.md +0 -0
  50. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/coffee.gif +0 -0
  51. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/coffee_stack.jpg +0 -0
  52. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/extreme-vignetting.jpg +0 -0
  53. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/flies.gif +0 -0
  54. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/flies_stack.jpg +0 -0
  55. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/flow-diagram.png +0 -0
  56. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/gui-finder.png +0 -0
  57. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/gui-project-new.png +0 -0
  58. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/gui-project-run.png +0 -0
  59. {shinestacker-1.5.1 → shinestacker-1.5.3}/img/gui-retouch.png +0 -0
  60. {shinestacker-1.5.1 → shinestacker-1.5.3}/index.html +0 -0
  61. {shinestacker-1.5.1 → shinestacker-1.5.3}/pyproject.toml +0 -0
  62. {shinestacker-1.5.1 → shinestacker-1.5.3}/requirements.txt +0 -0
  63. {shinestacker-1.5.1 → shinestacker-1.5.3}/scripts/build_release.py +0 -0
  64. {shinestacker-1.5.1 → shinestacker-1.5.3}/scripts/git-rev-list.sh +0 -0
  65. {shinestacker-1.5.1 → shinestacker-1.5.3}/scripts/validate-tomli.py +0 -0
  66. {shinestacker-1.5.1 → shinestacker-1.5.3}/setup.cfg +0 -0
  67. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/__init__.py +0 -0
  68. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/__init__.py +0 -0
  69. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/align.py +0 -0
  70. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/align_auto.py +0 -0
  71. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/align_parallel.py +0 -0
  72. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/balance.py +0 -0
  73. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
  74. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/denoise.py +0 -0
  75. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/depth_map.py +0 -0
  76. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/exif.py +0 -0
  77. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/multilayer.py +0 -0
  78. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/noise_detection.py +0 -0
  79. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/pyramid.py +0 -0
  80. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
  81. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
  82. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/sharpen.py +0 -0
  83. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/stack.py +0 -0
  84. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/stack_framework.py +0 -0
  85. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/utils.py +0 -0
  86. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/vignetting.py +0 -0
  87. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/algorithms/white_balance.py +0 -0
  88. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/__init__.py +0 -0
  89. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/about_dialog.py +0 -0
  90. /shinestacker-1.5.1/src/shinestacker/app/args.py → /shinestacker-1.5.3/src/shinestacker/app/args_parser_opts.py +0 -0
  91. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/gui_utils.py +0 -0
  92. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/help_menu.py +0 -0
  93. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/app/open_frames.py +0 -0
  94. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/config/__init__.py +0 -0
  95. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/config/config.py +0 -0
  96. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/config/constants.py +0 -0
  97. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/__init__.py +0 -0
  98. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/colors.py +0 -0
  99. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/core_utils.py +0 -0
  100. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/exceptions.py +0 -0
  101. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/framework.py +0 -0
  102. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/core/logging.py +0 -0
  103. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/__init__.py +0 -0
  104. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/action_config.py +0 -0
  105. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/action_config_dialog.py +0 -0
  106. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/base_form_dialog.py +0 -0
  107. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/colors.py +0 -0
  108. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/flow_layout.py +0 -0
  109. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/folder_file_selection.py +0 -0
  110. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/gui_images.py +0 -0
  111. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/gui_logging.py +0 -0
  112. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/gui_run.py +0 -0
  113. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  114. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  115. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  116. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  117. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
  118. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  119. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  120. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  121. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  122. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/main_window.py +0 -0
  123. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/menu_manager.py +0 -0
  124. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/new_project.py +0 -0
  125. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/project_controller.py +0 -0
  126. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/project_converter.py +0 -0
  127. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/project_editor.py +0 -0
  128. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/project_model.py +0 -0
  129. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/recent_file_manager.py +0 -0
  130. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/select_path_widget.py +0 -0
  131. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/sys_mon.py +0 -0
  132. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/tab_widget.py +0 -0
  133. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/gui/time_progress_bar.py +0 -0
  134. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/__init__.py +0 -0
  135. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/brush.py +0 -0
  136. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/brush_gradient.py +0 -0
  137. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/brush_preview.py +0 -0
  138. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/brush_tool.py +0 -0
  139. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/exif_data.py +0 -0
  140. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/file_loader.py +0 -0
  141. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/icon_container.py +0 -0
  142. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/image_view_status.py +0 -0
  143. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/io_gui_handler.py +0 -0
  144. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/io_manager.py +0 -0
  145. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/layer_collection.py +0 -0
  146. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/shortcuts_help.py +0 -0
  147. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/transformation_manager.py +0 -0
  148. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker/retouch/undo_manager.py +0 -0
  149. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  150. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/entry_points.txt +0 -0
  151. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/requires.txt +0 -0
  152. {shinestacker-1.5.1 → shinestacker-1.5.3}/src/shinestacker.egg-info/top_level.txt +0 -0
@@ -2,11 +2,35 @@
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
5
+ ## [v1.5.3] - 2025-09-21
6
+ **Bug fixes**
7
+
8
+ ### Fixed
9
+ - fixed brush preview visiblity in cursor style transitions
10
+ - fixed relative import in main app scripts
11
+
12
+ ### Changed
13
+ - implemenrted cursor dynamic color based on background image luminosity
14
+
15
+ ---
16
+
17
+ ## [v1.5.2] - 2025-09-21
18
+ **Bug fixes**
19
+
20
+ ### Fixed
21
+ - fixed white balance filter functionality
22
+ - fixed brush preview visiblity in view mode transitions
23
+
24
+ ### Changed
25
+ - code refactoring and cleanup
26
+
27
+ ---
28
+
29
+ ## [v1.5.1] - 2025-09-20
6
30
  **Several bug fixes**
7
31
 
8
32
  ### Added
9
- - new command-line parameters -v1, -v2, -v3, allow different view modes at startup
33
+ - new command-line arguments -v1, -v2, -v3, allow different view modes at startup
10
34
 
11
35
  ### Fixed
12
36
  - consistent and restyled cursor for current layer view
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.5.1
3
+ Version: 1.5.3
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -0,0 +1 @@
1
+ __version__ = '1.5.3'
@@ -20,7 +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
+ from shinestacker.app.args_parser_opts import add_project_arguments, add_retouch_arguments
24
24
 
25
25
 
26
26
  class SelectionDialog(QDialog):
@@ -17,7 +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
+ from shinestacker.app.args_parser_opts import add_project_arguments
21
21
 
22
22
 
23
23
  class ProjectApp(MainWindow):
@@ -13,7 +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
+ from shinestacker.app.args_parser_opts import add_retouch_arguments
17
17
 
18
18
 
19
19
  class RetouchApp(ImageEditorUI):
@@ -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, 200),
29
+ 'pen': (255, 255, 255, 200),
30
30
  'preview': (255, 180, 180),
31
31
  'cursor_inner': (255, 0, 0, 120),
32
32
  'preview_inner': (255, 255, 255, 150)
@@ -1,16 +1,25 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, W0718, R0915, R0903, R0913, R0917, R0902, R0914
2
2
  import traceback
3
- from abc import ABC, abstractmethod
3
+ from abc import abstractmethod
4
4
  import numpy as np
5
+ from PySide6.QtCore import Qt, QThread, QTimer, QObject, Signal
5
6
  from PySide6.QtWidgets import (
6
7
  QHBoxLayout, QLabel, QSlider, QDialog, QVBoxLayout, QCheckBox, QDialogButtonBox)
7
- from PySide6.QtCore import Qt, Signal, QThread, QTimer
8
+ from .layer_collection import LayerCollectionHandler
8
9
 
9
10
 
10
- class BaseFilter(ABC):
11
- def __init__(self, name, editor, allow_partial_preview=True,
11
+ class BaseFilter(QObject, LayerCollectionHandler):
12
+ update_master_thumbnail_requested = Signal()
13
+ mark_as_modified_requested = Signal()
14
+ filter_gui_set_enabled_requested = Signal(bool)
15
+
16
+ def __init__(self, name, parent, image_viewer, layer_collection, undo_manager,
17
+ allow_partial_preview=True,
12
18
  partial_preview_threshold=0.75, preview_at_startup=False):
13
- self.editor = editor
19
+ QObject.__init__(self, parent)
20
+ LayerCollectionHandler.__init__(self, layer_collection)
21
+ self.image_viewer = image_viewer
22
+ self.undo_manager = undo_manager
14
23
  self.name = name
15
24
  self.allow_partial_preview = allow_partial_preview
16
25
  self.partial_preview_threshold = partial_preview_threshold
@@ -31,11 +40,16 @@ class BaseFilter(ABC):
31
40
  def apply(self, image, *params):
32
41
  pass
33
42
 
43
+ def connect_signals(self, update_master_thumbnail, mark_as_modified, filter_gui_set_enabled):
44
+ self.update_master_thumbnail_requested.connect(update_master_thumbnail)
45
+ self.mark_as_modified_requested.connect(mark_as_modified)
46
+ self.filter_gui_set_enabled_requested.connect(filter_gui_set_enabled)
47
+
34
48
  def run_with_preview(self, **kwargs):
35
- if self.editor.has_no_master_layer():
49
+ if self.has_no_master_layer():
36
50
  return
37
- self.editor.copy_master_layer()
38
- dlg = QDialog(self.editor)
51
+ self.copy_master_layer()
52
+ dlg = QDialog(self.parent())
39
53
  layout = QVBoxLayout(dlg)
40
54
  active_worker = None
41
55
  last_request_id = 0
@@ -46,8 +60,8 @@ class BaseFilter(ABC):
46
60
  def cleanup():
47
61
  nonlocal active_worker, dialog_closed # noqa
48
62
  dialog_closed = True
49
- self.editor.restore_master_layer()
50
- self.editor.image_viewer.update_master_display()
63
+ self.restore_master_layer()
64
+ self.image_viewer.update_master_display()
51
65
  if active_worker and active_worker.isRunning():
52
66
  active_worker.wait()
53
67
  initial_timer.stop()
@@ -58,13 +72,13 @@ class BaseFilter(ABC):
58
72
  if dialog_closed or request_id != expected_id:
59
73
  return
60
74
  if region:
61
- current_region = self.editor.image_viewer.get_visible_image_portion()[1]
75
+ current_region = self.image_viewer.get_visible_image_portion()[1]
62
76
  if current_region == region:
63
- self.editor.set_master_layer(img)
64
- self.editor.image_viewer.update_master_display()
77
+ self.set_master_layer(img)
78
+ self.image_viewer.update_master_display()
65
79
  else:
66
- self.editor.set_master_layer(img)
67
- self.editor.image_viewer.update_master_display()
80
+ self.set_master_layer(img)
81
+ self.image_viewer.update_master_display()
68
82
  try:
69
83
  dlg.activateWindow()
70
84
  except Exception:
@@ -84,10 +98,10 @@ class BaseFilter(ABC):
84
98
  current_id = last_request_id
85
99
  visible_region = None
86
100
  if kwargs.get('partial_preview', self.allow_partial_preview):
87
- visible_data = self.editor.image_viewer.get_visible_image_portion()
101
+ visible_data = self.image_viewer.get_visible_image_portion()
88
102
  if visible_data:
89
103
  visible_img, visible_region = visible_data
90
- master_img = self.editor.master_layer_copy()
104
+ master_img = self.master_layer_copy()
91
105
  if visible_img.size < master_img.size * self.partial_preview_threshold:
92
106
  params = tuple(self.get_params() or ())
93
107
  worker = self.PreviewWorker(
@@ -107,14 +121,14 @@ class BaseFilter(ABC):
107
121
  params = tuple(self.get_params() or ())
108
122
  worker = self.PreviewWorker(
109
123
  self.apply,
110
- args=(self.editor.master_layer_copy(), *params),
124
+ args=(self.master_layer_copy(), *params),
111
125
  request_id=current_id
112
126
  )
113
127
  else:
114
128
  params = tuple(self.get_params() or ())
115
129
  worker = self.PreviewWorker(
116
130
  self.apply,
117
- args=(self.editor.master_layer_copy(), *params),
131
+ args=(self.master_layer_copy(), *params),
118
132
  request_id=current_id
119
133
  )
120
134
  active_worker = worker
@@ -123,8 +137,8 @@ class BaseFilter(ABC):
123
137
  active_worker.start()
124
138
 
125
139
  def restore_original():
126
- self.editor.restore_master_layer()
127
- self.editor.image_viewer.update_master_display()
140
+ self.restore_master_layer()
141
+ self.image_viewer.update_master_display()
128
142
  try:
129
143
  dlg.activateWindow()
130
144
  except Exception:
@@ -139,26 +153,34 @@ class BaseFilter(ABC):
139
153
  if accepted:
140
154
  params = tuple(self.get_params() or ())
141
155
  try:
142
- h, w = self.editor.master_layer().shape[:2]
156
+ h, w = self.master_layer().shape[:2]
143
157
  except Exception:
144
- h, w = self.editor.master_layer_copy().shape[:2]
158
+ h, w = self.master_layer_copy().shape[:2]
145
159
  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(),
160
+ self.undo_manager.extend_undo_area(0, 0, w, h)
161
+ self.undo_manager.save_undo_state(
162
+ self.master_layer_copy(),
149
163
  self.name
150
164
  )
151
165
  except Exception:
152
166
  pass
153
- final_img = self.apply(self.editor.master_layer_copy(), *params)
154
- self.editor.set_master_layer(final_img)
155
- self.editor.copy_master_layer()
156
- self.editor.image_viewer.update_master_display()
157
- self.editor.display_manager.update_master_thumbnail()
158
- self.editor.mark_as_modified()
167
+ final_img = self.apply(self.master_layer_copy(), *params)
168
+ self.set_master_layer(final_img)
169
+ self.copy_master_layer()
170
+ self.image_viewer.update_master_display()
171
+ self.update_master_thumbnail_requested.emit()
172
+ self.mark_as_modified_requested.emit()
159
173
  else:
160
174
  restore_original()
161
175
 
176
+ def connect_preview_toggle(self, preview_check, do_preview, restore_original):
177
+ def on_toggled(checked):
178
+ if checked:
179
+ do_preview()
180
+ else:
181
+ restore_original()
182
+ preview_check.toggled.connect(on_toggled)
183
+
162
184
  def create_base_widgets(self, layout, buttons, preview_latency, parent):
163
185
  self.preview_check = QCheckBox("Preview")
164
186
  self.preview_check.setChecked(self.preview_at_startup)
@@ -199,10 +221,12 @@ class BaseFilter(ABC):
199
221
 
200
222
 
201
223
  class OneSliderBaseFilter(BaseFilter):
202
- def __init__(self, name, editor, max_value, initial_value, title,
224
+ def __init__(self, name, parent, image_viewer, layer_collection, undo_manager,
225
+ max_value, initial_value, title,
203
226
  allow_partial_preview=True, partial_preview_threshold=0.5,
204
227
  preview_at_startup=True):
205
- super().__init__(name, editor, allow_partial_preview,
228
+ super().__init__(name, parent, image_viewer, layer_collection, undo_manager,
229
+ allow_partial_preview,
206
230
  partial_preview_threshold, preview_at_startup)
207
231
  self.max_range = 500
208
232
  self.max_value = max_value
@@ -234,7 +258,7 @@ class OneSliderBaseFilter(BaseFilter):
234
258
  self.preview_timer.timeout.connect(do_preview)
235
259
 
236
260
  slider_local.valueChanged.connect(self.config_changed)
237
- self.editor.connect_preview_toggle(
261
+ self.connect_preview_toggle(
238
262
  self.preview_check, self.do_preview_delayed, restore_original)
239
263
  self.button_box.accepted.connect(dlg.accept)
240
264
  self.button_box.rejected.connect(dlg.reject)
@@ -1,11 +1,12 @@
1
- # pylint: disable=C0114, C0115, C0116, E0611, W0221
1
+ # pylint: disable=C0114, C0115, C0116, E0611, W0221, R0913, R0917
2
2
  from .base_filter import OneSliderBaseFilter
3
3
  from .. algorithms.denoise import denoise
4
4
 
5
5
 
6
6
  class DenoiseFilter(OneSliderBaseFilter):
7
- def __init__(self, name, editor):
8
- super().__init__(name, editor, 10.0, 2.5, "Denoise",
7
+ def __init__(self, name, parent, image_viewer, layer_collection, undo_manager):
8
+ super().__init__(name, parent, image_viewer, layer_collection, undo_manager,
9
+ 10.0, 2.5, "Denoise",
9
10
  allow_partial_preview=True, preview_at_startup=False)
10
11
 
11
12
  def apply(self, image, strength):
@@ -1,4 +1,4 @@
1
- # pylint: disable=C0114, C0115, C0116, E0611, R0903, R0913, R0917, E1121, R0902
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0903, R0913, R0917, E1121, R0902, R0914
2
2
  import numpy as np
3
3
  from PySide6.QtWidgets import (QWidget, QListWidgetItem, QVBoxLayout, QLabel, QInputDialog,
4
4
  QAbstractItemView)
@@ -38,7 +38,6 @@ class DisplayManager(QObject, LayerCollectionHandler):
38
38
  self.update_timer = QTimer()
39
39
  self.update_timer.setInterval(gui_constants.PAINT_REFRESH_TIMER)
40
40
  self.update_timer.timeout.connect(self.process_pending_updates)
41
- self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
42
41
 
43
42
  def process_pending_updates(self):
44
43
  if self.needs_update:
@@ -110,14 +109,15 @@ class DisplayManager(QObject, LayerCollectionHandler):
110
109
  self.thumbnail_list, "Rename Label", "New label name:", text=old_label)
111
110
  if ok and new_label and new_label != old_label:
112
111
  label_widget.setText(new_label)
113
- self.set_layer_labels(i, new_label)
112
+ self.set_layer_label(i, new_label)
113
+ self.status_message_requested.emit("Label renamed.")
114
114
 
115
115
  label_widget.double_clicked.connect(lambda: rename_label(label_widget, label, i))
116
116
  content_layout.addWidget(label_widget)
117
117
  container_layout.addWidget(content_widget)
118
118
  if is_current:
119
119
  container.setStyleSheet(
120
- f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
120
+ f"#thumbnailContainer{{ border: 2px solid {gui_constants.THUMB_HI_COLOR}; }}")
121
121
  else:
122
122
  container.setStyleSheet("#thumbnailContainer{ border: 2px solid transparent; }")
123
123
  item = QListWidgetItem()
@@ -128,7 +128,7 @@ class DisplayManager(QObject, LayerCollectionHandler):
128
128
  if is_current:
129
129
  self.thumbnail_list.setCurrentItem(item)
130
130
 
131
- def highlight_thumbnail(self, index):
131
+ def highlight_thumbnail(self, index, color=gui_constants.THUMB_HI_COLOR):
132
132
  for i in range(self.thumbnail_list.count()):
133
133
  item = self.thumbnail_list.item(i)
134
134
  widget = self.thumbnail_list.itemWidget(item)
@@ -139,7 +139,7 @@ class DisplayManager(QObject, LayerCollectionHandler):
139
139
  widget = self.thumbnail_list.itemWidget(current_item)
140
140
  if widget:
141
141
  widget.setStyleSheet(
142
- f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
142
+ f"#thumbnailContainer{{ border: 2px solid {color}; }}")
143
143
  self.thumbnail_list.setCurrentRow(index)
144
144
  self.thumbnail_list.scrollToItem(
145
145
  self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
@@ -147,28 +147,26 @@ class DisplayManager(QObject, LayerCollectionHandler):
147
147
  def _master_refresh_and_thumb(self):
148
148
  self.image_viewer.show_master()
149
149
  self.refresh_master_view()
150
- self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
151
- self.highlight_thumbnail(self.current_layer_idx())
150
+ self.highlight_thumbnail(self.current_layer_idx(), gui_constants.THUMB_LO_COLOR)
152
151
 
153
152
  def _current_refresh_and_thumb(self):
154
153
  self.image_viewer.show_current()
155
154
  self.refresh_current_view()
156
- self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
157
- self.highlight_thumbnail(self.current_layer_idx())
155
+ self.highlight_thumbnail(self.current_layer_idx(), gui_constants.THUMB_HI_COLOR)
158
156
 
159
157
  def set_view_master(self):
160
158
  if self.has_no_master_layer():
161
159
  return
162
160
  self.view_mode = 'master'
163
161
  self._master_refresh_and_thumb()
164
- self.status_message_requested.emit("View mode: Master")
162
+ self.status_message_requested.emit("View: Master.")
165
163
 
166
164
  def set_view_individual(self):
167
165
  if self.has_no_master_layer():
168
166
  return
169
167
  self.view_mode = 'individual'
170
168
  self._current_refresh_and_thumb()
171
- self.status_message_requested.emit("View mode: Individual layers")
169
+ self.status_message_requested.emit("View: Individual layers.")
172
170
 
173
171
  def refresh_master_view(self):
174
172
  if self.has_no_master_layer():
@@ -186,19 +184,16 @@ class DisplayManager(QObject, LayerCollectionHandler):
186
184
  def start_temp_view(self):
187
185
  if self.view_mode == 'master':
188
186
  self._current_refresh_and_thumb()
189
- self.status_message_requested.emit("Temporary view: Individual layer")
187
+ self.status_message_requested.emit("Temporary view: Individual layer.")
190
188
  else:
191
189
  self._master_refresh_and_thumb()
192
190
  self.image_viewer.strategy.brush_preview.hide()
193
- self.status_message_requested.emit("Temporary view: Master")
191
+ self.status_message_requested.emit("Temporary view: Master.")
194
192
 
195
193
  def end_temp_view(self):
196
194
  if self.view_mode == 'master':
197
195
  self._master_refresh_and_thumb()
198
- self.status_message_requested.emit("View mode: Master")
196
+ self.status_message_requested.emit("View mode: Master.")
199
197
  else:
200
198
  self._current_refresh_and_thumb()
201
- self.status_message_requested.emit("View: Individual layer")
202
-
203
- def allow_cursor_preview(self):
204
- return self.view_mode == 'master'
199
+ self.status_message_requested.emit("View: Individual layer.")
@@ -0,0 +1,20 @@
1
+ # pylint: disable=C0114, C0115, C0116, R0913, R0917
2
+ class FilterManager:
3
+ def __init__(self, editor):
4
+ self.editor = editor
5
+ self.image_viewer = editor.image_viewer
6
+ self.layer_collection = editor.layer_collection
7
+ self.undo_manager = editor.undo_manager
8
+ self.filters = {}
9
+
10
+ def register_filter(self, name, filter_class,
11
+ update_master_thumbnail, mark_as_modified, filter_gui_set_enabled):
12
+ filter_obj = filter_class(
13
+ name, self.editor, self.image_viewer, self.layer_collection, self.undo_manager)
14
+ self.filters[name] = filter_obj
15
+ filter_obj.connect_signals(
16
+ update_master_thumbnail, mark_as_modified, filter_gui_set_enabled)
17
+
18
+ def apply(self, name, **kwargs):
19
+ if name in self.filters:
20
+ self.filters[name].run_with_preview(**kwargs)
@@ -1,6 +1,5 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904, W0108
2
2
  from functools import partial
3
- import numpy as np
4
3
  from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QMenu,
5
4
  QListWidget, QSlider, QMainWindow, QMessageBox)
6
5
  from PySide6.QtGui import QShortcut, QKeySequence, QAction, QActionGroup
@@ -31,9 +30,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
31
30
  QMainWindow.__init__(self)
32
31
  LayerCollectionHandler.__init__(self, LayerCollection())
33
32
  self._recent_file_manager = RecentFileManager("shinestacker-recent-images-files.txt")
34
- self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
35
33
  self.io_gui_handler = None
36
- self.display_manager = None
37
34
  self.brush = Brush()
38
35
  self.brush_tool = BrushTool()
39
36
  self.modified = False
@@ -43,11 +40,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
43
40
  self.undo_action = None
44
41
  self.redo_action = None
45
42
  self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
46
- self.filter_manager = FilterManager(self)
47
- self.filter_manager.register_filter("Denoise", DenoiseFilter)
48
- self.filter_manager.register_filter("Unsharp Mask", UnsharpMaskFilter)
49
- self.filter_manager.register_filter("White Balance", WhiteBalanceFilter)
50
- self.filter_manager.register_filter("Vignetting Correction", VignettingFilter)
51
43
  self.shortcuts_help_dialog = None
52
44
  self.update_title()
53
45
  self.resize(1400, 900)
@@ -144,7 +136,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
144
136
  self.master_thumbnail_frame = QFrame()
145
137
  self.master_thumbnail_frame.setObjectName("thumbnailContainer")
146
138
  self.master_thumbnail_frame.setStyleSheet(
147
- f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
139
+ f"#thumbnailContainer{{ border: 2px solid {gui_constants.THUMB_MASTER_HI_COLOR}; }}")
148
140
  self.master_thumbnail_frame.setFrameShape(QFrame.StyledPanel)
149
141
  master_thumbnail_layout = QVBoxLayout(self.master_thumbnail_frame)
150
142
  master_thumbnail_layout.setContentsMargins(8, 8, 8, 8)
@@ -229,6 +221,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
229
221
  self.display_manager = DisplayManager(
230
222
  self.layer_collection, self.image_viewer,
231
223
  self.master_thumbnail_label, self.thumbnail_list, parent=self)
224
+ self.filter_manager = FilterManager(self)
232
225
  self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
233
226
  self.display_manager.status_message_requested.connect(self.show_status_message)
234
227
  self.io_gui_handler.status_message_requested.connect(self.show_status_message)
@@ -243,8 +236,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
243
236
  self.image_viewer.set_preview_brush(self.brush_tool.brush)
244
237
  self.brush_tool.update_brush_thumb()
245
238
  self.io_gui_handler.setup_ui(self.display_manager, self.image_viewer)
246
- self.image_viewer.set_display_manager(self.display_manager)
247
-
248
239
  menubar = self.menuBar()
249
240
  file_menu = menubar.addMenu("&File")
250
241
  file_menu.addAction("&Open...", self.io_gui_handler.open_file, "Ctrl+O")
@@ -313,7 +304,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
313
304
 
314
305
  view_menu.addSeparator()
315
306
 
316
- view_strategy_menu = QMenu("View &Mode", view_menu)
307
+ self.view_strategy_menu = QMenu("View &Mode", view_menu)
317
308
 
318
309
  self.view_mode_actions = {
319
310
  'overlaid': QAction("Overlaid", self),
@@ -324,18 +315,32 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
324
315
  overlaid_mode.setShortcut("Ctrl+1")
325
316
  overlaid_mode.setCheckable(True)
326
317
  overlaid_mode.triggered.connect(lambda: self.set_strategy('overlaid'))
327
- view_strategy_menu.addAction(overlaid_mode)
318
+ self.view_strategy_menu.addAction(overlaid_mode)
328
319
  side_by_side_mode = self.view_mode_actions['sidebyside']
329
320
  side_by_side_mode.setShortcut("Ctrl+2")
330
321
  side_by_side_mode.setCheckable(True)
331
322
  side_by_side_mode.triggered.connect(lambda: self.set_strategy('sidebyside'))
332
- view_strategy_menu.addAction(side_by_side_mode)
323
+ self.view_strategy_menu.addAction(side_by_side_mode)
333
324
  side_by_side_mode = self.view_mode_actions['topbottom']
334
325
  side_by_side_mode.setShortcut("Ctrl+3")
335
326
  side_by_side_mode.setCheckable(True)
336
327
  side_by_side_mode.triggered.connect(lambda: self.set_strategy('topbottom'))
337
- view_strategy_menu.addAction(side_by_side_mode)
338
- view_menu.addMenu(view_strategy_menu)
328
+ self.view_strategy_menu.addAction(side_by_side_mode)
329
+ view_menu.addMenu(self.view_strategy_menu)
330
+
331
+ filter_handles = (
332
+ self.display_manager.update_master_thumbnail,
333
+ self.mark_as_modified,
334
+ self.view_strategy_menu.setEnabled
335
+ )
336
+ self.filter_manager.register_filter(
337
+ "Denoise", DenoiseFilter, *filter_handles)
338
+ self.filter_manager.register_filter(
339
+ "Unsharp Mask", UnsharpMaskFilter, *filter_handles)
340
+ self.filter_manager.register_filter(
341
+ "White Balance", WhiteBalanceFilter, *filter_handles)
342
+ self.filter_manager.register_filter(
343
+ "Vignetting Correction", VignettingFilter, *filter_handles)
339
344
 
340
345
  cursor_menu = view_menu.addMenu("Cursor Style")
341
346
 
@@ -462,6 +467,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
462
467
  def set_strategy(self, strategy):
463
468
  self.image_viewer.set_strategy(strategy)
464
469
  enable_shortcuts = strategy == 'overlaid'
470
+ self.display_manager.view_mode = 'master'
471
+ self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
465
472
  self.view_master_action.setEnabled(enable_shortcuts)
466
473
  self.view_individual_action.setEnabled(enable_shortcuts)
467
474
  self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
@@ -584,7 +591,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
584
591
 
585
592
  def copy_brush_area_to_master(self, view_pos):
586
593
  if self.layer_stack() is None or self.number_of_layers() == 0 \
587
- or not self.display_manager.allow_cursor_preview():
594
+ or self.display_manager.view_mode != 'master':
588
595
  return
589
596
  area = self.brush_tool.apply_brush_operation(
590
597
  self.master_layer_copy(),
@@ -594,7 +601,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
594
601
  self.undo_manager.extend_undo_area(*area)
595
602
 
596
603
  def begin_copy_brush_area(self, pos):
597
- if self.display_manager.allow_cursor_preview():
604
+ if self.display_manager.view_mode == 'master':
598
605
  self.mask_layer = self.io_gui_handler.blank_layer.copy()
599
606
  self.copy_master_layer()
600
607
  self.undo_manager.reset_undo_area()
@@ -605,7 +612,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
605
612
  self.mark_as_modified()
606
613
 
607
614
  def continue_copy_brush_area(self, pos):
608
- if self.display_manager.allow_cursor_preview():
615
+ if self.display_manager.view_mode == 'master':
609
616
  self.copy_brush_area_to_master(pos)
610
617
  self.display_manager.needs_update = True
611
618
  if not self.display_manager.update_timer.isActive():
@@ -647,53 +654,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
647
654
  def vignetting_correction(self):
648
655
  self.filter_manager.apply("Vignetting Correction")
649
656
 
650
- def connect_preview_toggle(self, preview_check, do_preview, restore_original):
651
- def on_toggled(checked):
652
- if checked:
653
- do_preview()
654
- else:
655
- restore_original()
656
- preview_check.toggled.connect(on_toggled)
657
-
658
- def get_pixel_color_at(self, pos, radius=None):
659
- item_pos = self.image_viewer.position_on_image(pos)
660
- x = int(item_pos.x())
661
- y = int(item_pos.y())
662
- master_layer = self.master_layer()
663
- if (0 <= x < self.master_layer().shape[1]) and \
664
- (0 <= y < self.master_layer().shape[0]):
665
- if radius is None:
666
- radius = int(self.brush.size)
667
- if radius > 0:
668
- y_indices, x_indices = np.ogrid[-radius:radius + 1, -radius:radius + 1]
669
- mask = x_indices**2 + y_indices**2 <= radius**2
670
- x0 = max(0, x - radius)
671
- x1 = min(master_layer.shape[1], x + radius + 1)
672
- y0 = max(0, y - radius)
673
- y1 = min(master_layer.shape[0], y + radius + 1)
674
- mask = mask[radius - (y - y0): radius + (y1 - y),
675
- radius - (x - x0): radius + (x1 - x)]
676
- region = master_layer[y0:y1, x0:x1]
677
- if region.size == 0:
678
- pixel = master_layer[y, x]
679
- else:
680
- if region.ndim == 3:
681
- pixel = [region[:, :, c][mask].mean() for c in range(region.shape[2])]
682
- else:
683
- pixel = region[mask].mean()
684
- else:
685
- pixel = self.master_layer()[y, x]
686
- if np.isscalar(pixel):
687
- pixel = [pixel, pixel, pixel]
688
- pixel = [np.float32(x) for x in pixel]
689
- if master_layer.dtype == np.uint16:
690
- pixel = [x / 256.0 for x in pixel]
691
- return tuple(int(v) for v in pixel)
692
- return (0, 0, 0)
693
-
694
- def highlight_master_thumbnail(self):
657
+ def highlight_master_thumbnail(self, color):
695
658
  self.master_thumbnail_frame.setStyleSheet(
696
- f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
659
+ f"#thumbnailContainer{{ border: 2px solid {color}; }}")
697
660
 
698
661
  def save_actions_set_enabled(self, enabled):
699
662
  self.save_action.setEnabled(enabled)
@@ -709,13 +672,11 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
709
672
 
710
673
  def set_view_master(self):
711
674
  self.display_manager.set_view_master()
712
- self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
713
- self.highlight_master_thumbnail()
675
+ self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
714
676
 
715
677
  def set_view_individual(self):
716
678
  self.display_manager.set_view_individual()
717
- self.thumbnail_highlight = gui_constants.THUMB_MASTER_LO_COLOR
718
- self.highlight_master_thumbnail()
679
+ self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_LO_COLOR)
719
680
 
720
681
  def toggle_view_master_individual(self):
721
682
  if self.display_manager.view_mode == 'master':
@@ -750,12 +711,10 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
750
711
  def handle_temp_view(self, start):
751
712
  if start:
752
713
  self.display_manager.start_temp_view()
753
- self.thumbnail_highlight = gui_constants.THUMB_MASTER_LO_COLOR
754
- self.highlight_master_thumbnail()
714
+ self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_LO_COLOR)
755
715
  else:
756
716
  self.display_manager.end_temp_view()
757
- self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
758
- self.highlight_master_thumbnail()
717
+ self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
759
718
 
760
719
  def handle_brush_size_change(self, delta):
761
720
  if delta > 0: