[MM-62191] Remove disintegration/imaging dependency (#29657)

* Remove disintegration/imaging dependency

* Simplify FillCenter logic

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Claudio Costa
2025-02-02 23:34:00 -06:00
коммит произвёл GitHub
родитель 28dbc3cabb
Коммит 316cde2569
29 изменённых файлов: 468 добавлений и 22 удалений

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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)
}

Просмотреть файл

@@ -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())
})
}
}

Просмотреть файл

@@ -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)
}

Просмотреть файл

@@ -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)
})
}
}

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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=

Двоичные данные
server/tests/crop_test_input.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичные данные
server/tests/crop_test_output_100x100.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 808 B

Двоичные данные
server/tests/crop_test_output_100x45.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 411 B

Двоичные данные
server/tests/crop_test_output_45x100.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 469 B

Двоичные данные
server/tests/crop_test_output_45x45.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 351 B

Двоичные данные
server/tests/fill_test_input.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.3 KiB

Двоичные данные
server/tests/fill_test_output_100x100.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1021 B

Двоичные данные
server/tests/fill_test_output_100x45.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 716 B

Двоичные данные
server/tests/fill_test_output_45x100.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 702 B

Двоичные данные
server/tests/fill_test_output_45x45.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 553 B

Двоичные данные
server/tests/fit_test_input.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.5 KiB

Двоичные данные
server/tests/fit_test_output_100x100.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.0 KiB

Двоичные данные
server/tests/fit_test_output_100x45.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.9 KiB

Двоичные данные
server/tests/fit_test_output_45x100.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.5 KiB

Двоичные данные
server/tests/fit_test_output_45x45.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.5 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 300 KiB

После

Ширина:  |  Высота:  |  Размер: 300 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 302 KiB

После

Ширина:  |  Высота:  |  Размер: 302 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 301 KiB

После

Ширина:  |  Высота:  |  Размер: 301 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 302 KiB

После

Ширина:  |  Высота:  |  Размер: 302 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 301 KiB

После

Ширина:  |  Высота:  |  Размер: 301 KiB

Двоичные данные
server/tests/testgif_expected_thumbnail.jpg

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 5.2 KiB

После

Ширина:  |  Высота:  |  Размер: 5.2 KiB