diff options
author | Peter Teichman <pteichman@fastly.com> | 2018-02-12 19:23:31 +0000 |
---|---|---|
committer | Brad Fitzpatrick <bradfitz@golang.org> | 2018-02-13 20:25:49 +0000 |
commit | ecba3714a3291073a2cde989c255de568bd1d512 (patch) | |
tree | fd5a31573aac07b6db762d4b293cc9df373113cd /src/image | |
parent | caa7d854c885c2a37b3da5f16e18be4b65cd8e75 (diff) | |
download | go-ecba3714a3291073a2cde989c255de568bd1d512.tar.gz go-ecba3714a3291073a2cde989c255de568bd1d512.zip |
image/gif: support non-looping animated gifs (LoopCount=-1)
The Netscape looping application extension encodes how many
times the animation should restart, and if it's present
there is no way to signal that a GIF should play only once.
Use LoopCount=-1 to signal when a decoded GIF had no looping
extension, and update the encoder to omit that extension
block when LoopCount=-1.
Fixes #15768
GitHub-Last-Rev: 249744f0e28ef8907aa876070a102cb5493f5084
GitHub-Pull-Request: golang/go#23761
Change-Id: Ic915268505bf12bdad690b59148983a7d78d693b
Reviewed-on: https://go-review.googlesource.com/93076
Reviewed-by: Andrew Bonventre <andybons@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Andrew Bonventre <andybons@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/image')
-rw-r--r-- | src/image/gif/reader.go | 13 | ||||
-rw-r--r-- | src/image/gif/reader_test.go | 71 | ||||
-rw-r--r-- | src/image/gif/writer.go | 5 |
3 files changed, 66 insertions, 23 deletions
diff --git a/src/image/gif/reader.go b/src/image/gif/reader.go index c1c9562067..763146ecc4 100644 --- a/src/image/gif/reader.go +++ b/src/image/gif/reader.go @@ -224,6 +224,8 @@ func (d *decoder) decode(r io.Reader, configOnly, keepAllFrames bool) error { d.r = bufio.NewReader(r) } + d.loopCount = -1 + err := d.readHeaderAndScreenDescriptor() if err != nil { return err @@ -566,9 +568,14 @@ func Decode(r io.Reader) (image.Image, error) { // GIF represents the possibly multiple images stored in a GIF file. type GIF struct { - Image []*image.Paletted // The successive images. - Delay []int // The successive delay times, one per frame, in 100ths of a second. - LoopCount int // The loop count. + Image []*image.Paletted // The successive images. + Delay []int // The successive delay times, one per frame, in 100ths of a second. + // LoopCount controls the number of times an animation will be + // restarted during display. + // A LoopCount of 0 means to loop forever. + // A LoopCount of -1 means to show each frame only once. + // Otherwise, the animation is looped LoopCount+1 times. + LoopCount int // Disposal is the successive disposal methods, one per frame. For // backwards compatibility, a nil Disposal is valid to pass to EncodeAll, // and implies that each frame's disposal method is 0 (no disposal diff --git a/src/image/gif/reader_test.go b/src/image/gif/reader_test.go index 220e8f52d4..29f47b6c08 100644 --- a/src/image/gif/reader_test.go +++ b/src/image/gif/reader_test.go @@ -318,23 +318,62 @@ func TestTransparentPixelOutsidePaletteRange(t *testing.T) { } func TestLoopCount(t *testing.T) { - data := []byte("GIF89a000\x00000,0\x00\x00\x00\n\x00" + - "\n\x00\x80000000\x02\b\xf01u\xb9\xfdal\x05\x00;") - img, err := DecodeAll(bytes.NewReader(data)) - if err != nil { - t.Fatal("DecodeAll:", err) - } - w := new(bytes.Buffer) - err = EncodeAll(w, img) - if err != nil { - t.Fatal("EncodeAll:", err) - } - img1, err := DecodeAll(w) - if err != nil { - t.Fatal("DecodeAll:", err) + testCases := []struct { + name string + data []byte + loopCount int + }{ + { + "loopcount-missing", + []byte("GIF89a000\x00000" + + ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table + "\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 0 image data & trailer + -1, + }, + { + "loopcount-0", + []byte("GIF89a000\x00000" + + "!\xff\vNETSCAPE2.0\x03\x01\x00\x00\x00" + // loop count = 0 + ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table + "\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data + ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table + "\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer + 0, + }, + { + "loopcount-1", + []byte("GIF89a000\x00000" + + "!\xff\vNETSCAPE2.0\x03\x01\x01\x00\x00" + // loop count = 1 + ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table + "\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data + ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table + "\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer + 1, + }, } - if img.LoopCount != img1.LoopCount { - t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, img1.LoopCount) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + img, err := DecodeAll(bytes.NewReader(tc.data)) + if err != nil { + t.Fatal("DecodeAll:", err) + } + w := new(bytes.Buffer) + err = EncodeAll(w, img) + if err != nil { + t.Fatal("EncodeAll:", err) + } + img1, err := DecodeAll(w) + if err != nil { + t.Fatal("DecodeAll:", err) + } + if img.LoopCount != tc.loopCount { + t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, tc.loopCount) + } + if img.LoopCount != img1.LoopCount { + t.Errorf("loop count failed round-trip: %d vs %d", img.LoopCount, img1.LoopCount) + } + }) } } diff --git a/src/image/gif/writer.go b/src/image/gif/writer.go index f26af8be47..5e80feb33f 100644 --- a/src/image/gif/writer.go +++ b/src/image/gif/writer.go @@ -178,7 +178,7 @@ func (e *encoder) writeHeader() { } // Add animation info if necessary. - if len(e.g.Image) > 1 { + if len(e.g.Image) > 1 && e.g.LoopCount >= 0 { e.buf[0] = 0x21 // Extension Introducer. e.buf[1] = 0xff // Application Label. e.buf[2] = 0x0b // Block Size. @@ -377,9 +377,6 @@ func EncodeAll(w io.Writer, g *GIF) error { if len(g.Image) != len(g.Delay) { return errors.New("gif: mismatched image and delay lengths") } - if g.LoopCount < 0 { - g.LoopCount = 0 - } e := encoder{g: *g} // The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added |