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