| // Copyright 2011 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 webp implements a decoder for WEBP images. |
| // |
| // WEBP is defined at: |
| // https://developers.google.com/speed/webp/docs/riff_container |
| package webp |
| |
| import ( |
| "bytes" |
| "errors" |
| "image" |
| "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" |
| ) |
| |
| var errInvalidFormat = errors.New("webp: invalid format") |
| |
| 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) { |
| formType, riffReader, err := riff.NewReader(r) |
| if err != nil { |
| return nil, image.Config{}, err |
| } |
| if formType != fccWEBP { |
| return nil, image.Config{}, errInvalidFormat |
| } |
| |
| var ( |
| alpha []byte |
| alphaStride int |
| wantAlpha bool |
| widthMinusOne uint32 |
| heightMinusOne uint32 |
| buf [10]byte |
| ) |
| for { |
| chunkID, chunkLen, chunkData, err := riffReader.Next() |
| if err == io.EOF { |
| err = errInvalidFormat |
| } |
| if err != nil { |
| return nil, image.Config{}, err |
| } |
| |
| 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 |
| |
| 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 |
| } |
| } |
| } |
| |
| // Decode reads a WEBP image from r and returns it as an image.Image. |
| func Decode(r io.Reader) (image.Image, error) { |
| m, _, err := decode(r, false) |
| if err != nil { |
| return nil, err |
| } |
| return m, err |
| } |
| |
| // DecodeConfig returns the color model and dimensions of a WEBP image without |
| // decoding the entire image. |
| func DecodeConfig(r io.Reader) (image.Config, error) { |
| _, c, err := decode(r, true) |
| return c, err |
| } |
| |
| func init() { |
| image.RegisterFormat("webp", "RIFF????WEBPVP8", Decode, DecodeConfig) |
| } |