github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/src/image/draw/draw_test.go (about)

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