Source file src/image/draw/draw_test.go

     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  	"testing/quick"
    14  )
    15  
    16  // slowestRGBA is a draw.Image like image.RGBA but it is a different type and
    17  // therefore does not trigger the draw.go fastest code paths.
    18  //
    19  // Unlike slowerRGBA, it does not implement the draw.RGBA64Image interface.
    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] // Small cap improves performance, see https://golang.org/issue/27857
    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] // Small cap improves performance, see https://golang.org/issue/27857
    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  // slowerRGBA is a draw.Image like image.RGBA but it is a different type and
    94  // therefore does not trigger the draw.go fastest code paths.
    95  //
    96  // Unlike slowestRGBA, it still implements the draw.RGBA64Image interface.
    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] // Small cap improves performance, see https://golang.org/issue/27857
   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] // Small cap improves performance, see https://golang.org/issue/27857
   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] // Small cap improves performance, see https://golang.org/issue/27857
   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  	// Uniform mask (0% opaque).
   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  	// Uniform mask (100%, 75%, nil) and uniform source.
   297  	// At (x, y) == (8, 8):
   298  	// The destination pixel is {136, 0, 0, 255}.
   299  	// The source pixel is {0, 0, 90, 90}.
   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  	// Uniform mask (100%, 75%, nil) and variable source.
   307  	// At (x, y) == (8, 8):
   308  	// The destination pixel is {136, 0, 0, 255}.
   309  	// The source pixel is {0, 48, 0, 90}.
   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  	// Uniform mask (100%, 75%, nil) and variable NRGBA source.
   317  	// At (x, y) == (8, 8):
   318  	// The destination pixel is {136, 0, 0, 255}.
   319  	// The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space.
   320  	// The result pixel is different than in the "copy*" test cases because of rounding errors.
   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  	// Uniform mask (100%, 75%, nil) and variable YCbCr source.
   328  	// At (x, y) == (8, 8):
   329  	// The destination pixel is {136, 0, 0, 255}.
   330  	// The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space.
   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  	// Uniform mask (100%, 75%, nil) and variable Gray source.
   338  	// At (x, y) == (8, 8):
   339  	// The destination pixel is {136, 0, 0, 255}.
   340  	// The source pixel is {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space.
   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  	// Same again, but with a slowerRGBA source.
   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  	// Same again, but with a slowestRGBA source.
   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  	// Uniform mask (100%, 75%, nil) and variable CMYK source.
   374  	// At (x, y) == (8, 8):
   375  	// The destination pixel is {136, 0, 0, 255}.
   376  	// The source pixel is {0, 136, 0, 63} in CMYK-space, which is {192, 89, 192} in RGB-space.
   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  	// Variable mask and uniform source.
   384  	// At (x, y) == (8, 8):
   385  	// The destination pixel is {136, 0, 0, 255}.
   386  	// The source pixel is {0, 0, 255, 255}.
   387  	// The mask pixel's alpha is 102, or 40%.
   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  	// Same again, but with a slowerRGBA mask.
   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  	// Same again, but with a slowestRGBA mask.
   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  	// Variable mask and variable source.
   401  	// At (x, y) == (8, 8):
   402  	// The destination pixel is {136, 0, 0, 255}.
   403  	// The source pixel is:
   404  	//   - {0, 48, 0, 90}.
   405  	//   - {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space.
   406  	// The mask pixel's alpha is 102, or 40%.
   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  	// Since golden is a newly allocated image, we don't have to check if the
   413  	// input source and mask images and the output golden image overlap.
   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  				// For i != 0, substitute a different-typed dst that will take
   477  				// us off the fastest code paths. We should still get the same
   478  				// result, in terms of final pixel RGBA values.
   479  				switch i {
   480  				case 1:
   481  					dst = convertToSlowerRGBA(dst)
   482  				case 2:
   483  					dst = convertToSlowestRGBA(dst)
   484  				}
   485  
   486  				// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
   487  				golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, 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  				// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
   495  				DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
   496  				if image.Pt(8, 8).In(r) {
   497  					// Check that the resultant pixel at (8, 8) matches what we expect
   498  					// (the expected value can be verified by hand).
   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  				// Check that the resultant dst image matches the golden output.
   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  				// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
   530  				golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, 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  				// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
   536  				DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
   537  				// Check that the resultant dst image matches the golden output.
   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  // TestNonZeroSrcPt checks drawing with a non-zero src point parameter.
   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  		// Draw 1 pixel at a time.
   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.ZP, nil, image.ZP, Src)
   600  			}
   601  		}
   602  		check("pixel")
   603  		// Draw 1 row at a time.
   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.ZP, nil, image.ZP, Src)
   608  		}
   609  		check("row")
   610  		// Draw 1 column at a time.
   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.ZP, nil, image.ZP, Src)
   615  		}
   616  		check("column")
   617  		// Draw the whole image at once.
   618  		c = color.RGBA{44, 55, 66, 77}
   619  		src = &image.Uniform{C: c}
   620  		DrawMask(m, b, src, image.ZP, nil, image.ZP, Src)
   621  		check("whole")
   622  	}
   623  }
   624  
   625  // TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg
   626  // error diffusion of a uniform 50% gray source image with a black-and-white
   627  // palette is a checkerboard pattern.
   628  func TestFloydSteinbergCheckerboard(t *testing.T) {
   629  	b := image.Rect(0, 0, 640, 480)
   630  	// We can't represent 50% exactly, but 0x7fff / 0xffff is close enough.
   631  	src := &image.Uniform{color.Gray16{0x7fff}}
   632  	dst := image.NewPaletted(b, color.Palette{color.Black, color.White})
   633  	FloydSteinberg.Draw(dst, b, src, image.Point{})
   634  	nErr := 0
   635  	for y := b.Min.Y; y < b.Max.Y; y++ {
   636  		for x := b.Min.X; x < b.Max.X; x++ {
   637  			got := dst.Pix[dst.PixOffset(x, y)]
   638  			want := uint8(x+y) % 2
   639  			if got != want {
   640  				t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want)
   641  				if nErr++; nErr == 10 {
   642  					t.Fatal("there may be more errors")
   643  				}
   644  			}
   645  		}
   646  	}
   647  }
   648  
   649  // embeddedPaletted is an Image that behaves like an *image.Paletted but whose
   650  // type is not *image.Paletted.
   651  type embeddedPaletted struct {
   652  	*image.Paletted
   653  }
   654  
   655  // TestPaletted tests that the drawPaletted function behaves the same
   656  // regardless of whether dst is an *image.Paletted.
   657  func TestPaletted(t *testing.T) {
   658  	f, err := os.Open("../testdata/video-001.png")
   659  	if err != nil {
   660  		t.Fatalf("open: %v", err)
   661  	}
   662  	defer f.Close()
   663  	video001, err := png.Decode(f)
   664  	if err != nil {
   665  		t.Fatalf("decode: %v", err)
   666  	}
   667  	b := video001.Bounds()
   668  
   669  	cgaPalette := color.Palette{
   670  		color.RGBA{0x00, 0x00, 0x00, 0xff},
   671  		color.RGBA{0x55, 0xff, 0xff, 0xff},
   672  		color.RGBA{0xff, 0x55, 0xff, 0xff},
   673  		color.RGBA{0xff, 0xff, 0xff, 0xff},
   674  	}
   675  	drawers := map[string]Drawer{
   676  		"src":             Src,
   677  		"floyd-steinberg": FloydSteinberg,
   678  	}
   679  	sources := map[string]image.Image{
   680  		"uniform":  &image.Uniform{color.RGBA{0xff, 0x7f, 0xff, 0xff}},
   681  		"video001": video001,
   682  	}
   683  
   684  	for dName, d := range drawers {
   685  	loop:
   686  		for sName, src := range sources {
   687  			dst0 := image.NewPaletted(b, cgaPalette)
   688  			dst1 := image.NewPaletted(b, cgaPalette)
   689  			d.Draw(dst0, b, src, image.Point{})
   690  			d.Draw(embeddedPaletted{dst1}, b, src, image.Point{})
   691  			for y := b.Min.Y; y < b.Max.Y; y++ {
   692  				for x := b.Min.X; x < b.Max.X; x++ {
   693  					if !eq(dst0.At(x, y), dst1.At(x, y)) {
   694  						t.Errorf("%s / %s: at (%d, %d), %v versus %v",
   695  							dName, sName, x, y, dst0.At(x, y), dst1.At(x, y))
   696  						continue loop
   697  					}
   698  				}
   699  			}
   700  		}
   701  	}
   702  }
   703  
   704  func TestSqDiff(t *testing.T) {
   705  	// This test is similar to the one from the image/color package, but
   706  	// sqDiff in this package accepts int32 instead of uint32, so test it
   707  	// for appropriate input.
   708  
   709  	// canonical sqDiff implementation
   710  	orig := func(x, y int32) uint32 {
   711  		var d uint32
   712  		if x > y {
   713  			d = uint32(x - y)
   714  		} else {
   715  			d = uint32(y - x)
   716  		}
   717  		return (d * d) >> 2
   718  	}
   719  	testCases := []int32{
   720  		0,
   721  		1,
   722  		2,
   723  		0x0fffd,
   724  		0x0fffe,
   725  		0x0ffff,
   726  		0x10000,
   727  		0x10001,
   728  		0x10002,
   729  		0x7ffffffd,
   730  		0x7ffffffe,
   731  		0x7fffffff,
   732  		-0x7ffffffd,
   733  		-0x7ffffffe,
   734  		-0x80000000,
   735  	}
   736  	for _, x := range testCases {
   737  		for _, y := range testCases {
   738  			if got, want := sqDiff(x, y), orig(x, y); got != want {
   739  				t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
   740  			}
   741  		}
   742  	}
   743  	if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
   744  		t.Fatal(err)
   745  	}
   746  }
   747  

View as plain text