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