shinestacker 1.4.0__py3-none-any.whl → 1.5.1__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.

@@ -6,8 +6,9 @@ from .. config.gui_constants import gui_constants
6
6
  class UndoManager(QObject):
7
7
  stack_changed = Signal(bool, str, bool, str)
8
8
 
9
- def __init__(self):
9
+ def __init__(self, transformation_manager):
10
10
  super().__init__()
11
+ self.transformation_manager = transformation_manager
11
12
  self.x_start = None
12
13
  self.y_start = None
13
14
  self.x_end = None
@@ -59,7 +60,16 @@ class UndoManager(QObject):
59
60
  'description': undo_state['description']
60
61
  }
61
62
  self.redo_stack.append(redo_state)
62
- layer[y_start:y_end, x_start:x_end] = undo_state['master']
63
+ descr = undo_state['description']
64
+ if descr.startswith(gui_constants.ROTATE_LABEL):
65
+ if descr == gui_constants.ROTATE_90_CW_LABEL:
66
+ self.transformation_manager.rotate_90_ccw(False)
67
+ elif descr == gui_constants.ROTATE_90_CCW_LABEL:
68
+ self.transformation_manager.rotate_90_cw(False)
69
+ elif descr == gui_constants.ROTATE_180_LABEL:
70
+ self.transformation_manager.rotate_180(False)
71
+ else:
72
+ layer[y_start:y_end, x_start:x_end] = undo_state['master']
63
73
  undo_desc = self.undo_stack[-1]['description'] if self.undo_stack else ""
64
74
  redo_desc = redo_state['description']
65
75
  self.stack_changed.emit(bool(self.undo_stack), undo_desc, bool(self.redo_stack), redo_desc)
@@ -76,7 +86,16 @@ class UndoManager(QObject):
76
86
  'description': redo_state['description']
77
87
  }
78
88
  self.undo_stack.append(undo_state)
79
- layer[y_start:y_end, x_start:x_end] = redo_state['master']
89
+ descr = undo_state['description']
90
+ if descr.startswith(gui_constants.ROTATE_LABEL):
91
+ if descr == gui_constants.ROTATE_90_CW_LABEL:
92
+ self.transformation_manager.rotate_90_cw(False)
93
+ elif descr == gui_constants.ROTATE_90_CCW_LABEL:
94
+ self.transformation_manager.rotate_90_ccw(False)
95
+ elif descr == gui_constants.ROTATE_180_LABEL:
96
+ self.transformation_manager.rotate_180(False)
97
+ else:
98
+ layer[y_start:y_end, x_start:x_end] = redo_state['master']
80
99
  undo_desc = undo_state['description']
81
100
  redo_desc = self.redo_stack[-1]['description'] if self.redo_stack else ""
82
101
  self.stack_changed.emit(bool(self.undo_stack), undo_desc, bool(self.redo_stack), redo_desc)
@@ -1,17 +1,79 @@
1
- # pylint: disable=C0114, C0115, C0116, E0611, R0904, R0903, R0902, E1101, R0914
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0904, R0903, R0902, E1101, R0914, R0913, R0917
2
2
  import math
3
3
  from abc import abstractmethod
4
4
  import numpy as np
5
- from PySide6.QtCore import Qt, QPointF, QTime, QPoint, Signal
6
- from PySide6.QtGui import QImage, QPainter, QColor, QBrush, QPen, QCursor
5
+ from PySide6.QtCore import Qt, QPointF, QTime, QPoint, Signal, QRectF
6
+ from PySide6.QtGui import QImage, QPainter, QColor, QBrush, QPen, QCursor, QPixmap, QPainterPath
7
7
  from PySide6.QtWidgets import (
8
- QGraphicsEllipseItem, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem)
8
+ QGraphicsEllipseItem, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem,
9
+ QGraphicsItemGroup, QGraphicsPathItem)
9
10
  from .. config.gui_constants import gui_constants
10
11
  from .layer_collection import LayerCollectionHandler
11
12
  from .brush_gradient import create_default_brush_gradient
12
13
  from .brush_preview import BrushPreviewItem
13
14
 
14
15
 
