shinestacker 1.3.1__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 (34) 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/config/gui_constants.py +2 -2
  9. shinestacker/core/core_utils.py +10 -1
  10. shinestacker/gui/action_config.py +172 -7
  11. shinestacker/gui/action_config_dialog.py +246 -285
  12. shinestacker/gui/gui_run.py +2 -2
  13. shinestacker/gui/main_window.py +14 -5
  14. shinestacker/gui/menu_manager.py +26 -2
  15. shinestacker/gui/project_controller.py +4 -0
  16. shinestacker/gui/recent_file_manager.py +93 -0
  17. shinestacker/retouch/base_filter.py +5 -5
  18. shinestacker/retouch/brush_preview.py +3 -0
  19. shinestacker/retouch/brush_tool.py +11 -11
  20. shinestacker/retouch/display_manager.py +21 -37
  21. shinestacker/retouch/image_editor_ui.py +129 -71
  22. shinestacker/retouch/image_view_status.py +61 -0
  23. shinestacker/retouch/image_viewer.py +89 -431
  24. shinestacker/retouch/io_gui_handler.py +12 -2
  25. shinestacker/retouch/overlaid_view.py +212 -0
  26. shinestacker/retouch/shortcuts_help.py +13 -3
  27. shinestacker/retouch/sidebyside_view.py +479 -0
  28. shinestacker/retouch/view_strategy.py +466 -0
  29. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/METADATA +1 -1
  30. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/RECORD +34 -29
  31. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/WHEEL +0 -0
  32. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/entry_points.txt +0 -0
  33. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/licenses/LICENSE +0 -0
  34. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ from PySide6.QtCore import Signal, Slot
9
9
  from .. config.constants import constants
10
10
  from .. config.gui_constants import gui_constants
11
11
  from .colors import RED_BUTTON_STYLE, BLUE_BUTTON_STYLE, BLUE_COMBO_STYLE
12
- from .. algorithms.utils import extension_tif_jpg, extension_pdf
12
+ from .. algorithms.utils import extension_jpg_tif_png, extension_pdf
13
13
  from .gui_logging import LogWorker, QTextEditLogger
14
14
  from .gui_images import GuiPdfView, GuiImageView, GuiOpenApp
15
15
  from .colors import (
@@ -209,7 +209,7 @@ class RunWindow(QTextEditLogger):
209
209
  try:
210
210
  if extension_pdf(path):
211
211
  image_view = GuiPdfView(path, self)
212
- elif extension_tif_jpg(path):
212
+ elif extension_jpg_tif_png(path):
213
213
  image_view = GuiImageView(path, self)
214
214
  else:
215
215
  raise RuntimeError(f"Can't visualize file type {os.path.splitext(path)[1]}.")
@@ -134,19 +134,28 @@ class MainWindow(QMainWindow, LogManager):
134
134
  self.update_title()
135
135
 
136
136
  self.project_editor.modified_signal.connect(handle_modified)
137
- self.project_editor.select_signal.connect(self.update_delete_action_state)
138
- self.project_editor.refresh_ui_signal.connect(self.refresh_ui)
137
+ self.project_editor.select_signal.connect(
138
+ self.update_delete_action_state)
139
+ self.project_editor.refresh_ui_signal.connect(
140
+ self.refresh_ui)
139
141
  self.project_editor.enable_delete_action_signal.connect(
140
142
  self.menu_manager.delete_element_action.setEnabled)
141
143
  self.project_editor.undo_manager.set_enabled_undo_action_requested.connect(
142
144
  self.menu_manager.set_enabled_undo_action)
143
- self.project_controller.update_title_requested.connect(self.update_title)
144
- self.project_controller.refresh_ui_requested.connect(self.refresh_ui)
145
- self.project_controller.activate_window_requested.connect(self.activateWindow)
145
+ self.project_controller.update_title_requested.connect(
146
+ self.update_title)
147
+ self.project_controller.refresh_ui_requested.connect(
148
+ self.refresh_ui)
149
+ self.project_controller.activate_window_requested.connect(
150
+ self.activateWindow)
146
151
  self.project_controller.enable_save_actions_requested.connect(
147
152
  self.menu_manager.save_actions_set_enabled)
148
153
  self.project_controller.enable_sub_actions_requested.connect(
149
154
  self.menu_manager.set_enabled_sub_actions_gui)
155
+ self.project_controller.add_recent_file_requested.connect(
156
+ self.menu_manager.add_recent_file)
157
+ self.menu_manager.open_file_requested.connect(
158
+ self.project_controller.open_project)
150
159
 
151
160
  def modified(self):
152
161
  return self.project_editor.modified()
@@ -1,13 +1,20 @@
1
1
  # pylint: disable=C0114, C0115, C0116, R0904, E0611, R0902, W0201
2
2
  import os
3
+ from functools import partial
4
+ from PySide6.QtCore import Signal, QObject
3
5
  from PySide6.QtGui import QAction, QIcon
4
6
  from PySide6.QtWidgets import QMenu, QComboBox
5
7
  from .. config.constants import constants
8
+ from .recent_file_manager import RecentFileManager
6
9
 
7
10
 
8
- class MenuManager:
11
+ class MenuManager(QObject):
12
+ open_file_requested = Signal(str)
13
+
9
14
  def __init__(self, menubar, actions, project_editor, parent):
15
+ super().__init__(parent)
10
16
  self.script_dir = os.path.dirname(__file__)
17
+ self._recent_file_manager = RecentFileManager("shinestacker-recent-project-files.txt")
11
18
  self.project_editor = project_editor
12
19
  self.parent = parent
13
20
  self.menubar = menubar
@@ -69,10 +76,27 @@ class MenuManager:
69
76
  action.triggered.connect(action_fun)
70
77
  return action
71
78
 
79
+ def update_recent_files(self):
80
+ self.recent_files_menu.clear()
81
+ recent_files = self._recent_file_manager.get_files_with_display_names()
82
+ for file_path, display_name in recent_files.items():
83
+ action = self.recent_files_menu.addAction(display_name)
84
+ action.setData(file_path)
85
+ action.triggered.connect(partial(self.open_file_requested.emit, file_path))
86
+ self.recent_files_menu.setEnabled(len(recent_files) > 0)
87
+
88
+ def add_recent_file(self, file_path):
89
+ self._recent_file_manager.add_file(file_path)
90
+ self.update_recent_files()
91
+
72
92
  def add_file_menu(self):
73
93
  menu = self.menubar.addMenu("&File")
74
- for name in ["&New...", "&Open...", "&Close"]:
94
+ for name in ["&New...", "&Open..."]:
75
95
  menu.addAction(self.action(name))
96
+ self.recent_files_menu = QMenu("Open &Recent", menu)
97
+ menu.addMenu(self.recent_files_menu)
98
+ self.update_recent_files()
99
+ menu.addAction(self.action("&Close"))
76
100
  menu.addSeparator()
77
101
  self.save_action = self.action("&Save")
78
102
  menu.addAction(self.save_action)
@@ -20,6 +20,7 @@ class ProjectController(QObject):
20
20
  activate_window_requested = Signal()
21
21
  enable_save_actions_requested = Signal(bool)
22
22
  enable_sub_actions_requested = Signal(bool)
23
+ add_recent_file_requested = Signal(str)
23
24
 
24
25
  def __init__(self, parent):
25
26
  super().__init__(parent)
@@ -242,6 +243,7 @@ class ProjectController(QObject):
242
243
  self.parent, "Open Project", "", "Project Files (*.fsp);;All Files (*)")
243
244
  if file_path:
244
245
  try:
246
+ abs_file_path = os.path.abspath(file_path)
245
247
  self.set_current_file_path(file_path)
246
248
  with open(self.current_file_path(), 'r', encoding="utf-8") as file:
