undid some of the changes from the last update since it didn't work properly; added some tests

This commit is contained in:
Thomas Friedel 2019-01-25 13:16:48 +01:00
parent c5d148331b
commit c1f855278d
7 changed files with 104 additions and 76 deletions

View file

@ -43,7 +43,7 @@ type drawState struct {
lineAlpha float64
lineWidth float64
lineJoin lineJoin
lineEnd lineEnd
lineCap lineCap
miterLimitSqr float64
globalAlpha float64
@ -83,9 +83,9 @@ type scissor struct {
}
type lineJoin uint8
type lineEnd uint8
type lineCap uint8
// Line join and end constants for SetLineJoin and SetLineEnd
// Line join and end constants for SetLineJoin and SetLineCap
const (
Miter = iota
Bevel
@ -729,10 +729,15 @@ func (cv *Canvas) SetLineJoin(join lineJoin) {
cv.state.lineJoin = join
}
// SetLineEnd sets the style of line endings for rendering a path with Stroke
// SetLineEnd is a synonym for SetLineCap that was misnamed by mistake. Deprecated
func (cv *Canvas) SetLineEnd(cap lineCap) {
cv.SetLineCap(cap)
}
// SetLineCap sets the style of line endings for rendering a path with Stroke
// The value can be Butt, Square, or Round
func (cv *Canvas) SetLineEnd(end lineEnd) {
cv.state.lineEnd = end
func (cv *Canvas) SetLineCap(cap lineCap) {
cv.state.lineCap = cap
}
// SetLineDash sets the line dash style

View file

@ -469,3 +469,53 @@ func TestConvexSelfIntersecting(t *testing.T) {
cv.Fill()
})
}
func TestTransform(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
path := canvas.NewPath2D()
path.MoveTo(-10, -10)
path.LineTo(10, -10)
path.LineTo(0, 10)
path.ClosePath()
cv.Translate(40, 20)
cv.BeginPath()
cv.LineTo(10, 10)
cv.LineTo(30, 10)
cv.LineTo(20, 30)
cv.ClosePath()
cv.SetStrokeStyle("#F00")
cv.Stroke()
cv.SetStrokeStyle("#0F0")
cv.StrokePath(path)
cv.Translate(20, 0)
cv.SetStrokeStyle("#00F")
cv.StrokePath(path)
cv.Translate(-40, 30)
cv.BeginPath()
cv.LineTo(10, 10)
cv.LineTo(30, 10)
cv.LineTo(20, 30)
cv.ClosePath()
cv.Translate(20, 0)
cv.SetStrokeStyle("#FF0")
cv.Stroke()
cv.Translate(20, 0)
cv.SetStrokeStyle("#F0F")
cv.StrokePath(path)
})
}
func TestTransform2(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#FFF")
cv.SetLineWidth(16)
cv.MoveTo(20, 20)
cv.LineTo(20, 50)
cv.Scale(2, 1)
cv.LineTo(45, 80)
cv.SetLineJoin(canvas.Round)
cv.SetLineCap(canvas.Round)
cv.Stroke()
})
}

View file

@ -10,7 +10,6 @@ type Path2D struct {
type pathPoint struct {
pos vec
tf vec
next vec
flags pathPointFlag
}
@ -39,7 +38,7 @@ func (p *Path2D) MoveTo(x, y float64) {
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, vec{x, y}, 0.1) {
return
}
p.p = append(p.p, pathPoint{pos: vec{x, y}, tf: vec{x, y}, flags: pathMove}) // todo more flags probably
p.p = append(p.p, pathPoint{pos: vec{x, y}, flags: pathMove}) // todo more flags probably
p.cwSum = 0
p.move = vec{x, y}
}
@ -61,7 +60,7 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
prev := &p.p[count-1]
prev.next = vec{x, y}
prev.flags |= pathAttach
p.p = append(p.p, pathPoint{pos: vec{x, y}, tf: vec{x, y}})
p.p = append(p.p, pathPoint{pos: vec{x, y}})
newp := &p.p[count]
px, py := prev.pos[0], prev.pos[1]

103
paths.go
View file