16
+ class BrushCursor(QGraphicsItemGroup):
17
+ def __init__(self, x0, y0, size, pen, brush):
18
+ super().__init__()
19
+ self._pen = pen
20
+ self._radius = size / 2
21
+ self._brush = brush
22
+ self._rect = QRectF(x0 - self._radius, y0 - self._radius, size, size)
23
+ self._arc_items = []
24
+ self._create_arcs()
25
+
26
+ def _point_on_circle(self, phi_deg):
27
+ phi = phi_deg / 180.0 * math.pi
28
+ x0 = self._rect.x() + self._radius
29
+ y0 = self._rect.y() + self._radius
30
+ return x0 + self._radius * math.cos(phi), y0 - self._radius * math.sin(phi)
31
+
32
+ def _create_arcs(self):
33
+ for item in self._arc_items:
34
+ self.removeFromGroup(item)
35
+ if item.scene():
36
+ item.scene().removeItem(item)
37
+ self._arc_items = []
38
+ half_gap = 20
39
+ arcs = [half_gap, 90 + half_gap, 180 + half_gap, 270 + half_gap]
40
+ span_angle = 90 - 2 * half_gap
41
+ for start_angle in arcs:
42
+ path = QPainterPath()
43
+ path.moveTo(*self._point_on_circle(start_angle))
44
+ path.arcTo(self._rect, start_angle, span_angle)
45
+ arc_item = QGraphicsPathItem(path)
46
+ arc_item.setPen(self._pen)
47
+ arc_item.setBrush(Qt.NoBrush)
48
+ self.addToGroup(arc_item)
49
+ self._arc_items.append(arc_item)
50
+
51
+ # pylint: disable=C0103
52
+ def setPen(self, pen):
53
+ self._pen = pen
54
+ for item in self._arc_items:
55
+ item.setPen(pen)
56
+
57
+ def pen(self):
58
+ return self._pen
59
+
60
+ def setBrush(self, brush):
61
+ self._brush = brush
62
+ for item in self._arc_items:
63
+ item.setBrush(Qt.NoBrush)
64
+
65
+ def brush(self):
66
+ return self._brush
67
+
68
+ def setRect(self, x, y, w, h):
69
+ self._rect = QRectF(x, y, w, h)
70
+ self._create_arcs()
71
+
72
+ def rect(self):
73
+ return self._rect
74
+ # pylint: enable=C0103
75
+
76
+
15
77
  class ViewSignals:
16
78
  temp_view_requested = Signal(bool)
17
79
  brush_operation_started = Signal(QPoint)
@@ -52,7 +114,6 @@ class ViewStrategy(LayerCollectionHandler):
52
114
  self.pinch_center_view = None
53
115
  self.pinch_center_scene = None
54
116
  self.pinch_start_scale = None
55
- self.last_scroll_pos = None
56
117
  self.scrolling = False
57
118
  self.dragging = False
58
119
  self.last_brush_pos = None
@@ -72,19 +133,19 @@ class ViewStrategy(LayerCollectionHandler):
72
133
  pass
73
134
 
74
135
  @abstractmethod
75
- def update_master_display(self):
136
+ def get_master_view(self):
76
137
  pass
77
138
 
78
139
  @abstractmethod
79
- def update_current_display(self):
140
+ def get_current_view(self):
80
141
  pass
81
142
 
82
143
  @abstractmethod
83
- def get_master_view(self):
144
+ def get_master_scene(self):
84
145
  pass
85
146
 
86
147
  @abstractmethod
87
- def get_master_scene(self):
148
+ def get_current_scene(self):
88
149
  pass
89
150
 
90
151
  @abstractmethod
@@ -103,12 +164,52 @@ class ViewStrategy(LayerCollectionHandler):
103
164
  def get_master_pixmap(self):
104
165
  pass
105
166
 
167
+ @abstractmethod
168
+ def get_current_pixmap(self):
169
+ pass
170
+
171
+ @abstractmethod
106
172
  def show_master(self):
107
173
  pass
108
174
 
175
+ @abstractmethod
109
176
  def show_current(self):
110
177
  pass
111
178
 
179
+ @abstractmethod
180
+ def arrange_images(self):
181
+ pass
182
+
183
+ def update_master_display(self):
184
+ if not self.empty():
185
+ master_qimage = self.numpy_to_qimage(self.master_layer())
186
+ if master_qimage:
187
+ pixmap = QPixmap.fromImage(master_qimage)
188
+ self.get_master_pixmap().setPixmap(pixmap)
189
+ self.get_master_scene().setSceneRect(QRectF(pixmap.rect()))
190
+ self.get_master_view().horizontalScrollBar().setValue(self.status.h_scroll)
191
+ self.get_master_view().verticalScrollBar().setValue(self.status.v_scroll)
192
+ self.arrange_images()
193
+
194
+ def update_current_display(self):
195
+ if not self.empty() and self.number_of_layers() > 0:
196
+ current_qimage = self.numpy_to_qimage(self.current_layer())
197
+ if current_qimage:
198
+ pixmap = QPixmap.fromImage(current_qimage)
199
+ self.get_current_pixmap().setPixmap(pixmap)
200
+ self.get_current_scene().setSceneRect(QRectF(pixmap.rect()))
201
+ self.get_current_view().horizontalScrollBar().setValue(self.status.h_scroll)
202
+ self.get_current_view().verticalScrollBar().setValue(self.status.v_scroll)
203
+ self.arrange_images()
204
+
205
+ def update_cursor_pen_width(self):
206
+ width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
207
+ if self.brush_cursor is not None:
208
+ pen = self.brush_cursor.pen()
209
+ pen.setWidthF(width)
210
+ self.brush_cursor.setPen(pen)
211
+ return width
212
+
112
213
  def set_allow_cursor_preview(self, state):
