chore: add resize tests
parent
aa78e3ab1f
commit
cb8ac5ebf1
2
go.mod
2
go.mod
|
@ -25,11 +25,13 @@ require (
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.6.1
|
github.com/spf13/viper v1.6.1
|
||||||
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
go.etcd.io/bbolt v1.3.3
|
go.etcd.io/bbolt v1.3.3
|
||||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
|
||||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b // indirect
|
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b // indirect
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
||||||
golang.org/x/text v0.3.2 // indirect
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -206,6 +206,8 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
@ -318,4 +320,6 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/spf13/afero"
|
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
"github.com/filebrowser/filebrowser/v2/files"
|
||||||
"github.com/filebrowser/filebrowser/v2/img"
|
"github.com/filebrowser/filebrowser/v2/img"
|
||||||
|
@ -21,7 +20,7 @@ const (
|
||||||
|
|
||||||
type ImgService interface {
|
type ImgService interface {
|
||||||
FormatFromExtension(ext string) (img.Format, error)
|
FormatFromExtension(ext string) (img.Format, error)
|
||||||
Resize(ctx context.Context, file afero.File, width, height int, out io.Writer, options ...img.Option) error
|
Resize(ctx context.Context, in io.Reader, width, height int, out io.Writer, options ...img.Option) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func previewHandler(imgSvc ImgService, enableThumbnails, resizePreview bool) handleFunc {
|
func previewHandler(imgSvc ImgService, enableThumbnails, resizePreview bool) handleFunc {
|
||||||
|
|
|
@ -2,14 +2,15 @@
|
||||||
package img
|
package img
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
"github.com/marusama/semaphore/v2"
|
"github.com/marusama/semaphore/v2"
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnsupportedFormat means the given image format is not supported.
|
// ErrUnsupportedFormat means the given image format is not supported.
|
||||||
|
@ -17,14 +18,12 @@ var ErrUnsupportedFormat = errors.New("unsupported image format")
|
||||||
|
|
||||||
// Service
|
// Service
|
||||||
type Service struct {
|
type Service struct {
|
||||||
lowPrioritySem semaphore.Semaphore
|
sem semaphore.Semaphore
|
||||||
highPrioritySem semaphore.Semaphore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(workers int) *Service {
|
func New(workers int) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
lowPrioritySem: semaphore.New(workers),
|
sem: semaphore.New(workers),
|
||||||
highPrioritySem: semaphore.New(workers),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +74,7 @@ func (x Quality) resampleFilter() imaging.ResampleFilter {
|
||||||
case QualityLow:
|
case QualityLow:
|
||||||
return imaging.NearestNeighbor
|
return imaging.NearestNeighbor
|
||||||
default:
|
default:
|
||||||
return imaging.Linear
|
return imaging.Box
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,13 +107,19 @@ func (s *Service) FormatFromExtension(ext string) (Format, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type resizeConfig struct {
|
type resizeConfig struct {
|
||||||
prioritized bool
|
format Format
|
||||||
resizeMode ResizeMode
|
resizeMode ResizeMode
|
||||||
quality Quality
|
quality Quality
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option func(*resizeConfig)
|
type Option func(*resizeConfig)
|
||||||
|
|
||||||
|
func WithFormat(format Format) Option {
|
||||||
|
return func(config *resizeConfig) {
|
||||||
|
config.format = format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithMode(mode ResizeMode) Option {
|
func WithMode(mode ResizeMode) Option {
|
||||||
return func(config *resizeConfig) {
|
return func(config *resizeConfig) {
|
||||||
config.resizeMode = mode
|
config.resizeMode = mode
|
||||||
|
@ -127,14 +132,19 @@ func WithQuality(quality Quality) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithHighPriority() Option {
|
func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, out io.Writer, options ...Option) error {
|
||||||
return func(config *resizeConfig) {
|
if err := s.sem.Acquire(ctx, 1); err != nil {
|
||||||
config.prioritized = true
|
return err
|
||||||
|
}
|
||||||
|
defer s.sem.Release(1)
|
||||||
|
|
||||||
|
format, wrappedReader, err := s.detectFormat(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Resize(ctx context.Context, file afero.File, width, height int, out io.Writer, options ...Option) error {
|
|
||||||
config := resizeConfig{
|
config := resizeConfig{
|
||||||
|
format: format,
|
||||||
resizeMode: ResizeModeFit,
|
resizeMode: ResizeModeFit,
|
||||||
quality: QualityMedium,
|
quality: QualityMedium,
|
||||||
}
|
}
|
||||||
|
@ -142,21 +152,7 @@ func (s *Service) Resize(ctx context.Context, file afero.File, width, height int
|
||||||
option(&config)
|
option(&config)
|
||||||
}
|
}
|
||||||
|
|
||||||
sem := s.lowPrioritySem
|
img, err := imaging.Decode(wrappedReader, imaging.AutoOrientation(true))
|
||||||
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -168,5 +164,22 @@ func (s *Service) Resize(ctx context.Context, file afero.File, width, height int
|
||||||
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
||||||
}
|
}
|
||||||
|
|
||||||
return imaging.Encode(out, img, format.toImaging())
|
return imaging.Encode(out, img, config.format.toImaging())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) detectFormat(in io.Reader) (Format, io.Reader, error) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
r := io.TeeReader(in, buf)
|
||||||
|
|
||||||
|
_, imgFormat, err := image.DecodeConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("%s: %w", err.Error(), ErrUnsupportedFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
format, err := ParseFormat(imgFormat)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, ErrUnsupportedFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
return format, io.MultiReader(buf, in), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,398 @@
|
||||||
|
package img
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/image/bmp"
|
||||||
|
"golang.org/x/image/tiff"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestService_Resize(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
options []Option
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
source func(t *testing.T) afero.File
|
||||||
|
matcher func(t *testing.T, reader io.Reader)
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"fill upscale": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 50, 20)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"fill downscale": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"fit upscale": {
|
||||||
|
options: []Option{WithMode(ResizeModeFit)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 50, 20)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(50, 20),
|
||||||
|
},
|
||||||
|
"fit downscale": {
|
||||||
|
options: []Option{WithMode(ResizeModeFit)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 75),
|
||||||
|
},
|
||||||
|
"keep original format": {
|
||||||
|
options: []Option{},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayPng(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: formatMatcher(FormatPng),
|
||||||
|
},
|
||||||
|
"convert to jpeg": {
|
||||||
|
options: []Option{WithFormat(FormatJpeg)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: formatMatcher(FormatJpeg),
|
||||||
|
},
|
||||||
|
"convert to png": {
|
||||||
|
options: []Option{WithFormat(FormatPng)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: formatMatcher(FormatPng),
|
||||||
|
},
|
||||||
|
"convert to gif": {
|
||||||
|
options: []Option{WithFormat(FormatGif)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: formatMatcher(FormatGif),
|
||||||
|
},
|
||||||
|
"convert to tiff": {
|
||||||
|
options: []Option{WithFormat(FormatTiff)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: formatMatcher(FormatTiff),
|
||||||
|
},
|
||||||
|
"convert to bmp": {
|
||||||
|
options: []Option{WithFormat(FormatBmp)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: formatMatcher(FormatBmp),
|
||||||
|
},
|
||||||
|
"convert to unknown": {
|
||||||
|
options: []Option{WithFormat(Format(-1))},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: formatMatcher(FormatJpeg),
|
||||||
|
},
|
||||||
|
"resize png": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayPng(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"resize gif": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayGif(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"resize tiff": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayTiff(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"resize bmp": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayBmp(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"resize with high quality": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityHigh)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"resize with medium quality": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityMedium)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"resize with low quality": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"resize with unknown quality": {
|
||||||
|
options: []Option{WithMode(ResizeModeFill), WithQuality(Quality(-1))},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
return newGrayJpeg(t, 200, 150)
|
||||||
|
},
|
||||||
|
matcher: sizeMatcher(100, 100),
|
||||||
|
},
|
||||||
|
"broken file": {
|
||||||
|
options: []Option{WithMode(ResizeModeFit)},
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
source: func(t *testing.T) afero.File {
|
||||||
|
t.Helper()
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file, err := fs.Create("image.jpg")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = file.WriteString("this is not an image")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return file
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
svc := New(1)
|
||||||
|
source := test.source(t)
|
||||||
|
defer source.Close()
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err := svc.Resize(context.Background(), source, test.width, test.height, buf, test.options...)
|
||||||
|
if (err != nil) != test.wantErr {
|
||||||
|
t.Fatalf("GetMarketSpecs() error = %v, wantErr %v", err, test.wantErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
test.matcher(t, buf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sizeMatcher(width, height int) func(t *testing.T, reader io.Reader) {
|
||||||
|
return func(t *testing.T, reader io.Reader) {
|
||||||
|
resizedImg, _, err := image.Decode(reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, width, resizedImg.Bounds().Dx())
|
||||||
|
require.Equal(t, height, resizedImg.Bounds().Dy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMatcher(format Format) func(t *testing.T, reader io.Reader) {
|
||||||
|
return func(t *testing.T, reader io.Reader) {
|
||||||
|
_, decodedFormat, err := image.DecodeConfig(reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, format.String(), decodedFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGrayJpeg(t *testing.T, width, height int) afero.File {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file, err := fs.Create("image.jpg")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||||
|
err = jpeg.Encode(file, img, &jpeg.Options{Quality: 90})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = file.Seek(0, io.SeekStart)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGrayPng(t *testing.T, width, height int) afero.File {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file, err := fs.Create("image.png")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||||
|
err = png.Encode(file, img)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = file.Seek(0, io.SeekStart)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGrayGif(t *testing.T, width, height int) afero.File {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file, err := fs.Create("image.gif")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||||
|
err = gif.Encode(file, img, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = file.Seek(0, io.SeekStart)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGrayTiff(t *testing.T, width, height int) afero.File {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file, err := fs.Create("image.tiff")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||||
|
err = tiff.Encode(file, img, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = file.Seek(0, io.SeekStart)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGrayBmp(t *testing.T, width, height int) afero.File {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file, err := fs.Create("image.bmp")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
img := image.NewGray(image.Rect(0, 0, width, height))
|
||||||
|
err = bmp.Encode(file, img)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = file.Seek(0, io.SeekStart)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestService_FormatFromExtension(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
ext string
|
||||||
|
want Format
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
"jpg": {
|
||||||
|
ext: ".jpg",
|
||||||
|
want: FormatJpeg,
|
||||||
|
},
|
||||||
|
"jpeg": {
|
||||||
|
ext: ".jpeg",
|
||||||
|
want: FormatJpeg,
|
||||||
|
},
|
||||||
|
"png": {
|
||||||
|
ext: ".png",
|
||||||
|
want: FormatPng,
|
||||||
|
},
|
||||||
|
"gif": {
|
||||||
|
ext: ".gif",
|
||||||
|
want: FormatGif,
|
||||||
|
},
|
||||||
|
"tiff": {
|
||||||
|
ext: ".tiff",
|
||||||
|
want: FormatTiff,
|
||||||
|
},
|
||||||
|
"bmp": {
|
||||||
|
ext: ".bmp",
|
||||||
|
want: FormatBmp,
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
ext: ".mov",
|
||||||
|
wantErr: ErrUnsupportedFormat,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
svc := New(1)
|
||||||
|
got, err := svc.FormatFromExtension(test.ext)
|
||||||
|
require.Truef(t, errors.Is(err, test.wantErr), "error = %v, wantErr %v", err, test.wantErr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Equal(t, test.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue