github.com/utopiagio/gio@v0.0.8/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/utopiagio/gio/f32" 10 f32internal "github.com/utopiagio/gio/internal/f32" 11 "github.com/utopiagio/gio/internal/ops" 12 "github.com/utopiagio/gio/op" 13 ) 14 15 // Rect represents the clip area of a pixel-aligned rectangle. 16 type Rect image.Rectangle 17 18 // Op returns the op for the rectangle. 19 func (r Rect) Op() Op { 20 return Op{ 21 outline: true, 22 path: r.Path(), 23 } 24 } 25 26 // Push the clip operation on the clip stack. 27 func (r Rect) Push(ops *op.Ops) Stack { 28 return r.Op().Push(ops) 29 } 30 31 // Path returns the PathSpec for the rectangle. 32 func (r Rect) Path() PathSpec { 33 return PathSpec{ 34 shape: ops.Rect, 35 bounds: image.Rectangle(r), 36 } 37 } 38 39 // UniformRRect returns an RRect with all corner radii set to the 40 // provided radius. 41 func UniformRRect(rect image.Rectangle, radius int) RRect { 42 return RRect{ 43 Rect: rect, 44 SE: radius, 45 SW: radius, 46 NE: radius, 47 NW: radius, 48 } 49 } 50 51 // RRect represents the clip area of a rectangle with rounded 52 // corners. 53 // 54 // Specify a square with corner radii equal to half the square size to 55 // construct a circular clip area. 56 type RRect struct { 57 Rect image.Rectangle 58 // The corner radii. 59 SE, SW, NW, NE int 60 } 61 62 // Op returns the op for the rounded rectangle. 63 func (rr RRect) Op(ops *op.Ops) Op { 64 if rr.SE == 0 && rr.SW == 0 && rr.NW == 0 && rr.NE == 0 { 65 return Rect(rr.Rect).Op() 66 } 67 return Outline{Path: rr.Path(ops)}.Op() 68 } 69 70 // Push the rectangle clip on the clip stack. 71 func (rr RRect) Push(ops *op.Ops) Stack { 72 return rr.Op(ops).Push(ops) 73 } 74 75 // Path returns the PathSpec for the rounded rectangle. 76 func (rr RRect) Path(ops *op.Ops) PathSpec { 77 var p Path 78 p.Begin(ops) 79 80 // https://pomax.github.io/bezierinfo/#circles_cubic. 81 const q = 4 * (math.Sqrt2 - 1) / 3 82 const iq = 1 - q 83 84 se, sw, nw, ne := float32(rr.SE), float32(rr.SW), float32(rr.NW), float32(rr.NE) 85 rrf := f32internal.FRect(rr.Rect) 86 w, n, e, s := rrf.Min.X, rrf.Min.Y, rrf.Max.X, rrf.Max.Y 87 88 p.MoveTo(f32.Point{X: w + nw, Y: n}) 89 p.LineTo(f32.Point{X: e - ne, Y: n}) // N 90 p.CubeTo( // NE 91 f32.Point{X: e - ne*iq, Y: n}, 92 f32.Point{X: e, Y: n + ne*iq}, 93 f32.Point{X: e, Y: n + ne}) 94 p.LineTo(f32.Point{X: e, Y: s - se}) // E 95 p.CubeTo( // SE 96 f32.Point{X: e, Y: s - se*iq}, 97 f32.Point{X: e - se*iq, Y: s}, 98 f32.Point{X: e - se, Y: s}) 99 p.LineTo(f32.Point{X: w + sw, Y: s}) // S 100 p.CubeTo( // SW 101 f32.Point{X: w + sw*iq, Y: s}, 102 f32.Point{X: w, Y: s - sw*iq}, 103 f32.Point{X: w, Y: s - sw}) 104 p.LineTo(f32.Point{X: w, Y: n + nw}) // W 105 p.CubeTo( // NW 106 f32.Point{X: w, Y: n + nw*iq}, 107 f32.Point{X: w + nw*iq, Y: n}, 108 f32.Point{X: w + nw, Y: n}) 109 110 return p.End() 111 } 112 113 // Ellipse represents the largest axis-aligned ellipse that 114 // is contained in its bounds. 115 type Ellipse image.Rectangle 116 117 // Op returns the op for the filled ellipse. 118 func (e Ellipse) Op(ops *op.Ops) Op { 119 return Outline{Path: e.Path(ops)}.Op() 120 } 121 122 // Push the filled ellipse clip op on the clip stack. 123 func (e Ellipse) Push(ops *op.Ops) Stack { 124 return e.Op(ops).Push(ops) 125 } 126 127 // Path constructs a path for the ellipse. 128 func (e Ellipse) Path(o *op.Ops) PathSpec { 129 bounds := image.Rectangle(e) 130 if bounds.Dx() == 0 || bounds.Dy() == 0 { 131 return PathSpec{shape: ops.Rect} 132 } 133 134 var p Path 135 p.Begin(o) 136 137 bf := f32internal.FRect(bounds) 138 center := bf.Max.Add(bf.Min).Mul(.5) 139 diam := bf.Dx() 140 r := diam * .5 141 // We'll model the ellipse as a circle scaled in the Y 142 // direction. 143 scale := bf.Dy() / diam 144 145 // https://pomax.github.io/bezierinfo/#circles_cubic. 146 const q = 4 * (math.Sqrt2 - 1) / 3 147 148 curve := r * q 149 top := f32.Point{X: center.X, Y: center.Y - r*scale} 150 151 p.MoveTo(top) 152 p.CubeTo( 153 f32.Point{X: center.X + curve, Y: center.Y - r*scale}, 154 f32.Point{X: center.X + r, Y: center.Y - curve*scale}, 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*scale}, 159 f32.Point{X: center.X + curve, Y: center.Y + r*scale}, 160 f32.Point{X: center.X, Y: center.Y + r*scale}, 161 ) 162 p.CubeTo( 163 f32.Point{X: center.X - curve, Y: center.Y + r*scale}, 164 f32.Point{X: center.X - r, Y: center.Y + curve*scale}, 165 f32.Point{X: center.X - r, Y: center.Y}, 166 ) 167 p.CubeTo( 168 f32.Point{X: center.X - r, Y: center.Y - curve*scale}, 169 f32.Point{X: center.X - curve, Y: center.Y - r*scale}, 170 top, 171 ) 172 ellipse := p.End() 173 ellipse.shape = ops.Ellipse 174 return ellipse 175 }