From a4826a3e121cb061b6ae6966636256e77fa73ff0 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Sun, 12 May 2019 13:02:29 +0200 Subject: [PATCH] fixed a problem with closed paths and self intersecting polygons added the readme example as a test --- README.md | 1 + canvas_test.go | 23 +++++++++++++++++++++++ path2d.go | 29 +++++++++++++++++++++++------ paths.go | 8 ++++++-- testdata/Readme.png | Bin 0 -> 1624 bytes triangulation.go | 7 ++++++- 6 files changed, 59 insertions(+), 9 deletions(-) create mode 100755 testdata/Readme.png diff --git a/README.md b/README.md index 1e70dde..03af53f 100644 --- a/README.md +++ b/README.md @@ -126,4 +126,5 @@ These features *should* work just like their HTML5 counterparts, but there are l # Missing features - globalCompositeOperation +- imageSmoothingEnabled - textBaseline hanging and ideographic (currently work just like top and bottom) diff --git a/canvas_test.go b/canvas_test.go index 92ff535..f0e3c5f 100644 --- a/canvas_test.go +++ b/canvas_test.go @@ -584,3 +584,26 @@ func TestShadow(t *testing.T) { cv.FillRect(50, 15, 30, 60) }) } + +func TestReadme(t *testing.T) { + run(t, func(cv *canvas.Canvas) { + w, h := 100.0, 100.0 + cv.SetFillStyle("#000") + cv.FillRect(0, 0, w, h) + + for r := 0.0; r < math.Pi*2; r += math.Pi * 0.1 { + cv.SetFillStyle(int(r*10), int(r*20), int(r*40)) + cv.BeginPath() + cv.MoveTo(w*0.5, h*0.5) + cv.Arc(w*0.5, h*0.5, math.Min(w, h)*0.4, r, r+0.1*math.Pi, false) + cv.ClosePath() + cv.Fill() + } + + cv.SetStrokeStyle("#FFF") + cv.SetLineWidth(10) + cv.BeginPath() + cv.Arc(w*0.5, h*0.5, math.Min(w, h)*0.4, 0, math.Pi*2, false) + cv.Stroke() + }) +} diff --git a/path2d.go b/path2d.go index 49e4668..f62c66d 100644 --- a/path2d.go +++ b/path2d.go @@ -82,18 +82,20 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) { newp.flags |= pathIsConvex } else if prev.flags&pathIsConvex > 0 { cuts := false + var cutPoint vec if checkSelfIntersection && !Performance.IgnoreSelfIntersections { b0, b1 := prev.pos, vec{x, y} for i := 1; i < count; i++ { a0, a1 := p.p[i-1].pos, p.p[i].pos - _, r1, r2 := lineIntersection(a0, a1, b0, b1) + var r1, r2 float64 + cutPoint, r1, r2 = lineIntersection(a0, a1, b0, b1) if r1 > 0 && r1 < 1 && r2 > 0 && r2 < 1 { cuts = true break } } } - if cuts { + if cuts && !isSamePoint(cutPoint, vec{x, y}, samePointTolerance) { newp.flags |= pathSelfIntersects } else { prev2 := &p.p[len(p.p)-3] @@ -288,24 +290,39 @@ func (p *Path2D) Rect(x, y, w, h float64) { // func (p *Path2D) Ellipse(...) { // } -func runSubPaths(path []pathPoint, fn func(subPath []pathPoint) bool) { +func runSubPaths(path []pathPoint, close bool, fn func(subPath []pathPoint) bool) { start := 0 for i, p := range path { if p.flags&pathMove == 0 { continue } if i >= start+3 { - if fn(path[start:i]) { + end := i + if runSubPath(path[start:end], close, fn) { return } } start = i } if len(path) >= start+3 { - fn(path[start:]) + runSubPath(path[start:], close, fn) } } +func runSubPath(path []pathPoint, close bool, fn func(subPath []pathPoint) bool) bool { + if !close || path[0].pos == path[len(path)-1].pos { + return fn(path) + } + + var buf [64]pathPoint + path2 := Path2D{ + p: append(buf[:0], path...), + move: path[0].pos, + } + path2.lineTo(path[0].pos[0], path[0].pos[1], true) + return fn(path2.p) +} + type pathRule uint8 // Path rule constants. See https://en.wikipedia.org/wiki/Nonzero-rule @@ -319,7 +336,7 @@ const ( // to the given rule func (p *Path2D) IsPointInPath(x, y float64, rule pathRule) bool { inside := false - runSubPaths(p.p, func(sp []pathPoint) bool { + runSubPaths(p.p, false, func(sp []pathPoint) bool { num := 0 prev := sp[len(sp)-1].pos for _, pt := range p.p { diff --git a/paths.go b/paths.go index 615bcca..18d4b13 100644 --- a/paths.go +++ b/paths.go @@ -362,7 +362,8 @@ func (cv *Canvas) fillPath(path *Path2D, tf mat) { var triBuf [500][2]float64 tris := triBuf[:0] - runSubPaths(path.p, func(sp []pathPoint) bool { + + runSubPaths(path.p, true, func(sp []pathPoint) bool { tris = appendSubPathTriangles(tris, tf, sp) return false }) @@ -381,6 +382,9 @@ func appendSubPathTriangles(tris [][2]float64, mat mat, path []pathPoint) [][2]f if last.flags&pathIsConvex != 0 { p0, p1 := path[0].pos.mulMat(mat), path[1].pos.mulMat(mat) last := len(path) + if path[0].pos == path[last-1].pos { + last-- + } for i := 2; i < last; i++ { p2 := path[i].pos.mulMat(mat) tris = append(tris, p0, p1, p2) @@ -410,7 +414,7 @@ func (cv *Canvas) clip(path *Path2D, tf mat) { var triBuf [500][2]float64 tris := triBuf[:0] - runSubPaths(path.p, func(sp []pathPoint) bool { + runSubPaths(path.p, true, func(sp []pathPoint) bool { tris = appendSubPathTriangles(tris, tf, sp) return false }) diff --git a/testdata/Readme.png b/testdata/Readme.png new file mode 100755 index 0000000000000000000000000000000000000000..fb6dde0264d3b751b88781abf820459158e4e9ca GIT binary patch literal 1624 zcmV-e2B-OnP)#+LI0y=rn|c8xBsW=>8k3U_qs`BO|Tf6U@*O3#As|;FELS<;a(Kzll#%-M%OAV0n?HR99|ptPmvDc;_2p|p z9mKqZrwK?IOja;27?vSrB|s+QB{_#n%EM%&2+N3zOu&nM0*h1>laV?sBNj3)FJ7Ld zMXKw%VY6ag+6o}mb=~J*UD1o;h6n>Kz37*x-=nR9%>_7%SnSnzpAl->vHYcfZ)iyE zz0<1~j>`T6UVr_kdf`-9L|a)>6bT{`wUsL(FM7m~V2MdpHdh$ey1dlFFy*Ogyiz@& zVm#uoDAeKT19-6_5r{!J5uxy+G!uc9PiiDm;ZBsE_w-<h4PA;3q30ugQ%^dq)>AK4cFfoVp-BIS;HsAR)pjigkO7eI<%FSa7E z&<<-@vl~fC#G(h_Q+*smImIF)X%s?ak`RXV{OvbKS7lZ-tfh;?ersn+qu**>{kLyF z01ggkP;*lU>oQ`!ht0~XF)@?KA*FTmsFr8ZsfCG{<>jHY5WUtCF{$5R`>^u&E)c1O z79$EJrNwBs+j)`bhb61SA_{0;=p*xq>9gEVSVhsr>b7d^g9p6khNKBb>o|{83RAVo ztlAF^q^O}G8Ir2W$g0f~79lm8WO=!Qhp7o8tHzI`c+@Pi#qwj9R9i+??VsH-^6m-@ zsgW?U;*Ex%yg4sipZjipyYBz}>(^n^SD5F4+uL9?q{!Y>Z`XY+%OAyrb?ZLsc;dm3 zy3ZjVJO1A7v)WQ?G#UnzGuwTi<*{%W^}#0%sMXE05))WkY|dw&tokFsq0D;Js$}r# zNj?{oY4)Ru7RzDX&!0X!WckZzQ8lUYMnl$sh)#rtr0iJ_;0_14Akbpz)X>Hvqv32e z%ba*1P8Oq~mr@v#!2vp6)1UmaqED!Ym^@oqnMgG4cUztcNl)q#nmtp>Tyy|P!SZ-f zEaslex>ic;Ie61;$riFuQnuJfuN|KfwpLb?VYA|PSn<^zbgQ$Ve%p1Ac2dz|3G<>$ z!DayTt}_au#=2n&lW4bcV%;!%^8`f_Shv@g#3KPiWjtY#tRik z7PVL$7TRs0<`Sn-`9gW2bxSp0|8|Gakt<|9VJ30>Eh!P>Dj9|{bIC~I>1Aw8#CQJ1 zEGD*O^itC8!m?mlb5YW$5)%WY-OA1=nfdQTVvTZB{n4v4%9U8VwBpg4mxZkR zTl5&X^QrcPMZw9YQ9NNme9H5mKYr4yPbuZwisY<(UaXzCfW*5(R7h!tVTxC^Ir0=Q z3MryUu!xUbhrBAU!Gyesq=*fqI!nw8kYu+qXA`L|sUoAH<<(?q&zegy`mA`b%wVl$ zeE9MdK0x+3FuJk)zq=bk?HX$a3*tfKF3egVFfKVbnzVmVTkUfdJ47z_&z;i$P- zm@2)v$4IcY=pj>t#l=+hl~6{qwOE5Je^uG=5@2fZvN5Bawd?nvzXiU9VfDAy;_J3~ zjh&Io3lc+cA*>ugRxMwUXf(ZgldUl$(|QT6S_a?zX8=-CteAca$73<||DXQ7;Yci2 zq8ii7^Eo9KD%aKYvkYuaKlmZk^iwiIy+c1zG>M_