import (
"bufio"
+ "bytes"
"compress/lzw"
"errors"
"image"
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
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.
}
}
-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) {
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 {
}
}
-// 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()