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.
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
21
func fillBlue(alpha int) image.Image {
22
return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
25
func fillAlpha(alpha int) image.Image {
26
return image.NewUniform(color.Alpha{uint8(alpha)})
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)})
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)})
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)})
59
func vgradCr() image.Image {
61
Y: make([]byte, 16*16),
62
Cb: make([]byte, 16*16),
63
Cr: make([]byte, 16*16),
66
SubsampleRatio: image.YCbCrSubsampleRatio444,
67
Rect: image.Rect(0, 0, 16, 16),
69
for y := 0; y < 16; y++ {
70
for x := 0; x < 16; x++ {
71
m.Cr[y*m.CStride+x] = uint8(y * 0x11)
77
func hgradRed(alpha int) Image {
78
m := image.NewRGBA(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.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)})
87
func gradYellow(alpha int) Image {
88
m := image.NewRGBA(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.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)})
97
type drawTest struct {
105
var drawTests = []drawTest{
106
// Uniform mask (0% opaque).
107
{"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}},
108
{"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}},
109
// Uniform mask (100%, 75%, nil) and uniform source.
110
// At (x, y) == (8, 8):
111
// The destination pixel is {136, 0, 0, 255}.
112
// The source pixel is {0, 0, 90, 90}.
113
{"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}},
114
{"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}},
115
{"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}},
116
{"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}},
117
{"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}},
118
{"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}},
119
// Uniform mask (100%, 75%, nil) and variable source.
120
// At (x, y) == (8, 8):
121
// The destination pixel is {136, 0, 0, 255}.
122
// The source pixel is {0, 48, 0, 90}.
123
{"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}},
124
{"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}},
125
{"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}},
126
{"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}},
127
{"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}},
128
{"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}},
129
// Uniform mask (100%, 75%, nil) and variable NRGBA source.
130
// At (x, y) == (8, 8):
131
// The destination pixel is {136, 0, 0, 255}.
132
// The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space.
133
// The result pixel is different than in the "copy*" test cases because of rounding errors.
134
{"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}},
135
{"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}},
136
{"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}},
137
{"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}},
138
{"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}},
139
{"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}},
140
// Uniform mask (100%, 75%, nil) and variable YCbCr source.
141
// At (x, y) == (8, 8):
142
// The destination pixel is {136, 0, 0, 255}.
143
// The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space.
144
{"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}},
145
{"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}},
146
{"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}},
147
{"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}},
148
{"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}},
149
{"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}},
150
// Variable mask and variable source.
151
// At (x, y) == (8, 8):
152
// The destination pixel is {136, 0, 0, 255}.
153
// The source pixel is {0, 0, 255, 255}.
154
// The mask pixel's alpha is 102, or 40%.
155
{"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
156
{"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
159
func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
160
// Since golden is a newly allocated image, we don't have to check if the
161
// input source and mask images and the output golden image overlap.
164
mb := image.Rect(-1e9, -1e9, 1e9, 1e9)
168
golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y))
169
for y := r.Min.Y; y < r.Max.Y; y++ {
170
sy := y + sp.Y - r.Min.Y
171
my := y + mp.Y - r.Min.Y
172
for x := r.Min.X; x < r.Max.X; x++ {
173
if !(image.Pt(x, y).In(b)) {
176
sx := x + sp.X - r.Min.X
177
if !(image.Pt(sx, sy).In(sb)) {
180
mx := x + mp.X - r.Min.X
181
if !(image.Pt(mx, my).In(mb)) {
186
var dr, dg, db, da uint32
188
dr, dg, db, da = dst.At(x, y).RGBA()
190
sr, sg, sb, sa := src.At(sx, sy).RGBA()
193
_, _, _, ma = mask.At(mx, my).RGBA()
195
a := M - (sa * ma / M)
196
golden.Set(x, y, color.RGBA64{
197
uint16((dr*a + sr*ma) / M),
198
uint16((dg*a + sg*ma) / M),
199
uint16((db*a + sb*ma) / M),
200
uint16((da*a + sa*ma) / M),
204
return golden.SubImage(b)
207
func TestDraw(t *testing.T) {
208
rr := []image.Rectangle{
209
image.Rect(0, 0, 0, 0),
210
image.Rect(0, 0, 16, 16),
211
image.Rect(3, 5, 12, 10),
212
image.Rect(0, 0, 9, 9),
213
image.Rect(8, 8, 16, 16),
214
image.Rect(8, 0, 9, 16),
215
image.Rect(0, 8, 16, 9),
216
image.Rect(8, 8, 9, 9),
217
image.Rect(8, 8, 8, 8),
219
for _, r := range rr {
221
for _, test := range drawTests {
222
dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
223
// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
224
golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
226
if !b.Eq(golden.Bounds()) {
227
t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds())
230
// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
231
DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
232
if image.Pt(8, 8).In(r) {
233
// Check that the resultant pixel at (8, 8) matches what we expect
234
// (the expected value can be verified by hand).
235
if !eq(dst.At(8, 8), test.expected) {
236
t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected)
240
// Check that the resultant dst image matches the golden output.
241
for y := b.Min.Y; y < b.Max.Y; y++ {
242
for x := b.Min.X; x < b.Max.X; x++ {
243
if !eq(dst.At(x, y), golden.At(x, y)) {
244
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))
253
func TestDrawOverlap(t *testing.T) {
254
for _, op := range []Op{Over, Src} {
255
for yoff := -2; yoff <= 2; yoff++ {
257
for xoff := -2; xoff <= 2; xoff++ {
258
m := gradYellow(127).(*image.RGBA)
259
dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA)
260
src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA)
262
// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
263
golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
264
if !b.Eq(golden.Bounds()) {
265
t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds())
268
// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
269
DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
270
// Check that the resultant dst image matches the golden output.
271
for y := b.Min.Y; y < b.Max.Y; y++ {
272
for x := b.Min.X; x < b.Max.X; x++ {
273
if !eq(dst.At(x, y), golden.At(x, y)) {
274
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))
284
// TestNonZeroSrcPt checks drawing with a non-zero src point parameter.
285
func TestNonZeroSrcPt(t *testing.T) {
286
a := image.NewRGBA(image.Rect(0, 0, 1, 1))
287
b := image.NewRGBA(image.Rect(0, 0, 2, 2))
288
b.Set(0, 0, color.RGBA{0, 0, 0, 5})
289
b.Set(1, 0, color.RGBA{0, 0, 5, 5})
290
b.Set(0, 1, color.RGBA{0, 5, 0, 5})
291
b.Set(1, 1, color.RGBA{5, 0, 0, 5})
292
Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over)
293
if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) {
294
t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0))
298
func TestFill(t *testing.T) {
299
rr := []image.Rectangle{
300
image.Rect(0, 0, 0, 0),
301
image.Rect(0, 0, 40, 30),
302
image.Rect(10, 0, 40, 30),
303
image.Rect(0, 20, 40, 30),
304
image.Rect(10, 20, 40, 30),
305
image.Rect(10, 20, 15, 25),
306
image.Rect(10, 0, 35, 30),
307
image.Rect(0, 15, 40, 16),
308
image.Rect(24, 24, 25, 25),
309
image.Rect(23, 23, 26, 26),
310
image.Rect(22, 22, 27, 27),
311
image.Rect(21, 21, 28, 28),
312
image.Rect(20, 20, 29, 29),
314
for _, r := range rr {
315
m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA)
317
c := color.RGBA{11, 0, 0, 255}
318
src := &image.Uniform{C: c}
319
check := func(desc string) {
320
for y := b.Min.Y; y < b.Max.Y; y++ {
321
for x := b.Min.X; x < b.Max.X; x++ {
322
if !eq(c, m.At(x, y)) {
323
t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y))
329
// Draw 1 pixel at a time.
330
for y := b.Min.Y; y < b.Max.Y; y++ {
331
for x := b.Min.X; x < b.Max.X; x++ {
332
DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src)
336
// Draw 1 row at a time.
337
c = color.RGBA{0, 22, 0, 255}
338
src = &image.Uniform{C: c}
339
for y := b.Min.Y; y < b.Max.Y; y++ {
340
DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src)
343
// Draw 1 column at a time.
344
c = color.RGBA{0, 0, 33, 255}
345
src = &image.Uniform{C: c}
346
for x := b.Min.X; x < b.Max.X; x++ {
347
DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src)
350
// Draw the whole image at once.
351
c = color.RGBA{44, 55, 66, 77}
352
src = &image.Uniform{C: c}
353
DrawMask(m, b, src, image.ZP, nil, image.ZP, Src)
358
// TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg
359
// error diffusion of a uniform 50% gray source image with a black-and-white
360
// palette is a checkerboard pattern.
361
func TestFloydSteinbergCheckerboard(t *testing.T) {
362
b := image.Rect(0, 0, 640, 480)
363
// We can't represent 50% exactly, but 0x7fff / 0xffff is close enough.
364
src := &image.Uniform{color.Gray16{0x7fff}}
365
dst := image.NewPaletted(b, color.Palette{color.Black, color.White})
366
FloydSteinberg.Draw(dst, b, src, image.Point{})
368
for y := b.Min.Y; y < b.Max.Y; y++ {
369
for x := b.Min.X; x < b.Max.X; x++ {
370
got := dst.Pix[dst.PixOffset(x, y)]
371
want := uint8(x+y) % 2
373
t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want)
374
if nErr++; nErr == 10 {
375
t.Fatal("there may be more errors")
382
// embeddedPaletted is an Image that behaves like an *image.Paletted but whose
383
// type is not *image.Paletted.
384
type embeddedPaletted struct {
388
// TestPaletted tests that the drawPaletted function behaves the same
389
// regardless of whether dst is an *image.Paletted.
390
func TestPaletted(t *testing.T) {
391
f, err := os.Open("../testdata/video-001.png")
393
t.Fatalf("open: %v", err)
396
src, err := png.Decode(f)
398
t.Fatalf("decode: %v", err)
402
cgaPalette := color.Palette{
403
color.RGBA{0x00, 0x00, 0x00, 0xff},
404
color.RGBA{0x55, 0xff, 0xff, 0xff},
405
color.RGBA{0xff, 0x55, 0xff, 0xff},
406
color.RGBA{0xff, 0xff, 0xff, 0xff},
408
drawers := map[string]Drawer{
410
"floyd-steinberg": FloydSteinberg,
414
for dName, d := range drawers {
415
dst0 := image.NewPaletted(b, cgaPalette)
416
dst1 := image.NewPaletted(b, cgaPalette)
417
d.Draw(dst0, b, src, image.Point{})
418
d.Draw(embeddedPaletted{dst1}, b, src, image.Point{})
419
for y := b.Min.Y; y < b.Max.Y; y++ {
420
for x := b.Min.X; x < b.Max.X; x++ {
421
if !eq(dst0.At(x, y), dst1.At(x, y)) {
422
t.Errorf("%s: at (%d, %d), %v versus %v",
423
dName, x, y, dst0.At(x, y), dst1.At(x, y))