mirror of
https://github.com/golang/go
synced 2024-11-19 18:54:41 -07:00
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: 249744f0e2
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>
This commit is contained in:
parent
caa7d854c8
commit
ecba3714a3
@ -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
|
||||
@ -568,7 +570,12 @@ func Decode(r io.Reader) (image.Image, error) {
|
||||
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.
|
||||
// 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
|
||||
|
@ -318,9 +318,43 @@ 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))
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@ -333,8 +367,13 @@ func TestLoopCount(t *testing.T) {
|
||||
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 mismatch: %d vs %d", img.LoopCount, img1.LoopCount)
|
||||
t.Errorf("loop count failed round-trip: %d vs %d", img.LoopCount, img1.LoopCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user