113
214
  self.allow_cursor_preview = state
114
215
 
@@ -154,10 +255,10 @@ class ViewStrategy(LayerCollectionHandler):
154
255
  return self.cursor_style
155
256
 
156
257
  def handle_key_press_event(self, _event):
157
- return
258
+ return True
158
259
 
159
260
  def handle_key_release_event(self, _event):
160
- return
261
+ return True
161
262
 
162
263
  def clear_image(self):
163
264
  for scene in self.get_scenes():
@@ -171,8 +272,16 @@ class ViewStrategy(LayerCollectionHandler):
171
272
  if self.brush_cursor:
172
273
  self.brush_cursor.hide()
173
274
 
275
+ def cleanup_brush_preview(self):
276
+ if self.brush_cursor:
277
+ self.brush_cursor.hide()
278
+ self.brush_preview.hide()
279
+
174
280
  def set_master_image_np(self, img):
175
281
  self.set_master_image(self.numpy_to_qimage(img))
282
+ if self.brush_cursor is None:
283
+ self.setup_brush_cursor()
284
+ self.show_master()
176
285
 
177
286
  def numpy_to_qimage(self, array):
178
287
  if array is None:
@@ -201,9 +310,14 @@ class ViewStrategy(LayerCollectionHandler):
201
310
  return pixmap_item
202
311
 
203
312
  def refresh_display(self):
204
- self.update_brush_cursor()
205
313
  for scene in self.get_scenes():
206
314
  scene.update()
315
+ self.update_brush_cursor()
316
+
317
+ def set_max_min_scales(self, img_width, img_height):
318
+ self.set_min_scale(min(gui_constants.MIN_ZOOMED_IMG_WIDTH / img_width,
319
+ gui_constants.MIN_ZOOMED_IMG_HEIGHT / img_height))
320
+ self.set_max_scale(gui_constants.MAX_ZOOMED_IMG_PX_SIZE)
207
321
 
208
322
  def zoom_in(self):
209
323
  if self.empty():
@@ -218,6 +332,15 @@ class ViewStrategy(LayerCollectionHandler):
218
332
  self.set_zoom_factor(new_scale)
219
333
  master_view.centerOn(old_center)
220
334
  self.update_brush_cursor()
335
+ self.update_cursor_pen_width()
336
+
337
+ def apply_zoom(self):
338
+ if self.empty():
339
+ return
340
+ for view in self.get_views():
341
+ current_scale = view.transform().m11()
342
+ scale_factor = self.zoom_factor() / current_scale
343
+ view.scale(scale_factor, scale_factor)
221
344
 
222
345
  def zoom_out(self):
223
346
  if self.empty():
@@ -232,12 +355,12 @@ class ViewStrategy(LayerCollectionHandler):
232
355
  self.set_zoom_factor(new_scale)
233
356
  master_view.centerOn(old_center)
234
357
  self.update_brush_cursor()
358
+ self.update_cursor_pen_width()
235
359
 
236
360
  def reset_zoom(self):
237
361
  if self.empty():
238
362
  return
239
363
  self.pinch_start_scale = 1.0
240
- self.last_scroll_pos = QPointF()
241
364
  self.gesture_active = False
242
365
  self.pinch_center_view = None
243
366
  self.pinch_center_scene = None
@@ -249,6 +372,7 @@ class ViewStrategy(LayerCollectionHandler):
249
372
  view.resetTransform()
250
373
  view.scale(self.zoom_factor(), self.zoom_factor())
251
374
  self.update_brush_cursor()
375
+ self.update_cursor_pen_width()
252
376
 
253
377
  def actual_size(self):
254
378
  if self.empty():
@@ -258,11 +382,7 @@ class ViewStrategy(LayerCollectionHandler):
258
382
  view.resetTransform()
259
383
  view.scale(self.zoom_factor(), self.zoom_factor())
260
384
  self.update_brush_cursor()
261
-
262
- def setup_outline_style(self):
263
- self.brush_cursor.setPen(QPen(QColor(*gui_constants.BRUSH_COLORS['pen']),
264
- gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()))
265
- self.brush_cursor.setBrush(Qt.NoBrush)
385
+ self.update_cursor_pen_width()
266
386
 
267
387
  def setup_simple_brush_style(self, center_x, center_y, radius):
268
388
  gradient = create_default_brush_gradient(center_x, center_y, radius, self.brush)
@@ -270,25 +390,47 @@ class ViewStrategy(LayerCollectionHandler):
270
390
  gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()))
271
391
  self.brush_cursor.setBrush(QBrush(gradient))
272
392
 
273
- def setup_brush_cursor(self):
274
- if not self.brush:
275
- return
276
- scene = self.get_master_scene()
393
+ def create_circle(self, scene, line_style=Qt.SolidLine):
277
394
  for item in scene.items():
278
395
  if isinstance(item, QGraphicsEllipseItem) and item != self.brush_preview:
