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  }