summaryrefslogtreecommitdiff
path: root/libgo/go/image/gif
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/image/gif')
-rw-r--r--libgo/go/image/gif/reader.go159
-rw-r--r--libgo/go/image/gif/reader_test.go49
-rw-r--r--libgo/go/image/gif/writer.go127
-rw-r--r--libgo/go/image/gif/writer_test.go280
4 files changed, 495 insertions, 120 deletions
diff --git a/libgo/go/image/gif/reader.go b/libgo/go/image/gif/reader.go
index 5a863e204f3..6a133124ad5 100644
--- a/libgo/go/image/gif/reader.go
+++ b/libgo/go/image/gif/reader.go
@@ -32,15 +32,20 @@ type reader interface {
// Masks etc.
const (
// Fields.
- fColorMapFollows = 1 << 7
-
- // Image fields.
- ifLocalColorTable = 1 << 7
- ifInterlace = 1 << 6
- ifPixelSizeMask = 7
+ fColorTable = 1 << 7
+ fInterlace = 1 << 6
+ fColorTableBitsMask = 7
// Graphic control flags.
gcTransparentColorSet = 1 << 0
+ gcDisposalMethodMask = 7 << 2
+)
+
+// Disposal Methods.
+const (
+ DisposalNone = 0x01
+ DisposalBackground = 0x02
+ DisposalPrevious = 0x03
)
// Section indicators.
@@ -66,14 +71,10 @@ type decoder struct {
vers string
width int
height int
- flags byte
- headerFields byte
- backgroundIndex byte
loopCount int
delayTime int
-
- // Unused from header.
- aspect byte
+ backgroundIndex byte
+ disposalMethod byte
// From image descriptor.
imageFields byte
@@ -83,13 +84,13 @@ type decoder struct {
hasTransparentIndex bool
// Computed.
- pixelSize uint
- globalColorMap color.Palette
+ globalColorTable color.Palette
// Used when decoding.
- delay []int
- image []*image.Paletted
- tmp [1024]byte // must be at least 768 so we can read color map
+ delay []int
+ disposal []byte
+ image []*image.Paletted
+ tmp [1024]byte // must be at least 768 so we can read color table
}
// blockReader parses the block structure of GIF image data, which
@@ -122,7 +123,7 @@ func (b *blockReader) Read(p []byte) (int, error) {
b.err = io.EOF
return 0, b.err
}
- b.slice = b.tmp[0:blockLen]
+ b.slice = b.tmp[:blockLen]
if _, b.err = io.ReadFull(b.r, b.slice); b.err != nil {
return 0, b.err
}
@@ -149,12 +150,6 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
return nil
}
- if d.headerFields&fColorMapFollows != 0 {
- if d.globalColorMap, err = d.readColorMap(); err != nil {
- return err
- }
- }
-
for {
c, err := d.r.ReadByte()
if err != nil {
@@ -171,19 +166,22 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
if err != nil {
return err
}
- useLocalColorMap := d.imageFields&fColorMapFollows != 0
- if useLocalColorMap {
- m.Palette, err = d.readColorMap()
+ useLocalColorTable := d.imageFields&fColorTable != 0
+ if useLocalColorTable {
+ m.Palette, err = d.readColorTable(d.imageFields)
if err != nil {
return err
}
} else {
- m.Palette = d.globalColorMap
+ if d.globalColorTable == nil {
+ return errors.New("gif: no color table")
+ }
+ m.Palette = d.globalColorTable
}
if d.hasTransparentIndex && int(d.transparentIndex) < len(m.Palette) {
- if !useLocalColorMap {
- // Clone the global color map.
- m.Palette = append(color.Palette(nil), d.globalColorMap...)
+ if !useLocalColorTable {
+ // Clone the global color table.
+ m.Palette = append(color.Palette(nil), d.globalColorTable...)
}
m.Palette[d.transparentIndex] = color.RGBA{}
}
@@ -204,9 +202,18 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
}
return errNotEnough
}
- // Both lzwr and br should be exhausted. Reading from them
- // should yield (0, io.EOF).
- if n, err := lzwr.Read(d.tmp[:1]); n != 0 || err != io.EOF {
+ // Both lzwr and br should be exhausted. Reading from them should
+ // yield (0, io.EOF).
+ //
+ // The spec (Appendix F - Compression), says that "An End of
+ // Information code... must be the last code output by the encoder
+ // for an image". In practice, though, giflib (a widely used C
+ // library) does not enforce this, so we also accept lzwr returning
+ // io.ErrUnexpectedEOF (meaning that the encoded stream hit io.EOF
+ // before the LZW decoder saw an explict end code), provided that
+ // the io.ReadFull call above successfully read len(m.Pix) bytes.
+ // See https://golang.org/issue/9856 for an example GIF.
+ if n, err := lzwr.Read(d.tmp[:1]); n != 0 || (err != io.EOF && err != io.ErrUnexpectedEOF) {
if err != nil {
return err
}
@@ -229,12 +236,13 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
}
// Undo the interlacing if necessary.
- if d.imageFields&ifInterlace != 0 {
+ if d.imageFields&fInterlace != 0 {
uninterlace(m)
}
d.image = append(d.image, m)
d.delay = append(d.delay, d.delayTime)
+ d.disposal = append(d.disposal, d.disposalMethod)
// The GIF89a spec, Section 23 (Graphic Control Extension) says:
// "The scope of this extension is the first graphic rendering block
// to follow." We therefore reset the GCE fields to zero.
@@ -254,44 +262,39 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
}
func (d *decoder) readHeaderAndScreenDescriptor() error {
- _, err := io.ReadFull(d.r, d.tmp[0:13])
+ _, err := io.ReadFull(d.r, d.tmp[:13])
if err != nil {
return err
}
- d.vers = string(d.tmp[0:6])
+ d.vers = string(d.tmp[:6])
if d.vers != "GIF87a" && d.vers != "GIF89a" {
return fmt.Errorf("gif: can't recognize format %s", d.vers)
}
d.width = int(d.tmp[6]) + int(d.tmp[7])<<8
d.height = int(d.tmp[8]) + int(d.tmp[9])<<8
- d.headerFields = d.tmp[10]
- d.backgroundIndex = d.tmp[11]
- d.aspect = d.tmp[12]
- d.loopCount = -1
- d.pixelSize = uint(d.headerFields&7) + 1
+ if fields := d.tmp[10]; fields&fColorTable != 0 {
+ d.backgroundIndex = d.tmp[11]
+ // readColorTable overwrites the contents of d.tmp, but that's OK.
+ if d.globalColorTable, err = d.readColorTable(fields); err != nil {
+ return err
+ }
+ }
+ // d.tmp[12] is the Pixel Aspect Ratio, which is ignored.
return nil
}
-func (d *decoder) readColorMap() (color.Palette, error) {
- if d.pixelSize > 8 {
- return nil, fmt.Errorf("gif: can't handle %d bits per pixel", d.pixelSize)
- }
- numColors := 1 << d.pixelSize
- if d.imageFields&ifLocalColorTable != 0 {
- numColors = 1 << ((d.imageFields & ifPixelSizeMask) + 1)
- }
- numValues := 3 * numColors
- _, err := io.ReadFull(d.r, d.tmp[0:numValues])
+func (d *decoder) readColorTable(fields byte) (color.Palette, error) {
+ n := 1 << (1 + uint(fields&fColorTableBitsMask))
+ _, err := io.ReadFull(d.r, d.tmp[:3*n])
if err != nil {
- return nil, fmt.Errorf("gif: short read on color map: %s", err)
+ return nil, fmt.Errorf("gif: short read on color table: %s", err)
}
- colorMap := make(color.Palette, numColors)
- j := 0
- for i := range colorMap {
- colorMap[i] = color.RGBA{d.tmp[j+0], d.tmp[j+1], d.tmp[j+2], 0xFF}
+ j, p := 0, make(color.Palette, n)
+ for i := range p {
+ p[i] = color.RGBA{d.tmp[j+0], d.tmp[j+1], d.tmp[j+2], 0xFF}
j += 3
}
- return colorMap, nil
+ return p, nil
}
func (d *decoder) readExtension() error {
@@ -318,7 +321,7 @@ func (d *decoder) readExtension() error {
return fmt.Errorf("gif: unknown extension 0x%.2x", extension)
}
if size > 0 {
- if _, err := io.ReadFull(d.r, d.tmp[0:size]); err != nil {
+ if _, err := io.ReadFull(d.r, d.tmp[:size]); err != nil {
return err
}
}
@@ -343,12 +346,13 @@ func (d *decoder) readExtension() error {
}
func (d *decoder) readGraphicControl() error {
- if _, err := io.ReadFull(d.r, d.tmp[0:6]); err != nil {
+ if _, err := io.ReadFull(d.r, d.tmp[:6]); err != nil {
return fmt.Errorf("gif: can't read graphic control: %s", err)
}
- d.flags = d.tmp[1]
+ flags := d.tmp[1]
+ d.disposalMethod = (flags & gcDisposalMethodMask) >> 2
d.delayTime = int(d.tmp[2]) | int(d.tmp[3])<<8
- if d.flags&gcTransparentColorSet != 0 {
+ if flags&gcTransparentColorSet != 0 {
d.transparentIndex = d.tmp[4]
d.hasTransparentIndex = true
}
@@ -356,7 +360,7 @@ func (d *decoder) readGraphicControl() error {
}
func (d *decoder) newImageFromDescriptor() (*image.Paletted, error) {
- if _, err := io.ReadFull(d.r, d.tmp[0:9]); err != nil {
+ if _, err := io.ReadFull(d.r, d.tmp[:9]); err != nil {
return nil, fmt.Errorf("gif: can't read image descriptor: %s", err)
}
left := int(d.tmp[0]) + int(d.tmp[1])<<8
@@ -380,7 +384,7 @@ func (d *decoder) readBlock() (int, error) {
if n == 0 || err != nil {
return 0, err
}
- return io.ReadFull(d.r, d.tmp[0:n])
+ return io.ReadFull(d.r, d.tmp[:n])
}
// interlaceScan defines the ordering for a pass of the interlace algorithm.
@@ -429,6 +433,24 @@ 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.
+ // 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
+ // specified).
+ Disposal []byte
+ // Config is the global color table (palette), width and height. A nil or
+ // empty-color.Palette Config.ColorModel means that each frame has its own
+ // color table and there is no global color table. Each frame's bounds must
+ // be within the rectangle defined by the two points (0, 0) and
+ // (Config.Width, Config.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
+ // BackgroundIndex is the background index in the global color table, for
+ // use with the DisposalBackground disposal method.
+ BackgroundIndex byte
}
// DecodeAll reads a GIF image from r and returns the sequential frames
@@ -442,6 +464,13 @@ func DecodeAll(r io.Reader) (*GIF, error) {
Image: d.image,
LoopCount: d.loopCount,
Delay: d.delay,
+ Disposal: d.disposal,
+ Config: image.Config{
+ ColorModel: d.globalColorTable,
+ Width: d.width,
+ Height: d.height,
+ },
+ BackgroundIndex: d.backgroundIndex,
}
return gif, nil
}
@@ -454,7 +483,7 @@ func DecodeConfig(r io.Reader) (image.Config, error) {
return image.Config{}, err
}
return image.Config{
- ColorModel: d.globalColorMap,
+ ColorModel: d.globalColorTable,
Width: d.width,
Height: d.height,
}, nil
diff --git a/libgo/go/image/gif/reader_test.go b/libgo/go/image/gif/reader_test.go
index 7b6f504367c..c294195b6f7 100644
--- a/libgo/go/image/gif/reader_test.go
+++ b/libgo/go/image/gif/reader_test.go
@@ -17,8 +17,8 @@ import (
const (
headerStr = "GIF89a" +
"\x02\x00\x01\x00" + // width=2, height=1
- "\x80\x00\x00" // headerFields=(a color map of 2 pixels), backgroundIndex, aspect
- paletteStr = "\x10\x20\x30\x40\x50\x60" // the color map, also known as a palette
+ "\x80\x00\x00" // headerFields=(a color table of 2 pixels), backgroundIndex, aspect
+ paletteStr = "\x10\x20\x30\x40\x50\x60" // the color table, also known as a palette
trailerStr = "\x3b"
)
@@ -141,7 +141,7 @@ var testGIF = []byte{
'G', 'I', 'F', '8', '9', 'a',
1, 0, 1, 0, // w=1, h=1 (6)
128, 0, 0, // headerFields, bg, aspect (10)
- 0, 0, 0, 1, 1, 1, // color map and graphics control (13)
+ 0, 0, 0, 1, 1, 1, // color table and graphics control (13)
0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, // (19)
// frame 1 (0,0 - 1,1)
0x2c,
@@ -200,22 +200,26 @@ func TestNoPalette(t *testing.T) {
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
// Encode the pixels: neither is in range, because there is no palette.
- pix := []byte{0, 128}
+ pix := []byte{0, 3}
enc := &bytes.Buffer{}
w := lzw.NewWriter(enc, lzw.LSB, 2)
- w.Write(pix)
- w.Close()
+ if _, err := w.Write(pix); err != nil {
+ t.Fatalf("Write: %v", err)
+ }
+ if err := w.Close(); err != nil {
+ t.Fatalf("Close: %v", err)
+ }
b.WriteByte(byte(len(enc.Bytes())))
b.Write(enc.Bytes())
b.WriteByte(0x00) // An empty block signifies the end of the image data.
b.WriteString(trailerStr)
- try(t, b.Bytes(), "gif: invalid pixel value")
+ try(t, b.Bytes(), "gif: no color table")
}
func TestPixelOutsidePaletteRange(t *testing.T) {
- for _, pval := range []byte{0, 1, 2, 3, 255} {
+ for _, pval := range []byte{0, 1, 2, 3} {
b := &bytes.Buffer{}
// Manufacture a GIF with a 2 color palette.
@@ -229,8 +233,12 @@ func TestPixelOutsidePaletteRange(t *testing.T) {
pix := []byte{pval, pval}
enc := &bytes.Buffer{}
w := lzw.NewWriter(enc, lzw.LSB, 2)
- w.Write(pix)
- w.Close()
+ if _, err := w.Write(pix); err != nil {
+ t.Fatalf("Write: %v", err)
+ }
+ if err := w.Close(); err != nil {
+ t.Fatalf("Close: %v", err)
+ }
b.WriteByte(byte(len(enc.Bytes())))
b.Write(enc.Bytes())
b.WriteByte(0x00) // An empty block signifies the end of the image data.
@@ -245,3 +253,24 @@ func TestPixelOutsidePaletteRange(t *testing.T) {
try(t, b.Bytes(), want)
}
}
+
+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)
+ }
+ if img.LoopCount != img1.LoopCount {
+ t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, img1.LoopCount)
+ }
+}
diff --git a/libgo/go/image/gif/writer.go b/libgo/go/image/gif/writer.go
index 49abde704c8..dd317901d48 100644
--- a/libgo/go/image/gif/writer.go
+++ b/libgo/go/image/gif/writer.go
@@ -6,6 +6,7 @@ package gif
import (
"bufio"
+ "bytes"
"compress/lzw"
"errors"
"image"
@@ -52,9 +53,13 @@ type encoder struct {
w writer
err error
// g is a reference to the data that is being encoded.
- g *GIF
- // buf is a scratch buffer. It must be at least 768 so we can write the color map.
- buf [1024]byte
+ g GIF
+ // globalCT is the size in bytes of the global color table.
+ globalCT int
+ // buf is a scratch buffer. It must be at least 256 for the blockWriter.
+ buf [256]byte
+ globalColorTable [3 * 256]byte
+ localColorTable [3 * 256]byte
}
// blockWriter writes the block structure of GIF image data, which
@@ -116,18 +121,27 @@ func (e *encoder) writeHeader() {
return
}
- pm := e.g.Image[0]
// Logical screen width and height.
- writeUint16(e.buf[0:2], uint16(pm.Bounds().Dx()))
- writeUint16(e.buf[2:4], uint16(pm.Bounds().Dy()))
+ writeUint16(e.buf[0:2], uint16(e.g.Config.Width))
+ writeUint16(e.buf[2:4], uint16(e.g.Config.Height))
e.write(e.buf[:4])
- // All frames have a local color table, so a global color table
- // is not needed.
- e.buf[0] = 0x00
- e.buf[1] = 0x00 // Background Color Index.
- e.buf[2] = 0x00 // Pixel Aspect Ratio.
- e.write(e.buf[:3])
+ if p, ok := e.g.Config.ColorModel.(color.Palette); ok && len(p) > 0 {
+ paddedSize := log2(len(p)) // Size of Global Color Table: 2^(1+n).
+ e.buf[0] = fColorTable | uint8(paddedSize)
+ e.buf[1] = e.g.BackgroundIndex
+ e.buf[2] = 0x00 // Pixel Aspect Ratio.
+ e.write(e.buf[:3])
+ e.globalCT = encodeColorTable(e.globalColorTable[:], p, paddedSize)
+ e.write(e.globalColorTable[:e.globalCT])
+ } else {
+ // All frames have a local color table, so a global color table
+ // is not needed.
+ e.buf[0] = 0x00
+ e.buf[1] = 0x00 // Background Color Index.
+ e.buf[2] = 0x00 // Pixel Aspect Ratio.
+ e.write(e.buf[:3])
+ }
// Add animation info if necessary.
if len(e.g.Image) > 1 {
@@ -147,28 +161,25 @@ func (e *encoder) writeHeader() {
}
}
-func (e *encoder) writeColorTable(p color.Palette, size int) {
- if e.err != nil {
- return
- }
-
- for i := 0; i < log2Lookup[size]; i++ {
+func encodeColorTable(dst []byte, p color.Palette, size int) int {
+ n := log2Lookup[size]
+ for i := 0; i < n; i++ {
if i < len(p) {
r, g, b, _ := p[i].RGBA()
- e.buf[3*i+0] = uint8(r >> 8)
- e.buf[3*i+1] = uint8(g >> 8)
- e.buf[3*i+2] = uint8(b >> 8)
+ dst[3*i+0] = uint8(r >> 8)
+ dst[3*i+1] = uint8(g >> 8)
+ dst[3*i+2] = uint8(b >> 8)
} else {
// Pad with black.
- e.buf[3*i+0] = 0x00
- e.buf[3*i+1] = 0x00
- e.buf[3*i+2] = 0x00
+ dst[3*i+0] = 0x00
+ dst[3*i+1] = 0x00
+ dst[3*i+2] = 0x00
}
}
- e.write(e.buf[:3*log2Lookup[size]])
+ return 3 * n
}
-func (e *encoder) writeImageBlock(pm *image.Paletted, delay int) {
+func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) {
if e.err != nil {
return
}
@@ -179,10 +190,14 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int) {
}
b := pm.Bounds()
- if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 || b.Min.X < 0 || b.Min.X >= 1<<16 || b.Min.Y < 0 || b.Min.Y >= 1<<16 {
+ if b.Min.X < 0 || b.Max.X >= 1<<16 || b.Min.Y < 0 || b.Max.Y >= 1<<16 {
e.err = errors.New("gif: image block is too large to encode")
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
for i, c := range pm.Palette {
@@ -192,14 +207,14 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int) {
}
}
- if delay > 0 || transparentIndex != -1 {
+ if delay > 0 || disposal != 0 || transparentIndex != -1 {
e.buf[0] = sExtension // Extension Introducer.
e.buf[1] = gcLabel // Graphic Control Label.
e.buf[2] = gcBlockSize // Block Size.
if transparentIndex != -1 {
- e.buf[3] = 0x01
+ e.buf[3] = 0x01 | disposal<<2
} else {
- e.buf[3] = 0x00
+ e.buf[3] = 0x00 | disposal<<2
}
writeUint16(e.buf[4:6], uint16(delay)) // Delay Time (1/100ths of a second)
@@ -220,11 +235,15 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int) {
e.write(e.buf[:9])
paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n).
- // Interlacing is not supported.
- e.writeByte(0x80 | uint8(paddedSize))
-
- // Local Color Table.
- e.writeColorTable(pm.Palette, paddedSize)
+ ct := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize)
+ if ct != e.globalCT || !bytes.Equal(e.globalColorTable[:ct], e.localColorTable[:ct]) {
+ // Use a local color table.
+ e.writeByte(fColorTable | uint8(paddedSize))
+ e.write(e.localColorTable[:ct])
+ } else {
+ // Use the global color table.
+ e.writeByte(0)
+ }
litWidth := paddedSize + 1
if litWidth < 2 {
@@ -281,7 +300,23 @@ func EncodeAll(w io.Writer, g *GIF) error {
g.LoopCount = 0
}
- e := encoder{g: g}
+ e := encoder{g: *g}
+ // The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
+ // in Go 1.5. Valid Go 1.4 code, such as when the Disposal field is omitted
+ // in a GIF struct literal, should still produce valid GIFs.
+ if e.g.Disposal != nil && len(e.g.Image) != len(e.g.Disposal) {
+ return errors.New("gif: mismatched image and disposal lengths")
+ }
+ if e.g.Config == (image.Config{}) {
+ p := g.Image[0].Bounds().Max
+ e.g.Config.Width = p.X
+ e.g.Config.Height = p.Y
+ } else if e.g.Config.ColorModel != nil {
+ if _, ok := e.g.Config.ColorModel.(color.Palette); !ok {
+ return errors.New("gif: GIF color model must be a color.Palette")
+ }
+ }
+
if ww, ok := w.(writer); ok {
e.w = ww
} else {
@@ -290,7 +325,11 @@ func EncodeAll(w io.Writer, g *GIF) error {
e.writeHeader()
for i, pm := range g.Image {
- e.writeImageBlock(pm, g.Delay[i])
+ disposal := uint8(0)
+ if g.Disposal != nil {
+ disposal = g.Disposal[i]
+ }
+ e.writeImageBlock(pm, g.Delay[i], disposal)
}
e.writeByte(sTrailer)
e.flush()
@@ -326,8 +365,22 @@ func Encode(w io.Writer, m image.Image, o *Options) error {
opts.Drawer.Draw(pm, b, m, image.ZP)
}
+ // When calling Encode instead of EncodeAll, the single-frame image is
+ // translated such that its top-left corner is (0, 0), so that the single
+ // frame completely fills the overall GIF's bounds.
+ if pm.Rect.Min != (image.Point{}) {
+ dup := *pm
+ dup.Rect = dup.Rect.Sub(dup.Rect.Min)
+ pm = &dup
+ }
+
return EncodeAll(w, &GIF{
Image: []*image.Paletted{pm},
Delay: []int{0},
+ Config: image.Config{
+ ColorModel: pm.Palette,
+ Width: b.Dx(),
+ Height: b.Dy(),
+ },
})
}
diff --git a/libgo/go/image/gif/writer_test.go b/libgo/go/image/gif/writer_test.go
index 93306ffdb34..db61a5c3c2e 100644
--- a/libgo/go/image/gif/writer_test.go
+++ b/libgo/go/image/gif/writer_test.go
@@ -8,10 +8,12 @@ import (
"bytes"
"image"
"image/color"
+ "image/color/palette"
_ "image/png"
"io/ioutil"
"math/rand"
"os"
+ "reflect"
"testing"
)
@@ -125,55 +127,317 @@ func TestSubImage(t *testing.T) {
}
}
+// palettesEqual reports whether two color.Palette values are equal, ignoring
+// any trailing opaque-black palette entries.
+func palettesEqual(p, q color.Palette) bool {
+ n := len(p)
+ if n > len(q) {
+ n = len(q)
+ }
+ for i := 0; i < n; i++ {
+ if p[i] != q[i] {
+ return false
+ }
+ }
+ for i := n; i < len(p); i++ {
+ r, g, b, a := p[i].RGBA()
+ if r != 0 || g != 0 || b != 0 || a != 0xffff {
+ return false
+ }
+ }
+ for i := n; i < len(q); i++ {
+ r, g, b, a := q[i].RGBA()
+ if r != 0 || g != 0 || b != 0 || a != 0xffff {
+ return false
+ }
+ }
+ return true
+}
+
var frames = []string{
"../testdata/video-001.gif",
"../testdata/video-005.gray.gif",
}
-func TestEncodeAll(t *testing.T) {
+func testEncodeAll(t *testing.T, go1Dot5Fields bool, useGlobalColorModel bool) {
+ const width, height = 150, 103
+
g0 := &GIF{
Image: make([]*image.Paletted, len(frames)),
Delay: make([]int, len(frames)),
LoopCount: 5,
}
for i, f := range frames {
- m, err := readGIF(f)
+ g, err := readGIF(f)
if err != nil {
t.Fatal(f, err)
}
- g0.Image[i] = m.Image[0]
+ m := g.Image[0]
+ if m.Bounds().Dx() != width || m.Bounds().Dy() != height {
+ t.Fatalf("frame %d had unexpected bounds: got %v, want width/height = %d/%d",
+ i, m.Bounds(), width, height)
+ }
+ g0.Image[i] = m
}
+ // The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
+ // in Go 1.5. Valid Go 1.4 or earlier code should still produce valid GIFs.
+ //
+ // On the following line, color.Model is an interface type, and
+ // color.Palette is a concrete (slice) type.
+ globalColorModel, backgroundIndex := color.Model(color.Palette(nil)), uint8(0)
+ if useGlobalColorModel {
+ globalColorModel, backgroundIndex = color.Palette(palette.WebSafe), uint8(1)
+ }
+ if go1Dot5Fields {
+ g0.Disposal = make([]byte, len(g0.Image))
+ for i := range g0.Disposal {
+ g0.Disposal[i] = DisposalNone
+ }
+ g0.Config = image.Config{
+ ColorModel: globalColorModel,
+ Width: width,
+ Height: height,
+ }
+ g0.BackgroundIndex = backgroundIndex
+ }
+
var buf bytes.Buffer
if err := EncodeAll(&buf, g0); err != nil {
t.Fatal("EncodeAll:", err)
}
- g1, err := DecodeAll(&buf)
+ encoded := buf.Bytes()
+ config, err := DecodeConfig(bytes.NewReader(encoded))
+ if err != nil {
+ t.Fatal("DecodeConfig:", err)
+ }
+ g1, err := DecodeAll(bytes.NewReader(encoded))
if err != nil {
t.Fatal("DecodeAll:", err)
}
+
+ if !reflect.DeepEqual(config, g1.Config) {
+ t.Errorf("DecodeConfig inconsistent with DecodeAll")
+ }
+ if !palettesEqual(g1.Config.ColorModel.(color.Palette), globalColorModel.(color.Palette)) {
+ t.Errorf("unexpected global color model")
+ }
+ if w, h := g1.Config.Width, g1.Config.Height; w != width || h != height {
+ t.Errorf("got config width * height = %d * %d, want %d * %d", w, h, width, height)
+ }
+
if g0.LoopCount != g1.LoopCount {
t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount)
}
+ if backgroundIndex != g1.BackgroundIndex {
+ t.Errorf("background indexes differ: %d and %d", backgroundIndex, g1.BackgroundIndex)
+ }
+ if len(g0.Image) != len(g1.Image) {
+ t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
+ }
+ if len(g1.Image) != len(g1.Delay) {
+ t.Fatalf("image and delay lengths differ: %d and %d", len(g1.Image), len(g1.Delay))
+ }
+ if len(g1.Image) != len(g1.Disposal) {
+ t.Fatalf("image and disposal lengths differ: %d and %d", len(g1.Image), len(g1.Disposal))
+ }
+
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())
+ t.Errorf("frame %d: bounds differ: %v and %v", 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)
+ t.Errorf("frame %d: delay values differ: %d and %d", i, d0, d1)
+ }
+ p0, p1 := uint8(0), g1.Disposal[i]
+ if go1Dot5Fields {
+ p0 = DisposalNone
+ }
+ if p0 != p1 {
+ t.Errorf("frame %d: disposal values differ: %d and %d", i, p0, p1)
}
}
+}
- g1.Delay = make([]int, 1)
- if err := EncodeAll(ioutil.Discard, g1); err == nil {
+func TestEncodeAllGo1Dot4(t *testing.T) { testEncodeAll(t, false, false) }
+func TestEncodeAllGo1Dot5(t *testing.T) { testEncodeAll(t, true, false) }
+func TestEncodeAllGo1Dot5GlobalColorModel(t *testing.T) { testEncodeAll(t, true, true) }
+
+func TestEncodeMismatchDelay(t *testing.T) {
+ images := make([]*image.Paletted, 2)
+ for i := range images {
+ images[i] = image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9)
+ }
+
+ g0 := &GIF{
+ Image: images,
+ Delay: make([]int, 1),
+ }
+ if err := EncodeAll(ioutil.Discard, g0); err == nil {
t.Error("expected error from mismatched delay and image slice lengths")
}
+
+ g1 := &GIF{
+ Image: images,
+ Delay: make([]int, len(images)),
+ Disposal: make([]byte, 1),
+ }
+ for i := range g1.Disposal {
+ g1.Disposal[i] = DisposalNone
+ }
+ if err := EncodeAll(ioutil.Discard, g1); err == nil {
+ t.Error("expected error from mismatched disposal and image slice lengths")
+ }
+}
+
+func TestEncodeZeroGIF(t *testing.T) {
if err := EncodeAll(ioutil.Discard, &GIF{}); err == nil {
t.Error("expected error from providing empty gif")
}
}
+func TestEncodeAllFramesOutOfBounds(t *testing.T) {
+ 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 TestEncodeNonZeroMinPoint(t *testing.T) {
+ points := []image.Point{
+ image.Point{-8, -9},
+ image.Point{-4, -4},
+ image.Point{-3, +3},
+ image.Point{+0, +0},
+ image.Point{+2, +2},
+ }
+ for _, p := range points {
+ src := image.NewPaletted(image.Rectangle{Min: p, Max: p.Add(image.Point{6, 6})}, palette.Plan9)
+ var buf bytes.Buffer
+ if err := Encode(&buf, src, nil); err != nil {
+ t.Errorf("p=%v: Encode: %v", p, err)
+ continue
+ }
+ m, err := Decode(&buf)
+ if err != nil {
+ t.Errorf("p=%v: Decode: %v", p, err)
+ continue
+ }
+ if got, want := m.Bounds(), image.Rect(0, 0, 6, 6); got != want {
+ t.Errorf("p=%v: got %v, want %v", p, got, want)
+ }
+ }
+}
+
+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)
+ }
+ }
+ }
+}
+
+func TestEncodePalettes(t *testing.T) {
+ const w, h = 5, 5
+ pals := []color.Palette{{
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x01, 0x00, 0x00, 0xff},
+ color.RGBA{0x02, 0x00, 0x00, 0xff},
+ }, {
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x01, 0x00, 0xff},
+ }, {
+ color.RGBA{0x00, 0x00, 0x03, 0xff},
+ color.RGBA{0x00, 0x00, 0x02, 0xff},
+ color.RGBA{0x00, 0x00, 0x01, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ }, {
+ color.RGBA{0x10, 0x07, 0xf0, 0xff},
+ color.RGBA{0x20, 0x07, 0xf0, 0xff},
+ color.RGBA{0x30, 0x07, 0xf0, 0xff},
+ color.RGBA{0x40, 0x07, 0xf0, 0xff},
+ color.RGBA{0x50, 0x07, 0xf0, 0xff},
+ }}
+ g0 := &GIF{
+ Image: []*image.Paletted{
+ image.NewPaletted(image.Rect(0, 0, w, h), pals[0]),
+ image.NewPaletted(image.Rect(0, 0, w, h), pals[1]),
+ image.NewPaletted(image.Rect(0, 0, w, h), pals[2]),
+ image.NewPaletted(image.Rect(0, 0, w, h), pals[3]),
+ },
+ Delay: make([]int, len(pals)),
+ Disposal: make([]byte, len(pals)),
+ Config: image.Config{
+ ColorModel: pals[2],
+ Width: w,
+ Height: h,
+ },
+ }
+
+ var buf bytes.Buffer
+ if err := EncodeAll(&buf, g0); err != nil {
+ t.Fatalf("EncodeAll: %v", err)
+ }
+ g1, err := DecodeAll(&buf)
+ if err != nil {
+ t.Fatalf("DecodeAll: %v", err)
+ }
+ if len(g0.Image) != len(g1.Image) {
+ t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
+ }
+ for i, m := range g1.Image {
+ if got, want := m.Palette, pals[i]; !palettesEqual(got, want) {
+ t.Errorf("frame %d:\ngot %v\nwant %v", i, got, want)
+ }
+ }
+}
+
func BenchmarkEncode(b *testing.B) {
b.StopTimer()