Files
2026-10-23 16:06:29 +02:00

59 lines
2.4 KiB
Python

"""Ograniczona obsluga duzych obrazow Pillow.
Pillow domyslnie ostrzega powyzej ~89 MP i zglasza blad powyzej ~179 MP.
Dla skanow dopuszczamy konfigurowalny limit, ale PRZED dekodowaniem
odrzucamy wszystko, co ten limit przekracza. Nie wylaczamy ochrony globalnie.
Narzędzie CLI wykonuje operacje obrazow sekwencyjnie; prog PIL jest globalny,
ale zmieniamy go tylko na czas kontrolowanego otwarcia i przywracamy po nim.
"""
from __future__ import annotations
import warnings
from contextlib import contextmanager
from PIL import Image
DEFAULT_MAX_IMAGE_PIXELS = 220_000_000 # 220 MP; obejmuje skany ~200 MP
class ImagePixelLimitExceeded(ValueError):
"""Obraz nie zostal zdekodowany, bo przekracza ustalony limit."""
@contextmanager
def safe_image_open(path, *, max_pixels=DEFAULT_MAX_IMAGE_PIXELS, check_pixels=True):
"""Otworz naglowek obrazu z limitem, zanim Pillow zdekoduje piksele.
Ostrzezenie Pillow dot. progu zostaje zastapione JAWNYM sprawdzeniem
wielkosci (i statusem SKIP w callerze). Pozostale ostrzezenia pozostaja
widoczne. Dla samego odczytu wymiarow check_pixels=False nie dekoduje.
"""
max_pixels = int(max_pixels)
if max_pixels <= 0:
raise ValueError('max_pixels musi byc wieksze od zera')
old_limit = Image.MAX_IMAGE_PIXELS
with warnings.catch_warnings():
warnings.filterwarnings('ignore', category=Image.DecompressionBombWarning)
Image.MAX_IMAGE_PIXELS = max_pixels
try:
try:
with Image.open(path) as image:
pixels = image.width * image.height
if check_pixels and pixels > max_pixels:
raise ImagePixelLimitExceeded(
f'{image.width}x{image.height} ({pixels / 1_000_000:.1f} MP) '
f'przekracza --max-image-mpixels={max_pixels / 1_000_000:g}; '
'pominieto dla ochrony pamieci RAM'
)
yield image
except Image.DecompressionBombError as exc:
# Bardzo duze obrazy Pillow moze odrzucic juz przy Image.open.
raise ImagePixelLimitExceeded(
f'Pillow odrzucil obraz przekraczajacy limit '
f'--max-image-mpixels={max_pixels / 1_000_000:g}: {exc}'
) from exc
finally:
Image.MAX_IMAGE_PIXELS = old_limit