Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
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())
|
|
})
|
|
}
|
|
}
|