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.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/align.py +4 -12
- shinestacker/algorithms/balance.py +11 -9
- shinestacker/algorithms/depth_map.py +0 -30
- shinestacker/algorithms/utils.py +10 -0
- shinestacker/algorithms/vignetting.py +116 -70
- shinestacker/app/about_dialog.py +101 -12
- shinestacker/app/gui_utils.py +1 -1
- shinestacker/app/help_menu.py +1 -1
- shinestacker/app/main.py +2 -2
- shinestacker/app/project.py +2 -2
- shinestacker/config/constants.py +4 -1
- shinestacker/config/gui_constants.py +10 -9
- shinestacker/gui/action_config.py +5 -561
- shinestacker/gui/action_config_dialog.py +567 -0
- shinestacker/gui/base_form_dialog.py +18 -0
- shinestacker/gui/colors.py +5 -6
- shinestacker/gui/gui_logging.py +0 -1
- shinestacker/gui/gui_run.py +54 -106
- shinestacker/gui/ico/shinestacker.icns +0 -0
- shinestacker/gui/ico/shinestacker.ico +0 -0
- shinestacker/gui/ico/shinestacker.png +0 -0
- shinestacker/gui/ico/shinestacker.svg +60 -0
- shinestacker/gui/main_window.py +276 -367
- shinestacker/gui/menu_manager.py +236 -0
- shinestacker/gui/new_project.py +75 -20
- shinestacker/gui/project_converter.py +6 -6
- shinestacker/gui/project_editor.py +248 -165
- shinestacker/gui/project_model.py +2 -7
- shinestacker/gui/tab_widget.py +81 -0
- shinestacker/gui/time_progress_bar.py +95 -0
- shinestacker/retouch/base_filter.py +173 -40
- shinestacker/retouch/brush_preview.py +0 -10
- shinestacker/retouch/brush_tool.py +25 -11
- shinestacker/retouch/denoise_filter.py +5 -44
- shinestacker/retouch/display_manager.py +57 -20
- shinestacker/retouch/exif_data.py +10 -13
- shinestacker/retouch/file_loader.py +1 -1
- shinestacker/retouch/filter_manager.py +1 -4
- shinestacker/retouch/image_editor_ui.py +365 -49
- shinestacker/retouch/image_viewer.py +34 -11
- shinestacker/retouch/io_gui_handler.py +96 -43
- shinestacker/retouch/io_manager.py +23 -7
- shinestacker/retouch/layer_collection.py +2 -0
- shinestacker/retouch/shortcuts_help.py +12 -0
- shinestacker/retouch/unsharp_mask_filter.py +10 -10
- shinestacker/retouch/vignetting_filter.py +69 -0
- shinestacker/retouch/white_balance_filter.py +46 -14
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/METADATA +14 -2
- shinestacker-1.0.0.dist-info/RECORD +90 -0
- shinestacker/app/app_config.py +0 -22
- shinestacker/gui/actions_window.py +0 -258
- shinestacker/retouch/image_editor.py +0 -201
- shinestacker/retouch/image_filters.py +0 -69
- shinestacker-0.4.0.dist-info/RECORD +0 -87
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/WHEEL +0 -0
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
74
|
+
qimg = QImage(source_layer.data, width, height, width, QImage.Format_Grayscale8)
|
|
74
75
|
return QPixmap.fromImage(
|
|
75
|
-
qimg.
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
self.
|
|
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:
|