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
diff --git a/testdata/blue-purple-pink-large.lossless.webp b/testdata/blue-purple-pink-large.lossless.webp
new file mode 100644
index 0000000..d00c81f
--- /dev/null
+++ b/testdata/blue-purple-pink-large.lossless.webp
Binary files differ
diff --git a/testdata/blue-purple-pink-large.no-filter.lossy.webp b/testdata/blue-purple-pink-large.no-filter.lossy.webp
new file mode 100644
index 0000000..9067f4d
--- /dev/null
+++ b/testdata/blue-purple-pink-large.no-filter.lossy.webp
Binary files differ
diff --git a/testdata/blue-purple-pink-large.no-filter.lossy.webp.ycbcr.png b/testdata/blue-purple-pink-large.no-filter.lossy.webp.ycbcr.png
new file mode 100644
index 0000000..2e32c28
--- /dev/null
+++ b/testdata/blue-purple-pink-large.no-filter.lossy.webp.ycbcr.png
Binary files differ
diff --git a/testdata/blue-purple-pink-large.normal-filter.lossy.webp b/testdata/blue-purple-pink-large.normal-filter.lossy.webp
new file mode 100644
index 0000000..a4ccc1a
--- /dev/null
+++ b/testdata/blue-purple-pink-large.normal-filter.lossy.webp
Binary files differ
diff --git a/testdata/blue-purple-pink-large.normal-filter.lossy.webp.ycbcr.png b/testdata/blue-purple-pink-large.normal-filter.lossy.webp.ycbcr.png
new file mode 100644
index 0000000..5f7ec42
--- /dev/null
+++ b/testdata/blue-purple-pink-large.normal-filter.lossy.webp.ycbcr.png
Binary files differ
diff --git a/testdata/blue-purple-pink-large.png b/testdata/blue-purple-pink-large.png
new file mode 100644
index 0000000..9755505
--- /dev/null
+++ b/testdata/blue-purple-pink-large.png
Binary files differ
diff --git a/testdata/blue-purple-pink-large.simple-filter.lossy.webp b/testdata/blue-purple-pink-large.simple-filter.lossy.webp
new file mode 100644
index 0000000..09fdb94
--- /dev/null
+++ b/testdata/blue-purple-pink-large.simple-filter.lossy.webp
Binary files differ
diff --git a/testdata/blue-purple-pink-large.simple-filter.lossy.webp.ycbcr.png b/testdata/blue-purple-pink-large.simple-filter.lossy.webp.ycbcr.png
new file mode 100644
index 0000000..946b3af
--- /dev/null
+++ b/testdata/blue-purple-pink-large.simple-filter.lossy.webp.ycbcr.png
Binary files differ
diff --git a/vp8/reconstruct.go b/vp8/reconstruct.go
index 309d1c1..c1cc4b5 100644
--- a/vp8/reconstruct.go
+++ b/vp8/reconstruct.go
@@ -266,8 +266,9 @@
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 @@
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 @@
}
}
-// 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 @@
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
diff --git a/webp/decode.go b/webp/decode.go
index 2ed7f36..453ccbd 100644
--- a/webp/decode.go
+++ b/webp/decode.go
@@ -65,7 +65,7 @@
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
diff --git a/webp/decode_test.go b/webp/decode_test.go
index 635c5d0..a86e32c 100644
--- a/webp/decode_test.go
+++ b/webp/decode_test.go
@@ -33,6 +33,9 @@
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 @@
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 @@
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 @@
// 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 @@
}
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 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 @@
}
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 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") }