feat: image variants engine
This commit is contained in:
1 parent
f0845de643
commit
f1d56ef50e
80 files changed
+7312
-84
No files matched your search
@@ -0,0 +1,114 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user