shinestacker 0.4.0__py3-none-any.whl → 1.0.0__py3-none-any.whl

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 (59) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +4 -12
  3. shinestacker/algorithms/balance.py +11 -9
  4. shinestacker/algorithms/depth_map.py +0 -30
  5. shinestacker/algorithms/utils.py +10 -0
  6. shinestacker/algorithms/vignetting.py +116 -70
  7. shinestacker/app/about_dialog.py +101 -12
  8. shinestacker/app/gui_utils.py +1 -1
  9. shinestacker/app/help_menu.py +1 -1
  10. shinestacker/app/main.py +2 -2
  11. shinestacker/app/project.py +2 -2
  12. shinestacker/config/constants.py +4 -1
  13. shinestacker/config/gui_constants.py +10 -9
  14. shinestacker/gui/action_config.py +5 -561
  15. shinestacker/gui/action_config_dialog.py +567 -0
  16. shinestacker/gui/base_form_dialog.py +18 -0
  17. shinestacker/gui/colors.py +5 -6
  18. shinestacker/gui/gui_logging.py +0 -1
  19. shinestacker/gui/gui_run.py +54 -106
  20. shinestacker/gui/ico/shinestacker.icns +0 -0
  21. shinestacker/gui/ico/shinestacker.ico +0 -0
  22. shinestacker/gui/ico/shinestacker.png +0 -0
  23. shinestacker/gui/ico/shinestacker.svg +60 -0
  24. shinestacker/gui/main_window.py +276 -367
  25. shinestacker/gui/menu_manager.py +236 -0
  26. shinestacker/gui/new_project.py +75 -20
  27. shinestacker/gui/project_converter.py +6 -6
  28. shinestacker/gui/project_editor.py +248 -165
  29. shinestacker/gui/project_model.py +2 -7
  30. shinestacker/gui/tab_widget.py +81 -0
  31. shinestacker/gui/time_progress_bar.py +95 -0
  32. shinestacker/retouch/base_filter.py +173 -40
  33. shinestacker/retouch/brush_preview.py +0 -10
  34. shinestacker/retouch/brush_tool.py +25 -11
  35. shinestacker/retouch/denoise_filter.py +5 -44
  36. shinestacker/retouch/display_manager.py +57 -20
  37. shinestacker/retouch/exif_data.py +10 -13
  38. shinestacker/retouch/file_loader.py +1 -1
  39. shinestacker/retouch/filter_manager.py +1 -4
  40. shinestacker/retouch/image_editor_ui.py +365 -49
  41. shinestacker/retouch/image_viewer.py +34 -11
  42. shinestacker/retouch/io_gui_handler.py +96 -43
  43. shinestacker/retouch/io_manager.py +23 -7
  44. shinestacker/retouch/layer_collection.py +2 -0
  45. shinestacker/retouch/shortcuts_help.py +12 -0
  46. shinestacker/retouch/unsharp_mask_filter.py +10 -10
  47. shinestacker/retouch/vignetting_filter.py +69 -0
  48. shinestacker/retouch/white_balance_filter.py +46 -14
  49. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/METADATA +14 -2
  50. shinestacker-1.0.0.dist-info/RECORD +90 -0
  51. shinestacker/app/app_config.py +0 -22
  52. shinestacker/gui/actions_window.py +0 -258
  53. shinestacker/retouch/image_editor.py +0 -201
  54. shinestacker/retouch/image_filters.py +0 -69
  55. shinestacker-0.4.0.dist-info/RECORD +0 -87
  56. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/WHEEL +0 -0
  57. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/entry_points.txt +0 -0
  58. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/licenses/LICENSE +0 -0
  59. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
- # pylint: disable=C0114, C0115, C0116, E0611, R0903, R0913, R0917, E1121
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0903, R0913, R0917, E1121, R0902
2
2
  import numpy as np
3
- from PySide6.QtWidgets import QWidget, QListWidgetItem, QVBoxLayout, QLabel, QInputDialog
3
+ from PySide6.QtWidgets import (QWidget, QListWidgetItem, QVBoxLayout, QLabel, QInputDialog,
4
+ QAbstractItemView)
4
5
  from PySide6.QtGui import QPixmap, QImage
5
6
  from PySide6.QtCore import Qt, QObject, QTimer, QSize, Signal
6
7
  from .. config.gui_constants import gui_constants
@@ -39,6 +40,7 @@ class DisplayManager(QObject, LayerCollectionHandler):
39
40
  self.update_timer = QTimer()
40
41
  self.update_timer.setInterval(gui_constants.PAINT_REFRESH_TIMER)
41
42
  self.update_timer.timeout.connect(self.process_pending_updates)
43
+ self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
42
44
 
43
45
  def process_pending_updates(self):
44
46
  if self.needs_update:
@@ -64,15 +66,15 @@ class DisplayManager(QObject, LayerCollectionHandler):
64
66
  self.display_master_layer()
