mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
gpu,op/clip: implement stroked paths with miter joins
Signed-off-by: Sebastien Binet <s@sbinet.org>
This commit is contained in:
committed by
Elias Naur
parent
7de8ce51a5
commit
7eb32360e5
+3
-2
@@ -162,8 +162,9 @@ 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]),
|
Join: clip.StrokeJoin(data[22]),
|
||||||
|
Miter: math.Float32frombits(bo.Uint32(data[23:])),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -423,6 +423,10 @@ 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) {
|
||||||
|
if sty.Miter > 0 {
|
||||||
|
strokePathMiterJoin(sty, rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
|
return
|
||||||
|
}
|
||||||
switch sty.Join {
|
switch sty.Join {
|
||||||
case clip.BevelJoin:
|
case clip.BevelJoin:
|
||||||
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
@@ -464,6 +468,47 @@ func strokePathRoundJoin(rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Po
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func strokePathMiterJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||||
|
if n0 == n1.Mul(-1) {
|
||||||
|
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to handle nearly linear joints that would be clipped otherwise.
|
||||||
|
limit := math.Max(float64(sty.Miter), 1.001)
|
||||||
|
|
||||||
|
cw := dotPt(rot90CW(n0), n1) >= 0.0
|
||||||
|
if cw {
|
||||||
|
// hw is used to calculate |R|.
|
||||||
|
// When running CW, n0 and n1 point the other way,
|
||||||
|
// so the sign of r0 and r1 is negated.
|
||||||
|
hw = -hw
|
||||||
|
}
|
||||||
|
hw64 := float64(hw)
|
||||||
|
|
||||||
|
cos := math.Sqrt(0.5 * (1 + float64(cosPt(n0, n1))))
|
||||||
|
d := hw64 / cos
|
||||||
|
if math.Abs(limit*hw64) < math.Abs(d) {
|
||||||
|
sty.Miter = 0 // Set miter to zero to disable the miter joint.
|
||||||
|
strokePathJoin(sty, rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mid := pivot.Add(normPt(n0.Add(n1), float32(d)))
|
||||||
|
|
||||||
|
rp := pivot.Add(n1)
|
||||||
|
lp := pivot.Sub(n1)
|
||||||
|
switch {
|
||||||
|
case cw:
|
||||||
|
// Path bends to the right, ie. CW.
|
||||||
|
lhs.lineTo(mid)
|
||||||
|
default:
|
||||||
|
// Path bends to the left, ie. CCW.
|
||||||
|
rhs.lineTo(mid)
|
||||||
|
}
|
||||||
|
rhs.lineTo(rp)
|
||||||
|
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 + 2
|
TypeClipLen = 1 + 4*4 + 4 + 2 + 4
|
||||||
TypeProfileLen = 1
|
TypeProfileLen = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,96 @@ func TestStrokedPathRoundRound(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStrokedPathFlatMiter(t *testing.T) {
|
||||||
|
run(t, func(o *op.Ops) {
|
||||||
|
const width = 10
|
||||||
|
sty := clip.StrokeStyle{
|
||||||
|
Cap: clip.FlatCap,
|
||||||
|
Join: clip.BevelJoin,
|
||||||
|
Miter: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
beg := f32.Pt(40, 10)
|
||||||
|
{
|
||||||
|
p := new(clip.Path)
|
||||||
|
p.Begin(o)
|
||||||
|
p.Move(beg)
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Line(f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
|
||||||
|
p.Stroke(width, sty).Add(o)
|
||||||
|
paint.Fill(o, colornames.Red)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
p := new(clip.Path)
|
||||||
|
p.Begin(o)
|
||||||
|
p.Move(beg)
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Line(f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
|
||||||
|
p.Stroke(2, clip.StrokeStyle{}).Add(o)
|
||||||
|
paint.Fill(o, colornames.Black)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, func(r result) {
|
||||||
|
r.expect(0, 0, colornames.White)
|
||||||
|
r.expect(40, 10, colornames.Black)
|
||||||
|
r.expect(40, 12, colornames.Red)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStrokedPathFlatMiterInf(t *testing.T) {
|
||||||
|
run(t, func(o *op.Ops) {
|
||||||
|
const width = 10
|
||||||
|
sty := clip.StrokeStyle{
|
||||||
|
Cap: clip.FlatCap,
|
||||||
|
Join: clip.BevelJoin,
|
||||||
|
Miter: float32(math.Inf(+1)),
|
||||||
|
}
|
||||||
|
|
||||||
|
beg := f32.Pt(40, 10)
|
||||||
|
{
|
||||||
|
p := new(clip.Path)
|
||||||
|
p.Begin(o)
|
||||||
|
p.Move(beg)
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Line(f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
|
||||||
|
p.Stroke(width, sty).Add(o)
|
||||||
|
paint.Fill(o, colornames.Red)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
p := new(clip.Path)
|
||||||
|
p.Begin(o)
|
||||||
|
p.Move(beg)
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Line(f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
|
||||||
|
p.Stroke(2, clip.StrokeStyle{}).Add(o)
|
||||||
|
paint.Fill(o, colornames.Black)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, func(r result) {
|
||||||
|
r.expect(0, 0, colornames.White)
|
||||||
|
r.expect(40, 10, colornames.Black)
|
||||||
|
r.expect(40, 12, colornames.Red)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestStrokedPathZeroWidth(t *testing.T) {
|
func TestStrokedPathZeroWidth(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2
|
const width = 2
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -55,6 +55,7 @@ func (p Op) Add(o *op.Ops) {
|
|||||||
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)
|
data[22] = uint8(p.style.Join)
|
||||||
|
bo.PutUint32(data[23:], math.Float32bits(p.style.Miter))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin the path, storing the path data and final Op into ops.
|
// Begin the path, storing the path data and final Op into ops.
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ package clip
|
|||||||
type StrokeStyle struct {
|
type StrokeStyle struct {
|
||||||
Cap StrokeCap
|
Cap StrokeCap
|
||||||
Join StrokeJoin
|
Join StrokeJoin
|
||||||
|
|
||||||
|
// Miter is the limit to apply to a miter joint.
|
||||||
|
// The zero Miter disables the miter joint; setting Miter to +∞
|
||||||
|
// unconditionally enables the miter joint.
|
||||||
|
Miter float32
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokeCap describes the head or tail of a stroked path.
|
// StrokeCap describes the head or tail of a stroked path.
|
||||||
|
|||||||
Reference in New Issue
Block a user