blob: 7b6f504367cf8e8983c80e69a0c0acf078bfa8c2 [file] [log] [blame]
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gif
import (
"bytes"
"compress/lzw"
"image"
"image/color"
"reflect"
"testing"
)
// header, palette and trailer are parts of a valid 2x1 GIF image.
const (
headerStr = "GIF89a" +
"\x02\x00\x01\x00" + // width=2, height=1
"\x80\x00\x00" // headerFields=(a color map of 2 pixels), backgroundIndex, aspect
paletteStr = "\x10\x20\x30\x40\x50\x60" // the color map, also known as a palette
trailerStr = "\x3b"
)
// lzwEncode returns an LZW encoding (with 2-bit literals) of n zeroes.
func lzwEncode(n int) []byte {
b := &bytes.Buffer{}
w := lzw.NewWriter(b, lzw.LSB, 2)
w.Write(make([]byte, n))
w.Close()
return b.Bytes()
}
func TestDecode(t *testing.T) {
testCases := []struct {
nPix int // The number of pixels in the image data.
extra bool // Whether to write an extra block after the LZW-encoded data.
wantErr error
}{
{0, false, errNotEnough},
{1, false, errNotEnough},
{2, false, nil},
{2, true, errTooMuch},
{3, false, errTooMuch},
}
for _, tc := range testCases {
b := &bytes.Buffer{}
b.WriteString(headerStr)
b.WriteString(paletteStr)
// Write an image with bounds 2x1 but tc.nPix pixels. If tc.nPix != 2
// then this should result in an invalid GIF image. First, write a
// magic 0x2c (image descriptor) byte, bounds=(0,0)-(2,1), a flags
// byte, and 2-bit LZW literals.
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
if tc.nPix > 0 {
enc := lzwEncode(tc.nPix)
if len(enc) > 0xff {
t.Errorf("nPix=%d, extra=%t: compressed length %d is too large", tc.nPix, tc.extra, len(enc))
continue
}
b.WriteByte(byte(len(enc)))
b.Write(enc)
}
if tc.extra {
b.WriteString("\x01\x02") // A 1-byte payload with an 0x02 byte.
}
b.WriteByte(0x00) // An empty block signifies the end of the image data.
b.WriteString(trailerStr)
got, err := Decode(b)
if err != tc.wantErr {
t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, err, tc.wantErr)
}
if tc.wantErr != nil {
continue
}
want := &image.Paletted{
Pix: []uint8{0, 0},
Stride: 2,
Rect: image.Rect(0, 0, 2, 1),
Palette: color.Palette{
color.RGBA{0x10, 0x20, 0x30, 0xff},
color.RGBA{0x40, 0x50, 0x60, 0xff},
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, got, want)
}
}
}
func TestTransparentIndex(t *testing.T) {
b := &bytes.Buffer{}
b.WriteString(headerStr)
b.WriteString(paletteStr)
for transparentIndex := 0; transparentIndex < 3; transparentIndex++ {
if transparentIndex < 2 {
// Write the graphic control for the transparent index.
b.WriteString("\x21\xf9\x00\x01\x00\x00")
b.WriteByte(byte(transparentIndex))
b.WriteByte(0)
}
// Write an image with bounds 2x1, as per TestDecode.
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
enc := lzwEncode(2)
if len(enc) > 0xff {
t.Fatalf("compressed length %d is too large", len(enc))
}
b.WriteByte(byte(len(enc)))
b.Write(enc)
b.WriteByte(0x00)
}
b.WriteString(trailerStr)
g, err := DecodeAll(b)
if err != nil {
t.Fatalf("DecodeAll: %v", err)
}
c0 := color.RGBA{paletteStr[0], paletteStr[1], paletteStr[2], 0xff}
c1 := color.RGBA{paletteStr[3], paletteStr[4], paletteStr[5], 0xff}
cz := color.RGBA{}
wants := []color.Palette{
{cz, c1},
{c0, cz},
{c0, c1},
}
if len(g.Image) != len(wants) {
t.Fatalf("got %d images, want %d", len(g.Image), len(wants))
}
for i, want := range wants {
got := g.Image[i].Palette
if !reflect.DeepEqual(got, want) {
t.Errorf("palette #%d:\ngot %v\nwant %v", i, got, want)
}
}
}
// testGIF is a simple GIF that we can modify to test different scenarios.
var testGIF = []byte{
'G', 'I', 'F', '8', '9', 'a',
1, 0, 1, 0, // w=1, h=1 (6)
128, 0, 0, // headerFields, bg, aspect (10)
0, 0, 0, 1, 1, 1, // color map and graphics control (13)
0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, // (19)
// frame 1 (0,0 - 1,1)
0x2c,
0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x01, 0x00, // (32)
0x00,
0x02, 0x02, 0x4c, 0x01, 0x00, // lzw pixels
// trailer
0x3b,
}
func try(t *testing.T, b []byte, want string) {
_, err := DecodeAll(bytes.NewReader(b))
var got string
if err != nil {
got = err.Error()
}
if got != want {
t.Fatalf("got %v, want %v", got, want)
}
}
func TestBounds(t *testing.T) {
// Make a local copy of testGIF.
gif := make([]byte, len(testGIF))
copy(gif, testGIF)
// Make the bounds too big, just by one.
gif[32] = 2
want := "gif: frame bounds larger than image bounds"
try(t, gif, want)
// Make the bounds too small; does not trigger bounds
// check, but now there's too much data.
gif[32] = 0
want = "gif: too much image data"
try(t, gif, want)
gif[32] = 1
// Make the bounds really big, expect an error.
want = "gif: frame bounds larger than image bounds"
for i := 0; i < 4; i++ {
gif[32+i] = 0xff
}
try(t, gif, want)
}
func TestNoPalette(t *testing.T) {
b := &bytes.Buffer{}
// Manufacture a GIF with no palette, so any pixel at all
// will be invalid.
b.WriteString(headerStr[:len(headerStr)-3])
b.WriteString("\x00\x00\x00") // No global palette.
// Image descriptor: 2x1, no local palette.
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
// Encode the pixels: neither is in range, because there is no palette.
pix := []byte{0, 128}
enc := &bytes.Buffer{}
w := lzw.NewWriter(enc, lzw.LSB, 2)
w.Write(pix)
w.Close()
b.WriteByte(byte(len(enc.Bytes())))
b.Write(enc.Bytes())
b.WriteByte(0x00) // An empty block signifies the end of the image data.
b.WriteString(trailerStr)
try(t, b.Bytes(), "gif: invalid pixel value")
}
func TestPixelOutsidePaletteRange(t *testing.T) {
for _, pval := range []byte{0, 1, 2, 3, 255} {
b := &bytes.Buffer{}
// Manufacture a GIF with a 2 color palette.
b.WriteString(headerStr)
b.WriteString(paletteStr)
// Image descriptor: 2x1, no local palette.
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
// Encode the pixels; some pvals trigger the expected error.
pix := []byte{pval, pval}
enc := &bytes.Buffer{}
w := lzw.NewWriter(enc, lzw.LSB, 2)
w.Write(pix)
w.Close()
b.WriteByte(byte(len(enc.Bytes())))
b.Write(enc.Bytes())
b.WriteByte(0x00) // An empty block signifies the end of the image data.
b.WriteString(trailerStr)
// No error expected, unless the pixels are beyond the 2 color palette.
want := ""
if pval >= 2 {
want = "gif: invalid pixel value"
}
try(t, b.Bytes(), want)
}
}