diff --git a/image.go b/image.go index 08b739390..ae1ea3592 100644 --- a/image.go +++ b/image.go @@ -411,6 +411,7 @@ type Vertex struct { DstY float32 // 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 SrcY float32 @@ -452,11 +453,6 @@ const MaxIndicesNum = graphics.IndicesNum // // 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. // // Internal mipmap is not used on DrawTriangles. @@ -468,10 +464,6 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o return } - // TODO: Implement this. - if img.isSubimage() { - panic("using a subimage at DrawTriangles is not implemented") - } if i.isSubimage() { 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) src := img.mipmap.original() + r := img.Bounds() 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.disposeMipmaps() diff --git a/image_test.go b/image_test.go index 429979338..83c4a4f3b 100644 --- a/image_test.go +++ b/image_test.go @@ -606,38 +606,96 @@ func TestImageEdge(t *testing.T) { for _, s := range []float64{1, 0.5, 0.25} { for _, f := range []Filter{FilterNearest, FilterLinear} { for _, a := range angles { - img1.Clear() - op := &DrawImageOptions{} - w, h := img0Sub.Size() - op.GeoM.Translate(-float64(w)/2, -float64(h)/2) - op.GeoM.Scale(s, s) - op.GeoM.Rotate(a) - op.GeoM.Translate(img1Width/2, img1Height/2) - op.Filter = f - img1.DrawImage(img0Sub, op) - allTransparent := true - for j := 0; j < img1Height; j++ { - for i := 0; i < img1Width; i++ { - c := img1.At(i, j) - if c == transparent { - continue + for _, testDrawTriangles := range []bool{false, true} { + img1.Clear() + w, h := img0Sub.Size() + b := img0Sub.Bounds() + var geo GeoM + geo.Translate(-float64(w)/2, -float64(h)/2) + geo.Scale(s, s) + geo.Rotate(a) + geo.Translate(img1Width/2, img1Height/2) + if !testDrawTriangles { + op := &DrawImageOptions{} + op.GeoM = geo + op.Filter = f + img1.DrawImage(img0Sub, op) + } 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 - 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) want: red or transparent, got: %v", i, j, f, s, a, c) + is := graphics.QuadIndices() + op.Filter = f + img1.DrawTriangles(vs, is, img0Sub, op) + } + allTransparent := true + for j := 0; j < img1Height; j++ { + for i := 0; i < img1Width; i++ { + c := img1.At(i, j) + if c == transparent { + continue + } + 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) } } } diff --git a/internal/graphics/vertices.go b/internal/graphics/vertices.go index ceb92a738..79b0c193f 100644 --- a/internal/graphics/vertices.go +++ b/internal/graphics/vertices.go @@ -155,7 +155,7 @@ func QuadIndices() []uint16 { 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) { panic("not reached") } @@ -163,21 +163,14 @@ func PutVertex(vs []float32, width, height int, dx, dy, sx, sy float32, cr, cg, 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[1] = dy - vs[2] = sx / wf - vs[3] = sy / hf - vs[4] = 0 - vs[5] = 0 - vs[6] = 1 - vs[7] = 1 + vs[2] = su + vs[3] = sv + vs[4] = u0 + vs[5] = v0 + vs[6] = u1 + vs[7] = v1 vs[8] = cr vs[9] = cg vs[10] = cb diff --git a/internal/shareable/shareable.go b/internal/shareable/shareable.go index c9bb12271..2db911881 100644 --- a/internal/shareable/shareable.go +++ b/internal/shareable/shareable.go @@ -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) } -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 { i.allocate(true) } ox, oy, _, _ := i.region() + oxf, oyf := float32(ox), float32(oy) 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