github.com/utopiagio/gio@v0.0.8/op/paint/paint.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package paint
     4  
     5  import (
     6  	"encoding/binary"
     7  	"image"
     8  	"image/color"
     9  	"image/draw"
    10  	"math"
    11  
    12  	"github.com/utopiagio/gio/f32"
    13  	"github.com/utopiagio/gio/internal/ops"
    14  	"github.com/utopiagio/gio/op"
    15  	"github.com/utopiagio/gio/op/clip"
    16  )
    17  
    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  
    28  // ImageOp sets the brush to an image.
    29  type ImageOp struct {
    30  	Filter ImageFilter
    31  
    32  	uniform bool
    33  	color   color.NRGBA
    34  	src     *image.RGBA
    35  
    36  	// handle is a key to uniquely identify this ImageOp
    37  	// in a map of cached textures.
    38  	handle interface{}
    39  }
    40  
    41  // ColorOp sets the brush to a constant color.
    42  type ColorOp struct {
    43  	Color color.NRGBA
    44  }
    45  
    46  // LinearGradientOp sets the brush to a gradient starting at stop1 with color1 and
    47  // ending at stop2 with color2.
    48  type LinearGradientOp struct {
    49  	Stop1  f32.Point
    50  	Color1 color.NRGBA
    51  	Stop2  f32.Point
    52  	Color2 color.NRGBA
    53  }
    54  
    55  // PaintOp fills the current clip area with the current brush.
    56  type PaintOp struct {
    57  }
    58  
    59  // OpacityStack represents an opacity applied to all painting operations
    60  // until Pop is called.
    61  type OpacityStack struct {
    62  	id      ops.StackID
    63  	macroID uint32
    64  	ops     *ops.Ops
    65  }
    66  
    67  // NewImageOp creates an ImageOp backed by src.
    68  //
    69  // NewImageOp assumes the backing image is immutable, and may cache a
    70  // copy of its contents in a GPU-friendly way. Create new ImageOps to
    71  // ensure that changes to an image is reflected in the display of
    72  // it.
    73  func NewImageOp(src image.Image) ImageOp {
    74  	switch src := src.(type) {
    75  	case *image.Uniform:
    76  		col := color.NRGBAModel.Convert(src.C).(color.NRGBA)
    77  		return ImageOp{
    78  			uniform: true,
    79  			color:   col,
    80  		}
    81  	case *image.RGBA:
    82  		return ImageOp{
    83  			src:    src,
    84  			handle: new(int),
    85  		}
    86  	}
    87  
    88  	sz := src.Bounds().Size()
    89  	// Copy the image into a GPU friendly format.
    90  	dst := image.NewRGBA(image.Rectangle{
    91  		Max: sz,
    92  	})
    93  	draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
    94  	return ImageOp{
    95  		src:    dst,
    96  		handle: new(int),
    97  	}
    98  }
    99  
   100  func (i ImageOp) Size() image.Point {
   101  	if i.src == nil {
   102  		return image.Point{}
   103  	}
   104  	return i.src.Bounds().Size()
   105  }
   106  
   107  func (i ImageOp) Add(o *op.Ops) {
   108  	if i.uniform {
   109  		ColorOp{
   110  			Color: i.color,
   111  		}.Add(o)
   112  		return
   113  	} else if i.src == nil || i.src.Bounds().Empty() {
   114  		return
   115  	}
   116  	data := ops.Write2(&o.Internal, ops.TypeImageLen, i.src, i.handle)
   117  	data[0] = byte(ops.TypeImage)
   118  	data[1] = byte(i.Filter)
   119  }
   120  
   121  func (c ColorOp) Add(o *op.Ops) {
   122  	data := ops.Write(&o.Internal, ops.TypeColorLen)
   123  	data[0] = byte(ops.TypeColor)
   124  	data[1] = c.Color.R
   125  	data[2] = c.Color.G
   126  	data[3] = c.Color.B
   127  	data[4] = c.Color.A
   128  }
   129  
   130  func (c LinearGradientOp) Add(o *op.Ops) {
   131  	data := ops.Write(&o.Internal, ops.TypeLinearGradientLen)
   132  	data[0] = byte(ops.TypeLinearGradient)
   133  
   134  	bo := binary.LittleEndian
   135  	bo.PutUint32(data[1:], math.Float32bits(c.Stop1.X))
   136  	bo.PutUint32(data[5:], math.Float32bits(c.Stop1.Y))
   137  	bo.PutUint32(data[9:], math.Float32bits(c.Stop2.X))
   138  	bo.PutUint32(data[13:], math.Float32bits(c.Stop2.Y))
   139  
   140  	data[17+0] = c.Color1.R
   141  	data[17+1] = c.Color1.G
   142  	data[17+2] = c.Color1.B
   143  	data[17+3] = c.Color1.A
   144  	data[21+0] = c.Color2.R
   145  	data[21+1] = c.Color2.G
   146  	data[21+2] = c.Color2.B
   147  	data[21+3] = c.Color2.A
   148  }
   149  
   150  func (d PaintOp) Add(o *op.Ops) {
   151  	data := ops.Write(&o.Internal, ops.TypePaintLen)
   152  	data[0] = byte(ops.TypePaint)
   153  }
   154  
   155  // FillShape fills the clip shape with a color.
   156  func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) {
   157  	defer shape.Push(ops).Pop()
   158  	Fill(ops, c)
   159  }
   160  
   161  // Fill paints an infinitely large plane with the provided color. It
   162  // is intended to be used with a clip.Op already in place to limit
   163  // the painted area. Use FillShape unless you need to paint several
   164  // times within the same clip.Op.
   165  func Fill(ops *op.Ops, c color.NRGBA) {
   166  	ColorOp{Color: c}.Add(ops)
   167  	PaintOp{}.Add(ops)
   168  }
   169  
   170  // PushOpacity creates a drawing layer with an opacity in the range [0;1].
   171  // The layer includes every subsequent drawing operation until [OpacityStack.Pop]
   172  // is called.
   173  //
   174  // The layer is drawn in two steps. First, the layer operations are
   175  // drawn to a separate image. Then, the image is blended on top of
   176  // the frame, with the opacity used as the blending factor.
   177  func PushOpacity(o *op.Ops, opacity float32) OpacityStack {
   178  	if opacity > 1 {
   179  		opacity = 1
   180  	}
   181  	if opacity < 0 {
   182  		opacity = 0
   183  	}
   184  	id, macroID := ops.PushOp(&o.Internal, ops.OpacityStack)
   185  	data := ops.Write(&o.Internal, ops.TypePushOpacityLen)
   186  	bo := binary.LittleEndian
   187  	data[0] = byte(ops.TypePushOpacity)
   188  	bo.PutUint32(data[1:], math.Float32bits(opacity))
   189  	return OpacityStack{ops: &o.Internal, id: id, macroID: macroID}
   190  }
   191  
   192  func (t OpacityStack) Pop() {
   193  	ops.PopOp(t.ops, ops.OpacityStack, t.id, t.macroID)
   194  	data := ops.Write(t.ops, ops.TypePopOpacityLen)
   195  	data[0] = byte(ops.TypePopOpacity)
   196  }