diff --git a/exp/sprite/portable/affine.go b/exp/sprite/portable/affine.go deleted file mode 100644 index c6aa37a..0000000 --- a/exp/sprite/portable/affine.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2014 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 portable - -import ( - "image" - "image/draw" - - "golang.org/x/mobile/exp/f32" -) - -// affine draws each pixel of dst using bilinear interpolation of the -// affine-transformed position in src. This is equivalent to: -// -// for each (x,y) in dst: -// dst(x,y) = bilinear interpolation of src(a*(x,y)) -// -// While this is the simpler implementation, it can be counter- -// intuitive as an affine transformation is usually described in terms -// of the source, not the destination. For example, a scale transform -// -// Affine{{2, 0, 0}, {0, 2, 0}} -// -// 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.Image, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) { - b := dst.Bounds() - var maskb image.Rectangle - if mask != nil { - maskb = mask.Bounds().Add(srcb.Min) - } - - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - // Interpolate from the bounds of the src sub-image - // to the bounds of the dst sub-image. - ix, iy := pt(a, x-b.Min.X, y-b.Min.Y) - sx := ix + float32(srcb.Min.X) - sy := iy + float32(srcb.Min.Y) - if !inBounds(srcb, sx, sy) { - continue - } - - // m is the maximum color value returned by image.Color.RGBA. - const m = 1<<16 - 1 - - ma := uint32(m) - if mask != nil { - mx := ix + float32(maskb.Min.X) - my := iy + float32(maskb.Min.Y) - if !inBounds(maskb, mx, my) { - continue - } - _, _, _, ma = bilinear(mask, mx, my).RGBA() - } - - sr, sg, sb, sa := bilinear(src, sx, sy).RGBA() - off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4 - - 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]) - - // 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. - a := m - (sa * ma / m) - a *= 0x101 - - 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] = 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) - } - } - } -} - -func inBounds(b image.Rectangle, x, y float32) bool { - if x < float32(b.Min.X) || x >= float32(b.Max.X) { - return false - } - if y < float32(b.Min.Y) || y >= float32(b.Max.Y) { - return false - } - return true -} - -func pt(a *f32.Affine, x0, y0 int) (x1, y1 float32) { - fx := float32(x0) + 0.5 - fy := float32(y0) + 0.5 - x1 = fx*a[0][0] + fy*a[0][1] + a[0][2] - y1 = fx*a[1][0] + fy*a[1][1] + a[1][2] - return x1, y1 -} diff --git a/exp/sprite/portable/bilinear.go b/exp/sprite/portable/bilinear.go deleted file mode 100644 index 37bc59a..0000000 --- a/exp/sprite/portable/bilinear.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2014 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 portable - -import ( - "image" - "image/color" - "math" -) - -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) - case *image.Uniform: - return src.C - default: - return bilinearGeneral(src, x, y) - } -} - -func bilinearGeneral(src image.Image, x, y float32) color.RGBA64 { - p := findLinearSrc(src.Bounds(), x, 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 - fb := float32(src.Pix[off00+2]) * p.frac00 - fa := float32(src.Pix[off00+3]) * p.frac00 - - fr += float32(src.Pix[off01+0]) * p.frac01 - fg += float32(src.Pix[off01+1]) * p.frac01 - fb += float32(src.Pix[off01+2]) * p.frac01 - fa += float32(src.Pix[off01+3]) * p.frac01 - - fr += float32(src.Pix[off10+0]) * p.frac10 - fg += float32(src.Pix[off10+1]) * p.frac10 - fb += float32(src.Pix[off10+2]) * p.frac10 - fa += float32(src.Pix[off10+3]) * p.frac10 - - fr += float32(src.Pix[off11+0]) * p.frac11 - fg += float32(src.Pix[off11+1]) * p.frac11 - fb += float32(src.Pix[off11+2]) * p.frac11 - fa += float32(src.Pix[off11+3]) * p.frac11 - - return color.RGBA{ - R: uint8(fr + 0.5), - G: uint8(fg + 0.5), - B: uint8(fb + 0.5), - A: uint8(fa + 0.5), - } -} - -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 - // Fraction of each pixel to take. The 0 suffix indicates - // top/left, and the 1 suffix indicates bottom/right. - frac00, frac01, frac10, frac11 float32 -} - -func floor(x float32) float32 { return float32(math.Floor(float64(x))) } -func ceil(x float32) float32 { return float32(math.Ceil(float64(x))) } - -func findLinearSrc(b image.Rectangle, sx, sy float32) bilinearSrc { - maxX := float32(b.Max.X) - maxY := float32(b.Max.Y) - minX := float32(b.Min.X) - minY := float32(b.Min.Y) - lowX := floor(sx - 0.5) - lowY := floor(sy - 0.5) - if lowX < minX { - lowX = minX - } - if lowY < minY { - lowY = minY - } - - highX := ceil(sx - 0.5) - highY := ceil(sy - 0.5) - if highX >= maxX { - highX = maxX - 1 - } - if highY >= maxY { - highY = maxY - 1 - } - - // In the variables below, the 0 suffix indicates top/left, and the - // 1 suffix indicates bottom/right. - - // Center of each surrounding pixel. - x00 := lowX + 0.5 - y00 := lowY + 0.5 - x01 := highX + 0.5 - y01 := lowY + 0.5 - x10 := lowX + 0.5 - y10 := highY + 0.5 - x11 := highX + 0.5 - y11 := highY + 0.5 - - p := bilinearSrc{ - low: image.Pt(int(lowX), int(lowY)), - high: image.Pt(int(highX), int(highY)), - } - - // Literally, edge cases. If we are close enough to the edge of - // the image, curtail the interpolation sources. - if lowX == highX && lowY == highY { - p.frac00 = 1.0 - } else if sy-minY <= 0.5 && sx-minX <= 0.5 { - p.frac00 = 1.0 - } else if maxY-sy <= 0.5 && maxX-sx <= 0.5 { - p.frac11 = 1.0 - } else if sy-minY <= 0.5 || lowY == highY { - p.frac00 = x01 - sx - p.frac01 = sx - x00 - } else if sx-minX <= 0.5 || lowX == highX { - p.frac00 = y10 - sy - p.frac10 = sy - y00 - } else if maxY-sy <= 0.5 { - p.frac10 = x11 - sx - p.frac11 = sx - x10 - } else if maxX-sx <= 0.5 { - p.frac01 = y11 - sy - p.frac11 = sy - y01 - } else { - p.frac00 = (x01 - sx) * (y10 - sy) - p.frac01 = (sx - x00) * (y11 - sy) - p.frac10 = (x11 - sx) * (sy - y00) - p.frac11 = (sx - x10) * (sy - y01) - } - - return p -} diff --git a/exp/sprite/portable/bilinear_test.go b/exp/sprite/portable/bilinear_test.go deleted file mode 100644 index 004dfa7..0000000 --- a/exp/sprite/portable/bilinear_test.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2014 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 portable - -import ( - "image" - "image/color" - "testing" -) - -type interpTest struct { - desc string - src []uint8 - srcWidth int - x, y float32 - expect uint8 -} - -func (p *interpTest) newSrc() *image.RGBA { - b := image.Rect(0, 0, p.srcWidth, len(p.src)/p.srcWidth) - src := image.NewRGBA(b) - i := 0 - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - src.SetRGBA(x, y, color.RGBA{ - R: p.src[i], - G: p.src[i], - B: p.src[i], - A: 0xff, - }) - i++ - } - } - return src -} - -var interpTests = []interpTest{ - { - desc: "center of a single white pixel should match that pixel", - src: []uint8{0x00}, - srcWidth: 1, - x: 0.5, - y: 0.5, - expect: 0x00, - }, - { - desc: "middle of a square is equally weighted", - src: []uint8{ - 0x00, 0xff, - 0xff, 0x00, - }, - srcWidth: 2, - x: 1.0, - y: 1.0, - expect: 0x80, - }, - { - desc: "center of a pixel is just that pixel", - src: []uint8{ - 0x00, 0xff, - 0xff, 0x00, - }, - srcWidth: 2, - x: 1.5, - y: 0.5, - expect: 0xff, - }, - { - desc: "asymmetry abounds", - src: []uint8{ - 0xaa, 0x11, 0x55, - 0xff, 0x95, 0xdd, - }, - srcWidth: 3, - x: 2.0, - y: 1.0, - expect: 0x76, // (0x11 + 0x55 + 0x95 + 0xdd) / 4 - }, -} - -func TestBilinear(t *testing.T) { - for _, p := range interpTests { - src := p.newSrc() - - 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 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 - } - } -} - -func TestBilinearSubImage(t *testing.T) { - b0 := image.Rect(0, 0, 4, 4) - src0 := image.NewRGBA(b0) - b1 := image.Rect(1, 1, 3, 3) - src1 := src0.SubImage(b1).(*image.RGBA) - src1.Set(1, 1, color.RGBA{0x11, 0, 0, 0xff}) - src1.Set(2, 1, color.RGBA{0x22, 0, 0, 0xff}) - src1.Set(1, 2, color.RGBA{0x33, 0, 0, 0xff}) - src1.Set(2, 2, color.RGBA{0x44, 0, 0, 0xff}) - - tests := []struct { - x, y float32 - want uint32 - }{ - {1, 1, 0x11}, - {3, 1, 0x22}, - {1, 3, 0x33}, - {3, 3, 0x44}, - {2, 2, 0x2b}, - } - - for _, p := range tests { - 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) - } - } -} diff --git a/exp/sprite/portable/portable.go b/exp/sprite/portable/portable.go index 162ef88..df6c9a0 100644 --- a/exp/sprite/portable/portable.go +++ b/exp/sprite/portable/portable.go @@ -13,6 +13,8 @@ import ( "image" "image/draw" + xdraw "golang.org/x/image/draw" + "golang.org/x/image/math/f64" "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/f32" "golang.org/x/mobile/exp/sprite" @@ -132,6 +134,8 @@ func (e *engine) render(n *sprite.Node, t clock.Time) { dx, dy := x.R.Dx(), x.R.Dy() if dx > 0 && dy > 0 { m.Scale(&m, 1/float32(dx), 1/float32(dy)) + // TODO(nigeltao): delete the double-inverse: one here and one + // inside func affine. m.Inverse(&m) // See the documentation on the affine function. affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over) } @@ -144,3 +148,44 @@ func (e *engine) render(n *sprite.Node, t clock.Time) { // Pop absTransforms. e.absTransforms = e.absTransforms[:len(e.absTransforms)-1] } + +// affine draws each pixel of dst using bilinear interpolation of the +// affine-transformed position in src. This is equivalent to: +// +// for each (x,y) in dst: +// dst(x,y) = bilinear interpolation of src(a*(x,y)) +// +// While this is the simpler implementation, it can be counter- +// intuitive as an affine transformation is usually described in terms +// of the source, not the destination. For example, a scale transform +// +// Affine{{2, 0, 0}, {0, 2, 0}} +// +// 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.Image, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) { + // For legacy compatibility reasons, the matrix a transforms from dst-space + // to src-space. The golang.org/x/image/draw package's matrices transform + // from src-space to dst-space, so we invert (and adjust for different + // origins). + i := *a + i[0][2] += float32(srcb.Min.X) + i[1][2] += float32(srcb.Min.Y) + i.Inverse(&i) + i[0][2] += float32(dst.Rect.Min.X) + i[1][2] += float32(dst.Rect.Min.Y) + m := f64.Aff3{ + float64(i[0][0]), + float64(i[0][1]), + float64(i[0][2]), + float64(i[1][0]), + float64(i[1][1]), + float64(i[1][2]), + } + // TODO(nigeltao): is the caller or callee responsible for detecting + // transforms that are simple copies or scales, for which there are faster + // implementations in the xdraw package. + xdraw.ApproxBiLinear.Transform(dst, &m, src, srcb, xdraw.Op(op), &xdraw.Options{ + SrcMask: mask, + }) +}