Skip to content

draw

visualize.draw

logger module-attribute

logger = getLogger('SaigeResearch')

add_opacity_to_color_tuple

add_opacity_to_color_tuple(color: Optional[Tuple[int, int, int]] = None, opacity: float = 1.0) -> Optional[Tuple[int, int, int, int]]

Convert RGB color tuple to RGBA with specific opacity.

Parameters:

  • color (Tuple[int, int, int], default: None ) –

    Tuple of RGB color, each value has the range [0, 255]. Defaults to None.

  • opacity (float, default: 1.0 ) –

    Opacity value. Expected range is [0., 1.]. Defaults to 1..

Returns:

  • Optional[Tuple[int, int, int, int]]

    Tuple[int, int, int, int]: Converted RGBA color tuple.

Note

Since the PIL can handle outliers, this function does not separately check for outliers for RGBA tuple.

It is correct not to consider the case where color is None, (if it is None, should not to call this function), but in this function, It is more efficient to assume that color is None rather than handling all exceptions for None in the Visualize implementation. (By keeping the color Tuple as None, PIL can handle it automatically.)

Source code in SaigeToolkit/visualize/util.py
def add_opacity_to_color_tuple(
    color: Optional[Tuple[int, int, int]] = None,
    opacity: float = 1.0,
) -> Optional[Tuple[int, int, int, int]]:
    """Convert RGB color tuple to RGBA with specific opacity.

    Args:
        color (Tuple[int, int, int], optional): Tuple of RGB color,
            each value has the range [0, 255]. Defaults to None.
        opacity (float, optional): Opacity value.
            Expected range is [0., 1.]. Defaults to 1..

    Returns:
        Tuple[int, int, int, int]: Converted RGBA color tuple.

    Note:
        Since the `PIL` can handle outliers,
        this function does not separately check for outliers for RGBA tuple.

        It is correct not to consider the case where `color` is None,
        (if it is `None`, should not to call this function),
        but in this function, It is more efficient to assume that `color` is `None`
        rather than handling all exceptions for `None` in the `Visualize` implementation.
        (By keeping the `color` Tuple as None, `PIL` can handle it automatically.)

    """
    if color is None:
        return None

    return (*color, int(opacity * 255))

blend_layers

blend_layers(layers: List[Image]) -> Image

Blend multiple layers into one layer respect alpha channel.

Parameters:

  • layers (List[Image]) –

    Layers to blend. First element will place to bottom, and last one to top. If layer's mode is not 'RGBA', it will convert to 'RGBA' with opacity 1.0.

Returns:

  • Image

    Image.Image: Blended image.

Source code in SaigeToolkit/visualize/util.py
def blend_layers(
    layers: List[Image.Image],
) -> Image.Image:
    """Blend multiple layers into one layer respect alpha channel.

    Args:
        layers (List[Image.Image]): Layers to blend.
            First element will place to bottom, and last one to top.
            If layer's mode is not 'RGBA', it will convert to 'RGBA' with opacity 1.0.

    Returns:
        Image.Image: Blended image.

    """
    base_layer = get_empty_layer(layers[0].size)

    for layer in layers:
        if layer.mode != "RGBA":
            layer = layer.convert("RGBA")
        base_layer.paste(Image.alpha_composite(base_layer, layer))

    return base_layer

get_empty_layer

get_empty_layer(size: Tuple[int, int], color: Tuple[int, int, int] = (0, 0, 0), opacity: float = 0.0) -> Image

Create empty layer.

Parameters:

  • size (Tuple[int, int]) –

    (width, height) in pixels.

  • color (Tuple[int, int, int], default: (0, 0, 0) ) –

    Layer's base color (Background). Defaults to (0, 0, 0) (black).

  • opacity (float, default: 0.0 ) –

    Opacity value. Defaults to 0..

Returns:

  • Image

    Image.Image: Empty layer.

Source code in SaigeToolkit/visualize/util.py
def get_empty_layer(
    size: Tuple[int, int],
    color: Tuple[int, int, int] = (0, 0, 0),
    opacity: float = 0.0,
) -> Image.Image:
    """Create empty layer.

    Args:
        size (Tuple[int, int]): (width, height) in pixels.
        color (Tuple[int, int, int], optional): Layer's base color (Background).
            Defaults to (0, 0, 0) (black).
        opacity (float, optional): Opacity value. Defaults to 0..

    Returns:
        Image.Image: Empty layer.

    """
    return Image.new(mode="RGBA", size=size, color=(*color, int(opacity * 255)))

