diff --git a/riff/example_test.go b/riff/example_test.go new file mode 100644 index 0000000..faa4975 --- /dev/null +++ b/riff/example_test.go @@ -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) +) diff --git a/riff/riff.go b/riff/riff.go new file mode 100644 index 0000000..2b50d8c --- /dev/null +++ b/riff/riff.go @@ -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 +} diff --git a/webp/decode.go b/webp/decode.go index 8cf874b..c688292 100644 --- a/webp/decode.go +++ b/webp/decode.go @@ -15,182 +15,171 @@ import ( "image/color" "io" + "code.google.com/p/go.image/riff" "code.google.com/p/go.image/vp8" "code.google.com/p/go.image/vp8l" "code.google.com/p/go.image/webp/nycbcra" ) -// roundUp2 rounds u up to an even number. -// 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 -} +var errInvalidFormat = errors.New("webp: invalid format") -const ( - formatVP8 = 1 - formatVP8L = 2 - formatVP8X = 3 +var ( + fccALPH = riff.FourCC{'A', 'L', 'P', 'H'} + fccVP8 = riff.FourCC{'V', 'P', '8', ' '} + 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) { - var b [20]byte - if _, err := io.ReadFull(r, b[:]); err != nil { + formType, riffReader, err := riff.NewReader(r) + if err != nil { return nil, image.Config{}, err } - format := 0 - switch string(b[8:16]) { - 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 + if formType != fccWEBP { + return nil, image.Config{}, errInvalidFormat } var ( - alpha []byte - alphaStride int + alpha []byte + alphaStride int + wantAlpha bool + widthMinusOne uint32 + heightMinusOne uint32 + buf [10]byte ) - if format == formatVP8X { - if dataLen != 10 { - return nil, image.Config{}, errors.New("webp: invalid format") + for { + chunkID, chunkLen, chunkData, err := riffReader.Next() + 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 { 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 " - // header and fall through. - if _, err := io.ReadFull(r, b[:8]); err != nil { - return nil, image.Config{}, err - } - if b[0] != 'V' || b[1] != 'P' || b[2] != '8' || b[3] != ' ' { - return nil, image.Config{}, errors.New("webp: invalid format") - } - dataLen = roundUp2(uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24) - if dataLen == 0 || dataLen >= 1<<31 { - return nil, image.Config{}, errors.New("webp: invalid format") - } - } + switch chunkID { + case fccALPH: + if !wantAlpha { + return nil, image.Config{}, errInvalidFormat + } + wantAlpha = false + // Read the Pre-processing | Filter | Compression byte. + if _, err := io.ReadFull(chunkData, buf[:1]); err != nil { + if err == io.EOF { + err = errInvalidFormat + } + 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() - d.Init(r, int(dataLen)) - fh, err := d.DecodeFrameHeader() - if err != nil { - return nil, image.Config{}, err + case fccVP8: + if wantAlpha { + return nil, image.Config{}, errInvalidFormat + } + 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.