From: Nigel Tao Date: Wed, 29 Apr 2015 06:40:24 +0000 (+1000) Subject: image/gif: don't encode local color tables if they're the same as the X-Git-Tag: go1.5beta1~767 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=4ddd751c9283050806701786649e6f7d79fca47b;p=gostls13.git image/gif: don't encode local color tables if they're the same as the global color table. Change-Id: Ia38f75708ed5e5b430680a1eecafb4fc8047269c Reviewed-on: https://go-review.googlesource.com/9467 Reviewed-by: Rob Pike --- diff --git a/src/image/gif/writer.go b/src/image/gif/writer.go index 322b353fcb..e14d034602 100644 --- a/src/image/gif/writer.go +++ b/src/image/gif/writer.go @@ -6,6 +6,7 @@ package gif import ( "bufio" + "bytes" "compress/lzw" "errors" "image" @@ -53,8 +54,12 @@ type encoder struct { 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 + // 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 @@ -127,7 +132,8 @@ func (e *encoder) writeHeader() { e.buf[1] = e.g.BackgroundIndex e.buf[2] = 0x00 // Pixel Aspect Ratio. e.write(e.buf[:3]) - e.writeColorTable(p, paddedSize) + 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. @@ -155,25 +161,22 @@ 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, disposal byte) { @@ -232,11 +235,15 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) 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 { diff --git a/src/image/gif/writer_test.go b/src/image/gif/writer_test.go index d661015b17..d33976e415 100644 --- a/src/image/gif/writer_test.go +++ b/src/image/gif/writer_test.go @@ -357,7 +357,60 @@ func TestEncodeImplicitConfigSize(t *testing.T) { } } -// TODO: add test for when a frame has the same color map (palette) as the global one. +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()