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
5package draw
6
7import (
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.
20type slowestRGBA struct {
21	Pix    []uint8
22	Stride int
23	Rect   image.Rectangle
24}
25
26func (p *slowestRGBA) ColorModel() color.Model { return color.RGBAModel }
27
28func (p *slowestRGBA) Bounds() image.Rectangle { return p.Rect }
29
30func (p *slowestRGBA) At(x, y int) color.Color {
31	return p.RGBA64At(x, y)
32}
33
34func (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
52func (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
65func (p *slowestRGBA) PixOffset(x, y int) int {
66	return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
67}
68
69func 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
86func 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.
97type slowerRGBA struct {
98	Pix    []uint8
99	Stride int
100	Rect   image.Rectangle
101}
102
103func (p *slowerRGBA) ColorModel() color.Model { return color.RGBAModel }
104
105func (p *slowerRGBA) Bounds() image.Rectangle { return p.Rect }
106
107func (p *slowerRGBA) At(x, y int) color.Color {
108	return p.RGBA64At(x, y)
109}
110
111func (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
129func (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
142func (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
154func (p *slowerRGBA) PixOffset(x, y int) int {
155	return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
156}
157
158func 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
175func init() {
176	var p any = (*slowerRGBA)(nil)
177	if _, ok := p.(RGBA64Image); !ok {
178		panic("slowerRGBA should be an RGBA64Image")
179	}
180}
181
182func 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
188func fillBlue(alpha int) image.Image {
189	return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
190}
191
192func fillAlpha(alpha int) image.Image {
193	return image.NewUniform(color.Alpha{uint8(alpha)})
194}
195
196func 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
206func 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
216func 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
226func 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
244func 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
254func 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
264func 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
274func 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
284type drawTest struct {
285	desc     string
286	src      image.Image
287	mask     image.Image
288	op       Op
289	expected color.Color
290}
291
292var 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
411func 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
459func 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
520func 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.
552func 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
565func 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
625func 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.
692func 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.
715type 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.
721func 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
768func 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