shinestacker 1.5.3__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.3'
1
+ __version__ = '1.5.4'
@@ -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)
@@ -3,6 +3,7 @@ import time
3
3
  from PySide6.QtCore import Qt, Signal, QEvent, QRectF
4
4
  from PySide6.QtGui import QCursor
5
5
  from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame
6
+
6
7
  from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
7
8
 
8
9
 
@@ -229,25 +230,20 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
229
230
  self.current_view.mousePressEvent = callbacks
230
231
  # pylint: enable=C0103
231
232
 
232
- # pylint: enable=R0801
233
- def handle_wheel_event(self, event):
234
- if self.empty() or self.gesture_active:
235
- return
236
- if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
237
- if self.control_pressed:
238
- self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
239
- else:
240
- if event.angleDelta().y() > 0: # Zoom in
241
- self.zoom_in()
242
- else: # Zoom out
243
- self.zoom_out()
244
- else: # Touchpad event - handle scrolling
245
- if not self.control_pressed:
246
- delta = event.pixelDelta() or event.angleDelta() / 8
247
- if delta:
248
- self.scroll_view(self.master_view, delta.x(), delta.y())
249
- self.scroll_view(self.current_view, delta.x(), delta.y())
250
- # 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
251
247
 
252
248
  def _apply_zoom_to_view(self, view, factor):
253
249
  view.scale(factor, factor)
@@ -383,14 +379,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
383
379
  pixmap = self.status.pixmap_master
384
380
  self.master_view.setSceneRect(QRectF(pixmap.rect()))
385
381
  self.pixmap_item_master.setPixmap(pixmap)
386
- img_width, img_height = pixmap.width(), pixmap.height()
387
- self.set_max_min_scales(img_width, img_height)
388
- view_rect = self.master_view.viewport().rect()
389
- scale_x = view_rect.width() / img_width
390
- scale_y = view_rect.height() / img_height
391
- scale_factor = min(scale_x, scale_y)
392
- scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
393
- self.set_zoom_factor(scale_factor)
382
+ img_width, img_height, scale_factor = self.setup_view_image(self.master_view, pixmap)
394
383
  self.master_view.resetTransform()
395
384
  self.master_view.scale(scale_factor, scale_factor)
396
385
  self.master_view.centerOn(self.pixmap_item_master)
@@ -439,8 +428,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
439
428
  size = self.brush.size
440
429
  radius = size / 2
441
430
  self.current_brush_cursor.setRect(
442
- scene_pos.x() - radius, scene_pos.y() - radius,
443
- size, size)
431
+ scene_pos.x() - radius, scene_pos.y() - radius, size, size)
444
432
  if self.brush_cursor.isVisible():
445
433
  self.update_current_cursor_color()
446
434
  self.current_brush_cursor.show()
@@ -68,6 +68,7 @@ class BrushCursor(QGraphicsItemGroup):
68
68
 
69
69
  def setRect(self, x, y, w, h):
70
70
  self._rect = QRectF(x, y, w, h)
71
+ self._radius = min(w, h) / 2
71
72
  self._create_arcs()
72
73
 
73
74
  def rect(self):
@@ -96,6 +97,8 @@ class ImageGraphicsViewBase(QGraphicsView):
96
97
  self.setRenderHint(QPainter.Antialiasing)
97
98
  self.setRenderHint(QPainter.SmoothPixmapTransform)
98
99
  self.setCursor(Qt.BlankCursor)
100
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
101
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
99
102
 
100
103
 
101
104
  class ViewStrategy(LayerCollectionHandler):
@@ -187,6 +190,10 @@ class ViewStrategy(LayerCollectionHandler):
187
190
  def set_mouse_callbacks(self, callbacks):
188
191
  pass
189
192
 
193
+ @abstractmethod
194
+ def get_view_with_mouse(self, event=None):
195
+ pass
196
+
190
197
  def hide_brush_cursor(self):
191
198
  if self.brush_cursor:
192
199
  self.brush_cursor.hide()
@@ -319,6 +326,17 @@ class ViewStrategy(LayerCollectionHandler):
319
326
  return QImage(memoryview(array), width, height, 3 * width, QImage.Format_RGB888)
320
327
  return QImage()
321
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
+
322
340
  def create_scene(self, view):
323
341
  scene = QGraphicsScene()
324
342
  view.setScene(scene)
@@ -340,21 +358,6 @@ class ViewStrategy(LayerCollectionHandler):
340
358
  gui_constants.MIN_ZOOMED_IMG_HEIGHT / img_height))
341
359
  self.set_max_scale(gui_constants.MAX_ZOOMED_IMG_PX_SIZE)
342
360
 
343
- def zoom_in(self):
344
- if self.empty():
345
- return
346
- master_view = self.get_master_view()
347
- old_center = master_view.mapToScene(master_view.viewport().rect().center())
348
- current_scale = self.get_current_scale()
349
- new_scale = current_scale * gui_constants.ZOOM_IN_FACTOR
350
- if new_scale <= self.max_scale():
351
- for view in self.get_views():
352
- view.scale(gui_constants.ZOOM_IN_FACTOR, gui_constants.ZOOM_IN_FACTOR)
353
- self.set_zoom_factor(new_scale)
354
- master_view.centerOn(old_center)
355
- self.update_brush_cursor()
356
- self.update_cursor_pen_width()
357
-
358
361
  def apply_zoom(self):
