shinestacker 1.3.0__py3-none-any.whl → 1.4.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 (50) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +229 -41
  3. shinestacker/algorithms/align_auto.py +15 -3
  4. shinestacker/algorithms/align_parallel.py +81 -25
  5. shinestacker/algorithms/balance.py +23 -13
  6. shinestacker/algorithms/base_stack_algo.py +14 -20
  7. shinestacker/algorithms/depth_map.py +9 -14
  8. shinestacker/algorithms/noise_detection.py +3 -1
  9. shinestacker/algorithms/pyramid.py +8 -22
  10. shinestacker/algorithms/pyramid_auto.py +5 -14
  11. shinestacker/algorithms/pyramid_tiles.py +18 -20
  12. shinestacker/algorithms/stack_framework.py +1 -1
  13. shinestacker/algorithms/utils.py +37 -10
  14. shinestacker/algorithms/vignetting.py +2 -0
  15. shinestacker/app/gui_utils.py +10 -0
  16. shinestacker/app/main.py +3 -1
  17. shinestacker/app/project.py +3 -1
  18. shinestacker/app/retouch.py +3 -1
  19. shinestacker/config/gui_constants.py +2 -2
  20. shinestacker/core/core_utils.py +10 -1
  21. shinestacker/gui/action_config.py +172 -7
  22. shinestacker/gui/action_config_dialog.py +443 -452
  23. shinestacker/gui/colors.py +1 -0
  24. shinestacker/gui/folder_file_selection.py +5 -0
  25. shinestacker/gui/gui_run.py +2 -2
  26. shinestacker/gui/main_window.py +18 -9
  27. shinestacker/gui/menu_manager.py +26 -2
  28. shinestacker/gui/new_project.py +5 -5
  29. shinestacker/gui/project_controller.py +4 -0
  30. shinestacker/gui/project_editor.py +6 -4
  31. shinestacker/gui/recent_file_manager.py +93 -0
  32. shinestacker/gui/sys_mon.py +24 -23
  33. shinestacker/retouch/base_filter.py +5 -5
  34. shinestacker/retouch/brush_preview.py +3 -0
  35. shinestacker/retouch/brush_tool.py +11 -11
  36. shinestacker/retouch/display_manager.py +21 -37
  37. shinestacker/retouch/image_editor_ui.py +129 -71
  38. shinestacker/retouch/image_view_status.py +61 -0
  39. shinestacker/retouch/image_viewer.py +89 -431
  40. shinestacker/retouch/io_gui_handler.py +12 -2
  41. shinestacker/retouch/overlaid_view.py +212 -0
  42. shinestacker/retouch/shortcuts_help.py +13 -3
  43. shinestacker/retouch/sidebyside_view.py +479 -0
  44. shinestacker/retouch/view_strategy.py +466 -0
  45. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/METADATA +1 -1
  46. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/RECORD +50 -45
  47. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/WHEEL +0 -0
  48. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/entry_points.txt +0 -0
  49. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/licenses/LICENSE +0 -0
  50. {shinestacker-1.3.0.dist-info → shinestacker-1.4.0.dist-info}/top_level.txt +0 -0
@@ -44,26 +44,21 @@ class DisplayManager(QObject, LayerCollectionHandler):
44
44
 
45
45
  def process_pending_updates(self):
46
46
  if self.needs_update:
47
- self.display_master_layer()
47
+ self.refresh_master_view()
48
48
  self.needs_update = False
49
49
 
50
- def display_image(self, img):
51
- if img is None:
52
- self.image_viewer.clear_image()
53
- else:
54
- self.image_viewer.set_image(self.numpy_to_qimage(img))
55
-
56
- def display_current_layer(self):
57
- self.display_image(self.current_layer())
58
-
59
- def display_master_layer(self):
60
- self.display_image(self.master_layer())
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()
61
56
 
62
- def display_current_view(self):
63
- if self.temp_view_individual or self.view_mode == 'individual':
64
- self.display_current_layer()
65
- else:
66
- self.display_master_layer()
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()
67
62
 