@ -77,10 +77,16 @@ func (cv *Canvas) Stroke() {
// StrokePath uses the current StrokeStyle to draw the given path
func (cv *Canvas) StrokePath(path *Path2D) {
for i := range path.p {
path.p[i].tf = cv.tf(path.p[i].pos)
path2 := Path2D{
p: make([]pathPoint, len(path.p)),
}
cv.strokePath(path)
// todo avoid allocation
for i, pt := range path.p {
path2.p[i].pos = cv.tf(pt.pos)
path2.p[i].next = cv.tf(pt.next)
path2.p[i].flags = pt.flags
}
cv.strokePath(&path2)
}
func (cv *Canvas) strokePath(path *Path2D) {
@ -116,7 +122,7 @@ func (cv *Canvas) strokePath(path *Path2D) {
lp3 := p1.sub(v1)
if start {
switch cv.state.lineEnd {
switch cv.state.lineCap {
case Butt:
// no need to do anything
case Square:
@ -128,7 +134,7 @@ func (cv *Canvas) strokePath(path *Path2D) {
}
if p.flags&pathAttach == 0 {
switch cv.state.lineEnd {
switch cv.state.lineCap {
case Butt:
// no need to do anything
case Square:
@ -139,14 +145,9 @@ func (cv *Canvas) strokePath(path *Path2D) {
}
}
lp0tf := cv.tf(lp0)
lp1tf := cv.tf(lp1)
lp2tf := cv.tf(lp2)
lp3tf := cv.tf(lp3)
tris = append(tris,
float32(lp0tf[0]), float32(lp0tf[1]), float32(lp1tf[0]), float32(lp1tf[1]), float32(lp3tf[0]), float32(lp3tf[1]),
float32(lp0tf[0]), float32(lp0tf[1]), float32(lp3tf[0]), float32(lp3tf[1]), float32(lp2tf[0]), float32(lp2tf[1]))
float32(lp0[0]), float32(lp0[1]), float32(lp1[0]), float32(lp1[1]), float32(lp3[0]), float32(lp3[1]),
float32(lp0[0]), float32(lp0[1]), float32(lp3[0]), float32(lp3[1]), float32(lp2[0]), float32(lp2[1]))
if p.flags&pathAttach != 0 && cv.state.lineWidth > 1 {
tris = cv.lineJoint(p, p0, p1, p.next, lp0, lp1, lp2, lp3, tris)
@ -228,19 +229,17 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
}
v := pp.pos.sub(lp.pos)
vtf := pp.tf.sub(lp.tf)
vl := v.len()
prev := ldo
for vl > 0 {
draw := ldp%2 == 0
newp := pathPoint{pos: pp.pos, tf: pp.tf}
newp := pathPoint{pos: pp.pos}
ldo += vl
if ldo > cv.state.lineDash[ldp] {
ldo = 0
dl := cv.state.lineDash[ldp] - prev
dist := dl / vl
newp.pos = lp.pos.add(v.mulf(dist))
newp.tf = lp.tf.add(vtf.mulf(dist))
vl -= dl
ldp++
ldp %= len(cv.state.lineDash)
@ -250,7 +249,7 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
}
if draw {
path2[len(path2)-1].next = newp.tf
path2[len(path2)-1].next = newp.pos
path2[len(path2)-1].flags |= pathAttach
path2 = append(path2, newp)
} else {
@ -260,7 +259,6 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
lp = newp
v = pp.pos.sub(lp.pos)
vtf = pp.tf.sub(lp.tf)
}
lp = pp
}
@ -272,20 +270,12 @@ func (cv *Canvas) lineJoint(p pathPoint, p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec,
v2 := p1.sub(p2).norm()
v3 := vec{v2[1], -v2[0]}.mulf(cv.state.lineWidth * 0.5)
p1tf := cv.tf(p1)
switch cv.state.lineJoin {
case Miter:
l1p0 := p2.sub(v3)
l1p1 := p1.sub(v3)
l1p2 := p2.add(v3)
l1p3 := p1.add(v3)
// l0p0tf := cv.tf(l0p0)
l0p1tf := cv.tf(l0p1)
// l0p2tf := cv.tf(l0p2)
l0p3tf := cv.tf(l0p3)
// l1p0tf := cv.tf(l1p0)
// l1p2tf := cv.tf(l1p2)
var ip0, ip1 vec
if l0p1.sub(l1p1).lenSqr() < 0.000000001 {
@ -301,12 +291,10 @@ func (cv *Canvas) lineJoint(p pathPoint, p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec,
if dist := ip0.sub(l0p1).lenSqr(); dist > cv.state.miterLimitSqr {
l1p1 := p1.sub(v3)
l1p3 := p1.add(v3)
l1p1tf := cv.tf(l1p1)
l1p3tf := cv.tf(l1p3)
tris = append(tris,
float32(p1tf[0]), float32(p1tf[1]), float32(l0p1tf[0]), float32(l0p1tf[1]), float32(l1p1tf[0]), float32(l1p1tf[1]),
float32(p1tf[0]), float32(p1tf[1]), float32(l1p3tf[0]), float32(l1p3tf[1]), float32(l0p3tf[0]), float32(l0p3tf[1]))
float32(p1[0]), float32(p1[1]), float32(l0p1[0]), float32(l0p1[1]), float32(l1p1[0]), float32(l1p1[1]),
float32(p1[0]), float32(p1[1]), float32(l1p3[0]), float32(l1p3[1]), float32(l0p3[0]), float32(l0p3[1]))
return tris
}
@ -323,37 +311,25 @@ func (cv *Canvas) lineJoint(p pathPoint, p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec,
if dist := ip1.sub(l1p1).lenSqr(); dist > cv.state.miterLimitSqr {
l1p1 := p1.sub(v3)
l1p3 := p1.add(v3)
l1p1tf := cv.tf(l1p1)
l1p3tf := cv.tf(l1p3)
tris = append(tris,
float32(p1tf[0]), float32(p1tf[1]), float32(l0p1tf[0]), float32(l0p1tf[1]), float32(l1p1tf[0]), float32(l1p1tf[1]),
float32(p1tf[0]), float32(p1tf[1]), float32(l1p3tf[0]), float32(l1p3tf[1]), float32(l0p3tf[0]), float32(l0p3tf[1]))
float32(p1[0]), float32(p1[1]), float32(l0p1[0]), float32(l0p1[1]), float32(l1p1[0]), float32(l1p1[1]),
float32(p1[0]), float32(p1[1]), float32(l1p3[0]), float32(l1p3[1]), float32(l0p3[0]), float32(l0p3[1]))
return tris
}
ip0tf := cv.tf(ip0)
ip1tf := cv.tf(ip1)
l1p1tf := cv.tf(l1p1)
l1p3tf := cv.tf(l1p3)
tris = append(tris,
float32(p1tf[0]), float32(p1tf[1]), float32(l0p1tf[0]), float32(l0p1tf[1]), float32(ip0tf[0]), float32(ip0tf[1]),
float32(p1tf[0]), float32(p1tf[1]), float32(ip0tf[0]), float32(ip0tf[1]), float32(l1p1tf[0]), float32(l1p1tf[1]),
float32(p1tf[0]), float32(p1tf[1]), float32(l1p3tf[0]), float32(l1p3tf[1]), float32(ip1tf[0]), float32(ip1tf[1]),
float32(p1tf[0]), float32(p1tf[1]), float32(ip1tf[0]), float32(ip1tf[1]), float32(l0p3tf[0]), float32(l0p3tf[1]))
float32(p1[0]), float32(p1[1]), float32(l0p1[0]), float32(l0p1[1]), float32(ip0[0]), float32(ip0[1]),
float32(p1[0]), float32(p1[1]), float32(ip0[0]), float32(ip0[1]), float32(l1p1[0]), float32(l1p1[1]),
float32(p1[0]), float32(p1[1]), float32(l1p3[0]), float32(l1p3[1]), float32(ip1[0]), float32(ip1[1]),
float32(p1[0]), float32(p1[1]), float32(ip1[0]), float32(ip1[1]), float32(l0p3[0]), float32(l0p3[1]))
case Bevel:
l1p1 := p1.sub(v3)
l1p3 := p1.add(v3)
l0p1tf := cv.tf(l0p1)
l0p3tf := cv.tf(l0p3)
l1p1tf := cv.tf(l1p1)
l1p3tf := cv.tf(l1p3)
tris = append(tris,
float32(p1tf[0]), float32(p1tf[1]), float32(l0p1tf[0]), float32(l0p1tf[1]), float32(l1p1tf[0]), float32(l1p1tf[1]),
float32(p1tf[0]), float32(p1tf[1]), float32(l1p3tf[0]), float32(l1p3tf[1]), float32(l0p3tf[0]), float32(l0p3tf[1]))
float32(p1[0]), float32(p1[1]), float32(l0p1[0]), float32(l0p1[1]), float32(l1p1[0]), float32(l1p1[1]),
float32(p1[0]), float32(p1[1]), float32(l1p3[0]), float32(l1p3[1]), float32(l0p3[0]), float32(l0p3[1]))
case Round:
tris = cv.addCircleTris(p1, cv.state.lineWidth*0.5, tris)
}
@ -368,13 +344,12 @@ func (cv *Canvas) addCircleTris(center vec, radius float64, tris []float32) []fl
} else if step < 0.05 {
step = 0.05
}
tfcenter := cv.tf(center)
p0 := cv.tf(vec{center[0], center[1] + radius})
p0 := vec{center[0], center[1] + radius}
for angle := step; angle <= math.Pi*2+step; angle += step {
s, c := math.Sincos(angle)
p1 := cv.tf(vec{center[0] + s*radius, center[1] + c*radius})
p1 := vec{center[0] + s*radius, center[1] + c*radius}
tris = append(tris,
float32(tfcenter[0]), float32(tfcenter[1]), float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]))
float32(center[0]), float32(center[1]), float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]))
p0 = p1
}
return tris
@ -485,10 +460,10 @@ func (cv *Canvas) FillPath(path *Path2D) {
func (cv *Canvas) appendSubPathTriangles(tris []float32, path []pathPoint) []float32 {
last := path[len(path)-1]
if last.flags&pathIsConvex != 0 {
p0, p1 := path[0].tf, path[1].tf
p0, p1 := path[0].pos, path[1].pos
last := len(path)
for i := 2; i < last; i++ {
p2 := path[i].tf
p2 := path[i].pos
tris = append(tris, float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]), float32(p2[0]), float32(p2[1]))
p1 = p2
}
@ -580,10 +555,10 @@ func (cv *Canvas) clip(path []pathPoint) {
func (cv *Canvas) scissor(path []pathPoint) {
tl, br := vec{math.MaxFloat64, math.MaxFloat64}, vec{}
for _, p := range path {
tl[0] = math.Min(p.tf[0], tl[0])
tl[1] = math.Min(p.tf[1], tl[1])
br[0] = math.Max(p.tf[0], br[0])
br[1] = math.Max(p.tf[1], br[1])
tl[0] = math.Min(p.pos[0], tl[0])
tl[1] = math.Min(p.pos[1], tl[1])
br[0] = math.Max(p.pos[0], br[0])
br[1] = math.Max(p.pos[1], br[1])
}
if cv.state.scissor.on {
@ -628,11 +603,11 @@ func (cv *Canvas) StrokeRect(x, y, w, h float64) {
v3 := vec{x, y + h}
v0t, v1t, v2t, v3t := cv.tf(v0), cv.tf(v1), cv.tf(v2), cv.tf(v3)
var p [5]pathPoint
p[0] = pathPoint{pos: v0, tf: v0t, flags: pathMove | pathAttach, next: v1t}
p[1] = pathPoint{pos: v1, tf: v1t, next: v2, flags: pathAttach}
p[2] = pathPoint{pos: v2, tf: v2t, next: v3, flags: pathAttach}
p[3] = pathPoint{pos: v3, tf: v3t, next: v0, flags: pathAttach}
p[4] = pathPoint{pos: v0, tf: v0t, next: v1, flags: pathAttach}
p[0] = pathPoint{pos: v0t, flags: pathMove | pathAttach, next: v1t}
p[1] = pathPoint{pos: v1t, next: v2, flags: pathAttach}
p[2] = pathPoint{pos: v2t, next: v3, flags: pathAttach}
p[3] = pathPoint{pos: v3t, next: v0, flags: pathAttach}
p[4] = pathPoint{pos: v0t, next: v1, flags: pathAttach}
path := Path2D{p: p[:]}
cv.strokePath(&path)
}

BIN
testdata/Transform.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

BIN
testdata/Transform2.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

View file

@ -66,7 +66,7 @@ func triangulatePath(path []pathPoint, target []float32) []float32 {
var buf [500]vec
polygon := buf[:0]
for _, p := range path {
polygon = append(polygon, p.tf)
polygon = append(polygon, p.pos)
}
for len(polygon) > 2 {
@ -161,9 +161,8 @@ func (cv *Canvas) cutIntersections(path []pathPoint) []pathPoint {
for _, cut := range cuts {
copy(newPath[cut.to+1:], newPath[cut.to:])
newPath[cut.to].next = newPath[cut.to+1].tf
newPath[cut.to].next = newPath[cut.to+1].pos
newPath[cut.to].pos = cut.point
newPath[cut.to].tf = cv.tf(cut.point)
}
return newPath