Initial commit: Go 1.23 release state

This commit is contained in:
Vorapol Rinsatitnon
2024-09-21 23:49:08 +10:00
commit 17cd57a668
13231 changed files with 3114330 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
// Copyright 2021 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"
"image"
"os"
"path/filepath"
"strings"
"testing"
)
func FuzzDecode(f *testing.F) {
if testing.Short() {
f.Skip("Skipping in short mode")
}
testdata, err := os.ReadDir("../testdata")
if err != nil {
f.Fatalf("failed to read testdata directory: %s", err)
}
for _, de := range testdata {
if de.IsDir() || !strings.HasSuffix(de.Name(), ".gif") {
continue
}
b, err := os.ReadFile(filepath.Join("../testdata", de.Name()))
if err != nil {
f.Fatalf("failed to read testdata: %s", err)
}
f.Add(b)
}
f.Fuzz(func(t *testing.T, b []byte) {
cfg, _, err := image.DecodeConfig(bytes.NewReader(b))
if err != nil {
return
}
if cfg.Width*cfg.Height > 1e6 {
return
}
img, typ, err := image.Decode(bytes.NewReader(b))
if err != nil || typ != "gif" {
return
}
for q := 1; q <= 256; q++ {
var w bytes.Buffer
err := Encode(&w, img, &Options{NumColors: q})
if err != nil {
t.Fatalf("failed to encode valid image: %s", err)
}
img1, err := Decode(&w)
if err != nil {
t.Fatalf("failed to decode roundtripped image: %s", err)
}
got := img1.Bounds()
want := img.Bounds()
if !got.Eq(want) {
t.Fatalf("roundtripped image bounds have changed, got: %v, want: %v", got, want)
}
}
})
}

641
src/image/gif/reader.go Normal file
View File