279
396
  scene.removeItem(item)
280
- pen = QPen(QColor(*gui_constants.BRUSH_COLORS['pen']), 1)
281
- brush = QBrush(QColor(*gui_constants.BRUSH_COLORS['cursor_inner']))
282
- self.brush_cursor = scene.addEllipse(
283
- 0, 0, self.brush.size, self.brush.size, pen, brush)
284
- self.brush_cursor.setZValue(1000)
285
- self.brush_cursor.hide()
397
+ pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
398
+ pen = QPen(QColor(*gui_constants.BRUSH_COLORS['pen']), pen_width, line_style)
399
+ brush = Qt.NoBrush
400
+ scene_center = scene.sceneRect().center()
401
+ brush_cursor = scene.addEllipse(
402
+ scene_center.x(), scene_center.y(),
403
+ self.brush.size, self.brush.size, pen, brush)
404
+ brush_cursor.setZValue(1000)
405
+ brush_cursor.hide()
406
+ return brush_cursor
407
+
408
+ def create_alt_circle(self, scene, line_style=Qt.SolidLine):
409
+ for item in scene.items():
410
+ if isinstance(item, BrushCursor) and item != self.brush_preview:
411
+ scene.removeItem(item)
412
+ pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
413
+ pen = QPen(QColor(*gui_constants.BRUSH_COLORS['pen']), pen_width, line_style)
414
+ brush = Qt.NoBrush
415
+ scene_center = scene.sceneRect().center()
416
+ brush_cursor = BrushCursor(
417
+ scene_center.x(), scene_center.y(),
418
+ self.brush.size, pen, brush
419
+ )
420
+ brush_cursor.setZValue(1000)
421
+ brush_cursor.hide()
422
+ scene.addItem(brush_cursor)
423
+ return brush_cursor
286
424
 
287
- def update_brush_cursor(self):
288
- if self.empty():
425
+ def setup_brush_cursor(self):
426
+ if not self.brush:
289
427
  return
290
- if not self.brush_cursor or not self.isVisible():
428
+ self.brush_cursor = self.create_circle(self.get_master_scene())
429
+
430
+ def update_brush_cursor(self):
431
+ if self.empty() or self.brush_cursor is None or not self.isVisible():
291
432
  return
433
+ self.update_cursor_pen_width()
292
434
  master_view = self.get_master_view()
293
435
  mouse_pos = master_view.mapFromGlobal(QCursor.pos())
294
436
  if not master_view.rect().contains(mouse_pos):
@@ -299,21 +441,19 @@ class ViewStrategy(LayerCollectionHandler):
299
441
  radius = size / 2
300
442
  self.brush_cursor.setRect(scene_pos.x() - radius, scene_pos.y() - radius, size, size)
301
443
  allow_cursor_preview = self.display_manager.allow_cursor_preview()
302
- if self.cursor_style == 'preview' and allow_cursor_preview:
303
- self.setup_outline_style()
304
- self.brush_cursor.hide()
305
- pos = QCursor.pos()
306
- if isinstance(pos, QPointF):
307
- scene_pos = pos
308
- else:
309
- cursor_pos = master_view.mapFromGlobal(pos)
310
- scene_pos = master_view.mapToScene(cursor_pos)
311
- self.brush_preview.update(scene_pos, int(size))
444
+ if self.cursor_style == 'preview':
445
+ if allow_cursor_preview:
446
+ self.brush_cursor.hide()
447
+ pos = QCursor.pos()
448
+ if isinstance(pos, QPointF):
449
+ scene_pos = pos
450
+ else:
451
+ cursor_pos = master_view.mapFromGlobal(pos)
452
+ scene_pos = master_view.mapToScene(cursor_pos)
453
+ self.brush_preview.update(scene_pos, int(size))
312
454
  else:
313
455
  self.brush_preview.hide()
314
- if self.cursor_style == 'outline' or not allow_cursor_preview:
315
- self.setup_outline_style()
316
- else:
456
+ if self.cursor_style != 'outline':
317
457
  self.setup_simple_brush_style(scene_pos.x(), scene_pos.y(), radius)
318
458
  if not self.brush_cursor.isVisible():
319
459
  self.brush_cursor.show()
@@ -354,41 +494,52 @@ class ViewStrategy(LayerCollectionHandler):
354
494
  def keyPressEvent(self, event):
355
495
  if self.empty():
356
496
  return
357
- master_view = self.get_master_view()
358
497
  if event.key() == Qt.Key_Space and not self.scrolling:
359
498
  self.space_pressed = True
360
- master_view.setCursor(Qt.OpenHandCursor)
499
+ self.get_master_view().setCursor(Qt.OpenHandCursor)
361
500
  if self.brush_cursor:
362
501
  self.brush_cursor.hide()
