// Copyright ©2017 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package cmpimg import ( "bytes" "encoding/base64" "fmt" "image" "image/draw" "image/png" "os" "path/filepath" "strings" "testing" ) const wantDiffEncoded = `iVBORw0KGgoAAAANSUhEUgAAAZAAAAEzEAIAAADAxR6YAAAHBklEQVR4nOzYjW3kRABA4RWiC0QXUMami2xRSRfrMu76gDKQZQ3+2907xEMg8X3SXZKxdzwZ29JTfrgAAJASWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAMYEFABATWAAAsR//7QX8X12v8//T9E/NvPje+R+tZjvP9882PjV/naZvf+J8lb96xb+/l+uar9d5zfPX8+rnY/PocvTZ9ebj4+j5TmyPjauNM8f8YyXrGh7NM2ZaPjXmHj8dZx4j+zUsax2zr0fHjGOG9frrOfs9WM/bjmx3bnxmP368/2MHzmtfj61j49/2+tv1bO/Ho1UdzzyO7898NsNx/NU+bHfkeMXZzz+Nnz4/3m/rPXi/He/B6r55Tt+evk3btZ73ffsGfH6MPfn8+PL1cvnydVnB779dLrfb/c/fa+zT9imaXhw9P3fH53H9fbdvwH6/lvdv/zSvT/B4e/dzL2e/7d7bZ98vOzqPvG3mWPZ42d375g1f5tzfgePI8Uk8P3GP1nAc+fWXeWT+f5rmOzH2dn5K9nt0/+b8j97ddYXnt+31vq2zHD+/3onz0XGH3m/P3tBnb8szr2Z5vdOvZzyv6Di+fDdNt9t5Dn/BAgCICSwAgJjAAgCICSwAgJjAAgCICSwAgJjAAgCICSwAgJjAAgCICSwAgJjAAgAAAOC/zV+wAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAICawAABiAgsAIPZHAAAA///KAlB2mCuyaAAAAABJRU5ErkJggg==` func TestDiff(t *testing.T) { got, err := os.ReadFile(filepath.FromSlash("./testdata/failed_input.png")) if err != nil { t.Fatalf("failed to read failed file: %v", err) } want, err := os.ReadFile(filepath.FromSlash("./testdata/good_golden.png")) if err != nil { t.Fatalf("failed to read golden file: %v", err) } v1, _, err := image.Decode(bytes.NewReader(got)) if err != nil { t.Fatalf("unexpected error decoding failed file: %v", err) } v2, _, err := image.Decode(bytes.NewReader(want)) if err != nil { t.Fatalf("unexpected error decoding golden file: %v", err) } dst := image.NewRGBA64(v1.Bounds().Union(v2.Bounds())) rect := Diff(dst, v1, v2) if rect != dst.Bounds() { t.Errorf("unexpected bound for diff: got:%+v want:%+v", rect, dst.Bounds()) } var buf bytes.Buffer err = png.Encode(&buf, dst) if err != nil { t.Fatalf("failed to encode difference png: %v", err) } gotDiff := base64.StdEncoding.EncodeToString(buf.Bytes()) if gotDiff != wantDiffEncoded { t.Errorf("unexpected encoded diff value:\ngot:%s\nwant:%s", gotDiff, wantDiffEncoded) } } func TestEqual(t *testing.T) { got, err := os.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 TestEqualApproxPNG(t *testing.T) { got, err := os.ReadFile("testdata/approx_got_golden.png") if err != nil { t.Fatal(err) } want, err := os.ReadFile("testdata/approx_want_golden.png") if err != nil { t.Fatal(err) } for _, tc := range []struct { delta float64 ok bool }{ {0, false}, {0.01, false}, {0.02, false}, {0.05, true}, {0.1, true}, {1, true}, } { t.Run(fmt.Sprintf("delta=%g", 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) } }) } } func TestEqualApprox(t *testing.T) { read := func(name string) []byte { raw, err := os.ReadFile(name) if err != nil { t.Fatalf("could not read file %q: %+v", name, err) } return raw } asPNG_RGBA64 := func(raw []byte) []byte { src, _, err := image.Decode(bytes.NewReader(raw)) if err != nil { t.Fatalf("could not decode image: %+v", err) } var ( bnds = src.Bounds() dst = image.NewRGBA64(bnds) out = new(bytes.Buffer) ) draw.Draw(dst, bnds, src, image.Point{}, draw.Src) err = png.Encode(out, dst) if err != nil { t.Fatalf("could not encode image: %+v", err) } return out.Bytes() } for _, tc := range []struct { name string img1 []byte img2 []byte delta float64 want bool }{ { name: "svg-ok", img1: []byte(""), img2: []byte(""), want: true, }, { name: "svg-diff", img1: []byte(""), img2: []byte("1"), want: false, }, { name: "eps-ok", img1: []byte("line1\nline2\nCreationDate:now\n"), img2: []byte("line1\nline2\nCreationDate:later\n"), want: true, }, { name: "eps-diff-1", img1: []byte("line1\nline2\nCreationDate:now\n"), img2: []byte("line1\nline2\nCreationDate:later"), want: false, }, { name: "eps-diff-2", img1: []byte("line1\nline2\nCreationDate:now\n"), img2: []byte("line1\nline3\nCreationDate:later\n"), want: false, }, { name: "pdf-ok", img1: read("../vg/vgpdf/testdata/arc_golden.pdf"), img2: read("../vg/vgpdf/testdata/arc_golden.pdf"), want: true, }, { name: "pdf-diff", img1: read("../vg/vgpdf/testdata/arc_golden.pdf"), img2: read("../vg/vgpdf/testdata/issue540_golden.pdf"), want: false, }, { name: "pdf-diff-2", img1: read("../vg/vgpdf/testdata/arc_golden.pdf"), img2: read("../vg/vgpdf/testdata/multipage_golden.pdf"), want: false, }, { name: "png-ok", img1: read("testdata/approx_got_golden.png"), img2: read("testdata/approx_want_golden.png"), delta: 0.1, want: true, }, { name: "png-ok-rgba64", img1: read("testdata/approx_got_golden.png"), img2: asPNG_RGBA64(read("testdata/approx_want_golden.png")), delta: 0.1, want: true, }, { name: "png-ok-rgba64-2", img1: read("testdata/approx_got_golden.png"), img2: asPNG_RGBA64(read("testdata/approx_got_golden.png")), delta: -10, // clips to 0.0 want: true, }, { name: "png-ok-rgba64-3", img1: asPNG_RGBA64(read("testdata/approx_got_golden.png")), img2: read("testdata/approx_got_golden.png"), delta: 0, want: true, }, { name: "png-diff-1", img1: read("testdata/approx_got_golden.png"), img2: read("testdata/approx_want_golden.png"), delta: 0, want: false, }, { name: "png-diff-2", img1: read("testdata/approx_got_golden.png"), img2: read("testdata/approx_want_golden.png"), delta: 0.01, want: false, }, { name: "png-diff-3", img1: read("testdata/approx_got_golden.png"), img2: read("testdata/good_golden.png"), delta: 10, // clips to 1.0 want: false, }, } { t.Run(tc.name, func(t *testing.T) { typ := tc.name[:strings.Index(tc.name, "-")] got, err := EqualApprox(typ, tc.img1, tc.img2, tc.delta) if err != nil { t.Fatalf("could not run equal-approx: %+v", err) } if got != tc.want { t.Fatalf("invalid equal-approx: got=%v, want=%v", got, tc.want) } }) } }