Source file
src/image/gif/writer.go
1
2
3
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 "internal/byteorder"
17 "io"
18 "math/bits"
19 )
20
21
22 const (
23 gcLabel = 0xF9
24 gcBlockSize = 0x04
25 )
26
27 func log2(x int) int {
28 if x < 2 {
29 return 0
30 }
31 return bits.Len(uint(x-1)) - 1
32 }
33
34
35 type writer interface {
36 Flush() error
37 io.Writer
38 io.ByteWriter
39 }
40
41
42 type encoder struct {
43
44
45 w writer
46 err error
47
48 g GIF
49
50 globalCT int
51
52 buf [256]byte
53 globalColorTable [3 * 256]byte
54 localColorTable [3 * 256]byte
55 }
56
57
58
59
60
61 type blockWriter struct {
62 e *encoder
63 }
64
65 func (b blockWriter) setup() {
66 b.e.buf[0] = 0
67 }
68
69 func (b blockWriter) Flush() error {
70 return b.e.err
71 }
72
73 func (b blockWriter) WriteByte(c byte) error {
74 if b.e.err != nil {
75 return b.e.err
76 }
77
78
79 b.e.buf[0]++
80 b.e.buf[b.e.buf[0]] = c
81 if b.e.buf[0] < 255 {
82 return nil
83 }
84
85
86 b.e.write(b.e.buf[:256])
87 b.e.buf[0] = 0
88 return b.e.err
89 }
90
91
92
93 func (b blockWriter) Write(data []byte) (int, error) {
94 for i, c := range data {
95 if err := b.WriteByte(c); err != nil {
96 return i, err
97 }
98 }
99 return len(data), nil
100 }
101
102 func (b blockWriter) close() {
103
104
105 if b.e.buf[0] == 0 {
106 b.e.writeByte(0)
107 } else {
108 n := uint(b.e.buf[0])
109 b.e.buf[n+1] = 0
110 b.e.write(b.e.buf[:n+2])
111 }
112 b.e.flush()
113 }
114
115 func (e *encoder) flush() {
116 if e.err != nil {
117 return
118 }
119 e.err = e.w.Flush()
120 }
121
122 func (e *encoder) write(p []byte) {
123 if e.err != nil {
124 return
125 }
126 _, e.err = e.w.Write(p)
127 }
128
129 func (e *encoder) writeByte(b byte) {
130 if e.err != nil {
131 return
132 }
133 e.err = e.w.WriteByte(b)
134 }
135
136 func (e *encoder) writeHeader() {
137 if e.err != nil {
138 return
139 }
140 _, e.err = io.WriteString(e.w, "GIF89a")
141 if e.err != nil {
142 return
143 }
144
145
146 byteorder.LEPutUint16(e.buf[0:2], uint16(e.g.Config.Width))
147 byteorder.LEPutUint16(e.buf[2:4], uint16(e.g.Config.Height))
148 e.write(e.buf[:4])
149
150 if p, ok := e.g.Config.ColorModel.(color.Palette); ok && len(p) > 0 {
151 paddedSize := log2(len(p))
152 e.buf[0] = fColorTable | uint8(paddedSize)
153 e.buf[1] = e.g.BackgroundIndex
154 e.buf[2] = 0x00
155 e.write(e.buf[:3])
156 var err error
157 e.globalCT, err = encodeColorTable(e.globalColorTable[:], p, paddedSize)
158 if err != nil && e.err == nil {
159 e.err = err
160 return
161 }
162 e.write(e.globalColorTable[:e.globalCT])
163 } else {
164
165
166 e.buf[0] = 0x00
167 e.buf[1] = 0x00
168 e.buf[2] = 0x00
169 e.write(e.buf[:3])
170 }
171
172
173 if len(e.g.Image) > 1 && e.g.LoopCount >= 0 {
174 e.buf[0] = 0x21
175 e.buf[1] = 0xff
176 e.buf[2] = 0x0b
177 e.write(e.buf[:3])
178 _, err := io.WriteString(e.w, "NETSCAPE2.0")
179 if err != nil && e.err == nil {
180 e.err = err
181 return
182 }
183 e.buf[0] = 0x03
184 e.buf[1] = 0x01
185 byteorder.LEPutUint16(e.buf[2:4], uint16(e.g.LoopCount))
186 e.buf[4] = 0x00
187 e.write(e.buf[:5])
188 }
189 }
190
191 func encodeColorTable(dst []byte, p color.Palette, size int) (int, error) {
192 if uint(size) >= 8 {
193 return 0, errors.New("gif: cannot encode color table with more than 256 entries")
194 }
195 for i, c := range p {
196 if c == nil {
197 return 0, errors.New("gif: cannot encode color table with nil entries")
198 }
199 var r, g, b uint8
200
201
202 if rgba, ok := c.(color.RGBA); ok {
203 r, g, b = rgba.R, rgba.G, rgba.B
204 } else {
205 rr, gg, bb, _ := c.RGBA()
206 r, g, b = uint8(rr>>8), uint8(gg>>8), uint8(bb>>8)
207 }
208 dst[3*i+0] = r
209 dst[3*i+1] = g
210 dst[3*i+2] = b
211 }
212 n := 1 << (size + 1)
213 if n > len(p) {
214
215 clear(dst[3*len(p) : 3*n])
216 }
217 return 3 * n, nil
218 }
219
220 func (e *encoder) colorTablesMatch(localLen, transparentIndex int) bool {
221 localSize := 3 * localLen
222 if transparentIndex >= 0 {
223 trOff := 3 * transparentIndex
224 return bytes.Equal(e.globalColorTable[:trOff], e.localColorTable[:trOff]) &&
225 bytes.Equal(e.globalColorTable[trOff+3:localSize], e.localColorTable[trOff+3:localSize])
226 }
227 return bytes.Equal(e.globalColorTable[:localSize], e.localColorTable[:localSize])
228 }
229
230 func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) {
231 if e.err != nil {
232 return
233 }
234
235 if len(pm.Palette) == 0 {
236 e.err = errors.New("gif: cannot encode image block with empty palette")
237 return
238 }
239
240 b := pm.Bounds()
241 if b.Min.X < 0 || b.Max.X >= 1<<16 || b.Min.Y < 0 || b.Max.Y >= 1<<16 {
242 e.err = errors.New("gif: image block is too large to encode")
243 return
244 }
245 if !b.In(image.Rectangle{Max: image.Point{e.g.Config.Width, e.g.Config.Height}}) {
246 e.err = errors.New("gif: image block is out of bounds")
247 return
248 }
249
250 transparentIndex := -1
251 for i, c := range pm.Palette {
252 if c == nil {
253 e.err = errors.New("gif: cannot encode color table with nil entries")
254 return
255 }
256 if _, _, _, a := c.RGBA(); a == 0 {
257 transparentIndex = i
258 break
259 }
260 }
261
262 if delay > 0 || disposal != 0 || transparentIndex != -1 {
263 e.buf[0] = sExtension
264 e.buf[1] = gcLabel
265 e.buf[2] = gcBlockSize
266 if transparentIndex != -1 {
267 e.buf[3] = 0x01 | disposal<<2
268 } else {
269 e.buf[3] = 0x00 | disposal<<2
270 }
271 byteorder.LEPutUint16(e.buf[4:6], uint16(delay))
272
273
274 if transparentIndex != -1 {
275 e.buf[6] = uint8(transparentIndex)
276 } else {
277 e.buf[6] = 0x00
278 }
279 e.buf[7] = 0x00
280 e.write(e.buf[:8])
281 }
282 e.buf[0] = sImageDescriptor
283 byteorder.LEPutUint16(e.buf[1:3], uint16(b.Min.X))
284 byteorder.LEPutUint16(e.buf[3:5], uint16(b.Min.Y))
285 byteorder.LEPutUint16(e.buf[5:7], uint16(b.Dx()))
286 byteorder.LEPutUint16(e.buf[7:9], uint16(b.Dy()))
287 e.write(e.buf[:9])
288
289
290
291
292
293 paddedSize := log2(len(pm.Palette))
294 if gp, ok := e.g.Config.ColorModel.(color.Palette); ok && len(pm.Palette) <= len(gp) && &gp[0] == &pm.Palette[0] {
295 e.writeByte(0)
296 } else {
297 ct, err := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize)
298 if err != nil {
299 if e.err == nil {
300 e.err = err
301 }
302 return
303 }
304
305
306
307 if ct <= e.globalCT && e.colorTablesMatch(len(pm.Palette), transparentIndex) {
308 e.writeByte(0)
309 } else {
310
311 e.writeByte(fColorTable | uint8(paddedSize))
312 e.write(e.localColorTable[:ct])
313 }
314 }
315
316 litWidth := paddedSize + 1
317 if litWidth < 2 {
318 litWidth = 2
319 }
320 e.writeByte(uint8(litWidth))
321
322 bw := blockWriter{e: e}
323 bw.setup()
324 lzww := lzw.NewWriter(bw, lzw.LSB, litWidth)
325 if dx := b.Dx(); dx == pm.Stride {
326 _, e.err = lzww.Write(pm.Pix[:dx*b.Dy()])
327 if e.err != nil {
328 lzww.Close()
329 return
330 }
331 } else {
332 for i, y := 0, b.Min.Y; y < b.Max.Y; i, y = i+pm.Stride, y+1 {
333 _, e.err = lzww.Write(pm.Pix[i : i+dx])
334 if e.err != nil {
335 lzww.Close()
336 return
337 }
338 }
339 }
340 lzww.Close()
341 bw.close()
342 }
343
344
345 type Options struct {
346
347
348 NumColors int
349
350
351
352 Quantizer draw.Quantizer
353
354
355
356 Drawer draw.Drawer
357 }
358
359
360
361 func EncodeAll(w io.Writer, g *GIF) error {
362 if len(g.Image) == 0 {
363 return errors.New("gif: must provide at least one image")
364 }
365
366 if len(g.Image) != len(g.Delay) {
367 return errors.New("gif: mismatched image and delay lengths")
368 }
369
370 e := encoder{g: *g}
371
372
373
374 if e.g.Disposal != nil && len(e.g.Image) != len(e.g.Disposal) {
375 return errors.New("gif: mismatched image and disposal lengths")
376 }
377 if e.g.Config == (image.Config{}) {
378 p := g.Image[0].Bounds().Max
379 e.g.Config.Width = p.X
380 e.g.Config.Height = p.Y
381 } else if e.g.Config.ColorModel != nil {
382 if _, ok := e.g.Config.ColorModel.(color.Palette); !ok {
383 return errors.New("gif: GIF color model must be a color.Palette")
384 }
385 }
386
387 if ww, ok := w.(writer); ok {
388 e.w = ww
389 } else {
390 e.w = bufio.NewWriter(w)
391 }
392
393 e.writeHeader()
394 for i, pm := range g.Image {
395 disposal := uint8(0)
396 if g.Disposal != nil {
397 disposal = g.Disposal[i]
398 }
399 e.writeImageBlock(pm, g.Delay[i], disposal)
400 }
401 e.writeByte(sTrailer)
402 e.flush()
403 return e.err
404 }
405
406
407 func Encode(w io.Writer, m image.Image, o *Options) error {
408
409 b := m.Bounds()
410 if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 {
411 return errors.New("gif: image is too large to encode")
412 }
413
414 opts := Options{}
415 if o != nil {
416 opts = *o
417 }
418 if opts.NumColors < 1 || 256 < opts.NumColors {
419 opts.NumColors = 256
420 }
421 if opts.Drawer == nil {
422 opts.Drawer = draw.FloydSteinberg
423 }
424
425 pm, _ := m.(*image.Paletted)
426 if pm == nil {
427 if cp, ok := m.ColorModel().(color.Palette); ok {
428 pm = image.NewPaletted(b, cp)
429 for y := b.Min.Y; y < b.Max.Y; y++ {
430 for x := b.Min.X; x < b.Max.X; x++ {
431 pm.Set(x, y, cp.Convert(m.At(x, y)))
432 }
433 }
434 }
435 }
436 if pm == nil || len(pm.Palette) > opts.NumColors {
437
438
439
440
441 pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors])
442 if opts.Quantizer != nil {
443 pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m)
444 }
445 opts.Drawer.Draw(pm, b, m, b.Min)
446 }
447
448
449
450
451 if pm.Rect.Min != (image.Point{}) {
452 dup := *pm
453 dup.Rect = dup.Rect.Sub(dup.Rect.Min)
454 pm = &dup
455 }
456
457 return EncodeAll(w, &GIF{
458 Image: []*image.Paletted{pm},
459 Delay: []int{0},
460 Config: image.Config{
461 ColorModel: pm.Palette,
462 Width: b.Dx(),
463 Height: b.Dy(),
464 },
465 })
466 }
467
View as plain text