359
362
  if self.empty():
360
363
  return
@@ -363,20 +366,98 @@ class ViewStrategy(LayerCollectionHandler):
363
366
  scale_factor = self.zoom_factor() / current_scale
364
367
  view.scale(scale_factor, scale_factor)
365
368
 
366
- 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):
367
405
  if self.empty():
368
406
  return
369
- master_view = self.get_master_view()
370
- old_center = master_view.mapToScene(master_view.viewport().rect().center())
371
- current_scale = self.get_current_scale()
372
- new_scale = current_scale * gui_constants.ZOOM_OUT_FACTOR
373
- if new_scale >= self.min_scale():
374
- for view in self.get_views():
375
- view.scale(gui_constants.ZOOM_OUT_FACTOR, gui_constants.ZOOM_OUT_FACTOR)
376
- self.set_zoom_factor(new_scale)
377
- 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)
378
425
  self.update_brush_cursor()
379
- 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())
380
461
 
381
462
  def reset_zoom(self):
382
463
  if self.empty():
@@ -479,7 +560,8 @@ class ViewStrategy(LayerCollectionHandler):
479
560
  self.update_master_cursor_color()
480
561
  if self.cursor_style == 'brush':
481
562
  self.setup_simple_brush_style(scene_pos.x(), scene_pos.y(), radius)
482
- self.show_brush_cursor()
563
+ if not self.scrolling:
564
+ self.show_brush_cursor()
483
565
 
484
566
  def update_color_time(self):
485
567
  current_time = time.time()
@@ -612,10 +694,6 @@ class ViewStrategy(LayerCollectionHandler):
612
694
  self.status.set_scroll(view.horizontalScrollBar().value(),
613
695
  view.verticalScrollBar().value())
614
696
 
615
- def center_image(self, view):
616
- view.horizontalScrollBar().setValue(self.status.h_scroll)
617
- view.verticalScrollBar().setValue(self.status.v_scroll)
618
-
619
697
  def mouse_move_event(self, event):
620
698
  if self.empty():
621
699
  return
@@ -662,7 +740,8 @@ class ViewStrategy(LayerCollectionHandler):
662
740
  self.last_brush_pos = event.position()
663
741
  self.brush_operation_started.emit(event.position().toPoint())
664
742
  self.dragging = True
665
- self.show_brush_cursor()
743
+ if not self.scrolling:
744
+ self.show_brush_cursor()
666
745
 
667
746
  def mouse_release_event(self, event):
668
747
  if self.empty():
@@ -681,28 +760,3 @@ class ViewStrategy(LayerCollectionHandler):
681
760
  elif self.dragging:
682
761
  self.dragging = False
683
762
  self.brush_operation_ended.emit()
684
-
685
- def handle_pinch_gesture(self, pinch):
686
- master_view = self.get_master_view()
687
- if pinch.state() == Qt.GestureStarted:
688
- self.pinch_start_scale = self.zoom_factor()
689
- self.pinch_center_view = pinch.centerPoint()
690
- self.pinch_center_scene = master_view.mapToScene(self.pinch_center_view.toPoint())
691
- self.gesture_active = True
692
- elif pinch.state() == Qt.GestureUpdated:
693
- new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
694
- new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
695
- if abs(new_scale - self.zoom_factor()) > 0.01:
696
- self.set_zoom_factor(new_scale)
697
- self.apply_zoom()
698
- new_center = master_view.mapToScene(self.pinch_center_view.toPoint())
699
- delta = self.pinch_center_scene - new_center
700
- h_scroll = master_view.horizontalScrollBar().value() + \
701
- int(delta.x() * self.zoom_factor())
702
- v_scroll = master_view.verticalScrollBar().value() + \
703
- int(delta.y() * self.zoom_factor())
704
- self.status.set_scroll(h_scroll, v_scroll)
705
- self.center_image(master_view)
706
- elif pinch.state() in (Qt.GestureFinished, Qt.GestureCanceled):
707
- self.gesture_active = False
708
- 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.3
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=zG2cd7WLo5fIizFwE9xNzjpfZPff6uzTZIgsxS6ri_g,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
@@ -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=2BrIrr38FxYJ522GLpHomScwbJgAczO9LiPwTAIlPUE,18647
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=Di0iaYwTMI58pheF51d9MBJ0eLDvJog1DnmBLNMMMRs,26100
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.3.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
101
- shinestacker-1.5.3.dist-info/METADATA,sha256=8-9HAKfGrk6TEYj1rr_O6habe-0YlUGpHrrlhmQvRwY,6978
102
- shinestacker-1.5.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
- shinestacker-1.5.3.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
104
- shinestacker-1.5.3.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
105
- shinestacker-1.5.3.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,,