repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

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
M imgs/api.go
+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 
A shared/img.go
+153, -0
  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+}