shinestacker 1.5.2__py3-none-any.whl → 1.5.4__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 CHANGED
@@ -1 +1 @@
1
- __version__ = '1.5.2'
1
+ __version__ = '1.5.4'
shinestacker/app/main.py CHANGED
@@ -20,7 +20,7 @@ from shinestacker.app.gui_utils import (
20
20
  disable_macos_special_menu_items, fill_app_menu, set_css_style)
21
21
  from shinestacker.app.help_menu import add_help_action
22
22
  from shinestacker.app.open_frames import open_frames
23
- from .args import add_project_arguments, add_retouch_arguments
23
+ from shinestacker.app.args_parser_opts import add_project_arguments, add_retouch_arguments
24
24
 
25
25
 
26
26
  class SelectionDialog(QDialog):
@@ -17,7 +17,7 @@ from shinestacker.gui.main_window import MainWindow
17
17
  from shinestacker.app.gui_utils import (
18
18
  disable_macos_special_menu_items, fill_app_menu, set_css_style)
19
19
  from shinestacker.app.help_menu import add_help_action
20
- from .args import add_project_arguments
20
+ from shinestacker.app.args_parser_opts import add_project_arguments
21
21
 
22
22
 
23
23
  class ProjectApp(MainWindow):
@@ -13,7 +13,7 @@ from shinestacker.app.gui_utils import (
13
13
  disable_macos_special_menu_items, fill_app_menu, set_css_style)
14
14
  from shinestacker.app.help_menu import add_help_action
15
15
  from shinestacker.app.open_frames import open_frames
16
- from .args import add_retouch_arguments
16
+ from shinestacker.app.args_parser_opts import add_retouch_arguments
17
17
 
18
18
 
19
19
  class RetouchApp(ImageEditorUI):
@@ -26,7 +26,7 @@ class _GuiConstants:
26
26
  'outer': (255, 0, 0, 200),
27
27
  'inner': (255, 0, 0, 150),
28
28
  'gradient_end': (255, 0, 0, 0),
29
- 'pen': (255, 0, 0, 200),
29
+ 'pen': (255, 255, 255, 200),
30
30
  'preview': (255, 180, 180),
31
31
  'cursor_inner': (255, 0, 0, 120),
32
32
  'preview_inner': (255, 255, 255, 150)
@@ -154,9 +154,14 @@ class MainWindow(QMainWindow, LogManager):
154
154
  self.menu_manager.set_enabled_sub_actions_gui)
155
155
  self.project_controller.add_recent_file_requested.connect(
156
156
  self.menu_manager.add_recent_file)
157
+ self.project_controller.set_enabled_file_open_close_actions_requested.connect(
158
+ self.set_enabled_file_open_close_actions)
159
+
157
160
  self.menu_manager.open_file_requested.connect(
158
161
  self.project_controller.open_project)
159
162
 
163
+ self.set_enabled_file_open_close_actions(False)
164
+
160
165
  def modified(self):
161
166
  return self.project_editor.modified()
162
167
 
@@ -571,3 +576,8 @@ class MainWindow(QMainWindow, LogManager):
571
576
  self.menu_manager.set_enabled_sub_actions_gui(enable_sub_actions)
572
577
  else:
573
578
  self.menu_manager.set_enabled_sub_actions_gui(False)
579
+
580
+ def set_enabled_file_open_close_actions(self, enabled):
581
+ for action in self.findChildren(QAction):
582
+ if action.property("requires_file"):
583
+ action.setEnabled(enabled)
@@ -60,8 +60,10 @@ class MenuManager(QObject):
60
60
  def get_icon(self, icon):
61
61
  return QIcon(os.path.join(self.script_dir, f"img/{icon}.png"))
62
62
 
63
- def action(self, name):
63
+ def action(self, name, requires_file=False):
64
64
  action = QAction(name, self.parent)
65
+ if requires_file:
66
+ action.setProperty("requires_file", True)
65
67
  shortcut = self.shortcuts.get(name, '')
66
68
  if shortcut:
67
69
  action.setShortcut(shortcut)
@@ -110,20 +112,20 @@ class MenuManager(QObject):
110
112
  self.undo_action.setEnabled(False)
111
113
  menu.addAction(self.undo_action)
112
114
  for name in ["&Cut", "Cop&y", "&Paste", "Duplicate"]:
113
- menu.addAction(self.action(name))
114
- self.delete_element_action = self.action("Delete")
115
+ menu.addAction(self.action(name, requires_file=True))
116
+ self.delete_element_action = self.action("Delete", requires_file=True)
115
117
  self.delete_element_action.setEnabled(False)
116
118
  menu.addAction(self.delete_element_action)
117
119
  menu.addSeparator()
118
120
  for name in ["Move &Up", "Move &Down"]:
119
- menu.addAction(self.action(name))
121
+ menu.addAction(self.action(name, requires_file=True))
120
122
  menu.addSeparator()
121
- self.enable_action = self.action("E&nable")
123
+ self.enable_action = self.action("E&nable", requires_file=True)
122
124
  menu.addAction(self.enable_action)
123
- self.disable_action = self.action("Di&sable")
125
+ self.disable_action = self.action("Di&sable", requires_file=True)
124
126
  menu.addAction(self.disable_action)
125
127
  for name in ["Enable All", "Disable All"]:
126
- menu.addAction(self.action(name))
128
+ menu.addAction(self.action(name, requires_file=True))
127
129
 
128
130
  def add_view_menu(self):
129
131
  menu = self.menubar.addMenu("&View")
@@ -133,13 +135,13 @@ class MenuManager(QObject):
133
135
 
134
136
  def add_job_menu(self):
135
137
  menu = self.menubar.addMenu("&Jobs")
136
- self.add_job_action = self.action("Add Job")
138
+ self.add_job_action = self.action("Add Job", requires_file=True)
137
139
  menu.addAction(self.add_job_action)
138
140
  menu.addSeparator()
139
- self.run_job_action = self.action("Run Job")
141
+ self.run_job_action = self.action("Run Job", requires_file=True)
140
142
  self.run_job_action.setEnabled(False)
141
143
  menu.addAction(self.run_job_action)
142
- self.run_all_jobs_action = self.action("Run All Jobs")
144
+ self.run_all_jobs_action = self.action("Run All Jobs", requires_file=True)
143
145
  self.set_enabled_run_all_jobs(False)
144
146
  menu.addAction(self.run_all_jobs_action)
145
147
 
@@ -148,6 +150,7 @@ class MenuManager(QObject):
148
150
  add_action_menu = QMenu("Add Action", self.parent)
149
151
  for action in constants.ACTION_TYPES:
150
152
  entry_action = QAction(action, self.parent)