safety_visualize

safety_visualize(original_fn: callable) -> callable

Decorator for stable visualization.

Temporarily convert the canvas image mode to RGBA for visualize operation. After visualize is completed, converted back to the original image mode and returned.

Source code in SaigeToolkit/visualize/util.py
def safety_visualize(original_fn: callable) -> callable:
    """Decorator for stable visualization.

    Temporarily convert the canvas image mode to RGBA for visualize operation.
    After visualize is completed, converted back to the original image mode and returned."""

    def wrapper(canvas: Image.Image, *args, **kwargs):
        canvas_mode = canvas.mode

        if canvas_mode != "RGBA":
            canvas = canvas.convert("RGBA")

        return original_fn(canvas, *args, **kwargs).convert(canvas_mode)

    return wrapper

get_draw

get_draw(canvas: Image) -> ImageDraw

Function to force PIL.ImageDraw.Draw to RGBA mode

Source code in SaigeToolkit/visualize/draw.py
def get_draw(canvas: Image.Image) -> ImageDraw.ImageDraw:
    """Function to force `PIL.ImageDraw.Draw` to RGBA mode"""
    if canvas.mode != "RGBA":
        canvas = canvas.convert("RGBA")

    return ImageDraw.Draw(canvas)

draw_ellipse

draw_ellipse(canvas: Image, origin: Tuple[int, int], radius: int, fill: Tuple[int, int, int] = (0, 0, 0), outline: Tuple[int, int, int] = None, width: int = None, opacity: float = 1.0) -> Image

Draw an ellipse in canvas.

Since the ImageDraw.ellipse() uses a bounding box using xy coordinates, it is converted into origin and radius methods.

Parameters:

  • canvas (Image) –

    Image to draw ellipse.

  • origin (Tuple[int, int]) –

    Ellipse's origin.

  • radius (int) –

    Ellipse's radius.

  • fill (Tuple[int, int, int], default: (0, 0, 0) ) –

    Tuple contain ellipse's RGB fill color. Defaults to (0, 0, 0) (black).

  • outline (Tuple[int, int, int], default: None ) –

    Tuple contain ellipse's RGB outline color. Defaults to None (Transparent).

  • width (int, default: None ) –

    Ellipse's outline width. Defaults to None.

  • opacity (float, default: 1.0 ) –

    Ellipse's opacity value. Defaults to 1..

Returns:

  • Image

    Image.Image: canvas with ellipse.

Source code in SaigeToolkit/visualize/draw.py
@safety_visualize
def draw_ellipse(
    canvas: Image.Image,
    origin: Tuple[int, int],
    radius: int,
    fill: Tuple[int, int, int] = (0, 0, 0),
    outline: Tuple[int, int, int] = None,
    width: int = None,
    opacity: float = 1.0,
) -> Image.Image:
    """Draw an ellipse in canvas.

    Since the `ImageDraw.ellipse()` uses a bounding box using xy coordinates,
    it is converted into origin and radius methods.

    Args:
        canvas (Image.Image): Image to draw ellipse.
        origin (Tuple[int, int]): Ellipse's origin.
        radius (int): Ellipse's radius.
        fill (Tuple[int, int, int], optional): Tuple contain ellipse's RGB fill color.
            Defaults to (0, 0, 0) (black).
        outline (Tuple[int, int, int], optional): Tuple contain ellipse's RGB outline color.
            Defaults to None (Transparent).
        width (int, optional): Ellipse's outline width. Defaults to None.
        opacity (float, optional): Ellipse's opacity value. Defaults to 1..

    Returns:
        Image.Image: `canvas` with ellipse.

    """
    new_layer = get_empty_layer(canvas.size)
    draw = get_draw(new_layer)

    fill = add_opacity_to_color_tuple(fill, opacity)
    outline = add_opacity_to_color_tuple(outline, opacity)

    x, y = origin
    draw.ellipse(
        xy=(x - radius, y - radius, x + radius, y + radius), fill=fill, outline=outline, width=width
    )

    return blend_layers([canvas, new_layer])

draw_polygon

draw_polygon(canvas: Image, xy: Sequence[Union[int, Tuple[int, int]]], fill: Optional[Tuple[int, int, int]] = None, outline: Optional[Tuple[int, int, int]] = None, width: int = 1, opacity: float = 1.0) -> Image

Draw an polygon in canvas.

Improved outline function because ImageDraw.polygon() cannot draw polygon outline to connect with origin.

