1
0
mirror of https://github.com/golang/go synced 2024-10-07 03:21:21 -06:00
go/src/pkg/image/draw/draw_test.go
Nigel Tao a2846e65dc image: spin off a new color package out of the image package.
The spin-off renames some types. The new names are simply better:
image.Color              -> color.Color
image.ColorModel         -> color.Model
image.ColorModelFunc     -> color.ModelFunc
image.PalettedColorModel -> color.Palette
image.RGBAColor          -> color.RGBA
image.RGBAColorModel     -> color.RGBAModel
image.RGBA64Color        -> color.RGBA64
image.RGBA64ColorModel   -> color.RGBA64Model
(similarly for NRGBAColor, GrayColorModel, etc)

The image.ColorImage type stays in the image package, but is renamed:
image.ColorImage -> image.Uniform

The image.Image implementations (image.RGBA, image.RGBA64, image.NRGBA,
image.Alpha, etc) do not change their name, and gain a nice symmetry:
an image.RGBA is an image of color.RGBA, etc.

The image.Black, image.Opaque uniform images remain unchanged (although
their type is renamed from image.ColorImage to image.Uniform). The
corresponding color types (color.Black, color.Opaque, etc) are new.

Nothing in the image/ycbcr is renamed yet. The ycbcr.YCbCrColor and
ycbcr.YCbCrImage types will eventually migrate to color.YCbCr and
image.YCbCr, but that will be a separate CL.

R=r, bsiegert
CC=golang-dev
https://golang.org/cl/5132048
2011-10-04 11:09:03 +11:00

356 lines
12 KiB
Go

// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package draw
import (
"image"
"image/color"
"image/ycbcr"
"testing"
)
func eq(c0, c1 color.Color) bool {
r0, g0, b0, a0 := c0.RGBA()
r1, g1, b1, a1 := c1.RGBA()
return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1
}
func fillBlue(alpha int) image.Image {
return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
}
func fillAlpha(alpha int) image.Image {
return image.NewUniform(color.Alpha{uint8(alpha)})
}
func vgradGreen(alpha int) image.Image {
m := image.NewRGBA(image.Rect(0, 0, 16, 16))
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)})
}
}
return m
}
func vgradAlpha(alpha int) image.Image {
m := image.NewAlpha(image.Rect(0, 0, 16, 16))
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
m.Set(x, y, color.Alpha{uint8(y * alpha / 15)})
}
}
return m
}
func vgradGreenNRGBA(alpha int) image.Image {
m := image.NewNRGBA(image.Rect(0, 0, 16, 16))
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)})
}
}
return m
}
func vgradCr() image.Image {
m := &ycbcr.YCbCr{
Y: make([]byte, 16*16),
Cb: make([]byte, 16*16),
Cr: make([]byte, 16*16),
YStride: 16,
CStride: 16,
SubsampleRatio: ycbcr.SubsampleRatio444,
Rect: image.Rect(0, 0, 16, 16),
}
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
m.Cr[y*m.CStride+x] = uint8(y * 0x11)
}
}
return m
}
func hgradRed(alpha int) Image {
m := image.NewRGBA(image.Rect(0, 0, 16, 16))
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)})
}
}
return m
}
func gradYellow(alpha int) Image {
m := image.NewRGBA(image.Rect(0, 0, 16, 16))
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)})
}
}
return m
}
type drawTest struct {
desc string
src image.Image
mask image.Image
op Op
expected color.Color
}
var drawTests = []drawTest{
// Uniform mask (0% opaque).
{"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}},
{"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}},
// Uniform mask (100%, 75%, nil) and uniform source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
// The source pixel is {0, 0, 90, 90}.
{"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}},
{"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}},
{"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}},
{"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}},
{"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}},
{"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}},
// Uniform mask (100%, 75%, nil) and variable source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
// The source pixel is {0, 48, 0, 90}.
{"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}},
{"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}},
{"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}},
{"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}},
{"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}},
{"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}},
// Uniform mask (100%, 75%, nil) and variable NRGBA source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
// The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space.
// The result pixel is different than in the "copy*" test cases because of rounding errors.
{"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}},
{"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}},
{"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}},
{"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}},
{"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}},
{"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}},
// Uniform mask (100%, 75%, nil) and variable YCbCr source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
// The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space.
{"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}},
{"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}},
{"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}},
{"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}},
{"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}},
{"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}},
// Variable mask and variable source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
// The source pixel is {0, 0, 255, 255}.
// The mask pixel's alpha is 102, or 40%.
{"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
{"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
}
func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
// Since golden is a newly allocated image, we don't have to check if the
// input source and mask images and the output golden image overlap.
b := dst.Bounds()
sb := src.Bounds()
mb := image.Rect(-1e9, -1e9, 1e9, 1e9)
if mask != nil {
mb = mask.Bounds()
}
golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y))
for y := r.Min.Y; y < r.Max.Y; y++ {
sy := y + sp.Y - r.Min.Y
my := y + mp.Y - r.Min.Y
for x := r.Min.X; x < r.Max.X; x++ {
if !(image.Point{x, y}.In(b)) {
continue
}
sx := x + sp.X - r.Min.X
if !(image.Point{sx, sy}.In(sb)) {
continue
}
mx := x + mp.X - r.Min.X
if !(image.Point{mx, my}.In(mb)) {
continue
}
const M = 1<<16 - 1
var dr, dg, db, da uint32
if op == Over {
dr, dg, db, da = dst.At(x, y).RGBA()
}
sr, sg, sb, sa := src.At(sx, sy).RGBA()
ma := uint32(M)
if mask != nil {
_, _, _, ma = mask.At(mx, my).RGBA()
}
a := M - (sa * ma / M)
golden.Set(x, y, color.RGBA64{
uint16((dr*a + sr*ma) / M),
uint16((dg*a + sg*ma) / M),
uint16((db*a + sb*ma) / M),
uint16((da*a + sa*ma) / M),
})
}
}
return golden.SubImage(b)
}
func TestDraw(t *testing.T) {
rr := []image.Rectangle{
image.Rect(0, 0, 0, 0),
image.Rect(0, 0, 16, 16),
image.Rect(3, 5, 12, 10),
image.Rect(0, 0, 9, 9),
image.Rect(8, 8, 16, 16),
image.Rect(8, 0, 9, 16),
image.Rect(0, 8, 16, 9),
image.Rect(8, 8, 9, 9),
image.Rect(8, 8, 8, 8),
}
for _, r := range rr {
loop:
for _, test := range drawTests {
dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
b := dst.Bounds()
if !b.Eq(golden.Bounds()) {
t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds())
continue
}
// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
if image.Pt(8, 8).In(r) {
// Check that the resultant pixel at (8, 8) matches what we expect
// (the expected value can be verified by hand).
if !eq(dst.At(8, 8), test.expected) {
t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected)
continue
}
}
// Check that the resultant dst image matches the golden output.
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
if !eq(dst.At(x, y), golden.At(x, y)) {
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))
continue loop
}
}
}
}
}
}
func TestDrawOverlap(t *testing.T) {
for _, op := range []Op{Over, Src} {
for yoff := -2; yoff <= 2; yoff++ {
loop:
for xoff := -2; xoff <= 2; xoff++ {
m := gradYellow(127).(*image.RGBA)
dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA)
src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA)
b := dst.Bounds()
// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
if !b.Eq(golden.Bounds()) {
t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds())
continue
}
// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
// Check that the resultant dst image matches the golden output.
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
if !eq(dst.At(x, y), golden.At(x, y)) {
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))
continue loop
}
}
}
}
}
}
}
// TestNonZeroSrcPt checks drawing with a non-zero src point parameter.
func TestNonZeroSrcPt(t *testing.T) {
a := image.NewRGBA(image.Rect(0, 0, 1, 1))
b := image.NewRGBA(image.Rect(0, 0, 2, 2))
b.Set(0, 0, color.RGBA{0, 0, 0, 5})
b.Set(1, 0, color.RGBA{0, 0, 5, 5})
b.Set(0, 1, color.RGBA{0, 5, 0, 5})
b.Set(1, 1, color.RGBA{5, 0, 0, 5})
Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over)
if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) {
t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0))
}
}
func TestFill(t *testing.T) {
rr := []image.Rectangle{
image.Rect(0, 0, 0, 0),
image.Rect(0, 0, 40, 30),
image.Rect(10, 0, 40, 30),
image.Rect(0, 20, 40, 30),
image.Rect(10, 20, 40, 30),
image.Rect(10, 20, 15, 25),
image.Rect(10, 0, 35, 30),
image.Rect(0, 15, 40, 16),
image.Rect(24, 24, 25, 25),
image.Rect(23, 23, 26, 26),
image.Rect(22, 22, 27, 27),
image.Rect(21, 21, 28, 28),
image.Rect(20, 20, 29, 29),
}
for _, r := range rr {
m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA)
b := m.Bounds()
c := color.RGBA{11, 0, 0, 255}
src := &image.Uniform{c}
check := func(desc string) {
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
if !eq(c, m.At(x, y)) {
t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y))
return
}
}
}
}
// Draw 1 pixel at a time.
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src)
}
}
check("pixel")
// Draw 1 row at a time.
c = color.RGBA{0, 22, 0, 255}
src = &image.Uniform{c}
for y := b.Min.Y; y < b.Max.Y; y++ {
DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src)
}
check("row")
// Draw 1 column at a time.
c = color.RGBA{0, 0, 33, 255}
src = &image.Uniform{c}
for x := b.Min.X; x < b.Max.X; x++ {
DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src)
}
check("column")
// Draw the whole image at once.
c = color.RGBA{44, 55, 66, 77}
src = &image.Uniform{c}
DrawMask(m, b, src, image.ZP, nil, image.ZP, Src)
check("whole")
}
}