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.
---------
(cherry picked from commit b868fbf8fa)
Co-authored-by: Jesse Hallam <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
254 lines
7.4 KiB
Go
254 lines
7.4 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package imaging
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestFwSeeker(t *testing.T) {
|
|
t.Run("Read", func(t *testing.T) {
|
|
data := []byte("hello world")
|
|
reader := bytes.NewReader(data)
|
|
seeker := &fwSeeker{r: reader}
|
|
|
|
// Read the first 5 bytes
|
|
buf := make([]byte, 5)
|
|
n, err := seeker.Read(buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 5, n)
|
|
require.Equal(t, []byte("hello"), buf)
|
|
|
|
// Read the next 6 bytes
|
|
buf = make([]byte, 6)
|
|
n, err = seeker.Read(buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 6, n)
|
|
require.Equal(t, []byte(" world"), buf)
|
|
|
|
// Try to read more, should get EOF
|
|
buf = make([]byte, 1)
|
|
n, err = seeker.Read(buf)
|
|
require.Equal(t, 0, n)
|
|
require.Equal(t, io.EOF, err)
|
|
})
|
|
|
|
t.Run("Seek forward from start", func(t *testing.T) {
|
|
data := []byte("hello world")
|
|
reader := bytes.NewReader(data)
|
|
seeker := &fwSeeker{r: reader}
|
|
|
|
// Seek forward 6 bytes
|
|
pos, err := seeker.Seek(6, io.SeekStart)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(6), pos)
|
|
|
|
// Seeking from the same position should work
|
|
pos, err = seeker.Seek(6, io.SeekStart)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(6), pos)
|
|
|
|
// Seeking again from start should work.
|
|
pos, err = seeker.Seek(7, io.SeekStart)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(7), pos)
|
|
|
|
// Seeking backwards should not be supported
|
|
_, err = seeker.Seek(6, io.SeekStart)
|
|
require.EqualError(t, err, "seeking backwards is not supported")
|
|
|
|
// Read the remaining data
|
|
buf := make([]byte, 4)
|
|
n, err := seeker.Read(buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 4, n)
|
|
require.Equal(t, []byte("orld"), buf)
|
|
})
|
|
|
|
t.Run("Seek forward from current", func(t *testing.T) {
|
|
data := []byte("hello world")
|
|
reader := bytes.NewReader(data)
|
|
seeker := &fwSeeker{r: reader}
|
|
|
|
// Read first 6 bytes
|
|
buf := make([]byte, 6)
|
|
n, err := seeker.Read(buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 6, n)
|
|
require.Equal(t, []byte("hello "), buf)
|
|
|
|
// Seek forward 2 more bytes from current position
|
|
pos, err := seeker.Seek(2, io.SeekCurrent)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(8), pos)
|
|
|
|
// Read the remaining data
|
|
buf = make([]byte, 3)
|
|
n, err = seeker.Read(buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 3, n)
|
|
require.Equal(t, []byte("rld"), buf)
|
|
})
|
|
|
|
t.Run("Seek backward not supported", func(t *testing.T) {
|
|
data := []byte("hello world")
|
|
reader := bytes.NewReader(data)
|
|
seeker := &fwSeeker{r: reader}
|
|
|
|
// Try to seek backward
|
|
_, err := seeker.Seek(-1, io.SeekCurrent)
|
|
require.EqualError(t, err, "seeking backwards is not supported")
|
|
|
|
// Try to seek from end
|
|
_, err = seeker.Seek(0, io.SeekEnd)
|
|
require.EqualError(t, err, "seeking backwards is not supported")
|
|
})
|
|
|
|
t.Run("Seek beyond EOF", func(t *testing.T) {
|
|
data := []byte("hello")
|
|
reader := bytes.NewReader(data)
|
|
seeker := &fwSeeker{r: reader}
|
|
|
|
// Try to seek beyond EOF
|
|
n, err := seeker.Seek(10, io.SeekStart)
|
|
require.EqualError(t, err, "failed to seek: EOF")
|
|
require.Equal(t, int64(5), n) // Should have read until EOF (5 bytes)
|
|
})
|
|
}
|
|
|
|
func TestGetImageOrientation(t *testing.T) {
|
|
imgDir, ok := fileutils.FindDir("tests/exif_samples")
|
|
require.True(t, ok, "Failed to find exif samples directory")
|
|
|
|
// Define orientations and their corresponding file prefixes
|
|
orientations := map[string]int{
|
|
"up": Upright,
|
|
"up-mirrored": UprightMirrored,
|
|
"down": UpsideDown,
|
|
"down-mirrored": UpsideDownMirrored,
|
|
"left": RotatedCCW,
|
|
"left-mirrored": RotatedCWMirrored,
|
|
"right": RotatedCW,
|
|
"right-mirrored": RotatedCCWMirrored,
|
|
}
|
|
|
|
// Define supported formats
|
|
formats := []string{"jpg", "png", "tiff", "webp"}
|
|
|
|
// Generate test cases for all combinations
|
|
var testCases []struct {
|
|
name string
|
|
fileName string
|
|
expectedOrientation int
|
|
}
|
|
|
|
for prefix, orientation := range orientations {
|
|
for _, format := range formats {
|
|
testCases = append(testCases, struct {
|
|
name string
|
|
fileName string
|
|
expectedOrientation int
|
|
}{
|
|
name: fmt.Sprintf("%s (%s)", prefix, format),
|
|
fileName: fmt.Sprintf("%s.%s", prefix, format),
|
|
expectedOrientation: orientation,
|
|
})
|
|
}
|
|
}
|
|
|
|
dec, err := NewDecoder(DecoderOptions{})
|
|
require.NoError(t, err)
|
|
|
|
for _, tc := range testCases {
|
|
var orientation int
|
|
imgPath := filepath.Join(imgDir, tc.fileName)
|
|
file, err := os.Open(imgPath)
|
|
require.NoError(t, err)
|
|
defer file.Close()
|
|
|
|
_, format, err := dec.DecodeConfig(file)
|
|
require.NoError(t, err)
|
|
|
|
t.Run(tc.name+"_file", func(t *testing.T) {
|
|
_, err = file.Seek(0, io.SeekStart)
|
|
require.NoError(t, err)
|
|
|
|
orientation, err = GetImageOrientation(file, format)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedOrientation, orientation, "Incorrect orientation detected for %s", tc.fileName)
|
|
})
|
|
|
|
t.Run(tc.name+"_reader", func(t *testing.T) {
|
|
_, err = file.Seek(0, io.SeekStart)
|
|
require.NoError(t, err)
|
|
|
|
orientation, err = GetImageOrientation(&io.LimitedReader{R: file, N: 1024 * 1024}, format)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedOrientation, orientation, "Incorrect orientation detected for %s", tc.fileName)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMakeImageUpright(t *testing.T) {
|
|
// Each case loads the canonical EXIF fixture for orientation N (the
|
|
// 128x128 quadrants pattern in its stored, uncorrected form), applies
|
|
// MakeImageUpright(., N), and asserts that the result has the same
|
|
// pixels as the upright reference.
|
|
tcs := []struct {
|
|
name string
|
|
orientation int
|
|
inputName string
|
|
}{
|
|
{"Upright (no-op)", Upright, "quadrants-orientation-1.png"},
|
|
{"UprightMirrored (FlipH)", UprightMirrored, "quadrants-orientation-2.png"},
|
|
{"UpsideDown (Rotate180)", UpsideDown, "quadrants-orientation-3.png"},
|
|
{"UpsideDownMirrored (FlipV)", UpsideDownMirrored, "quadrants-orientation-4.png"},
|
|
{"RotatedCWMirrored (Transpose)", RotatedCWMirrored, "quadrants-orientation-5.png"},
|
|
{"RotatedCCW (Rotate270)", RotatedCCW, "quadrants-orientation-6.png"},
|
|
{"RotatedCCWMirrored (Transverse)", RotatedCCWMirrored, "quadrants-orientation-7.png"},
|
|
{"RotatedCW (Rotate90)", RotatedCW, "quadrants-orientation-8.png"},
|
|
// Unsupported orientations fall through to the default branch and
|
|
// return the input unchanged. Pass the upright fixture so the
|
|
// no-op result still equals the upright reference.
|
|
{"unsupported orientation", 99, "quadrants-orientation-1.png"},
|
|
}
|
|
|
|
imgDir, ok := fileutils.FindDir("tests/exif_samples")
|
|
require.True(t, ok)
|
|
|
|
d, err := NewDecoder(DecoderOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, d)
|
|
|
|
uprightFile, err := os.Open(filepath.Join(imgDir, "quadrants-orientation-1.png"))
|
|
require.NoError(t, err)
|
|
defer uprightFile.Close()
|
|
|
|
uprightImg, format, err := d.Decode(uprightFile)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "png", format)
|
|
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
inputFile, err := os.Open(filepath.Join(imgDir, tc.inputName))
|
|
require.NoError(t, err)
|
|
defer inputFile.Close()
|
|
|
|
inputImg, format, err := d.Decode(inputFile)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "png", format)
|
|
|
|
requireSameImage(t, uprightImg, MakeImageUpright(inputImg, tc.orientation))
|
|
})
|
|
}
|
|
}
|