Parameters:

  • canvas (Image) –

    Image to draw polygon.

  • xy (Sequence[Union[int, Tuple[int, int]]]) –

    Sequence of [(x, y), (x, y), ...] or numeric values like [x, y, x, y, ...].

  • fill (Optional[Tuple[int, int, int]], default: None ) –

    Tuple contain polygon's RGB fill color. Defaults to None (Transparent).

  • outline (Optional[Tuple[int, int, int]], default: None ) –

    Tuple contain polygon's RGB outline color. Defaults to None (Transparent).

  • width (int, default: 1 ) –

    Polygon's outline width. Defaults to 1.

  • opacity (float, default: 1.0 ) –

    Polygon's opacity value. Defaults to 1..

Returns:

  • Image

    Image.Image: canvas with polygon.

Source code in SaigeToolkit/visualize/draw.py
@safety_visualize
def draw_polygon(
    canvas: Image.Image,
    xy: Sequence[Union[int, Tuple[int, int]]],
    fill: Optional[Tuple[int, int, int]] = None,
    outline: Optional[Tuple[int, int, int]] = None,
    width: int = 1,
    opacity: float = 1.0,
) -> Image.Image:
    """Draw an polygon in canvas.

    Improved outline function because `ImageDraw.polygon()`
    cannot draw polygon outline to connect with origin.

    Args:
        canvas (Image.Image): Image to draw polygon.
        xy (Sequence[Union[int, Tuple[int, int]]]): Sequence of `[(x, y), (x, y), ...]`
            or numeric values like `[x, y, x, y, ...]`.
        fill (Optional[Tuple[int, int, int]], optional): Tuple contain polygon's RGB fill color.
            Defaults to None (Transparent).
        outline (Optional[Tuple[int, int, int]], optional): Tuple contain polygon's RGB outline color.
            Defaults to None (Transparent).
        width (int, optional): Polygon's outline width. Defaults to 1.
        opacity (float, optional): Polygon's opacity value. Defaults to 1..

    Returns:
        Image.Image: `canvas` with polygon.

    """
    new_layer = get_empty_layer(canvas.size)
    draw = get_draw(new_layer)

    fill = add_opacity_to_color_tuple(fill, opacity)
    outline = add_opacity_to_color_tuple(outline, opacity)

    if fill:
        draw.polygon(xy, fill=fill)

    if outline:
        if isinstance(xy[0], numbers.Number):
            line = [*list(xy), *list(xy)[:2]]
        else:
            line = [*list(xy), xy[0]]

        draw.line(line, fill=outline, width=width)

    return blend_layers([canvas, new_layer])

draw_rectangle

draw_rectangle(canvas: Image, xy: Sequence[Union[int, Tuple[int, int]]], fill: Tuple[int, int, int] = (0, 0, 0), outline: Tuple[int, int, int] = None, width: int = 1, opacity: float = 1.0) -> Image

Draw an rectangle in canvas.

Parameters:

  • canvas (Image) –

    Image to draw rectangle.

  • xy (Sequence[Union[int, Tuple[int, int]]]) –

    Two points to define the bounding box. Sequence of either [(x0, y0), (x1, y1)] or [x0, y0, x1, y1].

  • fill (Tuple[int, int, int], default: (0, 0, 0) ) –

    Tuple contain rectangle's RGB fill color. Defaults to (0, 0, 0) (black).

  • outline (Tuple[int, int, int], default: None ) –

    Tuple contain rectangle's RGB outline color. Defaults to None (Transparent).

  • width (int, default: 1 ) –

    Rectangle's outline width. Defaults to 1.

  • opacity (float, default: 1.0 ) –

    Rectangle's opacity value. Defaults to 1..

Returns:

  • Image

    Image.Image: canvas with rectangle.

Source code in SaigeToolkit/visualize/draw.py
@safety_visualize
def draw_rectangle(
    canvas: Image.Image,
    xy: Sequence[Union[int, Tuple[int, int]]],
    fill: Tuple[int, int, int] = (0, 0, 0),
    outline: Tuple[int, int, int] = None,
    width: int = 1,
    opacity: float = 1.0,
) -> Image.Image:
    """Draw an rectangle in canvas.

    Args:
        canvas (Image.Image): Image to draw rectangle.
        xy (Sequence[Union[int, Tuple[int, int]]]): Two points to define the bounding box.
            Sequence of either `[(x0, y0), (x1, y1)]` or `[x0, y0, x1, y1]`.
        fill (Tuple[int, int, int], optional): Tuple contain rectangle's RGB fill color.
            Defaults to (0, 0, 0) (black).
        outline (Tuple[int, int, int], optional): Tuple contain rectangle's RGB outline color.
            Defaults to None (Transparent).
        width (int, optional): Rectangle's outline width. Defaults to 1.
        opacity (float, optional): Rectangle's opacity value. Defaults to 1..

    Returns:
        Image.Image: `canvas` with rectangle.

    """
    new_layer = get_empty_layer(canvas.size)
    draw = get_draw(new_layer)

    fill = add_opacity_to_color_tuple(fill, opacity)
    outline = add_opacity_to_color_tuple(outline, opacity)

    draw.rectangle(xy=xy, fill=fill, outline=outline, width=width)

    return blend_layers([canvas, new_layer])

