- commit
- 4ae5e13
- parent
- e1aaa8f
- author
- Eric Bower
- date
- 2022-09-01 02:44:17 +0000 UTC
refactor(imgs): move image processsing to shared
2 files changed,
+156,
-137
+3,
-137
1@@ -4,14 +4,8 @@ import (
2 "bytes"
3 "fmt"
4 "html/template"
5- "image"
6- gif "image/gif"
7- jpeg "image/jpeg"
8- png "image/png"
9- "io"
10 "net/http"
11 "net/url"
12- "strconv"
13 "strings"
14 "time"
15
16@@ -21,10 +15,7 @@ import (
17 "git.sr.ht/~erock/pico/db/postgres"
18 "git.sr.ht/~erock/pico/imgs/storage"
19 "git.sr.ht/~erock/pico/shared"
20- "github.com/disintegration/imaging"
21 "github.com/gorilla/feeds"
22- "github.com/kolesa-team/go-webp/encoder"
23- "github.com/kolesa-team/go-webp/webp"
24 "go.uber.org/zap"
25 "golang.org/x/exp/slices"
26 )
27@@ -194,131 +185,6 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
28 }
29 }
30
31-type deviceType int
32-
33-const (
34- desktopDevice deviceType = iota
35-)
36-
37-type ImgOptimizer struct {
38- // Specify the compression factor for RGB channels between 0 and 100. The default is 75.
39- // A small factor produces a smaller file with lower quality.
40- // Best quality is achieved by using a value of 100.
41- Quality float32
42- Optimized bool
43- Width uint
44- Height uint
45- DeviceType deviceType
46- Output []byte
47- Dimes string
48-}
49-
50-func (h *ImgOptimizer) GetImage(r io.Reader, mimeType string) (image.Image, error) {
51- switch mimeType {
52- case "image/png":
53- return png.Decode(r)
54- case "image/jpeg":
55- return jpeg.Decode(r)
56- case "image/jpg":
57- return jpeg.Decode(r)
58- case "image/gif":
59- return gif.Decode(r)
60- }
61-
62- return nil, fmt.Errorf("(%s) not supported optimization", mimeType)
63-}
64-
65-func (h *ImgOptimizer) GetRatio() error {
66- if h.Dimes == "" {
67- return nil
68- }
69-
70- // dimes = x250 -- width is auto scaled and height is 250
71- if strings.HasPrefix(h.Dimes, "x") {
72- height, err := strconv.ParseUint(h.Dimes[1:], 10, 64)
73- if err != nil {
74- return err
75- }
76- h.Height = uint(height)
77- return nil
78- }
79-
80- // dimes = 250x -- width is 250 and height is auto scaled
81- if strings.HasSuffix(h.Dimes, "x") {
82- width, err := strconv.ParseUint(h.Dimes[:len(h.Dimes)-1], 10, 64)
83- if err != nil {
84- return err
85- }
86- h.Width = uint(width)
87- return nil
88- }
89-
90- res := strings.Split(h.Dimes, "x")
91- if len(res) != 2 {
92- return fmt.Errorf("(%s) must be in format (x200, 200x, or 200x200)", h.Dimes)
93- }
94-
95- width, err := strconv.ParseUint(res[0], 10, 64)
96- if err != nil {
97- return err
98- }
99- h.Width = uint(width)
100-
101- height, err := strconv.ParseUint(res[1], 10, 64)
102- if err != nil {
103- return err
104- }
105- h.Height = uint(height)
106-
107- return nil
108-}
109-
110-type SubImager interface {
111- SubImage(r image.Rectangle) image.Image
112-}
113-
114-func (h *ImgOptimizer) Process(contents io.Reader, writer io.Writer, mimeType string) error {
115- if !h.Optimized {
116- _, err := io.Copy(writer, contents)
117- return err
118- }
119-
120- img, err := h.GetImage(contents, mimeType)
121- if err != nil {
122- return err
123- }
124-
125- nextImg := img
126- if h.Height > 0 || h.Width > 0 {
127- nextImg = imaging.Resize(img, int(h.Width), int(h.Height), imaging.MitchellNetravali)
128- }
129-
130- options, err := encoder.NewLossyEncoderOptions(
131- encoder.PresetDefault,
132- h.Quality,
133- )
134- if err != nil {
135- return err
136- }
137-
138- return webp.Encode(writer, nextImg, options)
139-}
140-
141-func NewImgOptimizer(logger *zap.SugaredLogger, optimized bool, dimes string) *ImgOptimizer {
142- opt := &ImgOptimizer{
143- Optimized: optimized,
144- DeviceType: desktopDevice,
145- Quality: 75,
146- Dimes: dimes,
147- }
148-
149- err := opt.GetRatio()
150- if err != nil {
151- logger.Error(err)
152- }
153- return opt
154-}
155-
156 type ImgHandler struct {
157 Username string
158 Subdomain string
159@@ -327,7 +193,7 @@ type ImgHandler struct {
160 Dbpool db.DB
161 Storage storage.ObjectStorage
162 Logger *zap.SugaredLogger
163- Img *ImgOptimizer
164+ Img *shared.ImgOptimizer
165 }
166
167 func imgHandler(w http.ResponseWriter, h *ImgHandler) {
168@@ -405,7 +271,7 @@ func imgRequestOriginal(w http.ResponseWriter, r *http.Request) {
169 Dbpool: dbpool,
170 Storage: st,
171 Logger: logger,
172- Img: NewImgOptimizer(logger, false, ""),
173+ Img: shared.NewImgOptimizer(logger, false, ""),
174 })
175 }
176
177@@ -440,7 +306,7 @@ func imgRequest(w http.ResponseWriter, r *http.Request) {
178 Dbpool: dbpool,
179 Storage: st,
180 Logger: logger,
181- Img: NewImgOptimizer(logger, true, dimes),
182+ Img: shared.NewImgOptimizer(logger, true, dimes),
183 })
184 }
185
1@@ -0,0 +1,153 @@
2+package shared
3+
4+import (
5+ "fmt"
6+ "image"
7+ gif "image/gif"
8+ jpeg "image/jpeg"
9+ png "image/png"
10+ "io"
11+ "strconv"
12+ "strings"
13+
14+ "github.com/disintegration/imaging"
15+ "github.com/kolesa-team/go-webp/encoder"
16+ "github.com/kolesa-team/go-webp/webp"
17+ "go.uber.org/zap"
18+)
19+
20+type deviceType int
21+
22+const (
23+ desktopDevice deviceType = iota
24+)
25+
26+type ImgOptimizer struct {
27+ // Specify the compression factor for RGB channels between 0 and 100. The default is 75.
28+ // A small factor produces a smaller file with lower quality.
29+ // Best quality is achieved by using a value of 100.
30+ Quality float32
31+ Optimized bool
32+ *Ratio
33+ DeviceType deviceType
34+}
35+
36+func (h *ImgOptimizer) GetImage(r io.Reader, mimeType string) (image.Image, error) {
37+ switch mimeType {
38+ case "image/png":
39+ return png.Decode(r)
40+ case "image/jpeg":
41+ return jpeg.Decode(r)
42+ case "image/jpg":
43+ return jpeg.Decode(r)
44+ case "image/gif":
45+ return gif.Decode(r)
46+ }
47+
48+ return nil, fmt.Errorf("(%s) not supported optimization", mimeType)
49+}
50+
51+type Ratio struct {
52+ Width int
53+ Height int
54+}
55+
56+func GetRatio(dimes string) (*Ratio, error) {
57+ if dimes == "" {
58+ return nil, nil
59+ }
60+
61+ // dimes = x250 -- width is auto scaled and height is 250
62+ if strings.HasPrefix(dimes, "x") {
63+ height, err := strconv.Atoi(dimes[1:])
64+ if err != nil {
65+ return nil, err
66+ }
67+ return &Ratio{Width: 0, Height: height}, nil
68+ }
69+
70+ // dimes = 250x -- width is 250 and height is auto scaled
71+ if strings.HasSuffix(dimes, "x") {
72+ width, err := strconv.Atoi(dimes[:len(dimes)-1])
73+ if err != nil {
74+ return nil, err
75+ }
76+ return &Ratio{Width: width, Height: 0}, nil
77+ }
78+
79+ res := strings.Split(dimes, "x")
80+ if len(res) != 2 {
81+ return nil, fmt.Errorf("(%s) must be in format (x200, 200x, or 200x200)", dimes)
82+ }
83+
84+ ratio := &Ratio{}
85+ width, err := strconv.Atoi(res[0])
86+ if err != nil {
87+ return nil, err
88+ }
89+ ratio.Width = width
90+
91+ height, err := strconv.Atoi(res[1])
92+ if err != nil {
93+ return nil, err
94+ }
95+ ratio.Height = height
96+
97+ return ratio, nil
98+}
99+
100+type SubImager interface {
101+ SubImage(r image.Rectangle) image.Image
102+}
103+
104+func (h *ImgOptimizer) Resize(img image.Image) *image.NRGBA {
105+ return imaging.Resize(
106+ img,
107+ h.Width,
108+ h.Height,
109+ imaging.MitchellNetravali,
110+ )
111+}
112+
113+func (h *ImgOptimizer) Process(contents io.Reader, writer io.Writer, mimeType string) error {
114+ if !h.Optimized {
115+ _, err := io.Copy(writer, contents)
116+ return err
117+ }
118+
119+ img, err := h.GetImage(contents, mimeType)
120+ if err != nil {
121+ return err
122+ }
123+
124+ nextImg := img
125+ if h.Height > 0 || h.Width > 0 {
126+ nextImg = h.Resize(img)
127+ }
128+
129+ options, err := encoder.NewLossyEncoderOptions(
130+ encoder.PresetDefault,
131+ h.Quality,
132+ )
133+ if err != nil {
134+ return err
135+ }
136+
137+ return webp.Encode(writer, nextImg, options)
138+}
139+
140+func NewImgOptimizer(logger *zap.SugaredLogger, optimized bool, dimes string) *ImgOptimizer {
141+ opt := &ImgOptimizer{
142+ Optimized: optimized,
143+ DeviceType: desktopDevice,
144+ Quality: 75,
145+ }
146+
147+ ratio, err := GetRatio(dimes)
148+ if err == nil {
149+ opt.Ratio = ratio
150+ } else {
151+ logger.Error(err)
152+ }
153+ return opt
154+}