fix(image): charge GIF frames at the canvas size Pillow decodes them at (#4526)
## Summary
A GIF can decode later frames onto a larger canvas than its first frame.
This follow-up to #4520 counts each frame's pixels at the canvas size
Pillow uses, so the aggregate image pixel limit applies before hi_res
decodes the frames.
The GIF scanner reads frame positions and dimensions without
decompressing image data. It tracks canvas growth and follows Pillow's
extension parsing, including empty extension blocks that could otherwise
cause the scanner to miss later frames. It closes its own file handle
when the image is rejected, even when the caller retains the exception
traceback. Borrowed streams remain open at their original position.
The check follows the resolved partition strategy: hi_res counts all
frames, while ocr_only counts only the first frame. Other formats retain
their existing accounting. The pixel budget limits the total pixels
partitioning will decode. Native decoders can still allocate memory when
they open a file.
## Validation
- 63 targeted image pixel, GIF and frame tests pass. Coverage includes
growing canvases, malformed extension blocks, exact budget boundaries,
no pixel allocation or downstream processing on rejection, and stream
ownership.
- The new regression tests for accounting and handle closure fail on the
pre-fix source and pass with this change. Ruff checks and formatting
pass.
(authored by codex)
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: cragwolfe <crag@unstructured.io>