fabiocaccamo/python-imageutil

View on GitHub
imageutil/args.py

Summary

Maintainability
A
2 hrs
Test Coverage
from __future__ import annotations

from pathlib import Path

import fsutil

from imageutil.exceptions import (
    InvalidAnchorError,
    InvalidColorError,
    InvalidImageError,
)
from imageutil.pil import PILImage, PILImageObject
from imageutil.types import AnchorIn, AnchorOut, ColorIn, ColorOut, ImageIn, ImageOut

__all__ = [
    "get_alpha",
    "get_anchor",
    "get_color",
    "get_image",
]


def get_alpha(opacity: float) -> int:
    """
    Gets the alpha (0-255 int value) mapping
    the opacity input (0.0-1.0 float value).

    :param opacity: The opacity
    :type opacity: float

    :returns: The alpha value.
    :rtype: int
    """
    # interpolate value
    alpha = int(round(255 * opacity))
    # constain value
    alpha = min(max(0, alpha), 255)
    return alpha


def get_anchor(name: AnchorIn) -> AnchorOut:
    """
    Gets the anchor converting anchor position name string
    to a tuple (x, y) where each value is a float between 0.0 and 1.0.

    :param name: The anchor name
    :type name: AnchorIn

    :returns: The anchor position.
    :rtype: AnchorOut

    :raises InvalidAnchorError: If name is not a valid anchor name.
    """
    name_value = name.strip().lower()
    name_value = name_value.replace(" ", "-").replace("_", "-").replace("--", "-")
    name_options = [
        "bottom",
        "bottom-left",
        "bottom-right",
        "center",
        "left",
        "right",
        "top",
        "top-left",
        "top-right",
    ]
    if name_value not in name_options:
        raise InvalidAnchorError(name_value)

    if "left" in name_value:
        left = 0.0
    elif "right" in name_value:
        left = 1.0
    else:
        left = 0.5
    if "top" in name_value:
        top = 0.0
    elif "bottom" in name_value:
        top = 1.0
    else:
        top = 0.5
    return (left, top)


def get_color(value: ColorIn | None = None, opacity: float | None = None) -> ColorOut:
    """
    Gets the color.

    :param color: The color
    :type color: ColorIn or None
    :param opacity: The opacity value (0.0-1.0)
    :type opacity: float

    :returns: The color.
    :rtype: ColorOut

    :raises InvalidColorError: If color is not a valid RGBA / RGB color.
    """
    if value is None:
        color = [255, 255, 255, 255]
    elif isinstance(value, (float, int)):
        color = [int(round(value))] * 3
    elif isinstance(value, (list, tuple)):
        color = list(value)
        if len(color) not in (3, 4):
            raise InvalidColorError(color)
    else:
        raise InvalidColorError(value)

    # TODO: add hex/hexa color format support

    # set alpha in case of rgb color value
    color = list(color)
    if len(color) == 3:
        color.append(255)

    # override color alpha using the opacity value
    if opacity is not None:
        alpha = get_alpha(opacity)
        color[3] = alpha

    # constain value
    r, g, b, a = color
    r = min(max(0, r), 255)
    g = min(max(0, g), 255)
    b = min(max(0, b), 255)
    a = min(max(0, a), 255)

    # convert color back to tuple and return it
    rgba = (r, g, b, a)
    return rgba


def get_image(src: ImageIn, copy: bool = True) -> ImageOut:
    """
    Gets the image.

    :param src: The source for an image object.
    :type src: ImageIn
    :param copy: The copy flag, if True copies the src PIL image.
    :type copy: bool

    :returns: The PIL image object.
    :rtype: ImageOut

    :raises InvalidImageError: If src is not a valid filepath / URL / PIL image.
    """
    from imageutil.core import Image

    if isinstance(src, (str, Path)):
        image_src = str(src)
        if image_src.startswith("https://") or image_src.startswith("http://"):
            image_filepath = fsutil.download_file(image_src)
        else:
            image_filepath = image_src
        image = PILImage.open(image_filepath)
    elif isinstance(src, PILImageObject):
        image = src.copy() if copy else src
    elif isinstance(src, Image):
        image = src.pil_image.copy() if copy else src
    else:
        raise InvalidImageError(src)
    return image