commit db524fb

delthas  ·  2024-07-29 13:10:29 +0000 UTC
parent 34095f1
Add image EXIF rotation support

Forgot to git add :-)
1 files changed,  +153, -0
+153, -0
  1@@ -0,0 +1,153 @@
  2+package ui
  3+
  4+import (
  5+	"bytes"
  6+	"encoding/binary"
  7+	"image"
  8+	"io"
  9+
 10+	"github.com/disintegration/imaging"
 11+)
 12+
 13+func exifOrientation(r io.Reader) int {
 14+	// Inspired by: https://github.com/disintegration/imageorient
 15+
 16+	const (
 17+		markerSOI      = 0xffd8
 18+		markerAPP1     = 0xffe1
 19+		exifHeader     = 0x45786966
 20+		byteOrderBE    = 0x4d4d
 21+		byteOrderLE    = 0x4949
 22+		orientationTag = 0x0112
 23+	)
 24+
 25+	// Check if JPEG SOI marker is present.
 26+	var soi uint16
 27+	if err := binary.Read(r, binary.BigEndian, &soi); err != nil {
 28+		return 0
 29+	}
 30+	if soi != markerSOI {
 31+		return 0 // Missing JPEG SOI marker.
 32+	}
 33+
 34+	// Find JPEG APP1 marker.
 35+	for {
 36+		var marker, size uint16
 37+		if err := binary.Read(r, binary.BigEndian, &marker); err != nil {
 38+			return 0
 39+		}
 40+		if err := binary.Read(r, binary.BigEndian, &size); err != nil {
 41+			return 0
 42+		}
 43+		if marker>>8 != 0xff {
 44+			return 0 // Invalid JPEG marker.
 45+		}
 46+		if marker == markerAPP1 {
 47+			break
 48+		}
 49+		if size < 2 {
 50+			return 0 // Invalid block size.
 51+		}
 52+		if _, err := io.CopyN(io.Discard, r, int64(size-2)); err != nil {
 53+			return 0
 54+		}
 55+	}
 56+
 57+	// Check if EXIF header is present.
 58+	var header uint32
 59+	if err := binary.Read(r, binary.BigEndian, &header); err != nil {
 60+		return 0
 61+	}
 62+	if header != exifHeader {
 63+		return 0
 64+	}
 65+	if _, err := io.CopyN(io.Discard, r, 2); err != nil {
 66+		return 0
 67+	}
 68+
 69+	// Read byte order information.
 70+	var (
 71+		byteOrderTag uint16
 72+		byteOrder    binary.ByteOrder
 73+	)
 74+	if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil {
 75+		return 0
 76+	}
 77+	switch byteOrderTag {
 78+	case byteOrderBE:
 79+		byteOrder = binary.BigEndian
 80+	case byteOrderLE:
 81+		byteOrder = binary.LittleEndian
 82+	default:
 83+		return 0 // Invalid byte order flag.
 84+	}
 85+	if _, err := io.CopyN(io.Discard, r, 2); err != nil {
 86+		return 0
 87+	}
 88+
 89+	// Skip the EXIF offset.
 90+	var offset uint32
 91+	if err := binary.Read(r, byteOrder, &offset); err != nil {
 92+		return 0
 93+	}
 94+	if offset < 8 {
 95+		return 0 // Invalid offset value.
 96+	}
 97+	if _, err := io.CopyN(io.Discard, r, int64(offset-8)); err != nil {
 98+		return 0
 99+	}
100+
101+	// Read the number of tags.
102+	var numTags uint16
103+	if err := binary.Read(r, byteOrder, &numTags); err != nil {
104+		return 0
105+	}
106+
107+	// Find the orientation tag.
108+	for i := 0; i < int(numTags); i++ {
109+		var tag uint16
110+		if err := binary.Read(r, byteOrder, &tag); err != nil {
111+			return 0
112+		}
113+		if tag != orientationTag {
114+			if _, err := io.CopyN(io.Discard, r, 10); err != nil {
115+				return 0
116+			}
117+			continue
118+		}
119+		if _, err := io.CopyN(io.Discard, r, 6); err != nil {
120+			return 0
121+		}
122+		var val uint16
123+		if err := binary.Read(r, byteOrder, &val); err != nil {
124+			return 0
125+		}
126+		if val < 1 || val > 8 {
127+			return 0 // Invalid tag value.
128+		}
129+		return int(val)
130+	}
131+	return 0 // Missing orientation tag.
132+}
133+
134+func DecodeImage(r io.Reader) (image.Image, string, error) {
135+	var b bytes.Buffer
136+	tr := io.TeeReader(io.LimitReader(r, 1<<20), &b)
137+	o := exifOrientation(tr)
138+	r = io.MultiReader(&b, r)
139+
140+	img, format, err := image.Decode(r)
141+	if err != nil {
142+		return img, format, err
143+	}
144+
145+	switch o {
146+	case 3:
147+		img = imaging.Rotate180(img)
148+	case 6:
149+		img = imaging.Rotate270(img)
150+	case 8:
151+		img = imaging.Rotate90(img)
152+	}
153+	return img, format, nil
154+}