github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/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  	"testing"
    11  )
    12  
    13  func eq(c0, c1 color.Color) bool {
    14  	r0, g0, b0, a0 := c0.RGBA()
    15  	r1, g1, b1, a1 := c1.RGBA()
    16  	return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1
    17  }
    18  
    19  func fillBlue(alpha int) image.Image {
    20  	return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
    21  }
    22  
    23  func fillAlpha(alpha int) image.Image {
    24  	return image.NewUniform(color.Alpha{uint8(alpha)})
    25  }
    26  
    27  func vgradGreen(alpha int) image.Image {
    28  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
    29  	for y := 0; y < 16; y++ {
    30  		for x := 0; x < 16; x++ {
    31  			m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)})
    32  		}
    33  	}
    34  	return m
    35  }
    36  
    37  func vgradAlpha(alpha int) image.Image {
    38  	m := image.NewAlpha(image.Rect(0, 0, 16, 16))
    39  	for y := 0; y < 16; y++ {
    40  		for x := 0; x < 16; x++ {
    41  			m.Set(x, y, color.Alpha{uint8(y * alpha / 15)})
    42  		}
    43  	}
    44  	return m
    45  }
    46  
    47  func vgradGreenNRGBA(alpha int) image.Image {
    48  	m := image.NewNRGBA(image.Rect(0, 0, 16, 16))
    49  	for y := 0; y < 16; y++ {
    50  		for x := 0; x < 16; x++ {
    51  			m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)})
    52  		}
    53  	}
    54  	return m
    55  }
    56  
    57  func vgradCr() image.Image {
    58  	m := &image.YCbCr{
    59  		Y:              make([]byte, 16*16),
    60  		Cb:             make([]byte, 16*16),
    61  		Cr:             make([]byte, 16*16),
    62  		YStride:        16,
    63  		CStride:        16,
    64  		SubsampleRatio: image.YCbCrSubsampleRatio444,
    65  		Rect:           image.Rect(0, 0, 16, 16),
    66  	}
    67  	for y := 0; y < 16; y++ {
    68  		for x := 0; x < 16; x++ {
    69  			m.Cr[y*m.CStride+x] = uint8(y * 0x11)
    70  		}
    71  	}
    72  	return m
    73  }
    74  
    75  func hgradRed(alpha int) Image {
    76  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
    77  	for y := 0; y < 16; y++ {
    78  		for x := 0; x < 16; x++ {
    79  			m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)})
    80  		}
    81  	}
    82  	return m
    83  }
    84  
    85  func gradYellow(alpha int) Image {
    86  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
    87  	for y := 0; y < 16; y++ {
    88  		for x := 0; x < 16; x++ {
    89  			m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)})
    90  		}
    91  	}
    92  	return m
    93  }
    94  
    95  type drawTest struct {
    96  	desc     string
    97  	src      image.Image
    98  	mask     image.Image
    99  	op       Op
   100  	expected color.Color
   101  }
   102  
   103  var drawTests = []drawTest{
   104  	// Uniform mask (0% opaque).
   105  	{"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}},
   106  	{"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}},
   107  	// Uniform mask (100%, 75%, nil) and uniform source.
   108  	// At (x, y) == (8, 8):
   109  	// The destination pixel is {136, 0, 0, 255}.
   110  	// The source pixel is {0, 0, 90, 90}.
   111  	{"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}},
   112  	{"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}},
   113  	{"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}},
   114  	{"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}},
   115  	{"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}},
   116  	{"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}},
   117  	// Uniform mask (100%, 75%, nil) and variable source.
   118  	// At (x, y) == (8, 8):
   119  	// The destination pixel is {136, 0, 0, 255}.
   120  	// The source pixel is {0, 48, 0, 90}.
   121  	{"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}},
   122  	{"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}},
   123  	{"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}},
   124  	{"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}},
   125  	{"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}},
   126  	{"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}},
   127  	// Uniform mask (100%, 75%, nil) and variable NRGBA source.
   128  	// At (x, y) == (8, 8):
   129  	// The destination pixel is {136, 0, 0, 255}.
   130  	// The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space.
   131  	// The result pixel is different than in the "copy*" test cases because of rounding errors.
   132  	{"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}},
   133  	{"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}},
   134  	{"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}},
   135  	{"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}},
   136  	{"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}},
   137  	{"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}},
   138  	// Uniform mask (100%, 75%, nil) and variable YCbCr source.
   139  	// At (x, y) == (8, 8):
   140  	// The destination pixel is {136, 0, 0, 255}.
   141  	// The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space.
   142  	{"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}},
   143  	{"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}},
   144  	{"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}},
   145  	{"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}},
   146  	{"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}},
   147  	{"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}},
   148  	// Variable mask and variable source.
   149  	// At (x, y) == (8, 8):
   150  	// The destination pixel is {136, 0, 0, 255}.
   151  	// The source pixel is {0, 0, 255, 255}.
   152  	// The mask pixel's alpha is 102, or 40%.
   153  	{"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
   154  	{"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
   155  }
   156  
   157  func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
   158  	// Since golden is a newly allocated image, we don't have to check if the
   159  	// input source and mask images and the output golden image overlap.
   160  	b := dst.Bounds()
   161  	sb := src.Bounds()
   162  	mb := image.Rect(-1e9, -1e9, 1e9, 1e9)
   163  	if mask != nil {
   164  		mb = mask.Bounds()
   165  	}
   166  	golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y))
   167  	for y := r.Min.Y; y < r.Max.Y; y++ {
   168  		sy := y + sp.Y - r.Min.Y
   169  		my := y + mp.Y - r.Min.Y
   170  		for x := r.Min.X; x < r.Max.X; x++ {
   171  			if !(image.Pt(x, y).In(b)) {
   172  				continue
   173  			}
   174  			sx := x + sp.X - r.Min.X
   175  			if !(image.Pt(sx, sy).In(sb)) {
   176  				continue
   177  			}
   178  			mx := x + mp.X - r.Min.X
   179  			if !(image.Pt(mx, my).In(mb)) {
   180  				continue
   181  			}
   182  
   183  			const M = 1<<16 - 1
   184  			var dr, dg, db, da uint32
   185  			if op == Over {
   186  				dr, dg, db, da = dst.At(x, y).RGBA()
   187  			}
   188  			sr, sg, sb, sa := src.At(sx, sy).RGBA()
   189  			ma := uint32(M)
   190  			if mask != nil {
   191  				_, _, _, ma = mask.At(mx, my).RGBA()
   192  			}
   193  			a := M - (sa * ma / M)
   194  			golden.Set(x, y, color.RGBA64{
   195  				uint16((dr*a + sr*ma) / M),
   196  				uint16((dg*a + sg*ma) / M),
   197  				uint16((db*a + sb*ma) / M),
   198  				uint16((da*a + sa*ma) / M),
   199  			})
   200  		}
   201  	}
   202  	return golden.SubImage(b)
   203  }
   204  
   205  func TestDraw(t *testing.T) {
   206  	rr := []image.Rectangle{
   207  		image.Rect(0, 0, 0, 0),
   208  		image.Rect(0, 0, 16, 16),
   209  		image.Rect(3, 5, 12, 10),
   210  		image.Rect(0, 0, 9, 9),
   211  		image.Rect(8, 8, 16, 16),
   212  		image.Rect(8, 0, 9, 16),
   213  		image.Rect(0, 8, 16, 9),
   214  		image.Rect(8, 8, 9, 9),
   215  		image.Rect(8, 8, 8, 8),
   216  	}
   217  	for _, r := range rr {
   218  	loop:
   219  		for _, test := range drawTests {
   220  			dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
   221  			// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
   222  			golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
   223  			b := dst.Bounds()
   224  			if !b.Eq(golden.Bounds()) {
   225  				t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds())
   226  				continue
   227  			}
   228  			// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
   229  			DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
   230  			if image.Pt(8, 8).In(r) {
   231  				// Check that the resultant pixel at (8, 8) matches what we expect
   232  				// (the expected value can be verified by hand).
   233  				if !eq(dst.At(8, 8), test.expected) {
   234  					t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected)
   235  					continue
   236  				}
   237  			}
   238  			// Check that the resultant dst image matches the golden output.
   239  			for y := b.Min.Y; y < b.Max.Y; y++ {
   240  				for x := b.Min.X; x < b.Max.X; x++ {
   241  					if !eq(dst.At(x, y), golden.At(x, y)) {
   242  						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))
   243  						continue loop
   244  					}
   245  				}
   246  			}
   247  		}
   248  	}
   249  }
   250  
   251  func TestDrawOverlap(t *testing.T) {
   252  	for _, op := range []Op{Over, Src} {
   253  		for yoff := -2; yoff <= 2; yoff++ {
   254  		loop:
   255  			for xoff := -2; xoff <= 2; xoff++ {
   256  				m := gradYellow(127).(*image.RGBA)
   257  				dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA)
   258  				src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA)
   259  				b := dst.Bounds()
   260  				// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
   261  				golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
   262  				if !b.Eq(golden.Bounds()) {
   263  					t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds())
   264  					continue
   265  				}
   266  				// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
   267  				DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
   268  				// Check that the resultant dst image matches the golden output.
   269  				for y := b.Min.Y; y < b.Max.Y; y++ {
   270  					for x := b.Min.X; x < b.Max.X; x++ {
   271  						if !eq(dst.At(x, y), golden.At(x, y)) {
   272  							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))
   273  							continue loop
   274  						}
   275  					}
   276  				}
   277  			}
   278  		}
   279  	}
   280  }
   281  
   282  // TestNonZeroSrcPt checks drawing with a non-zero src point parameter.
   283  func TestNonZeroSrcPt(t *testing.T) {
   284  	a := image.NewRGBA(image.Rect(0, 0, 1, 1))
   285  	b := image.NewRGBA(image.Rect(0, 0, 2, 2))
   286  	b.Set(0, 0, color.RGBA{0, 0, 0, 5})
   287  	b.Set(1, 0, color.RGBA{0, 0, 5, 5})
   288  	b.Set(0, 1, color.RGBA{0, 5, 0, 5})
   289  	b.Set(1, 1, color.RGBA{5, 0, 0, 5})
   290  	Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over)
   291  	if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) {
   292  		t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0))
   293  	}
   294  }
   295  
   296  func TestFill(t *testing.T) {
   297  	rr := []image.Rectangle{
   298  		image.Rect(0, 0, 0, 0),
   299  		image.Rect(0, 0, 40, 30),
   300  		image.Rect(10, 0, 40, 30),
   301  		image.Rect(0, 20, 40, 30),
   302  		image.Rect(10, 20, 40, 30),
   303  		image.Rect(10, 20, 15, 25),
   304  		image.Rect(10, 0, 35, 30),
   305  		image.Rect(0, 15, 40, 16),
   306  		image.Rect(24, 24, 25, 25),
   307  		image.Rect(23, 23, 26, 26),
   308  		image.Rect(22, 22, 27, 27),
   309  		image.Rect(21, 21, 28, 28),
   310  		image.Rect(20, 20, 29, 29),
   311  	}
   312  	for _, r := range rr {
   313  		m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA)
   314  		b := m.Bounds()
   315  		c := color.RGBA{11, 0, 0, 255}
   316  		src := &image.Uniform{C: c}
   317  		check := func(desc string) {
   318  			for y := b.Min.Y; y < b.Max.Y; y++ {
   319  				for x := b.Min.X; x < b.Max.X; x++ {
   320  					if !eq(c, m.At(x, y)) {
   321  						t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y))
   322  						return
   323  					}
   324  				}
   325  			}
   326  		}
   327  		// Draw 1 pixel at a time.
   328  		for y := b.Min.Y; y < b.Max.Y; y++ {
   329  			for x := b.Min.X; x < b.Max.X; x++ {
   330  				DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src)
   331  			}
   332  		}
   333  		check("pixel")
   334  		// Draw 1 row at a time.
   335  		c = color.RGBA{0, 22, 0, 255}
   336  		src = &image.Uniform{C: c}
   337  		for y := b.Min.Y; y < b.Max.Y; y++ {
   338  			DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src)
   339  		}
   340  		check("row")
   341  		// Draw 1 column at a time.
   342  		c = color.RGBA{0, 0, 33, 255}
   343  		src = &image.Uniform{C: c}
   344  		for x := b.Min.X; x < b.Max.X; x++ {
   345  			DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src)
   346  		}
   347  		check("column")
   348  		// Draw the whole image at once.
   349  		c = color.RGBA{44, 55, 66, 77}
   350  		src = &image.Uniform{C: c}
   351  		DrawMask(m, b, src, image.ZP, nil, image.ZP, Src)
   352  		check("whole")
   353  	}
   354  }