153
+ entry_action.setProperty("requires_file", True)
151
154
  entry_action.triggered.connect({
152
155
  constants.ACTION_COMBO: self.add_action_combined_actions,
153
156
  constants.ACTION_NOISEDETECTION: self.add_action_noise_detection,
@@ -161,6 +164,7 @@ class MenuManager(QObject):
161
164
  self.sub_action_menu_entries = []
162
165
  for action in constants.SUB_ACTION_TYPES:
163
166
  entry_action = QAction(action, self.parent)
167
+ entry_action.setProperty("requires_file", True)
164
168
  entry_action.triggered.connect({
165
169
  constants.ACTION_MASKNOISE: self.add_sub_action_make_noise,
166
170
  constants.ACTION_VIGNETTING: self.add_sub_action_vignetting,
@@ -21,6 +21,7 @@ class ProjectController(QObject):
21
21
  enable_save_actions_requested = Signal(bool)
22
22
  enable_sub_actions_requested = Signal(bool)
23
23
  add_recent_file_requested = Signal(str)
24
+ set_enabled_file_open_close_actions_requested = Signal(bool)
24
25
 
25
26
  def __init__(self, parent):
26
27
  super().__init__(parent)
@@ -138,6 +139,7 @@ class ProjectController(QObject):
138
139
  self.clear_action_list()
139
140
  self.mark_as_modified(False)
140
141
  self.project_editor.reset_undo()
142
+ self.set_enabled_file_open_close_actions_requested.emit(False)
141
143
 
142
144
  def new_project(self):
143
145
  if not self.check_unsaved_changes():
@@ -250,6 +252,7 @@ class ProjectController(QObject):
250
252
  project = Project.from_dict(json_obj['project'])
251
253
  if project is None:
252
254
  raise RuntimeError(f"Project from file {file_path} produced a null project.")
255
+ self.set_enabled_file_open_close_actions_requested.emit(True)
253
256
  self.set_project(project)
254
257
  self.mark_as_modified(False)
255
258
  self.add_recent_file_requested.emit(abs_file_path)
@@ -229,6 +229,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
229
229
  self.io_gui_handler.mark_as_modified_requested.connect(self.mark_as_modified)
230
230
  self.io_gui_handler.change_layer_requested.connect(self.change_layer)
231
231
  self.io_gui_handler.add_recent_file_requested.connect(self.add_recent_file)
232
+ self.io_gui_handler.set_enabled_file_open_close_actions_requested.connect(
233
+ self.set_enabled_file_open_close_actions)
232
234
  self.brush_tool.setup_ui(self.brush, self.brush_preview_widget, self.image_viewer,
233
235
  self.brush_size_slider, self.hardness_slider, self.opacity_slider,
234
236
  self.flow_slider)
@@ -277,22 +279,26 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
277
279
 
278
280
  transf_menu = QMenu("&Transform")
279
281
  rotate_90_cw_action = QAction(gui_constants.ROTATE_90_CW_LABEL, self)
280
- transf_menu.addAction(rotate_90_cw_action)
282
+ rotate_90_cw_action.setProperty("requires_file", True)
281
283
  rotate_90_cw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_cw())
284
+ transf_menu.addAction(rotate_90_cw_action)
282
285
  rotate_90_ccw_action = QAction(gui_constants.ROTATE_90_CCW_LABEL, self)
283
- transf_menu.addAction(rotate_90_ccw_action)
286
+ rotate_90_ccw_action.setProperty("requires_file", True)
284
287
  rotate_90_ccw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_ccw())
288
+ transf_menu.addAction(rotate_90_ccw_action)
285
289
  rotate_180_action = QAction(gui_constants.ROTATE_180_LABEL, self)
286
290
  rotate_180_action.triggered.connect(lambda: self.transformation_manager.rotate_180())
291
+ rotate_180_action.setProperty("requires_file", True)
287
292
  transf_menu.addAction(rotate_180_action)
288
293
  edit_menu.addMenu(transf_menu)
289
294
 
290
295
  edit_menu.addSeparator()
291
296
 
292
- copy_action = QAction("Copy Current Layer to Master", self)
293
- copy_action.setShortcut("Ctrl+M")
294
- copy_action.triggered.connect(self.copy_layer_to_master)
295
- edit_menu.addAction(copy_action)
297
+ copy_current_to_master_action = QAction("Copy Current Layer to Master", self)
298
+ copy_current_to_master_action.setShortcut("Ctrl+M")
299
+ copy_current_to_master_action.setProperty("requires_file", True)
300
+ copy_current_to_master_action.triggered.connect(self.copy_layer_to_master)
301
+ edit_menu.addAction(copy_current_to_master_action)
296
302
 
297
303
  view_menu = menubar.addMenu("&View")
298
304
 
@@ -351,15 +357,18 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
351
357
  }
352
358
  brush_action = self.cursor_style_actions['brush']
353
359
  brush_action.setCheckable(True)
360
+ brush_action.setProperty("requires_file", True)
354
361
  brush_action.triggered.connect(lambda: set_cursor_style('brush'))
355
362
  cursor_menu.addAction(brush_action)
356
363
 
357
364
  preview_action = self.cursor_style_actions['preview']
365
+ preview_action.setProperty("requires_file", True)
358
366
  preview_action.setCheckable(True)
359
367
  preview_action.triggered.connect(lambda: set_cursor_style('preview'))
360
368
  cursor_menu.addAction(preview_action)
361
369
 
362
370
  outline_action = self.cursor_style_actions['outline']
371
+ outline_action.setProperty("requires_file", True)
363
372
  outline_action.setCheckable(True)
364
373
  outline_action.triggered.connect(lambda: set_cursor_style('outline'))
365
374
  cursor_menu.addAction(outline_action)
@@ -382,37 +391,44 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
382
391
 
383
392
  zoom_in_action = QAction("Zoom In", self)
384
393
  zoom_in_action.setShortcut("Ctrl++")
394
+ zoom_in_action.setProperty("requires_file", True)
385
395
  zoom_in_action.triggered.connect(self.image_viewer.zoom_in)
386
396
  view_menu.addAction(zoom_in_action)
387
397
 
388
398
  zoom_out_action = QAction("Zoom Out", self)
389
399
  zoom_out_action.setShortcut("Ctrl+-")
400
+ zoom_out_action.setProperty("requires_file", True)
390
401
  zoom_out_action.triggered.connect(self.image_viewer.zoom_out)
391
402
  view_menu.addAction(zoom_out_action)
392
403
 
393
404
  adapt_action = QAction("Adapt to Screen", self)
394
405
  adapt_action.setShortcut("Ctrl+0")
406
+ adapt_action.setProperty("requires_file", True)
395
407
  adapt_action.triggered.connect(self.image_viewer.reset_zoom)
396
408
  view_menu.addAction(adapt_action)
397
409
 
398
410
  actual_size_action = QAction("Actual Size", self)
399
411
  actual_size_action.setShortcut("Ctrl+R")
412
+ actual_size_action.setProperty("requires_file", True)
400
413
  actual_size_action.triggered.connect(self.image_viewer.actual_size)
401
414
  view_menu.addAction(actual_size_action)
402
415
  view_menu.addSeparator()
403
416
 
404
417
  self.view_master_action = QAction("View Master", self)
405
418
  self.view_master_action.setShortcut("M")
419
+ self.view_master_action.setProperty("requires_file", True)
406
420
  self.view_master_action.triggered.connect(self.set_view_master)
407
421
  view_menu.addAction(self.view_master_action)
408
422
 
409
423
  self.view_individual_action = QAction("View Individual", self)
410
424
  self.view_individual_action.setShortcut("L")
425
+ self.view_individual_action.setProperty("requires_file", True)
411
426
  self.view_individual_action.triggered.connect(self.set_view_individual)
412
427
  view_menu.addAction(self.view_individual_action)
413
428
 
414
429
  self.toggle_view_master_individual_action = QAction("Toggle Master/Individual", self)
415
430
  self.toggle_view_master_individual_action.setShortcut("T")
431
+ self.toggle_view_master_individual_action.setProperty("requires_file", True)
416
432
  self.toggle_view_master_individual_action.triggered.connect(
417
433
  self.toggle_view_master_individual)
418
434
  view_menu.addAction(self.toggle_view_master_individual_action)
@@ -421,11 +437,13 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
421
437
  self.set_strategy('overlaid')
422
438
 
423
439
  sort_asc_action = QAction("Sort Layers A-Z", self)
424
- sort_asc_action.triggered.connect(lambda: self.sort_layers('asc'))
440
+ sort_asc_action.setProperty("requires_file", True)
441
+ sort_asc_action.triggered.connect(lambda: self.sort_layers_ui('asc'))
425
442
  view_menu.addAction(sort_asc_action)
426
443
 
427
444
  sort_desc_action = QAction("Sort Layers Z-A", self)
428
- sort_desc_action.triggered.connect(lambda: self.sort_layers('desc'))
445
+ sort_desc_action.setProperty("requires_file", True)
446
+ sort_desc_action.triggered.connect(lambda: self.sort_layers_ui('desc'))
429
447
  view_menu.addAction(sort_desc_action)
430
448
 
431
449
  view_menu.addSeparator()
@@ -433,15 +451,19 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
433
451
  filter_menu = menubar.addMenu("&Filter")
434
452
  filter_menu.setObjectName("Filter")
435
453
  denoise_action = QAction("Denoise", self)
454
+ denoise_action.setProperty("requires_file", True)
436
455
  denoise_action.triggered.connect(self.denoise_filter)
437
456
  filter_menu.addAction(denoise_action)
438
457
  unsharp_mask_action = QAction("Unsharp Mask", self)
458
+ unsharp_mask_action.setProperty("requires_file", True)
439
459
  unsharp_mask_action.triggered.connect(self.unsharp_mask)
440
460
  filter_menu.addAction(unsharp_mask_action)
441
461
  white_balance_action = QAction("White Balance", self)
462
+ white_balance_action.setProperty("requires_file", True)
442
463
  white_balance_action.triggered.connect(self.white_balance)
443
464
  filter_menu.addAction(white_balance_action)
444
465
  vignetting_action = QAction("Vignetting Correction", self)
466
+ vignetting_action.setProperty("requires_file", True)
445
467
  vignetting_action.triggered.connect(self.vignetting_correction)
446
468
  filter_menu.addAction(vignetting_action)
447
469
 
@@ -462,20 +484,29 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
462
484
  prev_layer.activated.connect(self.prev_layer)
463
485
  next_layer = QShortcut(QKeySequence(Qt.Key_Down), self, context=Qt.ApplicationShortcut)
464
486
  next_layer.activated.connect(self.next_layer)
487
+
488
+ self.set_enabled_file_open_close_actions(False)
465
489
  self.installEventFilter(self)
466
490
 
491
+ def set_enabled_view_toggles(self, enabled):
492
+ self.view_master_action.setEnabled(enabled)
493
+ self.view_individual_action.setEnabled(enabled)
494
+ self.toggle_view_master_individual_action.setEnabled(enabled)
495
+
467
496
  def set_strategy(self, strategy):
468
497
  self.image_viewer.set_strategy(strategy)
469
- enable_shortcuts = strategy == 'overlaid'
470
498
  self.display_manager.view_mode = 'master'
471
499
  self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
472
- self.view_master_action.setEnabled(enable_shortcuts)
473
- self.view_individual_action.setEnabled(enable_shortcuts)
474
- self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
500
+ self.set_enabled_view_toggles(strategy == 'overlaid')
475
501
  for label, mode in self.view_mode_actions.items():
476
502
  mode.setEnabled(label != strategy)
477
503
  mode.setChecked(label == strategy)
478
504
 
505
+ def set_enabled_file_open_close_actions(self, enabled):
506
+ for action in self.findChildren(QAction):
507
+ if action.property("requires_file"):
508
+ action.setEnabled(enabled)
509
+
479
510
  def update_title(self):
480
511
  title = constants.APP_TITLE
481
512
  if self.io_gui_handler is not None:
@@ -544,10 +575,10 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
544
575
  def save_master_only(self, _checked):
545
576
  self.update_title()
546
577
 
547
- def sort_layers(self, order):
578
+ def sort_layers_ui(self, order):
548
579
  self.sort_layers(order)
549
580
  self.display_manager.update_thumbnails()
550
- self.change_layer(self.current_layer())
581
+ self.change_layer(self.current_layer_idx())
551
582
 
552
583
  def change_layer(self, layer_idx):
553
584
  if 0 <= layer_idx < self.number_of_layers():
@@ -17,6 +17,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
17
17
  mark_as_modified_requested = Signal(bool)
18
18
  change_layer_requested = Signal(int)
19
19
  add_recent_file_requested = Signal(str)
20
+ set_enabled_file_open_close_actions_requested = Signal(bool)
20
21
 
21
22
  def __init__(self, layer_collection, undo_manager, parent):
22
23
  QObject.__init__(self, parent)
@@ -165,6 +166,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
165
166
  self.change_layer_requested.emit(0)
166
167
  self.status_message_requested.emit(message)
167
168
  self.update_title_requested.emit()
169
+ self.set_enabled_file_open_close_actions_requested.emit(True)
168
170
  self.add_recent_file_requested.emit(self.current_file_path_master)
169
171
 
170
172
  def save_file(self):
@@ -277,4 +279,5 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
277
279
  self.display_manager.thumbnail_list.clear()
278
280
  self.display_manager.update_thumbnails()
279
281
  self.update_title_requested.emit()
282
+ self.set_enabled_file_open_close_actions_requested.emit(False)
280
283
  self.status_message_requested.emit("File closed")
@@ -1,6 +1,5 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, E1101, R0904, R0912, R0914, R0902, E0202
2
2
  from PySide6.QtCore import Qt, QPointF, QEvent, QRectF
3
- from .. config.gui_constants import gui_constants
4
3
  from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
5
4
 
6
5
 
@@ -61,41 +60,9 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
61
60
  self.mouse_release_event(event)
62
61
  super().mouseReleaseEvent(event)
63
62
 
64
- # pylint: enable=R0801
65
63
  def wheelEvent(self, event):
66
- if self.empty() or self.gesture_active:
67
- return
68
- if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
69
- if self.control_pressed:
70
- self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
71
- else:
72
- zoom_in_factor = gui_constants.ZOOM_IN_FACTOR
73
- zoom_out_factor = gui_constants.ZOOM_OUT_FACTOR
74
- current_scale = self.get_current_scale()
75
- if event.angleDelta().y() > 0: # Zoom in
76
- new_scale = current_scale * zoom_in_factor
77
- if new_scale <= self.max_scale():
78
- self.scale(zoom_in_factor, zoom_in_factor)
79
- self.set_zoom_factor(new_scale)
80
- else: # Zoom out
81
- new_scale = current_scale * zoom_out_factor
82
- if new_scale >= self.min_scale():
83
- self.scale(zoom_out_factor, zoom_out_factor)
84
- self.set_zoom_factor(new_scale)
85
- self.update_brush_cursor()
86
- else: # Touchpad event - fallback for systems without gesture recognition
87
- if not self.control_pressed:
88
- delta = event.pixelDelta() or event.angleDelta() / 8
89
- if delta:
90
- self.scroll_view(self, delta.x(), delta.y())
91
- else: # Control + touchpad scroll for zoom
92
- zoom_in = event.angleDelta().y() > 0
93
- if zoom_in:
94
- self.zoom_in()
95
- else:
96
- self.zoom_out()
64
+ self.handle_wheel_event(event)
97
65
  event.accept()
98
- # pylint: disable=R0801
99
66
 
100
67
  def enterEvent(self, event):
101
68
  self.activateWindow()
@@ -114,10 +81,13 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
114
81
  def set_mouse_callbacks(self, callbacks):
115
82
  self.mousePressEvent = callbacks
116
83
 
84
+ def get_view_with_mouse(self, event=None):
85
+ return self
86
+
87
+ # pylint: enable=C0103
117
88
  def show(self):
118
89
  self.show_master()
119
90
  super().show()
120
- # pylint: enable=C0103
121
91
 
122
92
  def event(self, event):
123
93
  if event.type() == QEvent.Gesture:
@@ -126,14 +96,7 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
126
96
 
127
97
  def setup_scene_image(self, pixmap, pixmap_item):
128
98
  self.setSceneRect(QRectF(pixmap.rect()))
129
- img_width, img_height = pixmap.width(), pixmap.height()
130
- self.set_max_min_scales(img_width, img_height)
131
- view_rect = self.viewport().rect()
132
- scale_x = view_rect.width() / img_width
133
- scale_y = view_rect.height() / img_height
134
- scale_factor = min(scale_x, scale_y)
135
- scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
136
- self.set_zoom_factor(scale_factor)
99
+ _img_width, _img_height, scale_factor = self.setup_view_image(self, pixmap)
137
100
  self.resetTransform()
138
101
  self.scale(scale_factor, scale_factor)
139
102
  self.centerOn(pixmap_item)
@@ -1,7 +1,9 @@
1
1
  # pylint: disable=C0114, C0115, C0116, R0904, R0915, E0611, R0902, R0911, R0914, E1003
2
+ import time
2
3
  from PySide6.QtCore import Qt, Signal, QEvent, QRectF
3
4
  from PySide6.QtGui import QCursor
4
5
  from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame
6
+
5
7
  from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
6
8
 
7
9
 
@@ -58,6 +60,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
58
60
  self.current_view.setFocusPolicy(Qt.NoFocus)
59
61
  self.master_view.setFocusPolicy(Qt.NoFocus)
60
62
  self.current_brush_cursor = None
63
+ self.last_color_update_time_current = 0
61
64
 
62
65
  def setup_layout(self):
63
66
  raise NotImplementedError("Subclasses must implement setup_layout")
@@ -227,25 +230,20 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
227
230
  self.current_view.mousePressEvent = callbacks
228
231
  # pylint: enable=C0103
229
232
 
230
- # pylint: enable=R0801
231
- def handle_wheel_event(self, event):
232
- if self.empty() or self.gesture_active:
233
- return
234
- if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
235
- if self.control_pressed:
236
- self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
237
- else:
238
- if event.angleDelta().y() > 0: # Zoom in
239
- self.zoom_in()
240
- else: # Zoom out
241
- self.zoom_out()
242
- else: # Touchpad event - handle scrolling
243
- if not self.control_pressed:
244
- delta = event.pixelDelta() or event.angleDelta() / 8
245
- if delta:
246
- self.scroll_view(self.master_view, delta.x(), delta.y())
247
- self.scroll_view(self.current_view, delta.x(), delta.y())
248
- # pylint: disable=R0801
233
+ def get_view_with_mouse(self, event=None):
234
+ if event is None:
235
+ mouse_pos_global = QCursor.pos()
236
+ else:
237
+ mouse_pos_global = event.globalPosition().toPoint()
238
+ mouse_pos_current = self.current_view.mapFromGlobal(mouse_pos_global)
239
+ mouse_pos_master = self.master_view.mapFromGlobal(mouse_pos_global)
240
+ current_has_mouse = self.current_view.rect().contains(mouse_pos_current)
241
+ master_has_mouse = self.master_view.rect().contains(mouse_pos_master)
242
+ if master_has_mouse:
243
+ return self.master_view
244
+ if current_has_mouse:
245
+ return self.current_view
246
+ return None
249
247
 
250
248
  def _apply_zoom_to_view(self, view, factor):
251
249
  view.scale(factor, factor)
@@ -311,9 +309,11 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
311
309
  radius = size / 2
312
310
  self.current_brush_cursor.setRect(
313
311
  scene_pos.x() - radius, scene_pos.y() - radius, size, size)
312
+ self.update_current_cursor_color()
314
313
  self.current_brush_cursor.show()
315
314
  self.brush_cursor.setRect(
316
315
  scene_pos.x() - radius, scene_pos.y() - radius, size, size)
316
+ self.update_master_cursor_color()
317
317
  self.brush_cursor.show()
318
318
  else:
319
319
  self.brush_cursor.hide()
@@ -321,6 +321,19 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
321
321
  self.master_view.setCursor(Qt.ArrowCursor)
322
322
  self.current_view.setCursor(Qt.ArrowCursor)
323
323
 
324
+ def update_current_cursor_color(self):
325
+ self.update_cursor_color_based_on_background(
326
+ self.current_brush_cursor, self.current_layer(),
327
+ self.get_visible_current_image_region, self.get_current_pixmap,
328
+ self.update_color_time_current)
329
+
330
+ def update_color_time_current(self):
331
+ current_time = time.time()
332
+ if current_time - self.last_color_update_time_current < 0.2:
333
+ return False
334
+ self.last_color_update_time_current = current_time
335
+ return True
336
+
324
337
  def handle_master_mouse_press(self, event):
325
338
  self.setFocus()
326
339
  self.mouse_press_event(event)
@@ -366,14 +379,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
366
379
  pixmap = self.status.pixmap_master
367
380
  self.master_view.setSceneRect(QRectF(pixmap.rect()))
368
381
  self.pixmap_item_master.setPixmap(pixmap)
369
- img_width, img_height = pixmap.width(), pixmap.height()
370
- self.set_max_min_scales(img_width, img_height)
371
- view_rect = self.master_view.viewport().rect()
372
- scale_x = view_rect.width() / img_width
373
- scale_y = view_rect.height() / img_height
374
- scale_factor = min(scale_x, scale_y)
375
- scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
376
- self.set_zoom_factor(scale_factor)
382
+ img_width, img_height, scale_factor = self.setup_view_image(self.master_view, pixmap)
377
383
  self.master_view.resetTransform()
378
384
  self.master_view.scale(scale_factor, scale_factor)
379
385
  self.master_view.centerOn(self.pixmap_item_master)
@@ -422,9 +428,9 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
422
428
  size = self.brush.size
423
429
  radius = size / 2
424
430
  self.current_brush_cursor.setRect(
425
- scene_pos.x() - radius, scene_pos.y() - radius,
426
- size, size)
431
+ scene_pos.x() - radius, scene_pos.y() - radius, size, size)
427
432
  if self.brush_cursor.isVisible():
