Skip to content

Commit

Permalink
cmpimg: add EqualApprox and CheckPlotApprox
Browse files Browse the repository at this point in the history
  • Loading branch information
sbinet committed Dec 16, 2020
1 parent 76061dd commit 8a64654
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 3 deletions.
12 changes: 10 additions & 2 deletions cmpimg/checkplot.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,18 @@ func goldenPath(path string) string {
}

// CheckPlot checks a generated plot against a previously created reference.
// If generateTestData = true, it regenerates the reference.
// If GenerateTestData = true, it regenerates the reference.
// For image.Image formats, a base64 encoded png representation is output to
// the testing log when a difference is identified.
func CheckPlot(ExampleFunc func(), t *testing.T, filenames ...string) {
CheckPlotApprox(ExampleFunc, t, 0, filenames...)
}

// CheckPlotApprox checks a generated plot against a previously created reference.
// If GenerateTestData = true, it regenerates the reference.
// For image.Image formats, a base64 encoded png representation is output to
// the testing log when a difference is identified.
func CheckPlotApprox(ExampleFunc func(), t *testing.T, delta uint8, filenames ...string) {
t.Helper()

paths := make([]string, len(filenames))
Expand Down Expand Up @@ -68,7 +76,7 @@ func CheckPlot(ExampleFunc func(), t *testing.T, filenames ...string) {
continue
}
typ := filepath.Ext(path)[1:] // remove the dot in e.g. ".pdf"
ok, err := Equal(typ, got, want)
ok, err := EqualApprox(typ, got, want, delta)
if err != nil {
t.Errorf("failed to compare image for %s: %v", path, err)
continue
Expand Down
74 changes: 73 additions & 1 deletion cmpimg/cmpimg.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ import (
//
// Equal may return an error if the decoding of the raw image somehow failed.
func Equal(typ string, raw1, raw2 []byte) (bool, error) {
return EqualApprox(typ, raw1, raw2, 0)
}

// EqualApprox takes the raw representation of two images, raw1 and raw2,
// together with the underlying image type ("eps", "jpeg", "jpg", "pdf", "png", "svg", "tiff"),
// and returns whether the two images are equal or not.
//
// EqualApprox may return an error if the decoding of the raw image somehow failed.
func EqualApprox(typ string, raw1, raw2 []byte, delta uint8) (bool, error) {
switch typ {
case "svg", "tex":
return bytes.Equal(raw1, raw2), nil
Expand Down Expand Up @@ -73,7 +82,10 @@ func Equal(typ string, raw1, raw2 []byte) (bool, error) {
if err != nil {
return false, err
}
return reflect.DeepEqual(v1, v2), nil
if delta == 0 {
return reflect.DeepEqual(v1, v2), nil
}
return cmpImg(v1, v2, delta), nil

default:
return false, fmt.Errorf("cmpimg: unknown image type %q", typ)
Expand All @@ -100,6 +112,66 @@ func cmpPdf(pdf1, pdf2 *pdf.Reader) bool {
return t1 == t2
}

func cmpImg(v1, v2 image.Image, delta uint8) bool {
img1, ok := v1.(*image.RGBA)
if !ok {
img1 = newRGBAFrom(v1)
}

img2, ok := v2.(*image.RGBA)
if !ok {
img2 = newRGBAFrom(v2)
}

if len(img1.Pix) != len(img2.Pix) {
return false
}

diff := func(p1, p2 uint8) bool {
if p1 > p2 {
p1, p2 = p2, p1
}
return (p2 - p1) < delta
}

equalApprox := func(c1, c2 color.RGBA) bool {
var (
r1 = c1.R
g1 = c1.G
b1 = c1.B
a1 = c1.A

r2 = c2.R
g2 = c2.G
b2 = c2.B
a2 = c2.A
)
return diff(r1, r2) && diff(g1, g2) && diff(b1, b2) && diff(a1, a2)
}

bnd := img1.Bounds()
for x := bnd.Min.X; x < bnd.Max.X; x++ {
for y := bnd.Min.Y; y < bnd.Max.Y; y++ {
c1 := img1.RGBAAt(x, y)
c2 := img2.RGBAAt(x, y)
if !equalApprox(c1, c2) {
return false
}
}
}
return true

}

func newRGBAFrom(src image.Image) *image.RGBA {
var (
bnds = src.Bounds()
dst = image.NewRGBA(bnds)
)
draw.Draw(dst, bnds, src, image.Point{}, draw.Src)
return dst
}

// Diff calculates an intensity-scaled difference between images a and b
// and places the result in dst, returning the intersection of a, b and
// dst. It is the responsibility of the caller to construct dst so that
Expand Down
56 changes: 56 additions & 0 deletions cmpimg/cmpimg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package cmpimg
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"image/png"
"io/ioutil"
Expand Down Expand Up @@ -51,3 +52,58 @@ func TestDiff(t *testing.T) {
t.Errorf("unexpected encoded diff value:\ngot:%s\nwant:%s", gotDiff, wantDiffEncoded)
}
}

func TestEqual(t *testing.T) {
got, err := ioutil.ReadFile("testdata/approx_got_golden.png")
if err != nil {
t.Fatal(err)
}

ok, err := Equal("png", got, got)
if err != nil {
t.Fatalf("could not compare images: %+v", err)
}
if !ok {
t.Fatalf("same image does not compare equal")
}
}

func TestEqualApprox(t *testing.T) {
got, err := ioutil.ReadFile("testdata/approx_got_golden.png")
if err != nil {
t.Fatal(err)
}

want, err := ioutil.ReadFile("testdata/approx_want_golden.png")
if err != nil {
t.Fatal(err)
}

for _, tc := range []struct {
delta uint8
ok bool
}{
{0, false},
{1, false},
{2, false},
{3, false},
{4, false},
{5, false},
{6, false},
{7, true},
{8, true},
{9, true},
{10, true},
{255, true},
} {
t.Run(fmt.Sprintf("delta=%d", int(tc.delta)), func(t *testing.T) {
ok, err := EqualApprox("png", got, want, tc.delta)
if err != nil {
t.Fatalf("could not compare images: %+v", err)
}
if ok != tc.ok {
t.Fatalf("got=%v, want=%v", ok, tc.ok)
}
})
}
}
Binary file added cmpimg/testdata/approx_got_golden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cmpimg/testdata/approx_want_golden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 8a64654

Please sign in to comment.