Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 16 additions & 37 deletions src/pygerber/vm/pillow/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,19 +231,30 @@ def save_webp(
)

def get_image(self, style: Style = Style.presets.COPPER_ALPHA) -> Image.Image:
"""Get image with given color scheme."""
"""Get image with given color scheme using a fast compositing method."""
assert isinstance(style, Style)
if self.image is None:
msg = "Image is not available."
raise ValueError(msg)

image = replace_color(
self.image, (255, 255, 255, 255), style.foreground.as_rgba_int()
# The original rendered image is black and white ('1' mode). This is our mask.
# We need to convert it to grayscale ('L') for the composite function.
mask = self.image.convert("L")

# Create a solid background layer.
background_img = Image.new(
"RGBA", self.image.size, style.background.as_rgba_int()
)
return replace_color_in_place(
image, (0, 0, 0, 255), style.background.as_rgba_int()

# Create a solid foreground layer.
foreground_img = Image.new(
"RGBA", self.image.size, style.foreground.as_rgba_int()
)

# Composite the foreground onto the background using the render as a mask.
# This is a single, highly optimized C operation that replaces the slow loops.
return Image.composite(foreground_img, background_img, mask)

def get_image_no_style(self) -> Image.Image:
"""Get image without any color scheme."""
if self.image is None:
Expand All @@ -253,38 +264,6 @@ def get_image_no_style(self) -> Image.Image:
return self.image


def replace_color(
input_image: Image.Image,
original: tuple[int, ...] | int,
replacement: tuple[int, ...] | int,
*,
output_image_mode: str = "RGBA",
) -> Image.Image:
"""Replace `original` color from input image with `replacement` color."""
if input_image.mode != output_image_mode:
output_image = input_image.convert(output_image_mode)
else:
output_image = input_image.copy()

replace_color_in_place(output_image, original, replacement)

return output_image


def replace_color_in_place(
image: Image.Image,
original: tuple[int, ...] | int,
replacement: tuple[int, ...] | int,
) -> Image.Image:
"""Replace `original` color from input image with `replacement` color."""
for x in range(image.width):
for y in range(image.height):
if image.getpixel((x, y)) == original:
image.putpixel((x, y), replacement)

return image


class PillowEagerLayer(EagerLayer):
"""`PillowEagerLayer` class represents drawing space of known fixed size.

Expand Down
Loading