graphics: Bug fix: Violating edge pixels with linear filter

Fixes #724
This commit is contained in:
Hajime Hoshi 2018-10-26 00:48:49 +09:00
parent 58f4feda8d
commit 8b82667df1
3 changed files with 103 additions and 61 deletions

View File

@ -41,6 +41,7 @@ const (
var ( var (
gophersImage *ebiten.Image gophersImage *ebiten.Image
rotate = false rotate = false
clip = false
counter = 0 counter = 0
) )
@ -53,13 +54,20 @@ func update(screen *ebiten.Image) error {
if inpututil.IsKeyJustPressed(ebiten.KeyR) { if inpututil.IsKeyJustPressed(ebiten.KeyR) {
rotate = !rotate rotate = !rotate
} }
if inpututil.IsKeyJustPressed(ebiten.KeyC) {
clip = !clip
}
if ebiten.IsDrawingSkipped() { if ebiten.IsDrawingSkipped() {
return nil return nil
} }
s := 1.5 / math.Pow(1.01, float64(counter)) s := 1.5 / math.Pow(1.01, float64(counter))
ebitenutil.DebugPrint(screen, fmt.Sprintf("Minifying images (Nearest filter vs Linear filter): Press R to rotate the images.\nScale: %0.2f", s)) msg := fmt.Sprintf(`Minifying images (Nearest filter vs Linear filter):
Press R to rotate the images.
Press C to clip the images.
Scale: %0.2f`, s)
ebitenutil.DebugPrint(screen, msg)
for i, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} { for i, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} {
w, h := gophersImage.Size() w, h := gophersImage.Size()
@ -73,6 +81,10 @@ func update(screen *ebiten.Image) error {
op.GeoM.Scale(s, s) op.GeoM.Scale(s, s)
op.GeoM.Translate(32+float64(i*w)*s+float64(i*4), 64) op.GeoM.Translate(32+float64(i*w)*s+float64(i*4), 64)
op.Filter = f op.Filter = f
if clip {
r := image.Rect(10, 10, 100, 100)
op.SourceRect = &r
}
screen.DrawImage(gophersImage, op) screen.DrawImage(gophersImage, op)
} }

View File

@ -37,12 +37,13 @@ func init() {
type mipmap struct { type mipmap struct {
orig *shareable.Image orig *shareable.Image
imgs []*shareable.Image imgs map[image.Rectangle][]*shareable.Image
} }
func newMipmap(s *shareable.Image) *mipmap { func newMipmap(s *shareable.Image) *mipmap {
return &mipmap{ return &mipmap{
orig: s, orig: s,
imgs: map[image.Rectangle][]*shareable.Image{},
} }
} }
@ -50,18 +51,26 @@ func (m *mipmap) original() *shareable.Image {
return m.orig return m.orig
} }
func (m *mipmap) level(level int) *shareable.Image { func (m *mipmap) level(r image.Rectangle, level int) *shareable.Image {
if level == 0 { if level == 0 {
return m.orig return m.orig
} }
idx := level - 1 imgs, ok := m.imgs[r]
w, h := m.orig.Size() if !ok {
if len(m.imgs) > 0 { imgs = []*shareable.Image{}
w, h = m.imgs[len(m.imgs)-1].Size() m.imgs[r] = imgs
} }
for len(m.imgs) < idx+1 { idx := level - 1
src := m.level(len(m.imgs))
size := r.Size()
w, h := size.X, size.Y
if len(imgs) > 0 {
w, h = imgs[len(imgs)-1].Size()
}
for len(imgs) < idx+1 {
src := m.level(r, len(imgs))
w2 := w / 2 w2 := w / 2
h2 := h / 2 h2 := h / 2
if w2 == 0 || h2 == 0 { if w2 == 0 || h2 == 0 {
@ -73,17 +82,24 @@ func (m *mipmap) level(level int) *shareable.Image {
} else { } else {
s = shareable.NewImage(w2, h2) s = shareable.NewImage(w2, h2)
} }
vs := src.QuadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1) var vs []float32
if len(imgs) == 0 {
vs = src.QuadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
} else {
vs = src.QuadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
}
is := graphicsutil.QuadIndices() is := graphicsutil.QuadIndices()
s.DrawImage(src, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterLinear) s.DrawImage(src, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterLinear)
m.imgs = append(m.imgs, s) imgs = append(imgs, s)
w = w2 w = w2
h = h2 h = h2
} }
if len(m.imgs) <= idx { m.imgs[r] = imgs
if len(imgs) <= idx {
return nil return nil
} }
return m.imgs[idx] return imgs[idx]
} }
func (m *mipmap) isDisposed() bool { func (m *mipmap) isDisposed() bool {
@ -97,10 +113,12 @@ func (m *mipmap) dispose() {
} }
func (m *mipmap) disposeMipmaps() { func (m *mipmap) disposeMipmaps() {
for _, img := range m.imgs { for _, a := range m.imgs {
for _, img := range a {
img.Dispose() img.Dispose()
} }
m.imgs = nil }
m.imgs = map[image.Rectangle][]*shareable.Image{}
} }
// Image represents a rectangle set of pixels. // Image represents a rectangle set of pixels.
@ -346,19 +364,8 @@ func (i *Image) drawImage(img *Image, options *DrawImageOptions) {
level = 6 level = 6
} }
if level > 0 { // TODO: Move this logic to mipmap?
s := 1 << uint(level) if src := img.mipmap.level(image.Rect(sx0, sy0, sx1, sy1), level); src != nil {
a *= float32(s)
b *= float32(s)
c *= float32(s)
d *= float32(s)
sx0 = sx0 / s
sy0 = sy0 / s
sx1 = sx1 / s
sy1 = sy1 / s
}
if src := img.mipmap.level(level); src != nil {
colorm := options.ColorM.impl colorm := options.ColorM.impl
cr, cg, cb, ca := float32(1), float32(1), float32(1), float32(1) cr, cg, cb, ca := float32(1), float32(1), float32(1), float32(1)
if colorm.ScaleOnly() { if colorm.ScaleOnly() {
@ -368,12 +375,24 @@ func (i *Image) drawImage(img *Image, options *DrawImageOptions) {
cb = body[10] cb = body[10]
ca = body[15] ca = body[15]
} }
vs := src.QuadVertices(sx0, sy0, sx1, sy1, a, b, c, d, tx, ty, cr, cg, cb, ca) var vs []float32
if level == 0 {
vs = src.QuadVertices(sx0, sy0, sx1, sy1, a, b, c, d, tx, ty, cr, cg, cb, ca)
} else {
w, h := src.Size()
s := 1 << uint(level)
a *= float32(s)
b *= float32(s)
c *= float32(s)
d *= float32(s)
vs = src.QuadVertices(0, 0, w, h, a, b, c, d, tx, ty, cr, cg, cb, ca)
}
is := graphicsutil.QuadIndices() is := graphicsutil.QuadIndices()
if colorm.ScaleOnly() { if colorm.ScaleOnly() {
colorm = nil colorm = nil
} }
i.mipmap.original().DrawImage(src, vs, is, colorm, mode, filter) i.mipmap.original().DrawImage(src, vs, is, colorm, mode, filter)
} }
i.disposeMipmaps() i.disposeMipmaps()

View File

@ -508,11 +508,16 @@ func TestImageFill(t *testing.T) {
} }
} }
// Issue #317, #558 // Issue #317, #558, #724
func TestImageEdge(t *testing.T) { func TestImageEdge(t *testing.T) {
const ( const (
img0Width = 16 img0Width = 16
img0Height = 16 img0Height = 16
img0InnerWidth = 6
img0InnerHeight = 6
img0OffsetWidth = (img0Width - img0InnerWidth) / 2
img0OffsetHeight = (img0Height - img0InnerHeight) / 2
img1Width = 32 img1Width = 32
img1Height = 32 img1Height = 32
) )
@ -522,7 +527,8 @@ func TestImageEdge(t *testing.T) {
for i := 0; i < img0Width; i++ { for i := 0; i < img0Width; i++ {
idx := 4 * (i + j*img0Width) idx := 4 * (i + j*img0Width)
switch { switch {
case j < img0Height/2: case img0OffsetWidth <= i && i < img0Width-img0OffsetWidth &&
img0InnerHeight <= j && j < img0Height-img0InnerHeight:
pixels[idx] = 0xff pixels[idx] = 0xff
pixels[idx+1] = 0 pixels[idx+1] = 0
pixels[idx+2] = 0 pixels[idx+2] = 0
@ -544,18 +550,22 @@ func TestImageEdge(t *testing.T) {
for a := 0; a < 1440; a++ { for a := 0; a < 1440; a++ {
angles = append(angles, float64(a)/1440*2*math.Pi) angles = append(angles, float64(a)/1440*2*math.Pi)
} }
for a := 0; a < 4096; a++ { for a := 0; a < 4096; a += 3 {
// a++ should be fine, but it takes long to test.
angles = append(angles, float64(a)/4096*2*math.Pi) angles = append(angles, float64(a)/4096*2*math.Pi)
} }
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() img1.Clear()
op := &DrawImageOptions{} op := &DrawImageOptions{}
w, h := img0.Size() r := image.Rect(img0OffsetWidth, img0InnerHeight, img0Width-img0OffsetWidth, img0Height-img0InnerHeight)
r := image.Rect(0, 0, w, h/2)
op.SourceRect = &r op.SourceRect = &r
op.GeoM.Translate(-float64(img0Width)/2, -float64(img0Height)/2)
w, h := img0.Size()
op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
op.GeoM.Scale(s, s)
op.GeoM.Rotate(a) op.GeoM.Rotate(a)
op.GeoM.Translate(img1Width/2, img1Height/2) op.GeoM.Translate(img1Width/2, img1Height/2)
op.Filter = f op.Filter = f
@ -577,7 +587,8 @@ func TestImageEdge(t *testing.T) {
continue continue
} }
} }
t.Errorf("img1.At(%d, %d) (filter: %d, angle: %f) want: red or transparent, got: %v", i, j, f, a, c) t.Errorf("img1.At(%d, %d) (filter: %d, scale: %f, angle: %f) want: red or transparent, got: %v", i, j, f, s, a, c)
}
} }
} }
} }