Files
RenewCMS/internal/domain/image/animatedGif.go
T
2026-10-08 02:33:32 +02:00

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
}
}
}