exp/sprite/portable: use golang.org/x/image/draw.
Change-Id: Ib41fd6614e3a5504d0cebe9a84b23a675d0e88fd Reviewed-on: https://go-review.googlesource.com/12289 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
84f8e5edcc
commit
0d322895cb
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,6 +13,8 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
|
|
||||||
|
xdraw "golang.org/x/image/draw"
|
||||||
|
"golang.org/x/image/math/f64"
|
||||||
"golang.org/x/mobile/event/config"
|
"golang.org/x/mobile/event/config"
|
||||||
"golang.org/x/mobile/exp/f32"
|
"golang.org/x/mobile/exp/f32"
|
||||||
"golang.org/x/mobile/exp/sprite"
|
"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()
|
dx, dy := x.R.Dx(), x.R.Dy()
|
||||||
if dx > 0 && dy > 0 {
|
if dx > 0 && dy > 0 {
|
||||||
m.Scale(&m, 1/float32(dx), 1/float32(dy))
|
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.
|
m.Inverse(&m) // See the documentation on the affine function.
|
||||||
affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over)
|
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.
|
// Pop absTransforms.
|
||||||
e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user