diff --git a/image_test.go b/image_test.go index f18e402f6..39a2ef2bc 100644 --- a/image_test.go +++ b/image_test.go @@ -16,6 +16,7 @@ package ebiten_test import ( "bytes" + "fmt" "image" "image/color" "image/draw" @@ -2067,63 +2068,61 @@ func BenchmarkImageDrawOver(b *testing.B) { } // Issue #1171 -func Disabled_TestImageFloatTranslate(t *testing.T) { - const w, h = 16, 16 +func TestImageFloatTranslate(t *testing.T) { + const w, h = 32, 32 - dst, _ := NewImage(320, 240, FilterDefault) - src, _ := NewImage(w, h, FilterDefault) - pix := make([]byte, 4*w*h) - for j := 0; j < h; j++ { - for i := 0; i < w; i++ { - pix[4*(j*w+i)] = byte(j) - pix[4*(j*w+i)+3] = 0xff - } - } - src.ReplacePixels(pix) + for s := 2; s <= 8; s++ { + s := s + t.Run(fmt.Sprintf("scale%d", s), func(t *testing.T) { + check := func(src *Image) { + dst, _ := NewImage(w*(s+1), h*(s+1), FilterDefault) + dst.Fill(color.RGBA{0xff, 0, 0, 0xff}) - op := &DrawImageOptions{} - op.GeoM.Scale(2, 2) - op.GeoM.Translate(0, 0.501) - dst.DrawImage(src, op) + op := &DrawImageOptions{} + op.GeoM.Scale(float64(s), float64(s)) + op.GeoM.Translate(0, 0.501) + dst.DrawImage(src, op) - for j := 1; j < h*2+1; j++ { - for i := 0; i < w*2; i++ { - got := dst.At(i, j) - want := color.RGBA{(byte(j) - 1) / 2, 0, 0, 0xff} - if got != want { - t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want) + for j := 0; j < h*s+1; j++ { + for i := 0; i < w*s; i++ { + got := dst.At(i, j) + x := byte(0xff) + if j > 0 { + x = (byte(j) - 1) / byte(s) + } + want := color.RGBA{x, 0, 0, 0xff} + if got != want { + t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want) + } + } + } } - } - } -} - -// Issue #1171 -func Disabled_TestImageSubImageFloatTranslate(t *testing.T) { - const w, h = 16, 16 - - dst, _ := NewImage(320, 240, FilterDefault) - src, _ := NewImage(w*2, h*2, FilterDefault) - pix := make([]byte, 4*(w*2)*(h*2)) - for j := 0; j < h*2; j++ { - for i := 0; i < w*2; i++ { - pix[4*(j*(w*2)+i)] = byte(j) - pix[4*(j*(w*2)+i)+3] = 0xff - } - } - src.ReplacePixels(pix) - - op := &DrawImageOptions{} - op.GeoM.Scale(2, 2) - op.GeoM.Translate(0, 0.501) - dst.DrawImage(src.SubImage(image.Rect(0, 0, w, h)).(*Image), op) - - for j := 1; j < h*2+1; j++ { - for i := 0; i < w*2; i++ { - got := dst.At(i, j) - want := color.RGBA{(byte(j) - 1) / 2, 0, 0, 0xff} - if got != want { - t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want) - } - } + + t.Run("image", func(t *testing.T) { + src, _ := NewImage(w, h, FilterDefault) + pix := make([]byte, 4*w*h) + for j := 0; j < h; j++ { + for i := 0; i < w; i++ { + pix[4*(j*w+i)] = byte(j) + pix[4*(j*w+i)+3] = 0xff + } + } + src.ReplacePixels(pix) + check(src) + }) + + t.Run("subimage", func(t *testing.T) { + src, _ := NewImage(w*s, h*s, FilterDefault) + pix := make([]byte, 4*(w*s)*(h*s)) + for j := 0; j < h*s; j++ { + for i := 0; i < w*s; i++ { + pix[4*(j*(w*s)+i)] = byte(j) + pix[4*(j*(w*s)+i)+3] = 0xff + } + } + src.ReplacePixels(pix) + check(src.SubImage(image.Rect(0, 0, w, h)).(*Image)) + }) + }) } } diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index 45eaa3a5b..ab65364b4 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -203,14 +203,6 @@ func (q *commandQueue) Enqueue(command command) { q.commands = append(q.commands, command) } -func fract(x float32) float32 { - return x - float32(math.Floor(float64(x))) -} - -const ( - dstAdjustmentFactor = 1.0 / 256.0 -) - // Flush flushes the command queue. func (q *commandQueue) Flush() error { if len(q.commands) == 0 { @@ -236,22 +228,22 @@ func (q *commandQueue) Flush() error { vs[i*graphics.VertexFloatNum+6] /= s.width vs[i*graphics.VertexFloatNum+7] /= s.height - // Adjust the destination position to avoid jaggy (#929). - // This is not a perfect solution since texels on a texture can take a position on borders - // which can cause jaggy. But adjusting only edges should work in most cases. - // The ideal solution is to fix shaders, but this makes the applications slow by adding 'if' - // branches. - switch f := fract(vs[i*graphics.VertexFloatNum+0]); { - case 0.5-dstAdjustmentFactor <= f && f < 0.5: - vs[i*graphics.VertexFloatNum+0] -= f - (0.5 - dstAdjustmentFactor) - case 0.5 <= f && f < 0.5+dstAdjustmentFactor: - vs[i*graphics.VertexFloatNum+0] += (0.5 + dstAdjustmentFactor) - f - } - switch f := fract(vs[i*graphics.VertexFloatNum+1]); { - case 0.5-dstAdjustmentFactor <= f && f < 0.5: - vs[i*graphics.VertexFloatNum+1] -= f - (0.5 - dstAdjustmentFactor) - case 0.5 <= f && f < 0.5+dstAdjustmentFactor: - vs[i*graphics.VertexFloatNum+1] += (0.5 + dstAdjustmentFactor) - f + // Avoid the center of the pixel, which is problematic (#929, #1171). + // Instead, align the vertices with about 1/3 pixels. + for idx := 0; idx < 2; idx++ { + x := vs[i*graphics.VertexFloatNum+idx] + int := float32(math.Floor(float64(x))) + frac := x - int + switch { + case frac < 3.0/16.0: + vs[i*graphics.VertexFloatNum+idx] = int + case frac < 8.0/16.0: + vs[i*graphics.VertexFloatNum+idx] = int + 5.0/16.0 + case frac < 13.0/16.0: + vs[i*graphics.VertexFloatNum+idx] = int + 11.0/16.0 + default: + vs[i*graphics.VertexFloatNum+idx] = int + 16.0/16.0 + } } } } else {