From 96dbb32dd345cc5b461a1fb6479de543a9335c99 Mon Sep 17 00:00:00 2001 From: Herve Nicol <12008875+hervenicol@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:55:37 +0200 Subject: [PATCH] all: draw with FillRectangle when the display has it Most display drivers have a FillRectangle method. tinydraw used only SetPixel, so it drew all shapes one pixel at a time. Line now draws horizontal and vertical lines with one FillRectangle call when the display has it. FilledRectangle uses one FillRectangle call. The shapes that use these lines also get faster. tinydraw clips the rectangle to the display, because the drivers reject rectangles that are partly outside it. Run the tests of filled shapes on a display with and without FillRectangle. Add tests for filled rectangles, filled circles, rectangles and shapes partly outside the display. --- testdata/TestFilledCircles_Large.png | Bin 0 -> 1578 bytes testdata/TestFilledCircles_OffBottomRight.png | Bin 0 -> 1025 bytes testdata/TestFilledCircles_OffTopLeft.png | Bin 0 -> 1018 bytes testdata/TestFilledCircles_Radius0.png | Bin 0 -> 871 bytes testdata/TestFilledCircles_Radius1.png | Bin 0 -> 878 bytes testdata/TestFilledCircles_Small.png | Bin 0 -> 916 bytes testdata/TestFilledRectangles_FullyOff.png | Bin 0 -> 834 bytes testdata/TestFilledRectangles_HorizLine.png | Bin 0 -> 876 bytes testdata/TestFilledRectangles_Inside.png | Bin 0 -> 880 bytes ...TestFilledRectangles_LargerThanDisplay.png | Bin 0 -> 782 bytes .../TestFilledRectangles_OffBottomRight.png | Bin 0 -> 875 bytes testdata/TestFilledRectangles_OffTopLeft.png | Bin 0 -> 879 bytes testdata/TestFilledRectangles_OnePixel.png | Bin 0 -> 870 bytes testdata/TestFilledRectangles_VertLine.png | Bin 0 -> 870 bytes testdata/TestRectangles_Inside.png | Bin 0 -> 901 bytes testdata/TestRectangles_OffBottomRight.png | Bin 0 -> 886 bytes testdata/TestRectangles_OffTopLeft.png | Bin 0 -> 879 bytes testdata/TestTriangles_OffBottomRight.png | Bin 0 -> 1083 bytes .../TestTriangles_OffBottomRight_Filled.png | Bin 0 -> 1104 bytes testdata/TestTriangles_OffLeft.png | Bin 0 -> 1250 bytes testdata/TestTriangles_OffLeft_Filled.png | Bin 0 -> 1325 bytes testdata/TestTriangles_Wide.png | Bin 0 -> 1158 bytes testdata/TestTriangles_Wide_Filled.png | Bin 0 -> 1126 bytes tinydraw.go | 72 +++++-- tinydraw_test.go | 177 +++++++++++++++--- 25 files changed, 207 insertions(+), 42 deletions(-) create mode 100644 testdata/TestFilledCircles_Large.png create mode 100644 testdata/TestFilledCircles_OffBottomRight.png create mode 100644 testdata/TestFilledCircles_OffTopLeft.png create mode 100644 testdata/TestFilledCircles_Radius0.png create mode 100644 testdata/TestFilledCircles_Radius1.png create mode 100644 testdata/TestFilledCircles_Small.png create mode 100644 testdata/TestFilledRectangles_FullyOff.png create mode 100644 testdata/TestFilledRectangles_HorizLine.png create mode 100644 testdata/TestFilledRectangles_Inside.png create mode 100644 testdata/TestFilledRectangles_LargerThanDisplay.png create mode 100644 testdata/TestFilledRectangles_OffBottomRight.png create mode 100644 testdata/TestFilledRectangles_OffTopLeft.png create mode 100644 testdata/TestFilledRectangles_OnePixel.png create mode 100644 testdata/TestFilledRectangles_VertLine.png create mode 100644 testdata/TestRectangles_Inside.png create mode 100644 testdata/TestRectangles_OffBottomRight.png create mode 100644 testdata/TestRectangles_OffTopLeft.png create mode 100644 testdata/TestTriangles_OffBottomRight.png create mode 100644 testdata/TestTriangles_OffBottomRight_Filled.png create mode 100644 testdata/TestTriangles_OffLeft.png create mode 100644 testdata/TestTriangles_OffLeft_Filled.png create mode 100644 testdata/TestTriangles_Wide.png create mode 100644 testdata/TestTriangles_Wide_Filled.png diff --git a/testdata/TestFilledCircles_Large.png b/testdata/TestFilledCircles_Large.png new file mode 100644 index 0000000000000000000000000000000000000000..7aa75550811bef47054fc87d5b1077bf8ba7c05c GIT binary patch literal 1578 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fPrXo>F_lic-Hcl4O|7EXDIg;;lF4Zh= z&B}@c&mQI$u<>Pb9WvKfl$q)O;9=+eJ9F-zU@6Fun`dfqJh0MMeiD0HtX-%3Bi;gw zN88PwTMIGDbhRrt7t6I?|EO4@Q1nyf-AU<$@<-Fp{`{`e5G!dfUv^<4Ls{|{n@#O< zEE~9!pNsC!@MV~7T=eaRyEoH~DTT!?bGIlpoRu(5zv0czbfd+vxb^N8wT7#bhUGW> zeVHPrehg}oy{go3RYF(wW-u>PMC-<&=Gduf4M!z4O>c&KGDS=&ywo&zs#3#I302*j z(VUy@QdPcHv@*mc<@B7;J1=zL za!=31o6jY|T7&PbJ9EIVAFNXDSy-ytx2hHfxg?)6C-UwKf?|5#Q?b;vZ@b+=k-y{g z+ml>xD!M_@Uobg(^3)rjwZW!LzcX#kiFN<3KM)l=KYv`o>A!LUYJTd=8kE5>meyh+os{>`u`tvzgu$fLsfGe;o?hp z_ulrw@-xpBEgBrG5NU5Ap3xVC^R@2AfzBEHY|bR^S`yiGSk1-u&@a{ zm<_X%_yi11_Dsw_e%}18-cXooE8ob-@oOaf=7=J@}Lwf`ZSfPpscucU+4ZgFV7 z`=CFcS!LsQevsk^!rPTN-~0ixA-S;rL*)Tj*nd#nuA;r^bv@Wb^PRepowoaxK(4*> z^jZpQ*&bJL3{1N{dFIXcav%d9DCPyGrEGuC@gVWo5uZ0-IlzLNd7)_u%iY1I_!sXs zGWu=f%%E>*gL6|CA5bFQ{gdpa4Hl_Yyl@rP|?v{D1CX- z-l+-=u9B*{HzGBeBIXnZO|G4)(hw@4X?i0xlPRKiW6&hqs|pRFlDeul0(F@p=6)<{ z|9eHH!B@gC{DyBX(~Ta(;g(AHOx{ zy@$-P3pGOPoqxDJ;RWXYS)km{`QU?%O>uFZa^_ta%3r-6k`rVQr z%4gOkA*Wu&DO04Pct@?qQ>M(uP-Jfm-#m6tLlIk+3BKCL0=`tF3CNfv3$XSBi$Y#y z*T6Rx=QCEUNKs(rW9a&LKar8tQWsHHf7NDS`2U~x>ihi+*Ys_HbpV5>tDnm{r-UW| D*->s^ literal 0 HcmV?d00001 diff --git a/testdata/TestFilledCircles_OffBottomRight.png b/testdata/TestFilledCircles_OffBottomRight.png new file mode 100644 index 0000000000000000000000000000000000000000..4e6672747b99664f9a1017101f3521fdacfa4ef1 GIT binary patch literal 1025 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQKCp=voLn`LHx#gXA+kn9} zaMg={+vo5xEzJA8QT3_&z2akLYc3vDUK`J>;Kk6W!^pCUMc{;xgM#-cXEY43q=Pm0 z@Bd|B_+M3J_5W_J^_Q2Q?O6(rPQ4)ewMv3j;D=JY_-_BN6iGwMdiRm)%}e7z8^#5=xZ8cZB1oDbU6 z8CM86D0nk8>M*iwV)>x=z9B%N!C@K$QzS@UVS*}%WB0_{AZ9MA7>WWcoCnTe%l5Gd zoDf1-0OTClUBlSM1hNe(W4`CVV=L6~2kh(i2PlE;{Fn0Au@@%F4-{1aiAL3fToh*0 za7}(nE3-yjfH?l;D%$`6 literal 0 HcmV?d00001 diff --git a/testdata/TestFilledCircles_Radius0.png b/testdata/TestFilledCircles_Radius0.png new file mode 100644 index 0000000000000000000000000000000000000000..c258f95e91d6d178685b348ed4a18c41079aebb2 GIT binary patch literal 871 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQKdY&$hAr*7pTr(78FyJ|S zAns?eUqZ?SdnSLY|Lg*)4Gz;7m?D`tQaBYRjB*Ah434L*6K8PfV;_x#flP>785tP< a|4$TWzWb^C#v5P;V(@hJb6Mw<&;$VSs?To# literal 0 HcmV?d00001 diff --git a/testdata/TestFilledCircles_Radius1.png b/testdata/TestFilledCircles_Radius1.png new file mode 100644 index 0000000000000000000000000000000000000000..06bc2a01b65ffc1a539d300a3eda7fe655011757 GIT binary patch literal 878 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQKrk*a2Ar*7pTsP!BU?9M9 zP~(r~e(m57;?~U%R!x2PpJ5S;zzHD-1#gB%9Y&T-qa6B&L8RH{y$yPdDHnU^b1->6 zVrMwN^MJ!N2Bt_RjucLX391dF96E-e5w2G8Y$S|f{$hzY)K@v4ePAs@;Z~wpHjDSO1>`~u8Gn5;0 tK4=#`VrNi}8I3`DCfsY*3=IGOKVjs5D7WqHUtn%y@O1TaS?83{1OSLM>!bhx literal 0 HcmV?d00001 diff --git a/testdata/TestFilledRectangles_FullyOff.png b/testdata/TestFilledRectangles_FullyOff.png new file mode 100644 index 0000000000000000000000000000000000000000..0a8e3f88d045ea7e11fedc1e827e91aef3db06fc GIT binary patch literal 834 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQKoSrU@Ar*7pTwxSsVBla@ zJpXT@_!C8zdWJwImQ5@iDVzc)gcK&IIw*KII80+`)L~?b9F-Uil3|q?*03@#{QuvY X$oTO4Pl-lg-eB-_^>bP0l+XkK+KJ0I literal 0 HcmV?d00001 diff --git a/testdata/TestFilledRectangles_HorizLine.png b/testdata/TestFilledRectangles_HorizLine.png new file mode 100644 index 0000000000000000000000000000000000000000..542b2dbc975a3718997948be59d2decd79566f41 GIT binary patch literal 876 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQK#-1*YAr*7pTtCR!5WvIg zDEd1;Qc?HMowBy_bA^iMSp`%Z9HucaMKW=ua4Jj~<&Y5udvCMvmprhqn(ct0zQJe^ i4&`KYO`d__|9|gY%nuK(tX literal 0 HcmV?d00001 diff --git a/testdata/TestFilledRectangles_Inside.png b/testdata/TestFilledRectangles_Inside.png new file mode 100644 index 0000000000000000000000000000000000000000..9485e955594c82e338533afd58769301fc0cee9a GIT binary patch literal 880 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQK=AJH&Ar*7pT-(Um;2^*n zDEd1;GBMe4$CIYYe%*w{598S#rZ6x?GI6AEDojvqaF|AQ&i8lh|7?m6OfT*W6{C?A wG<5x_(WE5~28Ui&+NC9W#Ktvg28RFt%?>d?H2Y)_2+Tzopr0E4jAt^fc4 literal 0 HcmV?d00001 diff --git a/testdata/TestFilledRectangles_LargerThanDisplay.png b/testdata/TestFilledRectangles_LargerThanDisplay.png new file mode 100644 index 0000000000000000000000000000000000000000..2ef4c71e6787041e2049b71f9e08a6c0146e4317 GIT binary patch literal 782 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@HVh0*S3O-ELn`LHxxA5+K|$bv zgW&i0$np)9Oy$B$PVVGhaqBYU0efcW8I6i(4hWy;Ynm`GZ+;f4U<8a fAmV^Y`~UyetxS)iH|1Ugau_^a{an^LB{Ts5K{D^8 literal 0 HcmV?d00001 diff --git a/testdata/TestFilledRectangles_OffBottomRight.png b/testdata/TestFilledRectangles_OffBottomRight.png new file mode 100644 index 0000000000000000000000000000000000000000..3a6ce84a5a3848d8c78963462b5dc464e5741796 GIT binary patch literal 875 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQKMxHK?Ar*7pTnpr6P!M2s z6#N|?wxQ+FxdO&_*_Fi%E}RMzR2v+oF)&3kaiolLM#ErW(!uMm%m?hh?d9VZI3eVq q;LXse!^pCUh2ESs!VC=m|DUg8emK8U=@c*%F?hQAxvXn8XguD^Chs&4l_X1|j6bqopOj4Yd21WpJ!D0nk8>I^ClBSV(X qAavFs3#C!#P@HgI^D;2}|9@YR`C(iom5tnKuMJ8=wwOdKhk3KLWt9HucaMUHX?Bn*sR?O|kyuN;ko0ZoVI3=IGO YCyO%Ql`{Ec56nLdp00i_>zopr03&wJNdN!< literal 0 HcmV?d00001 diff --git a/testdata/TestFilledRectangles_VertLine.png b/testdata/TestFilledRectangles_VertLine.png new file mode 100644 index 0000000000000000000000000000000000000000..55d5c540cf0789c393c832bf4d87068aff50825c GIT binary patch literal 870 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQKx}GkMAr*7pTw~;9P!M2o z+*qH^>U`qxxdO>)x49L(7#ejLSvIizopr0DS?^UH||9 literal 0 HcmV?d00001 diff --git a/testdata/TestRectangles_Inside.png b/testdata/TestRectangles_Inside.png new file mode 100644 index 0000000000000000000000000000000000000000..c7e7b2ed6d593e5b4c65914f76a52c1e8261a98f GIT binary patch literal 901 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQKKAtX)Ar*7pTz|;PWGKLP zaZb#C-Q#R5to1o1_f|h>J}{wHr$K{}WfP0Q2_XjsZ-z!4s&n4GWXylNb-DV1jq6H% z{ePrBXD_HssW|!kLAGB4ZEU2WXGV=CI;{ZXUH)dazdr-R59y&E)AJY^82 z@#T&`!PD;hIb^?B#qsQ_!G2Z&)dq)Y3`~(s94VX%6Gl0sVK6Z1;O}yF2Kl0EGv^gd rW?;&%e*2Ph`;G$xmooi literal 0 HcmV?d00001 diff --git a/testdata/TestRectangles_OffTopLeft.png b/testdata/TestRectangles_OffTopLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..546350f737c088d4bc9f8718372078eeb4b3b0ce GIT binary patch literal 879 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fQKW}YsNAr*7pTsP!BU?9M9 zFkIx{6nmMNBS+6E?8sVL-BxT}UCmVB$Iz(5$g+t=;DnHag7@I!yq-IEuPqZpgZ$0| z4$~NzBAGZ+I29(SHjHvc!+-|q;F>rC!~g$%Myz)WwR?^LGZKTRtDnm{r-UW|G7Z&N literal 0 HcmV?d00001 diff --git a/testdata/TestTriangles_OffBottomRight.png b/testdata/TestTriangles_OffBottomRight.png new file mode 100644 index 0000000000000000000000000000000000000000..a6fee166d8701626fe081c06993c95408c3780b9 GIT binary patch literal 1083 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fOUOr9=|Ar*7p+&PW z;%x7G|EJz{C#F4_OFhRA!VcIBXGz`c| z2VX0{GByO;?5q8^wFT=yT#-%o#{C($IQZ}-~1_2aeP4GfL?_DA1c%l;@P z&iLo*>eu;a-|P8a7v>g-cr2D(c%zwdw(Qn_*XAT=wiHJ;9C+9k+wB}8apSJW>-VwC z*ST^FNSs`mU3ko#<-?sdK%v>!7qN1j$@(0nvqyEol1=~aeSI9Yg`u&j{F;)1BLA1! zf8-Sy3`}3=vvLbC%$W9d`y^Hl2A`9^ZXaM`VOW;@H#dcmi6MC7zp?}%eR;#bvLp~6 zNQ1-=Z~j-72Be)g|KkJ6AKvtj4FrbH7TCeu&99dBckSnEkD@m* zG)hL+6bN6t|JqC!Bsrt;D7$&eU)$}k!IFYN$>MAG=kvwZc#A0*Fm>}gt=WIy56mlI v?dD%}>h=3eN4Eo=l`c{A@h`Aw`2XMNsmI!a)0YH*g#v@8tDnm{r-UW|D0*R6 literal 0 HcmV?d00001 diff --git a/testdata/TestTriangles_OffBottomRight_Filled.png b/testdata/TestTriangles_OffBottomRight_Filled.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e6675af3eace3b9ef465e519682a00596c3d56 GIT binary patch literal 1104 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fOU;+`&!Ar*7p+}YhHpfs6M~J`?u%^IzU!3IkIl6GsZC!UWX@hiRjn(J&w{ z9bEYE_!V>7m%^`%2QufmekeRBd)dR@#w91RzSAtC;ec`H){5QiOm)S_%Ii;;9bsa* zxAjd_%p;G4$&&Ry=dQ98Qz%%r^4g;iQ?@eW$G@ttKCIou&=@JZPfXWg$9(HG`@?gy zX0meJIk;?V2IHZP)w%eV~$YeTSUg#e$+q=WRswaZe?)QYhxeB0!eCXGj{k<## zCxjd#W`F(8#>ldXMc`WM-@Tp;jXI1irjhm50uBn^42iba?)yQMb${Ki3{jT&_pJ{^ zSwuZML|NUn`;K5`GiHA+R{$$}miku~qRcX~UR}Up!K;sjUs(@`U%S6i?r|#vlW4#F zeF29VKx6moKa^$>*s*$lTRu>r{pe->+Z0!3xr6s?{7m4=f!1|G(0$&i>-VySqRRgQu&X%Q~loCIC1RV_pCN literal 0 HcmV?d00001 diff --git a/testdata/TestTriangles_OffLeft.png b/testdata/TestTriangles_OffLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..ba270fc1ecdb44bf1d05937989fd7b561d436a1e GIT binary patch literal 1250 zcmc(f|4&m_7{<@Nz4TscTd!MD%ET6yEd^v^=1RxHxTOUIOG8^=CLmNc21`}SSUa4~ z-iih_#$^j)6JRL_9r+>tEw z#}jv%5rUUE{#(ktgKuq}jo)J;uGgDA-URnRO5VPBv`qM>)=x?fPo+5(_VyA*H{ZID zeEzcxthG-j^^>;CAy1z|Z@b6NiAOK}epKmAFc$Pq6g7LwhWV#6urCXQY!fk)znx_w z)_=VJPBVGi8ZL;>Ch_$eKN;81SJ}(&6gIEPOhj9Q@vBb$K?UC*>nEi?;{~Wln(N;- z5jis2;8f6UV|W+AeKHtA*0K?F&?{1P17yFN8GVmK(mh_$PCLY0W;7AvD-fY-gK8B- zr&lC^sx6n1Z-JNu@iIVS)r@>M#7>AXITorKhzSrK02yizHT@|NgG*YA8;K%3)n3sd zSl1XG#9z|Tpxv>de;sD)V0bSK+d@r+Nfh8~EbiSf`wtjaBWPf25a&rKP~q6HN5k+w zuV@df+*rWKBW95^s}`CAsX;tq-l4UO+fdARA=I=e5R-+EW}c>70Vnc2Rv6bvp~G4W`4d2Ax+|J=mUN|#2I0?36(5Xj8`99!)m3D5 zaofqvIBE_;IHK(MvaC4+Hkj(rn$aJ=i?B-47K2Z6;dO6l2kG~6hqzm3MG z3zDfIe*B+bKtE;gX+euEo{km-lU3g_1YBc0F1)CU>t%(HpWXB*p?C5qxb-~h#OmK5 z^eS1~WkC9~d`=gONK-8sj0-g^4!k+Mc;~>mF-*_{-+TvqysgrgTEiYsw|b_A1@;hY z)@%kPV)p<)s&9iB)tcPusKIN^g z(E8foJV0CCcoMgKj#r+Gpyh{@PG^=+3mg0THu&V}rt%}+NMn+^=ExxA%Xm=~+EcO% zPRP}L3XmJ-)wtylm)ipn_OWjky`r>s6ss!PPSu^TCcV$I*P+%A`(t1shf_*}!^}rK za}L{~rNN7t$(cc1)e9$ziAnId~1aUV8+Yd}YH@dk2p6CyAr&t$?gh1JI_a7|rm&yM%r#m+wP2<5^qn$+-sLF>9 zY`RBk{n$wI+VGwd(Cl;L zd}AiR4q^Hn>8(v*c1+taj5{MaswJ4Ia-=JBK!sCUO!wa>!0Bk(n_oBC%y;Kha5Ak> zq`P=Et&DG#33KR_dcMcx>t3Rs!Hy7YC)Z$L4du( z$t1!2sVweWP1`KMK5*zXnLNy*<76#D((KrB{jlJC-R-j_3)obLu9X%Tshlh(iVrx9 zWX(eQU)?^_7J;@E$+Cigd$HwPI0u*#Y-e92P;@`|$UL&N}s;1T|8oW1;FHTaS@UqEJN z-42mP#T*S$*es%*YMf$k=E3AA=dZphwY?R9#ykYd3U?>YNCHh5>^273yu`{sIbc+r zKim>vuvyc`{S5o*XSe!5V`%c>+W>|iSJP8&ObKr%k-hCf zG!5~b@rFzD7M##g?EoQbZiI;oZ3RCuDjwTJ}E_p(m-TLy04|@@SJ^pVvSRu zSdAOLr8oZ43mV@(c<2UzN#;CI)b%bWJ2;L$a3+*7+V3rkjuazzCozdu$EtxiP-FiW z%6GEHiz2epz8*_)JwlU;=sDTnH_E_hkf!4SlxN!PvzYvS z+v&IfgVH+RiBq=0U~IYz{Z-0gh%4FJ1q^_5D`LMle_Q;Y>&F)az}nicu_{YPZtN9w P4T7xgxf$PWGoAboJE-9N literal 0 HcmV?d00001 diff --git a/testdata/TestTriangles_Wide.png b/testdata/TestTriangles_Wide.png new file mode 100644 index 0000000000000000000000000000000000000000..86fe94d0ba9e3c24072a96a5082a204daeba0c99 GIT binary patch literal 1158 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fOUzMd|QAr*7pT)P`|+ls;M zB6IP*|5IPO<}VPKEFV2>>-AkZ=^jbm)%(8xXIR7{a6-sI!JDB`hmmE|D2MbgSo5F% z$87oifBxTQoX`1TgL3khe2)8he}3<65x#Nt#EP0dKXxACeDi9J#hs{jWxLmLhd8@u zKb^FH7hk`^oDDl9^8&=28TfP3UGKad&%U9DEYyzo# zvbJQ#+QWt|lT+8!XskWFlSBRTN*TS^A9l4&J{-c|S-QjS;gj&VZKWr|4@o{+TAMEW ziRa-S1^)Wkr6(@7e0Gt0l)T0{O>vI(2ZMR(vPG_kex96U|4QYWqIR^=oZ$MM+?)9x zu3vfhWl~?F?4jgshj!kUEs{JA6x^+NQ2JZ@&e^3NM>Qwjest*UQL87fmUZ8m_Hv&< zd_?=17eNzm7p>HnPM0;hWHj;irZfDUoA@e2m5QQ;?2QgsJ(*VXWZM(@1B=!i7MoZ- zseQBE>jGY-at8i&$F1f}Kf(KK;kL;3$DMavYzpi)^L5(OUC}wr<=DUUk5y?n9+Rj;lq}+C;1LY1| z1k8E4{D$RZhJr2MH}7XS5ILHrNl(+zvf!661H=FSE=++tt#`yP1(q)ip00i_>zopr E0C5VCC;$Ke literal 0 HcmV?d00001 diff --git a/testdata/TestTriangles_Wide_Filled.png b/testdata/TestTriangles_Wide_Filled.png new file mode 100644 index 0000000000000000000000000000000000000000..f49855f567159aa9ef465c3c1b784aaad88d2ce0 GIT binary patch literal 1126 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*$fOUx}GkMAr*7pT<6ZZ?I7ZC z(d_sAVs6bT7uce`RL(!2x4FS!R^@x4{eKxcgd7yS85(sMSvIi5Pie38 zdCckc}AeD#hq=i=m_EZXPJbJKGj`%eCSGZlii{lRoZKu-GJ{Dzopr02PCB9smFU literal 0 HcmV?d00001 diff --git a/tinydraw.go b/tinydraw.go index e1f1e06..5e9c04b 100644 --- a/tinydraw.go +++ b/tinydraw.go @@ -10,22 +10,66 @@ import ( var errEmptyRectangle = errors.New("tinydraw: empty rectangle") +// rectangleFiller is a display with a fast FillRectangle, like most drivers +type rectangleFiller interface { + FillRectangle(x int16, y int16, width int16, height int16, color color.RGBA) error +} + +// fillRectangle draws the part of a filled rectangle that is on the display +func fillRectangle(display drivers.Displayer, x int16, y int16, w int16, h int16, color color.RGBA) { + filler, ok := display.(rectangleFiller) + if !ok { + for i := x; i < x+w; i++ { + for j := y; j < y+h; j++ { + display.SetPixel(i, j, color) + } + } + return + } + // FillRectangle rejects rectangles outside the display: clip them + sx, sy := display.Size() + if x < 0 { + w += x + x = 0 + } + if y < 0 { + h += y + y = 0 + } + if x+w > sx { + w = sx - x + } + if y+h > sy { + h = sy - y + } + if w <= 0 || h <= 0 { + return + } + filler.FillRectangle(x, y, w, h, color) +} + +// hLine draws a horizontal line between x0 and x1 +func hLine(display drivers.Displayer, x0 int16, x1 int16, y int16, color color.RGBA) { + if x0 > x1 { + x0, x1 = x1, x0 + } + fillRectangle(display, x0, y, x1-x0+1, 1, color) +} + +// vLine draws a vertical line between y0 and y1 +func vLine(display drivers.Displayer, x int16, y0 int16, y1 int16, color color.RGBA) { + if y0 > y1 { + y0, y1 = y1, y0 + } + fillRectangle(display, x, y0, 1, y1-y0+1, color) +} + // Line draws a line between two points func Line(display drivers.Displayer, x0 int16, y0 int16, x1 int16, y1 int16, color color.RGBA) { if x0 == x1 { - if y0 > y1 { - y0, y1 = y1, y0 - } - for ; y0 <= y1; y0++ { - display.SetPixel(x0, y0, color) - } + vLine(display, x0, y0, y1, color) } else if y0 == y1 { - if x0 > x1 { - x0, x1 = x1, x0 - } - for ; x0 <= x1; x0++ { - display.SetPixel(x0, y0, color) - } + hLine(display, x0, x1, y0, color) } else { // Bresenham dx := x1 - x0 if dx < 0 { @@ -82,9 +126,7 @@ func FilledRectangle(display drivers.Displayer, x int16, y int16, w int16, h int if w <= 0 || h <= 0 { return errEmptyRectangle } - for i := x; i < x+w; i++ { - Line(display, i, y, i, y+h-1, color) - } + fillRectangle(display, x, y, w, h, color) return nil } diff --git a/tinydraw_test.go b/tinydraw_test.go index 161265f..ab4939c 100644 --- a/tinydraw_test.go +++ b/tinydraw_test.go @@ -1,6 +1,7 @@ package tinydraw import ( + "errors" "fmt" "image" "image/color" @@ -10,6 +11,8 @@ import ( "path/filepath" "testing" "time" + + "tinygo.org/x/drivers" ) // Set to true to generate missing reference images for new tests @@ -101,11 +104,45 @@ func (d *testDisplay) Display() error { return nil } +// fillerDisplay is a testDisplay with FillRectangle, as strict as the drivers +type fillerDisplay struct { + testDisplay +} + +func (d *fillerDisplay) FillRectangle(x, y, width, height int16, c color.RGBA) error { + w, h := d.Size() + if x < 0 || y < 0 || width <= 0 || height <= 0 || x+width > w || y+height > h { + return errors.New("rectangle coordinates outside display area") + } + for i := x; i < x+width; i++ { + for j := y; j < y+height; j++ { + d.SetPixel(i, j, c) + } + } + return nil +} + +type displayUnderTest struct { + name string + display drivers.Displayer + image testDisplay +} + +// newTestDisplays returns a display without and a display with FillRectangle +func newTestDisplays() []displayUnderTest { + plain := newTestDisplay() + filler := &fillerDisplay{newTestDisplay()} + return []displayUnderTest{ + {"SetPixel", &plain, plain}, + {"FillRectangle", filler, filler.testDisplay}, + } +} + func TestFilledTriangleClockMinuteHand(t *testing.T) { black := color.RGBA{0, 0, 0, 255} unfilledDisplay := newTestDisplay() - filledDisplay := newTestDisplay() + filledDisplays := newTestDisplays() x := int16(160) y := int16(120) @@ -130,13 +167,14 @@ func TestFilledTriangleClockMinuteHand(t *testing.T) { x2 := x + int16(minuteRadius*mx) y2 := y - int16(minuteRadius*my) - FilledTriangle( - - &filledDisplay, - x0, y0, - x1, y1, - x2, y2, - black) + for _, d := range filledDisplays { + FilledTriangle( + d.display, + x0, y0, + x1, y1, + x2, y2, + black) + } Triangle( &unfilledDisplay, @@ -148,12 +186,14 @@ func TestFilledTriangleClockMinuteHand(t *testing.T) { testTime = testTime.Add(1 * time.Minute) } - err := compareWithReference("TestFilledTriangleClockMinuteHand", filledDisplay) - if err != nil { - t.Errorf("Fail %v", err) + for _, d := range filledDisplays { + err := compareWithReference("TestFilledTriangleClockMinuteHand", d.image) + if err != nil { + t.Errorf("Fail on %v display: %v", d.name, err) + } } - err = compareWithReference("TestTriangleClockMinuteHand", unfilledDisplay) + err := compareWithReference("TestTriangleClockMinuteHand", unfilledDisplay) if err != nil { t.Errorf("Fail %v", err) } @@ -179,9 +219,12 @@ func TestTriangles(t *testing.T) { {"HorizLine", 80, 200, 160, 200, 213, 200}, //Horizontal line {"VertLine", 80, 80, 80, 120, 80, 200}, //Vertical line - {"ClockHand1", 160, 115, 160, 125, 255, 120}, //Previously rendered too Wide - {"ClockHand2", 160, 116, 160, 124, 254, 129}, //Previusly fill extendended to the left outside of triangle - {"ClockHand3", 165, 120, 155, 120, 160, 215}, //Too Wide + {"ClockHand1", 160, 115, 160, 125, 255, 120}, //Previously rendered too Wide + {"ClockHand2", 160, 116, 160, 124, 254, 129}, //Previusly fill extendended to the left outside of triangle + {"ClockHand3", 165, 120, 155, 120, 160, 215}, //Too Wide + {"OffLeft", -40, 20, 100, 60, 30, 200}, //Partly outside the display + {"OffBottomRight", 250, 180, 360, 200, 200, 280}, //Partly outside the display + {"Wide", 20, 100, 300, 120, 160, 140}, //Wider than high } colors := []color.RGBA{ @@ -210,19 +253,99 @@ func TestTriangles(t *testing.T) { t.Errorf("Fail %v", err) } - filledDisplay := newTestDisplay() + for _, d := range newTestDisplays() { + //Test all permutations of coordinates + FilledTriangle(d.display, test.x0, test.y0, test.x1, test.y1, test.x2, test.y2, colors[0]) + FilledTriangle(d.display, test.x0, test.y0, test.x2, test.y2, test.x1, test.y1, colors[1]) + FilledTriangle(d.display, test.x1, test.y1, test.x0, test.y0, test.x2, test.y2, colors[2]) + FilledTriangle(d.display, test.x1, test.y1, test.x2, test.y2, test.x0, test.y0, colors[3]) + FilledTriangle(d.display, test.x2, test.y2, test.x0, test.y0, test.x1, test.y1, colors[4]) + FilledTriangle(d.display, test.x2, test.y2, test.x1, test.y1, test.x0, test.y0, colors[5]) - //Test all permutations of coordinates - FilledTriangle(&filledDisplay, test.x0, test.y0, test.x1, test.y1, test.x2, test.y2, colors[0]) - FilledTriangle(&filledDisplay, test.x0, test.y0, test.x2, test.y2, test.x1, test.y1, colors[1]) - FilledTriangle(&filledDisplay, test.x1, test.y1, test.x0, test.y0, test.x2, test.y2, colors[2]) - FilledTriangle(&filledDisplay, test.x1, test.y1, test.x2, test.y2, test.x0, test.y0, colors[3]) - FilledTriangle(&filledDisplay, test.x2, test.y2, test.x0, test.y0, test.x1, test.y1, colors[4]) - FilledTriangle(&filledDisplay, test.x2, test.y2, test.x1, test.y1, test.x0, test.y0, colors[5]) - - err = compareWithReference(fmt.Sprintf("TestTriangles_%v_Filled", test.name), filledDisplay) - if err != nil { - t.Errorf("Fail %v", err) + err = compareWithReference(fmt.Sprintf("TestTriangles_%v_Filled", test.name), d.image) + if err != nil { + t.Errorf("Fail on %v display: %v", d.name, err) + } + } + } +} + +type rectangleTest struct { + name string + x int16 + y int16 + w int16 + h int16 +} + +func TestFilledRectangles(t *testing.T) { + tests := []rectangleTest{ + {"Inside", 40, 30, 200, 150}, + {"OnePixel", 160, 120, 1, 1}, + {"HorizLine", 20, 100, 280, 1}, + {"VertLine", 100, 20, 1, 200}, + {"OffTopLeft", -20, -30, 100, 80}, + {"OffBottomRight", 260, 200, 100, 80}, + {"LargerThanDisplay", -10, -10, 340, 260}, + {"FullyOff", 400, 300, 20, 20}, + } + + for _, test := range tests { + for _, d := range newTestDisplays() { + FilledRectangle(d.display, test.x, test.y, test.w, test.h, color.RGBA{255, 0, 0, 255}) + + err := compareWithReference(fmt.Sprintf("TestFilledRectangles_%v", test.name), d.image) + if err != nil { + t.Errorf("Fail on %v display: %v", d.name, err) + } + } + } +} + +type circleTest struct { + name string + x int16 + y int16 + r int16 +} + +func TestFilledCircles(t *testing.T) { + tests := []circleTest{ + {"Large", 160, 120, 100}, + {"Small", 160, 120, 5}, + {"Radius1", 160, 120, 1}, + {"Radius0", 160, 120, 0}, + {"OffTopLeft", 10, 20, 60}, + {"OffBottomRight", 300, 230, 60}, + } + + for _, test := range tests { + for _, d := range newTestDisplays() { + FilledCircle(d.display, test.x, test.y, test.r, color.RGBA{0, 0, 255, 255}) + + err := compareWithReference(fmt.Sprintf("TestFilledCircles_%v", test.name), d.image) + if err != nil { + t.Errorf("Fail on %v display: %v", d.name, err) + } + } + } +} + +func TestRectangles(t *testing.T) { + tests := []rectangleTest{ + {"Inside", 40, 30, 200, 150}, + {"OffTopLeft", -20, -30, 100, 80}, + {"OffBottomRight", 260, 200, 100, 80}, + } + + for _, test := range tests { + for _, d := range newTestDisplays() { + Rectangle(d.display, test.x, test.y, test.w, test.h, color.RGBA{0, 255, 0, 255}) + + err := compareWithReference(fmt.Sprintf("TestRectangles_%v", test.name), d.image) + if err != nil { + t.Errorf("Fail on %v display: %v", d.name, err) + } } } }