feat: limit image resize workers

This commit is contained in:
Oleg Lobanov
2020-07-23 02:41:19 +02:00
parent 14e2f84ceb
commit 94ef59602f
7 changed files with 510 additions and 56 deletions

172
img/service.go Normal file
View File

@@ -0,0 +1,172 @@
//go:generate go-enum --sql --marshal --file $GOFILE
package img
import (
"context"
"errors"
"io"
"path/filepath"
"github.com/disintegration/imaging"
"github.com/marusama/semaphore/v2"
"github.com/spf13/afero"
)
// ErrUnsupportedFormat means the given image format is not supported.
var ErrUnsupportedFormat = errors.New("unsupported image format")
// Service
type Service struct {
lowPrioritySem semaphore.Semaphore
highPrioritySem semaphore.Semaphore
}
func New(workers int) *Service {
return &Service{
lowPrioritySem: semaphore.New(workers),
highPrioritySem: semaphore.New(workers),
}
}
// Format is an image file format.
/*
ENUM(
jpeg
png
gif
tiff
bmp
)
*/
type Format int
func (x Format) toImaging() imaging.Format {
switch x {
case FormatJpeg:
return imaging.JPEG
case FormatPng:
return imaging.PNG
case FormatGif:
return imaging.GIF
case FormatTiff:
return imaging.TIFF
case FormatBmp:
return imaging.BMP
default:
return imaging.JPEG
}
}
/*
ENUM(
high
medium
low
)
*/
type Quality int
func (x Quality) resampleFilter() imaging.ResampleFilter {
switch x {
case QualityHigh:
return imaging.Lanczos
case QualityMedium:
return imaging.Box
case QualityLow:
return imaging.NearestNeighbor
default:
return imaging.Linear
}
}
/*
ENUM(
fit
fill
)
*/
type ResizeMode int
func (s *Service) FormatFromExtension(ext string) (Format, error) {
format, err := imaging.FormatFromExtension(ext)
if err != nil {
return -1, ErrUnsupportedFormat
}
switch format {
case imaging.JPEG:
return FormatJpeg, nil
case imaging.PNG:
return FormatPng, nil
case imaging.GIF:
return FormatGif, nil
case imaging.TIFF:
return FormatTiff, nil
case imaging.BMP:
return FormatBmp, nil
}
return -1, ErrUnsupportedFormat
}
type resizeConfig struct {
prioritized bool
resizeMode ResizeMode
quality Quality
}
type Option func(*resizeConfig)
func WithMode(mode ResizeMode) Option {
return func(config *resizeConfig) {
config.resizeMode = mode
}
}
func WithQuality(quality Quality) Option {
return func(config *resizeConfig) {
config.quality = quality
}
}
func WithHighPriority() Option {
return func(config *resizeConfig) {
config.prioritized = true
}
}
func (s *Service) Resize(ctx context.Context, file afero.File, width, height int, out io.Writer, options ...Option) error {
config := resizeConfig{
resizeMode: ResizeModeFit,
quality: QualityMedium,
}
for _, option := range options {
option(&config)
}
sem := s.lowPrioritySem
if config.prioritized {
sem = s.highPrioritySem
}
if err := sem.Acquire(ctx, 1); err != nil {
return err
}
defer sem.Release(1)
format, err := s.FormatFromExtension(filepath.Ext(file.Name()))
if err != nil {
return ErrUnsupportedFormat
}
img, err := imaging.Decode(file, imaging.AutoOrientation(true))
if err != nil {
return err
}
switch config.resizeMode {
case ResizeModeFill:
img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter())
default:
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
}
return imaging.Encode(out, img, format.toImaging())
}

259
img/service_enum.go Normal file
View File

