Skip to content

Simplify ImageCropper crop: drop the leaking MemoryStream round-trip#1525

Draft
imnasnainaec wants to merge 1 commit into
fix/1275-imagecropper-crashfrom
fix/1275-cropper-memorystream-dispose
Draft

Simplify ImageCropper crop: drop the leaking MemoryStream round-trip#1525
imnasnainaec wants to merge 1 commit into
fix/1275-imagecropper-crashfrom
fix/1275-cropper-memorystream-dispose

Conversation

@imnasnainaec

@imnasnainaec imnasnainaec commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Simplifies the memory-leak concern raised on #1521 (CodeQL: MemoryStream created but not disposed; Devin: orphaned streams may accumulate across repeated crops).

What changed

GetCroppedImage no longer re-encodes the crop through a MemoryStream. That round-trip existed only to restore the returned bitmap's RawFormat to JPEG, but nothing reads it — PalasoImage.Save picks the format from the file extension, and SaveImageSafely already copies via new Bitmap(Image) before saving.

GetCroppedImage now returns a stand-alone new Bitmap(cropped):

  • No stream to leak — the CodeQL/Devin concern goes away entirely (there is no MemoryStream).
  • Severs the lazy file reference — a Clone of the file-backed originalImage keeps a lazy link to its source, which left the caller's temp file locked when the crop was later re-saved (the Image setter does exactly this). Copying into a fresh Bitmap breaks that link, the same defense PalasoImage.SaveImageSafely uses.

Also removes the now-dead _originalFormat field and its NRE-guard test.

Behavior change

The returned bitmap's RawFormat is now MemoryBmp rather than Jpeg. This is not observed anywhere: save format is chosen by extension. Tests updated to reflect the new contract.

Tests

All ImageCropperTests pass locally (net8.0-windows), including SetImage_ReCropPreviouslyCroppedJpeg_DoesNotThrow, the #1275 regression test.

🤖 Generated with Claude Code


This change is Reviewable

The MemoryStream fix for #1275 could not dispose the stream (GDI+ decodes
lazily, so the stream had to outlive the returned Bitmap), which CodeQL
flagged as a leak and which could accumulate orphaned streams across repeated
crops.

Drop the JPEG re-encode round-trip entirely. Its only effect was restoring the
returned bitmap's RawFormat to Jpeg, and nothing reads that: PalasoImage.Save
picks the format from the file extension. GetCroppedImage now copies the crop
into a fresh Bitmap (new Bitmap(...)), which also severs the lazy reference the
file-backed clone keeps to originalImage -- otherwise the caller's temp file
stays locked when the crop is re-saved (as the Image setter does), the same
defense SaveImageSafely already uses.

Removes the now-dead _originalFormat field and its NRE-guard test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Palaso Tests

     4 files  ±0       4 suites  ±0   10m 14s ⏱️ -19s
 5 109 tests ±0   4 875 ✅ ±0  234 💤 ±0  0 ❌ ±0 
16 639 runs  ±0  15 918 ✅ ±0  721 💤 ±0  0 ❌ ±0 

Results for commit e6ae09e. ± Comparison against base commit 6843326.

@imnasnainaec imnasnainaec self-assigned this Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant