forked from lelenanam/downsize
-
Notifications
You must be signed in to change notification settings - Fork 0
/
downsize.go
127 lines (109 loc) · 2.74 KB
/
downsize.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
package downsize
import (
"bytes"
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"sync"
"github.com/nfnt/resize"
)
// Options are the encoding parameters.
type Options struct {
// Size is desired output file size in bytes
Size int
// Format is image format to encode
Format string
// JpegOptions are the options for jpeg format
JpegOptions *jpeg.Options
// GifOptions are the options for gif format
GifOptions *gif.Options
}
// DefaultQuality is default quality to encode image
const DefaultQuality = 80
// defaultFormat is default output format
var defaultFormat = "jpeg"
// defaultJpegOptions is default options to encode jpeg format
var defaultJpegOptions = &jpeg.Options{Quality: DefaultQuality}
// defaultOptions is default options for downsize
var defaultOptions = &Options{Format: defaultFormat, JpegOptions: defaultJpegOptions}
//Accuracy for calculating specified file size Options.Size
//for Options.Size result might be in range [Options.Size - Options.Size*Accuracy; Options.Size]
const Accuracy = 0.05
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func setOptions(o *Options) *Options {
opts := defaultOptions
if o != nil {
opts = o
}
if opts.Format == "" {
opts.Format = defaultFormat
if opts.GifOptions != nil {
opts.Format = "gif"
}
}
if opts.Format == defaultFormat && opts.JpegOptions == nil {
opts.JpegOptions = defaultJpegOptions
}
return opts
}
// Encode changes size of Image img (result size<=o.Size)
// and writes the Image img to w with the given options.
// Default parameters are used if a nil *Options is passed.
func Encode(w io.Writer, img image.Image, o *Options) error {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
opts := setOptions(o)
if err := encode(buf, img, opts); err != nil {
return err
}
originSize := buf.Len()
if opts.Size <= 0 {
opts.Size = originSize
}
if originSize <= opts.Size {
_, err := io.Copy(w, buf)
return err
}
min := 0
max := img.Bounds().Dx()
for min < max {
buf.Reset()
newWidth := (min + max) / 2
newImg := resize.Resize(uint(newWidth), 0, img, resize.Lanczos3)
if err := encode(buf, newImg, opts); err != nil {
return err
}
newSize := buf.Len()
if newSize > opts.Size {
max = newWidth - 1
} else {
newAccur := 1 - float64(newSize)/float64(opts.Size)
if newAccur <= Accuracy {
break
}
min = newWidth + 1
}
}
_, err := io.Copy(w, buf)
return err
}
func encode(w io.Writer, img image.Image, o *Options) error {
switch o.Format {
case "jpeg":
return jpeg.Encode(w, img, o.JpegOptions)
case "png":
return png.Encode(w, img)
case "gif":
return gif.Encode(w, img, o.GifOptions)
default:
return fmt.Errorf("Unknown image format %q", o.Format)
}
}