op/clip: remove complex stroke support
In a discussion with Raph Levien, the author of our compute renderer implementation, it became clear to me that it's not at all certain that complex strokes will ever be efficiently supported by a GPU renderer. At the same time, the machinery for converting a complex stroke to a GPU-friendly outline has a significant maintenance cost. Further, it is surprising to users that complex strokes are significantly slower and allocate memory. This change removes support for complex strokes, leaving only round-capped, round-joined strokes supported by the compute renderer. The default renderer still converts all strokes to outline, but it also caches the result. This is an API change. The complex stroke conversion code has been moved to the external gioui.org/x/stroke package, with a similar API. Updats gio#282 (Inkeliz brought up the allocation issue) Signed-off-by: Elias Naur <mail@eliasnaur.com>
@@ -28,7 +28,6 @@ import (
|
||||
"gioui.org/internal/scene"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/shader"
|
||||
"gioui.org/shader/gio"
|
||||
"gioui.org/shader/piet"
|
||||
@@ -236,10 +235,10 @@ type encoderState struct {
|
||||
// clipKey completely describes a clip operation (along with its path) and is appropriate
|
||||
// for hashing and equality checks.
|
||||
type clipKey struct {
|
||||
bounds f32.Rectangle
|
||||
stroke clip.StrokeStyle
|
||||
relTrans f32.Affine2D
|
||||
pathHash uint64
|
||||
bounds f32.Rectangle
|
||||
strokeWidth float32
|
||||
relTrans f32.Affine2D
|
||||
pathHash uint64
|
||||
}
|
||||
|
||||
// paintKey completely defines a paint operation. It is suitable for hashing and
|
||||
@@ -1683,7 +1682,7 @@ func (c *opsCollector) reset() {
|
||||
c.layers = c.layers[:0]
|
||||
}
|
||||
|
||||
func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, key ops.Key, hash uint64, stroke clip.StrokeStyle, push bool) {
|
||||
func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, key ops.Key, hash uint64, strokeWidth float32, push bool) {
|
||||
// Rectangle clip regions.
|
||||
if len(path) == 0 && !push {
|
||||
// If the rectangular clip region contains a previous path it can be discarded.
|
||||
@@ -1713,10 +1712,10 @@ func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle,
|
||||
intersect: intersect,
|
||||
push: push,
|
||||
clipKey: clipKey{
|
||||
bounds: bounds,
|
||||
relTrans: state.relTrans,
|
||||
stroke: stroke,
|
||||
pathHash: hash,
|
||||
bounds: bounds,
|
||||
relTrans: state.relTrans,
|
||||
strokeWidth: strokeWidth,
|
||||
pathHash: hash,
|
||||
},
|
||||
})
|
||||
state.clip = &c.clipStates[len(c.clipStates)-1]
|
||||
@@ -1742,9 +1741,9 @@ func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textur
|
||||
key ops.Key
|
||||
hash uint64
|
||||
}
|
||||
str clip.StrokeStyle
|
||||
strWidth float32
|
||||
)
|
||||
c.addClip(&state, fview, fview, nil, ops.Key{}, 0, clip.StrokeStyle{}, false)
|
||||
c.addClip(&state, fview, fview, nil, ops.Key{}, 0, 0, false)
|
||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||
switch ops.OpType(encOp.Data[0]) {
|
||||
case ops.TypeProfile:
|
||||
@@ -1763,7 +1762,7 @@ func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textur
|
||||
state.t = st.t
|
||||
state.relTrans = st.relTrans
|
||||
case ops.TypeStroke:
|
||||
str = decodeStrokeOp(encOp.Data)
|
||||
strWidth = decodeStrokeOp(encOp.Data)
|
||||
case ops.TypePath:
|
||||
hash := bo.Uint64(encOp.Data[1:])
|
||||
encOp, ok = r.Decode()
|
||||
@@ -1776,9 +1775,9 @@ func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textur
|
||||
case ops.TypeClip:
|
||||
var op clipOp
|
||||
op.decode(encOp.Data)
|
||||
c.addClip(&state, fview, op.bounds, pathData.data, pathData.key, pathData.hash, str, op.push)
|
||||
c.addClip(&state, fview, op.bounds, pathData.data, pathData.key, pathData.hash, strWidth, op.push)
|
||||
pathData.data = nil
|
||||
str = clip.StrokeStyle{}
|
||||
strWidth = 0
|
||||
case ops.TypePopClip:
|
||||
for {
|
||||
push := state.clip.push
|
||||
@@ -1806,7 +1805,7 @@ func (c *collector) collect(root *op.Ops, viewport image.Point, texOps *[]textur
|
||||
if paintState.matType == materialTexture {
|
||||
// Clip to the bounds of the image, to hide other images in the atlas.
|
||||
bounds := paintState.image.src.Bounds()
|
||||
c.addClip(&paintState, fview, layout.FRect(bounds), nil, ops.Key{}, 0, clip.StrokeStyle{}, false)
|
||||
c.addClip(&paintState, fview, layout.FRect(bounds), nil, ops.Key{}, 0, 0, false)
|
||||
}
|
||||
intersect := paintState.clip.intersect
|
||||
if intersect.Empty() {
|
||||
@@ -2101,9 +2100,9 @@ func encodeOp(viewport image.Point, absOff image.Point, enc *encoder, texOps []t
|
||||
enc.transform(inv)
|
||||
for i := len(op.clipStack) - 1; i >= 0; i-- {
|
||||
cl := op.clipStack[i]
|
||||
if str := cl.state.stroke; str.Width > 0 {
|
||||
if w := cl.state.strokeWidth; w > 0 {
|
||||
enc.fillMode(scene.FillModeStroke)
|
||||
enc.lineWidth(str.Width)
|
||||
enc.lineWidth(w)
|
||||
fillMode = scene.FillModeStroke
|
||||
} else if fillMode != scene.FillModeNonzero {
|
||||
enc.fillMode(scene.FillModeNonzero)
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"gioui.org/internal/stroke"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/shader"
|
||||
"gioui.org/shader/gio"
|
||||
|
||||
@@ -135,15 +134,13 @@ type imageOp struct {
|
||||
place placement
|
||||
}
|
||||
|
||||
func decodeStrokeOp(data []byte) clip.StrokeStyle {
|
||||
func decodeStrokeOp(data []byte) float32 {
|
||||
_ = data[4]
|
||||
if ops.OpType(data[0]) != ops.TypeStroke {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
return clip.StrokeStyle{
|
||||
Width: math.Float32frombits(bo.Uint32(data[1:])),
|
||||
}
|
||||
return math.Float32frombits(bo.Uint32(data[1:]))
|
||||
}
|
||||
|
||||
type quadsOp struct {
|
||||
@@ -907,9 +904,9 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
|
||||
|
||||
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
||||
var (
|
||||
quads quadsOp
|
||||
str clip.StrokeStyle
|
||||
state drawState
|
||||
quads quadsOp
|
||||
strWidth float32
|
||||
state drawState
|
||||
)
|
||||
reset := func() {
|
||||
state = drawState{
|
||||
@@ -934,7 +931,7 @@ loop:
|
||||
d.transStack = d.transStack[:n-1]
|
||||
|
||||
case ops.TypeStroke:
|
||||
str = decodeStrokeOp(encOp.Data)
|
||||
strWidth = decodeStrokeOp(encOp.Data)
|
||||
|
||||
case ops.TypePath:
|
||||
encOp, ok = r.Decode()
|
||||
@@ -960,7 +957,7 @@ loop:
|
||||
op.bounds = v.bounds
|
||||
} else {
|
||||
pathData, bounds := d.buildVerts(
|
||||
quads.aux, trans, op.outline, str,
|
||||
quads.aux, trans, op.outline, strWidth,
|
||||
)
|
||||
op.bounds = bounds
|
||||
quads.aux = pathData
|
||||
@@ -974,7 +971,7 @@ loop:
|
||||
}
|
||||
d.addClipPath(&state, quads.aux, quads.key, op.bounds, off, op.push)
|
||||
quads = quadsOp{}
|
||||
str = clip.StrokeStyle{}
|
||||
strWidth = 0
|
||||
case ops.TypePopClip:
|
||||
for {
|
||||
push := state.cpath.push
|
||||
@@ -1343,7 +1340,7 @@ func (d *drawOps) writeVertCache(n int) []byte {
|
||||
}
|
||||
|
||||
// transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
|
||||
func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str clip.StrokeStyle) (verts []byte, bounds f32.Rectangle) {
|
||||
func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, strWidth float32) (verts []byte, bounds f32.Rectangle) {
|
||||
inf := float32(math.Inf(+1))
|
||||
d.qs.bounds = f32.Rectangle{
|
||||
Min: f32.Point{X: inf, Y: inf},
|
||||
@@ -1353,15 +1350,12 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
|
||||
startLength := len(d.vertCache)
|
||||
|
||||
switch {
|
||||
case str.Width > 0:
|
||||
case strWidth > 0:
|
||||
// Stroke path.
|
||||
ss := stroke.StrokeStyle{
|
||||
Width: str.Width,
|
||||
Miter: str.Miter,
|
||||
Cap: stroke.StrokeCap(str.Cap),
|
||||
Join: stroke.StrokeJoin(str.Join),
|
||||
Width: strWidth,
|
||||
}
|
||||
quads := stroke.StrokePathCommands(ss, stroke.DashOp{}, pathData)
|
||||
quads := stroke.StrokePathCommands(ss, pathData)
|
||||
for _, quad := range quads {
|
||||
d.qs.contour = quad.Contour
|
||||
quad.Quad = quad.Quad.Transform(tr)
|
||||
|
||||
@@ -125,10 +125,8 @@ func TestTexturedStrokeClipped(t *testing.T) {
|
||||
smallSquares.Add(o)
|
||||
defer op.Offset(f32.Pt(50, 50)).Push(o).Pop()
|
||||
defer clip.Stroke{
|
||||
Path: clip.RRect{Rect: f32.Rect(0, 0, 30, 30)}.Path(o),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
},
|
||||
Path: clip.RRect{Rect: f32.Rect(0, 0, 30, 30)}.Path(o),
|
||||
Width: 10,
|
||||
}.Op().Push(o).Pop()
|
||||
defer clip.RRect{Rect: f32.Rect(-30, -30, 60, 60)}.Push(o).Pop()
|
||||
defer op.Offset(f32.Pt(-10, -10)).Push(o).Pop()
|
||||
@@ -142,10 +140,8 @@ func TestTexturedStroke(t *testing.T) {
|
||||
smallSquares.Add(o)
|
||||
defer op.Offset(f32.Pt(50, 50)).Push(o).Pop()
|
||||
defer clip.Stroke{
|
||||
Path: clip.RRect{Rect: f32.Rect(0, 0, 30, 30)}.Path(o),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
},
|
||||
Path: clip.RRect{Rect: f32.Rect(0, 0, 30, 30)}.Path(o),
|
||||
Width: 10,
|
||||
}.Op().Push(o).Pop()
|
||||
defer op.Offset(f32.Pt(-10, -10)).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
@@ -165,175 +161,8 @@ func TestPaintClippedTexture(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrokedPathBevelFlat(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
p := newStrokedPath(o)
|
||||
defer clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2.5,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(10, 50, colornames.Red)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrokedPathBevelRound(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
p := newStrokedPath(o)
|
||||
defer clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2.5,
|
||||
Cap: clip.RoundCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(10, 50, colornames.Red)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrokedPathBevelSquare(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
p := newStrokedPath(o)
|
||||
defer clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2.5,
|
||||
Cap: clip.SquareCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(10, 50, colornames.Red)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrokedPathRoundRound(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
p := newStrokedPath(o)
|
||||
defer clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2.5,
|
||||
Cap: clip.RoundCap,
|
||||
Join: clip.RoundJoin,
|
||||
},
|
||||
}.Op().Push(o).Pop()
|
||||
|
||||
paint.Fill(o, red)
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(10, 50, colornames.Red)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrokedPathFlatMiter(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
p := newZigZagPath(o)
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
Miter: 5,
|
||||
},
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
p := newZigZagPath(o)
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(40, 10, colornames.Black)
|
||||
r.expect(40, 12, colornames.Red)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrokedPathFlatMiterInf(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
p := newZigZagPath(o)
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
p := newZigZagPath(o)
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
cl.Pop()
|
||||
}
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(40, 10, colornames.Black)
|
||||
r.expect(40, 12, colornames.Red)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrokedPathZeroWidth(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
p := new(clip.Path)
|
||||
p.Begin(o)
|
||||
p.Move(f32.Pt(10, 50))
|
||||
p.Line(f32.Pt(50, 0))
|
||||
cl := clip.Stroke{
|
||||
Path: p.End(),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o)
|
||||
|
||||
paint.Fill(o, black)
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
{
|
||||
p := new(clip.Path)
|
||||
p.Begin(o)
|
||||
@@ -354,213 +183,3 @@ func TestStrokedPathZeroWidth(t *testing.T) {
|
||||
r.expect(65, 50, transparent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDashedPathFlatCapEllipse(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
p := newEllipsePath(o)
|
||||
|
||||
var dash clip.Dash
|
||||
dash.Begin(o)
|
||||
dash.Dash(5)
|
||||
dash.Dash(3)
|
||||
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
Dashes: dash.End(),
|
||||
}.Op().Push(o)
|
||||
|
||||
paint.Fill(
|
||||
o,
|
||||
red,
|
||||
)
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
p := newEllipsePath(o)
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
},
|
||||
}.Op().Push(o)
|
||||
|
||||
paint.Fill(
|
||||
o,
|
||||
black,
|
||||
)
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(0, 62, colornames.Red)
|
||||
r.expect(0, 65, colornames.Black)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDashedPathFlatCapZ(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
p := newZigZagPath(o)
|
||||
var dash clip.Dash
|
||||
dash.Begin(o)
|
||||
dash.Dash(5)
|
||||
dash.Dash(3)
|
||||
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
Dashes: dash.End(),
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
{
|
||||
p := newZigZagPath(o)
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
cl.Pop()
|
||||
}
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(40, 10, colornames.Black)
|
||||
r.expect(40, 12, colornames.Red)
|
||||
r.expect(46, 12, transparent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDashedPathFlatCapZNoDash(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
p := newZigZagPath(o)
|
||||
var dash clip.Dash
|
||||
dash.Begin(o)
|
||||
dash.Phase(1)
|
||||
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
Dashes: dash.End(),
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
cl := clip.Stroke{
|
||||
Path: newZigZagPath(o),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
cl.Pop()
|
||||
}
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(40, 10, colornames.Black)
|
||||
r.expect(40, 12, colornames.Red)
|
||||
r.expect(46, 12, colornames.Red)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDashedPathFlatCapZNoPath(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
{
|
||||
var dash clip.Dash
|
||||
dash.Begin(o)
|
||||
dash.Dash(0)
|
||||
cl := clip.Stroke{
|
||||
Path: newZigZagPath(o),
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 10,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
Miter: float32(math.Inf(+1)),
|
||||
},
|
||||
Dashes: dash.End(),
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, red)
|
||||
cl.Pop()
|
||||
}
|
||||
{
|
||||
p := newZigZagPath(o)
|
||||
cl := clip.Stroke{
|
||||
Path: p,
|
||||
Style: clip.StrokeStyle{
|
||||
Width: 2,
|
||||
Cap: clip.FlatCap,
|
||||
Join: clip.BevelJoin,
|
||||
},
|
||||
}.Op().Push(o)
|
||||
paint.Fill(o, black)
|
||||
cl.Pop()
|
||||
}
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(40, 10, colornames.Black)
|
||||
r.expect(40, 12, transparent)
|
||||
r.expect(46, 12, transparent)
|
||||
})
|
||||
}
|
||||
|
||||
func newStrokedPath(o *op.Ops) clip.PathSpec {
|
||||
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))
|
||||
return p.End()
|
||||
}
|
||||
|
||||
func newZigZagPath(o *op.Ops) clip.PathSpec {
|
||||
p := new(clip.Path)
|
||||
p.Begin(o)
|
||||
p.Move(f32.Pt(40, 10))
|
||||
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))
|
||||
return p.End()
|
||||
}
|
||||
|
||||
func newEllipsePath(o *op.Ops) clip.PathSpec {
|
||||
p := new(clip.Path)
|
||||
p.Begin(o)
|
||||
p.Move(f32.Pt(0, 65))
|
||||
p.Line(f32.Pt(20, 0))
|
||||
p.Arc(f32.Pt(20, 0), f32.Pt(70, 0), 2*math.Pi)
|
||||
return p.End()
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 765 B |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |