diff --git a/image.go b/image.go index 574b9261a..5a8105763 100644 --- a/image.go +++ b/image.go @@ -251,12 +251,18 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error { a, b, c, d, tx, ty := geom.elements() - level := uint(0) + level := 0 if filter == graphics.FilterLinear { - det := math.Abs(float64(geom.det())) - for det < 0.25 { - level++ - det *= 4 + det := geom.det() + if det == 0 { + return + } + if math.IsNan(det) { + return + } + level = graphicsutil.MipmapLevel(det) + if level < 0 { + panic("not reached") } } if level > 6 { @@ -264,7 +270,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error { } if level > 0 { - s := 1 << level + s := 1 << uint(level) a *= float32(s) b *= float32(s) c *= float32(s) @@ -276,8 +282,8 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error { } w, h = img.shareableImages[len(img.shareableImages)-1].Size() - for uint(len(img.shareableImages)) < level+1 { - lastl := uint(len(img.shareableImages)) - 1 + for len(img.shareableImages) < level+1 { + lastl := len(img.shareableImages) - 1 src := img.shareableImages[lastl] w2 := int(math.Ceil(float64(w) / 2.0)) h2 := int(math.Ceil(float64(h) / 2.0)) @@ -298,7 +304,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error { h = h2 } - if level < uint(len(img.shareableImages)) { + if level < len(img.shareableImages) { src := img.shareableImages[level] vs := src.QuadVertices(sx0, sy0, sx1, sy1, a, b, c, d, tx, ty, options.ColorM.impl) is := graphicsutil.QuadIndices() diff --git a/internal/graphicsutil/mipmap.go b/internal/graphicsutil/mipmap.go new file mode 100644 index 000000000..035c649cd --- /dev/null +++ b/internal/graphicsutil/mipmap.go @@ -0,0 +1,41 @@ +// Copyright 2018 The Ebiten Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package graphicsutil + +import ( + "math" +) + +// MipmapLevel returns an appropriate mipmap level for the given determinant of a geometry matrix. +// +// MipmapLevel returns -1 if det is 0. +// +// MipmapLevel panics if det is NaN. +func MipmapLevel(det float32) int { + if math.IsNaN(float64(det)) { + panic("graphicsutil: det must be finite") + } + if det == 0 { + return -1 + } + + d := math.Abs(float64(det)) + level := 0 + for d < 0.25 { + level++ + d *= 4 + } + return level +} diff --git a/internal/graphicsutil/mipmap_test.go b/internal/graphicsutil/mipmap_test.go new file mode 100644 index 000000000..6dbb3507c --- /dev/null +++ b/internal/graphicsutil/mipmap_test.go @@ -0,0 +1,64 @@ +// Copyright 2018 The Ebiten Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package graphicsutil_test + +import ( + "math" + "testing" + + . "github.com/hajimehoshi/ebiten/internal/graphicsutil" +) + +func TestMipmapLevel(t *testing.T) { + inf := float32(math.Inf(1)) + cases := []struct { + In float32 + Out int + }{ + {0, -1}, + {1, 0}, + {-1, 0}, + {2, 0}, + {-2, 0}, + {100, 0}, + {-100, 0}, + {1.0 / 2.0, 0}, + {-1.0 / 2.0, 0}, + {1.0 / 4.0, 0}, + {-1.0 / 4.0, 0}, + {math.Nextafter32(1.0/4.0, 0), 1}, + {math.Nextafter32(-1.0/4.0, 0), 1}, + {1.0 / 8.0, 1}, + {-1.0 / 8.0, 1}, + {1.0 / 16.0, 1}, + {-1.0 / 16.0, 1}, + {math.Nextafter32(1.0/16.0, 0), 2}, + {math.Nextafter32(-1.0/16.0, 0), 2}, + {math.Nextafter32(1.0/256.0, 0), 4}, + {math.Nextafter32(-1.0/256.0, 0), 4}, + {math.SmallestNonzeroFloat32, 74}, + {-math.SmallestNonzeroFloat32, 74}, + {inf, 0}, + {-inf, 0}, + } + + for _, c := range cases { + got := MipmapLevel(c.In) + want := c.Out + if got != want { + t.Errorf("MipmapLevel(%v): got %v, want %v", c.In, got, want) + } + } +}