@@ -0,0 +1,259 @@
// Code generated by go-enum
// DO NOT EDIT!
package img
import (
"database/sql/driver"
"fmt"
)
const (
// FormatJpeg is a Format of type Jpeg
FormatJpeg Format = iota
// FormatPng is a Format of type Png
FormatPng
// FormatGif is a Format of type Gif
FormatGif
// FormatTiff is a Format of type Tiff
FormatTiff
// FormatBmp is a Format of type Bmp
FormatBmp
)
const _FormatName = "jpegpnggiftiffbmp"
var _FormatMap = map[Format]string{
0: _FormatName[0:4],
1: _FormatName[4:7],
2: _FormatName[7:10],
3: _FormatName[10:14],
4: _FormatName[14:17],
}
// String implements the Stringer interface.
func (x Format) String() string {
if str, ok := _FormatMap[x]; ok {
return str
}
return fmt.Sprintf("Format(%d)", x)
}
var _FormatValue = map[string]Format{
_FormatName[0:4]: 0,
_FormatName[4:7]: 1,
_FormatName[7:10]: 2,
_FormatName[10:14]: 3,
_FormatName[14:17]: 4,
}
// ParseFormat attempts to convert a string to a Format
func ParseFormat(name string) (Format, error) {
if x, ok := _FormatValue[name]; ok {
return x, nil
}
return Format(0), fmt.Errorf("%s is not a valid Format", name)
}
// MarshalText implements the text marshaller method
func (x Format) MarshalText() ([]byte, error) {
return []byte(x.String()), nil
}
// UnmarshalText implements the text unmarshaller method
func (x *Format) UnmarshalText(text []byte) error {
name := string(text)
tmp, err := ParseFormat(name)
if err != nil {
return err
}
*x = tmp
return nil
}
// Scan implements the Scanner interface.
func (x *Format) Scan(value interface{}) error {
var name string
switch v := value.(type) {
case string:
name = v
case []byte:
name = string(v)
case nil:
*x = Format(0)
return nil
}
tmp, err := ParseFormat(name)
if err != nil {
return err
}
*x = tmp
return nil
}
// Value implements the driver Valuer interface.
func (x Format) Value() (driver.Value, error) {
return x.String(), nil
}
const (
// QualityHigh is a Quality of type High
QualityHigh Quality = iota
// QualityMedium is a Quality of type Medium
QualityMedium
// QualityLow is a Quality of type Low
QualityLow
)
const _QualityName = "highmediumlow"
var _QualityMap = map[Quality]string{
0: _QualityName[0:4],
1: _QualityName[4:10],
2: _QualityName[10:13],
}
// String implements the Stringer interface.
func (x Quality) String() string {
if str, ok := _QualityMap[x]; ok {
return str
}
return fmt.Sprintf("Quality(%d)", x)
}
var _QualityValue = map[string]Quality{
_QualityName[0:4]: 0,
_QualityName[4:10]: 1,
_QualityName[10:13]: 2,
}
// ParseQuality attempts to convert a string to a Quality
func ParseQuality(name string) (Quality, error) {
if x, ok := _QualityValue[name]; ok {
return x, nil
}
return Quality(0), fmt.Errorf("%s is not a valid Quality", name)
}
// MarshalText implements the text marshaller method
func (x Quality) MarshalText() ([]byte, error) {
return []byte(x.String()), nil
}
// UnmarshalText implements the text unmarshaller method
func (x *Quality) UnmarshalText(text []byte) error {
name := string(text)
tmp, err := ParseQuality(name)
if err != nil {
return err
}
*x = tmp
return nil
}
// Scan implements the Scanner interface.
func (x *Quality) Scan(value interface{}) error {
var name string
switch v := value.(type) {
case string:
name = v
case []byte:
name = string(v)
case nil:
*x = Quality(0)
return nil
}
tmp, err := ParseQuality(name)
if err != nil {
return err
}
*x = tmp
return nil
}
// Value implements the driver Valuer interface.
func (x Quality) Value() (driver.Value, error) {
return x.String(), nil
}
const (
// ResizeModeFit is a ResizeMode of type Fit
ResizeModeFit ResizeMode = iota
// ResizeModeFill is a ResizeMode of type Fill
ResizeModeFill
)
const _ResizeModeName = "fitfill"
var _ResizeModeMap = map[ResizeMode]string{
0: _ResizeModeName[0:3],
1: _ResizeModeName[3:7],
}
// String implements the Stringer interface.
func (x ResizeMode) String() string {
if str, ok := _ResizeModeMap[x]; ok {
return str
}
return fmt.Sprintf("ResizeMode(%d)", x)
}
var _ResizeModeValue = map[string]ResizeMode{
_ResizeModeName[0:3]: 0,
_ResizeModeName[3:7]: 1,
}
// ParseResizeMode attempts to convert a string to a ResizeMode
func ParseResizeMode(name string) (ResizeMode, error) {
if x, ok := _ResizeModeValue[name]; ok {
return x, nil
}
return ResizeMode(0), fmt.Errorf("%s is not a valid ResizeMode", name)
}
// MarshalText implements the text marshaller method
func (x ResizeMode) MarshalText() ([]byte, error) {
return []byte(x.String()), nil
}
// UnmarshalText implements the text unmarshaller method
func (x *ResizeMode) UnmarshalText(text []byte) error {
name := string(text)
tmp, err := ParseResizeMode(name)
if err != nil {
return err
}
*x = tmp
return nil
}
// Scan implements the Scanner interface.
func (x *ResizeMode) Scan(value interface{}) error {
var name string
switch v := value.(type) {
case string:
name = v
case []byte:
name = string(v)
case nil:
*x = ResizeMode(0)
return nil
}
tmp, err := ParseResizeMode(name)
if err != nil {
return err
}
*x = tmp
return nil
}
// Value implements the driver Valuer interface.
func (x ResizeMode) Value() (driver.Value, error) {
return x.String(), nil
}