433
+ self.update_current_cursor_color()
428
434
  self.current_brush_cursor.show()
429
435
  else:
430
436
  self.current_brush_cursor.hide()
@@ -1,5 +1,6 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, R0904, R0903, R0902, E1101, R0914, R0913, R0917
2
2
  import math
3
+ import time
3
4
  from abc import abstractmethod
4
5
  import numpy as np
5
6
  from PySide6.QtCore import Qt, QPointF, QTime, QPoint, Signal, QRectF
@@ -67,6 +68,7 @@ class BrushCursor(QGraphicsItemGroup):
67
68
 
68
69
  def setRect(self, x, y, w, h):
69
70
  self._rect = QRectF(x, y, w, h)
71
+ self._radius = min(w, h) / 2
70
72
  self._create_arcs()
71
73
 
72
74
  def rect(self):
@@ -95,6 +97,8 @@ class ImageGraphicsViewBase(QGraphicsView):
95
97
  self.setRenderHint(QPainter.Antialiasing)
96
98
  self.setRenderHint(QPainter.SmoothPixmapTransform)
97
99
  self.setCursor(Qt.BlankCursor)
100
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
101
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
98
102
 
99
103
 
100
104
  class ViewStrategy(LayerCollectionHandler):
@@ -116,6 +120,7 @@ class ViewStrategy(LayerCollectionHandler):
116
120
  self.last_brush_pos = None