247
249
  json_obj = json.load(file)
@@ -250,6 +252,7 @@ class ProjectController(QObject):
250
252
  raise RuntimeError(f"Project from file {file_path} produced a null project.")
251
253
  self.set_project(project)
252
254
  self.mark_as_modified(False)
255
+ self.add_recent_file_requested.emit(abs_file_path)
253
256
  self.project_editor.reset_undo()
254
257
  self.refresh_ui(0, -1)
255
258
  if self.job_list_count() > 0:
@@ -311,6 +314,7 @@ class ProjectController(QObject):
311
314
  f.write(json_obj)
312
315
  self.mark_as_modified(False)
313
316
  self.update_title_requested.emit()
317
+ self.add_recent_file_requested.emit(file_path)
314
318
  except Exception as e:
315
319
  QMessageBox.critical(self.parent, "Error", f"Cannot save file:\n{str(e)}")
316
320
 
@@ -0,0 +1,93 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611
2
+ import os
3
+ from PySide6.QtCore import QStandardPaths
4
+
5
+
6
+ class RecentFileManager:
7
+ def __init__(self, filename, max_entries=10):
8
+ self.filename = filename
9
+ self.max_entries = max_entries
10
+ self._config_dir = None
11
+
12
+ def get_config_dir(self):
13
+ if self._config_dir is None:
14
+ config_dir = QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)
15
+ if not config_dir:
16
+ if os.name == 'nt': # Windows
17
+ config_dir = os.path.join(os.environ.get('APPDATA', ''), 'ShineStacker')
18
+ elif os.name == 'posix': # macOS and Linux
19
+ config_dir = os.path.expanduser('~/.config/shinestacker')
20
+ else:
21
+ config_dir = os.path.join(os.path.expanduser('~'), '.shinestacker')
22
+ os.makedirs(config_dir, exist_ok=True)
23
+ self._config_dir = config_dir
24
+ return self._config_dir
25
+
26
+ def get_file_path(self):
27
+ return os.path.join(self.get_config_dir(), self.filename)
28
+
29
+ def get_files(self):
30
+ file_path = self.get_file_path()
31
+ try:
32
+ with open(file_path, 'r', encoding='utf-8') as f:
33
+ files = [line.strip() for line in f.readlines()]
34
+ return [f for f in files if f and os.path.exists(f)]
35
+ except (FileNotFoundError, IOError):
36
+ return []
37
+
38
+ def get_files_with_display_names(self):
39
+ files = self.get_files()
40
+ basename_count = {}
41
+ for file_path in files:
42
+ basename = os.path.basename(file_path)
43
+ basename_count[basename] = basename_count.get(basename, 0) + 1
44
+ result = {}
45
+ for file_path in files:
46
+ basename = os.path.basename(file_path)
47
+ if basename_count[basename] == 1:
48
+ result[file_path] = basename
49
+ else:
50
+ parent_dir = os.path.basename(os.path.dirname(file_path))
51
+ result[file_path] = f"{basename} ({parent_dir})"
52
+ if list(result.values()).count(result[file_path]) > 1:
53
+ path_components = file_path.split(os.sep)
54
+ for i in range(2, min(5, len(path_components))):
55
+ display_name = os.sep.join(path_components[-i:])
56
+ if sum(1 for f in files
57
+ if os.sep.join(os.path.normpath(f).split(os.sep)[-i:]) ==
58
+ display_name) == 1:
59
+ result[file_path] = display_name
60
+ break
61
+ else:
62
+ if len(file_path) > 50:
63
+ result[file_path] = "..." + file_path[-47:]
64
+ else:
65
+ result[file_path] = file_path
66
+ return result
67
+
68
+ def add_file(self, file_path):
69
+ file_path = os.path.abspath(file_path)
70
+ recent_files = self.get_files()
71
+ if file_path in recent_files:
72
+ recent_files.remove(file_path)
73
+ recent_files.insert(0, file_path)
74
+ recent_files = recent_files[:self.max_entries]
75
+ self._save_files(recent_files)
76
+
77
+ def remove_file(self, file_path):
78
+ file_path = os.path.normpath(file_path)
79
+ recent_files = self.get_files()
80
+ if file_path in recent_files:
81
+ recent_files.remove(file_path)
82
+ self._save_files(recent_files)
83
+
84
+ def _save_files(self, files):
85
+ file_path = self.get_file_path()
86
+ try:
87
+ with open(file_path, 'w', encoding='utf-8') as f:
88
+ f.write('\n'.join(files))
89
+ except IOError as e:
90
+ raise e
91
+
92
+ def clear(self):
93
+ self._save_files([])
@@ -48,7 +48,7 @@ class BaseFilter(ABC):
48
48
  nonlocal active_worker, dialog_closed # noqa
