diff --git a/image_test.go b/image_test.go index 5151fa11b..8e7e97862 100644 --- a/image_test.go +++ b/image_test.go @@ -504,7 +504,7 @@ func TestImageFill(t *testing.T) { } } -// Issue #317 +// Issue #317, #558 func TestImageEdge(t *testing.T) { const ( img0Width = 16 @@ -536,15 +536,23 @@ func TestImageEdge(t *testing.T) { red := color.RGBA{0xff, 0, 0, 0xff} transparent := color.RGBA{0, 0, 0, 0} + angles := []float64{} + for a := 0; a < 360; a++ { + angles = append(angles, float64(a)/360*2*math.Pi) + } + for a := 0; a < 256; a++ { + angles = append(angles, float64(a)/256*2*math.Pi) + } + for _, f := range []Filter{FilterNearest, FilterLinear} { - for a := 0; a < 360; a += 5 { + for _, a := range angles { img1.Clear() op := &DrawImageOptions{} w, h := img0.Size() r := image.Rect(0, 0, w, h/2) op.SourceRect = &r op.GeoM.Translate(-float64(img0Width)/2, -float64(img0Height)/2) - op.GeoM.Rotate(float64(a) * math.Pi / 180) + op.GeoM.Rotate(a) op.GeoM.Translate(img1Width/2, img1Height/2) op.Filter = f img1.DrawImage(img0, op) @@ -565,7 +573,7 @@ func TestImageEdge(t *testing.T) { continue } } - t.Errorf("img1.At(%d, %d) (filter: %d, angle: %d) want: red or transparent, got: %v", i, j, f, a, c) + t.Errorf("img1.At(%d, %d) (filter: %d, angle: %f) want: red or transparent, got: %v", i, j, f, a, c) } } } diff --git a/internal/graphics/program.go b/internal/graphics/program.go index 73dec8933..833277668 100644 --- a/internal/graphics/program.go +++ b/internal/graphics/program.go @@ -321,17 +321,16 @@ func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, dst, sr s.lastColorMatrixTranslation = esTranslate } - if program == s.programLinear || program == s.programScreen { - sw, sh := src.Size() - sw = emath.NextPowerOf2Int(sw) - sh = emath.NextPowerOf2Int(sh) + sw, sh := src.Size() + sw = emath.NextPowerOf2Int(sw) + sh = emath.NextPowerOf2Int(sh) - if s.lastSourceWidth != sw || s.lastSourceHeight != sh { - c.UniformFloats(program, "source_size", []float32{float32(sw), float32(sh)}) - s.lastSourceWidth = sw - s.lastSourceHeight = sh - } + if s.lastSourceWidth != sw || s.lastSourceHeight != sh { + c.UniformFloats(program, "source_size", []float32{float32(sw), float32(sh)}) + s.lastSourceWidth = sw + s.lastSourceHeight = sh } + if program == s.programScreen { sw, _ := src.Size() dw, _ := dst.Size() diff --git a/internal/graphics/shader.go b/internal/graphics/shader.go index e9b521ba5..40a7c9786 100644 --- a/internal/graphics/shader.go +++ b/internal/graphics/shader.go @@ -76,9 +76,7 @@ uniform sampler2D texture; uniform mat4 color_matrix; uniform vec4 color_matrix_translation; -#if defined(FILTER_LINEAR) || defined(FILTER_SCREEN) uniform highp vec2 source_size; -#endif #if defined(FILTER_SCREEN) uniform highp float scale; @@ -104,51 +102,60 @@ void main(void) { // roundTexel adjusts pos by rounding it (#315, #558). pos = roundTexel(pos); -#if defined(FILTER_NEAREST) - vec4 color = texture2D(texture, pos); - if (pos.x < varying_tex_coord_min.x || - pos.y < varying_tex_coord_min.y || - varying_tex_coord_max.x <= pos.x || - varying_tex_coord_max.y <= pos.y) { - color = vec4(0, 0, 0, 0); - } -#endif - -#if defined(FILTER_LINEAR) highp vec2 texel_size = 1.0 / source_size; +#if defined(FILTER_NEAREST) || defined(FILTER_LINEAR) + // Even with the nearest filter, it is better to calculate the color with around 4 texels (#558). + highp vec2 p0 = pos - texel_size / 2.0; highp vec2 p1 = pos + texel_size / 2.0; vec4 c0 = texture2D(texture, p0); vec4 c1 = texture2D(texture, vec2(p1.x, p0.y)); vec4 c2 = texture2D(texture, vec2(p0.x, p1.y)); vec4 c3 = texture2D(texture, p1); - if (p0.x < varying_tex_coord_min.x) { + if (p0.x < (varying_tex_coord_min.x)) { c0 = vec4(0, 0, 0, 0); c2 = vec4(0, 0, 0, 0); } - if (p0.y < varying_tex_coord_min.y) { + if (p0.y < (varying_tex_coord_min.y)) { c0 = vec4(0, 0, 0, 0); c1 = vec4(0, 0, 0, 0); } - if (varying_tex_coord_max.x <= p1.x) { + if ((varying_tex_coord_max.x - texel_size.x / 256.0) <= p1.x) { c1 = vec4(0, 0, 0, 0); c3 = vec4(0, 0, 0, 0); } - if (varying_tex_coord_max.y <= p1.y) { + if ((varying_tex_coord_max.y - texel_size.y / 256.0) <= p1.y) { c2 = vec4(0, 0, 0, 0); c3 = vec4(0, 0, 0, 0); } vec2 rate = fract(p0 * source_size); + +#if defined(FILTER_NEAREST) + vec4 color; + if (rate.x < 0.5) { + if (rate.y < 0.5) { + color = c0; + } else { + color = c2; + } + } else { + if (rate.y < 0.5) { + color = c1; + } else { + color = c3; + } + } +#elif defined(FILTER_LINEAR) vec4 color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y); #endif +#endif + #if defined(FILTER_SCREEN) - highp vec2 texel_size = 1.0 / source_size; highp vec2 p0 = pos - texel_size / 2.0 / scale; highp vec2 p1 = pos + texel_size / 2.0 / scale; - vec4 c0 = texture2D(texture, p0); vec4 c1 = texture2D(texture, vec2(p1.x, p0.y)); vec4 c2 = texture2D(texture, vec2(p0.x, p1.y));