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.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  				// 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.Point{}, test.mask, image.Point{}, 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.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  				// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
   536  				DrawMask(dst, b, src, src.Bounds().Min, nil, image.Point{}, 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.Point{}, nil, image.Point{}, 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.Point{}, nil, image.Point{}, 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.Point{}, nil, image.Point{}, 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.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  	// dst and src are 1x3 images but the dr rectangle (and hence the overlap)
   637  	// is only 1x2. The Draw call should affect dst's pixels at (1, 10) and (2,
   638  	// 10) but the pixel at (0, 10) should be untouched.
   639  	//
   640  	// The src image is entirely transparent (and the Draw operator is Src) so
   641  	// the two touched pixels should be set to transparent colors.
   642  	//
   643  	// In general, Go's color.Color type (and specifically the Color.RGBA
   644  	// method) works in premultiplied alpha, where there's no difference
   645  	// between "transparent blue" and "transparent red". It's all "just
   646  	// transparent" and canonically "transparent black" (all zeroes).
   647  	//
   648  	// However, since the operator is Src (so the pixels are 'copied', not
   649  	// 'blended') and both dst and src images are *image.NRGBA (N stands for
   650  	// Non-premultiplied alpha which *does* distinguish "transparent blue" and
   651  	// "transparent red"), we prefer that this distinction carries through and
   652  	// dst's touched pixels should be transparent blue and transparent green,
   653  	// not just transparent black.
   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  	// Check image.NRGBA64 (not image.NRGBA) similarly.
   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  // TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg
   690  // error diffusion of a uniform 50% gray source image with a black-and-white
   691  // palette is a checkerboard pattern.
   692  func TestFloydSteinbergCheckerboard(t *testing.T) {
   693  	b := image.Rect(0, 0, 640, 480)
   694  	// We can't represent 50% exactly, but 0x7fff / 0xffff is close enough.
   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  // embeddedPaletted is an Image that behaves like an *image.Paletted but whose
   714  // type is not *image.Paletted.
   715  type embeddedPaletted struct {
   716  	*image.Paletted
   717  }
   718  
   719  // TestPaletted tests that the drawPaletted function behaves the same
   720  // regardless of whether dst is an *image.Paletted.
   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  	// This test is similar to the one from the image/color package, but
   770  	// sqDiff in this package accepts int32 instead of uint32, so test it
   771  	// for appropriate input.
   772  
   773  	// canonical sqDiff implementation
   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