117
121
  self.last_mouse_pos = None
118
122
  self.last_update_time = QTime.currentTime()
123
+ self.last_color_update_time = 0
119
124
 
120
125
  @abstractmethod
121
126
  def create_pixmaps(self):
@@ -185,6 +190,10 @@ class ViewStrategy(LayerCollectionHandler):
185
190
  def set_mouse_callbacks(self, callbacks):
186
191
  pass
187
192
 
193
+ @abstractmethod
194
+ def get_view_with_mouse(self, event=None):
195
+ pass
196
+
188
197
  def hide_brush_cursor(self):
189
198
  if self.brush_cursor:
190
199
  self.brush_cursor.hide()
@@ -236,6 +245,8 @@ class ViewStrategy(LayerCollectionHandler):
236
245
 
237
246
  def set_cursor_style(self, style):
238
247
  self.cursor_style = style
248
+ if style == 'preview':
249
+ self.show_brush_preview()
239
250
  self.update_brush_cursor()
240
251
 
241
252
  def get_cursor_style(self):
@@ -315,6 +326,17 @@ class ViewStrategy(LayerCollectionHandler):
315
326
  return QImage(memoryview(array), width, height, 3 * width, QImage.Format_RGB888)
316
327
  return QImage()
317
328
 
329
+ def setup_view_image(self, view, pixmap):
330
+ img_width, img_height = pixmap.width(), pixmap.height()
331
+ self.set_max_min_scales(img_width, img_height)
332
+ view_rect = view.viewport().rect()
333
+ scale_x = view_rect.width() / img_width
334
+ scale_y = view_rect.height() / img_height
335
+ scale_factor = min(scale_x, scale_y)
336
+ scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
337
+ self.set_zoom_factor(scale_factor)
338
+ return img_width, img_height, scale_factor
339
+
318
340
  def create_scene(self, view):
319
341
  scene = QGraphicsScene()
320
342
  view.setScene(scene)
@@ -336,21 +358,6 @@ class ViewStrategy(LayerCollectionHandler):
336
358
  gui_constants.MIN_ZOOMED_IMG_HEIGHT / img_height))
337
359
  self.set_max_scale(gui_constants.MAX_ZOOMED_IMG_PX_SIZE)
338
360
 
339
- def zoom_in(self):
340
- if self.empty():
341
- return
342
- master_view = self.get_master_view()
343
- old_center = master_view.mapToScene(master_view.viewport().rect().center())
344
- current_scale = self.get_current_scale()
345
- new_scale = current_scale * gui_constants.ZOOM_IN_FACTOR
346
- if new_scale <= self.max_scale():
347
- for view in self.get_views():
348
- view.scale(gui_constants.ZOOM_IN_FACTOR, gui_constants.ZOOM_IN_FACTOR)
349
- self.set_zoom_factor(new_scale)
350
- master_view.centerOn(old_center)
351
- self.update_brush_cursor()
352
- self.update_cursor_pen_width()
353
-
354
361
  def apply_zoom(self):
355
362
  if self.empty():
356
363
  return
@@ -359,20 +366,98 @@ class ViewStrategy(LayerCollectionHandler):
359
366
  scale_factor = self.zoom_factor() / current_scale
360
367
  view.scale(scale_factor, scale_factor)
361
368
 
362
- def zoom_out(self):
369
+ def center_image(self, view):
370
+ view.horizontalScrollBar().setValue(self.status.h_scroll)
371
+ view.verticalScrollBar().setValue(self.status.v_scroll)
372
+
373
+ def set_scroll_and_center(self, view, delta):
374
+ self.status.set_scroll(
375
+ view.horizontalScrollBar().value() + int(delta.x() * self.zoom_factor()),
376
+ view.verticalScrollBar().value() + int(delta.y() * self.zoom_factor()))
377
+ self.center_image(view)
378
+
379
+ def apply_zoom_and_center(self, view, new_scale, ref_pos, old_center):
380
+ self.set_zoom_factor(new_scale)
381
+ self.apply_zoom()
382
+ new_center = view.mapToScene(ref_pos)
383
+ delta = old_center - new_center
384
+ self.set_scroll_and_center(view, delta)
385
+
386
+ def handle_pinch_gesture(self, pinch):
387
+ master_view = self.get_master_view()
388
+ if pinch.state() == Qt.GestureStarted:
389
+ self.pinch_start_scale = self.zoom_factor()
390
+ self.pinch_center_view = pinch.centerPoint()
391
+ self.pinch_center_scene = master_view.mapToScene(self.pinch_center_view.toPoint())
392
+ self.gesture_active = True
393
+ elif pinch.state() == Qt.GestureUpdated:
394
+ new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
395
+ new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
396
+ if abs(new_scale - self.zoom_factor()) > 0.01:
397
+ old_center = self.pinch_center_scene
398
+ ref_pos = self.pinch_center_view.toPoint()
399
+ self.apply_zoom_and_center(master_view, new_scale, ref_pos, old_center)
400
+ elif pinch.state() in (Qt.GestureFinished, Qt.GestureCanceled):
401
+ self.gesture_active = False
402
+ self.update_cursor_pen_width()
403
+
404
+ def do_zoom(self, new_scale, view):
363
405
  if self.empty():
364
406
  return
365
- master_view = self.get_master_view()
366
- old_center = master_view.mapToScene(master_view.viewport().rect().center())
367
- current_scale = self.get_current_scale()
368
- new_scale = current_scale * gui_constants.ZOOM_OUT_FACTOR
369
- if new_scale >= self.min_scale():
370
- for view in self.get_views():
371
- view.scale(gui_constants.ZOOM_OUT_FACTOR, gui_constants.ZOOM_OUT_FACTOR)
372
- self.set_zoom_factor(new_scale)
373
- master_view.centerOn(old_center)
407
+ if not self.min_scale() <= new_scale <= self.max_scale():
408
+ return
409
+ if view is None:
410
+ view = self.get_master_view()
411
+ global_pos = QCursor.pos()
412
+ ref_pos = view.mapFromGlobal(global_pos)
413
+ old_center = view.mapToScene(ref_pos)
414
+ self.apply_zoom_and_center(view, new_scale, ref_pos, old_center)
415
+ self.update_cursor_pen_width()
416
+
417
+ def handle_wheel_event(self, event):
418
+ if self.empty() or self.gesture_active:
419
+ return
420
+ if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
421
+ if self.control_pressed:
422
+ self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
423
+ else:
424
+ self.handle_zoom_wheel(self.get_view_with_mouse(event), event)
374
425
  self.update_brush_cursor()
375
- self.update_cursor_pen_width()
426
+ else:
427
+ self.handle_wheel_touchpad_event(event)
428
+
429
+ def handle_wheel_touchpad_event(self, event):
430
+ if not self.control_pressed:
431
+ delta = event.pixelDelta() or event.angleDelta() / 8
432
+ if delta:
433
+ self.scroll_view(self.get_view_with_mouse(event), delta.x(), delta.y())
434
+ else:
435
+ zoom_in = event.angleDelta().y() > 0
436
+ if zoom_in:
437
+ self.zoom_in()
438
+ else:
439
+ self.zoom_out()
440
+
441
+ def handle_zoom_wheel(self, view, event):
442
+ if view is None:
443
+ return
444
+ current_scale = self.get_current_scale()
445
+ if event.angleDelta().y() > 0:
446
+ new_scale = current_scale * gui_constants.ZOOM_IN_FACTOR
447
+ else:
448
+ new_scale = current_scale * gui_constants.ZOOM_OUT_FACTOR
449
+ new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
450
+ self.do_zoom(new_scale, view)
451
+
452
+ def zoom_in(self):
453
+ self.do_zoom(
454
+ self.get_current_scale() * gui_constants.ZOOM_IN_FACTOR,
455
+ self.get_view_with_mouse())
456
+
457
+ def zoom_out(self):
458
+ self.do_zoom(
459
+ self.get_current_scale() * gui_constants.ZOOM_OUT_FACTOR,
460
+ self.get_view_with_mouse())
376
461
 
