vp8: skip filtering for all-zero-DC macroblock residuals.

This makes the Go code match the libwebp C code's output on
blue-purple-pink-large.*-filter.lossy.webp

Also make the various WEBP benchmarks all decode a similar image,
the image at http://blog.golang.org/gophercon/image01.jpg, to make
it more meaningful to e.g. compare the simple filter's numbers with
the normal filter's numbers.

Also fix a "go vet" warning in webp/decode.go.

The test data was generated by:
wget http://blog.golang.org/gophercon/image01.jpg -O blue-purple-pink-large.jpeg
convert blue-purple-pink-large.jpeg blue-purple-pink-large.png
cwebp -lossless blue-purple-pink-large.png -o blue-purple-pink-large.lossless.webp
cwebp -q 80 -f 0 blue-purple-pink-large.png -o blue-purple-pink-large.no-filter.lossy.webp
cwebp -q 80 -strong blue-purple-pink-large.png -o blue-purple-pink-large.normal-filter.lossy.webp
cwebp -q 80 -nostrong blue-purple-pink-large.png -o blue-purple-pink-large.simple-filter.lossy.webp
dwebp -pgm blue-purple-pink-large.no-filter.lossy.webp -o tmp.pgm && convert tmp.pgm blue-purple-pink-large.no-filter.lossy.webp.ycbcr.png && rm tmp.pgm
dwebp -pgm blue-purple-pink-large.normal-filter.lossy.webp -o tmp.pgm && convert tmp.pgm blue-purple-pink-large.normal-filter.lossy.webp.ycbcr.png && rm tmp.pgm
dwebp -pgm blue-purple-pink-large.simple-filter.lossy.webp -o tmp.pgm && convert tmp.pgm blue-purple-pink-large.simple-filter.lossy.webp.ycbcr.png && rm tmp.pgm

LGTM=r
R=r
CC=golang-codereviews
https://golang.org/cl/106230044
This commit is contained in:
Nigel Tao 2014-06-30 10:36:25 +10:00
parent dc115882f1
commit 07fcf9c2fe
11 changed files with 38 additions and 21 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
testdata/blue-purple-pink-large.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View File

