diff options
Diffstat (limited to 'libgo/go/image/gif/writer.go')
-rw-r--r-- | libgo/go/image/gif/writer.go | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/libgo/go/image/gif/writer.go b/libgo/go/image/gif/writer.go new file mode 100644 index 00000000000..15cd40fadf6 --- /dev/null +++ b/libgo/go/image/gif/writer.go @@ -0,0 +1,323 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gif + +import ( + "bufio" + "compress/lzw" + "errors" + "image" + "image/color" + "image/color/palette" + "image/draw" + "io" +) + +// Graphic control extension fields. +const ( + gcLabel = 0xF9 + gcBlockSize = 0x04 +) + +var log2Lookup = [8]int{2, 4, 8, 16, 32, 64, 128, 256} + +func log2(x int) int { + for i, v := range log2Lookup { + if x <= v { + return i + } + } + return -1 +} + +// Little-endian. +func writeUint16(b []uint8, u uint16) { + b[0] = uint8(u) + b[1] = uint8(u >> 8) +} + +// writer is a buffered writer. +type writer interface { + Flush() error + io.Writer + io.ByteWriter +} + +// encoder encodes an image to the GIF format. +type encoder struct { + // w is the writer to write to. err is the first error encountered during + // writing. All attempted writes after the first error become no-ops. + 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 +} + +// blockWriter writes the block structure of GIF image data, which +// comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the +// writer given to the LZW encoder, which is thus immune to the +// blocking. +type blockWriter struct { + e *encoder +} + +func (b blockWriter) Write(data []byte) (int, error) { + if b.e.err != nil { + return 0, b.e.err + } + if len(data) == 0 { + return 0, nil + } + total := 0 + for total < len(data) { + n := copy(b.e.buf[1:256], data[total:]) + total += n + b.e.buf[0] = uint8(n) + + n, b.e.err = b.e.w.Write(b.e.buf[:n+1]) + if b.e.err != nil { + return 0, b.e.err + } + } + return total, b.e.err +} + +func (e *encoder) flush() { + if e.err != nil { + return + } + e.err = e.w.Flush() +} + +func (e *encoder) write(p []byte) { + if e.err != nil { + return + } + _, e.err = e.w.Write(p) +} + +func (e *encoder) writeByte(b byte) { + if e.err != nil { + return + } + e.err = e.w.WriteByte(b) +} + +func (e *encoder) writeHeader() { + if e.err != nil { + return + } + _, e.err = io.WriteString(e.w, "GIF89a") + if e.err != nil { + 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())) + 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]) + + // Add animation info if necessary. + if len(e.g.Image) > 1 { + e.buf[0] = 0x21 // Extension Introducer. + e.buf[1] = 0xff // Application Label. + e.buf[2] = 0x0b // Block Size. + e.write(e.buf[:3]) + _, e.err = io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier. + if e.err != nil { + return + } + e.buf[0] = 0x03 // Block Size. + e.buf[1] = 0x01 // Sub-block Index. + writeUint16(e.buf[2:4], uint16(e.g.LoopCount)) + e.buf[4] = 0x00 // Block Terminator. + e.write(e.buf[:5]) + } +} + +func (e *encoder) writeColorTable(p color.Palette, size int) { + if e.err != nil { + return + } + + for i := 0; i < log2Lookup[size]; 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) + } else { + // Pad with black. + e.buf[3*i+0] = 0x00 + e.buf[3*i+1] = 0x00 + e.buf[3*i+2] = 0x00 + } + } + e.write(e.buf[:3*log2Lookup[size]]) +} + +func (e *encoder) writeImageBlock(pm *image.Paletted, delay int) { + if e.err != nil { + return + } + + if len(pm.Palette) == 0 { + e.err = errors.New("gif: cannot encode image block with empty palette") + return + } + + 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 { + e.err = errors.New("gif: image block is too large to encode") + return + } + + transparentIndex := -1 + for i, c := range pm.Palette { + if _, _, _, a := c.RGBA(); a == 0 { + transparentIndex = i + break + } + } + + if delay > 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 + } else { + e.buf[3] = 0x00 + } + writeUint16(e.buf[4:6], uint16(delay)) // Delay Time (1/100ths of a second) + + // Transparent color index. + if transparentIndex != -1 { + e.buf[6] = uint8(transparentIndex) + } else { + e.buf[6] = 0x00 + } + e.buf[7] = 0x00 // Block Terminator. + e.write(e.buf[:8]) + } + e.buf[0] = sImageDescriptor + writeUint16(e.buf[1:3], uint16(b.Min.X)) + writeUint16(e.buf[3:5], uint16(b.Min.Y)) + writeUint16(e.buf[5:7], uint16(b.Dx())) + writeUint16(e.buf[7:9], uint16(b.Dy())) + 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) + + litWidth := paddedSize + 1 + if litWidth < 2 { + litWidth = 2 + } + e.writeByte(uint8(litWidth)) // LZW Minimum Code Size. + + lzww := lzw.NewWriter(blockWriter{e: e}, lzw.LSB, litWidth) + _, e.err = lzww.Write(pm.Pix) + if e.err != nil { + lzww.Close() + return + } + lzww.Close() + e.writeByte(0x00) // Block Terminator. +} + +// Options are the encoding parameters. +type Options struct { + // NumColors is the maximum number of colors used in the image. + // It ranges from 1 to 256. + NumColors int + + // Quantizer is used to produce a palette with size NumColors. + // palette.Plan9 is used in place of a nil Quantizer. + Quantizer draw.Quantizer + + // Drawer is used to convert the source image to the desired palette. + // draw.FloydSteinberg is used in place of a nil Drawer. + Drawer draw.Drawer +} + +// EncodeAll writes the images in g to w in GIF format with the +// given loop count and delay between frames. +func EncodeAll(w io.Writer, g *GIF) error { + if len(g.Image) == 0 { + return errors.New("gif: must provide at least one image") + } + + if len(g.Image) != len(g.Delay) { + return errors.New("gif: mismatched image and delay lengths") + } + if g.LoopCount < 0 { + g.LoopCount = 0 + } + + e := encoder{g: g} + if ww, ok := w.(writer); ok { + e.w = ww + } else { + e.w = bufio.NewWriter(w) + } + + e.writeHeader() + for i, pm := range g.Image { + e.writeImageBlock(pm, g.Delay[i]) + } + e.writeByte(sTrailer) + e.flush() + return e.err +} + +// Encode writes the Image m to w in GIF format. +func Encode(w io.Writer, m image.Image, o *Options) error { + // Check for bounds and size restrictions. + b := m.Bounds() + if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 { + return errors.New("gif: image is too large to encode") + } + + opts := Options{} + if o != nil { + opts = *o + } + if opts.NumColors < 1 || 256 < opts.NumColors { + opts.NumColors = 256 + } + if opts.Drawer == nil { + opts.Drawer = draw.FloydSteinberg + } + + pm, ok := m.(*image.Paletted) + if !ok || len(pm.Palette) > opts.NumColors { + // TODO: Pick a better sub-sample of the Plan 9 palette. + pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors]) + if opts.Quantizer != nil { + pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m) + } + opts.Drawer.Draw(pm, b, m, image.ZP) + } + + return EncodeAll(w, &GIF{ + Image: []*image.Paletted{pm}, + Delay: []int{0}, + }) +} |