shinestacker 1.3.1__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.

Files changed (38) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +198 -18
  3. shinestacker/algorithms/align_parallel.py +17 -1
  4. shinestacker/algorithms/balance.py +23 -13
  5. shinestacker/algorithms/noise_detection.py +3 -1
  6. shinestacker/algorithms/utils.py +21 -10
  7. shinestacker/algorithms/vignetting.py +2 -0
  8. shinestacker/app/main.py +1 -1
  9. shinestacker/config/gui_constants.py +7 -2
  10. shinestacker/core/core_utils.py +10 -1
  11. shinestacker/gui/action_config.py +172 -7
  12. shinestacker/gui/action_config_dialog.py +246 -285
  13. shinestacker/gui/gui_run.py +2 -2
  14. shinestacker/gui/main_window.py +14 -5
  15. shinestacker/gui/menu_manager.py +26 -2
  16. shinestacker/gui/project_controller.py +4 -0
  17. shinestacker/gui/recent_file_manager.py +93 -0
  18. shinestacker/retouch/base_filter.py +13 -15
  19. shinestacker/retouch/brush_preview.py +3 -1
  20. shinestacker/retouch/brush_tool.py +11 -11
  21. shinestacker/retouch/display_manager.py +43 -59
  22. shinestacker/retouch/image_editor_ui.py +161 -82
  23. shinestacker/retouch/image_view_status.py +65 -0
  24. shinestacker/retouch/image_viewer.py +95 -431
  25. shinestacker/retouch/io_gui_handler.py +12 -2
  26. shinestacker/retouch/layer_collection.py +3 -0
  27. shinestacker/retouch/overlaid_view.py +215 -0
  28. shinestacker/retouch/shortcuts_help.py +13 -3
  29. shinestacker/retouch/sidebyside_view.py +477 -0
  30. shinestacker/retouch/transformation_manager.py +43 -0
  31. shinestacker/retouch/undo_manager.py +22 -3
  32. shinestacker/retouch/view_strategy.py +557 -0
  33. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/METADATA +7 -7
  34. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/RECORD +38 -32
  35. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/WHEEL +0 -0
  36. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/entry_points.txt +0 -0
  37. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/licenses/LICENSE +0 -0
  38. {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,14 @@
1
- # pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0902, R0914, R0915, R0904, W0108
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
7
8
  from PySide6.QtGui import QGuiApplication
8
9
  from .. config.constants import constants
9
10
  from .. config.gui_constants import gui_constants
11
+ from .. gui.recent_file_manager import RecentFileManager
10
12
  from .image_viewer import ImageViewer
11
13
  from .shortcuts_help import ShortcutsHelp
12
14
  from .brush import Brush
@@ -21,23 +23,26 @@ from .denoise_filter import DenoiseFilter
21
23
  from .unsharp_mask_filter import UnsharpMaskFilter
22
24
  from .white_balance_filter import WhiteBalanceFilter
23
25
  from .vignetting_filter import VignettingFilter
26
+ from .transformation_manager import TransfromationManager
24
27
 
25
28
 
26
29
  class ImageEditorUI(QMainWindow, LayerCollectionHandler):
27
30
  def __init__(self):
28
31
  QMainWindow.__init__(self)
29
32
  LayerCollectionHandler.__init__(self, LayerCollection())
33
+ self._recent_file_manager = RecentFileManager("shinestacker-recent-images-files.txt")
30
34
  self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
31
- self.undo_manager = UndoManager()
32
- self.undo_action = None
33
- self.redo_action = None
34
- self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
35
35
  self.io_gui_handler = None
36
36
  self.display_manager = None
37
37
  self.brush = Brush()
38
38
  self.brush_tool = BrushTool()
39
39
  self.modified = False
40
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)
41
46
  self.filter_manager = FilterManager(self)
42
47
  self.filter_manager.register_filter("Denoise", DenoiseFilter)
43
48
  self.filter_manager.register_filter("Unsharp Mask", UnsharpMaskFilter)
@@ -53,12 +58,12 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
53
58
  self.setCentralWidget(central_widget)
54
59
  layout = QHBoxLayout(central_widget)
55
60
  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)
61
+ self.image_viewer.connect_signals(
62
+ self.handle_temp_view,
63
+ self.begin_copy_brush_area,
64
+ self.continue_copy_brush_area,
65
+ self.end_copy_brush_area,
66
+ self.handle_brush_size_change)
62
67
  side_panel = QWidget()
