Files
opencloud/vendor/github.com/kovidgoyal/imaging/webp/animated.go
dependabot[bot] 85361fca67 build(deps): bump github.com/kovidgoyal/imaging from 1.7.2 to 1.8.17
Bumps [github.com/kovidgoyal/imaging](https://github.com/kovidgoyal/imaging) from 1.7.2 to 1.8.17.
- [Release notes](https://github.com/kovidgoyal/imaging/releases)
- [Changelog](https://github.com/kovidgoyal/imaging/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/kovidgoyal/imaging/compare/v1.7.2...v1.8.17)

---
updated-dependencies:
- dependency-name: github.com/kovidgoyal/imaging
  dependency-version: 1.8.17
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-26 14:46:30 +01:00

210 lines
4.3 KiB
Go

package webp
import (
"bytes"
"errors"
"image"
"io"
"golang.org/x/image/riff"
"golang.org/x/image/vp8"
"golang.org/x/image/vp8l"
)
var (
errNotExtended = errors.New("there was no vp8x header in this webp file, it cannot be animated")
errNotAnimated = errors.New("the vp8x header did not have the animation bit set")
)
func decodeAnimated(r io.Reader) (*AnimatedWEBP, error) {
riffReader, err := webpRiffReader(r)
if err != nil {
return nil, err
}
vp8xHeader, err := validateVP8XHeader(riffReader)
if err != nil {
return nil, err
}
animHeader, err := validateANIMHeader(riffReader)
if err != nil {
return nil, err
}
awp := AnimatedWEBP{
Frames: make([]Frame, 0, 128),
Header: animHeader,
Config: image.Config{
ColorModel: nil, // TODO(patricsss) set the color model correctly
Width: int(vp8xHeader.CanvasWidth),
Height: int(vp8xHeader.CanvasHeight),
},
}
for {
frame, err := parseFrame(riffReader)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
awp.Frames = append(awp.Frames, *frame)
}
return &awp, nil
}
func validateVP8XHeader(r *riff.Reader) (VP8XHeader, error) {
fourCC, chunkLen, chunkData, err := r.Next()
if err != nil {
return VP8XHeader{}, err
}
if fourCC != fccVP8X {
return VP8XHeader{}, errNotExtended
}
if chunkLen != 10 {
return VP8XHeader{}, errInvalidFormat
}
h := parseVP8XHeader(chunkData)
if !h.Animation {
return VP8XHeader{}, errNotAnimated
}
return h, nil
}
func validateANIMHeader(r *riff.Reader) (ANIMHeader, error) {
fourCC, chunkLen, chunkData, err := r.Next()
if err != nil {
return ANIMHeader{}, err
}
if fourCC != fccANIM {
return ANIMHeader{}, errInvalidFormat
}
if chunkLen != 6 {
return ANIMHeader{}, errInvalidFormat
}
h := parseANIMHeader(chunkData)
return h, nil
}
func parseFrame(r *riff.Reader) (*Frame, error) {
fourCC, chunkLen, chunkData, err := r.Next()
if err != nil {
return nil, err
}
if fourCC != fccANMF {
return nil, errInvalidFormat
}
anmfHeader := parseANMFHeader(chunkData)
// buffer chunk data based on chunkLen for safety
// TODO(patricsss): establish if this is necessary, perhaps chunkData has a bounds
// ANMF headers are 16 bytes
wrappedChunkData, err := rewrap(chunkData, int(chunkLen-16))
if err != nil {
return nil, err
}
subReader := NewSubChunkReader(wrappedChunkData)
var (
alpha []byte
stride int
i *image.YCbCr
)
subFourCC, subChunkData, subChunkLen, err := subReader.Next()
if subFourCC == fccALPH {
alpha, stride, err = decodeAlpha(subChunkData, int(subChunkLen), anmfHeader)
if err != nil {
return nil, err
}
// read next chunk
subFourCC, subChunkData, subChunkLen, err = subReader.Next()
if err != nil {
return nil, err
}
}
var out image.Image
switch subFourCC {
case fccVP8:
i, err = decodeVp8Bitstream(subChunkData, int(subChunkLen))
if err != nil {
return nil, err
}
if len(alpha) > 0 {
out = &image.NYCbCrA{
YCbCr: *i,
A: alpha,
AStride: stride,
}
} else {
out = i
}
case fccVP8L:
out, err = vp8l.Decode(subChunkData)
if err != nil {
return nil, err
}
default:
return nil, errInvalidFormat
}
return &Frame{
Header: anmfHeader,
Frame: out,
}, nil
}
func decodeVp8Bitstream(chunkData io.Reader, chunkLen int) (*image.YCbCr, error) {
dec := vp8.NewDecoder()
dec.Init(chunkData, chunkLen)
_, err := dec.DecodeFrameHeader()
if err != nil {
return nil, err
}
i, err := dec.DecodeFrame()
if err != nil {
return nil, err
}
return i, nil
}
func decodeAlpha(chunkData io.Reader, chunkLen int, h ANMFHeader) (alpha []byte, alphaStride int, err error) {
alphHeader := parseALPHHeader(chunkData)
// Length of the chunk minus 1 byte for the ALPH header
buf := make([]byte, chunkLen-1)
n, err := io.ReadFull(chunkData, buf)
if err != nil {
return nil, 0, err
}
if n != len(buf) {
return nil, 0, errInvalidFormat
}
alpha, alphaStride, err = readAlpha(bytes.NewReader(buf), h.FrameWidth-1, h.FrameHeight-1, alphHeader.Compression)
unfilterAlpha(alpha, alphaStride, alphHeader.FilteringMethod)
return alpha, alphaStride, nil
}
func rewrap(r io.Reader, length int) (io.Reader, error) {
data := make([]byte, length)
n, err := io.ReadFull(r, data)
if err != nil {
return nil, err
}
if n != length {
return nil, errInvalidFormat
}
return bytes.NewReader(data), nil
}