ITADN

KeyError: 'JPEG' when saving RGB images to PDF without explicit format argument

#9545ClosedYokomi422 创建于 2026-04-06
Regression
Y
Yokomi422commented
## What did you do? Save multiple RGB images to a PDF file using a file path (without explicitly passing `format="pdf"`): ```python from pathlib import Path from PIL import Image image_dir = Path("./images") output = image_dir / "output.pdf" files = sorted(image_dir.glob("*png")) images = [Image.open(file).convert("RGB") for file in files] images[0].save(output, append_images=images[1:]) ``` ## What did you expect to happen? A valid PDF file is created, the same as when `format="pdf"` is explicitly passed: ```python images[0].save(output, append_images=images[1:], format="pdf") ``` ## What actually happened? ``` Traceback (most recent call last): File "main.py", line 12, in <module> images[0].save(output, append_images=images[1:]) File ".../PIL/Image.py", line 2713, in save save_handler(self, fp, filename) File ".../PIL/PdfImagePlugin.py", line 44, in _save_all _save(im, fp, filename, save_all=True) File ".../PIL/PdfImagePlugin.py", line 262, in _save image_ref, procset = _write_image(im, filename, existing_pdf, image_refs) File ".../PIL/PdfImagePlugin.py", line 151, in _write_image Image.SAVE["JPEG"](im, op, filename) KeyError: 'JPEG' ``` ## What are your OS, Python and Pillow versions? * OS: Linux (Ubuntu) * Python: 3.13.11 * Pillow: 12.2.0 ## Root cause analysis This is a regression introduced by #9398 (lazy plugin loading, included in Pillow 12.2.0). When `format` is **not** explicitly passed, `Image.save()` calls `_import_plugin_for_extension(".pdf")` which imports **only** `PdfImagePlugin`. Since this succeeds and registers `"PDF"` in `SAVE`, the guard at line 2689 (`if format.upper() not in SAVE: init()`) does **not** trigger `init()`. As a result, `JpegImagePlugin` is never loaded and `Image.SAVE["JPEG"]` does not exist. When `format="pdf"` **is** explicitly passed, `Image.save()` calls `preinit()` (line 2652), which imports `JpegImagePlugin` (among others), so `Image.SAVE["JPEG"]` is available. `PdfImagePlugin._write_image()` directly accesses `Image.SAVE["JPEG"]` (line 151, for DCTDecode) and `Image.SAVE["JPEG2000"]` (line 154, for JPXDecode), but these are implicit cross-plugin dependencies that the new lazy loading mechanism does not account for. ### Affected image modes - **L, RGB, CMYK** → triggers DCTDecode → requires `Image.SAVE["JPEG"]` - **LA, RGBA** → triggers JPXDecode → requires `Image.SAVE["JPEG2000"]` ### Possible fixes 1. Have `PdfImagePlugin` explicitly import its plugin dependencies (`JpegImagePlugin`, `Jpeg2KImagePlugin`) 2. Or have `_write_image()` call `preinit()` / lazy-load the required plugin before accessing `Image.SAVE` Related: #9428
关闭于 2026-05-06 1 条评论