creatumlibre.graphics.boolean_operations.image_boolean

 1# pylint: disable=no-member
 2
 3
 4import cv2
 5import numpy as np
 6
 7from creatumlibre.ui.manager.image_handler import ImageHandler
 8
 9
10def merge(
11    from_obj: ImageHandler, to_obj: ImageHandler
12):  # pylint: disable=too-many-locals
13    """Composites the 'from' image into the 'to' image using its mask and position."""
14    overlay = from_obj.get_image()
15    mask = from_obj.get_mask()
16    act_postion = XY.from_tuple(from_obj.get_position())
17
18    base = to_obj.get_image()
19    h, w = overlay.shape[:2]
20    if h < 1 or w < 1:
21        return
22
23    base_h, base_w = base.shape[:2]
24
25    # Begrenzung berechnen
26    posTopLeft = act_postion.max_XY(XY(0, 0))
27    posRightBottom = posTopLeft.add(XY(w, h)).min_XY(XY(base_w, base_h))
28
29    # Falls komplett außerhalb: abbrechen
30    if posTopLeft.x >= posRightBottom.x or posTopLeft.y >= posRightBottom.y:
31        return
32
33    # Offset im Overlay berechnen
34    overlay_1 = posTopLeft.sub(act_postion)
35    overlay_2 = overlay_1.add(posRightBottom.sub(posTopLeft))
36
37    roi = base[posTopLeft.y : posRightBottom.y, posTopLeft.x : posRightBottom.x].astype(
38        np.float32
39    )
40    overlay_crop = overlay[overlay_1.y : overlay_2.y, overlay_1.x : overlay_2.x].astype(
41        np.float32
42    )
43
44    if mask is not None:
45        alpha = mask[overlay_1.y : overlay_2.y, overlay_1.x : overlay_2.x].astype(
46            np.float32
47        )
48        if len(alpha.shape) == 2:
49            alpha = cv2.merge([alpha] * 3)
50    else:
51        alpha = np.ones_like(overlay_crop, dtype=np.float32)
52
53    blended = overlay_crop * alpha + roi * (1 - alpha)
54    base[posTopLeft.y : posRightBottom.y, posTopLeft.x : posRightBottom.x] = (
55        blended.astype(np.uint8)
56    )
57
58    to_obj.set_image(base)
59
60
61class XY:
62    """2D vector class with basic operations."""
63
64    def __init__(self, x: int, y: int | None = None):
65        self.x = x
66        self.y = y
67
68    @classmethod
69    def from_tuple(cls, vector: tuple[int, int]) -> "XY":
70        return cls(vector[0], vector[1])
71
72    def to_tuple(self) -> tuple[int, int]:
73        return (self.x, self.y)
74
75    def add(self, other: "XY") -> "XY":
76        return XY(self.x + other.x, self.y + other.y)
77
78    def sub(self, other: "XY") -> "XY":
79        return XY(self.x - other.x, self.y - other.y)
80
81    def max_XY(self, ref: "XY") -> "XY":
82        return XY(max(self.x, ref.x), max(self.y, ref.y))
83
84    def min_XY(self, ref: "XY") -> "XY":
85        return XY(min(self.x, ref.x), min(self.y, ref.y))
86
87    def __repr__(self):
88        return f"XY(x={self.x}, y={self.y})"
11def merge(
12    from_obj: ImageHandler, to_obj: ImageHandler
13):  # pylint: disable=too-many-locals
14    """Composites the 'from' image into the 'to' image using its mask and position."""
15    overlay = from_obj.get_image()
16    mask = from_obj.get_mask()
17    act_postion = XY.from_tuple(from_obj.get_position())
18
19    base = to_obj.get_image()
20    h, w = overlay.shape[:2]
21    if h < 1 or w < 1:
22        return
23
24    base_h, base_w = base.shape[:2]
25
26    # Begrenzung berechnen
27    posTopLeft = act_postion.max_XY(XY(0, 0))
28    posRightBottom = posTopLeft.add(XY(w, h)).min_XY(XY(base_w, base_h))
29
30    # Falls komplett außerhalb: abbrechen
31    if posTopLeft.x >= posRightBottom.x or posTopLeft.y >= posRightBottom.y:
32        return
33
34    # Offset im Overlay berechnen
35    overlay_1 = posTopLeft.sub(act_postion)
36    overlay_2 = overlay_1.add(posRightBottom.sub(posTopLeft))
37
38    roi = base[posTopLeft.y : posRightBottom.y, posTopLeft.x : posRightBottom.x].astype(
39        np.float32
40    )
41    overlay_crop = overlay[overlay_1.y : overlay_2.y, overlay_1.x : overlay_2.x].astype(
42        np.float32
43    )
44
45    if mask is not None:
46        alpha = mask[overlay_1.y : overlay_2.y, overlay_1.x : overlay_2.x].astype(
47            np.float32
48        )
49        if len(alpha.shape) == 2:
50            alpha = cv2.merge([alpha] * 3)
51    else:
52        alpha = np.ones_like(overlay_crop, dtype=np.float32)
53
54    blended = overlay_crop * alpha + roi * (1 - alpha)
55    base[posTopLeft.y : posRightBottom.y, posTopLeft.x : posRightBottom.x] = (
56        blended.astype(np.uint8)
57    )
58
59    to_obj.set_image(base)

Composites the 'from' image into the 'to' image using its mask and position.

class XY:
62class XY:
63    """2D vector class with basic operations."""
64
65    def __init__(self, x: int, y: int | None = None):
66        self.x = x
67        self.y = y
68
69    @classmethod
70    def from_tuple(cls, vector: tuple[int, int]) -> "XY":
71        return cls(vector[0], vector[1])
72
73    def to_tuple(self) -> tuple[int, int]:
74        return (self.x, self.y)
75
76    def add(self, other: "XY") -> "XY":
77        return XY(self.x + other.x, self.y + other.y)
78
79    def sub(self, other: "XY") -> "XY":
80        return XY(self.x - other.x, self.y - other.y)
81
82    def max_XY(self, ref: "XY") -> "XY":
83        return XY(max(self.x, ref.x), max(self.y, ref.y))
84
85    def min_XY(self, ref: "XY") -> "XY":
86        return XY(min(self.x, ref.x), min(self.y, ref.y))
87
88    def __repr__(self):
89        return f"XY(x={self.x}, y={self.y})"

2D vector class with basic operations.

XY(x: int, y: int | None = None)
65    def __init__(self, x: int, y: int | None = None):
66        self.x = x
67        self.y = y
x
y
@classmethod
def from_tuple( cls, vector: tuple[int, int]) -> XY:
69    @classmethod
70    def from_tuple(cls, vector: tuple[int, int]) -> "XY":
71        return cls(vector[0], vector[1])
def to_tuple(self) -> tuple[int, int]:
73    def to_tuple(self) -> tuple[int, int]:
74        return (self.x, self.y)
def add( self, other: XY) -> XY:
76    def add(self, other: "XY") -> "XY":
77        return XY(self.x + other.x, self.y + other.y)
def sub( self, other: XY) -> XY:
79    def sub(self, other: "XY") -> "XY":
80        return XY(self.x - other.x, self.y - other.y)
def max_XY( self, ref: XY) -> XY:
82    def max_XY(self, ref: "XY") -> "XY":
83        return XY(max(self.x, ref.x), max(self.y, ref.y))
def min_XY( self, ref: XY) -> XY:
85    def min_XY(self, ref: "XY") -> "XY":
86        return XY(min(self.x, ref.x), min(self.y, ref.y))