63
68
  side_layout = QVBoxLayout(side_panel)
64
69
  side_layout.setContentsMargins(0, 0, 0, 0)
@@ -115,9 +120,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
115
120
  brush_layout.addWidget(self.flow_slider)
116
121
 
117
122
  side_layout.addWidget(brush_panel)
118
- self.brush_preview = QLabel()
119
- self.brush_preview.setContentsMargins(0, 0, 0, 0)
120
- self.brush_preview.setStyleSheet("""
123
+ self.brush_preview_widget = QLabel()
124
+ self.brush_preview_widget.setContentsMargins(0, 0, 0, 0)
125
+ self.brush_preview_widget.setStyleSheet("""
121
126
  QLabel {
122
127
  background-color: #f0f0f0;
123
128
  border: 1px solid #ccc;
@@ -126,9 +131,9 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
126
131
  margin: 0px;
127
132
  }
128
133
  """)
129
- self.brush_preview.setAlignment(Qt.AlignCenter)
130
- self.brush_preview.setFixedHeight(100)
131
- brush_layout.addWidget(self.brush_preview)
134
+ self.brush_preview_widget.setAlignment(Qt.AlignCenter)
135
+ self.brush_preview_widget.setFixedHeight(100)
136
+ brush_layout.addWidget(self.brush_preview_widget)
132
137
  side_layout.addWidget(brush_panel)
133
138
 
134
139
  master_label = QLabel("Master")
@@ -235,23 +240,26 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
235
240
  self.master_thumbnail_label, self.thumbnail_list, parent=self)
236
241
  self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
237
242
  self.display_manager.status_message_requested.connect(self.show_status_message)
238
- self.display_manager.cursor_preview_state_changed.connect(
239
- lambda state: setattr(self.image_viewer, 'allow_cursor_preview', state))
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)
@@ -269,8 +277,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
269
277
 
270
278
  file_menu.addAction("&Close", self.close_file, "Ctrl+W")
271
279
  file_menu.addSeparator()
272
- file_menu.addAction("&Import frames", self.io_gui_handler.import_frames)
273
- file_menu.addAction("Import &EXIF data", self.io_gui_handler.select_exif_path)
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)
274
282
 
275
283
  edit_menu = menubar.addMenu("&Edit")
276
284
  self.undo_action = QAction("Undo", self)
@@ -285,7 +293,21 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
285
293
  edit_menu.addAction(self.redo_action)
286
294
  edit_menu.addSeparator()
287
295
 
288
- copy_action = QAction("Copy Layer to Master", self)
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)
289
311
  copy_action.setShortcut("Ctrl+M")
290
312
  copy_action.triggered.connect(self.copy_layer_to_master)
291
313
  edit_menu.addAction(copy_action)
@@ -300,6 +322,78 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
300
322
 
301
323
  view_menu.addSeparator()
302
324
 
325
+ view_strategy_menu = QMenu("View &Mode", view_menu)
326
+
327
+ self.view_mode_actions = {
328
+ 'overlaid': QAction("Overlaid", self),
329
+ 'sidebyside': QAction("Side by Side", self),
330
+ 'topbottom': QAction("Top-Bottom", self)
331
+ }
332
+ overlaid_mode = self.view_mode_actions['overlaid']
333
+ overlaid_mode.setShortcut("Ctrl+1")
334
+ overlaid_mode.setCheckable(True)
335
+ overlaid_mode.triggered.connect(lambda: set_strategy('overlaid'))
336
+ view_strategy_menu.addAction(overlaid_mode)
337
+ side_by_side_mode = self.view_mode_actions['sidebyside']
338
+ side_by_side_mode.setShortcut("Ctrl+2")
339
+ side_by_side_mode.setCheckable(True)
340
+ side_by_side_mode.triggered.connect(lambda: set_strategy('sidebyside'))
341
+ view_strategy_menu.addAction(side_by_side_mode)
342
+ side_by_side_mode = self.view_mode_actions['topbottom']
343
+ side_by_side_mode.setShortcut("Ctrl+3")
344
+ side_by_side_mode.setCheckable(True)
345
+ side_by_side_mode.triggered.connect(lambda: set_strategy('topbottom'))
346
+ view_strategy_menu.addAction(side_by_side_mode)
347
+ view_menu.addMenu(view_strategy_menu)
348
+
349
+ def set_strategy(strategy):
350
+ self.image_viewer.set_strategy(strategy)
351
+ enable_shortcuts = strategy == 'overlaid'
352
+ self.view_master_action.setEnabled(enable_shortcuts)
353
+ self.view_individual_action.setEnabled(enable_shortcuts)
354
+ self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
355
+ for label, mode in self.view_mode_actions.items():
356
+ mode.setEnabled(label != strategy)
357
+ mode.setChecked(label == strategy)
358
+
359
+ cursor_menu = view_menu.addMenu("Cursor Style")
360
+
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']
367
+ brush_action.setCheckable(True)
368
+ brush_action.triggered.connect(lambda: set_cursor_style('brush'))
369
+ cursor_menu.addAction(brush_action)
370
+
371
+ preview_action = self.cursor_style_actions['preview']
372
+ preview_action.setCheckable(True)
373
+ preview_action.triggered.connect(lambda: set_cursor_style('preview'))
374
+ cursor_menu.addAction(preview_action)
375
+
376
+ outline_action = self.cursor_style_actions['outline']
377
+ outline_action.setCheckable(True)
378
+ outline_action.triggered.connect(lambda: set_cursor_style('outline'))
379
+ cursor_menu.addAction(outline_action)
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
+
389
+ cursor_group = QActionGroup(self)
390
+ cursor_group.addAction(preview_action)
391
+ cursor_group.addAction(outline_action)
392
+ cursor_group.addAction(brush_action)
393
+ cursor_group.setExclusive(True)
394
+
395
+ view_menu.addSeparator()
396
+
303
397
  zoom_in_action = QAction("Zoom In", self)
304
398
  zoom_in_action.setShortcut("Ctrl++")
305
399
  zoom_in_action.triggered.connect(self.image_viewer.zoom_in)
@@ -316,27 +410,30 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
316
410
  view_menu.addAction(adapt_action)
317
411
 
318
412
  actual_size_action = QAction("Actual Size", self)
319
- actual_size_action.setShortcut("Ctrl+=")
413
+ actual_size_action.setShortcut("Ctrl+R")
320
414
  actual_size_action.triggered.connect(self.image_viewer.actual_size)
321
415
  view_menu.addAction(actual_size_action)
322
416
  view_menu.addSeparator()
323
417
 
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)
418
+ self.view_master_action = QAction("View Master", self)
419
+ self.view_master_action.setShortcut("M")
420
+ self.view_master_action.triggered.connect(self.set_view_master)
421
+ view_menu.addAction(self.view_master_action)
422
+
423
+ self.view_individual_action = QAction("View Individual", self)
424
+ self.view_individual_action.setShortcut("L")
425
+ self.view_individual_action.triggered.connect(self.set_view_individual)
426
+ view_menu.addAction(self.view_individual_action)
427
+
428
+ self.toggle_view_master_individual_action = QAction("Toggle Master/Individual", self)
429
+ self.toggle_view_master_individual_action.setShortcut("T")
430
+ self.toggle_view_master_individual_action.triggered.connect(
431
+ self.toggle_view_master_individual)
432
+ view_menu.addAction(self.toggle_view_master_individual_action)
338
433
  view_menu.addSeparator()
339
434
 
435
+ set_strategy('overlaid')
436
+
340
437
  sort_asc_action = QAction("Sort Layers A-Z", self)
341
438
  sort_asc_action.triggered.connect(lambda: self.sort_layers('asc'))
342
439
  view_menu.addAction(sort_asc_action)
@@ -347,32 +444,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
347
444
 
348
445
  view_menu.addSeparator()
349
446
 
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
447
  filter_menu = menubar.addMenu("&Filter")
377
448
  filter_menu.setObjectName("Filter")
378
449
  denoise_action = QAction("Denoise", self)
@@ -384,13 +455,13 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
384
455
  white_balance_action = QAction("White Balance", self)
385
456
  white_balance_action.triggered.connect(self.white_balance)
386
457
  filter_menu.addAction(white_balance_action)
387
- vignetting_action = QAction("Vignetting correction", self)
458
+ vignetting_action = QAction("Vignetting Correction", self)
388
459
  vignetting_action.triggered.connect(self.vignetting_correction)
389
460
  filter_menu.addAction(vignetting_action)
390
461
 
391
462
  help_menu = menubar.addMenu("&Help")
392
463
  help_menu.setObjectName("Help")
393
- shortcuts_help_action = QAction("Shortcuts and mouse", self)
464
+ shortcuts_help_action = QAction("Shortcuts and Mouse", self)
394
465
 
395
466
  def shortcuts_help():
396
467
  self.shortcuts_help_dialog = ShortcutsHelp(self)
@@ -415,6 +486,19 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
415
486
  title += " *"
416
487
  self.window().setWindowTitle(title)
417
488
 
489
+ def update_recent_files(self):
490
+ self.recent_files_menu.clear()
491
+ recent_files = self._recent_file_manager.get_files_with_display_names()
492
+ for file_path, display_name in recent_files.items():
493
+ action = self.recent_files_menu.addAction(display_name)
494
+ action.setData(file_path)
495
+ action.triggered.connect(partial(self.io_gui_handler.open_file, file_path))
496
+ self.recent_files_menu.setEnabled(len(recent_files) > 0)
497
+
498
+ def add_recent_file(self, file_path):
499
+ self._recent_file_manager.add_file(file_path)
500
+ self.update_recent_files()
501
+
418
502
  def show_status_message(self, message):
419
503
  self.statusBar().showMessage(message)
420
504
 
@@ -440,7 +524,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
440
524
 
441
525
  # pylint: disable=C0103
442
526
  def keyPressEvent(self, event):
443
- if self.image_viewer.empty:
527
+ if self.image_viewer.empty():
444
528
  return
445
529
  if event.text() == '[':
446
530
  self.brush_tool.decrease_brush_size()
@@ -467,14 +551,12 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
467
551
 
468
552
  def change_layer(self, layer_idx):
469
553
  if 0 <= layer_idx < self.number_of_layers():
470
- view_state = self.image_viewer.get_view_state()
471
554
  self.set_current_layer_idx(layer_idx)
472
- self.display_manager.display_current_view()
473
- self.image_viewer.set_view_state(view_state)
555
+ self.display_manager.refresh_current_view()
474
556
  self.thumbnail_list.setCurrentRow(layer_idx)
475
557
  self.thumbnail_list.setFocus()
476
558
  self.image_viewer.update_brush_cursor()
477
- self.image_viewer.setFocus()
559
+ self.image_viewer.strategy.setFocus()
478
560
 
479
561
  def prev_layer(self):
480
562
  if self.layer_stack() is not None:
@@ -503,8 +585,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
503
585
  if reply == QMessageBox.Yes:
504
586
  self.set_master_layer(self.current_layer().copy())
505
587
  self.master_layer().setflags(write=True)
506
- self.display_manager.display_current_view()
507
- self.display_manager.update_thumbnails()
588
+ self.display_manager.refresh_master_view()
508
589
  self.mark_as_modified()
509
590
  self.statusBar().showMessage(f"Copied layer {self.current_layer_idx() + 1} to master")
510
591
 
@@ -540,8 +621,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
540
621
 
541
622
  def end_copy_brush_area(self):
542
623
  if self.display_manager.update_timer.isActive():
543
- self.display_manager.display_master_layer()
544
- self.display_manager.update_master_thumbnail()
624
+ self.display_manager.refresh_master_view()
545
625
  self.undo_manager.save_undo_state(self.master_layer_copy(), 'Brush Stroke')
546
626
  self.display_manager.update_timer.stop()
547
627
  self.mark_as_modified()
@@ -629,6 +709,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
629
709
 
630
710
  def close_file(self):
631
711
  if self.check_unsaved_changes():
712
+ self.image_viewer.reset_zoom()
632
713
  self.io_gui_handler.close_file()
633
714
  self.set_master_layer(None)
634
715
  self.mark_as_modified(False)
@@ -663,15 +744,13 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
663
744
 
664
745
  def undo(self):
665
746
  if self.undo_manager.undo(self.master_layer()):
666
- self.display_manager.display_current_view()
667
- self.display_manager.update_master_thumbnail()
747
+ self.display_manager.refresh_master_view()
668
748
  self.mark_as_modified()
669
749
  self.statusBar().showMessage("Undo applied", 2000)
670
750
 
671
751
  def redo(self):
672
752
  if self.undo_manager.redo(self.master_layer()):
673
- self.display_manager.display_current_view()
674
- self.display_manager.update_master_thumbnail()
753
+ self.display_manager.refresh_master_view()
675
754
  self.mark_as_modified()
676
755
  self.statusBar().showMessage("Redo applied", 2000)
677
756
 
@@ -0,0 +1,65 @@
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
62
+
63
+ def set_scroll(self, h_scroll, v_scroll):
64
+ self.h_scroll = h_scroll
65
+ self.v_scroll = v_scroll