github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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) setup() { 74 b.e.buf[0] = 0 75 } 76 77 func (b blockWriter) Flush() error { 78 return b.e.err 79 } 80 81 func (b blockWriter) WriteByte(c byte) error { 82 if b.e.err != nil { 83 return b.e.err 84 } 85 86 // Append c to buffered sub-block. 87 b.e.buf[0]++ 88 b.e.buf[b.e.buf[0]] = c 89 if b.e.buf[0] < 255 { 90 return nil 91 } 92 93 // Flush block 94 b.e.write(b.e.buf[:256]) 95 b.e.buf[0] = 0 96 return b.e.err 97 } 98 99 // blockWriter must be an io.Writer for lzw.NewWriter, but this is never 100 // actually called. 101 func (b blockWriter) Write(data []byte) (int, error) { 102 for i, c := range data { 103 if err := b.WriteByte(c); err != nil { 104 return i, err 105 } 106 } 107 return len(data), nil 108 } 109 110 func (b blockWriter) close() { 111 // Write the block terminator (0x00), either by itself, or along with a 112 // pending sub-block. 113 if b.e.buf[0] == 0 { 114 b.e.writeByte(0) 115 } else { 116 n := uint(b.e.buf[0]) 117 b.e.buf[n+1] = 0 118 b.e.write(b.e.buf[:n+2]) 119 } 120 b.e.flush() 121 } 122 123 func (e *encoder) flush() { 124 if e.err != nil { 125 return 126 } 127 e.err = e.w.Flush() 128 } 129 130 func (e *encoder) write(p []byte) { 131 if e.err != nil { 132 return 133 } 134 _, e.err = e.w.Write(p) 135 } 136 137 func (e *encoder) writeByte(b byte) { 138 if e.err != nil { 139 return 140 } 141 e.err = e.w.WriteByte(b) 142 } 143 144 func (e *encoder) writeHeader() { 145 if e.err != nil { 146 return 147 } 148 _, e.err = io.WriteString(e.w, "GIF89a") 149 if e.err != nil { 150 return 151 } 152 153 // Logical screen width and height. 154 writeUint16(e.buf[0:2], uint16(e.g.Config.Width)) 155 writeUint16(e.buf[2:4], uint16(e.g.Config.Height)) 156 e.write(e.buf[:4]) 157 158 if p, ok := e.g.Config.ColorModel.(color.Palette); ok && len(p) > 0 { 159 paddedSize := log2(len(p)) // Size of Global Color Table: 2^(1+n). 160 e.buf[0] = fColorTable | uint8(paddedSize) 161 e.buf[1] = e.g.BackgroundIndex 162 e.buf[2] = 0x00 // Pixel Aspect Ratio. 163 e.write(e.buf[:3]) 164 var err error 165 e.globalCT, err = encodeColorTable(e.globalColorTable[:], p, paddedSize) 166 if err != nil && e.err == nil { 167 e.err = err 168 return 169 } 170 e.write(e.globalColorTable[:e.globalCT]) 171 } else { 172 // All frames have a local color table, so a global color table 173 // is not needed. 174 e.buf[0] = 0x00 175 e.buf[1] = 0x00 // Background Color Index. 176 e.buf[2] = 0x00 // Pixel Aspect Ratio. 177 e.write(e.buf[:3]) 178 } 179 180 // Add animation info if necessary. 181 if len(e.g.Image) > 1 && e.g.LoopCount >= 0 { 182 e.buf[0] = 0x21 // Extension Introducer. 183 e.buf[1] = 0xff // Application Label. 184 e.buf[2] = 0x0b // Block Size. 185 e.write(e.buf[:3]) 186 _, err := io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier. 187 if err != nil && e.err == nil { 188 e.err = err 189 return 190 } 191 e.buf[0] = 0x03 // Block Size. 192 e.buf[1] = 0x01 // Sub-block Index. 193 writeUint16(e.buf[2:4], uint16(e.g.LoopCount)) 194 e.buf[4] = 0x00 // Block Terminator. 195 e.write(e.buf[:5]) 196 } 197 } 198 199 func encodeColorTable(dst []byte, p color.Palette, size int) (int, error) { 200 if uint(size) >= uint(len(log2Lookup)) { 201 return 0, errors.New("gif: cannot encode color table with more than 256 entries") 202 } 203 for i, c := range p { 204 if c == nil { 205 return 0, errors.New("gif: cannot encode color table with nil entries") 206 } 207 var r, g, b uint8 208 // It is most likely that the palette is full of color.RGBAs, so they 209 // get a fast path. 210 if rgba, ok := c.(color.RGBA); ok { 211 r, g, b = rgba.R, rgba.G, rgba.B 212 } else { 213 rr, gg, bb, _ := c.RGBA() 214 r, g, b = uint8(rr>>8), uint8(gg>>8), uint8(bb>>8) 215 } 216 dst[3*i+0] = r 217 dst[3*i+1] = g 218 dst[3*i+2] = b 219 } 220 n := log2Lookup[size] 221 if n > len(p) { 222 // Pad with black. 223 clear(dst[3*len(p) : 3*n]) 224 } 225 return 3 * n, nil 226 } 227 228 func (e *encoder) colorTablesMatch(localLen, transparentIndex int) bool { 229 localSize := 3 * localLen 230 if transparentIndex >= 0 { 231 trOff := 3 * transparentIndex 232 return bytes.Equal(e.globalColorTable[:trOff], e.localColorTable[:trOff]) && 233 bytes.Equal(e.globalColorTable[trOff+3:localSize], e.localColorTable[trOff+3:localSize]) 234 } 235 return bytes.Equal(e.globalColorTable[:localSize], e.localColorTable[:localSize]) 236 } 237 238 func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) { 239 if e.err != nil { 240 return 241 } 242 243 if len(pm.Palette) == 0 { 244 e.err = errors.New("gif: cannot encode image block with empty palette") 245 return 246 } 247 248 b := pm.Bounds() 249 if b.Min.X < 0 || b.Max.X >= 1<<16 || b.Min.Y < 0 || b.Max.Y >= 1<<16 { 250 e.err = errors.New("gif: image block is too large to encode") 251 return 252 } 253 if !b.In(image.Rectangle{Max: image.Point{e.g.Config.Width, e.g.Config.Height}}) { 254 e.err = errors.New("gif: image block is out of bounds") 255 return 256 } 257 258 transparentIndex := -1 259 for i, c := range pm.Palette { 260 if c == nil { 261 e.err = errors.New("gif: cannot encode color table with nil entries") 262 return 263 } 264 if _, _, _, a := c.RGBA(); a == 0 { 265 transparentIndex = i 266 break 267 } 268 } 269 270 if delay > 0 || disposal != 0 || transparentIndex != -1 { 271 e.buf[0] = sExtension // Extension Introducer. 272 e.buf[1] = gcLabel // Graphic Control Label. 273 e.buf[2] = gcBlockSize // Block Size. 274 if transparentIndex != -1 { 275 e.buf[3] = 0x01 | disposal<<2 276 } else { 277 e.buf[3] = 0x00 | disposal<<2 278 } 279 writeUint16(e.buf[4:6], uint16(delay)) // Delay Time (1/100ths of a second) 280 281 // Transparent color index. 282 if transparentIndex != -1 { 283 e.buf[6] = uint8(transparentIndex) 284 } else { 285 e.buf[6] = 0x00 286 } 287 e.buf[7] = 0x00 // Block Terminator. 288 e.write(e.buf[:8]) 289 } 290 e.buf[0] = sImageDescriptor 291 writeUint16(e.buf[1:3], uint16(b.Min.X)) 292 writeUint16(e.buf[3:5], uint16(b.Min.Y)) 293 writeUint16(e.buf[5:7], uint16(b.Dx())) 294 writeUint16(e.buf[7:9], uint16(b.Dy())) 295 e.write(e.buf[:9]) 296 297 // To determine whether or not this frame's palette is the same as the 298 // global palette, we can check a couple things. First, do they actually 299 // point to the same []color.Color? If so, they are equal so long as the 300 // frame's palette is not longer than the global palette... 301 paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n). 302 if gp, ok := e.g.Config.ColorModel.(color.Palette); ok && len(pm.Palette) <= len(gp) && &gp[0] == &pm.Palette[0] { 303 e.writeByte(0) // Use the global color table. 304 } else { 305 ct, err := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize) 306 if err != nil { 307 if e.err == nil { 308 e.err = err 309 } 310 return 311 } 312 // This frame's palette is not the very same slice as the global 313 // palette, but it might be a copy, possibly with one value turned into 314 // transparency by DecodeAll. 315 if ct <= e.globalCT && e.colorTablesMatch(len(pm.Palette), transparentIndex) { 316 e.writeByte(0) // Use the global color table. 317 } else { 318 // Use a local color table. 319 e.writeByte(fColorTable | uint8(paddedSize)) 320 e.write(e.localColorTable[:ct]) 321 } 322 } 323 324 litWidth := paddedSize + 1 325 if litWidth < 2 { 326 litWidth = 2 327 } 328 e.writeByte(uint8(litWidth)) // LZW Minimum Code Size. 329 330 bw := blockWriter{e: e} 331 bw.setup() 332 lzww := lzw.NewWriter(bw, lzw.LSB, litWidth) 333 if dx := b.Dx(); dx == pm.Stride { 334 _, e.err = lzww.Write(pm.Pix[:dx*b.Dy()]) 335 if e.err != nil { 336 lzww.Close() 337 return 338 } 339 } else { 340 for i, y := 0, b.Min.Y; y < b.Max.Y; i, y = i+pm.Stride, y+1 { 341 _, e.err = lzww.Write(pm.Pix[i : i+dx]) 342 if e.err != nil { 343 lzww.Close() 344 return 345 } 346 } 347 } 348 lzww.Close() // flush to bw 349 bw.close() // flush to e.w 350 } 351 352 // Options are the encoding parameters. 353 type Options struct { 354 // NumColors is the maximum number of colors used in the image. 355 // It ranges from 1 to 256. 356 NumColors int 357 358 // Quantizer is used to produce a palette with size NumColors. 359 // palette.Plan9 is used in place of a nil Quantizer. 360 Quantizer draw.Quantizer 361 362 // Drawer is used to convert the source image to the desired palette. 363 // draw.FloydSteinberg is used in place of a nil Drawer. 364 Drawer draw.Drawer 365 } 366 367 // EncodeAll writes the images in g to w in GIF format with the 368 // given loop count and delay between frames. 369 func EncodeAll(w io.Writer, g *GIF) error { 370 if len(g.Image) == 0 { 371 return errors.New("gif: must provide at least one image") 372 } 373 374 if len(g.Image) != len(g.Delay) { 375 return errors.New("gif: mismatched image and delay lengths") 376 } 377 378 e := encoder{g: *g} 379 // The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added 380 // in Go 1.5. Valid Go 1.4 code, such as when the Disposal field is omitted 381 // in a GIF struct literal, should still produce valid GIFs. 382 if e.g.Disposal != nil && len(e.g.Image) != len(e.g.Disposal) { 383 return errors.New("gif: mismatched image and disposal lengths") 384 } 385 if e.g.Config == (image.Config{}) { 386 p := g.Image[0].Bounds().Max 387 e.g.Config.Width = p.X 388 e.g.Config.Height = p.Y 389 } else if e.g.Config.ColorModel != nil { 390 if _, ok := e.g.Config.ColorModel.(color.Palette); !ok { 391 return errors.New("gif: GIF color model must be a color.Palette") 392 } 393 } 394 395 if ww, ok := w.(writer); ok { 396 e.w = ww 397 } else { 398 e.w = bufio.NewWriter(w) 399 } 400 401 e.writeHeader() 402 for i, pm := range g.Image { 403 disposal := uint8(0) 404 if g.Disposal != nil { 405 disposal = g.Disposal[i] 406 } 407 e.writeImageBlock(pm, g.Delay[i], disposal) 408 } 409 e.writeByte(sTrailer) 410 e.flush() 411 return e.err 412 } 413 414 // Encode writes the Image m to w in GIF format. 415 func Encode(w io.Writer, m image.Image, o *Options) error { 416 // Check for bounds and size restrictions. 417 b := m.Bounds() 418 if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 { 419 return errors.New("gif: image is too large to encode") 420 } 421 422 opts := Options{} 423 if o != nil { 424 opts = *o 425 } 426 if opts.NumColors < 1 || 256 < opts.NumColors { 427 opts.NumColors = 256 428 } 429 if opts.Drawer == nil { 430 opts.Drawer = draw.FloydSteinberg 431 } 432 433 pm, _ := m.(*image.Paletted) 434 if pm == nil { 435 if cp, ok := m.ColorModel().(color.Palette); ok { 436 pm = image.NewPaletted(b, cp) 437 for y := b.Min.Y; y < b.Max.Y; y++ { 438 for x := b.Min.X; x < b.Max.X; x++ { 439 pm.Set(x, y, cp.Convert(m.At(x, y))) 440 } 441 } 442 } 443 } 444 if pm == nil || len(pm.Palette) > opts.NumColors { 445 // Set pm to be a palettedized copy of m, including its bounds, which 446 // might not start at (0, 0). 447 // 448 // TODO: Pick a better sub-sample of the Plan 9 palette. 449 pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors]) 450 if opts.Quantizer != nil { 451 pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m) 452 } 453 opts.Drawer.Draw(pm, b, m, b.Min) 454 } 455 456 // When calling Encode instead of EncodeAll, the single-frame image is 457 // translated such that its top-left corner is (0, 0), so that the single 458 // frame completely fills the overall GIF's bounds. 459 if pm.Rect.Min != (image.Point{}) { 460 dup := *pm 461 dup.Rect = dup.Rect.Sub(dup.Rect.Min) 462 pm = &dup 463 } 464 465 return EncodeAll(w, &GIF{ 466 Image: []*image.Paletted{pm}, 467 Delay: []int{0}, 468 Config: image.Config{ 469 ColorModel: pm.Palette, 470 Width: b.Dx(), 471 Height: b.Dy(), 472 }, 473 }) 474 }