draw_text

draw_text(canvas: Image, xy: Tuple[int, int], text: str, font: ImageFont = None, font_size: Optional[int] = None, anchor: str = 'lt', fill: Tuple[int, int, int] = (255, 255, 255), outline: Tuple[int, int, int] = None, width: int = 0, opacity: float = 1.0) -> Image

Draw text in canvas.

Parameters:

  • canvas (Image) –

    Image to draw text.

  • xy (Tuple[int, int]) –

    Text's start point.

  • text (str) –

    Text to write on canvas.

  • font (ImageFont, default: None ) –

    Determines the font of text. If None, the default font will be used. Defaults to None.

  • font_size (Optional[int], default: None ) –

    [DEPRECATED] A parameter to be DISCARDED when font is not None. Determines the font size. Default to None.

  • anchor (str, default: 'lt' ) –

    Determines the alignment of text according to xy coordinates. The default value lt draws text with param xy aligned to the upper left. Defaults to "lt". get more information: Text anchors

  • fill (Tuple[int, int, int], default: (255, 255, 255) ) –

    Tuple contain Text's RGB color. Defaults to (255, 255, 255) (White),

  • outline (Tuple[int, int, int], default: None ) –

    Tuple contain text's RGB outline color. Defaults to None (Transparent).

  • width (int, default: 0 ) –

    Text's outline width. Defaults to 0.

  • opacity (float, default: 1.0 ) –

    Text's opacity value. Defaults to 1..

Returns:

  • Image

    Image.Image: canvas with text.

Source code in SaigeToolkit/visualize/draw.py
@safety_visualize
def draw_text(
    canvas: Image.Image,
    xy: Tuple[int, int],
    text: str,
    font: ImageFont.ImageFont = None,
    font_size: Optional[int] = None,
    anchor: str = "lt",
    fill: Tuple[int, int, int] = (255, 255, 255),
    outline: Tuple[int, int, int] = None,
    width: int = 0,
    opacity: float = 1.0,
) -> Image.Image:
    """Draw text in canvas.

    Args:
        canvas (Image.Image): Image to draw text.
        xy (Tuple[int, int]): Text's start point.
        text (str): Text to write on canvas.
        font (ImageFont.ImageFont, optional): Determines the font of text.
            If None, the default font will be used. Defaults to None.
        font_size (Optional[int], optional): [DEPRECATED] A parameter to be DISCARDED when `font` is not None.
            Determines the font size. Default to None.
        anchor (str, optional): Determines the alignment of text according to xy coordinates.
            The default value `lt` draws text with param `xy` aligned to the upper left.
            Defaults to "lt". get more information: [Text anchors](https://pillow.readthedocs.io/en/stable/handbook/text-anchors.html#text-anchors)
        fill (Tuple[int, int, int], optional): Tuple contain Text's RGB color.
            Defaults to (255, 255, 255) (White),
        outline (Tuple[int, int, int], optional): Tuple contain text's RGB outline color.
            Defaults to None (Transparent).
        width (int, optional): Text's outline width. Defaults to 0.
        opacity (float, optional): Text's opacity value. Defaults to 1..

    Returns:
        Image.Image: `canvas` with text.

    """
    if font is None:
        font = ImageFont.load_default()
        if font_size is not None:
            logger.warning(
                "`font_size` parameter is deprecated and will not be applied. Use `font` parameter instead."
            )

    new_layer = get_empty_layer(canvas.size)
    draw = get_draw(new_layer)
    fill = add_opacity_to_color_tuple(fill, opacity)
    outline = add_opacity_to_color_tuple(outline, opacity)

    draw.text(
        xy=xy, text=text, font=font, anchor=anchor, fill=fill, stroke_fill=outline, stroke_width=width
    )

    return blend_layers([canvas, new_layer])