diff --git a/server/channels/app/emoji.go b/server/channels/app/emoji.go index 3ddb566eb0..ab20ee2559 100644 --- a/server/channels/app/emoji.go +++ b/server/channels/app/emoji.go @@ -18,7 +18,7 @@ import ( "net/http" "path" - "github.com/disintegration/imaging" + "github.com/mattermost/mattermost/server/v8/channels/app/imaging" _ "golang.org/x/image/webp" "github.com/mattermost/mattermost/server/public/model" @@ -337,7 +337,7 @@ func resizeEmoji(img image.Image, width int, height int) image.Image { if emojiHeight <= MaxEmojiHeight && emojiWidth <= MaxEmojiWidth { return img } - return imaging.Fit(img, MaxEmojiWidth, MaxEmojiHeight, imaging.Lanczos) + return imaging.Fit(img, MaxEmojiWidth, MaxEmojiHeight) } func imageToPaletted(img image.Image) *image.Paletted { diff --git a/server/channels/app/imaging/orientation.go b/server/channels/app/imaging/orientation.go index 50cf5a1b41..267f80bcce 100644 --- a/server/channels/app/imaging/orientation.go +++ b/server/channels/app/imaging/orientation.go @@ -8,7 +8,7 @@ import ( "image" "io" - "github.com/disintegration/imaging" + "github.com/anthonynsimon/bild/transform" "github.com/rwcarlsen/goexif/exif" ) @@ -37,19 +37,19 @@ const ( func MakeImageUpright(img image.Image, orientation int) image.Image { switch orientation { case UprightMirrored: - return imaging.FlipH(img) + return transform.FlipH(img) case UpsideDown: - return imaging.Rotate180(img) + return transform.Rotate(img, 180, &transform.RotationOptions{ResizeBounds: true}) case UpsideDownMirrored: - return imaging.FlipV(img) + return transform.FlipV(img) case RotatedCWMirrored: - return imaging.Transpose(img) + return transform.Rotate(transform.FlipH(img), -90, &transform.RotationOptions{ResizeBounds: true}) case RotatedCCW: - return imaging.Rotate270(img) + return transform.Rotate(img, 90, &transform.RotationOptions{ResizeBounds: true}) case RotatedCCWMirrored: - return imaging.Transverse(img) + return transform.Rotate(transform.FlipV(img), -90, &transform.RotationOptions{ResizeBounds: true}) case RotatedCW: - return imaging.Rotate90(img) + return transform.Rotate(img, 270, &transform.RotationOptions{ResizeBounds: true}) default: return img } diff --git a/server/channels/app/imaging/preview.go b/server/channels/app/imaging/preview.go index fd61d06bcd..f4415f0e51 100644 --- a/server/channels/app/imaging/preview.go +++ b/server/channels/app/imaging/preview.go @@ -9,7 +9,7 @@ import ( "image" "image/jpeg" - "github.com/disintegration/imaging" + "github.com/anthonynsimon/bild/transform" ) // GeneratePreview generates the preview for the given image. @@ -18,7 +18,7 @@ func GeneratePreview(img image.Image, width int) image.Image { w := img.Bounds().Dx() if w > width { - preview = imaging.Resize(img, width, 0, imaging.Lanczos) + preview = Resize(img, width, 0, transform.Lanczos) } return preview @@ -31,16 +31,16 @@ func GenerateThumbnail(img image.Image, targetWidth, targetHeight int) image.Ima // We keep aspect ratio and ensure the output dimensions are never higher than the provided targets. if width > height { - return imaging.Resize(img, targetWidth, 0, imaging.Lanczos) + return Resize(img, targetWidth, 0, transform.Lanczos) } - return imaging.Resize(img, 0, targetHeight, imaging.Lanczos) + return Resize(img, 0, targetHeight, transform.Lanczos) } // GenerateMiniPreviewImage generates the mini preview for the given image. func GenerateMiniPreviewImage(img image.Image, w, h, q int) ([]byte, error) { var buf bytes.Buffer - preview := imaging.Resize(img, w, h, imaging.Lanczos) + preview := Resize(img, w, h, transform.Lanczos) if err := jpeg.Encode(&buf, preview, &jpeg.Options{Quality: q}); err != nil { return nil, fmt.Errorf("failed to encode image to JPEG format: %w", err) } diff --git a/server/channels/app/imaging/preview_test.go b/server/channels/app/imaging/preview_test.go index 68de50223a..f3aced826e 100644 --- a/server/channels/app/imaging/preview_test.go +++ b/server/channels/app/imaging/preview_test.go @@ -5,8 +5,10 @@ package imaging import ( "image" + "image/color" "testing" + "github.com/anthonynsimon/bild/transform" "github.com/stretchr/testify/require" ) @@ -75,3 +77,96 @@ func TestGenerateThumbnail(t *testing.T) { }) } } + +func createTestImage(t *testing.T, width, height int) image.Image { + t.Helper() + img := image.NewNRGBA(image.Rect(0, 0, width, height)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + img.Set(x, y, color.NRGBA{uint8(x % 256), uint8(y % 256), 0, 255}) + } + } + return img +} + +func TestResize(t *testing.T) { + for _, tc := range []struct { + name string + img image.Image + targetW int + targetH int + expectedW int + expectedH int + }{ + { + name: "zero target dimensions", + img: createTestImage(t, 100, 50), + targetW: 0, + targetH: 0, + expectedW: 0, + expectedH: 0, + }, + { + name: "negative target dimensions", + img: createTestImage(t, 100, 50), + targetW: -1, + targetH: 25, + expectedW: 0, + expectedH: 0, + }, + { + name: "zero source dimensions", + img: createTestImage(t, 0, 0), + targetW: 50, + targetH: 25, + expectedW: 0, + expectedH: 0, + }, + { + name: "preserve aspect ratio with width", + img: createTestImage(t, 100, 50), + targetW: 50, + targetH: 0, + expectedW: 50, + expectedH: 25, + }, + { + name: "preserve aspect ratio with width, height > width", + img: createTestImage(t, 50, 100), + targetW: 50, + targetH: 0, + expectedW: 50, + expectedH: 100, + }, + { + name: "preserve aspect ratio with height", + img: createTestImage(t, 100, 50), + targetW: 0, + targetH: 25, + expectedW: 50, + expectedH: 25, + }, + { + name: "preserve aspect ratio with height, height > width", + img: createTestImage(t, 50, 100), + targetW: 0, + targetH: 25, + expectedW: 13, + expectedH: 25, + }, + { + name: "valid target dimensions", + img: createTestImage(t, 100, 50), + targetW: 50, + targetH: 25, + expectedW: 50, + expectedH: 25, + }, + } { + t.Run(tc.name, func(t *testing.T) { + resizedImg := Resize(tc.img, tc.targetW, tc.targetH, transform.Lanczos) + require.Equal(t, tc.expectedW, resizedImg.Bounds().Dx()) + require.Equal(t, tc.expectedH, resizedImg.Bounds().Dy()) + }) + } +} diff --git a/server/channels/app/imaging/utils.go b/server/channels/app/imaging/utils.go index d119b07676..0c2eb8bd22 100644 --- a/server/channels/app/imaging/utils.go +++ b/server/channels/app/imaging/utils.go @@ -6,8 +6,10 @@ package imaging import ( "image" "image/color" + "math" - "github.com/disintegration/imaging" + "github.com/anthonynsimon/bild/clone" + "github.com/anthonynsimon/bild/transform" ) type rawImg interface { @@ -140,8 +142,126 @@ func FillImageTransparency(img image.Image, c color.Color) { } } +// CropAnchor cuts out a rectangular region with the specified size +// from the image using the specified anchor point and returns the cropped image. +// Adapted from github.com/disintegration/imaging +func CropCenter(img image.Image, w, h int) image.Image { + srcBounds := img.Bounds() + anchorPoint := image.Pt(srcBounds.Min.X+(srcBounds.Dx()-w)/2, srcBounds.Min.Y+(srcBounds.Dy()-h)/2) + r := image.Rect(0, 0, w, h).Add(anchorPoint) + b := srcBounds.Intersect(r) + return transform.Crop(img, b) +} + +// resizeAndCrop resizes the image to the smallest possible size that will cover the specified dimensions, +// crops the resized image to the specified dimensions using a centered anchor point and returns +// the transformed image. +// Adapted from github.com/disintegration/imaging +func resizeAndCropCenter(img image.Image, width, height int) image.Image { + dstW, dstH := width, height + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + srcAspectRatio := float64(srcW) / float64(srcH) + dstAspectRatio := float64(dstW) / float64(dstH) + + var tmp image.Image + if srcAspectRatio < dstAspectRatio { + tmp = Resize(img, dstW, 0, transform.Lanczos) + } else { + tmp = Resize(img, 0, dstH, transform.Lanczos) + } + + return CropCenter(tmp, dstW, dstH) +} + // FillCenter creates an image with the specified dimensions and fills it with // the centered and scaled source image. -func FillCenter(img image.Image, w, h int) *image.NRGBA { - return imaging.Fill(img, w, h, imaging.Center, imaging.Lanczos) +// To achieve the correct aspect ratio without stretching, the source image will be cropped. +// Adapted from github.com/disintegration/imaging +func FillCenter(img image.Image, dstW, dstH int) image.Image { + if dstW <= 0 || dstH <= 0 { + return &image.RGBA{} + } + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + + if srcW <= 0 || srcH <= 0 { + return &image.RGBA{} + } + + if srcW == dstW && srcH == dstH { + return clone.AsShallowRGBA(img) + } + + return resizeAndCropCenter(img, dstW, dstH) +} + +// Fit scales down the image to fit the specified +// maximum width and height and returns the transformed image. +// Adapted from github.com/disintegration/imaging +func Fit(img image.Image, maxW, maxH int) image.Image { + if maxW <= 0 || maxH <= 0 { + return &image.NRGBA{} + } + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + + if srcW <= 0 || srcH <= 0 { + return &image.RGBA{} + } + + if srcW <= maxW && srcH <= maxH { + return clone.AsShallowRGBA(img) + } + + srcAspectRatio := float64(srcW) / float64(srcH) + maxAspectRatio := float64(maxW) / float64(maxH) + + var newW, newH int + if srcAspectRatio > maxAspectRatio { + newW = maxW + newH = int(float64(newW) / srcAspectRatio) + } else { + newH = maxH + newW = int(float64(newH) * srcAspectRatio) + } + + return Resize(img, newW, newH, transform.Lanczos) +} + +// Resize resizes the image to the specified width and height using the specified resampling filter and returns the transformed image. +// If one of width or height is 0, the image aspect ratio is preserved. +// Adapted from github.com/disintegration/imaging +func Resize(img image.Image, targetWidth, targetHeight int, filter transform.ResampleFilter) image.Image { + if targetWidth < 0 || targetHeight < 0 { + return &image.NRGBA{} + } + + if targetWidth == 0 && targetHeight == 0 { + return &image.NRGBA{} + } + + srcW := img.Bounds().Dx() + srcH := img.Bounds().Dy() + if srcW <= 0 || srcH <= 0 { + return &image.NRGBA{} + } + + // If new width or height is 0 then preserve aspect ratio, minimum 1px. + if targetWidth == 0 { + tmpW := float64(targetHeight) * float64(srcW) / float64(srcH) + targetWidth = int(math.Max(1.0, math.Floor(tmpW+0.5))) + } + if targetHeight == 0 { + tmpH := float64(targetWidth) * float64(srcH) / float64(srcW) + targetHeight = int(math.Max(1.0, math.Floor(tmpH+0.5))) + } + + return transform.Resize(img, targetWidth, targetHeight, filter) } diff --git a/server/channels/app/imaging/utils_test.go b/server/channels/app/imaging/utils_test.go index 4bcea083f0..bf04b2f4e0 100644 --- a/server/channels/app/imaging/utils_test.go +++ b/server/channels/app/imaging/utils_test.go @@ -5,6 +5,7 @@ package imaging import ( "bytes" + "image" "image/color" "os" "testing" @@ -115,3 +116,234 @@ func TestFillImageTransparency(t *testing.T) { require.Equal(t, expectedImg, inputImg) }) } + +func TestCropCenter(t *testing.T) { + imgDir, ok := fileutils.FindDir("tests") + require.True(t, ok) + + d, err := NewDecoder(DecoderOptions{}) + require.NotNil(t, d) + require.NoError(t, err) + + for _, tc := range []struct { + name string + inputName string + outputName string + width int + height int + }{ + { + "Crop to center 100x100", + "crop_test_input.png", + "crop_test_output_100x100.png", + 100, + 100, + }, + { + "Crop to center 45x45", + "crop_test_input.png", + "crop_test_output_45x45.png", + 45, + 45, + }, + { + "Crop to center 100x45", + "crop_test_input.png", + "crop_test_output_100x45.png", + 100, + 45, + }, + { + "Crop to center 45x100", + "crop_test_input.png", + "crop_test_output_45x100.png", + 45, + 100, + }, + } { + t.Run(tc.name, func(t *testing.T) { + inputFile, err := os.Open(imgDir + "/" + tc.inputName) + require.NoError(t, err) + require.NotNil(t, inputFile) + defer func() { + require.NoError(t, inputFile.Close()) + }() + + inputImg, format, err := d.Decode(inputFile) + require.NoError(t, err) + require.NotNil(t, inputImg) + require.Equal(t, "png", format) + + expectedFile, err := os.Open(imgDir + "/" + tc.outputName) + require.NoError(t, err) + require.NotNil(t, expectedFile) + defer func() { + require.NoError(t, expectedFile.Close()) + }() + + expectedImg, format, err := d.Decode(expectedFile) + require.NoError(t, err) + require.NotNil(t, expectedImg) + require.Equal(t, "png", format) + + croppedImg := CropCenter(inputImg, tc.width, tc.height) + require.Equal(t, expectedImg.Bounds().Dx(), croppedImg.Bounds().Dx()) + require.Equal(t, expectedImg.Bounds().Dy(), croppedImg.Bounds().Dy()) + require.Equal(t, expectedImg.(*image.RGBA).Pix, croppedImg.(*image.RGBA).Pix) + }) + } +} + +func TestFit(t *testing.T) { + imgDir, ok := fileutils.FindDir("tests") + require.True(t, ok) + + d, err := NewDecoder(DecoderOptions{}) + require.NotNil(t, d) + require.NoError(t, err) + + for _, tc := range []struct { + name string + inputName string + outputName string + width int + height int + }{ + { + "Fit to 100x100", + "fit_test_input.png", + "fit_test_output_100x100.png", + 100, + 100, + }, + { + "Fit to 45x45", + "fit_test_input.png", + "fit_test_output_45x45.png", + 45, + 45, + }, + { + "Fit to 100x45", + "fit_test_input.png", + "fit_test_output_100x45.png", + 100, + 45, + }, + { + "Fit to 45x100", + "fit_test_input.png", + "fit_test_output_45x100.png", + 45, + 100, + }, + } { + t.Run(tc.name, func(t *testing.T) { + inputFile, err := os.Open(imgDir + "/" + tc.inputName) + require.NoError(t, err) + require.NotNil(t, inputFile) + defer func() { + require.NoError(t, inputFile.Close()) + }() + + inputImg, format, err := d.Decode(inputFile) + require.NoError(t, err) + require.NotNil(t, inputImg) + require.Equal(t, "png", format) + + expectedFile, err := os.Open(imgDir + "/" + tc.outputName) + require.NoError(t, err) + require.NotNil(t, expectedFile) + defer func() { + require.NoError(t, expectedFile.Close()) + }() + + expectedImg, format, err := d.Decode(expectedFile) + require.NoError(t, err) + require.NotNil(t, expectedImg) + require.Equal(t, "png", format) + + fittedImg := Fit(inputImg, tc.width, tc.height) + require.Equal(t, expectedImg, fittedImg) + }) + } +} + +func TestFillCenter(t *testing.T) { + imgDir, ok := fileutils.FindDir("tests") + require.True(t, ok) + + d, err := NewDecoder(DecoderOptions{}) + require.NotNil(t, d) + require.NoError(t, err) + + tcs := []struct { + name string + inputName string + outputName string + width int + height int + }{ + { + "Fill center 100x100", + "fill_test_input.png", + "fill_test_output_100x100.png", + 100, + 100, + }, + { + "Fill center 45x45", + "fill_test_input.png", + "fill_test_output_45x45.png", + 45, + 45, + }, + { + "Fill center 100x45", + "fill_test_input.png", + "fill_test_output_100x45.png", + 100, + 45, + }, + { + "Fill center 45x100", + "fill_test_input.png", + "fill_test_output_45x100.png", + 45, + 100, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + inputFile, err := os.Open(imgDir + "/" + tc.inputName) + require.NoError(t, err) + require.NotNil(t, inputFile) + defer func() { + require.NoError(t, inputFile.Close()) + }() + + inputImg, format, err := d.Decode(inputFile) + require.NoError(t, err) + require.NotNil(t, inputImg) + require.Equal(t, "png", format) + + expectedFile, err := os.Open(imgDir + "/" + tc.outputName) + require.NoError(t, err) + require.NotNil(t, expectedFile) + defer func() { + require.NoError(t, expectedFile.Close()) + }() + + expectedImg, format, err := d.Decode(expectedFile) + require.NoError(t, err) + require.NotNil(t, expectedImg) + require.Equal(t, "png", format) + + filledImg := FillCenter(inputImg, tc.width, tc.height) + require.Equal(t, expectedImg.Bounds().Dx(), filledImg.Bounds().Dx()) + require.Equal(t, expectedImg.Bounds().Dy(), filledImg.Bounds().Dy()) + require.Equal(t, expectedImg.(*image.RGBA).Pix, filledImg.(*image.RGBA).Pix) + }) + } +} diff --git a/server/go.mod b/server/go.mod index 438e1a6c27..ec16cae739 100644 --- a/server/go.mod +++ b/server/go.mod @@ -7,13 +7,13 @@ toolchain go1.22.6 require ( code.sajari.com/docconv/v2 v2.0.0-pre.4 github.com/Masterminds/semver/v3 v3.2.1 + github.com/anthonynsimon/bild v0.14.0 github.com/avct/uasurfer v0.0.0-20240501094946-ca0c4d1e541b github.com/aws/aws-sdk-go v1.55.5 github.com/blang/semver/v4 v4.0.0 github.com/blevesearch/bleve/v2 v2.4.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3 - github.com/disintegration/imaging v1.6.2 github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a github.com/elastic/go-elasticsearch/v8 v8.14.0 github.com/fatih/color v1.17.0 diff --git a/server/go.sum b/server/go.sum index b91c42ce7a..2f5bc45664 100644 --- a/server/go.sum +++ b/server/go.sum @@ -35,6 +35,8 @@ github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9Pq github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anthonynsimon/bild v0.14.0 h1:IFRkmKdNdqmexXHfEU7rPlAmdUZ8BDZEGtGHDnGWync= +github.com/anthonynsimon/bild v0.14.0/go.mod h1:hcvEAyBjTW69qkKJTfpcDQ83sSZHxwOunsseDfeQhUs= github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= @@ -125,8 +127,6 @@ github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3 h1:AqeKSZIG/NIC7 github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFauPHz7+NnjR/yHJGhrKo1Za+zStgwUETx3yzqgY= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= -github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= @@ -670,7 +670,6 @@ golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/server/tests/crop_test_input.png b/server/tests/crop_test_input.png new file mode 100644 index 0000000000..04f885b319 Binary files /dev/null and b/server/tests/crop_test_input.png differ diff --git a/server/tests/crop_test_output_100x100.png b/server/tests/crop_test_output_100x100.png new file mode 100644 index 0000000000..a01245624e Binary files /dev/null and b/server/tests/crop_test_output_100x100.png differ diff --git a/server/tests/crop_test_output_100x45.png b/server/tests/crop_test_output_100x45.png new file mode 100644 index 0000000000..34ef2eb990 Binary files /dev/null and b/server/tests/crop_test_output_100x45.png differ diff --git a/server/tests/crop_test_output_45x100.png b/server/tests/crop_test_output_45x100.png new file mode 100644 index 0000000000..d6c58d428e Binary files /dev/null and b/server/tests/crop_test_output_45x100.png differ diff --git a/server/tests/crop_test_output_45x45.png b/server/tests/crop_test_output_45x45.png new file mode 100644 index 0000000000..e33bbba2fe Binary files /dev/null and b/server/tests/crop_test_output_45x45.png differ diff --git a/server/tests/fill_test_input.png b/server/tests/fill_test_input.png new file mode 100644 index 0000000000..b93dc4ad2d Binary files /dev/null and b/server/tests/fill_test_input.png differ diff --git a/server/tests/fill_test_output_100x100.png b/server/tests/fill_test_output_100x100.png new file mode 100644 index 0000000000..514e639a27 Binary files /dev/null and b/server/tests/fill_test_output_100x100.png differ diff --git a/server/tests/fill_test_output_100x45.png b/server/tests/fill_test_output_100x45.png new file mode 100644 index 0000000000..b0ed90495e Binary files /dev/null and b/server/tests/fill_test_output_100x45.png differ diff --git a/server/tests/fill_test_output_45x100.png b/server/tests/fill_test_output_45x100.png new file mode 100644 index 0000000000..9e8c37b5a9 Binary files /dev/null and b/server/tests/fill_test_output_45x100.png differ diff --git a/server/tests/fill_test_output_45x45.png b/server/tests/fill_test_output_45x45.png new file mode 100644 index 0000000000..696d8b0a5a Binary files /dev/null and b/server/tests/fill_test_output_45x45.png differ diff --git a/server/tests/fit_test_input.png b/server/tests/fit_test_input.png new file mode 100644 index 0000000000..50b76a159b Binary files /dev/null and b/server/tests/fit_test_input.png differ diff --git a/server/tests/fit_test_output_100x100.png b/server/tests/fit_test_output_100x100.png new file mode 100644 index 0000000000..4ffee7a51f Binary files /dev/null and b/server/tests/fit_test_output_100x100.png differ diff --git a/server/tests/fit_test_output_100x45.png b/server/tests/fit_test_output_100x45.png new file mode 100644 index 0000000000..437ab0a4ce Binary files /dev/null and b/server/tests/fit_test_output_100x45.png differ diff --git a/server/tests/fit_test_output_45x100.png b/server/tests/fit_test_output_45x100.png new file mode 100644 index 0000000000..3e3521acb0 Binary files /dev/null and b/server/tests/fit_test_output_45x100.png differ diff --git a/server/tests/fit_test_output_45x45.png b/server/tests/fit_test_output_45x45.png new file mode 100644 index 0000000000..3e3521acb0 Binary files /dev/null and b/server/tests/fit_test_output_45x45.png differ diff --git a/server/tests/orientation_test_2_expected_preview.jpeg b/server/tests/orientation_test_2_expected_preview.jpeg index 262510eb82..769e4e1930 100644 Binary files a/server/tests/orientation_test_2_expected_preview.jpeg and b/server/tests/orientation_test_2_expected_preview.jpeg differ diff --git a/server/tests/orientation_test_3_expected_preview.jpeg b/server/tests/orientation_test_3_expected_preview.jpeg index ab59f368be..26aeb04a25 100644 Binary files a/server/tests/orientation_test_3_expected_preview.jpeg and b/server/tests/orientation_test_3_expected_preview.jpeg differ diff --git a/server/tests/orientation_test_6_expected_preview.jpeg b/server/tests/orientation_test_6_expected_preview.jpeg index 5909639e62..fecf18955d 100644 Binary files a/server/tests/orientation_test_6_expected_preview.jpeg and b/server/tests/orientation_test_6_expected_preview.jpeg differ diff --git a/server/tests/orientation_test_7_expected_preview.jpeg b/server/tests/orientation_test_7_expected_preview.jpeg index 8ea4c2ae44..7f5b519804 100644 Binary files a/server/tests/orientation_test_7_expected_preview.jpeg and b/server/tests/orientation_test_7_expected_preview.jpeg differ diff --git a/server/tests/orientation_test_8_expected_preview.jpeg b/server/tests/orientation_test_8_expected_preview.jpeg index bfe7b7a638..6b5803de0f 100644 Binary files a/server/tests/orientation_test_8_expected_preview.jpeg and b/server/tests/orientation_test_8_expected_preview.jpeg differ diff --git a/server/tests/testgif_expected_thumbnail.jpg b/server/tests/testgif_expected_thumbnail.jpg index 47711fd2ed..967a6589be 100644 Binary files a/server/tests/testgif_expected_thumbnail.jpg and b/server/tests/testgif_expected_thumbnail.jpg differ