363
- self.handle_key_press_event(event)
364
- if event.key() == Qt.Key_Control and not self.scrolling:
365
- self.control_pressed = True
366
- super().keyPressEvent(event)
502
+ if self.handle_key_press_event(event):
503
+ if event.key() == Qt.Key_Control and not self.scrolling:
504
+ self.control_pressed = True
505
+ super().keyPressEvent(event)
367
506
 
368
507
  def keyReleaseEvent(self, event):
369
508
  if self.empty():
370
509
  return
371
- master_view = self.get_master_view()
372
- self.update_brush_cursor()
373
510
  if event.key() == Qt.Key_Space:
374
511
  self.space_pressed = False
375
512
  if not self.scrolling:
376
- master_view.setCursor(Qt.BlankCursor)
513
+ self.get_master_view().setCursor(Qt.BlankCursor)
377
514
  if self.brush_cursor:
378
515
  self.brush_cursor.show()
379
- self.handle_key_release_event(event)
380
- if event.key() == Qt.Key_Control:
381
- self.control_pressed = False
382
- super().keyReleaseEvent(event)
516
+ if self.handle_key_release_event(event):
517
+ if event.key() == Qt.Key_Control:
518
+ self.control_pressed = False
519
+ super().keyReleaseEvent(event)
383
520
 
384
521
  def leaveEvent(self, event):
385
- if not self.empty():
522
+ if self.empty():
523
+ self.setCursor(Qt.ArrowCursor)
524
+ else:
386
525
  self.get_master_view().setCursor(Qt.ArrowCursor)
387
526
  if self.brush_cursor:
388
527
  self.brush_cursor.hide()
389
528
  super().leaveEvent(event)
390
529
  # pylint: enable=C0103
391
530
 
531
+ def scroll_view(self, view, delta_x, delta_y):
532
+ view.horizontalScrollBar().setValue(
533
+ view.horizontalScrollBar().value() - delta_x)
534
+ view.verticalScrollBar().setValue(
535
+ view.verticalScrollBar().value() - delta_y)
536
+ self.status.set_scroll(view.horizontalScrollBar().value(),
537
+ view.verticalScrollBar().value())
538
+
539
+ def center_image(self, view):
540
+ view.horizontalScrollBar().setValue(self.status.h_scroll)
541
+ view.verticalScrollBar().setValue(self.status.v_scroll)
542
+
392
543
  def mouse_move_event(self, event):
393
544
  if self.empty():
394
545
  return
@@ -424,12 +575,6 @@ class ViewStrategy(LayerCollectionHandler):
424
575
  self.last_mouse_pos = position
425
576
  self.scroll_view(master_view, delta.x(), delta.y())
426
577
 
427
- def scroll_view(self, view, delta_x, delta_y):
428
- view.horizontalScrollBar().setValue(
429
- view.horizontalScrollBar().value() - delta_x)
430
- view.verticalScrollBar().setValue(
431
- view.verticalScrollBar().value() - delta_y)
432
-
433
578
  def mouse_press_event(self, event):
434
579
  if self.empty():
435
580
  return
@@ -464,3 +609,28 @@ class ViewStrategy(LayerCollectionHandler):
464
609
  elif self.dragging:
465
610
  self.dragging = False
466
611
  self.brush_operation_ended.emit()
612
+
613
+ def handle_pinch_gesture(self, pinch):
614
+ master_view = self.get_master_view()
615
+ if pinch.state() == Qt.GestureStarted:
616
+ self.pinch_start_scale = self.zoom_factor()
617
+ self.pinch_center_view = pinch.centerPoint()
618
+ self.pinch_center_scene = master_view.mapToScene(self.pinch_center_view.toPoint())
619
+ self.gesture_active = True
620
+ elif pinch.state() == Qt.GestureUpdated:
621
+ new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
622
+ new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
623
+ if abs(new_scale - self.zoom_factor()) > 0.01:
624
+ self.set_zoom_factor(new_scale)
625
+ self.apply_zoom()
626
+ new_center = master_view.mapToScene(self.pinch_center_view.toPoint())
627
+ delta = self.pinch_center_scene - new_center
628
+ h_scroll = master_view.horizontalScrollBar().value() + \
629
+ int(delta.x() * self.zoom_factor())
630
+ v_scroll = master_view.verticalScrollBar().value() + \
631
+ int(delta.y() * self.zoom_factor())
632
+ self.status.set_scroll(h_scroll, v_scroll)
633
+ self.center_image(master_view)
634
+ elif pinch.state() in (Qt.GestureFinished, Qt.GestureCanceled):
635
+ self.gesture_active = False
636
+ self.update_cursor_pen_width()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.4.0
3
+ Version: 1.5.1
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -70,11 +70,11 @@ The GUI has two main working areas:
70
70
 
71
71
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/gui-retouch.png' width="600" referrerpolicy="no-referrer">
72
72
 
73
- # Resources
73
+ ## Resources
74
74
 
75
75
  🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
76
76
 
77
- # Note for macOS users
77
+ ## Note for macOS users
78
78
 
79
79
  **The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
80
80
 