@@ -0,0 +1,641 @@
// Copyright 2011 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 implements a GIF image decoder and encoder.
//
// The GIF specification is at https://www.w3.org/Graphics/GIF/spec-gif89a.txt.
package gif
import (
"bufio"
"compress/lzw"
"errors"
"fmt"
"image"
"image/color"
"io"
)
var (
errNotEnough = errors.New("gif: not enough image data")
errTooMuch = errors.New("gif: too much image data")
errBadPixel = errors.New("gif: invalid pixel value")
)
// If the io.Reader does not also have ReadByte, then decode will introduce its own buffering.
type reader interface {
io.Reader
io.ByteReader
}
// Masks etc.
const (
// Fields.
fColorTable = 1 << 7
fInterlace = 1 << 6
fColorTableBitsMask = 7
// Graphic control flags.
gcTransparentColorSet = 1 << 0
gcDisposalMethodMask = 7 << 2
)
// Disposal Methods.
const (
DisposalNone = 0x01
DisposalBackground = 0x02
DisposalPrevious = 0x03
)
// Section indicators.
const (
sExtension = 0x21
sImageDescriptor = 0x2C
sTrailer = 0x3B
)
// Extensions.
const (
eText = 0x01 // Plain Text
eGraphicControl = 0xF9 // Graphic Control
eComment = 0xFE // Comment
eApplication = 0xFF // Application
)
func readFull(r io.Reader, b []byte) error {
_, err := io.ReadFull(r, b)
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
}
func readByte(r io.ByteReader) (byte, error) {
b, err := r.ReadByte()
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return b, err
}
// decoder is the type used to decode a GIF file.
type decoder struct {
r reader
// From header.
vers string
width int
height int
loopCount int
delayTime int
backgroundIndex byte
disposalMethod byte
// From image descriptor.
imageFields byte
// From graphics control.
transparentIndex byte
hasTransparentIndex bool
// Computed.
globalColorTable color.Palette
// Used when decoding.
delay []int
disposal []byte
image []*image.Paletted
tmp [1024]byte // must be at least 768 so we can read color table
}
// blockReader parses the block structure of GIF image data, which comprises
// (n, (n bytes)) blocks, with 1 <= n <= 255. It is the reader given to the
// LZW decoder, which is thus immune to the blocking. After the LZW decoder
// completes, there will be a 0-byte block remaining (0, ()), which is
// consumed when checking that the blockReader is exhausted.
//
// To avoid the allocation of a bufio.Reader for the lzw Reader, blockReader
// implements io.ByteReader and buffers blocks into the decoder's "tmp" buffer.
type blockReader struct {
d *decoder
i, j uint8 // d.tmp[i:j] contains the buffered bytes
err error
}
func (b *blockReader) fill() {
if b.err != nil {
return
}
b.j, b.err = readByte(b.d.r)
if b.j == 0 && b.err == nil {
b.err = io.EOF
}
if b.err != nil {
return
}
b.i = 0
b.err = readFull(b.d.r, b.d.tmp[:b.j])
if b.err != nil {
b.j = 0
}
}
func (b *blockReader) ReadByte() (byte, error) {
if b.i == b.j {
b.fill()
if b.err != nil {
return 0, b.err
}
}
c := b.d.tmp[b.i]
b.i++
return c, nil
}
// blockReader must implement io.Reader, but its Read shouldn't ever actually
// be called in practice. The compress/lzw package will only call [blockReader.ReadByte].
func (b *blockReader) Read(p []byte) (int, error) {
if len(p) == 0 || b.err != nil {
return 0, b.err
}
if b.i == b.j {
b.fill()
if b.err != nil {
return 0, b.err
}
}
n := copy(p, b.d.tmp[b.i:b.j])
b.i += uint8(n)
return n, nil
}
// close primarily detects whether or not a block terminator was encountered
// after reading a sequence of data sub-blocks. It allows at most one trailing
// sub-block worth of data. I.e., if some number of bytes exist in one sub-block
// following the end of LZW data, the very next sub-block must be the block
// terminator. If the very end of LZW data happened to fill one sub-block, at
// most one more sub-block of length 1 may exist before the block-terminator.
// These accommodations allow us to support GIFs created by less strict encoders.
// See https://golang.org/issue/16146.
func (b *blockReader) close() error {
if b.err == io.EOF {
// A clean block-sequence terminator was encountered while reading.
return nil
} else if b.err != nil {
// Some other error was encountered while reading.
return b.err
}
if b.i == b.j {
// We reached the end of a sub block reading LZW data. We'll allow at
// most one more sub block of data with a length of 1 byte.
b.fill()
if b.err == io.EOF {
return nil
} else if b.err != nil {
return b.err
} else if b.j > 1 {
return errTooMuch
}
}
// Part of a sub-block remains buffered. We expect that the next attempt to
// buffer a sub-block will reach the block terminator.
b.fill()
if b.err == io.EOF {
return nil
} else if b.err != nil {
return b.err
}
return errTooMuch
}
// decode reads a GIF image from r and stores the result in d.
func (d *decoder) decode(r io.Reader, configOnly, keepAllFrames bool) error {
// Add buffering if r does not provide ReadByte.
if rr, ok := r.(reader); ok {
d.r = rr
} else {
d.r = bufio.NewReader(r)
}
d.loopCount = -1
err := d.readHeaderAndScreenDescriptor()
if err != nil {
return err
}
if configOnly {
return nil
}
for {
c, err := readByte(d.r)
if err != nil {
return fmt.Errorf("gif: reading frames: %v", err)
}
switch c {
case sExtension:
if err = d.readExtension(); err != nil {
return err
}
case sImageDescriptor:
if err = d.readImageDescriptor(keepAllFrames); err != nil {
return err
}
if !keepAllFrames && len(d.image) == 1 {
return nil
}
case sTrailer:
if len(d.image) == 0 {
return fmt.Errorf("gif: missing image data")
}
return nil
default:
return fmt.Errorf("gif: unknown block type: 0x%.2x", c)
}
}
}
func (d *decoder) readHeaderAndScreenDescriptor() error {
err := readFull(d.r, d.tmp[:13])
if err != nil {
return fmt.Errorf("gif: reading header: %v", err)
}
d.vers = string(d.tmp[:6])
if d.vers != "GIF87a" && d.vers != "GIF89a" {
return fmt.Errorf("gif: can't recognize format %q", d.vers)
}
d.width = int(d.tmp[6]) + int(d.tmp[7])<<8
d.height = int(d.tmp[8]) + int(d.tmp[9])<<8
if fields := d.tmp[10]; fields&fColorTable != 0 {
d.backgroundIndex = d.tmp[11]
// readColorTable overwrites the contents of d.tmp, but that's OK.
if d.globalColorTable, err = d.readColorTable(fields); err != nil {
return err
}
}
// d.tmp[12] is the Pixel Aspect Ratio, which is ignored.
return nil
}
func (d *decoder) readColorTable(fields byte) (color.Palette, error) {
n := 1 << (1 + uint(fields&fColorTableBitsMask))
err := readFull(d.r, d.tmp[:3*n])
if err != nil {
return nil, fmt.Errorf("gif: reading color table: %s", err)
}
j, p := 0, make(color.Palette, n)
for i := range p {
p[i] = color.RGBA{d.tmp[j+0], d.tmp[j+1], d.tmp[j+2], 0xFF}
j += 3
}
return p, nil
}
func (d *decoder) readExtension() error {
extension, err := readByte(d.r)
if err != nil {
return fmt.Errorf("gif: reading extension: %v", err)
}
size := 0
switch extension {
case eText:
size = 13
case eGraphicControl:
return d.readGraphicControl()
case eComment:
// nothing to do but read the data.
case eApplication:
b, err := readByte(d.r)
if err != nil {
return fmt.Errorf("gif: reading extension: %v", err)
}
// The spec requires size be 11, but Adobe sometimes uses 10.
size = int(b)
default:
return fmt.Errorf("gif: unknown extension 0x%.2x", extension)
}
if size > 0 {
if err := readFull(d.r, d.tmp[:size]); err != nil {
return fmt.Errorf("gif: reading extension: %v", err)
}
}
// Application Extension with "NETSCAPE2.0" as string and 1 in data means
// this extension defines a loop count.
if extension == eApplication && string(d.tmp[:size]) == "NETSCAPE2.0" {
n, err := d.readBlock()
if err != nil {
return fmt.Errorf("gif: reading extension: %v", err)
}
if n == 0 {
return nil
}
if n == 3 && d.tmp[0] == 1 {
d.loopCount = int(d.tmp[1]) | int(d.tmp[2])<<8
}
}
for {
n, err := d.readBlock()
if err != nil {
return fmt.Errorf("gif: reading extension: %v", err)
}
if n == 0 {
return nil
}
}
}
func (d *decoder) readGraphicControl() error {
if err := readFull(d.r, d.tmp[:6]); err != nil {
return fmt.Errorf("gif: can't read graphic control: %s", err)
}
if d.tmp[0] != 4 {
return fmt.Errorf("gif: invalid graphic control extension block size: %d", d.tmp[0])
}
flags := d.tmp[1]
d.disposalMethod = (flags & gcDisposalMethodMask) >> 2
d.delayTime = int(d.tmp[2]) | int(d.tmp[3])<<8
if flags&gcTransparentColorSet != 0 {
d.transparentIndex = d.tmp[4]
d.hasTransparentIndex = true
}
if d.tmp[5] != 0 {
return fmt.Errorf("gif: invalid graphic control extension block terminator: %d", d.tmp[5])
}
return nil
}
func (d *decoder) readImageDescriptor(keepAllFrames bool) error {
m, err := d.newImageFromDescriptor()
if err != nil {
return err
}
useLocalColorTable := d.imageFields&fColorTable != 0
if useLocalColorTable {
m.Palette, err = d.readColorTable(d.imageFields)
if err != nil {
return err
}
} else {
if d.globalColorTable == nil {
return errors.New("gif: no color table")
}
m.Palette = d.globalColorTable
}
if d.hasTransparentIndex {
if !useLocalColorTable {
// Clone the global color table.
m.Palette = append(color.Palette(nil), d.globalColorTable...)
}
if ti := int(d.transparentIndex); ti < len(m.Palette) {
m.Palette[ti] = color.RGBA{}
} else {
// The transparentIndex is out of range, which is an error
// according to the spec, but Firefox and Google Chrome
// seem OK with this, so we enlarge the palette with
// transparent colors. See golang.org/issue/15059.
p := make(color.Palette, ti+1)
copy(p, m.Palette)
for i := len(m.Palette); i < len(p); i++ {
p[i] = color.RGBA{}
}
m.Palette = p
}
}
litWidth, err := readByte(d.r)
if err != nil {
return fmt.Errorf("gif: reading image data: %v", err)
}
if litWidth < 2 || litWidth > 8 {
return fmt.Errorf("gif: pixel size in decode out of range: %d", litWidth)
}
// A wonderfully Go-like piece of magic.
br := &blockReader{d: d}
lzwr := lzw.NewReader(br, lzw.LSB, int(litWidth))
defer lzwr.Close()
if err = readFull(lzwr, m.Pix); err != nil {
if err != io.ErrUnexpectedEOF {
return fmt.Errorf("gif: reading image data: %v", err)
}
return errNotEnough
}
// In theory, both lzwr and br should be exhausted. Reading from them
// should yield (0, io.EOF).
//
// The spec (Appendix F - Compression), says that "An End of
// Information code... must be the last code output by the encoder
// for an image". In practice, though, giflib (a widely used C
// library) does not enforce this, so we also accept lzwr returning
// io.ErrUnexpectedEOF (meaning that the encoded stream hit io.EOF
// before the LZW decoder saw an explicit end code), provided that
// the io.ReadFull call above successfully read len(m.Pix) bytes.
// See https://golang.org/issue/9856 for an example GIF.
if n, err := lzwr.Read(d.tmp[256:257]); n != 0 || (err != io.EOF && err != io.ErrUnexpectedEOF) {
if err != nil {
return fmt.Errorf("gif: reading image data: %v", err)
}
return errTooMuch
}
// In practice, some GIFs have an extra byte in the data sub-block
// stream, which we ignore. See https://golang.org/issue/16146.
if err := br.close(); err == errTooMuch {
return errTooMuch
} else if err != nil {
return fmt.Errorf("gif: reading image data: %v", err)
}
// Check that the color indexes are inside the palette.
if len(m.Palette) < 256 {
for _, pixel := range m.Pix {
if int(pixel) >= len(m.Palette) {
return errBadPixel
}
}
}
// Undo the interlacing if necessary.
if d.imageFields&fInterlace != 0 {
uninterlace(m)
}
if keepAllFrames || len(d.image) == 0 {
d.image = append(d.image, m)
d.delay = append(d.delay, d.delayTime)
d.disposal = append(d.disposal, d.disposalMethod)
}
// The GIF89a spec, Section 23 (Graphic Control Extension) says:
// "The scope of this extension is the first graphic rendering block
// to follow." We therefore reset the GCE fields to zero.
d.delayTime = 0
d.hasTransparentIndex = false
return nil
}
func (d *decoder) newImageFromDescriptor() (*image.Paletted, error) {
if err := readFull(d.r, d.tmp[:9]); err != nil {
return nil, fmt.Errorf("gif: can't read image descriptor: %s", err)
}
left := int(d.tmp[0]) + int(d.tmp[1])<<8
top := int(d.tmp[2]) + int(d.tmp[3])<<8
width := int(d.tmp[4]) + int(d.tmp[5])<<8
height := int(d.tmp[6]) + int(d.tmp[7])<<8
d.imageFields = d.tmp[8]
// The GIF89a spec, Section 20 (Image Descriptor) says: "Each image must
// fit within the boundaries of the Logical Screen, as defined in the
// Logical Screen Descriptor."
//
// This is conceptually similar to testing
// frameBounds := image.Rect(left, top, left+width, top+height)
// imageBounds := image.Rect(0, 0, d.width, d.height)
// if !frameBounds.In(imageBounds) { etc }
// but the semantics of the Go image.Rectangle type is that r.In(s) is true
// whenever r is an empty rectangle, even if r.Min.X > s.Max.X. Here, we
// want something stricter.
//
// Note that, by construction, left >= 0 && top >= 0, so we only have to
// explicitly compare frameBounds.Max (left+width, top+height) against
// imageBounds.Max (d.width, d.height) and not frameBounds.Min (left, top)
// against imageBounds.Min (0, 0).
if left+width > d.width || top+height > d.height {
return nil, errors.New("gif: frame bounds larger than image bounds")
}
return image.NewPaletted(image.Rectangle{
Min: image.Point{left, top},
Max: image.Point{left + width, top + height},
}, nil), nil
}
func (d *decoder) readBlock() (int, error) {
n, err := readByte(d.r)
if n == 0 || err != nil {
return 0, err
}
if err := readFull(d.r, d.tmp[:n]); err != nil {
return 0, err
}
return int(n), nil
}
// interlaceScan defines the ordering for a pass of the interlace algorithm.
type interlaceScan struct {
skip, start int
}
// interlacing represents the set of scans in an interlaced GIF image.
var interlacing = []interlaceScan{
{8, 0}, // Group 1 : Every 8th. row, starting with row 0.
{8, 4}, // Group 2 : Every 8th. row, starting with row 4.
{4, 2}, // Group 3 : Every 4th. row, starting with row 2.
{2, 1}, // Group 4 : Every 2nd. row, starting with row 1.
}
// uninterlace rearranges the pixels in m to account for interlaced input.
func uninterlace(m *image.Paletted) {
var nPix []uint8
dx := m.Bounds().Dx()
dy := m.Bounds().Dy()
nPix = make([]uint8, dx*dy)
offset := 0 // steps through the input by sequential scan lines.
for _, pass := range interlacing {
nOffset := pass.start * dx // steps through the output as defined by pass.
for y := pass.start; y < dy; y += pass.skip {
copy(nPix[nOffset:nOffset+dx], m.Pix[offset:offset+dx])
offset += dx
nOffset += dx * pass.skip
}
}
m.Pix = nPix
}
// Decode reads a GIF image from r and returns the first embedded
// image as an [image.Image].
func Decode(r io.Reader) (image.Image, error) {
var d decoder
if err := d.decode(r, false, false); err != nil {
return nil, err
}
return d.image[0], nil
}
// GIF represents the possibly multiple images stored in a GIF file.
type GIF struct {
Image []*image.Paletted // The successive images.
Delay []int // The successive delay times, one per frame, in 100ths of a second.
// LoopCount controls the number of times an animation will be
// restarted during display.
// A LoopCount of 0 means to loop forever.
// A LoopCount of -1 means to show each frame only once.
// Otherwise, the animation is looped LoopCount+1 times.
LoopCount int
// Disposal is the successive disposal methods, one per frame. For
// backwards compatibility, a nil Disposal is valid to pass to EncodeAll,
// and implies that each frame's disposal method is 0 (no disposal
// specified).
Disposal []byte
// Config is the global color table (palette), width and height. A nil or
// empty-color.Palette Config.ColorModel means that each frame has its own
// color table and there is no global color table. Each frame's bounds must
// be within the rectangle defined by the two points (0, 0) and
// (Config.Width, Config.Height).
//
// For backwards compatibility, a zero-valued Config is valid to pass to
// EncodeAll, and implies that the overall GIF's width and height equals
// the first frame's bounds' Rectangle.Max point.
Config image.Config
// BackgroundIndex is the background index in the global color table, for
// use with the DisposalBackground disposal method.
BackgroundIndex byte
}
// DecodeAll reads a GIF image from r and returns the sequential frames
// and timing information.
func DecodeAll(r io.Reader) (*GIF, error) {
var d decoder
if err := d.decode(r, false, true); err != nil {
return nil, err
}
gif := &GIF{
Image: d.image,
LoopCount: d.loopCount,
Delay: d.delay,
Disposal: d.disposal,
Config: image.Config{
ColorModel: d.globalColorTable,
Width: d.width,
Height: d.height,
},
BackgroundIndex: d.backgroundIndex,
}
return gif, nil
}
// DecodeConfig returns the global color model and dimensions of a GIF image
// without decoding the entire image.
func DecodeConfig(r io.Reader) (image.Config, error) {
var d decoder
if err := d.decode(r, true, false); err != nil {
return image.Config{}, err
}
return image.Config{
ColorModel: d.globalColorTable,
Width: d.width,
Height: d.height,
}, nil
}
func init() {
image.RegisterFormat("gif", "GIF8?a", Decode, DecodeConfig)
}