68
63
  def create_thumbnail(self, layer):
69
64
  source_layer = (layer // 256).astype(np.uint8) if layer.dtype == np.uint16 else layer
@@ -167,22 +162,22 @@ class DisplayManager(QObject, LayerCollectionHandler):
167
162
  return
168
163
  self.view_mode = 'master'
169
164
  self.temp_view_individual = False
170
- self.display_master_layer()
165
+ self.refresh_master_view()
171
166
  self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
172
167
  self.highlight_thumbnail(self.current_layer_idx())
173
168
  self.status_message_requested.emit("View mode: Master")
174
- self.cursor_preview_state_changed.emit(True) # True = allow preview
169
+ self.cursor_preview_state_changed.emit(True)
175
170
 
176
171
  def set_view_individual(self):
177
172
  if self.has_no_master_layer():
178
173
  return
179
174
  self.view_mode = 'individual'
180
175
  self.temp_view_individual = False
181
- self.display_current_layer()
176
+ self.refresh_current_view()
182
177
  self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
183
178
  self.highlight_thumbnail(self.current_layer_idx())
184
179
  self.status_message_requested.emit("View mode: Individual layers")
185
- self.cursor_preview_state_changed.emit(False) # False = no preview
180
+ self.cursor_preview_state_changed.emit(False)
186
181
 
187
182
  def start_temp_view(self):
188
183
  if not self.temp_view_individual and self.view_mode == 'master':
@@ -190,7 +185,8 @@ class DisplayManager(QObject, LayerCollectionHandler):
190
185
  self.image_viewer.update_brush_cursor()
191
186
  self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
192
187
  self.highlight_thumbnail(self.current_layer_idx())
193
- self.display_current_layer()
188
+ self.image_viewer.show_current()
189
+ self.refresh_current_view()
194
190
  self.status_message_requested.emit("Temporary view: Individual layer (hold X)")
195
191
 
196
192
  def end_temp_view(self):
@@ -199,22 +195,10 @@ class DisplayManager(QObject, LayerCollectionHandler):
199
195
  self.image_viewer.update_brush_cursor()
200
196
  self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
201
197
  self.highlight_thumbnail(self.current_layer_idx())
202
- self.display_master_layer()
198
+ self.image_viewer.show_master()
199
+ self.refresh_master_view()
203
200
  self.status_message_requested.emit("View mode: Master")
204
- self.cursor_preview_state_changed.emit(True) # Restore preview
205
-
206
- def numpy_to_qimage(self, array):
207
- if array.dtype == np.uint16:
208
- array = np.right_shift(array, 8).astype(np.uint8)
209
- if array.ndim == 2:
210
- height, width = array.shape
211
- return QImage(memoryview(array), width, height, width, QImage.Format_Grayscale8)
212
- if array.ndim == 3:
213
- height, width, _ = array.shape
214
- if not array.flags['C_CONTIGUOUS']:
215
- array = np.ascontiguousarray(array)
216
- return QImage(memoryview(array), width, height, 3 * width, QImage.Format_RGB888)
217
- return QImage()
201
+ self.cursor_preview_state_changed.emit(True)
218
202
 
219
203
  def allow_cursor_preview(self):
220
204
  return self.view_mode == 'master' and not self.temp_view_individual
@@ -1,6 +1,7 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904
2
+ from functools import partial
2
3
  import numpy as np
3
- from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel,
4
+ from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QMenu,
4
5
  QListWidget, QSlider, QMainWindow, QMessageBox)
5
6
  from PySide6.QtGui import QShortcut, QKeySequence, QAction, QActionGroup
6
7
  from PySide6.QtCore import Qt
@@ -21,12 +22,14 @@ from .denoise_filter import DenoiseFilter
21
22
  from .unsharp_mask_filter import UnsharpMaskFilter
22
23
  from .white_balance_filter import WhiteBalanceFilter
