aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/op/clip/clip.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/op/clip/clip.go')
-rw-r--r--vendor/gioui.org/op/clip/clip.go464
1 files changed, 273 insertions, 191 deletions
diff --git a/vendor/gioui.org/op/clip/clip.go b/vendor/gioui.org/op/clip/clip.go
index 59554b5..5b4dad0 100644
--- a/vendor/gioui.org/op/clip/clip.go
+++ b/vendor/gioui.org/op/clip/clip.go
@@ -4,85 +4,240 @@ package clip
import (
"encoding/binary"
+ "hash/maphash"
"image"
"math"
"gioui.org/f32"
- "gioui.org/internal/opconst"
"gioui.org/internal/ops"
+ "gioui.org/internal/scene"
+ "gioui.org/internal/stroke"
"gioui.org/op"
)
+// Op represents a clip area. Op intersects the current clip area with
+// itself.
+type Op struct {
+ path PathSpec
+
+ outline bool
+ width float32
+}
+
+// Stack represents an Op pushed on the clip stack.
+type Stack struct {
+ ops *ops.Ops
+ id ops.StackID
+ macroID int
+}
+
+var pathSeed maphash.Seed
+
+func init() {
+ pathSeed = maphash.MakeSeed()
+}
+
+// Push saves the current clip state on the stack and updates the current
+// state to the intersection of the current p.
+func (p Op) Push(o *op.Ops) Stack {
+ id, macroID := ops.PushOp(&o.Internal, ops.ClipStack)
+ p.add(o)
+ return Stack{ops: &o.Internal, id: id, macroID: macroID}
+}
+
+func (p Op) add(o *op.Ops) {
+ path := p.path
+
+ bo := binary.LittleEndian
+ if path.hasSegments {
+ data := ops.Write(&o.Internal, ops.TypePathLen)
+ data[0] = byte(ops.TypePath)
+ bo.PutUint64(data[1:], path.hash)
+ path.spec.Add(o)
+ }
+
+ bounds := path.bounds
+ if p.width > 0 {
+ // Expand bounds to cover stroke.
+ half := int(p.width*.5 + .5)
+ bounds.Min.X -= half
+ bounds.Min.Y -= half
+ bounds.Max.X += half
+ bounds.Max.Y += half
+ data := ops.Write(&o.Internal, ops.TypeStrokeLen)
+ data[0] = byte(ops.TypeStroke)
+ bo := binary.LittleEndian
+ bo.PutUint32(data[1:], math.Float32bits(p.width))
+ }
+
+ data := ops.Write(&o.Internal, ops.TypeClipLen)
+ data[0] = byte(ops.TypeClip)
+ bo.PutUint32(data[1:], uint32(bounds.Min.X))
+ bo.PutUint32(data[5:], uint32(bounds.Min.Y))
+ bo.PutUint32(data[9:], uint32(bounds.Max.X))
+ bo.PutUint32(data[13:], uint32(bounds.Max.Y))
+ if p.outline {
+ data[17] = byte(1)
+ }
+ data[18] = byte(path.shape)
+}
+
+func (s Stack) Pop() {
+ ops.PopOp(s.ops, ops.ClipStack, s.id, s.macroID)
+ data := ops.Write(s.ops, ops.TypePopClipLen)
+ data[0] = byte(ops.TypePopClip)
+}
+
+type PathSpec struct {
+ spec op.CallOp
+ // hasSegments tracks whether there are any segments in the path.
+ hasSegments bool
+ bounds image.Rectangle
+ shape ops.Shape
+ hash uint64
+}
+
// Path constructs a Op clip path described by lines and
// Bézier curves, where drawing outside the Path is discarded.
-// The inside-ness of a pixel is determines by the even-odd rule,
+// The inside-ness of a pixel is determines by the non-zero winding rule,
// similar to the SVG rule of the same name.
//
// Path generates no garbage and can be used for dynamic paths; path
// data is stored directly in the Ops list supplied to Begin.
type Path struct {
- ops *op.Ops
- contour int
- pen f32.Point
- macro op.MacroOp
- start f32.Point
+ ops *ops.Ops
+ contour int
+ pen f32.Point
+ macro op.MacroOp
+ start f32.Point
+ hasSegments bool
+ bounds f32.Rectangle
+ hash maphash.Hash
}
-// Op sets the current clip to the intersection of
-// the existing clip with this clip.
-//
-// If you need to reset the clip to its previous values after
-// applying a Op, use op.StackOp.
-type Op struct {
- call op.CallOp
- bounds f32.Rectangle
+// Pos returns the current pen position.
+func (p *Path) Pos() f32.Point { return p.pen }
+
+// Begin the path, storing the path data and final Op into ops.
+func (p *Path) Begin(o *op.Ops) {
+ *p = Path{
+ ops: &o.Internal,
+ macro: op.Record(o),
+ contour: 1,
+ }
+ p.hash.SetSeed(pathSeed)
+ data := ops.Write(p.ops, ops.TypeAuxLen)
+ data[0] = byte(ops.TypeAux)
}
-func (p Op) Add(o *op.Ops) {
- p.call.Add(o)
- data := o.Write(opconst.TypeClipLen)
- data[0] = byte(opconst.TypeClip)
- bo := binary.LittleEndian
- bo.PutUint32(data[1:], math.Float32bits(p.bounds.Min.X))
- bo.PutUint32(data[5:], math.Float32bits(p.bounds.Min.Y))
- bo.PutUint32(data[9:], math.Float32bits(p.bounds.Max.X))
- bo.PutUint32(data[13:], math.Float32bits(p.bounds.Max.Y))
+// End returns a PathSpec ready to use in clipping operations.
+func (p *Path) End() PathSpec {
+ p.gap()
+ c := p.macro.Stop()
+ return PathSpec{
+ spec: c,
+ hasSegments: p.hasSegments,
+ bounds: boundRectF(p.bounds),
+ hash: p.hash.Sum64(),
+ }
}
-// Begin the path, storing the path data and final Op into ops.
-func (p *Path) Begin(ops *op.Ops) {
- p.ops = ops
- p.macro = op.Record(ops)
- // Write the TypeAux opcode
- data := ops.Write(opconst.TypeAuxLen)
- data[0] = byte(opconst.TypeAux)
+// Move moves the pen by the amount specified by delta.
+func (p *Path) Move(delta f32.Point) {
+ to := delta.Add(p.pen)
+ p.MoveTo(to)
}
-// MoveTo moves the pen to the given position.
-func (p *Path) Move(to f32.Point) {
- to = to.Add(p.pen)
+// MoveTo moves the pen to the specified absolute coordinate.
+func (p *Path) MoveTo(to f32.Point) {
+ if p.pen == to {
+ return
+ }
+ p.gap()
p.end()
p.pen = to
p.start = to
}
-// end completes the current contour.
-func (p *Path) end() {
+func (p *Path) gap() {
if p.pen != p.start {
- p.lineTo(p.start)
+ // A closed contour starts and ends in the same point.
+ // This move creates a gap in the contour, register it.
+ data := ops.Write(p.ops, scene.CommandSize+4)
+ bo := binary.LittleEndian
+ bo.PutUint32(data[0:], uint32(p.contour))
+ p.cmd(data[4:], scene.Gap(p.pen, p.start))
}
+}
+
+// end completes the current contour.
+func (p *Path) end() {
p.contour++
}
// Line moves the pen by the amount specified by delta, recording a line.
func (p *Path) Line(delta f32.Point) {
to := delta.Add(p.pen)
- p.lineTo(to)
+ p.LineTo(to)
}
-func (p *Path) lineTo(to f32.Point) {
- // Model lines as degenerate quadratic Béziers.
- p.quadTo(to.Add(p.pen).Mul(.5), to)
+// LineTo moves the pen to the absolute point specified, recording a line.
+func (p *Path) LineTo(to f32.Point) {
+ data := ops.Write(p.ops, scene.CommandSize+4)
+ bo := binary.LittleEndian
+ bo.PutUint32(data[0:], uint32(p.contour))
+ p.cmd(data[4:], scene.Line(p.pen, to))
+ p.pen = to
+ p.expand(to)
+}
+
+func (p *Path) cmd(data []byte, c scene.Command) {
+ ops.EncodeCommand(data, c)
+ p.hash.Write(data)
+}
+
+func (p *Path) expand(pt f32.Point) {
+ if !p.hasSegments {
+ p.hasSegments = true
+ p.bounds = f32.Rectangle{Min: pt, Max: pt}
+ } else {
+ b := p.bounds
+ if pt.X < b.Min.X {
+ b.Min.X = pt.X
+ }
+ if pt.Y < b.Min.Y {
+ b.Min.Y = pt.Y
+ }
+ if pt.X > b.Max.X {
+ b.Max.X = pt.X
+ }
+ if pt.Y > b.Max.Y {
+ b.Max.Y = pt.Y
+ }
+ p.bounds = b
+ }
+}
+
+// boundRectF returns a bounding image.Rectangle for a f32.Rectangle.
+func boundRectF(r f32.Rectangle) image.Rectangle {
+ return image.Rectangle{
+ Min: image.Point{
+ X: int(floor(r.Min.X)),
+ Y: int(floor(r.Min.Y)),
+ },
+ Max: image.Point{
+ X: int(ceil(r.Max.X)),
+ Y: int(ceil(r.Max.Y)),
+ },
+ }
+}
+
+func ceil(v float32) int {
+ return int(math.Ceil(float64(v)))
+}
+
+func floor(v float32) int {
+ return int(math.Floor(float64(v)))
}
// Quad records a quadratic Bézier from the pen to end
@@ -90,174 +245,101 @@ func (p *Path) lineTo(to f32.Point) {
func (p *Path) Quad(ctrl, to f32.Point) {
ctrl = ctrl.Add(p.pen)
to = to.Add(p.pen)
- p.quadTo(ctrl, to)
+ p.QuadTo(ctrl, to)
}
-func (p *Path) quadTo(ctrl, to f32.Point) {
- data := p.ops.Write(ops.QuadSize + 4)
+// QuadTo records a quadratic Bézier from the pen to end
+// with the control point ctrl, with absolute coordinates.
+func (p *Path) QuadTo(ctrl, to f32.Point) {
+ data := ops.Write(p.ops, scene.CommandSize+4)
bo := binary.LittleEndian
bo.PutUint32(data[0:], uint32(p.contour))
- ops.EncodeQuad(data[4:], ops.Quad{
- From: p.pen,
- Ctrl: ctrl,
- To: to,
- })
+ p.cmd(data[4:], scene.Quad(p.pen, ctrl, to))
p.pen = to
+ p.expand(ctrl)
+ p.expand(to)
+}
+
+// ArcTo adds an elliptical arc to the path. The implied ellipse is defined
+// by its focus points f1 and f2.
+// The arc starts in the current point and ends angle radians along the ellipse boundary.
+// The sign of angle determines the direction; positive being counter-clockwise,
+// negative clockwise.
+func (p *Path) ArcTo(f1, f2 f32.Point, angle float32) {
+ const segments = 16
+ m := stroke.ArcTransform(p.pen, f1, f2, angle, segments)
+
+ for i := 0; i < segments; i++ {
+ p0 := p.pen
+ p1 := m.Transform(p0)
+ p2 := m.Transform(p1)
+ ctl := p1.Mul(2).Sub(p0.Add(p2).Mul(.5))
+ p.QuadTo(ctl, p2)
+ }
+}
+
+// Arc is like ArcTo where f1 and f2 are relative to the current position.
+func (p *Path) Arc(f1, f2 f32.Point, angle float32) {
+ f1 = f1.Add(p.pen)
+ f2 = f2.Add(p.pen)
+ p.ArcTo(f1, f2, angle)
}
// Cube records a cubic Bézier from the pen through
// two control points ending in to.
func (p *Path) Cube(ctrl0, ctrl1, to f32.Point) {
- ctrl0 = ctrl0.Add(p.pen)
- ctrl1 = ctrl1.Add(p.pen)
- to = to.Add(p.pen)
- // Set the maximum distance proportionally to the longest side
- // of the bounding rectangle.
- hull := f32.Rectangle{
- Min: p.pen,
- Max: ctrl0,
- }.Canon().Add(ctrl1).Add(to)
- l := hull.Dx()
- if h := hull.Dy(); h > l {
- l = h
- }
- p.approxCubeTo(0, l*0.001, ctrl0, ctrl1, to)
-}
-
-// approxCube approximates a cubic Bézier by a series of quadratic
-// curves.
-func (p *Path) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int {
- // The idea is from
- // https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
- // where a quadratic approximates a cubic by eliminating its t³ term
- // from its polynomial expression anchored at the starting point:
- //
- // P(t) = pen + 3t(ctrl0 - pen) + 3t²(ctrl1 - 2ctrl0 + pen) + t³(to - 3ctrl1 + 3ctrl0 - pen)
- //
- // The control point for the new quadratic Q1 that shares starting point, pen, with P is
- //
- // C1 = (3ctrl0 - pen)/2
- //
- // The reverse cubic anchored at the end point has the polynomial
- //
- // P'(t) = to + 3t(ctrl1 - to) + 3t²(ctrl0 - 2ctrl1 + to) + t³(pen - 3ctrl0 + 3ctrl1 - to)
- //
- // The corresponding quadratic Q2 that shares the end point, to, with P has control
- // point
- //
- // C2 = (3ctrl1 - to)/2
- //
- // The combined quadratic Bézier, Q, shares both start and end points with its cubic
- // and use the midpoint between the two curves Q1 and Q2 as control point:
- //
- // C = (3ctrl0 - pen + 3ctrl1 - to)/4
- c := ctrl0.Mul(3).Sub(p.pen).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0)
- const maxSplits = 32
- if splits >= maxSplits {
- p.quadTo(c, to)
- return splits
+ p.CubeTo(p.pen.Add(ctrl0), p.pen.Add(ctrl1), p.pen.Add(to))
+}
+
+// CubeTo records a cubic Bézier from the pen through
+// two control points ending in to, with absolute coordinates.
+func (p *Path) CubeTo(ctrl0, ctrl1, to f32.Point) {
+ if ctrl0 == p.pen && ctrl1 == p.pen && to == p.pen {
+ return
}
- // The maximum distance between the cubic P and its approximation Q given t
- // can be shown to be
- //
- // d = sqrt(3)/36*|to - 3ctrl1 + 3ctrl0 - pen|
- //
- // To save a square root, compare d² with the squared tolerance.
- v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(p.pen)
- d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36)
- if d2 <= maxDist*maxDist {
- p.quadTo(c, to)
- return splits
+ data := ops.Write(p.ops, scene.CommandSize+4)
+ bo := binary.LittleEndian
+ bo.PutUint32(data[0:], uint32(p.contour))
+ p.cmd(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to))
+ p.pen = to
+ p.expand(ctrl0)
+ p.expand(ctrl1)
+ p.expand(to)
+}
+
+// Close closes the path contour.
+func (p *Path) Close() {
+ if p.pen != p.start {
+ p.LineTo(p.start)
}
- // De Casteljau split the curve and approximate the halves.
- t := float32(0.5)
- c0 := p.pen.Add(ctrl0.Sub(p.pen).Mul(t))
- c1 := ctrl0.Add(ctrl1.Sub(ctrl0).Mul(t))
- c2 := ctrl1.Add(to.Sub(ctrl1).Mul(t))
- c01 := c0.Add(c1.Sub(c0).Mul(t))
- c12 := c1.Add(c2.Sub(c1).Mul(t))
- c0112 := c01.Add(c12.Sub(c01).Mul(t))
- splits++
- splits = p.approxCubeTo(splits, maxDist, c0, c01, c0112)
- splits = p.approxCubeTo(splits, maxDist, c12, c2, to)
- return splits
-}
-
-// End the path and return a clip operation that represents it.
-func (p *Path) End() Op {
p.end()
- c := p.macro.Stop()
+}
+
+// Stroke represents a stroked path.
+type Stroke struct {
+ Path PathSpec
+ // Width of the stroked path.
+ Width float32
+}
+
+// Op returns a clip operation representing the stroke.
+func (s Stroke) Op() Op {
return Op{
- call: c,
+ path: s.Path,
+ width: s.Width,
}
}
-// Rect represents the clip area of a rectangle with rounded
-// corners.The origin is in the upper left
-// corner.
-// Specify a square with corner radii equal to half the square size to
-// construct a circular clip area.
-type Rect struct {
- Rect f32.Rectangle
- // The corner radii.
- SE, SW, NW, NE float32
-}
-
-// Op returns the Op for the rectangle.
-func (rr Rect) Op(ops *op.Ops) Op {
- r := rr.Rect
- // Optimize for the common pixel aligned rectangle with no
- // corner rounding.
- if rr.SE == 0 && rr.SW == 0 && rr.NW == 0 && rr.NE == 0 {
- ri := image.Rectangle{
- Min: image.Point{X: int(r.Min.X), Y: int(r.Min.Y)},
- Max: image.Point{X: int(r.Max.X), Y: int(r.Max.Y)},
- }
- // Optimize pixel-aligned rectangles to just its bounds.
- if r == fRect(ri) {
- return Op{bounds: r}
- }
- }
- return roundRect(ops, r, rr.SE, rr.SW, rr.NW, rr.NE)
-}
-
-// Add is a shorthand for Op(ops).Add(ops).
-func (rr Rect) Add(ops *op.Ops) {
- rr.Op(ops).Add(ops)
-}
-
-// roundRect returns the clip area of a rectangle with rounded
-// corners defined by their radii.
-func roundRect(ops *op.Ops, r f32.Rectangle, se, sw, nw, ne float32) Op {
- size := r.Size()
- // https://pomax.github.io/bezierinfo/#circles_cubic.
- w, h := float32(size.X), float32(size.Y)
- const c = 0.55228475 // 4*(sqrt(2)-1)/3
- var p Path
- p.Begin(ops)
- p.Move(r.Min)
-
- p.Move(f32.Point{X: w, Y: h - se})
- p.Cube(f32.Point{X: 0, Y: se * c}, f32.Point{X: -se + se*c, Y: se}, f32.Point{X: -se, Y: se}) // SE
- p.Line(f32.Point{X: sw - w + se, Y: 0})
- p.Cube(f32.Point{X: -sw * c, Y: 0}, f32.Point{X: -sw, Y: -sw + sw*c}, f32.Point{X: -sw, Y: -sw}) // SW
- p.Line(f32.Point{X: 0, Y: nw - h + sw})
- p.Cube(f32.Point{X: 0, Y: -nw * c}, f32.Point{X: nw - nw*c, Y: -nw}, f32.Point{X: nw, Y: -nw}) // NW
- p.Line(f32.Point{X: w - ne - nw, Y: 0})
- p.Cube(f32.Point{X: ne * c, Y: 0}, f32.Point{X: ne, Y: ne - ne*c}, f32.Point{X: ne, Y: ne}) // NE
- return p.End()
-}
-
-// fRect converts a rectangle to a f32.Rectangle.
-func fRect(r image.Rectangle) f32.Rectangle {
- return f32.Rectangle{
- Min: fPt(r.Min), Max: fPt(r.Max),
- }
+// Outline represents the area inside of a path, according to the
+// non-zero winding rule.
+type Outline struct {
+ Path PathSpec
}
-// fPt converts an point to a f32.Point.
-func fPt(p image.Point) f32.Point {
- return f32.Point{
- X: float32(p.X), Y: float32(p.Y),
+// Op returns a clip operation representing the outline.
+func (o Outline) Op() Op {
+ return Op{
+ path: o.Path,
+ outline: true,
}
}