advent22/api/advent22_api/routers/days.py
2022-10-10 20:24:37 +00:00

198 lines
4.4 KiB
Python

import colorsys
import random
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
router = APIRouter(prefix="/days", tags=["days"])
loesungswort = "ABCDEFGHIJKLMNOPQRSTUVWX"
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",
)
async def load_picture_standard() -> Image.Image:
"""
Bild laden und einen quadratischen Ausschnitt
aus der Mitte nehmen
"""
# Bild laden
img = Image.open("lena2.jpg")
# 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=(400, 400),
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(
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
xy = (100, 150)
# 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)
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/jpg",
)