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
shinestacker/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.3.
|
|
1
|
+
__version__ = '0.3.1'
|
shinestacker/algorithms/utils.py
CHANGED
|
@@ -47,10 +47,14 @@ def img_bw(img):
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def get_img_metadata(img):
|
|
50
|
+
if img is None:
|
|
51
|
+
return None, None
|
|
50
52
|
return img.shape[:2], img.dtype
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
def validate_image(img, expected_shape=None, expected_dtype=None):
|
|
56
|
+
if img is None:
|
|
57
|
+
raise RuntimeError("Image is None")
|
|
54
58
|
shape, dtype = get_img_metadata(img)
|
|
55
59
|
if expected_shape and shape[:2] != expected_shape[:2]:
|
|
56
60
|
raise ShapeError(expected_shape, shape)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def white_balance_from_rgb(img, target_rgb):
|
|
5
|
+
img_float = img.astype(np.float64)
|
|
6
|
+
target_bgr = (target_rgb[2], target_rgb[1], target_rgb[0])
|
|
7
|
+
target_gray = sum(target_bgr) / 3.0
|
|
8
|
+
scales = [target_gray / val if val != 0 else 1.0 for val in target_bgr]
|
|
9
|
+
for c in range(3):
|
|
10
|
+
img_float[..., c] *= scales[c]
|
|
11
|
+
max_val = np.iinfo(img.dtype).max
|
|
12
|
+
img_float = np.clip(img_float, 0, max_val)
|
|
13
|
+
return img_float.astype(img.dtype)
|
shinestacker/gui/new_project.py
CHANGED
|
@@ -85,6 +85,7 @@ class NewProjectDialog(QDialog):
|
|
|
85
85
|
bunch_overlap_range = gui_constants.NEW_PROJECT_BUNCH_OVERLAP
|
|
86
86
|
self.bunch_overlap.setRange(bunch_overlap_range['min'], bunch_overlap_range['max'])
|
|
87
87
|
self.bunch_overlap.setValue(constants.DEFAULT_OVERLAP)
|
|
88
|
+
self.update_bunch_options(gui_constants.NEW_PROJECT_BUNCH_STACK)
|
|
88
89
|
self.focus_stack_pyramid = QCheckBox()
|
|
89
90
|
self.focus_stack_pyramid.setChecked(gui_constants.NEW_PROJECT_FOCUS_STACK_PYRAMID)
|
|
90
91
|
self.focus_stack_depth_map = QCheckBox()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from PySide6.QtGui import QRadialGradient
|
|
2
|
+
from PySide6.QtGui import QColor
|
|
3
|
+
from .. config.gui_constants import gui_constants
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_brush_gradient(center_x, center_y, radius, hardness, inner_color=None, outer_color=None, opacity=100):
|
|
7
|
+
gradient = QRadialGradient(center_x, center_y, float(radius))
|
|
8
|
+
inner = inner_color if inner_color is not None else QColor(*gui_constants.BRUSH_COLORS['inner'])
|
|
9
|
+
outer = outer_color if outer_color is not None else QColor(*gui_constants.BRUSH_COLORS['gradient_end'])
|
|
10
|
+
inner_with_opacity = QColor(inner)
|
|
11
|
+
inner_with_opacity.setAlpha(int(float(inner.alpha()) * float(opacity) / 100.0))
|
|
12
|
+
if hardness < 100:
|
|
13
|
+
hardness_normalized = float(hardness) / 100.0
|
|
14
|
+
gradient.setColorAt(0.0, inner_with_opacity)
|
|
15
|
+
gradient.setColorAt(hardness_normalized, inner_with_opacity)
|
|
16
|
+
gradient.setColorAt(1.0, outer)
|
|
17
|
+
else:
|
|
18
|
+
gradient.setColorAt(0.0, inner_with_opacity)
|
|
19
|
+
gradient.setColorAt(1.0, inner_with_opacity)
|
|
20
|
+
return gradient
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from PySide6.QtWidgets import QGraphicsPixmapItem
|
|
3
|
-
from PySide6.QtCore import Qt
|
|
3
|
+
from PySide6.QtCore import Qt
|
|
4
4
|
from PySide6.QtGui import QPixmap, QPainter, QImage
|
|
5
5
|
|
|
6
6
|
|
|
@@ -42,6 +42,8 @@ def create_brush_mask(size, hardness_percent, opacity_percent):
|
|
|
42
42
|
class BrushPreviewItem(QGraphicsPixmapItem):
|
|
43
43
|
def __init__(self):
|
|
44
44
|
super().__init__()
|
|
45
|
+
self.layer_collection = None
|
|
46
|
+
self.brush = None
|
|
45
47
|
self.setVisible(False)
|
|
46
48
|
self.setZValue(500)
|
|
47
49
|
self.setTransformationMode(Qt.SmoothTransformation)
|
|
@@ -68,31 +70,26 @@ class BrushPreviewItem(QGraphicsPixmapItem):
|
|
|
68
70
|
else:
|
|
69
71
|
raise Exception("Bitmas is neither 8 bit nor 16, but of type " + area.dtype)
|
|
70
72
|
|
|
71
|
-
def update(self,
|
|
73
|
+
def update(self, scene_pos, size):
|
|
72
74
|
try:
|
|
73
|
-
if
|
|
75
|
+
if self.layer_collection.layer_stack is None or size <= 0:
|
|
74
76
|
self.hide()
|
|
75
77
|
return
|
|
76
78
|
radius = size // 2
|
|
77
|
-
if isinstance(pos, QPointF):
|
|
78
|
-
scene_pos = pos
|
|
79
|
-
else:
|
|
80
|
-
cursor_pos = editor.image_viewer.mapFromGlobal(pos)
|
|
81
|
-
scene_pos = editor.image_viewer.mapToScene(cursor_pos)
|
|
82
79
|
x = int(scene_pos.x() - radius + 0.5)
|
|
83
80
|
y = int(scene_pos.y() - radius)
|
|
84
81
|
w = h = size
|
|
85
|
-
if
|
|
82
|
+
if not self.layer_collection.valid_current_layer_idx():
|
|
86
83
|
self.hide()
|
|
87
84
|
return
|
|
88
|
-
layer_area = self.get_layer_area(
|
|
89
|
-
master_area = self.get_layer_area(
|
|
85
|
+
layer_area = self.get_layer_area(self.layer_collection.current_layer(), x, y, w, h)
|
|
86
|
+
master_area = self.get_layer_area(self.layer_collection.master_layer, x, y, w, h)
|
|
90
87
|
if layer_area is None or master_area is None:
|
|
91
88
|
self.hide()
|
|
92
89
|
return
|
|
93
|
-
height, width =
|
|
94
|
-
full_mask = create_brush_mask(size=size, hardness_percent=
|
|
95
|
-
opacity_percent=
|
|
90
|
+
height, width = self.layer_collection.current_layer().shape[:2]
|
|
91
|
+
full_mask = create_brush_mask(size=size, hardness_percent=self.brush.hardness,
|
|
92
|
+
opacity_percent=self.brush.opacity)[:, :, np.newaxis]
|
|
96
93
|
mask_x_start = max(0, -x) if x < 0 else 0
|
|
97
94
|
mask_y_start = max(0, -y) if y < 0 else 0
|
|
98
95
|
mask_x_end = size - (max(0, (x + w) - width)) if (x + w) > width else size
|