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