github.com/4ad/go@v0.0.0-20161219182952-69a12818b605/src/image/gif/writer.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gif
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"compress/lzw"
    11  	"errors"
    12  	"image"
    13  	"image/color"
    14  	"image/color/palette"
    15  	"image/draw"
    16  	"io"
    17  )
    18  
    19  // Graphic control extension fields.
    20  const (
    21  	gcLabel     = 0xF9
    22  	gcBlockSize = 0x04
    23  )
    24  
    25  var log2Lookup = [8]int{2, 4, 8, 16, 32, 64, 128, 256}
    26  
    27  func log2(x int) int {
    28  	for i, v := range log2Lookup {
    29  		if x <= v {
    30  			return i
    31  		}
    32  	}
    33  	return -1
    34  }
    35  
    36  // Little-endian.
    37  func writeUint16(b []uint8, u uint16) {
    38  	b[0] = uint8(u)
    39  	b[1] = uint8(u >> 8)
    40  }
    41  
    42  // writer is a buffered writer.
    43  type writer interface {
    44  	Flush() error
    45  	io.Writer
    46  	io.ByteWriter
    47  }
    48  
    49  // encoder encodes an image to the GIF format.
    50  type encoder struct {
    51  	// w is the writer to write to. err is the first error encountered during
    52  	// writing. All attempted writes after the first error become no-ops.
    53  	w   writer
    54  	err error
    55  	// g is a reference to the data that is being encoded.
    56  	g GIF
    57  	// globalCT is the size in bytes of the global color table.
    58  	globalCT int
    59  	// buf is a scratch buffer. It must be at least 256 for the blockWriter.
    60  	buf              [256]byte
    61  	globalColorTable [3 * 256]byte
    62  	localColorTable  [3 * 256]byte
    63  }
    64  
    65  // blockWriter writes the block structure of GIF image data, which
    66  // comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the
    67  // writer given to the LZW encoder, which is thus immune to the
    68  // blocking.
    69  type blockWriter struct {
    70  	e *encoder
    71  }
    72  
    73  func (b blockWriter) Write(data []byte) (int, error) {
    74  	if b.e.err != nil {
    75  		return 0, b.e.err
    76  	}
    77  	if len(data) == 0 {
    78  		return 0, nil
    79  	}
    80  	total := 0
    81  	for total < len(data) {
    82  		n := copy(b.e.buf[1:256], data[total:])
    83  		total += n
    84  		b.e.buf[0] = uint8(n)
    85  
    86  		_, b.e.err = b.e.w.Write(b.e.buf[:n+1])
    87  		if b.e.err != nil {
    88  			return 0, b.e.err
    89  		}
    90  	}
    91  	return total, b.e.err
    92  }
    93  
    94  func (e *encoder) flush() {
    95  	if e.err != nil {
    96  		return
    97  	}
    98  	e.err = e.w.Flush()
    99  }
   100  
   101  func (e *encoder) write(p []byte) {
   102  	if e.err != nil {
   103  		return
   104  	}
   105  	_, e.err = e.w.Write(p)
   106  }
   107  
   108  func (e *encoder) writeByte(b byte) {
   109  	if e.err != nil {
   110  		return
   111  	}
   112  	e.err = e.w.WriteByte(b)
   113  }
   114  
   115  func (e *encoder) writeHeader() {
   116  	if e.err != nil {
   117  		return
   118  	}
   119  	_, e.err = io.WriteString(e.w, "GIF89a")
   120  	if e.err != nil {
   121  		return
   122  	}
   123  
   124  	// Logical screen width and height.
   125  	writeUint16(e.buf[0:2], uint16(e.g.Config.Width))
   126  	writeUint16(e.buf[2:4], uint16(e.g.Config.Height))
   127  	e.write(e.buf[:4])
   128  
   129  	if p, ok := e.g.Config.ColorModel.(color.Palette); ok && len(p) > 0 {
   130  		paddedSize := log2(len(p)) // Size of Global Color Table: 2^(1+n).
   131  		e.buf[0] = fColorTable | uint8(paddedSize)
   132  		e.buf[1] = e.g.BackgroundIndex
   133  		e.buf[2] = 0x00 // Pixel Aspect Ratio.
   134  		e.write(e.buf[:3])
   135  		e.globalCT = encodeColorTable(e.globalColorTable[:], p, paddedSize)
   136  		e.write(e.globalColorTable[:e.globalCT])
   137  	} else {
   138  		// All frames have a local color table, so a global color table
   139  		// is not needed.
   140  		e.buf[0] = 0x00
   141  		e.buf[1] = 0x00 // Background Color Index.
   142  		e.buf[2] = 0x00 // Pixel Aspect Ratio.
   143  		e.write(e.buf[:3])
   144  	}
   145  
   146  	// Add animation info if necessary.
   147  	if len(e.g.Image) > 1 {
   148  		e.buf[0] = 0x21 // Extension Introducer.
   149  		e.buf[1] = 0xff // Application Label.
   150  		e.buf[2] = 0x0b // Block Size.
   151  		e.write(e.buf[:3])
   152  		_, e.err = io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier.
   153  		if e.err != nil {
   154  			return
   155  		}
   156  		e.buf[0] = 0x03 // Block Size.
   157  		e.buf[1] = 0x01 // Sub-block Index.
   158  		writeUint16(e.buf[2:4], uint16(e.g.LoopCount))
   159  		e.buf[4] = 0x00 // Block Terminator.
   160  		e.write(e.buf[:5])
   161  	}
   162  }
   163  
   164  func encodeColorTable(dst []byte, p color.Palette, size int) int {
   165  	n := log2Lookup[size]
   166  	for i := 0; i < n; i++ {
   167  		if i < len(p) {
   168  			r, g, b, _ := p[i].RGBA()
   169  			dst[3*i+0] = uint8(r >> 8)
   170  			dst[3*i+1] = uint8(g >> 8)
   171  			dst[3*i+2] = uint8(b >> 8)
   172  		} else {
   173  			// Pad with black.
   174  			dst[3*i+0] = 0x00
   175  			dst[3*i+1] = 0x00
   176  			dst[3*i+2] = 0x00
   177  		}
   178  	}
   179  	return 3 * n
   180  }
   181  
   182  func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) {
   183  	if e.err != nil {
   184  		return
   185  	}
   186  
   187  	if len(pm.Palette) == 0 {
   188  		e.err = errors.New("gif: cannot encode image block with empty palette")
   189  		return
   190  	}
   191  
   192  	b := pm.Bounds()
   193  	if b.Min.X < 0 || b.Max.X >= 1<<16 || b.Min.Y < 0 || b.Max.Y >= 1<<16 {
   194  		e.err = errors.New("gif: image block is too large to encode")
   195  		return
   196  	}
   197  	if !b.In(image.Rectangle{Max: image.Point{e.g.Config.Width, e.g.Config.Height}}) {
   198  		e.err = errors.New("gif: image block is out of bounds")
   199  		return
   200  	}
   201  
   202  	transparentIndex := -1
   203  	for i, c := range pm.Palette {
   204  		if _, _, _, a := c.RGBA(); a == 0 {
   205  			transparentIndex = i
   206  			break
   207  		}
   208  	}
   209  
   210  	if delay > 0 || disposal != 0 || transparentIndex != -1 {
   211  		e.buf[0] = sExtension  // Extension Introducer.
   212  		e.buf[1] = gcLabel     // Graphic Control Label.
   213  		e.buf[2] = gcBlockSize // Block Size.
   214  		if transparentIndex != -1 {
   215  			e.buf[3] = 0x01 | disposal<<2
   216  		} else {
   217  			e.buf[3] = 0x00 | disposal<<2
   218  		}
   219  		writeUint16(e.buf[4:6], uint16(delay)) // Delay Time (1/100ths of a second)
   220  
   221  		// Transparent color index.
   222  		if transparentIndex != -1 {
   223  			e.buf[6] = uint8(transparentIndex)
   224  		} else {
   225  			e.buf[6] = 0x00
   226  		}
   227  		e.buf[7] = 0x00 // Block Terminator.
   228  		e.write(e.buf[:8])
   229  	}
   230  	e.buf[0] = sImageDescriptor
   231  	writeUint16(e.buf[1:3], uint16(b.Min.X))
   232  	writeUint16(e.buf[3:5], uint16(b.Min.Y))
   233  	writeUint16(e.buf[5:7], uint16(b.Dx()))
   234  	writeUint16(e.buf[7:9], uint16(b.Dy()))
   235  	e.write(e.buf[:9])
   236  
   237  	paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n).
   238  	ct := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize)
   239  	if ct != e.globalCT || !bytes.Equal(e.globalColorTable[:ct], e.localColorTable[:ct]) {
   240  		// Use a local color table.
   241  		e.writeByte(fColorTable | uint8(paddedSize))
   242  		e.write(e.localColorTable[:ct])
   243  	} else {
   244  		// Use the global color table.
   245  		e.writeByte(0)
   246  	}
   247  
   248  	litWidth := paddedSize + 1
   249  	if litWidth < 2 {
   250  		litWidth = 2
   251  	}
   252  	e.writeByte(uint8(litWidth)) // LZW Minimum Code Size.
   253  
   254  	lzww := lzw.NewWriter(blockWriter{e: e}, lzw.LSB, litWidth)
   255  	if dx := b.Dx(); dx == pm.Stride {
   256  		_, e.err = lzww.Write(pm.Pix)
   257  		if e.err != nil {
   258  			lzww.Close()
   259  			return
   260  		}
   261  	} else {
   262  		for i, y := 0, b.Min.Y; y < b.Max.Y; i, y = i+pm.Stride, y+1 {
   263  			_, e.err = lzww.Write(pm.Pix[i : i+dx])
   264  			if e.err != nil {
   265  				lzww.Close()
   266  				return
   267  			}
   268  		}
   269  	}
   270  	lzww.Close()
   271  	e.writeByte(0x00) // Block Terminator.
   272  }
   273  
   274  // Options are the encoding parameters.
   275  type Options struct {
   276  	// NumColors is the maximum number of colors used in the image.
   277  	// It ranges from 1 to 256.
   278  	NumColors int
   279  
   280  	// Quantizer is used to produce a palette with size NumColors.
   281  	// palette.Plan9 is used in place of a nil Quantizer.
   282  	Quantizer draw.Quantizer
   283  
   284  	// Drawer is used to convert the source image to the desired palette.
   285  	// draw.FloydSteinberg is used in place of a nil Drawer.
   286  	Drawer draw.Drawer
   287  }
   288  
   289  // EncodeAll writes the images in g to w in GIF format with the
   290  // given loop count and delay between frames.
   291  func EncodeAll(w io.Writer, g *GIF) error {
   292  	if len(g.Image) == 0 {
   293  		return errors.New("gif: must provide at least one image")
   294  	}
   295  
   296  	if len(g.Image) != len(g.Delay) {
   297  		return errors.New("gif: mismatched image and delay lengths")
   298  	}
   299  	if g.LoopCount < 0 {
   300  		g.LoopCount = 0
   301  	}
   302  
   303  	e := encoder{g: *g}
   304  	// The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
   305  	// in Go 1.5. Valid Go 1.4 code, such as when the Disposal field is omitted
   306  	// in a GIF struct literal, should still produce valid GIFs.
   307  	if e.g.Disposal != nil && len(e.g.Image) != len(e.g.Disposal) {
   308  		return errors.New("gif: mismatched image and disposal lengths")
   309  	}
   310  	if e.g.Config == (image.Config{}) {
   311  		p := g.Image[0].Bounds().Max
   312  		e.g.Config.Width = p.X
   313  		e.g.Config.Height = p.Y
   314  	} else if e.g.Config.ColorModel != nil {
   315  		if _, ok := e.g.Config.ColorModel.(color.Palette); !ok {
   316  			return errors.New("gif: GIF color model must be a color.Palette")
   317  		}
   318  	}
   319  
   320  	if ww, ok := w.(writer); ok {
   321  		e.w = ww
   322  	} else {
   323  		e.w = bufio.NewWriter(w)
   324  	}
   325  
   326  	e.writeHeader()
   327  	for i, pm := range g.Image {
   328  		disposal := uint8(0)
   329  		if g.Disposal != nil {
   330  			disposal = g.Disposal[i]
   331  		}
   332  		e.writeImageBlock(pm, g.Delay[i], disposal)
   333  	}
   334  	e.writeByte(sTrailer)
   335  	e.flush()
   336  	return e.err
   337  }
   338  
   339  // Encode writes the Image m to w in GIF format.
   340  func Encode(w io.Writer, m image.Image, o *Options) error {
   341  	// Check for bounds and size restrictions.
   342  	b := m.Bounds()
   343  	if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 {
   344  		return errors.New("gif: image is too large to encode")
   345  	}
   346  
   347  	opts := Options{}
   348  	if o != nil {
   349  		opts = *o
   350  	}
   351  	if opts.NumColors < 1 || 256 < opts.NumColors {
   352  		opts.NumColors = 256
   353  	}
   354  	if opts.Drawer == nil {
   355  		opts.Drawer = draw.FloydSteinberg
   356  	}
   357  
   358  	pm, ok := m.(*image.Paletted)
   359  	if !ok || len(pm.Palette) > opts.NumColors {
   360  		// TODO: Pick a better sub-sample of the Plan 9 palette.
   361  		pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors])
   362  		if opts.Quantizer != nil {
   363  			pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m)
   364  		}
   365  		opts.Drawer.Draw(pm, b, m, image.ZP)
   366  	}
   367  
   368  	// When calling Encode instead of EncodeAll, the single-frame image is
   369  	// translated such that its top-left corner is (0, 0), so that the single
   370  	// frame completely fills the overall GIF's bounds.
   371  	if pm.Rect.Min != (image.Point{}) {
   372  		dup := *pm
   373  		dup.Rect = dup.Rect.Sub(dup.Rect.Min)
   374  		pm = &dup
   375  	}
   376  
   377  	return EncodeAll(w, &GIF{
   378  		Image: []*image.Paletted{pm},
   379  		Delay: []int{0},
   380  		Config: image.Config{
   381  			ColorModel: pm.Palette,
   382  			Width:      b.Dx(),
   383  			Height:     b.Dy(),
   384  		},
   385  	})
   386  }