Home | History | Annotate | Download | only in draw
      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 }
    470