shinestacker 0.3.0__py3-none-any.whl → 0.3.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.

@@ -1,26 +1,10 @@
1
1
  import math
2
2
  from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
3
- from PySide6.QtGui import QPixmap, QPainter, QColor, QPen, QBrush, QCursor, QShortcut, QKeySequence, QRadialGradient
4
- from PySide6.QtCore import Qt, QRectF, QTime, QPoint, Signal
3
+ from PySide6.QtGui import QPixmap, QPainter, QColor, QPen, QBrush, QCursor, QShortcut, QKeySequence
4
+ from PySide6.QtCore import Qt, QRectF, QTime, QPoint, QPointF, Signal
5
5
  from .. config.gui_constants import gui_constants
6
6
  from .brush_preview import BrushPreviewItem
7
-
8
-
9
- def create_brush_gradient(center_x, center_y, radius, hardness, inner_color=None, outer_color=None, opacity=100):
10
- gradient = QRadialGradient(center_x, center_y, float(radius))
11
- inner = inner_color if inner_color is not None else QColor(*gui_constants.BRUSH_COLORS['inner'])
12
- outer = outer_color if outer_color is not None else QColor(*gui_constants.BRUSH_COLORS['gradient_end'])
13
- inner_with_opacity = QColor(inner)
14
- inner_with_opacity.setAlpha(int(float(inner.alpha()) * float(opacity) / 100.0))
15
- if hardness < 100:
16
- hardness_normalized = float(hardness) / 100.0
17
- gradient.setColorAt(0.0, inner_with_opacity)
18
- gradient.setColorAt(hardness_normalized, inner_with_opacity)
19
- gradient.setColorAt(1.0, outer)
20
- else:
21
- gradient.setColorAt(0.0, inner_with_opacity)
22
- gradient.setColorAt(1.0, inner_with_opacity)
23
- return gradient
7
+ from .brush_gradient import create_brush_gradient
24
8
 
25
9
 
26
10
  class ImageViewer(QGraphicsView):
@@ -74,6 +58,8 @@ class ImageViewer(QGraphicsView):
74
58
  self.empty = False
75
59
  self.setFocus()
76
60
  self.activateWindow()
61
+ self.brush_preview.layer_collection = self.image_editor.layer_collection
62
+ self.brush_preview.brush = self.brush
77
63
 
78
64
  def clear_image(self):
79
65
  self.scene.clear()
@@ -123,7 +109,7 @@ class ImageViewer(QGraphicsView):
123
109
  def mousePressEvent(self, event):
124
110
  if self.empty:
125
111
  return
126
- if event.button() == Qt.LeftButton and self.image_editor.master_layer is not None:
112
+ if event.button() == Qt.LeftButton and self.image_editor.layer_collection.master_layer is not None:
127
113
  if self.space_pressed:
128
114
  self.scrolling = True
129
115
  self.last_mouse_pos = event.position()
@@ -245,7 +231,13 @@ class ImageViewer(QGraphicsView):
245
231
  if self.cursor_style == 'preview' and allow_cursor_preview:
246
232
  self._setup_outline_style()
247
233
  self.brush_cursor.hide()
248
- self.brush_preview.update(self.image_editor, QCursor.pos(), int(size))
234
+ pos = QCursor.pos()
235
+ if isinstance(pos, QPointF):
236
+ scene_pos = pos
237
+ else:
238
+ cursor_pos = self.image_editor.image_viewer.mapFromGlobal(pos)
239
+ scene_pos = self.image_editor.image_viewer.mapToScene(cursor_pos)
240
+ self.brush_preview.update(scene_pos, int(size))
249
241
  else:
250
242
  self.brush_preview.hide()
251
243
  if self.cursor_style == 'outline' or not allow_cursor_preview:
@@ -0,0 +1,57 @@
1
+ import cv2
2
+ from .. core.exceptions import ShapeError, BitDepthError
3
+ from .. algorithms.utils import read_img, validate_image, get_img_metadata
4
+ from .. algorithms.exif import get_exif, write_image_with_exif_data
5
+ from .. algorithms.multilayer import write_multilayer_tiff_from_images
6
+
7
+
8
+ class IOManager:
9
+ def __init__(self, layer_collection):
10
+ self.layer_collection = layer_collection
11
+ self.current_file_path = ''
12
+ self.exif_path = ''
13
+ self.exif_data = None
14
+
15
+ def import_frames(self, file_paths):
16
+ stack = []
17
+ labels = []
18
+ master = None
19
+ shape, dtype = get_img_metadata(self.layer_collection.master_layer)
20
+ for path in file_paths:
21
+ try:
22
+ label = path.split("/")[-1].split(".")[0]
23
+ img = cv2.cvtColor(read_img(path), cv2.COLOR_BGR2RGB)
24
+ if shape is not None and dtype is not None:
25
+ validate_image(img, shape, dtype)
26
+ else:
27
+ shape, dtype = get_img_metadata(img)
28
+ label_x = label
29
+ i = 0
30
+ while label_x in labels:
31
+ i += 1
32
+ label_x = f"{label} ({i})"
33
+ labels.append(label_x)
34
+ stack.append(img)
35
+ if master is None:
36
+ master = img.copy()
37
+ except ShapeError as e:
38
+ raise ShapeError(f"All files must have the same shape.\n{str(e)}")
39
+ except BitDepthError as e:
40
+ raise BitDepthError(f"All files must have the same bit depth.\n{str(e)}")
41
+ except Exception as e:
42
+ raise RuntimeError(f"Error loading file: {path}.\n{str(e)}")
43
+ return stack, labels, master
44
+
45
+ def save_multilayer(self, path):
46
+ master_layer = {'Master': self.layer_collection.master_layer}
47
+ individual_layers = {label: image for label, image in zip(
48
+ self.layer_collection.layer_labels, self.layer_collection.layer_stack)}
49
+ write_multilayer_tiff_from_images({**master_layer, **individual_layers}, path, exif_path=self.exif_path)
50
+
51
+ def save_master(self, path):
52
+ img = cv2.cvtColor(self.layer_collection.master_layer, cv2.COLOR_RGB2BGR)
53
+ write_image_with_exif_data(self.exif_data, img, path)
54
+
55
+ def set_exif_data(self, path):
56
+ self.exif_path = path
57
+ self.exif_data = get_exif(path)
@@ -0,0 +1,54 @@
1
+ import numpy as np
2
+
3
+
4
+ class LayerCollection:
5
+ def __init__(self):
6
+ self.master_layer = None
7
+ self.master_layer_copy = None
8
+ self.layer_stack = None
9
+ self.layer_labels = None
10
+ self.current_layer_idx = 0
11
+
12
+ def number_of_layers(self):
13
+ return len(self.layer_stack)
14
+
15
+ def valid_current_layer_idx(self):
16
+ return 0 <= self.current_layer_idx < self.number_of_layers()
17
+
18
+ def current_layer(self):
19
+ if self.layer_stack is not None and self.valid_current_layer_idx():
20
+ return self.layer_stack[self.current_layer_idx]
21
+ return None
22
+
23
+ def copy_master_layer(self):
24
+ self.master_layer_copy = self.master_layer.copy()
25
+
26
+ def sort_layers(self, order):
27
+ master_index = -1
28
+ master_label = None
29
+ master_layer = None
30
+ for i, label in enumerate(self.layer_labels):
31
+ if label.lower() == "master":
32
+ master_index = i
33
+ master_label = self.layer_labels.pop(i)
34
+ master_layer = self.layer_stack[i]
35
+ self.layer_stack = np.delete(self.layer_stack, i, axis=0)
36
+ break
37
+ if order == 'asc':
38
+ self.sorted_indices = sorted(range(len(self.layer_labels)),
39
+ key=lambda i: self.layer_labels[i].lower())
40
+ elif order == 'desc':
41
+ self.sorted_indices = sorted(range(len(self.layer_labels)),
42
+ key=lambda i: self.layer_labels[i].lower(),
43
+ reverse=True)
44
+ else:
45
+ raise ValueError(f"Invalid sorting order: {order}")
46
+ self.layer_labels = [self.layer_labels[i] for i in self.sorted_indices]
47
+ self.layer_stack = self.layer_stack[self.sorted_indices]
48
+ if master_index != -1:
49
+ self.layer_labels.insert(0, master_label)
50
+ self.layer_stack = np.insert(self.layer_stack, 0, master_layer, axis=0)
51
+ self.master_layer = master_layer.copy()
52
+ self.master_layer.setflags(write=True)
53
+ if self.current_layer_idx >= self.number_of_layers():
54
+ self.current_layer_idx = self.number_of_layers() - 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 0.3.0
3
+ Version: 0.3.1
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=sxG9J11a6Qpu_VcqsARRqGSPByHtDvQqR5ozWYjHfZU,387
2
- shinestacker/_version.py,sha256=gTggO06fb2c9XKEwlQYUSPlUfy82yVlM9pzLMOUqVcY,21
2
+ shinestacker/_version.py,sha256=2KwowXhmiT6-Bln7VPq9d9sRpAzJq9qLyclhp2KWmjA,21
3
3
  shinestacker/algorithms/__init__.py,sha256=X9nwToaB13GAACoaE-K0o9R1Jt7g9xSzeJI8Ra2GcL8,722
4
4
  shinestacker/algorithms/align.py,sha256=As7MIfjoJc_1vJ058iwP7b_PnDnfYsBQ1JNZ4GX5ZU4,16529
5
5
  shinestacker/algorithms/balance.py,sha256=UOmyUPJVBswUYWvYIB8WdlfTAxUAahZrnxQUSrYJ3I4,15649
@@ -13,8 +13,9 @@ shinestacker/algorithms/pyramid.py,sha256=iUbgRI0p0uzEXcZetAm3hgzwiXhF4mIaNxYMUU
13
13
  shinestacker/algorithms/sharpen.py,sha256=KtTDIytJW7z4Oj-E1FcxKvhzz27xLX2CCSwJAG_QRUY,911
14
14
  shinestacker/algorithms/stack.py,sha256=EZm2SIuoh8c7hgySB2tQGydKYQjJa2hEqR8YNzIeRuQ,5194
15
15
  shinestacker/algorithms/stack_framework.py,sha256=aJBjkQFxiPNjnsnZyoF8lXKR17tm0rTO7puEu_ZvASU,10928
16
- shinestacker/algorithms/utils.py,sha256=TV3NaGe6_2JTgGdFe4kKNzgihjt6vcK-SLy-HHnbts0,2054
16
+ shinestacker/algorithms/utils.py,sha256=Z81_tHTjzH_8oJ44UmgukHtm1Wbbm8g3YTCy4jBYrWo,2164
17
17
  shinestacker/algorithms/vignetting.py,sha256=EiD4O8GJPGOqByjDAfFj-de4cb64Qf685RujqlBgvX0,6774
18
+ shinestacker/algorithms/white_balance.py,sha256=AN33zegS8hMJK7I-7UZVmjaOg5PXy9wTnDei12EnPWg,470
18
19
  shinestacker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
20
  shinestacker/app/about_dialog.py,sha256=G_2hRQFpo96q0H8z02fMZ0Jl_2-PpnwNxqISY7Xn5Kg,885
20
21
  shinestacker/app/app_config.py,sha256=tQ1JBTG2gtHO1UbJYJjjYUBCakmtZpafvMUTHb5OyUo,1117
@@ -42,7 +43,7 @@ shinestacker/gui/gui_images.py,sha256=3HySCrldbYhUyxO0B1ckC2TLVg5nq94bBXjqsQzCyC
42
43
  shinestacker/gui/gui_logging.py,sha256=sN82OsGhMcZdgFMY4z-VbUYiRIsReN-ICaxi31M1J6E,8147
43
44
  shinestacker/gui/gui_run.py,sha256=LpBi1V91NrJpVpgS098lSgLtiege0aqcWIGwSbB8cL4,15701
44
45
  shinestacker/gui/main_window.py,sha256=KtMe6Hm74xk-lP3qcUsWAw0dzT7CynpdbZA_H651XN0,27203
45
- shinestacker/gui/new_project.py,sha256=kGTt8zf24w5o8wUOc0QgXVDbvi4XcM4ia0CZftUImOc,7002
46
+ shinestacker/gui/new_project.py,sha256=0bcFg8iB8yxSom0itiO1f9bIcKrCAEgT8aSgUUKyomQ,7075
46
47
  shinestacker/gui/project_converter.py,sha256=d66pbBzaBgANpudJLW0tGUSfSy0PXNhs1M6R2o_Fd5E,7390
47
48
  shinestacker/gui/project_editor.py,sha256=s0IU3l3xO_JGovazOHhKkT1rraUWNVzyAua1dsUqJyg,21657
48
49
  shinestacker/gui/project_model.py,sha256=buzpxppLuoNWao7M2_FOPVpCBex2WgEcvqyq9dxvrz8,4524
@@ -57,18 +58,21 @@ shinestacker/gui/img/plus-round-line-icon.png,sha256=LS068Hlu-CeBvJuB3dwwdJg1lZq
57
58
  shinestacker/retouch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