65
67
 
66
68
  def create_thumbnail(self, layer):
67
- if layer.dtype == np.uint16:
68
- layer = (layer // 256).astype(np.uint8)
69
- height, width = layer.shape[:2]
70
- if layer.ndim == 3 and layer.shape[-1] == 3:
71
- qimg = QImage(layer.data, width, height, 3 * width, QImage.Format_RGB888)
69
+ source_layer = (layer // 256).astype(np.uint8) if layer.dtype == np.uint16 else layer
70
+ height, width = source_layer.shape[:2]
71
+ if layer.ndim == 3 and source_layer.shape[-1] == 3:
72
+ qimg = QImage(source_layer.data, width, height, 3 * width, QImage.Format_RGB888)
72
73
  else:
73
- qimg = QImage(layer.data, width, height, width, QImage.Format_Grayscale8)
74
+ qimg = QImage(source_layer.data, width, height, width, QImage.Format_Grayscale8)
74
75
  return QPixmap.fromImage(
75
- qimg.scaled(*gui_constants.UI_SIZES['thumbnail'], Qt.KeepAspectRatio))
76
+ qimg.scaledToWidth(
77
+ gui_constants.UI_SIZES['thumbnail_width'], Qt.SmoothTransformation))
76
78
 
77
79
  def update_thumbnails(self):
78
80
  self.update_master_thumbnail()
@@ -103,17 +105,22 @@ class DisplayManager(QObject, LayerCollectionHandler):
103
105
  self.master_thumbnail_label.setPixmap(pixmap)
104
106
 
105
107
  def add_thumbnail_item(self, thumbnail, label, i, is_current):
106
- item_widget = QWidget()
107
- layout = QVBoxLayout(item_widget)
108
- layout.setContentsMargins(0, 0, 0, 0)
109
- layout.setSpacing(0)
110
-
108
+ container = QWidget()
109
+ container.setFixedWidth(gui_constants.UI_SIZES['thumbnail_width'] + 4)
110
+ container.setObjectName("thumbnailContainer")
111
+ container_layout = QVBoxLayout(container)
112
+ container_layout.setContentsMargins(2, 2, 2, 2)
113
+ container_layout.setSpacing(0)
114
+ content_widget = QWidget()
115
+ content_layout = QVBoxLayout(content_widget)
116
+ content_layout.setContentsMargins(0, 0, 0, 0)
117
+ content_layout.setSpacing(0)
111
118
  thumbnail_label = QLabel()
112
119
  thumbnail_label.setPixmap(thumbnail)
113
120
  thumbnail_label.setAlignment(Qt.AlignCenter)
114
- layout.addWidget(thumbnail_label)
115
-
121
+ content_layout.addWidget(thumbnail_label)
116
122
  label_widget = ClickableLabel(label)
123
+ label_widget.setFixedHeight(gui_constants.UI_SIZES['label_height'])
117
124
  label_widget.setAlignment(Qt.AlignCenter)
118
125
 
119
126
  def rename_label(label_widget, old_label, i):
@@ -124,21 +131,45 @@ class DisplayManager(QObject, LayerCollectionHandler):
124
131
  self.set_layer_labels(i, new_label)
125
132
 
126
133
  label_widget.double_clicked.connect(lambda: rename_label(label_widget, label, i))
127
- layout.addWidget(label_widget)
134
+ content_layout.addWidget(label_widget)
135
+ container_layout.addWidget(content_widget)
136
+ if is_current:
137
+ container.setStyleSheet(
138
+ f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
139
+ else:
140
+ container.setStyleSheet("#thumbnailContainer{ border: 2px solid transparent; }")
128
141
  item = QListWidgetItem()
129
- item.setSizeHint(QSize(gui_constants.IMG_WIDTH, gui_constants.IMG_HEIGHT))
142
+ item.setSizeHint(QSize(gui_constants.UI_SIZES['thumbnail_width'] + 4,
143
+ thumbnail.height() + label_widget.height() + 4))
130
144
  self.thumbnail_list.addItem(item)
131
- self.thumbnail_list.setItemWidget(item, item_widget)
132
-
145
+ self.thumbnail_list.setItemWidget(item, container)
133
146
  if is_current:
134
147
  self.thumbnail_list.setCurrentItem(item)
135
148
 
149
+ def highlight_thumbnail(self, index):
150
+ for i in range(self.thumbnail_list.count()):
151
+ item = self.thumbnail_list.item(i)
152
+ widget = self.thumbnail_list.itemWidget(item)
153
+ if widget:
154
+ widget.setStyleSheet("#thumbnailContainer{ border: 2px solid transparent; }")
155
+ current_item = self.thumbnail_list.item(index)
156
+ if current_item:
157
+ widget = self.thumbnail_list.itemWidget(current_item)
158
+ if widget:
159
+ widget.setStyleSheet(
160
+ f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
161
+ self.thumbnail_list.setCurrentRow(index)
162
+ self.thumbnail_list.scrollToItem(
163
+ self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
164
+
136
165
  def set_view_master(self):
137
166
  if self.has_no_master_layer():
138
167
  return
139
168
  self.view_mode = 'master'
140
169
  self.temp_view_individual = False
141
170
  self.display_master_layer()
171
+ self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
172
+ self.highlight_thumbnail(self.current_layer_idx())
142
173
  self.status_message_requested.emit("View mode: Master")
143
174
  self.cursor_preview_state_changed.emit(True) # True = allow preview
144
175
 
@@ -148,6 +179,8 @@ class DisplayManager(QObject, LayerCollectionHandler):
148
179
  self.view_mode = 'individual'
149
180
  self.temp_view_individual = False
150
181
  self.display_current_layer()
182
+ self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
183
+ self.highlight_thumbnail(self.current_layer_idx())
151
184
  self.status_message_requested.emit("View mode: Individual layers")
152
185
  self.cursor_preview_state_changed.emit(False) # False = no preview
153
186
 
@@ -155,6 +188,8 @@ class DisplayManager(QObject, LayerCollectionHandler):
155
188
  if not self.temp_view_individual and self.view_mode == 'master':
156
189
  self.temp_view_individual = True
157
190
  self.image_viewer.update_brush_cursor()
191
+ self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
192
+ self.highlight_thumbnail(self.current_layer_idx())
158
193
  self.display_current_layer()
159
194
  self.status_message_requested.emit("Temporary view: Individual layer (hold X)")
160
195
 
@@ -162,6 +197,8 @@ class DisplayManager(QObject, LayerCollectionHandler):
162
197
  if self.temp_view_individual:
163
198
  self.temp_view_individual = False
164
199
  self.image_viewer.update_brush_cursor()
200
+ self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
201
+ self.highlight_thumbnail(self.current_layer_idx())
165
202
  self.display_master_layer()
166
203
  self.status_message_requested.emit("View mode: Master")
167
204
  self.cursor_preview_state_changed.emit(True) # Restore preview
@@ -1,28 +1,25 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611
2
2
  from PIL.TiffImagePlugin import IFDRational
3
- from PySide6.QtWidgets import QFormLayout, QHBoxLayout, QPushButton, QDialog, QLabel
3
+ from PySide6.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel
4
4
  from PySide6.QtCore import Qt
5
5
  from .. algorithms.exif import exif_dict
6
6
  from .icon_container import icon_container
7
+ from .. gui.base_form_dialog import BaseFormDialog
7
8
 
8
9
 
9
- class ExifData(QDialog):
10
+ class ExifData(BaseFormDialog):
10
11
  def __init__(self, exif, parent=None):
11
- super().__init__(parent)
12
+ super().__init__("EXIF data", parent)
12
13
  self.exif = exif
13
- self.setWindowTitle("EXIF data")
14
- self.resize(500, self.height())
15
- self.layout = QFormLayout(self)
16
- self.layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
17
- self.layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
18
- self.layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
19
- self.layout.setLabelAlignment(Qt.AlignLeft)
20
14
  self.create_form()
21
- button_box = QHBoxLayout()
15
+ button_container = QWidget()
16
+ button_layout = QHBoxLayout(button_container)
17
+ button_layout.setAlignment(Qt.AlignCenter)
22
18
  ok_button = QPushButton("OK")
19
+ ok_button.setFixedWidth(100)
23
20
  ok_button.setFocus()
24
- button_box.addWidget(ok_button)
25
- self.layout.addRow(button_box)
21
+ button_layout.addWidget(ok_button)
22
+ self.add_row_to_layout(button_container)
26
23
  ok_button.clicked.connect(self.accept)
27
24
 
28
25
  def add_bold_label(self, label):
@@ -42,7 +42,7 @@ class FileLoader(QThread):
42
42
  current_labels = [f"Layer {i + 1}" for i in range(len(current_stack))]
43
43
  self.finished.emit(current_stack, current_labels, master_layer)
44
44
  except Exception as e:
45
- traceback.print_tb(e.__traceback__)
45
+ # traceback.print_tb(e.__traceback__)
46
46
  self.error.emit(f"Error loading file:\n{str(e)}")
47
47
 
48
48
  def load_stack(self, path):
@@ -5,10 +5,7 @@ class FilterManager:
5
5
  self.filters = {}
6
6
 
7
7
  def register_filter(self, name, filter_class):
8
- self.filters[name] = filter_class(self.editor)
9
-
10
- def get_filter(self, name):
11
- return self.filters.get(name)
8
+ self.filters[name] = filter_class(name, self.editor)
12
9
 
13
10
  def apply(self, name, **kwargs):
14
11
  if name in self.filters: