mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 02:15:34 +00:00
gpu: handle rounding errors when splitting quads
This CL handles rounding errors arising when splitting quads into linear segments. Rounding errors would lead to a pair of quad triplets (from,ctl,to) not exactly matching. ie: to(n-1) wouldn't exactly match from(n). Signed-off-by: Sebastien Binet <s@sbinet.org>
This commit is contained in:
committed by
Elias Naur
parent
8676a73a91
commit
5045a9e877
+39
-6
@@ -32,6 +32,15 @@ import (
|
|||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// strokeTolerance is used to reconcile rounding errors arising
|
||||||
|
// when splitting quads into smaller and smaller segments to approximate
|
||||||
|
// them into straight lines, and when joining back segments.
|
||||||
|
//
|
||||||
|
// The magic value of 0.01 was found by striking a compromise between
|
||||||
|
// aesthetic looking (curves did look like curves, even after linearization)
|
||||||
|
// and speed.
|
||||||
|
const strokeTolerance = 0.01
|
||||||
|
|
||||||
type strokeQuad struct {
|
type strokeQuad struct {
|
||||||
contour uint32
|
contour uint32
|
||||||
quad ops.Quad
|
quad ops.Quad
|
||||||
@@ -182,10 +191,9 @@ func (qs strokeQuads) offset(hw float32, stroke clip.StrokeStyle) (rhs, lhs stro
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const tolerance = 0.01
|
|
||||||
for i, state := range states {
|
for i, state := range states {
|
||||||
rhs = rhs.append(strokeQuadBezier(state, +hw, tolerance))
|
rhs = rhs.append(strokeQuadBezier(state, +hw, strokeTolerance))
|
||||||
lhs = lhs.append(strokeQuadBezier(state, -hw, tolerance))
|
lhs = lhs.append(strokeQuadBezier(state, -hw, strokeTolerance))
|
||||||
|
|
||||||
// join the current and next segments
|
// join the current and next segments
|
||||||
if hasNext := i+1 < len(states); hasNext || closed {
|
if hasNext := i+1 < len(states); hasNext || closed {
|
||||||
@@ -263,6 +271,21 @@ func (qs strokeQuads) append(ps strokeQuads) strokeQuads {
|
|||||||
case len(qs) == 0:
|
case len(qs) == 0:
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Consolidate quads and smooth out rounding errors.
|
||||||
|
// We need to also check for the strokeTolerance to correctly handle
|
||||||
|
// join/cap points or on-purpose disjoint quads.
|
||||||
|
p0 := qs[len(qs)-1].quad.To
|
||||||
|
p1 := ps[0].quad.From
|
||||||
|
if p0 != p1 && lenPt(p0.Sub(p1)) < strokeTolerance {
|
||||||
|
qs = append(qs, strokeQuad{
|
||||||
|
quad: ops.Quad{
|
||||||
|
From: p0,
|
||||||
|
Ctrl: p0.Add(p1).Mul(0.5),
|
||||||
|
To: p1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
return append(qs, ps...)
|
return append(qs, ps...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +425,10 @@ func strokeQuadBezier(state strokeState, d, flatness float32) strokeQuads {
|
|||||||
// flattenQuadBezier splits a Bézier quadratic curve into linear sub-segments,
|
// flattenQuadBezier splits a Bézier quadratic curve into linear sub-segments,
|
||||||
// themselves also encoded as Bézier (degenerate, flat) quadratic curves.
|
// themselves also encoded as Bézier (degenerate, flat) quadratic curves.
|
||||||
func flattenQuadBezier(qs strokeQuads, p0, p1, p2 f32.Point, d, flatness float32) strokeQuads {
|
func flattenQuadBezier(qs strokeQuads, p0, p1, p2 f32.Point, d, flatness float32) strokeQuads {
|
||||||
var t float32
|
var (
|
||||||
|
t float32
|
||||||
|
flat64 = float64(flatness)
|
||||||
|
)
|
||||||
for t < 1 {
|
for t < 1 {
|
||||||
s2 := float64((p2.X-p0.X)*(p1.Y-p0.Y) - (p2.Y-p0.Y)*(p1.X-p0.X))
|
s2 := float64((p2.X-p0.X)*(p1.Y-p0.Y) - (p2.Y-p0.Y)*(p1.X-p0.X))
|
||||||
den := math.Hypot(float64(p1.X-p0.X), float64(p1.Y-p0.Y))
|
den := math.Hypot(float64(p1.X-p0.X), float64(p1.Y-p0.Y))
|
||||||
@@ -411,7 +437,6 @@ func flattenQuadBezier(qs strokeQuads, p0, p1, p2 f32.Point, d, flatness float32
|
|||||||
}
|
}
|
||||||
|
|
||||||
s2 /= den
|
s2 /= den
|
||||||
flat64 := float64(flatness)
|
|
||||||
t = 2.0 * float32(math.Sqrt(flat64/3.0/math.Abs(s2)))
|
t = 2.0 * float32(math.Sqrt(flat64/3.0/math.Abs(s2)))
|
||||||
if t >= 1.0 {
|
if t >= 1.0 {
|
||||||
break
|
break
|
||||||
@@ -425,7 +450,15 @@ func flattenQuadBezier(qs strokeQuads, p0, p1, p2 f32.Point, d, flatness float32
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qs *strokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
|
func (qs *strokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
|
||||||
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
|
||||||
|
switch i := len(*qs); i {
|
||||||
|
case 0:
|
||||||
|
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
||||||
|
default:
|
||||||
|
// Address possible rounding errors and use previous point.
|
||||||
|
p0 = (*qs)[i-1].quad.To
|
||||||
|
}
|
||||||
|
|
||||||
p1 = p1.Add(strokePathNorm(p0, ctrl, p1, 1, d))
|
p1 = p1.Add(strokePathNorm(p0, ctrl, p1, 1, d))
|
||||||
|
|
||||||
*qs = append(*qs,
|
*qs = append(*qs,
|
||||||
|
|||||||
Reference in New Issue
Block a user