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:
parent
013424077b
commit
c087821dc6
141
webp/decode.go
141
webp/decode.go
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user