AdventImage module

This commit is contained in:
Jörn-Michael Miehe 2022-10-14 22:09:23 +00:00
parent f7f3fec891
commit 752c449831
2 changed files with 93 additions and 78 deletions

View file

@ -0,0 +1,83 @@
from dataclasses import dataclass
import numpy as np
from PIL import Image, ImageDraw, ImageFont
@dataclass
class AdventImage:
img: Image.Image
@classmethod
async def load_standard(cls, fp) -> "AdventImage":
"""
Bild laden und einen quadratischen Ausschnitt
aus der Mitte nehmen
"""
# Bild laden
img = Image.open(fp=fp)
# 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 cls(img=img.convert("RGB"))
async def get_text_box(
self,
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=self.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(
self,
box: tuple[int, int, int, int],
) -> tuple[int, int, int]:
"""
Durchschnittsfarbe eines rechteckigen Ausschnitts in
einem Bild `img` berechnen
"""
pixel_data = self.img.crop(box).getdata()
mean_color: np.ndarray = np.mean(pixel_data, axis=0)
return tuple(mean_color.astype(int).tolist())

View file

@ -4,12 +4,12 @@ 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 PIL import ImageDraw, ImageFont
from ..dav_common import get_file, list_files
from ._image import AdventImage
router = APIRouter(prefix="/days", tags=["days"])
@ -65,9 +65,9 @@ _RE_IMAGE_FILE = re.compile(
)
async def load_picture_standard(
async def load_image(
index: int,
) -> Image.Image:
) -> AdventImage:
"""
Bild laden und einen quadratischen Ausschnitt
aus der Mitte nehmen
@ -78,73 +78,7 @@ async def load_picture_standard(
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())
return await AdventImage.load_standard(img_buffer)
@router.get(
@ -154,7 +88,7 @@ async def get_average_color(
async def get_picture_for_day(
index: int,
letter: str = Depends(get_letter),
img: Image.Image = Depends(load_picture_standard),
adv_img: AdventImage = Depends(load_image),
) -> StreamingResponse:
"""
Bild für einen Tag erstellen
@ -168,8 +102,7 @@ async def get_picture_for_day(
xy = tuple(rnd.choices(range(30, 470), k=2))
# betroffenen Bildbereich bestimmen
text_box = await get_text_box(
img=img,
text_box = await adv_img.get_text_box(
xy=xy,
text=letter,
font=font,
@ -177,8 +110,7 @@ async def get_picture_for_day(
if text_box is not None:
# Durchschnittsfarbe bestimmen
text_color = await get_average_color(
img=img,
text_color = await adv_img.get_average_color(
box=text_box,
)
@ -196,7 +128,7 @@ async def get_picture_for_day(
text_color = tuple(int(val) for val in text_color)
# Buchstaben verstecken
ImageDraw.Draw(img).text(
ImageDraw.Draw(adv_img.img).text(
xy=xy,
text=letter,
font=font,
@ -206,7 +138,7 @@ async def get_picture_for_day(
# Bilddaten in Puffer laden
img_buffer = BytesIO()
img.save(img_buffer, format="JPEG", quality=85)
adv_img.img.save(img_buffer, format="JPEG", quality=85)
img_buffer.seek(0)
return StreamingResponse(