@@ -93,17 +93,17 @@ xattr -cr ~/Downloads/shinestacker/shinestacker.app
93
93
 
94
94
  macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
95
95
 
96
- # Credits
96
+ ## Credits
97
97
 
98
98
  The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
99
99
 
100
- # Resources
100
+ ## Resources
101
101
 
102
102
  * [Pyramid Methods in Image Processing](https://www.researchgate.net/publication/246727904_Pyramid_Methods_in_Image_Processing), E. H. Adelson, C. H. Anderson, J. R. Bergen, P. J. Burt, J. M. Ogden, RCA Engineer, 29-6, Nov/Dec 1984
103
103
  Pyramid methods in image processing
104
104
  * [A Multi-focus Image Fusion Method Based on Laplacian Pyramid](http://www.jcomputers.us/vol6/jcp0612-07.pdf), Wencheng Wang, Faliang Chang, Journal of Computers 6 (12), 2559, December 2011
105
105
 
106
- # License
106
+ ## License
107
107
 
108
108
  <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
109
109
 
@@ -112,7 +112,7 @@ Pyramid methods in image processing
112
112
 
113
113
  - **Logo**: The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista). Copyright © Alessandro Lista. All rights reserved. The logo is not covered by the LGPL-3.0 license of this project.
114
114
 
115
- # Attribution request
115
+ ## Attribution request
116
116
  📸 If you publish images created with Shine Stacker, please consider adding a note such as:
117
117
 
118
118
  *Created with Shine Stacker – https://github.com/lucalista/shinestacker*
@@ -1,5 +1,5 @@
1
1
  shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
2
- shinestacker/_version.py,sha256=R4yO1-T_1mGmXG30xDxIA44z6cxCh-PK6l33V9MTqxk,21
2
+ shinestacker/_version.py,sha256=oU1lLCdhmPP8CUfgzMFfhCPjCfk4eDfd8WSwOfXNBOk,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,16 +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
26
  shinestacker/app/gui_utils.py,sha256=fSpkwPXTON_l676UHdAnJNrGq7BPbSlPOiHpOF_LZaI,2519
26
27
  shinestacker/app/help_menu.py,sha256=g8lKG_xZmXtNQaC3SIRzyROKVWva_PLEgZsQWh6zUcQ,499
27
- shinestacker/app/main.py,sha256=XdWYUm1ed1q7aPghxgaFwWJVNijCvZZ_WG0fux8Nisc,10459
28
+ shinestacker/app/main.py,sha256=dcitc5vwIIyIXeDfZwQnC7KHRCdd3FaVJWyaU8mK86c,10935
28
29
  shinestacker/app/open_frames.py,sha256=bsu32iJSYJQLe_tQQbvAU5DuMDVX6MRuNdE7B5lojZc,1488
29
- shinestacker/app/project.py,sha256=oopOiqU6bOK1cQdpot88z49KbKrlB-LAz_q4-8Iui0U,2819
30
- shinestacker/app/retouch.py,sha256=dpSozNWSxL6wIO0SMjoviDbXZbbfRN_rVLjeL324c54,2527
30
+ shinestacker/app/project.py,sha256=X98pK_mMtE_NefTUZfebEaP1YCsVY97hcQD4bSxuNyY,2777
31
+ shinestacker/app/retouch.py,sha256=wlk-tHaei5YAFinGZWyzBopUhUqyxMT6jSH-4DMEwo8,2659
31
32
  shinestacker/config/__init__.py,sha256=aXxi-LmAvXd0daIFrVnTHE5OCaYeK1uf1BKMr7oaXQs,197
32
33
  shinestacker/config/config.py,sha256=eBko2D3ADhLTIm9X6hB_a_WsIjwgfE-qmBVkhP1XSvc,1636
33
34
  shinestacker/config/constants.py,sha256=EEdr7pZg4JpbIjUWaP7kJQfTuBB85FN739myDNAfn8A,8301
34
- shinestacker/config/gui_constants.py,sha256=7oZtBaeUxzU7WmbcR7yvQVlzQyRUaIMRcxY3SrKRGOk,2589
35
+ shinestacker/config/gui_constants.py,sha256=Aqan-AdUjtlARXpsefQvWW2uKVv1tvwc0gfKyo7xud4,2787
35
36
  shinestacker/core/__init__.py,sha256=IUEIx6SQ3DygDEHN3_E6uKpHjHtUa4a_U_1dLd_8yEU,484
36
37
  shinestacker/core/colors.py,sha256=kr_tJA1iRsdck2JaYDb2lS-codZ4Ty9gdu3kHfiWvuM,1340
37
38
  shinestacker/core/core_utils.py,sha256=1LYj19Dfc9jZN9-4dlf1paximDH5WZYa7DXvKr7R7QY,1719
@@ -50,7 +51,7 @@ shinestacker/gui/gui_logging.py,sha256=kiZcrC2AFYCWgPZo0O5SKw-E5cFrezwf4anS3HjPu
50
51
  shinestacker/gui/gui_run.py,sha256=zr7x4BVmM0n_ZRsSEaJVVKvHSWHuwhftgkUvgeg90gU,15767
51
52
  shinestacker/gui/main_window.py,sha256=5k_9TiZT9idKCmovUFYpUTSEQQj-DMQrlyq9dAgY1MU,24800
52
53
  shinestacker/gui/menu_manager.py,sha256=b5Cxh6uddOlio8i7fRISbGDJI-oe0ds6LIF5dWM7leI,11263
53
- shinestacker/gui/new_project.py,sha256=VSUaq1xm9CR0gimKHRKfCdQOQ-ErE1sxGmu6x14nlAQ,16113
54
+ shinestacker/gui/new_project.py,sha256=z8e3EhRMB-KtoPwYQSiKLSOQ2dS0-Okm7zVw21B7zy8,16391
54
55
  shinestacker/gui/project_controller.py,sha256=W4sbBGEPVtfF9F1rC-6Y0oKLq_y94HuFBvZRj87xNKQ,16272
55
56
  shinestacker/gui/project_converter.py,sha256=Gmna0HwbvACcXiX74TaQYumif8ZV8sZ2APLTMM-L1mU,7436
56
57
  shinestacker/gui/project_editor.py,sha256=lSgQ42IoaobHs-NQQWT88Qhg5l7nu5ejxAO5VgIupr8,25498
@@ -70,34 +71,35 @@ shinestacker/gui/img/forward-button-icon.png,sha256=lNw86T4TOEd_uokHYF8myGSGUXzd
70
71
  shinestacker/gui/img/play-button-round-icon.png,sha256=9j6Ks9mOGa-2cXyRFpimepAAvSaHzqJKBfxShRb4_dE,4595
71
72
  shinestacker/gui/img/plus-round-line-icon.png,sha256=LS068Hlu-CeBvJuB3dwwdJg1lZq6D5MUIv53lu1yKJA,7534
72
73
  shinestacker/retouch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- shinestacker/retouch/base_filter.py,sha256=l8sdqKJ8OS2EEwMVVF8DLLLqE_kvYudQ_nUBo_GnC-0,10346
74
+ shinestacker/retouch/base_filter.py,sha256=gvRGvhOhhWS05jKOXyNdMnia3b7rscQqR8-xnySIWFc,10260
74
75
  shinestacker/retouch/brush.py,sha256=dzD2FzSpBIPdJRmTZobcrQ1FrVd3tF__ZPnUplNE72s,357
75
76
  shinestacker/retouch/brush_gradient.py,sha256=F5SFhyzl8YTMqjJU3jK8BrIlLCYLUvITd5wz3cQE4xk,1453
76
- shinestacker/retouch/brush_preview.py,sha256=V-6yRH-paXOEC9bTCJ1NiE6xkTPUiJQGo1N61MMG1AI,4969
77
+ shinestacker/retouch/brush_preview.py,sha256=cOFVMCbEsgR_alzmr_-LLghtGU_unrE-hAjLHcvrZAY,5484
77
78
  shinestacker/retouch/brush_tool.py,sha256=8uVncTA375uC3Nhp2YM0eZjpOR-nN47i2eGjN8tJzOU,8714
78
79
  shinestacker/retouch/denoise_filter.py,sha256=TDUHzhRKlKvCa3D5SCYCZKTpjcl81kGwmONsgSDtO1k,440
79
- shinestacker/retouch/display_manager.py,sha256=9HKCU8HFs3OyenEa2sk1FanaRCWpmPt77x6G4JfhJ-c,8888
80
+ shinestacker/retouch/display_manager.py,sha256=wwbX4n7ulg53fM3E5QL5YEb5IFV0jN8za6yfYc74anA,8624
80
81
  shinestacker/retouch/exif_data.py,sha256=LF-fRXW-reMq-xJ_QRE5j8DC2LVGKIlC6MR3QbC1cdg,1896
81
82
  shinestacker/retouch/file_loader.py,sha256=z02-A8_uDZxayI1NFTxT2GVUvEBWStchX9hlN1o5-0U,4784
82
83
  shinestacker/retouch/filter_manager.py,sha256=SdYIZkZBUvuB6wDG0moGWav5sfEvIcB9ioUJR5wJFts,388
83
84
  shinestacker/retouch/icon_container.py,sha256=6gw1HO1bC2FrdB4dc_iH81DQuLjzuvRGksZ2hKLT9yA,585
84
- shinestacker/retouch/image_editor_ui.py,sha256=Y7gzbgkWa3ehQ9dUahiY4TGoQPkwI00BHTGwobdm8L4,32442
85
- shinestacker/retouch/image_view_status.py,sha256=8M1IA0T2cATWhOx1Zv0K9u8yMkujSnWLqy5zlEEy5c4,1745
86
- shinestacker/retouch/image_viewer.py,sha256=TV4RsEGQC4nhpfS7MCjQDd-ASojcVZpbcFKbi-TuDhQ,4187
87
- shinestacker/retouch/io_gui_handler.py,sha256=Upt8VWwDUrB-UnqCI54SQQwfR4w9kfznCdRL2UwupWQ,12010
85
+ shinestacker/retouch/image_editor_ui.py,sha256=ifrlHafXdELwCXoVfUiSM7jcz3O6qH7z2WjNpNPEmUs,33505
86
+ shinestacker/retouch/image_view_status.py,sha256=bdIhsXiYXm7eyjkTGWkw5PRShzaF_by-g7daqgmhwjM,1858
87
+ shinestacker/retouch/image_viewer.py,sha256=D7feFlaZkR2nzJ9VcKcVg8lYPS7AMjA_uxJs9L099kQ,4412
88
+ shinestacker/retouch/io_gui_handler.py,sha256=BRQ5eSt1tCMDYtOqxfdYGhx2BLCrncfNrNLGuWIy5Rk,11873
88
89
  shinestacker/retouch/io_manager.py,sha256=JUAA--AK0mVa1PTErJTnBFjaXIle5Qs7Ow0Wkd8at0o,2437
89
- shinestacker/retouch/layer_collection.py,sha256=Q7zoCYRn__jYkfrEC2lY1uKHWfOUbsJ27xaYHIoKVxo,5730
90
- shinestacker/retouch/overlaid_view.py,sha256=J4r9DqHzhPrPgJ2IUGuAB26UGxkr2Kf50NUo_NM6w98,8468
91
- shinestacker/retouch/shortcuts_help.py,sha256=EDxwR7MZwUC9NHLjvqAlh5iEHT9g3g8Tzl18VUGErI4,4130
92
- shinestacker/retouch/sidebyside_view.py,sha256=tS50VzmTc0Iqf9k3fIbQ3jeIIkDyOO_6TJoNiGg7pLI,19423
93
- shinestacker/retouch/undo_manager.py,sha256=_ekbcOLcPbQLY7t-o8wf-b1uA6OPY9rRyLM-KqMlQRo,3257
90
+ shinestacker/retouch/layer_collection.py,sha256=fZlGrkm9-Ycc7AOzFSpImhafiTieBeCZRk-UlvlFHbo,5819
91
+ shinestacker/retouch/overlaid_view.py,sha256=0V6Y0wVmf7vPzhB4O9BSF8UwBnw0RdBdYCt_tHD4vls,8478
92
+ shinestacker/retouch/shortcuts_help.py,sha256=BFWTT5QvodqMhqa_9LI25hZqjICfckgyWG4fGrGzvnM,4283
93
+ shinestacker/retouch/sidebyside_view.py,sha256=We4hY_ZGwINPVPHACPGYFPaiU3Khpax7Sf9Xur317FA,17358
94
+ shinestacker/retouch/transformation_manager.py,sha256=NSHGUF-JFv4Y81gSvizjQCTp49TLo1so7c0WoUElO08,1812
95
+ shinestacker/retouch/undo_manager.py,sha256=cKUkqnJtnJ-Hq-LQs5Bv49FC6qkG6XSw9oCVySJ8jS0,4312
94
96
  shinestacker/retouch/unsharp_mask_filter.py,sha256=uFnth8fpZFGhdIgJCnS8x5v6lBQgJ3hX0CBke9pFXeM,3510
95
- shinestacker/retouch/view_strategy.py,sha256=DjBXWsrJJPU4UmdHIeqy_AI6ltDWHBr-RRaFwwYwc04,17061
97
+ shinestacker/retouch/view_strategy.py,sha256=5AiSYH_FkeIhaUMtQwZ3v1_8r6KckvOYPFoufEtw12Y,23877
96
98
  shinestacker/retouch/vignetting_filter.py,sha256=MA97rQkSL0D-Nh-n2L4AiPR064RoTROkvza4tw84g9U,3658
97
99
  shinestacker/retouch/white_balance_filter.py,sha256=glMBYlmrF-i_OrB3sGUpjZE6X4FQdyLC4GBy2bWtaFc,6056
98
- shinestacker-1.4.0.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
99
- shinestacker-1.4.0.dist-info/METADATA,sha256=gqcYTHpTNsHmYEUz76mZzWXREe3d0D_iVmfesRQsyM0,6972
100
- shinestacker-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
101
- shinestacker-1.4.0.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
102
- shinestacker-1.4.0.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
103
- shinestacker-1.4.0.dist-info/RECORD,,
100
+ shinestacker-1.5.1.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
101
+ shinestacker-1.5.1.dist-info/METADATA,sha256=FrhfVRsmH4sx7-cQxDajn-zZinLt-UIhhHf2xYZNzvM,6978
102
+ shinestacker-1.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
+ shinestacker-1.5.1.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
104
+ shinestacker-1.5.1.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
105
+ shinestacker-1.5.1.dist-info/RECORD,,