2022-06-15 11:50:15 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2023-05-25 15:32:31 +01:00
|
|
|
import base64
|
|
|
|
from io import BytesIO
|
|
|
|
from typing import Any, Tuple
|
|
|
|
|
|
|
|
import webcolors
|
|
|
|
from PIL import Image
|
|
|
|
from werkzeug.datastructures import FileStorage
|
2022-06-15 11:50:15 +01:00
|
|
|
|
|
|
|
|
2022-06-23 11:38:27 +01:00
|
|
|
def is_integer(contender: Any) -> bool:
|
2022-06-15 11:50:15 +01:00
|
|
|
"""
|
|
|
|
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/.
|
|
|
|
|
2022-06-23 11:38:27 +01:00
|
|
|
:param contender: object to test
|
2022-06-15 11:50:15 +01:00
|
|
|
:return: true if it's an integer
|
|
|
|
"""
|
|
|
|
try:
|
2022-06-23 11:38:27 +01:00
|
|
|
float(contender)
|
2022-06-15 11:50:15 +01:00
|
|
|
except ValueError:
|
|
|
|
return False
|
|
|
|
else:
|
2022-06-23 11:38:27 +01:00
|
|
|
return float(contender).is_integer()
|
2023-05-25 15:32:31 +01:00
|
|
|
|
|
|
|
|
2024-12-06 18:15:47 +00:00
|
|
|
def thumbnail_uploaded_image(
|
|
|
|
file: FileStorage, max_size: Tuple[int, int] = (256, 256)
|
|
|
|
) -> bytes:
|
2023-05-25 15:32:31 +01:00
|
|
|
"""
|
|
|
|
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()
|
2024-12-06 18:15:47 +00:00
|
|
|
img.save(
|
|
|
|
byte_arr, format="PNG" if file.filename.lower().endswith(".png") else "JPEG"
|
|
|
|
)
|
2023-05-25 15:32:31 +01:00
|
|
|
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
|
2024-12-06 18:15:47 +00:00
|
|
|
encoded = base64.b64encode(bytes_data).decode("ascii")
|
2023-05-25 15:32:31 +01:00
|
|
|
# create data URI
|
2024-12-06 18:15:47 +00:00
|
|
|
data_uri = "data:image/{};base64,{}".format(
|
|
|
|
"jpeg" if file_extension == "jpg" else file_extension, encoded
|
|
|
|
)
|
2023-05-25 15:32:31 +01:00
|
|
|
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
|
2024-12-06 18:15:47 +00:00
|
|
|
if color.startswith("#"):
|
2023-05-25 15:32:31 +01:00
|
|
|
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)}")
|