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
This commit is contained in:
Nigel Tao 2014-10-31 17:06:42 +11:00
parent 013424077b
commit c087821dc6

View File

@ -70,45 +70,11 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
} }
return nil, image.Config{}, err return nil, image.Config{}, err
} }
filter := (buf[0] >> 2) & 0x03 alpha, alphaStride, err = readAlpha(chunkData, widthMinusOne, heightMinusOne, buf[0]&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 { 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 unfilterAlpha(alpha, alphaStride, (buf[0]>>2)&0x03)
// 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: case fccVP8:
if wantAlpha { 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. // Decode reads a WEBP image from r and returns it as an image.Image.
func Decode(r io.Reader) (image.Image, error) { func Decode(r io.Reader) (image.Image, error) {
m, _, err := decode(r, false) m, _, err := decode(r, false)