49
49
  dialog_closed = True
50
50
  self.editor.restore_master_layer()
51
- self.editor.display_manager.display_master_layer()
51
+ self.editor.image_viewer.update_master_display()
52
52
  if active_worker and active_worker.isRunning():
53
53
  active_worker.wait()
54
54
  initial_timer.stop()
@@ -62,10 +62,10 @@ class BaseFilter(ABC):
62
62
  current_region = self.editor.image_viewer.get_visible_image_portion()[1]
63
63
  if current_region == region:
64
64
  self.editor.set_master_layer(img)
65
- self.editor.display_manager.display_master_layer()
65
+ self.editor.image_viewer.update_master_display()
66
66
  else:
67
67
  self.editor.set_master_layer(img)
68
- self.editor.display_manager.display_master_layer()
68
+ self.editor.image_viewer.update_master_display()
69
69
  try:
70
70
  dlg.activateWindow()
71
71
  except Exception:
@@ -125,7 +125,7 @@ class BaseFilter(ABC):
125
125
 
126
126
  def restore_original():
127
127
  self.editor.restore_master_layer()
128
- self.editor.display_manager.display_master_layer()
128
+ self.editor.image_viewer.update_master_display()
129
129
  try:
130
130
  dlg.activateWindow()
131
131
  except Exception:
@@ -155,7 +155,7 @@ class BaseFilter(ABC):
155
155
  final_img = self.apply(self.editor.master_layer_copy(), *params)
156
156
  self.editor.set_master_layer(final_img)
157
157
  self.editor.copy_master_layer()
158
- self.editor.display_manager.display_master_layer()
158
+ self.editor.image_viewer.update_master_display()
159
159
  self.editor.display_manager.update_master_thumbnail()
160
160
  self.editor.mark_as_modified()
161
161
  else:
@@ -41,6 +41,7 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
41
41
  self.setVisible(False)
42
42
  self.setZValue(500)
43
43
  self.setTransformationMode(Qt.SmoothTransformation)
44
+ self.brush = None
44
45
 
45
46
  def get_layer_area(self, layer, x, y, w, h):
46
47
  if not isinstance(layer, np.ndarray):
@@ -64,6 +65,8 @@ class BrushPreviewItem(QGraphicsPixmapItem, LayerCollectionHandler):
64
65
  raise RuntimeError("Bitmas is neither 8 bit nor 16, but of type " + area.dtype)
65
66
 
66
67
  def update(self, scene_pos, size):
68
+ if self.brush is None:
69
+ return
67
70
  try:
68
71
  if self.layer_collection is None or self.number_of_layers() == 0 or size <= 0:
69
72
  self.hide()
@@ -12,7 +12,7 @@ from .brush_preview import create_brush_mask
12
12
  class BrushTool:
13
13
  def __init__(self):
14
14
  self.brush = None
15
- self.brush_preview = None
15
+ self.brush_preview_widget = None
16
16
  self.image_viewer = None
17
17
  self.size_slider = None
18
18
  self.hardness_slider = None