@ -266,8 +266,9 @@ func (d *Decoder) parseResiduals4(r *partition, plane int, context uint8, quant
return 1
}
// parseResiduals parses the residuals.
func (d *Decoder) parseResiduals(mbx, mby int) {
// parseResiduals parses the residuals and returns whether inner loop filtering
// should be skipped for this macroblock.
func (d *Decoder) parseResiduals(mbx, mby int) (skip bool) {
partition := &d.op[mby&(d.nOP-1)]
plane := planeY1SansY2
quant := &d.quant[d.segment]
@ -332,6 +333,11 @@ func (d *Decoder) parseResiduals(mbx, mby int) {
d.upMB[mbx].nzMask = uint8(unzMask)
d.nzDCMask = nzDCMask
d.nzACMask = nzACMask
// Section 15.1 of the spec says that "Steps 2 and 4 [of the loop filter]
// are skipped... [if] there is no DCT coefficient coded for the whole
// macroblock."
return nzDCMask == 0 && nzACMask == 0
}
// reconstructMacroblock applies the predictor functions and adds the inverse-
@ -384,7 +390,8 @@ func (d *Decoder) reconstructMacroblock(mbx, mby int) {
}
}
// reconstruct reconstructs one macroblock.
// reconstruct reconstructs one macroblock and returns whether inner loop
// filtering should be skipped for it.
func (d *Decoder) reconstruct(mbx, mby int) (skip bool) {
if d.segmentHeader.updateMap {
if !d.fp.readBit(d.segmentHeader.prob[0]) {
@ -411,9 +418,7 @@ func (d *Decoder) reconstruct(mbx, mby int) (skip bool) {
d.parsePredModeC8()
// Parse the residuals.
if !skip {
// TODO(nigeltao): make d.parseResiduals return a bool, and change this to
// skip = d.parseResiduals(mbx, mby)
d.parseResiduals(mbx, mby)
skip = d.parseResiduals(mbx, mby)
} else {
if d.usePredY16 {
d.leftMB.nzY16 = 0

View File

@ -65,7 +65,7 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
return m, image.Config{}, nil
}
r = &io.LimitedReader{r, int64(dataLen)}
r = &io.LimitedReader{R: r, N: int64(dataLen)}
if configOnly {
c, err := vp8l.DecodeConfig(r)
return nil, c, err

View File

@ -33,6 +33,9 @@ func hex(x []byte) string {
func TestDecodeVP8(t *testing.T) {
testCases := []string{
"blue-purple-pink",
"blue-purple-pink-large.no-filter",
"blue-purple-pink-large.simple-filter",
"blue-purple-pink-large.normal-filter",
"video-001",
"yellow_rose",
}
@ -40,17 +43,20 @@ func TestDecodeVP8(t *testing.T) {
for _, tc := range testCases {
f0, err := os.Open("../testdata/" + tc + ".lossy.webp")
if err != nil {
t.Fatal(err)
t.Errorf("%s: Open WEBP: %v", tc, err)
continue
}
defer f0.Close()
img0, err := Decode(f0)
if err != nil {
t.Fatal(err)
t.Errorf("%s: Decode WEBP: %v", tc, err)
continue
}
m0, ok := img0.(*image.YCbCr)
if !ok || m0.SubsampleRatio != image.YCbCrSubsampleRatio420 {
t.Fatal("decoded WEBP image is not a 4:2:0 YCbCr")
t.Errorf("%s: decoded WEBP image is not a 4:2:0 YCbCr", tc)
continue
}
// w2 and h2 are the half-width and half-height, rounded up.
w, h := m0.Bounds().Dx(), m0.Bounds().Dy()
@ -58,12 +64,14 @@ func TestDecodeVP8(t *testing.T) {
f1, err := os.Open("../testdata/" + tc + ".lossy.webp.ycbcr.png")
if err != nil {
t.Fatal(err)
t.Errorf("%s: Open PNG: %v", tc, err)
continue
}
defer f1.Close()
img1, err := png.Decode(f1)
if err != nil {
t.Fatal(err)
t.Errorf("%s: Open PNG: %v", tc, err)
continue
}
// The split-into-YCbCr-planes golden image is a 2*w2 wide and h+h2 high
@ -73,11 +81,13 @@ func TestDecodeVP8(t *testing.T) {
// BBRR
// See http://www.fourcc.org/yuv.php#IMC4
if got, want := img1.Bounds(), image.Rect(0, 0, 2*w2, h+h2); got != want {
t.Fatalf("bounds0: got %v, want %v", got, want)
t.Errorf("%s: bounds0: got %v, want %v", tc, got, want)
continue
}
m1, ok := img1.(*image.Gray)
if !ok {
t.Fatal("decoded PNG image is not a Gray")
t.Errorf("%s: decoded PNG image is not a Gray", tc)
continue
}
planes := []struct {
@ -101,14 +111,14 @@ func TestDecodeVP8(t *testing.T) {
}
nDiff++
if nDiff > 10 {
t.Errorf("%s plane: more rows differ", plane.name)
t.Errorf("%s: %s plane: more rows differ", tc, plane.name)
break
}
for i := range got {
diff[i] = got[i] - want[i]
}
t.Errorf("%s plane: m0 row %d, m1 row %d\ngot %s\nwant%s\ndiff%s",
plane.name, j, y, hex(got), hex(want), hex(diff))
t.Errorf("%s: %s plane: m0 row %d, m1 row %d\ngot %s\nwant%s\ndiff%s",
tc, plane.name, j, y, hex(got), hex(want), hex(diff))
}
}
}
@ -117,6 +127,7 @@ func TestDecodeVP8(t *testing.T) {
func TestDecodeVP8L(t *testing.T) {
testCases := []string{
"blue-purple-pink",
"blue-purple-pink-large",
"gopher-doc.1bpp",
"gopher-doc.2bpp",
"gopher-doc.4bpp",
@ -197,7 +208,7 @@ loop:
}
func benchmarkDecode(b *testing.B, filename string) {
data, err := ioutil.ReadFile("../testdata/" + filename + ".webp")
data, err := ioutil.ReadFile("../testdata/blue-purple-pink-large." + filename + ".webp")
if err != nil {
b.Fatal(err)
}
@ -213,6 +224,7 @@ func benchmarkDecode(b *testing.B, filename string) {
}
}
func BenchmarkDecodeVP8SimpleFilter(b *testing.B) { benchmarkDecode(b, "blue-purple-pink.lossy") }
func BenchmarkDecodeVP8NormalFilter(b *testing.B) { benchmarkDecode(b, "yellow_rose.lossy") }
func BenchmarkDecodeVP8L(b *testing.B) { benchmarkDecode(b, "yellow_rose.lossless") }
func BenchmarkDecodeVP8NoFilter(b *testing.B) { benchmarkDecode(b, "no-filter.lossy") }
func BenchmarkDecodeVP8SimpleFilter(b *testing.B) { benchmarkDecode(b, "simple-filter.lossy") }
func BenchmarkDecodeVP8NormalFilter(b *testing.B) { benchmarkDecode(b, "normal-filter.lossy") }
func BenchmarkDecodeVP8L(b *testing.B) { benchmarkDecode(b, "lossless") }