~eliasnaur/gio-patches

gio: gpu: handle rounding errors when splitting quads v3 PROPOSED

Elias Naur: 1
 gpu: handle rounding errors when splitting quads
Sebastien Binet: 1
 gpu: handle rounding errors when splitting quads

 3 files changed, 50 insertions(+), 6 deletions(-)
#383092 apple.yml success
#383093 freebsd.yml success
#383114 linux.yml failed
#383095 openbsd.yml success
I'll whip up the improved image comparing PR.

-s

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐

On Friday, January 1st, 2021 at 12:16 PM, Elias Naur <mail@eliasnaur.com> wrote:
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio-patches/patches/16302/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH gio v3] gpu: handle rounding errors when splitting quads Export this patch

This CL handles rounding errors arising when splitting quads into linear
segments.
Rounding errors would lead to a pair of quad triplets (from,ctl,to) not
exactly matching.
ie: to(n-1) wouldn't exactly match from(n).

Signed-off-by: Sebastien Binet <s@sbinet.org>
---
 gpu/stroke.go                                 |  45 +++++++++++++++---
 .../refs/TestDashedPathFlatCapEllipse.png     | Bin 6107 -> 5889 bytes
 2 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/gpu/stroke.go b/gpu/stroke.go
index ca37e59..031252f 100644
--- a/gpu/stroke.go
+++ b/gpu/stroke.go
@@ -32,6 +32,15 @@ import (
	"gioui.org/op/clip"
)

// strokeTolerance is used to reconcile rounding errors arising
// when splitting quads into smaller and smaller segments to approximate
// them into straight lines, and when joining back segments.
//
// The magic value of 0.01 was found by striking a compromise between
// aesthetic looking (curves did look like curves, even after linearization)
// and speed.
const strokeTolerance = 0.01

