Skip to content

fix(ui): sample composer image preview to avoid Canvas draw crash - #1210

Merged
torlando-tech merged 2 commits into
mainfrom
fix/issue-1209-sampled-image-preview
Oct 22, 2026
Merged

torlando-tech merged 2 commits into
mainfrom
fix/issue-1209-sampled-image-preview

Conversation

@torlando-tech

@torlando-tech torlando-tech commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Problem

Closes #1209. Sending a large image at Original quality crashed with Canvas: trying to draw too large(200540160 bytes) bitmap.

The composer preview decoded the selected image bytes at full resolution (MessagingScreen BitmapFactory.decodeByteArray). The ORIGINAL preset caps at 8192px, so the reporter's 8160×6144 photo passed through the pipeline unscaled; decoding it for the 80.dp preview allocated an 8160×6144×4 = 200,540,160-byte bitmap, which the hardware canvas refuses to draw above its 100MB limit (RecordingCanvas.MAX_BITMAP_SIZE).

Fix

Decode the preview bounds-first with power-of-two sampling via a new ImageUtils.decodeSampledBitmap(bytes, maxDimensionPx) (1024px cap) — the same strategy the message-bubble path already uses. Every live draw path now samples (preview, bubble, Micron images, Coil GIFs/fullscreen).

The send pipeline is unchanged: ORIGINAL still ships the full-resolution image. Only the preview is sampled, so image quality on the wire is preserved.

Metadata stripping still holds at ORIGINAL

The EXIF/GPS-stripping guarantee comes from decode → re-encode, not from the WebP container: loadBitmap decodes to raw pixels (baking EXIF orientation in) and Bitmap.compress writes a fresh file, so source APP1 metadata (GPS, device model) never survives — even though the preset path emits JPEG rather than WebP. A new regression test pins this: stamped GPS + rotate-90 input → ORIGINAL output has no GPS, claims no rotation, and carries the rotation in pixels.

Tests

ImageUtilsRobolectricTest (Robolectric, real compressImageWithPreset pipeline on a synthetic 8160×6144 JPEG):

  • ORIGINAL preset preview fits hardware canvas limit — the red repro (200,540,160 bytes) now ≤ 100MB
  • ORIGINAL preset preserves source dimensions on the wire — output stays 8160×6144 (guards against shrinking the send)
  • ORIGINAL preset strips EXIF metadata from the sent image — privacy invariant: GPS never rides along at ORIGINAL quality
  • HIGH preset preview fits hardware canvas limit — control
  • decodeSampledBitmap samples large images below twice the target dimension

ImageUtilsRobolectricTest 38/38, MessagingScreenTest green, :app:detekt clean.

Fixes COLUMBA-EC

The composer preview decoded the selected image bytes at full
resolution. An ORIGINAL-preset send of an 8160x6144 photo produced a
200,540,160-byte bitmap (8160*6144*4), which the hardware canvas
refuses to draw above 100MB, crashing with "Canvas: trying to draw
too large(...) bitmap" when attaching large images.

Decode the preview bounds-first with power-of-two sampling via the
new ImageUtils.decodeSampledBitmap (1024px cap), matching the
message-bubble path. The send pipeline is unchanged: ORIGINAL still
ships the full-resolution image; only the preview is sampled.

Fixes #1209
Fixes COLUMBA-EC
@greptile-apps

greptile-apps Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Low impact] The PR appears safe to merge; no actionable new issue was found.

Summary

The PR samples composer image previews without changing the image sent.

  • The composer samples static images before showing their preview.

No actionable new issues were found. Tests were not run during this review.

Reviews (3) · Last reviewed commit: "test(ui): pin that preset re-encoding st..." · Reviewed by Greptile

The privacy guarantee (camera GPS/device info never leaves the
device via an image message) comes from decode->re-encode, not from
the WebP container: the preset path emits JPEG, yet Bitmap.compress
writes a fresh file from raw pixels. Stamp GPS + rotate-90 EXIF onto
a JPEG, run the ORIGINAL preset, and assert the output carries no
GPS, claims no rotation, and has the rotation baked into pixels.
@codecov

codecov Bot commented Oct 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@torlando-tech

Copy link
Copy Markdown
Owner Author

@greptile review

@torlando-tech
torlando-tech merged commit cf66bd3 into main Oct 22, 2026
15 checks passed
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.

Columba crashes when sending large images

1 participant