23
24
  from .vignetting_filter import VignettingFilter
25
+ from .. gui.recent_file_manager import RecentFileManager
24
26
 
25
27
 
26
28
  class ImageEditorUI(QMainWindow, LayerCollectionHandler):
27
29
  def __init__(self):
28
30
  QMainWindow.__init__(self)
29
31
  LayerCollectionHandler.__init__(self, LayerCollection())
32
+ self._recent_file_manager = RecentFileManager("shinestacker-recent-images-files.txt")
30
33
  self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
31
34
  self.undo_manager = UndoManager()
32
35
  self.undo_action = None
@@ -53,12 +56,12 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
53
56
  self.setCentralWidget(central_widget)
54
57
  layout = QHBoxLayout(central_widget)
55
58
  self.image_viewer = ImageViewer(self.layer_collection)
56
- self.image_viewer.temp_view_requested.connect(self.handle_temp_view)
57
- self.image_viewer.brush_operation_started.connect(self.begin_copy_brush_area)
58
- self.image_viewer.brush_operation_continued.connect(self.continue_copy_brush_area)
59
- self.image_viewer.brush_operation_ended.connect(self.end_copy_brush_area)
60
- self.image_viewer.brush_size_change_requested.connect(self.handle_brush_size_change)
61
- self.image_viewer.setFocusPolicy(Qt.StrongFocus)
59
+ self.image_viewer.connect_signals(
60
+ self.handle_temp_view,
61
+ self.begin_copy_brush_area,
62
+ self.continue_copy_brush_area,
63
+ self.end_copy_brush_area,
64
+ self.handle_brush_size_change)
62
65
  side_panel = QWidget()
63
66
  side_layout = QVBoxLayout(side_panel)
64
67
  side_layout.setContentsMargins(0, 0, 0, 0)
@@ -115,9 +118,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
115
118
  brush_layout.addWidget(self.flow_slider)
116
119
 
117
120
  side_layout.addWidget(brush_panel)
