github.com/Seikaijyu/gio@v0.0.1/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/Seikaijyu/gio/f32" 13 "github.com/Seikaijyu/gio/internal/ops" 14 "github.com/Seikaijyu/gio/op" 15 "github.com/Seikaijyu/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 }