mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
gpu,op/clip: implement stroked paths with round joins
Signed-off-by: Sebastien Binet <s@sbinet.org>
This commit is contained in:
committed by
Elias Naur
parent
f3f32ed7aa
commit
700cec440e
+2
-1
@@ -162,7 +162,8 @@ func (op *clipOp) decode(data []byte) {
|
|||||||
bounds: layout.FRect(r),
|
bounds: layout.FRect(r),
|
||||||
width: math.Float32frombits(bo.Uint32(data[17:])),
|
width: math.Float32frombits(bo.Uint32(data[17:])),
|
||||||
style: clip.StrokeStyle{
|
style: clip.StrokeStyle{
|
||||||
Cap: clip.StrokeCap(data[21]),
|
Cap: clip.StrokeCap(data[21]),
|
||||||
|
Join: clip.StrokeJoin(data[22]),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-2
@@ -51,7 +51,7 @@ func (qs *strokeQuads) pen() f32.Point {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qs *strokeQuads) lineTo(pt f32.Point) {
|
func (qs *strokeQuads) lineTo(pt f32.Point) {
|
||||||
end := (*qs)[len(*qs)-1].quad.To
|
end := qs.pen()
|
||||||
*qs = append(*qs, strokeQuad{
|
*qs = append(*qs, strokeQuad{
|
||||||
quad: ops.Quad{
|
quad: ops.Quad{
|
||||||
From: end,
|
From: end,
|
||||||
@@ -273,6 +273,13 @@ func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
|
|||||||
func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
|
func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
|
||||||
func rot90CCW(p f32.Point) f32.Point { return f32.Pt(-p.Y, +p.X) }
|
func rot90CCW(p f32.Point) f32.Point { return f32.Pt(-p.Y, +p.X) }
|
||||||
|
|
||||||
|
// cosPt returns the cosine of the opening angle between p and q.
|
||||||
|
func cosPt(p, q f32.Point) float32 {
|
||||||
|
np := math.Hypot(float64(p.X), float64(p.Y))
|
||||||
|
nq := math.Hypot(float64(q.X), float64(q.Y))
|
||||||
|
return dotPt(p, q) / float32(np*nq)
|
||||||
|
}
|
||||||
|
|
||||||
func normPt(p f32.Point, l float32) f32.Point {
|
func normPt(p f32.Point, l float32) f32.Point {
|
||||||
d := math.Hypot(float64(p.X), float64(p.Y))
|
d := math.Hypot(float64(p.X), float64(p.Y))
|
||||||
l64 := float64(l)
|
l64 := float64(l)
|
||||||
@@ -416,7 +423,14 @@ func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32
|
|||||||
// strokePathJoin joins the two paths rhs and lhs, according to the provided
|
// strokePathJoin joins the two paths rhs and lhs, according to the provided
|
||||||
// stroke style sty.
|
// stroke style sty.
|
||||||
func strokePathJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
func strokePathJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||||
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
switch sty.Join {
|
||||||
|
case clip.BevelJoin:
|
||||||
|
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
|
case clip.RoundJoin:
|
||||||
|
strokePathRoundJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
|
default:
|
||||||
|
panic("impossible")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func strokePathBevelJoin(rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
func strokePathBevelJoin(rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||||
@@ -428,6 +442,28 @@ func strokePathBevelJoin(rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Po
|
|||||||
lhs.lineTo(lp)
|
lhs.lineTo(lp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func strokePathRoundJoin(rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||||
|
rp := pivot.Add(n1)
|
||||||
|
lp := pivot.Sub(n1)
|
||||||
|
cw := dotPt(rot90CW(n0), n1) >= 0.0
|
||||||
|
switch {
|
||||||
|
case cw:
|
||||||
|
// Path bends to the right, ie. CW (or 180 degree turn).
|
||||||
|
c := pivot.Sub(lhs.pen())
|
||||||
|
angle := -math.Acos(float64(cosPt(n0, n1)))
|
||||||
|
lhs.arc(c, c, float32(angle))
|
||||||
|
lhs.lineTo(lp) // Add a line to accomodate for rounding errors.
|
||||||
|
rhs.lineTo(rp)
|
||||||
|
default:
|
||||||
|
// Path bends to the left, ie. CCW.
|
||||||
|
angle := math.Acos(float64(cosPt(n0, n1)))
|
||||||
|
c := pivot.Sub(rhs.pen())
|
||||||
|
rhs.arc(c, c, float32(angle))
|
||||||
|
rhs.lineTo(rp) // Add a line to accomodate for rounding errors.
|
||||||
|
lhs.lineTo(lp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// strokePathCap caps the provided path qs, according to the provided stroke style sty.
|
// strokePathCap caps the provided path qs, according to the provided stroke style sty.
|
||||||
func strokePathCap(sty clip.StrokeStyle, qs *strokeQuads, hw float32, pivot, n0 f32.Point) {
|
func strokePathCap(sty clip.StrokeStyle, qs *strokeQuads, hw float32, pivot, n0 f32.Point) {
|
||||||
switch sty.Cap {
|
switch sty.Cap {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const (
|
|||||||
TypePushLen = 1
|
TypePushLen = 1
|
||||||
TypePopLen = 1
|
TypePopLen = 1
|
||||||
TypeAuxLen = 1
|
TypeAuxLen = 1
|
||||||
TypeClipLen = 1 + 4*4 + 4 + 1
|
TypeClipLen = 1 + 4*4 + 4 + 2
|
||||||
TypeProfileLen = 1
|
TypeProfileLen = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ func TestStrokedPathBevelFlat(t *testing.T) {
|
|||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2.5
|
const width = 2.5
|
||||||
sty := clip.StrokeStyle{
|
sty := clip.StrokeStyle{
|
||||||
Cap: clip.FlatCap,
|
Cap: clip.FlatCap,
|
||||||
|
Join: clip.BevelJoin,
|
||||||
}
|
}
|
||||||
|
|
||||||
p := new(clip.Path)
|
p := new(clip.Path)
|
||||||
@@ -133,7 +134,8 @@ func TestStrokedPathBevelRound(t *testing.T) {
|
|||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2.5
|
const width = 2.5
|
||||||
sty := clip.StrokeStyle{
|
sty := clip.StrokeStyle{
|
||||||
Cap: clip.RoundCap,
|
Cap: clip.RoundCap,
|
||||||
|
Join: clip.BevelJoin,
|
||||||
}
|
}
|
||||||
|
|
||||||
p := new(clip.Path)
|
p := new(clip.Path)
|
||||||
@@ -159,7 +161,35 @@ func TestStrokedPathBevelSquare(t *testing.T) {
|
|||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2.5
|
const width = 2.5
|
||||||
sty := clip.StrokeStyle{
|
sty := clip.StrokeStyle{
|
||||||
Cap: clip.SquareCap,
|
Cap: clip.SquareCap,
|
||||||
|
Join: clip.BevelJoin,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := new(clip.Path)
|
||||||
|
p.Begin(o)
|
||||||
|
p.Move(f32.Pt(10, 50))
|
||||||
|
p.Line(f32.Pt(10, 0))
|
||||||
|
p.Arc(f32.Pt(10, 0), f32.Pt(20, 0), math.Pi)
|
||||||
|
p.Line(f32.Pt(10, 0))
|
||||||
|
p.Line(f32.Pt(10, 10))
|
||||||
|
p.Arc(f32.Pt(0, 30), f32.Pt(0, 30), 2*math.Pi)
|
||||||
|
p.Line(f32.Pt(-20, 0))
|
||||||
|
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
|
||||||
|
p.Stroke(width, sty).Add(o)
|
||||||
|
|
||||||
|
paint.Fill(o, colornames.Red)
|
||||||
|
}, func(r result) {
|
||||||
|
r.expect(0, 0, colornames.White)
|
||||||
|
r.expect(10, 50, colornames.Red)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStrokedPathRoundRound(t *testing.T) {
|
||||||
|
run(t, func(o *op.Ops) {
|
||||||
|
const width = 2.5
|
||||||
|
sty := clip.StrokeStyle{
|
||||||
|
Cap: clip.RoundCap,
|
||||||
|
Join: clip.RoundJoin,
|
||||||
}
|
}
|
||||||
|
|
||||||
p := new(clip.Path)
|
p := new(clip.Path)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@@ -54,6 +54,7 @@ func (p Op) Add(o *op.Ops) {
|
|||||||
bo.PutUint32(data[13:], uint32(p.bounds.Max.Y))
|
bo.PutUint32(data[13:], uint32(p.bounds.Max.Y))
|
||||||
bo.PutUint32(data[17:], math.Float32bits(p.width))
|
bo.PutUint32(data[17:], math.Float32bits(p.width))
|
||||||
data[21] = uint8(p.style.Cap)
|
data[21] = uint8(p.style.Cap)
|
||||||
|
data[22] = uint8(p.style.Join)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin the path, storing the path data and final Op into ops.
|
// Begin the path, storing the path data and final Op into ops.
|
||||||
|
|||||||
+15
-2
@@ -3,9 +3,11 @@
|
|||||||
package clip
|
package clip
|
||||||
|
|
||||||
// StrokeStyle describes how a stroked path should be drawn.
|
// StrokeStyle describes how a stroked path should be drawn.
|
||||||
// StrokeStyle zero value draws a Bevel-joined and Flat-capped stroked path.
|
// The zero value of StrokeStyle represents bevel-joined and flat-capped
|
||||||
|
// strokes.
|
||||||
type StrokeStyle struct {
|
type StrokeStyle struct {
|
||||||
Cap StrokeCap
|
Cap StrokeCap
|
||||||
|
Join StrokeJoin
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokeCap describes the head or tail of a stroked path.
|
// StrokeCap describes the head or tail of a stroked path.
|
||||||
@@ -26,3 +28,14 @@ const (
|
|||||||
// stroked path's width.
|
// stroked path's width.
|
||||||
RoundCap
|
RoundCap
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StrokeJoin describes how stroked paths are collated.
|
||||||
|
type StrokeJoin uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BevelJoin joins path segments with sharp bevels.
|
||||||
|
BevelJoin StrokeJoin = iota
|
||||||
|
|
||||||
|
// RoundJoin joins path segments with a round segment.
|
||||||
|
RoundJoin
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user