go.image/riff: new package.
Also update package webp to use package riff. LGTM=r R=r CC=golang-codereviews, pascal.massimino https://golang.org/cl/162850043
This commit is contained in:
parent
ef5e0288ce
commit
013424077b
113
riff/example_test.go
Normal file
113
riff/example_test.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package riff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.google.com/p/go.image/riff"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleReader() {
|
||||||
|
formType, r, err := riff.NewReader(strings.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("RIFF(%s)\n", formType)
|
||||||
|
if err := dump(r, ".\t"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// RIFF(ROOT)
|
||||||
|
// . ZERO ""
|
||||||
|
// . ONE "a"
|
||||||
|
// . LIST(META)
|
||||||
|
// . . LIST(GOOD)
|
||||||
|
// . . . ONE "a"
|
||||||
|
// . . . FIVE "klmno"
|
||||||
|
// . . ZERO ""
|
||||||
|
// . . LIST(BAD )
|
||||||
|
// . . . THRE "def"
|
||||||
|
// . TWO "bc"
|
||||||
|
// . LIST(UGLY)
|
||||||
|
// . . FOUR "ghij"
|
||||||
|
// . . SIX "pqrstu"
|
||||||
|
}
|
||||||
|
|
||||||
|
func dump(r *riff.Reader, indent string) error {
|
||||||
|
for {
|
||||||
|
chunkID, chunkLen, chunkData, err := r.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if chunkID == riff.LIST {
|
||||||
|
listType, list, err := riff.NewListReader(chunkLen, chunkData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("%sLIST(%s)\n", indent, listType)
|
||||||
|
if err := dump(list, indent+".\t"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(chunkData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("%s%s %q\n", indent, chunkID, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeU32(u uint32) string {
|
||||||
|
return string([]byte{
|
||||||
|
byte(u >> 0),
|
||||||
|
byte(u >> 8),
|
||||||
|
byte(u >> 16),
|
||||||
|
byte(u >> 24),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(chunkID, contents string) string {
|
||||||
|
n := len(contents)
|
||||||
|
if n&1 == 1 {
|
||||||
|
contents += "\x00"
|
||||||
|
}
|
||||||
|
return chunkID + encodeU32(uint32(n)) + contents
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeMulti(typ0, typ1 string, chunks ...string) string {
|
||||||
|
n := 4
|
||||||
|
for _, c := range chunks {
|
||||||
|
n += len(c)
|
||||||
|
}
|
||||||
|
s := typ0 + encodeU32(uint32(n)) + typ1
|
||||||
|
for _, c := range chunks {
|
||||||
|
s += c
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
d0 = encode("ZERO", "")
|
||||||
|
d1 = encode("ONE ", "a")
|
||||||
|
d2 = encode("TWO ", "bc")
|
||||||
|
d3 = encode("THRE", "def")
|
||||||
|
d4 = encode("FOUR", "ghij")
|
||||||
|
d5 = encode("FIVE", "klmno")
|
||||||
|
d6 = encode("SIX ", "pqrstu")
|
||||||
|
l0 = encodeMulti("LIST", "GOOD", d1, d5)
|
||||||
|
l1 = encodeMulti("LIST", "BAD ", d3)
|
||||||
|
l2 = encodeMulti("LIST", "UGLY", d4, d6)
|
||||||
|
l01 = encodeMulti("LIST", "META", l0, d0, l1)
|
||||||
|
data = encodeMulti("RIFF", "ROOT", d0, d1, l01, d2, l2)
|
||||||
|
)
|
180
riff/riff.go
Normal file
180
riff/riff.go
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package riff implements the Resource Interchange File Format, used by media
|
||||||
|
// formats such as AVI, WAVE and WEBP.
|
||||||
|
//
|
||||||
|
// A RIFF stream contains a sequence of chunks. Each chunk consists of an 8-byte
|
||||||
|
// header (containing a 4-byte chunk type and a 4-byte chunk length), the chunk
|
||||||
|
// data (presented as an io.Reader), and some padding bytes.
|
||||||
|
//
|
||||||
|
// A detailed description of the format is at
|
||||||
|
// http://www.tactilemedia.com/info/MCI_Control_Info.html
|
||||||
|
package riff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errMissingPaddingByte = errors.New("riff: missing padding byte")
|
||||||
|
errMissingRIFFChunkHeader = errors.New("riff: missing RIFF chunk header")
|
||||||
|
errShortChunkData = errors.New("riff: short chunk data")
|
||||||
|
errShortChunkHeader = errors.New("riff: short chunk header")
|
||||||
|
errStaleReader = errors.New("riff: stale reader")
|
||||||
|
)
|
||||||
|
|
||||||
|
// u32 decodes the first four bytes of b as a little-endian integer.
|
||||||
|
func u32(b []byte) uint32 {
|
||||||
|
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkHeaderSize = 8
|
||||||
|
|
||||||
|
// FourCC is a four character code.
|
||||||
|
type FourCC [4]byte
|
||||||
|
|
||||||
|
// LIST is the "LIST" FourCC.
|
||||||
|
var LIST = FourCC{'L', 'I', 'S', 'T'}
|
||||||
|
|
||||||
|
// NewReader returns the RIFF stream's form type, such as "AVI " or "WAVE", and
|
||||||
|
// its chunks as a *Reader.
|
||||||
|
func NewReader(r io.Reader) (formType FourCC, data *Reader, err error) {
|
||||||
|
var buf [chunkHeaderSize]byte
|
||||||
|
if _, err := io.ReadFull(r, buf[:]); err != nil {
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
err = errMissingRIFFChunkHeader
|
||||||
|
}
|
||||||
|
return FourCC{}, nil, err
|
||||||
|
}
|
||||||
|
if buf[0] != 'R' || buf[1] != 'I' || buf[2] != 'F' || buf[3] != 'F' {
|
||||||
|
return FourCC{}, nil, errMissingRIFFChunkHeader
|
||||||
|
}
|
||||||
|
return NewListReader(u32(buf[4:]), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListReader returns a LIST chunk's list type, such as "movi" or "wavl",
|
||||||
|
// and its chunks as a *Reader.
|
||||||
|
func NewListReader(chunkLen uint32, chunkData io.Reader) (listType FourCC, data *Reader, err error) {
|
||||||
|
if chunkLen < 4 {
|
||||||
|
return FourCC{}, nil, errShortChunkData
|
||||||
|
}
|
||||||
|
z := &Reader{r: chunkData}
|
||||||
|
if _, err := io.ReadFull(chunkData, z.buf[:4]); err != nil {
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
err = errShortChunkData
|
||||||
|
}
|
||||||
|
return FourCC{}, nil, err
|
||||||
|
}
|
||||||
|
z.totalLen = chunkLen - 4
|
||||||
|
return FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}, z, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader reads chunks from an underlying io.Reader.
|
||||||
|
type Reader struct {
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
|
||||||
|
totalLen uint32
|
||||||
|
chunkLen uint32
|
||||||
|
|
||||||
|
chunkReader *chunkReader
|
||||||
|
buf [chunkHeaderSize]byte
|
||||||
|
padded bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next chunk's ID, length and data. It returns io.EOF if there
|
||||||
|
// are no more chunks. The io.Reader returned becomes stale after the next Next
|
||||||
|
// call, and should no longer be used.
|
||||||
|
//
|
||||||
|
// It is valid to call Next even if all of the previous chunk's data has not
|
||||||
|
// been read.
|
||||||
|
func (z *Reader) Next() (chunkID FourCC, chunkLen uint32, chunkData io.Reader, err error) {
|
||||||
|
if z.err != nil {
|
||||||
|
return FourCC{}, 0, nil, z.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drain the rest of the previous chunk.
|
||||||
|
if z.chunkLen != 0 {
|
||||||
|
_, z.err = io.Copy(ioutil.Discard, z.chunkReader)
|
||||||
|
if z.err != nil {
|
||||||
|
return FourCC{}, 0, nil, z.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z.chunkReader = nil
|
||||||
|
if z.padded {
|
||||||
|
_, z.err = io.ReadFull(z.r, z.buf[:1])
|
||||||
|
if z.err != nil {
|
||||||
|
if z.err == io.EOF {
|
||||||
|
z.err = errMissingPaddingByte
|
||||||
|
}
|
||||||
|
return FourCC{}, 0, nil, z.err
|
||||||
|
}
|
||||||
|
z.totalLen--
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are done if we have no more data.
|
||||||
|
if z.totalLen == 0 {
|
||||||
|
z.err = io.EOF
|
||||||
|
return FourCC{}, 0, nil, z.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the next chunk header.
|
||||||
|
if z.totalLen < chunkHeaderSize {
|
||||||
|
z.err = errShortChunkHeader
|
||||||
|
return FourCC{}, 0, nil, z.err
|
||||||
|
}
|
||||||
|
z.totalLen -= chunkHeaderSize
|
||||||
|
if _, err = io.ReadFull(z.r, z.buf[:chunkHeaderSize]); err != nil {
|
||||||
|
if z.err == io.EOF || z.err == io.ErrUnexpectedEOF {
|
||||||
|
z.err = errShortChunkHeader
|
||||||
|
}
|
||||||
|
return FourCC{}, 0, nil, z.err
|
||||||
|
}
|
||||||
|
chunkID = FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}
|
||||||
|
chunkLen = u32(z.buf[4:])
|
||||||
|
z.chunkLen = chunkLen
|
||||||
|
z.padded = chunkLen&1 == 1
|
||||||
|
z.chunkReader = &chunkReader{z}
|
||||||
|
return chunkID, chunkLen, z.chunkReader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type chunkReader struct {
|
||||||
|
z *Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *chunkReader) Read(p []byte) (int, error) {
|
||||||
|
if c != c.z.chunkReader {
|
||||||
|
return 0, errStaleReader
|
||||||
|
}
|
||||||
|
z := c.z
|
||||||
|
if z.err != nil {
|
||||||
|
if z.err == io.EOF {
|
||||||
|
return 0, errStaleReader
|
||||||
|
}
|
||||||
|
return 0, z.err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := int(z.chunkLen)
|
||||||
|
if n == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if n < 0 {
|
||||||
|
// Converting uint32 to int overflowed.
|
||||||
|
n = math.MaxInt32
|
||||||
|
}
|
||||||
|
if n > len(p) {
|
||||||
|
n = len(p)
|
||||||
|
}
|
||||||
|
n, err := z.r.Read(p[:n])
|
||||||
|
z.totalLen -= uint32(n)
|
||||||
|
z.chunkLen -= uint32(n)
|
||||||
|
if err != io.EOF {
|
||||||
|
z.err = err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
297
webp/decode.go
297
webp/decode.go
|
@ -15,182 +15,171 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"code.google.com/p/go.image/riff"
|
||||||
"code.google.com/p/go.image/vp8"
|
"code.google.com/p/go.image/vp8"
|
||||||
"code.google.com/p/go.image/vp8l"
|
"code.google.com/p/go.image/vp8l"
|
||||||
"code.google.com/p/go.image/webp/nycbcra"
|
"code.google.com/p/go.image/webp/nycbcra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// roundUp2 rounds u up to an even number.
|
var errInvalidFormat = errors.New("webp: invalid format")
|
||||||
// https://developers.google.com/speed/webp/docs/riff_container#riff_file_format
|
|
||||||
// says that "If Chunk Size is odd, a single padding byte... is added."
|
|
||||||
func roundUp2(u uint32) uint32 {
|
|
||||||
return u + u&1
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
var (
|
||||||
formatVP8 = 1
|
fccALPH = riff.FourCC{'A', 'L', 'P', 'H'}
|
||||||
formatVP8L = 2
|
fccVP8 = riff.FourCC{'V', 'P', '8', ' '}
|
||||||
formatVP8X = 3
|
fccVP8L = riff.FourCC{'V', 'P', '8', 'L'}
|
||||||
|
fccVP8X = riff.FourCC{'V', 'P', '8', 'X'}
|
||||||
|
fccWEBP = riff.FourCC{'W', 'E', 'B', 'P'}
|
||||||
)
|
)
|
||||||
|
|
||||||
func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
|
func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
|
||||||
var b [20]byte
|
formType, riffReader, err := riff.NewReader(r)
|
||||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
if err != nil {
|
||||||
return nil, image.Config{}, err
|
return nil, image.Config{}, err
|
||||||
}
|
}
|
||||||
format := 0
|
if formType != fccWEBP {
|
||||||
switch string(b[8:16]) {
|
return nil, image.Config{}, errInvalidFormat
|
||||||
case "WEBPVP8 ":
|
|
||||||
format = formatVP8
|
|
||||||
case "WEBPVP8L":
|
|
||||||
format = formatVP8L
|
|
||||||
case "WEBPVP8X":
|
|
||||||
format = formatVP8X
|
|
||||||
}
|
|
||||||
if string(b[:4]) != "RIFF" || format == 0 {
|
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
|
||||||
}
|
|
||||||
riffLen := uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24
|
|
||||||
dataLen := roundUp2(uint32(b[16]) | uint32(b[17])<<8 | uint32(b[18])<<16 | uint32(b[19])<<24)
|
|
||||||
if riffLen < dataLen+12 {
|
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
|
||||||
}
|
|
||||||
if dataLen == 0 || dataLen >= 1<<31 {
|
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
|
||||||
}
|
|
||||||
|
|
||||||
if format == formatVP8L {
|
|
||||||
r = &io.LimitedReader{R: r, N: int64(dataLen)}
|
|
||||||
if configOnly {
|
|
||||||
c, err := vp8l.DecodeConfig(r)
|
|
||||||
return nil, c, err
|
|
||||||
}
|
|
||||||
m, err := vp8l.Decode(r)
|
|
||||||
return m, image.Config{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
alpha []byte
|
alpha []byte
|
||||||
alphaStride int
|
alphaStride int
|
||||||
|
wantAlpha bool
|
||||||
|
widthMinusOne uint32
|
||||||
|
heightMinusOne uint32
|
||||||
|
buf [10]byte
|
||||||
)
|
)
|
||||||
if format == formatVP8X {
|
for {
|
||||||
if dataLen != 10 {
|
chunkID, chunkLen, chunkData, err := riffReader.Next()
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
if err == io.EOF {
|
||||||
|
err = errInvalidFormat
|
||||||
}
|
}
|
||||||
if _, err := io.ReadFull(r, b[:10]); err != nil {
|
|
||||||
return nil, image.Config{}, err
|
|
||||||
}
|
|
||||||
const (
|
|
||||||
animationBit = 1 << 1
|
|
||||||
xmpMetadataBit = 1 << 2
|
|
||||||
exifMetadataBit = 1 << 3
|
|
||||||
alphaBit = 1 << 4
|
|
||||||
iccProfileBit = 1 << 5
|
|
||||||
)
|
|
||||||
if b[0] != alphaBit {
|
|
||||||
return nil, image.Config{}, errors.New("webp: non-Alpha VP8X is not implemented")
|
|
||||||
}
|
|
||||||
widthMinusOne := uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16
|
|
||||||
heightMinusOne := uint32(b[7]) | uint32(b[8])<<8 | uint32(b[9])<<16
|
|
||||||
if configOnly {
|
|
||||||
return nil, image.Config{
|
|
||||||
ColorModel: nycbcra.ColorModel,
|
|
||||||
Width: int(widthMinusOne) + 1,
|
|
||||||
Height: int(heightMinusOne) + 1,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the 8-byte chunk header plus the mandatory PFC (Pre-processing,
|
|
||||||
// Filter, Compression) byte.
|
|
||||||
if _, err := io.ReadFull(r, b[:9]); err != nil {
|
|
||||||
return nil, image.Config{}, err
|
|
||||||
}
|
|
||||||
if b[0] != 'A' || b[1] != 'L' || b[2] != 'P' || b[3] != 'H' {
|
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
|
||||||
}
|
|
||||||
chunkLen := roundUp2(uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24)
|
|
||||||
// Subtract one byte from chunkLen, since we've already read the PFC byte.
|
|
||||||
if chunkLen == 0 {
|
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
|
||||||
}
|
|
||||||
chunkLen--
|
|
||||||
filter := (b[8] >> 2) & 0x03
|
|
||||||
if filter != 0 {
|
|
||||||
return nil, image.Config{}, errors.New("webp: VP8X Alpha filtering != 0 is not implemented")
|
|
||||||
}
|
|
||||||
compression := b[8] & 0x03
|
|
||||||
if compression != 1 {
|
|
||||||
return nil, image.Config{}, errors.New("webp: VP8X Alpha compression != 1 is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the VP8L-compressed alpha values. First, synthesize a 5-byte VP8L header:
|
|
||||||
// a 1-byte magic number, a 14-bit widthMinusOne, a 14-bit heightMinusOne,
|
|
||||||
// a 1-bit (ignored, zero) alphaIsUsed and a 3-bit (zero) version.
|
|
||||||
// TODO(nigeltao): be more efficient than decoding an *image.NRGBA just to
|
|
||||||
// extract the green values to a separately allocated []byte. Fixing this
|
|
||||||
// will require changes to the vp8l package's API.
|
|
||||||
if widthMinusOne > 0x3fff || heightMinusOne > 0x3fff {
|
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
|
||||||
}
|
|
||||||
b[0] = 0x2f // VP8L magic number.
|
|
||||||
b[1] = uint8(widthMinusOne)
|
|
||||||
b[2] = uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6)
|
|
||||||
b[3] = uint8(heightMinusOne >> 2)
|
|
||||||
b[4] = uint8(heightMinusOne >> 10)
|
|
||||||
alphaImage, err := vp8l.Decode(io.MultiReader(
|
|
||||||
bytes.NewReader(b[:5]),
|
|
||||||
&io.LimitedReader{R: r, N: int64(chunkLen)},
|
|
||||||
))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, image.Config{}, err
|
return nil, image.Config{}, err
|
||||||
}
|
}
|
||||||
// The green values of the inner NRGBA image are the alpha values of the outer NYCbCrA image.
|
|
||||||
pix := alphaImage.(*image.NRGBA).Pix
|
|
||||||
alpha = make([]byte, len(pix)/4)
|
|
||||||
for i := range alpha {
|
|
||||||
alpha[i] = pix[4*i+1]
|
|
||||||
}
|
|
||||||
alphaStride = int(widthMinusOne) + 1
|
|
||||||
|
|
||||||
// The rest of the image should be in the lossy format. Check the "VP8 "
|
switch chunkID {
|
||||||
// header and fall through.
|
case fccALPH:
|
||||||
if _, err := io.ReadFull(r, b[:8]); err != nil {
|
if !wantAlpha {
|
||||||
return nil, image.Config{}, err
|
return nil, image.Config{}, errInvalidFormat
|
||||||
}
|
}
|
||||||
if b[0] != 'V' || b[1] != 'P' || b[2] != '8' || b[3] != ' ' {
|
wantAlpha = false
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
// Read the Pre-processing | Filter | Compression byte.
|
||||||
}
|
if _, err := io.ReadFull(chunkData, buf[:1]); err != nil {
|
||||||
dataLen = roundUp2(uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24)
|
if err == io.EOF {
|
||||||
if dataLen == 0 || dataLen >= 1<<31 {
|
err = errInvalidFormat
|
||||||
return nil, image.Config{}, errors.New("webp: invalid format")
|
}
|
||||||
}
|
return nil, image.Config{}, err
|
||||||
}
|
}
|
||||||
|
filter := (buf[0] >> 2) & 0x03
|
||||||
|
if filter != 0 {
|
||||||
|
return nil, image.Config{}, errors.New(
|
||||||
|
"webp: VP8X Alpha filtering != 0 is not implemented")
|
||||||
|
}
|
||||||
|
compression := buf[0] & 0x03
|
||||||
|
if compression != 1 {
|
||||||
|
return nil, image.Config{}, errors.New(
|
||||||
|
"webp: VP8X Alpha compression != 1 is not implemented")
|
||||||
|
}
|
||||||
|
// Read the VP8L-compressed alpha values. First, synthesize a 5-byte VP8L header:
|
||||||
|
// a 1-byte magic number, a 14-bit widthMinusOne, a 14-bit heightMinusOne,
|
||||||
|
// a 1-bit (ignored, zero) alphaIsUsed and a 3-bit (zero) version.
|
||||||
|
// TODO(nigeltao): be more efficient than decoding an *image.NRGBA just to
|
||||||
|
// extract the green values to a separately allocated []byte. Fixing this
|
||||||
|
// will require changes to the vp8l package's API.
|
||||||
|
if widthMinusOne > 0x3fff || heightMinusOne > 0x3fff {
|
||||||
|
return nil, image.Config{}, errors.New("webp: invalid format")
|
||||||
|
}
|
||||||
|
buf[0] = 0x2f // VP8L magic number.
|
||||||
|
buf[1] = uint8(widthMinusOne)
|
||||||
|
buf[2] = uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6)
|
||||||
|
buf[3] = uint8(heightMinusOne >> 2)
|
||||||
|
buf[4] = uint8(heightMinusOne >> 10)
|
||||||
|
alphaImage, err := vp8l.Decode(io.MultiReader(
|
||||||
|
bytes.NewReader(buf[:5]),
|
||||||
|
chunkData,
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return nil, image.Config{}, err
|
||||||
|
}
|
||||||
|
// The green values of the inner NRGBA image are the alpha values of the
|
||||||
|
// outer NYCbCrA image.
|
||||||
|
pix := alphaImage.(*image.NRGBA).Pix
|
||||||
|
alpha = make([]byte, len(pix)/4)
|
||||||
|
for i := range alpha {
|
||||||
|
alpha[i] = pix[4*i+1]
|
||||||
|
}
|
||||||
|
alphaStride = int(widthMinusOne) + 1
|
||||||
|
|
||||||
d := vp8.NewDecoder()
|
case fccVP8:
|
||||||
d.Init(r, int(dataLen))
|
if wantAlpha {
|
||||||
fh, err := d.DecodeFrameHeader()
|
return nil, image.Config{}, errInvalidFormat
|
||||||
if err != nil {
|
}
|
||||||
return nil, image.Config{}, err
|
d := vp8.NewDecoder()
|
||||||
|
d.Init(chunkData, int(chunkLen))
|
||||||
|
fh, err := d.DecodeFrameHeader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, image.Config{}, err
|
||||||
|
}
|
||||||
|
if configOnly {
|
||||||
|
return nil, image.Config{
|
||||||
|
ColorModel: color.YCbCrModel,
|
||||||
|
Width: fh.Width,
|
||||||
|
Height: fh.Height,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
m, err := d.DecodeFrame()
|
||||||
|
if err != nil {
|
||||||
|
return nil, image.Config{}, err
|
||||||
|
}
|
||||||
|
if alpha != nil {
|
||||||
|
return &nycbcra.Image{
|
||||||
|
YCbCr: *m,
|
||||||
|
A: alpha,
|
||||||
|
AStride: alphaStride,
|
||||||
|
}, image.Config{}, nil
|
||||||
|
}
|
||||||
|
return m, image.Config{}, nil
|
||||||
|
|
||||||
|
case fccVP8L:
|
||||||
|
if wantAlpha || alpha != nil {
|
||||||
|
return nil, image.Config{}, errInvalidFormat
|
||||||
|
}
|
||||||
|
if configOnly {
|
||||||
|
c, err := vp8l.DecodeConfig(chunkData)
|
||||||
|
return nil, c, err
|
||||||
|
}
|
||||||
|
m, err := vp8l.Decode(chunkData)
|
||||||
|
return m, image.Config{}, err
|
||||||
|
|
||||||
|
case fccVP8X:
|
||||||
|
if chunkLen != 10 {
|
||||||
|
return nil, image.Config{}, errInvalidFormat
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(chunkData, buf[:10]); err != nil {
|
||||||
|
return nil, image.Config{}, err
|
||||||
|
}
|
||||||
|
const (
|
||||||
|
animationBit = 1 << 1
|
||||||
|
xmpMetadataBit = 1 << 2
|
||||||
|
exifMetadataBit = 1 << 3
|
||||||
|
alphaBit = 1 << 4
|
||||||
|
iccProfileBit = 1 << 5
|
||||||
|
)
|
||||||
|
if buf[0] != alphaBit {
|
||||||
|
return nil, image.Config{}, errors.New("webp: non-Alpha VP8X is not implemented")
|
||||||
|
}
|
||||||
|
widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16
|
||||||
|
heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16
|
||||||
|
if configOnly {
|
||||||
|
return nil, image.Config{
|
||||||
|
ColorModel: nycbcra.ColorModel,
|
||||||
|
Width: int(widthMinusOne) + 1,
|
||||||
|
Height: int(heightMinusOne) + 1,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
wantAlpha = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if configOnly {
|
|
||||||
return nil, image.Config{
|
|
||||||
ColorModel: color.YCbCrModel,
|
|
||||||
Width: fh.Width,
|
|
||||||
Height: fh.Height,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
m, err := d.DecodeFrame()
|
|
||||||
if err != nil {
|
|
||||||
return nil, image.Config{}, err
|
|
||||||
}
|
|
||||||
if alpha != nil {
|
|
||||||
return &nycbcra.Image{
|
|
||||||
YCbCr: *m,
|
|
||||||
A: alpha,
|
|
||||||
AStride: alphaStride,
|
|
||||||
}, image.Config{}, nil
|
|
||||||
}
|
|
||||||
return m, image.Config{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode reads a WEBP image from r and returns it as an image.Image.
|
// Decode reads a WEBP image from r and returns it as an image.Image.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user