From f2cd504a9f9f6accc3dc803a3fce2e0ed8fa680d Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Sat, 29 Aug 2020 14:59:33 +0000 Subject: [PATCH] op/clip: smoother implementation of Arc for large angles Signed-off-by: Sebastien Binet --- internal/rendertest/clip_test.go | 1 + internal/rendertest/refs/TestPaintArc.png | Bin 1888 -> 2221 bytes op/clip/clip.go | 53 +++++++--------------- 3 files changed, 17 insertions(+), 37 deletions(-) diff --git a/internal/rendertest/clip_test.go b/internal/rendertest/clip_test.go index 9debf7fa..b996d816 100644 --- a/internal/rendertest/clip_test.go +++ b/internal/rendertest/clip_test.go @@ -61,6 +61,7 @@ func TestPaintArc(t *testing.T) { p.Line(f32.Pt(0, 25)) p.Arc(f32.Pt(-10, 5), f32.Pt(10, 15), -math.Pi) p.Line(f32.Pt(0, 25)) + p.Arc(f32.Pt(10, 10), f32.Pt(10, 10), 2*math.Pi) p.Line(f32.Pt(-10, 0)) p.Arc(f32.Pt(-10, 0), f32.Pt(-40, 0), -math.Pi) p.Line(f32.Pt(-10, 0)) diff --git a/internal/rendertest/refs/TestPaintArc.png b/internal/rendertest/refs/TestPaintArc.png index d900cc32eabbfa73473c95f256bcab07d25d5aa3..8ffb300e67dfc4fc2c52db1d8aac5971684a94d2 100644 GIT binary patch delta 2210 zcmV;T2wnH!4y_T8BYy~VNklG zzx{^AMZ{wCrIr?4a}5q0z|~jds;jVXAFjL-J9p}wz_!Arn}F3-Oif{A1n<6!^>z9` zf`EL!@Twrte@p+A)-SyjXU^c*G2D0~cJJ0FlkEl3NIp;I=gFII5>X_AK!6KN>7Rm7 z{%6^iqDX_x&wtZ^PC}c2Sd5%KTkEhY7f@V3dzOZE42=rd*dTpUx{E`TvL1yo)TarB1Q0Ds1C0gT}S7*kV#W&45*g2nzi>=0WI zE`Tvy0AsiS#&7|Q;Q|=L1u%vSD1k-a7Z<=7E`Tvy0AoC?6JWukkK*`o?Ar&g7ak9? zS*)!g9>)(qn4gQbxguOHHY4L2ZY|$<1E){ZLvP&BE|oZ@28bfQ{1QixTIbM9I7Yx{ zpMT-lG1f^`!5NM4Bah(ZN!CeJ#~A{;yJ=%rqhy*4Odeb8J2fMiU5NdYW|- z_H~_!j{5%{>g#eES zXU?$6#gSC&jJxeNJp3?&T;vc08-M>?P-bA(+#K54Ac}?IP}0Amh|Nuu&0VJNz%0KX z_uPY)7F=-!cJIa|m!PSsFm2k_7P47vY+!X2fBi*I;fIElPG&5X?sCx}RTD0*m_B2c z{QZEBJ~FR2^x0?0@Gx0kR`&#-e4f5#c$hr%jH2F9DebCh6jw|~zR1l@@_+Kn#tPW8 zhrIV5iN|%=k9eGh+Ox+-0tg|=Bzf{lLj<_pq_>wO5<2fqB0X8WI}_ z)Y-spQmg!+|0j%*hH{6m*)R^OzI{OQ{D z*BcnTF;u0L$zXOCx-V`_|QW*ejJAnLg?sFTP7E9o@f*|-GqPtZCt>P9e^Ip7B$DBsrcZ9q1Q=H|j^S6$iw ziA)AfO-jmle18p4dDf_VSjP)sjpGIE*ugfe;{`M|>HoV#pHE=}zy+M+@yB%z_{0;+ zx8(wWTW@6;kPA3>vtGxsGd`cHp$J?6uxl4ydrh}!ufMLmBZvz)&)s+9!3TAS^S}c% zlo}j=C0ryL#o@z9CNrdJP& zyBy@;0+f->QXc@Co3$VGQn&!d@J5(1TmWOZ0LE|ujNt+p!v!#g3t$Wvz!)xoFVMhhX4Qo literal 1888 zcmbVN`9IW&8~@ChaSemH43lFft(&nku1OlkH8~=bs}dPjwPspk9F052l9VLpNRh2b zt`{3)4B6H=axcZI6bi$LZNu9C;QQSlp67YJ-p3Em5AWy6ba!)BvZ)Qk(@^I%ZUW`Zpoh0?u>Hu zm1ss;)Ye}_gXFg*tNo$9KMf2Nk%lv*m*$zgHKKTn7HuBC2S0d_NQR`O)JN(HUUWBv z%#%#f{H;QobM|tCyrgI`GVAzF`XhszWpFk- z@F~?#^^UMM&DSn)Eq*8K{{7Y;kl+ffOeinP1S=ryUHqQyb8a&{UM&6iBzoOIFBuuw zG{5tA71w5uIECO}hrrdycrvoe&f>eT9<=|Fi`PFdA8k8%;a)BOd76LqF_$SB7y zG|^I~C1ROnwEb?u6{}GV=YB5>8J(cfQq+CpUkw3KQKNXT-a?=nscto-n~f;5hK_Z1 z9O^0D-|NitSNn*bULZ4%l6rR=p_nYseBGARHs?$Yt#-4yMIus5sjl%dnu}~hmk8r1 zu&IZ;RI2Jq%QS{GtVxIMT%9o5Y0#;90ozyScwHo+FRMm5<}_uOzmOFVCd~ z8>^$l5?I6G1zQHH8=P?tB&Y|1d;k-JL@b6X2h&W4D*g}flB;)pMqhvGBLgS5^i~TZ zHjp9d;K;302)`i1hit`M>p0$r&6y{C*Sk@!?`w2MkPB~|e!g;JL}e5tDtBrF*VfKW znrP6LDl5gmZ*T|>RJP@Tqi$+^i5ng=tFsDIQ=@n*qzZU_UGuwiN@^&9Mh(}C0@vB4 z12;1%Nv8-jUir~cnO_kq;I_6JWK2jGL#OhiMsu^RKTiCGzilDt&DyKh)y=ytd0}En zndDUa3tn5QlGY&n+>|{&k^+T3JL1naDGrU}u2(7LH-^RXB_+&E7ysz;{<)B2v$#(Qn5wY4?07@jLnOb%$r+|_ojFQGtl zaz^n1EacFPoT(|ZZ6IJ zB!;lZcny)0ABGGUeT(lCqC;_;pJ+dV5s0wlkAu%b^UB~oJ!|h_TuF1#^7t@>JHO;T zSq~a0P5HwwzsTe@_W9OHC3If!s8na^>~re7%-&eg>j-C{s3T7-kOg@L+x= zyWY+aj6~b`xA2Qd5MM&R&R~Sc(xPbSz}F8N_0#7VlKqT3cK|x#u{qloPrqAw;aC~x z_k=gP5ym2xul7L+xJLTsGTzdcoUyl~926uV&?{%jCivTDJ1g38etvU*{dE7eLyFBs zJ0q`rL9UCdO=RPwvdTS{S@G_OXWUL@TU+cKA>g_yRbrKiMo*5tr!X%4$qK)6_=`B> zXn(&r;pkihcRd#?x3Qw%SJ!nzYqR6SsjO(wmBSe23r2J6Ar~XIVk^*aRO7!fuaegX zWms6}+S~idv|d$fZ%?S&h^}IsDkQ2}gU?H)rRl*YpW|>iwAu_W{iT^Hw+CSaR%Bg9 ztiJJf<>lZk%pQKNJ=3$I6Z$zfWiMWVM%B~21B z5eiL$)jUEGb_jjLU*S^LY=i^&-N@2TT>oz(f2yyq=3+7@fWL@>^Bog^7N!e@G0WpK z{vjBQyBBIMPR5&kriXz9@sIIl-!_C7M1WLOEGVjhlqPkos(NqfVVWnE8BBZ88(-%c zAje|a6d7U{+2vvWaJ$vO-;w{vY;*&7lih9MMVAGk7&O0u7#m;Jcv>`NgZra=cvjo{^H6hoq zhqOJlnfCkUlVPnQo=-z6B}v~;)~6$&J4Y&g%B7TtHr`5Amh`B$M0*)Hy}2cnbht;s wd1WR-h)YT;*B_=6|G&fW-_#2n5CH7$30qH4i@ErQ{m%)|?cMC2*ajy51HGO#cmMzZ diff --git a/op/clip/clip.go b/op/clip/clip.go index 75478523..955a6e0f 100644 --- a/op/clip/clip.go +++ b/op/clip/clip.go @@ -194,16 +194,9 @@ func arcFrom(f1, f2, p f32.Point) (c f32.Point, rx, ry, start, alpha float64) { // An electronic version may be found at: // http://spaceroots.org/documents/ellipse/elliptical-arc.pdf func (p *Path) arc(alpha float64, c f32.Point, rx, ry, beg, delta float64) { + const n = 16 var ( - n = math.Round(20 * math.Pi / math.Abs(delta)) - θ = delta / n - - sinθ64, cosθ64 = math.Sincos(θ) - sinθ, cosθ = float32(sinθ64), float32(cosθ64) - b = (cosθ - 1) / sinθ - ) - - var ( + θ = delta / n ref f32.Affine2D // transform from absolute frame to ellipse-based one rot f32.Affine2D // rotation matrix for each segment inv f32.Affine2D // transform from ellipse-based frame to absolute one @@ -215,43 +208,29 @@ func (p *Path) arc(alpha float64, c f32.Point, rx, ry, beg, delta float64) { Y: float32(1 / ry), }) inv = ref.Invert() - rot = rot.Rotate(f32.Point{}, float32(θ)) + rot = rot.Rotate(f32.Point{}, float32(0.5*θ)) // Instead of invoking math.Sincos for every segment, compute a rotation // matrix once and apply for each segment. // Before applying the rotation matrix rot, transform the coordinates // to a frame centered to the ellipse (and warped into a unit circle), then rotate. // Finally, transform back into the original frame. - // - // Also compute the control point C according to - // https://pomax.github.io/bezierinfo/#circles. - // If S is the starting point, S' is the orthogonal - // tangent, θ is clockwise: - // - // C = S + b*S', b = (cos θ - 1)/sin θ - // - // We apply the same original <-> ellipse frame transformation to the - // control point as well. - rotate := func(p f32.Point) (end, ctl f32.Point) { + step := func(p f32.Point) f32.Point { q := ref.Transform(p) - t := f32.Pt(-q.Y, q.X) - - end = rot.Transform(q) - ctl = q.Add(t.Mul(b)) - - end = inv.Transform(end) - ctl = inv.Transform(ctl) - - return end, ctl + q = rot.Transform(q) + q = inv.Transform(q) + return q } - var ( - ctl f32.Point - end = p.pen - ) - for i := 0; i < int(n); i++ { - end, ctl = rotate(p.pen) - p.quadTo(ctl, end) + for i := 0; i < n; i++ { + p0 := p.pen + p1 := step(p0) + p2 := step(p1) + ctl := f32.Pt( + 2*p1.X-0.5*(p0.X+p2.X), + 2*p1.Y-0.5*(p0.Y+p2.Y), + ) + p.quadTo(ctl, p2) } }