github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/image/webp/decode_test.go (about)

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