graphics: Use source-border check even at DrawTriangles

DrawTriangles can now take a sub-image.
This commit is contained in:
Hajime Hoshi 2018-12-23 02:35:50 +09:00
parent 4149a56524
commit 189b8a17e9
4 changed files with 113 additions and 56 deletions

View File

@ -411,6 +411,7 @@ type Vertex struct {
DstY float32 DstY float32
// SrcX and SrcY represents a point on a source image. // SrcX and SrcY represents a point on a source image.
// Note that SrcX/SrcY on a sub-image should be in its bounds.
SrcX float32 SrcX float32
SrcY float32 SrcY float32
@ -452,11 +453,6 @@ const MaxIndicesNum = graphics.IndicesNum
// //
// The rule in which DrawTriangles works effectively is same as DrawImage's. // The rule in which DrawTriangles works effectively is same as DrawImage's.
// //
// In contrast to DrawImage, DrawTriangles doesn't care source image edges.
// This means that you might need to add 1px gap on a source region when you render an image by DrawTriangles.
// Note that Ebiten creates texture atlases internally, so you still have to care this even when
// you render a single image.
//
// When the image i is disposed, DrawTriangles does nothing. // When the image i is disposed, DrawTriangles does nothing.
// //
// Internal mipmap is not used on DrawTriangles. // Internal mipmap is not used on DrawTriangles.
@ -468,10 +464,6 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
return return
} }
// TODO: Implement this.
if img.isSubimage() {
panic("using a subimage at DrawTriangles is not implemented")
}
if i.isSubimage() { if i.isSubimage() {
panic("render to a subimage is not implemented") panic("render to a subimage is not implemented")
} }
@ -499,8 +491,12 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
vs := make([]float32, len(vertices)*graphics.VertexFloatNum) vs := make([]float32, len(vertices)*graphics.VertexFloatNum)
src := img.mipmap.original() src := img.mipmap.original()
r := img.Bounds()
for idx, v := range vertices { for idx, v := range vertices {
src.PutVertex(vs[idx*graphics.VertexFloatNum:(idx+1)*graphics.VertexFloatNum], float32(v.DstX), float32(v.DstY), v.SrcX, v.SrcY, v.ColorR, v.ColorG, v.ColorB, v.ColorA) src.PutVertex(vs[idx*graphics.VertexFloatNum:(idx+1)*graphics.VertexFloatNum],
float32(v.DstX), float32(v.DstY), v.SrcX, v.SrcY,
float32(r.Min.X), float32(r.Min.Y), float32(r.Max.X), float32(r.Max.Y),
v.ColorR, v.ColorG, v.ColorB, v.ColorA)
} }
i.mipmap.original().DrawImage(img.mipmap.original(), vs, indices, options.ColorM.impl, mode, filter) i.mipmap.original().DrawImage(img.mipmap.original(), vs, indices, options.ColorM.impl, mode, filter)
i.disposeMipmaps() i.disposeMipmaps()

View File

