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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #96. Carries the deferred #81 EXIF follow-up. Backend, small.
Problem.
internal/imagenormdecodes and re-encodes to JPEG but ignores the EXIFOrientationtag. 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.
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).CGO_ENABLED=0— pure-Go only (e.g. read the tag via a small EXIF reader or parse the APP1 segment; rotate withgolang.org/x/image/drawor hand transforms). No new cgo deps.Acceptance.
Independent of the scan-UI issue; either can land first.