33
44from typing import Any
55
6- from blendmodes .blend import BlendType , blendLayers
6+ import numpy as np
7+ from blendmodes .blend import BlendType
78from loguru import logger
89from PIL import Image
910
@@ -30,12 +31,89 @@ def expandLayersToCanvas(layeredImage: LayeredImage, imageFormat: str) -> list[I
3031 extra = {"imageFormat" : imageFormat },
3132 )
3233 return [
33- blendLayers (
34- background = Image . new ( "RGBA" , layeredImage .dimensions , ( 0 , 0 , 0 , 0 )) ,
34+ expandLayer (
35+ dimensions = layeredImage .dimensions ,
3536 foreground = layer .image ,
36- blendType = layer .blendmode ,
3737 opacity = layer .opacity ,
3838 offsets = layer .offsets ,
3939 )
4040 for layer in layeredImage .extractLayers ()
4141 ]
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