@@ -21,11 +21,11 @@ class BrushTool:
21
21
  self._brush_mask_cache = {}
22
22
  self.brush_text = None
23
23
 
24
- def setup_ui(self, brush, brush_preview, image_viewer, size_slider, hardness_slider,
24
+ def setup_ui(self, brush, brush_preview_widget, image_viewer, size_slider, hardness_slider,
25
25
  opacity_slider, flow_slider):
26
26
  self.brush = brush
27
- self.brush_preview = brush_preview
28
- self.brush_text = QLabel(brush_preview.parent())
27
+ self.brush_preview_widget = brush_preview_widget
28
+ self.brush_text = QLabel(brush_preview_widget.parent())
29
29
  self.brush_text.setStyleSheet("color: navy; background: transparent;")
30
30
  self.brush_text.setAlignment(Qt.AlignLeft | Qt.AlignTop)
31
31
  self.brush_text.raise_()
@@ -96,13 +96,13 @@ class BrushTool:
96
96
  preview_size = min(self.brush.size, width + 30, height + 30)
97
97
  center_x, center_y = width // 2, height // 2
98
98
  radius = preview_size // 2
99
- if self.image_viewer.cursor_style == 'preview':
99
+ if self.image_viewer.strategy.cursor_style == 'preview':
100
100
  gradient = create_default_brush_gradient(center_x, center_y, radius, self.brush)
101
101
  painter.setBrush(QBrush(gradient))
102
102
  painter.setPen(
103
103
  QPen(QColor(*gui_constants.BRUSH_COLORS['outer']),
104
104
  gui_constants.BRUSH_PREVIEW_LINE_WIDTH))
105
- elif self.image_viewer.cursor_style == 'outline':
105
+ elif self.image_viewer.strategy.cursor_style == 'outline':
106
106
  painter.setBrush(Qt.NoBrush)
107
107
  painter.setPen(
108
108
  QPen(QColor(*gui_constants.BRUSH_COLORS['outer']),
@@ -113,7 +113,7 @@ class BrushTool:
113
113
  QPen(QColor(*gui_constants.BRUSH_COLORS['pen']),
114
114
  gui_constants.BRUSH_PREVIEW_LINE_WIDTH))
115
115
  painter.drawEllipse(QPoint(center_x, center_y), radius, radius)
116
- if self.image_viewer.cursor_style == 'preview':
116
+ if self.image_viewer.strategy.cursor_style == 'preview':
117
117
  painter.setPen(QPen(QColor(0, 0, 160)))
118
118
  font = QApplication.font()
119
119
  painter.setFont(font)
@@ -126,13 +126,13 @@ class BrushTool:
126
126
  f"Flow: {self.brush.flow}%"
127
127
  )
128
128
  self.brush_text.adjustSize()
129
- self.brush_text.move(10, self.brush_preview.height() // 2 + 125)
129
+ self.brush_text.move(10, self.brush_preview_widget.height() // 2 + 125)
130
130
  self.brush_text.show()
131
131
  else:
132
132
  self.brush_text.hide()
133
133
  painter.end()
134
- self.brush_preview.setPixmap(pixmap)
135
- self.image_viewer.update_brush_cursor()
134
+ self.brush_preview_widget.setPixmap(pixmap)
135
+ self.image_viewer.strategy.update_brush_cursor()
136
136
 
137
137
  def apply_brush_operation(self, master_layer, source_layer, dest_layer, mask_layer,
138
138
  view_pos):
@@ -140,7 +140,7 @@ class BrushTool:
140
140
  return False
141
141
  if dest_layer is None:
142
142
  dest_layer = master_layer
143
- scene_pos = self.image_viewer.mapToScene(view_pos)
143
+ scene_pos = self.image_viewer.strategy.map_to_scene(view_pos)
144
144
  x_center = int(round(scene_pos.x()))
145
145
  y_center = int(round(scene_pos.y()))
146
146
  radius = int(round(self.brush.size // 2))
@@ -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