Compare commits
No commits in common. "af08917155107e8890800b966a35943818f3123e" and "2931a4ce1b7ae3390e6bb2086d3266fac473d925" have entirely different histories.
af08917155
...
2931a4ce1b
6 changed files with 26 additions and 34 deletions
|
|
@ -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-extra/features/poetry:2": {},
|
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
||||||
"ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
|
"ghcr.io/devcontainers-contrib/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": {}
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,20 @@ 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 as PILImage
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
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
|
img: Image.Image
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_img(cls, img: Image, cfg: Config) -> Self:
|
async def from_img(cls, img: Image.Image, cfg: Config) -> Self:
|
||||||
"""
|
"""
|
||||||
Einen quadratischen Ausschnitt aus der Mitte des Bilds nehmen
|
Einen quadratischen Ausschnitt aus der Mitte des Bilds nehmen
|
||||||
"""
|
"""
|
||||||
|
|
@ -46,7 +42,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=Resampling.LANCZOS,
|
resample=Image.LANCZOS,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -54,10 +50,10 @@ class AdventImage:
|
||||||
self,
|
self,
|
||||||
xy: _XY,
|
xy: _XY,
|
||||||
text: str | bytes,
|
text: str | bytes,
|
||||||
font: FreeTypeFont,
|
font: "ImageFont._Font",
|
||||||
anchor: str | None = "mm",
|
anchor: str | None = "mm",
|
||||||
**text_kwargs,
|
**text_kwargs,
|
||||||
) -> _Box | None:
|
) -> "Image._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
|
||||||
|
|
@ -65,7 +61,7 @@ class AdventImage:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Neues 1-Bit Bild, gleiche Größe
|
# Neues 1-Bit Bild, gleiche Größe
|
||||||
mask = PILImage.new(mode="1", size=self.img.size)
|
mask = Image.new(mode="1", size=self.img.size, color=0)
|
||||||
|
|
||||||
# Text auf Maske auftragen
|
# Text auf Maske auftragen
|
||||||
ImageDraw.Draw(mask).text(
|
ImageDraw.Draw(mask).text(
|
||||||
|
|
@ -82,15 +78,14 @@ class AdventImage:
|
||||||
|
|
||||||
async def get_average_color(
|
async def get_average_color(
|
||||||
self,
|
self,
|
||||||
box: _Box,
|
box: "Image._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 = np.asarray(self.img.crop(box))
|
pixel_data = self.img.crop(box).getdata()
|
||||||
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)))
|
||||||
|
|
@ -99,7 +94,7 @@ class AdventImage:
|
||||||
self,
|
self,
|
||||||
xy: _XY,
|
xy: _XY,
|
||||||
text: str | bytes,
|
text: str | bytes,
|
||||||
font: FreeTypeFont,
|
font: "ImageFont._Font",
|
||||||
anchor: str | None = "mm",
|
anchor: str | None = "mm",
|
||||||
**text_kwargs,
|
**text_kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@ from io import BytesIO
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from PIL import ImageFont
|
from PIL import Image, 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
|
||||||
|
|
@ -140,7 +138,7 @@ class TTFont:
|
||||||
size: int = 50
|
size: int = 50
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def font(self) -> FreeTypeFont:
|
async def font(self) -> "ImageFont._Font":
|
||||||
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,
|
||||||
|
|
@ -171,7 +169,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
|
||||||
"""
|
"""
|
||||||
|
|
@ -202,7 +200,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 | None:
|
) -> Image.Image | None:
|
||||||
"""
|
"""
|
||||||
Bild für einen Tag abrufen
|
Bild für einen Tag abrufen
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ 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 as PILImage
|
from PIL import Image
|
||||||
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
|
||||||
|
|
@ -104,7 +103,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:
|
async def load_image(file_name: str) -> Image.Image:
|
||||||
"""
|
"""
|
||||||
Versuche, Bild aus Datei zu laden
|
Versuche, Bild aus Datei zu laden
|
||||||
"""
|
"""
|
||||||
|
|
@ -112,17 +111,17 @@ async def load_image(file_name: str) -> 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 PILImage.open(BytesIO(await WebDAV.read_bytes(file_name)))
|
return Image.open(BytesIO(await WebDAV.read_bytes(file_name)))
|
||||||
|
|
||||||
|
|
||||||
async def api_return_ico(img: Image) -> StreamingResponse:
|
async def api_return_ico(img: Image.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=Resampling.LANCZOS)
|
img.resize(size=(256, 256), resample=Image.LANCZOS)
|
||||||
img.save(img_buffer, format="ICO")
|
img.save(img_buffer, format="ICO")
|
||||||
img_buffer.seek(0)
|
img_buffer.seek(0)
|
||||||
|
|
||||||
|
|
@ -133,7 +132,7 @@ async def api_return_ico(img: Image) -> StreamingResponse:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def api_return_jpeg(img: Image) -> StreamingResponse:
|
async def api_return_jpeg(img: Image.Image) -> StreamingResponse:
|
||||||
"""
|
"""
|
||||||
JPEG-Bild mit API zurückgeben
|
JPEG-Bild mit API zurückgeben
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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.Image import Image
|
from PIL 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 | None = Depends(get_day_image),
|
image: Image.Image | None = Depends(get_day_image),
|
||||||
) -> StreamingResponse:
|
) -> StreamingResponse:
|
||||||
"""
|
"""
|
||||||
Bild für einen Tag erstellen
|
Bild für einen Tag erstellen
|
||||||
|
|
|
||||||
|
|
@ -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-extra/features/apt-get-packages:1": {
|
"ghcr.io/devcontainers-contrib/features/apt-get-packages:1": {
|
||||||
"packages": "git-flow, git-lfs"
|
"packages": "git-flow, git-lfs"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers-extra/features/vue-cli:2": {}
|
"ghcr.io/devcontainers-contrib/features/vue-cli:2": {}
|
||||||
},
|
},
|
||||||
// Configure tool-specific properties.
|
// Configure tool-specific properties.
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue