Source file 
src/image/draw/draw_test.go
     1  
     2  
     3  
     4  
     5  package draw
     6  
     7  import (
     8  	"image"
     9  	"image/color"
    10  	"image/png"
    11  	"os"
    12  	"testing"
    13  	"testing/quick"
    14  )
    15  
    16  
    17  
    18  
    19  
    20  type slowestRGBA struct {
    21  	Pix    []uint8
    22  	Stride int
    23  	Rect   image.Rectangle
    24  }
    25  
    26  func (p *slowestRGBA) ColorModel() color.Model { return color.RGBAModel }
    27  
    28  func (p *slowestRGBA) Bounds() image.Rectangle { return p.Rect }
    29  
    30  func (p *slowestRGBA) At(x, y int) color.Color {
    31  	return p.RGBA64At(x, y)
    32  }
    33  
    34  func (p *slowestRGBA) RGBA64At(x, y int) color.RGBA64 {
    35  	if !(image.Point{x, y}.In(p.Rect)) {
    36  		return color.RGBA64{}
    37  	}
    38  	i := p.PixOffset(x, y)
    39  	s := p.Pix[i : i+4 : i+4] 
    40  	r := uint16(s[0])
    41  	g := uint16(s[1])
    42  	b := uint16(s[2])
    43  	a := uint16(s[3])
    44  	return color.RGBA64{
    45  		(r << 8) | r,
    46  		(g << 8) | g,
    47  		(b << 8) | b,
    48  		(a << 8) | a,
    49  	}
    50  }
    51  
    52  func (p *slowestRGBA) Set(x, y int, c color.Color) {
    53  	if !(image.Point{x, y}.In(p.Rect)) {
    54  		return
    55  	}
    56  	i := p.PixOffset(x, y)
    57  	c1 := color.RGBAModel.Convert(c).(color.RGBA)
    58  	s := p.Pix[i : i+4 : i+4] 
    59  	s[0] = c1.R
    60  	s[1] = c1.G
    61  	s[2] = c1.B
    62  	s[3] = c1.A
    63  }
    64  
    65  func (p *slowestRGBA) PixOffset(x, y int) int {
    66  	return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
    67  }
    68  
    69  func convertToSlowestRGBA(m image.Image) *slowestRGBA {
    70  	if rgba, ok := m.(*image.RGBA); ok {
    71  		return &slowestRGBA{
    72  			Pix:    append([]byte(nil), rgba.Pix...),
    73  			Stride: rgba.Stride,
    74  			Rect:   rgba.Rect,
    75  		}
    76  	}
    77  	rgba := image.NewRGBA(m.Bounds())
    78  	Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
    79  	return &slowestRGBA{
    80  		Pix:    rgba.Pix,
    81  		Stride: rgba.Stride,
    82  		Rect:   rgba.Rect,
    83  	}
    84  }
    85  
    86  func init() {
    87  	var p any = (*slowestRGBA)(nil)
    88  	if _, ok := p.(RGBA64Image); ok {
    89  		panic("slowestRGBA should not be an RGBA64Image")
    90  	}
    91  }
    92  
    93  
    94  
    95  
    96  
    97  type slowerRGBA struct {
    98  	Pix    []uint8
    99  	Stride int
   100  	Rect   image.Rectangle
   101  }
   102  
   103  func (p *slowerRGBA) ColorModel() color.Model { return color.RGBAModel }
   104  
   105  func (p *slowerRGBA) Bounds() image.Rectangle { return p.Rect }
   106  
   107  func (p *slowerRGBA) At(x, y int) color.Color {
   108  	return p.RGBA64At(x, y)
   109  }
   110  
   111  func (p *slowerRGBA) RGBA64At(x, y int) color.RGBA64 {
   112  	if !(image.Point{x, y}.In(p.Rect)) {
   113  		return color.RGBA64{}
   114  	}
   115  	i := p.PixOffset(x, y)
   116  	s := p.Pix[i : i+4 : i+4] 
   117  	r := uint16(s[0])
   118  	g := uint16(s[1])
   119  	b := uint16(s[2])
   120  	a := uint16(s[3])
   121  	return color.RGBA64{
   122  		(r << 8) | r,
   123  		(g << 8) | g,
   124  		(b << 8) | b,
   125  		(a << 8) | a,
   126  	}
   127  }
   128  
   129  func (p *slowerRGBA) Set(x, y int, c color.Color) {
   130  	if !(image.Point{x, y}.In(p.Rect)) {
   131  		return
   132  	}
   133  	i := p.PixOffset(x, y)
   134  	c1 := color.RGBAModel.Convert(c).(color.RGBA)
   135  	s := p.Pix[i : i+4 : i+4] 
   136  	s[0] = c1.R
   137  	s[1] = c1.G
   138  	s[2] = c1.B
   139  	s[3] = c1.A
   140  }
   141  
   142  func (p *slowerRGBA) SetRGBA64(x, y int, c color.RGBA64) {
   143  	if !(image.Point{x, y}.In(p.Rect)) {
   144  		return
   145  	}
   146  	i := p.PixOffset(x, y)
   147  	s := p.Pix[i : i+4 : i+4] 
   148  	s[0] = uint8(c.R >> 8)
   149  	s[1] = uint8(c.G >> 8)
   150  	s[2] = uint8(c.B >> 8)
   151  	s[3] = uint8(c.A >> 8)
   152  }
   153  
   154  func (p *slowerRGBA) PixOffset(x, y int) int {
   155  	return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
   156  }
   157  
   158  func convertToSlowerRGBA(m image.Image) *slowerRGBA {
   159  	if rgba, ok := m.(*image.RGBA); ok {
   160  		return &slowerRGBA{
   161  			Pix:    append([]byte(nil), rgba.Pix...),
   162  			Stride: rgba.Stride,
   163  			Rect:   rgba.Rect,
   164  		}
   165  	}
   166  	rgba := image.NewRGBA(m.Bounds())
   167  	Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
   168  	return &slowerRGBA{
   169  		Pix:    rgba.Pix,
   170  		Stride: rgba.Stride,
   171  		Rect:   rgba.Rect,
   172  	}
   173  }
   174  
   175  func init() {
   176  	var p any = (*slowerRGBA)(nil)
   177  	if _, ok := p.(RGBA64Image); !ok {
   178  		panic("slowerRGBA should be an RGBA64Image")
   179  	}
   180  }
   181  
   182  func eq(c0, c1 color.Color) bool {
   183  	r0, g0, b0, a0 := c0.RGBA()
   184  	r1, g1, b1, a1 := c1.RGBA()
   185  	return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1
   186  }
   187  
   188  func fillBlue(alpha int) image.Image {
   189  	return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
   190  }
   191  
   192  func fillAlpha(alpha int) image.Image {
   193  	return image.NewUniform(color.Alpha{uint8(alpha)})
   194  }
   195  
   196  func vgradGreen(alpha int) image.Image {
   197  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
   198  	for y := 0; y < 16; y++ {
   199  		for x := 0; x < 16; x++ {
   200  			m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)})
   201  		}
   202  	}
   203  	return m
   204  }
   205  
   206  func vgradAlpha(alpha int) image.Image {
   207  	m := image.NewAlpha(image.Rect(0, 0, 16, 16))
   208  	for y := 0; y < 16; y++ {
   209  		for x := 0; x < 16; x++ {
   210  			m.Set(x, y, color.Alpha{uint8(y * alpha / 15)})
   211  		}
   212  	}
   213  	return m
   214  }
   215  
   216  func vgradGreenNRGBA(alpha int) image.Image {
   217  	m := image.NewNRGBA(image.Rect(0, 0, 16, 16))
   218  	for y := 0; y < 16; y++ {
   219  		for x := 0; x < 16; x++ {
   220  			m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)})
   221  		}
   222  	}
   223  	return m
   224  }
   225  
   226  func vgradCr() image.Image {
   227  	m := &image.YCbCr{
   228  		Y:              make([]byte, 16*16),
   229  		Cb:             make([]byte, 16*16),
   230  		Cr:             make([]byte, 16*16),
   231  		YStride:        16,
   232  		CStride:        16,
   233  		SubsampleRatio: image.YCbCrSubsampleRatio444,
   234  		Rect:           image.Rect(0, 0, 16, 16),
   235  	}
   236  	for y := 0; y < 16; y++ {
   237  		for x := 0; x < 16; x++ {
   238  			m.Cr[y*m.CStride+x] = uint8(y * 0x11)
   239  		}
   240  	}
   241  	return m
   242  }
   243  
   244  func vgradGray() image.Image {
   245  	m := image.NewGray(image.Rect(0, 0, 16, 16))
   246  	for y := 0; y < 16; y++ {
   247  		for x := 0; x < 16; x++ {
   248  			m.Set(x, y, color.Gray{uint8(y * 0x11)})
   249  		}
   250  	}
   251  	return m
   252  }
   253  
   254  func vgradMagenta() image.Image {
   255  	m := image.NewCMYK(image.Rect(0, 0, 16, 16))
   256  	for y := 0; y < 16; y++ {
   257  		for x := 0; x < 16; x++ {
   258  			m.Set(x, y, color.CMYK{0, uint8(y * 0x11), 0, 0x3f})
   259  		}
   260  	}
   261  	return m
   262  }
   263  
   264  func hgradRed(alpha int) Image {
   265  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
   266  	for y := 0; y < 16; y++ {
   267  		for x := 0; x < 16; x++ {
   268  			m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)})
   269  		}
   270  	}
   271  	return m
   272  }
   273  
   274  func gradYellow(alpha int) Image {
   275  	m := image.NewRGBA(image.Rect(0, 0, 16, 16))
   276  	for y := 0; y < 16; y++ {
   277  		for x := 0; x < 16; x++ {
   278  			m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)})
   279  		}
   280  	}
   281  	return m
   282  }
   283  
   284  type drawTest struct {
   285  	desc     string
   286  	src      image.Image
   287  	mask     image.Image
   288  	op       Op
   289  	expected color.Color
   290  }
   291  
   292  var drawTests = []drawTest{
   293  	
   294  	{"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}},
   295  	{"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}},
   296  	
   297  	
   298  	
   299  	
   300  	{"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}},
   301  	{"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}},
   302  	{"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}},
   303  	{"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}},
   304  	{"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}},
   305  	{"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}},
   306  	
   307  	
   308  	
   309  	
   310  	{"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}},
   311  	{"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}},
   312  	{"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}},
   313  	{"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}},
   314  	{"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}},
   315  	{"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}},
   316  	
   317  	
   318  	
   319  	
   320  	
   321  	{"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}},
   322  	{"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}},
   323  	{"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}},
   324  	{"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}},
   325  	{"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}},
   326  	{"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}},
   327  	
   328  	
   329  	
   330  	
   331  	{"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}},
   332  	{"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}},
   333  	{"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}},
   334  	{"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}},
   335  	{"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}},
   336  	{"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}},
   337  	
   338  	
   339  	
   340  	
   341  	{"gray", vgradGray(), fillAlpha(255), Over, color.RGBA{136, 136, 136, 255}},
   342  	{"graySrc", vgradGray(), fillAlpha(255), Src, color.RGBA{136, 136, 136, 255}},
   343  	{"grayAlpha", vgradGray(), fillAlpha(192), Over, color.RGBA{136, 102, 102, 255}},
   344  	{"grayAlphaSrc", vgradGray(), fillAlpha(192), Src, color.RGBA{102, 102, 102, 192}},
   345  	{"grayNil", vgradGray(), nil, Over, color.RGBA{136, 136, 136, 255}},
   346  	{"grayNilSrc", vgradGray(), nil, Src, color.RGBA{136, 136, 136, 255}},
   347  	
   348  	{"graySlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
   349  		Over, color.RGBA{136, 136, 136, 255}},
   350  	{"graySrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
   351  		Src, color.RGBA{136, 136, 136, 255}},
   352  	{"grayAlphaSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
   353  		Over, color.RGBA{136, 102, 102, 255}},
   354  	{"grayAlphaSrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
   355  		Src, color.RGBA{102, 102, 102, 192}},
   356  	{"grayNilSlower", convertToSlowerRGBA(vgradGray()), nil,
   357  		Over, color.RGBA{136, 136, 136, 255}},
   358  	{"grayNilSrcSlower", convertToSlowerRGBA(vgradGray()), nil,
   359  		Src, color.RGBA{136, 136, 136, 255}},
   360  	
   361  	{"graySlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
   362  		Over, color.RGBA{136, 136, 136, 255}},
   363  	{"graySrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
   364  		Src, color.RGBA{136, 136, 136, 255}},
   365  	{"grayAlphaSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
   366  		Over, color.RGBA{136, 102, 102, 255}},
   367  	{"grayAlphaSrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
   368  		Src, color.RGBA{102, 102, 102, 192}},
   369  	{"grayNilSlowest", convertToSlowestRGBA(vgradGray()), nil,
   370  		Over, color.RGBA{136, 136, 136, 255}},
   371  	{"grayNilSrcSlowest", convertToSlowestRGBA(vgradGray()), nil,
   372  		Src, color.RGBA{136, 136, 136, 255}},
   373  	
   374  	
   375  	
   376  	
   377  	{"cmyk", vgradMagenta(), fillAlpha(255), Over, color.RGBA{192, 89, 192, 255}},
   378  	{"cmykSrc", vgradMagenta(), fillAlpha(255), Src, color.RGBA{192, 89, 192, 255}},
   379  	{"cmykAlpha", vgradMagenta(), fillAlpha(192), Over, color.RGBA{178, 67, 145, 255}},
   380  	{"cmykAlphaSrc", vgradMagenta(), fillAlpha(192), Src, color.RGBA{145, 67, 145, 192}},
   381  	{"cmykNil", vgradMagenta(), nil, Over, color.RGBA{192, 89, 192, 255}},
   382  	{"cmykNilSrc", vgradMagenta(), nil, Src, color.RGBA{192, 89, 192, 255}},
   383  	
   384  	
   385  	
   386  	
   387  	
   388  	{"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
   389  	{"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
   390  	
   391  	{"genericSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
   392  		Over, color.RGBA{81, 0, 102, 255}},
   393  	{"genericSrcSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
   394  		Src, color.RGBA{0, 0, 102, 102}},
   395  	
   396  	{"genericSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
   397  		Over, color.RGBA{81, 0, 102, 255}},
   398  	{"genericSrcSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
   399  		Src, color.RGBA{0, 0, 102, 102}},
   400  	
   401  	
   402  	
   403  	
   404  	
   405  	
   406  	
   407  	{"rgbaVariableMaskOver", vgradGreen(90), vgradAlpha(192), Over, color.RGBA{117, 19, 0, 255}},
   408  	{"grayVariableMaskOver", vgradGray(), vgradAlpha(192), Over, color.RGBA{136, 54, 54, 255}},
   409  }
   410  
   411  func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
   412  	
   413  	
   414  	b := dst.Bounds()
   415  	sb := src.Bounds()
   416  	mb := image.Rect(-1e9, -1e9, 1e9, 1e9)
   417  	if mask != nil {
   418  		mb = mask.Bounds()
   419  	}
   420  	golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y))
   421  	for y := r.Min.Y; y < r.Max.Y; y++ {
   422  		sy := y + sp.Y - r.Min.Y
   423  		my := y + mp.Y - r.Min.Y
   424  		for x := r.Min.X; x < r.Max.X; x++ {
   425  			if !(image.Pt(x, y).In(b)) {
   426  				continue
   427  			}
   428  			sx := x + sp.X - r.Min.X
   429  			if !(image.Pt(sx, sy).In(sb)) {
   430  				continue
   431  			}
   432  			mx := x + mp.X - r.Min.X
   433  			if !(image.Pt(mx, my).In(mb)) {
   434  				continue
   435  			}
   436  
   437  			const M = 1<<16 - 1
   438  			var dr, dg, db, da uint32
   439  			if op == Over {
   440  				dr, dg, db, da = dst.At(x, y).RGBA()
   441  			}
   442  			sr, sg, sb, sa := src.At(sx, sy).RGBA()
   443  			ma := uint32(M)
   444  			if mask != nil {
   445  				_, _, _, ma = mask.At(mx, my).RGBA()
   446  			}
   447  			a := M - (sa * ma / M)
   448  			golden.Set(x, y, color.RGBA64{
   449  				uint16((dr*a + sr*ma) / M),
   450  				uint16((dg*a + sg*ma) / M),
   451  				uint16((db*a + sb*ma) / M),
   452  				uint16((da*a + sa*ma) / M),
   453  			})
   454  		}
   455  	}
   456  	return golden.SubImage(b)
   457  }
   458  
   459  func TestDraw(t *testing.T) {
   460  	rr := []image.Rectangle{
   461  		image.Rect(0, 0, 0, 0),
   462  		image.Rect(0, 0, 16, 16),
   463  		image.Rect(3, 5, 12, 10),
   464  		image.Rect(0, 0, 9, 9),
   465  		image.Rect(8, 8, 16, 16),
   466  		image.Rect(8, 0, 9, 16),
   467  		image.Rect(0, 8, 16, 9),
   468  		image.Rect(8, 8, 9, 9),
   469  		image.Rect(8, 8, 8, 8),
   470  	}
   471  	for _, r := range rr {
   472  	loop:
   473  		for _, test := range drawTests {
   474  			for i := 0; i < 3; i++ {
   475  				dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
   476  				
   477  				
   478  				
   479  				switch i {
   480  				case 1:
   481  					dst = convertToSlowerRGBA(dst)
   482  				case 2:
   483  					dst = convertToSlowestRGBA(dst)
   484  				}
   485  
   486  				
   487  				golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.Point{}, test.mask, image.Point{}, test.op)
   488  				b := dst.Bounds()
   489  				if !b.Eq(golden.Bounds()) {
   490  					t.Errorf("draw %v %s on %T: bounds %v versus %v",
   491  						r, test.desc, dst, dst.Bounds(), golden.Bounds())
   492  					continue
   493  				}
   494  				
   495  				DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.Point{}, test.mask, image.Point{}, test.op)
   496  				if image.Pt(8, 8).In(r) {
   497  					
   498  					
   499  					if !eq(dst.At(8, 8), test.expected) {
   500  						t.Errorf("draw %v %s on %T: at (8, 8) %v versus %v",
   501  							r, test.desc, dst, dst.At(8, 8), test.expected)
   502  						continue
   503  					}
   504  				}
   505  				
   506  				for y := b.Min.Y; y < b.Max.Y; y++ {
   507  					for x := b.Min.X; x < b.Max.X; x++ {
   508  						if !eq(dst.At(x, y), golden.At(x, y)) {
   509  							t.Errorf("draw %v %s on %T: at (%d, %d), %v versus golden %v",
   510  								r, test.desc, dst, x, y, dst.At(x, y), golden.At(x, y))
   511  							continue loop
   512  						}
   513  					}
   514  				}
   515  			}
   516  		}
   517  	}
   518  }
   519  
   520  func TestDrawOverlap(t *testing.T) {
   521  	for _, op := range []Op{Over, Src} {
   522  		for yoff := -2; yoff <= 2; yoff++ {
   523  		loop:
   524  			for xoff := -2; xoff <= 2; xoff++ {
   525  				m := gradYellow(127).(*image.RGBA)
   526  				dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA)
   527  				src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA)
   528  				b := dst.Bounds()
   529  				
   530  				golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.Point{}, op)
   531  				if !b.Eq(golden.Bounds()) {
   532  					t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds())
   533  					continue
   534  				}
   535  				
   536  				DrawMask(dst, b, src, src.Bounds().Min, nil, image.Point{}, op)
   537  				
   538  				for y := b.Min.Y; y < b.Max.Y; y++ {
   539  					for x := b.Min.X; x < b.Max.X; x++ {
   540  						if !eq(dst.At(x, y), golden.At(x, y)) {
   541  							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))
   542  							continue loop
   543  						}
   544  					}
   545  				}
   546  			}
   547  		}
   548  	}
   549  }
   550  
   551  
   552  func TestNonZeroSrcPt(t *testing.T) {
   553  	a := image.NewRGBA(image.Rect(0, 0, 1, 1))
   554  	b := image.NewRGBA(image.Rect(0, 0, 2, 2))
   555  	b.Set(0, 0, color.RGBA{0, 0, 0, 5})
   556  	b.Set(1, 0, color.RGBA{0, 0, 5, 5})
   557  	b.Set(0, 1, color.RGBA{0, 5, 0, 5})
   558  	b.Set(1, 1, color.RGBA{5, 0, 0, 5})
   559  	Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over)
   560  	if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) {
   561  		t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0))
   562  	}
   563  }
   564  
   565  func TestFill(t *testing.T) {
   566  	rr := []image.Rectangle{
   567  		image.Rect(0, 0, 0, 0),
   568  		image.Rect(0, 0, 40, 30),
   569  		image.Rect(10, 0, 40, 30),
   570  		image.Rect(0, 20, 40, 30),
   571  		image.Rect(10, 20, 40, 30),
   572  		image.Rect(10, 20, 15, 25),
   573  		image.Rect(10, 0, 35, 30),
   574  		image.Rect(0, 15, 40, 16),
   575  		image.Rect(24, 24, 25, 25),
   576  		image.Rect(23, 23, 26, 26),
   577  		image.Rect(22, 22, 27, 27),
   578  		image.Rect(21, 21, 28, 28),
   579  		image.Rect(20, 20, 29, 29),
   580  	}
   581  	for _, r := range rr {
   582  		m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA)
   583  		b := m.Bounds()
   584  		c := color.RGBA{11, 0, 0, 255}
   585  		src := &image.Uniform{C: c}
   586  		check := func(desc string) {
   587  			for y := b.Min.Y; y < b.Max.Y; y++ {
   588  				for x := b.Min.X; x < b.Max.X; x++ {
   589  					if !eq(c, m.At(x, y)) {
   590  						t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y))
   591  						return
   592  					}
   593  				}
   594  			}
   595  		}
   596  		
   597  		for y := b.Min.Y; y < b.Max.Y; y++ {
   598  			for x := b.Min.X; x < b.Max.X; x++ {
   599  				DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.Point{}, nil, image.Point{}, Src)
   600  			}
   601  		}
   602  		check("pixel")
   603  		
   604  		c = color.RGBA{0, 22, 0, 255}
   605  		src = &image.Uniform{C: c}
   606  		for y := b.Min.Y; y < b.Max.Y; y++ {
   607  			DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.Point{}, nil, image.Point{}, Src)
   608  		}
   609  		check("row")
   610  		
   611  		c = color.RGBA{0, 0, 33, 255}
   612  		src = &image.Uniform{C: c}
   613  		for x := b.Min.X; x < b.Max.X; x++ {
   614  			DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.Point{}, nil, image.Point{}, Src)
   615  		}
   616  		check("column")
   617  		
   618  		c = color.RGBA{44, 55, 66, 77}
   619  		src = &image.Uniform{C: c}
   620  		DrawMask(m, b, src, image.Point{}, nil, image.Point{}, Src)
   621  		check("whole")
   622  	}
   623  }
   624  
   625  func TestDrawSrcNonpremultiplied(t *testing.T) {
   626  	var (
   627  		opaqueGray       = color.NRGBA{0x99, 0x99, 0x99, 0xff}
   628  		transparentBlue  = color.NRGBA{0x00, 0x00, 0xff, 0x00}
   629  		transparentGreen = color.NRGBA{0x00, 0xff, 0x00, 0x00}
   630  		transparentRed   = color.NRGBA{0xff, 0x00, 0x00, 0x00}
   631  
   632  		opaqueGray64        = color.NRGBA64{0x9999, 0x9999, 0x9999, 0xffff}
   633  		transparentPurple64 = color.NRGBA64{0xfedc, 0x0000, 0x7654, 0x0000}
   634  	)
   635  
   636  	
   637  	
   638  	
   639  	
   640  	
   641  	
   642  	
   643  	
   644  	
   645  	
   646  	
   647  	
   648  	
   649  	
   650  	
   651  	
   652  	
   653  	
   654  	{
   655  		dst := image.NewNRGBA(image.Rect(0, 10, 3, 11))
   656  		dst.SetNRGBA(0, 10, opaqueGray)
   657  		src := image.NewNRGBA(image.Rect(1, 20, 4, 21))
   658  		src.SetNRGBA(1, 20, transparentBlue)
   659  		src.SetNRGBA(2, 20, transparentGreen)
   660  		src.SetNRGBA(3, 20, transparentRed)
   661  
   662  		dr := image.Rect(1, 10, 3, 11)
   663  		Draw(dst, dr, src, image.Point{1, 20}, Src)
   664  
   665  		if got, want := dst.At(0, 10), opaqueGray; got != want {
   666  			t.Errorf("At(0, 10):\ngot  %#v\nwant %#v", got, want)
   667  		}
   668  		if got, want := dst.At(1, 10), transparentBlue; got != want {
   669  			t.Errorf("At(1, 10):\ngot  %#v\nwant %#v", got, want)
   670  		}
   671  		if got, want := dst.At(2, 10), transparentGreen; got != want {
   672  			t.Errorf("At(2, 10):\ngot  %#v\nwant %#v", got, want)
   673  		}
   674  	}
   675  
   676  	
   677  	{
   678  		dst := image.NewNRGBA64(image.Rect(0, 0, 1, 1))
   679  		dst.SetNRGBA64(0, 0, opaqueGray64)
   680  		src := image.NewNRGBA64(image.Rect(0, 0, 1, 1))
   681  		src.SetNRGBA64(0, 0, transparentPurple64)
   682  		Draw(dst, dst.Bounds(), src, image.Point{0, 0}, Src)
   683  		if got, want := dst.At(0, 0), transparentPurple64; got != want {
   684  			t.Errorf("At(0, 0):\ngot  %#v\nwant %#v", got, want)
   685  		}
   686  	}
   687  }
   688  
   689  
   690  
   691  
   692  func TestFloydSteinbergCheckerboard(t *testing.T) {
   693  	b := image.Rect(0, 0, 640, 480)
   694  	
   695  	src := &image.Uniform{color.Gray16{0x7fff}}
   696  	dst := image.NewPaletted(b, color.Palette{color.Black, color.White})
   697  	FloydSteinberg.Draw(dst, b, src, image.Point{})
   698  	nErr := 0
   699  	for y := b.Min.Y; y < b.Max.Y; y++ {
   700  		for x := b.Min.X; x < b.Max.X; x++ {
   701  			got := dst.Pix[dst.PixOffset(x, y)]
   702  			want := uint8(x+y) % 2
   703  			if got != want {
   704  				t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want)
   705  				if nErr++; nErr == 10 {
   706  					t.Fatal("there may be more errors")
   707  				}
   708  			}
   709  		}
   710  	}
   711  }
   712  
   713  
   714  
   715  type embeddedPaletted struct {
   716  	*image.Paletted
   717  }
   718  
   719  
   720  
   721  func TestPaletted(t *testing.T) {
   722  	f, err := os.Open("../testdata/video-001.png")
   723  	if err != nil {
   724  		t.Fatalf("open: %v", err)
   725  	}
   726  	defer f.Close()
   727  	video001, err := png.Decode(f)
   728  	if err != nil {
   729  		t.Fatalf("decode: %v", err)
   730  	}
   731  	b := video001.Bounds()
   732  
   733  	cgaPalette := color.Palette{
   734  		color.RGBA{0x00, 0x00, 0x00, 0xff},
   735  		color.RGBA{0x55, 0xff, 0xff, 0xff},
   736  		color.RGBA{0xff, 0x55, 0xff, 0xff},
   737  		color.RGBA{0xff, 0xff, 0xff, 0xff},
   738  	}
   739  	drawers := map[string]Drawer{
   740  		"src":             Src,
   741  		"floyd-steinberg": FloydSteinberg,
   742  	}
   743  	sources := map[string]image.Image{
   744  		"uniform":  &image.Uniform{color.RGBA{0xff, 0x7f, 0xff, 0xff}},
   745  		"video001": video001,
   746  	}
   747  
   748  	for dName, d := range drawers {
   749  	loop:
   750  		for sName, src := range sources {
   751  			dst0 := image.NewPaletted(b, cgaPalette)
   752  			dst1 := image.NewPaletted(b, cgaPalette)
   753  			d.Draw(dst0, b, src, image.Point{})
   754  			d.Draw(embeddedPaletted{dst1}, b, src, image.Point{})
   755  			for y := b.Min.Y; y < b.Max.Y; y++ {
   756  				for x := b.Min.X; x < b.Max.X; x++ {
   757  					if !eq(dst0.At(x, y), dst1.At(x, y)) {
   758  						t.Errorf("%s / %s: at (%d, %d), %v versus %v",
   759  							dName, sName, x, y, dst0.At(x, y), dst1.At(x, y))
   760  						continue loop
   761  					}
   762  				}
   763  			}
   764  		}
   765  	}
   766  }
   767  
   768  func TestSqDiff(t *testing.T) {
   769  	
   770  	
   771  	
   772  
   773  	
   774  	orig := func(x, y int32) uint32 {
   775  		var d uint32
   776  		if x > y {
   777  			d = uint32(x - y)
   778  		} else {
   779  			d = uint32(y - x)
   780  		}
   781  		return (d * d) >> 2
   782  	}
   783  	testCases := []int32{
   784  		0,
   785  		1,
   786  		2,
   787  		0x0fffd,
   788  		0x0fffe,
   789  		0x0ffff,
   790  		0x10000,
   791  		0x10001,
   792  		0x10002,
   793  		0x7ffffffd,
   794  		0x7ffffffe,
   795  		0x7fffffff,
   796  		-0x7ffffffd,
   797  		-0x7ffffffe,
   798  		-0x80000000,
   799  	}
   800  	for _, x := range testCases {
   801  		for _, y := range testCases {
   802  			if got, want := sqDiff(x, y), orig(x, y); got != want {
   803  				t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
   804  			}
   805  		}
   806  	}
   807  	if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
   808  		t.Fatal(err)
   809  	}
   810  }
   811  
View as plain text