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 ..dav_common import get_file, list_files 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 startup() -> 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 load_picture_standard( index: int, ) -> Image.Image: """ Bild laden und einen quadratischen Ausschnitt aus der Mitte nehmen """ # Bild laden rnd = random.Random(f"{loesungswort}{index}") dat = rnd.choice(await list_files(_RE_IMAGE_FILE)) 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", )