type strokeQuad struct {
	contour uint32
	quad    ops.Quad
@@ -182,10 +191,9 @@ func (qs strokeQuads) offset(hw float32, stroke clip.StrokeStyle) (rhs, lhs stro
		})
	}

	const tolerance = 0.01
	for i, state := range states {
		rhs = rhs.append(strokeQuadBezier(state, +hw, tolerance))
		lhs = lhs.append(strokeQuadBezier(state, -hw, tolerance))
		rhs = rhs.append(strokeQuadBezier(state, +hw, strokeTolerance))
		lhs = lhs.append(strokeQuadBezier(state, -hw, strokeTolerance))

		// join the current and next segments
		if hasNext := i+1 < len(states); hasNext || closed {
@@ -263,6 +271,21 @@ func (qs strokeQuads) append(ps strokeQuads) strokeQuads {
	case len(qs) == 0:
		return ps
	}

	// Consolidate quads and smooth out rounding errors.
	// We need to also check for the strokeTolerance to correctly handle
	// join/cap points or on-purpose disjoint quads.
	p0 := qs[len(qs)-1].quad.To
	p1 := ps[0].quad.From
	if p0 != p1 && lenPt(p0.Sub(p1)) < strokeTolerance {
		qs = append(qs, strokeQuad{
			quad: ops.Quad{
				From: p0,
				Ctrl: p0.Add(p1).Mul(0.5),
				To:   p1,
			},
		})
	}
	return append(qs, ps...)
}

@@ -402,7 +425,10 @@ func strokeQuadBezier(state strokeState, d, flatness float32) strokeQuads {
// flattenQuadBezier splits a Bézier quadratic curve into linear sub-segments,
// themselves also encoded as Bézier (degenerate, flat) quadratic curves.
func flattenQuadBezier(qs strokeQuads, p0, p1, p2 f32.Point, d, flatness float32) strokeQuads {
	var t float32
	var (
		t      float32
		flat64 = float64(flatness)
	)
	for t < 1 {
		s2 := float64((p2.X-p0.X)*(p1.Y-p0.Y) - (p2.Y-p0.Y)*(p1.X-p0.X))
		den := math.Hypot(float64(p1.X-p0.X), float64(p1.Y-p0.Y))
@@ -411,7 +437,6 @@ func flattenQuadBezier(qs strokeQuads, p0, p1, p2 f32.Point, d, flatness float32
		}

		s2 /= den
		flat64 := float64(flatness)
		t = 2.0 * float32(math.Sqrt(flat64/3.0/math.Abs(s2)))
		if t >= 1.0 {
			break
@@ -425,7 +450,15 @@ func flattenQuadBezier(qs strokeQuads, p0, p1, p2 f32.Point, d, flatness float32
}

func (qs *strokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
	p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))

	switch i := len(*qs); i {
	case 0:
		p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
	default:
		// Address possible rounding errors and use previous point.
		p0 = (*qs)[i-1].quad.To
	}

	p1 = p1.Add(strokePathNorm(p0, ctrl, p1, 1, d))

	*qs = append(*qs,
diff --git a/internal/rendertest/refs/TestDashedPathFlatCapEllipse.png b/internal/rendertest/refs/TestDashedPathFlatCapEllipse.png
index 03843489480091ad0ef5e3bc219b5c666ac22324..66b43e1c64bbf1b274b3c9458cb12a6ddff09c4e 100644
GIT binary patch
literal 5889
zcmbtY<y+JZu>Apxlr#cT3n<;)wRE#|EFDtPAk9j5C`d>PFR{|nwSoxJ(kURhgml;C
z{tNehn3-ojo@bspbLPb9Xes04QsDvsfUl~ep!-ze{~OroPgw(YkqZFi1F8yg`u@55
zA$UGyiZhQ@yTf)_?^8c2<fDDctY$s2l!cDVfYKvc2Du3_6@Fk1eSjml|H;8X>+C#6
zgvYhGu|)J~LLlM|+B9Xd527jq5y8aIAp8oX&-krRvh4mld{>E>l<IA6%l`E`4|%6m
zASN}vJZ*ky<m)cw9!1XxDxxi)0ec``slb0?lN9oWgB%m-(Ww4kt->DR5v{N9(dn3u
z9!6uMjwN9+_T>^rQPI9`fEb=&c40`+_+I0~xKfcXsg4<5CXc))AK8oV$oEI>D|N_%
zu;_fL_rmGskixGT%vO(~Swaa=2wNg8-eg&?z}P^uSl)?L)$=N2%qk1~JaM_l2i`zI
z$y?5i&0lK=i!1&t3@5+N_ncS1N38k{ZTtFq^wS=l4Aq86<Vp<<I0`umfu^Qp4arFZ
z&GTry7T@X^l>V`*8(?9c<Wc&o3IGbqLoB7GUnxVb7$nchmO@1IOblHLNmp6E8Op_a
z{G$oJ7YaT?lR&eoTpyK~$9XhJ&A<e>Gl!t&2b7|<%p%R?g>-QH6L2U|Pp_|d-gg@(
ztZ@r*<DNF3v;r>nllV$Ak@FAp&#kS;YYI#gO)1N{Xb3uP-9WNxo3^J8*AtB{Zfar0
zf(fYyzz|OM!^THzYwL9Wb8Tu|9tnm>FF}wWDC=46U6M$X`$~RB#`$E>VQa<}Tc5Uc
znkCdSB}IA=Vi8O{Ir$sq8h}QGzZE?*E$1h^6jXC3!k1>E)mNSj4zur#4)1|9%y-zL
zCeGq<H^qppkZ`o#7{WsnV@l6E{~!Qk4E0&K6(aBA;zG+|2$KrBAVIzZFcWFSw4h{<
zGDrnTG;sGRwS2NHTNMkw1fvuzXYeEbnexWDQoDdWLn&e6z;FHC${MM5t;V~5xirSQ
zJ?gSzrMK8|ZwGz@R%DT)@R7(83}fuNva*EY0XmZgrIFEa_=^`_`XRUVL_nJEt)8|C
z=clu?vyr{oepP+8O=iE*+>qOgjLgjS+e5@LuzIv+Y%pDnF6SzT&J9CmTIe=v21^oF
z*VKr}TrPbEAQ5n8eCki>>FFOo>iOQau+yp?Bhkbv-!492p!xb*h}4)et-y5P0D+n}
zwko6Dg!wzD8YYhW?zsbGH>0fUM(uuq%WMuyM8-58fd1%yFvv*u(akFe+ZivyA!V-R
zB;=)|k4j|(JV$A+**=e`|3@u6uy%VHL~PaEfZUtybo<j~-Z|g<Gxp_6MMXt6HntQ3
z+QI95z#}$w7mH4(;Sn{-X&Qm{@k4D>lL_WzI8$K>NaEdAy@zffV5Ofz2gBzMIUU)L
zq0{wd1U_Z_($>tY96URp-9&asYN#NSs7;`AU9>LFsidi?@C1tWNp0KNYK>XXyVC-8
zG-Bd?VVv`@8$Ds<#ItrOXCxd0i$F9_%OSo@%N}&3oFDL?Ndm;1gg)MXhv?rjB6@$0
z0o{(6AF9@bMsNM=!{5I)ekerE_(!;VR~mS}DamQ~{+3Us;6<!b&!&wKP4f93AG1<&
zFiKbK=&$0nVmi5A&Nw2|Vz(_z)YE1L3mgluqowofwjMY=6Qo9KY`j{F@%qoDw%MME
zN&T?vGsT@fzy!3oW(S8UVh%Os0u~n-*v-^ch8pU&ZBe#E<WYd%{<4{_ZoTU~fA#ZB
z^mH~*;>(EHYRnVcj^C1?xr6$j+x<V{Z0tAFUMW9&PAQ9%jgzV!oAZ!Bu~|0r^}EdD
zOG2=ri_i!N`eW8YPsVCP<!kkp(Fp}t4%=o*JCIDoxa*^_%qHrq)+F*?UVv!TYb{r-
ze3A_$3K&Y?u$Br+N=ga|O}Dz`RXVfwz2Mlua&gY7DOtgW{y5$CJa-n%_E?b?tF29)
zXMyX(J>sG>v}F!FL^lut0hBL)Y4l`ppxvczv7x;gvnh~<-59EssmEoDdzJLrSQ{ZK
zkLvpE&^4Ivu51k~n<YbURGZegDMs@U8_)4d_1wGjbj9B;2qLEA`}#!vYjaDPHG57@
zWIMa(U6H6s%9q(JLeeINj3t$Cv^#uMy8?H~)VDt{eRZBvvl1R+`mmjtl~wuSLk5SD
zu38KMRXPRf`m`m#eLSI1xTWGMwuh+4rdmg!0nWfuZBfHihZ?2_9y-EkIJd&l%uvl#
zV@>c`acp+>)xfLzLT|6v>jzqzTI7H@hFu`Zcm?T@{CinAc6rSkUm-Qm6Fb9lV(nZ}
zYe6cB$Y{FFi%+EV*uxV|4*G`mu4mWVCzhFbD*@ZA475NZz=Cb<L?cS%?TvBX0}$5+
zG?*rU1%7q}HUF}UkBuD|9=3ZC90jj7H!-g@u)ll6gW7p*vlN65PG+d8fMl_N(^LkF
z1OCuAq(s>0_4ShN-*9nIim`HKLjwb;?pJr-%=c-pOoYDRudT|R?fe)8c!?$uL6;Dw
z7sAxs#>fvVAkY)TaHQ98Vo@@gEPpF!Js(maez(6`S;4hdR*<D+AR3#bx!C$qSKN83
z{N%*Tkb_n#Cu$`uOz*iDJ+?=Cwhd}_Ocf=mdJ`8}_<JC^HwJ^KJGyq>V?nT01hQMH
zuAxzqK1wYz>cm@qUfjHLVOI03n%w95<P)DQ2p$ETha*eI&buaJmXtg<aov_;eSPmN
z@23%XF@$A+ng1q)4V<0*ptu4;cXmGd?4B;HFDLh&9{bhJc|)Un3ND&_OjB`A=%jVs
zD)`ayi%+EfIG!Yq|2FMou`4=SyI&oJ%q7j%mU!OiO92NRHSK)Kxb4xkGgKnbN!QSD
zpVHmPz@_<*yX357>dmDf{G)CzFPe6~UEmzgl{)f#Pt-0}l8zBO$YHc3@<)IFkbJa|
z(A6&%=eIXo$n65e7xDC;>QPo>fBQ6P>W(wv%k3FxZ0+=S1gpaEr=!*RQ~!R1tcuLn
zmy#M$()I*eD=jSoi?E5~{!8(WGC0i=B%1g2*PkOcHr)U_A+Q{>&X!N=-hs3;Jb@zH
z`Tpr}n{o2-L5U*M)5#pL1qArGljHjOTe(7sJvMP4?hk{92N>)h{{g}l+iQNPA)foV
zCQYOb)Hzr9Ggn*C<>PF7vuzJ(n(B6r+a;Wso2xH9y#@=P1eiJ5AH!)m9d)3EICHi5
z;p^$mUhK00!6Ub^SI0AE(u$KMesI?Kv9TN`oxrutOat@}A3u}4p@xq{@6Fo!2%^JM
z+ky%*9T<OsD3hd<cCNMH%hhMHFJ8RB3Fi&EaJ?w|`uyEK(sO+Z2VaC^a8Rf0V+=mv
zpgvv1I98E@LM5dPzeE2M1_lRW%REqaKZb6<DZmM@)qXjFr*LSsh+K5%0PTO=rI*05
zt}58D8y-5Xu-Scu#!W#${hy$ZE!zw1I%cM#>4gP}Y>b4&=h^Uw`>L95Eu6SKp`q@q
z;Mc1s_N~YEf`+)^055^;#zNR%1r4<}14j?hqlPhF8L*3sB9unNji&Tlef>mARxqp(
zs#}~(u6|kxeuaO!+kHn;-yrnaex-*u+iF+8<x~qBpD9xEV$N2BzOPTm9G`+)Hc%Ya
zF*lHvqW}O)Yi!52|4zA@ng|qX=jnM43EF8D>O_Uf#eLwR_fH)AMDt2NiP6J>V%EpK
z8BL4!h;psDZ6d`_?T4+(U9D|5Bw;<L`F}bw(!L6Zhok%d6B7;4CGIKxqHItUurXz+
z9Ls+Hv(Vj=m+Jdwc*t!$0XW)zh;cL*tux;~^2BRresbIHn|YQ7>;@oWxDVutVCQzT
z6AY~)UTmjOO6j^nl>y^LMm&xybT^lmEcwDowY;0(BF^?o&)tnPE{2YNKZAARQVSz7
z2xZNeF`edZ91*bKMH0tZEwN3;meqj9-=HfUr=s+5efQ0<)-4j{M0zN6yzUhTmdlKw
zbdxZlf@nyN$#^PJiJ%=4&JBecHoSzqZeYhIdO4rvXv{H6@rqtPK#dHP-8-&RW8c|K
z34uV`d{DX*j*AF*8+Fdq{?3f18(l18U^GTK7iR_jzA%-t%DoOZ6LTK-c%1XHEU4_`
z9!6$D+?q)DbrSgZm1ATL%=#^U1=Rxe>TCGq!6!`jW_kP4GN-m*P!pWBwQ&T+b2`{!
zt3a&HVGfmwnhnVldPc@6%-U{TFphB-OzeQ^-Xil;=}_&WI~RIO9T&To3R<UDUieFa
z4Ma`nj0!rdiv(+b*n!GXIm#QoI>3U9=aLKmEtS3fAj$yMoVh(6eIE1pf^5m{T*nog
z!7EBxOjJZFa6ljBBUNg+Vh>4A{rL{Q^!!L=-ySuKcu{$UbM1Zv#SHkds8_rZ2GG$-
zQZj2te~(zJZVyhV(zIO?9T<t~feZ|DYN)_>ogcA}5E41MiE*u6Hc_&uL**25A(+(^
zE`9vp_4WQeP|-;OkH@tajd;2N6_2Ic#(EdwKuKA{T&by(tE+37R^C@bL~vu1unyik
zQ;GP5I)-UOM59@=TU}k<2~#GXx*C-X|BkIuBt0^gc)XEA1jn{N851s0rX{CE5PU}b
z96TE8j_JApT5j*f<3Z+2y-B8lDxkp>mfIUvlzJXvI+m6`R~}`42M=U)$>H!oDJ8`^
zhlMI$)|y!k%_%Wp*{7{o^D|YApX3?^n#@D2S<`A3Uom|LD_2FQN`(g8BJ5OKT~*7l
zBn+zd;96lD9W|VnaC><x$i0_2*0Nz|c#{lf0VYcacjUBdw6n9jlK8%H(L*$o{x<(B
zJd@BwP2KIh8W=cw3!5q6OwH)?YO_cQ*I~kzFBOi}EbBFCIJ!M_Ux+KpXXWafM%3U!
z+~GxlL4KYC7oSj?_GkvStlXTI3KoQ9#lLsx%w;XCY90z*%kCtRxuS=asUN35^b$4?
zY`~<Y@<@68TsQwM%f~!N{r;lt<ivK;ho-7ZNm0hR*Q$yvE8~F;B)xTVa`N#x>%9D9
zmi_U|=;T7@;;ixKTG{pEe$PXTe~#8;`BH0ZYsbgOHh#idIg!sWB;2|%sbGWVZLt*<
z$LgPa_UAln{^Fzh!N=f7oenu6I?c*x_A;$9@2#l{B{|WG*{1>VqlrwCl<vDuCq+&X
zf|XDLDKh_D>;HQjzP*$h9r_wN^~SCPssp%PhPSmL)Lvi{kLDiBJQzk*vhOTq7g%6k
zA<}n6{0?W_=RCf&TUh>{`U*9TPar>zo->U24yx55+QolnNU(5&K25D-9LvXdJ|Pa1
zLK^lRCDX*APzzP0p_k?X_Hc{C#Kc_f3LGo%W;csL7OtwQ3*vd#7V8MpIyR2QyVVFK
z_%)z4HmDuk;Rl*?^?$rqGg1g>_v53L$XRGqS6Bb&e)Shmn}_`^PCO#~%3pe2l8Z~T
zX14!q2l|ig-nuvEuRI44$EJY5bXTE~C70mm+lvDi{%n4JeqJF1^QQ5pTwgNsdO|}O
zvg6v}XPxcSKNxaz#>UBttTMZI3s{CDTM@tao&;&W-mrtWzyH-+CENeXPL<`5zI*7W
zZRE(c-h6JWdO#fIlSWMJ&|uQFwgcT?dPz$9`6?eN<|^)|F&$Ct&nPVo%MlFFFhO2S
zs8{m8^`sa{QnW8!2;CUPWRt=rVhXU6;R|V-%{1fGy!4xWF1@Zm{PAO*ZNEmRw5gY1
zhFWaUnWF(TXXHsXYIrtQPSY&e&-W6pcRzB)i01t>vQTCE0T7Lte?nX3s44ub%twSR
zh*tcxD)cqcR7nBh9tBg@?Oxx*+4JAAv-2`5U8pYx=3bao?c27*`ii?DDl01uxy`BM
z26W((+6W2vd@$D^OGqJ3?V{%(SrjmZ`kFpUDKzLPsP$QR#Opzl+l+ZJ!qEOj%TFT(
zN&9#vnXrX#PD)=qf0Ak&e<|)@#?i{1Yg;y4a256b4+1d17Ol3lY;?}}ZnZ;3{z)xX
zkD*W-+!#{pLXj`CGLdX1bFDCQ<?AaNBA&_fZfW6{)%e~y!m!Fql;&zgr1I>|up?fw
z83%{wcXTD>Qfo)E7FpjbiFX8KpjXHS;ChLZW8q?gNhyPVpgIA=2>FOniJ0fu8cxHk
z6g7E#2m%dcN{?al60Huj`4)d*n5nWA>ArC`^DrM?^QpfpO(bkzZgGTWf3GEz#UgrE
z`&FF+F~Movpu`N%OazCe_o}O72z2!3y*nMYfWEi3c1{j`2zbDC@jR}-J)Bi9XeKMv
z$O-&X9q>-vvp;!jPQ9@7f^e?8K!_|+3A&K}h6K*=`iC7q)9cq{1!56tY3yj-kDZ)P
z&cKBTvyz|XVZU+r@+e*KV+f5<{IRJHeM}H%gOXYTA&rWP3U9W?O=_yDZib~kY^Uqi
z&D*7qoB`=<ziYt=pZ%J*-VeUpMPy}B=4rhliYx`@!*#}R=9)A^<3(ySY>IriJ5LF?
zEfR5XTAWBjZ>wmsGGnMkDyc<}hAj-Y8q1W_;)74)NLk;$?-TJDCoP^t+1S_+&`ItD
z?{Q5duJFYx1I8IaQ!_TYBz`-EMDYF;b@?|!F@6U(@{Z*eyOtYfSukC{U6b~i1b9K1
zlA9zJ{s#<S@oB=E`U;%x>_ydPYAgbM9RFiQx1@MnT-~1De)TpB!XqrW{Ab~54~jp1
zef3~$wfSE&RU*I1nJKteEgyPFF7_LCcaHybo`+j8+u<;Xvk62;MQP?siaG7g<XZPu
zlA-@=kq`Z`rgK-qhgN0D(|>wCYXM?46Xb#o;FK2&#!Qqny0B>H4}Zfi4TLiR4iMvC
zda#{WfBV4#=oW#ft%V;k5$?97q85JQo=c)8cH-u(^!sMtSh=}J9R+tGkh6*1-hc^V
z!xgn4-T4E*x{@CWKq<%89G^D&kHqKg%WieXUFIGB0t}uX=0yPPbgHDEEiEk};85f0
za;gWkL6G|AB&ViNIg3SoN^ZDa_vA_`&zh-;HqZZ(<DKn0HO!APGv`on&c$8j3BF%c
zWCO$Zz_e0JHYhoj*Rnwnh^?)>QW82~6x!weiv$afze@5qZfEDzs5-V-MKP@nb`HD(
z9#jTauTR6;{XO;d$wc!!Rzf_s5<A#?4x)YctS<IHWxoF}NHs@;*PGdOiS;q$M767$
zfuH|GuKivPvZZI5Y>ck+&F+_21cQXc?P|68X8<If9=`)?_*v`uW$}RAyqc&Bram}J
zJ2=Fml_TglUKmO%eiomQptP^R#sJdFk38~NRma^Pwa4nm<8c&j?y|>S;DM0U)e_0v
z#5!H`bfsb-5OwMl2GiK#{HZzxAH0$#NYwDyq+VffUH%zLx6;y*mRDh5U`n_6GF7_&
zN#CH9%9<|2hR)0T_+oJ$frlrwPd=3cU`|gvV~DaSA8`JSnRvLH?e1`Wk`P}0Zq=E@
z`O@Y|0fS^b=wvbB=jT;LMjg}pEKAjSZF=5zuU!|Poo)~5@%AS|XCKVlHqO)e1z!Cy
z%cK)8`L}h4#6V6jcLZ>nb|tZd{cv<`IuoQa-G*B#`U>UC)VMuF#jf`UWdMB=x7Vi@
zOtM5%Bl_GhE299R?sK_G(^POd0wV^avT{T7zTGcs(am;^01F}a^7+@-jTM^ri&SVL
zw5Cb;Ee6D|Wo<z?<CJpff=a1C(*LP_qy~V;NAL&)CgBwx{A6tds)||))ex)j{{f<G
B5i|e*

literal 6107
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
NR8Cd4>YZ8W{{ZkuvV{Nu

-- 
2.30.0
Thanks. It seems the CI is still failing:

https://builds.sr.ht/~eliasnaur/job/383114#task-test_gio

Perhaps disable the tests, leaving improved image comparing for later?

Elias

Re: [PATCH gio v3] gpu: handle rounding errors when splitting quads Export this patch

It seems stroke paths sometimes don't line up the last and first points.
With

diff --git gpu/gpu.go gpu/gpu.go
index eaa000f..269f44a 100644
--- gpu/gpu.go
@@ -13,6 +13,7 @@ import (
	"fmt"
	"image"
	"image/color"
	"log"
	"math"
	"os"
	"reflect"
@@ -1351,11 +1352,21 @@ func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, outline bool, stroke c
			aux = aux[ops.QuadSize+4:]
		}
		quads = quads.stroke(stroke, dashes)
		first := true
		var firstPt, lastPt f32.Point
		for _, quad := range quads {
			d.qs.contour = quad.contour
			quad.quad = quad.quad.Transform(tr)

			d.qs.splitAndEncode(quad.quad)
			if first {
				first = false
				firstPt = quad.quad.From
			}
			lastPt = quad.quad.To
		}
		if lastPt != firstPt {
			log.Printf("last (%v) != first (%v)", lastPt, firstPt)
		}

	case outline:

I ran ajstarks' apisheet example:

$ git clone https://github.com/ajstarks/giocanvas.git
$ cd giocanvas/apisheet
$ $ GIORENDERER=forcecompute go run .
2021/01/02 19:05:37 last ((639.9999,278.3998)) != first ((640,278.4))
2021/01/02 19:05:37 last ((639.9999,278.3998)) != first ((640,278.4))
2021/01/02 19:05:37 last ((639.9999,278.3998)) != first ((640,278.4))
...

I believe that explains the corruption in the "Line" example of
apisheet.

Elias