399 lines
9.0 KiB
Go
399 lines
9.0 KiB
Go
|
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)
|
||
|
})
|
||
|
}
|
||
|
}
|