Initial QSfera import
This commit is contained in:
+209
@@ -0,0 +1,209 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user