github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/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 var err error 136 e.globalCT, err = encodeColorTable(e.globalColorTable[:], p, paddedSize) 137 if err != nil && e.err == nil { 138 e.err = err 139 return 140 } 141 e.write(e.globalColorTable[:e.globalCT]) 142 } else { 143 // All frames have a local color table, so a global color table 144 // is not needed. 145 e.buf[0] = 0x00 146 e.buf[1] = 0x00 // Background Color Index. 147 e.buf[2] = 0x00 // Pixel Aspect Ratio. 148 e.write(e.buf[:3]) 149 } 150 151 // Add animation info if necessary. 152 if len(e.g.Image) > 1 { 153 e.buf[0] = 0x21 // Extension Introducer. 154 e.buf[1] = 0xff // Application Label. 155 e.buf[2] = 0x0b // Block Size. 156 e.write(e.buf[:3]) 157 _, err := io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier. 158 if err != nil && e.err == nil { 159 e.err = err 160 return 161 } 162 e.buf[0] = 0x03 // Block Size. 163 e.buf[1] = 0x01 // Sub-block Index. 164 writeUint16(e.buf[2:4], uint16(e.g.LoopCount)) 165 e.buf[4] = 0x00 // Block Terminator. 166 e.write(e.buf[:5]) 167 } 168 } 169 170 func encodeColorTable(dst []byte, p color.Palette, size int) (int, error) { 171 if uint(size) >= uint(len(log2Lookup)) { 172 return 0, errors.New("gif: cannot encode color table with more than 256 entries") 173 } 174 n := log2Lookup[size] 175 for i := 0; i < n; i++ { 176 if i < len(p) { 177 c := p[i] 178 if c == nil { 179 return 0, errors.New("gif: cannot encode color table with nil entries") 180 } 181 r, g, b, _ := c.RGBA() 182 dst[3*i+0] = uint8(r >> 8) 183 dst[3*i+1] = uint8(g >> 8) 184 dst[3*i+2] = uint8(b >> 8) 185 } else { 186 // Pad with black. 187 dst[3*i+0] = 0x00 188 dst[3*i+1] = 0x00 189 dst[3*i+2] = 0x00 190 } 191 } 192 return 3 * n, nil 193 } 194 195 func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) { 196 if e.err != nil { 197 return 198 } 199 200 if len(pm.Palette) == 0 { 201 e.err = errors.New("gif: cannot encode image block with empty palette") 202 return 203 } 204 205 b := pm.Bounds() 206 if b.Min.X < 0 || b.Max.X >= 1<<16 || b.Min.Y < 0 || b.Max.Y >= 1<<16 { 207 e.err = errors.New("gif: image block is too large to encode") 208 return 209 } 210 if !b.In(image.Rectangle{Max: image.Point{e.g.Config.Width, e.g.Config.Height}}) { 211 e.err = errors.New("gif: image block is out of bounds") 212 return 213 } 214 215 transparentIndex := -1 216 for i, c := range pm.Palette { 217 if c == nil { 218 e.err = errors.New("gif: cannot encode color table with nil entries") 219 return 220 } 221 if _, _, _, a := c.RGBA(); a == 0 { 222 transparentIndex = i 223 break 224 } 225 } 226 227 if delay > 0 || disposal != 0 || transparentIndex != -1 { 228 e.buf[0] = sExtension // Extension Introducer. 229 e.buf[1] = gcLabel // Graphic Control Label. 230 e.buf[2] = gcBlockSize // Block Size. 231 if transparentIndex != -1 { 232 e.buf[3] = 0x01 | disposal<<2 233 } else { 234 e.buf[3] = 0x00 | disposal<<2 235 } 236 writeUint16(e.buf[4:6], uint16(delay)) // Delay Time (1/100ths of a second) 237 238 // Transparent color index. 239 if transparentIndex != -1 { 240 e.buf[6] = uint8(transparentIndex) 241 } else { 242 e.buf[6] = 0x00 243 } 244 e.buf[7] = 0x00 // Block Terminator. 245 e.write(e.buf[:8]) 246 } 247 e.buf[0] = sImageDescriptor 248 writeUint16(e.buf[1:3], uint16(b.Min.X)) 249 writeUint16(e.buf[3:5], uint16(b.Min.Y)) 250 writeUint16(e.buf[5:7], uint16(b.Dx())) 251 writeUint16(e.buf[7:9], uint16(b.Dy())) 252 e.write(e.buf[:9]) 253 254 paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n). 255 if ct, err := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize); err != nil { 256 if e.err == nil { 257 e.err = err 258 } 259 return 260 } else if ct != e.globalCT || !bytes.Equal(e.globalColorTable[:ct], e.localColorTable[:ct]) { 261 // Use a local color table. 262 e.writeByte(fColorTable | uint8(paddedSize)) 263 e.write(e.localColorTable[:ct]) 264 } else { 265 // Use the global color table. 266 e.writeByte(0) 267 } 268 269 litWidth := paddedSize + 1 270 if litWidth < 2 { 271 litWidth = 2 272 } 273 e.writeByte(uint8(litWidth)) // LZW Minimum Code Size. 274 275 lzww := lzw.NewWriter(blockWriter{e: e}, lzw.LSB, litWidth) 276 if dx := b.Dx(); dx == pm.Stride { 277 _, e.err = lzww.Write(pm.Pix[:dx*b.Dy()]) 278 if e.err != nil { 279 lzww.Close() 280 return 281 } 282 } else { 283 for i, y := 0, b.Min.Y; y < b.Max.Y; i, y = i+pm.Stride, y+1 { 284 _, e.err = lzww.Write(pm.Pix[i : i+dx]) 285 if e.err != nil { 286 lzww.Close() 287 return 288 } 289 } 290 } 291 lzww.Close() 292 e.writeByte(0x00) // Block Terminator. 293 } 294 295 // Options are the encoding parameters. 296 type Options struct { 297 // NumColors is the maximum number of colors used in the image. 298 // It ranges from 1 to 256. 299 NumColors int 300 301 // Quantizer is used to produce a palette with size NumColors. 302 // palette.Plan9 is used in place of a nil Quantizer. 303 Quantizer draw.Quantizer 304 305 // Drawer is used to convert the source image to the desired palette. 306 // draw.FloydSteinberg is used in place of a nil Drawer. 307 Drawer draw.Drawer 308 } 309 310 // EncodeAll writes the images in g to w in GIF format with the 311 // given loop count and delay between frames. 312 func EncodeAll(w io.Writer, g *GIF) error { 313 if len(g.Image) == 0 { 314 return errors.New("gif: must provide at least one image") 315 } 316 317 if len(g.Image) != len(g.Delay) { 318 return errors.New("gif: mismatched image and delay lengths") 319 } 320 if g.LoopCount < 0 { 321 g.LoopCount = 0 322 } 323 324 e := encoder{g: *g} 325 // The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added 326 // in Go 1.5. Valid Go 1.4 code, such as when the Disposal field is omitted 327 // in a GIF struct literal, should still produce valid GIFs. 328 if e.g.Disposal != nil && len(e.g.Image) != len(e.g.Disposal) { 329 return errors.New("gif: mismatched image and disposal lengths") 330 } 331 if e.g.Config == (image.Config{}) { 332 p := g.Image[0].Bounds().Max 333 e.g.Config.Width = p.X 334 e.g.Config.Height = p.Y 335 } else if e.g.Config.ColorModel != nil { 336 if _, ok := e.g.Config.ColorModel.(color.Palette); !ok { 337 return errors.New("gif: GIF color model must be a color.Palette") 338 } 339 } 340 341 if ww, ok := w.(writer); ok { 342 e.w = ww 343 } else { 344 e.w = bufio.NewWriter(w) 345 } 346 347 e.writeHeader() 348 for i, pm := range g.Image { 349 disposal := uint8(0) 350 if g.Disposal != nil { 351 disposal = g.Disposal[i] 352 } 353 e.writeImageBlock(pm, g.Delay[i], disposal) 354 } 355 e.writeByte(sTrailer) 356 e.flush() 357 return e.err 358 } 359 360 // Encode writes the Image m to w in GIF format. 361 func Encode(w io.Writer, m image.Image, o *Options) error { 362 // Check for bounds and size restrictions. 363 b := m.Bounds() 364 if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 { 365 return errors.New("gif: image is too large to encode") 366 } 367 368 opts := Options{} 369 if o != nil { 370 opts = *o 371 } 372 if opts.NumColors < 1 || 256 < opts.NumColors { 373 opts.NumColors = 256 374 } 375 if opts.Drawer == nil { 376 opts.Drawer = draw.FloydSteinberg 377 } 378 379 pm, ok := m.(*image.Paletted) 380 if !ok || len(pm.Palette) > opts.NumColors { 381 // TODO: Pick a better sub-sample of the Plan 9 palette. 382 pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors]) 383 if opts.Quantizer != nil { 384 pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m) 385 } 386 opts.Drawer.Draw(pm, b, m, image.ZP) 387 } 388 389 // When calling Encode instead of EncodeAll, the single-frame image is 390 // translated such that its top-left corner is (0, 0), so that the single 391 // frame completely fills the overall GIF's bounds. 392 if pm.Rect.Min != (image.Point{}) { 393 dup := *pm 394 dup.Rect = dup.Rect.Sub(dup.Rect.Min) 395 pm = &dup 396 } 397 398 return EncodeAll(w, &GIF{ 399 Image: []*image.Paletted{pm}, 400 Delay: []int{0}, 401 Config: image.Config{ 402 ColorModel: pm.Palette, 403 Width: b.Dx(), 404 Height: b.Dy(), 405 }, 406 }) 407 }