View File

@@ -0,0 +1,457 @@
// 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"
"encoding/hex"
"image"
"image/color"
"image/color/palette"
"io"
"os"
"reflect"
"runtime"
"runtime/debug"
"strings"
"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 table of 2 pixels), backgroundIndex, aspect
paletteStr = "\x10\x20\x30\x40\x50\x60" // the color table, also known as a palette
trailerStr = "\x3b"
)
// lzw.NewReader wants an io.ByteReader, this ensures we're compatible.
var _ io.ByteReader = (*blockReader)(nil)
// lzwEncode returns an LZW encoding (with 2-bit literals) of in.
func lzwEncode(in []byte) []byte {
b := &bytes.Buffer{}
w := lzw.NewWriter(b, lzw.LSB, 2)
if _, err := w.Write(in); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
panic(err)
}
return b.Bytes()
}
func TestDecode(t *testing.T) {
// extra contains superfluous bytes to inject into the GIF, either at the end
// of an existing data sub-block (past the LZW End of Information code) or in
// a separate data sub-block. The 0x02 values are arbitrary.
const extra = "\x02\x02\x02\x02"
testCases := []struct {
nPix int // The number of pixels in the image data.
// If non-zero, write this many extra bytes inside the data sub-block
// containing the LZW end code.
extraExisting int
// If non-zero, write an extra block of this many bytes.
extraSeparate int
wantErr error
}{
{0, 0, 0, errNotEnough},
{1, 0, 0, errNotEnough},
{2, 0, 0, nil},
// An extra data sub-block after the compressed section with 1 byte which we
// silently skip.
{2, 0, 1, nil},
// An extra data sub-block after the compressed section with 2 bytes. In
// this case we complain that there is too much data.
{2, 0, 2, errTooMuch},
// Too much pixel data.
{3, 0, 0, errTooMuch},
// An extra byte after LZW data, but inside the same data sub-block.
{2, 1, 0, nil},
// Two extra bytes after LZW data, but inside the same data sub-block.
{2, 2, 0, nil},
// Extra data exists in the final sub-block with LZW data, AND there is
// a bogus sub-block following.
{2, 1, 1, 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(make([]byte, tc.nPix))
if len(enc)+tc.extraExisting > 0xff {
t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d: compressed length %d is too large",
tc.nPix, tc.extraExisting, tc.extraSeparate, len(enc))
continue
}
// Write the size of the data sub-block containing the LZW data.
b.WriteByte(byte(len(enc) + tc.extraExisting))
// Write the LZW data.
b.Write(enc)
// Write extra bytes inside the same data sub-block where LZW data
// ended. Each arbitrarily 0x02.
b.WriteString(extra[:tc.extraExisting])
}
if tc.extraSeparate > 0 {
// Data sub-block size. This indicates how many extra bytes follow.
b.WriteByte(byte(tc.extraSeparate))
b.WriteString(extra[:tc.extraSeparate])
}
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, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v",
tc.nPix, tc.extraExisting, tc.extraSeparate, 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, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v",
tc.nPix, tc.extraExisting, tc.extraSeparate, 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\x04\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([]byte{0x00, 0x00})
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 table 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, and 2-bit LZW literals.
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.
enc := lzwEncode([]byte{0x00, 0x03})
b.WriteByte(byte(len(enc)))
b.Write(enc)
b.WriteByte(0x00) // An empty block signifies the end of the image data.
b.WriteString(trailerStr)
try(t, b.Bytes(), "gif: no color table")
}
func TestPixelOutsidePaletteRange(t *testing.T) {
for _, pval := range []byte{0, 1, 2, 3} {
b := &bytes.Buffer{}
// Manufacture a GIF with a 2 color palette.
b.WriteString(headerStr)
b.WriteString(paletteStr)
// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
// Encode the pixels; some pvals trigger the expected error.
enc := lzwEncode([]byte{pval, pval})
b.WriteByte(byte(len(enc)))
b.Write(enc)
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)
}
}
func TestTransparentPixelOutsidePaletteRange(t *testing.T) {
b := &bytes.Buffer{}
// Manufacture a GIF with a 2 color palette.
b.WriteString(headerStr)
b.WriteString(paletteStr)
// Graphic Control Extension: transparency, transparent color index = 3.
//
// This index, 3, is out of range of the global palette and there is no
// local palette in the subsequent image descriptor. This is an error
// according to the spec, but Firefox and Google Chrome seem OK with this.
//
// See golang.org/issue/15059.
b.WriteString("\x21\xf9\x04\x01\x00\x00\x03\x00")
// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
// Encode the pixels.
enc := lzwEncode([]byte{0x03, 0x03})
b.WriteByte(byte(len(enc)))
b.Write(enc)
b.WriteByte(0x00) // An empty block signifies the end of the image data.
b.WriteString(trailerStr)
try(t, b.Bytes(), "")
}
func TestLoopCount(t *testing.T) {
testCases := []struct {
name string
data []byte
loopCount int
}{
{
"loopcount-missing",
[]byte("GIF89a000\x00000" +
",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 0 image data & trailer
-1,
},
{
"loopcount-0",
[]byte("GIF89a000\x00000" +
"!\xff\vNETSCAPE2.0\x03\x01\x00\x00\x00" + // loop count = 0
",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
"\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data
",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table
"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer
0,
},
{
"loopcount-1",
[]byte("GIF89a000\x00000" +
"!\xff\vNETSCAPE2.0\x03\x01\x01\x00\x00" + // loop count = 1
",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
"\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data
",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table
"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer
1,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
img, err := DecodeAll(bytes.NewReader(tc.data))
if err != nil {
t.Fatal("DecodeAll:", err)
}
w := new(bytes.Buffer)
err = EncodeAll(w, img)
if err != nil {
t.Fatal("EncodeAll:", err)
}
img1, err := DecodeAll(w)
if err != nil {
t.Fatal("DecodeAll:", err)
}
if img.LoopCount != tc.loopCount {
t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, tc.loopCount)
}
if img.LoopCount != img1.LoopCount {
t.Errorf("loop count failed round-trip: %d vs %d", img.LoopCount, img1.LoopCount)
}
})
}
}
func TestUnexpectedEOF(t *testing.T) {
for i := len(testGIF) - 1; i >= 0; i-- {
_, err := DecodeAll(bytes.NewReader(testGIF[:i]))
if err == errNotEnough {
continue
}
text := ""
if err != nil {
text = err.Error()
}
if !strings.HasPrefix(text, "gif:") || !strings.HasSuffix(text, ": unexpected EOF") {
t.Errorf("Decode(testGIF[:%d]) = %v, want gif: ...: unexpected EOF", i, err)
}
}
}
// See golang.org/issue/22237
func TestDecodeMemoryConsumption(t *testing.T) {
const frames = 3000
img := image.NewPaletted(image.Rectangle{Max: image.Point{1, 1}}, palette.WebSafe)
hugeGIF := &GIF{
Image: make([]*image.Paletted, frames),
Delay: make([]int, frames),
Disposal: make([]byte, frames),
}
for i := 0; i < frames; i++ {
hugeGIF.Image[i] = img
hugeGIF.Delay[i] = 60
}
buf := new(bytes.Buffer)
if err := EncodeAll(buf, hugeGIF); err != nil {
t.Fatal("EncodeAll:", err)
}
s0, s1 := new(runtime.MemStats), new(runtime.MemStats)
runtime.GC()
defer debug.SetGCPercent(debug.SetGCPercent(5))
runtime.ReadMemStats(s0)
if _, err := Decode(buf); err != nil {
t.Fatal("Decode:", err)
}
runtime.ReadMemStats(s1)
if heapDiff := int64(s1.HeapAlloc - s0.HeapAlloc); heapDiff > 30<<20 {
t.Fatalf("Decode of %d frames increased heap by %dMB", frames, heapDiff>>20)
}
}
func BenchmarkDecode(b *testing.B) {
data, err := os.ReadFile("../testdata/video-001.gif")
if err != nil {
b.Fatal(err)
}
cfg, err := DecodeConfig(bytes.NewReader(data))
if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(cfg.Width * cfg.Height))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Decode(bytes.NewReader(data))
}
}
func TestReencodeExtendedPalette(t *testing.T) {
data, err := hex.DecodeString("4749463839616c02020157220221ff0b280154ffffffff00000021474946306127dc213000ff84ff840000000000800021ffffffff8f4e4554530041508f8f0202020000000000000000000000000202020202020207020202022f31050000000000000021f904ab2c3826002c00000000c00001009800462b07fc1f02061202020602020202220202930202020202020202020202020286090222202222222222222222222222222222222222222222222222222220222222222222222222222222222222222222222222222222221a22222222332223222222222222222222222222222222222222224b222222222222002200002b474946312829021f0000000000cbff002f0202073121f904ab2c2c000021f92c3803002c00e0c0000000f932")
if err != nil {
t.Fatal(err)
}
img, err := Decode(bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
err = Encode(io.Discard, img, &Options{NumColors: 1})
if err != nil {
t.Fatal(err)
}
}

469
src/image/gif/writer.go Normal file
View File

@@ -0,0 +1,469 @@
// 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 (
"bufio"
"bytes"
"compress/lzw"
"errors"
"image"
"image/color"
"image/color/palette"
"image/draw"
"internal/byteorder"
"io"
)
// Graphic control extension fields.
const (
gcLabel = 0xF9
gcBlockSize = 0x04
)
var log2Lookup = [8]int{2, 4, 8, 16, 32, 64, 128, 256}
func log2(x int) int {
for i, v := range log2Lookup {
if x <= v {
return i
}
}
return -1
}
// writer is a buffered writer.
type writer interface {
Flush() error
io.Writer
io.ByteWriter
}
// encoder encodes an image to the GIF format.
type encoder struct {
// w is the writer to write to. err is the first error encountered during
// writing. All attempted writes after the first error become no-ops.
w writer
err error
// g is a reference to the data that is being encoded.
g GIF
// globalCT is the size in bytes of the global color table.
globalCT int
// buf is a scratch buffer. It must be at least 256 for the blockWriter.
buf [256]byte
globalColorTable [3 * 256]byte
localColorTable [3 * 256]byte
}
// blockWriter writes the block structure of GIF image data, which
// comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the
// writer given to the LZW encoder, which is thus immune to the
// blocking.
type blockWriter struct {
e *encoder
}
func (b blockWriter) setup() {
b.e.buf[0] = 0
}
func (b blockWriter) Flush() error {
return b.e.err
}
func (b blockWriter) WriteByte(c byte) error {
if b.e.err != nil {
return b.e.err
}
// Append c to buffered sub-block.
b.e.buf[0]++
b.e.buf[b.e.buf[0]] = c
if b.e.buf[0] < 255 {
return nil
}
// Flush block
b.e.write(b.e.buf[:256])
b.e.buf[0] = 0
return b.e.err
}
// blockWriter must be an io.Writer for lzw.NewWriter, but this is never
// actually called.
func (b blockWriter) Write(data []byte) (int, error) {
for i, c := range data {
if err := b.WriteByte(c); err != nil {
return i, err
}
}
return len(data), nil
}
func (b blockWriter) close() {
// Write the block terminator (0x00), either by itself, or along with a
// pending sub-block.
if b.e.buf[0] == 0 {
b.e.writeByte(0)
} else {
n := uint(b.e.buf[0])
b.e.buf[n+1] = 0
b.e.write(b.e.buf[:n+2])
}
b.e.flush()
}
func (e *encoder) flush() {
if e.err != nil {
return
}
e.err = e.w.Flush()
}
func (e *encoder) write(p []byte) {
if e.err != nil {
return
}
_, e.err = e.w.Write(p)
}
func (e *encoder) writeByte(b byte) {
if e.err != nil {
return
}
e.err = e.w.WriteByte(b)
}
func (e *encoder) writeHeader() {
if e.err != nil {
return
}
_, e.err = io.WriteString(e.w, "GIF89a")
if e.err != nil {
return
}
// Logical screen width and height.
byteorder.LePutUint16(e.buf[0:2], uint16(e.g.Config.Width))
byteorder.LePutUint16(e.buf[2:4], uint16(e.g.Config.Height))
e.write(e.buf[:4])
if p, ok := e.g.Config.ColorModel.(color.Palette); ok && len(p) > 0 {
paddedSize := log2(len(p)) // Size of Global Color Table: 2^(1+n).
e.buf[0] = fColorTable | uint8(paddedSize)
e.buf[1] = e.g.BackgroundIndex
e.buf[2] = 0x00 // Pixel Aspect Ratio.
e.write(e.buf[:3])
var err error
e.globalCT, err = encodeColorTable(e.globalColorTable[:], p, paddedSize)
if err != nil && e.err == nil {
e.err = err
return
}
e.write(e.globalColorTable[:e.globalCT])
} else {
// All frames have a local color table, so a global color table
// is not needed.
e.buf[0] = 0x00
e.buf[1] = 0x00 // Background Color Index.
e.buf[2] = 0x00 // Pixel Aspect Ratio.
e.write(e.buf[:3])
}
// Add animation info if necessary.
if len(e.g.Image) > 1 && e.g.LoopCount >= 0 {
e.buf[0] = 0x21 // Extension Introducer.
e.buf[1] = 0xff // Application Label.
e.buf[2] = 0x0b // Block Size.
e.write(e.buf[:3])
_, err := io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier.
if err != nil && e.err == nil {
e.err = err
return
}
e.buf[0] = 0x03 // Block Size.
e.buf[1] = 0x01 // Sub-block Index.
byteorder.LePutUint16(e.buf[2:4], uint16(e.g.LoopCount))
e.buf[4] = 0x00 // Block Terminator.
e.write(e.buf[:5])
}
}
func encodeColorTable(dst []byte, p color.Palette, size int) (int, error) {
if uint(size) >= uint(len(log2Lookup)) {
return 0, errors.New("gif: cannot encode color table with more than 256 entries")
}
for i, c := range p {
if c == nil {
return 0, errors.New("gif: cannot encode color table with nil entries")
}
var r, g, b uint8
// It is most likely that the palette is full of color.RGBAs, so they
// get a fast path.
if rgba, ok := c.(color.RGBA); ok {
r, g, b = rgba.R, rgba.G, rgba.B
} else {
rr, gg, bb, _ := c.RGBA()
r, g, b = uint8(rr>>8), uint8(gg>>8), uint8(bb>>8)
}
dst[3*i+0] = r
dst[3*i+1] = g
dst[3*i+2] = b
}
n := log2Lookup[size]
if n > len(p) {
// Pad with black.
clear(dst[3*len(p) : 3*n])
}
return 3 * n, nil
}
func (e *encoder) colorTablesMatch(localLen, transparentIndex int) bool {
localSize := 3 * localLen
if transparentIndex >= 0 {
trOff := 3 * transparentIndex
return bytes.Equal(e.globalColorTable[:trOff], e.localColorTable[:trOff]) &&
bytes.Equal(e.globalColorTable[trOff+3:localSize], e.localColorTable[trOff+3:localSize])
}
return bytes.Equal(e.globalColorTable[:localSize], e.localColorTable[:localSize])
}
func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) {
if e.err != nil {
return
}
if len(pm.Palette) == 0 {
e.err = errors.New("gif: cannot encode image block with empty palette")
return
}
b := pm.Bounds()
if b.Min.X < 0 || b.Max.X >= 1<<16 || b.Min.Y < 0 || b.Max.Y >= 1<<16 {
e.err = errors.New("gif: image block is too large to encode")
return
}
if !b.In(image.Rectangle{Max: image.Point{e.g.Config.Width, e.g.Config.Height}}) {
e.err = errors.New("gif: image block is out of bounds")
return
}
transparentIndex := -1
for i, c := range pm.Palette {
if c == nil {
e.err = errors.New("gif: cannot encode color table with nil entries")
return
}
if _, _, _, a := c.RGBA(); a == 0 {
transparentIndex = i
break
}
}
if delay > 0 || disposal != 0 || transparentIndex != -1 {
e.buf[0] = sExtension // Extension Introducer.
e.buf[1] = gcLabel // Graphic Control Label.
e.buf[2] = gcBlockSize // Block Size.
if transparentIndex != -1 {
e.buf[3] = 0x01 | disposal<<2
} else {
e.buf[3] = 0x00 | disposal<<2
}
byteorder.LePutUint16(e.buf[4:6], uint16(delay)) // Delay Time (1/100ths of a second)
// Transparent color index.
if transparentIndex != -1 {
e.buf[6] = uint8(transparentIndex)
} else {
e.buf[6] = 0x00
}
e.buf[7] = 0x00 // Block Terminator.
e.write(e.buf[:8])
}
e.buf[0] = sImageDescriptor
byteorder.LePutUint16(e.buf[1:3], uint16(b.Min.X))
byteorder.LePutUint16(e.buf[3:5], uint16(b.Min.Y))
byteorder.LePutUint16(e.buf[5:7], uint16(b.Dx()))
byteorder.LePutUint16(e.buf[7:9], uint16(b.Dy()))
e.write(e.buf[:9])
// To determine whether or not this frame's palette is the same as the
// global palette, we can check a couple things. First, do they actually
// point to the same []color.Color? If so, they are equal so long as the
// frame's palette is not longer than the global palette...
paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n).
if gp, ok := e.g.Config.ColorModel.(color.Palette); ok && len(pm.Palette) <= len(gp) && &gp[0] == &pm.Palette[0] {
e.writeByte(0) // Use the global color table.
} else {
ct, err := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize)
if err != nil {
if e.err == nil {
e.err = err
}
return
}
// This frame's palette is not the very same slice as the global
// palette, but it might be a copy, possibly with one value turned into
// transparency by DecodeAll.
if ct <= e.globalCT && e.colorTablesMatch(len(pm.Palette), transparentIndex) {
e.writeByte(0) // Use the global color table.
} else {
// Use a local color table.
e.writeByte(fColorTable | uint8(paddedSize))
e.write(e.localColorTable[:ct])
}
}
litWidth := paddedSize + 1
if litWidth < 2 {
litWidth = 2
}
e.writeByte(uint8(litWidth)) // LZW Minimum Code Size.
bw := blockWriter{e: e}
bw.setup()
lzww := lzw.NewWriter(bw, lzw.LSB, litWidth)
if dx := b.Dx(); dx == pm.Stride {
_, e.err = lzww.Write(pm.Pix[:dx*b.Dy()])
if e.err != nil {
lzww.Close()
return
}
} else {
for i, y := 0, b.Min.Y; y < b.Max.Y; i, y = i+pm.Stride, y+1 {
_, e.err = lzww.Write(pm.Pix[i : i+dx])
if e.err != nil {
lzww.Close()
return
}
}
}
lzww.Close() // flush to bw
bw.close() // flush to e.w
}
// Options are the encoding parameters.
type Options struct {
// NumColors is the maximum number of colors used in the image.
// It ranges from 1 to 256.
NumColors int
// Quantizer is used to produce a palette with size NumColors.
// palette.Plan9 is used in place of a nil Quantizer.
Quantizer draw.Quantizer
// Drawer is used to convert the source image to the desired palette.
// draw.FloydSteinberg is used in place of a nil Drawer.
Drawer draw.Drawer
}
// EncodeAll writes the images in g to w in GIF format with the
// given loop count and delay between frames.
func EncodeAll(w io.Writer, g *GIF) error {
if len(g.Image) == 0 {
return errors.New("gif: must provide at least one image")
}
if len(g.Image) != len(g.Delay) {
return errors.New("gif: mismatched image and delay lengths")
}
e := encoder{g: *g}
// The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
// in Go 1.5. Valid Go 1.4 code, such as when the Disposal field is omitted
// in a GIF struct literal, should still produce valid GIFs.
if e.g.Disposal != nil && len(e.g.Image) != len(e.g.Disposal) {
return errors.New("gif: mismatched image and disposal lengths")
}
if e.g.Config == (image.Config{}) {
p := g.Image[0].Bounds().Max
e.g.Config.Width = p.X
e.g.Config.Height = p.Y
} else if e.g.Config.ColorModel != nil {
if _, ok := e.g.Config.ColorModel.(color.Palette); !ok {
return errors.New("gif: GIF color model must be a color.Palette")
}
}
if ww, ok := w.(writer); ok {
e.w = ww
} else {
e.w = bufio.NewWriter(w)
}
e.writeHeader()
for i, pm := range g.Image {
disposal := uint8(0)
if g.Disposal != nil {
disposal = g.Disposal[i]
}
e.writeImageBlock(pm, g.Delay[i], disposal)
}
e.writeByte(sTrailer)
e.flush()
return e.err
}
// Encode writes the Image m to w in GIF format.
func Encode(w io.Writer, m image.Image, o *Options) error {
// Check for bounds and size restrictions.
b := m.Bounds()
if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 {
return errors.New("gif: image is too large to encode")
}
opts := Options{}
if o != nil {
opts = *o
}
if opts.NumColors < 1 || 256 < opts.NumColors {
opts.NumColors = 256
}
if opts.Drawer == nil {
opts.Drawer = draw.FloydSteinberg
}
pm, _ := m.(*image.Paletted)
if pm == nil {
if cp, ok := m.ColorModel().(color.Palette); ok {
pm = image.NewPaletted(b, cp)
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
pm.Set(x, y, cp.Convert(m.At(x, y)))
}
}
}
}
if pm == nil || len(pm.Palette) > opts.NumColors {
// Set pm to be a palettedized copy of m, including its bounds, which
// might not start at (0, 0).
//
// TODO: Pick a better sub-sample of the Plan 9 palette.
pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors])
if opts.Quantizer != nil {
pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m)
}
opts.Drawer.Draw(pm, b, m, b.Min)
}
// When calling Encode instead of EncodeAll, the single-frame image is
// translated such that its top-left corner is (0, 0), so that the single
// frame completely fills the overall GIF's bounds.
if pm.Rect.Min != (image.Point{}) {
dup := *pm
dup.Rect = dup.Rect.Sub(dup.Rect.Min)
pm = &dup
}
return EncodeAll(w, &GIF{
Image: []*image.Paletted{pm},
Delay: []int{0},
Config: image.Config{
ColorModel: pm.Palette,
Width: b.Dx(),
Height: b.Dy(),
},
})
}

