shinestacker 1.4.0__py3-none-any.whl → 1.5.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/app/main.py +1 -1
- shinestacker/config/gui_constants.py +5 -0
- shinestacker/retouch/base_filter.py +8 -10
- shinestacker/retouch/brush_preview.py +0 -1
- shinestacker/retouch/display_manager.py +42 -42
- shinestacker/retouch/image_editor_ui.py +52 -31
- shinestacker/retouch/image_view_status.py +4 -0
- shinestacker/retouch/image_viewer.py +6 -0
- shinestacker/retouch/layer_collection.py +3 -0
- shinestacker/retouch/overlaid_view.py +87 -84
- shinestacker/retouch/sidebyside_view.py +113 -115
- shinestacker/retouch/transformation_manager.py +43 -0
- shinestacker/retouch/undo_manager.py +22 -3
- shinestacker/retouch/view_strategy.py +135 -44
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/METADATA +7 -7
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/RECORD +21 -20
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.4.0.dist-info → shinestacker-1.5.0.dist-info}/top_level.txt +0 -0
shinestacker/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '1.
|
|
1
|
+
__version__ = '1.5.0'
|
shinestacker/app/main.py
CHANGED
|
@@ -102,7 +102,7 @@ class MainApp(QMainWindow):
|
|
|
102
102
|
file_menu = action.menu()
|
|
103
103
|
break
|
|
104
104
|
if file_menu is not None:
|
|
105
|
-
import_action = QAction("Import
|
|
105
|
+
import_action = QAction("Import from Current Project", self)
|
|
106
106
|
import_action.triggered.connect(self.import_from_project)
|
|
107
107
|
file_menu.addAction(import_action)
|
|
108
108
|
else:
|
|
@@ -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
|
|
@@ -34,7 +34,6 @@ class BaseFilter(ABC):
|
|
|
34
34
|
def run_with_preview(self, **kwargs):
|
|
35
35
|
if self.editor.has_no_master_layer():
|
|
36
36
|
return
|
|
37
|
-
|
|
38
37
|
self.editor.copy_master_layer()
|
|
39
38
|
dlg = QDialog(self.editor)
|
|
40
39
|
layout = QVBoxLayout(dlg)
|
|
@@ -143,15 +142,14 @@ class BaseFilter(ABC):
|
|
|
143
142
|
h, w = self.editor.master_layer().shape[:2]
|
|
144
143
|
except Exception:
|
|
145
144
|
h, w = self.editor.master_layer_copy().shape[:2]
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
self.editor.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
pass
|
|
145
|
+
try:
|
|
146
|
+
self.editor.undo_manager.extend_undo_area(0, 0, w, h)
|
|
147
|
+
self.editor.undo_manager.save_undo_state(
|
|
148
|
+
self.editor.master_layer_copy(),
|
|
149
|
+
self.name
|
|
150
|
+
)
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
155
153
|
final_img = self.apply(self.editor.master_layer_copy(), *params)
|
|
156
154
|
self.editor.set_master_layer(final_img)
|
|
157
155
|
self.editor.copy_master_layer()
|
|
@@ -113,7 +113,6 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
|
|
|
113
113
|
self.setPixmap(final_pixmap)
|
|
114
114
|
x_start, y_start = max(0, x), max(0, y)
|
|
115
115
|
self.setPos(x_start, y_start)
|
|
116
|
-
self.show()
|
|
117
116
|
except Exception:
|
|
118
117
|
traceback.print_exc()
|
|
119
118
|
self.hide()
|
|
@@ -25,7 +25,6 @@ class ClickableLabel(QLabel):
|
|
|
25
25
|
|
|
26
26
|
class DisplayManager(QObject, LayerCollectionHandler):
|
|
27
27
|
status_message_requested = Signal(str)
|
|
28
|
-
cursor_preview_state_changed = Signal(bool)
|
|
29
28
|
|
|
30
29
|
def __init__(self, layer_collection, image_viewer, master_thumbnail_label,
|
|
31
30
|
thumbnail_list, parent=None):
|
|
@@ -35,7 +34,6 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
35
34
|
self.master_thumbnail_label = master_thumbnail_label
|
|
36
35
|
self.thumbnail_list = thumbnail_list
|
|
37
36
|
self.view_mode = 'master'
|
|
38
|
-
self.temp_view_individual = False
|
|
39
37
|
self.needs_update = False
|
|
40
38
|
self.update_timer = QTimer()
|
|
41
39
|
self.update_timer.setInterval(gui_constants.PAINT_REFRESH_TIMER)
|
|
@@ -47,21 +45,10 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
47
45
|
self.refresh_master_view()
|
|
48
46
|
self.needs_update = False
|
|
49
47
|
|
|
50
|
-
def refresh_master_view(self):
|
|
51
|
-
if self.has_no_master_layer():
|
|
52
|
-
return
|
|
53
|
-
self.image_viewer.update_master_display()
|
|
54
|
-
self.update_master_thumbnail()
|
|
55
|
-
self.image_viewer.refresh_display()
|
|
56
|
-
|
|
57
|
-
def refresh_current_view(self):
|
|
58
|
-
if self.number_of_layers() == 0:
|
|
59
|
-
return
|
|
60
|
-
self.image_viewer.update_current_display()
|
|
61
|
-
self.image_viewer.refresh_display()
|
|
62
|
-
|
|
63
48
|
def create_thumbnail(self, layer):
|
|
64
49
|
source_layer = (layer // 256).astype(np.uint8) if layer.dtype == np.uint16 else layer
|
|
50
|
+
if not source_layer.flags.c_contiguous:
|
|
51
|
+
source_layer = np.ascontiguousarray(source_layer)
|
|
65
52
|
height, width = source_layer.shape[:2]
|
|
66
53
|
if layer.ndim == 3 and source_layer.shape[-1] == 3:
|
|
67
54
|
qimg = QImage(source_layer.data, width, height, 3 * width, QImage.Format_RGB888)
|
|
@@ -157,48 +144,61 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
157
144
|
self.thumbnail_list.scrollToItem(
|
|
158
145
|
self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
|
|
159
146
|
|
|
147
|
+
def _master_refresh_and_thumb(self):
|
|
148
|
+
self.image_viewer.show_master()
|
|
149
|
+
self.refresh_master_view()
|
|
150
|
+
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
151
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
152
|
+
|
|
153
|
+
def _current_refresh_and_thumb(self):
|
|
154
|
+
self.image_viewer.show_current()
|
|
155
|
+
self.refresh_current_view()
|
|
156
|
+
self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
|
|
157
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
158
|
+
|
|
160
159
|
def set_view_master(self):
|
|
161
160
|
if self.has_no_master_layer():
|
|
162
161
|
return
|
|
163
162
|
self.view_mode = 'master'
|
|
164
|
-
self.
|
|
165
|
-
self.refresh_master_view()
|
|
166
|
-
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
167
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
163
|
+
self._master_refresh_and_thumb()
|
|
168
164
|
self.status_message_requested.emit("View mode: Master")
|
|
169
|
-
self.cursor_preview_state_changed.emit(True)
|
|
170
165
|
|
|
171
166
|
def set_view_individual(self):
|
|
172
167
|
if self.has_no_master_layer():
|
|
173
168
|
return
|
|
174
169
|
self.view_mode = 'individual'
|
|
175
|
-
self.
|
|
176
|
-
self.refresh_current_view()
|
|
177
|
-
self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
|
|
178
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
170
|
+
self._current_refresh_and_thumb()
|
|
179
171
|
self.status_message_requested.emit("View mode: Individual layers")
|
|
180
|
-
|
|
172
|
+
|
|
173
|
+
def refresh_master_view(self):
|
|
174
|
+
if self.has_no_master_layer():
|
|
175
|
+
return
|
|
176
|
+
self.image_viewer.update_master_display()
|
|
177
|
+
self.image_viewer.refresh_display()
|
|
178
|
+
self.update_master_thumbnail()
|
|
179
|
+
|
|
180
|
+
def refresh_current_view(self):
|
|
181
|
+
if self.number_of_layers() == 0:
|
|
182
|
+
return
|
|
183
|
+
self.image_viewer.update_current_display()
|
|
184
|
+
self.image_viewer.refresh_display()
|
|
181
185
|
|
|
182
186
|
def start_temp_view(self):
|
|
183
|
-
if
|
|
184
|
-
self.
|
|
185
|
-
self.
|
|
186
|
-
|
|
187
|
-
self.
|
|
188
|
-
self.image_viewer.
|
|
189
|
-
self.
|
|
190
|
-
self.status_message_requested.emit("Temporary view: Individual layer (hold X)")
|
|
187
|
+
if self.view_mode == 'master':
|
|
188
|
+
self._current_refresh_and_thumb()
|
|
189
|
+
self.status_message_requested.emit("Temporary view: Individual layer")
|
|
190
|
+
else:
|
|
191
|
+
self._master_refresh_and_thumb()
|
|
192
|
+
self.image_viewer.strategy.brush_preview.hide()
|
|
193
|
+
self.status_message_requested.emit("Temporary view: Master")
|
|
191
194
|
|
|
192
195
|
def end_temp_view(self):
|
|
193
|
-
if self.
|
|
194
|
-
self.
|
|
195
|
-
self.image_viewer.update_brush_cursor()
|
|
196
|
-
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
197
|
-
self.highlight_thumbnail(self.current_layer_idx())
|
|
198
|
-
self.image_viewer.show_master()
|
|
199
|
-
self.refresh_master_view()
|
|
196
|
+
if self.view_mode == 'master':
|
|
197
|
+
self._master_refresh_and_thumb()
|
|
200
198
|
self.status_message_requested.emit("View mode: Master")
|
|
201
|
-
|
|
199
|
+
else:
|
|
200
|
+
self._current_refresh_and_thumb()
|
|
201
|
+
self.status_message_requested.emit("View: Individual layer")
|
|
202
202
|
|
|
203
203
|
def allow_cursor_preview(self):
|
|
204
|
-
return self.view_mode == 'master'
|
|
204
|
+
return self.view_mode == 'master'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904, W0108
|
|
2
2
|
from functools import partial
|
|
3
3
|
import numpy as np
|
|
4
4
|
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QMenu,
|
|
@@ -8,6 +8,7 @@ from PySide6.QtCore import Qt
|
|
|
8
8
|
from PySide6.QtGui import QGuiApplication
|
|
9
9
|
from .. config.constants import constants
|
|
10
10
|
from .. config.gui_constants import gui_constants
|
|
11
|
+
from .. gui.recent_file_manager import RecentFileManager
|
|
11
12
|
from .image_viewer import ImageViewer
|
|
12
13
|
from .shortcuts_help import ShortcutsHelp
|
|
13
14
|
from .brush import Brush
|
|
@@ -22,7 +23,7 @@ from .denoise_filter import DenoiseFilter
|
|
|
22
23
|
from .unsharp_mask_filter import UnsharpMaskFilter
|
|
23
24
|
from .white_balance_filter import WhiteBalanceFilter
|
|
24
25
|
from .vignetting_filter import VignettingFilter
|
|
25
|
-
from
|
|
26
|
+
from .transformation_manager import TransfromationManager
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
@@ -31,16 +32,17 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
31
32
|
LayerCollectionHandler.__init__(self, LayerCollection())
|
|
32
33
|
self._recent_file_manager = RecentFileManager("shinestacker-recent-images-files.txt")
|
|
33
34
|
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
34
|
-
self.undo_manager = UndoManager()
|
|
35
|
-
self.undo_action = None
|
|
36
|
-
self.redo_action = None
|
|
37
|
-
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
38
35
|
self.io_gui_handler = None
|
|
39
36
|
self.display_manager = None
|
|
40
37
|
self.brush = Brush()
|
|
41
38
|
self.brush_tool = BrushTool()
|
|
42
39
|
self.modified = False
|
|
43
40
|
self.mask_layer = None
|
|
41
|
+
self.transformation_manager = TransfromationManager(self)
|
|
42
|
+
self.undo_manager = UndoManager(self.transformation_manager)
|
|
43
|
+
self.undo_action = None
|
|
44
|
+
self.redo_action = None
|
|
45
|
+
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
44
46
|
self.filter_manager = FilterManager(self)
|
|
45
47
|
self.filter_manager.register_filter("Denoise", DenoiseFilter)
|
|
46
48
|
self.filter_manager.register_filter("Unsharp Mask", UnsharpMaskFilter)
|
|
@@ -238,8 +240,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
238
240
|
self.master_thumbnail_label, self.thumbnail_list, parent=self)
|
|
239
241
|
self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
|
|
240
242
|
self.display_manager.status_message_requested.connect(self.show_status_message)
|
|
241
|
-
self.display_manager.cursor_preview_state_changed.connect(
|
|
242
|
-
self.image_viewer.set_allow_cursor_preview)
|
|
243
243
|
self.io_gui_handler.status_message_requested.connect(self.show_status_message)
|
|
244
244
|
self.io_gui_handler.update_title_requested.connect(self.update_title)
|
|
245
245
|
self.io_gui_handler.mark_as_modified_requested.connect(self.mark_as_modified)
|
|
@@ -277,8 +277,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
277
277
|
|
|
278
278
|
file_menu.addAction("&Close", self.close_file, "Ctrl+W")
|
|
279
279
|
file_menu.addSeparator()
|
|
280
|
-
file_menu.addAction("&Import
|
|
281
|
-
file_menu.addAction("Import &EXIF
|
|
280
|
+
file_menu.addAction("&Import Frames", self.io_gui_handler.import_frames)
|
|
281
|
+
file_menu.addAction("Import &EXIF Data", self.io_gui_handler.select_exif_path)
|
|
282
282
|
|
|
283
283
|
edit_menu = menubar.addMenu("&Edit")
|
|
284
284
|
self.undo_action = QAction("Undo", self)
|
|
@@ -293,7 +293,21 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
293
293
|
edit_menu.addAction(self.redo_action)
|
|
294
294
|
edit_menu.addSeparator()
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
transf_menu = QMenu("&Transform")
|
|
297
|
+
rotate_90_cw_action = QAction(gui_constants.ROTATE_90_CW_LABEL, self)
|
|
298
|
+
transf_menu.addAction(rotate_90_cw_action)
|
|
299
|
+
rotate_90_cw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_cw())
|
|
300
|
+
rotate_90_ccw_action = QAction(gui_constants.ROTATE_90_CCW_LABEL, self)
|
|
301
|
+
transf_menu.addAction(rotate_90_ccw_action)
|
|
302
|
+
rotate_90_ccw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_ccw())
|
|
303
|
+
rotate_180_action = QAction(gui_constants.ROTATE_180_LABEL, self)
|
|
304
|
+
rotate_180_action.triggered.connect(lambda: self.transformation_manager.rotate_180())
|
|
305
|
+
transf_menu.addAction(rotate_180_action)
|
|
306
|
+
edit_menu.addMenu(transf_menu)
|
|
307
|
+
|
|
308
|
+
edit_menu.addSeparator()
|
|
309
|
+
|
|
310
|
+
copy_action = QAction("Copy Current Layer to Master", self)
|
|
297
311
|
copy_action.setShortcut("Ctrl+M")
|
|
298
312
|
copy_action.triggered.connect(self.copy_layer_to_master)
|
|
299
313
|
edit_menu.addAction(copy_action)
|
|
@@ -310,22 +324,22 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
310
324
|
|
|
311
325
|
view_strategy_menu = QMenu("View &Mode", view_menu)
|
|
312
326
|
|
|
313
|
-
self.
|
|
327
|
+
self.view_mode_actions = {
|
|
314
328
|
'overlaid': QAction("Overlaid", self),
|
|
315
|
-
'sidebyside': QAction("Side
|
|
329
|
+
'sidebyside': QAction("Side by Side", self),
|
|
316
330
|
'topbottom': QAction("Top-Bottom", self)
|
|
317
331
|
}
|
|
318
|
-
overlaid_mode = self.
|
|
332
|
+
overlaid_mode = self.view_mode_actions['overlaid']
|
|
319
333
|
overlaid_mode.setShortcut("Ctrl+1")
|
|
320
334
|
overlaid_mode.setCheckable(True)
|
|
321
335
|
overlaid_mode.triggered.connect(lambda: set_strategy('overlaid'))
|
|
322
336
|
view_strategy_menu.addAction(overlaid_mode)
|
|
323
|
-
side_by_side_mode = self.
|
|
337
|
+
side_by_side_mode = self.view_mode_actions['sidebyside']
|
|
324
338
|
side_by_side_mode.setShortcut("Ctrl+2")
|
|
325
339
|
side_by_side_mode.setCheckable(True)
|
|
326
340
|
side_by_side_mode.triggered.connect(lambda: set_strategy('sidebyside'))
|
|
327
341
|
view_strategy_menu.addAction(side_by_side_mode)
|
|
328
|
-
side_by_side_mode = self.
|
|
342
|
+
side_by_side_mode = self.view_mode_actions['topbottom']
|
|
329
343
|
side_by_side_mode.setShortcut("Ctrl+3")
|
|
330
344
|
side_by_side_mode.setCheckable(True)
|
|
331
345
|
side_by_side_mode.triggered.connect(lambda: set_strategy('topbottom'))
|
|
@@ -338,31 +352,40 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
338
352
|
self.view_master_action.setEnabled(enable_shortcuts)
|
|
339
353
|
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
340
354
|
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
341
|
-
for label, mode in self.
|
|
355
|
+
for label, mode in self.view_mode_actions.items():
|
|
342
356
|
mode.setEnabled(label != strategy)
|
|
343
357
|
mode.setChecked(label == strategy)
|
|
344
358
|
|
|
345
359
|
cursor_menu = view_menu.addMenu("Cursor Style")
|
|
346
360
|
|
|
347
|
-
|
|
348
|
-
|
|
361
|
+
self.cursor_style_actions = {
|
|
362
|
+
'brush': QAction("Simple Brush", self),
|
|
363
|
+
'preview': QAction("Brush Preview", self),
|
|
364
|
+
'outline': QAction("Outline Only", self)
|
|
365
|
+
}
|
|
366
|
+
brush_action = self.cursor_style_actions['brush']
|
|
349
367
|
brush_action.setCheckable(True)
|
|
350
|
-
brush_action.
|
|
351
|
-
brush_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('brush'))
|
|
368
|
+
brush_action.triggered.connect(lambda: set_cursor_style('brush'))
|
|
352
369
|
cursor_menu.addAction(brush_action)
|
|
353
370
|
|
|
354
|
-
preview_action =
|
|
371
|
+
preview_action = self.cursor_style_actions['preview']
|
|
355
372
|
preview_action.setCheckable(True)
|
|
356
|
-
preview_action.
|
|
357
|
-
preview_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('preview'))
|
|
373
|
+
preview_action.triggered.connect(lambda: set_cursor_style('preview'))
|
|
358
374
|
cursor_menu.addAction(preview_action)
|
|
359
375
|
|
|
360
|
-
outline_action =
|
|
376
|
+
outline_action = self.cursor_style_actions['outline']
|
|
361
377
|
outline_action.setCheckable(True)
|
|
362
|
-
outline_action.
|
|
363
|
-
outline_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('outline'))
|
|
378
|
+
outline_action.triggered.connect(lambda: set_cursor_style('outline'))
|
|
364
379
|
cursor_menu.addAction(outline_action)
|
|
365
380
|
|
|
381
|
+
def set_cursor_style(cursor_style):
|
|
382
|
+
self.image_viewer.set_cursor_style(cursor_style)
|
|
383
|
+
for label, style in self.cursor_style_actions.items():
|
|
384
|
+
style.setEnabled(label != cursor_style)
|
|
385
|
+
style.setChecked(label == cursor_style)
|
|
386
|
+
|
|
387
|
+
set_cursor_style(self.image_viewer.get_cursor_style())
|
|
388
|
+
|
|
366
389
|
cursor_group = QActionGroup(self)
|
|
367
390
|
cursor_group.addAction(preview_action)
|
|
368
391
|
cursor_group.addAction(outline_action)
|
|
@@ -432,13 +455,13 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
432
455
|
white_balance_action = QAction("White Balance", self)
|
|
433
456
|
white_balance_action.triggered.connect(self.white_balance)
|
|
434
457
|
filter_menu.addAction(white_balance_action)
|
|
435
|
-
vignetting_action = QAction("Vignetting
|
|
458
|
+
vignetting_action = QAction("Vignetting Correction", self)
|
|
436
459
|
vignetting_action.triggered.connect(self.vignetting_correction)
|
|
437
460
|
filter_menu.addAction(vignetting_action)
|
|
438
461
|
|
|
439
462
|
help_menu = menubar.addMenu("&Help")
|
|
440
463
|
help_menu.setObjectName("Help")
|
|
441
|
-
shortcuts_help_action = QAction("Shortcuts and
|
|
464
|
+
shortcuts_help_action = QAction("Shortcuts and Mouse", self)
|
|
442
465
|
|
|
443
466
|
def shortcuts_help():
|
|
444
467
|
self.shortcuts_help_dialog = ShortcutsHelp(self)
|
|
@@ -693,13 +716,11 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
693
716
|
|
|
694
717
|
def set_view_master(self):
|
|
695
718
|
self.display_manager.set_view_master()
|
|
696
|
-
self.display_manager.refresh_master_view()
|
|
697
719
|
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
698
720
|
self.highlight_master_thumbnail()
|
|
699
721
|
|
|
700
722
|
def set_view_individual(self):
|
|
701
723
|
self.display_manager.set_view_individual()
|
|
702
|
-
self.display_manager.refresh_current_view()
|
|
703
724
|
self.thumbnail_highlight = gui_constants.THUMB_MASTER_LO_COLOR
|
|
704
725
|
self.highlight_master_thumbnail()
|
|
705
726
|
|
|
@@ -35,8 +35,14 @@ class ImageViewer(QWidget):
|
|
|
35
35
|
self.strategy.show()
|
|
36
36
|
self.strategy.resize(self.size())
|
|
37
37
|
if not self.strategy.empty():
|
|
38
|
+
self.strategy.cleanup_brush_preview()
|
|
38
39
|
self.strategy.update_master_display()
|
|
39
40
|
self.strategy.update_current_display()
|
|
41
|
+
self.strategy.setup_brush_cursor()
|
|
42
|
+
self.strategy.update_brush_cursor()
|
|
43
|
+
self.strategy.show_master()
|
|
44
|
+
self.strategy.setFocus()
|
|
45
|
+
self.strategy.activateWindow()
|
|
40
46
|
|
|
41
47
|
def empty(self):
|
|
42
48
|
return self.strategy.empty()
|
|
@@ -143,6 +143,9 @@ class LayerCollectionHandler:
|
|
|
143
143
|
def has_master_layer(self):
|
|
144
144
|
return self.layer_collection.has_master_layer()
|
|
145
145
|
|
|
146
|
+
def set_layer(self, idx, img):
|
|
147
|
+
self.layer_collection.layer_stack[idx] = img
|
|
148
|
+
|
|
146
149
|
def set_layer_stack(self, stk):
|
|
147
150
|
self.layer_collection.set_layer_stack(stk)
|
|
148
151
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, E1101, R0904, R0912, R0914, R0902
|
|
2
|
-
from PySide6.QtGui import QPixmap
|
|
3
2
|
from PySide6.QtCore import Qt, QPointF, QEvent, QRectF
|
|
4
3
|
from .. config.gui_constants import gui_constants
|
|
5
4
|
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
@@ -23,12 +22,21 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
23
22
|
def get_master_view(self):
|
|
24
23
|
return self
|
|
25
24
|
|
|
25
|
+
def get_current_view(self):
|
|
26
|
+
return self
|
|
27
|
+
|
|
26
28
|
def get_master_scene(self):
|
|
27
29
|
return self.scene
|
|
28
30
|
|
|
31
|
+
def get_current_scene(self):
|
|
32
|
+
return self.scene
|
|
33
|
+
|
|
29
34
|
def get_master_pixmap(self):
|
|
30
35
|
return self.pixmap_item_master
|
|
31
36
|
|
|
37
|
+
def get_current_pixmap(self):
|
|
38
|
+
return self.pixmap_item_current
|
|
39
|
+
|
|
32
40
|
def get_views(self):
|
|
33
41
|
return [self]
|
|
34
42
|
|
|
@@ -41,66 +49,6 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
41
49
|
self.pixmap_item_current: self
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
def set_master_image(self, qimage):
|
|
45
|
-
self.status.set_master_image(qimage)
|
|
46
|
-
pixmap = self.status.pixmap_master
|
|
47
|
-
self.setSceneRect(QRectF(pixmap.rect()))
|
|
48
|
-
|
|
49
|
-
img_width, img_height = pixmap.width(), pixmap.height()
|
|
50
|
-
self.set_min_scale(min(gui_constants.MIN_ZOOMED_IMG_WIDTH / img_width,
|
|
51
|
-
gui_constants.MIN_ZOOMED_IMG_HEIGHT / img_height))
|
|
52
|
-
self.set_max_scale(gui_constants.MAX_ZOOMED_IMG_PX_SIZE)
|
|
53
|
-
self.set_zoom_factor(1.0)
|
|
54
|
-
self.fitInView(self.pixmap_item_master, Qt.KeepAspectRatio)
|
|
55
|
-
self.set_zoom_factor(self.get_current_scale())
|
|
56
|
-
self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
|
|
57
|
-
self.scale(self.zoom_factor(), self.zoom_factor())
|
|
58
|
-
|
|
59
|
-
def set_current_image(self, qimage):
|
|
60
|
-
self.status.set_current_image(qimage)
|
|
61
|
-
if self.empty():
|
|
62
|
-
self.setSceneRect(QRectF(self.status.pixmap_current.rect()))
|
|
63
|
-
|
|
64
|
-
def show_master(self):
|
|
65
|
-
self.pixmap_item_master.setVisible(True)
|
|
66
|
-
self.pixmap_item_current.setVisible(False)
|
|
67
|
-
|
|
68
|
-
def show_current(self):
|
|
69
|
-
self.pixmap_item_master.setVisible(False)
|
|
70
|
-
self.pixmap_item_current.setVisible(True)
|
|
71
|
-
|
|
72
|
-
def update_master_display(self):
|
|
73
|
-
if not self.empty():
|
|
74
|
-
master_qimage = self.numpy_to_qimage(
|
|
75
|
-
self.master_layer())
|
|
76
|
-
if master_qimage:
|
|
77
|
-
self.pixmap_item_master.setPixmap(QPixmap.fromImage(master_qimage))
|
|
78
|
-
|
|
79
|
-
def update_current_display(self):
|
|
80
|
-
if not self.empty() and self.number_of_layers() > 0:
|
|
81
|
-
current_qimage = self.numpy_to_qimage(
|
|
82
|
-
self.current_layer())
|
|
83
|
-
if current_qimage:
|
|
84
|
-
self.pixmap_item_current.setPixmap(QPixmap.fromImage(current_qimage))
|
|
85
|
-
|
|
86
|
-
def set_view_state(self, state):
|
|
87
|
-
self.status.set_state(state)
|
|
88
|
-
if state:
|
|
89
|
-
self.resetTransform()
|
|
90
|
-
self.scale(state['zoom'], state['zoom'])
|
|
91
|
-
self.horizontalScrollBar().setValue(state['h_scroll'])
|
|
92
|
-
self.verticalScrollBar().setValue(state['v_scroll'])
|
|
93
|
-
self.set_zoom_factor(state['zoom'])
|
|
94
|
-
|
|
95
|
-
def handle_key_press_event(self, event):
|
|
96
|
-
if event.key() == Qt.Key_X:
|
|
97
|
-
self.temp_view_requested.emit(True)
|
|
98
|
-
self.update_brush_cursor()
|
|
99
|
-
|
|
100
|
-
def handle_key_release_event(self, event):
|
|
101
|
-
if event.key() == Qt.Key_X:
|
|
102
|
-
self.temp_view_requested.emit(False)
|
|
103
|
-
|
|
104
52
|
# pylint: disable=C0103
|
|
105
53
|
def mousePressEvent(self, event):
|
|
106
54
|
self.mouse_press_event(event)
|
|
@@ -113,6 +61,7 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
113
61
|
self.mouse_release_event(event)
|
|
114
62
|
super().mouseReleaseEvent(event)
|
|
115
63
|
|
|
64
|
+
# pylint: enable=R0801
|
|
116
65
|
def wheelEvent(self, event):
|
|
117
66
|
if self.empty() or self.gesture_active:
|
|
118
67
|
return
|
|
@@ -120,8 +69,8 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
120
69
|
if self.control_pressed:
|
|
121
70
|
self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
|
|
122
71
|
else:
|
|
123
|
-
zoom_in_factor =
|
|
124
|
-
zoom_out_factor =
|
|
72
|
+
zoom_in_factor = gui_constants.ZOOM_IN_FACTOR
|
|
73
|
+
zoom_out_factor = gui_constants.ZOOM_OUT_FACTOR
|
|
125
74
|
current_scale = self.get_current_scale()
|
|
126
75
|
if event.angleDelta().y() > 0: # Zoom in
|
|
127
76
|
new_scale = current_scale * zoom_in_factor
|
|
@@ -146,11 +95,14 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
146
95
|
else:
|
|
147
96
|
self.zoom_out()
|
|
148
97
|
event.accept()
|
|
98
|
+
# pylint: disable=R0801
|
|
149
99
|
|
|
150
100
|
def enterEvent(self, event):
|
|
151
101
|
self.activateWindow()
|
|
152
102
|
self.setFocus()
|
|
153
|
-
if
|
|
103
|
+
if self.empty():
|
|
104
|
+
self.setCursor(Qt.ArrowCursor)
|
|
105
|
+
else:
|
|
154
106
|
self.setCursor(Qt.BlankCursor)
|
|
155
107
|
if self.brush_cursor:
|
|
156
108
|
self.brush_cursor.show()
|
|
@@ -162,6 +114,77 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
162
114
|
return self.handle_gesture_event(event)
|
|
163
115
|
return super().event(event)
|
|
164
116
|
|
|
117
|
+
def set_master_image(self, qimage):
|
|
118
|
+
self.status.set_master_image(qimage)
|
|
119
|
+
pixmap = self.status.pixmap_master
|
|
120
|
+
self.setSceneRect(QRectF(pixmap.rect()))
|
|
121
|
+
img_width, img_height = pixmap.width(), pixmap.height()
|
|
122
|
+
self.set_max_min_scales(img_width, img_height)
|
|
123
|
+
self.set_zoom_factor(1.0)
|
|
124
|
+
self.resetTransform()
|
|
125
|
+
self.fitInView(self.pixmap_item_master, Qt.KeepAspectRatio)
|
|
126
|
+
self.set_zoom_factor(self.get_current_scale())
|
|
127
|
+
self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
|
|
128
|
+
self.scale(self.zoom_factor(), self.zoom_factor())
|
|
129
|
+
self.centerOn(self.pixmap_item_master)
|
|
130
|
+
self.center_image(self)
|
|
131
|
+
self.update_cursor_pen_width()
|
|
132
|
+
|
|
133
|
+
def set_current_image(self, qimage):
|
|
134
|
+
self.status.set_current_image(qimage)
|
|
135
|
+
if self.empty():
|
|
136
|
+
self.setSceneRect(QRectF(self.status.pixmap_current.rect()))
|
|
137
|
+
self.update_cursor_pen_width()
|
|
138
|
+
|
|
139
|
+
def setup_brush_cursor(self):
|
|
140
|
+
super().setup_brush_cursor()
|
|
141
|
+
self.update_cursor_pen_width()
|
|
142
|
+
|
|
143
|
+
def show_master(self):
|
|
144
|
+
self.pixmap_item_master.setVisible(True)
|
|
145
|
+
self.pixmap_item_current.setVisible(False)
|
|
146
|
+
self.brush_preview.show()
|
|
147
|
+
|
|
148
|
+
def show_current(self):
|
|
149
|
+
self.pixmap_item_master.setVisible(False)
|
|
150
|
+
self.pixmap_item_current.setVisible(True)
|
|
151
|
+
self.brush_preview.hide()
|
|
152
|
+
|
|
153
|
+
def arrange_images(self):
|
|
154
|
+
if self.empty():
|
|
155
|
+
return
|
|
156
|
+
if self.pixmap_item_master.isVisible():
|
|
157
|
+
pixmap = self.pixmap_item_master.pixmap()
|
|
158
|
+
if not pixmap.isNull():
|
|
159
|
+
self.setSceneRect(QRectF(pixmap.rect()))
|
|
160
|
+
self.centerOn(self.pixmap_item_master)
|
|
161
|
+
self.center_image(self)
|
|
162
|
+
elif self.pixmap_item_current.isVisible():
|
|
163
|
+
pixmap = self.pixmap_item_current.pixmap()
|
|
164
|
+
if not pixmap.isNull():
|
|
165
|
+
self.setSceneRect(QRectF(pixmap.rect()))
|
|
166
|
+
self.centerOn(self.pixmap_item_current)
|
|
167
|
+
self.center_image(self)
|
|
168
|
+
current_scale = self.get_current_scale()
|
|
169
|
+
scale_factor = self.zoom_factor() / current_scale
|
|
170
|
+
self.scale(scale_factor, scale_factor)
|
|
171
|
+
|
|
172
|
+
def handle_key_press_event(self, event):
|
|
173
|
+
if event.key() in [Qt.Key_Up, Qt.Key_Down]:
|
|
174
|
+
return False
|
|
175
|
+
if event.key() == Qt.Key_X:
|
|
176
|
+
self.temp_view_requested.emit(True)
|
|
177
|
+
return False
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
def handle_key_release_event(self, event):
|
|
181
|
+
if event.key() in [Qt.Key_Up, Qt.Key_Down]:
|
|
182
|
+
return False
|
|
183
|
+
if event.key() == Qt.Key_X:
|
|
184
|
+
self.temp_view_requested.emit(False)
|
|
185
|
+
return False
|
|
186
|
+
return True
|
|
187
|
+
|
|
165
188
|
def handle_gesture_event(self, event):
|
|
166
189
|
if self.empty():
|
|
167
190
|
return False
|
|
@@ -190,23 +213,3 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
190
213
|
self.scroll_view(self, int(scaled_delta.x()), int(scaled_delta.y()))
|
|
191
214
|
elif pan_gesture.state() == Qt.GestureFinished:
|
|
192
215
|
self.gesture_active = False
|
|
193
|
-
|
|
194
|
-
def handle_pinch_gesture(self, pinch):
|
|
195
|
-
if pinch.state() == Qt.GestureStarted:
|
|
196
|
-
self.pinch_start_scale = self.get_current_scale()
|
|
197
|
-
self.pinch_center_view = pinch.centerPoint()
|
|
198
|
-
self.pinch_center_scene = self.mapToScene(self.pinch_center_view.toPoint())
|
|
199
|
-
self.gesture_active = True
|
|
200
|
-
elif pinch.state() == Qt.GestureUpdated:
|
|
201
|
-
new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
|
|
202
|
-
new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
|
|
203
|
-
if abs(new_scale - self.get_current_scale()) > 0.01:
|
|
204
|
-
self.resetTransform()
|
|
205
|
-
self.scale(new_scale, new_scale)
|
|
206
|
-
self.set_zoom_factor(new_scale)
|
|
207
|
-
new_center = self.mapToScene(self.pinch_center_view.toPoint())
|
|
208
|
-
delta = self.pinch_center_scene - new_center
|
|
209
|
-
self.translate(delta.x(), delta.y())
|
|
210
|
-
self.update_brush_cursor()
|
|
211
|
-
elif pinch.state() in (Qt.GestureFinished, Qt.GestureCanceled):
|
|
212
|
-
self.gesture_active = False
|