Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 // Copyright 2013 The Go Authors. All rights reserved. | 1 // Copyright 2013 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 package gif | 5 package gif |
6 | 6 |
7 import ( | 7 import ( |
8 "bufio" | 8 "bufio" |
9 "compress/lzw" | 9 "compress/lzw" |
10 "errors" | 10 "errors" |
11 "image" | 11 "image" |
12 "image/color" | 12 "image/color" |
13 "image/draw" | |
13 "io" | 14 "io" |
14 ) | 15 ) |
15 | 16 |
16 // Graphic control extension fields. | 17 // Graphic control extension fields. |
17 const ( | 18 const ( |
18 gcLabel = 0xF9 | 19 gcLabel = 0xF9 |
19 gcBlockSize = 0x04 | 20 gcBlockSize = 0x04 |
20 ) | 21 ) |
21 | 22 |
22 var log2Lookup = [8]int{2, 4, 8, 16, 32, 64, 128, 256} | 23 var log2Lookup = [8]int{2, 4, 8, 16, 32, 64, 128, 256} |
23 | 24 |
24 func log2Int256(x int) int { | 25 func log2(x int) int { |
25 for i, v := range log2Lookup { | 26 for i, v := range log2Lookup { |
26 if x <= v { | 27 if x <= v { |
27 return i | 28 return i |
28 } | 29 } |
29 } | 30 } |
30 return -1 | 31 return -1 |
31 } | 32 } |
32 | 33 |
33 // Little-endian. | 34 // Little-endian. |
34 func writeUint16(b []uint8, u uint16) { | 35 func writeUint16(b []uint8, u uint16) { |
35 b[0] = uint8(u) | 36 b[0] = uint8(u) |
36 b[1] = uint8(u >> 8) | 37 b[1] = uint8(u >> 8) |
37 } | 38 } |
38 | 39 |
40 // writer is a buffered writer. | |
39 type writer interface { | 41 type writer interface { |
42 Flush() error | |
40 io.Writer | 43 io.Writer |
41 io.ByteWriter | 44 io.ByteWriter |
nigeltao
2013/07/03 03:27:25
Do you call writer.WriteByte anywhere? You probabl
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
42 } | 45 } |
43 | 46 |
47 // encoder encodes an image to the GIF format. | |
44 type encoder struct { | 48 type encoder struct { |
45 » w writer | 49 » // w is the writer to write to. err is the first error encountered durin g |
46 » g *GIF | 50 » // writing. All attempted writes after the first error become no-ops. |
51 » w writer | |
52 » err error | |
53 » // g is a reference to the data that is being encoded. | |
54 » g *GIF | |
55 » // bitsPerPixel is the number of bits required to represent each color | |
56 » // in the image. | |
47 bitsPerPixel int | 57 bitsPerPixel int |
48 » err error | 58 » // buf is a scratch buffer. It must be at least 768 so we can write the color map. |
49 » buf [16]byte | 59 » buf [1024]byte |
50 } | 60 } |
51 | 61 |
52 func newEncoder(w io.Writer) *encoder { | 62 // blockWriter writes the block structure of GIF image data, which |
53 » var e encoder | 63 // comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the |
54 » if ww, ok := w.(writer); ok { | 64 // writer given to the LZW encoder, which is thus immune to the |
55 » » e.w = ww | 65 // blocking. |
56 » } else { | |
57 » » e.w = bufio.NewWriter(w) | |
58 » } | |
59 » return &e | |
60 } | |
61 | |
62 type blockWriter struct { | 66 type blockWriter struct { |
63 » w writer | 67 » e *encoder |
64 » slice []byte | 68 } |
65 » err error | 69 |
66 » tmp [256]byte | 70 func (b blockWriter) Write(data []byte) (int, error) { |
67 } | 71 » if b.e.err != nil { |
68 | 72 » » return 0, b.e.err |
69 func (b *blockWriter) Write(data []byte) (int, error) { | |
70 » if b.err != nil { | |
71 » » return 0, b.err | |
72 } | 73 } |
73 if len(data) == 0 { | 74 if len(data) == 0 { |
74 return 0, nil | 75 return 0, nil |
75 } | 76 } |
76 total := 0 | 77 total := 0 |
77 for total < len(data) { | 78 for total < len(data) { |
78 » » b.slice = b.tmp[1:256] | 79 » » n := copy(b.e.buf[1:256], data[total:]) |
nigeltao
2013/07/03 03:27:25
Why is b.slice a field? Just use b.tmp[1:256] on t
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
79 » » n := copy(b.slice, data[total:]) | |
80 total += n | 80 total += n |
81 » » b.tmp[0] = uint8(n) | 81 » » b.e.buf[0] = uint8(n) |
82 | 82 |
83 » » n, b.err = b.w.Write(b.tmp[:n+1]) | 83 » » n, b.e.err = b.e.w.Write(b.e.buf[:n+1]) |
84 » » if b.err != nil { | 84 » » if b.e.err != nil { |
85 » » » return 0, b.err | 85 » » » return 0, b.e.err |
86 » » } | 86 » » } |
87 » } | 87 » } |
88 » return total, b.err | 88 » return total, b.e.err |
89 } | |
90 | |
91 func (e *encoder) flush() { | |
92 » if e.err != nil { | |
93 » » return | |
94 » } | |
95 » e.err = e.w.Flush() | |
89 } | 96 } |
90 | 97 |
91 func (e *encoder) write(p []byte) { | 98 func (e *encoder) write(p []byte) { |
92 if e.err != nil { | 99 if e.err != nil { |
93 return | 100 return |
94 } | 101 } |
95 _, e.err = e.w.Write(p) | 102 _, e.err = e.w.Write(p) |
96 } | 103 } |
97 | 104 |
105 func (e *encoder) writeByte(b byte) { | |
106 if e.err != nil { | |
107 return | |
108 } | |
109 e.err = e.w.WriteByte(b) | |
110 } | |
111 | |
98 func (e *encoder) writeHeader() { | 112 func (e *encoder) writeHeader() { |
99 if e.err != nil { | 113 if e.err != nil { |
100 return | 114 return |
101 } | 115 } |
102 // TODO: GIF87a could be valid depending on the features that | |
103 // the image uses. | |
104 _, e.err = io.WriteString(e.w, "GIF89a") | 116 _, e.err = io.WriteString(e.w, "GIF89a") |
105 if e.err != nil { | 117 if e.err != nil { |
106 return | 118 return |
107 } | 119 } |
108 | 120 |
109 // TODO: This bases the global color table on the first image | 121 // TODO: This bases the global color table on the first image |
110 // only. | 122 // only. |
111 pm := e.g.Image[0] | 123 pm := e.g.Image[0] |
112 // Logical screen width and height. | 124 // Logical screen width and height. |
113 » writeUint16(e.buf[:2], uint16(pm.Bounds().Dx())) | 125 » writeUint16(e.buf[0:2], uint16(pm.Bounds().Dx())) |
114 writeUint16(e.buf[2:4], uint16(pm.Bounds().Dy())) | 126 writeUint16(e.buf[2:4], uint16(pm.Bounds().Dy())) |
115 e.write(e.buf[:4]) | 127 e.write(e.buf[:4]) |
116 | 128 |
117 » e.bitsPerPixel = log2Int256(len(pm.Palette)) + 1 | 129 » e.bitsPerPixel = log2(len(pm.Palette)) + 1 |
118 e.buf[0] = 0x80 | ((uint8(e.bitsPerPixel) - 1) << 4) | (uint8(e.bitsPerP ixel) - 1) | 130 e.buf[0] = 0x80 | ((uint8(e.bitsPerPixel) - 1) << 4) | (uint8(e.bitsPerP ixel) - 1) |
119 e.buf[1] = 0x00 // Background Color Index. | 131 e.buf[1] = 0x00 // Background Color Index. |
120 e.buf[2] = 0x00 // Pixel Aspect Ratio. | 132 e.buf[2] = 0x00 // Pixel Aspect Ratio. |
121 e.write(e.buf[:3]) | 133 e.write(e.buf[:3]) |
122 | 134 |
123 // Global Color Table. | 135 // Global Color Table. |
124 e.writeColorTable(pm.Palette, e.bitsPerPixel-1) | 136 e.writeColorTable(pm.Palette, e.bitsPerPixel-1) |
125 | 137 |
126 // Add animation info if necessary. | 138 // Add animation info if necessary. |
127 if len(e.g.Image) > 1 { | 139 if len(e.g.Image) > 1 { |
128 » » e.buf[0] = 0x21 // Extention Introducer. | 140 » » e.buf[0] = 0x21 // Extension Introducer. |
nigeltao
2013/07/03 03:27:25
Typo in "Extension".
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
129 » » e.buf[1] = 0xff // Aplication Label. | 141 » » e.buf[1] = 0xff // Application Label. |
nigeltao
2013/07/03 03:27:25
Typo in "Application".
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
130 e.buf[2] = 0x0b // Block Size. | 142 e.buf[2] = 0x0b // Block Size. |
131 e.write(e.buf[:3]) | 143 e.write(e.buf[:3]) |
132 _, e.err = io.WriteString(e.w, "NETSCAPE2.0") // Application Ide ntifier. | 144 _, e.err = io.WriteString(e.w, "NETSCAPE2.0") // Application Ide ntifier. |
133 if e.err != nil { | 145 if e.err != nil { |
134 return | 146 return |
135 } | 147 } |
136 e.buf[0] = 0x03 // Block Size. | 148 e.buf[0] = 0x03 // Block Size. |
137 e.buf[1] = 0x01 // Sub-block Index. | 149 e.buf[1] = 0x01 // Sub-block Index. |
138 writeUint16(e.buf[2:4], uint16(e.g.LoopCount)) | 150 writeUint16(e.buf[2:4], uint16(e.g.LoopCount)) |
139 e.buf[4] = 0x00 // Block Terminator. | 151 e.buf[4] = 0x00 // Block Terminator. |
140 e.write(e.buf[:5]) | 152 e.write(e.buf[:5]) |
141 } | 153 } |
142 } | 154 } |
143 | 155 |
144 func (e *encoder) writeColorTable(p color.Palette, size int) { | 156 func (e *encoder) writeColorTable(p color.Palette, size int) { |
145 if e.err != nil { | 157 if e.err != nil { |
146 return | 158 return |
147 } | 159 } |
148 | 160 |
149 for i := 0; i < log2Lookup[size]; i++ { | 161 for i := 0; i < log2Lookup[size]; i++ { |
150 if i < len(p) { | 162 if i < len(p) { |
151 r, g, b, _ := p[i].RGBA() | 163 r, g, b, _ := p[i].RGBA() |
152 » » » e.buf[0] = uint8(r >> 8) | 164 » » » e.buf[3*i+0] = uint8(r >> 8) |
153 » » » e.buf[1] = uint8(g >> 8) | 165 » » » e.buf[3*i+1] = uint8(g >> 8) |
154 » » » e.buf[2] = uint8(b >> 8) | 166 » » » e.buf[3*i+2] = uint8(b >> 8) |
155 } else { | 167 } else { |
156 // Pad with black. | 168 // Pad with black. |
157 » » » e.buf[0] = 0x00 | 169 » » » e.buf[3*i+0] = 0x00 |
158 » » » e.buf[1] = 0x00 | 170 » » » e.buf[3*i+1] = 0x00 |
159 » » » e.buf[2] = 0x00 | 171 » » » e.buf[3*i+2] = 0x00 |
160 » » } | 172 » » } |
161 » » e.write(e.buf[:3]) | 173 » } |
nigeltao
2013/07/03 03:27:25
Making 1 write of 768 bytes will be more efficient
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
162 » } | 174 » e.write(e.buf[:3*log2Lookup[size]]) |
163 } | 175 } |
164 | 176 |
165 func (e *encoder) writeImageBlock(pm *image.Paletted, delay int) { | 177 func (e *encoder) writeImageBlock(pm *image.Paletted, delay int) { |
166 if e.err != nil { | 178 if e.err != nil { |
179 return | |
180 } | |
181 | |
182 if len(pm.Palette) == 0 { | |
183 e.err = errors.New("gif: cannot encode image block with empty pa lette") | |
184 return | |
185 } | |
186 | |
187 b := pm.Bounds() | |
188 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 { | |
189 e.err = errors.New("gif: image block is too large to encode") | |
167 return | 190 return |
168 } | 191 } |
169 | 192 |
170 transparentIndex := -1 | 193 transparentIndex := -1 |
171 for i, c := range pm.Palette { | 194 for i, c := range pm.Palette { |
172 if _, _, _, a := c.RGBA(); a == 0 { | 195 if _, _, _, a := c.RGBA(); a == 0 { |
173 transparentIndex = i | 196 transparentIndex = i |
174 break | 197 break |
175 } | 198 } |
176 } | 199 } |
(...skipping 12 matching lines...) Expand all Loading... | |
189 // Transparent color index. | 212 // Transparent color index. |
190 if transparentIndex != -1 { | 213 if transparentIndex != -1 { |
191 e.buf[6] = uint8(transparentIndex) | 214 e.buf[6] = uint8(transparentIndex) |
192 } else { | 215 } else { |
193 e.buf[6] = 0x00 | 216 e.buf[6] = 0x00 |
194 } | 217 } |
195 e.buf[7] = 0x00 // Block Terminator. | 218 e.buf[7] = 0x00 // Block Terminator. |
196 e.write(e.buf[:8]) | 219 e.write(e.buf[:8]) |
197 } | 220 } |
198 e.buf[0] = sImageDescriptor | 221 e.buf[0] = sImageDescriptor |
199 » writeUint16(e.buf[1:3], uint16(pm.Bounds().Min.X)) | 222 » writeUint16(e.buf[1:3], uint16(b.Min.X)) |
nigeltao
2013/07/03 03:27:25
You should probably return an error if any of thes
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
200 » writeUint16(e.buf[3:5], uint16(pm.Bounds().Min.Y)) | 223 » writeUint16(e.buf[3:5], uint16(b.Min.Y)) |
201 » writeUint16(e.buf[5:7], uint16(pm.Bounds().Dx())) | 224 » writeUint16(e.buf[5:7], uint16(b.Dx())) |
202 » writeUint16(e.buf[7:9], uint16(pm.Bounds().Dy())) | 225 » writeUint16(e.buf[7:9], uint16(b.Dy())) |
203 e.write(e.buf[:9]) | 226 e.write(e.buf[:9]) |
204 | 227 |
205 » if len(pm.Palette) > 0 { | 228 » paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n ). |
nigeltao
2013/07/03 03:27:25
I'd just return an error if pm has an empty palett
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
206 » » paddedSize := log2Int256(len(pm.Palette)) // Size of Local Color Table: 2^(1+n). | 229 » // Interlacing is not supported. |
207 » » // Interlacing is not supported. | 230 » e.writeByte(0x80 | uint8(paddedSize)) |
208 » » e.buf[0] = 0x80 | uint8(paddedSize) | 231 |
209 » » e.write(e.buf[:1]) | 232 » // Local Color Table. |
210 | 233 » e.writeColorTable(pm.Palette, paddedSize) |
211 » » // Local Color Table. | |
212 » » e.writeColorTable(pm.Palette, paddedSize) | |
213 » } else { | |
214 » » e.buf[0] = 0x00 | |
215 » » e.write(e.buf[:1]) | |
216 » } | |
217 | 234 |
218 litWidth := e.bitsPerPixel | 235 litWidth := e.bitsPerPixel |
219 if litWidth < 2 { | 236 if litWidth < 2 { |
220 litWidth = 2 | 237 litWidth = 2 |
221 } | 238 } |
222 » e.buf[0] = uint8(litWidth) | 239 » e.writeByte(uint8(litWidth)) // LZW Minimum Code Size. |
223 » e.write(e.buf[:1]) // LZW Minimum Code Size. | 240 |
224 | 241 » lzww := lzw.NewWriter(blockWriter{e: e}, lzw.LSB, litWidth) |
225 » bw := &blockWriter{w: e.w} | |
226 » lzww := lzw.NewWriter(bw, lzw.LSB, litWidth) | |
227 _, e.err = lzww.Write(pm.Pix) | 242 _, e.err = lzww.Write(pm.Pix) |
228 if e.err != nil { | 243 if e.err != nil { |
229 lzww.Close() | 244 lzww.Close() |
230 return | 245 return |
231 } | 246 } |
232 lzww.Close() | 247 lzww.Close() |
233 | 248 » e.writeByte(0x00) // Block Terminator. |
234 » e.buf[0] = 0x00 // Block Terminator. | |
235 » e.write(e.buf[:1]) | |
236 } | 249 } |
237 | 250 |
238 // Options are the encoding parameters. | 251 // Options are the encoding parameters. |
239 type Options struct { | 252 type Options struct { |
240 » Quantizer Quantizer | 253 » // NumColors is the maximum number of colors used in the image. |
254 » // It ranges from 1 to 256. | |
255 » NumColors int | |
256 | |
257 » // Quantizer is used to produce a palette with size NumColors. | |
258 » // color.Plan9Palette is used in place of a nil Quantizer. | |
259 » Quantizer draw.Quantizer | |
260 | |
261 » // Drawer is used to convert the source image to the desired palette. | |
262 » // draw.FloydSteinberg is used in place of a nil Drawer. | |
263 » Drawer draw.Drawer | |
241 } | 264 } |
242 | 265 |
243 // EncodeAll writes the images in g to w in GIF format with the | 266 // EncodeAll writes the images in g to w in GIF format with the |
244 // given loop count and delay between frames. | 267 // given loop count and delay between frames. |
245 func EncodeAll(w io.Writer, g *GIF) error { | 268 func EncodeAll(w io.Writer, g *GIF) error { |
246 if len(g.Image) == 0 { | 269 if len(g.Image) == 0 { |
247 return errors.New("gif: must provide at least one image") | 270 return errors.New("gif: must provide at least one image") |
248 } | 271 } |
249 | 272 |
250 if len(g.Image) != len(g.Delay) { | 273 if len(g.Image) != len(g.Delay) { |
251 return errors.New("gif: mismatched image and delay lengths") | 274 return errors.New("gif: mismatched image and delay lengths") |
252 } | 275 } |
253 if g.LoopCount < 0 { | 276 if g.LoopCount < 0 { |
254 g.LoopCount = 0 | 277 g.LoopCount = 0 |
255 } | 278 } |
256 | 279 |
257 » e := newEncoder(w) | 280 » e := encoder{g: g} |
258 » e.g = g | 281 » if ww, ok := w.(writer); ok { |
282 » » e.w = ww | |
283 » } else { | |
284 » » e.w = bufio.NewWriter(w) | |
285 » } | |
286 | |
259 e.writeHeader() | 287 e.writeHeader() |
260 for i, pm := range g.Image { | 288 for i, pm := range g.Image { |
261 e.writeImageBlock(pm, g.Delay[i]) | 289 e.writeImageBlock(pm, g.Delay[i]) |
262 } | 290 } |
263 » e.buf[0] = sTrailer | 291 » e.writeByte(sTrailer) |
264 » e.write(e.buf[:1]) | 292 » e.flush() |
265 return e.err | 293 return e.err |
nigeltao
2013/07/03 03:27:25
If e.w was created above by bufio.NewWriter, then
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
266 } | 294 } |
267 | 295 |
268 // Encode writes the Image m to w in GIF format. | 296 // Encode writes the Image m to w in GIF format. |
269 func Encode(w io.Writer, m image.Image, o *Options) error { | 297 func Encode(w io.Writer, m image.Image, o *Options) error { |
270 // Check for bounds and size restrictions. | 298 // Check for bounds and size restrictions. |
271 b := m.Bounds() | 299 b := m.Bounds() |
272 if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 { | 300 if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 { |
273 return errors.New("gif: image is too large to encode") | 301 return errors.New("gif: image is too large to encode") |
274 } | 302 } |
275 | 303 |
276 » if o == nil || o.Quantizer == nil { | 304 » opts := Options{} |
277 » » o = &Options{Quantizer: &MedianCutQuantizer{NumColor: 256}} | 305 » if o != nil { |
278 » } | 306 » » opts = *o |
279 | 307 » } |
280 » e := newEncoder(w) | 308 » if opts.NumColors < 1 || 256 < opts.NumColors { |
281 » var pm *image.Paletted | 309 » » opts.NumColors = 256 |
282 » pm, e.err = o.Quantizer.Quantize(m) | 310 » } |
nigeltao
2013/07/03 03:27:25
Quantization is unnecessary work if m is already a
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
283 » if e.err != nil { | 311 » if opts.Drawer == nil { |
284 » » return e.err | 312 » » opts.Drawer = draw.FloydSteinberg |
285 » } | 313 » } |
286 » e.g = &GIF{Image: []*image.Paletted{pm}} | 314 |
nigeltao
2013/07/03 03:27:25
All this below should just be a call to EncodeAll.
Andrew Bonventre
2013/07/03 17:19:35
Done.
| |
287 | 315 » pm, ok := m.(*image.Paletted) |
288 » e.writeHeader() | 316 » if !ok || len(pm.Palette) > opts.NumColors { |
289 » e.writeImageBlock(pm, 0) | 317 » » // TODO: Pick a better sub-sample of the Plan 9 palette. |
290 | 318 » » pm = image.NewPaletted(b, color.Plan9Palette[:opts.NumColors]) |
291 » e.buf[0] = sTrailer | 319 » » if opts.Quantizer != nil { |
292 » e.write(e.buf[:1]) | 320 » » » pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m) |
293 » return e.err | 321 » » } |
294 } | 322 » » opts.Drawer.Draw(pm, b, m, image.ZP) |
323 » } | |
324 | |
325 » return EncodeAll(w, &GIF{ | |
326 » » Image: []*image.Paletted{pm}, | |
327 » » Delay: []int{0}, | |
328 » }) | |
329 } | |
LEFT | RIGHT |