Commit 6abfdc3f authored by Nigel Tao's avatar Nigel Tao

image/gif: check that individual frame's bounds are within the overall

GIF's bounds.

Also change the implicit Config Width and Height to be the
Rectangle.Max, not the Dx and Dy, of the first frame's bounds. For the
case where the first frame's bounds is something like (5,5)-(8,8), the
overall width should be 8, not 3.

Change-Id: I3affc484f5e32941a36f15517a92ca8d189d9c22
Reviewed-on: https://go-review.googlesource.com/9465Reviewed-by: default avatarRob Pike <r@golang.org>
parent a0cff298
...@@ -430,10 +430,13 @@ type GIF struct { ...@@ -430,10 +430,13 @@ type GIF struct {
Disposal []byte Disposal []byte
// Config is the global color map (palette), width and height. A nil or // Config is the global color map (palette), width and height. A nil or
// empty-color.Palette Config.ColorModel means that each frame has its own // empty-color.Palette Config.ColorModel means that each frame has its own
// color map and there is no global color map. For backwards compatibility, // color map and there is no global color map. Each frame's bounds must be
// a zero-valued Config is valid to pass to EncodeAll, and implies that the // within the rectangle defined by the two points (0, 0) and (Config.Width,
// overall GIF's width and height equals the first frame's width and // Config.Height).
// height. //
// For backwards compatibility, a zero-valued Config is valid to pass to
// EncodeAll, and implies that the overall GIF's width and height equals
// the first frame's bounds' Rectangle.Max point.
Config image.Config Config image.Config
// BackgroundIndex is the background index in the global color map, for use // BackgroundIndex is the background index in the global color map, for use
// with the DisposalBackground disposal method. // with the DisposalBackground disposal method.
......
...@@ -191,6 +191,10 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) ...@@ -191,6 +191,10 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte)
e.err = errors.New("gif: image block is too large to encode") e.err = errors.New("gif: image block is too large to encode")
return return
} }
if !b.In(image.Rectangle{Max: image.Point{e.g.Config.Width, e.g.Config.Height}}) {
e.err = errors.New("gif: image block is out of bounds")
return
}
transparentIndex := -1 transparentIndex := -1
for i, c := range pm.Palette { for i, c := range pm.Palette {
...@@ -297,9 +301,9 @@ func EncodeAll(w io.Writer, g *GIF) error { ...@@ -297,9 +301,9 @@ func EncodeAll(w io.Writer, g *GIF) error {
return errors.New("gif: mismatched image and disposal lengths") return errors.New("gif: mismatched image and disposal lengths")
} }
if e.g.Config == (image.Config{}) { if e.g.Config == (image.Config{}) {
b := g.Image[0].Bounds() p := g.Image[0].Bounds().Max
e.g.Config.Width = b.Dx() e.g.Config.Width = p.X
e.g.Config.Height = b.Dy() e.g.Config.Height = p.Y
} else if e.g.Config.ColorModel != nil { } else if e.g.Config.ColorModel != nil {
if _, ok := e.g.Config.ColorModel.(color.Palette); !ok { if _, ok := e.g.Config.ColorModel.(color.Palette); !ok {
return errors.New("gif: GIF color model must be a color.Palette") return errors.New("gif: GIF color model must be a color.Palette")
......
...@@ -297,8 +297,66 @@ func TestEncodeZeroGIF(t *testing.T) { ...@@ -297,8 +297,66 @@ func TestEncodeZeroGIF(t *testing.T) {
} }
} }
// TODO: add test for when individual frames are out of the global bounds. func TestEncodeFrameOutOfBounds(t *testing.T) {
// TODO: add test for when the first frame's bounds are not the same as the global bounds. images := []*image.Paletted{
image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9),
image.NewPaletted(image.Rect(2, 2, 8, 8), palette.Plan9),
image.NewPaletted(image.Rect(3, 3, 4, 4), palette.Plan9),
}
for _, upperBound := range []int{6, 10} {
g := &GIF{
Image: images,
Delay: make([]int, len(images)),
Disposal: make([]byte, len(images)),
Config: image.Config{
Width: upperBound,
Height: upperBound,
},
}
err := EncodeAll(ioutil.Discard, g)
if upperBound >= 8 {
if err != nil {
t.Errorf("upperBound=%d: %v", upperBound, err)
}
} else {
if err == nil {
t.Errorf("upperBound=%d: got nil error, want non-nil", upperBound)
}
}
}
}
func TestEncodeImplicitConfigSize(t *testing.T) {
// For backwards compatibility for Go 1.4 and earlier code, the Config
// field is optional, and if zero, the width and height is implied by the
// first (and in this case only) frame's width and height.
//
// A Config only specifies a width and height (two integers) while an
// image.Image's Bounds method returns an image.Rectangle (four integers).
// For a gif.GIF, the overall bounds' top-left point is always implicitly
// (0, 0), and any frame whose bounds have a negative X or Y will be
// outside those overall bounds, so encoding should fail.
for _, lowerBound := range []int{-1, 0, 1} {
images := []*image.Paletted{
image.NewPaletted(image.Rect(lowerBound, lowerBound, 4, 4), palette.Plan9),
}
g := &GIF{
Image: images,
Delay: make([]int, len(images)),
}
err := EncodeAll(ioutil.Discard, g)
if lowerBound >= 0 {
if err != nil {
t.Errorf("lowerBound=%d: %v", lowerBound, err)
}
} else {
if err == nil {
t.Errorf("lowerBound=%d: got nil error, want non-nil", lowerBound)
}
}
}
}
// TODO: add test for when a frame has the same color map (palette) as the global one. // TODO: add test for when a frame has the same color map (palette) as the global one.
func BenchmarkEncode(b *testing.B) { func BenchmarkEncode(b *testing.B) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment