...

Source file src/golang.org/x/image/webp/decode_test.go

Documentation: golang.org/x/image/webp

     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 benchmarkDecode(b *testing.B, filename string) {
   275  	data, err := ioutil.ReadFile("../testdata/blue-purple-pink-large." + filename + ".webp")
   276  	if err != nil {
   277  		b.Fatal(err)
   278  	}
   279  	s := string(data)
   280  	cfg, err := DecodeConfig(strings.NewReader(s))
   281  	if err != nil {
   282  		b.Fatal(err)
   283  	}
   284  	b.SetBytes(int64(cfg.Width * cfg.Height * 4))
   285  	b.ResetTimer()
   286  	for i := 0; i < b.N; i++ {
   287  		Decode(strings.NewReader(s))
   288  	}
   289  }
   290  
   291  func BenchmarkDecodeVP8NoFilter(b *testing.B)     { benchmarkDecode(b, "no-filter.lossy") }
   292  func BenchmarkDecodeVP8SimpleFilter(b *testing.B) { benchmarkDecode(b, "simple-filter.lossy") }
   293  func BenchmarkDecodeVP8NormalFilter(b *testing.B) { benchmarkDecode(b, "normal-filter.lossy") }
   294  func BenchmarkDecodeVP8L(b *testing.B)            { benchmarkDecode(b, "lossless") }
   295  

View as plain text