59 lines
2.4 KiB
Python
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
|