Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(176)

Delta Between Two Patch Sets: src/pkg/image/gif/writer.go

Issue 10896043: image/gif: add writer implementation (Closed)
Left Patch Set: diff -r 09e39a9ce38e https://code.google.com/p/go Created 10 years, 9 months ago
Right Patch Set: diff -r 26f441a1f78b https://code.google.com/p/go/ Created 10 years, 8 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « src/pkg/image/gif/reader.go ('k') | src/pkg/image/gif/writer_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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
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 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b