3
3
4
4
from typing import Any
5
5
6
- from blendmodes .blend import BlendType , blendLayers
6
+ import numpy as np
7
+ from blendmodes .blend import BlendType
7
8
from loguru import logger
8
9
from PIL import Image
9
10
@@ -30,12 +31,89 @@ def expandLayersToCanvas(layeredImage: LayeredImage, imageFormat: str) -> list[I
30
31
extra = {"imageFormat" : imageFormat },
31
32
)
32
33
return [
33
- blendLayers (
34
- background = Image . new ( "RGBA" , layeredImage .dimensions , ( 0 , 0 , 0 , 0 )) ,
34
+ expandLayer (
35
+ dimensions = layeredImage .dimensions ,
35
36
foreground = layer .image ,
36
- blendType = layer .blendmode ,
37
37
opacity = layer .opacity ,
38
38
offsets = layer .offsets ,
39
39
)
40
40
for layer in layeredImage .extractLayers ()
41
41
]
42
+
43
+
44
+ def expandLayer (
45
+ dimensions : tuple [int , int ],
46
+ foreground : np .ndarray | Image .Image ,
47
+ opacity : float = 1.0 ,
48
+ offsets : tuple [int , int ] = (0 , 0 ),
49
+ ) -> Image .Image :
50
+ """
51
+ Args:
52
+ ----
53
+ foreground (np.ndarray | Image.Image): The foreground layer (must be the same size as the background).
54
+ opacity (float, optional): The opacity of the foreground image. Defaults to 1.0.
55
+ offsets (Tuple[int, int], optional): Offsets for the foreground layer. Defaults to (0, 0).
56
+
57
+ Returns:
58
+ -------
59
+ Image.Image: The image.
60
+
61
+ """
62
+ # Convert the Image.Image to a numpy array if required
63
+ if isinstance (foreground , Image .Image ):
64
+ foreground = np .array (foreground .convert ("RGBA" ))
65
+
66
+ # do any offset shifting first
67
+ if offsets [0 ] > 0 :
68
+ foreground = np .hstack (
69
+ (np .zeros ((foreground .shape [0 ], offsets [0 ], 4 ), dtype = np .float64 ), foreground )
70
+ )
71
+ elif offsets [0 ] < 0 :
72
+ if offsets [0 ] > - 1 * foreground .shape [1 ]:
73
+ foreground = foreground [:, - 1 * offsets [0 ] :, :]
74
+ else :
75
+ # offset offscreen completely, there is nothing left
76
+ return Image .fromarray (np .zeros (dimensions , dtype = np .uint8 ))
77
+ if offsets [1 ] > 0 :
78
+ foreground = np .vstack (
79
+ (np .zeros ((offsets [1 ], foreground .shape [1 ], 4 ), dtype = np .float64 ), foreground )
80
+ )
81
+ elif offsets [1 ] < 0 :
82
+ if offsets [1 ] > - 1 * foreground .shape [0 ]:
83
+ foreground = foreground [- 1 * offsets [1 ] :, :, :]
84
+ else :
85
+ # offset offscreen completely, there is nothing left
86
+ return Image .fromarray (np .zeros (dimensions , dtype = np .uint8 ))
87
+
88
+ # resize array to fill small images with zeros
89
+ if foreground .shape [0 ] < dimensions [0 ]:
90
+ foreground = np .vstack (
91
+ (
92
+ foreground ,
93
+ np .zeros (
94
+ (dimensions [0 ] - foreground .shape [0 ], foreground .shape [1 ], 4 ),
95
+ dtype = np .float64 ,
96
+ ),
97
+ )
98
+ )
99
+ if foreground .shape [1 ] < dimensions [1 ]:
100
+ foreground = np .hstack (
101
+ (
102
+ foreground ,
103
+ np .zeros (
104
+ (foreground .shape [0 ], dimensions [1 ] - foreground .shape [1 ], 4 ),
105
+ dtype = np .float64 ,
106
+ ),
107
+ )
108
+ )
109
+
110
+ # crop the source if the backdrop is smaller
111
+ foreground = foreground [: dimensions [0 ], : dimensions [1 ], :]
112
+
113
+ upper_norm = foreground
114
+
115
+ upper_alpha = upper_norm [:, :, 3 ] * opacity
116
+ upper_rgb = upper_norm [:, :, :3 ]
117
+
118
+ arr = np .nan_to_num (np .dstack ((upper_rgb , upper_alpha )), copy = False )
119
+ return Image .fromarray (np .uint8 (np .around (arr , 0 )))
0 commit comments