115 lines
2.4 KiB
Go
115 lines
2.4 KiB
Go
package image
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
var ErrNotAGif = errors.New("not a gif")
|
|
|
|
const (
|
|
gifSignaturePrefix = "GIF"
|
|
gifHeaderLength = 6
|
|
gifScreenDescriptorSize = 7
|
|
gifImageDescriptorSize = 9
|
|
gifExtensionIntroducer = 0x21
|
|
gifImageSeparator = 0x2C
|
|
gifTrailer = 0x3B
|
|
gifColorTableFlag = 0x80
|
|
gifColorTableSizeMask = 0x07
|
|
gifColorTableEntryBytes = 3
|
|
gifFramesBeforeAnimation = 2
|
|
)
|
|
|
|
func IsAnimatedGIF(source io.Reader) (bool, error) {
|
|
reader := bufio.NewReader(source)
|
|
if err := skipGifPreamble(reader); err != nil {
|
|
return false, err
|
|
}
|
|
frames := 0
|
|
for {
|
|
marker, err := reader.ReadByte()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
switch marker {
|
|
case gifTrailer:
|
|
return false, nil
|
|
case gifExtensionIntroducer:
|
|
err = skipGifExtension(reader)
|
|
case gifImageSeparator:
|
|
frames++
|
|
if frames >= gifFramesBeforeAnimation {
|
|
return true, nil
|
|
}
|
|
err = skipGifImage(reader)
|
|
default:
|
|
return false, ErrNotAGif
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func skipGifPreamble(reader *bufio.Reader) error {
|
|
header := make([]byte, gifHeaderLength)
|
|
if _, err := io.ReadFull(reader, header); err != nil {
|
|
return err
|
|
}
|
|
if string(header[:len(gifSignaturePrefix)]) != gifSignaturePrefix {
|
|
return ErrNotAGif
|
|
}
|
|
screen := make([]byte, gifScreenDescriptorSize)
|
|
if _, err := io.ReadFull(reader, screen); err != nil {
|
|
return err
|
|
}
|
|
return skipGifColorTable(reader, screen[4])
|
|
}
|
|
|
|
func skipGifColorTable(reader *bufio.Reader, packed byte) error {
|
|
if packed&gifColorTableFlag == 0 {
|
|
return nil
|
|
}
|
|
entries := 1 << (int(packed&gifColorTableSizeMask) + 1)
|
|
_, err := reader.Discard(entries * gifColorTableEntryBytes)
|
|
return err
|
|
}
|
|
|
|
func skipGifExtension(reader *bufio.Reader) error {
|
|
if _, err := reader.ReadByte(); err != nil {
|
|
return err
|
|
}
|
|
return skipGifSubBlocks(reader)
|
|
}
|
|
|
|
func skipGifImage(reader *bufio.Reader) error {
|
|
descriptor := make([]byte, gifImageDescriptorSize)
|
|
if _, err := io.ReadFull(reader, descriptor); err != nil {
|
|
return err
|
|
}
|
|
if err := skipGifColorTable(reader, descriptor[8]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := reader.ReadByte(); err != nil {
|
|
return err
|
|
}
|
|
return skipGifSubBlocks(reader)
|
|
}
|
|
|
|
func skipGifSubBlocks(reader *bufio.Reader) error {
|
|
for {
|
|
size, err := reader.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if size == 0 {
|
|
return nil
|
|
}
|
|
if _, err := reader.Discard(int(size)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|