github.com/spotify/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/src/pkg/image/draw/draw_test.go (about)

     1  // Copyright 2010 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 draw
     6  
     7  import (
     8  	"image"
     9  	"image/color"
    10  	"image/png"
    11  	"os"
    12  	"testing"
    13  )
    14  
    15  func eq(c0, c1 color.Color) bool {
    16  	r0, g0, b0, a0 := c0.RGBA()
    17  	r1, g1, b1, a1 := c1.RGBA()
    18  	return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1
    19  }
    20  
    21  func fillBlue(alpha int) image.Image {
    22  	return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
    23  }
    24  
    25  func fillAlpha(alpha int) image.Image {
    26  	return image.NewUniform(color.Alpha{uint8(alpha)})
    27  }
    28  
    29  func vgradGreen(alpha int) image.Image {
    30  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
    31  	for y := 0; y < 16; y++ {
    32  		for x := 0; x < 16; x++ {
    33  			m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)})
    34  		}
    35  	}
    36  	return m
    37  }
    38  
    39  func vgradAlpha(alpha int) image.Image {
    40  	m := image.NewAlpha(image.Rect(0, 0, 16, 16))
    41  	for y := 0; y < 16; y++ {
    42  		for x := 0; x < 16; x++ {
    43  			m.Set(x, y, color.Alpha{uint8(y * alpha / 15)})
    44  		}
    45  	}
    46  	return m
    47  }
    48  
    49  func vgradGreenNRGBA(alpha int) image.Image {
    50  	m := image.NewNRGBA(image.Rect(0, 0, 16, 16))
    51  	for y := 0; y < 16; y++ {
    52  		for x := 0; x < 16; x++ {
    53  			m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)})
    54  		}
    55  	}
    56  	return m
    57  }
    58  
    59  func vgradCr() image.Image {
    60  	m := &image.YCbCr{
    61  		Y:              make([]byte, 16*16),
    62  		Cb:             make([]byte, 16*16),
    63  		Cr:             make([]byte, 16*16),
    64  		YStride:        16,
    65  		CStride:        16,
    66  		SubsampleRatio: image.YCbCrSubsampleRatio444,
    67  		Rect:           image.Rect(0, 0, 16, 16),
    68  	}
    69  	for y := 0; y < 16; y++ {
    70  		for x := 0; x < 16; x++ {
    71  			m.Cr[y*m.CStride+x] = uint8(y * 0x11)
    72  		}
    73  	}
    74  	return m
    75  }
    76  
    77  func hgradRed(alpha int) Image {
    78  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
    79  	for y := 0; y < 16; y++ {
    80  		for x := 0; x < 16; x++ {
    81  			m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)})
    82  		}
    83  	}
    84  	return m
    85  }
    86  
    87  func gradYellow(alpha int) Image {
    88  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
    89  	for y := 0; y < 16; y++ {
    90  		for x := 0; x < 16; x++ {
    91  			m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)})
    92  		}
    93  	}
    94  	return m
    95  }
    96  
    97  type drawTest struct {
    98  	desc     string
    99  	src      image.Image
   100  	mask     image.Image
   101  	op       Op
   102  	expected color.Color
   103  }
   104  
   105  var drawTests = []drawTest{
   106  	// Uniform mask (0% opaque).
   107  	{"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}},
   108  	{"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}},
   109  	// Uniform mask (100%, 75%, nil) and uniform source.
   110  	// At (x, y) == (8, 8):
   111  	// The destination pixel is {136, 0, 0, 255}.
   112  	// The source pixel is {0, 0, 90, 90}.
   113  	{"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}},
   114  	{"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}},
   115  	{"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}},
   116  	{"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}},
   117  	{"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}},
   118  	{"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}},
   119  	// Uniform mask (100%, 75%, nil) and variable source.
   120  	// At (x, y) == (8, 8):
   121  	// The destination pixel is {136, 0, 0, 255}.
   122  	// The source pixel is {0, 48, 0, 90}.
   123  	{"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}},
   124  	{"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}},
   125  	{"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}},
   126  	{"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}},
   127  	{"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}},
   128  	{"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}},
   129  	// Uniform mask (100%, 75%, nil) and variable NRGBA source.
   130  	// At (x, y) == (8, 8):
   131  	// The destination pixel is {136, 0, 0, 255}.
   132  	// The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space.
   133  	// The result pixel is different than in the "copy*" test cases because of rounding errors.
   134  	{"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}},
   135  	{"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}},
   136  	{"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}},
   137  	{"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}},
   138  	{"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}},
   139  	{"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}},
   140  	// Uniform mask (100%, 75%, nil) and variable YCbCr source.
   141  	// At (x, y) == (8, 8):
   142  	// The destination pixel is {136, 0, 0, 255}.
   143  	// The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space.
   144  	{"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}},
   145  	{"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}},
   146  	{"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}},
   147  	{"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}},
   148  	{"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}},
   149  	{"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}},
   150  	// Variable mask and variable source.
   151  	// At (x, y) == (8, 8):
   152  	// The destination pixel is {136, 0, 0, 255}.
   153  	// The source pixel is {0, 0, 255, 255}.
   154  	// The mask pixel's alpha is 102, or 40%.
   155  	{"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
   156  	{"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
   157  }
   158  
   159  func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
   160  	// Since golden is a newly allocated image, we don't have to check if the
   161  	// input source and mask images and the output golden image overlap.
   162  	b := dst.Bounds()
   163  	sb := src.Bounds()
   164  	mb := image.Rect(-1e9, -1e9, 1e9, 1e9)
   165  	if mask != nil {
   166  		mb = mask.Bounds()
   167  	}
   168  	golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y))
   169  	for y := r.Min.Y; y < r.Max.Y; y++ {
   170  		sy := y + sp.Y - r.Min.Y
   171  		my := y + mp.Y - r.Min.Y
   172  		for x := r.Min.X; x < r.Max.X; x++ {
   173  			if !(image.Pt(x, y).In(b)) {
   174  				continue
   175  			}
   176  			sx := x + sp.X - r.Min.X
   177  			if !(image.Pt(sx, sy).In(sb)) {
   178  				continue
   179  			}
   180  			mx := x + mp.X - r.Min.X
   181  			if !(image.Pt(mx, my).In(mb)) {
   182  				continue
   183  			}
   184  
   185  			const M = 1<<16 - 1
   186  			var dr, dg, db, da uint32
   187  			if op == Over {
   188  				dr, dg, db, da = dst.At(x, y).RGBA()
   189  			}
   190  			sr, sg, sb, sa := src.At(sx, sy).RGBA()
   191  			ma := uint32(M)
   192  			if mask != nil {
   193  				_, _, _, ma = mask.At(mx, my).RGBA()
   194  			}
   195  			a := M - (sa * ma / M)
   196  			golden.Set(x, y, color.RGBA64{
   197  				uint16((dr*a + sr*ma) / M),
   198  				uint16((dg*a + sg*ma) / M),
   199  				uint16((db*a + sb*ma) / M),
   200  				uint16((da*a + sa*ma) / M),
   201  			})
   202  		}
   203  	}
   204  	return golden.SubImage(b)
   205  }
   206  
   207  func TestDraw(t *testing.T) {
   208  	rr := []image.Rectangle{
   209  		image.Rect(0, 0, 0, 0),
   210  		image.Rect(0, 0, 16, 16),
   211  		image.Rect(3, 5, 12, 10),
   212  		image.Rect(0, 0, 9, 9),
   213  		image.Rect(8, 8, 16, 16),
   214  		image.Rect(8, 0, 9, 16),
   215  		image.Rect(0, 8, 16, 9),
   216  		image.Rect(8, 8, 9, 9),
   217  		image.Rect(8, 8, 8, 8),
   218  	}
   219  	for _, r := range rr {
   220  	loop:
   221  		for _, test := range drawTests {
   222  			dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
   223  			// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
   224  			golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
   225  			b := dst.Bounds()
   226  			if !b.Eq(golden.Bounds()) {
   227  				t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds())
   228  				continue
   229  			}
   230  			// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
   231  			DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
   232  			if image.Pt(8, 8).In(r) {
   233  				// Check that the resultant pixel at (8, 8) matches what we expect
   234  				// (the expected value can be verified by hand).
   235  				if !eq(dst.At(8, 8), test.expected) {
   236  					t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected)
   237  					continue
   238  				}
   239  			}
   240  			// Check that the resultant dst image matches the golden output.
   241  			for y := b.Min.Y; y < b.Max.Y; y++ {
   242  				for x := b.Min.X; x < b.Max.X; x++ {
   243  					if !eq(dst.At(x, y), golden.At(x, y)) {
   244  						t.Errorf("draw %v %s: at (%d, %d), %v versus golden %v", r, test.desc, x, y, dst.At(x, y), golden.At(x, y))
   245  						continue loop
   246  					}
   247  				}
   248  			}
   249  		}
   250  	}
   251  }
   252  
   253  func TestDrawOverlap(t *testing.T) {
   254  	for _, op := range []Op{Over, Src} {
   255  		for yoff := -2; yoff <= 2; yoff++ {
   256  		loop:
   257  			for xoff := -2; xoff <= 2; xoff++ {
   258  				m := gradYellow(127).(*image.RGBA)
   259  				dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA)
   260  				src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA)
   261  				b := dst.Bounds()
   262  				// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
   263  				golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
   264  				if !b.Eq(golden.Bounds()) {
   265  					t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds())
   266  					continue
   267  				}
   268  				// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
   269  				DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
   270  				// Check that the resultant dst image matches the golden output.
   271  				for y := b.Min.Y; y < b.Max.Y; y++ {
   272  					for x := b.Min.X; x < b.Max.X; x++ {
   273  						if !eq(dst.At(x, y), golden.At(x, y)) {
   274  							t.Errorf("drawOverlap xoff=%d,yoff=%d: at (%d, %d), %v versus golden %v", xoff, yoff, x, y, dst.At(x, y), golden.At(x, y))
   275  							continue loop
   276  						}
   277  					}
   278  				}
   279  			}
   280  		}
   281  	}
   282  }
   283  
   284  // TestNonZeroSrcPt checks drawing with a non-zero src point parameter.
   285  func TestNonZeroSrcPt(t *testing.T) {
   286  	a := image.NewRGBA(image.Rect(0, 0, 1, 1))
   287  	b := image.NewRGBA(image.Rect(0, 0, 2, 2))
   288  	b.Set(0, 0, color.RGBA{0, 0, 0, 5})
   289  	b.Set(1, 0, color.RGBA{0, 0, 5, 5})
   290  	b.Set(0, 1, color.RGBA{0, 5, 0, 5})
   291  	b.Set(1, 1, color.RGBA{5, 0, 0, 5})
   292  	Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over)
   293  	if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) {
   294  		t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0))
   295  	}
   296  }
   297  
   298  func TestFill(t *testing.T) {
   299  	rr := []image.Rectangle{
   300  		image.Rect(0, 0, 0, 0),
   301  		image.Rect(0, 0, 40, 30),
   302  		image.Rect(10, 0, 40, 30),
   303  		image.Rect(0, 20, 40, 30),
   304  		image.Rect(10, 20, 40, 30),
   305  		image.Rect(10, 20, 15, 25),
   306  		image.Rect(10, 0, 35, 30),
   307  		image.Rect(0, 15, 40, 16),
   308  		image.Rect(24, 24, 25, 25),
   309  		image.Rect(23, 23, 26, 26),
   310  		image.Rect(22, 22, 27, 27),
   311  		image.Rect(21, 21, 28, 28),
   312  		image.Rect(20, 20, 29, 29),
   313  	}
   314  	for _, r := range rr {
   315  		m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA)
   316  		b := m.Bounds()
   317  		c := color.RGBA{11, 0, 0, 255}
   318  		src := &image.Uniform{C: c}
   319  		check := func(desc string) {
   320  			for y := b.Min.Y; y < b.Max.Y; y++ {
   321  				for x := b.Min.X; x < b.Max.X; x++ {
   322  					if !eq(c, m.At(x, y)) {
   323  						t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y))
   324  						return
   325  					}
   326  				}
   327  			}
   328  		}
   329  		// Draw 1 pixel at a time.
   330  		for y := b.Min.Y; y < b.Max.Y; y++ {
   331  			for x := b.Min.X; x < b.Max.X; x++ {
   332  				DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src)
   333  			}
   334  		}
   335  		check("pixel")
   336  		// Draw 1 row at a time.
   337  		c = color.RGBA{0, 22, 0, 255}
   338  		src = &image.Uniform{C: c}
   339  		for y := b.Min.Y; y < b.Max.Y; y++ {
   340  			DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src)
   341  		}
   342  		check("row")
   343  		// Draw 1 column at a time.
   344  		c = color.RGBA{0, 0, 33, 255}
   345  		src = &image.Uniform{C: c}
   346  		for x := b.Min.X; x < b.Max.X; x++ {
   347  			DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src)
   348  		}
   349  		check("column")
   350  		// Draw the whole image at once.
   351  		c = color.RGBA{44, 55, 66, 77}
   352  		src = &image.Uniform{C: c}
   353  		DrawMask(m, b, src, image.ZP, nil, image.ZP, Src)
   354  		check("whole")
   355  	}
   356  }
   357  
   358  // TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg
   359  // error diffusion of a uniform 50% gray source image with a black-and-white
   360  // palette is a checkerboard pattern.
   361  func TestFloydSteinbergCheckerboard(t *testing.T) {
   362  	b := image.Rect(0, 0, 640, 480)
   363  	// We can't represent 50% exactly, but 0x7fff / 0xffff is close enough.
   364  	src := &image.Uniform{color.Gray16{0x7fff}}
   365  	dst := image.NewPaletted(b, color.Palette{color.Black, color.White})
   366  	FloydSteinberg.Draw(dst, b, src, image.Point{})
   367  	nErr := 0
   368  	for y := b.Min.Y; y < b.Max.Y; y++ {
   369  		for x := b.Min.X; x < b.Max.X; x++ {
   370  			got := dst.Pix[dst.PixOffset(x, y)]
   371  			want := uint8(x+y) % 2
   372  			if got != want {
   373  				t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want)
   374  				if nErr++; nErr == 10 {
   375  					t.Fatal("there may be more errors")
   376  				}
   377  			}
   378  		}
   379  	}
   380  }
   381  
   382  // embeddedPaletted is an Image that behaves like an *image.Paletted but whose
   383  // type is not *image.Paletted.
   384  type embeddedPaletted struct {
   385  	*image.Paletted
   386  }
   387  
   388  // TestPaletted tests that the drawPaletted function behaves the same
   389  // regardless of whether dst is an *image.Paletted.
   390  func TestPaletted(t *testing.T) {
   391  	f, err := os.Open("../testdata/video-001.png")
   392  	if err != nil {
   393  		t.Fatalf("open: %v", err)
   394  	}
   395  	defer f.Close()
   396  	src, err := png.Decode(f)
   397  	if err != nil {
   398  		t.Fatalf("decode: %v", err)
   399  	}
   400  	b := src.Bounds()
   401  
   402  	cgaPalette := color.Palette{
   403  		color.RGBA{0x00, 0x00, 0x00, 0xff},
   404  		color.RGBA{0x55, 0xff, 0xff, 0xff},
   405  		color.RGBA{0xff, 0x55, 0xff, 0xff},
   406  		color.RGBA{0xff, 0xff, 0xff, 0xff},
   407  	}
   408  	drawers := map[string]Drawer{
   409  		"src":             Src,
   410  		"floyd-steinberg": FloydSteinberg,
   411  	}
   412  
   413  loop:
   414  	for dName, d := range drawers {
   415  		dst0 := image.NewPaletted(b, cgaPalette)
   416  		dst1 := image.NewPaletted(b, cgaPalette)
   417  		d.Draw(dst0, b, src, image.Point{})
   418  		d.Draw(embeddedPaletted{dst1}, b, src, image.Point{})
   419  		for y := b.Min.Y; y < b.Max.Y; y++ {
   420  			for x := b.Min.X; x < b.Max.X; x++ {
   421  				if !eq(dst0.At(x, y), dst1.At(x, y)) {
   422  					t.Errorf("%s: at (%d, %d), %v versus %v",
   423  						dName, x, y, dst0.At(x, y), dst1.At(x, y))
   424  					continue loop
   425  				}
   426  			}
   427  		}
   428  	}
   429  }