shapes.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package clip
4
5 import (
6 "image"
7 "math"
8
9 "github.com/p9c/p9/pkg/gel/gio/f32"
10 "github.com/p9c/p9/pkg/gel/gio/op"
11 )
12
13 // Rect represents the clip area of a pixel-aligned rectangle.
14 type Rect image.Rectangle
15
16 // Op returns the op for the rectangle.
17 func (r Rect) Op() Op {
18 return Op{
19 bounds: image.Rectangle(r),
20 outline: true,
21 }
22 }
23
24 // Add the clip operation.
25 func (r Rect) Add(ops *op.Ops) {
26 r.Op().Add(ops)
27 }
28
29 // UniformRRect returns an RRect with all corner radii set to the
30 // provided radius.
31 func UniformRRect(rect f32.Rectangle, radius float32) RRect {
32 return RRect{
33 Rect: rect,
34 SE: radius,
35 SW: radius,
36 NE: radius,
37 NW: radius,
38 }
39 }
40
41 // RRect represents the clip area of a rectangle with rounded
42 // corners.
43 //
44 // Specify a square with corner radii equal to half the square size to
45 // construct a circular clip area.
46 type RRect struct {
47 Rect f32.Rectangle
48 // The corner radii.
49 SE, SW, NW, NE float32
50 }
51
52 // Op returns the op for the rounded rectangle.
53 func (rr RRect) Op(ops *op.Ops) Op {
54 if rr.SE == 0 && rr.SW == 0 && rr.NW == 0 && rr.NE == 0 {
55 r := image.Rectangle{
56 Min: image.Point{X: int(rr.Rect.Min.X), Y: int(rr.Rect.Min.Y)},
57 Max: image.Point{X: int(rr.Rect.Max.X), Y: int(rr.Rect.Max.Y)},
58 }
59 // Only use Rect if rr is pixel-aligned, as Rect is guaranteed to be.
60 if fPt(r.Min) == rr.Rect.Min && fPt(r.Max) == rr.Rect.Max {
61 return Rect(r).Op()
62 }
63 }
64 return Outline{Path: rr.Path(ops)}.Op()
65 }
66
67 // Add the rectangle clip.
68 func (rr RRect) Add(ops *op.Ops) {
69 rr.Op(ops).Add(ops)
70 }
71
72 // Path returns the PathSpec for the rounded rectangle.
73 func (rr RRect) Path(ops *op.Ops) PathSpec {
74 var p Path
75 p.Begin(ops)
76
77 // https://pomax.github.io/bezierinfo/#circles_cubic.
78 const q = 4 * (math.Sqrt2 - 1) / 3
79 const iq = 1 - q
80
81 se, sw, nw, ne := rr.SE, rr.SW, rr.NW, rr.NE
82 w, n, e, s := rr.Rect.Min.X, rr.Rect.Min.Y, rr.Rect.Max.X, rr.Rect.Max.Y
83
84 p.MoveTo(f32.Point{X: w + nw, Y: n})
85 p.LineTo(f32.Point{X: e - ne, Y: n}) // N
86 p.CubeTo( // NE
87 f32.Point{X: e - ne*iq, Y: n},
88 f32.Point{X: e, Y: n + ne*iq},
89 f32.Point{X: e, Y: n + ne})
90 p.LineTo(f32.Point{X: e, Y: s - se}) // E
91 p.CubeTo( // SE
92 f32.Point{X: e, Y: s - se*iq},
93 f32.Point{X: e - se*iq, Y: s},
94 f32.Point{X: e - se, Y: s})
95 p.LineTo(f32.Point{X: w + sw, Y: s}) // S
96 p.CubeTo( // SW
97 f32.Point{X: w + sw*iq, Y: s},
98 f32.Point{X: w, Y: s - sw*iq},
99 f32.Point{X: w, Y: s - sw})
100 p.LineTo(f32.Point{X: w, Y: n + nw}) // W
101 p.CubeTo( // NW
102 f32.Point{X: w, Y: n + nw*iq},
103 f32.Point{X: w + nw*iq, Y: n},
104 f32.Point{X: w + nw, Y: n})
105
106 return p.End()
107 }
108
109 // Circle represents the clip area of a circle.
110 type Circle struct {
111 Center f32.Point
112 Radius float32
113 }
114
115 // Op returns the op for the circle.
116 func (c Circle) Op(ops *op.Ops) Op {
117 return Outline{Path: c.Path(ops)}.Op()
118 }
119
120 // Add the circle clip.
121 func (c Circle) Add(ops *op.Ops) {
122 c.Op(ops).Add(ops)
123 }
124
125 // Path returns the PathSpec for the circle.
126 func (c Circle) Path(ops *op.Ops) PathSpec {
127 var p Path
128 p.Begin(ops)
129
130 center := c.Center
131 r := c.Radius
132
133 // https://pomax.github.io/bezierinfo/#circles_cubic.
134 const q = 4 * (math.Sqrt2 - 1) / 3
135
136 curve := r * q
137 top := f32.Point{X: center.X, Y: center.Y - r}
138
139 p.MoveTo(top)
140 p.CubeTo(
141 f32.Point{X: center.X + curve, Y: center.Y - r},
142 f32.Point{X: center.X + r, Y: center.Y - curve},
143 f32.Point{X: center.X + r, Y: center.Y},
144 )
145 p.CubeTo(
146 f32.Point{X: center.X + r, Y: center.Y + curve},
147 f32.Point{X: center.X + curve, Y: center.Y + r},
148 f32.Point{X: center.X, Y: center.Y + r},
149 )
150 p.CubeTo(
151 f32.Point{X: center.X - curve, Y: center.Y + r},
152 f32.Point{X: center.X - r, Y: center.Y + curve},
153 f32.Point{X: center.X - r, Y: center.Y},
154 )
155 p.CubeTo(
156 f32.Point{X: center.X - r, Y: center.Y - curve},
157 f32.Point{X: center.X - curve, Y: center.Y - r},
158 top,
159 )
160 return p.End()
161 }
162
163 func fPt(p image.Point) f32.Point {
164 return f32.Point{
165 X: float32(p.X), Y: float32(p.Y),
166 }
167 }
168