Source file
src/image/gif/reader_test.go
1
2
3
4
5 package gif
6
7 import (
8 "bytes"
9 "compress/lzw"
10 "image"
11 "image/color"
12 "image/color/palette"
13 "io"
14 "os"
15 "reflect"
16 "runtime"
17 "runtime/debug"
18 "strings"
19 "testing"
20 )
21
22
23 const (
24 headerStr = "GIF89a" +
25 "\x02\x00\x01\x00" +
26 "\x80\x00\x00"
27 paletteStr = "\x10\x20\x30\x40\x50\x60"
28 trailerStr = "\x3b"
29 )
30
31
32 var _ io.ByteReader = (*blockReader)(nil)
33
34
35 func lzwEncode(in []byte) []byte {
36 b := &bytes.Buffer{}
37 w := lzw.NewWriter(b, lzw.LSB, 2)
38 if _, err := w.Write(in); err != nil {
39 panic(err)
40 }
41 if err := w.Close(); err != nil {
42 panic(err)
43 }
44 return b.Bytes()
45 }
46
47 func TestDecode(t *testing.T) {
48
49
50
51 const extra = "\x02\x02\x02\x02"
52
53 testCases := []struct {
54 nPix int
55
56
57 extraExisting int
58
59 extraSeparate int
60 wantErr error
61 }{
62 {0, 0, 0, errNotEnough},
63 {1, 0, 0, errNotEnough},
64 {2, 0, 0, nil},
65
66
67 {2, 0, 1, nil},
68
69
70 {2, 0, 2, errTooMuch},
71
72 {3, 0, 0, errTooMuch},
73
74 {2, 1, 0, nil},
75
76 {2, 2, 0, nil},
77
78
79 {2, 1, 1, errTooMuch},
80 }
81 for _, tc := range testCases {
82 b := &bytes.Buffer{}
83 b.WriteString(headerStr)
84 b.WriteString(paletteStr)
85
86
87
88
89 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
90 if tc.nPix > 0 {
91 enc := lzwEncode(make([]byte, tc.nPix))
92 if len(enc)+tc.extraExisting > 0xff {
93 t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d: compressed length %d is too large",
94 tc.nPix, tc.extraExisting, tc.extraSeparate, len(enc))
95 continue
96 }
97
98
99 b.WriteByte(byte(len(enc) + tc.extraExisting))
100
101
102 b.Write(enc)
103
104
105
106 b.WriteString(extra[:tc.extraExisting])
107 }
108
109 if tc.extraSeparate > 0 {
110
111 b.WriteByte(byte(tc.extraSeparate))
112 b.WriteString(extra[:tc.extraSeparate])
113 }
114 b.WriteByte(0x00)
115 b.WriteString(trailerStr)
116
117 got, err := Decode(b)
118 if err != tc.wantErr {
119 t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v",
120 tc.nPix, tc.extraExisting, tc.extraSeparate, err, tc.wantErr)
121 }
122
123 if tc.wantErr != nil {
124 continue
125 }
126 want := &image.Paletted{
127 Pix: []uint8{0, 0},
128 Stride: 2,
129 Rect: image.Rect(0, 0, 2, 1),
130 Palette: color.Palette{
131 color.RGBA{0x10, 0x20, 0x30, 0xff},
132 color.RGBA{0x40, 0x50, 0x60, 0xff},
133 },
134 }
135 if !reflect.DeepEqual(got, want) {
136 t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v",
137 tc.nPix, tc.extraExisting, tc.extraSeparate, got, want)
138 }
139 }
140 }
141
142 func TestTransparentIndex(t *testing.T) {
143 b := &bytes.Buffer{}
144 b.WriteString(headerStr)
145 b.WriteString(paletteStr)
146 for transparentIndex := 0; transparentIndex < 3; transparentIndex++ {
147 if transparentIndex < 2 {
148
149 b.WriteString("\x21\xf9\x04\x01\x00\x00")
150 b.WriteByte(byte(transparentIndex))
151 b.WriteByte(0)
152 }
153
154 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
155 enc := lzwEncode([]byte{0x00, 0x00})
156 if len(enc) > 0xff {
157 t.Fatalf("compressed length %d is too large", len(enc))
158 }
159 b.WriteByte(byte(len(enc)))
160 b.Write(enc)
161 b.WriteByte(0x00)
162 }
163 b.WriteString(trailerStr)
164
165 g, err := DecodeAll(b)
166 if err != nil {
167 t.Fatalf("DecodeAll: %v", err)
168 }
169 c0 := color.RGBA{paletteStr[0], paletteStr[1], paletteStr[2], 0xff}
170 c1 := color.RGBA{paletteStr[3], paletteStr[4], paletteStr[5], 0xff}
171 cz := color.RGBA{}
172 wants := []color.Palette{
173 {cz, c1},
174 {c0, cz},
175 {c0, c1},
176 }
177 if len(g.Image) != len(wants) {
178 t.Fatalf("got %d images, want %d", len(g.Image), len(wants))
179 }
180 for i, want := range wants {
181 got := g.Image[i].Palette
182 if !reflect.DeepEqual(got, want) {
183 t.Errorf("palette #%d:\ngot %v\nwant %v", i, got, want)
184 }
185 }
186 }
187
188
189 var testGIF = []byte{
190 'G', 'I', 'F', '8', '9', 'a',
191 1, 0, 1, 0,
192 128, 0, 0,
193 0, 0, 0, 1, 1, 1,
194 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00,
195
196 0x2c,
197 0x00, 0x00, 0x00, 0x00,
198 0x01, 0x00, 0x01, 0x00,
199 0x00,
200 0x02, 0x02, 0x4c, 0x01, 0x00,
201
202 0x3b,
203 }
204
205 func try(t *testing.T, b []byte, want string) {
206 _, err := DecodeAll(bytes.NewReader(b))
207 var got string
208 if err != nil {
209 got = err.Error()
210 }
211 if got != want {
212 t.Fatalf("got %v, want %v", got, want)
213 }
214 }
215
216 func TestBounds(t *testing.T) {
217
218 gif := make([]byte, len(testGIF))
219 copy(gif, testGIF)
220
221 gif[32] = 2
222 want := "gif: frame bounds larger than image bounds"
223 try(t, gif, want)
224
225
226
227 gif[32] = 0
228 want = "gif: too much image data"
229 try(t, gif, want)
230 gif[32] = 1
231
232
233 want = "gif: frame bounds larger than image bounds"
234 for i := 0; i < 4; i++ {
235 gif[32+i] = 0xff
236 }
237 try(t, gif, want)
238 }
239
240 func TestNoPalette(t *testing.T) {
241 b := &bytes.Buffer{}
242
243
244
245 b.WriteString(headerStr[:len(headerStr)-3])
246 b.WriteString("\x00\x00\x00")
247
248
249 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
250
251
252 enc := lzwEncode([]byte{0x00, 0x03})
253 b.WriteByte(byte(len(enc)))
254 b.Write(enc)
255 b.WriteByte(0x00)
256
257 b.WriteString(trailerStr)
258
259 try(t, b.Bytes(), "gif: no color table")
260 }
261
262 func TestPixelOutsidePaletteRange(t *testing.T) {
263 for _, pval := range []byte{0, 1, 2, 3} {
264 b := &bytes.Buffer{}
265
266
267 b.WriteString(headerStr)
268 b.WriteString(paletteStr)
269
270
271 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
272
273
274 enc := lzwEncode([]byte{pval, pval})
275 b.WriteByte(byte(len(enc)))
276 b.Write(enc)
277 b.WriteByte(0x00)
278
279 b.WriteString(trailerStr)
280
281
282 want := ""
283 if pval >= 2 {
284 want = "gif: invalid pixel value"
285 }
286 try(t, b.Bytes(), want)
287 }
288 }
289
290 func TestTransparentPixelOutsidePaletteRange(t *testing.T) {
291 b := &bytes.Buffer{}
292
293
294 b.WriteString(headerStr)
295 b.WriteString(paletteStr)
296
297
298
299
300
301
302
303
304 b.WriteString("\x21\xf9\x04\x01\x00\x00\x03\x00")
305
306
307 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
308
309
310 enc := lzwEncode([]byte{0x03, 0x03})
311 b.WriteByte(byte(len(enc)))
312 b.Write(enc)
313 b.WriteByte(0x00)
314
315 b.WriteString(trailerStr)
316
317 try(t, b.Bytes(), "")
318 }
319
320 func TestLoopCount(t *testing.T) {
321 testCases := []struct {
322 name string
323 data []byte
324 loopCount int
325 }{
326 {
327 "loopcount-missing",
328 []byte("GIF89a000\x00000" +
329 ",0\x00\x00\x00\n\x00\n\x00\x80000000" +
330 "\x02\b\xf01u\xb9\xfdal\x05\x00;"),
331 -1,
332 },
333 {
334 "loopcount-0",
335 []byte("GIF89a000\x00000" +
336 "!\xff\vNETSCAPE2.0\x03\x01\x00\x00\x00" +
337 ",0\x00\x00\x00\n\x00\n\x00\x80000000" +
338 "\x02\b\xf01u\xb9\xfdal\x05\x00" +
339 ",0\x00\x00\x00\n\x00\n\x00\x80000000" +
340 "\x02\b\xf01u\xb9\xfdal\x05\x00;"),
341 0,
342 },
343 {
344 "loopcount-1",
345 []byte("GIF89a000\x00000" +
346 "!\xff\vNETSCAPE2.0\x03\x01\x01\x00\x00" +
347 ",0\x00\x00\x00\n\x00\n\x00\x80000000" +
348 "\x02\b\xf01u\xb9\xfdal\x05\x00" +
349 ",0\x00\x00\x00\n\x00\n\x00\x80000000" +
350 "\x02\b\xf01u\xb9\xfdal\x05\x00;"),
351 1,
352 },
353 }
354
355 for _, tc := range testCases {
356 t.Run(tc.name, func(t *testing.T) {
357 img, err := DecodeAll(bytes.NewReader(tc.data))
358 if err != nil {
359 t.Fatal("DecodeAll:", err)
360 }
361 w := new(bytes.Buffer)
362 err = EncodeAll(w, img)
363 if err != nil {
364 t.Fatal("EncodeAll:", err)
365 }
366 img1, err := DecodeAll(w)
367 if err != nil {
368 t.Fatal("DecodeAll:", err)
369 }
370 if img.LoopCount != tc.loopCount {
371 t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, tc.loopCount)
372 }
373 if img.LoopCount != img1.LoopCount {
374 t.Errorf("loop count failed round-trip: %d vs %d", img.LoopCount, img1.LoopCount)
375 }
376 })
377 }
378 }
379
380 func TestUnexpectedEOF(t *testing.T) {
381 for i := len(testGIF) - 1; i >= 0; i-- {
382 _, err := Decode(bytes.NewReader(testGIF[:i]))
383 if err == errNotEnough {
384 continue
385 }
386 text := ""
387 if err != nil {
388 text = err.Error()
389 }
390 if !strings.HasPrefix(text, "gif:") || !strings.HasSuffix(text, ": unexpected EOF") {
391 t.Errorf("Decode(testGIF[:%d]) = %v, want gif: ...: unexpected EOF", i, err)
392 }
393 }
394 }
395
396
397 func TestDecodeMemoryConsumption(t *testing.T) {
398 const frames = 3000
399 img := image.NewPaletted(image.Rectangle{Max: image.Point{1, 1}}, palette.WebSafe)
400 hugeGIF := &GIF{
401 Image: make([]*image.Paletted, frames),
402 Delay: make([]int, frames),
403 Disposal: make([]byte, frames),
404 }
405 for i := 0; i < frames; i++ {
406 hugeGIF.Image[i] = img
407 hugeGIF.Delay[i] = 60
408 }
409 buf := new(bytes.Buffer)
410 if err := EncodeAll(buf, hugeGIF); err != nil {
411 t.Fatal("EncodeAll:", err)
412 }
413 s0, s1 := new(runtime.MemStats), new(runtime.MemStats)
414 runtime.GC()
415 defer debug.SetGCPercent(debug.SetGCPercent(5))
416 runtime.ReadMemStats(s0)
417 if _, err := Decode(buf); err != nil {
418 t.Fatal("Decode:", err)
419 }
420 runtime.ReadMemStats(s1)
421 if heapDiff := int64(s1.HeapAlloc - s0.HeapAlloc); heapDiff > 30<<20 {
422 t.Fatalf("Decode of %d frames increased heap by %dMB", frames, heapDiff>>20)
423 }
424 }
425
426 func BenchmarkDecode(b *testing.B) {
427 data, err := os.ReadFile("../testdata/video-001.gif")
428 if err != nil {
429 b.Fatal(err)
430 }
431 cfg, err := DecodeConfig(bytes.NewReader(data))
432 if err != nil {
433 b.Fatal(err)
434 }
435 b.SetBytes(int64(cfg.Width * cfg.Height))
436 b.ReportAllocs()
437 b.ResetTimer()
438 for i := 0; i < b.N; i++ {
439 Decode(bytes.NewReader(data))
440 }
441 }
442
View as plain text