mirror of
https://github.com/golang/go
synced 2024-11-19 21:14:43 -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.r = bufio.NewReader(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.loopCount = -1
|
||||||
|
|
||||||
err := d.readHeaderAndScreenDescriptor()
|
err := d.readHeaderAndScreenDescriptor()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// GIF represents the possibly multiple images stored in a GIF file.
|
||||||
type GIF struct {
|
type GIF struct {
|
||||||
Image []*image.Paletted // The successive images.
|
Image []*image.Paletted // The successive images.
|
||||||
Delay []int // The successive delay times, one per frame, in 100ths of a second.
|
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
|
// Disposal is the successive disposal methods, one per frame. For
|
||||||
// backwards compatibility, a nil Disposal is valid to pass to EncodeAll,
|
// backwards compatibility, a nil Disposal is valid to pass to EncodeAll,
|
||||||
// and implies that each frame's disposal method is 0 (no disposal
|
// and implies that each frame's disposal method is 0 (no disposal
|
||||||
|
@ -318,23 +318,62 @@ func TestTransparentPixelOutsidePaletteRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoopCount(t *testing.T) {
|
func TestLoopCount(t *testing.T) {
|
||||||
data := []byte("GIF89a000\x00000,0\x00\x00\x00\n\x00" +
|
testCases := []struct {
|
||||||
"\n\x00\x80000000\x02\b\xf01u\xb9\xfdal\x05\x00;")
|
name string
|
||||||
img, err := DecodeAll(bytes.NewReader(data))
|
data []byte
|
||||||
if err != nil {
|
loopCount int
|
||||||
t.Fatal("DecodeAll:", err)
|
}{
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
w := new(bytes.Buffer)
|
|
||||||
err = EncodeAll(w, img)
|
for _, tc := range testCases {
|
||||||
if err != nil {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Fatal("EncodeAll:", err)
|
img, err := DecodeAll(bytes.NewReader(tc.data))
|
||||||
}
|
if err != nil {
|
||||||
img1, err := DecodeAll(w)
|
t.Fatal("DecodeAll:", err)
|
||||||
if err != nil {
|
}
|
||||||
t.Fatal("DecodeAll:", err)
|
w := new(bytes.Buffer)
|
||||||
}
|
err = EncodeAll(w, img)
|
||||||
if img.LoopCount != img1.LoopCount {
|
if err != nil {
|
||||||
t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, img1.LoopCount)
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ func (e *encoder) writeHeader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add animation info if necessary.
|
// 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[0] = 0x21 // Extension Introducer.
|
||||||
e.buf[1] = 0xff // Application Label.
|
e.buf[1] = 0xff // Application Label.
|
||||||
e.buf[2] = 0x0b // Block Size.
|
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) {
|
if len(g.Image) != len(g.Delay) {
|
||||||
return errors.New("gif: mismatched image and delay lengths")
|
return errors.New("gif: mismatched image and delay lengths")
|
||||||
}
|
}
|
||||||
if g.LoopCount < 0 {
|
|
||||||
g.LoopCount = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
e := encoder{g: *g}
|
e := encoder{g: *g}
|
||||||
// The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
|
// The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
|
||||||
|
Loading…
Reference in New Issue
Block a user