377
462
  def reset_zoom(self):
378
463
  if self.empty():
@@ -402,9 +487,12 @@ class ViewStrategy(LayerCollectionHandler):
402
487
  self.update_cursor_pen_width()
403
488
 
404
489
  def setup_simple_brush_style(self, center_x, center_y, radius):
490
+ if self.brush_cursor:
491
+ pen = self.brush_cursor.pen()
492
+ else:
493
+ pen = QPen(QColor(*gui_constants.BRUSH_COLORS['pen']), self.current_line_width())
405
494
  gradient = create_default_brush_gradient(center_x, center_y, radius, self.brush)
406
- self.brush_cursor.setPen(QPen(QColor(*gui_constants.BRUSH_COLORS['pen']),
407
- self.current_line_width()))
495
+ self.brush_cursor.setPen(pen)
408
496
  self.brush_cursor.setBrush(QBrush(gradient))
409
497
 
410
498
  def create_circle(self, scene, line_style=Qt.SolidLine):
@@ -469,9 +557,58 @@ class ViewStrategy(LayerCollectionHandler):
469
557
  self.brush_preview.update(scene_pos, int(size))
470
558
  else:
471
559
  self.hide_brush_preview()
472
- if self.cursor_style != 'outline':
473
- self.setup_simple_brush_style(scene_pos.x(), scene_pos.y(), radius)
474
- self.show_brush_cursor()
560
+ self.update_master_cursor_color()
561
+ if self.cursor_style == 'brush':
562
+ self.setup_simple_brush_style(scene_pos.x(), scene_pos.y(), radius)
563
+ if not self.scrolling:
564
+ self.show_brush_cursor()
565
+
566
+ def update_color_time(self):
567
+ current_time = time.time()
568
+ if current_time - self.last_color_update_time < 0.2:
569
+ return False
570
+ self.last_color_update_time = current_time
571
+ return True
572
+
573
+ def update_master_cursor_color(self):
574
+ self.update_cursor_color_based_on_background(
575
+ self.brush_cursor, self.master_layer(),
576
+ self.get_visible_image_region, self.get_master_pixmap,
577
+ self.update_color_time)
578
+
579
+ def update_cursor_color_based_on_background(
580
+ self, cursor, layer,
581
+ visible_region, get_pixmap, update_timer):
582
+ if not update_timer():
583
+ return
584
+ cursor_rect = cursor.rect()
585
+ image_region = visible_region()
586
+ if image_region and cursor_rect.intersects(image_region):
587
+ intersect_rect = cursor_rect.intersected(image_region)
588
+ top_left = get_pixmap().mapFromScene(intersect_rect.topLeft())
589
+ bottom_right = get_pixmap().mapFromScene(intersect_rect.bottomRight())
590
+ x1, y1 = max(0, int(top_left.x())), max(0, int(top_left.y()))
591
+ x2, y2 = min(layer.shape[1], int(bottom_right.x())), \
592
+ min(layer.shape[0], int(bottom_right.y()))
593
+ if x2 > x1 and y2 > y1:
594
+ region = layer[y1:y2, x1:x2]
595
+ if region.size > 10000:
596
+ step = int(math.sqrt(region.size / 100))
597
+ region = region[::step, ::step]
598
+ if region.ndim == 3:
599
+ luminosity = np.dot(region[..., :3], [0.299, 0.587, 0.114])
600
+ avg_luminosity = np.mean(luminosity)
601
+ else:
602
+ avg_luminosity = np.mean(region)
603
+ if region.dtype == np.uint16:
604
+ avg_luminosity /= 256.0
605
+ if avg_luminosity < 128:
606
+ new_color = QColor(255, 255, 255)
607
+ else:
608
+ new_color = QColor(0, 0, 0)
609
+ current_pen = cursor.pen()
610
+ current_pen.setColor(new_color)
611
+ cursor.setPen(current_pen)
475
612
 
476
613
  def position_on_image(self, pos):
477
614
  master_view = self.get_master_view()
@@ -485,11 +622,20 @@ class ViewStrategy(LayerCollectionHandler):
485
622
  return None
486
623
  master_view = self.get_master_view()
487
624
  master_pixmap = self.get_master_pixmap()
488
- pixmap = self.get_master_pixmap()
489
625
  view_rect = master_view.viewport().rect()
490
626
  scene_rect = master_view.mapToScene(view_rect).boundingRect()
491
627
  image_rect = master_pixmap.mapFromScene(scene_rect).boundingRect().toRect()
492
- return image_rect.intersected(pixmap.boundingRect().toRect())
628
+ return image_rect.intersected(master_pixmap.boundingRect().toRect())
629
+
630
+ def get_visible_current_image_region(self):
631
+ if self.empty():
632
+ return None
633
+ current_view = self.get_current_view()
634
+ current_pixmap = self.get_current_pixmap()
635
+ view_rect = current_view.viewport().rect()
636
+ scene_rect = current_view.mapToScene(view_rect).boundingRect()
637
+ image_rect = current_pixmap.mapFromScene(scene_rect).boundingRect().toRect()
638
+ return image_rect.intersected(current_pixmap.boundingRect().toRect())
493
639
 
494
640
  def get_visible_image_portion(self):
495
641
  if self.has_no_master_layer():
@@ -548,10 +694,6 @@ class ViewStrategy(LayerCollectionHandler):
548
694
  self.status.set_scroll(view.horizontalScrollBar().value(),
549
695
  view.verticalScrollBar().value())
550
696
 
551
- def center_image(self, view):
552
- view.horizontalScrollBar().setValue(self.status.h_scroll)
553
- view.verticalScrollBar().setValue(self.status.v_scroll)
554
-
555
697
  def mouse_move_event(self, event):
556
698
  if self.empty():
557
699
  return
@@ -598,7 +740,8 @@ class ViewStrategy(LayerCollectionHandler):
598
740
  self.last_brush_pos = event.position()
599
741
  self.brush_operation_started.emit(event.position().toPoint())
600
742
  self.dragging = True
601
- self.show_brush_cursor()
743
+ if not self.scrolling:
744
+ self.show_brush_cursor()
602
745
 
603
746
  def mouse_release_event(self, event):
604
747
  if self.empty():
@@ -617,28 +760,3 @@ class ViewStrategy(LayerCollectionHandler):
617
760
  elif self.dragging:
618
761
  self.dragging = False
619
762
  self.brush_operation_ended.emit()
620
-
621
- def handle_pinch_gesture(self, pinch):
622
- master_view = self.get_master_view()
623
- if pinch.state() == Qt.GestureStarted:
624
- self.pinch_start_scale = self.zoom_factor()
625
- self.pinch_center_view = pinch.centerPoint()
626
- self.pinch_center_scene = master_view.mapToScene(self.pinch_center_view.toPoint())
627
- self.gesture_active = True
628
- elif pinch.state() == Qt.GestureUpdated:
629
- new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
630
- new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
631
- if abs(new_scale - self.zoom_factor()) > 0.01:
632
- self.set_zoom_factor(new_scale)
633
- self.apply_zoom()
634
- new_center = master_view.mapToScene(self.pinch_center_view.toPoint())
635
- delta = self.pinch_center_scene - new_center
636
- h_scroll = master_view.horizontalScrollBar().value() + \
637
- int(delta.x() * self.zoom_factor())
638
- v_scroll = master_view.verticalScrollBar().value() + \
639
- int(delta.y() * self.zoom_factor())
640
- self.status.set_scroll(h_scroll, v_scroll)
641
- self.center_image(master_view)
642
- elif pinch.state() in (Qt.GestureFinished, Qt.GestureCanceled):
643
- self.gesture_active = False
644
- self.update_cursor_pen_width()
@@ -49,7 +49,6 @@ class WhiteBalanceFilter(BaseFilter):
49
49
  self.value_labels[name] = val_label
