From c1f855278d9ca57aefb601c27d0c90f81010b2fc Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Fri, 25 Jan 2019 13:16:48 +0100 Subject: [PATCH] undid some of the changes from the last update since it didn't work properly; added some tests --- canvas.go | 17 ++++--- canvas_test.go | 50 +++++++++++++++++++ path2d.go | 5 +- paths.go | 103 +++++++++++++++------------------------- testdata/Transform.png | Bin 0 -> 498 bytes testdata/Transform2.png | Bin 0 -> 543 bytes triangulation.go | 5 +- 7 files changed, 104 insertions(+), 76 deletions(-) create mode 100755 testdata/Transform.png create mode 100755 testdata/Transform2.png diff --git a/canvas.go b/canvas.go index b030678..e26ea5b 100644 --- a/canvas.go +++ b/canvas.go @@ -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 diff --git a/canvas_test.go b/canvas_test.go index 5220396..5416972 100644 --- a/canvas_test.go +++ b/canvas_test.go @@ -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() + }) +} diff --git a/path2d.go b/path2d.go index da530ad..225ea70 100644 --- a/path2d.go +++ b/path2d.go @@ -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] diff --git a/paths.go b/paths.go index e1517d4..f47ca62 100644 --- a/paths.go +++ b/paths.go @@ -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) } diff --git a/testdata/Transform.png b/testdata/Transform.png new file mode 100755 index 0000000000000000000000000000000000000000..7bb15b84239d2d3885c7f1caa921b0c65a91809f GIT binary patch literal 498 zcmeAS@N?(olHy`uVBq!ia0vp^DImV`PlG8nNQ{MhJ5(aw|%;PG1B?0LQRIO{1b z_r-J7=R0YxR$9(#`{z7ws_4@X=@b1g9?;llx_498!ZnwUsNcKwA#mD(Bc-J(djHdQ zy3PNsIjj2loQ-=7%>8cP6#1^Bz9eYQ=0!U8R@H{*yEf_UTyxA``&7}=i|2$Q1E(1- zXI(ox_kKG)~XU_svdqjno#{7#B8$_3{FEcwz}+bfE|8!07vL;nK(Tw8c;s P7?%v5u6{1-oD!M<;?UBK literal 0 HcmV?d00001 diff --git a/testdata/Transform2.png b/testdata/Transform2.png new file mode 100755 index 0000000000000000000000000000000000000000..df69ea47e44cb4fbd8f8219b2dc3eba808d6468d GIT binary patch literal 543 zcmeAS@N?(olHy`uVBq!ia0vp^DIm|CNG6Oq|>*IukZHpfN6-f4jeOUi$CD zg)*Pz{9oLtEL+emzlHJU7Slh6H~KKVj_>;vw>Y9%VC&uzQx@LDtMjIReC8jP7`ax_ zBSImuv4vGgj3mYhme)0@A1Buu&skHI@+>=|^UXior7NV&7p$vm^ta7ee17v$$z`)Q zxn@^H?z+qK(N6nxUfY4Jqw36`_y6!=diQM0|HQ-)4WmB+F=nb9H|MfH{Bi2h>TfS@ z#AU^$2>0#czq#e{+YPfn^9NU4Si=`E>CCsA8rx=fZ`*rpf$rV~DeA=)hbNVr+uj#g zE^YF^zF1Z1^YN^uH_mEA>^;#e# 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