mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Add benchmarks for image transformation functions Covers GeneratePreview, GenerateThumbnail, GenerateMiniPreviewImage, FillCenter, and MakeImageUpright — all functions that delegate to the external imaging library. Baseline before/after the library swap. * Replace anthonynsimon/bild with boxes-ltd/imaging Reverts the imaging library swap from PR #29657 (disintegration/imaging → anthonynsimon/bild) and replaces it with github.com/boxes-ltd/imaging, the maintained fork of disintegration/imaging with the same API. - emoji.go: restore direct use of imaging.Fit with Lanczos filter - orientation.go: restore FlipH/FlipV/Rotate90/180/270/Transpose/Transverse - preview.go: restore imaging.Resize calls with Lanczos filter - utils.go: remove bild-based Resize/Fit/CropCenter helpers; restore the original FillCenter using imaging.Fill - Remove tests and test images added for the bild-specific helpers - Regenerate orientation test expected images (2,3,6,7,8) and GIF thumbnail to match boxes-ltd/imaging output * gofmt imaging package files * Add tests and fixtures for the boxes-ltd/imaging-backed functions Cover MakeImageUpright across all 8 EXIF orientations, GeneratePreview, GenerateMiniPreviewImage, and FillCenter against fixtures regenerated under the new library. * Contain boxes-ltd/imaging behind the local imaging wrapper Add a Fit wrapper alongside FillCenter and route emoji.go through the local package so boxes-ltd/imaging is only imported from channels/app/imaging. * Add TestFit to cover the local Fit wrapper Dimensional checks for the Fit wrapper used by emoji.go, mirroring TestGenerateThumbnail. Pixel correctness is covered by the upstream boxes-ltd/imaging tests; this guards against wrapper-level mistakes (transposed args, wrong filter). * Address CodeRabbit nits in TestFillCenter and TestFit Decode a fresh source image per TestFillCenter subtest so cases stay isolated even if the wrapper ever mutates input. Rename the TestFit "smaller than bounds (clone)" case to "no resize when smaller than bounds" since the assertion only checks dimensions, not clone semantics. * Revert per-subtest decode in TestFillCenter imaging.Fill never mutates its input — it always returns a fresh *image.NRGBA — so re-decoding the source for every subtest was unnecessary work. Decode once, share across subtests. * Compare decoded pixels instead of raw PNG/JPEG bytes in tests image/png isn't byte-stable across Go versions, so comparing re-encoded byte streams against checked-in fixtures is brittle to toolchain bumps and re-encoding. Decode both sides and compare RGBA pixels via a shared requireSameImage helper. Fixtures stay on disk unchanged. Covers TestFillCenter, TestGeneratePreview, TestGenerateMiniPreviewImage, and TestMakeImageUpright. TestFillImageTransparency is left alone since its byte-comparison pattern predates this branch. * Report total diff rate when requireSameImage fails Walk every pixel before failing instead of stopping at the first mismatch. The failure message now includes how many pixels differ, the percentage of the image that's off, and the first divergence. --------- Co-authored-by: Mattermost Build <[email protected]> Co-authored-by: Hanzo Dev <[email protected]>
256 lines
6.2 KiB
Go
256 lines
6.2 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package imaging
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
"image/color"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// requireSameImage asserts that want and got cover the same bounds and have
|
|
// identical RGBA values at every pixel. We compare decoded pixels rather than
|
|
// re-encoded byte streams because image/png is not byte-stable across Go
|
|
// versions even for identical pixel content. On mismatch we walk every pixel
|
|
// before failing so the report includes the total diff rate, not just the
|
|
// first divergence.
|
|
func requireSameImage(t *testing.T, want, got image.Image) {
|
|
t.Helper()
|
|
require.Equal(t, want.Bounds(), got.Bounds())
|
|
b := got.Bounds()
|
|
total := b.Dx() * b.Dy()
|
|
var (
|
|
diff int
|
|
firstX int
|
|
firstY int
|
|
firstWant [4]uint32
|
|
firstGot [4]uint32
|
|
)
|
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
for x := b.Min.X; x < b.Max.X; x++ {
|
|
wr, wg, wb, wa := want.At(x, y).RGBA()
|
|
gr, gg, gb, ga := got.At(x, y).RGBA()
|
|
if wr == gr && wg == gg && wb == gb && wa == ga {
|
|
continue
|
|
}
|
|
if diff == 0 {
|
|
firstX, firstY = x, y
|
|
firstWant = [4]uint32{wr, wg, wb, wa}
|
|
firstGot = [4]uint32{gr, gg, gb, ga}
|
|
}
|
|
diff++
|
|
}
|
|
}
|
|
if diff > 0 {
|
|
t.Fatalf("%d / %d pixels differ (%.2f%%); first at (%d, %d): want %v got %v",
|
|
diff, total, 100*float64(diff)/float64(total),
|
|
firstX, firstY, firstWant, firstGot)
|
|
}
|
|
}
|
|
|
|
func TestFillImageTransparency(t *testing.T) {
|
|
tcs := []struct {
|
|
name string
|
|
inputName string
|
|
outputName string
|
|
fillColor color.Color
|
|
}{
|
|
{
|
|
"8-bit Palette",
|
|
"fill_test_8bit_palette.png",
|
|
"fill_test_8bit_palette_out.png",
|
|
color.RGBA{0, 255, 0, 255},
|
|
},
|
|
{
|
|
"8-bit RGB",
|
|
"fill_test_8bit_rgb.png",
|
|
"fill_test_8bit_rgb_out.png",
|
|
color.RGBA{0, 255, 0, 255},
|
|
},
|
|
{
|
|
"8-bit RGBA",
|
|
"fill_test_8bit_rgba.png",
|
|
"fill_test_8bit_rgba_out.png",
|
|
color.RGBA{0, 255, 0, 255},
|
|
},
|
|
{
|
|
"16-bit RGB",
|
|
"fill_test_16bit_rgb.png",
|
|
"fill_test_16bit_rgb_out.png",
|
|
color.RGBA{0, 255, 0, 255},
|
|
},
|
|
{
|
|
"16-bit RGBA",
|
|
"fill_test_16bit_rgba.png",
|
|
"fill_test_16bit_rgba_out.png",
|
|
color.RGBA{0, 255, 0, 255},
|
|
},
|
|
}
|
|
|
|
imgDir, ok := fileutils.FindDir("tests")
|
|
require.True(t, ok)
|
|
|
|
e, err := NewEncoder(EncoderOptions{})
|
|
require.NotNil(t, e)
|
|
require.NoError(t, err)
|
|
|
|
d, err := NewDecoder(DecoderOptions{})
|
|
require.NotNil(t, d)
|
|
require.NoError(t, err)
|
|
|
|
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)
|
|
|
|
expectedBytes, err := os.ReadFile(imgDir + "/" + tc.outputName)
|
|
require.NoError(t, err)
|
|
|
|
FillImageTransparency(inputImg, tc.fillColor)
|
|
|
|
var b bytes.Buffer
|
|
err = e.EncodePNG(&b, inputImg)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedBytes, b.Bytes())
|
|
})
|
|
}
|
|
|
|
t.Run("Opaque image", func(t *testing.T) {
|
|
inputFile, err := os.Open(imgDir + "/fill_test_opaque.png")
|
|
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)
|
|
|
|
_, err = inputFile.Seek(0, 0)
|
|
require.NoError(t, err)
|
|
|
|
expectedImg, format, err := d.Decode(inputFile)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, expectedImg)
|
|
require.Equal(t, "png", format)
|
|
|
|
FillImageTransparency(inputImg, color.RGBA{0, 255, 0, 255})
|
|
require.Equal(t, expectedImg, inputImg)
|
|
})
|
|
}
|
|
|
|
func TestFillCenter(t *testing.T) {
|
|
tcs := []struct {
|
|
name string
|
|
outputName string
|
|
width int
|
|
height int
|
|
}{
|
|
{"100x100", "fill_test_output_100x100.png", 100, 100},
|
|
{"45x45", "fill_test_output_45x45.png", 45, 45},
|
|
{"100x45", "fill_test_output_100x45.png", 100, 45},
|
|
{"45x100", "fill_test_output_45x100.png", 45, 100},
|
|
}
|
|
|
|
imgDir, ok := fileutils.FindDir("tests")
|
|
require.True(t, ok)
|
|
|
|
d, err := NewDecoder(DecoderOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, d)
|
|
|
|
inputFile, err := os.Open(filepath.Join(imgDir, "fill_test_input.png"))
|
|
require.NoError(t, err)
|
|
defer inputFile.Close()
|
|
|
|
inputImg, format, err := d.Decode(inputFile)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "png", format)
|
|
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
expectedFile, err := os.Open(filepath.Join(imgDir, tc.outputName))
|
|
require.NoError(t, err)
|
|
defer expectedFile.Close()
|
|
|
|
expectedImg, format, err := d.Decode(expectedFile)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "png", format)
|
|
|
|
out := FillCenter(inputImg, tc.width, tc.height)
|
|
requireSameImage(t, expectedImg, out)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFit(t *testing.T) {
|
|
tcs := []struct {
|
|
name string
|
|
inputImg image.Image
|
|
maxW int
|
|
maxH int
|
|
expectedWidth int
|
|
expectedHeight int
|
|
}{
|
|
{
|
|
name: "no resize when smaller than bounds",
|
|
inputImg: image.NewRGBA(image.Rect(0, 0, 50, 50)),
|
|
maxW: 100,
|
|
maxH: 100,
|
|
expectedWidth: 50,
|
|
expectedHeight: 50,
|
|
},
|
|
{
|
|
name: "landscape clamps to width",
|
|
inputImg: image.NewRGBA(image.Rect(0, 0, 200, 100)),
|
|
maxW: 100,
|
|
maxH: 100,
|
|
expectedWidth: 100,
|
|
expectedHeight: 50,
|
|
},
|
|
{
|
|
name: "portrait clamps to height",
|
|
inputImg: image.NewRGBA(image.Rect(0, 0, 100, 200)),
|
|
maxW: 100,
|
|
maxH: 100,
|
|
expectedWidth: 50,
|
|
expectedHeight: 100,
|
|
},
|
|
{
|
|
name: "both dimensions exceed",
|
|
inputImg: image.NewRGBA(image.Rect(0, 0, 400, 200)),
|
|
maxW: 100,
|
|
maxH: 100,
|
|
expectedWidth: 100,
|
|
expectedHeight: 50,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
out := Fit(tc.inputImg, tc.maxW, tc.maxH)
|
|
require.Equal(t, tc.expectedWidth, out.Bounds().Dx())
|
|
require.Equal(t, tc.expectedHeight, out.Bounds().Dy())
|
|
})
|
|
}
|
|
}
|