mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
internal/stroke,gpu: move stroking of path data to package internal/strokg
Pure refactor, preparing for use in op/clip. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+1
-2
@@ -707,14 +707,13 @@ func encodePath(p *pathOp) encoder {
|
|||||||
var enc encoder
|
var enc encoder
|
||||||
verts := p.pathVerts
|
verts := p.pathVerts
|
||||||
if p.stroke.Width > 0 && !supportsStroke(p) {
|
if p.stroke.Width > 0 && !supportsStroke(p) {
|
||||||
quads := decodeToStrokeQuads(verts)
|
|
||||||
ss := stroke.StrokeStyle{
|
ss := stroke.StrokeStyle{
|
||||||
Width: p.stroke.Width,
|
Width: p.stroke.Width,
|
||||||
Miter: p.stroke.Miter,
|
Miter: p.stroke.Miter,
|
||||||
Cap: stroke.StrokeCap(p.stroke.Cap),
|
Cap: stroke.StrokeCap(p.stroke.Cap),
|
||||||
Join: stroke.StrokeJoin(p.stroke.Join),
|
Join: stroke.StrokeJoin(p.stroke.Join),
|
||||||
}
|
}
|
||||||
quads = quads.Stroke(ss, p.dashes)
|
quads := stroke.StrokePathCommands(ss, p.dashes, verts)
|
||||||
for _, quad := range quads {
|
for _, quad := range quads {
|
||||||
q := quad.Quad
|
q := quad.Quad
|
||||||
enc.quad(q.From, q.Ctrl, q.To)
|
enc.quad(q.From, q.Ctrl, q.To)
|
||||||
|
|||||||
+2
-119
@@ -1361,14 +1361,13 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
|
|||||||
switch {
|
switch {
|
||||||
case str.Width > 0:
|
case str.Width > 0:
|
||||||
// Stroke path.
|
// Stroke path.
|
||||||
quads := decodeToStrokeQuads(pathData)
|
|
||||||
ss := stroke.StrokeStyle{
|
ss := stroke.StrokeStyle{
|
||||||
Width: str.Width,
|
Width: str.Width,
|
||||||
Miter: str.Miter,
|
Miter: str.Miter,
|
||||||
Cap: stroke.StrokeCap(str.Cap),
|
Cap: stroke.StrokeCap(str.Cap),
|
||||||
Join: stroke.StrokeJoin(str.Join),
|
Join: stroke.StrokeJoin(str.Join),
|
||||||
}
|
}
|
||||||
quads = quads.Stroke(ss, dashes)
|
quads := stroke.StrokePathCommands(ss, dashes, pathData)
|
||||||
for _, quad := range quads {
|
for _, quad := range quads {
|
||||||
d.qs.contour = quad.Contour
|
d.qs.contour = quad.Contour
|
||||||
quad.Quad = quad.Quad.Transform(tr)
|
quad.Quad = quad.Quad.Transform(tr)
|
||||||
@@ -1403,7 +1402,7 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
|||||||
q = q.Transform(tr)
|
q = q.Transform(tr)
|
||||||
qs.splitAndEncode(q)
|
qs.splitAndEncode(q)
|
||||||
case scene.OpCubic:
|
case scene.OpCubic:
|
||||||
for _, q := range splitCubic(scene.DecodeCubic(cmd)) {
|
for _, q := range stroke.SplitCubic(scene.DecodeCubic(cmd)) {
|
||||||
q = q.Transform(tr)
|
q = q.Transform(tr)
|
||||||
qs.splitAndEncode(q)
|
qs.splitAndEncode(q)
|
||||||
}
|
}
|
||||||
@@ -1414,47 +1413,6 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeToStrokeQuads is like decodeOutlineQuads, except it returns a list of stroke
|
|
||||||
// quads ready to stroke.
|
|
||||||
func decodeToStrokeQuads(pathData []byte) stroke.StrokeQuads {
|
|
||||||
quads := make(stroke.StrokeQuads, 0, 2*len(pathData)/(scene.CommandSize+4))
|
|
||||||
for len(pathData) >= scene.CommandSize+4 {
|
|
||||||
contour := bo.Uint32(pathData)
|
|
||||||
cmd := ops.DecodeCommand(pathData[4:])
|
|
||||||
switch cmd.Op() {
|
|
||||||
case scene.OpLine:
|
|
||||||
var q stroke.QuadSegment
|
|
||||||
q.From, q.To = scene.DecodeLine(cmd)
|
|
||||||
q.Ctrl = q.From.Add(q.To).Mul(.5)
|
|
||||||
quad := stroke.StrokeQuad{
|
|
||||||
Contour: contour,
|
|
||||||
Quad: q,
|
|
||||||
}
|
|
||||||
quads = append(quads, quad)
|
|
||||||
case scene.OpQuad:
|
|
||||||
var q stroke.QuadSegment
|
|
||||||
q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd)
|
|
||||||
quad := stroke.StrokeQuad{
|
|
||||||
Contour: contour,
|
|
||||||
Quad: q,
|
|
||||||
}
|
|
||||||
quads = append(quads, quad)
|
|
||||||
case scene.OpCubic:
|
|
||||||
for _, q := range splitCubic(scene.DecodeCubic(cmd)) {
|
|
||||||
quad := stroke.StrokeQuad{
|
|
||||||
Contour: contour,
|
|
||||||
Quad: q,
|
|
||||||
}
|
|
||||||
quads = append(quads, quad)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unsupported scene command")
|
|
||||||
}
|
|
||||||
pathData = pathData[scene.CommandSize+4:]
|
|
||||||
}
|
|
||||||
return quads
|
|
||||||
}
|
|
||||||
|
|
||||||
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
||||||
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
|
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
|
||||||
if isPureOffset(tr) {
|
if isPureOffset(tr) {
|
||||||
@@ -1534,78 +1492,3 @@ func isPureOffset(t f32.Affine2D) bool {
|
|||||||
a, b, _, d, e, _ := t.Elems()
|
a, b, _, d, e, _ := t.Elems()
|
||||||
return a == 1 && b == 0 && d == 0 && e == 1
|
return a == 1 && b == 0 && d == 0 && e == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitCubic(from, ctrl0, ctrl1, to f32.Point) []stroke.QuadSegment {
|
|
||||||
quads := make([]stroke.QuadSegment, 0, 10)
|
|
||||||
// Set the maximum distance proportionally to the longest side
|
|
||||||
// of the bounding rectangle.
|
|
||||||
hull := f32.Rectangle{
|
|
||||||
Min: from,
|
|
||||||
Max: ctrl0,
|
|
||||||
}.Canon().Add(ctrl1).Add(to)
|
|
||||||
l := hull.Dx()
|
|
||||||
if h := hull.Dy(); h > l {
|
|
||||||
l = h
|
|
||||||
}
|
|
||||||
approxCubeTo(&quads, 0, l*0.001, from, ctrl0, ctrl1, to)
|
|
||||||
return quads
|
|
||||||
}
|
|
||||||
|
|
||||||
// approxCube approximates a cubic Bézier by a series of quadratic
|
|
||||||
// curves.
|
|
||||||
func approxCubeTo(quads *[]stroke.QuadSegment, splits int, maxDist float32, from, ctrl0, ctrl1, to f32.Point) int {
|
|
||||||
// The idea is from
|
|
||||||
// https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
|
|
||||||
// where a quadratic approximates a cubic by eliminating its t³ term
|
|
||||||
// from its polynomial expression anchored at the starting point:
|
|
||||||
//
|
|
||||||
// P(t) = pen + 3t(ctrl0 - pen) + 3t²(ctrl1 - 2ctrl0 + pen) + t³(to - 3ctrl1 + 3ctrl0 - pen)
|
|
||||||
//
|
|
||||||
// The control point for the new quadratic Q1 that shares starting point, pen, with P is
|
|
||||||
//
|
|
||||||
// C1 = (3ctrl0 - pen)/2
|
|
||||||
//
|
|
||||||
// The reverse cubic anchored at the end point has the polynomial
|
|
||||||
//
|
|
||||||
// P'(t) = to + 3t(ctrl1 - to) + 3t²(ctrl0 - 2ctrl1 + to) + t³(pen - 3ctrl0 + 3ctrl1 - to)
|
|
||||||
//
|
|
||||||
// The corresponding quadratic Q2 that shares the end point, to, with P has control
|
|
||||||
// point
|
|
||||||
//
|
|
||||||
// C2 = (3ctrl1 - to)/2
|
|
||||||
//
|
|
||||||
// The combined quadratic Bézier, Q, shares both start and end points with its cubic
|
|
||||||
// and use the midpoint between the two curves Q1 and Q2 as control point:
|
|
||||||
//
|
|
||||||
// C = (3ctrl0 - pen + 3ctrl1 - to)/4
|
|
||||||
c := ctrl0.Mul(3).Sub(from).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0)
|
|
||||||
const maxSplits = 32
|
|
||||||
if splits >= maxSplits {
|
|
||||||
*quads = append(*quads, stroke.QuadSegment{From: from, Ctrl: c, To: to})
|
|
||||||
return splits
|
|
||||||
}
|
|
||||||
// The maximum distance between the cubic P and its approximation Q given t
|
|
||||||
// can be shown to be
|
|
||||||
//
|
|
||||||
// d = sqrt(3)/36*|to - 3ctrl1 + 3ctrl0 - pen|
|
|
||||||
//
|
|
||||||
// To save a square root, compare d² with the squared tolerance.
|
|
||||||
v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(from)
|
|
||||||
d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36)
|
|
||||||
if d2 <= maxDist*maxDist {
|
|
||||||
*quads = append(*quads, stroke.QuadSegment{From: from, Ctrl: c, To: to})
|
|
||||||
return splits
|
|
||||||
}
|
|
||||||
// De Casteljau split the curve and approximate the halves.
|
|
||||||
t := float32(0.5)
|
|
||||||
c0 := from.Add(ctrl0.Sub(from).Mul(t))
|
|
||||||
c1 := ctrl0.Add(ctrl1.Sub(ctrl0).Mul(t))
|
|
||||||
c2 := ctrl1.Add(to.Sub(ctrl1).Mul(t))
|
|
||||||
c01 := c0.Add(c1.Sub(c0).Mul(t))
|
|
||||||
c12 := c1.Add(c2.Sub(c1).Mul(t))
|
|
||||||
c0112 := c01.Add(c12.Sub(c01).Mul(t))
|
|
||||||
splits++
|
|
||||||
splits = approxCubeTo(quads, splits, maxDist, from, c0, c01, c0112)
|
|
||||||
splits = approxCubeTo(quads, splits, maxDist, c0112, c12, c2, to)
|
|
||||||
return splits
|
|
||||||
}
|
|
||||||
|
|||||||
+124
-1
@@ -26,9 +26,12 @@
|
|||||||
package stroke
|
package stroke
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/internal/ops"
|
||||||
|
"gioui.org/internal/scene"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The following are copies of types from op/clip to avoid a circular import of
|
// The following are copies of types from op/clip to avoid a circular import of
|
||||||
@@ -152,7 +155,7 @@ func (qs StrokeQuads) split() []StrokeQuads {
|
|||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qs StrokeQuads) Stroke(stroke StrokeStyle, dashes DashOp) StrokeQuads {
|
func (qs StrokeQuads) stroke(stroke StrokeStyle, dashes DashOp) StrokeQuads {
|
||||||
if !IsSolidLine(dashes) {
|
if !IsSolidLine(dashes) {
|
||||||
qs = qs.dash(dashes)
|
qs = qs.dash(dashes)
|
||||||
}
|
}
|
||||||
@@ -765,3 +768,123 @@ func dist(p1, p2 f32.Point) float64 {
|
|||||||
)
|
)
|
||||||
return math.Hypot(dx, dy)
|
return math.Hypot(dx, dy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StrokePathCommands(style StrokeStyle, dashes DashOp, scene []byte) StrokeQuads {
|
||||||
|
quads := decodeToStrokeQuads(scene)
|
||||||
|
return quads.stroke(style, dashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeToStrokeQuads decodes scene commands to quads ready to stroke.
|
||||||
|
func decodeToStrokeQuads(pathData []byte) StrokeQuads {
|
||||||
|
quads := make(StrokeQuads, 0, 2*len(pathData)/(scene.CommandSize+4))
|
||||||
|
for len(pathData) >= scene.CommandSize+4 {
|
||||||
|
contour := binary.LittleEndian.Uint32(pathData)
|
||||||
|
cmd := ops.DecodeCommand(pathData[4:])
|
||||||
|
switch cmd.Op() {
|
||||||
|
case scene.OpLine:
|
||||||
|
var q QuadSegment
|
||||||
|
q.From, q.To = scene.DecodeLine(cmd)
|
||||||
|
q.Ctrl = q.From.Add(q.To).Mul(.5)
|
||||||
|
quad := StrokeQuad{
|
||||||
|
Contour: contour,
|
||||||
|
Quad: q,
|
||||||
|
}
|
||||||
|
quads = append(quads, quad)
|
||||||
|
case scene.OpQuad:
|
||||||
|
var q QuadSegment
|
||||||
|
q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd)
|
||||||
|
quad := StrokeQuad{
|
||||||
|
Contour: contour,
|
||||||
|
Quad: q,
|
||||||
|
}
|
||||||
|
quads = append(quads, quad)
|
||||||
|
case scene.OpCubic:
|
||||||
|
for _, q := range SplitCubic(scene.DecodeCubic(cmd)) {
|
||||||
|
quad := StrokeQuad{
|
||||||
|
Contour: contour,
|
||||||
|
Quad: q,
|
||||||
|
}
|
||||||
|
quads = append(quads, quad)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unsupported scene command")
|
||||||
|
}
|
||||||
|
pathData = pathData[scene.CommandSize+4:]
|
||||||
|
}
|
||||||
|
return quads
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitCubic(from, ctrl0, ctrl1, to f32.Point) []QuadSegment {
|
||||||
|
quads := make([]QuadSegment, 0, 10)
|
||||||
|
// Set the maximum distance proportionally to the longest side
|
||||||
|
// of the bounding rectangle.
|
||||||
|
hull := f32.Rectangle{
|
||||||
|
Min: from,
|
||||||
|
Max: ctrl0,
|
||||||
|
}.Canon().Add(ctrl1).Add(to)
|
||||||
|
l := hull.Dx()
|
||||||
|
if h := hull.Dy(); h > l {
|
||||||
|
l = h
|
||||||
|
}
|
||||||
|
approxCubeTo(&quads, 0, l*0.001, from, ctrl0, ctrl1, to)
|
||||||
|
return quads
|
||||||
|
}
|
||||||
|
|
||||||
|
// approxCubeTo approximates a cubic Bézier by a series of quadratic
|
||||||
|
// curves.
|
||||||
|
func approxCubeTo(quads *[]QuadSegment, splits int, maxDist float32, from, ctrl0, ctrl1, to f32.Point) int {
|
||||||
|
// The idea is from
|
||||||
|
// https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
|
||||||
|
// where a quadratic approximates a cubic by eliminating its t³ term
|
||||||
|
// from its polynomial expression anchored at the starting point:
|
||||||
|
//
|
||||||
|
// P(t) = pen + 3t(ctrl0 - pen) + 3t²(ctrl1 - 2ctrl0 + pen) + t³(to - 3ctrl1 + 3ctrl0 - pen)
|
||||||
|
//
|
||||||
|
// The control point for the new quadratic Q1 that shares starting point, pen, with P is
|
||||||
|
//
|
||||||
|
// C1 = (3ctrl0 - pen)/2
|
||||||
|
//
|
||||||
|
// The reverse cubic anchored at the end point has the polynomial
|
||||||
|
//
|
||||||
|
// P'(t) = to + 3t(ctrl1 - to) + 3t²(ctrl0 - 2ctrl1 + to) + t³(pen - 3ctrl0 + 3ctrl1 - to)
|
||||||
|
//
|
||||||
|
// The corresponding quadratic Q2 that shares the end point, to, with P has control
|
||||||
|
// point
|
||||||
|
//
|
||||||
|
// C2 = (3ctrl1 - to)/2
|
||||||
|
//
|
||||||
|
// The combined quadratic Bézier, Q, shares both start and end points with its cubic
|
||||||
|
// and use the midpoint between the two curves Q1 and Q2 as control point:
|
||||||
|
//
|
||||||
|
// C = (3ctrl0 - pen + 3ctrl1 - to)/4
|
||||||
|
c := ctrl0.Mul(3).Sub(from).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0)
|
||||||
|
const maxSplits = 32
|
||||||
|
if splits >= maxSplits {
|
||||||
|
*quads = append(*quads, QuadSegment{From: from, Ctrl: c, To: to})
|
||||||
|
return splits
|
||||||
|
}
|
||||||
|
// The maximum distance between the cubic P and its approximation Q given t
|
||||||
|
// can be shown to be
|
||||||
|
//
|
||||||
|
// d = sqrt(3)/36*|to - 3ctrl1 + 3ctrl0 - pen|
|
||||||
|
//
|
||||||
|
// To save a square root, compare d² with the squared tolerance.
|
||||||
|
v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(from)
|
||||||
|
d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36)
|
||||||
|
if d2 <= maxDist*maxDist {
|
||||||
|
*quads = append(*quads, QuadSegment{From: from, Ctrl: c, To: to})
|
||||||
|
return splits
|
||||||
|
}
|
||||||
|
// De Casteljau split the curve and approximate the halves.
|
||||||
|
t := float32(0.5)
|
||||||
|
c0 := from.Add(ctrl0.Sub(from).Mul(t))
|
||||||
|
c1 := ctrl0.Add(ctrl1.Sub(ctrl0).Mul(t))
|
||||||
|
c2 := ctrl1.Add(to.Sub(ctrl1).Mul(t))
|
||||||
|
c01 := c0.Add(c1.Sub(c0).Mul(t))
|
||||||
|
c12 := c1.Add(c2.Sub(c1).Mul(t))
|
||||||
|
c0112 := c01.Add(c12.Sub(c01).Mul(t))
|
||||||
|
splits++
|
||||||
|
splits = approxCubeTo(quads, splits, maxDist, from, c0, c01, c0112)
|
||||||
|
splits = approxCubeTo(quads, splits, maxDist, c0112, c12, c2, to)
|
||||||
|
return splits
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user