mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-20 21:43:36 -07:00
Update logic to add max dimensions to all images
This commit is contained in:
parent
ca85e173bd
commit
b662b72086
1 changed files with 90 additions and 34 deletions
|
@ -57,7 +57,7 @@ class ABCMinifier(ABC):
|
|||
)
|
||||
|
||||
@abstractmethod
|
||||
def minify(self, image: Path, force=True): ...
|
||||
def minify(self, image_path: Path, force=True): ...
|
||||
|
||||
def purge(self, image: Path):
|
||||
if not self._purge:
|
||||
|
@ -71,7 +71,11 @@ class ABCMinifier(ABC):
|
|||
class PillowMinifier(ABCMinifier):
|
||||
@staticmethod
|
||||
def _convert_image(
|
||||
image_file: Path | None = None, image_format: ImageFormat, dest: Path | None = None, quality: int = 100, img: Image.Image | None = None
|
||||
image_file: Path | None = None,
|
||||
image_format: ImageFormat = WEBP,
|
||||
dest: Path | None = None,
|
||||
quality: int = 100,
|
||||
img: Image.Image | None = None,
|
||||
) -> Path:
|
||||
"""
|
||||
Converts an image to the specified format in-place. The original image is not
|
||||
|
@ -94,75 +98,127 @@ class PillowMinifier(ABCMinifier):
|
|||
|
||||
img.save(dest, image_format.format, quality=quality)
|
||||
|
||||
return dest
|
||||
width, height = img.size
|
||||
new_dest = dest.with_name(f"{dest.stem}_{width}x{height}{dest.suffix}")
|
||||
|
||||
return new_dest
|
||||
|
||||
@staticmethod
|
||||
def to_jpg(image_file_path: Path | None = None, dest_path: Path | None = None, quality: int = 100, img: Image.Image | None = None) -> Path:
|
||||
return PillowMinifier._convert_image(image_file_path, JPG, dest_path, quality, img)
|
||||
def to_jpg(
|
||||
image_file_path: Path | None = None,
|
||||
dest: Path | None = None,
|
||||
quality: int = 100,
|
||||
img: Image.Image | None = None,
|
||||
) -> Path:
|
||||
return PillowMinifier._convert_image(image_file_path, JPG, dest, quality, img)
|
||||
|
||||
@staticmethod
|
||||
def to_webp(image_file_path: Path | None = None, dest_path: Path | None = None, quality: int = 100, img: Image.Image | None = None) -> Path:
|
||||
def to_webp(
|
||||
image_file_path: Path | None = None,
|
||||
dest_path: Path | None = None,
|
||||
quality: int = 100,
|
||||
img: Image.Image | None = None,
|
||||
) -> Path:
|
||||
return PillowMinifier._convert_image(image_file_path, WEBP, dest_path, quality, img)
|
||||
|
||||
@staticmethod
|
||||
def crop_center(pil_img: Image, crop_width=300, crop_height=300) -> Image.Image:
|
||||
img_width, img_height = pil_img.size
|
||||
return pil_img.crop(
|
||||
(
|
||||
(img_width - crop_width) // 2,
|
||||
(img_height - crop_height) // 2,
|
||||
(img_width + crop_width) // 2,
|
||||
(img_height + crop_height) // 2,
|
||||
)
|
||||
)
|
||||
def crop_center(img: Image.Image, size=(300, 300), high_res: bool = True) -> Image.Image:
|
||||
img = img.copy()
|
||||
target_width, target_height = size
|
||||
|
||||
def minify(self, image_file: Path, force=True):
|
||||
if not image_file.exists():
|
||||
raise FileNotFoundError(f"{image_file.name} does not exist")
|
||||
# For retina displays, double the target size
|
||||
if high_res:
|
||||
target_width *= 2
|
||||
target_height *= 2
|
||||
|
||||
org_dest = image_file.parent.joinpath("original.webp")
|
||||
min_dest = image_file.parent.joinpath("min-original.webp")
|
||||
tiny_dest = image_file.parent.joinpath("tiny-original.webp")
|
||||
img_ratio = img.width / img.height
|
||||
target_ratio = target_width / target_height
|
||||
|
||||
# If original image smaller than target, do not upscale
|
||||
if img.width < size[0] or img.height < size[1]:
|
||||
return img
|
||||
|
||||
# Resize first to fill area while preserving aspect ratio
|
||||
if img_ratio > target_ratio:
|
||||
# Wider than target
|
||||
scale_height = target_height
|
||||
scale_width = int(scale_height * img_ratio)
|
||||
else:
|
||||
# Taller than target
|
||||
scale_width = target_width
|
||||
scale_height = int(scale_width / img_ratio)
|
||||
|
||||
img = img.resize((scale_width, scale_height), Image.LANCZOS)
|
||||
|
||||
# Crop center of the resized image
|
||||
left = (img.width - target_width) // 2
|
||||
top = (img.height - target_height) // 2
|
||||
right = left + target_width
|
||||
bottom = top + target_height
|
||||
|
||||
return img.crop((left, top, right, bottom))
|
||||
|
||||
def minify(self, image_path: Path, force=True):
|
||||
if not image_path.exists():
|
||||
raise FileNotFoundError(f"{image_path.name} does not exist")
|
||||
|
||||
org_dest = image_path.parent.joinpath("original.webp")
|
||||
min_dest = image_path.parent.joinpath("min-original.webp")
|
||||
tiny_dest = image_path.parent.joinpath("tiny-original.webp")
|
||||
|
||||
if not force and min_dest.exists() and tiny_dest.exists() and org_dest.exists():
|
||||
self._logger.info(f"{image_file.name} already exists in all formats")
|
||||
self._logger.info(f"{image_path.name} already exists in all formats")
|
||||
return
|
||||
|
||||
success = False
|
||||
|
||||
try:
|
||||
with Image.open(image_file) as img:
|
||||
|
||||
with Image.open(image_path) as img:
|
||||
img = ImageOps.exif_transpose(img)
|
||||
if self._opts.original:
|
||||
# Used as default
|
||||
if not force and org_dest.exists():
|
||||
self._logger.info(f"{org_dest} already minified")
|
||||
else:
|
||||
result_path = PillowMinifier.to_webp(dest_path=org_dest, quality=80, img=img.copy())
|
||||
result_path = PillowMinifier.to_webp(
|
||||
dest_path=org_dest, quality=80, img=PillowMinifier.crop_center(img, size=(810, 400))
|
||||
)
|
||||
self._logger.info(f"{result_path} created")
|
||||
success = True
|
||||
|
||||
if self._opts.miniature:
|
||||
# Used /g/home in desktop / tablet view
|
||||
# Used g/home/cookbooks/ desktop view
|
||||
if not force and min_dest.exists():
|
||||
self._logger.info(f"{min_dest} already minified")
|
||||
else:
|
||||
mini = img.copy()
|
||||
mini.thumbnail((1024, 1024), Image.LANCZOS)
|
||||
result_path = PillowMinifier.to_webp(dest_path=min_dest, quality=70, img=mini)
|
||||
result_path = PillowMinifier.to_webp(
|
||||
dest_path=min_dest,
|
||||
quality=70,
|
||||
img=PillowMinifier.crop_center(img, size=(414, 200)),
|
||||
)
|
||||
self._logger.info(f"{result_path} created")
|
||||
success = True
|
||||
|
||||
if self._opts.tiny:
|
||||
# Used /g/home/ in mobile view
|
||||
# Used /g/home/recipes/finder all views
|
||||
# Used /household/mealplan/planner/edit (currently used)
|
||||
# Used /g/home/recipes/timeline Desktop view only
|
||||
# Used g/home/cookbooks/cookbook in mobile / tablet
|
||||
|
||||
if not force and tiny_dest.exists():
|
||||
self._logger.info(f"{tiny_dest} already minified")
|
||||
else:
|
||||
tiny_image = PillowMinifier.crop_center(ImageOps.exif_transpose(img.copy()))
|
||||
result_path = PillowMinifier.to_webp(dest_path=tiny_dest, quality=70, img=tiny_image)
|
||||
result_path = PillowMinifier.to_webp(
|
||||
dest_path=tiny_dest, quality=70, img=PillowMinifier.crop_center(img, size=(300, 300))
|
||||
)
|
||||
self._logger.info(f"{result_path} created")
|
||||
success = True
|
||||
|
||||
except Exception as e:
|
||||
self._logger.error(f"[ERROR] Failed to minify {image_file.name}. Error: {e}")
|
||||
self._logger.error(f"[ERROR] Failed to minify {image_path.name}. Error: {e}")
|
||||
raise
|
||||
|
||||
if self._purge and success:
|
||||
self.purge(image_file)
|
||||
self.purge(image_path)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue