from __future__ import annotations import base64 from io import BytesIO from typing import Any, Tuple import webcolors from PIL import Image from werkzeug.datastructures import FileStorage def is_integer(contender: Any) -> bool: """ Determine if a string (or other object type that can be converted automatically) represents an integer. Thanks to https://note.nkmk.me/en/python-check-int-float/. :param contender: object to test :return: true if it's an integer """ try: float(contender) except ValueError: return False else: return float(contender).is_integer() def thumbnail_uploaded_image(file: FileStorage, max_size: Tuple[int, int] = (256, 256)) -> bytes: """ Process an uploaded image file into a resized image of a specific size. :param file: An uploaded image file. :param max_size: A tuple containing the maximum width and height for the thumbnail image. Default is (256, 256). :return: The byte data of the thumbnail image. """ if file.filename is None: raise ValueError("No file was uploaded") img = Image.open(file) img.thumbnail(max_size) byte_arr = BytesIO() img.save(byte_arr, format='PNG' if file.filename.lower().endswith('.png') else 'JPEG') return byte_arr.getvalue() def create_data_uri(bytes_data: bytes, file_extension: str) -> str: """ Create a data URI from binary data and a file extension. :param bytes_data: The binary data of an image. :param file_extension: The file extension of the image. :return: A data URI representing the image. """ # base64 encode encoded = base64.b64encode(bytes_data).decode('ascii') # create data URI data_uri = "data:image/{};base64,{}".format('jpeg' if file_extension == 'jpg' else file_extension, encoded) return data_uri def normalize_color(color: str) -> str: """ Normalize a string representing a color to its hexadecimal representation. This function accepts a string representing a color in one of the following formats: - A CSS color name, such as 'red', 'green', or 'blue'. - A 6-digit hexadecimal color code, such as '#FF0000' for red. - A 3-digit hexadecimal color code, such as '#F00' for red. The function returns a string with the color in 6-digit or 3-digit hexadecimal format, with lowercase letters. If the color is given as a CSS color name, the function returns its equivalent 6-digit hexadecimal format. :param color: A string representing a color. :return: The color in 6-digit or 3-digit hexadecimal format. :raises: ValueError: If the input string does not represent a valid color in any of the accepted formats. """ try: return webcolors.name_to_hex(color) # type: ignore[no-any-return] except ValueError: pass if color.startswith('#'): color = color[1:].lower() if len(color) in [3, 6]: try: _ = int(color, 16) return f"#{color}" except ValueError: pass raise ValueError(f"color must be a valid HTML color, got: {repr(color)}")