From c087821dc698d228aacb0905f52daa167a19546a Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Fri, 31 Oct 2014 17:06:42 +1100 Subject: [PATCH] go.image/webp: implement alpha filtering and uncompressed alpha. This passes the four new alpha_*.webp conformance tests referred to in https://golang.org/cl/162850043/#msg7 LGTM=pascal.massimino, r R=r, pascal.massimino CC=golang-codereviews https://golang.org/cl/163630043 --- webp/decode.go | 141 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 105 insertions(+), 36 deletions(-) diff --git a/webp/decode.go b/webp/decode.go index c688292..faf9ebf 100644 --- a/webp/decode.go +++ b/webp/decode.go @@ -70,45 +70,11 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) { } 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, - )) + alpha, alphaStride, err = readAlpha(chunkData, widthMinusOne, heightMinusOne, buf[0]&0x03) 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 + unfilterAlpha(alpha, alphaStride, (buf[0]>>2)&0x03) case fccVP8: if wantAlpha { @@ -182,6 +148,109 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) { } } +func readAlpha(chunkData io.Reader, widthMinusOne, heightMinusOne uint32, compression byte) ( + alpha []byte, alphaStride int, err error) { + + switch compression { + case 0: + w := int(widthMinusOne) + 1 + h := int(heightMinusOne) + 1 + alpha = make([]byte, w*h) + if _, err := io.ReadFull(chunkData, alpha); err != nil { + return nil, 0, err + } + return alpha, w, nil + + case 1: + // 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, 0, errors.New("webp: invalid format") + } + alphaImage, err := vp8l.Decode(io.MultiReader( + bytes.NewReader([]byte{ + 0x2f, // VP8L magic number. + uint8(widthMinusOne), + uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6), + uint8(heightMinusOne >> 2), + uint8(heightMinusOne >> 10), + }), + chunkData, + )) + if err != nil { + return nil, 0, 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] + } + return alpha, int(widthMinusOne) + 1, nil + } + return nil, 0, errInvalidFormat +} + +func unfilterAlpha(alpha []byte, alphaStride int, filter byte) { + if len(alpha) == 0 || alphaStride == 0 { + return + } + switch filter { + case 1: // Horizontal filter. + for i := 1; i < alphaStride; i++ { + alpha[i] += alpha[i-1] + } + for i := alphaStride; i < len(alpha); i += alphaStride { + // The first column is equivalent to the vertical filter. + alpha[i] += alpha[i-alphaStride] + + for j := 1; j < alphaStride; j++ { + alpha[i+j] += alpha[i+j-1] + } + } + + case 2: // Vertical filter. + // The first row is equivalent to the horizontal filter. + for i := 1; i < alphaStride; i++ { + alpha[i] += alpha[i-1] + } + + for i := alphaStride; i < len(alpha); i++ { + alpha[i] += alpha[i-alphaStride] + } + + case 3: // Gradient filter. + // The first row is equivalent to the horizontal filter. + for i := 1; i < alphaStride; i++ { + alpha[i] += alpha[i-1] + } + + for i := alphaStride; i < len(alpha); i += alphaStride { + // The first column is equivalent to the vertical filter. + alpha[i] += alpha[i-alphaStride] + + // The interior is predicted on the three top/left pixels. + for j := 1; j < alphaStride; j++ { + c := int(alpha[i+j-alphaStride-1]) + b := int(alpha[i+j-alphaStride]) + a := int(alpha[i+j-1]) + x := a + b - c + if x < 0 { + x = 0 + } else if x > 255 { + x = 255 + } + alpha[i+j] += uint8(x) + } + } + } +} + // 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)