Skip to content

Commit 7da8b29

Browse files
committed
performance improvements to new blending
1 parent bb1dcfa commit 7da8b29

31 files changed

+85
-10
lines changed

layeredimage/io/common.py

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
from typing import Any
55

6-
from blendmodes.blend import BlendType, blendLayers
6+
import numpy as np
7+
from blendmodes.blend import BlendType
78
from loguru import logger
89
from 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)))

layeredimage/io/lsr.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
from pathlib import Path
55

6-
from blendmodes.blend import blendLayers
7-
from PIL import Image
8-
6+
from layeredimage.io.common import expandLayer
97
from layeredimage.layeredimage import LayeredImage
108
from layeredimage.layergroup import Group, Layer
119

@@ -47,10 +45,9 @@ def saveLayer_LSR(fileName: str, layeredImage: LayeredImage) -> None:
4745
else:
4846
imageData = [
4947
pylsr.LSRImageData(
50-
blendLayers(
51-
background=Image.new("RGBA", group.dimensions, (0, 0, 0, 0)),
48+
expandLayer(
49+
dimensions=group.dimensions,
5250
foreground=layer.image,
53-
blendType=layer.blendmode,
5451
opacity=layer.opacity,
5552
offsets=layer.offsets,
5653
),

tests/data/gif_output.ora

0 Bytes
Binary file not shown.

tests/data/layered_output.ora

0 Bytes
Binary file not shown.

tests/data/layered_output.tiff

0 Bytes
Binary file not shown.

tests/data/layered_output.webp

-100 Bytes
Binary file not shown.

tests/data/layeredc_output.ora

0 Bytes
Binary file not shown.

tests/data/layeredc_output.tiff

0 Bytes
Binary file not shown.

tests/data/layeredc_output.webp

-100 Bytes
Binary file not shown.

tests/data/lsr_output.lsr

3.49 KB
Binary file not shown.

0 commit comments

Comments
 (0)