mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
op/paint: add nearest neighbor scaling
This adds support for nearest neighbor filtering, which can be useful in quite a few scenarios. img := paint.NewImageOp(m) img.Filter = paint.FilterNearest img.Add(gtx.Ops) Fixes: https://todo.sr.ht/~eliasnaur/gio/414 Signed-off-by: Egon Elbre <egonelbre@gmail.com>
This commit is contained in:
+32
-3
@@ -186,10 +186,16 @@ type material struct {
|
|||||||
uvTrans f32.Affine2D
|
uvTrans f32.Affine2D
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
filterLinear = 0
|
||||||
|
filterNearest = 1
|
||||||
|
)
|
||||||
|
|
||||||
// imageOpData is the shadow of paint.ImageOp.
|
// imageOpData is the shadow of paint.ImageOp.
|
||||||
type imageOpData struct {
|
type imageOpData struct {
|
||||||
src *image.RGBA
|
src *image.RGBA
|
||||||
handle interface{}
|
handle interface{}
|
||||||
|
filter byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type linearGradientOpData struct {
|
type linearGradientOpData struct {
|
||||||
@@ -207,6 +213,7 @@ func decodeImageOp(data []byte, refs []interface{}) imageOpData {
|
|||||||
return imageOpData{
|
return imageOpData{
|
||||||
src: refs[0].(*image.RGBA),
|
src: refs[0].(*image.RGBA),
|
||||||
handle: handle,
|
handle: handle,
|
||||||
|
filter: data[1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,19 +461,41 @@ func (g *gpu) Profile() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
|
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
|
||||||
|
type cachekey struct {
|
||||||
|
filter byte
|
||||||
|
handle any
|
||||||
|
}
|
||||||
|
key := cachekey{
|
||||||
|
filter: data.filter,
|
||||||
|
handle: data.handle,
|
||||||
|
}
|
||||||
|
|
||||||
var tex *texture
|
var tex *texture
|
||||||
t, exists := cache.get(data.handle)
|
t, exists := cache.get(key)
|
||||||
if !exists {
|
if !exists {
|
||||||
t = &texture{
|
t = &texture{
|
||||||
src: data.src,
|
src: data.src,
|
||||||
}
|
}
|
||||||
cache.put(data.handle, t)
|
cache.put(key, t)
|
||||||
}
|
}
|
||||||
tex = t.(*texture)
|
tex = t.(*texture)
|
||||||
if tex.tex != nil {
|
if tex.tex != nil {
|
||||||
return tex.tex
|
return tex.tex
|
||||||
}
|
}
|
||||||
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, data.src.Bounds().Dx(), data.src.Bounds().Dy(), driver.FilterLinearMipmapLinear, driver.FilterLinear, driver.BufferBindingTexture)
|
|
||||||
|
var minFilter, magFilter driver.TextureFilter
|
||||||
|
switch data.filter {
|
||||||
|
case filterLinear:
|
||||||
|
minFilter, magFilter = driver.FilterLinearMipmapLinear, driver.FilterLinear
|
||||||
|
case filterNearest:
|
||||||
|
minFilter, magFilter = driver.FilterNearest, driver.FilterNearest
|
||||||
|
}
|
||||||
|
|
||||||
|
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA,
|
||||||
|
data.src.Bounds().Dx(), data.src.Bounds().Dy(),
|
||||||
|
minFilter, magFilter,
|
||||||
|
driver.BufferBindingTexture,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 379 B |
@@ -359,6 +359,73 @@ func TestImageRGBA(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageRGBA_ScaleLinear(t *testing.T) {
|
||||||
|
run(t, func(o *op.Ops) {
|
||||||
|
w := newWindow(t, 128, 128)
|
||||||
|
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
|
||||||
|
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||||
|
|
||||||
|
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||||
|
im.Set(0, 0, colornames.Red)
|
||||||
|
im.Set(1, 0, colornames.Green)
|
||||||
|
im.Set(0, 1, colornames.White)
|
||||||
|
im.Set(1, 1, colornames.Black)
|
||||||
|
|
||||||
|
op := paint.NewImageOp(im)
|
||||||
|
op.Filter = paint.FilterLinear
|
||||||
|
op.Add(o)
|
||||||
|
|
||||||
|
paint.PaintOp{}.Add(o)
|
||||||
|
|
||||||
|
if err := w.Frame(o); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}, func(r result) {
|
||||||
|
r.expect(0, 0, colornames.Red)
|
||||||
|
r.expect(8, 8, colornames.Red)
|
||||||
|
|
||||||
|
// TODO: this currently seems to do srgb scaling
|
||||||
|
// instead of linear rgb scaling,
|
||||||
|
r.expect(64-4, 0, color.RGBA{R: 197, G: 87, B: 0, A: 255})
|
||||||
|
r.expect(64+4, 0, color.RGBA{R: 175, G: 98, B: 0, A: 255})
|
||||||
|
|
||||||
|
r.expect(127, 0, colornames.Green)
|
||||||
|
r.expect(127-8, 8, colornames.Green)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageRGBA_ScaleNearest(t *testing.T) {
|
||||||
|
run(t, func(o *op.Ops) {
|
||||||
|
w := newWindow(t, 128, 128)
|
||||||
|
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||||
|
|
||||||
|
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||||
|
im.Set(0, 0, colornames.Red)
|
||||||
|
im.Set(1, 0, colornames.Green)
|
||||||
|
im.Set(0, 1, colornames.White)
|
||||||
|
im.Set(1, 1, colornames.Black)
|
||||||
|
|
||||||
|
op := paint.NewImageOp(im)
|
||||||
|
op.Filter = paint.FilterNearest
|
||||||
|
op.Add(o)
|
||||||
|
|
||||||
|
paint.PaintOp{}.Add(o)
|
||||||
|
|
||||||
|
if err := w.Frame(o); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}, func(r result) {
|
||||||
|
r.expect(0, 0, colornames.Red)
|
||||||
|
r.expect(8, 8, colornames.Red)
|
||||||
|
|
||||||
|
r.expect(64-4, 0, colornames.Red)
|
||||||
|
r.expect(64+4, 0, colornames.Green)
|
||||||
|
|
||||||
|
r.expect(127, 0, colornames.Green)
|
||||||
|
r.expect(127-8, 8, colornames.Green)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestGapsInPath(t *testing.T) {
|
func TestGapsInPath(t *testing.T) {
|
||||||
ops := new(op.Ops)
|
ops := new(op.Ops)
|
||||||
var p clip.Path
|
var p clip.Path
|
||||||
|
|||||||
+1
-1
@@ -142,7 +142,7 @@ const (
|
|||||||
TypePushOpacityLen = 1 + 4
|
TypePushOpacityLen = 1 + 4
|
||||||
TypePopOpacityLen = 1
|
TypePopOpacityLen = 1
|
||||||
TypeRedrawLen = 1 + 8
|
TypeRedrawLen = 1 + 8
|
||||||
TypeImageLen = 1
|
TypeImageLen = 1 + 1
|
||||||
TypePaintLen = 1
|
TypePaintLen = 1
|
||||||
TypeColorLen = 1 + 4
|
TypeColorLen = 1 + 4
|
||||||
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
||||||
|
|||||||
@@ -15,8 +15,20 @@ import (
|
|||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ImageFilter is the scaling filter for images.
|
||||||
|
type ImageFilter byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FilterLinear uses linear interpolation for scaling.
|
||||||
|
FilterLinear ImageFilter = iota
|
||||||
|
// FilterNearest uses nearest neighbor interpolation for scaling.
|
||||||
|
FilterNearest
|
||||||
|
)
|
||||||
|
|
||||||
// ImageOp sets the brush to an image.
|
// ImageOp sets the brush to an image.
|
||||||
type ImageOp struct {
|
type ImageOp struct {
|
||||||
|
Filter ImageFilter
|
||||||
|
|
||||||
uniform bool
|
uniform bool
|
||||||
color color.NRGBA
|
color color.NRGBA
|
||||||
src *image.RGBA
|
src *image.RGBA
|
||||||
@@ -103,6 +115,7 @@ func (i ImageOp) Add(o *op.Ops) {
|
|||||||
}
|
}
|
||||||
data := ops.Write2(&o.Internal, ops.TypeImageLen, i.src, i.handle)
|
data := ops.Write2(&o.Internal, ops.TypeImageLen, i.src, i.handle)
|
||||||
data[0] = byte(ops.TypeImage)
|
data[0] = byte(ops.TypeImage)
|
||||||
|
data[1] = byte(i.Filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ColorOp) Add(o *op.Ops) {
|
func (c ColorOp) Add(o *op.Ops) {
|
||||||
|
|||||||
Reference in New Issue
Block a user