Skip to content

Commit

Permalink
image/gif: encode disposal, bg index and Config.
Browse files Browse the repository at this point in the history
The previous CL implemented decoding, but not encoding.

Also return the global color map (if present) for DecodeConfig.

Change-Id: I3b99c93720246010c9fe0924dc40a67875dfc852
Reviewed-on: https://go-review.googlesource.com/9389
Reviewed-by: Rob Pike <[email protected]>
  • Loading branch information
nigeltao committed Apr 28, 2015
1 parent 0c62c93 commit baf3814
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 73 deletions.
87 changes: 38 additions & 49 deletions src/image/gif/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,9 @@ type reader interface {
// Masks etc.
const (
// Fields.
fColorMapFollows = 1 << 7

// Screen Descriptor flags.
sdGlobalColorTable = 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
Expand Down Expand Up @@ -77,15 +71,11 @@ type decoder struct {
vers string
width int
height int
headerFields byte
backgroundIndex byte
loopCount int
delayTime int
backgroundIndex byte
disposalMethod byte

// Unused from header.
aspect byte

// From image descriptor.
imageFields byte

Expand All @@ -94,7 +84,6 @@ type decoder struct {
hasTransparentIndex bool

// Computed.
pixelSize uint
globalColorMap color.Palette

// Used when decoding.
Expand Down Expand Up @@ -134,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
}
Expand All @@ -161,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 {
Expand All @@ -183,9 +166,9 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
if err != nil {
return err
}
useLocalColorMap := d.imageFields&fColorMapFollows != 0
useLocalColorMap := d.imageFields&fColorTable != 0
if useLocalColorMap {
m.Palette, err = d.readColorMap()
m.Palette, err = d.readColorMap(d.imageFields)
if err != nil {
return err
}
Expand Down Expand Up @@ -241,7 +224,7 @@ 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)
}

Expand All @@ -267,40 +250,35 @@ 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]
if d.headerFields&sdGlobalColorTable != 0 {
if fields := d.tmp[10]; fields&fColorTable != 0 {
d.backgroundIndex = d.tmp[11]
// readColorMap overwrites the contents of d.tmp, but that's OK.
if d.globalColorMap, err = d.readColorMap(fields); err != nil {
return err
}
}
d.aspect = d.tmp[12]
// d.tmp[12] is the Pixel Aspect Ratio, which is ignored.
d.loopCount = -1
d.pixelSize = uint(d.headerFields&7) + 1
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) readColorMap(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)
}
colorMap := make(color.Palette, numColors)
colorMap := make(color.Palette, n)
j := 0
for i := range colorMap {
colorMap[i] = color.RGBA{d.tmp[j+0], d.tmp[j+1], d.tmp[j+2], 0xFF}
Expand Down Expand Up @@ -333,7 +311,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
}
}
Expand All @@ -358,7 +336,7 @@ 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)
}
flags := d.tmp[1]
Expand All @@ -372,7 +350,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
Expand All @@ -396,7 +374,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.
Expand Down Expand Up @@ -444,10 +422,21 @@ func Decode(r io.Reader) (image.Image, error) {
type GIF struct {
Image []*image.Paletted // The successive images.
Delay []int // The successive delay times, one per frame, in 100ths of a second.
Disposal []byte // The successive disposal methods, one per frame.
LoopCount int // The loop count.
Config image.Config
// The background index in the Global Color Map.
// 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 map (palette), width and height. A nil or
// empty-color.Palette Config.ColorModel means that each frame has its own
// color map and there is no global color map. 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 width and
// height.
Config image.Config
// BackgroundIndex is the background index in the global color map, for use
// with the DisposalBackground disposal method.
BackgroundIndex byte
}

Expand Down
65 changes: 49 additions & 16 deletions src/image/gif/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type encoder struct {
w writer
err error
// g is a reference to the data that is being encoded.
g *GIF
g GIF
// buf is a scratch buffer. It must be at least 768 so we can write the color map.
buf [1024]byte
}
Expand Down Expand Up @@ -116,18 +116,26 @@ 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.writeColorTable(p, paddedSize)
} 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 {
Expand Down Expand Up @@ -168,7 +176,7 @@ func (e *encoder) writeColorTable(p color.Palette, size int) {
e.write(e.buf[:3*log2Lookup[size]])
}

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
}
Expand All @@ -192,14 +200,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)

Expand Down Expand Up @@ -281,7 +289,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{}) {
b := g.Image[0].Bounds()
e.g.Config.Width = b.Dx()
e.g.Config.Height = b.Dy()
} 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 {
Expand All @@ -290,7 +314,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()
Expand Down Expand Up @@ -329,5 +357,10 @@ func Encode(w io.Writer, m image.Image, o *Options) error {
return EncodeAll(w, &GIF{
Image: []*image.Paletted{pm},
Delay: []int{0},
Config: image.Config{
ColorModel: pm.Palette,
Width: b.Dx(),
Height: b.Dy(),
},
})
}
Loading

0 comments on commit baf3814

Please sign in to comment.