paint.go raw

   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/p9c/p9/pkg/gel/gio/f32"
  13  	"github.com/p9c/p9/pkg/gel/gio/internal/opconst"
  14  	"github.com/p9c/p9/pkg/gel/gio/op"
  15  	"github.com/p9c/p9/pkg/gel/gio/op/clip"
  16  )
  17  
  18  // ImageOp sets the brush to an image.
  19  //
  20  // Note: the ImageOp may keep a reference to the backing image.
  21  // See NewImageOp for details.
  22  type ImageOp struct {
  23  	uniform bool
  24  	color   color.NRGBA
  25  	src     *image.RGBA
  26  
  27  	// handle is a key to uniquely identify this ImageOp
  28  	// in a map of cached textures.
  29  	handle interface{}
  30  }
  31  
  32  // ColorOp sets the brush to a constant color.
  33  type ColorOp struct {
  34  	Color color.NRGBA
  35  }
  36  
  37  // LinearGradientOp sets the brush to a gradient starting at stop1 with color1 and
  38  // ending at stop2 with color2.
  39  type LinearGradientOp struct {
  40  	Stop1  f32.Point
  41  	Color1 color.NRGBA
  42  	Stop2  f32.Point
  43  	Color2 color.NRGBA
  44  }
  45  
  46  // PaintOp fills fills the current clip area with the current brush.
  47  type PaintOp struct {
  48  }
  49  
  50  // NewImageOp creates an ImageOp backed by src. See
  51  // github.com/p9c/p9/pkg/gel/gio/io/system.FrameEvent for a description of when data
  52  // referenced by operations is safe to re-use.
  53  //
  54  // NewImageOp assumes the backing image is immutable, and may cache a
  55  // copy of its contents in a GPU-friendly way. Create new ImageOps to
  56  // ensure that changes to an image is reflected in the display of
  57  // it.
  58  func NewImageOp(src image.Image) ImageOp {
  59  	switch src := src.(type) {
  60  	case *image.Uniform:
  61  		col := color.NRGBAModel.Convert(src.C).(color.NRGBA)
  62  		return ImageOp{
  63  			uniform: true,
  64  			color:   col,
  65  		}
  66  	case *image.RGBA:
  67  		bounds := src.Bounds()
  68  		if bounds.Min == (image.Point{}) && src.Stride == bounds.Dx()*4 {
  69  			return ImageOp{
  70  				src:    src,
  71  				handle: new(int),
  72  			}
  73  		}
  74  	}
  75  
  76  	sz := src.Bounds().Size()
  77  	// Copy the image into a GPU friendly format.
  78  	dst := image.NewRGBA(image.Rectangle{
  79  		Max: sz,
  80  	})
  81  	draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
  82  	return ImageOp{
  83  		src:    dst,
  84  		handle: new(int),
  85  	}
  86  }
  87  
  88  func (i ImageOp) Size() image.Point {
  89  	if i.src == nil {
  90  		return image.Point{}
  91  	}
  92  	return i.src.Bounds().Size()
  93  }
  94  
  95  func (i ImageOp) Add(o *op.Ops) {
  96  	if i.uniform {
  97  		ColorOp{
  98  			Color: i.color,
  99  		}.Add(o)
 100  		return
 101  	}
 102  	data := o.Write2(opconst.TypeImageLen, i.src, i.handle)
 103  	data[0] = byte(opconst.TypeImage)
 104  }
 105  
 106  func (c ColorOp) Add(o *op.Ops) {
 107  	data := o.Write(opconst.TypeColorLen)
 108  	data[0] = byte(opconst.TypeColor)
 109  	data[1] = c.Color.R
 110  	data[2] = c.Color.G
 111  	data[3] = c.Color.B
 112  	data[4] = c.Color.A
 113  }
 114  
 115  func (c LinearGradientOp) Add(o *op.Ops) {
 116  	data := o.Write(opconst.TypeLinearGradientLen)
 117  	data[0] = byte(opconst.TypeLinearGradient)
 118  
 119  	bo := binary.LittleEndian
 120  	bo.PutUint32(data[1:], math.Float32bits(c.Stop1.X))
 121  	bo.PutUint32(data[5:], math.Float32bits(c.Stop1.Y))
 122  	bo.PutUint32(data[9:], math.Float32bits(c.Stop2.X))
 123  	bo.PutUint32(data[13:], math.Float32bits(c.Stop2.Y))
 124  
 125  	data[17+0] = c.Color1.R
 126  	data[17+1] = c.Color1.G
 127  	data[17+2] = c.Color1.B
 128  	data[17+3] = c.Color1.A
 129  	data[21+0] = c.Color2.R
 130  	data[21+1] = c.Color2.G
 131  	data[21+2] = c.Color2.B
 132  	data[21+3] = c.Color2.A
 133  }
 134  
 135  func (d PaintOp) Add(o *op.Ops) {
 136  	data := o.Write(opconst.TypePaintLen)
 137  	data[0] = byte(opconst.TypePaint)
 138  }
 139  
 140  // FillShape fills the clip shape with a color.
 141  func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) {
 142  	defer op.Save(ops).Load()
 143  	shape.Add(ops)
 144  	Fill(ops, c)
 145  }
 146  
 147  // Fill paints an infinitely large plane with the provided color. It
 148  // is intended to be used with a clip.Op already in place to limit
 149  // the painted area. Use FillShape unless you need to paint several
 150  // times within the same clip.Op.
 151  func Fill(ops *op.Ops, c color.NRGBA) {
 152  	defer op.Save(ops).Load()
 153  	ColorOp{Color: c}.Add(ops)
 154  	PaintOp{}.Add(ops)
 155  }
 156