"""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