import logging
from typing import Optional
from PIL import Image
from pydantic import Field
from todays_paper_web.widgets import registerView, View
from todays_paper_web.widgets.view import ViewParams
_LOGGER = logging.getLogger(__name__)
class ImageParams(ViewParams):
"""Shared by every image-producing widget (`image`, `gocomics`, `weather.icon`) - sizing/
fitting only. Each concrete widget's own `Params` adds whatever it needs on top (e.g.
`LocalImageParams.path` below) to say where the source bitmap actually comes from."""
width: Optional[int] = Field(
None, description="Target width; unset keeps the source image's own."
)
height: Optional[int] = Field(
None, description="Target height; unset keeps the source image's own."
)
fillmode: str = Field(
"preserveaspectfit",
description="How the source image fills the width/height box: `preserveaspectfit` shrinks "
"to fit within the box without enlarging; `preserveaspectstretch` scales to touch at least "
"one edge of the box. Both preserve aspect ratio. An unrecognized value is logged and "
"leaves the image unresized, rather than raising.",
)
class ImageBaseView(View):
"""Shared resize/fit logic for any image-producing widget - not registered as a tag itself.
A subclass (`ImageView` below, `GoComics`, `WeatherIconView`) implements `fetch_img()` to say
where its source bitmap comes from; this class doesn't know or care."""
Params = ImageParams
def render(self):
img = self.fetch_img()
if img is None:
return None
if img.mode in ("LA"):
img = img.convert("RGBA")
new_width, new_height = img.size
if self.params.width:
new_width = self.params.width
if self.params.height:
new_height = self.params.height
if new_width != img.size[0] or new_height != img.size[1]:
img = self.resize(img, new_width, new_height)
return img
def fetch_img(self) -> Image:
raise NotImplementedError
def resize(self, img: Image, box_width: int, box_height: int) -> Image:
orig_width, orig_height = img.size
aspect = orig_width / orig_height
fillmode = self.params.fillmode.lower()
new_width, new_height = img.size
if fillmode in ("preserveaspectfit", "preserve_aspect_fit"):
if orig_width > box_width:
new_width = box_width
new_height = round(box_width / orig_width * orig_height)
if orig_height > box_height:
new_width = round(box_height / orig_height * orig_width)
new_height = box_height
elif fillmode in ("preserveaspectstretch", "preserve_aspect_stretch"):
if box_height * aspect <= box_width:
new_width = round(box_height * aspect)
new_height = box_height
else:
new_width = box_width
new_height = round(box_width / aspect)
else:
_LOGGER.warning("Unknown fillmode %s", fillmode)
return img
return img.resize((new_width, new_height))
[docs]
class LocalImageParams(ImageParams):
path: str = Field(
..., description="File path, resolved like any other file the config references."
)
@registerView("image")
class ImageView(ImageBaseView):
Params = LocalImageParams
def fetch_img(self) -> Image:
return Image.open(self.openFile(self.params.path))