118
- self.brush_preview = QLabel()
119
- self.brush_preview.setContentsMargins(0, 0, 0, 0)
120
- self.brush_preview.setStyleSheet("""
121
+ self.brush_preview_widget = QLabel()
122
+ self.brush_preview_widget.setContentsMargins(0, 0, 0, 0)
123
+ self.brush_preview_widget.setStyleSheet("""
121
124
  QLabel {
122
125
  background-color: #f0f0f0;
123
126
  border: 1px solid #ccc;
@@ -126,9 +129,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
126
129
  margin: 0px;
127
130
  }
128
131
  """)
129
- self.brush_preview.setAlignment(Qt.AlignCenter)
130
- self.brush_preview.setFixedHeight(100)
131
- brush_layout.addWidget(self.brush_preview)
132
+ self.brush_preview_widget.setAlignment(Qt.AlignCenter)
133
+ self.brush_preview_widget.setFixedHeight(100)
134
+ brush_layout.addWidget(self.brush_preview_widget)
132
135
  side_layout.addWidget(brush_panel)
133
136
 
134
137
  master_label = QLabel("Master")
@@ -236,22 +239,27 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
236
239
  self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
237
240
  self.display_manager.status_message_requested.connect(self.show_status_message)
238
241
  self.display_manager.cursor_preview_state_changed.connect(
239
- lambda state: setattr(self.image_viewer, 'allow_cursor_preview', state))
242
+ self.image_viewer.set_allow_cursor_preview)
240
243
  self.io_gui_handler.status_message_requested.connect(self.show_status_message)
241
244
  self.io_gui_handler.update_title_requested.connect(self.update_title)
242
245
  self.io_gui_handler.mark_as_modified_requested.connect(self.mark_as_modified)
243
246
  self.io_gui_handler.change_layer_requested.connect(self.change_layer)
244
- self.brush_tool.setup_ui(self.brush, self.brush_preview, self.image_viewer,
247
+ self.io_gui_handler.add_recent_file_requested.connect(self.add_recent_file)
248
+ self.brush_tool.setup_ui(self.brush, self.brush_preview_widget, self.image_viewer,
245
249
  self.brush_size_slider, self.hardness_slider, self.opacity_slider,
246
250
  self.flow_slider)
247
- self.image_viewer.brush = self.brush_tool.brush
251
+ self.image_viewer.set_brush(self.brush_tool.brush)
252
+ self.image_viewer.set_preview_brush(self.brush_tool.brush)
248
253
  self.brush_tool.update_brush_thumb()
249
254
  self.io_gui_handler.setup_ui(self.display_manager, self.image_viewer)
250
- self.image_viewer.display_manager = self.display_manager
255
+ self.image_viewer.set_display_manager(self.display_manager)
251
256
 
252
257
  menubar = self.menuBar()
253
258
  file_menu = menubar.addMenu("&File")
254
259
  file_menu.addAction("&Open...", self.io_gui_handler.open_file, "Ctrl+O")
260
+ self.recent_files_menu = QMenu("Open &Recent", file_menu)
261
+ file_menu.addMenu(self.recent_files_menu)
262
+ self.update_recent_files()
255
263
  self.save_action = QAction("&Save", self)
256
264
  self.save_action.setShortcut("Ctrl+S")
257
265
  self.save_action.triggered.connect(self.io_gui_handler.save_file)
@@ -300,6 +308,69 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
300
308
 
301
309
  view_menu.addSeparator()
302
310
 
311
+ view_strategy_menu = QMenu("View &Mode", view_menu)
312
+
313
+ self.view_action_modes = {
314
+ 'overlaid': QAction("Overlaid", self),
315
+ 'sidebyside': QAction("Side By Side", self),
316
+ 'topbottom': QAction("Top-Bottom", self)
317
+ }
318
+ overlaid_mode = self.view_action_modes['overlaid']
319
+ overlaid_mode.setShortcut("Ctrl+1")
320
+ overlaid_mode.setCheckable(True)
321
+ overlaid_mode.triggered.connect(lambda: set_strategy('overlaid'))
322
+ view_strategy_menu.addAction(overlaid_mode)
323
+ side_by_side_mode = self.view_action_modes['sidebyside']
324
+ side_by_side_mode.setShortcut("Ctrl+2")
325
+ side_by_side_mode.setCheckable(True)
326
+ side_by_side_mode.triggered.connect(lambda: set_strategy('sidebyside'))
327
+ view_strategy_menu.addAction(side_by_side_mode)
328
+ side_by_side_mode = self.view_action_modes['topbottom']
329
+ side_by_side_mode.setShortcut("Ctrl+3")
330
+ side_by_side_mode.setCheckable(True)
331
+ side_by_side_mode.triggered.connect(lambda: set_strategy('topbottom'))
332
+ view_strategy_menu.addAction(side_by_side_mode)
333
+ view_menu.addMenu(view_strategy_menu)
334
+
335
+ def set_strategy(strategy):
336
+ self.image_viewer.set_strategy(strategy)
337
+ enable_shortcuts = strategy == 'overlaid'
338
+ self.view_master_action.setEnabled(enable_shortcuts)
339
+ self.view_individual_action.setEnabled(enable_shortcuts)
340
+ self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
341
+ for label, mode in self.view_action_modes.items():
342
+ mode.setEnabled(label != strategy)
343
+ mode.setChecked(label == strategy)
344
+
345
+ cursor_menu = view_menu.addMenu("Cursor Style")
346
+
347
+ cursor_stype = self.image_viewer.get_cursor_style()
348
+ brush_action = QAction("Simple Brush", self)
349
+ brush_action.setCheckable(True)
350
+ brush_action.setChecked(cursor_stype == 'brush')
351
+ brush_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('brush'))
352
+ cursor_menu.addAction(brush_action)
353
+
354
+ preview_action = QAction("Brush Preview", self)
355
+ preview_action.setCheckable(True)
356
+ preview_action.setChecked(cursor_stype == 'preview')
357
+ preview_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('preview'))
358
+ cursor_menu.addAction(preview_action)
359
+
360
+ outline_action = QAction("Outline Only", self)
361
+ outline_action.setCheckable(True)
362
+ outline_action.setChecked(cursor_stype == 'outline')
363
+ outline_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('outline'))
364
+ cursor_menu.addAction(outline_action)
365
+
366
+ cursor_group = QActionGroup(self)
367
+ cursor_group.addAction(preview_action)
368
+ cursor_group.addAction(outline_action)
369
+ cursor_group.addAction(brush_action)
370
+ cursor_group.setExclusive(True)
371
+
372
+ view_menu.addSeparator()
373
+
303
374
  zoom_in_action = QAction("Zoom In", self)
304
375
  zoom_in_action.setShortcut("Ctrl++")
305
376
  zoom_in_action.triggered.connect(self.image_viewer.zoom_in)
@@ -316,27 +387,30 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
316
387
  view_menu.addAction(adapt_action)
317
388
 
318
389
  actual_size_action = QAction("Actual Size", self)
319
- actual_size_action.setShortcut("Ctrl+=")
390
+ actual_size_action.setShortcut("Ctrl+R")
320
391
  actual_size_action.triggered.connect(self.image_viewer.actual_size)
321
392
  view_menu.addAction(actual_size_action)
322
393
  view_menu.addSeparator()
323
394
 
324
- view_master_action = QAction("View Master", self)
325
- view_master_action.setShortcut("M")
326
- view_master_action.triggered.connect(self.set_view_master)
327
- view_menu.addAction(view_master_action)
328
-
329
- view_individual_action = QAction("View Individual", self)
330
- view_individual_action.setShortcut("L")
331
- view_individual_action.triggered.connect(self.set_view_individual)
332
- view_menu.addAction(view_individual_action)
333
-
334
- toggle_view_master_individual_action = QAction("Toggle Master/Individual", self)
335
- toggle_view_master_individual_action.setShortcut("T")
336
- toggle_view_master_individual_action.triggered.connect(self.toggle_view_master_individual)
337
- view_menu.addAction(toggle_view_master_individual_action)
395
+ self.view_master_action = QAction("View Master", self)
396
+ self.view_master_action.setShortcut("M")
397
+ self.view_master_action.triggered.connect(self.set_view_master)
398
+ view_menu.addAction(self.view_master_action)
399
+
400
+ self.view_individual_action = QAction("View Individual", self)
401
+ self.view_individual_action.setShortcut("L")
402
+ self.view_individual_action.triggered.connect(self.set_view_individual)
403
+ view_menu.addAction(self.view_individual_action)
404
+
405
+ self.toggle_view_master_individual_action = QAction("Toggle Master/Individual", self)
406
+ self.toggle_view_master_individual_action.setShortcut("T")
407
+ self.toggle_view_master_individual_action.triggered.connect(
408
+ self.toggle_view_master_individual)
409
+ view_menu.addAction(self.toggle_view_master_individual_action)
338
410
  view_menu.addSeparator()
339
411
 
412
+ set_strategy('overlaid')
413
+
340
414
  sort_asc_action = QAction("Sort Layers A-Z", self)
341
415
  sort_asc_action.triggered.connect(lambda: self.sort_layers('asc'))
342
416
  view_menu.addAction(sort_asc_action)
@@ -347,32 +421,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
347
421
 
348
422
  view_menu.addSeparator()
349
423
 
350
- cursor_menu = view_menu.addMenu("Cursor Style")
351
-
352
- brush_action = QAction("Simple Brush", self)
353
- brush_action.setCheckable(True)
354
- brush_action.setChecked(self.image_viewer.cursor_style == 'brush')
355
- brush_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('brush'))
356
- cursor_menu.addAction(brush_action)
357
-
358
- preview_action = QAction("Brush Preview", self)
359
- preview_action.setCheckable(True)
360
- preview_action.setChecked(self.image_viewer.cursor_style == 'preview')
361
- preview_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('preview'))
362
- cursor_menu.addAction(preview_action)
363
-
364
- outline_action = QAction("Outline Only", self)
365
- outline_action.setCheckable(True)
366
- outline_action.setChecked(self.image_viewer.cursor_style == 'outline')
367
- outline_action.triggered.connect(lambda: self.image_viewer.set_cursor_style('outline'))
368
- cursor_menu.addAction(outline_action)
369
-
370
- cursor_group = QActionGroup(self)
371
- cursor_group.addAction(preview_action)
372
- cursor_group.addAction(outline_action)
373
- cursor_group.addAction(brush_action)
374
- cursor_group.setExclusive(True)
375
-
376
424
  filter_menu = menubar.addMenu("&Filter")
377
425
  filter_menu.setObjectName("Filter")
378
426
  denoise_action = QAction("Denoise", self)
@@ -415,6 +463,19 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
415
463
  title += " *"
416
464
  self.window().setWindowTitle(title)
417
465
 
466
+ def update_recent_files(self):
467
+ self.recent_files_menu.clear()
468
+ recent_files = self._recent_file_manager.get_files_with_display_names()
469
+ for file_path, display_name in recent_files.items():
470
+ action = self.recent_files_menu.addAction(display_name)
471
+ action.setData(file_path)
472
+ action.triggered.connect(partial(self.io_gui_handler.open_file, file_path))
473
+ self.recent_files_menu.setEnabled(len(recent_files) > 0)
474
+
475
+ def add_recent_file(self, file_path):
476
+ self._recent_file_manager.add_file(file_path)
477
+ self.update_recent_files()
478
+
418
479
  def show_status_message(self, message):
419
480
  self.statusBar().showMessage(message)
420
481
 
@@ -440,7 +501,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
440
501
 
441
502
  # pylint: disable=C0103
442
503
  def keyPressEvent(self, event):
443
- if self.image_viewer.empty:
504
+ if self.image_viewer.empty():
444
505
  return
445
506
  if event.text() == '[':
446
507
  self.brush_tool.decrease_brush_size()
@@ -467,14 +528,12 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
467
528
 
468
529
  def change_layer(self, layer_idx):
469
530
  if 0 <= layer_idx < self.number_of_layers():
470
- view_state = self.image_viewer.get_view_state()
471
531
  self.set_current_layer_idx(layer_idx)
472
- self.display_manager.display_current_view()
473
- self.image_viewer.set_view_state(view_state)
532
+ self.display_manager.refresh_current_view()
474
533
  self.thumbnail_list.setCurrentRow(layer_idx)
475
534
  self.thumbnail_list.setFocus()
476
535
  self.image_viewer.update_brush_cursor()
477
- self.image_viewer.setFocus()
536
+ self.image_viewer.strategy.setFocus()
478
537
 
479
538
  def prev_layer(self):
480
539
  if self.layer_stack() is not None:
@@ -503,8 +562,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
503
562
  if reply == QMessageBox.Yes:
504
563
  self.set_master_layer(self.current_layer().copy())
505
564
  self.master_layer().setflags(write=True)
506
- self.display_manager.display_current_view()
507
- self.display_manager.update_thumbnails()
565
+ self.display_manager.refresh_master_view()
508
566
  self.mark_as_modified()
509
567
  self.statusBar().showMessage(f"Copied layer {self.current_layer_idx() + 1} to master")
510
568
 
@@ -540,8 +598,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
540
598
 
541
599
  def end_copy_brush_area(self):
542
600
  if self.display_manager.update_timer.isActive():
543
- self.display_manager.display_master_layer()
544
- self.display_manager.update_master_thumbnail()
601
+ self.display_manager.refresh_master_view()
545
602
  self.undo_manager.save_undo_state(self.master_layer_copy(), 'Brush Stroke')
546
603
  self.display_manager.update_timer.stop()
547
604
  self.mark_as_modified()
@@ -629,17 +686,20 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
629
686
 
630
687
  def close_file(self):
631
688
  if self.check_unsaved_changes():
689
+ self.image_viewer.reset_zoom()
632
690
  self.io_gui_handler.close_file()
633
691
  self.set_master_layer(None)
634
692
  self.mark_as_modified(False)
635
693
 
636
694
  def set_view_master(self):
637
695
  self.display_manager.set_view_master()
696
+ self.display_manager.refresh_master_view()
638
697
  self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
639
698
  self.highlight_master_thumbnail()
640
699
 
641
700
  def set_view_individual(self):
642
701
  self.display_manager.set_view_individual()
702
+ self.display_manager.refresh_current_view()
643
703
  self.thumbnail_highlight = gui_constants.THUMB_MASTER_LO_COLOR
644
704
  self.highlight_master_thumbnail()
645
705
 
@@ -663,15 +723,13 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
663
723
 
664
724
  def undo(self):
665
725
  if self.undo_manager.undo(self.master_layer()):
666
- self.display_manager.display_current_view()
667
- self.display_manager.update_master_thumbnail()
726
+ self.display_manager.refresh_master_view()
668
727
  self.mark_as_modified()
669
728
  self.statusBar().showMessage("Undo applied", 2000)
670
729
 
671
730
  def redo(self):
672
731
  if self.undo_manager.redo(self.master_layer()):
673
- self.display_manager.display_current_view()
674
- self.display_manager.update_master_thumbnail()
732
+ self.display_manager.refresh_master_view()
675
733
  self.mark_as_modified()
676
734
  self.statusBar().showMessage("Redo applied", 2000)
677
735
 
@@ -0,0 +1,61 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0902
2
+ from PySide6.QtCore import QObject, QRectF
3
+ from PySide6.QtGui import QPixmap
4
+
5
+
6
+ class ImageViewStatus(QObject):
7
+ def __init__(self, parent=None):
8
+ super().__init__(parent)
9
+ self.pixmap_master = QPixmap()
10
+ self.pixmap_current = QPixmap()
11
+ self.zoom_factor = 1.0
12
+ self.min_scale = 0.0
13
+ self.max_scale = 0.0
14
+ self.h_scroll = 0
15
+ self.v_scroll = 0
16
+ self.scene_rect = QRectF()
17
+
18
+ def empty(self):
19
+ return self.pixmap_master.isNull()
20
+
21
+ def set_master_image(self, qimage):
22
+ pixmap = QPixmap.fromImage(qimage)
23
+ self.pixmap_master = pixmap
24
+ if not self.empty():
25
+ self.scene_rect = QRectF(pixmap.rect())
26
+
27
+ def set_current_image(self, qimage):
28
+ pixmap = QPixmap.fromImage(qimage)
29
+ self.pixmap_current = pixmap
30
+
31
+ def clear(self):
32
+ self.pixmap_master = QPixmap()
33
+ self.pixmap_current = QPixmap()
34
+ self.zoom_factor = 1.0
35
+ self.min_scale = 0.0
36
+ self.max_scale = 0.0
37
+ self.h_scroll = 0
38
+ self.v_scroll = 0
39
+ self.scene_rect = QRectF()
40
+
41
+ def get_state(self):
42
+ return {
43
+ 'zoom': self.zoom_factor,
44
+ 'h_scroll': self.h_scroll,
45
+ 'v_scroll': self.v_scroll
46
+ }
47
+
48
+ def set_state(self, state):
49
+ if state:
50
+ self.zoom_factor = state['zoom']
51
+ self.h_scroll = state['h_scroll']
52
+ self.v_scroll = state['v_scroll']
53
+
54
+ def set_zoom_factor(self, zoom_factor):
55
+ self.zoom_factor = zoom_factor
56
+
57
+ def set_min_scale(self, min_scale):
58
+ self.min_scale = min_scale
59
+
60
+ def set_max_scale(self, min_scale):
61
+ self.max_scale = min_scale