go.mobile/sprite/portable: support masks in affine transform
LGTM=nigeltao R=nigeltao CC=golang-codereviews https://golang.org/cl/166100043
This commit is contained in:
Родитель
3e603f3a38
Коммит
df04a5b449
|
@ -25,7 +25,7 @@ import (
|
|||
//
|
||||
// will produce a dst that is half the size of src. To perform a
|
||||
// traditional affine transform, use the inverse of the affine matrix.
|
||||
func affine(dst *image.RGBA, src *image.RGBA, a *f32.Affine, op draw.Op) {
|
||||
func affine(dst *image.RGBA, src *image.RGBA, a *f32.Affine, mask image.Image, op draw.Op) {
|
||||
srcb := src.Bounds()
|
||||
b := dst.Bounds()
|
||||
|
||||
|
@ -40,40 +40,48 @@ func affine(dst *image.RGBA, src *image.RGBA, a *f32.Affine, op draw.Op) {
|
|||
continue
|
||||
}
|
||||
|
||||
c := bilinear(src, sx, sy)
|
||||
off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
|
||||
if op == draw.Over {
|
||||
// This logic comes from drawCopyOver in the image/draw package.
|
||||
sr := uint32(c.R) * 0x101
|
||||
sg := uint32(c.G) * 0x101
|
||||
sb := uint32(c.B) * 0x101
|
||||
sa := uint32(c.A) * 0x101
|
||||
// m is the maximum color value returned by image.Color.RGBA.
|
||||
const m = 1<<16 - 1
|
||||
|
||||
ma := uint32(m)
|
||||
if mask != nil {
|
||||
_, _, _, ma = bilinear(mask, sx, sy).RGBA()
|
||||
}
|
||||
|
||||
c := bilinearRGBA(src, sx, sy)
|
||||
off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
|
||||
|
||||
// c.R, c.G, c.B, c.A, dr, dg, db, and da are all 8-bit color at the moment,
|
||||
// ranging in [0,255]. We work in 16-bit color, and so would normally do:
|
||||
// dr |= dr << 8
|
||||
// and similarly for the other values, but instead we multiply by 0x101
|
||||
// to shift these to 16-bit colors, ranging in [0,65535].
|
||||
// This yields the same result, but is fewer arithmetic operations.
|
||||
//
|
||||
// This logic comes from drawCopyOver in the image/draw package.
|
||||
sr := uint32(c.R) * 0x101
|
||||
sg := uint32(c.G) * 0x101
|
||||
sb := uint32(c.B) * 0x101
|
||||
sa := uint32(c.A) * 0x101
|
||||
|
||||
if op == draw.Over {
|
||||
dr := uint32(dst.Pix[off+0])
|
||||
dg := uint32(dst.Pix[off+1])
|
||||
db := uint32(dst.Pix[off+2])
|
||||
da := uint32(dst.Pix[off+3])
|
||||
|
||||
// m is the maximum color value returned by image.Color.RGBA.
|
||||
const m = 1<<16 - 1
|
||||
a := m - (sa * ma / m)
|
||||
a *= 0x101
|
||||
|
||||
// dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
|
||||
// We work in 16-bit color, and so would normally do:
|
||||
// dr |= dr << 8
|
||||
// and similarly for dg, db and da, but instead we multiply a
|
||||
// (which is a 16-bit color, ranging in [0,65535]) by 0x101.
|
||||
// This yields the same result, but is fewer arithmetic operations.
|
||||
a := (m - sa) * 0x101
|
||||
|
||||
dst.Pix[off+0] = uint8((dr*a/m + sr) >> 8)
|
||||
dst.Pix[off+1] = uint8((dg*a/m + sg) >> 8)
|
||||
dst.Pix[off+2] = uint8((db*a/m + sb) >> 8)
|
||||
dst.Pix[off+3] = uint8((da*a/m + sa) >> 8)
|
||||
dst.Pix[off+0] = uint8((dr*a + sr*ma) / m >> 8)
|
||||
dst.Pix[off+1] = uint8((dg*a + sg*ma) / m >> 8)
|
||||
dst.Pix[off+2] = uint8((db*a + sb*ma) / m >> 8)
|
||||
dst.Pix[off+3] = uint8((da*a + sa*ma) / m >> 8)
|
||||
} else {
|
||||
dst.Pix[off+0] = c.R
|
||||
dst.Pix[off+1] = c.G
|
||||
dst.Pix[off+2] = c.B
|
||||
dst.Pix[off+3] = c.A
|
||||
dst.Pix[off+0] = uint8(sr * ma / m >> 8)
|
||||
dst.Pix[off+1] = uint8(sg * ma / m >> 8)
|
||||
dst.Pix[off+2] = uint8(sb * ma / m >> 8)
|
||||
dst.Pix[off+3] = uint8(sa * ma / m >> 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ func TestAffine(t *testing.T) {
|
|||
a.Scale(&a, 40/float32(src.Rect.Dx()), 20/float32(src.Rect.Dy()))
|
||||
a.Inverse(&a)
|
||||
|
||||
affine(got, src, &a, draw.Over)
|
||||
affine(got, src, &a, nil, draw.Over)
|
||||
|
||||
ptTopLeft := geom.Point{0, 24}
|
||||
ptBottomRight := geom.Point{12 + 32, 16}
|
||||
|
@ -87,27 +87,72 @@ func TestAffine(t *testing.T) {
|
|||
}
|
||||
|
||||
if !imageEq(got, want) {
|
||||
// Write out the image we got.
|
||||
f, err = ioutil.TempFile("", "testpattern-window-got")
|
||||
gotPath, err := writeTempPNG("testpattern-window-got", got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
gotPath := f.Name() + ".png"
|
||||
f, err = os.Create(gotPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := png.Encode(f, got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineMask(t *testing.T) {
|
||||
f, err := os.Open("../../testdata/testpattern.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
srcOrig, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := srcOrig.Bounds()
|
||||
src := image.NewRGBA(b)
|
||||
draw.Draw(src, src.Rect, srcOrig, b.Min, draw.Src)
|
||||
mask := image.NewAlpha(b)
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
mask.Set(x, y, color.Alpha{A: uint8(x - b.Min.X)})
|
||||
}
|
||||
}
|
||||
want := image.NewRGBA(b)
|
||||
draw.DrawMask(want, want.Rect, src, b.Min, mask, b.Min, draw.Src)
|
||||
|
||||
a := new(f32.Affine)
|
||||
a.Identity()
|
||||
got := image.NewRGBA(b)
|
||||
affine(got, src, a, mask, draw.Src)
|
||||
|
||||
if !imageEq(got, want) {
|
||||
gotPath, err := writeTempPNG("testpattern-mask-got", got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantPath, err := writeTempPNG("testpattern-mask-want", want)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
||||
}
|
||||
}
|
||||
|
||||
func writeTempPNG(prefix string, m image.Image) (string, error) {
|
||||
f, err := ioutil.TempFile("", prefix+"-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
f.Close()
|
||||
path := f.Name() + ".png"
|
||||
f, err = os.Create(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := png.Encode(f, m); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
return path, f.Close()
|
||||
}
|
||||
|
||||
func drawCross(m *image.RGBA, x, y int) {
|
||||
c := color.RGBA{0xff, 0, 0, 0xff} // red
|
||||
m.SetRGBA(x+0, y-2, c)
|
||||
|
|
|
@ -10,14 +10,61 @@ import (
|
|||
"math"
|
||||
)
|
||||
|
||||
func bilinear(src *image.RGBA, x, y float32) color.RGBA {
|
||||
func bilinear(src image.Image, x, y float32) color.Color {
|
||||
switch src := src.(type) {
|
||||
case *image.RGBA:
|
||||
return bilinearRGBA(src, x, y)
|
||||
case *image.Alpha:
|
||||
return bilinearAlpha(src, x, y)
|
||||
default:
|
||||
return bilinearGeneral(src, x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func bilinearGeneral(src image.Image, x, y float32) color.RGBA64 {
|
||||
p := findLinearSrc(src.Bounds(), x, y)
|
||||
|
||||
// Array offsets for the surrounding pixels.
|
||||
off00 := offRGBA(src, p.low.X, p.low.Y)
|
||||
off01 := offRGBA(src, p.high.X, p.low.Y)
|
||||
off10 := offRGBA(src, p.low.X, p.high.Y)
|
||||
off11 := offRGBA(src, p.high.X, p.high.Y)
|
||||
r00, g00, b00, a00 := src.At(p.low.X, p.low.Y).RGBA()
|
||||
r01, g01, b01, a01 := src.At(p.high.X, p.low.Y).RGBA()
|
||||
r10, g10, b10, a10 := src.At(p.low.X, p.high.Y).RGBA()
|
||||
r11, g11, b11, a11 := src.At(p.high.X, p.high.Y).RGBA()
|
||||
|
||||
fr := float32(r00) * p.frac00
|
||||
fg := float32(g00) * p.frac00
|
||||
fb := float32(b00) * p.frac00
|
||||
fa := float32(a00) * p.frac00
|
||||
|
||||
fr += float32(r01) * p.frac01
|
||||
fg += float32(g01) * p.frac01
|
||||
fb += float32(b01) * p.frac01
|
||||
fa += float32(a01) * p.frac01
|
||||
|
||||
fr += float32(r10) * p.frac10
|
||||
fg += float32(g10) * p.frac10
|
||||
fb += float32(b10) * p.frac10
|
||||
fa += float32(a10) * p.frac10
|
||||
|
||||
fr += float32(r11) * p.frac11
|
||||
fg += float32(g11) * p.frac11
|
||||
fb += float32(b11) * p.frac11
|
||||
fa += float32(a11) * p.frac11
|
||||
|
||||
return color.RGBA64{
|
||||
R: uint16(fr + 0.5),
|
||||
G: uint16(fg + 0.5),
|
||||
B: uint16(fb + 0.5),
|
||||
A: uint16(fa + 0.5),
|
||||
}
|
||||
}
|
||||
|
||||
func bilinearRGBA(src *image.RGBA, x, y float32) color.RGBA {
|
||||
p := findLinearSrc(src.Bounds(), x, y)
|
||||
|
||||
// Slice offsets for the surrounding pixels.
|
||||
off00 := src.PixOffset(p.low.X, p.low.Y)
|
||||
off01 := src.PixOffset(p.high.X, p.low.Y)
|
||||
off10 := src.PixOffset(p.low.X, p.high.Y)
|
||||
off11 := src.PixOffset(p.high.X, p.high.Y)
|
||||
|
||||
fr := float32(src.Pix[off00+0]) * p.frac00
|
||||
fg := float32(src.Pix[off00+1]) * p.frac00
|
||||
|
@ -47,6 +94,23 @@ func bilinear(src *image.RGBA, x, y float32) color.RGBA {
|
|||
}
|
||||
}
|
||||
|
||||
func bilinearAlpha(src *image.Alpha, x, y float32) color.Alpha {
|
||||
p := findLinearSrc(src.Bounds(), x, y)
|
||||
|
||||
// Slice offsets for the surrounding pixels.
|
||||
off00 := src.PixOffset(p.low.X, p.low.Y)
|
||||
off01 := src.PixOffset(p.high.X, p.low.Y)
|
||||
off10 := src.PixOffset(p.low.X, p.high.Y)
|
||||
off11 := src.PixOffset(p.high.X, p.high.Y)
|
||||
|
||||
fa := float32(src.Pix[off00]) * p.frac00
|
||||
fa += float32(src.Pix[off01]) * p.frac01
|
||||
fa += float32(src.Pix[off10]) * p.frac10
|
||||
fa += float32(src.Pix[off11]) * p.frac11
|
||||
|
||||
return color.Alpha{A: uint8(fa + 0.5)}
|
||||
}
|
||||
|
||||
type bilinearSrc struct {
|
||||
// Top-left and bottom-right interpolation sources
|
||||
low, high image.Point
|
||||
|
@ -128,7 +192,3 @@ func findLinearSrc(b image.Rectangle, sx, sy float32) bilinearSrc {
|
|||
|
||||
return p
|
||||
}
|
||||
|
||||
func offRGBA(src *image.RGBA, x, y int) int {
|
||||
return (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4
|
||||
}
|
||||
|
|
|
@ -84,14 +84,40 @@ func TestBilinear(t *testing.T) {
|
|||
for _, p := range interpTests {
|
||||
src := p.newSrc()
|
||||
|
||||
// Fast path.
|
||||
c := bilinear(src, p.x, p.y)
|
||||
if c.R != c.G || c.R != c.B || c.A != 0xff {
|
||||
t.Errorf("expect channels to match, got %v", c)
|
||||
c0 := bilinearGeneral(src, p.x, p.y)
|
||||
c0R, c0G, c0B, c0A := c0.RGBA()
|
||||
r := uint8(c0R >> 8)
|
||||
g := uint8(c0G >> 8)
|
||||
b := uint8(c0B >> 8)
|
||||
a := uint8(c0A >> 8)
|
||||
|
||||
if r != g || r != b || a != 0xff {
|
||||
t.Errorf("expect channels to match, got %v", c0)
|
||||
continue
|
||||
}
|
||||
if c.R != p.expect {
|
||||
t.Errorf("%s: got 0x%02x want 0x%02x", p.desc, c.R, p.expect)
|
||||
if r != p.expect {
|
||||
t.Errorf("%s: got 0x%02x want 0x%02x", p.desc, r, p.expect)
|
||||
continue
|
||||
}
|
||||
|
||||
// fast path for *image.RGBA
|
||||
c1 := bilinearRGBA(src, p.x, p.y)
|
||||
if r != c1.R || g != c1.G || b != c1.B || a != c1.A {
|
||||
t.Errorf("%s: RGBA fast path mismatch got %v want %v", p.desc, c1, c0)
|
||||
continue
|
||||
}
|
||||
|
||||
// fast path for *image.Alpha
|
||||
alpha := image.NewAlpha(src.Bounds())
|
||||
for y := src.Bounds().Min.Y; y < src.Bounds().Max.Y; y++ {
|
||||
for x := src.Bounds().Min.X; x < src.Bounds().Max.X; x++ {
|
||||
r, _, _, _ := src.At(x, y).RGBA()
|
||||
alpha.Set(x, y, color.Alpha{A: uint8(r >> 8)})
|
||||
}
|
||||
}
|
||||
c2 := bilinearAlpha(alpha, p.x, p.y)
|
||||
if c2.A != r {
|
||||
t.Errorf("%s: Alpha fast path mismatch got %v want %v", p.desc, c2, c0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +135,7 @@ func TestBilinearSubImage(t *testing.T) {
|
|||
|
||||
tests := []struct {
|
||||
x, y float32
|
||||
want uint8
|
||||
want uint32
|
||||
}{
|
||||
{1, 1, 0x11},
|
||||
{3, 1, 0x22},
|
||||
|
@ -119,9 +145,10 @@ func TestBilinearSubImage(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, p := range tests {
|
||||
c := bilinear(src1, p.x, p.y)
|
||||
if c.R != p.want {
|
||||
t.Errorf("(%.0f, %.0f): got 0x%02x want 0x%02x", p.x, p.y, c.R, p.want)
|
||||
r, _, _, _ := bilinear(src1, p.x, p.y).RGBA()
|
||||
r >>= 8
|
||||
if r != p.want {
|
||||
t.Errorf("(%.0f, %.0f): got 0x%02x want 0x%02x", p.x, p.y, r, p.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче