| // Copyright 2013 The Go 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 gif |
| |
| import ( |
| "bytes" |
| "image" |
| "image/color" |
| _ "image/png" |
| "io/ioutil" |
| "math/rand" |
| "os" |
| "testing" |
| ) |
| |
| func readImg(filename string) (image.Image, error) { |
| f, err := os.Open(filename) |
| if err != nil { |
| return nil, err |
| } |
| defer f.Close() |
| m, _, err := image.Decode(f) |
| return m, err |
| } |
| |
| func readGIF(filename string) (*GIF, error) { |
| f, err := os.Open(filename) |
| if err != nil { |
| return nil, err |
| } |
| defer f.Close() |
| return DecodeAll(f) |
| } |
| |
| func delta(u0, u1 uint32) int64 { |
| d := int64(u0) - int64(u1) |
| if d < 0 { |
| return -d |
| } |
| return d |
| } |
| |
| // averageDelta returns the average delta in RGB space. The two images must |
| // have the same bounds. |
| func averageDelta(m0, m1 image.Image) int64 { |
| b := m0.Bounds() |
| var sum, n int64 |
| for y := b.Min.Y; y < b.Max.Y; y++ { |
| for x := b.Min.X; x < b.Max.X; x++ { |
| c0 := m0.At(x, y) |
| c1 := m1.At(x, y) |
| r0, g0, b0, _ := c0.RGBA() |
| r1, g1, b1, _ := c1.RGBA() |
| sum += delta(r0, r1) |
| sum += delta(g0, g1) |
| sum += delta(b0, b1) |
| n += 3 |
| } |
| } |
| return sum / n |
| } |
| |
| var testCase = []struct { |
| filename string |
| tolerance int64 |
| }{ |
| {"../testdata/video-001.png", 1 << 12}, |
| {"../testdata/video-001.gif", 0}, |
| {"../testdata/video-001.interlaced.gif", 0}, |
| } |
| |
| func TestWriter(t *testing.T) { |
| for _, tc := range testCase { |
| m0, err := readImg(tc.filename) |
| if err != nil { |
| t.Error(tc.filename, err) |
| continue |
| } |
| var buf bytes.Buffer |
| err = Encode(&buf, m0, nil) |
| if err != nil { |
| t.Error(tc.filename, err) |
| continue |
| } |
| m1, err := Decode(&buf) |
| if err != nil { |
| t.Error(tc.filename, err) |
| continue |
| } |
| if m0.Bounds() != m1.Bounds() { |
| t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds()) |
| continue |
| } |
| // Compare the average delta to the tolerance level. |
| avgDelta := averageDelta(m0, m1) |
| if avgDelta > tc.tolerance { |
| t.Errorf("%s: average delta is too high. expected: %d, got %d", tc.filename, tc.tolerance, avgDelta) |
| continue |
| } |
| } |
| } |
| |
| func TestSubImage(t *testing.T) { |
| m0, err := readImg("../testdata/video-001.gif") |
| if err != nil { |
| t.Fatalf("readImg: %v", err) |
| } |
| m0 = m0.(*image.Paletted).SubImage(image.Rect(0, 0, 50, 30)) |
| var buf bytes.Buffer |
| err = Encode(&buf, m0, nil) |
| if err != nil { |
| t.Fatalf("Encode: %v", err) |
| } |
| m1, err := Decode(&buf) |
| if err != nil { |
| t.Fatalf("Decode: %v", err) |
| } |
| if m0.Bounds() != m1.Bounds() { |
| t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds()) |
| } |
| if averageDelta(m0, m1) != 0 { |
| t.Fatalf("images differ") |
| } |
| } |
| |
| var frames = []string{ |
| "../testdata/video-001.gif", |
| "../testdata/video-005.gray.gif", |
| } |
| |
| func TestEncodeAll(t *testing.T) { |
| g0 := &GIF{ |
| Image: make([]*image.Paletted, len(frames)), |
| Delay: make([]int, len(frames)), |
| LoopCount: 5, |
| } |
| for i, f := range frames { |
| m, err := readGIF(f) |
| if err != nil { |
| t.Fatal(f, err) |
| } |
| g0.Image[i] = m.Image[0] |
| } |
| var buf bytes.Buffer |
| if err := EncodeAll(&buf, g0); err != nil { |
| t.Fatal("EncodeAll:", err) |
| } |
| g1, err := DecodeAll(&buf) |
| if err != nil { |
| t.Fatal("DecodeAll:", err) |
| } |
| if g0.LoopCount != g1.LoopCount { |
| t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount) |
| } |
| for i := range g0.Image { |
| m0, m1 := g0.Image[i], g1.Image[i] |
| if m0.Bounds() != m1.Bounds() { |
| t.Errorf("%s, bounds differ: %v and %v", frames[i], m0.Bounds(), m1.Bounds()) |
| } |
| d0, d1 := g0.Delay[i], g1.Delay[i] |
| if d0 != d1 { |
| t.Errorf("%s: delay values differ: %d and %d", frames[i], d0, d1) |
| } |
| } |
| |
| g1.Delay = make([]int, 1) |
| if err := EncodeAll(ioutil.Discard, g1); err == nil { |
| t.Error("expected error from mismatched delay and image slice lengths") |
| } |
| if err := EncodeAll(ioutil.Discard, &GIF{}); err == nil { |
| t.Error("expected error from providing empty gif") |
| } |
| } |
| |
| func BenchmarkEncode(b *testing.B) { |
| b.StopTimer() |
| |
| bo := image.Rect(0, 0, 640, 480) |
| rnd := rand.New(rand.NewSource(123)) |
| |
| // Restrict to a 256-color paletted image to avoid quantization path. |
| palette := make(color.Palette, 256) |
| for i := range palette { |
| palette[i] = color.RGBA{ |
| uint8(rnd.Intn(256)), |
| uint8(rnd.Intn(256)), |
| uint8(rnd.Intn(256)), |
| 255, |
| } |
| } |
| img := image.NewPaletted(image.Rect(0, 0, 640, 480), palette) |
| for y := bo.Min.Y; y < bo.Max.Y; y++ { |
| for x := bo.Min.X; x < bo.Max.X; x++ { |
| img.Set(x, y, palette[rnd.Intn(256)]) |
| } |
| } |
| |
| b.SetBytes(640 * 480 * 4) |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| Encode(ioutil.Discard, img, nil) |
| } |
| } |
| |
| func BenchmarkQuantizedEncode(b *testing.B) { |
| b.StopTimer() |
| img := image.NewRGBA(image.Rect(0, 0, 640, 480)) |
| bo := img.Bounds() |
| rnd := rand.New(rand.NewSource(123)) |
| for y := bo.Min.Y; y < bo.Max.Y; y++ { |
| for x := bo.Min.X; x < bo.Max.X; x++ { |
| img.SetRGBA(x, y, color.RGBA{ |
| uint8(rnd.Intn(256)), |
| uint8(rnd.Intn(256)), |
| uint8(rnd.Intn(256)), |
| 255, |
| }) |
| } |
| } |
| b.SetBytes(640 * 480 * 4) |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| Encode(ioutil.Discard, img, nil) |
| } |
| } |