@ -606,38 +606,96 @@ func TestImageEdge(t *testing.T) {
for _, s := range []float64{1, 0.5, 0.25} { for _, s := range []float64{1, 0.5, 0.25} {
for _, f := range []Filter{FilterNearest, FilterLinear} { for _, f := range []Filter{FilterNearest, FilterLinear} {
for _, a := range angles { for _, a := range angles {
img1.Clear() for _, testDrawTriangles := range []bool{false, true} {
op := &DrawImageOptions{} img1.Clear()
w, h := img0Sub.Size() w, h := img0Sub.Size()
op.GeoM.Translate(-float64(w)/2, -float64(h)/2) b := img0Sub.Bounds()
op.GeoM.Scale(s, s) var geo GeoM
op.GeoM.Rotate(a) geo.Translate(-float64(w)/2, -float64(h)/2)
op.GeoM.Translate(img1Width/2, img1Height/2) geo.Scale(s, s)
op.Filter = f geo.Rotate(a)
img1.DrawImage(img0Sub, op) geo.Translate(img1Width/2, img1Height/2)
allTransparent := true if !testDrawTriangles {
for j := 0; j < img1Height; j++ { op := &DrawImageOptions{}
for i := 0; i < img1Width; i++ { op.GeoM = geo
c := img1.At(i, j) op.Filter = f
if c == transparent { img1.DrawImage(img0Sub, op)
continue } else {
op := &DrawTrianglesOptions{}
dx0, dy0 := geo.Apply(0, 0)
dx1, dy1 := geo.Apply(float64(w), 0)
dx2, dy2 := geo.Apply(0, float64(h))
dx3, dy3 := geo.Apply(float64(w), float64(h))
vs := []Vertex{
{
DstX: float32(dx0),
DstY: float32(dy0),
SrcX: float32(b.Min.X),
SrcY: float32(b.Min.Y),
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
{
DstX: float32(dx1),
DstY: float32(dy1),
SrcX: float32(b.Max.X),
SrcY: float32(b.Min.Y),
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
{
DstX: float32(dx2),
DstY: float32(dy2),
SrcX: float32(b.Min.X),
SrcY: float32(b.Max.Y),
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
{
DstX: float32(dx3),
DstY: float32(dy3),
SrcX: float32(b.Max.X),
SrcY: float32(b.Max.Y),
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
} }
allTransparent = false is := graphics.QuadIndices()
switch f { op.Filter = f
case FilterNearest: img1.DrawTriangles(vs, is, img0Sub, op)
if c == red { }
continue allTransparent := true
} for j := 0; j < img1Height; j++ {
case FilterLinear: for i := 0; i < img1Width; i++ {
if _, g, b, _ := c.RGBA(); g == 0 && b == 0 { c := img1.At(i, j)
continue if c == transparent {
} continue
} }
t.Fatalf("img1.At(%d, %d) (filter: %d, scale: %f, angle: %f) want: red or transparent, got: %v", i, j, f, s, a, c) allTransparent = false
switch f {
case FilterNearest:
if c == red {
continue
}
case FilterLinear:
if _, g, b, _ := c.RGBA(); g == 0 && b == 0 {
continue
}
}
t.Fatalf("img1.At(%d, %d) (filter: %d, scale: %f, angle: %f, draw-triangles?: %t) want: red or transparent, got: %v", i, j, f, s, a, testDrawTriangles, c)
}
}
if allTransparent {
t.Fatalf("img1 (filter: %d, scale: %f, angle: %f, draw-triangles?: %t) is transparent but should not", f, s, a, testDrawTriangles)
} }
}
if allTransparent {
t.Fatalf("img1 (filter: %d, scale: %f, angle: %f) is transparent but should not", f, s, a)
} }
} }
} }

View File

@ -155,7 +155,7 @@ func QuadIndices() []uint16 {
return quadIndices return quadIndices
} }
func PutVertex(vs []float32, width, height int, dx, dy, sx, sy float32, cr, cg, cb, ca float32) { func PutVertex(vs []float32, width, height int, dx, dy, su, sv float32, u0, v0, u1, v1 float32, cr, cg, cb, ca float32) {
if !isPowerOf2(width) { if !isPowerOf2(width) {
panic("not reached") panic("not reached")
} }
@ -163,21 +163,14 @@ func PutVertex(vs []float32, width, height int, dx, dy, sx, sy float32, cr, cg,
panic("not reached") panic("not reached")
} }
wf := float32(width)
hf := float32(height)
// Specify -1 for the source region, which means the source region is ignored.
//
// NaN would make more sense to represent an invalid state, but vertices including NaN values doesn't work on
// some machines (#696). Let's use negative numbers to represent such state.
vs[0] = dx vs[0] = dx
vs[1] = dy vs[1] = dy
vs[2] = sx / wf vs[2] = su
vs[3] = sy / hf vs[3] = sv
vs[4] = 0 vs[4] = u0
vs[5] = 0 vs[5] = v0
vs[6] = 1 vs[6] = u1
vs[7] = 1 vs[7] = v1
vs[8] = cr vs[8] = cr
vs[9] = cg vs[9] = cg
vs[10] = cb vs[10] = cb

View File

@ -197,13 +197,23 @@ func (i *Image) QuadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32,
return graphics.QuadVertices(w, h, sx0+ox, sy0+oy, sx1+ox, sy1+oy, a, b, c, d, tx, ty, cr, cg, cb, ca) return graphics.QuadVertices(w, h, sx0+ox, sy0+oy, sx1+ox, sy1+oy, a, b, c, d, tx, ty, cr, cg, cb, ca)
} }
func (i *Image) PutVertex(dest []float32, dx, dy, sx, sy float32, cr, cg, cb, ca float32) { func (i *Image) PutVertex(dest []float32, dx, dy, sx, sy float32, bx0, by0, bx1, by1 float32,
cr, cg, cb, ca float32) {
if i.backend == nil { if i.backend == nil {
i.allocate(true) i.allocate(true)
} }
ox, oy, _, _ := i.region() ox, oy, _, _ := i.region()
oxf, oyf := float32(ox), float32(oy)
w, h := i.backend.restorable.SizePowerOf2() w, h := i.backend.restorable.SizePowerOf2()
graphics.PutVertex(dest, w, h, dx, dy, sx+float32(ox), sy+float32(oy), cr, cg, cb, ca)
su := (sx + oxf) / float32(w)
sv := (sy + oyf) / float32(h)
u0 := (bx0 + oxf) / float32(w)
v0 := (by0 + oyf) / float32(h)
u1 := (bx1 + oxf) / float32(w)
v1 := (by1 + oyf) / float32(h)
graphics.PutVertex(dest, w, h, dx, dy, su, sv, u0, v0, u1, v1, cr, cg, cb, ca)
} }
const MaxCountForShare = 10 const MaxCountForShare = 10