59
  shinestacker/retouch/brush.py,sha256=49YNdZp1dbfowh6HmLfxuHKz7Py9wkFQsN9-pH38P7Q,319
59
60
  shinestacker/retouch/brush_controller.py,sha256=R_pWs7Cf_5sC07TkkqqHnX3L_vV5j9CP8S3TkoxYbvs,2945
60
- shinestacker/retouch/brush_preview.py,sha256=ePyoZPdSY4ytK4uXV-eByQgfsqvhHJqXpleycSxshDg,5247
61
+ shinestacker/retouch/brush_gradient.py,sha256=b7vEnluGkdUVXn7AIECEhQUh1OYbBFIv2UbW8exBRWg,1014
62
+ shinestacker/retouch/brush_preview.py,sha256=3GL8FC6Z3zJ1hxfC4Wt3NkZ2AhwSHY_SAjFT2-cMRnM,5018
61
63
  shinestacker/retouch/exif_data.py,sha256=cYl0ZAAEOmimh2O0REtY0fU_1Y_4uIUtk9orpVLyQKY,2234
62
64
  shinestacker/retouch/file_loader.py,sha256=TbPjiD9Vv-i3LCsPVcumYJ2DxgnLmQA6xYCiLCqbEcg,4565
63
- shinestacker/retouch/image_editor.py,sha256=j-IMm8vWrUgI8XROWgGWDpeQ2HKK5MgrC_BPIr4uijI,28461
64
- shinestacker/retouch/image_editor_ui.py,sha256=-N_ZSuzC6AjDgf0t04un-WStBQcH3c3-C-6uJjHsXCw,16723
65
- shinestacker/retouch/image_filters.py,sha256=un1CxlMdfzlBEQ1x7RHPLrb5iDsyUihDYel2uyk40qA,19129
66
- shinestacker/retouch/image_viewer.py,sha256=5t7JRNoNwSgS7btn7zWmSXPPzXvZKbWBiZMm-YA7Xhg,14827
65
+ shinestacker/retouch/image_editor.py,sha256=uUSMTN8C0MOXK-g0P43gtiQo_lZfeymsV_gIuqThSAk,24027
66
+ shinestacker/retouch/image_editor_ui.py,sha256=klPE328IW9ykZFxlHHaWnsxa2kPjnv83Dt8pTFcPVSs,16943
67
+ shinestacker/retouch/image_filters.py,sha256=6X1Y-GvaYxswJBgT0mcQukmXZsgvLxmS2cd6tqb-LJs,16667
68
+ shinestacker/retouch/image_viewer.py,sha256=oisEnpX2gxneFJ1RqAsQQm9rooty1hhZHWWd2F7C5sQ,14385
69
+ shinestacker/retouch/io_manager.py,sha256=PxB_VLfhuCzGHu-lr41hMmsF_jr9PujLU5i5k_JZcBo,2405
70
+ shinestacker/retouch/layer_collection.py,sha256=xUhRBWpo03or9PKNM5oRIA3VKLUcboWe8SAbtvFfulc,2193
67
71
  shinestacker/retouch/shortcuts_help.py,sha256=iYwD7YGV2epNDwOBVDc99zqB9bbxLR_IUtanMJ-abPQ,3749
68
72
  shinestacker/retouch/undo_manager.py,sha256=5jWPsaERSh7TuWV5EbvqL6EsRS88Ct6VmM7FHxYzufs,3042
69
- shinestacker-0.3.0.dist-info/licenses/LICENSE,sha256=cBN0P3F6BWFkfOabkhuTxwJnK1B0v50jmmzZJjGGous,80
70
- shinestacker-0.3.0.dist-info/METADATA,sha256=lqCr530LfhBtToLsuwCNx2LUN37n0pq4QMDgI4LbETE,4219
71
- shinestacker-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
72
- shinestacker-0.3.0.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
73
- shinestacker-0.3.0.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
74
- shinestacker-0.3.0.dist-info/RECORD,,
73
+ shinestacker-0.3.1.dist-info/licenses/LICENSE,sha256=cBN0P3F6BWFkfOabkhuTxwJnK1B0v50jmmzZJjGGous,80
74
+ shinestacker-0.3.1.dist-info/METADATA,sha256=7K0Xd0y_bZtCbSIs3cKcGfswt2sePqDygGRjb9CkRDs,4219
75
+ shinestacker-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
76
+ shinestacker-0.3.1.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
77
+ shinestacker-0.3.1.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
78
+ shinestacker-0.3.1.dist-info/RECORD,,