/
generator.go
120 lines (99 loc) · 2.6 KB
/
generator.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
package thumbnails
import (
"bytes"
"context"
"fmt"
"image"
"image/jpeg"
_ "image/jpeg"
_ "image/png"
"io"
"runtime"
"strings"
"golang.org/x/image/draw"
"golang.org/x/sync/semaphore"
)
type Generator struct {
semaphore *semaphore.Weighted
options GeneratorOptions
}
type GeneratorOptions struct {
MaxParallel int64
Interpolator draw.Interpolator
JPEGQuality int
}
func NewGenerator(options GeneratorOptions) *Generator {
if options.MaxParallel == 0 {
options.MaxParallel = int64(runtime.NumCPU())
}
if options.Interpolator == nil {
options.Interpolator = draw.ApproxBiLinear
}
if options.JPEGQuality == 0 {
options.JPEGQuality = jpeg.DefaultQuality
}
return &Generator{
semaphore: semaphore.NewWeighted(options.MaxParallel),
options: options,
}
}
func (g *Generator) GenerateThumbnail(ctx context.Context, readCloser io.ReadCloser, extension string) (image.Image, error) {
err := g.semaphore.Acquire(ctx, 1)
if err != nil {
return nil, fmt.Errorf("acquire semaphore: %w", err)
}
defer g.semaphore.Release(1)
extension = strings.ToLower(extension)
switch extension {
case ".jpg", ".jpeg", ".png":
break
default:
return nil, fmt.Errorf("extension %s is not supported", extension)
}
img, _, err := image.Decode(readCloser)
if err != nil {
return nil, fmt.Errorf("decode image: %w", err)
}
width, height := 480, 360
origWidth, origHeight := img.Bounds().Size().X, img.Bounds().Size().Y
baseWidth, baseHeight := origWidth, origHeight
if width > baseWidth {
width = baseWidth
width = width - width%4
height = (width * 3) / 4
}
if height > baseHeight {
height = baseHeight
height = height - height%3
width = (height * 4) / 3
}
if baseWidth > baseHeight*4/3 {
baseWidth = (baseHeight * 4) / 3
}
if baseHeight > baseWidth*3/4 {
baseHeight = (baseWidth * 3) / 4
}
resultImg := image.NewRGBA(image.Rect(0, 0, width, height))
offsetX := (origWidth - baseWidth) / 2
offsetY := (origHeight - baseHeight) / 2
g.options.Interpolator.Scale(
resultImg, resultImg.Rect,
img, image.Rect(offsetX, offsetY, offsetX+baseWidth, offsetY+baseHeight),
draw.Src, nil,
)
return resultImg, nil
}
func (g *Generator) GenerateThumbnailJPEG(ctx context.Context, readCloser io.ReadCloser, extension string) ([]byte, error) {
thumbImg, err := g.GenerateThumbnail(ctx, readCloser, extension)
if err != nil {
return nil, fmt.Errorf("generate image: %w", err)
}
var buf bytes.Buffer
err = jpeg.Encode(&buf, thumbImg, &jpeg.Options{
Quality: g.options.JPEGQuality,
})
if err != nil {
return nil, fmt.Errorf("encode to JPEG: %w", err)
}
return buf.Bytes(), nil
}