Skip to content

Commit 5fa94ff

Browse files
egonelbreeliasnaur
authored andcommitted
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 <[email protected]>
1 parent 23b6f06 commit 5fa94ff

File tree

6 files changed

+113
-4
lines changed

6 files changed

+113
-4
lines changed

gpu/gpu.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,16 @@ type material struct {
186186
uvTrans f32.Affine2D
187187
}
188188

189+
const (
190+
filterLinear = 0
191+
filterNearest = 1
192+
)
193+
189194
// imageOpData is the shadow of paint.ImageOp.
190195
type imageOpData struct {
191196
src *image.RGBA
192197
handle interface{}
198+
filter byte
193199
}
194200

195201
type linearGradientOpData struct {
@@ -207,6 +213,7 @@ func decodeImageOp(data []byte, refs []interface{}) imageOpData {
207213
return imageOpData{
208214
src: refs[0].(*image.RGBA),
209215
handle: handle,
216+
filter: data[1],
210217
}
211218
}
212219

@@ -454,19 +461,41 @@ func (g *gpu) Profile() string {
454461
}
455462

456463
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
464+
type cachekey struct {
465+
filter byte
466+
handle any
467+
}
468+
key := cachekey{
469+
filter: data.filter,
470+
handle: data.handle,
471+
}
472+
457473
var tex *texture
458-
t, exists := cache.get(data.handle)
474+
t, exists := cache.get(key)
459475
if !exists {
460476
t = &texture{
461477
src: data.src,
462478
}
463-
cache.put(data.handle, t)
479+
cache.put(key, t)
464480
}
465481
tex = t.(*texture)
466482
if tex.tex != nil {
467483
return tex.tex
468484
}
469-
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, data.src.Bounds().Dx(), data.src.Bounds().Dy(), driver.FilterLinearMipmapLinear, driver.FilterLinear, driver.BufferBindingTexture)
485+
486+
var minFilter, magFilter driver.TextureFilter
487+
switch data.filter {
488+
case filterLinear:
489+
minFilter, magFilter = driver.FilterLinearMipmapLinear, driver.FilterLinear
490+
case filterNearest:
491+
minFilter, magFilter = driver.FilterNearest, driver.FilterNearest
492+
}
493+
494+
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA,
495+
data.src.Bounds().Dx(), data.src.Bounds().Dy(),
496+
minFilter, magFilter,
497+
driver.BufferBindingTexture,
498+
)
470499
if err != nil {
471500
panic(err)
472501
}
2.92 KB
Loading
379 Bytes
Loading

gpu/internal/rendertest/render_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,73 @@ func TestImageRGBA(t *testing.T) {
359359
})
360360
}
361361

362+
func TestImageRGBA_ScaleLinear(t *testing.T) {
363+
run(t, func(o *op.Ops) {
364+
w := newWindow(t, 128, 128)
365+
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
366+
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
367+
368+
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
369+
im.Set(0, 0, colornames.Red)
370+
im.Set(1, 0, colornames.Green)
371+
im.Set(0, 1, colornames.White)
372+
im.Set(1, 1, colornames.Black)
373+
374+
op := paint.NewImageOp(im)
375+
op.Filter = paint.FilterLinear
376+
op.Add(o)
377+
378+
paint.PaintOp{}.Add(o)
379+
380+
if err := w.Frame(o); err != nil {
381+
t.Error(err)
382+
}
383+
}, func(r result) {
384+
r.expect(0, 0, colornames.Red)
385+
r.expect(8, 8, colornames.Red)
386+
387+
// TODO: this currently seems to do srgb scaling
388+
// instead of linear rgb scaling,
389+
r.expect(64-4, 0, color.RGBA{R: 197, G: 87, B: 0, A: 255})
390+
r.expect(64+4, 0, color.RGBA{R: 175, G: 98, B: 0, A: 255})
391+
392+
r.expect(127, 0, colornames.Green)
393+
r.expect(127-8, 8, colornames.Green)
394+
})
395+
}
396+
397+
func TestImageRGBA_ScaleNearest(t *testing.T) {
398+
run(t, func(o *op.Ops) {
399+
w := newWindow(t, 128, 128)
400+
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
401+
402+
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
403+
im.Set(0, 0, colornames.Red)
404+
im.Set(1, 0, colornames.Green)
405+
im.Set(0, 1, colornames.White)
406+
im.Set(1, 1, colornames.Black)
407+
408+
op := paint.NewImageOp(im)
409+
op.Filter = paint.FilterNearest
410+
op.Add(o)
411+
412+
paint.PaintOp{}.Add(o)
413+
414+
if err := w.Frame(o); err != nil {
415+
t.Error(err)
416+
}
417+
}, func(r result) {
418+
r.expect(0, 0, colornames.Red)
419+
r.expect(8, 8, colornames.Red)
420+
421+
r.expect(64-4, 0, colornames.Red)
422+
r.expect(64+4, 0, colornames.Green)
423+
424+
r.expect(127, 0, colornames.Green)
425+
r.expect(127-8, 8, colornames.Green)
426+
})
427+
}
428+
362429
func TestGapsInPath(t *testing.T) {
363430
ops := new(op.Ops)
364431
var p clip.Path

internal/ops/ops.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ const (
142142
TypePushOpacityLen = 1 + 4
143143
TypePopOpacityLen = 1
144144
TypeRedrawLen = 1 + 8
145-
TypeImageLen = 1
145+
TypeImageLen = 1 + 1
146146
TypePaintLen = 1
147147
TypeColorLen = 1 + 4
148148
TypeLinearGradientLen = 1 + 8*2 + 4*2

op/paint/paint.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,20 @@ import (
1515
"gioui.org/op/clip"
1616
)
1717

18+
// ImageFilter is the scaling filter for images.
19+
type ImageFilter byte
20+
21+
const (
22+
// FilterLinear uses linear interpolation for scaling.
23+
FilterLinear ImageFilter = iota
24+
// FilterNearest uses nearest neighbor interpolation for scaling.
25+
FilterNearest
26+
)
27+
1828
// ImageOp sets the brush to an image.
1929
type ImageOp struct {
30+
Filter ImageFilter
31+
2032
uniform bool
2133
color color.NRGBA
2234
src *image.RGBA
@@ -103,6 +115,7 @@ func (i ImageOp) Add(o *op.Ops) {
103115
}
104116
data := ops.Write2(&o.Internal, ops.TypeImageLen, i.src, i.handle)
105117
data[0] = byte(ops.TypeImage)
118+
data[1] = byte(i.Filter)
106119
}
107120

108121
func (c ColorOp) Add(o *op.Ops) {

0 commit comments

Comments
 (0)