advent22/api/advent22_api/routers/days.py

243 lines
5.5 KiB
Python

import colorsys
import random
import re
from datetime import date
from io import BytesIO
import numpy as np
from fastapi import APIRouter, Depends
from fastapi.responses import StreamingResponse
from PIL import Image, ImageDraw, ImageFont
from webdav3.client import Client as WebDAVclient
from ..settings import SETTINGS
router = APIRouter(prefix="/days", tags=["days"])
loesungswort = "ABCDEFGHIJKLMNOPQRSTUVWX"
_WEBDAV_CLIENT = WebDAVclient({
"webdav_hostname": SETTINGS.webdav.url,
"webdav_login": SETTINGS.webdav.username,
"webdav_password": SETTINGS.webdav.password,
"disable_check": SETTINGS.webdav.disable_check,
})
async def shuffle(string: str) -> str:
rnd = random.Random(loesungswort)
return "".join(rnd.sample(string, len(string)))
@router.on_event("startup")
async def narf() -> None:
print(loesungswort)
print(await shuffle(loesungswort))
@router.get("/letter/{index}")
async def get_letter(
index: int,
) -> str:
return (await shuffle(loesungswort))[index]
@router.get("/date")
def get_date() -> int:
return date.today().day
@router.get(
"/picture",
response_class=StreamingResponse,
)
async def get_picture():
img = Image.open("hand.png").convert("RGBA")
d1 = ImageDraw.Draw(img)
font = ImageFont.truetype("Lena.ttf", 50)
d1.text((260, 155), "W", font=font, fill=(0, 0, 255))
# d1.text(xy=(400, 210), text="Deine Hände auch?",
# font=Font, fill=(255, 0, 0))
img_buffer = BytesIO()
img.save(img_buffer, format="PNG", quality=85)
img_buffer.seek(0)
return StreamingResponse(
content=img_buffer,
media_type="image/png",
)
_RE_IMAGE_FILE = re.compile(
r"\.(gif|jpe?g|tiff?|png|bmp)$",
flags=re.IGNORECASE,
)
async def list_images(path: str) -> list[str]:
ls = _WEBDAV_CLIENT.list(path)
return [
path
for path in ls
if _RE_IMAGE_FILE.search(path)
]
async def get_file(path: str) -> BytesIO:
resource = _WEBDAV_CLIENT.resource(path)
buffer = BytesIO()
resource.write_to(buffer)
buffer.seek(0)
return buffer
async def load_picture_standard(
index: int,
) -> Image.Image:
"""
Bild laden und einen quadratischen Ausschnitt
aus der Mitte nehmen
"""
print(await list_images(""))
# Bild laden
rnd = random.Random(f"{loesungswort}{index}")
dat = rnd.choice(await list_images(""))
img_buffer = await get_file(dat)
img = Image.open(img_buffer)
# Größen bestimmen
width, height = img.size
square = min(width, height)
# Bild zuschneiden und skalieren
img = img.crop(box=(
int((width - square)/2),
int((height - square)/2),
int((width + square)/2),
int((height + square)/2),
))
img = img.resize(
size=(500, 500),
resample=Image.ANTIALIAS,
)
# Farbmodell festlegen
return img.convert("RGB")
async def get_text_box(
img: Image.Image,
xy: tuple[float, float],
text: str | bytes,
font: "ImageFont._Font",
anchor: str | None = "mm",
**text_kwargs,
) -> tuple[int, int, int, int] | None:
"""
Koordinaten (links, oben, rechts, unten) des betroffenen
Rechtecks bestimmen, wenn das Bild `img` mit einem Text
versehen wird
"""
# Neues 1-Bit Bild, gleiche Größe
mask = Image.new(mode="1", size=img.size, color=0)
# Text auf Maske auftragen
ImageDraw.Draw(mask).text(
xy=xy,
text=text,
font=font,
anchor=anchor,
fill=1,
**text_kwargs,
)
# betroffenen Pixelbereich bestimmen
return mask.getbbox()
async def get_average_color(
img: Image.Image,
box: tuple[int, int, int, int],
) -> tuple[int, int, int]:
"""
Durchschnittsfarbe eines rechteckigen Ausschnitts in
einem Bild `img` berechnen
"""
pixel_data = img.crop(box).getdata()
mean_color: np.ndarray = np.mean(pixel_data, axis=0)
return tuple(mean_color.astype(int).tolist())
@router.get(
"/picture/{index}",
response_class=StreamingResponse,
)
async def get_picture_for_day(
index: int,
letter: str = Depends(get_letter),
img: Image.Image = Depends(load_picture_standard),
) -> StreamingResponse:
"""
Bild für einen Tag erstellen
"""
# Font laden
font = ImageFont.truetype("Lena.ttf", 50)
# Position des Buchstaben bestimmen
rnd = random.Random(f"{loesungswort}{index}")
xy = tuple(rnd.choices(range(30, 470), k=2))
# betroffenen Bildbereich bestimmen
text_box = await get_text_box(
img=img,
xy=xy,
text=letter,
font=font,
)
if text_box is not None:
# Durchschnittsfarbe bestimmen
text_color = await get_average_color(
img=img,
box=text_box,
)
# etwas heller/dunkler machen
tc_h, tc_s, tc_v = colorsys.rgb_to_hsv(*text_color)
tc_v = int((tc_v - 127) * 0.97) + 127
if tc_v < 127:
tc_v += 3
else:
tc_v -= 3
text_color = colorsys.hsv_to_rgb(tc_h, tc_s, tc_v)
text_color = tuple(int(val) for val in text_color)
# Buchstaben verstecken
ImageDraw.Draw(img).text(
xy=xy,
text=letter,
font=font,
anchor="mm",
fill=text_color,
)
# Bilddaten in Puffer laden
img_buffer = BytesIO()
img.save(img_buffer, format="JPEG", quality=85)
img_buffer.seek(0)
return StreamingResponse(
content=img_buffer,
media_type="image/jpeg",
)