Address EXIF review: single alloc, no per-pixel boxing, hardened parser
Build image / build-and-push (push) Successful in 8s
Build image / build-and-push (push) Successful in 8s
Gadfly on #103: - (4 models) applyOrientation allocated a WxH RGBA then threw it away for a quarter-turn to allocate HxW. Compute the output dims once, allocate one buffer. - (perf) The At/Set loop boxed a color.Color per pixel — millions of heap allocs on a full-res rotation. Convert to *image.RGBA once (draw.Draw) then copy 4 bytes per pixel by offset. No boxing. - (2 findings) exifOrientation didn't skip 0xFF fill bytes before a marker, so a spec-valid padded APP1 would be misread. Skip them. - (2 findings) orientationFromApp1 read the tag's inline value without checking its type/count — a mistyped LONG/offset would be read as a bogus SHORT. Require type=SHORT, count=1; also validate the TIFF 0x2A magic agrees with the byte order. - Tests now cover all 8 orientations (added the mirror/transpose cases 2/4/5/7, the most error-prone switch arms) as t.Run subtests. All imagenorm tests green; gofmt clean; still CGO_ENABLED=0. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
@@ -269,36 +269,42 @@ func TestNormalizeAppliesExifOrientation(t *testing.T) {
|
||||
// orientation, wantW/H is the corrected canvas and (mx,my) is where that
|
||||
// marker must land — derived from the same transform Normalize applies.
|
||||
cases := []struct {
|
||||
name string
|
||||
orient int
|
||||
wantW, wantH int
|
||||
mx, my int
|
||||
}{
|
||||
{0, 40, 24, 10, 6}, // no EXIF → unchanged, marker top-left
|
||||
{1, 40, 24, 10, 6}, // normal → unchanged
|
||||
{3, 40, 24, 29, 17}, // 180 → bottom-right
|
||||
{6, 24, 40, 17, 10}, // 90° CW → top-right (dims swap)
|
||||
{8, 24, 40, 6, 29}, // 90° CCW → bottom-left (dims swap)
|
||||
{"none", 0, 40, 24, 10, 6}, // no EXIF → unchanged, marker top-left
|
||||
{"normal", 1, 40, 24, 10, 6}, // normal → unchanged
|
||||
{"mirror-h", 2, 40, 24, 29, 6}, // flip horizontal → top-right
|
||||
{"rotate-180", 3, 40, 24, 29, 17}, // → bottom-right
|
||||
{"mirror-v", 4, 40, 24, 10, 17}, // flip vertical → bottom-left
|
||||
{"transpose", 5, 24, 40, 6, 10}, // main diagonal (dims swap)
|
||||
{"rotate-90-cw", 6, 24, 40, 17, 10}, // → top-right (dims swap)
|
||||
{"transverse", 7, 24, 40, 17, 29}, // anti-diagonal (dims swap)
|
||||
{"rotate-90-ccw", 8, 24, 40, 6, 29}, // → bottom-left (dims swap)
|
||||
}
|
||||
for _, tc := range cases {
|
||||
out, format, err := Normalize(bytes.NewReader(orientedJPEG(t, tc.orient)), Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("orient %d: Normalize err = %v", tc.orient, err)
|
||||
}
|
||||
if format != "jpeg" {
|
||||
t.Errorf("orient %d: format = %q, want jpeg", tc.orient, format)
|
||||
}
|
||||
m, _, err := image.Decode(bytes.NewReader(out))
|
||||
if err != nil {
|
||||
t.Fatalf("orient %d: decode output: %v", tc.orient, err)
|
||||
}
|
||||
if m.Bounds().Dx() != tc.wantW || m.Bounds().Dy() != tc.wantH {
|
||||
t.Errorf("orient %d: output %dx%d, want %dx%d",
|
||||
tc.orient, m.Bounds().Dx(), m.Bounds().Dy(), tc.wantW, tc.wantH)
|
||||
}
|
||||
if !bright(m.At(m.Bounds().Min.X+tc.mx, m.Bounds().Min.Y+tc.my)) {
|
||||
t.Errorf("orient %d: white marker not at (%d,%d) — orientation not applied",
|
||||
tc.orient, tc.mx, tc.my)
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
out, format, err := Normalize(bytes.NewReader(orientedJPEG(t, tc.orient)), Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("Normalize err = %v", err)
|
||||
}
|
||||
if format != "jpeg" {
|
||||
t.Errorf("format = %q, want jpeg", format)
|
||||
}
|
||||
m, _, err := image.Decode(bytes.NewReader(out))
|
||||
if err != nil {
|
||||
t.Fatalf("decode output: %v", err)
|
||||
}
|
||||
if m.Bounds().Dx() != tc.wantW || m.Bounds().Dy() != tc.wantH {
|
||||
t.Errorf("output %dx%d, want %dx%d",
|
||||
m.Bounds().Dx(), m.Bounds().Dy(), tc.wantW, tc.wantH)
|
||||
}
|
||||
if !bright(m.At(m.Bounds().Min.X+tc.mx, m.Bounds().Min.Y+tc.my)) {
|
||||
t.Errorf("white marker not at (%d,%d) — orientation not applied", tc.mx, tc.my)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user