Source code for pygerber.parser.pillow.parser

# -*- coding: utf-8 -*-
from __future__ import annotations

from dataclasses import dataclass
from typing import Deque, Tuple

from PIL import Image, ImageDraw

from pygerber.mathclasses import BoundingBox
from pygerber.parser.parser import AbstractParser
from pygerber.parser.pillow.apertures import (
    PillowCircle,
    PillowCustom,
    PillowObround,
    PillowPolygon,
    PillowRectangle,
    PillowRegion,
)
from pygerber.renderer.apertureset import ApertureSet
from pygerber.tokens.token import Token

Color_Type = Tuple[float, float, float, float]


[docs]@dataclass class ColorSet: dark: Color_Type clear: Color_Type = (0, 0, 0, 0) background: Color_Type = (0, 0, 0, 0)
DEFAULT_COLOR_SET_ORANGE = ColorSet( (209, 110, 44), (0, 0, 0, 0), (0, 0, 0, 0), ) DEFAULT_COLOR_SET_GREEN = ColorSet( (66, 166, 66, 255), (16, 66, 36, 255), (0, 0, 0, 0), )
[docs]class ImageSizeNullError(IndexError): pass
[docs]class ParserWithPillow(AbstractParser): apertureSet = ApertureSet( PillowCircle, PillowRectangle, PillowObround, PillowPolygon, PillowCustom, PillowRegion, ) is_rendered: bool def __init__( self, *, ignore_deprecated: bool = True, dpi: int = 600, colors: ColorSet = DEFAULT_COLOR_SET_GREEN, image_padding: int = 0, ) -> None: super().__init__(ignore_deprecated=ignore_deprecated) self.image_padding = image_padding self.is_rendered = False self.colors = colors self.dpmm = float(dpi) / 25.4 @property def canvas(self) -> Image.Image: return self.renderer.canvas def _pre_render(self, bbox: BoundingBox): self.__inject_canvas(bbox) self.renderer.colors = self.colors self.renderer.dpmm = self.dpmm def __inject_canvas(self, bbox: BoundingBox) -> None: width, height = self.__get_canvas_size(bbox) if width == 0 or height == 0: raise ImageSizeNullError("Image has null width or height.") self.renderer.canvas = Image.new( "RGBA", (width, height), self.colors.background ) self.renderer.draw_canvas = ImageDraw.Draw(self.canvas) left_offset, bottom_offset = self.__get_drawing_offset(bbox) self.renderer.left_offset = left_offset self.renderer.bottom_offset = bottom_offset def __get_canvas_size(self, bbox: BoundingBox) -> Tuple[int, int]: return ( self._to_pixel_value(bbox.width()) + self.image_padding * 2, self._to_pixel_value(bbox.height()) + self.image_padding * 2, ) def __get_drawing_offset(self, bbox: BoundingBox) -> Tuple[int, int]: return ( self._to_pixel_value(-bbox.left) + self.image_padding, self._to_pixel_value(-bbox.lower) + self.image_padding, ) def _to_pixel_value(self, value: float) -> float: return int(value * self.dpmm) def _render(self, token_stack: Deque[Token]) -> None: if not self.is_rendered: self.renderer.render(token_stack) self.is_rendered = True else: raise RuntimeError("Attempt to render already rendered canvas.")
[docs] def get_image(self) -> Image.Image: if self.is_rendered: return self.canvas.transpose(Image.FLIP_TOP_BOTTOM) else: raise RuntimeError("Can't return canvas that was not rendered.")
[docs] def save(self, file_path: str, format: str = None) -> None: """ Saves rendered image. `file_path` A filename (string), pathlib.Path object or file object. `format` Optional format override. If omitted, the format to use is determined from the filename extension. If a file object was used instead of a filename, this parameter should always be used. """ if format is not None: self.get_image().save(file_path, format) else: self.get_image().save(file_path)