From 3fd231367f4a316c9474a8ba9ca6e129a97fe272 Mon Sep 17 00:00:00 2001 From: Egon Elbre Date: Sat, 12 Mar 2022 14:11:56 +0200 Subject: [PATCH] internal/stroke: optimize arc drawing Arc with a small angle doesn't need many segments. Updates: https://todo.sr.ht/~eliasnaur/gio/313 Signed-off-by: Egon Elbre --- gpu/internal/rendertest/refs/TestPaintArc.png | Bin 1390 -> 1747 bytes .../TestStrokedPathCoincidentControlPoint.png | Bin 1918 -> 1895 bytes .../rendertest/refs/TestTexturedStroke.png | Bin 390 -> 692 bytes .../refs/TestTexturedStrokeClipped.png | Bin 390 -> 692 bytes internal/stroke/stroke.go | 19 ++++++++++++++---- op/clip/clip.go | 4 +--- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/gpu/internal/rendertest/refs/TestPaintArc.png b/gpu/internal/rendertest/refs/TestPaintArc.png index f432914fa9ce36b4c608d9ff49869a8760512776..9ebbe5bf42e98ce685e9e3a90285e621108660f3 100644 GIT binary patch literal 1747 zcmb7_`ynU33OjxJ_&I&K}&I4uj?%sr;dkZVhY zhA2fcO~P3_=NP%=x)Y_aLgmsy$NT;T@B72^JU=|o&z~pT-`5kPj8_H#0ORfD7PynF z{|E`+@h*ojDFDDryxp9SFv=FoL$W9o-QH9t6_LE09I8Kk^O^$z<;HbFwe8O8h;0tc zAnXzlvSK+dMrt|GGp4oX&p1TnjZ$T!DxqOb)^#N`N}@-;)L)CeQu68dr10~@qr{5+ z6{5bWn0nF3$Fw<*h!3Ab7d9#%I1cwxxt`D9mR?(=5}J z06#Si>+#Bl9IxsNez@HRlIjKI@9(XS()PwH5w)`jnl}$x&j=Dk8VVUAG6l`nc#b_DTX-mfEi60^_UwAaC-Bymze*C6)P+^g9NXFz?USpfW zd_4*#s53W*^x7uzY2qp5R2d@nH+f{F;l6&lDj_Z+@5&jSghc;0viqQWa4V|jy#Imo??LZt z{4*^|{4)b3JEI};vTIOZ>F9Hr1e<+@^twpjwrSOF3}T#*C&Ui|*;Q~aJ(AO_8jyx3 zrr=;S>si46Us2bTV~M@2eR4wFJ<-uUK5QY2BsLSkl8iWi5H9pYfY%Otw!Jk%svCFz zXlGHf?vqW}$IoPc;5!9U0Mc!k3;j*Q&#i8L)&3(Ecix>tu!iXa`aNqUpi~$T|K*&PD3&J^me?B zCqImqC$3btI{SMV)Bsa1OnJACE$HmB66G%ZiwW7vU?W^m?=tiKKm$`+n@L~KMth?e zmTC5abgZBhRN`>`2iO^9I}#A#ijt&tAouWkEf3P<+cKAx>+s!06rIKWM3cC4k^4k4 zg+?5Y-rgv`YHKIO6{po86xn$|@) z9G(^ScQL#U77LDr1bxxK3qj_&;&F9!`w2886Z$JIc)IT?nptPtt8(s7a$-@b77w@1 z6I-)29gUVM#0tvLi;8i6fFxpBIG}(<`GHE2S2ua+iMPUrhkK9T#?uVILTLUvxt%>T zP?3qd(@|OuCyDpMJ)j*cG}e|`>#l_4dbrRXLqtSPQ$ED+55MEJ?W568;19jBss+6X zmpia4$-dik!NL$m_&tWm(+Qp*mQ>DLZzKkv{e^S~WJ8mug8g*YAJQNSN6mni>Ad|4 zsk4B|k&TH0uE(2n&*(l%X*yQ^Gs)>&Wt{wytmI=jNeF5yxF6QD(AqiBw0fsDp5%l$ zi>LWSirnL4Ooo7ulK$O}D0If~ajbB1NH7dXo}aTX?A&0zUQ2)B5H!32EqpYhI8ew> zoRX>!0wAe*?$bmyoCd>1?i%&MJZVf}Pn&7pvR+GFJ(KaAA7Uu_H5=Z5s(%oAT7QKK zKDLy~4gp8dPnavF-{0%asE=12+$t$9viqzx`qS8x#S^>&dWzNR==Xw!c0x3(p-FTT zWW~%^2o~yR#s7XX{VX)RW`|ZuPBgl^>zv6fSLYUDqP6n~Oj4OwYm--HocVbC?w*bN zO{NLDDUTWj_}bQ>~4dcd#5gH+XrL(^&KilP(_r-g3&gD;#QPH z5+Dsd+8Hw0jHm&ui)TzX3cJmMsk2TbCzuzDgn__%mHrEc8WuWbIpj7wG*_?Mnyn?U zX6N)NS)@w?AxC;UG74{f;xM%qPfE9$*UU8j%kwN7L*r8k z%13cCXb=)-3+t;Iyp*0AIhVwoZeOYDUEEoE6~h?(5dzLgV}c Dn_VCY delta 1372 zcmb7%c{J2{0KmU9%x^|!TF1sQf& z_f}HbxYzNm+apV%s#ftHM@>Vao=g-M`FNhafrEI_GUWU+^ifVKSR@DFIkZ@75RG3PY5CUj++NjT8> zVBTmvy7`n>I(8;2QxA!zL`4QaK5AsauO2?%8Xf-}`wE>UA2%@PNGGBh17urGYEoVI zaE@sHs>mPo;b4d7Z?sYdOpY|kUrRd3-*tMf1~d@F^7HaIMPjCh7rD(+HuRV`J-@22 z4vaUbmRlFaTw3y{O-kX*0Ou>UI|Gr3B^mlyB#l?K?5BlB@x`15wzY{7oWT-cNv5^8s6sAHAG7_eVhA zvPOQg54iJ2cW1Ps9y8lI-YYWpxwmR(NAxF%zp&&S-n8yth1b*kK6o*_e+2zCE29Q+ zGaUqbE8yG37yn*%J?z~Dz@Z^`H3}2m!FEXONGx$1oEdtT%u+@2fjeTps507hCF5~p(>I<@$ zcb&w5T6;ih{e6Bt7m)Y#S53`zlwz)Gz)3bPh}Ur!F7+1K8?B>1WrvxN+`_+A#kTh~ zt@e0FHp<2pzUK86KZqYYC4k4g!<9pg&c58gzbnb2lLYbPQKC?DVU~97{D$5H+*Cqj z3m*h^t0S>p{29QOs3U~P7WP>3`_}ZzHA7u#3ATu%iA^ELRiF)WPvPmu@CLgeH(gS0 z;GlZX5kpywVPjpjIKTsXLP07~0!}|rZ8PaU-Cd32UA2*u<`=No+HR&=*za$-(vfoy zYV8WOsdmW5O6KAyw+muoi}D{veyC~B{Q2Q!byhC@Ki&*mQEir5H(c1$dJp^q%h}VZ JW_Rf6zX6dsZ1n&D diff --git a/gpu/internal/rendertest/refs/TestStrokedPathCoincidentControlPoint.png b/gpu/internal/rendertest/refs/TestStrokedPathCoincidentControlPoint.png index 77540a4b6b0d29720a493a2ec3060898b87f6895..f589f753dc5d8d55af052a8466701ee8679b6fb5 100644 GIT binary patch delta 1880 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 NJYD@<);T3K0RZ9(2O0nX literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrVD$BLaSW-r_4bZ!-XRBphKo>c#>-*nc)z)|Z+5?6S@D z)XzWnWIunOy~VKfOp5%%Uk{Hb{o-YQ5#Jww>R{X>yPw6o80?PCR8VM`Wzmpjf1CY+ z5c`EU>AxAO9x?3t%hBK)TXp$*^ovUgdPh1K1Q^Wu6TUv?a$sN*XkcJbU|{4xVk%sl zD&Bm}eqFaCHd&~`2|5h_xpxHDiRwT#A6U!=wDQ6Ghm4F@jxg?Uba7w^e#9_OCnNj! zChNNsG~*f8FWvK2M?h%&-M9iXHEsZhAAvqywAX(^ObMIe<9CT RVdYIAc~4hAmvv4FO#tb{gC+m~ diff --git a/gpu/internal/rendertest/refs/TestTexturedStrokeClipped.png b/gpu/internal/rendertest/refs/TestTexturedStrokeClipped.png index 637c93294a8284b771fee032ee2368d5662cef83..8da06b39b2a953f7c9df1f377a08d1998fa326a5 100644 GIT binary patch literal 692 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV5;_XaSW-L^X87XpGcsL+sE}+ zT6kw#6i9zDGdaYxhPR-2TvApt?byD8k_4lp*tDStV^4{pV^ZmrHZ2OqD zbgP7W&inuC*>#JZcMGOkinm(-JGp>y%Vm}HtJ-B2r(e$r>wjUG!B81$`=G%z|F%&U z!}(S0Z%o%#+~!-%_F+fW0mkfm+fuJEY?)wqpG~f6b@~78rR976&G>A6UgwD@!#Rcr z$M&$w}`+ZFR=b^iVT zXOB%aSo6EYdbJNT&VTq?*Yl@Q(wL!MKf&-G6U0&-aH>&kIMADE4DT-+J-5-Fts9PQ28RFtub#FH?=pC}3YZ!h NJYD@<);T3K0RZ9(2O0nX literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrVD$BLaSW-r_4bZ!-XRBphKo>c#>-*nc)z)|Z+5?6S@D z)XzWnWIunOy~VKfOp5%%Uk{Hb{o-YQ5#Jww>R{X>yPw6o80?PCR8VM`Wzmpjf1CY+ z5c`EU>AxAO9x?3t%hBK)TXp$*^ovUgdPh1K1Q^Wu6TUv?a$sN*XkcJbU|{4xVk%sl zD&Bm}eqFaCHd&~`2|5h_xpxHDiRwT#A6U!=wDQ6Ghm4F@jxg?Uba7w^e#9_OCnNj! zChNNsG~*f8FWvK2M?h%&-M9iXHEsZhAAvqywAX(^ObMIe<9CT RVdYIAc~4hAmvv4FO#tb{gC+m~ diff --git a/internal/stroke/stroke.go b/internal/stroke/stroke.go index 741e56a1..a115dc79 100644 --- a/internal/stroke/stroke.go +++ b/internal/stroke/stroke.go @@ -91,9 +91,8 @@ func (qs *StrokeQuads) lineTo(pt f32.Point) { } func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) { - const segments = 16 pen := qs.pen() - m := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle, segments) + m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle) for i := 0; i < segments; i++ { p0 := qs.pen() p1 := m.Transform(p0) @@ -546,7 +545,19 @@ func strokePathRoundCap(qs *StrokeQuads, hw float32, pivot, n0 f32.Point) { // cubic Bezier curves", L. Maisonobe // An electronic version may be found at: // http://spaceroots.org/documents/ellipse/elliptical-arc.pdf -func ArcTransform(p, f1, f2 f32.Point, angle float32, segments int) f32.Affine2D { +func ArcTransform(p, f1, f2 f32.Point, angle float32) (transform f32.Affine2D, segments int) { + const segmentsPerCircle = 16 + const anglePerSegment = 2 * math.Pi / segmentsPerCircle + + s := angle / anglePerSegment + if s < 0 { + s = -s + } + segments = int(math.Ceil(float64(s))) + if segments <= 0 { + segments = 1 + } + var rx, ry, alpha float64 if f1 == f2 { // degenerate case of a circle. @@ -605,7 +616,7 @@ func ArcTransform(p, f1, f2 f32.Point, angle float32, segments int) f32.Affine2D // 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. - return inv.Mul(rot).Mul(ref) + return inv.Mul(rot).Mul(ref), segments } func dist(p1, p2 f32.Point) float64 { diff --git a/op/clip/clip.go b/op/clip/clip.go index d251d7a0..3ac02fe8 100644 --- a/op/clip/clip.go +++ b/op/clip/clip.go @@ -286,9 +286,7 @@ func (p *Path) QuadTo(ctrl, to f32.Point) { // The sign of angle determines the direction; positive being counter-clockwise, // negative clockwise. func (p *Path) ArcTo(f1, f2 f32.Point, angle float32) { - const segments = 16 - m := stroke.ArcTransform(p.pen, f1, f2, angle, segments) - + m, segments := stroke.ArcTransform(p.pen, f1, f2, angle) for i := 0; i < segments; i++ { p0 := p.pen p1 := m.Transform(p0)