-
Notifications
You must be signed in to change notification settings - Fork 242
/
encode.go
134 lines (109 loc) · 3.19 KB
/
encode.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package images
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"image"
"image/jpeg"
"io"
"regexp"
"strings"
"github.com/nfnt/resize"
)
type EncodeConfig struct {
Quality int
}
func Encode(w io.Writer, img image.Image, config EncodeConfig) error {
// Currently a wrapper for renderJpeg, but this function is useful if multiple render formats are needed
return renderJpeg(w, img, config)
}
func renderJpeg(w io.Writer, m image.Image, config EncodeConfig) error {
o := new(jpeg.Options)
o.Quality = config.Quality
return jpeg.Encode(w, m, o)
}
type FileSizeError struct {
expected int
received int
}
func (e *FileSizeError) Error() string {
return fmt.Sprintf("image size after processing exceeds max, expected < '%d', received < '%d'", e.expected, e.received)
}
func EncodeToLimits(bb *bytes.Buffer, img image.Image, bounds FileSizeLimits) error {
q := MaxJpegQuality
for q > MinJpegQuality-1 {
err := Encode(bb, img, EncodeConfig{Quality: q})
if err != nil {
return err
}
if bounds.Ideal > bb.Len() {
return nil
}
if q == MinJpegQuality {
if bounds.Max > bb.Len() {
return nil
}
return &FileSizeError{expected: bounds.Max, received: bb.Len()}
}
bb.Reset()
q -= 2
}
return nil
}
// CompressToFileLimits takes an image.Image and analyses the pixel dimensions, if the longest side is greater
// than the `longSideMax` image.Image will be resized, before compression begins.
// Next the image.Image is repeatedly encoded and resized until the data fits within
// the given FileSizeLimits. There is no limit on the number of times the cycle is performed, the image.Image
// is reduced to 95% of its size at the end of every round the file size exceeds the given limits.
func CompressToFileLimits(bb *bytes.Buffer, img image.Image, bounds FileSizeLimits) error {
longSideMax := 2000
// Do we need to do a pre-compression resize?
if img.Bounds().Max.X > img.Bounds().Max.Y {
// X is longer
if img.Bounds().Max.X > longSideMax {
img = resize.Resize(uint(longSideMax), 0, img, resize.Bilinear)
}
} else {
// Y is longer or equal
if img.Bounds().Max.Y > longSideMax {
img = resize.Resize(0, uint(longSideMax), img, resize.Bilinear)
}
}
for {
err := EncodeToLimits(bb, img, bounds)
if err == nil {
return nil
}
// If error is not a FileSizeError then we need to return it up
if fse := (*FileSizeError)(nil); !errors.As(err, &fse) {
return err
}
img = ResizeTo(95, img)
}
}
func EncodeToBestSize(bb *bytes.Buffer, img image.Image, size ResizeDimension) error {
return EncodeToLimits(bb, img, DimensionSizeLimit[size])
}
func GetPayloadDataURI(payload []byte) (string, error) {
if len(payload) == 0 {
return "", nil
}
mt, err := GetMimeType(payload)
if err != nil {
return "", err
}
b64 := base64.StdEncoding.EncodeToString(payload)
return "data:image/" + mt + ";base64," + b64, nil
}
func GetPayloadFromURI(uri string) ([]byte, error) {
re := regexp.MustCompile("^data:image/(.*?);base64,(.*?)$")
res := re.FindStringSubmatch(uri)
if len(res) != 3 {
return nil, errors.New("wrong uri format")
}
return base64.StdEncoding.DecodeString(res[2])
}
func IsPayloadDataURI(uri string) bool {
return strings.HasPrefix(uri, "data:image")
}