Compare commits

...

4 commits

6 changed files with 34 additions and 26 deletions

View file

@ -6,8 +6,8 @@
"image": "mcr.microsoft.com/vscode/devcontainers/python:1-3.11-bookworm", "image": "mcr.microsoft.com/vscode/devcontainers/python:1-3.11-bookworm",
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
"features": { "features": {
"ghcr.io/devcontainers-contrib/features/poetry:2": {}, "ghcr.io/devcontainers-extra/features/poetry:2": {},
"ghcr.io/devcontainers-contrib/features/apt-get-packages:1": { "ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
"packages": "git-flow, git-lfs" "packages": "git-flow, git-lfs"
}, },
"ghcr.io/itsmechlark/features/redis-server:1": {} "ghcr.io/itsmechlark/features/redis-server:1": {}

View file

@ -3,20 +3,24 @@ from dataclasses import dataclass
from typing import Self, TypeAlias, cast from typing import Self, TypeAlias, cast
import numpy as np import numpy as np
from PIL import Image, ImageDraw, ImageFont from PIL import Image as PILImage
from PIL import ImageDraw
from PIL.Image import Image, Resampling
from PIL.ImageFont import FreeTypeFont
from .config import Config from .config import Config
_RGB: TypeAlias = tuple[int, int, int] _RGB: TypeAlias = tuple[int, int, int]
_XY: TypeAlias = tuple[float, float] _XY: TypeAlias = tuple[float, float]
_Box: TypeAlias = tuple[int, int, int, int]
@dataclass(slots=True, frozen=True) @dataclass(slots=True, frozen=True)
class AdventImage: class AdventImage:
img: Image.Image img: Image
@classmethod @classmethod
async def from_img(cls, img: Image.Image, cfg: Config) -> Self: async def from_img(cls, img: Image, cfg: Config) -> Self:
""" """
Einen quadratischen Ausschnitt aus der Mitte des Bilds nehmen Einen quadratischen Ausschnitt aus der Mitte des Bilds nehmen
""" """
@ -42,7 +46,7 @@ class AdventImage:
return cls( return cls(
img.resize( img.resize(
size=(cfg.image.size, cfg.image.size), size=(cfg.image.size, cfg.image.size),
resample=Image.LANCZOS, resample=Resampling.LANCZOS,
) )
) )
@ -50,10 +54,10 @@ class AdventImage:
self, self,
xy: _XY, xy: _XY,
text: str | bytes, text: str | bytes,
font: "ImageFont._Font", font: FreeTypeFont,
anchor: str | None = "mm", anchor: str | None = "mm",
**text_kwargs, **text_kwargs,
) -> "Image._Box | None": ) -> _Box | None:
""" """
Koordinaten (links, oben, rechts, unten) des betroffenen Koordinaten (links, oben, rechts, unten) des betroffenen
Rechtecks bestimmen, wenn das Bild mit einem Text Rechtecks bestimmen, wenn das Bild mit einem Text
@ -61,7 +65,7 @@ class AdventImage:
""" """
# Neues 1-Bit Bild, gleiche Größe # Neues 1-Bit Bild, gleiche Größe
mask = Image.new(mode="1", size=self.img.size, color=0) mask = PILImage.new(mode="1", size=self.img.size)
# Text auf Maske auftragen # Text auf Maske auftragen
ImageDraw.Draw(mask).text( ImageDraw.Draw(mask).text(
@ -78,14 +82,15 @@ class AdventImage:
async def get_average_color( async def get_average_color(
self, self,
box: "Image._Box", box: _Box,
) -> tuple[int, int, int]: ) -> tuple[int, int, int]:
""" """
Durchschnittsfarbe eines rechteckigen Ausschnitts in Durchschnittsfarbe eines rechteckigen Ausschnitts in
einem Bild berechnen einem Bild berechnen
""" """
pixel_data = self.img.crop(box).getdata() pixel_data = np.asarray(self.img.crop(box))
print(pixel_data)
mean_color: np.ndarray = np.mean(pixel_data, axis=0) mean_color: np.ndarray = np.mean(pixel_data, axis=0)
return cast(_RGB, tuple(mean_color.astype(int))) return cast(_RGB, tuple(mean_color.astype(int)))
@ -94,7 +99,7 @@ class AdventImage:
self, self,
xy: _XY, xy: _XY,
text: str | bytes, text: str | bytes,
font: "ImageFont._Font", font: FreeTypeFont,
anchor: str | None = "mm", anchor: str | None = "mm",
**text_kwargs, **text_kwargs,
) -> None: ) -> None:

View file

@ -5,7 +5,9 @@ from io import BytesIO
from typing import cast from typing import cast
from fastapi import Depends from fastapi import Depends
from PIL import Image, ImageFont from PIL import ImageFont
from PIL.Image import Image
from PIL.ImageFont import FreeTypeFont
from .advent_image import _XY, AdventImage from .advent_image import _XY, AdventImage
from .calendar_config import CalendarConfig, get_calendar_config from .calendar_config import CalendarConfig, get_calendar_config
@ -138,7 +140,7 @@ class TTFont:
size: int = 50 size: int = 50
@property @property
async def font(self) -> "ImageFont._Font": async def font(self) -> FreeTypeFont:
return ImageFont.truetype( return ImageFont.truetype(
font=BytesIO(await WebDAV.read_bytes(self.file_name)), font=BytesIO(await WebDAV.read_bytes(self.file_name)),
size=100, size=100,
@ -169,7 +171,7 @@ async def gen_day_auto_image(
auto_image_names: dict[int, str], auto_image_names: dict[int, str],
day_parts: dict[int, str], day_parts: dict[int, str],
ttfonts: list[TTFont], ttfonts: list[TTFont],
) -> Image.Image: ) -> Image:
""" """
Automatisch generiertes Bild erstellen Automatisch generiertes Bild erstellen
""" """
@ -200,7 +202,7 @@ async def get_day_image(
auto_image_names: dict[int, str] = Depends(get_all_auto_image_names), auto_image_names: dict[int, str] = Depends(get_all_auto_image_names),
day_parts: dict[int, str] = Depends(get_all_parts), day_parts: dict[int, str] = Depends(get_all_parts),
ttfonts: list[TTFont] = Depends(get_all_ttfonts), ttfonts: list[TTFont] = Depends(get_all_ttfonts),
) -> Image.Image | None: ) -> Image | None:
""" """
Bild für einen Tag abrufen Bild für einen Tag abrufen
""" """

View file

@ -6,7 +6,8 @@ from io import BytesIO
from typing import Any, Awaitable, Callable, Iterable, Self, Sequence, TypeVar from typing import Any, Awaitable, Callable, Iterable, Self, Sequence, TypeVar
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from PIL import Image from PIL import Image as PILImage
from PIL.Image import Image, Resampling
from .config import get_config from .config import get_config
from .dav.webdav import WebDAV from .dav.webdav import WebDAV
@ -103,7 +104,7 @@ list_images_manual = list_helper("/images_manual", RE_IMG)
list_fonts = list_helper("/files", RE_TTF) list_fonts = list_helper("/files", RE_TTF)
async def load_image(file_name: str) -> Image.Image: async def load_image(file_name: str) -> Image:
""" """
Versuche, Bild aus Datei zu laden Versuche, Bild aus Datei zu laden
""" """
@ -111,17 +112,17 @@ async def load_image(file_name: str) -> Image.Image:
if not await WebDAV.exists(file_name): if not await WebDAV.exists(file_name):
raise RuntimeError(f"DAV-File {file_name} does not exist!") raise RuntimeError(f"DAV-File {file_name} does not exist!")
return Image.open(BytesIO(await WebDAV.read_bytes(file_name))) return PILImage.open(BytesIO(await WebDAV.read_bytes(file_name)))
async def api_return_ico(img: Image.Image) -> StreamingResponse: async def api_return_ico(img: Image) -> StreamingResponse:
""" """
ICO-Bild mit API zurückgeben ICO-Bild mit API zurückgeben
""" """
# JPEG-Daten in Puffer speichern # JPEG-Daten in Puffer speichern
img_buffer = BytesIO() img_buffer = BytesIO()
img.resize(size=(256, 256), resample=Image.LANCZOS) img.resize(size=(256, 256), resample=Resampling.LANCZOS)
img.save(img_buffer, format="ICO") img.save(img_buffer, format="ICO")
img_buffer.seek(0) img_buffer.seek(0)
@ -132,7 +133,7 @@ async def api_return_ico(img: Image.Image) -> StreamingResponse:
) )
async def api_return_jpeg(img: Image.Image) -> StreamingResponse: async def api_return_jpeg(img: Image) -> StreamingResponse:
""" """
JPEG-Bild mit API zurückgeben JPEG-Bild mit API zurückgeben
""" """

View file

@ -2,7 +2,7 @@ from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from PIL import Image from PIL.Image import Image
from ..core.calendar_config import CalendarConfig, DoorsSaved, get_calendar_config from ..core.calendar_config import CalendarConfig, DoorsSaved, get_calendar_config
from ..core.config import Config, Site, get_config from ..core.config import Config, Site, get_config
@ -75,7 +75,7 @@ async def get_doors(
async def get_image_for_day( async def get_image_for_day(
user_can_view: bool = Depends(user_can_view_day), user_can_view: bool = Depends(user_can_view_day),
is_admin: bool = Depends(user_is_admin), is_admin: bool = Depends(user_is_admin),
image: Image.Image | None = Depends(get_day_image), image: Image | None = Depends(get_day_image),
) -> StreamingResponse: ) -> StreamingResponse:
""" """
Bild für einen Tag erstellen Bild für einen Tag erstellen

View file

@ -6,10 +6,10 @@
"image": "mcr.microsoft.com/vscode/devcontainers/javascript-node:1-18-bookworm", "image": "mcr.microsoft.com/vscode/devcontainers/javascript-node:1-18-bookworm",
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
"features": { "features": {
"ghcr.io/devcontainers-contrib/features/apt-get-packages:1": { "ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
"packages": "git-flow, git-lfs" "packages": "git-flow, git-lfs"
}, },
"ghcr.io/devcontainers-contrib/features/vue-cli:2": {} "ghcr.io/devcontainers-extra/features/vue-cli:2": {}
}, },
// Configure tool-specific properties. // Configure tool-specific properties.
"customizations": { "customizations": {