1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 // Public op API for Gio. Merges op.go, clip/*.go, and paint/*.go into
4 // package gio alongside the internal ops (ops.mx).
5 //
6 // Name mapping from the original multi-package layout:
7 // op.Ops -> OpList (wraps internal Ops via .Internal field)
8 // op.MacroOp -> MacroOp
9 // op.CallOp -> CallOp
10 // op.TransformOp -> TransformOp
11 // op.TransformStack-> TransformStack
12 // op.InvalidateCmd -> InvalidateCmd
13 // clip.Op -> ClipOp
14 // clip.Stack -> ClipStack
15 // clip.PathSpec -> PathSpec
16 // clip.Path -> ClipPath
17 // clip.Stroke -> ClipStroke
18 // clip.Outline -> ClipOutline
19 // clip.Rect -> ClipRect
20 // clip.RRect -> ClipRRect
21 // clip.Ellipse -> ClipEllipse
22 // paint.ImageFilter -> ImageFilter
23 // paint.ImageOp -> PaintImageOp
24 // paint.ColorOp -> PaintColorOp
25 // paint.LinearGradientOp -> PaintLinearGradientOp
26 // paint.PaintOp -> PaintPaintOp
27 // paint.OpacityStack -> PaintOpacityStack
28
29 package gio
30
31 import (
32 "encoding/binary"
33 "hash"
34 "hash/fnv"
35 "image"
36 "image/color"
37 "image/draw"
38 "math"
39 "time"
40 )
41
42 // ---------------------------------------------------------------------------
43 // op.go - OpList and friends
44 // ---------------------------------------------------------------------------
45
46 // OpList is the public ops list (was op.Ops). It wraps the internal Ops.
47 type OpList struct {
48 Internal Ops
49 }
50
51 // MacroOp records a list of operations for later use.
52 type MacroOp struct {
53 ops *Ops
54 id StackID
55 pc PC
56 }
57
58 // CallOp invokes the operations recorded by Record.
59 type CallOp struct {
60 ops *Ops
61 start PC
62 end PC
63 }
64
65 // InvalidateCmd requests a redraw at the given time. Use
66 // the zero value to request an immediate redraw.
67 type InvalidateCmd struct {
68 At time.Time
69 }
70
71 func (InvalidateCmd) ImplementsCommand() {}
72
73 // TransformOp represents a transformation that can be pushed on the
74 // transformation stack.
75 type TransformOp struct {
76 t Affine2D
77 }
78
79 // TransformStack represents a TransformOp pushed on the transformation stack.
80 type TransformStack struct {
81 id StackID
82 macroID uint32
83 ops *Ops
84 }
85
86 // Defer executes c after all other operations have completed, including
87 // previously deferred operations.
88 func Defer(o *OpList, c CallOp) {
89 if c.ops == nil {
90 return
91 }
92 state := SaveOps(&o.Internal)
93 m := Record(o)
94 state.Load()
95 c.Add(o)
96 c = m.Stop()
97 data := WriteOps(&o.Internal, TypeDeferLen)
98 data[0] = byte(TypeDefer)
99 c.Add(o)
100 }
101
102 // Reset the OpList, preparing it for re-use.
103 func (o *OpList) Reset() {
104 ResetOps(&o.Internal)
105 }
106
107 // Record a macro of operations.
108 func Record(o *OpList) MacroOp {
109 m := MacroOp{
110 ops: &o.Internal,
111 id: PushMacro(&o.Internal),
112 pc: PCFor(&o.Internal),
113 }
114 data := WriteOps(m.ops, TypeMacroLen)
115 data[0] = byte(TypeMacro)
116 return m
117 }
118
119 // Stop ends a previously started recording and returns a CallOp for replaying it.
120 func (m MacroOp) Stop() CallOp {
121 PopMacro(m.ops, m.id)
122 FillMacro(m.ops, m.pc)
123 return CallOp{
124 ops: m.ops,
125 start: m.pc.Add(TypeMacro),
126 end: PCFor(m.ops),
127 }
128 }
129
130 // Add the recorded list of operations.
131 func (c CallOp) Add(o *OpList) {
132 if c.ops == nil {
133 return
134 }
135 AddCall(&o.Internal, c.ops, c.start, c.end)
136 }
137
138 // Offset converts an offset to a TransformOp.
139 func Offset(off image.Point) TransformOp {
140 offf := Pt(float32(off.X), float32(off.Y))
141 return AffineTransform(AffineId().Offset(offf))
142 }
143
144 // AffineTransform creates a TransformOp representing the transformation a.
145 func AffineTransform(a Affine2D) TransformOp {
146 return TransformOp{t: a}
147 }
148
149 // Push the current transformation to the stack and then multiply the
150 // current transformation with t.
151 func (t TransformOp) Push(o *OpList) TransformStack {
152 id, macroID := PushOp(&o.Internal, TransStack)
153 t.add(o, true)
154 return TransformStack{ops: &o.Internal, id: id, macroID: macroID}
155 }
156
157 // Add is like Push except it doesn't push the current transformation to the
158 // stack.
159 func (t TransformOp) Add(o *OpList) {
160 t.add(o, false)
161 }
162
163 func (t TransformOp) add(o *OpList, push bool) {
164 data := WriteOps(&o.Internal, TypeTransformLen)
165 data[0] = byte(TypeTransform)
166 if push {
167 data[1] = 1
168 }
169 bo := binary.LittleEndian()
170 a, b, c, d, e, f := t.t.Elems()
171 bo.PutUint32(data[2:], math.Float32bits(a))
172 bo.PutUint32(data[2+4*1:], math.Float32bits(b))
173 bo.PutUint32(data[2+4*2:], math.Float32bits(c))
174 bo.PutUint32(data[2+4*3:], math.Float32bits(d))
175 bo.PutUint32(data[2+4*4:], math.Float32bits(e))
176 bo.PutUint32(data[2+4*5:], math.Float32bits(f))
177 }
178
179 func (t TransformStack) Pop() {
180 PopOp(t.ops, TransStack, t.id, t.macroID)
181 data := WriteOps(t.ops, TypePopTransformLen)
182 data[0] = byte(TypePopTransform)
183 }
184
185 // ---------------------------------------------------------------------------
186 // clip/clip.go - ClipOp, ClipStack, PathSpec, ClipPath
187 // ---------------------------------------------------------------------------
188
189 // ClipOp represents a clip area. ClipOp intersects the current clip area
190 // with itself. (was clip.Op)
191 type ClipOp struct {
192 path PathSpec
193 outline bool
194 width float32
195 }
196
197 // ClipStack represents a ClipOp pushed on the clip stack. (was clip.Stack)
198 type ClipStack struct {
199 ops *Ops
200 id StackID
201 macroID uint32
202 }
203
204 // PathSpec describes a completed path for use in clip operations.
205 type PathSpec struct {
206 spec CallOp
207 hasSegments bool
208 bounds image.Rectangle
209 shape Shape
210 hash uint64
211 }
212
213 // ClipPath constructs a clip path described by lines and Bezier curves.
214 // (was clip.Path)
215 type ClipPath struct {
216 ops *Ops
217 contour int32
218 pen Point
219 macro MacroOp
220 start Point
221 hasSegments bool
222 bounds Rectangle
223 hash hash.Hash64
224 }
225
226 // Push saves the current clip state on the stack and updates the current
227 // state to the intersection of the current p.
228 func (p ClipOp) Push(o *OpList) ClipStack {
229 id, macroID := PushOp(&o.Internal, ClipStackKind)
230 p.add(o)
231 return ClipStack{ops: &o.Internal, id: id, macroID: macroID}
232 }
233
234 func (p ClipOp) add(o *OpList) {
235 path := p.path
236
237 if !path.hasSegments && p.width > 0 {
238 switch p.path.shape {
239 case ShapeRect:
240 b := FRect(path.bounds)
241 var rect ClipPath
242 rect.Begin(o)
243 rect.MoveTo(b.Min)
244 rect.LineTo(Pt(b.Max.X, b.Min.Y))
245 rect.LineTo(b.Max)
246 rect.LineTo(Pt(b.Min.X, b.Max.Y))
247 rect.Close()
248 path = rect.End()
249 case ShapePath:
250 // Nothing to do.
251 default:
252 panic("invalid empty path for shape")
253 }
254 }
255 bo := binary.LittleEndian()
256 if path.hasSegments {
257 data := WriteOps(&o.Internal, TypePathLen)
258 data[0] = byte(TypePath)
259 bo.PutUint64(data[1:], path.hash)
260 path.spec.Add(o)
261 }
262
263 bounds := path.bounds
264 if p.width > 0 {
265 half := int(p.width*.5 + .5)
266 bounds.Min.X -= half
267 bounds.Min.Y -= half
268 bounds.Max.X += half
269 bounds.Max.Y += half
270 data := WriteOps(&o.Internal, TypeStrokeLen)
271 data[0] = byte(TypeStroke)
272 bo.PutUint32(data[1:], math.Float32bits(p.width))
273 }
274
275 data := WriteOps(&o.Internal, TypeClipLen)
276 data[0] = byte(TypeClip)
277 bo.PutUint32(data[1:], uint32(bounds.Min.X))
278 bo.PutUint32(data[5:], uint32(bounds.Min.Y))
279 bo.PutUint32(data[9:], uint32(bounds.Max.X))
280 bo.PutUint32(data[13:], uint32(bounds.Max.Y))
281 if p.outline {
282 data[17] = byte(1)
283 }
284 data[18] = byte(path.shape)
285 }
286
287 func (s ClipStack) Pop() {
288 PopOp(s.ops, ClipStackKind, s.id, s.macroID)
289 data := WriteOps(s.ops, TypePopClipLen)
290 data[0] = byte(TypePopClip)
291 }
292
293 // Pos returns the current pen position.
294 func (p *ClipPath) Pos() Point { return p.pen }
295
296 // Begin the path, storing the path data and final op into o.
297 func (p *ClipPath) Begin(o *OpList) {
298 h := fnv.New64a()
299 *p = ClipPath{
300 ops: &o.Internal,
301 macro: Record(o),
302 contour: 1,
303 hash: h,
304 }
305 BeginMulti(p.ops)
306 data := WriteMulti(p.ops, TypeAuxLen)
307 data[0] = byte(TypeAux)
308 }
309
310 // End returns a PathSpec ready to use in clipping operations.
311 func (p *ClipPath) End() PathSpec {
312 p.gap()
313 c := p.macro.Stop()
314 EndMulti(p.ops)
315 return PathSpec{
316 spec: c,
317 hasSegments: p.hasSegments,
318 bounds: p.bounds.Round(),
319 hash: p.hash.Sum64(),
320 }
321 }
322
323 // Move moves the pen by delta.
324 func (p *ClipPath) Move(delta Point) {
325 to := delta.Add(p.pen)
326 p.MoveTo(to)
327 }
328
329 // MoveTo moves the pen to the specified absolute coordinate.
330 func (p *ClipPath) MoveTo(to Point) {
331 if p.pen == to {
332 return
333 }
334 p.gap()
335 p.end()
336 p.pen = to
337 p.start = to
338 }
339
340 func (p *ClipPath) gap() {
341 if p.pen != p.start {
342 data := WriteMulti(p.ops, CommandSize+4)
343 bo := binary.LittleEndian()
344 bo.PutUint32(data[0:], uint32(p.contour))
345 p.cmd(data[4:], Gap(p.pen, p.start))
346 }
347 }
348
349 func (p *ClipPath) end() {
350 p.contour++
351 }
352
353 // Line moves the pen by delta, recording a line.
354 func (p *ClipPath) Line(delta Point) {
355 to := delta.Add(p.pen)
356 p.LineTo(to)
357 }
358
359 // LineTo moves the pen to 'to', recording a line.
360 func (p *ClipPath) LineTo(to Point) {
361 if to == p.pen {
362 return
363 }
364 data := WriteMulti(p.ops, CommandSize+4)
365 bo := binary.LittleEndian()
366 bo.PutUint32(data[0:], uint32(p.contour))
367 p.cmd(data[4:], Line(p.pen, to))
368 p.expand(p.pen)
369 p.expand(to)
370 p.pen = to
371 }
372
373 func (p *ClipPath) cmd(data []byte, c Command) {
374 EncodeCommand(data, c)
375 p.hash.Write(data)
376 }
377
378 func (p *ClipPath) expand(pt Point) {
379 if !p.hasSegments {
380 p.hasSegments = true
381 p.bounds = Rectangle{Min: pt, Max: pt}
382 } else {
383 b := p.bounds
384 if pt.X < b.Min.X {
385 b.Min.X = pt.X
386 }
387 if pt.Y < b.Min.Y {
388 b.Min.Y = pt.Y
389 }
390 if pt.X > b.Max.X {
391 b.Max.X = pt.X
392 }
393 if pt.Y > b.Max.Y {
394 b.Max.Y = pt.Y
395 }
396 p.bounds = b
397 }
398 }
399
400 // Quad records a quadratic Bezier from the pen to 'to'
401 // with the control point ctrl (relative).
402 func (p *ClipPath) Quad(ctrl, to Point) {
403 ctrl = ctrl.Add(p.pen)
404 to = to.Add(p.pen)
405 p.QuadTo(ctrl, to)
406 }
407
408 // QuadTo records a quadratic Bezier from the pen to 'to'
409 // with the control point ctrl, with absolute coordinates.
410 func (p *ClipPath) QuadTo(ctrl, to Point) {
411 if ctrl == p.pen && to == p.pen {
412 return
413 }
414 data := WriteMulti(p.ops, CommandSize+4)
415 bo := binary.LittleEndian()
416 bo.PutUint32(data[0:], uint32(p.contour))
417 p.cmd(data[4:], Quad(p.pen, ctrl, to))
418 p.expand(p.pen)
419 p.expand(ctrl)
420 p.expand(to)
421 p.pen = to
422 }
423
424 // ArcTo adds an elliptical arc to the path.
425 func (p *ClipPath) ArcTo(f1, f2 Point, angle float32) {
426 m, segments := ArcTransform(p.pen, f1, f2, angle)
427 for i := 0; i < segments; i++ {
428 p0 := p.pen
429 p1 := m.Transform(p0)
430 p2 := m.Transform(p1)
431 ctl := p1.Mul(2).Sub(p0.Add(p2).Mul(.5))
432 p.QuadTo(ctl, p2)
433 }
434 }
435
436 // Arc is like ArcTo where f1 and f2 are relative to the current position.
437 func (p *ClipPath) Arc(f1, f2 Point, angle float32) {
438 f1 = f1.Add(p.pen)
439 f2 = f2.Add(p.pen)
440 p.ArcTo(f1, f2, angle)
441 }
442
443 // Cube records a cubic Bezier from the pen through
444 // two control points ending in 'to' (relative).
445 func (p *ClipPath) Cube(ctrl0, ctrl1, to Point) {
446 p.CubeTo(p.pen.Add(ctrl0), p.pen.Add(ctrl1), p.pen.Add(to))
447 }
448
449 // CubeTo records a cubic Bezier from the pen through
450 // two control points ending in 'to', with absolute coordinates.
451 func (p *ClipPath) CubeTo(ctrl0, ctrl1, to Point) {
452 if ctrl0 == p.pen && ctrl1 == p.pen && to == p.pen {
453 return
454 }
455 data := WriteMulti(p.ops, CommandSize+4)
456 bo := binary.LittleEndian()
457 bo.PutUint32(data[0:], uint32(p.contour))
458 p.cmd(data[4:], Cubic(p.pen, ctrl0, ctrl1, to))
459 p.expand(p.pen)
460 p.expand(ctrl0)
461 p.expand(ctrl1)
462 p.expand(to)
463 p.pen = to
464 }
465
466 // Close closes the path contour.
467 func (p *ClipPath) Close() {
468 if p.pen != p.start {
469 p.LineTo(p.start)
470 }
471 p.end()
472 }
473
474 // ---------------------------------------------------------------------------
475 // clip/clip.go - ClipStroke, ClipOutline
476 // ---------------------------------------------------------------------------
477
478 // ClipStroke represents a stroked path. (was clip.Stroke)
479 type ClipStroke struct {
480 Path PathSpec
481 Width float32
482 }
483
484 // Op returns a ClipOp representing the stroke.
485 func (s ClipStroke) Op() ClipOp {
486 return ClipOp{
487 path: s.Path,
488 width: s.Width,
489 }
490 }
491
492 // ClipOutline represents the area inside of a path, according to the
493 // non-zero winding rule. (was clip.Outline)
494 type ClipOutline struct {
495 Path PathSpec
496 }
497
498 // Op returns a ClipOp representing the outline.
499 func (ol ClipOutline) Op() ClipOp {
500 return ClipOp{
501 path: ol.Path,
502 outline: true,
503 }
504 }
505
506 // ---------------------------------------------------------------------------
507 // clip/shapes.go - ClipRect, ClipRRect, ClipEllipse
508 // ---------------------------------------------------------------------------
509
510 // ClipRect represents the clip area of a pixel-aligned rectangle.
511 type ClipRect image.Rectangle
512
513 // Op returns the ClipOp for the rectangle.
514 func (r ClipRect) Op() ClipOp {
515 return ClipOp{
516 outline: true,
517 path: r.Path(),
518 }
519 }
520
521 // Push the clip operation on the clip stack.
522 func (r ClipRect) Push(o *OpList) ClipStack {
523 return r.Op().Push(o)
524 }
525
526 // Path returns the PathSpec for the rectangle.
527 func (r ClipRect) Path() PathSpec {
528 return PathSpec{
529 shape: ShapeRect,
530 bounds: image.Rectangle(r),
531 }
532 }
533
534 // UniformClipRRect returns a ClipRRect with all corner radii set to radius.
535 func UniformClipRRect(rect image.Rectangle, radius int) ClipRRect {
536 return ClipRRect{
537 Rect: rect,
538 SE: radius,
539 SW: radius,
540 NE: radius,
541 NW: radius,
542 }
543 }
544
545 // ClipRRect represents the clip area of a rectangle with rounded corners.
546 type ClipRRect struct {
547 Rect image.Rectangle
548 SE, SW, NW, NE int
549 }
550
551 // Op returns the ClipOp for the rounded rectangle.
552 func (rr ClipRRect) Op(o *OpList) ClipOp {
553 if rr.SE == 0 && rr.SW == 0 && rr.NW == 0 && rr.NE == 0 {
554 return ClipRect(rr.Rect).Op()
555 }
556 return ClipOutline{Path: rr.Path(o)}.Op()
557 }
558
559 // Push the rectangle clip on the clip stack.
560 func (rr ClipRRect) Push(o *OpList) ClipStack {
561 return rr.Op(o).Push(o)
562 }
563
564 // Path returns the PathSpec for the rounded rectangle.
565 func (rr ClipRRect) Path(o *OpList) PathSpec {
566 var p ClipPath
567 p.Begin(o)
568
569 const q = 4 * (math.Sqrt2 - 1) / 3
570 const iq = 1 - q
571
572 se, sw, nw, ne := float32(rr.SE), float32(rr.SW), float32(rr.NW), float32(rr.NE)
573 rrf := FRect(rr.Rect)
574 w, n, e, s := rrf.Min.X, rrf.Min.Y, rrf.Max.X, rrf.Max.Y
575
576 p.MoveTo(Point{X: w + nw, Y: n})
577 p.LineTo(Point{X: e - ne, Y: n})
578 p.CubeTo(
579 Point{X: e - ne*iq, Y: n},
580 Point{X: e, Y: n + ne*iq},
581 Point{X: e, Y: n + ne})
582 p.LineTo(Point{X: e, Y: s - se})
583 p.CubeTo(
584 Point{X: e, Y: s - se*iq},
585 Point{X: e - se*iq, Y: s},
586 Point{X: e - se, Y: s})
587 p.LineTo(Point{X: w + sw, Y: s})
588 p.CubeTo(
589 Point{X: w + sw*iq, Y: s},
590 Point{X: w, Y: s - sw*iq},
591 Point{X: w, Y: s - sw})
592 p.LineTo(Point{X: w, Y: n + nw})
593 p.CubeTo(
594 Point{X: w, Y: n + nw*iq},
595 Point{X: w + nw*iq, Y: n},
596 Point{X: w + nw, Y: n})
597
598 return p.End()
599 }
600
601 // ClipEllipse represents the largest axis-aligned ellipse contained in its bounds.
602 type ClipEllipse image.Rectangle
603
604 // Op returns the ClipOp for the filled ellipse.
605 func (el ClipEllipse) Op(o *OpList) ClipOp {
606 return ClipOutline{Path: el.Path(o)}.Op()
607 }
608
609 // Push the filled ellipse clip op on the clip stack.
610 func (el ClipEllipse) Push(o *OpList) ClipStack {
611 return el.Op(o).Push(o)
612 }
613
614 // Path constructs a path for the ellipse.
615 func (el ClipEllipse) Path(o *OpList) PathSpec {
616 bounds := image.Rectangle(el)
617 if bounds.Dx() == 0 || bounds.Dy() == 0 {
618 return PathSpec{shape: ShapeRect}
619 }
620
621 var p ClipPath
622 p.Begin(o)
623
624 bf := FRect(bounds)
625 center := bf.Max.Add(bf.Min).Mul(.5)
626 diam := bf.Dx()
627 r := diam * .5
628 scale := bf.Dy() / diam
629
630 const q = 4 * (math.Sqrt2 - 1) / 3
631
632 curve := r * q
633 top := Point{X: center.X, Y: center.Y - r*scale}
634
635 p.MoveTo(top)
636 p.CubeTo(
637 Point{X: center.X + curve, Y: center.Y - r*scale},
638 Point{X: center.X + r, Y: center.Y - curve*scale},
639 Point{X: center.X + r, Y: center.Y},
640 )
641 p.CubeTo(
642 Point{X: center.X + r, Y: center.Y + curve*scale},
643 Point{X: center.X + curve, Y: center.Y + r*scale},
644 Point{X: center.X, Y: center.Y + r*scale},
645 )
646 p.CubeTo(
647 Point{X: center.X - curve, Y: center.Y + r*scale},
648 Point{X: center.X - r, Y: center.Y + curve*scale},
649 Point{X: center.X - r, Y: center.Y},
650 )
651 p.CubeTo(
652 Point{X: center.X - r, Y: center.Y - curve*scale},
653 Point{X: center.X - curve, Y: center.Y - r*scale},
654 top,
655 )
656 ellipse := p.End()
657 ellipse.shape = ShapeEllipse
658 return ellipse
659 }
660
661 // ---------------------------------------------------------------------------
662 // paint/paint.go
663 // ---------------------------------------------------------------------------
664
665 // ImageFilter is the scaling filter for images.
666 type ImageFilter byte
667
668 const (
669 ImageFilterLinear ImageFilter = iota
670 ImageFilterNearest
671 )
672
673 // PaintImageOp sets the brush to an image. (was paint.ImageOp)
674 type PaintImageOp struct {
675 Filter ImageFilter
676 uniform bool
677 color color.NRGBA
678 src *image.RGBA
679 handle any
680 }
681
682 // PaintColorOp sets the brush to a constant color.
683 type PaintColorOp struct {
684 Color color.NRGBA
685 }
686
687 // PaintLinearGradientOp sets the brush to a gradient.
688 type PaintLinearGradientOp struct {
689 Stop1 Point
690 Color1 color.NRGBA
691 Stop2 Point
692 Color2 color.NRGBA
693 }
694
695 // PaintPaintOp fills the current clip area with the current brush.
696 type PaintPaintOp struct{}
697
698 // PaintOpacityStack represents an opacity applied to all painting operations
699 // until Pop is called.
700 type PaintOpacityStack struct {
701 id StackID
702 macroID uint32
703 ops *Ops
704 }
705
706 // NewPaintImageOp creates a PaintImageOp backed by src.
707 func NewPaintImageOp(src image.Image) PaintImageOp {
708 switch src := src.(type) {
709 case *image.Uniform:
710 col := color.NRGBAModel().Convert(src.C).(color.NRGBA)
711 return PaintImageOp{
712 uniform: true,
713 color: col,
714 }
715 case *image.RGBA:
716 // Use src pointer as handle so GPU texture is cached across frames
717 // (same *image.RGBA = same cache key = texture reuse, not re-upload).
718 return PaintImageOp{
719 src: src,
720 handle: src,
721 }
722 }
723
724 sz := src.Bounds().Size()
725 dst := image.NewRGBA(image.Rectangle{
726 Max: sz,
727 })
728 draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
729 return PaintImageOp{
730 src: dst,
731 handle: dst, // use pointer as stable cache key
732 }
733 }
734
735 func (i PaintImageOp) Size() image.Point {
736 if i.src == nil {
737 return image.Point{}
738 }
739 return i.src.Bounds().Size()
740 }
741
742 func (i PaintImageOp) Add(o *OpList) {
743 if i.uniform {
744 PaintColorOp{
745 Color: i.color,
746 }.Add(o)
747 return
748 } else if i.src == nil || i.src.Bounds().Empty() {
749 return
750 }
751 data := WriteOps2(&o.Internal, TypeImageLen, i.src, i.handle)
752 data[0] = byte(TypeImage)
753 data[1] = byte(i.Filter)
754 }
755
756 func (c PaintColorOp) Add(o *OpList) {
757 data := WriteOps(&o.Internal, TypeColorLen)
758 data[0] = byte(TypeColor)
759 data[1] = c.Color.R
760 data[2] = c.Color.G
761 data[3] = c.Color.B
762 data[4] = c.Color.A
763 }
764
765 func (c PaintLinearGradientOp) Add(o *OpList) {
766 data := WriteOps(&o.Internal, TypeLinearGradientLen)
767 data[0] = byte(TypeLinearGradient)
768
769 bo := binary.LittleEndian()
770 bo.PutUint32(data[1:], math.Float32bits(c.Stop1.X))
771 bo.PutUint32(data[5:], math.Float32bits(c.Stop1.Y))
772 bo.PutUint32(data[9:], math.Float32bits(c.Stop2.X))
773 bo.PutUint32(data[13:], math.Float32bits(c.Stop2.Y))
774
775 data[17+0] = c.Color1.R
776 data[17+1] = c.Color1.G
777 data[17+2] = c.Color1.B
778 data[17+3] = c.Color1.A
779 data[21+0] = c.Color2.R
780 data[21+1] = c.Color2.G
781 data[21+2] = c.Color2.B
782 data[21+3] = c.Color2.A
783 }
784
785 func (d PaintPaintOp) Add(o *OpList) {
786 data := WriteOps(&o.Internal, TypePaintLen)
787 data[0] = byte(TypePaint)
788 }
789
790 // FillShape fills the clip shape with a color.
791 func FillShape(o *OpList, c color.NRGBA, shape ClipOp) {
792 defer shape.Push(o).Pop()
793 Fill(o, c)
794 }
795
796 // Fill paints an infinitely large plane with the provided color.
797 func Fill(o *OpList, c color.NRGBA) {
798 PaintColorOp{Color: c}.Add(o)
799 PaintPaintOp{}.Add(o)
800 }
801
802 // PushOpacity creates a drawing layer with an opacity in the range [0;1].
803 func PushOpacity(o *OpList, opacity float32) PaintOpacityStack {
804 if opacity > 1 {
805 opacity = 1
806 }
807 if opacity < 0 {
808 opacity = 0
809 }
810 id, macroID := PushOp(&o.Internal, OpacityStackKind)
811 data := WriteOps(&o.Internal, TypePushOpacityLen)
812 bo := binary.LittleEndian()
813 data[0] = byte(TypePushOpacity)
814 bo.PutUint32(data[1:], math.Float32bits(opacity))
815 return PaintOpacityStack{ops: &o.Internal, id: id, macroID: macroID}
816 }
817
818 func (t PaintOpacityStack) Pop() {
819 PopOp(t.ops, OpacityStackKind, t.id, t.macroID)
820 data := WriteOps(t.ops, TypePopOpacityLen)
821 data[0] = byte(TypePopOpacity)
822 }
823