50
50
  row_layout.addLayout(sliders_layout)
51
51
  layout.addLayout(row_layout)
52
-
53
52
  rbg_layout = QHBoxLayout()
54
53
  rbg_layout.addWidget(QLabel("RBG hex:"))
55
54
  self.rgb_hex = QLineEdit(self.hex_color(self.initial_val))
@@ -58,7 +57,6 @@ class WhiteBalanceFilter(BaseFilter):
58
57
  rbg_layout.addWidget(self.rgb_hex)
59
58
  rbg_layout.addStretch(1)
60
59
  layout.addLayout(rbg_layout)
61
-
62
60
  pick_button = QPushButton("Pick Color")
63
61
  layout.addWidget(pick_button)
64
62
  self.create_base_widgets(
@@ -126,17 +124,18 @@ class WhiteBalanceFilter(BaseFilter):
126
124
  bgr = self.get_pixel_color_at(
127
125
  pos, radius=int(self.image_viewer.get_brush().size))
128
126
  rgb = (bgr[2], bgr[1], bgr[0])
127
+ QApplication.restoreOverrideCursor()
128
+ self.image_viewer.unsetCursor()
129
+ self.image_viewer.strategy.set_mouse_callbacks(self.original_mouse_press)
130
+ self.filter_gui_set_enabled_requested.emit(True)
131
+ self.image_viewer.hide_brush_preview()
129
132
  new_filter = WhiteBalanceFilter(
130
133
  self.name, self.parent(), self.image_viewer, self.layer_collection,
131
134
  self.undo_manager)
132
135
  new_filter.run_with_preview(init_val=rgb)
133
- QApplication.restoreOverrideCursor()
134
- self.image_viewer.unsetCursor()
135
- self.image_viewer.strategy.set_mouse_callbacks(self.original_mouse_press)
136
136
  self.image_viewer.set_cursor_style(self.original_cursor_style)
137
137
  self.image_viewer.show_brush_cursor()
138
138
  self.image_viewer.show_brush_preview()
139
- self.filter_gui_set_enabled_requested.emit(True)
140
139
 
141
140
  def reset_rgb(self):
142
141
  for name, slider in self.sliders.items():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.5.2
3
+ Version: 1.5.4
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -1,5 +1,5 @@
1
1
  shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
2
- shinestacker/_version.py,sha256=7jrl0OaiREGeLqi1Vco5z6ma0w6Mxq8kDBXuswywlc8,21
2
+ shinestacker/_version.py,sha256=HfPNw49U2WWQOQlRuPSUUE8x8nkPx62dtjaGJfbhyTk,21
3
3
  shinestacker/algorithms/__init__.py,sha256=1FwVJ3w9GGbFFkjYJRUedTvcdE4j0ieSgaH9RC9iCY4,877
4
4
  shinestacker/algorithms/align.py,sha256=mb44u-YxZI1TTSHz81nRpX_2c8awlOhnGrK0LyfTQeQ,33543
5
5
  shinestacker/algorithms/align_auto.py,sha256=pJetw6zZEWQLouzcelkI8gD4cPiOp887ePXzVbm0E6Q,3800
@@ -22,17 +22,17 @@ shinestacker/algorithms/vignetting.py,sha256=gJOv-FN3GnTgaVn70W_6d-qbw3WmqinDiO9
22
22
  shinestacker/algorithms/white_balance.py,sha256=PMKsBtxOSn5aRr_Gkx1StHS4eN6kBN2EhNnhg4UG24g,501
23
23
  shinestacker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  shinestacker/app/about_dialog.py,sha256=pkH7nnxUP8yc0D3vRGd1jRb5cwi1nDVbQRk_OC9yLk8,4144
25
- shinestacker/app/args.py,sha256=TlfMR5GBd6Zz9wZNrN7CGJ1_hm1FnLZn5EoP5mkRHrk,776
25
+ shinestacker/app/args_parser_opts.py,sha256=TlfMR5GBd6Zz9wZNrN7CGJ1_hm1FnLZn5EoP5mkRHrk,776
26
26
  shinestacker/app/gui_utils.py,sha256=fSpkwPXTON_l676UHdAnJNrGq7BPbSlPOiHpOF_LZaI,2519
27
27
  shinestacker/app/help_menu.py,sha256=g8lKG_xZmXtNQaC3SIRzyROKVWva_PLEgZsQWh6zUcQ,499
28
- shinestacker/app/main.py,sha256=dcitc5vwIIyIXeDfZwQnC7KHRCdd3FaVJWyaU8mK86c,10935
28
+ shinestacker/app/main.py,sha256=UkWz4jvaBPH4Fs-arHe8H0NqKVTA4B_1e9BRn9S5oFo,10963
29
29
  shinestacker/app/open_frames.py,sha256=bsu32iJSYJQLe_tQQbvAU5DuMDVX6MRuNdE7B5lojZc,1488
30
- shinestacker/app/project.py,sha256=X98pK_mMtE_NefTUZfebEaP1YCsVY97hcQD4bSxuNyY,2777
31
- shinestacker/app/retouch.py,sha256=wlk-tHaei5YAFinGZWyzBopUhUqyxMT6jSH-4DMEwo8,2659
30
+ shinestacker/app/project.py,sha256=_kopeyb8e5JAA3XjNXoCshlYBfT5Avw-kTqiJN4GVlo,2805
31
+ shinestacker/app/retouch.py,sha256=On2oV2QonfMziQAvRkO6qw-h6E6wWQE0svKzZQKHeyg,2687
32
32
  shinestacker/config/__init__.py,sha256=aXxi-LmAvXd0daIFrVnTHE5OCaYeK1uf1BKMr7oaXQs,197
33
33
  shinestacker/config/config.py,sha256=eBko2D3ADhLTIm9X6hB_a_WsIjwgfE-qmBVkhP1XSvc,1636
34
34
  shinestacker/config/constants.py,sha256=EEdr7pZg4JpbIjUWaP7kJQfTuBB85FN739myDNAfn8A,8301
35
- shinestacker/config/gui_constants.py,sha256=Aqan-AdUjtlARXpsefQvWW2uKVv1tvwc0gfKyo7xud4,2787
35
+ shinestacker/config/gui_constants.py,sha256=55Qr0KzrTx8eQHCkT-EVabSiD59VvLZIh5o2cA9028s,2791
36
36
  shinestacker/core/__init__.py,sha256=IUEIx6SQ3DygDEHN3_E6uKpHjHtUa4a_U_1dLd_8yEU,484
37
37
  shinestacker/core/colors.py,sha256=kr_tJA1iRsdck2JaYDb2lS-codZ4Ty9gdu3kHfiWvuM,1340
38
38
  shinestacker/core/core_utils.py,sha256=1LYj19Dfc9jZN9-4dlf1paximDH5WZYa7DXvKr7R7QY,1719
@@ -49,10 +49,10 @@ shinestacker/gui/folder_file_selection.py,sha256=IYWfZQFkoD5iO7zJ7BxVVDP9F3Dc0EX
49
49
  shinestacker/gui/gui_images.py,sha256=k39DpdsxcmYoRdHNNZj6OpFAas0GOHS4JSG542wfheg,5728
50
50
  shinestacker/gui/gui_logging.py,sha256=kiZcrC2AFYCWgPZo0O5SKw-E5cFrezwf4anS3HjPuNw,8168
51
51
  shinestacker/gui/gui_run.py,sha256=zr7x4BVmM0n_ZRsSEaJVVKvHSWHuwhftgkUvgeg90gU,15767
52
- shinestacker/gui/main_window.py,sha256=5k_9TiZT9idKCmovUFYpUTSEQQj-DMQrlyq9dAgY1MU,24800
53
- shinestacker/gui/menu_manager.py,sha256=b5Cxh6uddOlio8i7fRISbGDJI-oe0ds6LIF5dWM7leI,11263
52
+ shinestacker/gui/main_window.py,sha256=zOxIRn6urmnmfDOR1JJ3n6xsT1qryOt1s7kZxwh-qYI,25202
53
+ shinestacker/gui/menu_manager.py,sha256=legmYEpQxuzEQoDhxMUWiwCcYTXwd-uRfAju-Nymy8g,11664
54
54
  shinestacker/gui/new_project.py,sha256=z8e3EhRMB-KtoPwYQSiKLSOQ2dS0-Okm7zVw21B7zy8,16391
55
- shinestacker/gui/project_controller.py,sha256=W4sbBGEPVtfF9F1rC-6Y0oKLq_y94HuFBvZRj87xNKQ,16272
55
+ shinestacker/gui/project_controller.py,sha256=7vSyoxepplJrf0VsbPrZkMqtHW6rtPgEOZgdPsOPVoQ,16490
56
56
  shinestacker/gui/project_converter.py,sha256=Gmna0HwbvACcXiX74TaQYumif8ZV8sZ2APLTMM-L1mU,7436
57
57
  shinestacker/gui/project_editor.py,sha256=lSgQ42IoaobHs-NQQWT88Qhg5l7nu5ejxAO5VgIupr8,25498
58
58
  shinestacker/gui/project_model.py,sha256=eRUmH3QmRzDtPtZoxgT6amKzN8_5XzwjHgEJeL-_JOE,4263
@@ -82,24 +82,24 @@ shinestacker/retouch/exif_data.py,sha256=LF-fRXW-reMq-xJ_QRE5j8DC2LVGKIlC6MR3QbC
82
82
  shinestacker/retouch/file_loader.py,sha256=z02-A8_uDZxayI1NFTxT2GVUvEBWStchX9hlN1o5-0U,4784
83
83
  shinestacker/retouch/filter_manager.py,sha256=tOGIWj5HjViL1-iXHkd91X-sZ1c1G531pDmLO0x6zx0,866
84
84
  shinestacker/retouch/icon_container.py,sha256=6gw1HO1bC2FrdB4dc_iH81DQuLjzuvRGksZ2hKLT9yA,585
85
- shinestacker/retouch/image_editor_ui.py,sha256=wvsYmS7cXt61KJlv7b3X9EbymMLgdUfeEOzi8jEuR3g,31662
85
+ shinestacker/retouch/image_editor_ui.py,sha256=-UVWeFSnXyHOG7XEIl9wNhiCjH5R0UFeM2VOg5R6ozs,33435
86
86
  shinestacker/retouch/image_view_status.py,sha256=bdIhsXiYXm7eyjkTGWkw5PRShzaF_by-g7daqgmhwjM,1858
87
87
  shinestacker/retouch/image_viewer.py,sha256=H8w-ORug1aKf7X3FeSX4lQV-a0IewZ9OVG1-50BK4cE,4452
88
- shinestacker/retouch/io_gui_handler.py,sha256=BRQ5eSt1tCMDYtOqxfdYGhx2BLCrncfNrNLGuWIy5Rk,11873
88
+ shinestacker/retouch/io_gui_handler.py,sha256=UOnrFT00s0075Ng_yJACJX9TP8UT9mQOWXwQwNAfpMw,12079
89
89
  shinestacker/retouch/io_manager.py,sha256=JUAA--AK0mVa1PTErJTnBFjaXIle5Qs7Ow0Wkd8at0o,2437
90
90
  shinestacker/retouch/layer_collection.py,sha256=fZlGrkm9-Ycc7AOzFSpImhafiTieBeCZRk-UlvlFHbo,5819
91
- shinestacker/retouch/overlaid_view.py,sha256=uIMolD1984uQPWXpd27tMvGBTcSrYT-4vxO0pWVlEO4,8686
91
+ shinestacker/retouch/overlaid_view.py,sha256=KBwuzC2OQsK7rdtFAKrSvXrHrEV1SiOoZhxPCQLGkvg,6734
92
92
  shinestacker/retouch/shortcuts_help.py,sha256=BFWTT5QvodqMhqa_9LI25hZqjICfckgyWG4fGrGzvnM,4283
93
- shinestacker/retouch/sidebyside_view.py,sha256=dN5uDG0ioFvYcbmZbx97slh4cOLmJ2T14jtTshg5F9w,17918
93
+ shinestacker/retouch/sidebyside_view.py,sha256=Ipx5ckaAuEv6fnxg4RpENj00ew6-M2jLsnTKUcYKcAk,18055
94
94
  shinestacker/retouch/transformation_manager.py,sha256=NSHGUF-JFv4Y81gSvizjQCTp49TLo1so7c0WoUElO08,1812
95
95
  shinestacker/retouch/undo_manager.py,sha256=cKUkqnJtnJ-Hq-LQs5Bv49FC6qkG6XSw9oCVySJ8jS0,4312
96
96
  shinestacker/retouch/unsharp_mask_filter.py,sha256=Iapc8UmSVpj3V0LcJq_38P5qerRqTevMynbbk5Rk6iE,3634
97
- shinestacker/retouch/view_strategy.py,sha256=_Zo-SU2hlVVSv1g5eWgp72njcmfMpAlkkPggd89XJFo,23326
97
+ shinestacker/retouch/view_strategy.py,sha256=u9oOB-fxap_ijNJ3O3Ev5OMLH-bDZNGj4hyBkJUDeeo,27993
98
98
  shinestacker/retouch/vignetting_filter.py,sha256=JhFr6OVIripQzSJrZEG4lxq7wBsmpofLqJQ-aP2bKw8,3789
99
- shinestacker/retouch/white_balance_filter.py,sha256=QlMnzWmBYqUQqckY8XTH3dWPdqo7mz4gTwTZafxldPw,8237
100
- shinestacker-1.5.2.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
101
- shinestacker-1.5.2.dist-info/METADATA,sha256=AuETmZdF7h5LSior9SUBcdWaiSmCxenl_9bECasvSLk,6978
102
- shinestacker-1.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
- shinestacker-1.5.2.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
104
- shinestacker-1.5.2.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
105
- shinestacker-1.5.2.dist-info/RECORD,,
99
+ shinestacker/retouch/white_balance_filter.py,sha256=UaH4yxG3fU4vPutBAkV5oTXIQyUTN09x0uTywAzv3sY,8286
100
+ shinestacker-1.5.4.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
101
+ shinestacker-1.5.4.dist-info/METADATA,sha256=1cFUICQsSTKF1T8L7sNiT7XQ_UkwXlIMRr64bym9TvA,6978
102
+ shinestacker-1.5.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
+ shinestacker-1.5.4.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
104
+ shinestacker-1.5.4.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
105
+ shinestacker-1.5.4.dist-info/RECORD,,
File without changes