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.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/utils.py +4 -0
- shinestacker/algorithms/white_balance.py +13 -0
- shinestacker/gui/new_project.py +1 -0
- shinestacker/retouch/brush_gradient.py +20 -0
- shinestacker/retouch/brush_preview.py +11 -14
- shinestacker/retouch/image_editor.py +93 -202
- shinestacker/retouch/image_editor_ui.py +15 -8
- shinestacker/retouch/image_filters.py +307 -379
- shinestacker/retouch/image_viewer.py +13 -21
- shinestacker/retouch/io_manager.py +57 -0
- shinestacker/retouch/layer_collection.py +54 -0
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.1.dist-info}/METADATA +1 -1
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.1.dist-info}/RECORD +18 -14
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.1.dist-info}/WHEEL +0 -0
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-0.3.0.dist-info → shinestacker-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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
|
-
|
|
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,5 +1,5 @@
|
|
|
1
1
|
shinestacker/__init__.py,sha256=sxG9J11a6Qpu_VcqsARRqGSPByHtDvQqR5ozWYjHfZU,387
|
|
2
|
-
shinestacker/_version.py,sha256=
|
|
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=
|
|
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=
|
|
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/
|
|
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=
|
|
64
|
-
shinestacker/retouch/image_editor_ui.py,sha256
|
|
65
|
-
shinestacker/retouch/image_filters.py,sha256=
|
|
66
|
-
shinestacker/retouch/image_viewer.py,sha256=
|
|
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.
|
|
70
|
-
shinestacker-0.3.
|
|
71
|
-
shinestacker-0.3.
|
|
72
|
-
shinestacker-0.3.
|
|
73
|
-
shinestacker-0.3.
|
|
74
|
-
shinestacker-0.3.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|