package artwork
import (
"bytes"
"encoding/binary"
"image"
"image/color"
"image/gif"
"image/png"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Animation detection", func() {
Describe("isAnimatedGIF", func() {
It("detects an animated GIF with multiple frames", func() {
Expect(isAnimatedGIF(createAnimatedGIF(2))).To(BeTrue())
})
It("detects an animated GIF with many frames", func() {
Expect(isAnimatedGIF(createAnimatedGIF(5))).To(BeTrue())
})
It("does not flag a static GIF (single frame)", func() {
Expect(isAnimatedGIF(createAnimatedGIF(1))).To(BeFalse())
})
It("returns false for non-GIF data", func() {
Expect(isAnimatedGIF(nil)).To(BeFalse())
Expect(isAnimatedGIF([]byte{0xFF, 0xD8})).To(BeFalse())
})
})
Describe("isAnimatedWebP", func() {
It("detects an animated WebP with ANMF chunk", func() {
Expect(isAnimatedWebP(createAnimatedWebPBytes())).To(BeTrue())
})
It("does not flag a static WebP (no ANMF chunk)", func() {
Expect(isAnimatedWebP(createStaticWebPBytes())).To(BeFalse())
})
It("returns false for non-WebP data", func() {
Expect(isAnimatedWebP(nil)).To(BeFalse())
Expect(isAnimatedWebP([]byte{0xFF, 0xD8})).To(BeFalse())
})
})
Describe("isAnimatedPNG", func() {
It("detects an APNG with acTL chunk", func() {
Expect(isAnimatedPNG(createAPNGBytes())).To(BeTrue())
})
It("does not flag a static PNG (no acTL chunk)", func() {
Expect(isAnimatedPNG(createStaticPNGBytes())).To(BeFalse())
})
It("returns false for non-PNG data", func() {
Expect(isAnimatedPNG(nil)).To(BeFalse())
Expect(isAnimatedPNG([]byte{0xFF, 0xD8})).To(BeFalse())
})
})
})
func createAnimatedGIF(frames int) []byte {
g := &gif.GIF{
LoopCount: 0,
}
for range frames {
img := image.NewPaletted(image.Rect(0, 0, 2, 2), color.Palette{color.Black, color.White})
g.Image = append(g.Image, img)
g.Delay = append(g.Delay, 10)
}
var buf bytes.Buffer
err := gif.EncodeAll(&buf, g)
if err != nil {
panic(err)
}
return buf.Bytes()
}
func writeUint32LE(buf *bytes.Buffer, v uint32) {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, v)
buf.Write(b)
}
func writeUint32BE(buf *bytes.Buffer, v uint32) {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, v)
buf.Write(b)
}
func createAnimatedWebPBytes() []byte {
var buf bytes.Buffer
buf.WriteString("RIFF")
writeUint32LE(&buf, 100)
buf.WriteString("WEBP")
buf.WriteString("VP8X")
writeUint32LE(&buf, 10)
buf.Write(make([]byte, 10))
buf.WriteString("ANIM")
writeUint32LE(&buf, 6)
buf.Write(make([]byte, 6))
buf.WriteString("ANMF")
writeUint32LE(&buf, 16)
buf.Write(make([]byte, 16))
return buf.Bytes()
}
func createStaticWebPBytes() []byte {
var buf bytes.Buffer
buf.WriteString("RIFF")
writeUint32LE(&buf, 20)
buf.WriteString("WEBP")
buf.WriteString("VP8 ")
writeUint32LE(&buf, 4)
buf.Write(make([]byte, 4))
return buf.Bytes()
}
func createAPNGBytes() []byte {
staticPNG := createStaticPNGBytes()
ihdrEnd := 8 + 25
var buf bytes.Buffer
buf.Write(staticPNG[:ihdrEnd])
writeUint32BE(&buf, 8)
buf.WriteString("acTL")
writeUint32BE(&buf, 2)
writeUint32BE(&buf, 0)
writeUint32BE(&buf, 0)
buf.Write(staticPNG[ihdrEnd:])
return buf.Bytes()
}
func createStaticPNGBytes() []byte {
img := image.NewRGBA(image.Rect(0, 0, 2, 2))
var buf bytes.Buffer
err := png.Encode(&buf, img)
if err != nil {
panic(err)
}
return buf.Bytes()
}