mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
5fa94ff67b
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>
197 lines
4.8 KiB
Go
197 lines
4.8 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package paint
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"math"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/internal/ops"
|
|
"gioui.org/op"
|
|
"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.
|
|
type ImageOp struct {
|
|
Filter ImageFilter
|
|
|
|
uniform bool
|
|
color color.NRGBA
|
|
src *image.RGBA
|
|
|
|
// handle is a key to uniquely identify this ImageOp
|
|
// in a map of cached textures.
|
|
handle interface{}
|
|
}
|
|
|
|
// ColorOp sets the brush to a constant color.
|
|
type ColorOp struct {
|
|
Color color.NRGBA
|
|
}
|
|
|
|
// LinearGradientOp sets the brush to a gradient starting at stop1 with color1 and
|
|
// ending at stop2 with color2.
|
|
type LinearGradientOp struct {
|
|
Stop1 f32.Point
|
|
Color1 color.NRGBA
|
|
Stop2 f32.Point
|
|
Color2 color.NRGBA
|
|
}
|
|
|
|
// PaintOp fills the current clip area with the current brush.
|
|
type PaintOp struct {
|
|
}
|
|
|
|
// OpacityStack represents an opacity applied to all painting operations
|
|
// until Pop is called.
|
|
type OpacityStack struct {
|
|
id ops.StackID
|
|
macroID uint32
|
|
ops *ops.Ops
|
|
}
|
|
|
|
// NewImageOp creates an ImageOp backed by src.
|
|
//
|
|
// NewImageOp assumes the backing image is immutable, and may cache a
|
|
// copy of its contents in a GPU-friendly way. Create new ImageOps to
|
|
// ensure that changes to an image is reflected in the display of
|
|
// it.
|
|
func NewImageOp(src image.Image) ImageOp {
|
|
switch src := src.(type) {
|
|
case *image.Uniform:
|
|
col := color.NRGBAModel.Convert(src.C).(color.NRGBA)
|
|
return ImageOp{
|
|
uniform: true,
|
|
color: col,
|
|
}
|
|
case *image.RGBA:
|
|
return ImageOp{
|
|
src: src,
|
|
handle: new(int),
|
|
}
|
|
}
|
|
|
|
sz := src.Bounds().Size()
|
|
// Copy the image into a GPU friendly format.
|
|
dst := image.NewRGBA(image.Rectangle{
|
|
Max: sz,
|
|
})
|
|
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
|
|
return ImageOp{
|
|
src: dst,
|
|
handle: new(int),
|
|
}
|
|
}
|
|
|
|
func (i ImageOp) Size() image.Point {
|
|
if i.src == nil {
|
|
return image.Point{}
|
|
}
|
|
return i.src.Bounds().Size()
|
|
}
|
|
|
|
func (i ImageOp) Add(o *op.Ops) {
|
|
if i.uniform {
|
|
ColorOp{
|
|
Color: i.color,
|
|
}.Add(o)
|
|
return
|
|
} else if i.src == nil || i.src.Bounds().Empty() {
|
|
return
|
|
}
|
|
data := ops.Write2(&o.Internal, ops.TypeImageLen, i.src, i.handle)
|
|
data[0] = byte(ops.TypeImage)
|
|
data[1] = byte(i.Filter)
|
|
}
|
|
|
|
func (c ColorOp) Add(o *op.Ops) {
|
|
data := ops.Write(&o.Internal, ops.TypeColorLen)
|
|
data[0] = byte(ops.TypeColor)
|
|
data[1] = c.Color.R
|
|
data[2] = c.Color.G
|
|
data[3] = c.Color.B
|
|
data[4] = c.Color.A
|
|
}
|
|
|
|
func (c LinearGradientOp) Add(o *op.Ops) {
|
|
data := ops.Write(&o.Internal, ops.TypeLinearGradientLen)
|
|
data[0] = byte(ops.TypeLinearGradient)
|
|
|
|
bo := binary.LittleEndian
|
|
bo.PutUint32(data[1:], math.Float32bits(c.Stop1.X))
|
|
bo.PutUint32(data[5:], math.Float32bits(c.Stop1.Y))
|
|
bo.PutUint32(data[9:], math.Float32bits(c.Stop2.X))
|
|
bo.PutUint32(data[13:], math.Float32bits(c.Stop2.Y))
|
|
|
|
data[17+0] = c.Color1.R
|
|
data[17+1] = c.Color1.G
|
|
data[17+2] = c.Color1.B
|
|
data[17+3] = c.Color1.A
|
|
data[21+0] = c.Color2.R
|
|
data[21+1] = c.Color2.G
|
|
data[21+2] = c.Color2.B
|
|
data[21+3] = c.Color2.A
|
|
}
|
|
|
|
func (d PaintOp) Add(o *op.Ops) {
|
|
data := ops.Write(&o.Internal, ops.TypePaintLen)
|
|
data[0] = byte(ops.TypePaint)
|
|
}
|
|
|
|
// FillShape fills the clip shape with a color.
|
|
func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) {
|
|
defer shape.Push(ops).Pop()
|
|
Fill(ops, c)
|
|
}
|
|
|
|
// Fill paints an infinitely large plane with the provided color. It
|
|
// is intended to be used with a clip.Op already in place to limit
|
|
// the painted area. Use FillShape unless you need to paint several
|
|
// times within the same clip.Op.
|
|
func Fill(ops *op.Ops, c color.NRGBA) {
|
|
ColorOp{Color: c}.Add(ops)
|
|
PaintOp{}.Add(ops)
|
|
}
|
|
|
|
// PushOpacity creates a drawing layer with an opacity in the range [0;1].
|
|
// The layer includes every subsequent drawing operation until [OpacityStack.Pop]
|
|
// is called.
|
|
//
|
|
// The layer is drawn in two steps. First, the layer operations are
|
|
// drawn to a separate image. Then, the image is blended on top of
|
|
// the frame, with the opacity used as the blending factor.
|
|
func PushOpacity(o *op.Ops, opacity float32) OpacityStack {
|
|
if opacity > 1 {
|
|
opacity = 1
|
|
}
|
|
if opacity < 0 {
|
|
opacity = 0
|
|
}
|
|
id, macroID := ops.PushOp(&o.Internal, ops.OpacityStack)
|
|
data := ops.Write(&o.Internal, ops.TypePushOpacityLen)
|
|
bo := binary.LittleEndian
|
|
data[0] = byte(ops.TypePushOpacity)
|
|
bo.PutUint32(data[1:], math.Float32bits(opacity))
|
|
return OpacityStack{ops: &o.Internal, id: id, macroID: macroID}
|
|
}
|
|
|
|
func (t OpacityStack) Pop() {
|
|
ops.PopOp(t.ops, ops.OpacityStack, t.id, t.macroID)
|
|
data := ops.Write(t.ops, ops.TypePopOpacityLen)
|
|
data[0] = byte(ops.TypePopOpacity)
|
|
}
|