~eliasnaur/gio-patches

1

[PATCH gio v4 2/2] gpu: handle rounding errors when splitting quads

Details
Message ID
<gGkoRiKwoSHOxpxG6MUyRtRbWH58RZBz3SRzxjixz8@cp3-web-033.plabs.ch>
DKIM signature
missing
Download raw message
Patch: +39 -6
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>
---
 gpu/stroke.go | 45 +++++++++++++++++++++++++++++++++++++++------
 1 file changed, 39 insertions(+), 6 deletions(-)

diff --git a/gpu/stroke.go b/gpu/stroke.go
index ca37e59..031252f 100644
--- a/gpu/stroke.go
+++ b/gpu/stroke.go
@@ -32,6 +32,15 @@ import (
	"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 {
	contour uint32
	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 {
		rhs = rhs.append(strokeQuadBezier(state, +hw, tolerance))
		lhs = lhs.append(strokeQuadBezier(state, -hw, tolerance))
		rhs = rhs.append(strokeQuadBezier(state, +hw, strokeTolerance))
		lhs = lhs.append(strokeQuadBezier(state, -hw, strokeTolerance))

		// join the current and next segments
		if hasNext := i+1 < len(states); hasNext || closed {
@@ -263,6 +271,21 @@ func (qs strokeQuads) append(ps strokeQuads) strokeQuads {
	case len(qs) == 0:
		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...)
}

@@ -402,7 +425,10 @@ func strokeQuadBezier(state strokeState, d, flatness float32) strokeQuads {
// flattenQuadBezier splits a Bézier quadratic curve into linear sub-segments,
// themselves also encoded as Bézier (degenerate, flat) quadratic curves.
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 {
		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))
@@ -411,7 +437,6 @@ func flattenQuadBezier(qs strokeQuads, p0, p1, p2 f32.Point, d, flatness float32
		}

		s2 /= den
		flat64 := float64(flatness)
		t = 2.0 * float32(math.Sqrt(flat64/3.0/math.Abs(s2)))
		if t >= 1.0 {
			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) {
	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))

	*qs = append(*qs,
-- 
2.30.0
Details
Message ID
<C88P6IV6DVSQ.3BRB99FTYBEWE@themachine>
In-Reply-To
<gGkoRiKwoSHOxpxG6MUyRtRbWH58RZBz3SRzxjixz8@cp3-web-033.plabs.ch> (view parent)
DKIM signature
pass
Download raw message
Thank you, the rendertests succeed on both CI and my machine now.

Elias
Reply to thread Export thread (mbox)