apps/shrink/exif.go 2.3 K raw
1
package main
2
3
import "encoding/binary"
4
5
const exifPrefix = "Exif\x00\x00"
6
7
func extractExif(orig []byte) []byte {
8
	if len(orig) < 4 || orig[0] != 0xff || orig[1] != 0xd8 {
9
		return nil
10
	}
11
	pos := 2
12
	for pos+4 <= len(orig) {
13
		if orig[pos] != 0xff {
14
			return nil
15
		}
16
		marker := orig[pos+1]
17
		pos += 2
18
		if marker == 0xda || marker == 0xd9 { // SOS or EOI
19
			return nil
20
		}
21
		if marker >= 0xd0 && marker <= 0xd7 { // restart markers have no length
22
			continue
23
		}
24
		if pos+2 > len(orig) {
25
			return nil
26
		}
27
		segLen := int(binary.BigEndian.Uint16(orig[pos : pos+2]))
28
		if segLen < 2 || pos+segLen > len(orig) {
29
			return nil
30
		}
31
		payload := orig[pos+2 : pos+segLen]
32
		if marker == 0xe1 && len(payload) >= len(exifPrefix) && string(payload[:len(exifPrefix)]) == exifPrefix {
33
			exif := make([]byte, len(payload)-len(exifPrefix))
34
			copy(exif, payload[len(exifPrefix):])
35
			return exif
36
		}
37
		pos += segLen
38
	}
39
	return nil
40
}
41
42
func stripGPS(exif []byte) []byte {
43
	if len(exif) < 8 {
44
		return exif
45
	}
46
	out := make([]byte, len(exif))
47
	copy(out, exif)
48
49
	var order binary.ByteOrder
50
	switch string(out[:2]) {
51
	case "II":
52
		order = binary.LittleEndian
53
	case "MM":
54
		order = binary.BigEndian
55
	default:
56
		return out
57
	}
58
	if order.Uint16(out[2:4]) != 42 {
59
		return out
60
	}
61
	ifd0 := int(order.Uint32(out[4:8]))
62
	if ifd0 < 0 || ifd0+2 > len(out) {
63
		return out
64
	}
65
	count := int(order.Uint16(out[ifd0 : ifd0+2]))
66
	entries := ifd0 + 2
67
	for i := 0; i < count; i++ {
68
		entry := entries + i*12
69
		if entry+12 > len(out) {
70
			return out
71
		}
72
		tag := order.Uint16(out[entry : entry+2])
73
		if tag == 0x8825 { // GPSInfo IFDPointer
74
			gpsOffset := int(order.Uint32(out[entry+8 : entry+12]))
75
			if gpsOffset >= 0 && gpsOffset+2 <= len(out) {
76
				order.PutUint16(out[gpsOffset:gpsOffset+2], 0)
77
			}
78
			return out
79
		}
80
	}
81
	return out
82
}
83
84
func injectExif(jpegBytes, exif []byte) []byte {
85
	if len(exif) == 0 || len(jpegBytes) < 2 || jpegBytes[0] != 0xff || jpegBytes[1] != 0xd8 {
86
		return jpegBytes
87
	}
88
	payloadLen := len(exifPrefix) + len(exif)
89
	segLen := payloadLen + 2
90
	if segLen > 0xffff {
91
		return jpegBytes
92
	}
93
	out := make([]byte, 0, len(jpegBytes)+segLen+2)
94
	out = append(out, jpegBytes[:2]...)
95
	out = append(out, 0xff, 0xe1, byte(segLen>>8), byte(segLen))
96
	out = append(out, exifPrefix...)
97
	out = append(out, exif...)
98
	out = append(out, jpegBytes[2:]...)
99
	return out
100
}