apps/cellar/image.go 3.5 K raw
1
package main
2
3
import (
4
	"bytes"
5
	"encoding/binary"
6
	"fmt"
7
	"image"
8
	"image/draw"
9
	"image/jpeg"
10
	_ "image/png"
11
)
12
13
func processImage(data []byte) ([]byte, error) {
14
	orientation := readOrientation(data)
15
	img, _, err := image.Decode(bytes.NewReader(data))
16
	if err != nil {
17
		return nil, fmt.Errorf("Failed to decode image: %w", err)
18
	}
19
	img = applyOrientation(img, orientation)
20
	var out bytes.Buffer
21
	if err := jpeg.Encode(&out, img, &jpeg.Options{Quality: 75}); err != nil {
22
		return nil, fmt.Errorf("JPEG encoding failed: %w", err)
23
	}
24
	return out.Bytes(), nil
25
}
26
27
func readOrientation(data []byte) int {
28
	exif := extractExifSegment(data)
29
	if len(exif) < 8 {
30
		return 1
31
	}
32
	var order binary.ByteOrder
33
	switch string(exif[:2]) {
34
	case "II":
35
		order = binary.LittleEndian
36
	case "MM":
37
		order = binary.BigEndian
38
	default:
39
		return 1
40
	}
41
	if order.Uint16(exif[2:4]) != 42 {
42
		return 1
43
	}
44
	ifd0 := int(order.Uint32(exif[4:8]))
45
	if ifd0 < 0 || ifd0+2 > len(exif) {
46
		return 1
47
	}
48
	count := int(order.Uint16(exif[ifd0 : ifd0+2]))
49
	entries := ifd0 + 2
50
	for i := 0; i < count; i++ {
51
		entry := entries + i*12
52
		if entry+12 > len(exif) {
53
			return 1
54
		}
55
		tag := order.Uint16(exif[entry : entry+2])
56
		if tag == 0x0112 { // Orientation
57
			return int(order.Uint16(exif[entry+8 : entry+10]))
58
		}
59
	}
60
	return 1
61
}
62
63
func extractExifSegment(orig []byte) []byte {
64
	const prefix = "Exif\x00\x00"
65
	if len(orig) < 4 || orig[0] != 0xff || orig[1] != 0xd8 {
66
		return nil
67
	}
68
	pos := 2
69
	for pos+4 <= len(orig) {
70
		if orig[pos] != 0xff {
71
			return nil
72
		}
73
		marker := orig[pos+1]
74
		pos += 2
75
		if marker == 0xda || marker == 0xd9 {
76
			return nil
77
		}
78
		if marker >= 0xd0 && marker <= 0xd7 {
79
			continue
80
		}
81
		if pos+2 > len(orig) {
82
			return nil
83
		}
84
		segLen := int(binary.BigEndian.Uint16(orig[pos : pos+2]))
85
		if segLen < 2 || pos+segLen > len(orig) {
86
			return nil
87
		}
88
		payload := orig[pos+2 : pos+segLen]
89
		if marker == 0xe1 && len(payload) >= len(prefix) && string(payload[:len(prefix)]) == prefix {
90
			return payload[len(prefix):]
91
		}
92
		pos += segLen
93
	}
94
	return nil
95
}
96
97
func applyOrientation(src image.Image, orientation int) image.Image {
98
	if orientation <= 1 || orientation > 8 {
99
		return src
100
	}
101
	b := src.Bounds()
102
	w, h := b.Dx(), b.Dy()
103
	rgba := image.NewRGBA(image.Rect(0, 0, w, h))
104
	draw.Draw(rgba, rgba.Bounds(), src, b.Min, draw.Src)
105
106
	var dst *image.RGBA
107
	switch orientation {
108
	case 2: // flip horizontal
109
		dst = image.NewRGBA(image.Rect(0, 0, w, h))
110
		for y := 0; y < h; y++ {
111
			for x := 0; x < w; x++ {
112
				dst.Set(w-1-x, y, rgba.At(x, y))
113
			}
114
		}
115
	case 3: // rotate 180
116
		dst = image.NewRGBA(image.Rect(0, 0, w, h))
117
		for y := 0; y < h; y++ {
118
			for x := 0; x < w; x++ {
119
				dst.Set(w-1-x, h-1-y, rgba.At(x, y))
120
			}
121
		}
122
	case 4: // flip vertical
123
		dst = image.NewRGBA(image.Rect(0, 0, w, h))
124
		for y := 0; y < h; y++ {
125
			for x := 0; x < w; x++ {
126
				dst.Set(x, h-1-y, rgba.At(x, y))
127
			}
128
		}
129
	case 5: // transpose
130
		dst = image.NewRGBA(image.Rect(0, 0, h, w))
131
		for y := 0; y < h; y++ {
132
			for x := 0; x < w; x++ {
133
				dst.Set(y, x, rgba.At(x, y))
134
			}
135
		}
136
	case 6: // rotate 90 CW
137
		dst = image.NewRGBA(image.Rect(0, 0, h, w))
138
		for y := 0; y < h; y++ {
139
			for x := 0; x < w; x++ {
140
				dst.Set(h-1-y, x, rgba.At(x, y))
141
			}
142
		}
143
	case 7: // transverse
144
		dst = image.NewRGBA(image.Rect(0, 0, h, w))
145
		for y := 0; y < h; y++ {
146
			for x := 0; x < w; x++ {
147
				dst.Set(h-1-y, w-1-x, rgba.At(x, y))
148
			}
149
		}
150
	case 8: // rotate 270 CW (90 CCW)
151
		dst = image.NewRGBA(image.Rect(0, 0, h, w))
152
		for y := 0; y < h; y++ {
153
			for x := 0; x < w; x++ {
154
				dst.Set(y, w-1-x, rgba.At(x, y))
155
			}
156
		}
157
	}
158
	return dst
159
}