EXIF orientation when normalizing uploaded images #103

Closed
opened 2026-07-22 04:37:31 +00:00 by steve · 0 comments
Owner

Part of #96. Carries the deferred #81 EXIF follow-up. Backend, small.

Problem. internal/imagenorm decodes and re-encodes to JPEG but ignores the EXIF Orientation tag. Phone cameras very often store the pixels in one orientation and set an EXIF flag to rotate on display — so a packet photographed in portrait can reach the vision model rotated 90°/180°, hurting OCR and making any future thumbnail sideways.

Do.

  • In imagenorm.Normalize, read the EXIF orientation from the original bytes (before/after decode) and apply the corresponding rotate/flip so the output JPEG's pixels are upright, then drop the orientation metadata (the re-encode already strips EXIF, which is why we must bake in the rotation).
  • Cover the 8 EXIF orientation values (the 4 rotations + mirrored variants); at minimum the common 3/6/8 (180/90CW/90CCW).
  • Keep CGO_ENABLED=0 — pure-Go only (e.g. read the tag via a small EXIF reader or parse the APP1 segment; rotate with golang.org/x/image/draw or hand transforms). No new cgo deps.
  • Test with fixtures carrying each orientation tag, asserting the decoded output pixels land where expected.

Acceptance.

  • A JPEG tagged orientation 6 (rotate 90° CW) comes out of Normalize visually upright.
  • HEIC/webp/png paths are unaffected (they don't carry the JPEG EXIF path, or are handled consistently).
  • Binary stays static; tests pass.

Independent of the scan-UI issue; either can land first.

Part of #96. Carries the deferred **#81 EXIF** follow-up. Backend, small. **Problem.** `internal/imagenorm` decodes and re-encodes to JPEG but ignores the EXIF `Orientation` tag. Phone cameras very often store the pixels in one orientation and set an EXIF flag to rotate on display — so a packet photographed in portrait can reach the vision model rotated 90°/180°, hurting OCR and making any future thumbnail sideways. **Do.** - In `imagenorm.Normalize`, read the EXIF orientation from the original bytes (before/after decode) and apply the corresponding rotate/flip so the output JPEG's pixels are upright, then drop the orientation metadata (the re-encode already strips EXIF, which is why we must bake in the rotation). - Cover the 8 EXIF orientation values (the 4 rotations + mirrored variants); at minimum the common 3/6/8 (180/90CW/90CCW). - Keep `CGO_ENABLED=0` — pure-Go only (e.g. read the tag via a small EXIF reader or parse the APP1 segment; rotate with `golang.org/x/image/draw` or hand transforms). No new cgo deps. - Test with fixtures carrying each orientation tag, asserting the decoded output pixels land where expected. **Acceptance.** - A JPEG tagged orientation 6 (rotate 90° CW) comes out of Normalize visually upright. - HEIC/webp/png paths are unaffected (they don't carry the JPEG EXIF path, or are handled consistently). - Binary stays static; tests pass. Independent of the scan-UI issue; either can land first.
steve added the backend label 2026-07-22 04:37:31 +00:00
steve closed this issue 2026-07-22 05:35:47 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#103