View File

@@ -0,0 +1,734 @@
// 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"
"image"
"image/color"
"image/color/palette"
"image/draw"
_ "image/png"
"io"
"math/rand"
"os"
"reflect"
"testing"
)
func readImg(filename string) (image.Image, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
m, _, err := image.Decode(f)
return m, err
}
func readGIF(filename string) (*GIF, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
return DecodeAll(f)
}
func delta(u0, u1 uint32) int64 {
d := int64(u0) - int64(u1)
if d < 0 {
return -d
}
return d
}
// averageDelta returns the average delta in RGB space. The two images must
// have the same bounds.
func averageDelta(m0, m1 image.Image) int64 {
b := m0.Bounds()
return averageDeltaBound(m0, m1, b, b)
}
// averageDeltaBound returns the average delta in RGB space. The average delta is
// calculated in the specified bounds.
func averageDeltaBound(m0, m1 image.Image, b0, b1 image.Rectangle) int64 {
var sum, n int64
for y := b0.Min.Y; y < b0.Max.Y; y++ {
for x := b0.Min.X; x < b0.Max.X; x++ {
c0 := m0.At(x, y)
c1 := m1.At(x-b0.Min.X+b1.Min.X, y-b0.Min.Y+b1.Min.Y)
r0, g0, b0, _ := c0.RGBA()
r1, g1, b1, _ := c1.RGBA()
sum += delta(r0, r1)
sum += delta(g0, g1)
sum += delta(b0, b1)
n += 3
}
}
return sum / n
}
// lzw.NewWriter wants an interface which is basically the same thing as gif's
// writer interface. This ensures we're compatible.
var _ writer = blockWriter{}
var testCase = []struct {
filename string
tolerance int64
}{
{"../testdata/video-001.png", 1 << 12},
{"../testdata/video-001.gif", 0},
{"../testdata/video-001.interlaced.gif", 0},
}
func TestWriter(t *testing.T) {
for _, tc := range testCase {
m0, err := readImg(tc.filename)
if err != nil {
t.Error(tc.filename, err)
continue
}
var buf bytes.Buffer
err = Encode(&buf, m0, nil)
if err != nil {
t.Error(tc.filename, err)
continue
}
m1, err := Decode(&buf)
if err != nil {
t.Error(tc.filename, err)
continue
}
if m0.Bounds() != m1.Bounds() {
t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds())
continue
}
// Compare the average delta to the tolerance level.
avgDelta := averageDelta(m0, m1)
if avgDelta > tc.tolerance {
t.Errorf("%s: average delta is too high. expected: %d, got %d", tc.filename, tc.tolerance, avgDelta)
continue
}
}
}
func TestSubImage(t *testing.T) {
m0, err := readImg("../testdata/video-001.gif")
if err != nil {
t.Fatalf("readImg: %v", err)
}
m0 = m0.(*image.Paletted).SubImage(image.Rect(0, 0, 50, 30))
var buf bytes.Buffer
err = Encode(&buf, m0, nil)
if err != nil {
t.Fatalf("Encode: %v", err)
}
m1, err := Decode(&buf)
if err != nil {
t.Fatalf("Decode: %v", err)
}
if m0.Bounds() != m1.Bounds() {
t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds())
}
if averageDelta(m0, m1) != 0 {
t.Fatalf("images differ")
}
}
// palettesEqual reports whether two color.Palette values are equal, ignoring
// any trailing opaque-black palette entries.
func palettesEqual(p, q color.Palette) bool {
n := len(p)
if n > len(q) {
n = len(q)
}
for i := 0; i < n; i++ {
if p[i] != q[i] {
return false
}
}
for i := n; i < len(p); i++ {
r, g, b, a := p[i].RGBA()
if r != 0 || g != 0 || b != 0 || a != 0xffff {
return false
}
}
for i := n; i < len(q); i++ {
r, g, b, a := q[i].RGBA()
if r != 0 || g != 0 || b != 0 || a != 0xffff {
return false
}
}
return true
}
var frames = []string{
"../testdata/video-001.gif",
"../testdata/video-005.gray.gif",
}
func testEncodeAll(t *testing.T, go1Dot5Fields bool, useGlobalColorModel bool) {
const width, height = 150, 103
g0 := &GIF{
Image: make([]*image.Paletted, len(frames)),
Delay: make([]int, len(frames)),
LoopCount: 5,
}
for i, f := range frames {
g, err := readGIF(f)
if err != nil {
t.Fatal(f, err)
}
m := g.Image[0]
if m.Bounds().Dx() != width || m.Bounds().Dy() != height {
t.Fatalf("frame %d had unexpected bounds: got %v, want width/height = %d/%d",
i, m.Bounds(), width, height)
}
g0.Image[i] = m
}
// The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
// in Go 1.5. Valid Go 1.4 or earlier code should still produce valid GIFs.
//
// On the following line, color.Model is an interface type, and
// color.Palette is a concrete (slice) type.
globalColorModel, backgroundIndex := color.Model(color.Palette(nil)), uint8(0)
if useGlobalColorModel {
globalColorModel, backgroundIndex = color.Palette(palette.WebSafe), uint8(1)
}
if go1Dot5Fields {
g0.Disposal = make([]byte, len(g0.Image))
for i := range g0.Disposal {
g0.Disposal[i] = DisposalNone
}
g0.Config = image.Config{
ColorModel: globalColorModel,
Width: width,
Height: height,
}
g0.BackgroundIndex = backgroundIndex
}
var buf bytes.Buffer
if err := EncodeAll(&buf, g0); err != nil {
t.Fatal("EncodeAll:", err)
}
encoded := buf.Bytes()
config, err := DecodeConfig(bytes.NewReader(encoded))
if err != nil {
t.Fatal("DecodeConfig:", err)
}
g1, err := DecodeAll(bytes.NewReader(encoded))
if err != nil {
t.Fatal("DecodeAll:", err)
}
if !reflect.DeepEqual(config, g1.Config) {
t.Errorf("DecodeConfig inconsistent with DecodeAll")
}
if !palettesEqual(g1.Config.ColorModel.(color.Palette), globalColorModel.(color.Palette)) {
t.Errorf("unexpected global color model")
}
if w, h := g1.Config.Width, g1.Config.Height; w != width || h != height {
t.Errorf("got config width * height = %d * %d, want %d * %d", w, h, width, height)
}
if g0.LoopCount != g1.LoopCount {
t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount)
}
if backgroundIndex != g1.BackgroundIndex {
t.Errorf("background indexes differ: %d and %d", backgroundIndex, g1.BackgroundIndex)
}
if len(g0.Image) != len(g1.Image) {
t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
}
if len(g1.Image) != len(g1.Delay) {
t.Fatalf("image and delay lengths differ: %d and %d", len(g1.Image), len(g1.Delay))
}
if len(g1.Image) != len(g1.Disposal) {
t.Fatalf("image and disposal lengths differ: %d and %d", len(g1.Image), len(g1.Disposal))
}
for i := range g0.Image {
m0, m1 := g0.Image[i], g1.Image[i]
if m0.Bounds() != m1.Bounds() {
t.Errorf("frame %d: bounds differ: %v and %v", i, m0.Bounds(), m1.Bounds())
}
d0, d1 := g0.Delay[i], g1.Delay[i]
if d0 != d1 {
t.Errorf("frame %d: delay values differ: %d and %d", i, d0, d1)
}
p0, p1 := uint8(0), g1.Disposal[i]
if go1Dot5Fields {
p0 = DisposalNone
}
if p0 != p1 {
t.Errorf("frame %d: disposal values differ: %d and %d", i, p0, p1)
}
}
}
func TestEncodeAllGo1Dot4(t *testing.T) { testEncodeAll(t, false, false) }
func TestEncodeAllGo1Dot5(t *testing.T) { testEncodeAll(t, true, false) }
func TestEncodeAllGo1Dot5GlobalColorModel(t *testing.T) { testEncodeAll(t, true, true) }
func TestEncodeMismatchDelay(t *testing.T) {
images := make([]*image.Paletted, 2)
for i := range images {
images[i] = image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9)
}
g0 := &GIF{
Image: images,
Delay: make([]int, 1),
}
if err := EncodeAll(io.Discard, g0); err == nil {
t.Error("expected error from mismatched delay and image slice lengths")
}
g1 := &GIF{
Image: images,
Delay: make([]int, len(images)),
Disposal: make([]byte, 1),
}
for i := range g1.Disposal {
g1.Disposal[i] = DisposalNone
}
if err := EncodeAll(io.Discard, g1); err == nil {
t.Error("expected error from mismatched disposal and image slice lengths")
}
}
func TestEncodeZeroGIF(t *testing.T) {
if err := EncodeAll(io.Discard, &GIF{}); err == nil {
t.Error("expected error from providing empty gif")
}
}
func TestEncodeAllFramesOutOfBounds(t *testing.T) {
images := []*image.Paletted{
image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9),
image.NewPaletted(image.Rect(2, 2, 8, 8), palette.Plan9),
image.NewPaletted(image.Rect(3, 3, 4, 4), palette.Plan9),
}
for _, upperBound := range []int{6, 10} {
g := &GIF{
Image: images,
Delay: make([]int, len(images)),
Disposal: make([]byte, len(images)),
Config: image.Config{
Width: upperBound,
Height: upperBound,
},
}
err := EncodeAll(io.Discard, g)
if upperBound >= 8 {
if err != nil {
t.Errorf("upperBound=%d: %v", upperBound, err)
}
} else {
if err == nil {
t.Errorf("upperBound=%d: got nil error, want non-nil", upperBound)
}
}
}
}
func TestEncodeNonZeroMinPoint(t *testing.T) {
points := []image.Point{
{-8, -9},
{-4, -4},
{-3, +3},
{+0, +0},
{+2, +2},
}
for _, p := range points {
src := image.NewPaletted(image.Rectangle{
Min: p,
Max: p.Add(image.Point{6, 6}),
}, palette.Plan9)
var buf bytes.Buffer
if err := Encode(&buf, src, nil); err != nil {
t.Errorf("p=%v: Encode: %v", p, err)
continue
}
m, err := Decode(&buf)
if err != nil {
t.Errorf("p=%v: Decode: %v", p, err)
continue
}
if got, want := m.Bounds(), image.Rect(0, 0, 6, 6); got != want {
t.Errorf("p=%v: got %v, want %v", p, got, want)
}
}
// Also test having a source image (gray on the diagonal) that has a
// non-zero Bounds().Min, but isn't an image.Paletted.
{
p := image.Point{+2, +2}
src := image.NewRGBA(image.Rectangle{
Min: p,
Max: p.Add(image.Point{6, 6}),
})
src.SetRGBA(2, 2, color.RGBA{0x22, 0x22, 0x22, 0xFF})
src.SetRGBA(3, 3, color.RGBA{0x33, 0x33, 0x33, 0xFF})
src.SetRGBA(4, 4, color.RGBA{0x44, 0x44, 0x44, 0xFF})
src.SetRGBA(5, 5, color.RGBA{0x55, 0x55, 0x55, 0xFF})
src.SetRGBA(6, 6, color.RGBA{0x66, 0x66, 0x66, 0xFF})
src.SetRGBA(7, 7, color.RGBA{0x77, 0x77, 0x77, 0xFF})
var buf bytes.Buffer
if err := Encode(&buf, src, nil); err != nil {
t.Errorf("gray-diagonal: Encode: %v", err)
return
}
m, err := Decode(&buf)
if err != nil {
t.Errorf("gray-diagonal: Decode: %v", err)
return
}
if got, want := m.Bounds(), image.Rect(0, 0, 6, 6); got != want {
t.Errorf("gray-diagonal: got %v, want %v", got, want)
return
}
rednessAt := func(x int, y int) uint32 {
r, _, _, _ := m.At(x, y).RGBA()
// Shift by 8 to convert from 16 bit color to 8 bit color.
return r >> 8
}
// Round-tripping a still (non-animated) image.Image through
// Encode+Decode should shift the origin to (0, 0).
if got, want := rednessAt(0, 0), uint32(0x22); got != want {
t.Errorf("gray-diagonal: rednessAt(0, 0): got 0x%02x, want 0x%02x", got, want)
}
if got, want := rednessAt(5, 5), uint32(0x77); got != want {
t.Errorf("gray-diagonal: rednessAt(5, 5): got 0x%02x, want 0x%02x", got, want)
}
}
}
func TestEncodeImplicitConfigSize(t *testing.T) {
// For backwards compatibility for Go 1.4 and earlier code, the Config
// field is optional, and if zero, the width and height is implied by the
// first (and in this case only) frame's width and height.
//
// A Config only specifies a width and height (two integers) while an
// image.Image's Bounds method returns an image.Rectangle (four integers).
// For a gif.GIF, the overall bounds' top-left point is always implicitly
// (0, 0), and any frame whose bounds have a negative X or Y will be
// outside those overall bounds, so encoding should fail.
for _, lowerBound := range []int{-1, 0, 1} {
images := []*image.Paletted{
image.NewPaletted(image.Rect(lowerBound, lowerBound, 4, 4), palette.Plan9),
}
g := &GIF{
Image: images,
Delay: make([]int, len(images)),
}
err := EncodeAll(io.Discard, g)
if lowerBound >= 0 {
if err != nil {
t.Errorf("lowerBound=%d: %v", lowerBound, err)
}
} else {
if err == nil {
t.Errorf("lowerBound=%d: got nil error, want non-nil", lowerBound)
}
}
}
}
func TestEncodePalettes(t *testing.T) {
const w, h = 5, 5
pals := []color.Palette{{
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0x01, 0x00, 0x00, 0xff},
color.RGBA{0x02, 0x00, 0x00, 0xff},
}, {
color.RGBA{0x00, 0x00, 0x00, 0xff},
color.RGBA{0x00, 0x01, 0x00, 0xff},
}, {
color.RGBA{0x00, 0x00, 0x03, 0xff},
color.RGBA{0x00, 0x00, 0x02, 0xff},
color.RGBA{0x00, 0x00, 0x01, 0xff},
color.RGBA{0x00, 0x00, 0x00, 0xff},
}, {
color.RGBA{0x10, 0x07, 0xf0, 0xff},
color.RGBA{0x20, 0x07, 0xf0, 0xff},
color.RGBA{0x30, 0x07, 0xf0, 0xff},
color.RGBA{0x40, 0x07, 0xf0, 0xff},
color.RGBA{0x50, 0x07, 0xf0, 0xff},
}}
g0 := &GIF{
Image: []*image.Paletted{
image.NewPaletted(image.Rect(0, 0, w, h), pals[0]),
image.NewPaletted(image.Rect(0, 0, w, h), pals[1]),
image.NewPaletted(image.Rect(0, 0, w, h), pals[2]),
image.NewPaletted(image.Rect(0, 0, w, h), pals[3]),
},
Delay: make([]int, len(pals)),
Disposal: make([]byte, len(pals)),
Config: image.Config{
ColorModel: pals[2],
Width: w,
Height: h,
},
}
var buf bytes.Buffer
if err := EncodeAll(&buf, g0); err != nil {
t.Fatalf("EncodeAll: %v", err)
}
g1, err := DecodeAll(&buf)
if err != nil {
t.Fatalf("DecodeAll: %v", err)
}
if len(g0.Image) != len(g1.Image) {
t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
}
for i, m := range g1.Image {
if got, want := m.Palette, pals[i]; !palettesEqual(got, want) {
t.Errorf("frame %d:\ngot %v\nwant %v", i, got, want)
}
}
}
func TestEncodeBadPalettes(t *testing.T) {
const w, h = 5, 5
for _, n := range []int{256, 257} {
for _, nilColors := range []bool{false, true} {
pal := make(color.Palette, n)
if !nilColors {
for i := range pal {
pal[i] = color.Black
}
}
err := EncodeAll(io.Discard, &GIF{
Image: []*image.Paletted{
image.NewPaletted(image.Rect(0, 0, w, h), pal),
},
Delay: make([]int, 1),
Disposal: make([]byte, 1),
Config: image.Config{
ColorModel: pal,
Width: w,
Height: h,
},
})
got := err != nil
want := n > 256 || nilColors
if got != want {
t.Errorf("n=%d, nilColors=%t: err != nil: got %t, want %t", n, nilColors, got, want)
}
}
}
}
func TestColorTablesMatch(t *testing.T) {
const trIdx = 100
global := color.Palette(palette.Plan9)
if rgb := global[trIdx].(color.RGBA); rgb.R == 0 && rgb.G == 0 && rgb.B == 0 {
t.Fatalf("trIdx (%d) is already black", trIdx)
}
// Make a copy of the palette, substituting trIdx's slot with transparent,
// just like decoder.decode.
local := append(color.Palette(nil), global...)
local[trIdx] = color.RGBA{}
const testLen = 3 * 256
const padded = 7
e := new(encoder)
if l, err := encodeColorTable(e.globalColorTable[:], global, padded); err != nil || l != testLen {
t.Fatalf("Failed to encode global color table: got %d, %v; want nil, %d", l, err, testLen)
}
if l, err := encodeColorTable(e.localColorTable[:], local, padded); err != nil || l != testLen {
t.Fatalf("Failed to encode local color table: got %d, %v; want nil, %d", l, err, testLen)
}
if bytes.Equal(e.globalColorTable[:testLen], e.localColorTable[:testLen]) {
t.Fatal("Encoded color tables are equal, expected mismatch")
}
if !e.colorTablesMatch(len(local), trIdx) {
t.Fatal("colorTablesMatch() == false, expected true")
}
}
func TestEncodeCroppedSubImages(t *testing.T) {
// This test means to ensure that Encode honors the Bounds and Strides of
// images correctly when encoding.
whole := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9)
subImages := []image.Rectangle{
image.Rect(0, 0, 50, 50),
image.Rect(50, 0, 100, 50),
image.Rect(0, 50, 50, 50),
image.Rect(50, 50, 100, 100),
image.Rect(25, 25, 75, 75),
image.Rect(0, 0, 100, 50),
image.Rect(0, 50, 100, 100),
image.Rect(0, 0, 50, 100),
image.Rect(50, 0, 100, 100),
}
for _, sr := range subImages {
si := whole.SubImage(sr)
buf := bytes.NewBuffer(nil)
if err := Encode(buf, si, nil); err != nil {
t.Errorf("Encode: sr=%v: %v", sr, err)
continue
}
if _, err := Decode(buf); err != nil {
t.Errorf("Decode: sr=%v: %v", sr, err)
}
}
}
type offsetImage struct {
image.Image
Rect image.Rectangle
}
func (i offsetImage) Bounds() image.Rectangle {
return i.Rect
}
func TestEncodeWrappedImage(t *testing.T) {
m0, err := readImg("../testdata/video-001.gif")
if err != nil {
t.Fatalf("readImg: %v", err)
}
// Case 1: Encode a wrapped image.Image
buf := new(bytes.Buffer)
w0 := offsetImage{m0, m0.Bounds()}
err = Encode(buf, w0, nil)
if err != nil {
t.Fatalf("Encode: %v", err)
}
w1, err := Decode(buf)
if err != nil {
t.Fatalf("Dencode: %v", err)
}
avgDelta := averageDelta(m0, w1)
if avgDelta > 0 {
t.Fatalf("Wrapped: average delta is too high. expected: 0, got %d", avgDelta)
}
// Case 2: Encode a wrapped image.Image with offset
b0 := image.Rectangle{
Min: image.Point{
X: 128,
Y: 64,
},
Max: image.Point{
X: 256,
Y: 128,
},
}
w0 = offsetImage{m0, b0}
buf = new(bytes.Buffer)
err = Encode(buf, w0, nil)
if err != nil {
t.Fatalf("Encode: %v", err)
}
w1, err = Decode(buf)
if err != nil {
t.Fatalf("Dencode: %v", err)
}
b1 := image.Rectangle{
Min: image.Point{
X: 0,
Y: 0,
},
Max: image.Point{
X: 128,
Y: 64,
},
}
avgDelta = averageDeltaBound(m0, w1, b0, b1)
if avgDelta > 0 {
t.Fatalf("Wrapped and offset: average delta is too high. expected: 0, got %d", avgDelta)
}
}
func BenchmarkEncodeRandomPaletted(b *testing.B) {
paletted := image.NewPaletted(image.Rect(0, 0, 640, 480), palette.Plan9)
rnd := rand.New(rand.NewSource(123))
for i := range paletted.Pix {
paletted.Pix[i] = uint8(rnd.Intn(256))
}
b.SetBytes(640 * 480 * 1)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(io.Discard, paletted, nil)
}
}
func BenchmarkEncodeRandomRGBA(b *testing.B) {
rgba := image.NewRGBA(image.Rect(0, 0, 640, 480))
bo := rgba.Bounds()
rnd := rand.New(rand.NewSource(123))
for y := bo.Min.Y; y < bo.Max.Y; y++ {
for x := bo.Min.X; x < bo.Max.X; x++ {
rgba.SetRGBA(x, y, color.RGBA{
uint8(rnd.Intn(256)),
uint8(rnd.Intn(256)),
uint8(rnd.Intn(256)),
255,
})
}
}
b.SetBytes(640 * 480 * 4)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(io.Discard, rgba, nil)
}
}
func BenchmarkEncodeRealisticPaletted(b *testing.B) {
img, err := readImg("../testdata/video-001.png")
if err != nil {
b.Fatalf("readImg: %v", err)
}
bo := img.Bounds()
paletted := image.NewPaletted(bo, palette.Plan9)
draw.Draw(paletted, bo, img, bo.Min, draw.Src)
b.SetBytes(int64(bo.Dx() * bo.Dy() * 1))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(io.Discard, paletted, nil)
}
}
func BenchmarkEncodeRealisticRGBA(b *testing.B) {
img, err := readImg("../testdata/video-001.png")
if err != nil {
b.Fatalf("readImg: %v", err)
}
bo := img.Bounds()
// Converting img to rgba is redundant for video-001.png, which is already
// in the RGBA format, but for those copy/pasting this benchmark (but
// changing the source image), the conversion ensures that we're still
// benchmarking encoding an RGBA image.
rgba := image.NewRGBA(bo)
draw.Draw(rgba, bo, img, bo.Min, draw.Src)
b.SetBytes(int64(bo.Dx() * bo.Dy() * 4))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(io.Discard, rgba, nil)
}
}