1
2
3
4
5 package cmpimg
6
7 import (
8 "bytes"
9 "encoding/base64"
10 "fmt"
11 "image"
12 "image/draw"
13 "image/png"
14 "os"
15 "path/filepath"
16 "strings"
17 "testing"
18 )
19
20 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==`
21
22 func TestDiff(t *testing.T) {
23 got, err := os.ReadFile(filepath.FromSlash("./testdata/failed_input.png"))
24 if err != nil {
25 t.Fatalf("failed to read failed file: %v", err)
26 }
27 want, err := os.ReadFile(filepath.FromSlash("./testdata/good_golden.png"))
28 if err != nil {
29 t.Fatalf("failed to read golden file: %v", err)
30 }
31
32 v1, _, err := image.Decode(bytes.NewReader(got))
33 if err != nil {
34 t.Fatalf("unexpected error decoding failed file: %v", err)
35 }
36 v2, _, err := image.Decode(bytes.NewReader(want))
37 if err != nil {
38 t.Fatalf("unexpected error decoding golden file: %v", err)
39 }
40
41 dst := image.NewRGBA64(v1.Bounds().Union(v2.Bounds()))
42 rect := Diff(dst, v1, v2)
43 if rect != dst.Bounds() {
44 t.Errorf("unexpected bound for diff: got:%+v want:%+v", rect, dst.Bounds())
45 }
46
47 var buf bytes.Buffer
48 err = png.Encode(&buf, dst)
49 if err != nil {
50 t.Fatalf("failed to encode difference png: %v", err)
51 }
52 gotDiff := base64.StdEncoding.EncodeToString(buf.Bytes())
53 if gotDiff != wantDiffEncoded {
54 t.Errorf("unexpected encoded diff value:\ngot:%s\nwant:%s", gotDiff, wantDiffEncoded)
55 }
56 }
57
58 func TestEqual(t *testing.T) {
59 got, err := os.ReadFile("testdata/approx_got_golden.png")
60 if err != nil {
61 t.Fatal(err)
62 }
63
64 ok, err := Equal("png", got, got)
65 if err != nil {
66 t.Fatalf("could not compare images: %+v", err)
67 }
68 if !ok {
69 t.Fatalf("same image does not compare equal")
70 }
71 }
72
73 func TestEqualApproxPNG(t *testing.T) {
74 got, err := os.ReadFile("testdata/approx_got_golden.png")
75 if err != nil {
76 t.Fatal(err)
77 }
78
79 want, err := os.ReadFile("testdata/approx_want_golden.png")
80 if err != nil {
81 t.Fatal(err)
82 }
83
84 for _, tc := range []struct {
85 delta float64
86 ok bool
87 }{
88 {0, false},
89 {0.01, false},
90 {0.02, false},
91 {0.05, true},
92 {0.1, true},
93 {1, true},
94 } {
95 t.Run(fmt.Sprintf("delta=%g", tc.delta), func(t *testing.T) {
96 ok, err := EqualApprox("png", got, want, tc.delta)
97 if err != nil {
98 t.Fatalf("could not compare images: %+v", err)
99 }
100 if ok != tc.ok {
101 t.Fatalf("got=%v, want=%v", ok, tc.ok)
102 }
103 })
104 }
105 }
106
107 func TestEqualApprox(t *testing.T) {
108 read := func(name string) []byte {
109 raw, err := os.ReadFile(name)
110 if err != nil {
111 t.Fatalf("could not read file %q: %+v", name, err)
112 }
113 return raw
114 }
115
116 asPNG_RGBA64 := func(raw []byte) []byte {
117 src, _, err := image.Decode(bytes.NewReader(raw))
118 if err != nil {
119 t.Fatalf("could not decode image: %+v", err)
120 }
121 var (
122 bnds = src.Bounds()
123 dst = image.NewRGBA64(bnds)
124 out = new(bytes.Buffer)
125 )
126 draw.Draw(dst, bnds, src, image.Point{}, draw.Src)
127 err = png.Encode(out, dst)
128 if err != nil {
129 t.Fatalf("could not encode image: %+v", err)
130 }
131 return out.Bytes()
132 }
133
134 for _, tc := range []struct {
135 name string
136 img1 []byte
137 img2 []byte
138 delta float64
139 want bool
140 }{
141 {
142 name: "svg-ok",
143 img1: []byte("<svg></svg>"),
144 img2: []byte("<svg></svg>"),
145 want: true,
146 },
147 {
148 name: "svg-diff",
149 img1: []byte("<svg></svg>"),
150 img2: []byte("<svg>1</svg>"),
151 want: false,
152 },
153 {
154 name: "eps-ok",
155 img1: []byte("line1\nline2\nCreationDate:now\n"),
156 img2: []byte("line1\nline2\nCreationDate:later\n"),
157 want: true,
158 },
159 {
160 name: "eps-diff-1",
161 img1: []byte("line1\nline2\nCreationDate:now\n"),
162 img2: []byte("line1\nline2\nCreationDate:later"),
163 want: false,
164 },
165 {
166 name: "eps-diff-2",
167 img1: []byte("line1\nline2\nCreationDate:now\n"),
168 img2: []byte("line1\nline3\nCreationDate:later\n"),
169 want: false,
170 },
171 {
172 name: "pdf-ok",
173 img1: read("../vg/vgpdf/testdata/arc_golden.pdf"),
174 img2: read("../vg/vgpdf/testdata/arc_golden.pdf"),
175 want: true,
176 },
177 {
178 name: "pdf-diff",
179 img1: read("../vg/vgpdf/testdata/arc_golden.pdf"),
180 img2: read("../vg/vgpdf/testdata/issue540_golden.pdf"),
181 want: false,
182 },
183 {
184 name: "pdf-diff-2",
185 img1: read("../vg/vgpdf/testdata/arc_golden.pdf"),
186 img2: read("../vg/vgpdf/testdata/multipage_golden.pdf"),
187 want: false,
188 },
189 {
190 name: "png-ok",
191 img1: read("testdata/approx_got_golden.png"),
192 img2: read("testdata/approx_want_golden.png"),
193 delta: 0.1,
194 want: true,
195 },
196 {
197 name: "png-ok-rgba64",
198 img1: read("testdata/approx_got_golden.png"),
199 img2: asPNG_RGBA64(read("testdata/approx_want_golden.png")),
200 delta: 0.1,
201 want: true,
202 },
203 {
204 name: "png-ok-rgba64-2",
205 img1: read("testdata/approx_got_golden.png"),
206 img2: asPNG_RGBA64(read("testdata/approx_got_golden.png")),
207 delta: -10,
208 want: true,
209 },
210 {
211 name: "png-ok-rgba64-3",
212 img1: asPNG_RGBA64(read("testdata/approx_got_golden.png")),
213 img2: read("testdata/approx_got_golden.png"),
214 delta: 0,
215 want: true,
216 },
217 {
218 name: "png-diff-1",
219 img1: read("testdata/approx_got_golden.png"),
220 img2: read("testdata/approx_want_golden.png"),
221 delta: 0,
222 want: false,
223 },
224 {
225 name: "png-diff-2",
226 img1: read("testdata/approx_got_golden.png"),
227 img2: read("testdata/approx_want_golden.png"),
228 delta: 0.01,
229 want: false,
230 },
231 {
232 name: "png-diff-3",
233 img1: read("testdata/approx_got_golden.png"),
234 img2: read("testdata/good_golden.png"),
235 delta: 10,
236 want: false,
237 },
238 } {
239 t.Run(tc.name, func(t *testing.T) {
240 typ := tc.name[:strings.Index(tc.name, "-")]
241 got, err := EqualApprox(typ, tc.img1, tc.img2, tc.delta)
242 if err != nil {
243 t.Fatalf("could not run equal-approx: %+v", err)
244 }
245
246 if got != tc.want {
247 t.Fatalf("invalid equal-approx: got=%v, want=%v", got, tc.want)
248 }
249 })
250 }
251 }
252
View as plain text