blob: 1372ad889ad8c59ccd72939beb3f4c8022b75f13 [file] [log] [blame]
package diff
import (
"bytes"
"image"
"image/png"
"io"
"math"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/testutils/unittest"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/image/text"
one_by_five "go.skia.org/infra/golden/go/testutils/data_one_by_five"
)
const (
TESTDATA_DIR = "testdata"
)
func TestDiffMetrics(t *testing.T) {
unittest.MediumTest(t)
// Assert different images with the same dimensions.
assertDiffs(t, "4029959456464745507", "16465366847175223174",
&DiffMetrics{
NumDiffPixels: 16,
PixelDiffPercent: 0.0064,
MaxRGBADiffs: [4]int{54, 100, 125, 0},
DimDiffer: false})
assertDiffs(t, "5024150605949408692", "11069776588985027208",
&DiffMetrics{
NumDiffPixels: 2233,
PixelDiffPercent: 0.8932,
MaxRGBADiffs: [4]int{0, 0, 1, 0},
DimDiffer: false})
// Assert the same image.
assertDiffs(t, "5024150605949408692", "5024150605949408692",
&DiffMetrics{
NumDiffPixels: 0,
PixelDiffPercent: 0,
MaxRGBADiffs: [4]int{0, 0, 0, 0},
DimDiffer: false})
// Assert different images with different dimensions.
assertDiffs(t, "ffce5042b4ac4a57bd7c8657b557d495", "fffbcca7e8913ec45b88cc2c6a3a73ad",
&DiffMetrics{
NumDiffPixels: 571674,
PixelDiffPercent: 89.324066,
MaxRGBADiffs: [4]int{255, 255, 255, 0},
DimDiffer: true})
// Assert with images that match in dimensions but where all pixels differ.
assertDiffs(t, "4029959456464745507", "4029959456464745507-inverted",
&DiffMetrics{
NumDiffPixels: 250000,
PixelDiffPercent: 100.0,
MaxRGBADiffs: [4]int{255, 255, 255, 0},
DimDiffer: false})
// Assert different images where neither fits into the other.
assertDiffs(t, "fffbcca7e8913ec45b88cc2c6a3a73ad", "fffbcca7e8913ec45b88cc2c6a3a73ad-rotated",
&DiffMetrics{
NumDiffPixels: 172466,
PixelDiffPercent: 74.8550347222,
MaxRGBADiffs: [4]int{255, 255, 255, 0},
DimDiffer: true})
// Make sure the metric is symmetric.
assertDiffs(t, "fffbcca7e8913ec45b88cc2c6a3a73ad-rotated", "fffbcca7e8913ec45b88cc2c6a3a73ad",
&DiffMetrics{
NumDiffPixels: 172466,
PixelDiffPercent: 74.8550347222,
MaxRGBADiffs: [4]int{255, 255, 255, 0},
DimDiffer: true})
// Compare two images where one has an alpha channel and the other doesn't.
assertDiffs(t, "b716a12d5b98d04b15db1d9dd82c82ea", "df1591dde35907399734ea19feb76663",
&DiffMetrics{
NumDiffPixels: 8750,
PixelDiffPercent: 2.8483074,
MaxRGBADiffs: [4]int{255, 2, 255, 0},
DimDiffer: false})
// Compare two images where the alpha differs.
assertDiffs(t, "df1591dde35907399734ea19feb76663", "df1591dde35907399734ea19feb76663-6-alpha-diff",
&DiffMetrics{
NumDiffPixels: 6,
PixelDiffPercent: 0.001953125,
MaxRGBADiffs: [4]int{0, 0, 0, 235},
DimDiffer: false})
}
// lineDiff lists the differences in the lines of a and b.
func lineDiff(t *testing.T, a, b string) {
aslice := strings.Split(a, "\n")
bslice := strings.Split(b, "\n")
if len(aslice) != len(bslice) {
t.Fatal("Can't diff text, mismatched number of lines.\n")
return
}
for i, s := range aslice {
if s != bslice[i] {
t.Errorf("Line %d: %q != %q\n", i+1, s, bslice[i])
}
}
}
// assertImagesEqual asserts that the two images are identical.
func assertImagesEqual(t *testing.T, got, want *image.NRGBA) {
// Do the compare by converting them to sktext format and doing a string
// compare.
gotbuf := &bytes.Buffer{}
if err := text.Encode(gotbuf, got); err != nil {
t.Fatalf("Failed to encode: %s", err)
}
wantbuf := &bytes.Buffer{}
if err := text.Encode(wantbuf, want); err != nil {
t.Fatalf("Failed to encode: %s", err)
}
if gotbuf.String() != wantbuf.String() {
t.Errorf("Pixel mismatch:\nGot:\n\n%v\n\nWant:\n\n%v\n", gotbuf, wantbuf)
// Also print out the lines that are different, to make debugging easier.
lineDiff(t, gotbuf.String(), wantbuf.String())
}
}
// assertDiffMatch asserts that you get expected when you diff
// src1 and src2.
//
// Note that all images are in sktext format as strings.
func assertDiffMatch(t *testing.T, expected, src1, src2 string, expectedDiffMetrics ...*DiffMetrics) {
dm, got := PixelDiff(text.MustToNRGBA(src1), text.MustToNRGBA(src2))
want := text.MustToNRGBA(expected)
assertImagesEqual(t, got, want)
for _, expDM := range expectedDiffMetrics {
assert.Equal(t, expDM, dm)
}
}
// TestDiffImages tests that the diff images produced are correct.
func TestDiffImages(t *testing.T) {
unittest.MediumTest(t)
assertDiffMatch(t, one_by_five.DiffNone, one_by_five.ImageOne, one_by_five.ImageOne)
assertDiffMatch(t, one_by_five.DiffNone, one_by_five.ImageTwo, one_by_five.ImageTwo)
assertDiffMatch(t, one_by_five.DiffImageOneAndTwo, one_by_five.ImageOne, one_by_five.ImageTwo)
assertDiffMatch(t, one_by_five.DiffImageOneAndTwo, one_by_five.ImageTwo, one_by_five.ImageOne)
assertDiffMatch(t, one_by_five.DiffImageOneAndThree, one_by_five.ImageThree, one_by_five.ImageOne)
assertDiffMatch(t, one_by_five.DiffImageOneAndThree, one_by_five.ImageOne, one_by_five.ImageThree)
assertDiffMatch(t, one_by_five.DiffImageOneAndFour, one_by_five.ImageOne, one_by_five.ImageFour)
assertDiffMatch(t, one_by_five.DiffImageOneAndFour, one_by_five.ImageFour, one_by_five.ImageOne)
assertDiffMatch(t, one_by_five.DiffImageTwoAndFive, one_by_five.ImageTwo, one_by_five.ImageFive, &DiffMetrics{
NumDiffPixels: 24,
PixelDiffPercent: (24.0 / 25.0) * 100,
MaxRGBADiffs: [4]int{0, 0, 0, 0},
DimDiffer: true,
})
}
// assertDiffs asserts that the DiffMetrics reported by Diffing the two images
// matches the expected DiffMetrics.
func assertDiffs(t *testing.T, d1, d2 string, expectedDiffMetrics *DiffMetrics) {
img1, err := openNRGBAFromFile(filepath.Join(TESTDATA_DIR, d1+".png"))
if err != nil {
t.Fatal("Failed to open test file: ", err)
}
img2, err := openNRGBAFromFile(filepath.Join(TESTDATA_DIR, d2+".png"))
if err != nil {
t.Fatal("Failed to open test file: ", err)
}
diffMetrics, _ := PixelDiff(img1, img2)
assert.Equal(t, expectedDiffMetrics, diffMetrics)
}
func TestDeltaOffset(t *testing.T) {
unittest.SmallTest(t)
testCases := []struct {
offset int
want int
}{
{
offset: 1,
want: 0,
},
{
offset: 2,
want: 1,
},
{
offset: 5,
want: 1,
},
{
offset: 6,
want: 2,
},
{
offset: 100,
want: 4,
},
{
offset: 1024,
want: 6,
},
}
for _, tc := range testCases {
if got, want := deltaOffset(tc.offset), tc.want; got != want {
t.Errorf("deltaOffset(%d): Got %v Want %v", tc.offset, got, want)
}
}
}
func TestCombinedDiffMetric(t *testing.T) {
unittest.SmallTest(t)
dm := &DiffMetrics{
MaxRGBADiffs: [4]int{255, 255, 255, 255},
PixelDiffPercent: 1.0,
}
assert.InDelta(t, 1.0, CombinedDiffMetric(dm, nil, nil), 0.000001)
dm = &DiffMetrics{
MaxRGBADiffs: [4]int{255, 255, 255, 255},
PixelDiffPercent: 0.5,
}
assert.InDelta(t, math.Sqrt(0.5), CombinedDiffMetric(dm, nil, nil), 0.000001)
}
func loadBenchmarkImage(fileName string) image.Image {
img, err := openNRGBAFromFile(filepath.Join(TESTDATA_DIR, fileName))
if err != nil {
sklog.Fatal("Failed to open test file: ", err)
}
return img
}
func benchmarkDiff(b *testing.B, img1, img2 image.Image) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
PixelDiff(img1, img2)
}
}
const (
img1 = "4029959456464745507.png" // 500x500.
img2 = "4029959456464745507-inverted.png" // 500x500.
img3 = "b716a12d5b98d04b15db1d9dd82c82ea.png" // 640x480.
)
func BenchmarkDiffIdentical(b *testing.B) {
benchmarkDiff(b, loadBenchmarkImage(img1), loadBenchmarkImage(img1))
}
func BenchmarkDiffSameSize(b *testing.B) {
benchmarkDiff(b, loadBenchmarkImage(img1), loadBenchmarkImage(img2))
}
func BenchmarkDiffDifferentSize(b *testing.B) {
benchmarkDiff(b, loadBenchmarkImage(img1), loadBenchmarkImage(img3))
}
// openNRGBAFromFile opens the given file path to a PNG file and returns the image as image.NRGBA.
func openNRGBAFromFile(fileName string) (*image.NRGBA, error) {
var img *image.NRGBA
err := util.WithReadFile(fileName, func(r io.Reader) error {
im, err := png.Decode(r)
if err != nil {
return err
}
img = GetNRGBA(im)
return nil
})
if err != nil {
return nil, skerr.Wrap(err)
}
return img, nil
}