git.lucas.co / go_mono
git clone https://git.lucas.co/go_mono.git

webp/decode_test.go (7.5K)

  1 // Copyright 2014 The Go Authors. All rights reserved.
  2 // Use of this source code is governed by a BSD-style
  3 // license that can be found in the LICENSE file.
  4 
  5 package webp
  6 
  7 import (
  8 	"bytes"
  9 	"fmt"
 10 	"image"
 11 	"image/png"
 12 	"io/ioutil"
 13 	"os"
 14 	"strings"
 15 	"testing"
 16 )
 17 
 18 // hex is like fmt.Sprintf("% x", x) but also inserts dots every 16 bytes, to
 19 // delineate VP8 macroblock boundaries.
 20 func hex(x []byte) string {
 21 	buf := new(bytes.Buffer)
 22 	for len(x) > 0 {
 23 		n := len(x)
 24 		if n > 16 {
 25 			n = 16
 26 		}
 27 		fmt.Fprintf(buf, " . % x", x[:n])
 28 		x = x[n:]
 29 	}
 30 	return buf.String()
 31 }
 32 
 33 func testDecodeLossy(t *testing.T, tc string, withAlpha bool) {
 34 	webpFilename := "../testdata/" + tc + ".lossy.webp"
 35 	pngFilename := webpFilename + ".ycbcr.png"
 36 	if withAlpha {
 37 		webpFilename = "../testdata/" + tc + ".lossy-with-alpha.webp"
 38 		pngFilename = webpFilename + ".nycbcra.png"
 39 	}
 40 
 41 	f0, err := os.Open(webpFilename)
 42 	if err != nil {
 43 		t.Errorf("%s: Open WEBP: %v", tc, err)
 44 		return
 45 	}
 46 	defer f0.Close()
 47 	img0, err := Decode(f0)
 48 	if err != nil {
 49 		t.Errorf("%s: Decode WEBP: %v", tc, err)
 50 		return
 51 	}
 52 
 53 	var (
 54 		m0 *image.YCbCr
 55 		a0 *image.NYCbCrA
 56 		ok bool
 57 	)
 58 	if withAlpha {
 59 		a0, ok = img0.(*image.NYCbCrA)
 60 		if ok {
 61 			m0 = &a0.YCbCr
 62 		}
 63 	} else {
 64 		m0, ok = img0.(*image.YCbCr)
 65 	}
 66 	if !ok || m0.SubsampleRatio != image.YCbCrSubsampleRatio420 {
 67 		t.Errorf("%s: decoded WEBP image is not a 4:2:0 YCbCr or 4:2:0 NYCbCrA", tc)
 68 		return
 69 	}
 70 	// w2 and h2 are the half-width and half-height, rounded up.
 71 	w, h := m0.Bounds().Dx(), m0.Bounds().Dy()
 72 	w2, h2 := int((w+1)/2), int((h+1)/2)
 73 
 74 	f1, err := os.Open(pngFilename)
 75 	if err != nil {
 76 		t.Errorf("%s: Open PNG: %v", tc, err)
 77 		return
 78 	}
 79 	defer f1.Close()
 80 	img1, err := png.Decode(f1)
 81 	if err != nil {
 82 		t.Errorf("%s: Open PNG: %v", tc, err)
 83 		return
 84 	}
 85 
 86 	// The split-into-YCbCr-planes golden image is a 2*w2 wide and h+h2 high
 87 	// (or 2*h+h2 high, if with Alpha) gray image arranged in IMC4 format:
 88 	//   YYYY
 89 	//   YYYY
 90 	//   BBRR
 91 	//   AAAA
 92 	// See http://www.fourcc.org/yuv.php#IMC4
 93 	pngW, pngH := 2*w2, h+h2
 94 	if withAlpha {
 95 		pngH += h
 96 	}
 97 	if got, want := img1.Bounds(), image.Rect(0, 0, pngW, pngH); got != want {
 98 		t.Errorf("%s: bounds0: got %v, want %v", tc, got, want)
 99 		return
100 	}
101 	m1, ok := img1.(*image.Gray)
102 	if !ok {
103 		t.Errorf("%s: decoded PNG image is not a Gray", tc)
104 		return
105 	}
106 
107 	type plane struct {
108 		name     string
109 		m0Pix    []uint8
110 		m0Stride int
111 		m1Rect   image.Rectangle
112 	}
113 	planes := []plane{
114 		{"Y", m0.Y, m0.YStride, image.Rect(0, 0, w, h)},
115 		{"Cb", m0.Cb, m0.CStride, image.Rect(0*w2, h, 1*w2, h+h2)},
116 		{"Cr", m0.Cr, m0.CStride, image.Rect(1*w2, h, 2*w2, h+h2)},
117 	}
118 	if withAlpha {
119 		planes = append(planes, plane{
120 			"A", a0.A, a0.AStride, image.Rect(0, h+h2, w, 2*h+h2),
121 		})
122 	}
123 
124 	for _, plane := range planes {
125 		dx := plane.m1Rect.Dx()
126 		nDiff, diff := 0, make([]byte, dx)
127 		for j, y := 0, plane.m1Rect.Min.Y; y < plane.m1Rect.Max.Y; j, y = j+1, y+1 {
128 			got := plane.m0Pix[j*plane.m0Stride:][:dx]
129 			want := m1.Pix[y*m1.Stride+plane.m1Rect.Min.X:][:dx]
130 			if bytes.Equal(got, want) {
131 				continue
132 			}
133 			nDiff++
134 			if nDiff > 10 {
135 				t.Errorf("%s: %s plane: more rows differ", tc, plane.name)
136 				break
137 			}
138 			for i := range got {
139 				diff[i] = got[i] - want[i]
140 			}
141 			t.Errorf("%s: %s plane: m0 row %d, m1 row %d\ngot %s\nwant%s\ndiff%s",
142 				tc, plane.name, j, y, hex(got), hex(want), hex(diff))
143 		}
144 	}
145 }
146 
147 func TestDecodeVP8(t *testing.T) {
148 	testCases := []string{
149 		"blue-purple-pink",
150 		"blue-purple-pink-large.no-filter",
151 		"blue-purple-pink-large.simple-filter",
152 		"blue-purple-pink-large.normal-filter",
153 		"video-001",
154 		"yellow_rose",
155 	}
156 
157 	for _, tc := range testCases {
158 		testDecodeLossy(t, tc, false)
159 	}
160 }
161 
162 func TestDecodeVP8XAlpha(t *testing.T) {
163 	testCases := []string{
164 		"yellow_rose",
165 	}
166 
167 	for _, tc := range testCases {
168 		testDecodeLossy(t, tc, true)
169 	}
170 }
171 
172 func TestDecodeVP8L(t *testing.T) {
173 	testCases := []string{
174 		"blue-purple-pink",
175 		"blue-purple-pink-large",
176 		"gopher-doc.1bpp",
177 		"gopher-doc.2bpp",
178 		"gopher-doc.4bpp",
179 		"gopher-doc.8bpp",
180 		"tux",
181 		"yellow_rose",
182 	}
183 
184 loop:
185 	for _, tc := range testCases {
186 		f0, err := os.Open("../testdata/" + tc + ".lossless.webp")
187 		if err != nil {
188 			t.Errorf("%s: Open WEBP: %v", tc, err)
189 			continue
190 		}
191 		defer f0.Close()
192 		img0, err := Decode(f0)
193 		if err != nil {
194 			t.Errorf("%s: Decode WEBP: %v", tc, err)
195 			continue
196 		}
197 		m0, ok := img0.(*image.NRGBA)
198 		if !ok {
199 			t.Errorf("%s: WEBP image is %T, want *image.NRGBA", tc, img0)
200 			continue
201 		}
202 
203 		f1, err := os.Open("../testdata/" + tc + ".png")
204 		if err != nil {
205 			t.Errorf("%s: Open PNG: %v", tc, err)
206 			continue
207 		}
208 		defer f1.Close()
209 		img1, err := png.Decode(f1)
210 		if err != nil {
211 			t.Errorf("%s: Decode PNG: %v", tc, err)
212 			continue
213 		}
214 		m1, ok := img1.(*image.NRGBA)
215 		if !ok {
216 			rgba1, ok := img1.(*image.RGBA)
217 			if !ok {
218 				t.Fatalf("%s: PNG image is %T, want *image.NRGBA", tc, img1)
219 				continue
220 			}
221 			if !rgba1.Opaque() {
222 				t.Fatalf("%s: PNG image is non-opaque *image.RGBA, want *image.NRGBA", tc)
223 				continue
224 			}
225 			// The image is fully opaque, so we can re-interpret the RGBA pixels
226 			// as NRGBA pixels.
227 			m1 = &image.NRGBA{
228 				Pix:    rgba1.Pix,
229 				Stride: rgba1.Stride,
230 				Rect:   rgba1.Rect,
231 			}
232 		}
233 
234 		b0, b1 := m0.Bounds(), m1.Bounds()
235 		if b0 != b1 {
236 			t.Errorf("%s: bounds: got %v, want %v", tc, b0, b1)
237 			continue
238 		}
239 		for i := range m0.Pix {
240 			if m0.Pix[i] != m1.Pix[i] {
241 				y := i / m0.Stride
242 				x := (i - y*m0.Stride) / 4
243 				i = 4 * (y*m0.Stride + x)
244 				t.Errorf("%s: at (%d, %d):\ngot  %02x %02x %02x %02x\nwant %02x %02x %02x %02x",
245 					tc, x, y,
246 					m0.Pix[i+0], m0.Pix[i+1], m0.Pix[i+2], m0.Pix[i+3],
247 					m1.Pix[i+0], m1.Pix[i+1], m1.Pix[i+2], m1.Pix[i+3],
248 				)
249 				continue loop
250 			}
251 		}
252 	}
253 }
254 
255 // TestDecodePartitionTooLarge tests that decoding a malformed WEBP image
256 // doesn't try to allocate an unreasonable amount of memory. This WEBP image
257 // claims a RIFF chunk length of 0x12345678 bytes (291 MiB) compressed,
258 // independent of the actual image size (0 pixels wide * 0 pixels high).
259 //
260 // This is based on golang.org/issue/10790.
261 func TestDecodePartitionTooLarge(t *testing.T) {
262 	data := "RIFF\xff\xff\xff\x7fWEBPVP8 " +
263 		"\x78\x56\x34\x12" + // RIFF chunk length.
264 		"\xbd\x01\x00\x14\x00\x00\xb2\x34\x0a\x9d\x01\x2a\x96\x00\x67\x00"
265 	_, err := Decode(strings.NewReader(data))
266 	if err == nil {
267 		t.Fatal("got nil error, want non-nil")
268 	}
269 	if got, want := err.Error(), "too much data"; !strings.Contains(got, want) {
270 		t.Fatalf("got error %q, want something containing %q", got, want)
271 	}
272 }
273 
274 func TestDuplicateVP8X(t *testing.T) {
275 	data := []byte{'R', 'I', 'F', 'F', 49, 0, 0, 0, 'W', 'E', 'B', 'P', 'V', 'P', '8', 'X', 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'V', 'P', '8', 'X', 10, 0, 0, 0, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0}
276 	_, err := Decode(bytes.NewReader(data))
277 	if err != errInvalidFormat {
278 		t.Fatalf("unexpected error: want %q, got %q", errInvalidFormat, err)
279 	}
280 }
281 
282 func benchmarkDecode(b *testing.B, filename string) {
283 	data, err := ioutil.ReadFile("../testdata/blue-purple-pink-large." + filename + ".webp")
284 	if err != nil {
285 		b.Fatal(err)
286 	}
287 	s := string(data)
288 	cfg, err := DecodeConfig(strings.NewReader(s))
289 	if err != nil {
290 		b.Fatal(err)
291 	}
292 	b.SetBytes(int64(cfg.Width * cfg.Height * 4))
293 	b.ResetTimer()
294 	for i := 0; i < b.N; i++ {
295 		Decode(strings.NewReader(s))
296 	}
297 }
298 
299 func BenchmarkDecodeVP8NoFilter(b *testing.B)     { benchmarkDecode(b, "no-filter.lossy") }
300 func BenchmarkDecodeVP8SimpleFilter(b *testing.B) { benchmarkDecode(b, "simple-filter.lossy") }
301 func BenchmarkDecodeVP8NormalFilter(b *testing.B) { benchmarkDecode(b, "normal-filter.lossy") }
302 func BenchmarkDecodeVP8L(b *testing.B)            { benchmarkDecode(b, "lossless") }