pax_global_header00006660000000000000000000000064146561015220014514gustar00rootroot0000000000000052 comment=277066a3d6d7f67458940a61801b1b0b4395b276 swayimg-3.1/000077500000000000000000000000001465610152200130375ustar00rootroot00000000000000swayimg-3.1/.clang-format000066400000000000000000000011241465610152200154100ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: WebKit AlignAfterOpenBracket: Align AlignEscapedNewlines: Left AlignConsecutiveMacros: true AlignTrailingComments: true AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortLambdasOnASingleLine: Empty BreakBeforeBinaryOperators: None ColumnLimit: 80 IncludeBlocks: Regroup IncludeCategories: - Regex: '^".*\.h"' Priority: 1 - Regex: '^<.*\.h>' Priority: 2 - Regex: '^<.*' Priority: 3 - Regex: '.*' Priority: 4 IndentCaseLabels: true SortIncludes: true swayimg-3.1/.clang-tidy000066400000000000000000000001221465610152200150660ustar00rootroot00000000000000Checks: 'clang-diagnostic-*,clang-analyzer-*,performance-*' WarningsAsErrors: '*' swayimg-3.1/.github/000077500000000000000000000000001465610152200143775ustar00rootroot00000000000000swayimg-3.1/.github/gallery.png000066400000000000000000004354661465610152200165660ustar00rootroot00000000000000PNG  IHDRvX.|tgAMA asRGB, cHRMz&u0`:pQ< IDATx[dy߻UrM2 $ %qփ[ A ?وd9p HB'XaLCR$Ù!G3C\vԽy8VthtڵvK~gT-UI/t88PJ7ov;@a;@a;@a;xTW]٢'"}</6j%|<]k=&usVGb}X_/@v;8Ttyp.)J~V{?tB$teךI{ٵZe?c,=ɟGʧ%%iRX_/b}-9a T4]xD;8GJ>wJ>e+xq hcu+?=Ա3G"/ɊRX)ޗb}X_'l}=a; `u<(4ꃏY=#ڎ=eNS9-wE5"jK^˗]Aye?+E%Q6Yb}X_/kX9; aR}(i^Wڪ8v33t\YXD#rvkXĪ%ZY3iG:QȖԑYܬ/b}=kkm7; asIqhD8B,AiYgQDzg"QlG:}dtZЭ=풟݉uiU>,>&8M3#ucb}X_/kX9; ar~b" Y,VG!\e;H"9h}vaזOHE˾FJpPG +sb} vE,J/ Q.ZkӒ_ʖke= ۑEn,et^=2%;H#][2{#6#Ɩuu˩Ŝ:p8 vJܼib øYX9)Y>]ҌɊ>M*م),OkK*|'QʓsHKz)E"kGז@$z6#}6=8"^Uy.IO*#׎[:X_3I£m(>ȭ$yE_0X9; asKwȦyHey.^\Qflݧ钲ϑ)bHw둗,ywy$q,ih1oǯuJ/nԚ b#ڈ*a;@ a+X"(1rHɧ^\I?*sUXʬ%Ec4#^Ӓe\2rՌc+v$W'RG;bGX?yjwY;\~`}~}Lrjx6K/$_*N"2%HLkUK%%_[ɘXR~G>psv;@ID,Nq-2Q1c!+yfqbŮT:%$޲o DHK%Q)s("7"oFUYRhgh~--~JeWUQ>GKXM9k4ťohM,ZX&R5z2~\ oF,vvxRyb%˗]KO1GbB\^Yy7R~dOU{9_݌WҏěW{dcb/O3z)/^Z,rY)W<ގسbDFrnDVR)~hWsQ}uVNw)==\xFJ~ X9; asIhŪ,>Lw%ԟ~ J6coI3bK~J}Ke+p #<͖y^<ʣ࠿l/~J0ҧ []6g#: X/MRgբV龪YCկoU[3g17~zWs$IIweckŵohMlbpA a;'8ŵXX:ƑXħH%uD_^;Kx5Z}ۑ-V؏ 8v4rIPEs@wfYi$JWeI"-A{_;KߍDE*[.I=kYeiAuיjYե(b^gY_;Rz̺sa;@<dE匄pqeJw>T؋8^I?YHL"-<22ER~^ш[U,)YFڴ$g%+{=[viՍkZXT#ߌJr\O+>3Z_zCa T^TXx˲Q1Kl_{"߉G_DfHD^j|42Y#G(Nn5I39ۥ20|wx7#?\ʖ\hɵTyTud+xd;oZ2j_^{C=[_oD.l..~sZz}F'R7ɋk/>z`8 v%GS6dIeS* OѾ~ſ~fag;*>`k}}qES&XZlIvuUW2`}*їKwcF_~.\ti9a vp.>K~8s&7H(~#/iFV" u HPHHٴd$9{iibV9ŏa]k>#bۑ>cnwJac^9Guj}s+_8Z[/cS-~ˑv4#(OV֎z`8 vËY,_b/KX4R喇# "QK"㌢UO"OK=G&/n`Euu HPq,BT$*fuL#IE˳99OVTZe7呹5DK/}R姹4uhb}t}=Rʶ+X_v}}??,~V]^Z+׃9a TMS\"Kb\N"Q&ͼ,$Ґi'pS%EU~FblKNd4򬤲"IĢe"c%uq&y~X;D,[=?Nƒ_UlYrN3Csb}=_/U[*ZY3; axDKGbhY$4H|ICb/;lI;WDH;4jY?eKb&%_I?Rd)Xq{VEeGu:ʓזD][_KKE(-.no㱾6D=d}/-|:<v@ΞG/A{"qjqGĞS,#_ɳRIk_Ib=iHRG'I7pIG%d%Z<+Y.I'%GK]F:zVdjDʵ$:+*_-:v-E:ٽXW1.\S,=*#b}X_'o}=ܪa; `uT͛ǓHI׊FLtU>Y22..OOQGYEyV,Qr4+.Kbe6,Y^_RG#R,֒:%iNg"rYg8Xɀ'e}X_/Sv;@I屈9,R>Zl3]l_vd*)j2-|hx))UQg%L1)Qz{%UE4F4[,3l;ˮ-g}X_/I\_ pA a;'(W>N++:Y9ŊUIV6()ư[E4KB~%'֦e_J/[ӒTOQGj%OsOdk/\VD>?lmSb}X_'q}\p@ a;K|Β!iLk&H*OU4@rw7"gaY}PIɎ[֤XKꨔ#-eʖWO5 먭|ٵᭋ%u ۥY,b}Ubz9a T~8*-:{ĢSL,R^IJ>:*+k*_ljv kE#X_/DrC vxRyQ1J^x`8V6ز*ʆ_=M!>4u']aVu|aeqb}X_p@ a;'D|= VNJWb27b}X_ ;>?|ap=ihz4M'EQ( U*E<ϕ$Փejz]jUJEyn~UU}{ӭ['yjc3H{wd~7oEƋ*8voZ }D~}兪^4΋j繦i\%DfEσZ4M5NUTfĭZ=^0͋3>qGьj5>gcaѮ[:};̺gyˮZ8Nu]ݼyJxͥmhYt: ݘRMh4fIj͈IjAXcihwp8 м. H-v^Lz+Ns= o?^dyh4q>r<j͈-hEя /Bg10ZM PZ̻qڻλp3wy˙we4歖/X6޺uKn/b~iݻwO/X~"܏prh4M5s]M4[ٜYCoٟ֟ƍPNؙH7 `V&6vċ /4өy7/ժA6ƶ2aάBvVoLmMxqe8ͻԙVi8jXͻoTFoaF K;g-7g ޚ-rVfc>B~^X]^TW* l<X05!>jysO罽=}[RۍWpFd`0we4Y#,d2q3d"S^86yW>̛P4agD\r?01UL :&6ac IDAT]͕o~]+nsEo&cĒo}^{GR ACJFFAzm0^фN_F ƒ  4Ml6c]fz&FaXzz_~xbpFx˓mRy h&lkW`&W6^XDGv Ͱ>|0pL&t:}gϞiͬ^&| sF,gN냈Xh<./<}jp֦hN/}E"Zj>NZh49w`esNhWR Zjk$?q[NIԻS6뭷>wU5wy|||,IsN5 's0k|n+ylΜ2K2[Q2d[5}h>GYEv6˻ͦ$Cef7( -ou[}<>g4fs kf3K=˿3K=Ьe0h8Μ@k*cuvͻĚeЏEQ)W\QZٳ^P n,SG5kY|-Bb9"-$3uU`)@@)l= ?UYբkZ-5xF;6M Ù*-Y1If.\ \i~CoFAHXDcbY0N&j(7HJE~_NGpF|};'фӮ7 mllh?*/Dd>ҩ]/N%/}VBk"d3<NG ;3ƣ( 50W_휨Vz/Kuht_bw@:ge[l hEQ{}"H ]KBj{m{fذyN6֋5;ϲ@,=ϟ3+E`7eʭs=3up^A^y1df>|o/T*E1ͲL`FY|@{_{&ѾXbyq"VZl6uU=}áﳺ|By[?nWooW^~H18cpF؆6bfA,`>J۟zh*,"zˎJؿLG+!-Zd2Q՚q5g߂jwʲLfs&%&8oQ'{F#>^1G,; Z֯~iukaKfb> }'.~.>Ltxx_37+W66n6l[[瓩4\~]ׯ_իWX ~>Y0ޒejsOi/BLyOY`.fw0aچGVl>ߙN()&xܿ%͟-Hkkk as3)ԋ WUGtX>ՄYުhwR3b>O}bsp"-vo5qb:vu=gl;#O}JYݻ3͟B%nۺu^x%,ojṓ,xɻNݿOʓ$u= pFM0//>L܋@~sFăk,&L9*{G&m"g>Јwg|;&p$QmV2/h=JПmADL_ܼEź'I] ֗FC;;;?٘еN&_ {zZIv wVbDk6"ՔKRxzLXU笎z{[lđh}]>Z)0IRb]>Otť-s{> =,.oa3hylnu{J_2>3QiEQ%ki/f~pHs|z>8{Y }l6o߾-IzW\07vkA VckY`vP6!db'7D|&̅Ѣ(z0D44;C5:.~O-Ϳ哳!^.9l# }{%074cWkӼ>Pl/o]>K` ޕw=>]D["Ynb}O׮] i«VڵkODFAZBz=ݺuK7g扏jsz$gE4eBΟu20LB2o-A756>djZ!R&fy&G4YL<͟`& LjmT*jj43ϱzi~ֿYҬS紳%K `㭒s"VU5e5ϟg9ox!nc008tGGGzz/.{9=s kcdsmZJD7ooR@\k43S-Aev3xbpFF5xۻ9sQ<@L[LxGj~kY1oMI u}{wJ T,>07?s81⭎&GLN;Gb׋<^Y_[}yqg}2l6&]}޲eccgV}3U?f+92煨m[Y_$kgggsmm-<ͤ0+o~3cemT*AiCI \}c /̪ɲL^HvG46r^$`fA7v[]li^(^\y {hZf҉… :jqD4,Iw55 K&,]3LΗyAd϶/{,:h}rGXńO[sZLv-IQ}; B֟NRz"T*!&|Mv &ҲFG ._vԱ],_6sbU:Ʀ\KirIlG}^n Mͪmm&V8X'ۻ861Q{ /M%z=͋/Bͭ[Ƽ[f|VƆ<3<F*B/^T }r= C(ImookmmM{{{֍7^z  2hVO&5 |sciϴsbk"1oL۹94pŻ-ڳ|9v E{WIzSOݗKk׮[áZVsu֌;Qt¹FF:Zf#Z-vxW>,cN!)Y,ȈmM`09->ټ2Km Ũ :Ihmm-X3(\|N7䶱7e7v[V>hvvvl6výpBj6AHU*]z5]Vutt_~9nFꭘ&_7w4koejf-ҩg&Fkcy^3>=܉mwbh]]xqk_$IO|\ׯ_g5_.I׿>#eĬR R?_l>8<.ju&:^N2!] s4W>o'fXv;Sx !f[̒f-$f1ȉp&o${v.kss3X^jávwwF|Yi)2hww7p 35e~pǴpG "ăϿYE  ϸzWP\bצiZ`yaEcs$^ݞq3>(v:F#MS+MS]|9nܸCc5އJDv[z]V8c烼ۡ we//|!$ضY__t*j8Gq+v͓9`>b`؛@&-W| K/a~MP׃v[}H//ψJnimMT 6~4$ gWL3F "v >M" k1K?T:,z7oNzsY}O?MR76Lt]tIL&:<K-6^~F445`g#h;w:}֍7fy迬_BT`ѻp̼p&'J,vO/aqj$Zf]OsOqu,kG ?ລ4!)9IFє,R>=$A515g,[wUp8O?\& ĄnB`vu:^[qx5"imQvU3ϨnzAZݣ(D\[[S 8K;H.^L#I]SS^>}2J]&*~Jǒ~ʲ,T0Ktb!5 YA}bp#ޚjQ]{1\am.8З6! 6 нW_y}c V{+xR>Ҩt4vx&M 4z^i}u`}ɶf'n>=m͊e9Nm[Yп. ~᜞<6!`f M Jnԧ^yLz>s,YJ%F˗gOC޺zJܔ$mllm3·-ښ$3^z%ٟYp94AdԻŚ̟Y|v͟&]ֿfcm_ҋDV|0Y}$ShcaʢZ__e"qwwWm( mllzi"}:(lnngM@Y+á~v&zݞ b=6:7;hwM$ & cAaL ZaggGHwܑpЙƬv&RXX^Z4y=]z54QnuEݻwO\3bmm>z}kҟU4fb[HMg+Z;D^ٵ4 6wZ-Kh4qb|A f4N[#outty>8PR e"ڹIi 8`8ٺdL&P3\ŻbŬ^ĭѬqY[jh",zs,hcof%4K|E>lpӴ8-5N,L^O;;;esggG@҉%immM3Z[[ uZ[[S^WL2r';WhVv5}֗%|h>񺏐im7S[aRqyϳ1<ٷ^Ez/bf?-+y` e:W_Տ؏I:o/R8gs[+V+XAՃ_y%Ih4gz^x/im_O=T[jk2h{{;`At jsykmm-X,F3sjiӟtxkcG)ƌsw&j8"[`yd >A&|ڃ vЬ6Ooτ?hx?? {wQz^Y;NֆJM֭[ʲL??z] f2Lؙ6~?X8ϋ=i`"P0q`BHRfb6YxFuu]]|9WBvҠh &j{SKjzٟ$nݺ??o#?ά-뼀2QSYxjea-Vp,_]wqhfoXvn; Dt: 9 "@EQhggGԧ`ìf@3KSxqgV煟 hἰ3qg!d7vNkނb0MpDI̸Y۷oX@Y3l>C -3~^`t:ZVC}քt:LN… ADHf!rJ8 4G>}կ֭[xbjRRؘ裚zWMsQQ6wwwCXN7҄7XZXzhe۳[F=} _+MF q6Vv[{{{Z[[IF}Y=zꩧ;(2p8Խ{fBۦڧ] IDAT\|q;77n8XL|؂hx5/(-S/R,,tMYdV7fi6A\sZ-_Zy  o988b`<kgg'F[Z__vtݻ!O^>3Ss'AX(1˛?hϴVOacŒwe?}}}=kb/}`d`(횉n7xCov&V.Dөq8gkCRH oeڄ'?I}C//$~ L z=ݻw/f[,kmҟC&&L|i63Q0kOB?sYLÙv NG8lv8fĻ< xITnWkkk AݼySC@͛!睝L&Tŋg,ZCp`0#ԭ{z饗k5zkyo&Luf0hoo/ŵ#{+u&Ƚ˯c'N|֟韪h`5j5EvNRj ժ׃62mmm?q[ؤdv ~&*RQUN'zg~Ĺ6nWGGG?#5R[5]jT&ʋGoQmsۻz]Hg9TiUʋĕJg,?9zZ}L?j穋ڭ(KsRrQ鸧< ^F^Ч4MըfVQ5_QZvX^_@~Gƪol)5jVBp\ FB2mLi6R̘OjʋLz 7ue5.k:mj'}޼MzVR-hrOEezn*N58dA7Q>NZSJFt@Gz{W赗&*rVk?~tkvF\:::I[`mSll6&,̾͝}Mȼ mx>@ }>8C߻wϙK)\vwwk?Xu]=S] 5ݻ8Un0詧 A]4Յ tpnG~G={DmsZ63l6gΞU*()w]n-wv}ӟYvj4Z__b]}T.EqV:88K/f۷o/>xݾ/EOaJժz:aV8g)>ቓM*%|x<֠ystD/sKñ&SeYME$iNSE8_b,1w'/][oQO7Q6j ^?ڛM*ՊDʒDJ*tgH_|NШ_0";Vi<*/RM&cM#%TT剦ӱXX} U'6j4Z*R)M4L|t,ϔerNǪUGC*#YEI@G={R-+ZWӟ@oouo2=Jc^v>E;;Jh4^oNf|>lK?0Y$DŸL@\t.iTk|=ͲI*޽;s'βLkkkʲ,$߶6Yh͐̈́D YⷱZ6XŒOSmnn }֏ȏhn71KE%MS]tI?s?'I?`T >pkkk}v fwLy dcD >MgeWW^yE@.] _y>gԒYfm6{9Iҕ+WyMmnn>?+Re ;4Iw#"ܹ7xScݺ5xah4JEMӬpPfQzuUI s9g׼P^Q VH=mdm yKҤ*'{iZL~)ՄnVdJDP^_}C_FfR^-4ORxT%yv#QQW5uqJ+áuqulk^{C򗾫}wOWͿ5*2%Hi]*=d)UBD3t9eN˩P IׂVL߷LrLl4`xz%QK<$;RVk4v}ָP"H'4 +eʉ`guL*וGS%^nd6 =5JxaܛEnWbM*kazPuU;U{'A!Θw`֎y7ن6&r4 EsϾ7ah.y&laq fG<-ߘymMH'FFMwRQVmB(fYiHN(&R/]w͛v$vvvBLKCa3MSh̜=Ny @|VI } _ /~ ލ lj_ydz^o.[VpKjs玚fpQ1ξ rؘW\?#j :x<0z=} bsksss\~_nN E6IJb:(N]5JӪڭ uMMlʲLQ<ꥯ]|*ꤩr7kj6&#UT['V }]rIYݻz?t"幔zKO]jB*#Uk-m\TS}h}}Xdj O$ebbϣ<W |3gJpE'ony2s9kUhd|lhgT(Ʀoo+J&CӟsF5j*TIȕf'ck٬5U+ɻZQV|Ҵ:,DyV}YRKI\ָjN,iZRhʉ$S'*Lն2CZWv5::|C; S>~Mj4J )44ʊ\͓<9t4W{XK֪}s{7$ERhˤdYY I$ $yK1@^Đa`S&<1l3I"Ѳ%2/j۹u]kCa7EdK6yz_VZU.aeB$<(,z=8Pj_~~);mGЄ &LHZuvj\_@ugYF"%߳z\}Zjٷ:`3NI~Ic6ٶ VO!㘵*pPR}:Ҙ8Uo82z~`PlFۭ/,[9bt8<<9S!!W=RWܹs_V;ͪq<j2uqIa:y׫_Pa囹jօz?f qӁe~5l6|Eٴ(cRnz%uN5ֶ8ûMڀB)~Y~0$iJ;iΡB+Ѡށ*HA(pE ."،nrO9{@Lgc2(gPtށ.0*/ ՚)I&P$y㛊yu4`qΝg}}cHz/9v [>0 ~w{r?>1i;m'Z^6ϰi|iyPh0JfMAyA^$XyCJfHZ_p_hS8EpLkѦ A@Vv:Ҏ'l};pspnIGGW|`؅ dB>mYNh](Ey8qt=+mFlFiވ$7Y0Kyn޾(F?ݸ@` p*X5vw[*'S+9+ZO%vn9%Lۻޣ!0•2-*Ä2%(,Fil`쌦yՐV?Ccϙs>xWJ) KAHS8,;Erk0+s8_ŏ.-X}K_u骧惞W?[B@X D&_:S&suzl_zcHh=M(M^{_~_kJ$1abXYY،}auvLS0d2aZO=1t:ܾ}OW$z<ϗ-RJq~ie:UgT޻Re7ԵIMʜYٗ-ʶ^~evvv XEv$5XRfŦqI?֘L&u$۾tRl6UμTyA2G` bDՠPṣ 6úr<1p1+10"m<%]P_2-8b#WBjy^ڢmEzs9ku4^tt@# I HbF 0@D4'IrN , fKK;ok /<+yBweIbUuյZZ NcDS\sWpBe-ePƁ.zO4()p$ZoR6-(e3+bA0捪i\=lOzb)ư3+AhY(\iUwGwplu ':پ/x?lA̫S`wN MRM{ޒzܕ痀^$/~_1W8P"a_ t:y^1:u&r>seܹùsF  /kww,g?[|p8@h4ZCj{{^RsU}q6~®i(J`ȉA?/ٌ$I*ƬQI:pԼ_aܼy0 +AYz1}UR_jA(R ' OGį/\DQT nx8w IDATGjAxhXx5#1^SZ,Ďl u5`zq,h`~L" Zx헬KH_췦j<$`]Ƌ^kט[sgpbo:v^CGs.|$:&ajX&"XȌ x u^(1i M@F)q2;'.nCMp>üv{1‡!7vf|L'Da&qUoXF-LT9@Hi7 h6Ȃ* !Tzy@kL) ڂ |uJdØwH.Z)  Df$Ɍ,9G+|$2҄4[!*(Hqd֡hԷp>A#B/^sRȲyu Ni kb,  ",4:"$(FA:xoXm15ʕ+DQx<^|^ՙ,*pTgi׉2&m>6Id2a8VL^tkkk C>U$ n[ܹS ԥi&DQ"IVVV[OɲSPAW 0b^7ŕTzrR ;R[#aXYO@8ɓ~,˖So5oϗ' ~JihirM+ORoqt,#I.]hŐ4R1s'ԗ~M-C9p(0N-$hN=s4% xu@U#43孕śy{o5=l)2q㈳6HwoW#")S]M8W3 89.Op4^k1ΡFYv|!SQu?;"T~i[2tFt0!<#v)P4S*6r%aw0,dxg"NX㵵c,A`mShUf=ܝzoT1{oa]_v7}2|;e_>ӰS:V9iZ zHGQQsrèNɗ@UܺDժpG7"%O]z"!bׯ_WUzP%۴߯)Zɤ2C666*&hpy.\PH0{UgΜS'dԷIWVVD#N[ݏNνU @?Kit:C_w( */LbEA_j՗7o$ ᰲ# HӨ .pƍ%K+}} ,'j<H7a)*URZP,|B|ŵ2^?՗_! :g}ǬscCaKH<]6#=.=h|vDd:^R6V6c4ynA){\ +߸å>A8amtưn3b}4qE—V봚ƴfooÝFKC;PsG.Mqw 5pX[֭V]Y3H+=mdt0!Qi-:(c^x8L}Cr6@ŘhD*ӱb<tv9x[驅J*FCo6h=ܝ:Y as='> ?ꔱ;mSJqE ZRƘ&:(ho: uYAф,\2D[`x {(|3Dh\cw/OyV(cQi׿Mmrh7vƜ9׾{EhUh>/ `kp\nv6b:ez;DŽY<#P9](rVv߹s(M)BCsY;bcwBcp3yCl<2-Pa&nS9:(,uZh=F;(gz0S訔VhT|氮mBn(l,Fa"ƒ<.h AlX\bVB(|)D3s9Nkw(Wւ"a]:r1jFU@c뀞ornL($Z%ipc9VW;"e0d̓>V,Gb\F3Rٌsg&cVptpVЋZ`:Oۣc:: [$_/׾+_?7W26*R/v3 Tlq1wn9Mf;r9+ W*qI#nS 녽C0 :Ain5hmp -2XyjPZVFoXsf7cwk° ȡ}^{ٴ[Z!t[#U<p#]4@ .ֽOռ:v=&A& EYyzp~2mMWI=0 lKNE&7)8y.]ZJ48+:N+uI[E腤FvW^ҥKDQ$!yNgg( ar||\QeY!ϟh4qM& H:Zk+^GNJ֭NRu{u%x:VZΓO>xu]'-sL@'|7x=i4͊!IqWAy]SX}\RժId<W.\[d1YYYt:~0Lb8ynw%R\e _]7oZJ-s]ET?[V&ꢌYp(@L>K$<;wI9iU'E|dOO?s?ꔱ;mz:X٤jU(* $rՃ{ +dR 6Jn,.FQD^2 U=~%Prttkkk$IREj+g:bakkJzJ]S̿{9._\}3*pmmbflllT.Y__O9_:NZgR@5M+AtHjqe FĂ@m~})T^2(œ )b3 gm>O˵kxǸtRu.en;)_I~W~?bPȲJ[ ՙ+WC-5"@󠮂)ۺV|"b,-HH9M,fb &bo$^~+xoGh҆nӊ,|ΛhVCfK g>/~Ü=lvxsˈ36zL2tQdXG݃1V; SZ݈jHg(]“BC8rƹ\0.4zY0NW/U*4J2& iGajfs`;fsG<4[`UxꬅHMatl9>$YD|U-u^}``U>O;mWX)ysʕ 9mΟ?Ĝ0 Ð$I55p!EQp <\xFQ)1:*v`qL2geSad.|ӟnoWor.Pl6M}곝x'*s:)*ŒtWOQ 󼚃qeY5~Wh@I:-cRmkOX-(Yj)O҂@) G<" h(k6(UbN o92wSv){,~Ti$r4ܜrD4Dod*K ׾ ߟ2=ū|?C9Ys~ZaNgoqO=$.]Z "˙'$7>v_n Xd?`.7oq&+)IzKLK+Kiu@py™xڠl *t[v[[[qoWJޥK%0WyJ)vww. C&Iunp׮]ŋ29shTy fm6x K5^,SKꡀf믿p8dkkkJA59ik!@ZkO?SO=Uyۉܾ-1qw~Ð35O<[w]Eʣmgtuq5ш`d $qe5SOcU uOBZ[~}P+(@F`!Ä ! 0`4"kB&^h.CQbJCo(TgyAjofo3̦ky^)Zbl4>@+Fb01XY҈FS6||P2 ,+&I+,+x"y ֆL :ms7p8|M95%42z~@sN̡tdhtBfn(UdGQm丢р֣S)QeI<>fz>7jGjԑ^[cu( b!#Rlq//~m݀w N2;m_(+yu Ni{@M؎K9*pF51%`XWaD褾_'Y8I[WJK@Qj׫?ae+++2˥/{{{dYF֭[ձRps0\2ϲ2Cq6U3͘N;wnFUטi(Յi|ͪ0az}XݮիW@GEQmʯ O>$Apڵ߭V*`@}.2I!c x)\}!c IDATj :ntVvw.{R݂S0mJ%LKi(%9Y>e^U #Zk,#ostbi+,6ye.O>/߾,C\4dnw-~ f61<3ی hnٛ$\Zsf0oup6{ڈ33bG3~&;ZZ2K<*kC 9@J9K&-Fa,lcFra $wC>, }~9 8'lܳơ>&0;_x;{x_G&gS4 xƟ;M|?Y/}K]O~~s}ٺͫIO p: Ni;m{wŊLP*KЇ-UUM)2M<> ( U*fxZc?8:!OIfcw̧1y2K#l1Z77StV!N;;ë関)7<:{,U4`@l6ٳwoDAPId2D)tmm 8::ZJcFT1_U[=;;;RjJjOe:2z %Efg qz8Iun:f9Oo(f. bA׾*ξ [S <={p{\OpKDƶΈ Z)Η@c&U Y (.I,$x| |5TV)ͨAݦny/sM@=.}6Csem@)Q2x7)F;9w_?{e mSfG}6 6~{WD=f"X_m1pyFh hG?W虐y6f@YrΜoyΰͅ3]89#ށJٜ~ͽmΜd]Lmqßq@Ɩ)x+os1C(*IDtJ42V`QxE@aWWy̢3кcC[d`=dCYଥ DcL mDdIdf?9xU>yGڬY>, m8dg)گZN++K-?w_*=iig9^x^T^5NU1Oi{@.B!lGQ$!'Sp/<[ eY!v"f!u?k @Ch42cLU&j4ٳ;w,iɶKz#O-j^lGGG Ae^;>>lff3>~vvvXYYR89nl2NFq5 9f{{bAW2*czv… \x/Viz?zMhM/2y[i֣g.(gOuxI4x} Ch-Ft|Iblqޫw5h]L%ln4yyKܼs^B6T<|y yC'>Z4Oa},/ #}ڵ v seVWW}JiZR7'l'5R 8՘q7hAN`XZ^amE6fZHHgc|PLxکY5 \2xdIڹ#M=] Xg-Hɦ!#¨Gl1lAL3}˜s5 [rN|i"+ R^AETl>~<ՓO>ךgoZvw%VkIAR$M:'0:2I_T 0QO@(za$ y駉ޓ}4 Jc6668>>-}4#={˗/3uN TƺO y,czR@EΙ+ eNYauuW&>Ij=USPKQYSNaAiB,SOe>'wMo TjA{`x^9W J,|Ɲ;wsnh4h5ܸqB'Gxse D&h;Ua[:0D|" 3hlMi7;>O=yW^Ae09*2r-wr}8$'t{C {DŽ*H q̦SB@j ˷s͜لx$Y=Z{\ge.+( jޛڒeZ+=}s<3#Ma1t Pj?XHYhT u[PPJ -JHP*e&v83;i;ƵG'Cͼ9Oں!vĊ;w Rߛ?vP(墄IrC ucQ.}^{wii/SP.ԫpdeciy' )>RrR!= =\#u:s ƽC!_;4N0ڐWG s$ϵ.fư:qa@jw%;̷;EcB+wWB UWѨfFN^YR<މ'R +yHjH2HU4@h"c4CZVQשT|Z-ʹTCeoڽ_o+ |gqx׻g>qq-w,巕ŋo{oWw^8z(?xkܽcۏx–YPc6=weC4M OV.u^%ePh~A`0(J-,[ ZaVZ8)$ \91=߸yQ]0>}d•+Wr Vv]A@i=ZgJ QvIӴ`?@Yٲe<)%Jezaa  ς2x/2KX^{{f(".\p#̓2W]T2P|7_-1c)76SmTvL 8Ұ`ayZyGӻ14k-c؞ш, au fAR8?0."QQ׸pu^?$N2ip8*Ԅ܄%o N]wS!S4xRhLS\vC tq`D3(Cw4i4P"$#+ӇI\W9 GC##[8nu'2C䬜һO |sU8v:k:J'Q)w|wBcnu{O~jÇvn8; ȼߕ+wAo{Ǐsڵ׽Z>cG BL JJi69ՖfZwزX@Oٻ:WRLA$d+( Pzڣh4b ֨) Yin.e1Gpyyy s`eBp`ɀVl7p V@i?^&%R $g[7sO}j%@Mp`J0ƤVX2i ÄvtظPq*&s @x,l(G߾Ϳ:qfB%P-%"pҭ`X\CMC3L"3p^{{^ϩB E(OzB5 >+0:*&a.X v[E!R2pqIGP)K8am ɸK,#bLܶȲ|F@mmO;5.N`A~A~3|$}[\[WNg&c&Y:{/thGM++WzV*JMUH!"?ïY&9[[[E_^o^gAL l7{^c_w{3 qm5ﲭ=~Z.7~ڃuz| ݯv˷ZkjZQ*hqrY,oE9ϗ1m^D&{.F+ IDATn`{,f/^z@{+++xbH %ui/OL#q#DajuvB!KHF^2s%3 M!Y(,1mIPiA,T/8HGI/WU$49ʤD&bʡP$~n$& 28yZo DERZhɁP1&)qĉޣGiݥKfcȸ?[)ߓ~zzzwZ2s|#'xS؏(,J=D-I-Mmk3VP" ÙYP^/+Id XF,R﷢&֨w:ϫhfv 1|c&/]YZZ*ܿ0,UqgΜ̙3j5 Ðm*Jaa fǎ7 ۛgJk^nAMZ%Œ'$ Br4҂hD̙3;vcǎqE666>Ți`02^I#9S;V}k,,,*HXwZf2lrтմs*9X<(eEؽ%W='jc*LOJ`1Ӟ.-ry] 5q`Ra ]J"UzZ)r炖)#5d~=J E+\37h B1'TKx$Fa !DžXgDBj<$ $cI:Fmoˤ$:Ĉ4# /=O|+_̡#O rQ㇎0NqF4 EʱWVɴ$6१YoPA$Aj(J'.L14LhYjr4V 箆*qOL5d0 D!6tV8;[ !\@&ːi8&0pELf^ Τ*R,|`P{6pȐ̂L@X=T -PILeDe`.t88NdPCQSm2Hנ~gݣ_OԃZϨTͶ2{_~7~@Ryߌ1\x<6 PwL+B۽g^{ K1c?ޢeeriAP۲I}mod9yL(f2TP(FIݯ(h6\z fXEkm*KZ),//Eg>kkk\~}F -c$ f .]vlvqLZudyy8`P3T*%/fFQlgaa`%-̗'}pXX<1z!V?~~_,--qȂ;&e-) Z{^~ej1OZlZeˌz+ZV^שV\+[s C+ۉK>{b{ט9spvAHq=2j^ĎkPxiF4w׺dzadZ ɄRd;f##~gYl5W~k_au.^K-Nƀr"mRkd786| Id fh4"vRR41HkT1Y|W!RA {aą DsZrh\í-30)DḨק}u # s,Ng,=R3TU#M&5"۰0t7 [W$@VhC6t$Z Cyy hV+:p8J'!xk| Mב*'Ջyc3k8d^߹x=w/_~ ]ݾ}xȏHʯbkn~¶/[ >ۏ6hT$69-8UJWl+QrHpisMmM-+'ۅW\q6779vXqj( 4M 0077Gq$&E ɓ' ܼyq8tP1neV5ӧeZg 'f'IQzT(;nnnV`0^b<0,Rz=acc8P8wVYt:9L&EbWv~~M*${ΞWE./̉'HӔt:oL2`V5xq/V%8E_^y児rhۮ~~s'(@ `ތFJv-%t&P3fcOk,x 'J @\bzB`ߥ!hdcMUg#D$MH;1a̹_2{>x<[|K_wj8K0C .ˁV`8fxmqpH*N ?@&(zhRSRvZJҏ sM̸ j !D䊔B1*U)yWkq%fPmCP(YJ0-Ce H0zBuM13č 9葟g=qm *YlBkoQj. G6L hISh 91dRP$B  xuM{Bֽ2۷WY_Hsީվcz=c 2Xnmm=y䕯v߮ae|Y$I 4ᲇMMX+ٲrQٌ2&eef}Mj籹ɑ#Gyf0m^Y__aɲu:^(\__/<8@+|666 +q rUT`ee=+aHߧ^$I2Y!KKKm6ȱ)XXgEF1 ð}kkx)JB-ܕ^t{EpU>|}Ξ= KKKc/0cY2vv]8P>ynwwjWj-~uEER335~}YִM$xBJJM KGLEUJ O~_u@bCjBR[&[.i[XثdxrPi},8 o8$j]i~IED-TRK.jHoL!%+H 6-shaǁ6n*"Xm ae kUO՚.dxzq␎!vLbKJ{EyӀ@8:o& zނi2 I*f$(_;ƑC]\cw@Ԑ%#&C6'LBIzxQSpٽ c={T[F@;!X<2G?吏z}w]yjf~~~<%])$C"7&A zLFFhРɐS%p\EnaZF&xLhe|P'&w>4"7N4ZDxN@F B#bag0&O!Dv]#(T&Htp2Rbb=A }[@'pG_/eCMDSNI']$1:aiyWn[l/aan[XI,V47Dhcxa"K)q.@;Ny`A*#t|#Q>M 5 da\p(jY킿0&v` I ɄgFʉ5V1?w_GL@bF+:Kn ,4] IZY lv+J)'?@u?*ԕK/mŶ{D+W1*ErXr6ۻhBx=uw^}K_*~>O}3gμy.W<@lp]_{(Vջ7]2ﶾ{u߻;y0z;;y]˘YshgLr^pXarj8Ki2 3k[g)R~c˗/t>/^dqqup8,L_ٟms'wb%߯~|I F,y#N_xu2 7 LǤ"7^oI*hT\lLgn[.[ [ G#d&8~d =a3 MLAR9q: i] "MNgӱ<'JqUl*8RxEKpyĠ1DrVf0#R %5;qdqvZE\U!tBfb4fwdBP2??ϩ)ׯdBi @Q |gp|ڽ;ūe;MA=Wi)z/yU6~~|3?pMJYrrbk,#jrI- yȲeoXZ >!… 3I:f}}cǎsϱ6#a- =oT*:tx\(hZ>sƘW^)C \v7o}h(찹d2pk۬.\VUӥiٳgGj1Lt>lq11t]:2W\)^_^^l,RڳsP\k{3*<իz=.^ /0;?A>??v|u`0hpԩbOcfDo옔e{vy  dv {qhAܾ@XӺvޘ{:AJ+$sWnKTZjW qm9T% 2H! E/T[5~_?D-WD0` (V~) "SlmE ư.ϟǔX0n<_c4 SM H(0ddViTև]\UA*GjtHsyw}& Akh@ U )ͶބX1Iqtjo8J⪀"s5ylLL{ic2ߣ-bX{;̐@jcnclH$|{o5{3@ݫ$P7/{/ >|K.F 47Ft,& AxYjJV+BDž`0G񊋢z^e߸qo78fvM^/~0/yORp =s,//WWŘ:t9VWWyg}vkyQh쓝c{{2C˦~ŋ e\eqqj< P V$'JaN+yb>srd{e%-K)ݖL`_jp.R~ |_QTh4 04IL:_b *%&Fk(‘fB3Uw0\DBq'u_Zzbx<mTOztī.^=w-pe7ρwxQo>)/x9O%ۃ+Øx)LFݩcPࡇ vXY5clqFɅ xzX^>HMTT[ujf4 T#7d"K Zg #\i$V1q( \_gHIǡ*C$/0TM8ǁͯ';ɐ& jnfcGqTeʲNmcDH JchI6- ټAXUIi% ހ*|LLPw7pgAN\βlf9>_t8bV2vRٱEE/@Ru]z)>!@WPZse1lnn277W|U%׮]+@J)&_W*J@"-<{lam'~'y&O?4(,u)i{A e!sf0p% /=wɤXhb<>4M1|E)UWlinַz׭6l1NH vʪq[;nvnɜN9C<W4.oŔIwT˽k_|Zx'azxlOho~C Ō]Fm9t+6AM&Ǥ(pY_1)7@aWo~PJc&',@/3nlwy\\έ %! &!m'glmm88%LFFθK1qdFFn|M*3[idǐL)2-qQ WTk M CS0抗Il 㡃:qiV~a;Ɛ1Q=ٰe-_*)3rekݗ(f1ł]+9ȁޞaQ Cbn @L<^|#s nD\Cg0DxF2gOo%̵C*+,>/ Qŕc֖w1u*m gjTɔʯ"bg:M3\!ID s>O,|"ÑF(,K.ՐazZ~7OD\>&v{ ?OcL](J߿Szv.14Mww:woռvFwͿwݣr۪pjΝ=_w~r[;n =El[S6/'{r_N- *3n(ro/h/\7TnscKf{{Mq iJ٤V>tRJ0OЭLۥO˺; 2L y~:ZbL\{/ihofM'y衇 f~2Lì4l흄NZٴlm?9o^RK){w:|ȑ#^+2 2twld Sh%b)tZ]֭a=^xy|(JNBpjt3/\de++\tlETCORaAx}w*중eLqA j:F 39 t2"Ą]VH:R-q9Q )Y:UUVRRh4JB & қL-2Fz;![!8J+'q5R Dھ[m?v%){}SNI^~_gc?c?c?`Sc$Z .T+*< hDGҌqB/N`|:DpAbǿGc= D!1UH֐f4LcFV' E_Y'*6 n1cRR:Ȋ$) .d6ϲ@868H-˜Ng_ +lM\Cc4Iyjju F ¸`"ؼaVVd_ZkɄh1qF'""ͿPe BH[pPSAC4]^ )3;D40z!TD[. 1שM-)?ɟdmmm潯uy=Q.x]Ax; F㎂TѣoYj~ J+q^Z RR Mj`{UYIY–Y p-{6QRLe/|3aHEqƍjqVTyfQi{:q0VӖm0,X0 9BŊqLSfjJ8xd23gYZZ<쳜r)- 뺌F^+w0ؕWjQiM^Z$~i8rgϞŋ=j<Y1Ă[[.B/~j,..,.X𤔢VqU~qJRC :m-}Ԛ?ôZ\-p(-C@oR>(ŏl9E]f43(w)9!o|iHsU,cnۖ^V2R,? i-$LԐ&~d^fjA@V >VVV(.%v{/(0,cQ7:kmoe IG6i6!e#r8[fe{l2ͱ=XXL UVcVe0h 4 v~^'"ZVBVVV>믿*Ρ155Eߡiɓ={ui6EQ [WE7̏txTE/<+p{= `8vXp>ߎvӲfe: fffqܾvxeaq-* .,?Zd2;W@]vn)vFwڻoM^E1g7=e ʝqf,.} ԚT>0kj?)| R9?|?wҬWqyg ~||,]^jNkt09MPo3spDT`reu1e4q[YR lwp 8.~(uRΜ_ jfit`HbAq%>Z2i򖷼\A>>wy'<.LNNrwԎ),uzYvׂr魽6eǎԩS]buuqh5}I㠵Vl6 \QZc4jŜs*})U \݉bGivao;1=AD #wK޳hPJ? | 3\ LjXGu(DrerMuc:61Qm, .qv XX #ko{zW MJIV<0 iZ48v5 S,- 3!^lA~gGCb4@q*7zɉ9V.lҜNNR]!lK6F6;6OB( @^ % #Ѻm]ȥ6 g4icO<ų\>4dh9D+7"MoI5S.QH5E=_("opw^Gy%[v[_<2([_{Sy˼~ܶhrt}2&g ~^1>>ɥMHʢ(*&`I8++nq\Y̾fc Zmd4Mdmm( o4[VzN:z^S:CEq\C;>J):N;+jrr:8qDg 0y^:Yl62L NԬ/h0䅃UYr1 JbUzUz݄D;F)"sklF(Ae39rLO`b}u?pήmJ8ި'CzCv̸O2D%/ EhbBo}D `5LIn *Q3UkskĦRm%i$뒩!Lx3TNFPLט4KUv,}'Q6~jㅨ/T@w=WgWo%;ʋ&؋kX'1Zq!QN^-+24 l2]6 e n0en w2??h4H-˷FV#M/neeqA~8hu,--c}z^4??O,,,>sv+i}s+Lď9B^/@FDCpj0-x ࢝V@h >vff09/Wv]W1n }8^0?|E4b_eI_8 @,czz8 3rgh'ѲvY9!1)[r- qH)><H" GiH eKtJ{ f «cd2hܘ !ApEup6&d5z&q''0T 7zښpBdDU&Q~W0X32X0M&Yj#S.CwtZc0(JQzl.|g{qm6Vg6$?r1nO,=ǰ]w~6]*. Hd BBZ$5Tj:ZŒ ~]ٹTU H=%ƽX!g'#"7 I& V 0rZGGTGuBzB0Yq{n/2pX"ܬt٠WlʞmӖ7VlieIébCǏ3?3Z-R{Vc:{޾tD Τ:u8Ř:tk IDAT}ceuu$Ih4u]EU,RFiݶeE(qΗp4q$I ִs5װ\tiJE8E>d - J0ߖ-APxZc=w/G:{WQ;?ˌ]y8K?ڽ]Rm&! ;"ea,+ۜ]X&ހNoD4^5@ ʟe,$ Z\/<ֻC6I>nB!ISHzm-83=;H1Jcv5߿y\ "d#,v3i32\BKL p&;['qk@1T*6ZGu:"N&BRj( sWSUpehD(<%avDI J+q߫*FIſkZi&$Si:E;D `ϭaHQiHރyU^u6(**B{1y;wnn^^^J1Eg'$Mݶ/2L{[Ks&|G3]vyoFp=$T? P2_WyK5f0F+aTY:Qx>4C,g&?'s罷 u>HԨjTB: ]z $uuxaɐ0HdHGm25Bk=fWF|ĠZ 2!HBW0imo Ks$ۼ$#=zIN<@;϶Y&ԫ5yc8Wpbi5$FFڕA> _V c9l 1s+=y)#\0F7.㐠\dԤ;2)Jp5<ڸcTC%Qs\gyЛu" xJٳgw, }/U3q̙c xgϞ-Եu/911QT ؋xq",xRfʥefgY8]+'@!a?S>vʬ+e,ʯ V`Sۇ|3Y*K+a^Wbel6\eiiJRMOO_YY!IіC̮R9{,׿ujZA {ml) ̲l-NRa{{ed߁b^9l:w?-a?;jY9jΫ/O'D"zʫtx> 9+a˜V*[ H#`NLhr*G9v]qrP$ f]&C-F9HuO)Ѝ0wí:h$pMq\VbHb精33nhDKwCePiV[  Ԥ@a$FT& .Gྻg8ttg6yMaӧOtXZZbP;pq]p:$Z!KƕGV5*W,zNMh$%FdvNҀV4󤇑G c4dh4r)iJ(D;J%/A3&׾pr 9x1^qT;b/RF_W , }h[t1);[[nqEnin=[Vg{V B:VӧOm{wzg u mZavM,(XZZ*@N.:l6wxW$F,h.DNl9|ͅ\V+ymmmqa^WswlA=N;GC.ia*U mϤ-9=>|{޶?O:G?’Rr]岶2ZP[[3MP^h@\"\}pqmf&e1י?sD\uQH@7pbR9V#0=ȑBt(0LdT)&K \'O|'x-Cl;l hhyic |eZ^p.'_}3 븮`rFM19ȯ_EU:iH#TQ)FZqx^ ˆi@:ݍ!A4'4a qbH3( R8,FYt<Ǚӧ>;q߾WWXڦ1pfQ撥>nXA ^&J q$R  6&Z)q1Z0Bbb043 )]z -5,8GH@dH-qD h2#BeGڣUj 7.Ȳ\flTX=+lnn/&:"v{{ݨ,lnM-Vfzl"[V 6G$ vhz*ٿ, qX hyw}po|#?pᡷ`PL:GŃyssqX__/Xв=n_n qaY\\ѿfNS fffð`677m:t0a/vjZt *ZYe+cYrN< y^G$|+֚'NF$;3-pveO^g8Nʰz=14YPUsuEЕ'{qb=>997m9cg+9Mz9yɣ3Tx0xHBף UJ%hYAA߫2#BcQ`F(!!gN/1"E{.‘luYpd.|' }{T\/x?!GG?Q|~w9pO}Sz?m\xqtع{ <.= hbdZ0<׶ly1K~9>+y ,貉U~J,Vʂٶ2Fb,SV,3HaHxˆB+%ˬYGz?xQZh(-PJ84 9R07'NcV [h4 ;ѣJ P@k5 8~xq^vO?4]w7 4e0,Regff Cx Z.%oyEy$Nvͧ?i~~`P, XYڢLeu!r6 `{uFٲƘBjZyXV^|`Ҏݯt++pYm܊ZQ}͓@a"(!3c1wH.*{zG/i_ayu<&g݋YXМrvGi0 !.%}*B]5R#Yc~HSTP-*$e]Xa}<:8<'N<42rNRoVU4IR~呅b|dz|]aHtn0 [T(`Rӷ*c'|q^;#t/$0ɗE8$.~k^y3~ͭO299`Hvx&& 42Hx:C 1.ME,a(t RR'!%l5C%:3\quPRᡍ#YPM A-E8l!KEz(="N {+sj>>я>Zl6i=.Z~}/v{^|M-Uz6Z(;ۭeK/@>69w(]ɯUq[P]Ellll76ZNnjF#~a֊z ϲ4΂cǎsXpe"5SSSr-?뮻JB[v\,tVWܨܑ;W;eiɹrΣ`F C Bh@b"Y pO'8}4Dt;1CD0#ED2LsM^F9rFL>7P $#?Oߏ.r*]{ 8s{: +>:8AdVL$5G᩵sȭ802sVh~ 띭N:.YjʰAmm͹mmYel'|35`(WoGsg9ē\u 0:!Z 3JprpGdx < Nn$פxf&!xӏx$?@]k0 BЛ$H|\k+(0 [t/gzvc^+7x#O=Tq/Y<kƇ>!~~ۂ8~ߵr+!p\۸ {n/ؕo@LɬMB7vZ| *Zؒ:ۧgBT]6p\VWOSSSl6ԩSq=0991,WE婧~SO= 7P0GE-> CR B߂"^Grݧ-5jjjy'OÅg8w[oJ;G=jp[puVefffk3˂16)9_*/:{oRxeF(\Jي&z\P>#k.0;;Kj&#.JD:-+UZCWDIZqpp@II13<,!@8 '<1H,ltO׿'ۦVgp3='+VN=tc'5Y!!%\\//E iۈ15HqC:$Bjzzp%Ji*:eOo3Y[,8ݓ(=??dL&YdCX#UKz/ీ@dD ?#LQqĴu0`qcQF " z(|QYq@i< uLFdjzniiWA>3tl][{p_(cp9P/|Cgv[[2v{{{߳!cC\s0:w )!F HxDX|,ODD/o'*@:iM:aኅKk4 %=Hϵiw6Zd\;.RDB Cr7="R4 RLRBa-Ϳ@kUcGWfW=:?$Ӭ}nkH:*I #qTGX̤f| ʏǧ@Kde22BIbC92$W{_qA%44<6cu M{%/Oo vHi($\,x 4ӫ29 B QLFA:Bv"M\B h4Z48wx*RaHU"Ebm[ HG=(^:_%7~.`DSn&78F7Xm vv_b/RXyz8 nϖL ,cj)V!Ҿ^J-Y6y,3xe6ξo,-XVܴyw/KIE ڎ1=U333loo>r˂4MT*E57ܲ`}}"Eӧ rY`}}p_BŞkӡhq!2x9[A8.rIePˌT!1f,e@mմL좵9C۱_;e֢rRnr˲B^FQZ6;hKP]+ =;wfsE%hWw 1'WDt¡s?_njebF?̷M/PτĈ\f-A *KoY@E$W4NQGA@ %# t p 0H%7~7Yc7?[бy1$B ˑ=K6#TcHjbF!J -x~n1BÚ3(I02dg8b~jQ6"74F2 @?015f %,)C׹ti&JY\a(]Ûos ~P*qn+MgdL a9#⁣IMaBuMp$Cօ%TK ^a R|v)9^M￟__޵Huiq'N<~hQi~fsse v؋JBъ=l)Y9q-=K m_[&qB VӖI) )z[i[('Ip뭷rԩ ۂ8.ƩSJ=ZVŅ ~y6199IE9E9fْm>L. 4ess(t:E٦89{,sO-;&!jQVwX!ky^Q2Y.)hPe `[^Vȴ^sTl $ vu{yߌT۳iw:S\?;6fYsss(_ZV3Zg,B^N\Ӗ>W.t=0">2cDy.jȾ:s?+=$K'q/#%QcqϜ#1Cͺ4vfc >uaeЅp6d2YcG$u JIQ>"UHc\^hp%;oɯD?VzوjPa* 26Vαȹs􆻐SS?3N<V 4iEEO>dō-&7! Jcsu<|zBlwXR9:O7coei8h R:H!M%B7Og C?d/.,jVQ IDATlze&^orʘ߉r>ؕ+r?X|__|^|)A\Rurk=eeT&纱_Rs ^f[_?zԆN]F/U NpeAQ{|,f˶M@) G<;P(+AY"c0ٿ[rRi٫nIN>]XqxLA^[ا$ɻnwO~~QǼZe{^U ˨faZY7&eV w:.B0,JB,PY|cq8wEn&8Yㅧ۱c7Q+qk `t*QVVV kmm0 i6,,,y;\`aff¯Ç9{lb>|Fo|>Ge`߾}.fs _و۾gx%Iab|Ylm}AFZv]bڞyfgg ִ,STh4 eR\;?mGj'֓.Xgr#[Y1*%2p݉VԂ<i^ 12DÜ+Lʍ1d3%[Ҩ , (ErQ3&0Jg߷4 N<(G\+^ \!*Tr 'CQ#Pē#~r<?{bsmH^_Wyf4gmZk sx0}掐83eo=@8pO $2p Q!}:Ä:M0ЌyRDQT,!x>FBg@DJ5Z&W:.i;VpE{ۿ݋/&٥ȑ#E['?+c|ٳ\s5+v󖷼Ey؋>XŲA~erϗMmR]NhAq\$6.X۾nÇ L쟣шzT*AE^KKK[˓2W([T*byWmVrV^ k~حze~?cfgg5>FQT|مX{VkM$;XJ;^eV/y)"ٙܕ\>(17B1K]Ր ȍ&PȯiZ,&#Kdid1C>DaCP0Q0wm|Ʒ=MV 0- U Ԣ AxflDFBĵf21VgM5Wo<3vKC- ZOg4ηu> |p4!f4Aҫ0pa# <&kLaz6c3lmneA.oR|4!AA7fc+qaQ &J @i\.H}#UV1@^4J~3'jr]! KnO!2 e+a:#"e@ڧoq azңVKCU+g2f..,0(\I\{(/* xnn Թ S^֗x3u{n/*G͂ݒJYrݥn6qRXDye&6|Ϯ,o{l?\=q\i0,X4Mi6]wU,\ߘ79hMm0??_(6e@fk-'.Y+%CymR})eqiɛnjB`Me|ggy ;ςrssюnm{{ gˢ80 e[8CZ- Ð[nW?~Pdzz8 ղ^+ǞCٟѣ8p`\( jYվoi?ˌe/W\p8ܱHaE(Tbi64&MlY5tu}zA/:Y:ȸ`d Y&-@h:0c{[gH̀2IxdĽgpw2+dN '\4j&A'AFt 8K2((E Ajh%8#C&<ͯsje nQH_eH;Q'NuGڭ!-O1٬'y<&j tZ;f zgzj y!2c֨:>7k:U\?C|oFHFF#])/@":682$d4?1$dɀצaptĠ2MDރ2**g>J8zj'///|y`~...>'[r=۽؋؋F]rh 0dqhcVޛ[rwsryo6UPZ 0R7ؘ؀;hp#3wG0:lLn1ؖ E$CT^ow7s揼TWJUR0;z/|b#bQ VzZWHLC] Y٪%pH тTI. T_)t&$Cx$QJh BR$a߲;nI1QB(Ԍ8sxCuc)2T& 0r0/$U)JQik$(%( ÐlBwg^X?'i,4kD:+sԗ=s|ӜoG-wkfZ33#:o¡K ٱe;å FVPZGA#/L_y\ gem_:KT5Rfv4>,6IOճņ z|ISJYH`uv~{)ri311֚Y:v0^pI2뛺8Tq(yo'^wRJ A]8|cg㮻… t:~ sbH<\ץnHTb˖-4a}.δ͌@>iYz}3鈆! Z!DZpttZGbhvvLjklr(D@*M%6lƝ9 4 3h45pQ0fۆ  fcF#A(( IX">4AlVb&0ȇ"$ٱ(zRh|TQh8J"tӥ3n0Ayfs!mAԉ3 n&ƙ%%xT!n/xK)(R~>CZpN/4汃'Govo.s-ЉrR\9Bo}/m%MY_d-8|1#e N=Tܴa1#g,.ܐ4I:YKPY>fZCh%b A0&UL"e_E3/vQǏ޻i vPhTi#Q@*z<+ì'hH5r5@︌%82V`ӤCXH-E]2tqw |;7w7tx׻5zn(4b|ПW4A)&-\ƼquD 87 $_g9򪙅B^1 sF(ĽMoԩSv&sǎm{Qٗ qLRرc FcӦMtMlA,|;2Enx>,t: رc>ndž9G410k&5Ĥ60_=4kGͤ<4ZRZwQXTѺo=Pbǂ+ bnR%AV*ۭ/}G-5hR혤\䙧Hiq, N|6TF,& !4ZCdTঙ̹( DIe Y# ^/ALL( qdJ K>JFD*{p/_*Tyb <|>^qZgV9&Ku=ϼe7xETܪ5h$$ 鐔.==G UT98B:&7@85ΞBp}Hd^K.{U3ﵟWMhOO>|`qaaSO177wMɓnɲ7onh4ft]+a睦)v ضm>,ONN7~)<ϳ6@V FU!4رcSSSV<$D`&_3g뜷0(ϳHѰ" :jjt]Z\FP%ό&ȃSΙ9|yo|-^8k kJ\ܿB[`'kKs\|E>Gh"f Wng={u?)ܶ}_EKA\vA.fpWh$s\RQ6ٳ}{_7_~@GH 8E(G]] Zl)22ka8ܱ88O,f4CP@h@Jj=ŊbU31}ڵr1)2e~+mVV^$rzfpDCLT@'JN5'mIю{ʡ8ntcmR}j'{t62 ѡDtSI޺ݵ4! WU[~ヨ{[o}O|W/+xwz[o7ڰ{|ג56E>57'DQdlܛϒ$J% mf$I,{gks썍qiﳴdFGG[xIpN:ũSchhӧOm۶&HA!"2SSSLMMXT*ŢUgΝJ%8`8bG>N8 S)  ئaNNNLv]ofv0E??̣>C=c= +++V~\0 mjc94?V'NcǎkcIy5aHwi-0dƍ,//[[SPgp=cttQ63) :h"*&4j>9hW FP(6"0f$IxFU vܕcq^)zpf]T*Xrm]98 UP:fߝ1??G?W@d.% ~kt.J%- NP&I4X镩G ,UpQ"U̞mޝ3  eaK):1(ϲWQO 8F5tfܯf{U6$QG?A$ ]BKczaژ+$% )QA.{܋Ic7OC '(;5lƍ9(W~W^|mq۴ik:c jAX.T$MU d>[+y4}(Giwt]RȥR0z=f`?={jeA3 IDATZ E_,5nzzwXdQ%J*\Gt8ZcRvVosrxoF%fI"tT$$ c\n#REӮtA$L*(&]CMXJŊqF~ӣzqrA,E؉GC" uZ*8j4:c4SۤiKJ)$z]R6O\]j.T*|3 zu|#F~^>o]>я''6]wj71~:_6e.1z-v#nk{\˳5-/}eߦL)eٯ4Mmyn2k&^ V+^11~t}XEA`7@ԾSAtlz[1/r935޹s'kfyfz)^xZJ9+yfnٳgbtt7!{6nV.۷oСC6^2.\h|ЦA@Zl6Z244>9:ngt(,//ۺ¡!|Iǹ[ivmvYFFFx0:d;{,RV9v@ЀΝ/rLm+c:dSODZހkl6T*Z-a׮]v7"8f&-8/=iv=Z(Hp`<1dJ\>1K`xRP)|/"DB# I )"\#fjHvIE&5)j'pw\'%Ndg>W=WڇvZⳟ,gyݵXDOu~g~U;NbÍ#z[omv4k sZ_ TBP+].7fZÞZSw'qu؟uW$QN !I/ejj^Mh1ViCW:̀v^g||y<R4a`+A&2~q8؍WD :E jAPJS&P8ߥVo331r%K<ʄddƾJ$Z' W4]0K/f˘g)힝Ϋu]3)o  |3~Zk+pG_N3ʉWyZv]?99oo^ >OD5fvXsKt}|S8 W}FjYulحF1!UTJYEE3,F>iW^`?A.5VlI%IԲ[y[n޽{ٲe a>];diiwRaiiRDX0UPw~w,j$V)V8aaa{w p a򪘆5,'I|9w֒(ahEͶ{bq@uڎ30*0)M Os\4=Z/t#!5O.5/Cs>FJuNB %DWOwɚu$a7u ᗋ$Z?;K!Il9{ƾCH1>:© i4=P JCO- ^(T6fmd|\em4ЉK|K͂ Dtm)$ZOr_eN1vg3Ah\=w;DJ_&k&ueQzT'%kgiZh퐦4Np >I"SOO&/\n-g3kX={p!>OO|_=ΗjE~WuMYV_qzm]s0B!VVRnKӱ-Jy ,h0 xu]g>{әڮ0 i6l޼nwݜ:u~f'?O|ӟ4M(brr^|+_affJÇٵkUp4fׇ oxMc5ʕJ)y;iK0 9yyſ.Rɾ ( GGvʕvT*T*wpM7TGyP.)I2ZXX,Z877Mgy͛7333cL$֚ 2gTT*1<<<`@nmz>00b=&פgT.f/w3O^//baӤ5^N$%dܜ-Rf꟫ \ +_Fz.<~xPHB%(G J%=L5(͉癭A/T8z.h%O36>Im3?&v4CFit㊀+%uH,2^bM(-q\/e_2:02'Oi)}DucZK@]^,ЇֶfݴpJ~L~A` ˿|YUf֚={X{[gzAeL#E2^`5 b@^>b2UNɘfm5f$?g&۠F)Ez= Rs=y /| T*@u:Ο?7l!m6,..8'N`Æ 6RX󩧞bӦMٳ|=<={}066fj(&g,jժnP0'$ jl߾/| Z-$_}1}yUS#`_&iRTٻw/.\[X5P(W3u=56y_I x 2uO''߶iXLА}L]a~1ۊմXRfB'JSH"ͮO*Tj?_mXѤn RiMhG"pHVLq%] I,@1qS[=؏8JPJ\<Ke.%ҨwCV3)}P@j4\L);&$x.i+5# uܳ P0DH} Aq, jq!۳O?$(g $Fh72XVhC$BEp$ CQG!)e)%Z g)s'w_j0O^xH{Gh1SܸF5P=E*BG+A " R;!/QMN_̉ѹ핲LWZ5H[l]k0ekZСCoohu`z{ųP&Τ ,%u9yv$a~I3B1YCf駟f˖-,,,P.-3tVctt)%CCC>|vbtt|3']TVt:.\`Rp.j{9zI0::J>ݻmc СC{<կ~5ﳸhYo,Y^^u]߿=yLNNk`T򌫑B7wڗJ%|.jbbYktؽ{n1j^ςʖNVEvJ%0 4A;'^~|i&7aʏՓkkM3Jf=mcamjZ){vuE.k4$4NW/]qыuT\"R=b+*?[g*S\Au ^+Ի)iV_R Ylty .hVVVq8{,S 'Xj(\KTd¦!lDXittJ2X(5D|'/NU*ҹK/֤"R?leȁ᦭3tB;W\*! ).r/HuJRLfee^GZq0d߿gR.djj=gΜaff{/77֘{˖-۷SVYZZbiiLء)Z->-b%wΝ;ٳgǏjsh\=fX(XYYAJĄÌYuKfjf0)bqٌLV?L*WmTb3vL:f׳ r^Le7 D+RHWs[_&_\6CZ_:2xV\q|<:uyMǤ\1\B k]u׺/\L&5} &T3a؊g,{1ZWWo;nswm)_8oݺG}u .vm\axxx,(*J߿߮k ލϝa*JQFGG[@V044D(?~Ǐ055EXzZGyĂV]wũShZ{O쵀,qn6$Hy{eዋlٲŦq~`xxsα{nw4gݰO-fzk]~ ٓ]"qd1]HHW8>nG J϶7^8Z" 8Jܦ8 ||Y!җ˼ցZGY8q=j0(xMvz[omāI&NN2N 1HMq|Y#V8RD>dr+D 8A GfHv<}L UD{Z*:%љAvYSڥ!NHH(կgzDx0E+$:$E"BpGP =ORbqS_;2>Lq|X>$TF<{w*FE\d]O@]H(G̰bP5# :LBד 2ww/Yjyg?_պ.\Xsɓ'/i&6_+mnԌ}>ʘ6uҰRJ[d#d%n0U^xX լJǜ8qekow͖-[8}4y{,3Gy7r :in0ϟ)6mF۷[kmq{E;wRd.4dVVT* yP(wg|ӧO[qDZc=fӍ7;[7|3 e˖:68wZ͚jVA IDATDDco3&c-Ka pg 3FӬkƼ ̏|@O4̳Ic6ףT*144DP.mW7c,tf\9}w)H"WI ]) ũUnqZ!g_런]=\HΗm&biN[N)9ä* G(q/LvtfUTT_TeRNM9|z ВJ!%nLoaqN7:az$eRwբ(a$gfIbI|U@& z_B^#ZH a )6ޜ q Z!A*v@xh{eF*3lءLP*:  (WϯOثگ|;Wb+n~zmkmcOlw:[o5S eJBٴ\yE@S __[F(5= 0Nd֭|E8vozӛ,*4 R!w 8ggazzznU,e rǏnFgdd%N Ko1~pFÈ@Vog (9|b͛ umI13033c7>>n-LٳǪyJ)9r4M6gdÄaJ)%jGj~7R9s 9r sssVf۾[ᔕ|gbb^fI\feeeJYV4.?`(_qlA,{ i&55&ҀGuٰar٦}e*gq"J!MRR)(RPr,9βta_PnY h๤iB s+HY7H s1Pi4z>$QQ(9(jH3!i'DϽrNDur2}O;R_RHgaxyv}2*bk7A)y@(M7qy9?]h  ӄ8!P@\\D$e:D; K<@G%DL$>*T0w-dqqq=Yc9ׁz[o7lQ@JIݦ4Wow^dхBn3djowNcZf#Gp4>}jʷ->Sy {uQo###Z-{{eaau2U)EPV8N[Ö38|0O?4 &RDZrq v7mDEȟw:Ţ0"!\gK^Ɯ$Fň={Rd}ڸq#'OSSSYgLj5T}v ҌLXf˙ (y`g6s/:S7YVqknZk6uR+ 9ox @RJ h?BO#P )&8%:^ā) }3[$$(Zh)Z h7zQv€B,# (z t&SԪɑke4EJ)##eW1v<OǏ&SSS? |LLL0:::pNh_׳ ;(}[pq&&&@>P(paz!FFFgfF${= lZ0TU5T7Ƹ5Q (F2K^ ^Ϧqq֚e+c|ާ}+uY\\LRR40s6)V  sc2l뺔e*ju`4ыx&D8ti1p(}Պftd/P7 Mm`=Ʈ];9㤩ꢅ/EfU6~vR(.z y} +IS}AZ|˕x+sۿ{ YzhS jT1Z'1Y=`}̘BQm#.ԥ#/M5>>ƿC4LJKˋ On/ eNH6Z2KQEh *(\4$IH'"6<hy/(KT3ײ)SXv^bie^m׸5\1]~/v%)ݘ}/^*fՕP봼p-<f &J}oL0ka M=FQ4`E`}"RJΜ9c͞}YVVV۬K3i###;vnА-T>ussst]T￟#G055w_*oy[xg@g\L?>{e Z_ٰajժ~6i{@da̿###0X.pe%琦MH&^yu cL̤4vB*U4+++v\q̦M( t:K @Ϝ@V{L:v&5Lyfʞk3"6VZ!T)hFAE(ǽ)b#9N):A)P@)A^*HbJH zAIe"(HPэS̶C azj຤!:NMQ/j/m";$N/HI$$!ɐ:u'"AQQR+p<A ] $> (JDl-2TcT&2i8(dEãG*nI%tR 8H =BGxA87qDSB!.!!qqք{tCn@O$Y/_dR'X˰.3THT>Z&:d9褉NTC4*JEE?A+$r@F)Eѡfv(*٥u4ZݷFKr&h5װΤEAXP`X/ώͺ&΀|I}BC2KgyCf ͛?̮][YYqv{߿ .pIxR&? h뭷jύ$IS ooa rrWSgI (KLPg@OR9I@#bJ^U*3N|߷";\J}ߦͮ4cĀJ3zMie'XC3MZ)`0!Bs\6ǗO5 c3z_FÒ``ttbhjk1cZ_=%ؙv"Y2rWdSRJkaiROi .*NRF](:JhTZh|V%NF% u(xF8QJ1pQ̏0f*lz&.7~ART'*DG(iJ2B)\W>ZH嬿.zgt^s FהnP3 Ml@ղmySq^ї7 l1g6iImBVs.'N-oy Vqxx~ghhA;wRי^ě& bY6hcnV h4طooxxcصk56>vy&I {7|ex۶mcxx؂Q@\=m,Mf#Ϡ4t&nI \6q-IzR˻iJX KdN󕴌qT RTߗ <݄ .^h@( {<{uCBI:8ZK5*l3<1L,C.B& %(U/P12T<B) /pPH!J*>Ed' F.w`5ciS܎8zlc相}!ǯ0ebQF.xNQ#Dg?Ă|}r~(϶O ;$ JH1b.+eJ2"T!aKbiENJq=Bd=N޻uZrݸH DeűK)qdƉ=_7rUJDϖISaRaLIBm&Ժ2@A5}s5͑hM0:65AJ߰݁Ǖ2UJ@܍ՀI{}4j}x\*f9dVݧX|lŒrSҍSwa[S %cB?$I6[[I1I !5d ?˂_|| sр,Բ@NO|bɛi/^~j$ovc^MbP'y,A<:|W= |D2~Ng֚~_nR>t0-//swyK,*6 Μ9CZu,z=9u7y'>}fffX__w,VavMΫ8JWb C9،rԜq>Dw$LMM=tG緵vlOHk r^yYMm$& 񘙫݈ \13]cmcZNfmM"5`$"MYe>> Н B33[f<Ρ DF$BѡT{NgIVO?K/2_g/x!SLsa5,_0yGM1`e0D!͘MfPmLUo24ei5cyycZL-eRE{AlQU1*ȅS5 0Ũot ){3eh$Mi|Y7v1lll8h;z^kI]Tr5W1Љ˒)!Ebo8Y[[c0 ,--q}yLLLgϟgrr .#!gpHR^y|A/RDt&ᳳs= ΋N'B _v2_>|RɁʢaT:j:k4c%%R>/ֵ *9!yvIY W.dnn΍FiJ3(/V+8p`L,FRXwbbмN]4MݵRL>T*۷I7EI@Nd]V~|jُkk^#"*nLA&˪(ON8I:bl˫K-jY@{EIlx4U0%J,K3v3T<c  NcRzUYr3cs4~"*4#wPOAoJŴn8o;q`~? B+4/ِG30]Z|[OCdi·4`hXR٢H (RQf~*2`fLEDQ(QeIF1i<) ͤJcTTm!Oi<?lz3w>z׿ڼ>k|yzޛv!?t͌Vhp޿gmǛX}H$ȔV"66E` })ujgqq1KP)J9aG6q48qu9|0?8v gySN/]@>9r.]RםH$" ࡇJ%'MߧV}yI_І)@BW,)p^@V YTƊ~0EO,QKƕRίO9ۦRNnu V$I&K!(s+I+ȶ8w~85~a!?8?8RIz)ǸeX[[sLZuJ16>,Zk=vMݦsra ?`ϰ i2==?g̱rJ'[\v@I^i2 \_Jb/¢(rl[ e|DF^},ȢdeN'>RNΣryw@kM\v~uY%-W"K\^|űVpR mC`I1:#%!Jc=t7<;Ҙ4;CevYR4 I8g06&!ی|bzAcjrpv;G .-5 IDAT|[Xl(ZEۥ$}I$hQSv)aЛ1W*Bcj?Ͱi7*zr]oZ|eV^oך ך1v{mݦ&^lZ t%]yRO$gϞٳN]seez'N#M\faa* z=QJ/;Qyyyo~;v41>8vM,+++\|٩ʶ8+ؙ+!ES%cX NuCwc4I8{:t E =qwHZ"O`iyffye+-eϪC3/|aï{LtvsP,G+@}V8-|.$YRPhBjM੫gedFS RBo76{^k5e~~XnT \!IgPy} hwkKYC$@TUb-ԩS4 'xijZE~/\|zȱ%~qNLLʊy1N1m&fC1 8!ժJƒ><$U^S ZzȚNLur㌒9.SFQ4V%c&`݀Gc0$ R4(鱒vYӄa莩9,Looo͝'b=F@}іRV^Z^5EƬxycZ,(4(/LxZ;˲0Ӥic/'eXQhy>Wd41xXkH2l\fҀ(]ioN^ }XsrZN̓ﳴRtL z1Va@bx$`S>TB^aAl೴_r5!P0׃,a0J.ܷ;]Ą5l}J&X<?$VBEgjdXWCJ#^^xω{?;O+dGLC>4IϐW'WEA2RF-Stږίsye }m~WV!TM5-k l e1A:EN%i K4%[4DVJ=CUH{,{pk{^{5IY$2'-n1X`3Mbl M8p~|鰲ĉ'xǥKt'Nk_$O`Їަv ~OeΫ/2*j9=ɓ.};OOw䤫OOk1;;)0گ^c0+`PXfq5EaR(r"]a𤖭򊬓QFUZNi-q'bQYSRY;qrrI}*NgrAp9@]cۭMRR4 sC"حjhz3NXLNW:+d^&윿iywʙ 25ξѣG}~g}OQ/8ZNg1WRUFBK+RCwV7 k[ۻϤ4C0% Q $;(Y\_?7XN*AL7BRxU?w] VAuΝ}W^z^F^~k뛜9sJ?Uvݣ4 3yjh_mLSEW\de:{}BsPUv&&BS<$5fMOWt#}3T|#Mn%ymΎ-zC{p;UF,,65=`^k{m5-X`hΐ\U1%SKX1`[&udEo1a%mT~7Fd>/q,Νr<{? &-2<& ,eW׆yG|ֺ /^fuE@? G6#ﻺx:dusĂ_*eμvNKJH}=>$~ xb}MmIX`c ]F#E<:рInʦx@ʭ:M~IN3ϩC4'`,OAKX<*?3oj,6u;m,]X}5fߒ+Iӈ4ڐe1blO)!/i*Iݛ'L^½lGq zo< ?O7_z+AݩSsxI䛥~y㊕*V˼*qпic~aXHW* /icy]]4b2iW،w.{KV Ði:DsgΜq9s 'XZZҥKNRğ,I:/alDرc,--qi*Zͭ5M=J?wqcGqǭvb'K $~q'l0_)A(OV*CȅQDrI>IK ^Sy/`RGu/~ ˘ܿOK QQ*Ĩƒ%xMǿ?>JPaHRakkrQc `<8*4[q&3CRQ0ų6VB\49N$IJ^Z!(i#?;֠tx|9:i3ڤ6/!Q!IHVkɀ`O]wwW΂J2QJ.rcŗcuyRSSc CRMXaG-o0ňSxloo/87lT*NaR&aIs{+ `+֒ 􈽅0wR/&cR*yqjDSb}*n ʥVO怰n]}MNN~β)#NNN:T XLAHsB/XG>d:!b)&MjKPAH <<$=,҄V9zxeXdxU.rA?_ 3v.jkjD  Of"J$y睬|geeɅ t^qNO+ppv wˡChZc?MMM9Uɢ_H*_EӳL_nKfrrrI_n;8vxnu~t2Eoٳgo[N-̢1 g l6pj57Z(X 0\/ETޯjJ_j5& [W\(5u;-YjLD6ye hc>#?qB <-ZAxx$KKvgyYW 0 Yߴp<> plreyT!I4d H$˓V.W_3?Ow>LN1c{{? .b"FA\0 l@W*ԩSq MnK'"&''9uc 7b~~SN/|WߧRO}̱7$NEb3}$IpO>?Xkv5_ᐍ Ƙ)+;/_rU,"ijfffhLSsmmuch4Wy !}&b-@Z܆XHP$E@Vܮs*ٙ\4zjZVGb2mI*%:VRͳEpDvf#3hk\a~PkJaERT*% miz?WtLĦOQ,DQLE(3`WxOpYa)ty/У")ՠN{Sn5V(|thye^~i~E|=\X\FS]P5dxCHOy,} v/uY/EP]N@*g5s,?lBϷ_Wo@{ BZAiCfTJq|~江y6 Blnn(Y/_׷ R<{9f֐Yd?I?!1 Ư%3b0 ,#V]K}~Ïry._Z(|?$4-I)|anr gcFHXV' k2 cynQ1R*R3_4s=T[p.A ^x^."T*`TF)C/*k<42kXhW$!S0LT[Z^E }x6Gz`G0\eADBJJFFJgТʪGkv'VFnZ?ڣEŽ;{myyy{3fΫm`w]u_K{q6׳.gݷwQ/]ky}Z}5Q1Ę{?Bțuٶj$A@^l:vYTܩ>R lnn>Dn355EBkMZ临D`ss'O:huu9={Gy/_]:̙3T;3}j$nJ//;z4osvK)ERnfnnΝSRKV"V,t:ؤ߅9I g*2sssNDj,4 Ǧ VLslNh`ECq%Ο?<ȑ#LMM)|,ȱm0!EbZxB_"#WL8߮χ7Jw3̪}Ϝf`5I&<ÄzF3d =&eʹ % |aDr3d16] IDAT켓i[07v^;[ iC.Iiu^[螾9H\QmU5h2=5A?MJ%LAQg(3-[*(yD|arx NK6zCEkaG]j1==M6$9ko=|.j5 ,ryen4(^͸wN3QRXë LD2[#9yb TtN۳JAoW@.]ej1iWu{Y36ي*.=CxS$DOo۷]5nk{mn4+EibP1x mגntb*H\%%TӧC}~~g299p8tSSS?s裏277VUIӔO}SPɲ5~/}$ w}LMMqY?:R{{TTRT*- 8HNHVř#j'})aRVT*:,333lnn:0Ykz;* 6;8 pmmm{zzMfgg9{,KyLr (J?S_EX/*0MSW۷7Ŕ"^l,h4edƚc Z)|_y i9+6R.%wjj*X,sa2~)vd1ztqV=s%T2p8Ѭ~=ŵr0hQ`9GijaE4}#M'SCphybY>O>DVNB6T\j) <Edss ܵak/xj 3Nr~g~{;@8Q"њ (S jc|S!Z 6k2dֲK7‚.O< ^־>OԨT̝я~/}K7;{Uv…[U@nyy4`auH+LA$(b- Ԙ$ַ\_xrc=K/s=Ǿ}tc0t`LjxWw@<&''c??gaajw~;33þ}xWrfff_>񏻾5a9?U5XI3"*EO5aoI+&b-.5MSWoXVr )`N/crhV^zi S)l6o{FvM\xѭĊ򹹹X.9/aՄՔ<~qVիryA+ZnǶFG@db4zMD IrUV$+5 [[[u]<}$%FK//7،d8KB&f&RVRE7\Pvju:A.,B^<褶pcc\a;2H|B"2RxڢLD%@!0Yc_h @ӨtcN)ŕ|WX2m 'fJX3v@+Kǧ\OR vk~g0 T<O+op O5~ٿMEzkp-V/Swe-l:!Fvϫ ^kI)z}qav쬍oS'NǩO;v~ډWm*?ٟX[[SҥK,//x衇 (b^|#GyyWHӔSN9`桇l~89v@wVΰ[P@@AV0cɄeMJU۩XdYlh]LfVƭYd0&''9p/^Mdc#.\wGKzbqIvcTJquo8'~"TST)v@"+kg2:˜2bt ޕQf,0˲+_YzRge, 'C$$:fg@d2S DGמ]rCIR$%V0#8\׷ЁŽ̒{yZ}ZۭM/>AF)Zu05vMP!Pehؽq+$kuٜ[| zZ}VWnS+(aJ'b@l J@J`8RTFѣG$2r|y}K_rgNY)wGСC>}= hZqʊKl6<3uN<ԔT*Op<#|S@& Uߧ%ӣ(rl>> 0[ƠfȽ,Ee3 C*R }ά\鸺/ cxG^Wh4.f~~{j5b*}s H>X.ۖHmُ|.EM9L@+;cu9sGeXSus^LMѱrL#lD0,{ _(4@!CÄ!F{Bjɬd \ShmFl[.]Hx)}DBHu(7B$(2/?P?tSYҐt:Nɢ!)0LSiD T}TVqL\bhbNZi4y*e.T< azNpHhdu7@ LϔiZ*>{yqqkGҏcl#5*0gS, XS) @) 1FF0bn][6tc:GS(hE襄i2hԩZUPFVJs]AxnwԽ]of^2yӏC^-MꞄ@_guu[ڊAXVLIX"brj 1j?8?8A8RPk-N h6133<.\… DQ4fcǎM\fii% s=| 癟gjj~j`vv~c}tMXz=ZKq$(QL+wIEsgY6V/'i;kWL9I E"'iGm^ρpRWw~YqLI͔> c\ryl1DCƵY.ao n=oFӲ4cRL3WEэh(4yc[7oPΈﮊ9j\3y3nk< 9 ~cҊ@H !d1ߊ*֚8yY]]ua9rW:}vɲ%jNZcssu]'O̙3KlŠ0R ̜z?ɆEde~~er>>5?^A0V{SWU0ءZM ZGe :jobĵlvRg'  SvW.#|K^kj&>_+|Jc31۠19W^E%/raȹsZS+hc-M-R@ا<#ujiqž,֜^{s cKZؔvˠ Y߶$"vX$I:_?C-N w՛Uz@VJjҤM{جE: a1 4e5 ` ̀b~~ Uuο]1w#9v?}KWcmjnױvJedVEtR8f(^.Rˤ W/o۬>۷o/^$cǖ䌟ckK2j#á(v*i *iǪI\.)"W/~t)&+Og~Q)yML⳯0;d~jJMn})EQPkhM. bl46X%} Q{VC)P`c, XH37 Y_ZNō\\(Fp= &5svw;1mD$% e?3uM0 F[ϼ/-qq8nby,0 cҴM(ʺB)iܦVV~J8Dؤj$$P5(Jg>~w~߹ fyeW}9.=ʪM Z@T I mن16v'txm!p=c" b&6C&nh7 ’ڗګ2r{]qyY*TUqŋR.:yD_CXXXX\_N_]NZ3+_ܜnovuZ^;'q'keb\][ϰÕI[/&U3333 UVWWz}yG]CE'4ɮ}R2uP񸵹W8.7s'ݫXKٞR'|szغC{-cl*2vaaCJiSAٴ&e;Y#?ȃV֭[ym?h48q^{-[wIq|駩jt]b1fy:O=o{xGґewG7[RT{}{f׮]= W册w:*N$Ih66OoӡlZ&zv0 08q~XY*Lmfgg-[NNNh4l2YQ8vvi3όLq Ddnn-[DZ3gX-'R7===̘" s#F\1,R<}Okmas FE7bf<A8\@’ (T l'&j3У0.S Rg jH#μr <6 +>gd0$QBTi&zF.~z YcrLz]gR rxV+"KM{yS'9:g@fRj'ǐQy|ᰱJdt\U*aO M9q1?飥ss\ jj7O5~ZCSG S~~䢯UY[; pwu.nwرcڶ{6uku~95.F,ȷ˕:=6ͶٮR bJ1 ,s"5(.:- ["Va~dNV*^^|E7h"0duu;E{9k"j,Sl6g||'&&,wy'wy'Ayt:v>33C׳_drr(;n[)c502j^GCkmY7cN5wQIP,ܹs߿裏r!˄-,,pYN<ɓ'yx9t~8|0ѣ۬A Å#GX0$J;D+`]r%Or0Pزe #J2"t ȶ7OKƪq(E8c=KװC'7FA J@~)MUְilW b ퟵH\7D/ M^ 8|&& ^YfJȵG<: 4g5MXĬ$) KQJGQ>0xYA+]I{aB:3~i,fb;p=2&3cU^O C Dw> {E*K%UL5j7kT" 49&O(Tʌw\3 /"G<_6,-pN$t֭&(NYTk8{=77g JTBZeqq"v Q133ٳgmm{ K̩>u02cf\ɻQZR͝@~+]`YFǩu]{/VPAgu:I3=s~S:+wtñunPQ } frAPtVVNQ}v[$pI|2Oy|[)2VN!`r*o5/<;|㐴Pcw S j#wݐ/IP9ne ԇxѐ*CG!59ʙ x9+b)U4NʑótWʨg?{pwRPT)L>J&}oXVdŕe.u»{|}ݱ+C( ^hulyn_*#tffWw`wYM)flWINA`Ho4iJV9Z&ZɆHْ$a߾}:uǏInɓv,}+e:8FNömzPXSNq=ĵ^KKKvOrmqAyj2޽}c&N9]1/q#!YU];s.2Ir)Q>/e||(VÄ2xlZfp۶mahc:=9n vKܱ#r8G P\ȽQK})Mkze>5h/ 0977ji>9W֡vkOVE(S+0&0МB!,r<"2> |)r0=;RZhCz^~qBf2=IH/_^0ƋK9$@ FD%;64 (0ʚ`X⌄2?2a(\* zZ Y;|{S{ P UI: (Q +iJ0cZ{؅Ng D(45h~re`|s 'hZ֝QdN×%þ}l(^aDb)L~fiii<ϭy$IbM@IV*8)sWJqQtz%PGٴ$I׿n-l.߄aȹsܭKk6#.k,Zc&=v'nv <11v"풽(_[̿#Nq ;j(+)њ!(٬jx~`eZkB7+ߒ+ZV `☱]d *ǎ-j{}K{E_Zz' !5׷.ͳBp-vV+/4 3OE_A>x=WM(ӪcK]sYa5+L2Ht|d1/<`V~iLNQ 5 Oh1Zp؋Tjo!@taB+7Eg>ßɟlN . t:vp-?cWO}S|#Y;w@]qկ~__@{mͶ6fl4Ok|ߣD Zi+( $iApY)/r4[lЋ]͌1ne/=74jK9f c!{93R+*\!ƎON?jx&Z< >' J ƅHӫ^*^ {c^u<=ٳ.$}kk[J&Bq 6fJMwf(RGQYlҹi /PÐ7,//v6I077G$Z 7`mqq]vY`pI;iv-oy(rq>ǭo~3+++<|_nȏy 9??o%~u]ݮ&D7M^jH$ ){D($IFzN۵&"P^zv|8qqyވSԯ\>qʔ֠3(3)A'kpj &/3q鿍mŖ<# WnG4N2fj?E)17EI ZR}.J)O{*+Pn/ֹRS^{yC,,?.]COWtt_?Oyۜd wȑ;R?yoͿ𺎾>OO|ׅܼW/%~r.6{|9N9+/4wd^sm~h84y$؉{b֎ߝ NH :[;m6yks]y6K\}YzKKK\wu$Ibs/++eqqZȉ z|["2[($I&F"x6͗699ɉ'úeVVVsss˲pqfͭQT,*BtkVVVFlnj67 @ٯ|V\I z@,l؈Iٴ~.MƤkZ,fXv['t'. [׷-.[dh W@u= vEcP#Q )MНHאeEZKEro?4{uъ(ȧߏ)F>qaECjjHf4+qӊʋ Y1:D);wn𙳘bc-fQ {JT= 4EE9&Mv"A'`r*(G)zOtCܩӛj%P>OJKpBnFJ=ЮH}ռO^s,+'9?{ם<8|+Ud߾7Tkru(t:|#ٰjoP{Ͷ6Wnx;iwD֝ 95bXN,O>mݶZOX nɓ'ٷo dzz^4vuŔ'jH9)nV7ϵw0cҧ^vm?:BP;vmcm:W&QJ)*HmL| pY {C"ѣLNN$G[n,,,t]6jÇn-kv5א)]w?ö-c&&&(U昘^n__N&ɾS8l=7O$X}Ϟ=v2. \$IQr\)5"3'`5s4#MCm~f0Ai5E>H.xW󶞉1dlW(>qqlÃ,< 9EZ@+1J~ˆswpx~AC)s BQEBՇ$ztYr M)6d QZ1d;UNw7'e+Թ w\̌o-E{~yUWa6flj3%R4S5QqFQDbx L@r23 Q\$*#9: /f`4IfxyZ֓Re4ӭ5EZLLC@mCEDTP/eG?X4QaЉ~O*;ǔbH~)c4^f.Bx425 cϖ[b{5a\7s&`Lktd+7(h~ş~+Kk `"h{9UN*0j+=X\])9Uj]@ah?@l+DJ(rxãRf^ƙYJ I3vTas?mӓ^@Ԯȋvv!kI+]lܿoؿw1lVտzݍM`6Uj.3!6ojXDZ‰$n`Zik8qlC*uY8>}2xxh޲v8TU*?0< 6Q{n+Ӝu|zcǎo~[nŞ<\7 e}Kl0: syxG7I,..2;;,O⨙9~~O$3K9ny.l{e"o~533362RXihVr:??oF E*Saa%rAƏ.>c C'ۖ,u|"۔<뗦)QQF wn={!w*Aa0,,3i (Q# ʺ4| FQj`?<8D{/qj>c[aqp(Ͳ TJ|?zjMfǨS%{~rYQrR(tz=⼠@[3/34h|<]fͧV+xSvS;Ï&ϟ7_xzƮm-¬-7N21ضuv Y  {N09(`Y!|7{L-[߸HxED]'x ҌV*sD2 q/ۯ{%[gìˮ?͉fh馛F~߽{/O}jײmJ17fʫCH9 Fjt] ܺ'wr ȐU"Cl=`HZVY|WE5QIJ-WVVj IDATu"@ݵߗk'RG^R'râ(( {Y FSJrA,WLY Ð`@Ery^Z vʄzȍQƮ{Z y\W*PIF]JpF1*s<㟿$s%LU6Z+TV{y)z}MB>u~vX3!ύ  5XYNr-[1#tNAE`T&dPGEQޫᣠ]kf,_]o.4%}{ɷ)Z1^o Aj/ů2>>_I: fkkܴ~?MQo~90!1)JO3 cҼ@a, J9xx3tl@0ae"]] !7p__aoe3$/w?yF&\l\-cY+_}{帮渺<.} 饦MF 6x~K죻dCk/ez6zmxQfRlx&Ja}XduyyvmA]RnMz&6%L;wSO8=޽Zȝ;w2??o-%ٳ޽믿:<߿vyΒ$ 333-//[3vmC ؐ4aF~ K[&*O:Edjjj@7[+RJLǖp)wnwcA.H2/IZZ͂cPEQPVgR+€u: [ǵv^75sǧ\[(h4]SRqk="9z=<ۘ(0EQfMBs_Μ) g.ۜJ}<( 5 ϯG$5?*[έd2G'(;DA7S4Bt[&}Rrϻon[`J}ye,Ǎ@AQDD@MC-v jUBx;ݏ|/|?[[6fŐ@ <M|oQ5=lbV^ix^8Ķcb|iR2tX٧|vwu3/>IXe\J}(.3s5pAC=dY {9 Oݶf2>>,r=fiCy\`*qnn6MFBfFQݽ~ҷZkzH@k1b|"KB cj:"sjָD"/uTJ`. "S@J=%n:n߾}$}ak|wD'\caaon轔: 7Ki]}5?CZ[/ wRϹ+ Yj)."m[xG' c CLbH)Ŷmۨ./0 6WO[7_au 2~7~(ddf5IFne*FԢ jXi*ZctP(<B2p+JUi!T`)0W( Z-M P䃴tDFN|1Da̻p][)0YɊ#˨ S[sT+Urz 8 F :f/W4fh{zߧ?inŸvm]&67@Y}05lk'2amn(e>j0X Y+] Ð~ϖ-[LŢZر^>}۷c> yyܹ5sssLMMq5%;w/}KVB7A`e$17\d4[&,ז-[u)cAh6B0m.ײOqy U༄T]&yNѰ!,+t:u0^, + \լQ"l?R.u# ^W`ygs^RHƽH)NdlYǏ3gΰ<*Az(04Tɳm)qUATJT".'B{T*3 kʴݕP߇dŗ&QB@jyL_,9{* 01 DԊeMV1(Nlx:$=)cA95/ 3 p.f/[&fȊ EZ)|E~y+GSs9șգjT(SQԩT[Y"F\nq0 Ƚ5U6.9hoUEyIS3A Հ,+1ae%9Tbhݬ@:;E>!1{'Ї~"ԯ6v14W|,..2>>@FM~_lՌW>}u16flW5Dq'e2Aꪵ]DD-v ~??cv+VJ\yL…I9u=|k_VvygVp}^me[V|SNH$H{s{}a@vQJe˖!m 4Skm>¬fll̂;wRT*M7DRѣ=zk<\m >'Vx HJZ-ir90SٯDgH^eʳ,czzZfsݬ;w1Dݍ7`0瞳VOf}2/izuY3#͇n,r_}\AZ&]_"^mKv}2ՄJs.j?fz|)0ʐ 9EI-V.>TM=P_W0Ph` D)s#mSun~FZ4= 7֜&UI  \hKsFJ1 `iA'+o 1=۷cKl< [Vz)8faas111 s!nnF;箻b֭v&&&b=sZ|P&V;wβRZn+cj6N5u;FLL\}T O_]Za- 鰲bb٧cVlZfPhĐ<ޚŔ fW&+EG?Q kD>*7rE(x z_rS5#IPm ?SF>%5jh_e{)U]^{J+*` d1iQX>›n ?)YRXVZ9,lMR)4|=9Α3˅ < LDP^bߐe~dX%t_Ve`ʉ\$+r~[ȇA{n=ʻAҦ lUM +F;EGvIɎ!d  UH!Uug W\ T2Q/CM+yUPU=Lҥ] ]^& z)Qr@c :'U)/yW14.?^²r_2=b^[KPm)DeK|K'KL~zΛf /W,ny6?ʊzf~eeVe 0{>۹9x ccc([ho|۷s)~ڵGy]vG?Q>O_$!"ǭԗq7  l%pͷLd|;v('ry>b2mɌL`]fK%Y"QT*zLWU?f_2'NSp c$KPV81fkrr'Nh6֥PXJartr\Ѝٖ+ӕf 92vG,8A0dpZ /HP Ea`fX!k&ǡRt)fD>_/_#1Ur(!ԭmKYA%P&O}mYQ̈Mx!Ɇur?f1`qBH <x Ba Wn7$[7>>NN8{Wb-2jN]{Y^k4|%JɞHgeb:yke"gXyݭC?A<ȟzL}jHӔnHI{MSj%EV+PEQ$ ,v| 0vCĥ_]@,1`y5![^^'ge~~j5[(q 6B958]T̎))rRw79~/0aMU+uC N)E&ZK/5# (+ \*JC4 $NAL0i)w5_JL)r)(sYΝ[i7,ZyW!T z֢_Edaa)Bgcj:OAT ~Dk9zﱁVT&1i򂜂Ph t. Z,+Gfȶ~ 3=uzLkl+Zv U;'񆛹;tNh5 Qh(#V^NjIܨMP.׸qunmn8fuu9zf&~E(jEXݻ9o%;n~#̙3t]7?P ÐIz;v]w7M,crr-ӓZ7pQ1gΜG?<wugۧyͲsa:ҭY)n`RDRҍYbÛ[/2vnZ0/;r-ovpq>OqQ^ab$abbvGY ZP`V%o%x]>+`cGBaL%5F,`N^KǏ~* z~wx(2놵e=o O(S"E^?O9I~yj)xrH1Mg{}Q<5Ye]*x)/d ;$Qu]h S?{o#y_ {oݥn-]KM$%jeI%Gg</ p8A`Al#1$(dɖlYI)nd]W%V+cZn{~s";8bznnZ yHdR,!N#J//[[x/nv7Fz/z~2WjuEJ(XZ[a)@RE}qrkHQÌ F N0n0}C@#tagbg37ô-yYLI\Hݤ ^yn q‹W:yXng~R06yCϫ+;WTD}Ƚ9`ji (P/^ yvy7J205Pr̢(BZyسgϐ$4u C:h8 ?C¬m!ydd>b||\J)3 ~i,//c߾}<Ν"sw]-JNСCO~0* HFC䵱vQ.q!HCN\A v-$X,e~$I${#V_$o;VT 3==6KdbP1 IDAT^f)\ԱMZ=G/7e*{\.RUy,% Bs|ccǎ0b;Dp0~GqAm8X[_ŭ[:/lۆ뺛8amrL&|#xdc3Ϝ^l&!քn؅mZ4XM348Ba{);wa_![R tA\vԃc dSPͿ"ӟO7?=D^ Ŋx#cW:xWW{^ib&l6 ufeQ]ױ.ew\d\a;C´o_W8ϑ|=cx׻ޅ|2yP*pQLMMx;)'OP(u]9R2ɹ%?Fb 1!\#1( b G&<͇7q ڕ I>fY֥^e!횰(#fl Ih"ě~(4!ci$Q^5Ķ)tuQtF`Fp*:bl=:݁ii/J٩j?yK@p@`P7S}_9wQD. 1 $%Y&~}ApJS*U8"h=l=$سXlE; 6mIH 6c۝AƱpf_EJ! 5R\z"k c̥1a`Nua)̣b#iЂa@7!rcK#r3=rQF 8I+FqzͰ\}+ u(%*a/*)j!^ w71}%jͫ76_E]6٪&p9w:]W8FK-nnq?Wr.UG+9o^MrmΕPj#10J.0rՐjB/^(WB Sy6bch1+"J ࠅ>VC<}LOL# ]뺘e4=t/^aT\; )!{͇`YW-[ZCPs:ae B $IKz6lD I ,A僓`3FI+T9j@304}3K\Vݟ_ӅPH* N^O|=տyU[8E\ׅ<P(`jj `.05!@J|ǣ>G}_=2ACXD.CP@P8۶y岔OhH࣏>|> $뺸P(h4t0==i8^xhZ>*4{޽{|FL/ك/}KЇ>$=4tf⍋f{J|P( /fyyt:m[.VWWQ.Q.z,Fe}Q ԲMF0J.Mx/8}gĀiعs'>OY Lq8dxe߇oO-v-C+"7:68s,.\ *aLÁ])t}Ӆ>zU왚tI=w  0ŵ, ._  XtCu#xy໏^ XaEك\.~~.uqcllLQŎ&< ~o3xvi[}(d"J^#L*(}9nkrTH@&Ō7PW6n Ž]1RH@*e0/qTBrٶ3gȓz gϞŲ_FrVD!y#o.=J`}}]Hɍ7ވX^^*݋g}Vδ_XXfy0PI|GǡCr LC=;vԩSm[z J}:O~/4P,Ip`Y9NOOSpZADJj4V$bjUMUSho6n, tK T@57z3A}񳁟{K154ְfN#*jyސ)\P!RU"-"}nWԅ7һwF߇8hZb>!~%؄3TQܼQQ%AR`}}]JU2ȲY`r^f)zd_?r?7,V eHJ(V ~@s޴= J 1-iV V<67U$INQo405d2?vhvh{s;p2 " ~c!J2pNOgǏ;~bs_)N4:6jU#[qc޽ky~,QC@>Gݿ2Yzr` dc ܔq< "lcrz 7K7XGx/[c)88|emԵM~0͌FK_-JOHAWr MU-jqs_ׯe|.nۑzu&:R9۶mKT:4Zn;TF<̒$e۷c-0D>GRǏ^ t1TU\xRoy=ؿ8q%U8zTs߾}8z+c… BN|+_~9.{j`/ϣV$h40>>.c@,$9C&#Zȿ ˽OKul$ԈPSjFl۶ Pcbbab}}LFHN߇m}_rkVϙsM}*h*&cx P\lZC<Pe5 C3v'DR `-!?bE.d_1C?6KjnK/`B2z.CsS=߻>?('wa5'$q]NvwAv՝ϫAngw]]%\sͫ}[?NCC'`Z VkK]&, JU)A.LR8<~ERfQ(/"(yVKD.&zo.ac~8u,*Ν;`5MLLLu]* 4tZ"8<6a4^CCCCC]V44L7@Q#~PŠږdĈ&%LgYdw} 9bjNEՙ00Vz6HwilllСCk`6VVVs(DbO'Ja׮]x"J> 曱 ClÙ3gdĐS5eY؀md2:­RFp<Հs*@a-;0}bT]:~E 2<_8T)Y^;֙~=g`T}v)T=vo0TYDZbu߬CihhΈX.zr11nz؛n n.ZYo>/f0 CFNtZjVհ $Rn'yZѿػw/$AP@*2٬\>}x;o>\pIԲ,d2? e2)m[+3Xi5\# mϟT.dqܷj&bH_.7"!}r4QQ޽QDRH|=ϟعs琙j䢖HX=qV{<كY)T]^OwY\\‚7;#%49d2BP{[544444,ХWYz^'e\Ђ{TeFuFT\Ⱥ5<^T*k$ ŋ@a(fSl8.DP(BW̶rVp]~wabbz˸7.9Wf24d1{ccCԐn $ќ^OoXTmI>yչF*DQuueY(JCcccOPITJb.ͷZ-LMMabbBPoSEt{DHĽpQeƛ ZиJ Q`rAXBwn7MVkd4Āw]YW*Qͪ*,׾5|C0p{}cjx衇WWWEyt]V T-qcffF΋ب8Pyvj~*$<&0PHFYS'Wύn{T8 u~G &Y۶133\.t:=}u~aN#;}g١15ѠF%ة/E!\.`1-~>1-~V_O;~}ٯ1+x -m.a(b*1#XQR{~3Ֆ=JaeeE|>/jtd' IDAT0胣 NqAl߾weMlvvp))=fXT4.{N8BiQAT(  rJGc=g"Y.=CVm۰, \NɪJZVSj6{x>9u6_OnsTY%@/~=xy1 ^5}TM-$WϛPUX*hϕjpDZ"TZyU>P-Udihhl qSK.(\$j&x,kLpN("Q|߇X^^L۶nB$Iz ~;`ii # C1Ÿw\NHHkŏ~#ض'NJ\ÇSO{^;Ejm&qtӴ,KK=VVVpz=r-rlOg2vmXZZm]ݻ~ٳBO$ڋ/Z&:11<(?THCy "V,,aR%p$RhɣJbH UV̄$JTxlUr~_%M͡}_zQFA0:z j9)[zj(*9=ahZQ8Hh4SCCCCMmV44XFFb}}sss> }AC_Z%M#, nQc<gEx\RXV,VT*<&I"aT'''OuC%,?SNs~q]zocǎpy1pǬ:XDC5XɒS0:{BհDSQFU5iRUmԒN۶%M5Q)=Vխu]Q ϋrs嶜j1:)GL>;Ut:X^^Py'UL57:3x^Feh "Q5444444Ԝ..^ K#Ȃ \] n߶m)53 517j']1 Yq%Jr9h4099)P`n&,--T*%?!u]>E#< ʾY"ZTpAj5?~\Ɗ%۶mCP8g# P8F*ɡ I WT"l5?csQ>|c)mpGSIU2H^$jI:X:q:GYr$n2ڡnc}}}:YK# ^c^t: u}Ū=ؽ,^/˹eClI3~ j}RVeǰ^_+ܭ:~]2ZvدŞ%.IRŷ8Ȍ* # Q.9J%YTs=ڿs4 7uE9H \Ҹg@@:FXDR4m݆Ç{‚eLOO… ;NSO=4dbӒ <#k͂ e& C!*y{cݖ~77 ɐECU9z*I!q!u} />Tys1F;vvT6}4vK)]͕#icoj3jpBŹsFDE!wBnihhl FB^-U{|9\̫aت88;RənvFchqp̙3R~:1̭jRGn=˲kkk(J׿@ǩSsss(8rpwb޽QO ZfYr9vfHӰ, H(1T*LLLȘd[j,AՊPMF8X9F<UGUSɕ>1mJ%,}uaZ&^La^@^ Jٔa,j4M1yT*n;cƂhhhhhhhbe hJEuAOZݫU1s|ΝBzرch6HgjS]S c[[V:tHߏU<3x{ߋz.'9!q;wC???Aؘl>ƐyC%TJBSnQ133#cnWT5,Q%R$V$'&Q#_-|9m999R4^ Ðrߣ,qyAAEt]!jdtR# @ryVBfרFkhhhhhhb%0M]j5 fгeYbDu6$+j06MRZ211!le2QC;&NӒaV(x_ض-ɉ8cp r97ވ{b~~^z~x;//(J@nTKd=J^f\.'}tZ MyZdڥZj'9ڣ*^m˘pGS˙J%8pIŋ i䊄J-GKIz:&&&`Y\VzN3Tbk-t'2 :8F6ER *LUY,S5cy8q/ǎ;p 4M#<dBF0=hت1z4Q!yvfSOyfT*%pqSd% KNAWCCCCCcUP蘹U+~Wqyƿ^ͱt68Ϸk[& U5%iRIšT*accCΨQ!G]YjDHRٶ=Fծ!a}}R Jɛ4Q!Nŋ|=73hrQiy .H`Ɓ@ [Tk%'N*=B .>qcuujvUJwH%N"㰏N-嶣Yw܎cƜ;2XZ,[ZZB&mۘ?)xxajǙM-S HyZFIkRrT滍fe'j l}hZt:CaV ff I4}rl6}_nw^.ͦ[h r9z=d9NR{444444xUPh8(* l7si^ٽ`eeEzHЩ|^O EnqqQ&mF8Ο?x".cffӸ;%}رwƉ'*QΝC:2jJj8y'T>EQCaaa۶m2P/FsG*j.܎S sSIG{)i>2*TF{y^Ν;qQfxt;5@\y[V4efe`qqQJy [B u]彡 $9Ɩ:wt3dU .iAAۓ`=s[PmjU \rMc3\oll ɠ#auuU%}ǃ>__ɓ'q8e?jPtfEJVL&# BqtNqYannNW)U*nOBfQ1ȍ$1$$jضy?T'Ouj(?\:WEGR88j}JR 7 :rAדjX]]<* Ðr`CW۶eWsx޼kkkC!ihhl ,;rC6$v"9a܀v .ع=-=>>. z.vTŢO{(_V*04m6" Cq p7bqq]w9ؾ};Ν;r/ٶuTڶ-jjyKH9~_Ç.P,EE#9(72%<LD*ec*n,r^}}j^Jp@J*U'xj)c H$qf)ęDecCWc"pu4 K;OIXkT+^*60R/4444444xŘE6*c$0Pa$ %J= pPᣊn q055nd)LOOU!K,-MRڵ ǎCxq 7`rr{rf|n\{ v# CJ%!{fqa⩧00^޽{Q,V'ci.q;U]UU"bjJ&0$#J!yuq,@&v#Ӆo.,/ƛq NbDq a*7l&ihh,.W hhшON#VuZVjS f Ki_:^0 111^'J4MZ-QY5TDZQdd>GZu]`fYmLNNceeEnac޽]i=oT{K=R>fgg166&; 5U3ް$R5GQgy^jT*S%*rQJVn.g_=qjzT87U۶%΢tO5 VKLt9f .gcccfRVy9ẮԜ; 竆&v[\ i)WT8ƪm;j)JTA4NXjRX 6fgg.*Ɖ 0 czzSSS`gIh^G:Fӧ7l\.4M_4I`/ 0 le!}qK$i6x'97ȹ9d2)v2S%IEDZ({IR:NΧܞZ%Dߗ{ŒQkuN'yiJnfrLHz4`4Z<ڵKr 0đ#GΞ=;UHRRr,NNnjU\cΙ~n ۶{ćihhl nWzǑxBUTWo穦̖GC.l[(uk\tsA㩋 v[T=..цao}+~)ssshۨVOB9iرCB-6xQ DQu!DZm6cũc8z56jU-Tj(9l~*GbJ7LLn4N^uJ%t]Z-qf%Y.ѡ,EȂ~>fU4 ?9rkfq{fe2<TUHLM444444xUT*0MlVT"pIRzv\.~?d$QE\pA!8qacc.\^ `+s0 ,7pn<BB h^o.l߾mT*ɵ1/رch4dD e"U5<ĉ* \H >U C:fթďի<6K3UL>.wihhlnWJIF,I48u]%j8jzѲ*G$( bĬ<`iiIHЮZjk0(3,طoΜ9#( G?z(2pѣhۢ---o}gΜB!s^gٳgaPMOOĄ28PʩoTgdpF%KTT$KݷQs*m,TOyN$ܗ:W *ym߾iG{dr(sqqQnfStl䤜GFG:F>m2+`y!LjUpS)&v[mX[[4jAb R$}\Njf[R/޸ZM[HD>< zEonn/"Ulvv|ihZ VWW%n#I\pAģ>)u]xKVVV犥$" OR(J&R{FMCRIcQaSM:֍'+%M5O=* U3sIJrU}3SF5iIӸ94hlرcB6 4Mض^T*5T;ɑ0,w}}]JYJ\.r3۶e[*|jƛ :GCCCCCCCCCCC ih\%$IL&Vj*JjF BbTJ훣 5vȒ(28!a̱Khn~7wq|=طo:e{8}!LMMannwpm y~*+JIDATWrrb(st Uu}Zw܉gybBK{PytZZ>Ѳ<0q,USRJլ cS#h㏒ RݶI JՒF-5sy ǤP(jI9Z~ϲM DF'<y2j(v:$FC+j/ ',%<=NG666`ECCCCCC; WEh.zP\A&{w4155%n\RP%=]4ET\mL$V^fs1cYHgY7j@R*_Q 0Mo|3g`mm r VWW6Yt:-$J,fp*() xj9Fw~FU9o0_؅qGMsH-  `r@8NL&#sps4T(1nZmH};wD^Z-1iCSo; ::#F)NGIY3Ëeq\xB-* Ν;Et:IR.J y9y$&&&$lbbΝ<0 e\sarr333>,}Y,,,0 8pabbB{ZML1>~Jf$I066&F &' CUTƜ@;F5\UTEjh =D*)SH8T5UU`9l;PRP?KIűP[h4đ!*ƱTj8^$tZK}pRr.ecccjCP2Femmt:; 7bq1JԜ:պ f=u\S90=$ D?V,7dFrjhhhhhhb%z=Y|/cbI.L;.ŪJdyO eϛ޽{ψġnu]elllby׮]뮻cavvȶ"!N[p뭷^Esvb(}sm6?~+++8x Μ9۷>(BVxTk0M%T@rI1Ic$7J ROS)SyHGsixCC3j90 >>lT9Jpu!㵱44>:'I"XʌG:HnY|H`ynzj*N\bq(\CCCCCBO.54^O;U=?1͗n򞆆O>kM^11~7NoI3p, kuXJe Wٹ<[__?=1u]M;ۻ ;wo&a $Hf$EQDжRKkX[[;VfGgvڑZǖ)ZNb+i鍱蚀MH\m!\,s޳~>36{;ys/{~4iiERt<:痿TL7UNC=UjZ\d(ɮQ6v5;`W *k{#YyNsUs8Sy1 5N:fS+F v:iܧUdרw2k4ސX{I[@ZvnHkTNjs{0V +v um/&HX)k]RRi1U_ 59 uIΊhn3@8khδxLUNb+3UK{V`|՘+5uGݢDYωXh\u4F][KTGqcI|h]16c%FBשRp7:k zMmh L(|4ʊh7N?ջ}4FjtywNU2]T8F_';+vv@+cݳSXsHhM0VfkD'yنW-|4 Mv/FJ |Sђw5Pw4P`WVo'H2kYbv0sg]wpx;Qja+]Wm $՘55⍊f{Y@b@@@tTCLXIy/X;f)]1P#0V3tLs+v`,+jdjư7}vtޝg\pA* HSO{c38xE*VSv&)6΁,\؟?_}Y:4w/;wq mzxS*}sŝiu_sFIo7TkW蛹g=yE5kko*.D}w'ɟ&I~ߕ{ vq} D3:6OϙeW5O]uU.~{;4tݝ͛3uk\3֯ȪUy?SuСHj3ϼ,Νkfͯd:'o<Mv۵9q [*[eeIǒ?c}6W27]pSv}>'rվrv8[3'J% zEo;zHjZlhv_w]۲gӦL$I{}}Yqc:=7_\/EcJ3];$]/8ؑkMYٵ+پ=9thzҥe%^b&IW׾\qş70,;JU;K`?e"=?f+8ۖ`{jIju|GtiP,{{+L;mhiI߿?E{}T4OH2bE&&'Iw}z궷zedÏ+%Xŋ릛R7/GFGe̺ux{ېG3<<,^ܗOeɒ9tPϤ(R<{`j \sM]]ۗglcݙy墋ʫS9xLL\{kO:;ST244O=OW]uTCi)s+fwIۓ::1j@sܙ .<6/w^rPK6SJsϭg|-;v|-= ~ERc^ݫu;٪/?O}ۏ|:\2CS<`;;+jxf_`s+fY{Ԙs5Ċ``` 0Kiƀptovm~IENDB`swayimg-3.1/.github/viewer.png000066400000000000000000011354511465610152200164200ustar00rootroot00000000000000PNG  IHDRvX.|tgAMA asRGB, cHRMz&u0`:pQ< IDATxwՕ9n{;B HB``cc{iyg֚3ocm`c3fpcrB!I bչ:GnHVޮu뜽gﳷsp8ph/qWp8p8NAmn+]p88qp8p8>N(ԟp8|e}?yL/uɽ߿'vwS L>j]lyckn't$[vv7~=Y:{hN꟯?%aHsB 5nC̘8Zٖh}bA0r_67GsaWϴ1ot郃a Xbْ3t^z6/tS8҅}EkO8nqp8Sտs'f~=vEVm`]h.nşIvnhhj18h.Xږ__G`|;fp8Q{'Ѽ0Aҹ9\H@x/YQ>ﴌqp8xqqb7rij=KO𛥻 C&\M K7J ~cxO96c 2fYLGxk}nmnA 2i)w?1h ~x>SQ{xgx셷G>̆}NHw#ae31a\ǥR@F1~s/LƟ5SNch}-;xkՋ<0AV1r|Ν>@˞lUEtz^9ؕwLyw8n8c*S_*>W\3}a~rŵrFmu}+yR>o3 >+)a˛Ҳl8j4O4*yFV.Y²M>؅#Y!F>sgeX]uѴm+/m}>csnl:a'Ĕ)}I}:K]fSLHФڑL v>&GIOOϤvҸk/fL>s ێCr8ǘ )6eI>12ƲAt5_6ˤOgis߽s%mݤ'Q͘3g1gܿq<~F6ZˤAOoxqkM [c 2%L*CchmYD.R+i&lԏ;ӧ9# e sټNP}lF}6>ygh:>TCgsȽ w??X8J 9LJu5sg`],.ĭu ^ ;zvl)K;2/_0S1ݭʹ5F09\|e>;~d-}Q͕_NRNW3K1f'C.ͪ%~&f)lgv A-]Ιg͜_ƿ.ꯝuڬ:q.b]wswod!_j|?Ƌ<_AtORdq3ٳڳt50ͳ.#g.'˺-*(sGSEoV&Cf{-|qf;wYyn=z!_xͿ~3m(.+xjI޳1|S6{mtĀ?o2j[Ck4P0|l]O~[ۺ{_SyϦ{Z>VF-u 6]~/^p%7}.i ?G'k៹-}7tw aį_yK3`hoG7~DZп XaUw-كwf3>7#K_G#8 U~_X+I*ssևb̳o7ph}l<'q>\ǹ{1؅Y=1XͽWtn,:毹-v_׭_P?la?]} ҆dfuxp-g \oF3|i\Ţ5ĩb+1p.W=8^vُsq2la = mcj}]Wv~"ʻYJZp&Ͼ#RsqocuqΘ:g)5Q+ >㧌!c$gh|r@ iDtj^\Ci܇j03gD4~Ə8&e׏ 0/iva-5Kag'J,"2tEg}'F9ig Ѹmx:LTէ8\?Bli{qc;526Sx /3MrW:Zᱥ"ERb8HDΤmz[K>nK)=$Z:׎ik ksD5>i.?ES [K̞>Yq w|RHlnPڗXա3}*}ި23/V8n8c*v43}X X{sqm'm9.Q uoc*smÞC̣F6o~]=.q5In'fˇuST@>w{prp|]F-Z?z_.I3Q!v=5ł[yR||. _w{86bU(!iHGSP\ϲm̟?y!31q68n8c窓Dg}y'm>w'[鈴۶vvvG7[؟c YqԸ+5(ulo[?IYG:v">-MVh=E˞t7WnO5?^hYQ;Rjfӆ2caȨAo>_O !뗯syF O/jA9s vC,i|{ ;w&Sǎ)Ù4=lh{Y/o6(mi;ҹ)rNQV.W#|7|+r|ﮤx\󍿜Oi yk aW ;6M-(rØWyd[&>{ܱ=mM/q)<탧s ˦߯d9~q8c檓ILG`a4a,նG\i&P+@qÅ#gbخ_>OC1x.1T6?,"w},Y,]wX,y`׸mwwLԻ݋t'vc̉wnfMɊ-% Rz 7ruCx/%Ocz l9[惍ǍqL:(Yid5r*~:f\Wl5 ";_z-r43ǵoCc:?X8dsşug"}LT KЧhEoP#gr`}3g* -[؟؅جx7Pç2A,`.qńCcGlsu:q>G0 uq%̖̽ 9d_>u|}v^0,[?~>E%뚡1^ks wG\a'p5quPl+-Ϲ+/٥Z!4-O~?|WN`˴F#5F<鐫M*UiӸC-%5Cظb{89 F HGKԱW7ylG4AZVk'>w,k98᩟Iײp\1YT&nvLOpˣ_# s%2_7ގ;š/57_3s/1^澟sų9ol +oOLk/~= v7~DZMG:W$e~0 `fU e'Yv@qpc%nμ*=0f/-]*o~ ^9TZ)Ѳy~xp\[?nc'̇}n:sjNӕj҆6:=G.l+˪OilDs\_?^vU>5""v f:BW\ħ{+6o *ƞ'CU<*.A!:e*G]u@FtVxCRm-=?njgQ'-mt]_3UPQ7`2=j_S:gtZ<u;6D \QG?R;CY)vZ<N@"MUV"j:3MR.냂jMW;d\h~d#AS(yƓp8n81̮zMJQV8jOsk=v?$ڬj:aN1/p̻W4-,ŝ<$ŎmYȭy]tF.~3qy5Un$#]VrXPJUJqhcC&=Cy.Qǩ#@Y^ I,cX۸J.}>7Nh{Z=G,LgK3%px^!Z(J#D"Byh)%ZK|Ck(c QFeJ2qKQF!`0`Lt8d|?8N=v' J(;)c(:G1LU)%B*PC "$B=L :kYD+I*F)iYZ($"ʥ0(˔J!a,( 1㨌(.!qls8GN=v' CX>LOOȠxJA!5B)^EiP(+MV)l1o$2'z\E!D"|_JxZ JI'hJd.:)!eBb'®Jg1WDQ]>$K#w8G,N9>xH{t%G)>B$"N :sB^'dwl =!DEyR ZINJRTqLh"eLw$"gc6Ƙ(ڥB\)Eep8 ;q *@Vt Hi<$RjKLU"P ;zw<>QJi'SH)zO*XB"UDE`IIN2Zt\x=Ѽ$ѻ(,eb<:p8 ;><)}Ht"|/J%_R&)*)^W`S\_N"̒ț^:u5CO`me˶B|HwI f%C}u*]ˠ*fڊD}JB! DqD b0VǺқNG:qE0VD!̪榊06͈C+ 0Aҹ9\HLGtp`oC4B*L#D ʖ=QHU|%QP*y"⒦`2 j">R24z^n99J>΄upN>)SJJtۥfSLHФڑL];FBdž~'ƧgRo;iܵP3`\&Mςo{mf?,Ymo|KH4ӥ8b$' ǟdiHFsW>UMlJwOys{-fGO1JIL `1F`¢"DVA)9#)S,E iپߦHB 6J"/ *i֍96>ygh:`xpYdW)0hd:Rw6SSx{g_|%.=BnZɬk`uweqJ+ 2HBh$uҏS 0L޷]] 5Q?fg; /=K7e2572D{Xa1PkM [s;) 3r3:LKQP3nϘW6G^PE>zn>_ ?K-/xs78EEv8=K~ϫ='^><2>rr~v:u㥝E,#.z f7U"ňnђ\Gk՜qWrWᒶG/k4ޢ%͕_^Ňq*㚉ܻiYN/̂c;Xbu|ݯMl =z!_xͿ~3m(.+xj薱NSGA GJdPLT*]^LUl9 Cavv T(\J0R UԦ dDlPF$&m(KxJ`(wJl!3f_D/[I.8NR.rRtT2/OVJ-tMO%LI.[EuqQwBrll2%D"BxZy4U5B];Q*`l cU gHpRs/V(JDLE9Ttc8؎xa5L:x(1)f^X82Ә(v0x9L||Yӆl0a Y"@*PG$b'%R HSJBcAngsMńةc3} CֽM[ B͠ ;Ć/ԍGʓJ.KϜ̀KObkyE6p0:_hkW".UrUŘg`EDZ{@nu|;6&0=,l9fþ[ʣww-KjjN{rO߉ jcceg1 ^ſO'vmч^M1n\;(9#,6W*jyZMwj,'eS?~̝I#&j;=UIM*Pkx8 A+|_V)_{ x( J .XIDhq[P CR9X*S.GJBDR,)#Jd iOY A@Xc6a#NJWAȤ=2iL#Z(JKWHG*m%Z+wXE*@O R5~5Jn9]uڋkR#9)~GP 匙yռTFB`(n:;VȲ͔ %:!^d鶐lmy?&z l`PChPO]]-5kjsd3dӤSd3r>A#HgtrHdV@PM<E6* UY2_YJYvQZ<*_`tTdTd |Tj`}Vj4W}: ~s}>P4,Ed@Ey,uq;V??;ײ{pUÈ"EUJ뷲:XdRS}ř~b;_cZ42Yts]48;kQ[G쟎6⭥~7''N{?:DV"t]R'`W"t)Guu\!vqHT7硵DDy80/X@*I*@ ,qZ$%EȗH%0I$ت˭8NE Qس(L"z*b&Ȼi]@ؤUBA'J%NW+4GP] :-H C l6VUFII,eO(@bHEv1pG׽̞5KiPbج"J nt59JeDlrZi?_Ku LaV !${ Sh/%ZheGAdaJJ[,Bئ@AHDY4 YD B2RPTJQ*7P_ۍGSuħtP#.:D]l'mgދZؼ;)+*@>'D~89Q<*^ïtOhRf9? hj: qW7RHأoCljR* "@t%2ic=,=>"P[[C}]55y,\<5U>iAi] 1tuP$BRy:I,2i".E"b ľrB`Eɘ86c*.J T`J+DEߌ uwŦ=~IAaX{(L PLU?(#( ^1XbE$))B`m)gWj¨7ùc5L;kw'[鈴۶vvv^v'j2OseUT>3=G=`˴H;ΘM,_0M1-]K?AO%GELe]R2>.4A.O]m 5TWg"ːͦIR"6!m%=&EK%-HeҢ'&-]z[RRUR,!*OT"d""ljPM{cL%JT&r$/);&HƋ}^7\RAMʂҪO/)Yn+S%XEl%JkLStUHH`=QJEgLJ5>ndOLi y*v=qgXjxuL4`[&_S ϑ,VjsIGJ R#*I$Bl6M&Ud~JIIZ{ٺt:~u@.i3z65c Pd$Q"J!R!:PxyJe5!&5Tarx̦ eÐQH?ObhZz'>ΝԱ#8mp&MO? op8aw'\gGoW-vr|ﮤꔺ3mWCNH!e%ST^A#,y6Ouu594O*}\#%AD%)+Ja1(8, ZyxJ:Qf1lQT\6IsފҬb"L\LC%m UoOt.9EyIlK @Sh-4x(KR@'NVuQ|˔IJheA& ,I@c4i#OE!R&{ރ Q(+pSgC*6Oa &)34jV(/c"3-Z):d 逴H)";)$NJ51@lt#QDT6Mn0F[ؒDFm7E'[u-+&iCJSS@GtD$MG*EDX"*ŕB+QM]ɶ|~M|rcL%} ܲT@MF {gg]?vARmxl>/Lnΐ}l!r9ͬ|0UGr;]b I1x.1T6?,\D4Rf2vR"V0 Dꪨ>"h":^"ꄨ0(D (+R.Dl$53,1J4$2p$)BR*ı! b)ahK~u=U0MlL.b&}a)$Ox(Bʡ:QީP:q|42iӢ8de4-rQlk}2NWʤW;CҤF6~ͭX6)܂QX|7#VMk@xE۶3oLމk|]$KULA.@h 2|T'i2"[9w#T֕w陣`%cb{QEPB .Ϣ3d_4i%2+@9([dQ2elNuqr}:I~FVEƇq_ ;ңJLͼ@e⤡UvcVj5KwչLͦK3fsy`Ky5d>2+oCgJSO Y11Uh6OMu O: |M&#I*ިIJ2\Rh!MU(dJŚ2D e_Q}"K'Q8 ˆrVtR2 r3q"8N\d{AD|J)A@. CCg1q+Ih~ R (E9c-rr^3IuMJjL |P@NWEQjCRO#$0KTVX8nɣEzL6MU& | M;i5a5#Ddj2;yXUA<>]YkO-El| W|Z2knzToEqgLW_S/_AϾ]*C\`]+i]:dm~6]ʍo_ZdACPk7:Q#/Y$J&p%iU(B}QK^T*e JdR__E}]45)LFIK@%T%AJ"e"쬍1VP.UȑT$ZCJb$=QctM!-4Ou>:Cd<% 3!RDEd7/\OUq! Nt\}-rEiо$hbC7g/"IĠ*=L"B(ڤ2O˘0+Q' ĔB,I8A{T dVLPaQS]OUСzJ9UYOy)vØH{eZvUZ^yO1^ݝo)/ 4 >j(-!le;ggs( Y̲QjwLOT1,Uljs;ˀ+pesmxLUUAQuq}vs~h(& &9f5hZQ`:en$<)BShkœ|Oj@TXDb{/ܧ9|iGa , [['mZIٺ](bwG\Ez$GeɢcS7rLSb j\~sIl5/?鉕~ IDAT3ʫ>xj8"}#.ǝ<=}ϥ CjʵM6tk􃇩X+={%kNWkSj)_x];FK-ӭ-_z&,eӗzB8n2e}^;gmEY&TՄSc+vڈCYVt] xO%w|AFrTȂ#S,LkրsR~^T6",eo,z`sȁc\8ЖKghibޱ0_&<lc C]KH B;=ELAMqQ!0v㲚̭ҥB:\B l(  뚢,(GeYJvShBPgQWRʩhqbl̝ ߦR$=>yB tŎ&4,Z<]L95bK*s8kԕ)-NQA<4h|HJoMO&Hl#~ m/,!t2 Lgid:ML6ZXd$..Ce/LHPL>,θ/!m‹7й7OCJ "}CSrA>{liN[c u] ;*R2AYh-& f5iHcg/]}KWuE*F :U9礤ܹZz2[&G]+0hchd(Ksnz@մf]u󹀹]l Evv:|X,!NhI)uZġwkmiێ٬;#~ `0ޠmM#`x>OdRd6h@]$(6j1U,bLX[VX|0tY}V(2K嗪e 庤_#[La4卡Db@tR&O^ )tA $93b<{anEj>-tNFyAaFur>.H,asBja7p`5Yլ;#m&;KG*1,njFk e5 4.PzYaG),]M#s`Jd>^}x{ iXrK2c^eM =hkbfQIY:\Jhmry 5>YsъEISLY!>=KgRX:X>b2QM ,vd)F[IPҕmWNTpCLZ⃧ mh|G;|c4N;umܦ>#M_ݙ,zzz|d4 %!5;3̡n12Ry?EuA\o`,Aد36<(e.Y7xdaduY1Ԇbе ]+T }f7&\vKf -MhhY7go4(ȍ#jWŊTT0eUB`IK๓ç$, 8RҔҔ25M66ǮlK ]+ Fh;ao`N{s 63(ElAr`cui;O[-^A1Emh璸լfVon:(U5  H(3֩RL(\Y:uSXk߁Mك& } j.Ģ18燠{,]b1-ϩ'F]':{bE -pܑ @8T"H9ODefr}I@rgEd0u_dhQ}QCM1'cRXOR,r̴4aP@7t{%&A4ef(G@L2M#DjV8< +DzzOr T/t ]h\mѕFՙ3i)$ *\xbE3>H\~n3 bhY7cL1j;/z@'1bZ^ ֐ʲO9X*[aڙybc$da Սj-J)::W$K 3hV<mzf=g>]I3W$SƠBX)f5guk(=B!=urB+ڑtEїB ŋu}ut//)5E_ϲ2> Ύ.[=r=`}IT:aL" Ȝ@"#Zg 3am HSm0,iϷ"I!^KjIfږ˚y~F֐V|rypZ|y\K(-d˾iZ( 2s]} cB$ل ^U$FrqfT Ĵ#_C`uQuŒ cJHEH.K$= M$c/@*!%D; I  -Mh6Sv=v]b:}Ė@X$=̥Rp>UQQڒV Ha \ۇXc,Ф(=kjq ReTւHHU((Q`r8e*3Lgn:\j"=7xW)\qU\gOrvX)Ua'qGy^.)J #^C\t5Фj@vΎqP SQWH-**@HT(P9$b<4\0vNޞ9 [KUfyFBLeʠ]/sE) /E3ZU2̤OT* QJ  UJiάS5ĤS@e])BkSw&,Rv޿_1(ѲevDc<ε) OJSfb.VHjV5Z1|>;{x饗^Jl\J/"Rʈ*NAm1b>o a!x(!)mlYikfg_h)]) E7q@.!ϧ)%A]dx>t1aٜvTr#WSg(($h+:%9S 8ќ'/%u ITAi-Z˾io&;DT޵ mLf5PQ*7/u4]l~'_ȣ7̾ͤk_9b7!~s{ch=(.<J!c2:;:)\aP1rU@ϊu^պ.7-~H쥄]h2kY,|> ׬}(;)DRޱ9 ER"J9SM:u48P R!$b#`/uh} .[{)i qxkRneq(,cFxv l_ +J>4f1m'To1\j0z15u!bTV6yӞ |߃>/ܗvك; щ"lQ+DR' J$HAL4"}G7|bGDftEʼnVh1kX%EBSzO])"|<* fj%)))]SZk:I@`!ʠ.iIEQKߞg,ӹ,N՛oh+jV-|տϽq;Ș y+_铼_o?^LrS+P ק^֙Iڥ*Q&v+QzTT1clL*l/pHt]\a1{E/,k_)0}Għ3xk3!ḁ Fb I0EO5(5T4B@cȮb`_a< ,I=JgHEK';93 S@]~tʕtZg^^bӴ%WW?,Z/z cMGJ!D:%̭좉Ĵ+jFY$Jm@8E4֩2$c\g8e:ohnsi7(BD39bKU_zA2c$DV$`j:iU%KQ1Ap^8}M۵t#,- q8aS+9>!|JZ039*C.>Gg'eyJ@`)aBeF;TAhIjnoVoa`ݓ'zy>}ݭDz0e]Ϋy֎mbiUUT+Rimƨ :1Z1=KOJ*   s罢"mN[ڶ? 'R`#iHS>mxBPX[u`cKBh0抃>\gdc\ {8,>sj!&":0FT$TI. L\9f)weR]\IyX0/p뜣 }q:*L] 9SCI t,4fb;j3!#ݲqrddkjWSR`hMR{@"υD#@\TԌԈ6,܂KKZOi^: fLHZIj Fغ(VYT4.UDKR k:(AUv`C&dcjJwYͷ3\Yu&K>vr&gW],^ԇa9\؏=q_JG)yb|=pYs7Ļ?t;g#9Ae]˳`;?g>>^ֿ9ss<.;r5[oCPZ@zLh 5FM!m:Lzٞ( 5"$Z,1Τ@] R\@j+T5Ί(*|(vx2*h<(ɡL}ZvxUw1"I- 1/,[1*WӐ"khZ3 ΁u&tTLd2 闽P%nd2*4*;{ gyme0"Yڇ1HB b1Ɠy@@w;IH->W[c<媋9o'9{YoM.{\wwq %S_3wEȒa}77|&<{_“x[/z {ԈyivyS.}M6FT!:]sQ]mP׉Ns}wsO[OT1n Us έ֙X; HHN IDAT6R–OkNMQ4a#Ruˉz pI$edFTm%dԍ\50rZkJ]! Xdbu{CIi2k7Ti)`Fi6XI*U$(CR}ٺW ux4MV*լ ^W?R{M} q啗 SJ9p3-w9׎S4J~GPؽ',\~W\n~?Ӭ\|yxg~zS' νmoT=><_^wAZF)+^ ?Rw \q5O?|͝q[p>p9|%篽7~h {To qs8recwN"xz[. 4޸~Y^G"f2.;eF@]1:&|)(Q2$48[3ZQ)ܷGݳβ"!tTT"@RW:l`wuw+k:y=Q(m1(9%,5 BzҘbGWPVkPNrÇװI7UYVՌʓ{bvce}AbwezD"`R+̩uq ܺ_7l\D?nwq?O`ƏGa߆epImBȋxc^|/7S<۟.i8=&yS8ćY(?+ቇ[v</oyvP?ogy^l~k/_l{P|O4/}x޻o/ho$ ȝ,U17'^v>+II:`o4$|7t{%YKaDf9ũb زf}R@m>W dGzdK: :6ǔ4NYxAnZu⻰"O 쐭\ ,ffؑ'DM~CM3x-c[sp]3?q7 e ֌72vv JzN-YKzb+%jTy=ƈYhvO5m i)9-ƛ9:$} !—jnÕGV?OJ. ndfC t>WHj vISCc"q\Ä zP~Ie.暅Ѕ(MhEA_s1*n٥p [,+rG]a K[+'t= 3Hbso@]??N*jb 4#9Y1wY7} (u-֛x}缹_ q_t @s:6|3K>w7W=\6mv-w-SNp>G\zu N)n}x߽rN.{#8thsKJt@9U8r '?Ag,P*]IԈ^J@$.B6@O/]$!!&ՔUIY$⢣K mMί49_Sv/kNP0*kBO"l/:vi280`'nkګw8O^{.{x4}.kh?~#kR[q}}W?{'{I'y?S z_ثEnb?r?5W^J|Q.X>şke:qFoeHO6zP&#Qh|K.\#hc[l)رI0EX|.>=%)Pc,+qS"8P@yIzFj|Nڈ-&Nɻ"Wwq́J1 iAH$(4,V^^ t>,]Nl o9n)TeK<(*j[K]W7.I1 ]1 -mhCG:ͣӷsL>#c%@F'}IGp*e5H\լf2xͫ>vf/q3C|tZ*sYcο|ƟweOi ??sGiuM~? 'b׉w}gX|?_5/~Gxc7_ͻ=&~T@h՜LrhԢ͒jrI]ST]ZQJjnQQ4 s[8<.?S_];K\\]@`}jz]$4%6DdַS6vu6p`11RirH죑b“zO]dF᳟ 194*LEJŞ/svYLpVô\p&=l*GR7hVJt|i@ۆ f%RvN 1lZC][( kɾH\Lrl\lI^2ɀڎXk(5͜CiSG\q$om|.EcNd*F1%W=޹}>3m|>Z+bR3+PCmGII$`$@z|my܁ 09>:p w|*~cǎɭ{5ȍٿ"K_Wt:vﳇo` G/mvҤ QN5&@.R:qu|A1$UFPdc*r$SQw(->.)R>sGaΛD 67Yْ k'־TQVe,bZ!Ǖ^@E+"J19RG@B[::^>(jHJLqtlVSWQw5e#}vb>BapJ]iJZwmi\,;O쏏C%RI,!N]}H)!)T褰ɠHdPI6Ȝ:&f5+`7i}@G#@WnN:@:@|yC̿BX) y Jcli2cq&ѩDR e(L]f3+T 2e}eJ;"63v(-q nKmr9$ZMEO+)JITT,Ai٠~i_z'}Ip1(Q).eif: 1NWu:>}͜|ӸݧH\9Eӟv-W=" YJ|+Z9gooyē.PKxӰ$И@,Eؘ(1r\M1*}ҒhLAi3`-F[6u2 B [6T80d^gXt%pW7RL°E?Th --:v]+@ IL!G%)ͬ AU Du.K#%3_"v)G4Ɩ{g򙭣uPe E}ݧRCണ0RTi*tV+cVܩxg_};aY}?7M^,6Ύк&֗h%u0B)bXvR݌SwoŽ7^XJv,̮ iK `k jQX+ɏjT_ƝSԨԥ Q~q[06>z~ @&j/햐D3oPR8O}S꥓}=qscN%KRi`{Y"!ɑdȄk*.}U*ų!Wt/wtIf1ht u)y#ƖIO^{%^vλI|n澳;?0o0%K/7u/Q=}tf׿=O;翂W+-}"AfAcղj&LHW:9nyKxs@5?7O_PWP{k&X;AZ]o𡔢qd\ѐR650@0طX©gRhM}RI D/բYEQXa wF/@py6(N}DM4K8F[))$bY:BR$tA):9ǎE@Xؙ5@'(Y X *(<3`U"4a]B U)0?k ]$/˼$rqG@JcxRs=1)qF|wݷssw_ -Xgq+}w ZjU=!1[,WGiZX^1-n38}|/;_嵟|wc?P%( U;ړJ"` ;[4ӆ@UIUXR4X]RƈҒ.i+(U~L_pٸyr۶vDTN@ݤ0-L -)ʖ$lSG@'R J_%6@JA@JLJBƞ K "Qtu揹,ӎzSҧ`d20v 5V 5릵%E|fiI'ƿ$],ZAUJΧ (CW$JHt=j^C$ ybS؃8^ˆea"j!N퍟d)sgd>db,ei1P6M"dyvRf)ϩ4ٮ(R(T䡋K)QA#tl1Jz )Ez {{0?[ I16":0AGfJq߰{/孼~s Mꈿ'>Gk1_|^?(Ox]_ͩw<#X_3Wr>H2V($(< .'L!yb(&YJ= 'H9n(AHѬ@"N92} =S3q5U1.j&i5vu)=v[7DaBjNzI1rcLin^zꢊmp v4,ک" P0m5V1c@" %`2WkbJTn͏KQ> =vZ*zsˢQ$2*U:]’o[sx e8wg9Wm~Ts[o.9n= u/p.-hkG],0x̬p7L Bj g,j(J9y1=Kخ)OtȬIa]΄%,rjLJ:@ЕdK.ؾ[o*$t IDATՅyP4;9PC)L~*=m(4l]pڛV U"DtQ(D~7N)k}M*t0:CUYPU hF|t3hv@֖* zH:k;\hH#ƎM =[[xn_B3xS>g=%o>p7;L*vog>󸵻|'ޝooz%Oz{Ѓb'} ;ϲo_Z7{|jn{_73K[>N>krף sw˿v;G||~]̬H1- "I*O\I=UW9{-&e!H_ i?mDwKC[yzkhʆ6)겦.*.ITfK9pED-+挄 w E"=I[`.C",|ӆ5:b . ݕ*thC~޺tM 5=[߲K Џ)}D6{N)E `&K""9y)eD m~f5:$JI sTHZH7n36A韇oK0 Rw~=5oݓ~، ˠn 6X#NEP/5˾canMIc$@ٝ0a-ۧ9>C9$9Ēӧw:uFg{7;qcL\ܝ+ Z!;pp OڢN7b:uT9_2ޝS!%K,bCdwdR%0ajH;_HBxʣ)$iΜ)ݵL&riyO>Ҷcd7\__vp}=?g|7|*=:s~5(N.rW޵?{xCo~%Xғ5-8K(IAQ!>(S MLr^>co*ƥ_bᶭ0-T%lEUV4N:ꢦvA>싋R7%6{; @Nk1EJcPJ%mhY 9}SJH:B4$%r{S* ]ХNBFW%Y?J}/$0> [ ൡZr 4`1D"۱aqXJz< *C8 6'xh3ՄH"l(،͸CKH Rn[Gf811[;g?«=Ӝ:]˜OWݴs|ڀƺgX;!%uC۟{';>nla P{H |R.O"k*kKUYMLN4aμSt]bFem=yKؓR'-wyR$"c4{z)6`?9TL%UeqW 4!H=Cb c kY#!HEI_>x$07J%rtc{`ehJ}Ju2XDfǞCŋ?X ̹ڱu m{ȲH]`mzv\ܿĬOpj9,µ\~83Ԟニ\)NT Hp,*Kv؞N&KYKarpgI|\)Hwz(r|Dz_2oKK<0{r眽z*gg9}ӓ;6L 6$F [z=]-G#c. //r~ 3"!^$)˰P嗴%DOJTUԮiYJOE\>ܭ8Sturrz,:mKZ\/rq]?Mx貢HtR,* SPJ>GP> y ]RPDZ@ qD?Zf/-6,7h—@]AYlS[="tm46_!*8uQVPU⭫0Ri` M?j0E9Rv=>HrR!J ۔S!%{+Iq\s"XH 4;;;;Mrt$pr@YҤ)I_mSgK ;&GQh 'I]P7Y:+/FxU+mR&k=Z$1L\O-1tpM?HtG-݂HWУY?c/s"mTIaa6^U>xI]~d_SROcQy?t)-ߐnL7j=;>ͣGċ?n+b8`VI0zD2s5Pӷ;$6SC5tNHr?#@S(S(6;rKPaH\%o}^@O#} "B{C\6i7rlseׇ.}QjvJ,YtN=_˟+0jdݤn}-Zٛ*N.u9Sdb+C]k3ɉ>XDP=E1;):! $jCJf1; 7!ƎΉq3yڌˮe[̍.,2 sgU ޹t.JH;:ダw,cL"S h$'"t9+ vC ̚oXcJ-’<^҄>@r ) ܴZY7T%Ci :DbGTQwPsK&nBa 4KnƑ?b>'iq1_֑RϬ~yMKq"WB&\!ʥ`U"(K=VXm&A|36qzՖ)ݸq|qvm)Nqn 4h]3+Qɠ&(Je7~QA)%vV8#MFn@kIX4Fa*KFpgL̏dׇH`]zu)9޷a[\DxR9J33@#ݒD,FN 6x"}{\;Ux%Pp >;57s:e-NRϣ0v2QU"<-QFGǖ_FXJ5J9HzLꉦg|?A*T%Ti?ڄ.t)XL.Y9XjU#} >ŎA/3#BLvknZ#cR"!|$Pwb)n:b'ʰ`}2}'cZf()ѯMsUs[V<?sy{k:q2w \< # 90Mܧ~F {G@^4(Uw$h!8E S$yDf *N|?G7)#`iujPL82c'?@g<7xk9+<ٗpIM̲L) *z9!mN3S}㇡*q6Nv1M˷b͸ly , GYq.ҠaXcZ {fSُ~7E^B}]hDe ԭL#^إ6ΒLB$)L On:-iS'@k*Og-u+jc ۴8ڬ-%dOvKck1ZөcgGSR"&EEst: \ ǩ=q' :597 ePaL-%z OT6 Q*RdP}w("쪃-ƈA>NFr*e􄐽u9]M1va/)EYQ>jƲR8s>qcc<,b.s@]: ~ -_RYN,n(mO)a% y7[OxJ뎅ZtUfI  ZA^c;xHգŭe*h:ߍT29%w\N`\Ty}Uido@i @B\4*Jo=nXflmfIس Ka",F <,*=ԭelBYWZrgM :j=赧=8,x Y$J@l1@+ZkxAam@iF!z)*SKkKZC4}W*7R#bb]q|4|v7c36n3>֎5nR5F(5aIH ceYPW4eSg I9 sdR^L#[]0J\*?+⃤`>е^ؠ^U1+fP%D'ۭzBqr)fx{ 1w,-0ue!闃/c+E t&)}$HVtl<4iPU4k_I@٪7Mbt̛@]kSVls= CXբRSK-ֶXw ԕ5d RT@̟%R5{FsM$RfF.,ɘnq7Rw ؐ,e1j))ͽycBG,ŕCQR:yn eH-_Rq1=,ؒBǒV +L-6ZOK̠O=OiQ*<3N 1H(1$F%e0+ 3Tq 3NUAKR0uD!rʟ،OSƠBd}\71n;wmIʥnI% Rq7Ra Rʴ]'.̀ndFå8OVj:N`1,Dy|y|[$ቱ'>^P*@* iBH# W(J\ VBZCUTZ =urŘ=t:kuþXUAp4j̉@Q7V@\e땧Z5&^>{CE*X=ۖs5}g8%J(zdZHbIl9%s3>A]A]F?0x䩈Rwtb{@zvmI&_a O̅}쥘ַ{u~vB~>P/~a{K_ɾut}/b6JZ_Js=G!맑+K//ud`5KP]2ʠgRK4I'Y2N; Ofs' Bt0bib=tсJhH:ZփS*݁?8Ot:gX;EcDny2r j*?u-L]QHxQX%zb Z>9=zܵNLJ̉! !xԩ"ɘ'R \P}?3@} 侩 63­$ƈTBNq AͥW/d@5`)߇PoMqa{$jm)^]6 v@)Ab:5%X)RnUYdˁ]Jx(DYF*Rg\fXU&|UJg֮C%Y*p|?PdWBRAr**4<$>z5_"sxDjЛnٷ,Rz2&nV"C=m/̚~`(!_;]/xoYt ~1_OzB3f\XstuP7,Euw8 vnڥ9wgP:D1y )R肒r,O!t #^*<ﬠȶ)҆,:p@L^yEq!KpA Yfdd[H⧓kML  ;`{ocqcޢT UUG4.St IDATw5!?c‹>͝/J^x9xvӧ_r%oU-׻?݀?(܄ƚuP'!)Q7t|y2Lg픂JGYZ).,Α'COY1uC9z;ɘQC8c"dd_KC@:zHrL PYޏ;0u$ʲ4U(R$wVICIP霋q=DPQ8l0S'7O[L:UݣrԱ`dyJ:4C8x'ufge}{W(6FQ|hu֎_szxR%1V(աzV%Tnɠn(9B*KQ`JM*"z‰^[ӇVDf  B+ -9ꎲkܩ,;Tʉݒ X[KxRYr sY+.'%Bwr'u%tG]URgΰ;8$hT$^ܴcuAL-E*l%y ə)& 3+PheLs:> 0H3  QBPDH0Ppj5HڧFU8έg}0x-UWr)3qcݒʿտ]\QI~5w~Gtfn)aӹaz3]jk&8t3x&"~O,%r`$1DU 1.q–5,r0I# Sl/Fas`̐ir= yr9:J( ֵSy}cql͞Ajv!䠅 ROj~? g4G-:,3`rNN<}qZy暀(A:j $jʼo]gm(}tPwbf/$U`LIJ ѴTB}&*jHeue+ 2uE8TW[) 4ַbN "bGe*I$دꃡ䱍->3>PL܄Ut&_vHwc@Aoivu1nɬq3zqw" 5.$NC]Ov@-FP}0t9(u<,NH-נ@Xx>Ufވy(\iPThe$!(0b@EER~쫋Q;Vуj *U"4JaJ@eByI A:0vfͧ泽; _I|ʛ>N8:G| K׾?g.Uy Wla$L@]&c)-rN1 @{kq-4J&Nn7F֪QW", 2tm{s5e8>жMF@WWR&̙ljб*arLk3[JE^ +2UQI׊>;(^$^Z3;b1.PԵdEm*kc t(UR1-1Ր:JgIgSCAQEe(ꂲ) W;j42Rm_ŜxY,%w8% .3?aiCˬ7_)% Z.1Wk !ʰ&@bҷ,}KGC1'!7 -ˮ0΄қSt1ŶvxrYڌ͸N`g˞z9K?F~m7~s1)z~}KW"LpyOjS<9oݺhuU7?B*'>yq6vq{[yumQ-\1p)SAR PSHU%q?F9RP%.uN4V %C_c턑xe)7t[3Tf2c~U+Zũe708R`9o9:<9F;abvдpE_AA,1uX7JDBY;J7 ȡ@$aLc]VCUv~q(P$3Z3+0~̨1|Uyƚ!(ķ7]Wrgc -L:ZbL5lg +AWUN W9iI]WMuV]+L5)CIrYwB.^;-IyZY u-/Eغ– ) jAKTXc1+bʵpGs%]jTG!c T1?MÙ;L+Km0b1IG6r`kg$Q'Ni"Eչ VS=)%n br]uj+uE}JkE۪^E5טN~vckono7c3)t|].8nů큇~y3?OT S*oFs4'O{%#_m=-;; O4{ZM{~>1b+y tv 32nM8Q.EOO~.O[y[OsJv|y5F>qپ}ط͜}>/ /I88,Qt}ćq♳ܲx7dvQl[}˳ywo{چ+ RN-3. Xt#{` :a8 'N::Rs5Ea>#KƢʹ\j-R*58m8 )hZKFgr:tզ(s;cIfvŖB,I:cQ"1t13fEL3ELK3GɸBۃ\~@fvi%,vs/y UIRU&A9̐LԪ'VҕHEs IB\^҈S̒ o%*-.1 WwZP5-hk6Es [we=\G$f&qQEZ+cWMrz:"aw66(,hʅm\҆v4 A9u )J RJ+t 1BKrCPRT,-flƵֶ? k/㍯///3uJ};fx=K{x?w1ztmkzGxᅦC/s>guυ<7<[xN>x> Gٶ< 9X8?~7x7u=piq/ob~}ԺNQQvil5E1Źm 7Ź-]HS 1Nӹn-%MC5W2̮ #@ǜ(,sS{OWU&(į\:MQRO83nJ|r58ҌRA699M$5$hQ 3 ſ}pۖ, D&+S77\GnpmU]:FYlkki0ƚڢ(vmRmiP)lNTu7q֦=h*T$eϐl)`.Pp`mOǵ.3M&%go9{Π`ob2@'e`BH UHm$%Ck^@M8E$DIb7%4Dd4+n`zј> d$|G K2Ks̰O*s63j.ĕgEdˌZt?ZCh3E-4? P7)jt!(0՘*>2)TA;"q]!r;))/d4W86;nҔ \Y"lCB/Jh>a!/W PFj;rs( "cg6kLevy\c<3rs=>tt0؍3Dv=Ar n<7FѦ)#basL)62p{sc} 14JmSןzwpDii)>}H8먌Š`B'"pЩ,R !f.,qsmmcT4afg4af,Y5cVϨr")ŎW$SqMH2V[a<-pBG %HeИZ$̠I^*sNoNց-B{:G%w#'Fy_O}%U}amssꄩ}* ^rIIO{2rIz*2Trym)"L%"pLm@-x+fSZ쌳G⦟nlJ:J`dLjN1]~Z 5ɴ(ׄ!Vs^s̝VbSpј- 7*j[c!:M67tTvc7L;5_Pz#+=uF3Z[¤geTw璂ٶv*#7FB7(OVgg*^>w::Siq&?HYCFKdKb\JG h-DB$RQ5Fu 9v}aord Ydu]`VuF`}u)@Nu_6Jĥ$+)/S:RLi.|D1=L1IUM3_BJU8ؽG5s\|^P7Fcn580`x`[1 [7uY974NSVXK9CMe桸sv"LbBe"SGω<׸^N7y Uր㹀;E &9qO4V[j[Qk.> Vu ^l즤%6Զf^b9O&^y~3Fkq$ֵUKcںYG"Q X2`|M9Sd"4w$&9Gpt*1Ackk-ap6ȵ+,twB؍x9)V~fo m~ӟ1_|#$5{相N?=Xt>1oA~z6핷~;o :.Zu+_ė?>Zs&xWP:_V^cSm3g6c>bO]υTFϰw)MBTRT}2DV&/|h{;,q.򢃃Wbƕ+-\2Ac&P79'lX>V.x?2Mٙt` :Ͱ  z^Xf$lJ`LHbIӌJ .̺bV;By-u(c+?7X.V'ܙ{ cҳ^U"pzpPUK7߹|2ggR0Fkx#DžCκ;SUlʤuy\xMjRnfx& cjt.%ܗݸuCꪙ/W/u#=`2fR{&UI KA<B`w|~Ľ/ *Kc*]-f 0Q'`ܑV[R㧓SC|@$S!.8[րуPnW2?3}?}x׿U(*-5UsX9Qi$S#퓧=>8",:ieH7a霳!65ihv RAAjLa~Ns!2A9:nB+~,)d6v`0 c5B190aO7ˁnN /?`  ~5?s'3kn_wg~Sۓ5OM[]>_NO2PyG#w?z} S{%sk4U_lAjrSQ? >th}S vE2=IFɥ<)9z )5dBTQF@r%B)!D|dNЫW+88{ i()֌ntSc|kf<k|~06Fe- 2lI#h0icn4:GRJF#oZcE{3bMM:La(YsI369 ӧTΈ4s$!h\rnH8C6%%t\JU騰jC8Cx X%fg;`v?a/}&q4K#?s𖳯_om72IKM>[?Os* 2|Ϸ/Ag?4{Evūț_] kfo>yh>{G><%qf-Prs)!9\Ս2YHcb(Mk˅{oޞc>7,2 疺Pk*옄 H.γZ D8[&LAigk~=i*>bJiy T(2(# ƠC-t+BM[;\Q:ҭVrY%O(%}\ca49UG/}v''|`>T@]Id#Ù:sbz]XX:ٙYsv&Uk_pJt@ƘM=)SrsƇmjiкAQH2qUCvKm󊋗/T4XFR̢+i5h >30'Qz}־c58w-37[*%5vJe )L1J)Vam9TijF@x9.i,wu23W<•E+};帿ȕFCOy6x!L$TPX-zHhB XcP]F5nB/RR@XS*?؞$h'pWMnS?Go(;:JgαH3U6Vc&V Dziz)s4hAx;Ansz_y7~t`Š'7cq 7wZd^_ ?005~#s?sO. `W7~_lGy-{{.4c>Fclk2ff!͇NN{.`0|~$Wy>6[^޺Kb! ZIo$D{>LK)_q,ptkn9> , moRR.5"2T,J* ozVOLbJˤ]T)ASx2ĚYk|GZ\qJ-.\PW3dԈ1LGzւsrYOV x\3 y1 kÚ_s>?x8y81s3լH 'ԕFJZʓ?@$k-fH>ϩ0pM|VڶnY[Ө>plMU;T³YmZ_ac^cC]7UCjWQ9,RVDLzB y (_)'Vbs>e2z쫸:ʕ̮peqKU3CX5+x}QwIwaӫMH;vTڢȂqhU{"/:Н;𧁰 'IDŽaϾX޼s\^h[8ֽ46jY5*Pf!CL:z0Å1/h9m;fTUs-0g-Z%pF}$B˿yw-i$aWJE񞒬)_$1ӝysQa7<1泚0@UCmVU H[0>*Zˡ0;F\y϶gG}YP/%U4i,Ι*9/N]72vUJ;S*?9vh]aL%P81 kбc^ rh5p\p[YlmJO TY%st$i!G<B 0L2et8ԟ)s;cg6/M+I~*hH*nB=GӎKEֱ f`#x.޴pUipOgYٗ{j;N3!o EQxSœhF+@+:%JGl6w8O7=C0> ;oSLy3p- u/ař_N:t*,QM:$L%x0FK%H.He7vo^-g0M|~퉴K d~_}~oޓݲ˼/J5^-3fZꪕZ:qFFe)|^TʮE${[9a` I(L!^^+%/rIw<}mÞ9&FI7m31HkI)ך __ˎOl|سM[w"_j!pFZ6z-M&9g^ucj9>89,zRyJ4nwR 6+fN"RJ KGciѺ"EuVqEY#I#kl\WJ&P@c,Is͊!H@zVaͱ?`x13.4̫mn6;`G@ JohlHr"@6KJYg^h|k3{(`Ϩ, ORJ0Id60'<`*BGq Ws:W + +຋C")0e ؈39\=Ê>dI6/^+- Fӕ.ܼ8 q7rIL`X c7v?a5gmOjK~)?rw|w[ {kɯ}]Zػ@ݴT8kk0%IPG,~0@gVT~ް/ z99PwvdzPg, 3wnyη7 Zm 뉽SBlL.^;;%v0kQNa+S @WlOLq[$ŗR N ! OrWxpgtng\l/ԺqL)ݞRױ-Y ,'I,Jɓux㖏r.> [)"K=7|˂M #m?As7֟p+n_2M$OS밦 CG:zSJٟoCョ=až8N9 Oƴe#;ڤK?Ҩ!D0T#Գn݃}'~?ۭ?χ~Oj\P/w qtmҵd*hPaTj*mp:mnK8;6 S/Sm;؀T:/yV5e@I% = C(jt%q( Rr-*"FzCUbks N1pOXȺp~?>džMDS CzsK:FOj ?^Gz^}eYC:CMF3vչ}[У?iJ%zrhU 3ngpFJdb:ػ04rr:&\>/+E:;K9$BߖO^R̹F\n.1ĺj K,v7+IJEU(#a*aBc9*x8on^@4% $sZ6JgM;)"\&n"90waq3זMȖc…p",SrX7Ծr.H""uѸ#Z}ճk__q3A.mU w)RLy"5l3YgNl*C09hWۍm?#/ӝ58nal.Ȅ:mۗ|;発`W.c,WRQU cRak!Hsz62I]T3$=i145JiG@ԕU؏'?Ak#L?._:x0 wɉg6`4̴0 s H1Y[M5tO(1vvvrIcykrvڑsCZ.I_QU]"r3f#Lpyp'<1rndq*2G0* 0TIMXkWSuۋ3Ri7j$T~I@DV ̓"̥l?u[@]Rnyi A{K1H҆lHğgP$&V Wr|X{1Mw uWs)0Ua+[e^Ykc}}D?'VU?“,rmņ\/t&m]"*Ӵ{͎ۍmx֚]]^_ cg>$:/:}+%nL..2: Df |rJ2aC_/=i2u4T,5UHI^'aLNn;$SB^QIiw ~P#@mx2yd)gL(՗m%" 0RTC(Ybt $a2I_Sʥq|.o1, *jӑsmҰ;Q=En愥 dI0lF cL,(-Ii,p}pS+j=Zj{nE3uUBS0Sm)'aJ} P)Hd<̳$t% !I=A.1% hƺ)bJ8ȪM^EFinlm.D$N#ks?fۧ iN٪HkGW.6̪a͙9n]p&W'T 1 Q^O*2Ӥ~&u[Xl3Ҙ,~K܍ۍxB[#eMFtwIђ&iWq Q)byL_@N?%-މ9zA&(E.i*]J{r躁q8'7 wH7%NJAlVX0z{ItRS(%f.`&8QZ{>=+l};=F&WU@Jdnu C,KFuJSasCgl圦Ͷʤ4J2-Jgj׮=x5"Cs2:qK`*酹ucxJkrU;jj4ShiO{Z#ުJWheŵtcV83s3089&ΨbA$ryX'6Gf'$9 IDATIep1}l0ӥ$3zLE$1+3:g ,QLr[p'Ej\3'avE._I6|'JdV x?5N6LY`kLCSamjZК =!y[8VrdLT$p}tѕ5 ;n7vn7v4I"FElJcx#)^ KS7 boXݒFgIZbF.Nl]"!x<7nzeRfb$S@\D ڨ*鞫jQ{X. 2Wg-8+-5 8}=MTn ^*<ϓ rQra*c 7#%3RRȬt NN]0^Q箂\3UDhnFL{'EЍhLhU"HPA $]$RbiMKjj]ѨֵX-˜wF80؁հƩ! F:7pT*|Y%} )Lp$W~ AP}:I/P7+.U!`'MM5Vу6(5S\ 59α\[\ !'ƁtaT~ϸݠҝ}k9PZTw}[ѸF>SњV572$W·9CdJT;RdI_J*qm]Unݝ'ƠbsmviZGA:-(3/b.ɽt},Sdž؞[@fb>2lvgehKCaB񪉏ny޳^͛MkERijXcaTx1fYf4PWIH.Ѝ [7n 0u]7vya)<wʥ|8Y{=VRt/ )MP_03"|v򆑓cJe!aYc5c|7&vAQK]1$h$($E`!'@e4JWen4ZiyQ%!990>vTj#TY32dOm+40"&nLE<|ez4S>s;U  y~}~c'/V(S%$C7d\_&&jN.{^2I/d;N2LBgybxbLWдhZ몰mқfmrΖ lT/N ˥c]av%0%U ^$w*#, GQ2݌37sn̙)f*+LL JJ uQuZnKKz*)Loӗzч>H:tsM绹݂+Z WFǜX +jSaW}aUJ^TF$R@P LeH:$V{6zqpRGN+ϓHtVکAwTY$Ul>z|؄e[hZhui<#Ye|h0$|N\5P:F7(HK;Vn"44kEKtn;CG^j4{+ ~&7S_F{[eGKZ􀷵Җ )k|V(MƘ5cdAe_naKR Up]7#S-wwi步㷆lXGI/-EUkfmE8r,u-+U%eU%ɕ9+VQdRK1"l687_#]M譓 za6-=';Iqp{3'؝O4Mr~ݔ2ੜ0$[9J67yx57*qaL>xokPJ5Qwb͌OwWX:c5B À/N {V~*)V*IJ_ZpEd2' ݦ(|u#a*XVr4FEhULl SpShJ,EK0N[6 6ȫȩ?ícH?Fr&D8 )Ѕ" ;J4FBXTPXǎs9&fD6/)t$Jɯ(p!1!}疻/ gc>p,ļ;^,Y×%%n.֩V*E$73š)8u֭fus3EA5\z]صxv,ةK]>7w$=`>snlWRN3X[C)@7bot}qqbv,~14N4MA|v"cjL챮%;=%N\*3,FJMxE,s3@Ww݌ֶ&\IKU-*+TRzHe%E 9 oD=!EB-磧=}B7>[esd5gtedNA5jRq6DSj*d%V%cxJ2 $;%ZcaR:l8Enǁ6 )l\ p?vD1s$cT.!RB)Ֆr%CH;owoFiY$LVkSLH&0ģ$؍?Yӛ7qQ9Տ]Cd?z9lxnCk2FYj먪 ER- ,#y)fB9[~NՆdU 3gQB%P ZrHC.|6XX_4BYVLԘ 8FOwd@uQbkM @Do2JOWaXX0DamEgae t^Q9>A*l%P i@!1O^mp} P>-nRF&RX{` 7ӢuEJuP6 $+uZ54.,;Ǣ^03+IK+-n=QE3+cdR8M'=}BO֬eXn7JOIɣ.A,d&ЗQJVXT 1'nJKv)Tlh3d2F[)=pJ|p&L5w}lӫ_]a>y㌓_8ȹ+I~:N2%% Ns #RDžO峡ɲ2j Qѕ,:ƟW`gx7|}mR_Г6|WB1 |7=QGWU?Fo7r~?V]_y_Io7-'e~z`\ ^w5o"^>uZs'̟iWV?|Oox_/fذg]b}};oxl}h-8ms-u5%DCb® Kބ8gڔ#̚Y6( V7#ԍϭρ5KGM Em iVq8.?qܦnL6wwF'KX[O :8U˾e:% g0 *b)ufJ/0F2k鬋&2(Yu2Q764E5g,ܜY23Pz["x:ݡuqdܢ v6&{ ! ʗ#S g?ŘC|<>˄]X5˰("4phtC5Zi ] =bWb/2<ɓ'GfG\{.+ڋpu!eZ, }D|TͥG05{7x#l:k=OkbGprcκ@*)CKЎ`#U,@% JcYMN=]YCUE:I3DuFM<,CӲ0vt* <6% SH 2 a0087>z|誢aFIfts.^hPc"=u \ϞYRQ3<V'oJ`*Ml5Wne|&K{C׿Qyn޿w~s|795y-FE>w;jG׹4p=哿9ҕxB#?-$*9'|񻾕=)?kܟjNEoop:́7G/w9vV;ڦk꺥 +b " RIm,šLHBaGֆXTNjBo% ʄ|,.Vli7Vϗ `*Gim)ll"@vH@ry3qfXXEk-MUmq0G@WC/TjڅfX](DY?fnuCuꖶWֶS;J'ʬ6w ݹ3B‡ c:f@a "7[!xOcwL(30oe"5)(59KA" 1] B׉ SY i# L&K)ѤxhKL|uv1"tti)ה3Ad 1Ǟ&HF]SHkrv'\b P@]:Y50Js2nO}hҋg.#},2aiT 8y[L\أ>Wy[ z^oYd9<1?8jS9zUZObO's^s pՇ''W>;渺•,?uXQ gU `q';IFdjbpUo@k&:`J qEXs7-=~ߴgn"dI6 ,Hl8)U)TRv10!F<(d(ZCO=L{Xk}Cx5sݷڵϹ^k@R &\1}&+*+ jWd>/{Gy%gO_< \Gc_'TJ<_q4}+~_%o;/_|_U|ˇc#pi9xӟ;;|Mwϯ3^·Lbx׽=x|7|=oO ѷ\-؃o~U~#˲peYQ@I %^fzHN@S:^uvNQ_VL~u!DԵiedM[1P$J@~Uf`S5=׹Rى7l&qr",ݭ[HJ_p;@#)">9@QNwXh54mvl ڟلݻdn&݂\u5!dq{][urmqݝuΌF)3)b4}^62hm Kﳩuފ"SJb,zm3!r >lB,9$Ӡ"exLR\\a^ΙŴUE%nŤ3`06#JMԄ"a$9dž1ĀS$aO MҦ:E~VY<-vi6|`@h|M욪]”k5YV^-v(e8{}~EU}8|#,Z~Mtn8ᙓcnĊ3qVU3ݰ:Ԭ%)fܷiPr {eCVA)jNW;,ٗv*t<婛xB:)RSKge>/lښ>ʨ'?~k?wO c;W~y>rㅿReEQJsRL*bo"%P׊M-'m0ZzTgxlto}gʮc[Ɔܿz1YYJrlArdfî_Gh2lõ6}@ gHI,sz*}CϹ}r >(pVQǘWtea ֜B6>&C={ƽ78io| 5'+vQjc vA:}nlBw!$෎Y1ۗ˜\bL|ǶMt=RDAVrh- c'pƥʋL9PVKj /Τ~h4WWTvM)+sS&NpZ,16ZM N;9/"X3N84쐙C)pZq an$?-)&j#Wv]a a [dՃ fҮ)%gQQ髼xsbwtRO~֍cgfFG<ԙ\ǓyI9OB &N>P쾮@kIlMHJH{< ] IDATRp!dō5z3N#}fņ sfSKKVmh7̬`o8s'9ܤOCْagQrtrU07|5l9ESliik;r\F/9fC4|/|{ɧ~0mXo8Ņwr:LY{ M|)ߦ<7_w`?/.qÛ܊jR+\I\Q2ڒA!Fף)KpE]/}LGԱiY8E#E3xt"b!7MO'1cPƨ2J4m.Ё;g;i4To9-m A RNO$XIۃeЍ8%fb~>O#g@]7TvZm  zYs񋚂'BNgP9vs,Ha_yTixƘO-;{H9N$4 1ޥr@yZ笋H=zUvZ+,E$>AiG*%q l[aj3ZX1˽^ ͅRu&} ɅVw,sd꞉9Ĭ$ZNN"''rY.=1nS)F+躛=uܬ;h}*5mUYwyz"#SZ=kwn%r !hNIok[a(X:V 0FVQS`t3ꦹ,u^a-[`$ՠR`łT}]* [PZ+8JKLr\jDE[Vq%f+YfciN~+ eVD#;C\]`WILägBxj[ %Lx%OdfGO=-Y4E]^cb |Ev+ `3B!4AGY aǟ'D%IuKZfoX{.3X6v'<>((zOK5A5kT `C$Oޱau۝3wje39+6>f;ԺФ]$tMKR\q >-`ƍO]}#Nלm"lF~{z Q<(}wL}ٰdru.lZm3(e M*4Ύ:\N(Eq9 p[Yȱ MFo°ՙ)L007/`"+#,=gh{rqdZu@T#vYQFJ[̑0@=,FX mË>)ww <0F? ޹kv'W}di}>76Ү@]tz rTu-Dr bp}`R`/ǽu>Y[8]9(\Aa-(lA*buqU^xBi@u؟`EU&)cou\\_erbtY1cb&'՘ &1 MP͏u35.8- %i}g9J&r! '7p[9c$Zo|S\65%&sOy*OHϛ]_WZ͒L kց੍ƤHL c8i A3hY{,%'Zy !g8ȦleB"m~;4vܽvr\4m#o;[xO~u]~-t_?^V*aBهvQg`QlcUdlq Ԩp%罩 ;;H2΅r`.hr3*vٰyEa!zҨxW${\ uKbu4pFˍmPw~"5bvSqH@w .9S?PFy^33( d^*&+ 7>m^ 9c}‡@%d!c0D3dI1hui|YF]HߓM$zЎN[ ]ʾGȽ54Q `N8pW#*bيg,ѣ8SL3J3;G9?gotE`RL UYj{`Te݈Cg*QcXtɭ;eb1͵|%v"M>U],6rYGLbvr] %RYmh僗LtVz00~_z|j)j'RKV!4$3aOkM)J+T؊Μog+XXFGDjqHڕ]r\K`vUdUfIRdTPw3+7=H$Wo옥DNaG Zef'n]A;  -5X6cUo"&1a!GH Nj)}w!j[2袏 (, c]qjru+pF0fٟK9ڮ1(; iWxu Q}*9&>#=1>[ XlIw߶sv&[NNYtޖ9vˮ}58J٣Uu՟x>cp,:13a+F_|{=v&;& a܄i9%ʟI mnrC儔>GO%SQ<pj}9|α_s(C茗}o]Z9S:2-BbOcmh%<\y9JB@eEY4`#OVR9$]z65994ƕs [ܿ8;kߟ1'fi30D6XM*!a[MVu9.g2Ƕ< -o*xG>]'l1}=oߵWuM8vj~VJJR) _|j޿/{YDnض&eW+q]'Qmb+l[ MNR2;׹dZ+]t4CuHX4RؙWti\T"15Me/R(,n\tm`.ZtvbA#R=2@i#8 0[7mD&yVM3Kқ+( }{yV ]ʼyً\޻@Rqᐛ(&$ƏU0!8BX QhxXuXm1u]y8H89v@% qC+OD7W{uZ> i鏒p;bMTQ"l/2L|{@bdw}McTV`A,e2`zdb`gìI^%{Ԫa;KBy>xf&l|53vg l8ic{OlyL{7HTseregA4>W'@4e:N5mTMب'zzͱ\-$I: bu- -9S`s|ߋiH E֖,3fVK0rb>W֢0&rHQMϹ'i!BXhY0 6YE~d9f W/xHM)y m+_y _q >C${ _Ͼwx̮=7} =Қ8=oO?+A)|}xG/soePE~wߤoX57?A~wlWO׿?|3Z3ڣqe~?]ܪ =b\ QG=pEs 1ހD(e`]qTʬ :gr1阶m/sN!d.ā>,7 ӷz89IשnszL EiجXL~Nd}+&W:0Wn@峾*$28-,|+fU؉%m.Yq ^?} u_3O~KR1[}!|*N?a~f|)b/mېLuox~L];[{͟c//5>1Z|xS#w/0v `J(,E(Kɴ/DaRqŝOO4E4:>@E$ IԒa r2)oios<q`׆AfP'X#3s8q~G͹v?;jʼSSv9U)ut ۦ)lnK ]PXjZu>⛢y4'0=>t9-P N%ݽ/LrhuI1°0e_:C㧟䮖Bcr0Uݕ6X0-Yr\Q7”EO \c}NĶC)qU}fY=P@:e[I(Ôkc3Zb0F\rT -vCҁjWpY=8*$v|b*v\'eDl.Yr$}Va3n8τa*,p 76/u#xwwuw4Jb RS׊5=3j2<2hcpEp"ZGKXrZȶzڠ7ղxC݆p2t <3ym *x|L #CлTPBƒĸLu{&Hr~964Zfzw(uDLgxBxoocV#6r{Wb0sD 5SF$\Nmy1ūyd۹L8pevb`^Ιs&Pn{f^ڬ8ݜr:hu[[yz4Ϝ=>GJ"K@#3kyh!\*}Ղ.;N+QJ]^=oiۖfŲ^r:>-6G\puȍ ?{.c-"i@ 33|=ewtMa?*fMaݮY7NNSWos~CWy>gbg|l2eZ-XPĖUK%,3 :uRT%ҕCVkNen$mh}M65vêYs:Ƨ+g'n#Cb#ߥ"mTB/9 HXG24G/#/k/9l4O,< ܴ(pԄL. wc@N{au۾ӟ֚`2)NJbRUeAYTrEo8uFzo|VVEؾ:NhmEvْ:wmľ8-ar^3yi(,e(zX!;+^\YC$J@]gY #us{QD#sRܫaJ9gcR$qAYk>xMޖ}2R*K1syfؽP|]Gvct>$ ZjE"%COzY|1zq^lס mݾ5){HbH؞>6a=ULݔfnRN a*'` qP_ĀQsSIT$HȬ`U-t+ŤLuٳJz.WK $|m8r^r~~ݫKRLBPMB;X&.xO33|9}Ot/~+g kOqkWx[?pe9pf2qLDYUYi+` {69Mӎp D:Cڃ7!6M] C7B]~'I qJuls 'Z+70fEa&,Z4zwuZ /L5RSD{`=HXЇC(߻ %>۬{[_سs.ż0ص#M}m/nSW~U6O19*0ƉKfExc2a*HZ2>h Q IDAT)FYPF?#qc g4wnnشa톓͙:$;LkC3S`'lN9M2.L(4uAj@jƽ콚r¡㌝ɂi1ͽj]06alv;wD>Y?YE#<W\_`l|tF]Rϓ,ZL>9LELZڼӫ[.HPcKTA2, 80c%fc6 GAnPAM$ewV2m'$\h\;Q;<TA](let]K`woX̘g<6n(z?wg}WotlSX|~˱wEQɤt_W.;hHtǮKFBq9HČ0H t!Rm@tԥȰƨ3MXq^m+ 9CQ$&ib5Gr'^p&|e Io`7m~:A؋R^,Ubv2StbrK$Ddu`6mK GLl^a~d9?дirdA"H~.:b-U(%)Zr(;C -1^fڽ@w:t7=vl 繳Lصv"e]o}E}[Q,~M~MZ߲kVU`s*Ŭ dIVF\@?h@\7?N.^{fƛb_\ea&vάrUbFe*)q͙' f& ^o|H"q|eS&ߏsA(%ĝ4DoBügj'6{@HͱMlpa-&t2]c) #a80*RY n=ᙀB܌GхrJL Z.'Aפ"kWYtr\{|׏i?A*J36ЄFkLY;6Qq:nH-RaIۓ0uN)́Œ6@PGΓۮwr!?wce i yfӃ(=o"rvY !s nmc 2C^\>uuWB޿0P= `b:5.  'njr<Ɛ[rfYWM$䫥yLFsvMTܜJغP?MZdRSPs>nևܨorGꝆ &E;{8%~ae9eBz^X{i@mﭵb>w\Z+n2T(%)u2q,2ÚZBAسtF1) {ش䙷F@z!Dݖ}~=AZC. S%]dRxSC:x$ul>`rM(6VmCcKnSoXk튕_n~ʯXKVe{Y{asگGLE=C\smyBϚb؍I1zRGe`w˼K^1cVM &s@,f*5a]lšI[qPsyD~u,)Rb N8J[❗\(4}yuQ"F4F+l R]F=]R( ]3mZG&JlFz*%f.7OM?t"L?_NTA%ZOi}MY7.s ԍO餘!Y?z_blIٔ8#!j_c;}ֳnwn٬Xk6q/Yg,gg풣_UΎ̠䟭6f09QZey~y68D|䲁OL9Npevi5cQ-$3+gT;LI;"$ڶ{k|Ec)L_+備 lهsPb`4HAN^tTGԪf唃>>yZ=Gi * L%iJ ne0gU(ηgSX4V; sJĔhC+͘ O榼'^LPV颏Qvr^aTK1 B=bV$)1 2}.Z@J,RЗ5XAX.%]$B}n7ی8ת< Ղ1uSŔdάE/ ARo=e(iP c͔ؕz'sFo_R"?DZK k)|A*RR[rcs_<IzPth":鑄Xtnd;ZFmT1X!L+v8S;%΀jKBOIi&žN[ N H>4evr>@V@c_dYKv[Ex; ܺ!j?fn{s`dOAT%1kc6]181&ZIm2v  (f11S$P=$] VFw=rql98p9 s|e) {mg.?{{GG%DWBȠKsc\!2Z&bҤd pƫgP;򼘡UBx/t,s,Pz剩 JQTE T.#eta@$MgG ܨo3Ǵ f}/YsV jZ64ka.9m2;>==Ƨ:Z_=v[P rۧ€i)ѿks#žO,$h@pڞ|yB|kX &Tb I>-&L g\>-7b3 L+@@JUg,7s_gnsIܖ:m]d[0ECUT =}j_szhrho9Uk_˶!6$z.&d0,4fŌJW8\K2|sҖ8[88{U1K>}$P@qV7s1.Y cl^ ܸpd`2?3:5a()IS 1l6ĝ:7Hp6Ul94ގq/Ft5>\$d#{Z;z.AuCyET Ĵ\>'/%a+'Ok kqVyȦ)AQhvw {}epg1:JQ:WkL5Qסy!`D؄ޣ\h}7u~@J6Ku=:ltcDEcX`c`ƣ2SgYhݠT%J@AJUUlc H ;)iqԚ˸F}WK:65ԩ[͚U=!(}uV ہ#3Gۋ"RKLG#7"n; k"W"uHQZij_s3JAjW;Cl#TWI.Kr܁_ nw}]dʩ{YS|>o~3$D_Mob^yj{W_̏'y&szw~'/I7YvIE)fSJ97L~Z@]& M84g fr ggҥ o& ZrQD5f8H\>&fp(Ne{n^ 4k-Ô5z)]0׉Q8q+ >U1|{=ޞˠΰXfT39q6!"3%Y׮Uܸffz%k)RcK7s D]'z:i?>Bʰ/.r:ag(\(+rNQc'x鵀;UҶdzs jVyޗø(JW p/|; IKA|ڞ2kXc*Ѳ &qծ7^!_K_qڞqsXr+H 5Vws8saHJl7~#nQmmu9K st}5el}M>;}6kc@J}Rj ͋9S+2I1( =WH_QR ԅxqA gRCb9.Gq|P%%ms7{UFaFq!s0Xe'Pg sթBvTQ+30>څۺ윍dzn}_^',LA*vJa *[QH z;?#Q@SB<"A;LSN6rZP*S>Wb9o[3XC?_ws3*.#_~k//G`75_o_]y>Rm)d,䜹@b'Fac3cP(Bل$4L&) F)]Tc ts6b®;:.0r>YSg%q^,qP\ u2LKJcJF1Q.^|קByiUi)K w(ջv(]ޞcwaqfNYiG"0yhHFvw ]-8:4d*Ug&oS_+ҘheM\5̗-wh,|%1}0VLؙ;41H9婣JeQPe~Oùe W㴣Ѕ;bCbL2X֧@ԵaZLIxOаn,%'17CN P !IVk|@1]3 o̶|Iܗi1 Qz+3{Z&4Yի4k͒[[w?S"_&rXz)^9reQ{sב3v[75Ǎϕ)e1ܷe7f,6$'-cG<{u|PVUgQS&Ml%OV2kc^6 )P71q25AR&YKJ3e)tTENJ vC6$gƷ9AMlb}nfKv> [(ü?^>ϏsY[/˘C|_{,眻K/jII d%`0qjf!LHf\5LTe&v8'e&qbbX 0 I-޳<{ιvj-x򞮷޷{=|5pTWIc_Y(#}xD 6P ]*%3sW[pE^X%}XgV+GjY4Zk6F&"T+޺%AR.% w2I2Vς^P'WU9^ޓX0lnX66_7ڜjVȄF0uudR]]qR- sÂqzUz(83 m9HQ+^Biz/O´0'Җ¤q-RVJ+)rt8V3=^,'[N1,Meefٴ &`0TbĖ=O&5Աf|_ ][~E\(p"EPRs0HPcҵ$e܍Y Fv4Hjּ nA9s7.s]ìp wOhh2KgKʾwOC5޽JWAzGXS|=rև(VYxyqbOHq-s Ri` ,˜I =| +A*oB]&aZwZ'gXG=w R"ˀ+[Iٽ;^)c1i{{R?Ǿ?{^.IJHF<٘KqsXvF3[|Y8 Okr;xڰcc?uL rlC@4>$5S`Kݠ(t~S,F Mcp`)QZ1y. I^0ʬ%w.XxUrYx=p]]&]\z hK]_^ H *|O=&x<U,I?bQ!+԰i٥/K&FAUٵIjxz=lD]wL͍:A#NO\۳oyE¥_.BIbTkϯ^KϏ>@ [(OaQl3L %!X+R51{[\~/D)(m!?VK5(1N $"]8$ҩ15#4Hv:RNmhcÓP%}$R K4e}uTߧFdFu3fj[۰0 gU9XzE dI[0υu+qJUì=;12!Ԥgi[x۹ .%\rWIv,[2t$ٳ%}(IHaxϭ@5${Zek;_ݽfOCSI!fkG< IDAT.(`1ظ/KhuaB=2!.tYa]]= $ Y܎,EY:w<.oO`gx͛mXR\|7ŏ=zn7/7, ox#yϫV#;5>O~ߞ^:^gs *<ݳ~\1PLo}9osw[OYDf~|ǯoM/Mn^|ė?;|pT!7&^Ss/çZ,%N^};y &\̯2/&^r~?gU}3wY|b vsrJ\nq*^8}n0I|ˉ]>woiDUPǘQ `E!42H0\*#U,SVlli&5mq-YHC#5Ǒ,꙱D$=X"SYb'kA/ѵZKɔz0[ 6Nm13.P`\v~ެ;Օe4`c\RXMa$HL-( -3&*KƆxStjxlLb2I[/*Iu^h0Y66$cNnp[ 2ñ֒9ōA3@, pt..{*3K@Iާ~ 9` =\[}5 J[ XPk9dg7yȟ~>{_fWAvBX^+!RDN )/EbU g3~,usx0?%]&9왜:rٯ8jH7^2E,+b;5mC̛9vμkpqQp9F~ LYދhӧX]ͤQ(- e/^T mbS9*Z/P.Ű(C\x%r m9 ,%TE,10 A{0c^ڙ%k#@>‚F9/|Zg킄ǥ6_}慄 x\ӱ ٩BB.#eu 쎷 Slz'/z]8Kf'W{􊛯=q/z)H9y;/Gm4OoFw qǘÏ1%-ϟ(gLs%?/tqK8 -wu7ox?ᡫ\o_Yq{w;_KOK̟7.yk^ͼO#랖C?»^{0K~xg_bN,wP<⟞'-^13<,DeGӖ8a넡ygbT{ZxlO Y;BRV&6ҶhRWU"{rZ9&u!uWKN~e_i1\*Fgsq9#G/=e딡L Ո37nQ!IPVcFL8qKE "+F[<"-Ǯ(++0 ,hn*hdL$a WW]kr;*NKƣ(OLƧ tc\Sb}AP*b'PTSXԎ@B[Qa"(m1VQXCa<1AzlH2k$!&>Oԍ|'7pSN滸Ηg?L44ӥz_W1;4ڪ Q$rHb%)G $w.vl0%uL<]e0}PItkifX\arHR_+]o1.,cURi(>z\熀y3Yڎ.Ypvvr-8`ʼn[y۔xტin&pZ ġ._,k }cP֡f5jKe^ {TD Ua!|ǤYe'kD e,J"TQ{t͇b+X1\1Ѿ0ȡ:dn0SܳyG( >3"{ m^?ďOܳ/\|_Ή '_Roq}_=|OO̯;<t8F~G?vk'?/]/0o~i'_Ļ{x~wg9-15n>?yWevyo#?0R}'|Ͽ߾@|˛B˽x`ZN]IO"ϼ>;.(n|]v~̈́jZGAGBj[$qx/ GEJ%OAFS&MsS3(F FzWJ$]\5U%lHE[DZ^[.ᝡmmiGGLtR`r^]t~_Fc(˄"4順>DfYp'%t9Džy'@`:<~9S(7O]& \8h)*˦S`s:!U0+˂jF\&3e( ϹMUdaʪ`:oQhNR{aW}Y;?MKRiK)"zGzZ[m Aز H R@1ĤDPJX:vk#JYY튇O]QVOtǛy??{Pg?#|[>y~龧mR/XPYR䜰ڮa1Eqrc ZtDD]Rջ$k9q|;dlAZ҅"`*MhM5=Ox7߈~i}>泯Eϡ34G.}O:_ |ßm/37|J&Ѷ|,2̓>˛^ ~'.N7pKoF_]>%_x1w9>q<~aJ_7.>fԩd4R%7W~51_7J^zsϡ>^L?p3;{'?1[{/3lg)R?ٝ,K2&ɓLCP- (bE;٤VCR&;zIdz@YJE U$!DaUNEOxf|j4FX3,u.c{M@Y*꺣iVeˈ|Ez^\RCYYtOV ٣ز{)Tbq{+7 #, xR?d%)c,FE 0FYQ3:'P բ0#+䳭*THVm .(Sn:8qq1jBt8 xXЪBn2M5 5d q^*$꾋#)d+0#Ɔq^.A^8u`7-XХk׃:("bAa뒔Űu;i=Qοbe1&4Nv ͕mW>A|qlL˩ n*ஓ%hC R'h}hk7fz. 2f֮P}m'4s;j]O??G&mCb](^ua7*7~'%hUK׶0 C@%s4xONT9CSJFVM:FTbcjiFRYe)|VDU)c*))י1hm̿"X(ޮT#xLzPƠ45VXru=(KOlDR2lm0ʽhf¼ P98%N)gCVA]~X YO.epRBeEa:*7ĵ}ʨ0s͵@#dKPV$+`c:bs+ع"}^{hJ<:)9nK2d KA =skIrΰšH5X鹝!<.CbՔdB_œW~{ݨGhxd\ 23P&I:άs$f䢒Jq )K%BgZ/[Tucf"b +}1?DB$fI$};ad+If4{҅miY3cحM%ԴUxMeaA{y4#X%dZ JWW]!o5! -K^:mV4{_\JSs Y1K:Q訇KkEkc +(PuN>_k;9;6c7aݜe ˍҖ=U oҴ mv=v&3 x;v_MX#qxQ4ӷgaZ8u+<8:S?y/v0#Nh` kyRM_:OI# 9tC ) 8麀D9ҹ^elE(㤄% Vj !a#JyuRdH VJt#%'&sRR7%iP|GXd{}Dk*f˯+XӃe 5xze蜄EA%QOCW@X{1،,ZfW"_`F cʿ ʹKtn5b{H1҇Y+w0ƀC9mʟ,0Ds!`jS2o(Z 3ՐCB!XBH+^|.\2wtZxHd(%6{B "LC.@BUx<:*Ze.Pkb凌hZ)TP\-y\m/uO)ȹUmҸbDi$]O=Soi]˼/A RAmsqg9` h>1I% ED8?E+λ @D!e뙺и.5ݜfͅf\)j PKkJI޽Geٯ-{q"iglU@`XB =30c=O*ukvAmcgZLURHn\k= %2*F8|Ǥ0-4eô12L3 o*N"Ip{65o~~m8V&׉ '?y*eٗ]Kyv)BYA&80Um+u=hz+%2Y+e&Q5U!(B!*ҦZ5ܫ\똻dkrCٖ>ڮC@j%4F*M$w%ߕy0WJؼ PP08'R R y" $'=9/]ȟ6kV*q@#`iXX]]sQ f/ )%r(e@ɉdEy30U6601Fj\PHL14$agu>1_nɔYp|P>(?Ǹ"< dIf i`nrI=zJTe Bk2[XE^{5=u, OP2&j%ɇ+2Psbq'WdMd2-TEKZRw5݌zGmbFGt^"Upzox8PGώנ%qښQQј&:CiKu62;h[C}åf06# #=F+c%*acqH'@Χp i;R?ƖB Te%nA[T h CL)Q>6}ERT~TE؏iʆ0/挪1)[Mvwit+`-,]4#XHex;vil_}pE47zOForOF2:G4#E3 Z"bHchd/KV[*{02ޣrزF0ڞ-Pj鞣Qb4Ry@!0HJb蔲89N2`; egMe^^-.P|Oj`8Z@loUJ׮J ,PĈ-0/DN?iZ顋8EF {G+nsBE5,єy;ƾL=A^8),],څ2 mK-;EșfؠbjE\P*b9'u>xȠN2.I sgP6VφbX]clml<7?Ǎ3-PW(W\hIّQQ1.LFc&1zA肀9@'l9ގ]jO+$jm9/g?pvo'#CM7P ^V+?Oq y,'r+~GymR'?]]GĤB@a%ܛ+)$c IDATՓse5:KM{eמHZ(G3pkr師$mH'97;}rCS cr ۸w)E]4 YH:DZ&%|tu 8po7 e`xOylp > c!-+DLb-^4:)b* */tC0K$5 &GU(ХuN])_? hEMcm貔8 4ͨC[x3f2i)uJT1Ŧ /uB;mVTFmA} ɥ&Ks`WWyÉ̲ifݜws݂@K-8ls^σּu#S1-X]PfRVL$ ;_F!eӐ~* 0V}>!eOLj%@iiѥPH yGR*'Ty>N(TaXmyXJC"50BX&eVw_/To9`yɫxt;S>'I'yq}yV&#W/c[Ǫ wRn`ܷwEÅVi?}Ga9żż[k#xkk<pl1/y9~7SW3/|ueyza^b:}}qhw㆗}?_:'?f1 '76%R̮KhӢT@&F?E&41h2bfb'g:0asȼme4ֻ<2weiZe**[b-NdR#F6^ƅc2㦤rEZv؅(A+G}x/"WIa3bX )5Y.1Tm&>6kWȅg:%$D}71(M),Li H2i} k> YT}x/U>/g }#g/armկ)~*wzLsG#"}/{=S>p q|KNq緾r Gm<'2oo1|q6ps)~˷?wo07p3p掗7g9}*&Y _nq7o~֗Oy/rOy/<ٿЛ).{'?>x^98ЮO"@j@4,!Tev§. f!/+I^ujb=4>KT3fğ`(?)u1ÊG'F"K}|<'Lɼnq!T)_[ ',i'[lxʊ4T[HoDZ]}]@f(C`Ã:歀m9Ȃ n8]Ӟ_Ir{Wa(KUY&! + 2RMQ4MGL:NX!8.]jYxV~#_!Ule]`Cq 8qM0j+ʮ dzNl.z'A-]OTap[e|PQ~W1$sGg0х,*sq!<\mp5_.QGk5wлf!ɰ: eEHJX%!`N@VZa* *tṛv ?|~[y{}_\o~]OGRw7=/Wя9o^Zy7>_׼$.~y{x xn'C@wo?Y~#7ز{A>~'Q˻獼-k2{_}?\VkA~_Z]oT >|}o⛀C͟5s(S;FG1q zs2H&&YƯAO )'ƛ5cfaw u6'6F6WEXE N@JQpڌ40^t?o|;O]Fp1ܲ}-j2(~9R`g'J'ߪGNIOՕU~c*ƣksҢ9M tq |!m Ϻg߱MYs2!{3#>x-9}9w\ur%+w iӨ£R)Cǀ : *9R8U/02=79}SiCS b,Rc g2_0v׸|bzO ' š=fRf=ԫW[n> @%iQn'f]'R;&n¸`ܲ: O9t4iHƕ(Mx 2hxZKX_bo&$fd!sWVZ sɊA6.DŽV,kE*Oţn)2s`M;VT^ c f}Z,Bl<vݦ~ ĉ7GڞRiloZW1;t8s?khm1f&a#10 Ub.4MX ΅b^[F ^XX W WSkhT0ݐR*-Ue=+NlmY-'NeJ5+RMLİ{$q"usmC3q. O<)ӓS[cF'F+iIUbbTPbsRt5u|!>9Ce+J3e=3tp!rZ;\P0xTH9'e7௏;j,<%]V#A^qyҫ͓S i&l؜l2.$as]NݬsP_?̎"TI 09F1d20e9*+2gc-eUd5 Z=׸f:u}{}Rf@3X}cKY;ˀuڨޙnۺ33l99>vhjq9$λ9=vxt;q@G"g098>c'LŌ!}4?y;Oi~zM8y Jwڶj!8B J b4ywJ%>² dd%ˠ}Z6>2 oYSӐ'Q:/_Y Kc6/tc=KMB EN$mVVUDF#j_hPa2%(0-?6{b)xΔ)sBb_ѐVt)+MWs D6ٶ<:$,]hە i,`OJ33+4%L\mttVgvMWBȾ%KrJŔa>) AI>JRߺ,c>kb8u#ܸvYjTiKƣȯ&%LR(t%At%b,d "Lve]G2'a3C%a$*26khG=f跾'z JjWr^{95:ɬ9ddGh4wS{}qݹ*)h#{41GNmE[R1C*ѯ7A2={SCx:Z.xj4Q~; <@!....բ'Ŵ^F9d Aͦ1)K`J&Fx|\$egҥ2a}&裀ZCYqrvuR49S@LzOuְ93π.}y&?ER75eSP61RKB ʄ2*be!FH&$Ӵ1&PrUi%*,T$C"ILA,]c'ǬAxq6|<֦>k1R(%nĉ 6 ńҊ5>ʐDz:C骫/^YE^Ǫggo])py=76BP^?$J-a#̖?X$uU |`*e.ˇx]$r$@/{]6ATJ s[Yf݌6Cg_MB1oA_gL[}C-!8qYفHL_h[V؇%+dRn0[]mq=S^ߛ>^S2ޛۖ}wXwQFMi lk(!)E*+ d*J>)U.*!rLq`¢Hϴ;]k}}o[mIg{^C@D?' 097[qJen~h|]g eaӘ}12 !R{+܃PY$*%3gkrߗ{`D1ipf xIho@vc#4aras{S8#mX./ +yPqL"4fau~(3N9:-SKP|I(2Ork$L#* ˛B9z-U@hIw zWpkyw4eU)uQDzg]t܊y7 ,LvFцh^:u:cvvI6N鈱ͲKR2Frf-Gft s)0c~J 0* E|"[-a T23udMA],M/`VT4\Pz݈0Ud$r4a:֨/1 IDAT }e`)GSD8'RVƀs ɘv)պMلF~gFf3O];RkHzƳXlё/Yq"WfCz*V8II06)AY|sbOv چx3,ȕ0k1gA<[rj*Ua*D(MI0Fλ0,Ôь*`+Q\`RĚHL6Z#lrܝye_tW A]Zi&fuz>?c>c4ٍ`JjRLdT#Fafň'$0A )N騇81suzby~P@eTQMiTzj7iUX12Xb3b`DbӨߦ%;5c\cau΁:_mX.L-ϚI5$" li)~XY/v.;"M`VzJpМ1Eq"Q 588RhV]3 c,&Tt(cFSX6QQ%9~ s3\z0^{VkWOP_U崅]3q*aI9{T^$/l> kj?1{ vҺC 6If]oySftvN ,GVLFCVzp6G3A+͉;!N1и.v>}"ߓãr轁e\ Oo!Ax6;[6>1)K-c" y>B"HslEk,X̀2>1eIqȨ 1 z/ ç$sWeOy}=PN7SH>H%o㡱οw{ЗΛV)cPWqޜXYm/-o.Ǝ{֮%"2IЛHH: N5]+M#@j4br<* ,uv hAlK#G,gjW*.ʲDI1!lPuv5ۻjF6}`zku:Al]-!fS0~&V+b*M]O Uejj"WKr)֭8)x1ԲwؘFNU&|-t(є?nd P*4',qd.NybZpHAi0J\CWhHQU.EάVj t?)n.hauШ,SbK"=5<E} ku EAbG l}\A:Ӆ (\=#0Z({C0q}x>dcH~d)j ?0ԞB/= C4՚}S2lGmiC8roީX4{~1ϙ*-:__kR,>OO"SN՗y_;gf=!1|f2o?Zj5E5SSGJ1F"lL CH5#1]gƠ%%躼&4<{A5;A0'KCL9r^a*;kiM7#A 9ḀJ㌿a}uqI^a Z;lx I0u9cgZlT)݂_z8Lu&.(꽬qlJϟ 88A rڪL&;9.2Y.=@1egZ)`zcdjw&("i3.N~k.Лdľ#ɯr8gTDke5RM) N2$v_Q.[{kXBYsX'NgÔszȒ3& `rZʬ^~Dۥrl9KN,N &!"svCnq1\nhW =dƓ +K5l]SVo3XHu=ipԃ0Cj0p>(p7UݭM]t-g<׷SQ:@1 $.8E" tN <8Fkj%*!lJ… `*C(;awoN:qݽ^22Z!̣ek*T35IMY8Bh E떘:b,Q#B'F8a 1 9^ٺi0ʙi k {gupD&VFg4IBX#cpVkFu|9|28|Y T`~j])aGok5Ω% [Xs_dz&8ܔ_A]..D%@z#y,;uux/bNuսL7@DҦ F4F^ÜLdZ>䆁4 sFO:J^[ۇ[PQDA,$ƀsw_g?KE-.1kN{Zb5pR쳖k YvK tđ/"&9"={13u}a)w& `] ]TX/o]CXA2O["P) B ıg]Ð\̆@BkF+빔 LEcJX+w)l qZf5/ '\L%# ؐM d)<ЃedRXe%܍ulr|ar^y.MW2QrZ=2cKЍC۵Rb`Tf.{rQEqDVIbQf#S"<.HmҶ-wˮa1|5LDTk:qHO^~k`TTT0H1 1Z#!e3Ρ8oH# ug<(ۣ:!D`gxs[Z?{Dw}mc%P|ا<,6oo0__^Zя|O?]'9/E#cZ)9rJND28Yz(&L5h"]bQ؈Sj:*,V@ihs}ʌZL'zhy SrΠF+]um+L̘ F.uqm<[ ʁ"ƚ,L)D_㩋HYٌY]bA.,{#61g뜃yfko, wcQlMKr΢s8,lOKQ;\"[T=xv+;'ܾ$+3(dY ;Yz3fP'xvsYH.8Pg X H)g\Dž݀ ~|w^˻vy~Yo=ƅ`x>ƟȒ̔| txqrKG.,[L.o]S|͹>ݧOPNvwؙm3Q>t(7b9e)L0$:*f;[Lwn߹cǙbn]֤0%qkwY>ÀzKw٪-:t,r`{O^~)zk ER3\h9m(tAa-FaN,$l"qu,8\GZiy9!x\gqLU?$g'fU*4vZUԺ)TE,RLa}c"$ az`Z\p){a.zPbnf:x=7ub牧yYMJO\{O3`,= EW_4낮~ &<1j|~7f _1y{C\Gܾ1y佼 o)~'?ecf;7܅ɬͬݙEUQۛazkw]2ݮ(SF`VA E5k1G+?0R%5`ل.*RZvybAؔPTiT4lXJ1gi0[5:44m@uBQuɜ@XLFl1VR F(45\T(L~=bػzRA] Ռz2c{v['\|iygGش\x:̈́ӢaKM)ٹE[naM-E4"v!)lt#n< b29$3is7LY3fjC\E9)`ZjX4dHK/4csBUv*ZdfSOMٝ,qE{G31?^\b>={~[oy?ۿ+\s1͹yr] ď_%ާ{'<3WT(/iGZn/? .LBy]|ٙ.!jvnKg.#TV,-*)ΊVF|+1jK3ROݻĮ-SxHo-w^7pₓ-o}R}G ,(KA`k*o{"e8v؝ӷ@$F!:dI PVEX mPB~Xm)}"it ) )!5]˪]h/NCWպ*&h">8(-ҹaJTZXJSa!!e&:pT}LB$NY␍:̼W+$*JY$fe1W"|K>SkwG? ٷ}+-?ƏyPo7{sݗj% U5 Ck;b(UGQغ{;e$]1Ve{2c:bvV HL<_KfL#VQYsV}\id:p}1ѫuKl*A:Y9!vKT UT,O,[NE6ܿuݳM=cJʢ|GGj` %JE{[U>w㫰 yZ\ D>ƿ?_e(^I=|[ӽ4̷{?,'LOO]Go?o<-IH(ٽb牯;^湻$޷>;C}:U"/5$m؟]GR]bҜA L4uVr΢u!zZSM30^´B5 T ;WgTe^Ys9Vw[8~ \;q6̮v/ U5۪ @ WzW|k^ S5 _CH{Z+PB@)(B;k)RdfK$ Vb/-A x'F]h}s7=4뙭>Tڬ}>i0lLZ:UR$`h1!NQkÙ~(3!J$8V[o=ox>/ߵXul,&*'IIy!'+g@  J -)&/v,-QYB@ZEr-K7)ϥp .q赙W@̡ Q ]he -thKsp.}(uPz;1'4bn @R()KCQO4XsA@ YD IDAT%E5 nlIk iPw1prްGqqosgyZ 6A8,8Ea"mI (OWuFAqj~hHӜ~KVsM#s5ABٝ>3Ol}o+G'Q*[SϠ`ʠB[S x`AszNL(%e$yR.,8>axN) ,b'`>bₓ.`LSa~qz¦hK\QpNp/G' t"jǼxWxO|smhPl")t {E L;_v&ّQI\p f ;V⨙h|^3xMHBkE׈e )Pv.ە"ٚY9cfw؞P@M"l"pQQ ;HەL}`:Y['pl:ʝ]T_-++L9Y#MY, DErat=9|SȽ4eWopKJZߓ@T!߭(+S2+TE-.VXf WWyTH~N`4#sxyjj0_7l#ٺwTy"dE)έ19{ʎk_o15{neRz2h|~lCYNÚ*B(1&&/@O ,LܸMJ@b=z pvfQ`6eF{0)3VywZ mv5DQdc~S`E!ˆ㦱E]!G-xo;S<lw['rDΊ0mIUZ^Ϣa<[ &lFy6;imuEa-(J&'z0ƀ/Dr^a73 7i kڄ֞$yˣ߾ƥ{\ N}-|S٩FElƫ k!""9qYQk"&{'h\K_4ޔxc0:J딨l͞ByYTlf´QNo&yTc5уI1'wKH[u}*1oݛJ'3Nfx3C)Hvjʬ&\R-3u1g@ A\0dvϹu9t G1!`^9! ]v #fi-4bb`V?wMN%(LcVsQQl"fIs 2+#0K%b\C:vykLN?w9~@XUXSmI3 `ܘfĥ{Y>"Xbs/L*R".YuThb6,z\vRsRQ)ipjMPy,u!H3ƣTm#.[◥=L=$K$*Ttt>gYK\ޭ3$w?|gŭta)ssV"4%X. R0ȟap3]Ms\]?ǔT>ի_|*G'FGJ^:|GUv1y!8~'{\4|BxԷ6|UE;tKbm8.%Q>|Qf3M) hK ΈPيń_JJ)){{..X 5cjgń)QDrH=+h,C[ꮰװ$`:|F7<6zia=e(2c1h%=2A9O(RK:ֵ̻MX{ܳd / ܅ N݋.F0@+ɬJ02 91ti`|6VwQ" ^zB.x|_|?Ěot.%|Jt)H#n۲߶4KuCKDBHT)9IP(THbz˛7K'͒vf+bÒۋhSpav~^^˶*zNmͤR",I_DA9l ie-)ow4tZyjml }㮷ُ9΢M %$1 K>zJErB6Ƶ4]üsk 2/H%@k=˻dRߠ,)7.q*$|ryWu)JFLR'~dЗq 17kpLP{sFt:vZVL(xSC|}R/_mJ~/;,'X[C mvlI"i1Stɨœp?agȀ̬%NIִtcJ(h<+EQM(˂vf_yZ4T[+.K?σ |p Gz@Jav.bĀWp.x1qbL^ r0ε3+馎6N9㈃M9{nK-_.bo?ܳG,o|7o x?po'γafjkɬ]ٺk*d>lݖY:Nv0ڑeӪ f.1$vfv ?o -E^?"@*oXb*l]圦e2.Ak`nŋ4kYt% rNrH%1SF` 3uT(ɪKI=17#@9S}a'\E4JM̓~1dnGRE.[Ҵ  њ*.fLʂYU]ʒԆ0,-HZF(RBCa};T_8)c(_ѼvTZ1M}p%ѽ+lm%.^{009{§\gqf巽|i..>#[]l8vPjlCfI)kϕrOƖ5cVn̾Effvw)ڎ.IWS7/-2dsBBiMYԓ%d`K" Q\Uv;w%5SK,, nl8wkSL:"ic(MZN5I:/]v>BXtilB;OW' \8iw/O))Wef6 $ >:AN蘂/LJx:NJӫ 3AEMέDaO:v]ȏ?̷}_ývl9x7_[Ajw>VAXo7ί?9n~l\5+&FJ5ڥ$v¬ 2.V֔j*nEj|l3|񡧚L%դAcx5VKAb BQ2ϼ]`5VرXv9,[1_Nf*I7ˏl fދ!4(ӊzj'ЮZZdKI $ڹ(4E6 킃#Wg~~z{eCb8b.U\p0!wN<{ˎ|7n…mv '4]dwgi\N:$ hKUp-PT5CFdy:~NeR6\IEPΤ T*fWdUbؒi#R[EG eRv4͂|d(Fm4Ӣ`*ٲZkZ1њV$HLb`CMb<єZSE9G7\,hb"E@P>)[7e ZE.[i1{M[`oK EA< ] =9bۑcqpf4:.Vb&5l__7aa"q1/-5 (E98H=֌&$[t]mN.ST`RnQ53SSuUSRb! d)%EEM0I=2@vUfw+7 J;\}B=Wn G>j-ͮ/1xD1IɊ17XLS EazgDQlWf-+[V9mh=S?a"siyPٕ<PS3abRLLIUBrT)GU9pPCXեN=^шڤ0ʐ:^eNLSBBs" Z'?Dׇ,{\؝P!Cu].O9K'hirq0POeCawz;(*P%wݜG-@K8R%H+Rgg,5XhLF+hDzCwF3ĮEhQkM6{~89T!;?{ok[wְ3WUWUs;D2D" #@"_DbA@d)HdBpB "ێcL#lwkx&>}CuWW;{}?um-Ujճ^Ċ1ُԱSM Hg ]ڔ?T*m+B*w>W!$BiHIm{uRZ* EgLE*qd&b Ht|ZK;Oz˞anhL۽yaHPW:HJsq.2B _!9S\;qvv;w^RF.W A3!(DJ*)b*%pJI(h!Ȕ[n[iB9G-fRWLnV51p+K҄9?h!k][qQ);esARJO MiJ: DzeD9+ˣ8 cE PB%~=sk\\xzy9gZLZKu5fɲ[r^ o>CN'z?'.Vj:s{_.c%y!\OoømSԟlo{>i#u 2zu$crcѶܪIpQֹ,.0;ӘY wύ;IS} -ۀ.*+)tAYI 2B Tq~vh:ZӲZ"c1 IDAT#P| &tEF+@jSǹN];ů q8b x)Iq$ZTQ^b"运)scSXD9pN> my|Ĺ=z7.:AU(xmT QlFX*Mqax1DոAC_m6V%u쵳Br"#PB|oP Ub='ZI@*9lgI~[jE_غoA]dxDtCB#Ӛ 2uIv {\v>nnQv@ %ubc5J)|`ȒElxQz)e 6fd=!X_#&9\Q@ L9->o!Q؉h%Zk!5%]`I0"+;z@`BJv5ո89ye%8\sRi&q{KS4|_moOa.wkd@4QI4 "*ƪ[g0HDw:ғ_."Ht;k``"PGВE*t&:mEZ #QDؕao\6l%d:CXaee~В,}vqdYuR"^X3`T8>v C. яePdY.#Z/Rc{ha ޯ~MoV ] rJ8VLF ' "0*!(Cޚ!.*)_~9ʛ7)ЇU ˢ4z|1q/%Çir) %vPS !9#J]ecmb(@&o8^eֈ"GyO҇*V%(gRv**PnmYՎpgbxy]ZrTJJ],23YNG]%-Rj?!D9P9dAfq̘_skxо3@ΗRD&H M ZK8@cwtEbaiXo]YLD]o,vp Եw]S:ׁCerzƤ(tI yo),Ei5x-#ȍ48J]r.l hE>34V-St 1tA8$\>P^[BuW ]񮌮kY2lVک}+ ⊻Oacɞ$ږE2E  qN>aX1ޟ>tźwM(OkU@oIny;~'I6޺K ww†+uFgdi{2c&s|mc ܦp<l8",u >PSM("R\p޷xb*ܒ;g:Y "z&2L,rS`"`N ;C@Ϫ~ f|(u*KdQ d6T*)FF;{Ŝ/8,ggjb y6ɑO[֔ENY %V(&`?oıPD%fDB@},{TS>it2x~ya˳7=L l\!@ U%f79.rXI'Zڦla{=^9}{$_6tD)`!"+Y\e2ٺEX]Wj\wmܽ&ɜڵHS1)7n`B슷}lV`t??b QA{Ecd H1}6\x a':ǃ1dq$}R$ #vtj.b/lFbw` V9y'Aq[dYluxĴ%1C$ubTw_p?3V@ʶJ T^f9鹣.~2ZST*8);-!4Xږ4~IӮXW8߼-07Qby+7B`FeީKvά8x^Dq&EslȲDyEJKd O&ȾGS2qb豒 uyN9Pf z:jMstc=حLsɓ#I~励P@^p`+ŵc`J)T(Y:'l@w RU:{NC*2HPUN 4[ޱR.# oT%B19z٧=ҳ/:grOQ@Fu-+K2U^&@e7 Irk >A$Eq?R.* X!Tu:@"6\_+`w5ƻ;sJ]Rvh|Bt(Y%Jf_{Vn\-a3) 2umVx)! DZ/r!EB iamu^eӉXM*hʟH~1!ƈgbtmZo`w۾$L.d0h,#ˊI<nEN'n`0#[=tCAK%)dHːXlM4{m*%)qf z&dIkTU!lP  7\, i߶ ^C",`]z|rYF=RRL'Ud "J*RSHI&O!a4oa,UJ aoY? d| 3UnFI@V#Rg #$_lG*XoK,1%-ԪkT2nAzBDd# >* TH,V*:GJ;S9٫ДqvG[vkcmX:B( cط7d<6 CfnʠeSA$GIV O"lI#{u``<^&-+NR Ef9m'=,3\^8Vlo!nӎXҬA\0m9"n K'ul]r[(BmR£>%#̻z]QBwD-uQQȘ8u#BD 9zĖg7Za2C^rX;zxT']܉Z1Or۠͘nˆT`JIOžVҏbse=>  + xp]ոvWj@-EV96+Dژ)s-1(]nMucغG Y ȐRs)2D>Ɇ HӮz2lKӗ:i('F9$P)Za_dq~|sݵ: $ !2zL_&?[H~ Al:u`=֐:}RM`΍RFkL SwbO b=[ɛ{!tx_`l1 cWES!IqeZ1sU\R[!O"4׾3>aϿ57iL|">x~[,0`xWi9q]R#R&7=<>>(3BX6U ؅=p#X.,Wtgg.c0,R GA )wu `XA'ZM "+z1Rɝx~#K{dYӿKlAa#,d;I]bdR!!f%]b_DFPלu9[ K>bG;T M )p]2-TP~5@a:9Y;%[H) B<.,N5[ 0c*_xRܼV7q~d îrI 'VBH]ְK [oSIez- P\ (UAqD@!J SI9/ &{q'i@y57QȤO{? ArSݕ*.]ɗ{Y>@cXJ˘cN+YeKf߇ZKg;3\Jg&.7VS|]ݕj|W''[;Qc>mY>k_}~s3>O|χ_;磕7ޟ,/~_orc<,k*pSC(tE !=[mO*w8QZRq_ҺKsV/xD-@]z"eY}3 ۖ:$U>#k4g>ϩڒ_F[VжЬi`H<@˱[":s{ 9jYuK!?#eY60uez䉑uSh7=ζX` ӯ]V~Eݷt3tL3'p r("0E8emao9/(`?GC?G?ϴ\vd~1mKwrBߢ{ W^aկ>|#V*vxy{#+ ]Wd]U) >z匡oZ,8c}o\?=‹ᰊaށ|]Nb[c,cb^̙9EV!JH#0KlB1ԙ-4^dPS#VppFw>gwBlم˭pIe>&el]EUd* C Nm;)e2suaꍍK6-jYgiLҮ@I?y=6EN$^G8Kε]V`(3 !!BV/x{n<j:eyzW7xk:n4|CTn!9˹w=m\GgSmǿ-͒ԥKE~)|ƴDa%.ƀ-cPF.$ 7bW✋!m2]ƼYऽ85}@?ꦤhL⛴J)6PJm@_w>|8Zk`q>&|}ˢYЛ< ɷlQB]7Yd,*%߭勿ݜq<֨O<`5ӣ*N9>Ww(* 2 IDATO#j}NUXS`tk\Ze;a"a(ް0n?m cI8!ޫhO0`ĉ|uRtdt^mtD W$/^&3,S<)G Ha.J2)P~8=Hے]{s* sf3MUe[i5JH)2 ';noa\BGL_z?|7y+Unܧ?Pu=3Cg]7t-k#k= #d6~/yMdFWnuJX@Xx:-Yw14(= b`DiS)Dx:CW1x/A~.B*Y+=ÌJuLZ>LaE>2uG1bzw}Gwsx@n9Q&`ޑ\m[u^Mw+aNxnHW߻G<4z^g5s[A􄺞evXۡrȑ2/ %lev[2$`TJ01|$>N6)r*.= 4|P-K<P'Mܰ"0)RXP .~¤In?-s^ڵk9ytF#=T,=Eڧ9jX~J- Dd鴆#C j%aFY.#bmcIH@/nL1TJR7#{ƞVgXB+fA6!-pA=OۇAsXt֍̗C"}61}ݣ{od)2ϑZ#lY"eяfK9vKj;;/|Yų)4Dn7n09??mWx^g/[)͘)C*-̎gvt|>vlI6uؕguQXTU,UJG{mǘ؉K:XejϜp<$Iy@.M+2)]8=_!YaAHC["|PܭLC閔IvhF~vͺĖyaI@YeZR ic 闿B~pk[/Hqx*X6_Hkm_Ke21vR:l5 Uqk*KLӠ瘶E5kq]PU9Xg:jjanD6=HsG!!ː2I TUᵆ$ y1f79}xz3C8vO8:R jņ`@%B)\gg=lڇya!7?9}&8Azi;ZDΤmX>ȼ?=J(i{Nyǝ=Gk.(1z .&&|:޶-Mp֜јZF@:¸;pk7]uW /~O~R(*g#߹p:Ї/O~$;)Y.sDry? ɛz8C?S|ߏկ L0',?c>_{>4}?/;_?&p'>/srӟQOu|c_ήVkwtNhӒ\E? (/T1lBeQ%*t MEQ̨ٗ $yYSYxQT>*]P8/zP=R.葡RdH%Fa,Lg:XR盐zZQ**ɭ//_ m@Y, XƐkk<#X&La,v!DV3M!d¤k9Jn< kpXM{;\_r4WԵ,U OQ#Y٭.PV2VNĩF!J;t!I?[:c< AU̹v'AkD!u2nX:dyj'6ozz&!D˷!LGX6 ZTCt%%rd~\1OwA.g%e$RQypeNREQ!i4 m [Cce1ߚ#)zB]獻+|؎wYܾ}2e4~x:>AAM佱 ]1`:2t'fOȂG!`]Q cͅ_"l$o5 u.zZr>̜E80 !{ ]qWW Y _;yr-kܻoK_O|T/WYšGGԟ+O 3s_?_^s3q5E?W_U,}ԟK?mvs〉^7(7s(YjJd*ɊY޶t.NUuͤw,;sQ ۜqį"[,A$٥)<']dMJޘ- "K%A(& Dv7H$kV}9ǰjJV+zcu$ c-C^-]،B+5u)Es/Aҷ֞ɤ ݀:duMl]sn^ osw@3f7nr}~K9G53M] JPzvY.偮 ghۀp!B%*#tmX腱?Z^Ha4Ov1gx68<>(fGL&׷Y݄?{1%z&vԯ[Q3<@;ǝU1Rm]$)!2dyNQרa$۷Yu;.lB)tYfU.KdY" eokỎ~?=;=E Sn~q6NhݲȌ5s#\bld/6 Tr+4o@ߺbɃvkG`YD__ fĝ}GZ nMzoY%~y?W;q-)(m|ps"Xk=Yf3ܸ֫78%R!w\e)j%*$Jh!Vn6^$s[MkڱXrp=} iLˢ_D+/YyXR\K:%zs'iŇ@7hEK%Od'Aލl]6#2uG]ob, iTO^ "c] OЁ,6ګvΧ?u yow1׿OW}W/} |ÝW<|~}{?9>W9zw ˇtX*(4"I\kX5v3)a".#WPc bcAVCVL+bPcpJn] RN**&KV]@᷋>}ncAw@k1qB-mkS6x45n:3Ϸ9(Ĝ. ;_/I쫛rEU &ӂ$gDGiQ8mhC-YMNE6ɘəraXU8>?:&q񻯌 ^衿sc+tWSRy]c%"^`Z ,{^9Ypgݟsڝq֞r-h q \Q՗Cov']r=`< Llvc`0C]H7ϧ!WG9| \Y:=uާ>- ;V̊ucq @:ۀ@&i%צ3i !ut:NΩɀimfD^5BxTYiA CGy>\CtlOgZq֝vxfP'.yFD`J H;读W ؽn\߼ýK [o9r47?}kɯ|=zȉUmc#jJKjt-(x#E!iQ9T@sU)5+R(>6S3>D_wRDCHͤ -ХP uO6+Hr>ѰܲLa;s 5c}oGSgЮY)";7+sO\g%(kl^RL2ɤ`6Ե3&M]g@5#X}uIYu('*WFpe>2%O kvR&^T!wN0 13+aN!+faEe|דlb\JaTRp[, c~zONe1E)3tQCGrcqHw1\ <&WzcB,r6Ȟ+߿cZ`xڞj2<:"*-WL?Q^'TnQݸA>2E,8LOdWv;}-O_b%g-Ws\~N1AHe{,B jy &S'^byq,!s,SNNStX&4Jn/-LCh;17ؠ Mb>&RJd4`(^ Yp2J7OȴąKs \Dz[,rh'-1e1"#}Pv+\:jֶ6-[A*qc`[+v)MRұWoKmfaSn!~jt5ݻ;4C</̪P}ݟ}Q^Byur`:(J*1KlDŽLuD@q@ dN6WAl_#cgzEVJ'MLF^>vButm{U4Uea21H,^yR/Z8+i@ǩznpD[:ɜIn事dEL8DͰb[6d *wFѝCpKo^%\ۭb RJAbZtSw#tiDK & ,)0N֕tMC{>;=>>&ݽ9=_MKLlɋhBp5(hRS5jhbLp61eי +k^T7*dw]G{p@󵯱×joMtЪ$zٓ'<|bBt< =xöK+u g?x^x]vS6sP @F"O1(g|4'?ϐKZBS %=D盬 |H 53|L)}-}ZIMb$Y(4/]bnB@"(v3_ؿ96SXẄ́KƱL{B]|zT$ާw&5%D#; R0mKԮ mJ2t@Aήqy=݇5[>|Nwdz 'GTZ[[\/dAHyaWɤh!PZN6A NTZH_7|5KD_<-ndrm6ggo %Z"y%q,LCIؘqKR闿ֈSO#VyZafo)Dڭ/f~ŭT6:nK4t=$,2rW(RHEq!X?| K ulÆֵ:˪YK;7Dԏ'B$A{($Lw I! Je(tt!ymphRM]\sR*I(4uцnd! Z(dX3BfZXO!+n5oi^Bf2;׳u^ :z\e_ezYֶ,geV ZWHYbE Pa~_ 7S@ptc>HJ'?"^qbb+ADw&Zb~.grwR\b-̓ʏ; $0]0w.w}~:ޗ<*:hEQ$u3FS.I 7JcdrLkFM=9ネɐR2jBPUCW4FgPa͞7aRR =ѥ)oEҾg*2|u 4ɔ*FyKb%m`'1s*@SD\F OWd`K.v-9ZRUv 6ځ)C&Ŷ$i* SqʰS]n3R#FzD LS_Dq )Х.I|3k[r؉ NmɝXo0C۾] 8rk I KgCG9ySWu@\zƮlu-u1#ri #ˋ7^{=ĤOiS/3 w>3|P7rЇ9NNEs-9HFvQ@^'>CO&뉽D)J$D`  RFkI |b䒜2d fb,BI׮oM|5Ĩ&Ll8?_xV5fҝax- oe.Q\8e2`c Zo4\v1Zˎ1c}j4hS!D J0(LwhQ7ٺ͉T9+ #b:3T@/RNn\`Q& &uZϫq-qH[̙3;Ǩ+ 6[W5Dz}8I44,M&tU) ,e!LJ" R'YK{lU B*1Czfb92E0ŀ}LUϩۖzi[V!X.;n(9+Sc(:uCLK |uns~ݏQ\\U3,ΰO8W9W9WX[g3B%[,'FSktE H^~ᓄj0[2BCF[NrdvV;U TY`{)2(2RX pl!4Q%y`%/ -ZT4mYއDž)WLT.rz\z=z) KmrcRm1cVx`+*UQBtf6Tg@gCfM@-Pޡ:{O3V!l% &@!_ mVeuE078߭H%ݵz\o'E?/z?Gyء~/c~oy3?,;2؅cW7;Q u݇8e>?hU`u%R(CBd`QFKcAtN"&if1bu1F̨BH'Rg 'eU \ ]!sp Đ3Aؠ4],S6zEVKj3c4ѝ-mh]egRKkmN"TEK%]UiS.j]-iwvO*b}L4ia<- @ S(R9$vJ0D`DȂɴDFQg.3ĤGFGtL1xRZn7bo)cNqJNd6bϙ? ֵ,KɄ|~9t]]SFf3-*Jc:/ p𖁙 b; =ygh@F± 1"-ޝB8Y2E;"54!U`9?暃r-+^= Ą8.NOV2 S3agr[zQ0%*)TV- 2WD\X"`ZLt@o`RUVe3)UA׵eA_uhA\_y0:]k`!/9 ?^`՘(9xu|7?w<~/cOol~.;w~/ 8ⷾz }Կǯן9.-X)S5d1%ڔ8ߠ}5^ ܅KbY!"`t1d2BR)!0Zu@HrƆt""A S()JT2˱_ۜ'o׺&ft[P ^4I|eg^ &swYYJBoSLMQv:େR]Ͳl-#a+nvyV\D(mv'"X4hQJo0n=<><"4MFcLV4!(nbz6-LYbf>guxfYέx}4t6;J6i? Fpk{lXtc|k@n33mJmNOii?fλ, [1mr1)c99U5vF t> JHP!C DN -p-|cTsڠ(3Gop}Xm`1_s{ݻT+Z/0Irqw- F %u{)mj.׶{ Z/)&3'?z[R su -~`]LV}jy ɭ4 B)tfcYQ(t)R\/rrIu]ͨ`"tJcrRH`<]Lq.Z oh6Zu'SR=_rUfy]>Kg:%}0/o:߫wSrO;wvqIh__4#_"zį8 ~^ /W6_q;Nv94 'kwKK:C9Uc.Nn;R+PjNB<ɑR&`tHG?MjOQhFcˮIeLqn-_H B&P/}KƸ,eW(`4câѵ,jV5aTx4?Fa}&8gϺI51RSժ0uJ9Z)S@k{wnm?"9vz+roai I}[>a΄уo9~trZ8hQvG7oss%5s?~yanowsWg<~n݂ UbA7+Zpcwd@K8G$YJ] ")>E("1ꤔdfaZL1jbR:R()d:F㱡6[WHP&dx/,4QS,c=ND6 y{Ե\^::Zy=!ӅF;m0cyzi>>5bE7(1[ou/(*^\^sxnCW>vwUMUmSmRU۔[;o8;!iʼ2ػϤyʃʰyq}&>-ýW'ܽ7 7+GܼY[SPU[ Jd8?;saӧgFO\'0lˀ'"]n=cqH)6~?DP/}{liW+\](fd2a43͘RأߧؿIuz{LDQJs]:غ;?}D߆-1,[(cs,9{=NrZq\6=>@﹖@Y~u^beWSxo0h3Hm^s~$587$Qzk鎎X=z}uUUs3ÅPb<(˴(Hqf}bsuXň˷&-yBh>ySO]gJxvy#kYnPSix rbTC']6s>@}yE- t|!02]%TRh%amK MTAS"- )J"( T٨'H [! nV7*fL̄26cFz2(9HX~Jԩ&4q C7͌):ڮskY%E>hnMV.-ON4"%l_3Y[sDVe}K*^b֯oN]>뮋jػ)TX. Ru8~_{4X6A*BHt۫A݅ے_Z&34v}4`7aLs^Q'WL#fᘩ믳=}=.z2N\/_A>* hTwnޮSZa|7e:V{ӣcNUZ\u9a=OLmy,pۿf3tYtJZJ̦lڇg^kcsZ#dB9FUM9C g! 铼O;͠{O>Ime6["8k/,dmJ `ՎVe!Lc@.L@!1Tƍ`:#Bp`/Kئ}?80S\]bT*P2k=!/%'CHA*]m&z،J QP2R)!UDkJT4O,x/VPSG]ĹU*YBz\Ƥߵ^"Ik=Υ޵%duhI5͘L]I׭V6^{9G((Uif@cƕ=9`~YP B2_\j1Sg;Ht"}ޡcxB< e,d~ow=UA(C]ZlH35cr[$ۯ9v?1fwPlSf$вLqgeyե9hXsȮC{ao-)G'n^&='_@*8k9::+)$\B%=Srͮ+Wm>@7]z.1+mg:EYjP2*GJ]9g/'SDH@TfBxˡ轗F~W$dbؓ =Hנ>,(x>\QNrL`Pu򉑼Z1FhgO2'{W(ww1 b#Tdm̤2 !oE>DMH3 IDATj^yjo/yږ)'O89Y՜j]7/F륌i1UI#wsv`<QU$j0lSkd>Мi?=9}$AOƔ{{x(}eb)PAkCTSJpn"&P Eĕn[>9^)xW1s@q!k TOz!/-aB亂mƋŗ!L7Pz 6!f$t=aZLc=҉tR2 (Ez.鈈QD 8 E@ Æ!Xߦ{(/y`ycP>~P70uiAvߡyW{_x<~glx=2(Q2Ki@SEbBQqN!eD!1q}I-ʘaWD.KR>1`(,,}A顳 =J Vfc.v=QK=38gᯋrѵ\/d&c$\)=|UN#e舥L] !U\qX H}ۃdT5r !<=};NIuW|=w~L>1&A]sW~acH/ ,ݺCg̗KN떓ai3~EU9fUyFF3rX~1'1ȲD{=9y&T((f4C6$끨B^ ч݄@bxNd&1>='@+D:@eyfw~λ8]A"ś;-6엸̰ ?D<^`te/bX8m7XMA/00Re.m \Ptw#}R֔dbfV*bȌXqP"$0{gB\\tH%Qh.| `C|Am2+ր oi[SȌu9.{Avz|m8=;(J6(D!Lbl+iRxp#8H/ȉ uT0Klc-a`Ku-+2cKOH&,Hbc&$J&o͹_kg/]_K#u_Z0cu-c/2'[BgOTf Vh@ׁ 9Ƽ=2ڐ/q_zPC@^PL|qM{6;)5ʘ4SRȏp'Uv~ӌ_{}Klq_Y[.O@Xi|8?Tѭ[iX8=t⸮9]hvm5py56ޱԆiY0њVTHkhֺ6(P10HH Q(CDBJ$P @09 (к=!_w, 9Lv:hJ 7*-12=EzHS3np&k}}:- 4e^uJ\28Iueu[f2JUL2L4F'g{ov$λ$7)ZRTBJ}N'l|5ߍ碧pJO]Kt%vzih]"F"3v)ZReIfbp%=E]l\D:iO!4::O O !#]MYc#f~@X2}KtgdBU`d6ވ\FMS~ư?iv~^{Ѿ^ooz|ѝ>xߠ޽O8##DmKXpZqִ5g6<!E6;:u2WiY0UVFp1|KnޤLRf/8hH֬ ~AeIr)Nw=ca~8 }`4{|bL@Qa\ 747f锦ԉR˔r=J(DJ !ʈC'%wDRrʦ%ϯĨJmYz_]b9wO <>rf=%r^pA]_ne-YJ t=W>ɵנz\q=ǏPU...H2HJ!]* .l %9 $cDf6CGRRI*Y|[1+idm<|I/Cf60P"'/噽 41X^4Ƿ^_oe)1E.\7*?fTR` IF <1Xϗ2dǘ/PTi.~j֠NIQ Bm$@]Kx:41Ե(B-xj}umkGz.1)#`[lzNYb1wpOf{cd:1͘Gʒ(A@'K4I\Brb$vqu}eu=x4H|c?sa(!sSQoR?x@:b!P{b]YKf_ueJmg}Z%(ot\?}2>ıTPjm=p?uE))KIUד@bʇVmOfcAf;CJO'2ŐSACQјSU4M`Gf`2zLmd>wlmyV+ba%U2jZ0Ȝ0zSXʲXtԵ~_.-y/zq@qq.ۜNdƪ$ɘhDUc֫![[ tE\5np%xTUB Ƥ6`؅]9XY(Jc2E(I&u0s.!ޣ}]i>)C. 1ZrYNږ.K/P8Ȭe<K6ى,gٶZ361RJtY)oUuO-u믽Dž$"E ůFƔ9[!=%wvytizxwq᩾y-لV\SҕY"̑,Els>I3Ũ^~7鳗2I8LU{Ml%IgVIe"+L?)$T4 UQRISٕH!r]h|Kkj[r5.9 Ǯq ]C. L}HK85/{ew x_`'B|+z|/ǘ21s:3t6u]s).a CwR 2$ Y(>q1|K2^;?h2(9e]ރ,=f>N}26j$( AY(<J?kG%u)C&^PHW*:dXѨmdft%)f%VdlvfD\Z52a^3+r:UjX.e1hģc'Ǐ)N!>y-]\}-K&ɄxĨwvP1jkz27)SrsxK|{ͷg9e=!Y̘"w1YdOk11U`JdbRi=?Gc_A @.q_sړScCiP1P!!RƈpVH3,'uͪ풷+טc:8}KUȡ!a߂ bƒik-F3mws}6͛ik/W#:]GZBST/yxLuk=%Prbޅs yRTB* F+gGE *4J&9f6 .fP=tGsFekpZ\K9Iy;%C]\ س>PEK8% K,}| Y( C]4X$Q CC.4qfIkgZIU,iM]i[!=SL;b\s"=b]c2Q3m3PMצѹ\hm_nchYtG'GC?ߤ}+gtB2t 7ݺ-70;7ofQ Li4(AB{\ץ +ш%^4M1&BW[)Oduxk]G𞢪OU$nR8?=?COHFqu_%7?=9=xJw|L{||[Pń1 py1:/$a(lI(e3]:2{ȻԽkwyɄ8{wﲽh:Ea|{l4 ]Pk)rݶSx!eIP*'2UmV5jy4#Rd5Ef|oZJ&B{?)`7?( ȒL@\Nqkۈ!3f.F?^%)"*R2 M3%]{"'V<2=c$]Ȧ HP2"H o-V I P(ע߸XRTUjm^R ȟÚIiHeϠD.n9+ʡ۱IA)ui:[hO OO"Ɨi|@t$;flݾ+0 [0{7([YUuJ@21FYNeZ\USl RL5*ܺy BY,9tQPǘBn{b#i1l#q~ˬGmm)n_hu!0?ǞONNpus{^m"YdTad#2oj7m~{8CM=yxMQ$3"Et$AAQY ALFH+%ɖHH(N|z3!>N޽鑪 oߪ}NZbRk8A] v${\O.-cIL&.~"g.^bN:!Jgp5,) d1QfAjL7H:2兀$,du CjllS'.]d (4橩Xl4ֲ>D_ q΅cm2.(s92H4#@Af)XeaT)FFQ˷A.'X6ڠ'l|#g D[±8+gXC%*"sbc?56؊))5 ͂Q4 f,5Mw)BO]djS390~njĥK; ,n3IiA]kReNݪYո+M?]CUWyo.3o^b6`#㱳s<ղt[fX[`'thRπ#2jq5նv- E޵aƨq[hИ {LaQB.*K֭3D8C1͸ }q!ldKƮͰw!KyPZZZ7>sSu+ŴiB:MӸv.02ЧNٵ]X {{yz}O,[`G][Ґ }" JpS3tB$dyN6pmoc'?x:bt0IXKtX||y멧7yPih Rw'},.^:u$u]_IsEv8h|ٶdABmmiJ(Ε ҖMI) @m̩Ms&J9Maq_?P@4 YsbtH$QD pA; 1y-He+x(\=u֢ }OBR :f0kղZ'Ago1HmCЯ%Ƿ,}lڂ>߫Adr Wy25an/{lPTlFUEn(i1 IDAT?6BبW#>8 IT9.@Z6TӐ% lS ,E1R!qzJGœsLGYh|[KFХ}jR{mk-6qΆxǚYCH{+,l[~e'$" eԅ)O*jeSEw 6 `Uj_cl%[לd͜y=q”4&”'zG_2rIB& BT"0v?%UdZ9jj )Nc=o֓C_ +,JA~ӻq8??+[[ü})~WFN87~o"nϟqpTNH)*@uvK'ƴhmط[Xv9߅w]ԀA<ؒ-M9!Avhmzcm-Z5^oQw\e-} CdrX/Q!!֒$Mh{ʢ9ud0)8*4õ18>,/ nU]xY(#`'4lp,X)[_fSc.?Gv4c5^j{e֮^eof{ٹsF"{:`ۈ7udU"#mZ稍vD)$(Z/v@U/ ܢ2d\+)KGEPg8n, PD+{(mv|n?K"xh6iAH9l-ED \w LYFA^eY$idd5UQ8<8<. L]wY{- w޻9crSL;5GdDZ8:)*ɘi{E"pc nY3 g-1B1TIJ-tI$kIJ"}ub#{g1kE݅,a/|MsXPbQϙ 9`V!4hHj7Q]VYR6.gb\ߞ#F;(zN~ gƬCB5dz!krvrty$ #oe@ݪYkW&̪O?~Go<&6֑=q˾y#9xefE5Oe>K<{" |Ƿw[P0FQv֟uM~Q[~p_3.;y?g8;Oxw?V~ꧾ>sG\"j@ܞ?c`$͂tRFʲWm>Rn7=1~Im1iL4Q.)#P[*1,}q~!@A3R!Eȶ> Z(zZhJWeSt*ԞU,dJ{|,`p6Y`efӪ6|0as$hfG׮#Xp]==`<ʑE4ӋBUYcp0sa24;7f{i]bG(֙3\\}zd@ocobأO ;Oa-3ch@G ^A.a 8n? 4_ HFqQ XZcD^f%^`}kl8D*~k bA1RQ}M)DQK,N (z`oGK#Ʉ;p/xRBJ,BIu]- sΟp=W1L2ɼH$ZhP y!l>ʓuJ_XaW`IkS1k2wcΟ E2 3v)\ڻhcI"RGFj PGPiq5 g!oсe>Syq#s͂Ҕ͜(X45u᨜S)Ə~=Gy[bFScƊ`x!~>9g=ʵ:Ϙl\ W2ad7h9_vUj `g|{?c\sI7z˘Wu߬xm_yY<1|/|~0|~~?zۿ,~{oY |_+{~ _9S[v;y~ԍO#Iz x9>kۯ8jg5vȲ< N);=Nr*?.` hJ2a!lR蜈.v;З{z9u98?c7y0IAx\ri}5{j6%;N9wQώx>j&gϲ~fFs_s2zj}~$C=7vv0P,NN:&>DY 4'p9*)cfX>.D#C:pm Fy_Z Ak\h­}u>uWdkL]eeϞe8i([S) Cʣ stwSpځ;zNdߵF;/U#tS/we7$a cf΅kq=3ng 63:x< -XK% .:6B`d:'U"sKycmuR"I} pľtsh& BNqqshlQYi1>)c!I60l6G@) M>0J<墢t_4)ᔙTx=" ̜YUbŐq*os/]Mx1gw>D3zKL{7\sv#`^£ _si(ҷFR+ u4=qzg5Vaȳ;?%p0s|Owe>1G|{FZ<ǯOO>n\}esOq?/~mm^U5$1ZLs{ha(J( (PMdJd =+E@%UXŸ/"@EGxOsaBEveE<_?|EFb ))Z}HE9p@݋>x>M&.b|5q )25ƌ%WSӾa>J>EDih'$P(\R'HDIJ8X#i$2J GRHeAAB*ña:AI0D9hK4dip3M3 C=$:=(d9g1|"h C:_娞Q!1 =v%5dp<}s<_3ݞr~,Y]yK792ˣܟ20$Ou8ɸ_ ؝cȼ\a:Y&vklIK7پBKy:{ln!x<|wQh A2z^ynv,<ٞ[Ys#$ Su-PDwEs݉R |(@{ƭӹJBږߜ 8kʂsٱotr[Z鑋2OK[J9|*%Iq`A'3DWNDכ\8#NtDF,i܈D:lm: j)Yhl?3`g]lZY˚+y\/rqOs<'@]kᣏ_ڵu֘( zoнtM@ȍ +] |e˞{&xN2C3kw.Z.05i£np:{:;`Z;fkWF) M/RO4 >3k,u&5kΡۄD|a=)s`Ѓ$z5+, 7{E`i>nIˢXrȺ}O 0kf,X7H6 0" L9Bhf,b 8g" |EY\JphsdYN",GgBlaxQ^0 >i> ˲.fc|3g ,hS瞧yjWЌ i5uYne*&)U+UIeK}t, l\ B8 }}Nq;=%篱~"kYIuUN9ݣxR˗ ׂdXF;Bn`̦X+R3n9jE*r2##Q]fݪY&v>}O~]̐< ||) =abim2VdS[kEuiLp &fѹ,Q.^V{%.3CuV3d1ZR)R\)SI3I}pPD+Ԇ6855eUQG5 95V踯9W,#Sag&N0'.x\-[@2?Gc.\ Y3Wغ.QN14{{{TmW5~8IZ4 Wxc{X ~Vw(zzgc7kxO=M+CS3lơ|0.,8pN\$4;;!BLj92*E{ElKm2:sF#Tƨ# f̧S@ettC(fٝ^lƀ!!}N[J!Ӱo*ջ@J^$] Udّ×c'Y[ǬMxzu%)}ИJKh"]v-.чE'X{!Ctl\\P]]`A!I?lWQgPtYxӚ>[/8,5g67Z7 !V:ʛ`B'X)c&@`,BrQ0΢)KB乧iN&g&WfVuw k~#\ 97nt-KOοm $kT{l]cWcqtز r<{oqJi8ཋ.v}pwA`WPRe9i^pxkbt>dD9bP腦 =X<Μfi"@jNu؉{\$R2}yqCݘslcKaS B'n꾕=H-Cr|"sװֲ}k*nh=C k=C <ƈMg[8{۱IK[K9gBq4,F1 >2P L!NV>K"'{J!:ۄa\*V6\+h(4 ̨1"JG}XFE Xx.#͎xcvb]. ih >yd ^7 ΄_bLHƤk-MMr8D2C^\0uM=S^|SOapK}!F/.[!= $BgHuOH&dkIJx<;*:tqZpOir6c`zʫ@ǨX}ٺ51{2;G`֤Ҩhs70rړJ!Pyҥn>= ֮.oX^,r8@x:6Z/kQu^,Iu =!PGA{НsC6:$J̇|MHk!i>-";Uxh!AF0Cīdqpнsw)Qm)puC亦IuZرt~~ߝR~;;]Fa60K]//̙ .X.*@+hhTP(tWI M Y5vFQByQ*AI=Uɰp&/,hHW$Yi0O) h/q* Xp1a5haVΡm$w~XoF`GGy˿$~yF 93ϰ}h3q>)Fظp _> nlnuOXKƌ yTU-mOaw{~sQ*-XA6h"kY5c3uKULD`(Ѩ$;`e2.2xmăû&qIʄ5$E'ĶcL iuQpܬg-jLRGϘ#i lFmG!Q猼 *R)4ܥ?hhW "ݥz951y'`70|:2σRݞ|lc A<>$/gP**wyx U ra0D&ap Q޾̵!ĕNTB`pQ/]~E`&(!76E.9LYb˂!, ѓcug;ױ׾hΒѥK\0[v.a.\گquD4TEik34X[Q'ZxT+zNGxb|/,^*Q R& `c]藫407 vk/lyNzYh&LΜeCHԽ[m*GL)Y2+%f<꓏qT3LFjP[9*1^"ExJʐo'@ M/εk/񩻝n?Y $*aV_c>y9_/r˭bώ(qUƗ,MU=gJcb;QZBv/.H6cM071K t. z$Fx4" b]4Z$0w8p~@jB`56fY"z ֒ EuԅE$aXDPH!T`P3 uccN:EYaL*a_crQoՂޥi^ 0'K&֞|!AʎlA؛ӑZ|YBUF&!Q#A"j>cgF$9p P:AEV}^lOH91M|]ӃЗO @]Wlƶ'ϗ\{}q^,ȋWV?;dksO}P@wbtqj"t4belU`=~3ؑ\ح(l<+Y% +TT J/օ^:zŃpԉeB\ S,nq PAvΗ,JN%:M.`yKQuB uJ%Lp'HȒY l^sZh5TH:Ri3gVSvjd8.ȆRS\hD˲5"2urJAy_;y{+T/~_ϟUlUj< B $f>2-_~"l<^{ܬ_㱾ٳX[$ɳ1Y6!MH I2BI2FRJ*Huu\>ED<9Z;v(HiTxR)z*D08kI] u ,RZJ΂$#M,Z6Y4F0 *K0Fc,%M#*A9o91ʚd6J 3nSk]'IMMhdB$Dx޴5\}lm(,U8uZBעo.1y|x_{u[N_`vwbc *7n0o:u)}db BA VIl>H вs'ckşs{{vwd| twLƾ7la,$읏Z9T mx[Ô譎 ]Et sAu DP c X/C\.hE\ }۶@hA᳌Q$IB-Jϓ:IUH|- el`dgcLY2y,~cȺ]4E3uAQ*[(m˝l-[Ec̦SnmsC3${:8cemuG3Q5R>k̝m?cyv$]_cǨ n~ L;Khάi]Mc‰>#qBe0 ߾G^`8 2s";'#D J Q$2=F"M(N5D&(-5:7Ku̸(ZuKKS'aJ? RZDUsx஗zg5VLS<>]@[7Vj>2M=N-kWtHDKr)Zwm#6D4{.2X'czٷ,h}`4MfqZR Esɮ(xЮIK%ZKhV$AJrsD{eS1[sE" k\sSdG:y8.8UHkB=v!~{=(r4z~rl/pgޣTs*;zDBg  ;<%-jho-ݾcp9٦)KJ3{=;-H8. SCp̳J2O;2Q5T ۿwgx^;w \H`Ը%' 澝aR,&eV:q&0Y1hFհ3 {-t!\m|`Tѓ(T[r, ~˅`$p, HA2i4R (aJRI -簱Mظ2>FE'*I6_Qjjހtx<+ɤX}C' %3㪭sAC CT*wu}pߓў;,c6PIL#qFaL3|mhA9uLke.h{d2RFWP'd$6PV SMwj逳s .YsQ=hcf9{FQgYTk9Vʷv'P>Xֻ#?l譭3mws9h{/}?4>8%)ReU'[e_|\ <v,dOf)z17ؿuR ,FU:uBE#>ܚ8 Z#H&ݹNV~;CmNx\U"ZY-{Ҕٳ Qy&;K$7)8<$ !.8+.u)F9&l*!A~s$XR(v]u60k*VZjBl U (hФB" Kɩ,x&2;vprhD0Li"k RZJ/^::CȨ[ӯj|;SqWgL"=%:}( =n|F> xA]0_}gt 9(U`3gv) J[  qM&]$s]s E>-dz(ZuRuyd3xo;9sN+հen0^|]{[̣Lm0"z/e~Ϛ]d`5(cJu=G>™9)ʯ~m^C'..dTt 2uǠdmm`}|8is=;/ލ똪 (޴(u}pfu3|GG4EIeY9o4E [`v?wW74@ JނT-%:ԋ9LamppJ_Ax  u*/;moCtx ]e c- TF2&I[pB}a&ԲNKa/GFN1.`k4SeAϦ̞ {K/XXXo1xs\x\|Ekl ݵHE$k%J ˵|!5mev94Mqmwȝ&G ޛIvgzYUYUh @67$A%7Q Q̄B1[pأH1l"4LH2"5H")R M4{WWUVn?ν7MHtdTuV̛y3o!7/ᮿa{w331vϳӌ$g5bH5BfIYFt.,]P'd?/A*|>NЦ(Ʉ+ϿK1ّ!><u/i3./Lj[ sIk2zȲDPYF#kp~ W`':HjL(9zOǬٯ刑H|0u}ȕ㊙Hռ|؞4*wZn OBcꩋKxL.pGw!JIQB,@UԚ[DB,ip2glܵb%d_R&[2BiU:dcrucf;s$(yPu KSdز&&m!QUѹޓ09ӗ8w(gJeT'{ٌp|A*˒ ] 1L3f1[]/݋&t)8 ᖪރa8~Y(iKaU1eU_x 1= vv`Q#TKK ŌHm&hr괆ekJMEHh~@'Ϲv"^xhtKh겦 ux1#IM'WF7EAv$B>X_G%((h q7v?2~iB]ϮͰ p,E/eX2ܵN"IeA]l:@Փ S!R9|Sj0usByN%;lp`1)2Ҵ-ҩW7%i[E-4BҦdxkO2VU8JNԾH1=gEmߒ6s;4`#Ń*] tKuVPX.pwo/KK EQґIG !r k&?q.cr8PZ}$g̤·.6,8Aw(.bZTA sGl'c7~a:nJWl,fB7BP"xZDZNؽNvoNNz,GgߘSekmt}{{ofEΘ& d3&PǏ'e x+]t._޹ٹm#`f sއA*D֚:i͵@'s\:w662ԽPD1rL!Ꮈvק|Yzz#bĦ;Fqʇxk}s7o uZhDI7S2IeyT;kszִaL!Dl#ry""&T:51EZOH3мozb FYN E&رN)3gШs;΍#u.8u`;sM=+.[pQ,*hn5V0 ޯ^Wc5^Ĩ+5 IDATW.m.]",ާKQX|hUu J&NP7=j 㒅=B` SBHCRJO<ԃNރ"E)7 gZƮw/\[%$P\5(9UŌaAͶq1[-}ia4.JAEApa<ux:cd4c>gZ-Bڃ'),kW"~@i9%fZ<…:A {D%zF|+'3b#=|^ LIĵ\w:t&\>9G!GS2 R JV[ٵ}vιO+Eĥ㊮JC&പY;'Ֆ掖O0 @~w刭MozI?}Qc xhv6&)ʲ$Ӻ鷒 ^d jUDlre8* ZCCT*{U$A'HS FԹFK'\:wM=ur>N3DٽƗt˟6(\u:KmDBPXFP9ďeY}ìmd]a(]ԅy4(R׮ٌ.zRrڟqBTZZ9B6Ik}%Uu: 0vO=5v%8|ש=r˰pl!.D|.BkDR*)/m4lFL-3kvt ѐ%kb +n5VUvrN!zq:A?]eC-}1F~?=Rs]V`7-Gvv5yk }k(˚^ojAYfѪB>Qb4PBVH!N 3HY̷DG :Rt <ДJFh嬹}jâ HKBL8 PSGkNR:<^nHdN_q8G`}H &u 6q>h25(!ț *052QǎO"+KtmuL`^S|U=&)]ƻ/ΧIƉll > +&X\0~L&7b[@4{}QdSʐ::['d.t O*zkjcTs=ޕ8{3rS׆/{KJ*ECQ@B sܬ-b@ sTg~l]d(0YqtDgrg^aCTG6/25pLj,>j^9(zbF_cxsΓM[k>B:tz?Hu صPpd;IW t&'e|GX,SMtmKcRBԷ BۄM?]@ݬV;#DZ i##&,T:uj/O=;Êz7'u>6Y:b{[jqf3;`PlEQqޒي,cmEBV}!Ƃˮ ɤv"}(A8ho-ds#7/Zb}p"&׹85 ?sld9'>6|seŻɓw>t~ 1b먲HM2ŶY+{=lTLJ(p 2Ȕk!-8cPB !@׻ǻ7[[ 3[@Z0XAlQ:5NYXW2޹qwd}Z]bo^.DBq;י&7OZo䥉@6RdOLODcR8OS1HP qŞgG3fm$(q:bHv$!Vd-m:p}N@*ElQN2ҲP8$LvnBD $Z&o Ion[[CgQLrCuN" MmOSh c7K"F U[2qglٔY҆-TK'.?WϟstAV !ŏwEnq4H-=.X-c>DM,FEA񷓿]Ǐ/Pۆ yws]t$Ե?,QEAl.6F@ySF]|0pMHaL%g\ɸD6 R.3IW[U.Kvx#Y| \tou)iUUTQ'ݾsM6R|#؅@0 M9uxLeSނ #U cnRU6jƫ|M_-~_k7~giG! ?-K_H/~v_z=:8Fc{g9ϱO-y3я7?0DϷyKog_=CϷrv=8(u?yi|iT՜˗/2X/v^?}O}eN5LJ([q]g=||IjB, !'r"9!j >oaTc\Ҭj>2/p!0q1d/,$魯121vGgMFBJ}NK1l[zlBnٚI%*R2Irmb^g1޹tUb' K.^ jf9k(̄!"(ַX 1S{CMosH/ݯŀZ\ Lm8O@(KF0O۷6.N4P< ;MyTznԺ[]r3<{w3LM=31G(1f7FI1o#c'ѥV)E^՜asϠP5',8}S?O`$ "'OY%J"DA.,.*3BY%Z 8'YeFrRRkL`}cg Ƙ6\\ӂ6"0QK)Rn6pXE^:͒=v>Ǚ<M_; 4}Y v{ z4)ƀmJA]^!EN>$_$s|t_R7ԈPLeyV%y9 {yIY({{{\p.Yț/ &H(є6auTh*e5%P8斡Zŋ\?w[UH kNax0wxUg *f֋*g0#"[fe>o>؝[<$xþ)9ybBeͤ1El$YQkw=齺}Eo|udEqZw;7PpCڢx'( 7,]nsYlp䐞t}>৳cA.+J RCAާP9d+\KiPa!:#+mt!ATH {եrrO0%{" ])g +ovuλ4jOq:7.M'.YYtzq8cSq.ruL V6s|; K-&g ?*/qKI >7s~F/I?Q~>q-mSWXQ|o>3*-Ȳzd&e9JhIR*Zm@0sl/)(E5>:bS&3MoGQD$$+)0Zd=A301%z MHttG肬1 Ch'=% ܌9SxQlp@sK:)t`) ,gi.^W >{v5mQg)77<¾ؿJPh4^s^KVMn[S8vQgd)^exX `),01vK0RN @%EDEAgnn(sy~6H%:@Ѿ#ZE.^2%K)ֺ\6?Ւ§_K$T$xIULp kZIor|-P1SC2!RVo\u}gnXK#[ڜ+MOpjd1ٷ clU38뛛 EIf/G,g76R]uw^fb0a#Reٟvð=#9ɤ&[FԆ:Jʜc_c|]fJ=c2tũ~#E*2)t)@5Q)` &BeSa4>$˲)iDSrQY!Zq MVI _likx٬QWX+Zڤ@w +3دkBYE1 a?F=[r<MɊsNJ7zl a@lԧ;~7 R@51"k8Kg"'&OddBv~Nkk]Y @F]XL k0haU]".^t^"%JeB$%ը2B0vO>Ϗh{uZ,9Z ;R1LAm"V7Y.$ IDATs2"7.M8m@ 䲁kTڸp {ߏβxI)9rB Y$kLw7}9ƣ9c=~Mg1%̙;f? )= N,.ddfLfd"d*e-L)%|}H1cfSHc?ѹX`ceH&+;2gQmHj`S?3c, [ߔ^~:$ \0uORjjn5Vc5#/p`WW[}ʼ$ h]TS**I#=dιiDcr5\KwI*G0ZC$T=t!ĂqBUI u~BU{dCE&'Ք8s%误Sb0XcQfrJ;AԳI$vhg3;@E(n?tB)t~!ːkk?7ܔ&׹Y@-Q qdbmxbaB\CP5~PoRIq@`eKw Wz W׷{ tpdZ BF&̩MֲW[K1]TBs}diSA ^)0ԾQRj;}"U8v6z%q\=􊜼(z1߿֊B,[R, !3zjA(dрPh4t9qmyL!vJOS$KK'=R֞ xq¡zHlLp5f^UTuΆHeB'\j.~’R*V+[l?C!2yuZg@N^p?b2FY}ɳ "2$c(&. BlIBqL]WD1(9#(Aa1&rD;b>78_8f6e:7ci.%E?9!N *b aRFGngjן/O#o7S')٠ t\MI.R$lldB?LPgϲy}IZ`Óvw[1RCWZ yj!;P٪q4eIB\}!{*]Ⱥ eb"uM&ZGf{tGAcHy;g79$[ o?SSδn0vm0Q`{ i%W؞kfvfHTQ%u'8f.桔" gcY+$3|BB0agA޸ 5tzV_zu.zbXoqߝw[/#''#'Fl&EJH:u96x#KP'R٥|RaDrϴ+Xؽ*f*"z}zf?:<&OOoXq#?rv|5@ Ow}xq͊(`@Y)(<)x*CL!fԓ]P9YVeZ8vr}Gj]grwJgw( I`W@23q#dA07ؒ'qh:sZD lE9>TPыb 66! [ϏőP/z6Sm !"L 瘭-`16*Fx&#&mfy.$9|nm_u--$+U,4޻xk>QWfQ&`l@eI?Uu)rjCLbd#wLo @66Kw2N^e[]B HSꁭ0&m}f#QYeHo؏j*YEĐ"ErzQ (\!]!P['9QqaŇwާM=3i$KX(/!!]oq8>z @gHޥ>vg\t Zk.7aAZI0h۬j ^#\/ԨǾo{'~;?]o?yTzEpQȁc^ n4x1~ cO׆7ȲeE91>|/JPeޅ?; 0gNdsoF\ܽxVrlo)Q#^vPHF47w@kQ78k u]aꚪx{烜Cֵ]Mouk,|xc)6deo}v5#G!eI(Kf5򺦿ni'C'FdCSɢoj#nQ2(B1B㐂rIkF/u^mah|o &{-E |})SO@C8u}6TW`% ^gtOOrz ;p}a;hm@w;ko/ 5)L"{fE0e8A*<*zcp3pCӔFJ2ѣ !GA pNR'ۮ4xuRN\]^7|Sz_'l6ɠ7+p.IA c"YK ޢ;s)6=jj5/}ⓜwOg'^a36Noy_3Gfትur1R5+&'>:}}|]'?ij'EIAο0O}{Nlx׿5 Eً+_| o|[靻}C}6OםkS1c1ḨDAd2@-|h 5C(Zߴ2z\S/g\VΑgMtIyK Di$>FsPە䉦 @?8 y;g&|f͵H{ni[cq'"vECm~ vsgqᐍc7QzcT,%"써_x_<䩧:K폩{C?($eƱ8mZ=\yA]8Զ%8\2Es T)c|)\]աS8C:oc~O򶹛Q)јSa{,&YOl{Ax:1}yz^N03~ɟc$I R;L|$A`w1XaW32)üg"&>0~쳌s&]'!\ ̒3$s`P~ K2ogTrT çOnY8"*{ŋ =`m;_uXx7.>ùx7=SX+ش -xD6&C&)?|} y=޼H^q08vg <}ŋj+W=ǶocUNPO;bz(Lpqʝ2Ca{)#9'cL풪g'\d֣V, 3(f ZW0xʾc.$.%;rS$"D$xD2@1b~;۔`:3TpCYAQRhyWEMCD <%)oI`;aaIsUj7?!v:61e4w['7гv.MfQ?'[9#o^ǟh:ӳAdYNYHB{l hMk)Rn,lT@);G9sM~Hy'`}FO`#vvFTA8ohg(ާL)o?[{=ޜ/f[*Svo*dIZr\.#{@q*ʜL<4?IvUjk&#DE(qɋHb.+v?tsJ0og x>';\V98L.,/ MEΛHǕD HڨS Xg9s[1S7ňDv? bL[BH1DSdH|z\ΥqQbWc5Vj}bjv5~{ZSGtLw2+=,,fay^FIEQ(V.2,"E7aK1ni?Z&̧O/MR-F.`xK{{^M"Ҡ{k&޹NP/Z@m,O?x6Gunl[ {SU\}V"0F0&!eN6Yc4qUɚHc9ر&;.R"76P 49$i఍:@dtp;7cqExU!֒bbez]As\#5SiMt NvL@ ,RfB%2L!>6az윹3wusGs9>&Lhx﻾H ]饰hέVWc5i+ vG>#w\VvU j_0ƘTlrӧ[eyÙVKD@rs)E)}twN~Po 2Iyޕ19%]@]#Z& 1µk8yq  bwZ.?,/}M^bFx!_T/ec=/\ۃ& t%__w~CP|Ta ! oy3>ϟ?+s滻A}} q 0I:-ZkyeB,;G8v K&eZM "?, a&T Z82{kL ݉=UVMOXVhAǙ}1t>&SJ0@I}_NliJ) 4?x.7 K:qw3d''5'Iv}-ynyfKrܚy4/\į[aXg;>ܕV^9BsnB],HJafp2 0X`jtLTyDEf)G;h۴T+j\i J(]*nT)UT}⟕TRt]=-p@7~=Q :kF:A@ ]wԤ:Mb&ns[{̀.}Wnsy\3gX|+ u1-fm~ P2'؎m[qݧHDD vŌj뢚:7ǠFɛRߑi (vaw^Ի.UTg.UToI!dHAuZ h#(d3Aե偓j!&m',44^|r Z*JfC95Nz o΢MX.փN{`F)?b², v2;Yx%T_Ԏ>3,?q mۥNjQ=z]8 II}MVkjYoѤ4%E2Ni(zgQ1:2MQimBF@'YR萑%Q f:TQ..+V/ZD>SJj=`*UN~0:djy%@]g7T|Ѥb&J)FFa]}=:p!F z&Ǯ{>NOkAp0=QZ%r=ZցH) x)W;؍7`;VÖܩZC670~,'=鴫Xy*s>޻ƹm=G;Sy˯a6YP0 iQ*PA1n[헆I)S F>&iA e ]EAds+-K*U vRzCJ AO2\նu tW*2Sa!(P׉ QjggRP@ك8}F:Eƀ:>AQ5i#ԦO5j҆PFJ3;z]&&}6j#0 GeASmtEzs /@in\n{o(Eq,u}qۤ؆Q ޼HqN$JNRJu$]*U%qw:Yzۑ0$(0gn3PQu4W-i݋dzEBPz$Zo8}rLֈ5KcqN Pw)q(3*v'?Slv4g!17׎nyZ?} ׾mqE.yiR2 ʆqͪhH܋>=:42UTW@Fa 0&mR~KIu 7HaKC@ 3J!5&ZJܱ1R3` k-rIR[ 7`2s[5j/%j1q.|\ǰ@trS;NNjD!?V|5ugraNo< YP6MʆA^T*UW]XWyηG)'2;8xǭܰgP_ȩ#zT]C1=w_?ژ %wp{e|sۺ/Sdgbd(nz+d[Cl.KֶB>^E { =@cIAS}\)6<ܱ104}ͨ{?u B!DiMJeM 6u٩)Dz5P٣7? IDATc&᮳lϭXJ#o80>~Νj'Or_?B̙+ tCJ `4ȹ٥J*՛ 2" E rc7k__,m5{{# ?L?᱗>ژQn~/;)إJ5al| cEQkuG:%ș}^zYPS`?Y&bPkxA{r31/^\B$r\o..82ׅ0-ש U`^Θɥ6޽&'{&kFu?0Yrys9v:OyN}kd?Dv߾Mwnk{qN77u :XgfeR0^1SJvmܿ_-a*,6@c/~G>@]g鬰]Wj=[&[T6-/)BēKu61Y]%¾uB]"'zyFö40MSO~aaMa]A0 MR w"u"b#_u;ۭn T r=|/nrUKY9dy/~"&Ra)t}nu8wٟ'nȶeeˢda@ *UOmiq gvHO?w^#Q[8Wj=3~K*':AmLaX5-*.jԭm5lRhL!6YcӘldkB!G2,7Q&5JkrUΘ0hl^Ɏ$f&A`7X)Rnƪ]~gѻI6 z8n;,FF ycy'O"6_fq}RPaaL(UTd`gp=Cךk.#VƇ`S\#!s{߹'eXz||# m㎷Z,ϟyoj6-=ofkc?7?J%_.񡇹a0㈙CZ:Ǚ* Ҷ  et :oxs `m~C.ak ꯎl#Ї[L^ʿϾq*)0C|ݳ c6.a>a' _xc/i.ՠc?Yj!`>_杇G2fQvm8U .$h\dn#09&fvPКD la}x"nعw@hnR/Ki`eyZ u:w:;+})2mj9f. Ҧ8R.gf^{ Ch;;6Pk I*ȍ3Y0ee541ZcJ$#sZǎ4b !!dq,qRvZE+du>W0(i[TRimP.wp{sOGOaW'˟DP[xR8"=S $;g۟Oưwc-7;Wj&܋^ "O$?I.gAPg u:hD \Ftz0Mn7Pk,\ŹuN)c/γXw <&aؿV.O{zĹ/S'fNFzuS1ytPg|{&4N.iY7yؙ ٝ;^vo0cJi Ll&CɐDoM A ?Z{ zMZkZ++x++ܣ ucW.JP0eaA u% i7fTDS7/ؕ賳ʘ+wLåE !uJy|X{$^UVhAy+5fKE*J3#;ThtE+V!a0N'C@+"[h=K,ցzvvf]wa#9N?d&ZB#e{U{Z ,K]mʥtծMĆ$~P:Ӡcy4ΝūV{G7iש{]w{`&:A}FJIN , RJ_RGF7ʘ7;T[=v?Wj̐! Azq:\f{u'Vjdɝ{}<C<ϥr uˈd@?;ֶyT !Ҡ].cNMlɠE"5D1n^B2rTڗD*ͦfg˿]ofMu\@NM̰]'1"QOe MlSrm!e_ BsGdGQauvLwy8*S)~? !C(C# ۲;W xIJIٔ B M*U vRJ(4RJ5]`TBF ga;\IwvMP_Xȑ9SN*K,Tnf"1Uy%%Rz.g gj^ǯ׻O ~K}DF"Jx9'cc_b{1gMDlPkX_k;w2wӾxk<<-1M-,5F(,¶/ZdQĭ(j9HRJ|ϣ~GYw֨0DQva-뙾9HʒaPiKTR~[>=*yƼq :OC^:`ԉBMVV%lkl爣`eJdljaY8N|H~l  -5@/l͒n $e:R*ɒ;6b@|,v>vwv.7qrԦ1w:+M,L|j|(24"Icn7Z)D Ӊh]0 NM,!ٓC" ww)7. s"ve&C[vsO=W+ "h)MzeD@ӏS;w'xLǹlp!3hG -0KZSJǜG  :3zP7Ӣ!)ӻRz늩8{+wLN6`G>{oG9qrb@uwvle@Zs7(V~Sais䭵=4ӔS 6cӌ55.7"YEvLQ*)Ƙ,ȃJM`}id&12:їzi>k- bVunX].~L0uRʞ.3>؝w`=yYD{>=O[9C{xAVoȶfgԓTNoh9=X!O4d3MbZAym_5P<3q?i߹RzMkmoCw+9v^;w#c]K.)uF@A6O2 n~4ugy]S,=s:|l>t?o_bG/ygielf-3sC2k RD%@ácgE1h] fM6 ¤aD;Af妔B ;^@I\Tϝiw^阑'jKpPx7CӎfjuEzuJ:Mj'4s$q{}>s__KOMD ҷz©.:8i$"#h8jMfa 6 GJa0~vRJLv^2L~WCjg/#<~;o>HʘK:"\|v lp/3~gD׼xǻqkvg£Pկߘ? }wy5Te%jH g%X ϭ3L51o]*~EPcGXXȒ+: 5UI4KulEMaF7Hnjk>"f(4PG1LNV*ДNw+јaF4)M OXSCfb0w2oѣQ"oT0FSH RRcߡpu]ǡ3ӗ8oКK{-~'"mf$0QAmYX!`{#[ΨK3cg̒i`^5*U\F0>Y,GʘJfQl. Njn|cd"lU_q̥\wp9?\u܀41 @Zl$B=F뫗Fg= Hi>\v}p=v6P wq=ˉo}f7 ꒓J—ڭq}2z)HE !eiRa4LtM{/|gfzx#]ꆵ |G?5y跸&39=%^S.Hˀ0 PzSjږiJ"T,Ѭߐ@<})o*EZMj*M*ի|H\٪TvzDYKo1[Po\R7 %*WZwfH .]9EfBIwEkCa>LM׿=$ԉvڥ"1KKkLT:}duZ<u]5Q kZF|Ds.hF784Lpѯ~`tk5y̽2Go8cPWXp1"AyRX$8`ҭ=F !JɘiQyTz]a38[w6RW.yR9ui |O)dوЁ=`{fV&r 23vaۇPJuV6λފpG"Ivz^^Wڶ(G$nL>}ӛ2C0m{h]gvuv[?_~-FZ>k_ g7}NȞc&v|?;+,|o/wMfbw̼!ARJVh)MTmZUJuKUw4+4RxJӊyM@jC $A2 ZN-0 ݻЎyQcj4ML$ Clfꖛ _i.. z|$فbnM)r ؉$%^zu'{iD?Zgl= u~9|0O>Y\gG6TA@N>ѯ}W_68uɺR dm;3Kːz96RR2mT*U7~6jRJT2}r۲6K5?$MSa :BFѺQ@k0g{ٳ 0,L&C>G敿~Vzc t:)!(bƮSwɸ[gYb007xB?%75>)6O\}]%S)E^O\*Hd!ys=6Y9s_O1s]퀣݈LC4MvMOS.ZS_nFn{<^LI3mT*UT?;5vRzSך q&@lX+Fh-D&aRAIhZ4 "ƮS9 uf.GvgϮ/WѩڹBcB+1s}X|i0GAAAN/=s/c!n8V6 B7橞>+P=yo K2ѿ& Cn"UtnvsƻqhHʖIVHiTR]J.UTW 4kXbu66K-2aIN0PhPvy￟'93: 3 A=`7SK'bv4e LS"[.I\. uɉ|e6A Ct!G|(LӶxzNBP[iyaqӶD/'Men*% oH43UT)إJ*jEER&Au ffal:;c;mut"Ӷ I1>NӘ]TVtJjVikE1 FT?ד֗ߨPANkn]Z)l/Lgll~.TGѬD_>ImkG#E:l>O~bLi5 Bڠ^3 ֞G̙{ KmXzQ*10pm0nd2}6Pp-i'UTWIŴW]XWyηG)'2;8xǭܰgP_ȩ#*sw^<7>J78|=2{A:|GF2o>-cгUȕajGyn176oXa8۳Ol$39A{i^"u'o[Wam]o4)-ࠁ@H35k&Qg箬PyUg$44m|e?)c#oHLL*U7'dJE!1F_܁G?e/\ן>K[XIn@]g鬰K2hJ>$o[7@)AH3T1d N92 ֡r2Rk6iT*Ln1ӯT+URiSFeٷ4s6Xa u4B&HQ;mCs s̖ع<4W+]\no wAsw] Q:eA@N&8? 7 !10mU*U7ڧ8};$;/Mm.6D0A:|4r 0-l9: BhV#U06E<>Gf.Gv|lw&%3~/=n;!f}&*iXldaT:{uHؖnжuTNdG?^Fn}*6wa@qp2^gK}abG (JI4uލN*U v07,/}1s~mܺќȏǣO_1[{C>O9k9,]8sO<'jǙcpZvOp/?{Ϝnמ1o5>rOͲ~vEH+Ԫ1*vb9c(n֩ۄzH$76F\"?1iۄՆiloM܈P7 $z~ur],\ u_+ g|"DWa@ڨ\inN5 _OmcfL~OܵNHaϞm;fRW0ex٪ ցDۯYC&ʼn"_gv߷s 92!4a6n9hNPg!,R%AպKI( RZ/f;c|z|.ڸ:Z{SQqWZ0As夠dd4oTR]`'(=ȡCɧL2Y ?|q `c8tDTtl|xH!A=|w͌y|O}9*MT=Lwͽ'7OŠGf|۸mw_:NJ94~I:~t]rsuZv~|A??}BO\/~"68wa>n~yvwChRe8X̙94Փ0lw%`+ hiMK)|Cb[#qfxF:9yg :5`җl`Zv>/`:ibZB w]jKKNu(ΛCZIRbMM93D~ B4+ yOk Rb|8z~]PPy 33`n_> /3}Lh &bTkED:tSaP LL6O1| O'0pyˊez@#,v&KYbknfGI |Vd Lm8yg ѾuEքkd| 1 iTR]` }.q+?oweÿ?{S_˱='>r~$D>+{p<t['?!~:_FAWw|wQw{o IJ6?|OTO//;豕n2׉55J ǝ3]bHieʵ&K^8;&M ñ1l i]Yc6fݾ$KB;7716DCIPjc(NMUh) @OGRbHٍmLAfbIx4$QRT㵯| 9 Sk|ȍ3Y0ee Dz>ZK<_S0ML莙*XZ1VgL!1>.ꄏE~rS2c&߮)23,su@al1J65hIPYAԛM'ˏQXrPYEcQ#@]Gv\gKJ*UְʓO"ЭSZ ,+j`X- 4@x1uX1ea[|0-߁|s0Ą b0,iN]{r0jN;&`nЫfB8Lv' 3w0M(֞i@z7]2$U6 MC RR0 l)RTR`zKRxH!G?~edx烅zQ]- ÈߨxFOma Q6R4Rt10VvՀf.0 tҪkV>(оpyIcONF`7&S.dpjJ!7hF<:Νݖcy,>4Kcxo_yu,l ZW-DôL_1yLhL['cyx}4 1lOlQ{x6d38CX1LˡVQvmƽ5eGR3!P0$YRTR`*U/##Luڕj}=˴e6( ^uIE"!HQ}I>niR W*ӬV6t\$ m&Mtv*DerѣPD:8 DS!n*E}*' k69˫&Lx#bP> Ospf?Z3 <<-1-PPxn h[H%ݾ)c7S2IJ0x@RJ˒8ds9`-, |F6K~n;JEzx*2y%>*{*1qZ, ݶ͔Რ.UT)إJuIcBM ;}I}ִ4h).a։IVӷw&:adRf:8p#"%׎b܉155#GX` b weDF!W>B# |RQ~u)/ |W:řke򦛘ZhMjѮX9w^cqr ngcg}<OSx%w>Ll,c:)Ȕ*;:;Į 7SC q[mڮG&v *hGiz5'84hNl ZK~/s!OGZl|RFQ;)7RJHyy]dgy Ab;2wxyյvR vpwK s/{g9lrߌ9J MܸƭfiGա" CB1:.}B8dԑNV̙;gFrRbXbk{R1NiYwv)I b],x.^MHˊ C0 {oo_ -1N~~?5O=/Y-;瞷4_~zE~t;>̱elfK_>4x*xcWٯ5Dsak$P;T!a'b=;|:Jmr]X3] i#A]wL6S.Ә_k&u:(Zd"): {iMvuZ G`IWm/(2H5N:&z;Fb2'P&nҴsdi0(c*l>Zkz@¾ A٢[q<hds9,Dy%W}9'׻R*@RhGRرYc;lO;cRbbdQ{^Mݸ>'ft*괟vҼB AM<)AeQF v9;yN^ȷfQ ݆wIk qğOxlFRms)":ioٗs˫~iE[/}ӣc}͛xռ0Ut0&dȷ=?>!?_x3_~q+pXl z?[_b~:L1'{cyv.wqq).{ЭWf+bq; * \l[:QxLe* a;p[:#}*-?40Oܹ|1(V)LR`Io[&ֺ/N2% ̕(nt:B| .S!bR)2wWWRĝi =wzݡJwi|ta?.0m62s-M n4 W+ZLt A05z5TM=gnbHKs( `YXNb"&F7d2Xmr׷8 5 0 j6G^e"ro2((2.qqJM+ *͜U.~d5Ai0 t麐f bf.FBB]Mf :DZne۸Ց.d)tEQY&Nkv%h6\p\w`* Qi. Av{Hq)It)BC݆cHkMݡInT \%cČ3vƘNJ$e͎ &r -9r,]ϣ^S($vTƠJe2(2(KVZmA@K)ïֈ]zd_)!v*>¶ٺ겥8ĄqR"OBGрEVjf'Ynj)t: YVdˠʲu.[)cvZ_b;R&f])6.2؆c;q2Ly%lgN%'jh..>*Be]e, ?ӄe?OoS$;O͎i-#?kD[M+ɯlx.M\]VFYL9[6+. $UYW:v;7-ԥ?G.Qu]')^PfpG*֑.ẍ́EҟI|d*J16:9@ggG1-.fSlV,,֭7G1>j3I\("i~ A$$Yd78 k!HVB$ٺ,>(L=0gAJK pxWp˫oT2?xc.[eQ&o 1m#\?kQt_biur/y fRE9oeqtzbݡf+Ylz>nƖ.+ԚBkTFNݩT<u E4 GB]~zүq -ǜmBYi-I3*zfBUMY66=XYJ _&JR`ygt'YG[M:Z qp,  Cp'Hwy~}H+\C,cI2V[C+EcvYOpđͯz%wM2(㢀]?ۼ * 4}_!c>6VX~K6y<(YgWVX][JlTziDswa\kBcX.4&P+gg5ܝ;ÙÄ!Ӭ'4=u?K!!&I]}j`m-?gT.y`nh{MiPXɔed +[Y16ի2 6ۣlQY.4ULLk7'&oH&/nP~6R+&s+T綳eN#DCoW|-2.GLDsTO$!i}+|өIsHqnxʱQƳ?Xh118Rb.>m@jL@hMj!U'RD*B$~l{^ʁxB]^~3qj0dzdl&PMz,vF#7+2!c{6zHBB]˾L3f"r[T&%"1 $Vf` (U&5˲&΃,38MSrB1VZJFu^;! 吢Y4sz#J4& X.wm:KKtV֨TժDxuD oeQ v_V./ً`o+r[T?,savޛ[9=~O}1VKrלa_sGE>q<.y+9l^\m?u'7Ŷvc?!CmK:ךMV]AHVeJ>tP7 |҈] ±CG!q#ůT3Tٳ9; ;3;;7?ۘ \]e4! 屦uFnca[̬ܐ,n֤7S?v$4['ց,Od_gd?q`''jZ|m'l|O=$VCo]'zK!]7rq|U,oۏ>Ǐ\jԫ] wk!/~~Gw붲Vv[Ϲz|W=s oU?}oOeuiu39d- (vl,r]uzyP'冴k6B38BuĽay.,bnyZc5mxWrmc䗾LlX~9 ~mjǯZ]%R7Uy LH`%GBN7,DfيO%?y7E .:5H:&)υ,a8 0_&*E8Rb$(a^kRU*Xyx)Y†%11vᙐK=/CRb9~F H,!p͒K uveq`]{ʾ領C}=B|1{}ޯe5Q9xy?=G-Oq?7c7w'w }|>m>xξ/;_kvr3Ǫwy/~ً9Gv\yZR#m%?eY(' 9R5`IVr]dG9T2Mu]\۽N׾DU&eÊ*i@$b'6pzvM/aBwuv%X=fL7a,8eT6yi9as"% 4eAPl͈JB_ KIK2sc([8 a@wmN8RX1h)CSB5FMdTgsm+fb-Jj8k?PaYX[k+*QRJ+^+_=q^kG_eQyݳ!ڟW>dmtO~{bwpq|+\䫟.^pMϜng+<["ky{ Ajsg94t^4,wh*"ɲqR{} U^!q$peۉ㠥L9S`i9XwJ{,r 2~,).cP`IyN , <Ӆ9i; LcQ'%y p@?a(W>Fd zj 1hag2@jƘ\64 &3y 1{"d:@, ֆZp"gOa%0X$o磿;|r5"r}wʸ]<#-fqǭ7 _4oeQF=+2.М Uq4i]Ե֚& +.˞e%V2ǰs+8NTؾ{ѣO>92q#(H ԉ>@jsUц(Ǹ:MATx!$9!)H{R+6%$̚JM|YY/)c4Ii!bo_`/;IKٸL$o|DN3`乌3 @7 3\ aI|?1<,Z:PU lƶh~cӄAۘG`4d׮h`R²qu4 ] LJ+2J{vD۔QƳo^+֎}O6ܝLj[D:> )+ NQz:PVALs+"R#LKtJ2rR&0be? &\'L_3ES |L3 {p5q7s|1/f!wͯܺ6e?~wГ3 |^wzKkk4fH0R" [p2i)bL!(1PFvl;p%",1Rb+)J/79NN(B]v IfQ"iiYYKT/ 䲲Gc~2$'K9M`ÙYSՍ֍L1C &93(T@e! F]79S)ZfVw-<T]Hk"!- Ji"A;33e7"L6x+'O>G!l6`^m{ *([0 ƫ %&SCSta/;䈥"QNs2( y ly^!K0Q.(? xF絵ϻZn垳GGxl@:eOxɱ;{^ڬv{!8t=uC6lsbt{v"ӽK)ѕ Ξێ=yr"ǚ%e~v `YL֩wJh4er+9 VA}lXi)ajr-%K8ee`'GpŌb\6gF!6uBknkr sYZ9=wM IDAT"-f e'~pcB I$ ҆c _w8lߎYLc z~gԕqf.6[ }|ԝj͑o?̢.[VO??qw\M/;ȭ٢:ZC_>9zF\65mY-]I0qX." 1;5TJF.cߏX\ y1*S 4%tB*5nuC8qQц0}o2@B[fi?,7+gN$ge >pʂx4?a+d0LmSק!>? |ôhY;-2v3w$"*Bǻn*7ވw/ Bʉz9:uy9F1b~{pf[k:`D6ƵAڈFcn"3aϋ1؎Lrl^LJ2 Wqǭ; 9>iq2.?(g,3`vÀ { 7DSLࡸ`>3]T=5??}9&`Gh~˄c!,ۧ+%uz;*I8؞BGf3?z^:y- lg& T3 0GD_0?צ`UWz\W}1e6kS^'6XkR$"3~L7k Ql*AQy_7 @IACJ{PQݻ|?/+uE˾tΜCtU=j4 VP)W\ThuvxqOhz BTouCBcnk.kKg2(OX)3v?xcK.6[ cyW^2.ymKj.]6Z)αX㘰ӹ`PW\R!Q;ńea-lý •&"]27ˍ X[:Vm`{BJRx:(D-d !J'\(/l"WYF1,0QP4Nq!?0)89t)s1i_% ,i[ZgaORXϬͨ˄)B߷60gufn7Qۿ[ZL%ހs'>^ZVy(L++>vW n颥D:12 cL]+2JCF^c{Ժ'/ i-/L&m(Hu:Y]m`&Nlu,Xn(޻D^vDtӋ>r6Z-;1ư\KJNS&Ƅr΁E|qjX:=>1ލh>{*V^,dA?23}@&" p2hmrMR(擦b1R@'o2 3i1]ӣ4#7FqC{JY,Xz+ĭVM_[Ō݈:`gAT*{1B;J|P"ؾR&R+tqlb)"إV$}Kf)?(?2(UtB`93;vP?pǙzZkԩEZ_'(-,OF ҄YؖL|)wRݶ y! 21ړFyfk(Ȼƃٖ5ct)K$p|/rV)F w4ۦ=6ı  5~]# :`4(rb\7;lߞٺ"M;5Z)YgERH’uq+&%B&77jUj], m =Gqn#{{-]]eqFcWFef/+ڮ!wP]o#7KClE rarL]GchlRTWoT$u=258vD1-[e:E$\v€H=(rʂW\*^mDU@ 3v9[ C ⤅V}:!7ҌϠh ) >C :1[fGդФƮpta^N!*N 6F1lF;eYS8 !ζm8W]E879u F^x0DfxB`(Y"Э&V FCDb^6QFφ(2vaH;hM`Y`L0crO.1ZY}_ڸ+m2o3X+UPzڭu@ $}gˢ}V7f}]!xfn: juB毻̌d&A8+fl]KhJ!LέePA"#qR^0J&2H@Oi 2(2( e kA@K)Zf;b 6ԍ[g K ܕUssľβ5jVI/ӏ32=2de+>+vZ\$q#fIN$B 3i Ahy B\;0:,xc\VqIg=A]tvJ3УIzĀpANW i0F"ڭ;JME uj_O:ͦ}ӿY1{]q'2 ^1[ASlNu1^Si^첾ҼOɅD b) bM5|,2 `', eYP>eqF3iE1M%H@c.8"\[#<{~933HC&c;3ra=NAR-~a18Էog1]3y.+4'/""H,ϻGY*E[33IcN@f̲uɗ2/M2&5"IIP:k j0Z&U&< ۩by3n˲PQz4;:45P۳ƕ ,ʡn2"i?n4 a,niog_&9~+{~h""zJ2(9v׽o^>?Sx nkScZK8ȏyƶ%p\M^y;W_?q%A^v!ǿ^ۺqEm #|f.ۢ=ungͲec 1YZ9l41}EF?^"Иb9p})< ɀhq^MuwLH"I)|-,^O8r%ç|߲/]@ >l DsR3uVsvFJ1?I0T6: "!B"4 3|(UFlSEH'70ӗq$+0Ʀ@Tmn8ioݴu0vſYF \8 CdAJwvVnAW!h ʗ/r>E򳱌2ߘQc.z>r5p3~c~ Nٱm -x&vj.61f tNxg=2$*Y+f *ME!F)9cYBff+9=v=Z`aW)ذvҷ5g>֞y}/ud}K|'XU{qor;5XϿW" _͓{=5/;_ž?4# SeKLD`WgYpPgн&˝x} PhMgm Ȳ{~Dw{$%]ҮPW "igY2-`,iKK2bɳ^70 0Yg WCcEevBBNE4.qۀ۠ZWzr싡]_B"f3\@QeJ,_r{+rF!8u~Jeao{%۶cX,TZ-U'h>qeLWsl$ʠZk, x)m"C6)Ҷq|?} "e7 sX怇R_]c(lkT-aתZ=J҆1h,gy͵~<3 ɞ~_= \ß,nq|+?( eW\Z7F>|mB`_ jwͩd< f9x˭~'ݓ}/{*gB7r 媿w!VϥS_5+]J|Y_ݿ|3Lӧαlxy=}o{oy \ed91Bi@vR^u0bA ܨX/rv?={qX1ڠz=ViǎzT\~^oO!G@y;ɛ_zC)j/_f:gwWn}-o#dϽ?Gx'fᦷ_M]?po~(1^w^ϫ!>r5] ~<~) pwr:pǧOZ{_/C?|y;qm;ڧNw-jɑO ~J 3Nj^o wn\˙B`譜jDfٵjtK&AWqL{i / ;Xiʾzuhh^= P)I1wDiGnv31ڐfvbWD )i5:D4 C4~|U{UYun4I'Ueeeeު_s~?0F!Aq8Fc76pbq#lmяM¥\y` tbl2pX㞺4[`81%PgD9j"&ȊETsre?J̯GQ10hG6.P7،R,;u,PX8O偱f,z+8Gƛ\q"w>3V^tf\V IDATt| bnZ꥞o8XabNKNAᐨ׃Ttx7SB&Zti*Z.+ Kma1l3t}^%sMΕ?+Xjrsx͹"(btmB1_|6 {;>~?-W#.msxx=GGevxO-uW_QOhʆ|=_sߝq884{k_#w*i>J^%l.swrix7')B u*|:w|od&p_1-_hǩ\#) c?AӭP*y׈=ɝB]~t:{࡯~[GO÷[/qLM L fX)E0kK4ڢ}$kGOҏ4*=[qn,"ԅG;N&FPN{Emwsc;}sF0ר!I,C|$3d3aGOT)_4֘ߊscE\/=ϩmKG cJRZ+.ǜ?~N]Bξa{Q(?}"FG c_ [jR[?]W,9)vLS8 +[+)JFj+I)# 8%3HkEw$~:Tku<۷U!ˆ~wŴ3X[r+HpgwӐgfpg'r&)\MBONU ϖ1asek'*&i.ؘ֚4Sg0VcF5h-4/UM݋8.U~>Ǐ#98Daӥ\%G$ԉac~`AHCT*caHqOm1뺔摎"z>Xp"4?iNV!ɸak%LdfҡT8GW״2u8y?cO@l1a0B@n-Ҧ0b3;=*dZ)]}- /R1wNJI⃴'FN=7e$+q}>:Q^\+YColk:-PV5">b@nC$/c-h`Dv]Ұo^Kv+_h*ˇŷp >_xyʃiv*[7ơ/=Z\7‹NwWn jȅ@ը #P87ڍ.en>}jyG}=?/}oߢg]E/%y?zz,^~Xg`>ABMaweeڡC l FeI/A:vH0,RJUޭg*WȔȄR Hfc.I[Q gʲ5XŪTPf}[S{kuZT*5ݱ% bVީS : $SgȤtΒf ȁ`#MdM}&>1d!ƎawH0FO Q 0b2~&=vq`~ŕ_~8F&[w6Ӣ(\ߧe'7Gw]LRmF9\Cðbȥ%zU.ߊad}M#PgсS4g%TVAHK˔t5?f~svMTaOz0BAj(W9IY8 O)pwMsB9 /BQ/AL !U^b 2' [[xOZg| "z>b L؏TJ?D$\|6ҐzTsиwΎQG=w{mNvYkyf[Lݧ"t=yp3m|Sw}=Be%#XV , B6)E6֡1_!x&g sD\;y˟_:y{OrGM.'ظ%\w sx9Dqn,vϾ4_WYzvh28mD/zpr2Ju~L5x˪nf9ܻ_,bQ\A^_~K5=h!.TYب˘ns@p<ה| Bᨴ' T4noz!zG)“<[>DG1Q+N+b|(vӉ~IKNp(!ٿ H,[C }jWD3]$WHL#Xu\)$7m-}>1Lq˹k)6 I3.I4q#LZhM剰#@Y\rj_>mN sscwОOBiHBr|LD\5pgr\3ocUWgGMC4c8;l4QP%F {}$%aٿʡiۏZ&'QsuM\7xUoC<}8bM58F̲uJa:NnJ::f;6<|fhs<\ ȹ,scE<_,2vϝmu?a!Is@}Tc<&/eyC1NJm oZcF+BlXaEQݞЫRU,ir* :lHw{v(jl 4e@D:0+$$Il 6 )%Zk1UZN,<#B븞yE3;."~)܏DTt .,TqF[J\W>uQ2EWc0Z!amm?NA= t<)8d*rR!qvZd&u B0t$[a=hFE"("(₉Hk6z}Zsu$g뢔J m TJǩFq s\Rz̄8#U bJuHLƳ>;YiJ29RS!:}##j6nVC^D"x' dV@޸Ld&+EqvB%i,ҡ_CIHǘ0$j VO2|q'tg|N^A 0i_+֌2J,6- Mn#F; p#/e+jDr"nY!ifKtXך=EQDvEQDTltl--5]mk Dqu,P|bk =9Yvk+p v3zzuRxRX$*U2̄0HLJ*$bvT6v#0QbV.('&IW pc7nX^^u$`grg7nW/{);⥬/P_{/%eS/6Ͽ?F灯>M? wbƉ:^\ .-[埛#*;MLt8v7(s*jtGĮ<>>&JJ8W.W**faM'vqY'c^0րX,#HҞR+PtR)"2Ь阵 d+"("( -:[aE8R06Zt4s^L IDAT$gaKF=i3d0@4K`.5N3v#K0I3x]+^MkȬoqf;W.S^Z|"Z\Lwa:ҌkG&Q&AL!n/iۭ3RLnl3$?9EW; j0[B)JJ8LXӚ 4'j1:!f YV L_ɖ*IAlt4a-8ٺ"(vB)\(?y)"qVZU)Y16"]P88Y!*PƜ@T}8++DN"~PG{j.H"\KJ)Ek5&)L_guǩ' e퐩Ҡ;?Ν )jD@%w:=\/Q2Ý,3;[s:rKX6S*Ƕ(TCUJg?ioؼK(a!a'i "8,华 *:8@{ :BeJRV}xϔc;)J)\B'uenR`{[mڒsNlnu"W)S{Ze4NCZFJ1:2vL*!CdaN e*#_RLà;M3/kb񱶄D QYfގd g}Z y#lo38ujTB+xLbkl qeFHBHd([hDu0^#1'!01f ƧrQDSB 3B÷_y#/h я:'›x43؝9m"^3J5[MvRu DsQa!24wxdqؙ TY~ዉ6}TseRHJcz'WC*YCn73&\_G7zem1 @ H/?= ץhPY\D%3$^Xq0oŨT Su}*:>XQZ.!{ C"(9vC|ο%Ͽk|?ƽm5Mv,{߾/Z~w:)"Vu92ɔ$EE %8Mdxv9{h_v]܅yda}I&A0 h/EfW*IT\2uIljbbڻAq 2sS o0bM,IJ~ R0CV2`F}RK/L;kޥWrm,\~w&k#IfJ: 6ϧ~t6y[c|8ȝ)"Y8X.:,@ؑH8BuA3fTJoqpX}\)&-B`<2U2]TxD\f0yMf Ը\ɬ3{]dT ^qĸ}0ihW7V̬ Θa: eu[_U=uL줔]< 0qj젤sC/:|l ub'h>y×#]6&SO1* A[<8x,~q>-".d]r ]ȡT_=cJ{J¥\[N,mgy oO>ǎ]ǯqm :-{]^I:PW_{ +&ٶ򁛸zJRbR:p3o| ܰa8*B^:ΝO^˯(oos?t)W_s'ٴnl l|E "2jc;XS9]*y8֢P#\Z${"B)ä&8QLUhc5B9L_XxJQ2Y)dڌLI4R:~dH)p|\ǐc|wl@'dɇ.#,΂"eOTNs1 cGCTwEPB!U i&ٻ*%e/?T:c=ڢ# x"'S1Gw:Tuԝlõ5`@6j5JPJ8NyNdP s >O,\"B\xȩhhȱ^R"(Br'{+r-_Z_wݏ1M{l xG OҊA/M~|HqYcu/ x> :Vc?εEW ~WNz;?v y>p3y6 9sZes'%59hlT:CO)Dv8B$~aJP148IJĆd)Ľ6&[t]M  Z RI2̓&F!%5w= @9#41k:Xx@s"( YY&P`{Op%N*Dr?7O?;_/w񞻓OOK, 1s\?_H \O?@S]µWVg-2iB<^g=(C #SL ?ӥ.˃pT/9D)T;M !0BJK.ʄ@'S&&VPX,VevXku 6iUz](w[S=JSZ'2Bca@$PAo{E>BC\B}za"q"IBH}Nw Rۇh $CvDdN bJ4GЮOl"V>G>Ch)NEQDvE9C|}wSM׿ɤ V+vcfZaXR԰|$u3c`VY\B[K(q %Q)m$u"; )BY`l4]IKmj8n X$(WQzzm #TK@&3l&U8w*'U^=qaݳ+" '9#,(i̎XUƩT@`" "5#G{s^Jm~*8R2x0 l6:ETЙX#i2h]_glzF \E,|x.ゐ` ZDmVHuy!}C\H~awXGV 9jFEQDvbBəW*rcW?O:yb57n>H>9#YJ*vo\=tJ̲㰨$9㬭#;>kKrU!dn:iN(<*-ǭTP( l aR 5:I1ڠAXfL,"-ʃGcHa]T){ H uq|rW.~>z6$O 6webJ5J45x{M' "(g/me.b~ޏp_z=G~92(T㩃E(Bn*b(e:yZe[R7G ŏEQݳ=#?{+eyx j80 <͙/޿n?uf끨ՙSQ`Wyuݏ_/}Y^^ʭ/\fΟۅfl݄dC "X08NdP8B!yyN'WApik2ub6YgRN vYnݠ.{73a:ġΜ,-|ÈX ϯP(toNECkuQ>hVp.IQ !Ux$a/ZQn:OܧjU߯}r3KZؾJ^ufώ=ȁjҩ8"}8*xՅCw!:yj> Y$uzV.@ǭ]rI[fBs=Q)Cd%spGtY@Bb#)mK`QnB%%XDqE;BbQX1-n;!rAF314[!1hgJps an9T4c7Z1^i_VL{qV IDATA/O&Qgn vHtu$:aNV#aZ4Nx4/qzIm6Zyfxi^ n$N=~01֠1>W}LÝ1ć \ cL,FK^ 2KѦNgAbذR3Onms׮%)?CRJ?QwLZ,]|nW#_sG.%%<*P)bv~~+7)U_iZ`In;+@]GSx/l8cw, x yNwfIaX2얋@X\[ii'6FcR*ws٢F16Ͱi \4xb7jjsQEtcb6NHbn1ϣY%\>kD>]:uĮ$fg\}U8WRLfSlWx˥YnھRJ?~J9j=&dBZdu{={EjK!B),P융X!IqI2lL`oye͛bNϑdzsa;_efҬ012l"@kNח~7flI-T+1L 6G5Y1` B 0UE"TIBٳ G> YPE-g}\h 7isCj&`dl/3x"yxfCAubTRvMZѻllL(ETdRz3Lw-iuxVaZϣ!A u#څ$#k& /00YK3Laz0cJ!Rx @y.ӈ$A?zz:7Un[d-;!} c%BHYei7\z=dQLXo7-Ḃ@$&[vy\IJĥ)$R B`sxHOj4oh<Sm-v|*B+|G tp޲Ps8Û@O5s̥z<  xlfNiPRJ |VO|JeOXue k5&|qqI.wp4I {ud}y:_( .ݝ{v@ $:1@(hIY&|[Qը!vܗ[/=%e~\ '2Ls|CѮ+?4O)*p#p#S5FVoǠ;3`gJ*UzjRJYS>bˍvr79L_CbB͑;cqYF$IH'OԚMHH tT^96BgVͣU+N"aW޲ߟ pGesn# ILmPJZK?۷O "ƒi^TnGo)|`ahRhX@;`(weA51DM|f']vE:"*TR *UTi4k Jr0N>ֹ|{ϫBcβ¦#@],e+l#}#,ɒ?'X։k#sjRHruT ^'v?t4Z!"-u,fD^s >[I$/g11znb;OdBw<'T+BJbTV݂Xk,l>~fq$dq8Et벿cSRJUTj<13x2$m!yq-scr+V%PWX>ҩPt98γҔV0$7xȲ!XHX0wWZ`p\엦VcW2 Å<2U8IV)U8Gn!*uƚuڍD=BcZX]<{׾Np4Bic0ZsnqUXPTH-\R V,IaFz"x VxYߏknTR *UTjOQ}=EM*jEvEneK [D( )8Q8c:&B gXQ %Z-KM\hVpؔ{ϋs077ޜڭ؂)΂Cb9$y8hknh7j"P10{ǝxEvL4e/yUoF,scEU;ߗ<^;Inꢳ[6^m$VG+YĞ4Gf9<T1*Ut`'*WUJ*QOM1Ѩ3+ڲf)*G[`+B]Z^aJY\[ˠ9NRq.CG!SaЮPz^ȍ^Ƥ)֚K,<--9PL0eZ7ABm$9pNZjE>cc [u!qHY')=08`Vヌ'?im߾ǚ"cB/l\!Ӛ@)\X~4;A@F-sA*=Ø3yl@6Z]]H EYǷ1( Y)Ҩ9kqEzYa]l"NJGp XA;3?VRudN{ZڞfWBIPW)%7+E1Vaibɧ+IRW{Fz_Tb<h\/rP+!Q!* 0Q4 1P"^0j4qBi' :sH_jL5i7k5sCO 6$8*n3rXCx'9v&sk?uYb8aI%I)W1xb1QYw?}wx^;0=Îh@hyJT2 qwx3@uZ5O!Ƥ it̕)y#y;kâ+e(,RmZccu @yZqÙcYKϣn :ѯE7^%-λWyl[_';پc8[uTꀝ`lE\|{>Cvg8^B.0q>WslJxͳs7-ģow~한S3 fm^Ϋ^w{I>|C̘}lz xqϮu0ͽWv'_w#g<nx9/?wsūtatf;uIλ^p m]ֱ7}R Ʉ7 2 3G LN6P:W:5яp^#a@- lj)sL iZ4[-jZg<cef}OưOO ە)i,,-]tfoqƆ5OkQ|$,M'~D; S̰=q~Lt#;7|OfZ$Ue;T8c0+:[K9i9,{'gEW3yd1sҽ'evVnxrg'vM~ xܓܺ9گ]I}f7ΆgqEmʝWTiuNC#U-+܏OEyOŅx$S+a3o~>n}i)oo;^^SR}T%k s^o|ON4_Z^{ĎV3~+ElH3S=I EyV"]8\ &ln^j)=ZzaCf,>)|+%S8"$ Jh0 "J.szKV;!j5kP}LG0ZcXԊL4[9s8J*LӋ(G ֘`#2s*jsNWH l~Ko$gܪ]m/}1׿ƋEΗ_/'z~*U:)-S=·;ڳbo=8c)yD~n<3]C_o'z=91ɹ&OݦӉ~ˇS@@z۾Cfs~s\dgnPɷK0}5O}#鉾~|sszst#z^jΎyg gnݹVgp/ڹ\zQcߓ،Od1O)iU}FR<1n,5XX#0PN!  3k &Ȳ4ˈ`@cDZD>9A#-#UJ*Rz FNS7߳‰<&FR8/|۹~AWWoJ^১ZxlG͗42mݢ\9/zr3ܽ:םp2$F?wsB`7Ęe^޽L199`i_|m# fe˨eEKfS8gq:Cf X_pYeE0i*2Uo˅xZKǤqFE¯Ȅ@)Zk#|h_2,ni~ZlE*{3rAd-ZcxGcmk_-|-lQ+fY7U]R;,Z1G<4~Zs'Q3֒YK! k&P&ga 6B.C IDAT'..|KyFavWZ.J)|cyPeV=8ji`fZ7b5bgt{ȃI~BQRyqѽs|W]φQ>~Wy2|/{GQk}JU*M.|yk.`RÑF3pZߙLc)^MgoSV^:9|=>Kʳ.Eyd~#K??^x&fg+:zuNy|`EsV!$mwPB0T@aj`0KB sԚMZ VIIE= Bi'IXo k!QZL`}N'SXۢ pq`Bj]{CL>cwG Z{u9s-[0x cBE oYyΡ #c1kkI!< [ؼolBIL2ow$RJ8Ua#n$j ;6R.*UE῾<>}{<1 rõ|^4}4gosn>KvNsO|8{꫹_1xw^e~*^?Ƿ?x pt)952e+ZHss :]4%67aY/ 4^@9 aw'@ͣngPlQ+/Jlۏ\:|K9kgYDEЎZI­k 7e# Nk5yêy8_xGnuATRv?β cl- EԊ6^p.0|u;;s[qn);a>wo3?yʾ#5sq\{&jlڷJ'$hGGnfdvNy0bȅ1̪~N%DC sq eŸqp.B Gptу!mXU b9lÔ0~Y慔aHhy9NR8&I2 @ +e.of&94%}^?o,hȒgb|DCzԕ91[7yI'HQE\|RqL+H@ g/g Kխ!DH ?>eծV_tK~0֢p c-hҖ@n$ܺږ6!oueé h5Xkō !}BjTk~F=d7.s͛x=xݧZ_RNsh"c@S;a*<Wqm`r|у_RPkԎ@=mJ+ sy;^Ͷhgw\uOoy1U|Hk޼By%cG4R)_7`#e-=FK8(Yϩڟyܝ\eA׾k/X~/㦛^.8s/+/J BM& YM(LM-Ȇq aH,3&"h4 6qړ&'h)qe!kyY D>I@롋ٻb`#uID>ic )%* A+殘nF`U2! x?{S.@rWZl`j]9s--y[lrS՛!VԕǃW@vxZscY2=ҿGx#+E3m@XitDh`m1ƤbLg8%yoF|?1v?~z^ k}Jlqc_λ~mMCk=gDw/y>^}k?dws os BN%p̓a0jmqW.󮘣)0 u @ A` AyBJNF9 #3!/I6C4s4Nuy(!ɴI9Q[;n CEQ+iFS B=œ]D˯/1 wn>RGmd YE1'ۘ)FҖpg%Y;k֢Mђ:CXZK6x64ִmp1X#+!,d)@/Ƙc|OީJ~񆳈O|¡6}[\W7ǎOkduTO8k7W]K &,$?@oy }NqgX;iR u3 /㚛.@ p,;%3t+g\^wxQ=s;m[sws+ ?z/71oW3C0}v|eREGWg_/s~>Dgd` ^t͵\sų?me^{?U7>Ϸ9sH}oz5kJ 3;sܵ*{_՛[80~zѴ+AA9  x˫xrYuC^yx |RX@a%AΑeB \*qȃjF RdeTX eb:]"i^'[s҅ߣjmڡ7$kLLsUf+p"wqΏbr-k[>&.QAОdXOVs^^c5u|ҟe6>㞔p&] M ~H]h64F %S+*U Duec}ƨ kpB`&bz9fgu:^ue#(O sŜe><6bΏE(dOVZ A?{755?A(@`[\+Z(uD>Y&8=i9W[f u6ds+jbeZiM$$il-՘[_#R'#)DA`jx^Z0BF~D!S 23;Kg}Md*U:FWLC::GOFҝ@wLjƫm%3s JǹП>iڝfXh^?jNuK.358~a*!`DkBY+ӉE0P:e^ 7SGبsqZeI4?&xkVbUlm{n86\s[?Aj1 `,KF"O@>f@>' "jf&gI&igm}_*y4I(YX:ˑONaqy9d+ER6%Mu!!a>lnb9MuXhimRM+vZhkSˆ&JgJյRJNnri';_W֫TӖ6Z_M9_iK`YJD"HVjC+ߤĉ|lWᄘw)QǘqRsgD ?gP*]R".^)u\ 膰ey(a V 4I$w"otO;2)ؘ&`X^y Bݨ=A;rg$a:5MMꛚk}!^aMf0h%M}YIz@mZkIɲ,cm\R-Uގi]J*=I<ƄݽJV-Rz3LwgFd,d# ILy`B qK^Fmp4b(F?_8iOQA/G@\-k-NH^G]/7W8k(km%+ez`7|kq_2 ZA Y]O{PV9m-Q}e6gmeݦ&D#EI1)#\9rXsL Gu0&.`dY^ N>ƤyG|RETTRvSG>gg'Rt?U:A }ś,N-J ;J*J1AB}& H$8 -ahj&$&KS(s¤6A)eM'8kied>'Y ;5vFN,sng w0:G&!aMlĤcmЦsW WB*BE,hm:(թR%  X Ʒy`M a2=s<»Bg ˫ry8kp3)v8qΒ@B"`4NJTRgUTOJ&bњ|H |BX|L`==R:a*paF&򼹲b6:sW‚B R5b2 h @ULj}cFHpe˪R:Jxa1G1]=`fҫQ+-!PëP@sS*oc8c@ktb]dfI֒Cb-Ym[i=k33xͷA҅9[+bD-TXR Fsΰ`e o"DJR窊]J*U`WRJOSfPk x^ނ Ӓ,(P}MBroj)zP*;sXq]t#;KWJBj&Iaxyj'%X {!Fӌs/|&NG䑏mj%x"@) )%X[«u~~1RWbQ6jWo7 %T\a*2X5f~tn81emI!S 11Fgm¹ank9W̴pcB^## @WJz7A-hS B E|rKGD4TȄ`Lix>LNZmFCXJc" !Ɏ\rƐCl Zq-lcz&Z 0@J1q^~C s ]T ( F>^9BϢ:TR *UT")r,2UuJb4d@ʢuM0&:Yt;3)5GZ ,6dkwhlƼJ^DTMs_q-7p;H8 {>+6 `׈w<vyd~uxzR`7m _\Sх"< IDAT7D, $μbXw#lLl8S(k^+'|Ƽ]gnrwA~]MO]<}Nld| P׈F4bq{<~,Zb(1.Rk:R1B>n!j&A=@mMtjhSTx"~a #*Süagu@8F[8ٺ$f@*t "|ű%M^zr EZr w?RU؊AT(I5dR7Sv2-3;;YiAОGD+eXMUR@T9WQ *Ej0.B4}8VO(>gnΎe[7s "D&}U8Kk3x>׳N.a<a c ZЫA;["/>ծLD2.Z'&jifHWֵ?qWϞaᗯeXu0c/@O3nX,7Cݔi;cu4`bq·5;J ǜ'N;ҥ%0l>27sC3FB"ZXx,{wrԥWpMy7bF&,ҫa/ueS<} g}x1LOkhس bYe+X>CLBmӇ Af<CKd,͆*B VE~R5OOӤV mE¦<*Ph &NTTqcJGqj :tG )a.1aǘ ( 8 "`1HYETn4spV) f1ھuWk}Ӯc nlAL;>;'{/͏=DN" =-l@ \ĜIE]7v[e}0s(Wh!yo=??R@c̟v" @ͺnv!IsqKYVYV|zO+GW06L.mӿI1O8S|%Lڱ(`1˙Z|u' K峋Nà1ex9g|_*řN0] t]t=cu}{^;\xsͱTd>Jqz*'Z.λ/},^:.fE8&u6r%v,=7r×.d饳Ja \|BNpǦ*hw~t٦xWKK(m݄'=IKL!LfΨqrVAsЄ܉ Vu>mYPeF2 R!"@J5: 3&Mf1Hp*~'u>[cҤc!C;Xuma-$:RD( Iy‰EGFab "*!:B(ZE s8кU0""V.Ɣ1 c,AT!Λ΁;q`!BK(Ucd,FHʹ#-,ֳycRk;ye^6 &7K(gw<Ͽ޹=hީqgIW|OaucU~b[kcը#о$VL|߯{aOM;<ǟ'zX|\5{0-c8쳏cj_{ %%us\t-yl0sQ8:.-tޯ^Js Yx˸v|suę9Y?^tjf˹Xe^al7Q%7ȕ']M p`yKlk|k 73?E>y曬'ãGv9d: ޒ̬5Nj;5w 3bj$`M`'Fhd XbELI"fR2+(!n*zVղE}Wg2@R #X-.gQpp8&#,#PUր܄<"&r ΀[m*:#k5~61R搪96Ѿo&1yܾ6|6I+Kӷӵsk♯W@Ȝ>EX[Y[WN;N0\S7W@4//U/pf}m0G_ q„)׾Wh]ȵ-cp=?to~o|f6Ћ]#f {9sنGitT<qUp=v [_w5|˧kМPy>yX̸8Iz8kg>gdZ_W2,1 L(I-&)[Ŭ։%beOi+Ҭi-4Ey :Gh-bpl\K -r9th5MA]PG%I@&,_$Egb/&"G)3@$I yr  mT1OP c;OY̘Sgz^%Ν[)BDO"8vʗoKiP*G>=ScmR)ڢA)J*5pZN oa9n{|%6u߼sFJpWr޵7VŽƪo/ܣҰ<ЋOL/^lX:v}|Vz?AG-]ΊYtL$؈ 闿7Ckw웻=‚F{]F1I}mh 2P%[.;!.$<1=|h frIkRMu[B)`Fy";;yY|+l7wk.m‹/ldq;UCnž-wO[ԈAs*E& Gc:z̤\@&L2wɿ=5.?_r%Iлp;{3 }Td?*6Q4fuQ@^& `Q**R0$(Z[s9Rno JJ::]ꤵtðO2Gm9%1/?'Uupٰv]Βn%[ur8 *;O@3SA1/pOahH0K>w-:vǷr-Tt(mAOUzߵ<+7\]z4o$[z"'_;A&Mߖ'{˓ tL:YsᤳbǶ?x&DZ=ZP60~rnH΁}OkBX`$s5IzT aj.%3Q̊ԀD´ϲUYbRELfS("B~0lf\ *5ʦ"r򹐁ݝ\jf/{T$ԜhvX_'Um&o1EmQآ@"sF@+bl U'>J&5`@C/&ar(琲X[Ek nF8OJRJt0M"Mm,3uaxF 5>} mekڏ4@hj(0/OobNuAzfȒ ̘юy(i5k)˧ ^&~z-JЧ;w{wƍ+f f~=FA{_'L]2s' S$rnO؞Z-5Vneӽ.ӕN=T>$y&bɴy CUT Jyrt)M4M srLݟB.G8~<-<}<;)1g]fRa8?Co'L\=Ƀ.\AbgbN {Raʕaq,2t[Dj)x0RL0v6+ꀘBkMsADE6sL]AT!RP3bJ9`ڍWZfpม̗Ͼk}>6L :v=NO Q6.YT*?{k64[oqIB~K57v=oohsb3MXKw1Zޱo8Q^?4潇*&q qðk8,)(4;koTPNQ'>lbm s5KPwmK);ټ&A97?s\zLfFo=L>K8{RN9rn2r,7T'.`"{jϡC죣l:fV 3f]yԼw,Cw^ Uf^V%Ad V*'9DwÎ.ocsa5o⋼{Mb=촯iޤ'Ad(fPiC.x4in҂4XeE֥J]m$h:TgAy. ko_`[h=w  ͑T+Ɋ]`ZqZ:iC;j5""8QGU JjBD(~k8&bHm +mY=qB%)=[1YQrk3~"M]clیMAd6]p'}+*Ϳ2/ ]ŒַaP0]2ȩ9=DNaOEfɢd2Otę@[3j#(sz'ߨf.a`{0I17>zb9y9g4Ƽvm\O|)O&`'2>ndNs4%Y[Kf=/Š iz4'u|hc&⨙ǰ`ݥ- IDAT_rVf^xq[;Sg11Lnֵ2 %oo,8 LSl1@4{ 5 ص3Sk~&-ɚKjkE0%_4{uvD8f_ΙK':~}}tT`=ޯ6(5`e0jD>>Aq'ORg$1@g̛Lɘ\MnRn15Y L7X"%(t6@[&DcF 4XPmPՂX{H+ŷK#,V>kw|3͍gl-59>a,_.IMɡVXWʝG$J9UKi%X o,x;lۉ~ZAXGAHraSq9%M X][MEں?"8!(Tn$'䱚f^2S/JM߳=Ǧ<͘Vo䂏 ZdK]2'ѯ7Nkm`v=.#:<޾n3]U[M}5ھ7\g{\ȕgL_Wvژ~ɜh2oǎM]kqk/f=IJHǔ#P/¿?u r ׍yNoo ξ[aӓ .2N<Ɏch˽|rgqP%RUz6=/nިuV}5[8s8{q9ȍG}lY;~poyl{}no|ĵWiW~,S{#a`z9X{7}vpfՋPK&Oܲ:C飣߿1"MqT/hDੌI=]u#S$"ۉJ2g@c)S&RN$cm Uq5rb#aMRdj3X}thÎNn?>zl]d kB)5ܝKHZ:׶NqggM9pPT%rhzS1x$uǚ? 'BTүj Df];-Pj2ct>e>gU㿜w v_;)U?;ke\vlJ[F67 -!~ף꣣߻Z)hoS)ZCbAԤ($tKLR"椦LE0' %I.`2Jj`P3g,`F'NAQp|6`yՃ:>jqa \{4QRd€4U*Jy\0tia?‰jJřCT*T+Θ a !ڎm -hnOssMv°Xj\`pNEOfz^E>R:(TQJ*K* 4{*lBX0@hAS; x>Jo*1ez)cy9<6=w;7wwu>YPJ.vwC=9:}L}6fk'EGׯGGGuwLq̝ kkRN$ \@>/E:aN\-USx41 U˚ulHȀHdZⳗ}aּYudj ge DFp/  $MA)^L@"y74 6[.T1BV 3gV#E iKi";D2GuŧFS1Z}\w`O@43pQԥi~Gk-X8o{0ulݜlR.7s ttv9|O,nڿ fͿ7шFJ{9QЫ\3vl2)6Y>H2kM&S;'WJB>ʤ EݶAW=f{g2ol}s9 (yܩLJl쒔XZW:"TAA˜*q JCǵk3m qH7pqV4m2SQ1A#sTM chSS1zkY5(,J<ðH1r:p_l*ڤT`k 6hD#{`Aq2yِmlD#9l.*wYg!(8)y)C0QtJ:Ϩ`A'{$H.MF,%@J9z@kY$jut,&kDy6Rz /3;@@ݾ|kK,ma3JAާ`JTFƠ+R6ڨFT*SZַeP :޴?w2+IJTHz*U9/R k6C55*+DYkX4ZW^ dXX˴&̉4ƅF4x;+wF7e_xWB Knz){WKɴjr΃ g:Jenƒ;j&:g[V#Iֽ%}5)Gen?/wK)$Z: cc0uWtuq%b)5Fk|JF0l"J<*CDQ?Txx 4M!V-扨LȒ:7uٳsCh]یR\J,q; 2$mDQ*YEJE҉1&cZ.4ľQ0&Y`EF4ha шF4Z^'2^u Sa 䄯)Zy LAJu5eKlMfγ Q c(>r{o920l!Sð (q5/93Mkp,YcRP*_usը(NubUzm.356 :3lI7OT2U!U2+Teqw%5֤^z0O. Fq!=il" |&YxYz7hD5hD#,UZIm:y $ O̪IaŤ@.Iof]2 ;}2p& ڽ@فꔌE )aN>B65MaS0 g ;Ǥi:5uJe9@!֠hIZZk,&0^Ľ  d8O;;P'e 1PHB,RcEKX[ʘ'Ԥ 5v끞n0vhD#vhD#qE#E͠]HL <@EkjD IJ)w6sI]jLQ=2xk@7&FV\ojmP[2$oKA]>B sE"[݁+2r2U)MLp`;&D*@Tx)EZb]&j5vR!Zu: (@C Izά5R cBC^LT2 j)i:"谀T$T5 'PlM1}Jfb2+jF4hF48 CYu$ѱuΪ=î&ԡÃeֹvK5g)^u ؾ@]7֠n8:+ ].^Ry_gG )E5wv2U$um{2*R}jbŋ( \Q&$Z(UYHF Q4D5 X1yz:(=s!A)~&>}G>SPȏ'z/KO1v5F4h; RidN4hEqMdde9#rI%@Iy뀕dR䒴M|.̼.Pʚ^};\Wso1ktN2 (<zA)stz`W{8QIlH q%°ك!Yۖ FXL31ARUր{MDFTa`h]ERAyzM5.mS߆Z,1$ NzO;2dx/F4h; B?A8 }r0y0q41];KFEǟ}:GG/ÛxL8Y > k^+ ̌g7߿Ms9^}߽箏w_16h-2Y<69H)>实smN>tq9am2: &q:JoR92{غgץWztZ :uaX 團KΤ$Υ<̨J͔J9x+p(0V靖 Zzte`-XE=ʝu.#hKA~K\Jw@W'C@"e3З9fNiAJh`w9F4VB +hs.7|bkWY$ _=]״z˅ ~p^t*ekq{>+6淍#k]t.{.kc ժx3j/6A퓏`BeovQW,ɔKjƸ?4)@sWڧ%`?ua";7OI¾$ucFcD!ud!a#:/+_"fR"MìYIzW :GT+7qpaX R4\Dәq+j1SLɘT* M!ZUqd`2%CWj%ʥւťZ FGrQ4Db@]oz#aM5S ƽ27|6R/ݥ,\𩋙g_oɚ qSQ+݈F4ql}:h֖8N&BJP1֧ o`gS7V~r/Q^Х^)HPfRDŽ%I\xȲ\ Du KZ:I( : %yr"Z)xJTy&d .JЧ\cE.^ 1&$P"Uua 4q\FEZڂ9036XzhD#шQ;Ŝ{$^_u1ӎD%0l>2(C3G_ ken~r&Ĕs),ކOn~t\¹ȼimN^}f5>Ƣ~@3VdT&4K}#'VVrY/\cV$) xr|tAd5Rj S*x&1uD^3&#j&5a7T̰THiSBdR0ZE].4Q/B)xۇEt*Zkb(O{@5#Fu<-MFGS^U2B' D@'Лxn Vq6}]ەsR] +N[w^_{&h)z`VPb0hg;¿VﱇQ[^߈F4Ŭ鿳z 똾WG_ecggǟƛƷJrS8_\k]fۼ\k7~ɳɲS8 9b\<6Xnj8:.-tޯ^'9WϾk?7W%&T&Yw^ֽs?U*6MͩJ0n>}3k8ﳪ1/U/ʮ;=ԃ}[\ o_d31,.Fԑ3οo<%όn^1N#wmF4Vx'"@u'56l(l|Uw\h7kWe :uB}ȉ,\0{ d'qqE# IDAT/ZɺRl.5,e~Y?l⋜EST4*yM{6}w,ßpYp z;CtKufh^ˈcν?nO*l}q6jIǸ'VCܷƽO7Wm3=ZAVNtYn#+eZ԰W&1odH T{(>[UeY-,KHlc'f3BӁНt7e%~NoD4& ah06ml ɖ%K*|s{V* HuW w8~?>'9#ڕϾ/͢]yݳ,c{F.]ϣ B?w?w>?s+^F5߇r?)'3/S:Zu.GrE)&@.b۾ z˝u3##Z  |0uzI1};(#]GsY矓b{7BB;EչF0JiEM)^~9.'`,Wʼ7nwW7;ӺM;qHDB jcuJ6 bI8f1J޶ɂLI^#M$Iҡ!&) EpT8Q%󊫣AK4=[񖽚8T͆Гo D>wQrL檦(B떜/~2Vmx?Tz v/a,lnF`҅Hf6_z$2~9u5j1Riwrƚ,j'Ndz)Ui}U̘jW]oWh< ?cZᗣx<5 {:}]{ac_n~?gyޝ<.v͎xɧ9eq9g^/=;f7{nƏq:}`u;\}e[0FPHvR&r_DPS> b B(~6<[:,\#R?,iTBJ'oV2+2"8/$ 姞 ., %Q psʞC2t(:$$Sd&kqH7m]/H oAs|R^[h[d>A#6pλg@&BvqFdCyo])Akr\)\0s 3e;f!?:AJAI~z_ 3\BA,ҩE̪Wphua]}$㧺,{yͺG>^g4GSX& 11ѭ!fs.iaEPrvg*8erhEZboOiV(Vxkoz#f͡{ʾ EJSF D@4 XJ;\;Wm'rg+'8b;/98FFڔfs. Q$[*.mte]t^o$,7]վ s۱9UIz5- $=w{fhSF@8Q3{C_ƛ;1y۫w͂ >KXlgz8,~![}Q㕌Se*hry8ly&WZk,]<+F Zˋs/$5s٩,K"-;oqs:5mMbu6PӞT׼֬?;>U>s- (W]?6\.ڇ/dq]<"p=~do>ud`Ip Mm9 lZ.S}/nfפxud#'03VsSz,d:c'z̠My.Γ2 T"P27hMAm&FFÿ&'7q<@~ݳׯ>U`[ o$+[^"S8bۏ{W2ۍ<΃]7}x1`ʓPO~/32}bB"-`}k8Գذ᷹bx+O>,ۇHD%ְ~YNvP沜ϻ>)zUgl;O\M?[XPkgxq"C i.፯[mϙ`cnݡe b'=@0 X'BU1Fct„u0JI^.M?wΜjsDWU-WەΈ2$]ɼOs.-ԛ|Vn1P 9TgE.z($I|b^fØ4Fc:dYcBd+K)Eߍ I7I7Qnm6 (S =p`L=PJ{y@{ d177/-1޺}xxMvzk.k_wI{A/17S旞C~l&s]>+pv6a{?ȃ:=|/0rٯgv-'ft[^|^niCOvfyEaNg]|?o+ʋ^7弼)`c?7};!-g[BW">g"arϿ{O5~#?"޵bm{xhv>ߘ2>6eKIqsƒe "": d/CA-?(+2NJPJ(Wd_Y$dpn[(9+ W|uE#g,9Z28;)IQwv MJNBjwl-vI}>EO䨐xfZ hgpd.o~ cӘ,y)jqe5vJ,d`h%rMg|~;bK:oO,]ɹoy'`{#lk|{rp(V_kxeC| ܯ޷~ÈY t3͘6ΏZBwβ.$dNg7if9H?+UҬ&!{f>? Lp2tb_-K-*.nR<9Ȩ <'ݹV}z6@y"CC\ɂ\ʂˈ!qӓ 3F$]&Geddݻw0EviKhwNבe4hp1P]s[}Qh84Oq.#K29®ٟ5Idm{ &&;czN0ܝ8]ctW濒1'/Mg=ﴦ={6~\cr˒%ii)'qH+:=vȸT)qP> u &R~yre$ &i;@#$XVf %ϯt'Ռ54gn?X{;<+gU2ݾgcT׍+)i6ISw6 &0)iE:qR>n)0LWcN=r|LnFW,2S}'{bQκF/OLNL.&IZ hĻ.*U-JH#XaRI) BHX"PDkYd EZ0O&"ۻuf)'6Į,᫪u*$8Wl<;|*ᾏ.ڌ,u'}GӕIRٜI*̲3ٔ3]:mq8ku 6SZTNc&IIq8k$ ~> Ћ 2݅RjGkԨQqM j `Ё8mfRFGHꒂ5抣5~q:ga%,\0Df$ )d3tE9dA&6IDQH(!H eLy@'!jZ߃eSMȲ{ ΥXs3~l2{8>UGq<}z}9,(r콼̓.i&M'd6+yNrUn&'9 پIzlK45MVrR\JEDQ !dDQ3iٷ,d]oP[5k*~5jԨQp豫QF)V(췆'!~gKe3"7^DQı-` hAg)Y#IrWYF5jbWF?Gh;2…C4[qQv] U;2 iE~\:k(¹,(4žR ҕ䊙7 =D$&h%˺X%mK*aQk,2ПFD- R-*BI2 Fe檣s~DQ4m=O]AI)fk6rV-̉Zn&}eҹoEj1c*: aL^2Ld;J!dҤGyRpdo1OgըQFըQ9vÊ28D)!asҡt.y"am`d≛'+jq.R$M{\3N7ExR牊(]?i(EʛmUEu Y1 J FȘgv9->.b$q:ӤehtRf U\ܿByEQT+7BzFn5\ut.!FIO NyrB5dYJHn.)3͝2-ɃQF5QFƧX`y7C !<%*alŜ# J"9Ƞ@)s^U%!22am( {y_@k&hbu}Y`LL F%eidIL|/YD ejE |j R e#4'p )LHZ^Kt *)ZDQLWA^JxmEL']3!fog$neʦ'vAyP_LSHiZg$AZ/ N&r6֗Zk2])MɎ7q. 7'w"5jԨQ0;l5jԨqdc׮,]FSWΖRi+<QHyRoD)Q "NHYuCdŅrե!MҬ\1 7dӅ:x@=QeKb^ǿ}I?+wru6p櫖2iF^bOS/L-+ 8eQnţ#}]Z|ڏ w?;/V10Ni38DHý$z<̫lW΄Dߣ DWϥa9Ow|tպ,w=wN0Ɨ]*•r:iɕ3_&+yɤW⸉TzNMh!Ϯ,!e%J`Qba ql.Ƙ4T͖ajw`RYd\B u!W|&wsbɲ8+yi^ƄK/ΚVdNkKc}=C~э.5j)Z1]ek擟x+]*urr9~3om/A.}='z~QŘ2p)U8A E|+` * }w\N)Q}X5eg]OK))E`cIR<j+2\ثrқHTL7QUy.Wt|}hԲ {ƤA,$Fb9U[Ui]?;TR7]Q#WTD` :)^1 ua0R=^ٵPkL}RzpH%ﱒQQJރ%CTBɐy,zg9mYl2I "SHT$d;{R]5N2d!%z'QoBp V> 07iS?R?R6#?)M\*%VFe!'Tc:b r"oR<@\X%MOda\_/ GLo⿛iڗ819_5ybX{ьI~i:*>|P :J>+ ȣοZe3{r=W6On\`UpUn"0#w}Mu{zoB^^~nٗrͧeDyλxp{:=S՗\f}j[ڽS[c0~S9sU,&gO}uq:͚˺yAc|'3wY_D.~[X/osx\cF.r6c O{2DQ3WHr5>)D(P˷DGkoE\89g f9kAJz'DcrV@`.URɦl+MJ*T(BEȜB%QUL d_OO>?5no"T^T[?Y %Ƅ(z $e[5D+` Ocw!Ӊq.1yo>&Nk:c#K8׼a~|7M?aq:Nj\mcś^Giw|<0_6Y"x:NYڕq˃;"Ay.RcQJꂥ=齨?]PB&^󒑭@ 8kpBٺc)d a8_s5i#_ ANDžh3O{;_)HioӿL?|wƻ[vi﹚c𞭌kkxۯ>uY[Z~_\e9?f,ӿdy?| iS7$Z]·xk['wH烿1xu\_q稷3>vDj}.G]ɚ G_ƌbq;ąow&!ӹnss&ޟ 6y|)Vo~8?|g8aל|7=l'ږ(&@Й@IoRW(2',.<ϗ{ҕG؊e(7 v ce-V`q}Zp[jPZGrO#zOlq{c#ΊAg-TI`YM'Y*M4Ux}Zl[OؾݴW# RUU?!$|wuUVTUOƭ_[*uZ;t2ql71T&F|U˗?ypgGS$q+3f=npUώO_޴Rl̯ܥF:f _{d ]=˜:|~6naRN_|<[ [W2f?}a&q݂ˉ;VVo[fwK"{^?w?w>̼7Q+h  dĽ= ٞ݌X ׸-wqӝ X}%vr㨷c{F.]ϣj`No)'3/S:Zu.GrEӛ^y7{mcԃ<:ym-ꤍ|G<̘˲^Ы7("M5*R(MUr'љCIO uTD02)BD@0bW9v$T.Wm,R/GkEA!P! -ꑬO%W<ա2C6¶߇bdćhNqJ$8evKFHѐ6$}{+{1\U͐97Xk 7Af/fc,֥AiLк1ݐ!(pkm ^ʓgc^B\K?lOL $h-Zem\`ɴhGZĒ$G*!/~2Vmx?Tz>ϵ?϶iz#q̞1YbK$n.%ܥF9ODd{_{TX7{lXrwӛg7b=J{wfo5^v<Ͽd;y]޽۷ԓOsr$ϼ7^zwn{hgNcZkmnsq7sֳ2jt<aQ5vD[!I-N:oT}@f^ ػZR8$p' +Wl"P*q+@qr(ȟk/Z{/e؂|.  QN: m0Ȃҥ*sbrKgZuZ*_:C^{u$$MG! އ7B\@ y'1.ϙR?HkTp31y\&*|Ҙ]nuXA^RU"TAGl$Ub+ر2KRgd u4վ3$I֖^/Ȳy,\>~ɰ6O0~cy2usG|]FW4C hwKvb`8]΁oRj\$Z<;xlX#0V^ʏ5V#}8Ϻʘ3۹]\lZr/OU&!g9&nBd(AW`׎@kT.<)/K1g|4u/yX K'@(߳L/%cA5 (`xc\ (üRTrڢJ]P* mڗcӢλcn 精r%EfWT8>~+ Z#D'w^O|Fh2{07&/֤DQ4eGϗaH&lp6O&6A3hp1,dڑ"< nFh$UV\~%H3cZ,Cn=hhX7SOa7t-.5jl\N&MN;o'9D/$םÅ;ur^b'? ٞ?>7ʫ5qo=l&[3ײ#+Y譺m:9~:D2~y'No݇wѝ=.bpYCsm^9d9'<<ژ3NN'EFJicG8ȗ)&J#^9yJ d,: @'JgE 'VK'0V|Y5@ܫHQP {5)R"RI(KSK93'v^f$8ֶCe sIyp[Xf "!k)v~rrW={j:.?}I*ܬZ%ӛd_]Ψ^VO{62\tBkKktR&{Q<ūy\ǦόKVz|JXElW@P_AQ+pgaosV|YOKVaXnqHۡ9G쵼%)O|q}\=T#H8ĩ/LS qT"OŚ`!q5!KbmXs tE«'_o _'\ٶ[?KuWr[ɅAz5vý> ֛u{w+C# Fי~=o?;ܩv-'f7K틏KM0m}詣T?f]|?o+ʋ^7"`c?7}q:u;1Fy聧#X~{8p/as1<,΢drmYF1ʝ4imF[Ru1^h v=g6J3xÛΘ,wŖѿy>y#JsL3Ln77aݡ\KK+pzߒc~㣿I.>#.gg$s#lq ;L=Bx- v)1z 5Z(cblΐ-XLax2_1ou l btdv掙qzPv朇wI)YqN8a5i""c%&ZGFh8)| CR<ϼT8dYB\i3^ e8+^rNW8$d蟋*"y@^Xty+ T:Kt"`65Dq$b3'uJ2(BE+pe+*nq y@dAPf`V:?`Ҭ]ҴM7EL黭`ڠԥd%M=ڡMM4Z1Y&HR,ݮfbǞ=S ]_VZ,]8`X c_w.5j梱# $QmE5 GryDA1@ȢוֹDqz, kBDsU ޞu<d 7 RΗ<%%22Ȳ,Khw`t+}56yU5~n  q@~ۣF&ZQc8ZG &&Z 2 *uDϠӑPdP! '/*'%YQN9Q R=ckh_J.xΆPskF916!)ePwuMQJh9o6祓jYCr7PQ*eN,sKbV2RT*veyI]~ ƀs16B %epHm`L1qܢDWc"Iy p"!|6,,1F$$tqCWDn>5j,q|ըQF>Z1kO;ŋT4ՂfS00-Igg"RPWu 1AP*\0'< )ZͿHDrKy]4wc3tu;Y.YF)TL?(O +Sn[?d0F=o<2B8(cC]C)y'JFHDB4JPj( Z1T;|^ʰfR*x&Ȳ^>i֦ככ MvFȲ` dl/M념vM2m+Αe2azҗ4tI&',I]}IeHW c`BJCn2ct)jϢ |Z ^_* b(Y!>4"s.'vR@L("F!?+Oc}e4uYB%M%Y SɩZQFըQFC_@ )[H)HC,!,4wb EgUD7ħ$.BX!Q41c%P(†w,leX@H1y_(q+ eޕғ"E3 H]j °,ıIC\IBb#<S*=ep(of Vzbg R_QZg]!ޡYBFØsA^U$n21jԨQFMjԨQa@gx8^1B(::?slK{{o1L 6IL5yg$L%3$%clcl\0ʲ\d[~i{8Yf&]W垻{>sbHK]C)PHeTIm\EJ S_?`9o">) 3 /LhC TJSzqӏ2)%ӌe5uԵ}4 X7?i9O%MY$;Sau] QF.)Q*9a8hۗvMM)}z/|yOx$ZJ8u=+e+l[]0ۤ/-KôKkTC4@\N:uSֻ @3jkϐNIɄax&++ 1_k^J1:$%WC=N%6Y. _iKJ+|!8Z8{2+r1"le[9]{;˷SEx)IMC+8/'n/FVlOky>b #9-xIir%-#G"u$Jy%'4E$Re8-m3O)>K6p}%O özژ zIu]pYY ^Zvpݐγ6iLZ>p}ٸ&pOn{(RhTupl- ]Y`YI]<N ,!Eg]h1^_Izƶp>[ىP5|678 Ojr]mI0m Tf]@ЛQ'DjKl"&,Yʄɖ]'vh&t0< X48^ӌD㾼1kICFl]O)a/wF(E0l_1ӋKI^t0%Y=w̴#Pu -\7p!AJZ&sks (e$!_E W(eEtp drt :H apMS!QBKSмq|+RToa8•^^:긮8: 8GLSb 'uHD< NzPLTxG/ViׯF1iSgYfv{ [e~JQwpVMsY"YtpU /44 ]뚯y ?3\lOB)˜'r^( <_20ma؞XL5`c^= 3hŲh+H7rɴ2q]RL61iK;$|oEsFJDZ"6K4rON8JH!Ji8N8@堳le),Ka.icZiI) SF= @Xb(3"5a%[fZ c\;sL7#nİ#ׅ6 eZ.t]jk9x6.Hi OO1@ɤZfܝʨT& !H/$@YwÈ \uQLƓ&i:n`D4t]CÄđ2~Aߑ{fZQ2Iؒ2-m-n]7J"8Q?p'`Y #N! @ DJ̈A8[\률mJ'Ly \ tI$/7]sy a ,+Hk @W},ib_Mڹ+E/CD]??'cۓz7t?6GĒ3nV(-gu 2=vd;Ljxz]C2f3qpFC6 bQ!WqgWFpֽ̋ky$\07oG2x@Y86Юwyskͽ5B`1 (݉Q7pKnV7!nٜ*/ۨi47],vfb LKC5T\W&;;,I`/~ϟ]m?M ,ھrno%usi&ug vr$yS<2&m#e #*1l =p(Nd O/xqvUu+}K;N2fNaY:gژ;&KH$,qE".Ƽ#v)/A$;L7jG.a0 grnCrh4t?E2zlVq< M5ZDfp㤩L-}~Eu n:fO}y)z錚8٣{ISGyyw׳!0JR^6}8Dd|~nd#QfyL>>~=̳%GqWr̦T״2fJf,X̜yMkM7c-DcQBe^9e ]0T*yGD !ې"3N/f nF5i$qT)9dF8WL xIюIWL,r}]ӬAӲ r,OlB<$t*ê鉢x s]xV;˒8v\J`Y6G1x,F4J"!lA"!E5  Kv>|,cc_C߻[_~ϾK\u_LO=N K澯eq'V]m[|?\E"{7= Ϲl'ca3/ǭ{XD7^~ΰws(Տј\pB%>?OQ/;HڨN/?%gTM}ng6ӟQkohF5 !ZTOpzۼU}iHH>fMeQj:Ld.;djgǘ>K^YLYKg~ Gvw \1z (*А5t4{x˳f;U}ӵKAQ|4Xި(d'? c?Se97d.Jsqs|;lH !N!L3j~r/..:3OAF- d,Ryy<)g&sIy@9cdBnOC`g/㴢뤮gO\*t@J/} R$R \׳|JҲLˏK$'$&e0fBU6~? @]J(4]~֪_N-huPR&Ϧ\B/PRH~znڐ|Rv??uc+m7YR]b's^r%~,o2/0RȤ+LunOt_dFɟ]1~J\0]q-qLqx"G  @]9hϓtX:ַ[KY6cq7e4F_abΜ CهkZumRti=P&ٹ䠈镺YT8/:w)NoMؖCSS RҐ(F:)ݤU΋>!1h i('c鑾tԳpݐOԻRLEL)%]FL,ua3}o2/^y o\&8JdL?-]/}B_znN W S--[b3HhRʲ5{ Ŝ<ٖheCb\>uʮnXxŰ IDATё,]]CĢq-1X%pUiK/A i*ړX45,qh,Jkk+h+xXxxDl2l+iF,nqZqV,ljaۭv͎حf3݄i֓)uR*_y e-[6Vp=WٸJ(oW/ĺDuyh"u$ I\6G7pWGP#MQ =m=kzL^x[X=Ql,sNc{W;fփ8֮蔕1tNԴ :i. $wpzn*ֽ˯ܿVU=YCضA^~8k{JH) 4_C#b CTh¶-,Sb̊-ð z]70 /?MfSb8;'e8NێH4aMn9I]{5Rvpp) GJ$c.J[M'iY>R*Eӷҹwy.vؐH( O%3  @]vP(Xӗ9jvkw'*^/\qВ؎r"2񫥨]s",zoB&N׵`u(ĖAݴJ 1bԵ؁Jv+ [;"Aӿ0,͡{SHuz:,/xYYYdeB.,t:Puh`YBF! Yt?[2ɚyʘuM_TK8qtT 9^tRQ&#wir$zmR~Rt41k{mLReu _$>RJ2}eu=9S<$_ {~(5D{%.V½|&4$ಟ?nxsN-y=P8Fjc}][C,q +K1 q2"-I!)7J81а,ٶb &v`s˽LP 7\rvn.:llmsuɎ|a M(tCKS>y\0&h:M)M]yߩihBK(M)u.ʨI+N:A֒x QBR''\"*|A6<)6,I+lYDӕiMrTff\0 @Ǹ2b %e&&<˜ВD-);&R0@] 4RNxHBc뺴.9!s„BJ L7]S MxEE5B!M}4/y FRtt[R3~ٖyJĸAҲ}ej'pIK&NN?t4\k1@:b @.qhjvhZD"&H]z)4wLZ<&04 TDLL'_W*L)2cN!TM_AT>$KZy) GKyy4l[an2i H\]$p]hԋs4bC8 5н;/ϝ A]1,{8[)ݵ%XdX r'RĽ2dK.Mmi rtIWöq(@ƩSwRz^_D)I\ҖL=cy.c(XIL3onȚlr,œp:MT|?TOn᪹r 66Otr,X˦N>5EӦ[]gYfE$طyx ޡ1qbl?rXuSzΕ?qsi?)0OlKT%@;'Oē8-{^7g|ǛhNn[B'W97Uf25;C`طO{\S [`qW(W䍶vc|Ѧ@ &h}M^8$uaˎVM?zM.eY M;,<{M/u0&˲xn03mV_s&v ֝+r\y|KnV]rdk **99g:aU.;ɹm5{+(3˶;-aSF2HR}v[gLSy(dyc&S6rIO5'~b0U-#S>Y;Gぴ^d S,bAύ`M:}+Td6^ĸVbA9#J0jvij6nhG+gՔl ǒ/tB6ٲwxt<&d]H4=6¡ )N{!Kad'v+llxe-[O.?fLg҈^60~6UnאܸQם"[ G '8]z:;t^oW2x,z':ɹh"K-/}m-o9WiΜt"Fl>,MAr̴̺Tk )aҵbu+Yk(> ,BұMذ?t@2zlVq< M5ZDfp㤩L-}~2c r7|".zØ0u.Rk{u<(ӽ,ZˉN2c|8f&zᚌ6x#b=y4 &֭v.!-_[Gec6x1V2cb͓~?;Sgh g,~79~/=]:QOʢpn.\N衺ߤY, <d΂yӟ˂)/8 HvVs+Oo:ե97c[zcWepu̻j{-kvZzyw:)0aN>5.[p0t]iE]C߻]ḿ Q|f?Ρ?8&?]o6AK?WRE~ϳU,(R>4韮ONߌ6WYg[l;@Űn9%l^]">gXGxjw7\tl;Tא0/ [q77Ҩ\(qLK> [Sf-s>waĜ9ݱGP2?dCRj+LOAad.iLrdʷܺs5#:/|S"NoLR45쌇W>΅.;\=O8;~Pì~yMZƎ-3EzϬd^Q+۞[$W@  ֿ~x_wDeeEhvYbĬs/I dz]]7Č3j[Spؖs˒qnlfY(-=<)HbF >|SÆ1vdT[ ,+EkI&.DcZEF=RO" 1Nt`=B%ENd5,( ]*Fž*dkjˬn=s]6m֟A "4$R =ލU65ҤcGԢ˒au,ֶNUsy}SFLjJ;)od'pcES~Ejn  4Tɯ}z)(ꗏ F= ~ IDATGx( t]94j/ߵ'X Cec?Ae!bED!?Yn(eOc;\zZ)@8}&} =[iSY}-qg4+@[9vh\QtO% /| B~ 49 e ]^>{I jh#O.&.ݜ ]^wzVug:7n3>L^P8O;3+VKuKl Up^1f/3iٱ=zJ*ww|y؉?gj5MW1stzEaF>~RM aK&:|8NsS߽6TrTAPF/e3 }Jhd5ot_6D9w\DdבS킉uɟ_~Hϡ.8"$(89r.\<'O&,E$9ݭER^ !iBK=ʛ?d'ɿ69jkk9q{Tp|=ǻ'zc5oc΋tn݂7Hq9/0#J(JO %ut<jjXb Z2 Nȝ KÇTC[JA܁KLS}-߯`Pi=#5FON_=7o]DSe-p=~r5i/dnę]tb oqݩ?zyպ)3EGwϠq?}}3931f]{(݅1owt?6{^῞yNkMYvf\Ď'6c ̺>[[&Z<{w",*8ޤ@VՍ52hP.zE:#>ϊѣ)0b )ө}8MS;Kh9μswC1S>6n:G]M{XGǝ9ڪwu9Wgz  뻺} +]=dʘa,Ҝΰ'C2k!k)ݸ抇eOʘB(N_Sw<d fP8û16<=NYNjz̬ͣpŘ4+l<֭'ϱV4heٺNԭʁ0㦌#bW=uOmG %]X35s&v́MQx) 7Eũ!qn7?0t&Wپ'ɩ^\fƹ~B(AKaB$(>G7 *%:A0rQF32|A]VK=a $6ϸcviH³Oѡ%EO(UuF'l "K(uzP?EH{x{gQT23gR9>qŮzŤE^@ :/k.vuT /<{S|q)Do~:*/IP,+)&+6:X1p>v׬}^xl V˧+~[/SYWXg wo͸à_,|=^}E( : GkȟV2b%}3.Ly-4a>#z +D6VAl)_ƨiMػl:-ȷEZ߳!r`?ζ2l& jf7W0k6=TU5`FC1jޣ4N`j'UH1QfxϞ5N,ZNΒ.%u(D(଼~e :HhjDP+f`mBXʲĎ]֢Z8^Jai1CR=:JsHF$TǞj'Eˏدs=bGy%J|>Ovnٳrmר鎎@א?qם^L9@3zV/'sUn]8St\csqRYuoOU9ϗ~P4sFٹ4l{x|{OK8/N=`L.4ͺF $`fA]ϟ@<#9 p]p(5L8h4(̬QEdC3^qc99XG5yez;!K6RgRf.O͙N_VI1`LƅE!V8l&#pL| 2dtn*篝hJ'TrчOfr `пvB_֮zNK 9C[C1}L=w %#G(-~g}>T‚[0BD?iZ6(q'}jEYkロv,^{+;k (BǏrXGRFBuuacrNگw w|~%#hB5T7чNaF᯿3٧;]ݻX,(ԡDBa8|teh.oqݕLsX2<~o{>)>1m]Ѓ/֣3Ewݛ }SWV < +_-=f-L8y\ hX8L qkYΆUt"jaO )Qcw,M9b7/˯Ok}ʉR[uO?﷟~s>Ο}oT7ay_-7ͤu#';Nޭjq|aoMuz.(?YW Jh4K若,K3׶lE Uϖۘz<.r5s))ĜCڑV4mowͯbF-A{כSWo/ fGiѲ1|>*_hziKF0fxL&Kسu"BQʧgum=$ LY6866j5є^Zxy|>0jkuNә54z-;cWڭ#Becs |%!6~ABg!nɁVpC'euNj !'nZ{#V#-S<)sg3QmDzݷ!TEk8z /̰-ur(;YO/r՗D^>Z*zAy:pOL ffkxڻO8'uVΜD֩5Tg.ɺwqߪJ۽86eZJFd҉ L[ճdtstgtu\w~j?cwqƗ_>=Һ][>K{I]ksdJnÌز=ꉪe{u.~;+i+ $ Q$ы)6`Kl6.oblj$8q$;.1.jcS :^wjwDQ#H<'O`3gS33g3>y Q6_kWuG:4h]9C7wD#;vUWf1cF1X7SX\͢.D!g5*ov)V1΂ !lXVﺊ9n{hYYWfU<vOCLned|^7R/|3XKǹ~Mu(KƫoV ]l_}|% cغ~fr*p{7_|xLǭÈa[,qMG4ww;&ԝ_iz;uRv.w/eI,Y}qioTY7>T|}k5xYW~81mm٧-;8s@bETZ3/_A HdR$8ЦMSZuK3(S16K-qvXqDӁjm6 Kְ|L.b⥸Upa'|@y_i\6@Ƒ>sMx2E))vXl:uSnR_GYZ&1:p&(ഐql9Kg켴*'3ی@?Ԯ['NJ~\;oGY[x+˗d;y2c?} ]VW튝A[=>ѣWލ8f cC΍mnĸnkfo套-,]2 SՎ!0nn eȜ=vTuL pG*$/ukx,i}Fyw(>cy'vyw05ο;\7/@;{^3KJW_8)N4k~O?iy뼃䍛j\^Fg $$ Ê:x탛ӌ'H{N7;xi|@a{~zNUV\kF7(IYyz1`}pZ^7 ?/t3F,l}p.zG <(gV}p=<#0#[p590zZWu'_Q 5*4xb0bnT(_fa쇛utՉ>+(3r4. 7Վ`vtji[۾iW5[ۤX'[ ?`wsyEaxҁ>ʎo7F\w,4z_80*1)lmStW;A+[nc`J;7xޓ$3GC:o>y:?_DZh4.@B;,Z۷rR]IqueωYW UGZ372kUFu`(Ҧ6MGFFƁffms9EwӞԲuobrz'9qă]:΋=QŜi˕BKA!?%Jeœq6g_˟c餑L<'QRu}6w$-><6'I9[|ѯwMEA CQ>,;:ls"]971h_;߆xWJ;Qɾd|&ږF.gsLԜ;ƉYTZ{ٔ^cȄ ńϠP|.WMˏpUWPnX!:ەG#3g/'u6LW㓆zB|*ʩ:zao];1ꃫyD;UŔ[vB.߱FfSiyUDG'T{v-i7yQŷ?ފeW1'< a>̚-Nb1!eLq4{"#.'.̹k}?-1a{kU}[}tGQPpY0!qx}|d>ޔ\0\YPPǰl,|{ [~VüG0${3ol>_p '3$F싟 =14Z %#ZJ{19{)vM/.|CɵsGnv3n9{r~,CӘNd}KuoSߗIN]Zi|ˍB!w"Wqrhiepg W"=aV¨Qu7t6 { (DI`1mY#y$<++?aq{-hpO7`LIQ9U:B&2<.oȫm8$cBy6⇏"B5RVa#&$ _!nbIJB ܅e)lI8ؐ1G4(2}X<7%6u!+o|Uo>y>̲a+*[H4 ͽW 7PYY4gu,.wOdD\"&lovSิ`;v,8e]<kgh }4X eTXT!{p<WH_T| ޣ淿!^N,%[5L섙,(wZ!qӘqvۢJp̚ɟӞOLHa^1ѳ8goDߌ f 6{r-r;+0p, nW Oowl_F%$2~*S=Md4G똗OɉX̭Om79n/YA](Vfܶ3oRm,!c9*Οإ YxQz'̌G|o KRWtA9 :.|oɾ<5ܮzFLcF?x_g[2r _^╽%!a Ҳﴣ8Y0ٍ/+egc2eL9_=mlyo2Oq-E\0kW(>LXv;IY:2Ե{7nYVK丱[`2~~2[.5v6Kﺍig1lqTp搑dNh8!si'Gc;꿮)5e㬏L`V[Ό=SI_7mC#P% vHRg]YC/?Ƚ r9}^/jfD|1R_^'vGjAg0O1[o.+Ɨ+~'U>ǡJyXquQkr&R /mS^CޞC;5_>!Eyyv%~~mt7_:+{~]721 KD_- }i Ppj'om74!č CSzۮ$'o)ҡz !vt_}:p96[2*PCS$ą4JonHCA1M.g;e8bVA51*3}8ZrĆB6|հlNc}~ @ @\=j7]^ٵk_)g Ws\2vo"&ANۏ5fūuI x쯿Kj"+B#-* L|%1*T(xG摑SW3ؾz5G*j?Bv<;Ɛ4$&G{*?IXAKr߬Pk4Rz/6Qz\zJb2qn.rgsMyBݵP Μ)j0:)>9gWY憓IfF4I־q  >zDžlKn(/ҥ鉆wT3F Lή 8*ʣfŦr U@_VM;r>m74~ipQL~d+(5@?lS]>N1a3|gYk=Xr2iɥMh#uƏ j^1%=*(t J˯$qRJ)[mi .@_44|'Om66ܔvO/}I]* *frSif4z̆cq^F604PBt7(ߟ< N߰P4oɬ\yͷ6ӻ_6yɉnv~$FAΚݝXLJb-.JV,.p4hF>I֊jr"_8BN8isgroh|[9=ì3|̳lF)胣?+]/ x-Iw_>ÒBr))) /g)4q4lijA;x!ݜf6UR8˅قb*k_G3}Ɔ|!4.H5 G;[\Zktv]8-MUqNܶ;M7]ެu*`fޙ܉X}j(Lm>g:[fx.fQXOn9Nܪ&;H϶savo6!ٙ4|AF _:(WZh?ص ,{#&{O]Qnyd*Ko@ч1~\kk8]??ǬZAM>I9Si2upfl~_}7?ÆJՎqx3XyP6XSPU(.3mXFπQ ۙMsռ"]q>ۥ2dZ@eUUi +8ųf4!Õ%76{nиm[t)|vlwsils퍇Iu|jL]3OeOa$̐q1շ"y~{8,?h[x g`NL ~ji);y厗O17i?(@K[IN c\p:/\Mxjx)}|km`8x t\iUyyOq;GM tZhv _q`}<`0?uG6^è;_f!>v4'UG<h=NtPϘAO}M^]%ز9xmGI-łuc% \:P{j!uj=n]\~*#G򥫾ݺTRuu*N )|ZO£4<ɷ,b~As]frKx(z]l4s|ʭ1 p/%ˣ{2QoX9=X'{f~D1}v#q#۶;}ק5<[Ƈ4k%Bndzxqt4T(mpGMOsy ~>(o틃┏x6ܡi:WVsgl=6lcH"8q,YEFl/ elbk]W?QRIi$5lάEؖ\K +Vp8w0n.;t ӦCI@My~~0d#!0ƱtV)\|*޿fBIQv|>뎖8BI^Kcԅ$xԿA_9{:3Rv.w/eI,wq{;igWu3 Ej7qd+/O᜽?a`9BQJ;g^^}6gbf0~V}k޲M'1,A|Iaof[ڕ{>j‹XYt&ewUCe~h#;a6{+/laL2٦1d3aX5rng$C8c; *3yenǔNLyGYZ&1 pn^a$]Dݸl\ ?@GH\ȶꞲui/%,7Ž혈xnloعOZpZ8r}L[v >ѣW]8f cCvϞ;sp 7Oy#t+׮#%asdL )gŲ~Ut#Cʢ$s-zo?Ԕsn?GHN_z>/_v{1<~&+?FK+1]~ A.Lj\FZLGoeҩgy|qjW?u:?_DZ@/ !npTK-pW\+>d{ﲳPDp1,[>O~'së|$q3ifNͣtFe5`%Oc+M'J.MGހd]9/8aSuŨ̙Grj43 ÃBIZ7>>y) "}j\YAEU|Ϫ-پQ6:t > $GITHUB_OUTPUT id: version - name: Build docker image run: docker build --tag=swayimg --file=.github/workflows/Dockerfile.Alpine . - name: Run docker container run: docker run --tty --detach --name=swayimg --volume=$PWD:$PWD:ro --workdir=$PWD swayimg - name: Configure run: > docker exec swayimg meson setup -D version=${{steps.version.outputs.VERSION}} -D heif=enabled -D bash=enabled -D exif=enabled -D gif=enabled -D jpeg=enabled -D jxl=auto -D svg=enabled -D webp=enabled -D man=true -D desktop=true --prefix=/usr --werror ${{ env.BUILD_PATH }} - name: Compile and link run: docker exec swayimg meson compile -C ${{ env.BUILD_PATH }} - name: Install run: > docker exec swayimg env DESTDIR=${{ env.INSTALL_PATH }} meson install -C ${{ env.BUILD_PATH }} - name: Run installed run: docker exec swayimg ${{ env.INSTALL_PATH }}/usr/bin/swayimg --version swayimg-3.1/.github/workflows/Arch.yml000066400000000000000000000027371465610152200200450ustar00rootroot00000000000000name: Linux/x86_64 on: [push, pull_request] jobs: check: runs-on: ubuntu-latest env: BUILD_PATH: /tmp/build INSTALL_PATH: /tmp/install steps: - name: Check out source code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get swayimg version run: echo "VERSION=$(git describe --tags --long --always | sed 's/^v//;s/-/./')" >> $GITHUB_OUTPUT id: version - name: Build docker image run: docker build --tag=swayimg --file=.github/workflows/Dockerfile.Arch . - name: Run docker container run: docker run --tty --detach --name=swayimg --volume=$PWD:$PWD:ro --workdir=$PWD swayimg - name: Configure run: > docker exec swayimg meson setup -D version=${{steps.version.outputs.VERSION}} -D tests=enabled --auto-features=enabled --prefix=/usr --werror ${{ env.BUILD_PATH }} - name: Compile and link run: docker exec swayimg meson compile -C ${{ env.BUILD_PATH }} - name: Install run: > docker exec swayimg env DESTDIR=${{ env.INSTALL_PATH }} meson install -C ${{ env.BUILD_PATH }} - name: Run installed run: docker exec swayimg ${{ env.INSTALL_PATH }}/usr/bin/swayimg --version - name: Run unit tests run: docker exec swayimg meson test --verbose -C ${{ env.BUILD_PATH }} - name: Run clang tidy run: docker exec swayimg ninja -C ${{ env.BUILD_PATH }} clang-tidy swayimg-3.1/.github/workflows/DCO.yml000066400000000000000000000006301465610152200175630ustar00rootroot00000000000000name: DCO check on: pull_request: branches: [ "master" ] jobs: check: runs-on: ubuntu-latest steps: - name: Get PR Commits id: 'get-pr-commits' uses: tim-actions/get-pr-commits@master with: token: ${{ secrets.GITHUB_TOKEN }} - name: Check DCO uses: tim-actions/dco@master with: commits: ${{ steps.get-pr-commits.outputs.commits }} swayimg-3.1/.github/workflows/Dockerfile.Alpine000066400000000000000000000004601465610152200216350ustar00rootroot00000000000000FROM i386/alpine:latest RUN apk add \ build-base \ bash-completion-dev \ giflib-dev \ json-c-dev \ libexif-dev \ libheif-dev \ libjpeg-turbo-dev \ libjxl-dev \ librsvg-dev \ libwebp-dev \ libxkbcommon-dev \ meson \ wayland-dev \ wayland-protocols swayimg-3.1/.github/workflows/Dockerfile.Arch000066400000000000000000000011201465610152200212740ustar00rootroot00000000000000FROM archlinux:latest RUN pacman --sync --sysupgrade --refresh --refresh --noconfirm \ bash-completion \ clang \ fontconfig \ gtest \ libavif \ libexif \ libheif \ libjpeg-turbo \ libjxl \ librsvg \ libtiff \ libwebp \ libxkbcommon \ meson \ openexr \ pkgconf \ wayland \ wayland-protocols RUN useradd --create-home builder ENV USER=builder ENV HOME=/home/builder ENV CC="clang" ENV CXX="clang++" ENV CFLAGS="-g -fsanitize=address" ENV CXXFLAGS="-g -fsanitize=address" ENV LDFLAGS="-fsanitize=address" USER builder swayimg-3.1/.github/workflows/FreeBSD.yml000066400000000000000000000025541465610152200203770ustar00rootroot00000000000000name: FreeBSD/x86_64 on: [push, pull_request] jobs: check: runs-on: ubuntu-latest env: BUILD_PATH: /tmp/build INSTALL_PATH: /tmp/install steps: - name: Check out source code uses: actions/checkout@v4 with: fetch-depth: 0 - name: FreeBSD check uses: vmactions/freebsd-vm@v1 with: usesh: true prepare: > pkg install -y meson pkgconf bash-completion json-c freetype2 fontconfig libinotify libxkbcommon libexif wayland wayland-protocols libavif libjxl librsvg2-rust giflib jpeg-turbo openexr png tiff webp googletest run: | set -e meson setup --werror -D tests=enabled -D heif=auto -D bash=enabled -D exif=enabled -D exr=enabled -D gif=enabled -D jpeg=enabled -D jxl=auto -D svg=enabled -D tiff=enabled -D webp=enabled -D man=true -D desktop=true --prefix=/usr ${{ env.BUILD_PATH }} meson compile -C ${{ env.BUILD_PATH }} meson test --verbose -C ${{ env.BUILD_PATH }} DESTDIR=${{ env.INSTALL_PATH }} meson install -C ${{ env.BUILD_PATH }} tar czf swayimg-freebsd.tar.gz -C ${{ env.INSTALL_PATH }} . - name: Upload binary package uses: actions/upload-artifact@v4 with: name: swayimg-freebsd.tar.gz path: swayimg-freebsd.tar.gz swayimg-3.1/.github/workflows/Ubuntu.yml000066400000000000000000000035701465610152200204460ustar00rootroot00000000000000name: Ubuntu on: [push, pull_request] jobs: check: runs-on: ubuntu-latest env: BUILD_PATH: /tmp/build INSTALL_PATH: /tmp/install steps: - name: Update package info run: sudo apt update - name: Install dependencies run: > sudo apt install --no-install-recommends --yes build-essential meson pkg-config wayland-protocols libwayland-dev libjson-c-dev libxkbcommon-dev libfreetype-dev libfontconfig-dev libopenexr-dev libgif-dev libheif-dev libavif-dev libjpeg-dev librsvg2-dev libtiff-dev libwebp-dev libgtest-dev cmake - name: Install gtest run: | mkdir /tmp/gtest cd /tmp/gtest && cmake /usr/src/gtest && make -j$(nproc) sudo cp /tmp/gtest/lib/* /usr/lib/ - name: Check out source code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get swayimg version run: echo "VERSION=$(git describe --tags --long --always | sed 's/^v//;s/-/./')" >> $GITHUB_OUTPUT id: version - name: Configure run: > meson setup -D version=${{steps.version.outputs.VERSION}} -D tests=enabled -D heif=enabled -D bash=enabled -D exif=enabled -D exr=auto -D gif=enabled -D jpeg=enabled -D jxl=auto -D svg=enabled -D tiff=enabled -D webp=enabled -D man=true -D desktop=true --prefix=/usr --werror ${{ env.BUILD_PATH }} - name: Compile and link run: meson compile -C ${{ env.BUILD_PATH }} - name: Install run: env DESTDIR=${{ env.INSTALL_PATH }} meson install -C ${{ env.BUILD_PATH }} - name: Run installed run: ${{ env.INSTALL_PATH }}/usr/bin/swayimg --version - name: Run unit tests run: meson test --verbose -C ${{ env.BUILD_PATH }} swayimg-3.1/LICENSE000066400000000000000000000020671465610152200140510ustar00rootroot00000000000000Copyright (c) 2020 Artem Senichev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. swayimg-3.1/README.md000066400000000000000000000054541465610152200143260ustar00rootroot00000000000000# Swayimg: image viewer for Wayland Lightweight image viewer for Wayland display servers. In a [Sway](https://swaywm.org) compatible mode, the viewer creates an "overlay" above the currently active window, which gives the illusion that you are opening the image directly in a terminal window. ![Viewer mode](https://raw.githubusercontent.com/artemsen/swayimg/master/.github/viewer.png) ![Gallery mode](https://raw.githubusercontent.com/artemsen/swayimg/master/.github/gallery.png) ## Supported image formats - JPEG (via [libjpeg](http://libjpeg.sourceforge.net)); - JPEG XL (via [libjxl](https://github.com/libjxl/libjxl)); - PNG (via [libpng](http://www.libpng.org)); - GIF (via [giflib](http://giflib.sourceforge.net)); - SVG (via [librsvg](https://gitlab.gnome.org/GNOME/librsvg)); - WebP (via [libwebp](https://chromium.googlesource.com/webm/libwebp)); - HEIF/AVIF (via [libheif](https://github.com/strukturag/libheif)); - AV1F/AVIFS (via [libavif](https://github.com/AOMediaCodec/libavif)); - TIFF (via [libtiff](https://libtiff.gitlab.io/libtiff)); - EXR (via [OpenEXR](https://openexr.com)); - BMP (built-in); - PNM (built-in); - TGA (built-in). ## Usage `swayimg [OPTIONS]... [FILE]...` See `man swayimg` for details. Examples: - View multiple files: ``` swayimg photo.jpg logo.png ``` - Start slideshow for all files (recursively) in the current directory in random order: ``` swayimg --slideshow --recursive --order=random ``` - View using pipes: ``` wget -qO- https://www.kernel.org/theme/images/logos/tux.png | swayimg - ``` - Loading stdout from external commands: ``` swayimg "exec://wget -qO- https://www.kernel.org/theme/images/logos/tux.png" \ "exec://curl -so- https://www.kernel.org/theme/images/logos/tux.png" ``` - View all images from the current directory in gallery mode: ``` swayimg --gallery ``` ## Configuration The viewer searches for the configuration file with name `config` in the following directories: - `$XDG_CONFIG_HOME/swayimg` - `$HOME/.config/swayimg` - `$XDG_CONFIG_DIRS/swayimg` - `/etc/xdg/swayimg` Sample file is available [here](https://github.com/artemsen/swayimg/blob/master/extra/swayimgrc) or locally `/usr/share/swayimg/swayimgrc`. See `man swayimgrc` for details. ## Install [![Packaging status](https://repology.org/badge/tiny-repos/swayimg.svg)](https://repology.org/project/swayimg/versions) List of supported distributives can be found on the [Repology page](https://repology.org/project/swayimg/versions). Arch users can install the program from the extra repository: [swayimg](https://archlinux.org/packages/extra/x86_64/swayimg) or from AUR [swayimg-git](https://aur.archlinux.org/packages/swayimg-git) package. ## Build The project uses Meson build system: ``` meson setup _build_dir meson compile -C _build_dir meson install -C _build_dir ``` swayimg-3.1/extra/000077500000000000000000000000001465610152200141625ustar00rootroot00000000000000swayimg-3.1/extra/bash.completion000066400000000000000000000011701465610152200171710ustar00rootroot00000000000000# swayimg(1) completion _swayimg() { local cur=${COMP_WORDS[COMP_CWORD]} local opts="-g --gallery \ -r --recursive \ -o --order \ -s --scale \ -l --slideshow \ -f --fullscreen \ -p --position \ -w --size \ -a --class \ -c --config \ -v --version \ -h --help" if [[ ${cur} == -* ]]; then COMPREPLY=($(compgen -W "${opts}" -- "${cur}")) else _filedir fi } && complete -o filenames -F _swayimg swayimg # ex: filetype=sh swayimg-3.1/extra/icon_128.png000066400000000000000000000563311465610152200162220ustar00rootroot00000000000000PNG  IHDR>agAMA asRGB, cHRMz&u0`:pQ< IDATxgmg}{J;|QW^e!! %dl63r;k60&laPFBU sv^ aiTtS|9g=럞.EVg6 øm1h٭5v߅XGU [! EpѦ%Qaf0w޽whkOl}]? k螿-]>&vE;&q5!;NYr>v=_L>R+? k^>u]A/[fZ1K2X78e<JU4.RYaTOՉ{v7~@W9^7Ã|o%~0j`,af0$e=56lU f.fgQCN/?Ƿn@W!x8zp{;F=$" E-iLfQ6k^/y^DPM? -?y>w}栒d\(X]"SA1!I b,<+2"+ΚǵM̛~bڟ[U{?f?.Ey"JYP O2DYAA6# Ӟ 5 5r|{ۿv|#.{ѦZ}/_^K:42[H!җxI$m{!-a X9IƘ0mS~x8aO[?CkOa#*4k4CmƒQ z m8`) (Om̖zh~?>_xD'W ?Cr+TAxePDO" ߇M1x#)sEYփj)^jsO#IRG_J0NJ^?&Uw7pr-0{ ~/ LǵބNZdt67hD8\ Z P5AXz0F T"j8)SovŹO;/{˧g"gn/>l8td}҅GxC2,_/>zrk7~s(S,M'S|iؗ|0G? ӫ7U.'ܾ>;~<^TpְHgƞiu^Oq!(΀Uwīm^8}~۟,A;ؗez<_VGo_kX6Cy}xX7xf ,A 6n{ƈIw>*;&n;k >Q3r?}߷D;T 7O_3Tjarf.2,+ aϻgСQ!(@`Bs&ؙ)5;H٫RHa/"ϙiΫ 7?;?@wgL-NiezAq^( "BGgE̠(M'T+{ 'hT*IA TUP9=ö7(zZrQ䰃cHJ|@AгXa8p҂P s"i)ZF+P Ax! 4P2$Xh՚/|޻Ez~u`774kT+F9x]U\X(Fy;]ԧM!s[sv)Ӭ8EoϫI@tIJA]:8S!M@!<(RD29sp( Jw [:!GEE2 bPz<`pSiꍮXdLM2j a֐"E)"; D*XUszOZc%g5rPSI͐ A0S<`PV(KI Spx%n[#ց|@b-.P [5f{H q.EHs5(T쑱+%3LC?ا/W_G>Кp76$z9h!qDB@q=}/#Bɰ8'@JdhQv[h9XFp|Ҽe˅1+^7+98$VI)0V mH3ŰH (%̍i8Hՠ, ImמشD EnAM|ɯ "tQع'6VOlYsLBeAT}gf"P-=B &,°Eȹ"Iu4f-jV8.1k5V&I;Mg]S m@Y zj@DV ~SjAև*Tar G}(/qxmBh" )U5N7jq' |5O}zikvD]-ڐ2⌴ TދI_KkNB*eq t@VLZQ ),<*_adrn NJ͉Ov(eS̟Uj Q (#A0]5cTk=I%;Xt*0^b ʐ%K2a kQ)EzWD`QSȠN|GxN滀g|F<E_V?j[p#!5_=A\Y2]õ$.#RQ! (evw#X8DhV#h`z)qE6^j~fB[0()zIƫTjSa>&,,/غEzգt%B-Fڂ 8/҃D^]0\'Ak0g]a5~ᯘ.DeTy@7J<ٛCRHZ3DI|qrF1Y8px >A,r Q?K/oT2фII ;ϑ%X tZhOT5gX;w. &Je@ .i A,׈@ כyA|5v7М8{rn^^[}o0nqx+\e)<616F֦Xmq\2b9vƉ̅ /I(Qʑ.a&uP'<*m#^itu,PJ iH1X-Y=1, I3$$:^%nIIy9I^B \Xř$V$4P)\ i=ix*=]'~c mNJWY>uZ{D ~S]8J:,/NpdhF`JGcA&VstՠRx?yDkCjae^E&F"&@$qT!xE80.3Wt<𕷎 &ZgP`駟s۵OK ~&89~Jٺe(+8^^X!'x%'0Ĵ;uEcg"EH) # z3ߕ@A s'0GyDc~bjP* CzpaZF3i^$gdv0=8)ʐ8yA!y GPmzrLbߌ -dau=IYfaxͫ3)E rBSf$aq 9^y dyD!h41 Ё! K޹J0]TIn(g. AhY:#Wά"_͏^DUaIu%_)c7^s^/ lJ?P-ٱዂSS',bՠ(5J$:BN $϶q%Yĕ!mHY?֣BtWa| W4{/$~Ԫ9W'W#+;R2c [EXƌHҡ읆p !5V(3D3ȅ4w|ޯl q;_?n=w?/^5R,]4 nKuT~헄.y~N#E΅' /}Z3&ʁ^0dnl:Tܠd@qhB9O=@TUQ;S@IAxrj h]2H 7mff ]N{XszL8<]OC}Ґ-o5g8p=Ͻ|-?. Wk?tB=_Mox@xd??<7LseG{$Aئ;, aeT EQwvT `Nē?Bit @kHҿE?&jAT5P.> H-$IJK 'zgU&K")ӌn' Z>I)$NϱT`|cφ-8&&T'I[+w>?ͧV.kljuVY& +%Yڬ3siɹ.JE/=A02^8Q%vWIfA: #x 5¹ѕ- ]RMԺ ]T73ӛ_8ٛO-i[t3%93;6^,wPBk҄5m2|>ZCK,fkc,wSXQx :4/pzpψ@h⑊'h 憤1~#]O%cc$;v\u]k\XJ2>*O.~ľŠ9|UR3۟}8|3ꕈ.<8;Ĥ9W4ƹ , z%bDKAf:$(C<Sl g@22M}JDaDDi*՘cS濾S Zc|@y0eD!qHe`?7xY_YdPiƼ.:|z+6y(—4'tV"Z BGGv)$ra$@{魈x˨gGr28}OڜA̷p5"e8c\g ]H/pތjL(3?F~ɰE+rFGy8myâ[?ˋ*x?W߳mǴ?O,/ǘ/1eemٺw7`h%LI0\W(e ) 4=JDC5P"R8~>`(E%_Hb3 'M! * H ΓN&WEJX=>}:*Xew/_Exx֍&D{~'n":lI!O8-|ƲzpAcŽ׷R8+tNz$5B%)C $mUy^"(Kϰ?o&c322A酷n7ʋ$ ޮҬ9UrN0cpdę10[ *l4)I#'qA9 RE hbzR#&+LLN%5LGS i6ovLmӜXTwcK>o*G#*tiN!9'kXSkN'O_b?FRP)y󮷽i~[BX^ ?gm`T|_4,Mꬮ'.w'G? o3zJX!Vfxkp4(@H pƠ ucf^)&:WNq * FA CNgeWq}HW(݆8NNɒ'깱[.g9}C6mʶmt,q6ǜ9gu)c0ApX4+Q'-AwmНf|zVoa<Ʊ li-)hV)eT _A9j!;jC20@(8;銽`9ҤOԛ[D3h)a8tMz;XX^?x+3ߵn,suWl]Sv1=Tnc+8g}ʼGwM>$~%tV WXZXh-H =@ TB0`ڑAhh5Ljp>Wb*H/u=5涴NZ5dy%sܼ9je<,, ReDE94Y?ú?JNJvеW^!.$ IDATEx`&/!ɖw!F 2EB$< RV UEvxAo9}(Ls,AA2wќZ0V[tW, ,,, ( Ej h__hyPK~NH2{B tH\H}|V}R~~3KcpO+ٹcg߽ɼa1腧Nn"sAZpO|b8Lٲa[>F+QxnsήpvqˮF"lvgtڞ^U1$xI^WPB֛/w 6+bGɪX0xzEЪ^ҘU'^LtvgBA2&V&Z}Y`qnWKGS}5\^ScnRMB~koLN}oo..ԚL79x _gY%sizX˦ M'ox.L67QJl&RZYaLjCK1>Z f&HUŰy$Y@h4P1Fv*leqJӈα125q%+q-U*?aNGDJ%Lm:=So|ug| {=}K~mYYYͳMO}A1Ě)`0賾)2@Q%\wqI(L5m/U** !b\O beI"Ia[!A-)PQAaH!Ö27/5ҚMY5wrB鍈NUalBūsdԳ/h!Y zmtunz|guIS3ٲ:ʲ`a Ȅng6ҭTJ399AkE_{X6SS| O=y+?TC`ݴm7Ts47T(Y_f8L 3N1XY>6mم:I0 iKkdEI %SlڸzJ /j(^[MK!8V*5-YYp*\wMIoRf҆lٺm;vs]wg)w]>{~#/X6lc&3ٶe釜pi {MbJJwHX|k.vA4R{S[/cuMKKCwVP%R *:YkPB) JvQ;U\8?ƛ-8O'RڽYZ]ZRcvn!%W]u96odrA+JarrZרZU" [6]9~96mf9I('/d</<ӕ:_|nvlebU; *$c訊tCJZ b%L";D5Khg=Pc]Iw0RaKCcv&6llQaNoi^/H-yZ"(,Q}nd8RTPR$AD=ЪDZRfJBZ 2| p7/}kX<36 <ƂHۦWͷO}o$   QN d[B Zٱ)4ZFҤďZ K(J%su6o(fBOX- & "H!gL4)%PRFRHL"T'/z,.>eצjEZ*L4c&u*|ˉlݺJReū94yauP R4$QÀF5>R`<$"Th!TP@GqbKjL4Bm 0e%q%$hP % 8P՛tz=pFzE E$%X(LA>챡5{QW/bP);=?2up=eY2LKZ:KGy{dq,/<'Ns >,A<ѬT āG8+ 6ϱz3%(!Ht#Hj$%J$"%ƾ'%p޿r'K38"Ix :XcP pgŰ@ 5RaDXKabUς?Ww~sZt-$A^jƷ}=\g?eſs&0F A-NEq H19H%8Z!jtЃ5đQwl;AI4Øx-R -g-t%%J).:6b]w܈j 3|? \LLLuL&O3G܋9z N[/ @ 3B=R@F!eQ; _9";w_8diIY8`rA5Dp]%Q;%NZ<")t#FH3ap"ICSTy+_PB<7\OY_xpy,/Q8WlxZ-³8v4 (L;gI%O$H!Qvk QDi)҂|3ѬPOp$RP49~:T*as c Rbtk,g9xgG፠5رcj$WciݻO/=e.ȋ!_vwVo.1HI3>1ET<R >A&cL!^5<,/ylijDP:%Ɛhpb/)J]XeOQr1efF2i5uS3C7x$ZD( fo_GH#ciw?8~,Q)YJs 7]Y tZ3)MNaSZcENiJjJE1aсyB{3Jr!i nԺI?J stVIH^z%0yIkŀs˧*Sjzt\PN{wlU^`rbt޸izjmV\?O ^|{(N>⭛rB !Baig`ۡwgܸ=gChl 6AEB{nw5adKoZϩs~}~3~KԛM"tvX;}ػ' j61jx)% ?8T+I 8 DEk<!T}+֖a4!h!hژDKJHҘdְ>|pqcƺ-%բY6ڒQaLZ+Nu}%wHQBq=EY;\{!Qo,Oi5 q;j}4#cyNѴ X@7]ú㬮E [d+z sLkKjc,LlO}oxd'O;xa`0S9YYAĉsp(ckkLf fm 9zH)x, {0Â2d,,̑9: M0,i\ޤN!^zh8ǗS\l#$l\(83m!ԭF5LYmǔNER"E)dZ Tt\ppjѨo=›D+ֺwUBe8x|knB՚HcX9QS=vdCZxb:*ՎյuE䵜v]U e3fDniG18&Afҥ2t4w%AL0og_L^B8_N7پX(5 QZu$ ilvot)ԣ;[(-ཧ٬!U@HA6~zOqU2$=Ydp loo lȵlXPZQ~Tz?iCĭ9S,;4VCF4jR:Ǹ0zfBH Ry7_/p`(By SBAYbևKڝVk t& HBda$IR A`'/ ݠBav؞`u˂h㧎u955G2.iH@8{osۃ`[>Di=3rı9l9zH{˴qv7jd٘NUp hԛ  n j \]ZI=jCÓ3z/h-'b+g{jOX9z5;'~mlEZ>J _r@B bwy>d#t:s '#zKL=8R7N)=ԨIAVL8FhLYUvV{,lDTSGIUKWX.]D-:Nt/^di2(ֲ;5<+p%O-:\ȽzɑBF~3q}km5} եgvŧoJxyMk1s]ڭ{&k wgIQL&#QK,_~ ,N>`(JxQJ¹jj&B"-8 6Vb-qTGFJhHy#ԒMpSDP^Py|1`Sd7L#dX}PiٽgLobbw ROr:mOgγXrOSRʡ~^Z4Bs8),PJ!B@M'FV+M z2BZS%RZWM15% ށpCF8rLF#jr ]<\(=WhzKLHuD'7 :t4vY`/uG~/4WoNUo"fAa_hYD|3,9HfJ>4* q"D9l\c:7.̀z4xHSdJU{WK$@ a=r8;$Bmli_r^_j2Ɔ4S B~c wy&.Э(?=/5{ wmz=v6o ѰЋw$'E;Gwwu_c|h/ĢcTcJ"UGuxJ#X "1 GCv4;)$Tc4ۙvn{/dgMp#{kYX%Jq1hQǵ mhm~W_~^} ]b f:Di%$Q{B QuDD:r#3 :#9TTeQ&D EMlQ`p,.Pt1N1vΓ5"y{.q>tO'ÿw@GE5z\b# l1BJǒqΗ&O?B7]}]ya#MWuA&CFj&3b(mx,\tXcySNGx"#Y?EΒHijqBVZWZ-`P=O łCkGjC1`ső cZLi#F;9f>@Ҁ$׏wNrxo'NOr+$n'1BDRP:(wF牣\TRI _S"Am@4 quF''1 څ1BhV7{fn llgw=8uM-r4"9R Q#qt뵛MY?OϷYqtZ-ĩE* S'@SxG%hgӪM=뒻G]'#^I9 "VX͞*DPQyLJZ[%s)(@ [uKJdZfT'Y6VO52H!'t7d}H(ЕC `0i $D  :Ī$Ӥpt٫7?IDAT__[Uo6HJ(1%)>BPb6qadSɴ(\ؑH5̧xwoJipP$RH Kެ;4lǓ9W=CGӯ !P%.r cO*.QB3y`JN=MS.lF: ?75OCCq(g8Z#V|!qW)gXj/dK8Rw}H|K_.9@SVšcZdSAQLYi{*15Ai8f2H5G,S8:ԟqBVB ֵU RHq_Dxi#g@%+CI ^M'-3 6#TB79%hgS)s=N}~խ?RNpb%/aG"MnkqS)Ew9=y,~*0*tk0 EZIPU5|`k$=4}66=Ix'!eR'uM\ ԃj'&fD#xk$@Jt,Q~_QBbCC+ 37F4/VN;pE/~Et)\C e1K0ye 'uEvBQz4lc'޻9|GvIl%_^Q! AGXi " vT$RJj$ka -O-ôP 3Ca5F`4NQNrHjwúǵ8Er=U'ʼnE,DEmdba'uC%EbH@Plnd[Y8}'Pd'u>*IU̾mv&ydJƹGQvlZL|IӺ}/?dZ>W\7B6(˜,+XQujCeSKYgoF.gAJ DjEpn'-q׽8t^mNm_ONIV8UH1'QfYAYLD.@nq4EHu %WBʊM\ͦ>TUE-IDE"/5.(Hq'Ep|'WvQ aJQ`8s8Pl*t@)ʼn񞛘h6Q`7R?(pA"*$*Pڀ9HLji6{ 3xșb7JJsFTPb)GF Z‘.(m)ĉ=$,4=I/%ED 2xCHpi-c{>=m12KkRvxηeN4b%xA,>enE5b}ߨq襏IJ SX(,at KO%N 2',%Pdh g[`E/Yƺ-Hu%BJ|d !iEjZ*NiP$'JEj$%gO&&Ja5E:::~.'x_\cy it_9F6G'%t/RD`xBALҶGEppkA1ɶ.,`L1;i gsc=2GdEQ q=˗1{(8S0VckD*ۈwPrˑcyoN']s-*oH)!$-\T#I 胪)馻輨a!`zh:42 ڽ#DJa{T\ '@(”Չ02Z:U2Zid yfp֣ƽR=$Ӭd`ʂ eY=m.Yv~me5u,W%(<PЈ; RKoD< W CT\);&ΤO8\P,JBޥ_g^3i_|p3&_3־j{}4TpR`(2C BQaq. Tq"誤`YVZMswd1*p@fJrdiїhȀr D5cd,NT%ְ[Y,8dr%.}fs|3K9Ei`_K/Wx7y$g.k$j,y͞xF4NЪ+iԛHQ}<=AU᪅<% [B, !Pd#xf+Vqq]y!DUMA454u-y@¾YݽҼ,ʒ`4%8K$yUZ(fɝ"< ( ޣ"R1*G#Ԛ-֚j{,rF_J:L֣к;n$H!q*$v06@R(<$Fᓗe_#sK~?;{,x__4>sk?FuLǨYuDJE-IΖJJX#ܞI=U1i<#tsI8yh`g)#pȡ{sƋ/ѮJi\Bi5oހZ) S${"- jM HM+{ovxw|o7\[eO`qEʽZJj"U>zF+CB P$+oZ%:酊P P& s eEԪt!RFh@Ă"\bATq4.a\Ƒg_%`o~wzYvQi$E@!̈=B5#jf$!TOF%1BAQW.J%i&-+#f\8I_&QtMPͥoGחQ"0^*q u e7HaL MRϐ?q8O;^we͸@vw}o0׍ي@8V̡:\`"%HzW_a,CY2!d7VK/M_bJT[q8|J`"QB HACo>{0[ 4ٻ ]BfqS4Q`u~|Sw;q:!bSI 4Z@ْHFȵFDRT Yc'/J"-Y\ls^.:acce+}PgcҖG'@QFCL4 RETjrHWi_#ynF;$|F/j/7ls/S^hdKk5g9yQ-Ʊ&Oa 8:QLs84)i# k[nswg;[M>"VX.Í+}  Pd _ـpvIWⅣ%uy6%-j= =w^CuJXn5d>Y|ve?|{>, ۟^,=B5nKZ=NӈH) cW ֐$U~ܼ5L nkk\4}xn5/{t~/IzdT(, ԴC vw.f:6BB?Fuh~7(v?+66 4I16_r;9O?wv.=~ĵlIVQfq}ZU)%<;6*#_Grimv{uZɈՄogx.s!ꄪ ,iPN4hL,6l=J.#j@9:~7Ryѿ%w (VKyam =x-/}~s z[^c 7IQX&qhu&l*ZIǿЍc//Խl7mN['Vۼ`nQIXI$ ekV#iѹd]\k5EH{&>uZ4?;m%V9=$H[7 Q8:2".5D}ĵC$cLS rƣ6G?A6#qxcI^(}y)\ʼnog{/X_uu{Jk8$-Z0a tq" ⸎sdK-m?DHۤh?EИ?}'Oq"¨kݷҚ"?{ iog]/W&s"S}1{NE6&VZZ G dB,h]L9="|,?!!X\>'[޳pz6}}+v&އIfDQl]LE# uOZW 'I;S+$js Zj0|\>7F|{ڵ/{E+ e*ڼF2%=5@\@4A|q-kMA{ QZAdGB "rW1".`[u7^O'O{_{l5]-sr9G|+!\ݨ02 U ctm#x&(03(4F$f3P\ RA$4;ˡ/;U¡,[^n7KO{8saCJudÂrl'dE*lNORw+X۰g| `gn͍e{v?: ٳR( _IV+Af$U2hTC"t8bM X_: ~ƟOgz|O?! z@2$1<Ӓ1)"wubRVKPiѪ38'u5t~݆w=$`w~5K=o6ډyW@$ b%A`Xh)bMk~B/)ܫPƋo{ػ?vq'^Bu\s\׹1:@S+Žz-Gƞ\/ ɨuї4l|׿O/G/%esgw8r"/750ۛe>:漮-w}OVvMsIENDB`swayimg-3.1/extra/icon_256.png000066400000000000000000002451261465610152200162260ustar00rootroot00000000000000PNG  IHDR\rfgAMA asRGB, cHRMz&u0`:pQ< IDATxi]lgΙ}-# !`r@l'J\vĦ*@ ˀY%9Z=h̜}]^yTKa}UuuUWW,urx3OgK5;tF[14T_n6wlKcFQ=p*ܵ܂N:.76zsYGL}ywnï7W'_]9~8`F ieĭI"Z><}[+э> #$ڔ7(+d"*$ɑ 7ұs=5y\erdu-EJW ʻ[ k|j}; *8 W_>o?d}m_Y54.Њj{9UhN+L^<)AfC[h|)bUlxK/ıUGz|.;g=l|֭ͫ 't8dkuɭBڊ,$dKIˢ f>& ʣtW;H?h\kuƲ,d"ݴ`8ia%smdΐt[Hbe5:+{L&dhFB(*NjiK=:]O[;=7vq\=dO/ۥGwxGsҋ>oC3_:g^~ jgD*$&(r=o`~1y}p(+9v|kxD䌫üy^d&8.!R-ժN/~?E8 `ƫKOܼ-u緮lJ!bIzLI;4q !єeiE6&5I@Mh1&座bt(eVA]YQLdX*t 8.]by__bТ>QG#֒5X@WyD<薡nu 7)9PBU)\7U)0Q ΡfX y]Iّ|\; F5OӃz~?y֏cYYtq^{Lsv>G.Ek1"ԁ:ʡmL:5tԢu{!g[<0Z|9:-0❦,b.&t|eq7EYA=d/!Ӟ^GĢiߝGwVt;NY7w㿸޽u|_+O_?O/H;q>`TD9b6 `UEStZH"'jt)\=huQTv #ТOYCKU"~A 0(7Нɀx(֩F?8>xpnٿU2-i+*ӚCtыa?~oiBУҧg[|rt1vB/UhSd~b5"LD4TαQ&i%gY K JpOBZ}#Gװsk+7>D4M @BD"+Mcĭ֢@((VT ]߈[yS? W/]͍׏ũ3gAOk݊ad1b)r] l z"Lj#!FmP7yAn'8q1\8 XG|@|S^H'mH26%:8_֛8gާ&gG'[^7a/$e?yP%@nҁ;h.4!~3@y&&K-+o8K j;o BI H@trlTWG@tc6=sѿK 8 ?ظ⛯]E.O`|K}W駖ܔ^ =zVܳj(GiS;F*?MA}iS{(y]Z"@ P9j*<0#-DEpJ '(҂a2hLU[gn;@2BP1Vz- .v@d0R 8p <98b呢 DL J[y+kwĹ"5:QʰP#Ե+Oy~x;[3G/,fIϙ:L3҉Ðrt?ʧ5:{>Gڽ=eӟ[{u9`fs9= Ri6 O Qjfdww_|!@RBMO| R Dy]z1\Ws/21!`&!5GʁKjlnm !4H{}7PoY׋+*BXyS^7'Vtu׸2#/8~8~Nnǿxi 3OY}:@< ?.,xQ|USC{|t7_nwp[?酻~^2/r"~O?^ݏmo+Q)Xw;F--K r&!vW.ykh:IšE⸅6g7s nZ3AuX8ѡcT> C"T)z, x˦@HJ8t0FXCg˯~_oϿW{. 2ʋ%qF"FPy&*s ˫GzxԻ|ϓG'=׵xgʛ/F~rǹ+Pg玅6q6DRBK$kAB$@zlu?/ꤵUK.:9j.ͣo.Ɵ_7x^iɔo{j]`g}ijũ{;11U!`>FF$F&g;,RM0Pp7g_s?{khG/.IQN`y5Wc"#d)`SLyrD'NZ'^"TB]_$+.'M@P6c* MN[rlmNm#IBkR`q^atM,SVtPBB]G&q~1G CP M,u㒬~*μN_ww-TwnEy7qBk{!BX\>ޣ&.(kKLctH("ly$"=,](jS5܄K3I2Π\fBj c4JNݔLFD"A DޕqX^YwҎs>'~iw[G0NDQIg(qw7V/9r$'5S$xb@]XHR(JB$-FkJuV8jalUb˳K+[UIeOkG]" 'BR؉g0yp&-j+tk: lΤNb:QJk\9(D+Ԃ(.]:5Ns JvBEG N2AlÑ@a4Ty`kC!vx'%mV3 xE9 h-TuAt [ Ij-[89: 6LX$B *@=3,m5ɺQ ogG|>gCCAa]R CkI^ ?c ?@_o\t[L&Mʲ74H h wmAu[5ɣG|׶V5/n~ Q퍚cO\N)mŅsM@` t1%)U%5DY3zN:n>o0q$>% my`DAnZ+$xBU!BM ?`C@iBMd!ة L| WD\a 9 7[j : n6Er zW"Alh J)J !ЌJW YyOie«=|>|Տ?|_#v̀[ہ=ChƀC(#8PA8kWP<@M$ıÊcN Rt¬X\U7VfxTV(ɀ5*x\5kKҐgvgj(շ8Pm '~;MZ^`ԝ[dwX<&M AϓmJ.)"OGЄ ΢mFFc)'h`I3*i㭧.bҼ6(i&Df"B IDAT{LWO9E]l1VDɔ$hQ5qUtu 5bܮQUŗPYkРQZڵغxHU1|-jR,<j<C-Ə (B 6HjRMb'NOT蜠L.gBsPy 1Z+naE+耎Qܼ:klP;Ҟs Å>6= ?s-\x#NGq`h`yI4&,lGe!nu('E{RF&QxlÈ1CquhTL=D X F5Rx2OK`p+Ct!D5ƲvL8VȼKL1v7~Qv6a&Αe# 4Apcn,BY [D7 p6l]Ӌ$-!>^S4xPWB<đ *&DqSK Gă&bhL)ѓ6f$(_P5iP%Q |X X0zŤT4vdmޖPԮr1k?|*l_>>}O>Oc8'߽bRk z%qW m(&WKxQxkE%Z+5x\pCM) 9!Цqg,,  9-'cxM  4-SۦC4P.O|ibCw*ډ09;ל#373BhHt6YsFE)Y\2-q CR Z0` !KSVPIC7GG`$Q, '0SZ-2}<@W`1~-!l)'MaRd<&t t$4gR!J5uP53 н#֘SP| ~<̿~ǭ[%\{I4u0e>nU>v&ĔNF8yҝ"}"2CE|y)bP@ia֏&WHO >L>55FAOF)ÊiB*ڶ.ieC0SП/Ch`9KG#rPm8&tk_cQѼvz,6?޺4('HBa"lZ 4y}mEL7Vox]/[)kB J&ňe#!'4uUi!#t #J^玄EĉRqAV:׾|ITu$*x>  tzBQ$GB@BLhTmEA`)jD2\~KYigBeoϣ]pvAPczRD&ǂVMɢ@]2t5u0JN ܦzGZeh3Zycʌ [^ji[yZcauȲűNv@O( $9+cQB>$N>o<C . "v>4:-@T& 6mwRn-[Woi*^9܌yC`˒K*g"U%'h`W(\VFd 7n^8tdiNUP9ܼ'<&wm=kԐ`A@*$Dޒz`XSj[.=!X,)I%bƬ2`k$iQ`]c:"r06J 9 xr{en_a<"1X$jxnb0)./mo|WXV><ܢcI2,5آD+бC ·V1B]# FrpMX>(QS@+D^MFE7iz b~6i+!c(]-aQJIb  䊻z$r5-:H Z Ħ߇*`k<4s [Q\:G׈!#cQ !:.I1Q#QY|DB\_nB$Q47 OlK1 5.*Kg}dRbKUex>hG}30Iyy  o&}SZypMQ82RE6q*G*pKoH. vţGn︞=e``qq>~c"Z YKX?@srz^_}})[WEI.}KTZ+?B,N.q&H#[XB}<\jHED&*C>)>/p֞jK5":wed)JMW{uĉ U02"0DkOR(<^UKsMUb5&רXnt[%TP5~ n'4s?uqV4j%%P9;I#zK1eq(& XR6͘qyj4Rh6AwMZYkϫh&^JM/5:j!h Ų03l!:K\ E]mb!5q^#qhga{W-:Ob&jv 8.8UԕP&g ("Ⱥ-T\&ڀ[ DƠ=6t{nM]e`e)`k g=F PtkGt(I;'N,xspݪ< ʓeațE֞3$#`ai zTԴ II:PτCIT#jT@0O]NܔcǨVzVe+?ixsjg{%ws]3Y&5NGSE\YrJ"q-LƗ n*# =TrV`]2nXơN_aڏ;g;;y!2 '`FB@]2E7o5lNB`"K]TT YxTHz8m]h=jeYB]CwiέxY[w̽=ID"*XAedрGL\Vy}kg=(Je:VVjSN2tڦ2LimyUbYv,9nE=L $~pxu!h_dR/IFμ"q0EI-c @ad U;"9DQ鴏Q;שo|$\xI+Rq}ŋ/XiH2iTG18li,Q>h#h44)4&Mkp$9JOPąq&^ j CU8lq^C2*@S = 6]@55ak)In 4D'A)Eh ׿S̰a=H"LJlGh 4[ 9J'ޠ]Ni+?br# wenNv7K/q疡y^A% +鄻tU)%E!&6nb\ {kk~3Eezdg$ I͓xVsb$Wotpc;O;vo^)ۜSteW:g}#; I=N t qKn" X|1s"}qy$DE"'dx1@НÔ^%YZ TiqB yIUy'Ғ0e4XxgߺO?=6~4h]#| L .WcEc,:Y^X'k&{YkKgvhڑ9"%XYI0q.$-$RIo w mx['W=wdBES%Q~ef| QI2A+ee1{e!}VZX0wdj_0Oe8aL#)&# ʕTgIJDJz|@wVgB+"]E4(%@ i)idaEh 93;SA{DPOd4rg^1`8$m ^JKJD:(cI TQ6=:{Ѱ)h'շhL'OsxW#D3&n\|k"080Ơ .,ˍpwg^[敋OtŐ䘤)1ܿS4f4vc'$Hcl;q !NRxWPp& V6gL/;.q$u$xk\[ȝ=K*ĩg(4RO3V!%A`nI)quE9(8o3];J[agmHBHb]&;$8w@MCHAi58oO-=vn[k[1s} h۟1̵C] HB`] 8W$1:yٹPOj:6U=fwp8*~ugdjSrDb5+B5Tyrq dIT` ?yk7ҭOŭ֓Rҙgs>( \]\yXrxUXs:e(uFc k$&ًN3 t`x8;㜾;#9q8Z]O"2 iFTd Kw+bUnS Z[GbkicJC raѹ{O k ę8k! $(f:٢>5gՖғJ6,nS֗iY]k!.>T6T3 ̪ !5GPj#H?5ڭ䤍nwg2?S9(歭6KG1ɤ^cWӌX әjHUnw#wv=ůn瑎 9UDeZiSzd~rq&akz^3{Dr(>ޤ:QD&9xn<(npS@mD}:GQynDmvilIyFˡg,A IDATBG SI(ㆢ2W* j EX晃DI{!C/PaV' 3^(LSM$ChKqM .FSb}X8FwY7OI3UÇ6yGY\꽃nTŘF&AK"k(Ɖ5:mMdOE^l3ع=^e<\ʥ.KݦbqԅGO,Օk5'?PޖlRsly`{> ip%.2 K1b2 m!|1li.EW;qsן|vnQkd29yXcoYD9%4\V8t]/ |3X IVwg ) >nQxgĉj|? S7lJ_G%Beۑ5 m< dp8:A[6~xnbuqtQp!HWl":M<Q ZēDjH;0Zs1SWEqsJv*ztgZV?5(]ϸ3մ?[/5ĉ@+A`& Nm豽Gaft6(,qw&8HZ AJMQ5@ϰS$Z ı& V,-msoM.ܖ9i吲CQϲf8s3_G!o\cZʅas%> .!`&r-_CeakOm=͖@+G 9 p3o=GWo?Sm6NWV8$,Lߠl-rsv$mRE w1yeH{q͓sͣH5eqyI+@W۞l!՘il(23 F%ЊY!Qp1Xjy8Cx?>fDg^Ɠ Ο`ǯba?LNdv'yu(^GK}L/Pci>vW* (8MBV\Ɂ08a8V_8!id-N9z1iFyWU.)BDQ daT1-vwS._t?7&*I6c-8_ N73VO/|Sux~?Kgzzw_]:=?{19 K#Հչ;[zۣˬuQTe*u}'9uhvCUM3fj83ON;"2,nTh#ɇ|'5I% tx #q^RYE(!^QG1c$H`S4Y 4b)_HSDsRZ7j@7i[7kE])/#ǟԞtAٙ@: 7wyu ,·/ݓ X费:-I3c<2,,$}zB#5hJPԕ x:D#\aёbjbL!Hla!ΞP9Y];k]~{7ƳWEOӴպ-z?߼ѫϽ_zs?|O$,o"iR5&̵rPTxiJνEXi6]'c *1GmqL $<֢ i:LE^x @*DF)Z :c>l~1E;H߃48a`޿`惬_(iCQ([;oan^X -"9=QV q O6aiX[s.rnKԵK6-$%N KdDMӺυW4d$LY4*]^'UBq+H4_{z-NIl9~߾MwϿ7W6~a{;(E(71!=Ne#C[Ո[|V_{lU%ߢ )D2zLP c~QЩ* N8 ^>[D_Ȑ[Bq {%i67QUq8Έ{%2:Yǔw`gڋ'NH>8_Fn]`,B7bģ3 DFɀ!SDAHgRBST7*hH͜ZB('4jG[*`!SG R.Bўg2UD^<DZ#)v'N^geG0Fv3=~⯄ #T|K;qNJpc,ŴNx:#mJwcW1CDR[p Uƕ"K,h=:h-ix;5D)p¡cf␃KIaR!d'3e#d"D^ܻH4ۧpӛ9īߋjv.Ptb81hIOա9Sd.\(n*6lp :p G<E`.TRur"Kkd-Px>^L.hʺ fAZb8}ucC7fŧ_diU6% Զvps/B6O~ھ|Ce6^ŗ/5z/l>4oHWo;Og摓ׯo{޼uOHL{Wv3^x3 Zs}%&q~6$,N̓RnIlnťULweEݷ:+$j͹0_M)EMn ;6-3&;!3/Q\(ntSɴ/QYƢH'kN!D5݀(!XbTz"DisOݤ Aͭ[54+<  n0ݢrdjwYY?A&_qka?н@^N>+O}S߷:WEF,6%/\D%!j撈.-#i5hmŗZp[t2\ڂؽ5 3Dn#?:(v2ߠ,L1 %,v2lh,$cHBYM$iWO`vIJ"8*oY8M-9vm5-$i8JkF7 w#VX3";c&Z $i dⰢAL6!,q9fe" / $aIgjTD5o9pL*K0#4[,6-1Xl-p??,kBDOI3Q bpuyЭF_V>(| QJQ;*cp8Gu鶧/I1+k[43 ]k<бј eeZ(j3-8a)RLw:q|E;z1[Hk(-/Ic>|! g1֢ۧI2^UJS J:(f"RHL (i> y>%IR4NG@ j$.͐JyTRxɣ Oz( k݁9p ~xU`AD!~ͷ6.\E4%+eJ-$J6 JX*x~.,3*)2dFύu%;o(f=gnN<Júi<爅 Z!U֖(EVEp8W`EIȝT,0 {5N$֏Ec,=v%nX\(Q%ϋJ5y5o4c $! ဪ2t٫bQf$M c:÷;v/e@~=2u#xN $!E:ߖ>(䏂 R qi#2_q%+#=^dcɩWPm|yC ʏ-c7$_yWz{߳:s|| \yfC3nfӼD"1> z8Yc| /OQrMV7^xSVBqrpфbBt(TPMdq($RJvoHЙ[QJQCXs! [HsƯ}#F[!Їdfil傝h6"I%ZzQR8><"P[q Gyi%V 6os B*"| 2xnyuŗMЗ6 {{o+ןӏ oli%|WG,Թh5SZ&\g[]M+\,8S ] QȋB'orH;fz}֫#F xc+.f?og2_GܐTG5Č:$UUELxc)z+?^02S]f:ܢ.J|5eYc'ݕ{P;Ox pg a/C00쟧 V[(bBr\.n{,a "A(9xgsi lNHiQ &SPip>I0Nz鹋SgW}{N̹_|T]|]O~o}k;?KGԺ4>O>ǎnuի[D qNIZ[:Iݛ%u4W.S}G9j:Z_Er|/Pjv+j\UcBF`*pZP:@=rFy ex.!BHx*\zKargD~9y2xd$k5L,GxSƺюѾD A9tD6*Dt:m3~&?^)* &&T_‰-)E*"a@CBk)_xnKLZ?Zcg$CGo?3/Ѓַ/oo;t2r7W˗ΣO~c4'Xk6hWXZLm1oSˌvt[pHgچy(Fotbܡy5iPNZ"iJYHGab&Ι ,a`o[4MtuS\fpk4gKft5^ ꫸jl`Fο f9@`=^ۿ+9Vٜ~ןڒw*AXN ;$K7]@3.1BrZX#g؆RRdB&u-sbB.^mg4~[a2eBt0v_IBDP?Z]>0W~oq5mܟY{ƍ_Ȳf;/z\?|xl>~GO@@XlE|}z,h .E9)JTE{!PDxI;"eYSK1 X*v(Mx7p"gl7۝K)Vb aor'7o.qҔHnS-yTEk2 BLBΤr? ς'R (羉.wfnO zaKB6N;jӢqHujLx6& IDATQ #K,Oi" BNph@z*uYj9xg9rn>Z'l_5Z2 ZO|8I8_E͍U.^ƫ=ч'mwߺ>w3ȃnNdN{<~?8mB`4-pcFATx瘎r9O]ԝsdK[oD~3bR͙4mգJv{ 9sx]Në'- v-1Ī {U>^0K&H:5*0C02y )hv`y.8'4"]ea>֏מLbL+L]ӈj(֚R"UVM3 \dZZbNFN"Bēe%^Qۣify;{}`$;HE*FU(,eR+Nʎ!Q)l$(2-H f3Lo9'nr*U)~]o<< g1k_'pQ&g5vn?=}!nX넞Bi.+lc{ا7NzVEgo |;L_Y]]94p׀ͷ ߷_-6:0tjjq]e* )Ib>Y4|JJJq7w(UΟE>}IcG#V2N;Hѥ8F/R!2s ǻ1$W"OX2 Õ1z'-ћb">-)Q5p8&>q G'sړP1A:kcfi {Sн`~)X>2)ZR*,^K5._]hν
    U%w.Z ki_NrAʲu#`ʨ7˭Ryfn?,t!H&`3F$XRǙi{{qmȵX%5DxrcGWvR#u5!0}MٜcmrGYju3=&Dh99BcS Iԡ@\?t,7t&NSoO"<[j(v 3Sb% RZXRPU4y+Z~rMOb;ZݾIookh%'O c>/ow?`]S'$i"B<ק¶,/;?>Es@R@`DؔDqR4juֈe,JN>7cْRk\Rm5x kҞubQ(z_'%*G˂~/W.i.ۊ~3µ<&Hdua#v~o/|ɎٳN=iM6Vysz| +d.?ED3=íiNM k% 3N%QeKu#P! L.}2X DڝI;,@(c>Ƒ:A _Z J %c$ _A"+plYTqZbPM l @ę)kZ' ש.-J&kx ? H&N]\=O,%^|i_JpK3 ~]Ak]H;J%SᰒuSua:«ؑ)JE\6n c6z㎶~(w_r}Ovwx3b4lzqmlf:9? ̈́5&Ib/9=đ+Du\7Dww~N5 ˮtZSu_MAK>sWrc0Zf5&cAH(lR%IaH'Hy[*Dz+c *h 2"7w7KCDM%&&r(s-vFm![jEe V@m,d7:sԦ^~B+,Q.3-F$QE7΅7^2}v%Ȓu@"ƫ/5D J\$Up 6-L.D9ސvWxC_¿O9~.> b!f@Vgݦ,3>S<\=%$i tw!G?u>)/}?Ewk*0^\/~_r=17;ρE>nZo= H3T5~cDG ݆h=6/^3 AAK."F.CX.sLx{ WL7Q^|؝er>Jy{2Lo3 3Dcj6VA"MEG%?䙍Xt, e3-~AWdT}  z!A}eB@aRÜ D$5 1Av-riݔq(D㮡DC!_#;;{%j )2E2t]Hq,)| Dex^d4R -g i,|TYEk.ݼ0\CF"|Wtg>8³گvYʹ9| Qq!z{)׻F3L4lKP)þf44LޱIr8lnE91^ALx2(( m94oDd]eə nb u2FHCT.uIuF;` Q ?>ߢB7@r8o~䈑}ߛ~ACeΏ@k"Ѧ~o TIt,^_iy*cmx5R#IK*p}M$ׯٗϑ*#,O3ҡi} uAWgc-( Ie <[1v8utji^~wU~s|w~w3G.~q"N"10?wvwp,Ss]8`4쑦񦚷 J0C*Eؕ43 B hUi1[+`2)u'0[YD A9ڥaО1n}+EKӰ\ \BHo*T@S![fjW9D}]%?߳sH,aTV?˪²¶ShxhQ4:N`3w&a\VʯoX[ w(R.Fz:(8xƕ+w8B5vo<ǽHtŊ0Tda84J!,XYirk#J9?Qd`4cQ?c?3~zбC7nuկA@5x7nVI`U-H'RYit^P&i LҞ\?22CZ]x51ZilWtTxpxS\zu)ʺCo`c(^"xU]^;uZ[> v|~w3_ ūٞݼ•+oPk4xGȳ!Mx-zR Bg i3+h[ɔ!^b;x _ҁK|So{nvs89{!n(>$vm*q}DrJ#Ki$%ɒȸltG),Zut|VV@؞CIA^Dv  ;*`s4t(=S-+U]o`JRib*Š,%Oٿҕ%4FcaG9NdfSr# ЖCQ@xNlf<b h318I ;\.g˫/OR(,&YyI9 톂'W9{&iFiҤN#[6kun̸ݛ@xq .^݋p߸?_x#km.?躀_drsgړ3pjqA sVqk!OC}%(_)BV «H(ct1g,4i7X#\Q#O%0$.nC"hyw@b/ "-QK %\Ek,o-lcULXQQy0rA-(D(RObN]"4*4E.j'5݌ A52ap17!\Np+lpdow;\xg:{[,MG -dyu /pmX4Ю"24 ұc\@t, nEȳ6"y,5|\qyF}{η:\];7e*FsW~']KyZE?x{ä#71eIQ"'F}Id1fGFcJ2c>@q` [ WFkrL)2HnFB³5%u* y gMƤ,Z]xC|S&v$c1 6ɍO؁첱["lk0e1TG :jI|ö \Hbh8Pf ,{aȭ8^ 6w)j\q.\nFf&g J؇{;gf^>ye@] Ƌ+"VZvgZ9+++W5>x?O#ix\p c|z6802i$W' AAb:&ՂL"ev|A]$i4aӬF TIkk;,L94PlR?p25`Xت\=IT-: :v hv< sRC 0%Q$*[l+I ^ 4 rm2хE>rI JH@ml*]q,!= ukG#v9p r([V@s ?I C&aJ1vYZ ߸4󫳼xu,;dv˪_kr$>[wx?GcǾ½OВէ^IH5#w"GGſ72X#8q˿әĠI1h/X\LjF}3`}u qab\ G C 陜f3wlRӤy36m%DMZtw+U-F #)/ǮKsl UDEkȋ1gbWx`ޮٴA"q$xK*lѨx3udP92B^SИHOoiGHY!kMTBfn`AhmPT UcPKsvqg-ҥ>s KHaPޅ>Wn&z)^BY>C8yySƃ!sO|'_?qڶQU՝%OYqjhPZ׿-uIţ?}?:?Ch<'Kc,M<kJMZ:nwnH2t'9YD _1)ԕFh>I5-~h͚E46br0Lm;_Z4ф$= ) c>\ƶ4#*#LFA"#-4F3k\± o3Ȋ*ۯۤ!/X`M#۶db .U@#5 Euzd2=203D#G`ijȋ }:nm/c3meD)n?eG?ۛQhK95i8i tjNkfm7"ttwn ܂Pf IDATPciE |;M x4Hamd o6|gB TTD(mȔ& q%d&ؾ M+- ? I R .,՘!Xe &,gG'X9J"P8NuqchivlLbhu.j5 HJQຒ,繾> 3?!7,iy7_( \8ѣG\Xi>'UԶx޷AGO};՜OUs/;\ZCO~ťCYՕ;T}[\2Ͱ-F<^}<AEiL[-y׻%33Q f=JRQ̍kwȒO%x_Zl1,q# ñ9:A/{UKG$b ,w/jIÜ]9.> ĊRI\fg0ffvhxeY%1'C xXZ"Kb^ zc=~#(w>G ~Zwg?w'u*!)Bz5~~zDZ~ 3\q^}yoss=FI^Cz.q#B Gܦ} "1W"vGkv [z\3JZuA0AJƩfXHqDB'eZe^J20Y糿ªFrqDNZX(gs?Oՠ]>wKA+U9N8ͯ"{9jaHVh-s}ja81%B3:loo8ǥ=5% (~eYE) }ޟu(wcjrR)v{="s\m48q8S5:)z{fn]֚vghOͰN7]"U)q:MTQ')t&w6(ҔvE,_>C֝lnm'?W9y4шҲ Y!enr Z-eqj1|{7qmm5HKIӠnS{ Iehc㈽~˲ϒңl095z>Gg'FB,RK WV)x{Ab;.Q2>|ۦ39M2aYG_eέ[^(wp>G·᯿-> nޤԚRC 'bbjt8z#vn`4`F ȑ#QI1A0KGA%aYź9eY%ed)F㸤ECVqlftZ5% 43skf'?ۗwkЙ eBM1XR)k~ZSd Ǐ#$qw<9uDZI N-8te=QIWKVyg/|Y퐦)QRgSON9he0ƶ~x *ZdFjs-vdSA[l8d֒..4Ɇ=UK}:'ne eYb;Q19&=VRp!P.stYxK5 m2\e~nn h4Dzsd )]iC24,.FR<' 4KqD͛7>ᘛCf*2Z>ę{p1#SN=.n1hEG\Y ks:D+lmw#A34KriF0l -W.iy}wq*ۛcz<4fzf#Gƻ 4${ZMQ"3ye_F`Jm eQTR&3,nybܬLuD=۶qm[Jm^wqxW$AV4쳳&VZY!kG>L#:#907Ë^`fzQoylrU$vAO>[t񼀰ؒh礨 `M,)i&ex,\D.*( 6ȆC7x_31B0#>}kl YY`.ˇ1/}e^xwwѥƑ/ܠQ I1/2olAPѣ$ sy<۩:Ex/lW~4p3,kz`A ,/,#0l!l:¶)J,ïU²dyN^4uʢ,4eY}(JBE(Mb۪FѐqX\{h}//,M5odVe[$M<'ש7|N S_4[!2R$}7޸\r1!f==z X`i+JQhCgH'p%HG DɘFʼn<w1ݽu6vmcc'ߠ FUA v6 ˪6xVj7-ќ$5+oܠl1 8,Kr<i<FYV`!vvv`4q-j&IӪ7V dq~5y^`I 5yQEَM<'/ "l~% Exe$B`$i5k?} NrMZ++>/8˹xRoyKOf[aYNa$QJlnm m8th8EC\Dz$q ,א& \f&8G887Wy#4EZ L*rigy^`FʒfIҘ,(!uZ&YRgRbH׭m" |%K2,Q|Dz_s\()&O8UQ֨@do/箰?s&-Û~9x;:r 4t:_w<{e/78yޛڙe}=}Ù|9EVkjճ$ˑ%+$~9lIYAeYhhIn[n*U,;}[ "YZ :N5.>8O|cs_}.^X̙L\tko_C^Ao]c8:CWe(Ah PQw)P9$ETJ"HJZ@ ":ulBe=U 8C.30*A"P@:Yu hC`΀bҌhGlچ3g`i6F<~P*J*)ONH.D I$Fg,E Ie8wuLc<ة@B8>G8 mSElHg-JJtuxǩ2f%|%Ycxe1[feM"$x?E/XY!$;^.}$(nCW.;IB@H m8'!M:TQ̧mHLIahM't;=lRVs|ڒe,cmAXm4JD[cCt..q4>jƑdri jtHL]X*R怋mZ\kCm,Xi2 ZAJ6i1LFKuci[ +_$>(2#icf"IS,i& JI!='vP;Eg>O?6Jʳù3g1&L7__*R{pf/|#wgDHⱮo7lf]Nf>[uPJ#<$Zx% BBUQQ0cORR1S%@@KEYJ7RQ R}X1_hH0Z"QJRKAO'_un%M2Nk,yR5.6-[G7ͱ85Mwc4H-Iif?6Ϯt{gS7zE!hB4O﯃,^]d⣧8YLWs?KU;GOg>9~7'Ͽ}Rxdul)>\Fkַ'hC]UH@D%douBtj Rb}e5yO/end&2\mA < 0V褆0 HYF:卮{ܢ`KmIMBU0:Ys*% ^:y$b o4V^c{Rӡw,FQ7kiyk[[_?h ?KF.߻өZJ.أ|}Z(( M}<{N/?dr?NgSܾǧ>Ir(g?he׮Cn1Q$LKhHH¨gMcBMw%U|2IbPJ!hMO>ty3~×xÇhj%~@XF>+g6x;aL<f._ʅ W?$ofmm?=ʢ{]-F65D %PR1!ڼstni  -,qἏy}R AN "`IQKTQD hIqi42ewu >଍c>>Kk-tUzѶnG[]%G =B{LD1T$IHb訏 ?%uYS΋eB!Zb-eUdvt/ą-yZjq?D&^O=Xym&GᰚQ5JJkLg%RAoS5>җ>FYbtc!H&Ao@Y͙IB/l/E4Tqõ8xpM"tF8g n+"LJhBHPZ# Ic&Rl!vRF~тBbNh E!iK/@bRcP2.@lVa3,BRȰ 1qH Aj4ynZg2& %½7>ě[K{IyFdqo!wdmR!MXl q2N'5 8Ҵ EhJbRu[RT%EUcC>+VF˒tBY70>+m4~+/|>|fz6>{|&)NHolH`k gϝ/]7og*o]ʲ xO[׌C>l )DhI&3LR-~٦71;4@X@E"DXЀn:2K.& bxu~ l1MF;4Yw4uC]q!h[z5XYYAo! y"ő%'ǔ~K \qYj|eϡkg3o?p\sO>2$YH?貲2m;:De):Io|=㓂_|'|"ouk&A/g}Vm k#|Q&CD&)IH!5 FH*nݕh,"xmP":=s4M1:JpoVZR\.-/pO3V+ IDAT۶rF4t9p݈{ޓ<", OTeASNh*Hӄ$K9{ I( ~` O X%$L 꺡i,1<,;)qGBFR=I  O$|'FI|/}\[S?_≇O^c$"M4\:[1w-ϼ<Vx嗩\+╗ߤW>F!xǝk)K_@ (N z=9̦Qң2o]-I3"Z`pAX9»ss\{ I!v4Xk! 8e:yN3uRnQ| DpBo㼣iZ겢*O&7xmS30-8a>ZڶmTUm=B RIl}P^D@[4UEbP_&ٌmo6Ͻ~@|n?W~~-O,q_;}7>Eϼ>w_fRiڿͬ)x5^yU?u eMӔ޹Z7x+W@Q,26eg&(AxYRq!DgݒBG lTHQ2 JI7[P DE1-!(IwEny׮9ш]39w]jO5~˟O'?6Rq>sDvᱫOS?!~R|U]$i`[҄S{ 86MƝ@!ok;m4#$AJD| !CL%Fx\h"k/@5YF(f $[c ^Bږ,b{Ͻ-(2-\@Q uS0Xuyc[;˟_xO<&wÝ}SO#ӊsg.w񶥬*Om}z;J{}Ν@7d}}Yŗ^^r&C>.|4m R馔@JAʨ[ 1Ojpeti-* dhI"%7%0R&U<1$8Apa_A졅d6x<9O%]$=EM$[WMt,rƽj99ƴeiچ لm׶f] !D3ZY(Gyj[{_A:^q<w~W~Uwָ|O<%.\`8bN'%M G}|'~}w8>>b}mm,ii܋/<ؓWO?}4ahOj bR)4uBP~"сeo !Q@k-t! $t T@#9Ve#-QXhbq!WH!!\`>[fUD(0QRp(cQZq$hHq )r_*=mxzhuDkJh6*p")"*ҊY˨G8HR@&%N.@SzrCh$Z#FzR!Ȥ KjDo0h 7woo@5&z8E4XeE+Dnkޓ)kkCT- iP, 's0"\dqi(eܝґ8ۇfk_ҧR(wi+ ~/ΝϽEidpkQ2k[at~*W}NgwekEYSO Ǔ R1jRomqlnnp|a+.|6(ms&%Ϣn =NR△?"By>D!zzC#(SvKQ@T 62X|L*ˊɌxY3!|EY4RCHI@]9iZtb\mvo3x12cR4BT(ZK$uͬ.wi [brtHF7x1>[t;i[M0e9풦)E4 eYж5٘ lw߼c?_ ^~ESǜӶ-6X8gs4[x[Oq(ҼpB]VuK֊,Mh,5QN+%’s$I:B,툇L+%*p R$aDU^Xmt< `xe ZK_ԡ%d+om6MFGhr< Eh@,oUU Ο;Еc}_ae8xŶ^`ZJ6L&SdQ̩ʂbNY3n<<rCI]lo|X;30:x w(8ܻ[o[]lri %޵[=nZ۠ ]]?@rb׸{K8"xϒIX.HD,ht7sRk֚^QXbs"L)B P"B݊N ͦ V4UYS-ݼOWwɤ  K%].@6&8` Μawܗ~:b^s)k u]lMMThmRU%ZNcRSN[^~>|!Gx892ZY,Iɲx-._@HeskK.SOb6^p9UͽH=z9 Rvke RH,eQD} I_,A{0j)²vX&)xM"H%)eqDG82]ήֺGN`Xm ~s/Ip!h8,RϹ } 5J.]9K6;ՇYՊ.6$n[7? HpXe$^d>xz.'1}u0P->~\MI!M`T4eWSO|<眜|o`cUޑpCNqjkLzA볘/;d<a$'JcA%z.Z [+q} j,N\`4ĉƈQݥ?d68CmV sE] \Hi@Q 0G"շ,+TloorDKc Fdj MERQӒpgN [7oDi G?b g#sppl2;=)OZw/st$UXɂ<(C3nGq->؇y|ZY@TeI]L9US"ςnwLc0聈e[qEeUp8!oHX̪98^LP̦(9bl]8;•-&)o''qİ-O~ W׹ymN!PgPh6ITp(IEhc4Ih~$:Q.=X\5rtĵ-o=i6F<[:." p8`#49sҌI# ĭvkh"Sv{3hD+88>ٔ2C4ëa?[;4΢dt>!M2, ~-O<Ο=ϭ۔UI3,ME$8HHǸ!;UC)( 1 hXksx7xӟ`wZN4Zhl3b6eu wd COiJjR>/B.&Rg;ulQ IR׶xWaXփ^9 "* $uHNYR]Z~>ʹ..w-u>8jH״;\<~4<&KHNd4Mh~@TcݹMV=|2ͬ><+9ӟNowϻ7w[E$puN:G' "rۣ‡\ؑ ]oÈG !1P؆Ň2!(["_4&U乢ۦށȸ|VqnS"Y^yl:'i! ֆl>ENQ/@/sOom'ج9ٌu& N㓥Lpx>z A4u:EΞC4t8<*ڿ!7{?rͭ Uzg\d|~"iaʠ7kȍ9I0!Q V$ e[ !0) V8-!h b4OK*. )i4K1.NR;{}3LC>:Gk#QՎԘXlR 1hMmփAvDq署@ ҷt:p2;C6AAq`)/ 0(0b0S53o[cC2\.-++)mS)Ciƹn/g^,w1j<U^yuϓfxEQ&}ΝcW~r>[r*< BX.\8sxBB8u)5i#~p7߳Ծ+%;:-dTYdvhNu8%Qc4R+X<yO\륒NNh-EL++A JyL*(203Rn[QD9Hۊ=rԊ7ߝ2sC mM@m8"D`us)=Zcv,}g~?O S[nwvrk|^zz%C{Z{VֺB8/L;f0B39d)R9Ȅ~l|1#RVVVx9u4e5'1$iJd!hoaUwΦKo@tXZD/R" h.}BKJ$ {&'$!HIQTUE۶Lk/,[д=Rh6QEk<#nYPzFߠDx2 ֕H!xn@x1*X ¬^-[o&VrxUn:4e0Pbp~@oIxsO vJ .@tyt#MNma\VbBRWu޵ -bP!PYziN6hcC cg\t >GzZb-Sz]g{g= )۴7"i)SUؠ bF]:'`ssՍd̦crbˊ)c5Ab  F +]mS? 6"\t^)<#RdZo;{'jD4MҒtvIBا/X[[ooSq[{>nN]qm!&ɘ`m8}6ZIIS5C dK.mXYYQ]lQ %+nh-QTNݷXXQ`, Kr4łmiچN%xä1<tds܄Iq*x!:#4'K;PW ɱ"EE?}-1`O+ZWCX!MF#.JNll>hwmtv8x3OmSGkݣK]D00)w͘r>[晡 BH!j=O$9u{~Rg};'buu`[l[0Ϩnyާ+4mM=V1:}6TMo;=|2FH拈O'S41]x6ή68<ѴFJ@uL} CZFpGȥH剂ݻN?"n2_L!ƛ),y?&.iE3/!zqUμZHYRV%&M(˒$1819FH{EKBmrd:Qa-4nۺ6Tg:?GvPn?_{\],MEXa>b.HM1>I~ €ecA8Z2&12[Fܼ|Qmzm0>9,H9>:fk [DE4ADkq1'3e1$K8MЪBoh;)uzQ0OPZc CߣrSQ&,AJ!iM ٌʈIz98e\e|Ō;bw/psA#LǣHD%9y)iB@JVwH.XDR7>e4Xh {4e<>" D]2ܿ?W'/n/ݺ3*9ٺplVۑ3?tfɔD@(d6su'n+( `ip$U :uU ϳR9:<$:}B3 ќ^s IDATu8viJu92eIO"z e iۆ^olo33n6Eբd1?ƍcVVG(8> ;yeU%ۼ{mI}Μbu{/ chd[LOm.&wP M t)<%RMIs̪ >"ɩhD"a1_kη OY'eB~ƛW\\OX_Knum4ΡLattcu**Iٟ)կQ? iAʐuRBŢX( Le)UM1ym@M0/h g}ʤYQx)xyl;c:9.YzV#671ITCzR(*P*:, ,K/Ѵz2cf mAg1G9\|, .(4x{ϝuK%'"28|唝TlW*) 0MVKݚowߩNg;){NʐT{>{9g}~׳Zk}N3ЙsuePP9ֶgs^c'gn| ewĄmds=I3,+zh*r'Fu@JT 3@b!BPT)كѠ%t2!d4W,mߘg~w}mނ6{EJA8ks$O>o}A^EFO,YV#@Ӷ)779y)<&PL$)fLYMM뱨*#8whۖ'79-@JbLCZ.aQH'袥…~{oc؋ظxkwZA_?Ʌ//x? Sax<Â@E<- ":[)ZH^Π?H3%բi\Zmwb!(V=O&J2T,K;OmcUd*,BkIYD ȔȴJtRJ,m*z'hg{8vJۛ=<hfd"kY)}\$D"Fjvo?_)Џ|~tޱ|N>WAqrN^%M2Lpߙm |3o~Y.[~;L&o`[MUnZx\KLӴe`bPFs4Eٌt))'zH7JkZڦb슆H2y/9L< ![t4,f "Msz٤T! 8G;"D4TDmkYTsU~=E5m5g@rY1+Ő~ 39Y.zFkX?V>1(bU-upn"2X1&q%BT >̱-B,cf8٣+1*f<CлRko?mqoйVF3hc m \Y)o.Nl ;s훷8{q10Tq%&`,=C4hnwlQ d 䪃yMuᄡw[֭m ~ϰ?H:&IRA!Q)u$A`X[b:E{.Hq`Rd+.h2cI62!܋ "!)zCz}/pS-^.F0؜ EkhK !IVc$,dʃږ`Ft $яGo{Go%D*$Z/.ֿOßܿR#csFYׅ"3`9Ϩqiŝwg"q̪d. {~O$Ʃ7ܵ>wEU0N[gXO~ Iy~Bv;kqD]t HQsm5E^9Z)Lf'vCO=Bs:k/i@P9r^uPg;HØJ ^ub)%Jjd/MEK1Y #@y6[(imllaY<c@+tfx/( Kt =ΘOL#Dx-wK("bʠ㥟~^Obw }3k+,YY( rڶf}E:˙D=?43O_v UëO3K]?dIuIx5}3P7/>U:ŢQ"{ R~^ ɝm-y^歫{COֵAnuF*$I9{;kZp6~", %GLuȤ.~m>t^Ą,O2ze9eK*#icy3 $OO/虆R<*H^B6t䝸7 DB,9ycf4ŪY^WF_y{-os% e8M}NDO;~??ѼRsagM;ՙ5]e[ѹJG(-(hY(-6+ BDIԑ }Rsu߲3љk}=W\,oiZV\7g3.m;%$E7jZk)ub)cO+Di\2)SOJ 7ҩ)yVFcbs+6m FNҐic#>{! D«{:G) y*ZR)4:Gx16XzԪK3Nѩsrxɥ[{d/+^מ:ٻݫl?0Hm%.mdh-XΗyt:!g]_5⥿NC5|tpd0C(2P utp%Ph;8Z`ɠةsԻ_a;Ȍ; vMT-A`:q- N6Z`b ӒxY[i'}nJ5 h&HDZElǴo=O-3ߣ۷i5DP bֽ͍]/0m_t_9f<%2h9_}ݑkc+9ߺ+q!FHEI#J7|~Ee ѿD9:o?|$Jwm,Ղm~&gOhgQKG҈s {}4H9RH<:4APb,B-DWt?N/'h+KSK|,&)cvR 6QWMdiC UX©ee$mAgZi ѪO i)T<jDoeM ) \SO5k^xN!_ kSߡK,YV|CGsF\֩7kO~Uolh?qѧAe_0KV7$A +$K~z,MOrjZor؉8{Raڪfv'\t"]m@RE"dZ>8Sh JqG} zt)i62Y ֲ͉E (#4e8*" ,syVe@:HP=!GT$.C,r~AZ}2[Pd,&ڑIȳ>]b} 3"~.>y&=G6'1E~8F &ޘqOֆX%B"/n#%.0ӭ|q $+: ՔEm!mȴ'Z(W3_;vn~f/d!jCF3hCx?#/YJkcg-&SuQR%Y'q D: |= T.0rAv9A%O|a\'O0GQ-;a#79.2+$tD(`+QZ!D:A§y7JJBctKa%Qbe wPBu~R!$8%UJ%9E&02bd$AM^(B,(2jJdbEҺĈl8;{h^DJW_aݯcv<}WuNUYLw"~}cM>^ 6s5Jv yqBGp#9)s1ZGrWlb,ql{;EA=&Sl:ٶ/Ģoysb~cRkC/ !/JKMQr4d)=V7#=٣W5@=P#J<!%ɭڋi$YliS4hn!XBH\yiwb=\R$iڦFzeZ$/2(va%;3ЈV,ԾӠ/1'Jv]A>9e&6usJ)aNiޠDIA SՌ`CN\ (qNdɔmn0Q<!a/9'X]Y7. y~3OsD$3}T*+M{5g/=aCPjym3_~F}Dzީ(s㗏'Uʓ8V)YC3Ut.jkB~o'~9oU5&kyoyxUt*qka9d^ୠE<[WG#wG(QHl+hx_arI/iJlf 1$2@f h-U| Ue@ oCe%R=ͦ<+/sֽ 1}L>7?~Xn)qUE۠c8)8~Nbv3@QЂDH Upղ̏zV+/ϟ{W J \>m;AMR#hcb!:x~7g?K1v%/WپЊHf2t !ZD)R|iޥ)u)H.I mEj9mHݠ?`4!hLs5.b@  B aDvRR(bȜeҠ\)˩ 7oyDܙ {b<^3w >t}rݡiDZ8갍11B 1%&RB@57pde`Uv/Pk?vnxfzvrGS:=?I9\?*UO\۽)V$'kaYb;`P)b>*{-(7 v=BtS X1xnAF( "IDdVD33Z7ȍ#xA )ƕN2\ #)K!!0^X6Yw9tc0;ƢےVd юBvvv%Y\ Ĺ+&>F:vp&2+{ߘ.~st^yR5޺"Bx͘6| 25o=Q " sI1 ]@<>@qخ1_Խy7χ̓jWQ_~6^{|޿K_)%e&' 9=$ߪQ $:]CƵB/" Z}>Ǩ(R ԖFP:┥4L}J $͢Q*`&H?*2wy(_ԄKҢ1&\^kMf瘆@:*c >`z%R0i*JH# xg&CYOHg)@>5!n'2,O !j\%֥N%dE6&/eaniUb:]-/o9ҾG_//D>z=d&{Hoie]dd* T@dGą>]F0(@"Os00dʏK]lO?_Oz_)\dYY#Liͯi@\<٧e#%BF D B<{Sq3${8Q:'`]IL@ICMIC'졋P{O#8A /aB#@2)=ȵdѤRȲSй 瓼I/M$/ 4n2OaUqVPlp6o6l / u3*FX+nz l4d@#ˆkdۦ7?߻PUU-AB/DzXҸ6'"(>}10) |P~/{py ;NKY^EBD`#qlF!;:yNN"!~ӎ/lſs>huH`2{)eCƘdT Jk1}I4w0Fk m;!XY3inu#q6Mϝ Ȑ!dN9 +dFnAh]<{ˆ9߸|vG>9׮ſQ Q3s#17 EmW Ux)*ي1Zx$)D)2xiCinSXFЛd9njٿD$q tv۸O/xͅ'p\ܼ]>j 7 |S,h>'xI/`V 8I$q^&c4.tɲl"5c=/A+L/^WHa0@b j/MB&Ld;L] 2QrCdt%##a[}HCPVcB!$ڴHClJ)&3KQ  x ad_T676PRdZ" <"`\#KXbO@dVi<ܜRS뫛˵յ.~Ua 's!"7,0'+ePcYbI&\j!vM#i:-4GCں$Ƃ:\`@yw_qxR0!VNDJ_=}*c` O(9hH.yx':û@kabHUk '3 ڀwi(JE8G }uf  qbi ̦3RBbo !cɗ/vζ1vLAEڅc~>JvgZd](JxѸ򫍤R&O)JkP(\n-tB* ڛPxY9,瞶ED[FTmݗ2?'JF)5ɕ[8;`^9ˬxoQjc{6UݧX;&9F^zL0>7a0eN4%1ʓDf1&b#B4QBxZښi|cn pb)x&< @.`XٻzMs\O}|;r}Kg7=U6J6K3a*ȨP턦].nYwI;&o8L쾥qM<09OEs۔+r[Ƨrey;Vڜ`Ĩ# @ \':8. 'Ơ&VF@78D6v/CY!6g$I{L %>vŀ6z^& &Ծ`0 ` t^c2HC+Lhs)!Qc>6\y!p-rC*eLW_iOݿf*(Q}xA8})ש5H,*ؕ,]Vwgw3x'y/-X,y$r6'pP|]۶{ȇץO8^x"g>Ϊi\+c HVX.)U8 6L;A(&\9+ nNTvH9v2gKN}?4=zP%w[8o¢ISڣuRE=iso{dHqCJJ鮋H^>>z }G8R9s{>G{pIȨ_"1KIg$1GD֑/Q}Ɍ#_r:"b7G{^A#iѐQv]6/;SAw!fv Z2J KQL-2蕂Z3d9Ԓ6JVd`@2oQK& oWؾ͙S몶7odSr&Lv;#E2-d#&$1I_Js ->AF06vEI Ƌ}'Y,GT{S=ˏFn]/󆷿 )ݍP2,Y&/HNlr&|^7N0ѧ3wN>Bip-~]/a"4r@Za/>զ}>l_]Pv'?! c+5apΣa&oL\1u@څ=(v?ĎU@tn'iE5:c4 U'DJ:2&K,N7|4l{R:U XYZdBtB0[j敤 ! If\sP;A[V۷e3YL8S@8cj7*ϘoLv$DK+*3b >*^"x]9vvi!ɽb>' d2 'FI\'HXuHTqnN#sm1JUM%z dPrƺES2o$mHF'%4٠·7/3gn|'ϬyY9ۻVKjd /Bm,֐&!3TB2*R5YH`0{`&c ^-ɖeݭw9q{%*PA.TuT=9~ןu qVq=[b5Ե$ӧ!GpJ o܏*n%ג|17(՟E4xMkMD6k<ҿh@[0c{+Ε?E5X̯{寶pxtxzT"'{fwߠu`<ɘ;fӌR%;u4OI$'cUókNEնjr-_FMg"[߽僚}\+Ф'%_@AɪKH$J>z*pH5BS{peXנtFgG|g1e&hkeF] *mPI ֓و.3?df#y,pP\sy?|pBCiV]*QXyY5ܟk鍧)d'x_ɴõJ+՘[dU;@ ǨwU J˜Ud?DYE6|;BzBxnFBfIlMVz]B[#^C_ [Fŵ['>>l^PBۊvvڊ!fw;06 'tט0uv5|l sӎO)]ߺTg;헟/pP Ɍ6Zc4C޳X,("eSIz(kZ3Cfc`byc'@5s!m:!қ,:ːJ^~MWTU2MLr룞D 4`> FIV5ii@Y6z1:72<{fuy~l8GŒEh;Wh[~*fuƚjƩ9!xBRGDž5ڭ5?DT0 у$Qd0* !XY]x=bT{!Q:5 B6@f4*D%pǔ$@ˈg(&tW5Q,οoWrx xx_;V)L붳 Ϗ}+gj[6]ԩ oawbHhԙ;l[.fÕj_z~8~_/e0Ppxh1HZR #9}^ϟ3/ϑd9׶qͷ&}T81@pwCd2L4HS7!;0補A$ 8T}#3*A!Atݱ<HQOx A;sAf:JۢeNedP  \!kM۶lom%>0גV&Ё`v`RJk)ɝ[př3Da8VNFmrB}Iv/4WA!D@#ueD(H tM @Vg^ '-KtKp#ǂf|Q l?3xr!&< Sd׽u׋k|~S:+WGO^^dU\fy[/NUŪ}M`0j0_μtvng&_ c7}-@pI:/Nc#zs}6G7ʲ,TMĐ}ǀ'p!C@Xއ pڮ_;!x&E}@*$'[OX_JO=H$QPlg],eonbѶ㢎{/sV[Tf#?7gVgClJmzzO@ؘcKs4mMFxNқFr||mX6$t'<7JhJlkmƠaK8ǟp4 ߡ IDATᅫ9FcIfVr2-`Q_6 "'JCkVѴirn; 5 ,'uV,cr΢"5Ŕo!!BK=1\ fyI=tE 9YA,h2͟ R_Dr1N.B*$BbKȐZ"u:Y(0Y'H}GSZ1Y,jK!PtDWCqK  ,B{LѦ)4+pnOpVƆ -D!Y y9qbkۂYR呦Ҿ` F3PkAJw%M*" Atm6) KڣuA #N4Rzs"Kb82^)Y"ށ]Eq~9:JD-j.l-^}įD8>Eu{E2bg{ծ)+BkY6f.*kHk4lB_IO(1mt';q规/DzPе-CUVi$_8!$ߞz`6Ӵ-FkɈJ7ovhZ""(ۉ8sAԑr,yI̗qa.Z(3Y=*P_-oFםm>krcLB . Z@H/Қ"/ڤ в(*̦LHcAOOtr=AyT=ђBZ!41h>H> QSBKBP !Z Њm\5Hac{ILƫ6pq`k9Wt:Aox_wܷTû~Lq'yyE>c*=;g3fOؾ g%cɵń-0GPviWrV Ei2cc'i rPYJPʲ:)QPQJO=ݚ^7*WwQ)Wk-Cgk:1v](ehJA"JkpQdΥ_o] T9vwvȋ(J')Z<*A@ Ұ֢ FcN\7%ڣ 8fF&xS)H"|"YK+4AdX gZHT `n4,$JZ^R%Ӷz`2 u;gp/= iDShVPF ꝟcMg[GwspyӣLV^z¯G''-C SBS$ĘCJ} ]\s(%d ##cJWy}#x^ae-a3 MfWbǀ󁮝vMGf A TO='R \/(,ٙsHz_rIL@m&) Q#Y#~L>C&i2Wms y)& ώ: "gwf>T1}v ]OE"M9T=<]@ hĿ蔇xOY>&De9s(=zԱt0ZD,>|m?<].Xdxm:H45}Ɣ*:^ d1D9ۤ(wZ|'PyIX)ЂNDL)ayZ g&Wvwg'Dâ?649ƒw+G> ?J׉,"q!G(ż^Ei(^B)!FU֧>R,IR:)F%C2M/$-XXLKXq/~V6h]CM@ lGl죕9]ւ-M6䲸?2/>4o,>s^Ȳ]AagQKˎaq&I e6xzfI>2R/oG'U]1!ټWbH!ik+d^-! #ō&o]5,CEHA׵>Kn@YNO$?gGtmmYp0ŜfK54W47:fi(q%A:xi>5!B*v/3䕧,[@Vlq]"n. xL82ypBf=׾O_s}c3qz3{Ƙ2/3WW9؟Ѻ:QgmG0OsLo@IMBJud.$zh 1({ *?5 }L>۵[!#{DY CbA/h\f%"Ǵ-{he JBp4"upX>AHtamtRqׇ~*Q Iϙ%N -,kXDtك=c}nIa2E J@;N© P&s݆ǟ\8sZqmɐ$=tmB Xή>Om2XZe.{0DfsÐͶ0MEM٭r_e?ƫg[z7Gw.oe{{,E6$dY`X1Dlgɲ y$g̥뀈`c7%9>8dy?4%yrY H׀:KOڤ"jm hpa6?5:g{H\/ O!'yDRan>;J &ِ,8؟yWŷ?RpKy;O\'w&8 ECWoI*{@1 8sA$4LFgTpf%rdư2| %i$1y!WŔSe5nF)Oy8Չ[1c;FUv0[]xWVVQ""cΰ\Hp>sd% aIy|?6 ʥRP}4XfTdSΦ~BH:Y]3ԒҤ^>_k-Qa|OO,zHYUz;ftnhs<f}!|_}a#B*2q.svN N|@&&-wp GZ;I)9Q@(O$&&cc2C=93SEB'a='%7?xIJ1]HHN, |ݠPS׻ 8A0_/.̳%]yw^l]əDQ Zj֬BSvvWxŜ3nvkNO7LߨXNoϽTl~hYv4Z!`6cz2Y/:|.&`$F쓁%ˡ=K+ݗ5e^ 4] K&²h%{NOE,4۵ J$]gG.5 RmB=@gfbmx45,ꈖS<[x9F;Bp88 =qJƑ^3L~i3? ^,2Ȅ ,"o<>O=suMK᜛nE c=vjĶs2,M"95ܺՖmb7=j{o4:r_jULDX&1R@UUeAgH3}E#E†z 1OM֗!x%1!OL*CR ޥ jTxɎ"1R P(R8"z].Sa]G蓍9|xY %:/0L&'G FDV !C!HAvtXp>ё襂2.bjȲ9Dߧ)w:HLBt t5 H:aD@QM!Ke j ɴ\@s-:Tsm;Q-fW /~ ϝ[^K_}Gx=7*?z{_?;;`4Zs7sU !%EGJ(K;"q%^'B`Nq f^Ŕ_/92x^Q=hQd2(:!xdHI"HC)8du 8z$cx >puk+Y[2#U *E3u \/ؚy_/}ry[8suxUwDj}ol_=cTUty ϕٔnԙ"R&V }Gt]xtVZp &}@+P@Zh&H&hIJIb>`8d6O׋TMp.^皧Fc< Kd< Ԃbb*rN=mQ2/MِLL!umXڌt$:2TL&5=J={U2bw}OWlDAd>[ZF]L⧟bdzOmn3CyyzΝ˿sggsnv1J+ *)RL/c 3ڶKKHEҩ#^Ao6Q~ ]ofJ(N!$^Ҷ- nMb!M|To=tLHndMDLH,_+PZxzAȧ@D 4*r кD}~r&9`2$XG9(Ѧ |OY\Y*mHˍU. tLeJ:8HBy0RԻ)D1`]A%TAi9SѪ ̽]dk8!y>Ci$ܡj/DT};yCxKBL?wbAڤp~{+h&e_ɢ@JS`$1WIu*iIes xOuji:O^B>_N bLWu]HýW$= K-ܱA;9Br$K)D!#´i,)>jc,1x ^ 2j1Θ_.胎0YBzsDo*CSikΐZ< ML\!q}u+؅DEGH"-!P d̘Z{x90F"$Q(bBۍ'0{Yɳkĥ~:Z}no\?k~oS/={6Y&L&yݓ=[?z4x|;d,34]e(S"rc4gu8TZ ,];273yx "A4W Ϸ?\Y&I`e)0^,L&~6\t%)pEʈ$/#yi]e6ČNQ,p^6+`&ktnAB }H-AXCl\!3tV݄}}w }?83tj>_|('w} 2c0Y%_{Lb d|zL!TJdZ#_aK2O_#@}#Цkє^i:{L>نbK;ӛb?7oo5l="'N,g ֗}WC粷G4 s7A"}p .9y{1'%_7P?D-W-dJZF7=Fάv^|aA1 ') 5a#"HglDnl7+?OnO/  ^,(ȳX&6,} 1}oBJtaٓ!0_лJ |/Bi]%>`!h0yGg;:u]ovJ AuHmFTBM9w1V-4@(2SP;hV! To!74!F'A!RQ`%W?^H7}Øҙ7rpp.1K|"C RBdR}c"[KN,;~|7/?:z]TUeRNLҦ $62N B':ZD ) )"qP ^ӨOBB2 If!kLhD侞qlSzMS *NnxT5eױV̀aM 8ЕGK0t{!HIEϟbwm;IۘʘN+ƛqYp SsJL9+`}w.z4BHTü/J\ 7}%d :^F)^ol7VZ?3668KN>.mdhet{{lʦ7Za}R)+CU=IH ЇP2[X4_ H:%A5ɫo5&Kcڦ{wiIԚz6嵯_5zDĖZpbb[V TjF Sʱ5X v6nR% ,-ʭ䕦1Z{˓do`t;Kb9Dr3z]0Z2)7B6yBg;T, x]: K/-&'+_{>ħ?)k|^ݙ^jS֊xm2ڢfH3 eR zC OoGVc#2 XtԶ-𝥮1YV074u>isՋN'E^][#3b>:{'o[чr˼\8)9ͷ} | gxC/\ `g[St B#kgHy@KlDQTH怠 Qak9l7Ź_=| ; <#ԓZeb)~YƧ~'Wevў vg" JR5x⁺ʙ?k;e&ٞ=pc?{?ė.n}ٳNդ\,̔c99Pyu謿ƃi"%J!%#ݼO$2T4m2ɐ{{buLr'/ysSkv̗5GӝeַZ&Ycʾ <X:PDA=!)̽c8(q^7>80xMn>e4͔+{CJAK~7Gsܿ}}]@GB!H^IHq%pAEo+f?{?$=GX#=9/v\,='W?nhq}:FgT-Zst 5ڦcmu]2 -h8Q^LihH5ewǃ4)W.]\SeU5'>w˭gY&ܽt떓g~;|/ċ˱턄v'c4 eT mdASu`m5p*JpIc%w&ͬ8z΍sVpn$K⸚oO*iwng>\ !lŤ` =V:NR._p+)f82q\O "L=/#jYNc{Z ikgQ&2 n n_d(#$)t*gH;oĭ.~/HNgn J_%Ehm'c5d aUfko=_) gh-|'K7r Rr8/te<Bv*c*MXΏ"V !vFB7B\D*@5NQ}+'/:Nomܨ<+]gn֕ ܝrMd<.ҳ70B@=_~tvDv[im}@6@uޏ(Z|PJ{ (Y0w7or=k({ _ivoEU9bh9 Pݴ lPL!J:8?)fMZBːAkA#+D/y _wkg;n5p;j+-X "ƣh(yۅںpy4zMʖb.:TEUxVbh6a+=;{VߔwoqいEǠ[AfVSe+AS(6kQkFHgY0J SoAQJԶcU#.PUCNȁ>FUt= LO24GNi{>xBu\<6hD9Gz^ag}L5.q\kh- 6c hǟWYoW哹0%8Q 4ыs bckKP9o~׻*<(1 DڌևXk z9|G;xAI\k ԩl$Xv1a? ?T84`?Lg},:ml2[Gk^.~tVXB ,[(cgY7I $2)Q6#OJ儝}?>wǎH[`Eʾ%a1wFNUƃU}eAU]{PR4"}0kQNiߏT)REjT@A?Fe 2N9S^ĸDȩPJ=q(-G'7Pͬ+湛 S4u=Bdcn:"aUW!"s޳46"!TT;ZFmd5 8owՍ@)@Ȁ,YJ+,%k6M^Fb +$&FFֵU4; xTY+אA6V4kHJzDD"Js ,`9>bVfia*w^w@x e‰/GD)Ol.d |NH\" bi89Iz?Ftt":%@sAۨs ߡTUV6}7m뫟߸"f 3{;p )V]Kf8|:&gQ{׺WI= qE7@~{PkIdmQ䂼"DTkd@&Fz˿,.q n4~{m bi& ;Ѝ@>ľ=L2x ǵiAcQ`8jP.#>PN5l>Ex1Ѩx+F9Z3T>G/{t1Í@h'čWf^ZkZ($Dz;*।SZABV 9z;:h\ivS1OܼRgKFÄ3 dYNyt=CW n(<TvNrOvX~\90{j+E'6^::2$VEJ0ނPH(!T#ˤ(+St(DN T!nņ!Zm\}uDQ? DDl:(\ jhO>yg}[Tu8T"btJXtEPO^ƣa0B7UX1*Q4R^>z]0F1Wq4.wwcvqtێkL%i$(] dMU&ƾ0I5*;c NOݎthl0ɇ/dBNPQ˩c_&v:QD-$-92$&}s`\Xanl$8۴QJ\ LeO0q;L zw~8v3 77IE6qݦ$2xn/ݺ xyab L >*<ǿQ&b,u· 7B) Bxxp$*1g8%6"u!Je_erQ6X2>| d5:. bHubkmBUi Lj{( }/׼v]^=SQ~ٵ篈)`wH$k߿' IY <[=^yU.n);韟NzZIUD(Pc@X.C:?ܖ` ]OWx#CWϵXٿx#3hAǾ… /p]qu7c'G?8r*ct7aĥZ"LT!t?DV0qQK{yቲ_~}2+3ta,-r C@IodFS2TNp*҆txaRYkZ6L{עRTLF:2Ry8Uicey{Bô# ^@q2*Ou |x7;iva"3MkDɼx>~'}쓯k?`6 8o8t3D7;#C@K6ǎN@ JGvYi`"]]#<.zNO%46 D;9Zr0rLop_w,f7 D5t#PZAV R l(4*xgd8>yn9uv Y` za?lYH9aܧ&/u5}9Gos-ųg֫+ $ێ\ՙ ;4xmATD}(!*\xa,t͘wܓs[$͆ 9$ROPԡ!|U+jt;Qld!^{O~oMb2;xSs["ʖQҐ ЦA9܆HZHMi.vX(J|GGL%B&J|c("'H% } [Ү!JיZ$(A/:qxƓo'%z)'4ε*ڣ /؉Ξ(ɒ/7g`ƛC~'V(\"Md*BaVg w`ѦV RM=jQ~Q/G-jC[ӫhФIs7uHq^K۴M퉄 L [G: ɲ ;WrHw}0+3t}i~hpa}Սh[>#95"ًuIvJC^ڎڑ&YAfuٓuQH-[?_ I) u)v]KKYxF$"@]=kxsx7]3KkkC3m%ɭ7)1вbXL YE]"7D=: -Zs6U 6B 8 x`(xTcK5uYSIڈDZh$8hcM #CpD 8~-!ҵYhQvfΛ-} }-qsK_0͋/Mo?aLv?pz!b2q%lu%q`8DbA"%HH4^Rvy HO5;N`@::8+*pa<,;B&%H&AIL$)@K|p"lF8iwv{]щ_,kD=~~y \IDATzz]f3ι O /.z4!P[(מGHϞeAiYK*VK}^Zr R-j;q޳7$1 iӻO(Q;Y'0NJqIl(l١sO?v?28vKBt(hVf>E4-/nN:ˢV0!59"ťKEn)&G:BN=A'J)%NBCݫ)'Q G t#`2D> !0q! @%S4ȕhjeӄJ^~f=O'^#ׅ-_\^/V#;*V #yZw!M+=QL1osa#. {CD>p!H$i@ =I&uhj^ 1 eV\ X :^Y6DaCD6?P9+3fol]E ' N^,o;Eޑ !5L|E, ʡeb3BHk!#nqn R@@ @Rn#-RD)WQPI  x qD ROf&R;&U0Å>]A0-C6. #GS'7o;{.k7"JbP]|gɠ`yWD3+Q634"]Ĥ-TŇ:h'J⺋#@R%!X|0 mEU[cuf@Hq3O݀H-6+ʑCx}ˇׯif\<'zҟ^<؋8hS؈s[=דzw4O'ݢ֕P[1+WzF_:$iȀ&%T֣5Vgpخ>CZ70{WgL/\>+3Ҩf}N㫝 }dK95JQ y(3S'bx0D}kGLN=c0c_nt<=8_zX:حLSMh(T`4mˠ8Țupkn;^k#])#9ALSbGM@0!A:oc-X'q03¹ ]]}Ͽwh1>vKq7L;m^i8Rh)Br#d \&iTS_w[{J]zTC В cYCv(dΑ57=_Of`ƌμw c}DopaS,/!f)rxb)& LbjI( Rפi!t(AITRŠ#񑢹{N%ڸV sO+}g`_!w$}ϟ(G^ZmLo_){hg0WL(lj^JPJ!?j!#W]D:b+aj?6@Q tεUyX{/xBq}#lxG^ٟť94YvP+͂b",;=P21Y gBצֶ8~wKn㽭G$RPAud=e+j ڻmMg`O|%q x7?q]RɆH ~)-7,ΏPޢƅVkLP [\H;YZc^2쌛>wmݷ?Dr4^u=gGo(y {P護υL_yDў[:.\CD n ێ9AL `RRom_Kp=bϟk O|bܖnM\"i[N&~yuk}\Zx]5+8"-mOV?u?ַ9k/qF0`4~P2,1(#9n ^8'H7MaS/|U Go𠰠 *mX>ϙi/s3{ԶyX2#MAv@gC85m%$(-R13qyG^+ _KO:O 0b#ǰ]\I=b}R_Phsf_̋ iaҌZiH #  1C]<sy y4>o8<a#_~KI^n`epԃM Y<+t1EL9d"DPK>6/o]gaL5#s( q@eWTҤ@hH2DFk4 Z8,%~N$#irEHd!Omh#:$V8YYۮԛW}ғdn3]~!Č.X: VtHCw-ف6b2(HAb G/ȲhkCyr!.A/~ݮ8T(ql&g.8a:KZW{Af݄yҎH 3xۗMCЋ(zjVzTh/xzsO>\}~˾ =O/T* =dXͣH(ف;a4$k)ƿA F9H I 6^bmH0?%6YW#n|M7ݼx.C'J k߹%,sL7-m.4*Zyg7L?wk"bITIk۰? \a|9Dg˒eX{ ?b'=O7_۸h_+=]} =v~}[n x(kFL\R@#vҕi~> zz m2fC8 AD6X%f=o'ViU)Xw{Z?oKw6/X`a'V0#?׆ ţL2}"㙿(0t A}>nz4~Yj͔oViZq}E?b|2Grz9~-Vt7Z .[H;o3\p ؼJ̓l>N<q.Hvp~9h#]:˲mlIt t,`'|tkb:Xfn>,t08سa?|4.=y߽RH휞`jb2NMSiVaiz;E%M T&Ic@FX3pҊky` Ho?Ho㸧"Ϋ/;qQ "O=\obs~i<‰u'q Ӕ+x5C,+MF9Z C7{vÒQ9RM*Er<}ʱ|M'nwu/d_=4o&egی|ZYVMʼn}bNc  hC I1xI3~?= 1bj³Bv=`5,4#m;rn޹=(iPK?{|g;~z;w}'KKXKq9 8s +;0_5]"^M dMٍ":Ae 'gZ5FVD hU`ϑC=Yr~Mkn M6oM'h//`%נ,c(;e5Ҷnފpx o~{P:`_,SDBDŽB:ǾG[(e\ǥZ]"GǩvwUj/@5?6L\çh-412*U:By>~BOޗX7щγ 4Q8vf2?9lZ6.[h')*x%t>yxϻ޾뮻N@:[D6vf|` 7{X2Fh,ע˗ac fX6Ji6% ]E Oe$Ig(4&YXS,o[jڍr~z}w}߉c{}f} X1PµR,BJ2M&oKT#! &}IXQ;G϶6(W ة״7=kV_6a$ Yf%iИ#tZS@'g?Z$D*B2zH <ܩ+e#-jW9P̵{*玿0:Ss^_+7/^wE9kW\),[QIt":_K|$H1"{Xhl"{$M{d-8M9:sj1?Ͻ۸__j?'|..xY{:&]5z?M xh"IS,- 4ؖ,uXBb!h1q&{S'MSKu֭bˍ;yGp Zݟ-nRQ+h5]5a/⇃$? 4{ǀ4rH71$SXi؋ed!R `²a@XPcMeX&R~t'n,CI89q{xwPv۝hSˢ)'+@l܎jŅfy.Po˾bEie )2|z N%8:i9vrV8OɮW=(R2!'Qf⊌]ng_ \##liZKp*ʁ05|axiyFVf(0Z`%*JFJ@pY :6Ė j"=b* "Xt/!p% Q¥ORn' o~h:3˥䌱,]$qp: ZCυw:/g8nrD tHM\tD t\f9.Zvfs߾O9*gAcoAy;i8Fw,MWl=rmЬZ W,^vN\O1S9;.شe`o;4&CcH# 1DI:dZ a>DKv<Bh9\aښcx +p|$̡RH+Bkl}4=\sgY0jG8I<TFY|| Ztu*f_,G9{f%92Q  9VB[2п~\|"]45 ԭJӭ2ZQ.{Zpr.%w7/܋i F$[t0c5x>\g3h4#sU1FD1L.ciKX kHRCFQZ1\7nܶy!;'`qnsgps!S򊴖 a 8^IkZGf$>O۴aŜvHw]$AZ,0,ñ-z =k& m`;Nj0aĵ7㌽܊]KG ӏ-}i]۪͆]g[b !J"5QcRS*yn{K뵙j4gA_YAG|0qe>I~'AV!8S~ew{UosGGye7#DݎkݕFfȱSs+'&f~co~y:Ś6kQh"ilGu_Y~.hrwEǟv7tV볘T;8"Dm84H a %I'%dM̝KP.М]JRE7F-WSN4 21L:DEۮ&WK̨'Nl&gDID4i`&=?YJu/kt_ K::kJ霧oYhfEa @ CJdQ,Rg meq^WS?u#?>:nm ≦[[5!e#;sSkmIENDB`swayimg-3.1/extra/swayimg.1000066400000000000000000000152001465610152200157220ustar00rootroot00000000000000.\" Swayimg: image viewer for Sway/Wayland .\" Copyright (C) 2021 Artem Senichev .TH SWAYIMG 1 2021-12-28 swayimg "Swayimg manual" .SH "NAME" swayimg \- lightweight image viewer for Wayland display servers .SH "SYNOPSIS" swayimg [\fIOPTIONS\fR]... \fI[FILE]...\fR .\" **************************************************************************** .\" Description .\" **************************************************************************** .SH "DESCRIPTION" If no input files or directories are specified, the viewer will try to read all files in the current directory. .PP By default, the application generates a list of images with all files in the same directory as the image being loaded. This behavior can be changed with the \fBlist.all\fR parameter in the config file. .PP Use '-' as \fIFILE\fR to read image data from stdin. .PP Use prefix 'exec://' to get image data from stdout printed by external command. .\" **************************************************************************** .\" Options .\" **************************************************************************** .SH "OPTIONS" Mandatory arguments to long options are mandatory for short options too. .\" ---------------------------------------------------------------------------- .IP "\fB\-h\fR, \fB\-\-help\fR" Display help message. .\" ---------------------------------------------------------------------------- .IP "\fB\-v\fR, \fB\-\-version\fR" Display version information and list of supported image formats. .\" ---------------------------------------------------------------------------- .IP "\fB\-g\fR, \fB\-\-gallery\fR" Start in gallery mode. .\" ---------------------------------------------------------------------------- .IP "\fB\-r\fR, \fB\-\-recursive\fR" Read directories recursively. .\" ---------------------------------------------------------------------------- .IP "\fB\-o\fR, \fB\-\-order\fR=\fIORDER\fR:" Set order of the image list: .nf \fInone\fR: unsorted, order is system depended; \fIalpha\fR: sorted alphabetically (default); \fIrandom\fR: randomize list. .\" ---------------------------------------------------------------------------- .IP "\fB\-s\fR, \fB\-\-scale\fR=\fIMODE\fR" Set the default image scale, valid modes are: .nf \fIoptimal\fR: 100% or less to fit to window (default); \fIwidth\fR: fit image width to window width; \fIheight\fR: fit image height to window height; \fIfit\fR: fit to window; \fIfill\fR: crop image to fill the window; \fIreal\fR: real size (100%). .\" ---------------------------------------------------------------------------- .IP "\fB\-l\fR, \fB\-\-slideshow\fR" Run slideshow mode on startup. .\" ---------------------------------------------------------------------------- .IP "\fB\-f\fR, \fB\-\-fullscreen\fR" Start in full screen mode. .\" ---------------------------------------------------------------------------- .IP "\fB\-p\fR, \fB\-\-position\fR=\fIPOS\fR" Set initial position of the window (Sway only): .nf \fIparent\fR: set position from parent (currently active) window (default); \fIX,Y\fR: absolute coordinates of the top left corner. .\" ---------------------------------------------------------------------------- .IP "\fB\-w\fR, \fB\-\-size\fR=\fISIZE\fR" Set initial size of the window: .nf \fIparent\fR: set size from parent (currently active) window (Sway only, default); \fIimage\fR: set size from the first loaded image; \fIWIDTH,HEIGHT\fR: absolute size of the window in pixels. .\" ---------------------------------------------------------------------------- .IP "\fB\-a\fR, \fB\-\-class\fR=\fINAME\fR" Set a constant window class/app_id. Setting this may break the window layout. .\" ---------------------------------------------------------------------------- .IP "\fB\-c\fR, \fB\-\-config\fR=\fISECTION.KEY=VALUE\fR" Set a configuration parameter, see swayimgrc(5) for a list of sections and its parameters. .\" **************************************************************************** .\" SWAY integration .\" **************************************************************************** .SH "SWAY MODE" The Sway compatible mode is automatically enabled if the environment variable \fISWAYSOCK\fR points to a valid Sway IPC socket file. This mode provides some features such as setting the window position and getting the workspace layout. By default, the application creates an "overlay" above the currently active window, which gives the illusion that the image is opened directly inside the terminal window. .\" **************************************************************************** .\" Environment variables .\" **************************************************************************** .SH "ENVIRONMENT" .IP \fISWAYSOCK\fR Path to the socket file used for Sway IPC. .IP "\fIXDG_CONFIG_HOME\fR, \fIXDG_CONFIG_DIRS\fR, \fIHOME\fR" Prefix of the path to the application config file. .IP "\fISHELL\fR" Shell for executing an external command and loading an image from stdout. .\" **************************************************************************** .\" Signals .\" **************************************************************************** .SH "SIGNALS" .IP \fISIGUSR1\fR Perform the actions specified in the config file, \fIreload\fR by default. .IP \fISIGUSR2\fR Perform the actions specified in the config file, \fInext_file\fR by default. .\" **************************************************************************** .\" Exit status .\" **************************************************************************** .SH "EXIT STATUS" The exit status is 0 if the program completed successfully and 1 if an error occurred. .\" **************************************************************************** .\" Examples .\" **************************************************************************** .SH "EXAMPLES" .PP swayimg photo.jpg logo.png .RS 4 View multiple files. .RE .PP swayimg --slideshow --recursive --order=random .RS 4 Start slideshow for all files (recursively) in the current directory in random order. .RE .PP wget -qO- https://www.kernel.org/theme/images/logos/tux.png | swayimg - .RS 4 View using pipes. .RE .PP swayimg "exec://wget -qO- https://www.kernel.org/theme/images/logos/tux.png" .RS 4 Loading stdout from external commands. .RE .\" **************************************************************************** .\" Cross links .\" **************************************************************************** .SH SEE ALSO swayimgrc(5) .\" **************************************************************************** .\" Home page .\" **************************************************************************** .SH BUGS For suggestions, comments, bug reports etc. visit the .UR https://github.com/artemsen/swayimg project homepage .UE . swayimg-3.1/extra/swayimg.desktop000066400000000000000000000010021465610152200172260ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Swayimg GenericName=Image viewer Comment=Image viewer for Sway/Wayland Icon=swayimg Exec=swayimg %F Terminal=false Categories=Graphics;Viewer StartupNotify=false MimeType=image/avif;image/bmp;image/gif;image/heif;image/jpeg;image/jpg;image/pbm;image/pjpeg;image/png;image/svg+xml;image/tiff;image/webp;image/x-bmp;image/x-exr;image/x-png;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-targa;image/x-tga NoDisplay=true swayimg-3.1/extra/swayimgrc000066400000000000000000000131101465610152200161060ustar00rootroot00000000000000# Swayimg configuration file. # vim: filetype=dosini # This file contains the default configuration. # The viewer searches for the config file in the following locations: # 1. $XDG_CONFIG_HOME/swayimg/config # 2. $HOME/.config/swayimg/config # 3. $XDG_CONFIG_DIRS/swayimg/config # 4. /etc/xdg/swayimg/config # Any of these options can be overridden using the --config argument # on the command line, for instance: # $ swayimg --config="general.scale=real" ################################################################################ # General configuration ################################################################################ [general] # Mode at startup (viewer/gallery) mode = viewer # Window position (parent or absolute coordinates, e.g. 100,200) position = parent # Window size (fullscreen/parent/image, or absolute size, e.g. 800,600) size = parent # Action performed by SIGUSR1 signal (same format as for key bindings) sigusr1 = reload # Action performed by SIGUSR2 signal (same format as for key bindings) sigusr2 = next_file ################################################################################ # Viewer mode configuration ################################################################################ [viewer] # Window background color (RGBA) window = #00000000 # Background for transparent images (grid/RGBA) transparency = grid # Default image scale (optimal/fit/width/height/fill/real) scale = optimal # Fix position of the image on the window surface (yes/no) fixed = yes # Anti-aliasing (yes/no) antialiasing = no # Run slideshow at startup (yes/no) slideshow = no # Slideshow image display time (seconds) slideshow_time = 3 # Number of previously viewed images to store in cache history = 1 # Number of preloaded images (read ahead) preload = 1 ################################################################################ # Gallery mode configuration ################################################################################ [gallery] # Max size of the thumbnail (pixels) size = 200 # Use anti-aliasing for thumbnails (yes/no) antialiasing = no # Background color of the window (RGBA) window = #00000000 # Background color of the tile (RGBA) background = #202020ff # Background color of the selected tile (RGBA) select = #404040ff # Border color of the selected tile (RGBA) border = #000000ff # Shadow color of the selected tile (RGBA) shadow = #000000ff ################################################################################ # Image list configuration ################################################################################ [list] # Default order (none/alpha/random) order = alpha # Looping list of images (yes/no) loop = yes # Read directories recursively (yes/no) recursive = no # Open all files in the start directory (yes/no) all = yes ################################################################################ # Font configuration ################################################################################ [font] # Font name name = monospace # Font size (pt) size = 14 # Font color (RGBA) color = #ccccccff # Shadow color (RGBA) shadow = #000000a0 ################################################################################ # Image meta info scheme (format, size, EXIF, etc) ################################################################################ [info] # Show on startup (yes/no) show = yes # Timeout to hide info (seconds, 0 to always show) info_timeout = 5 # Timeout to hide status message (seconds) status_timeout = 3 # Display scheme for viewer mode (position = content) [info.viewer] top_left = +name,+format,+filesize,+imagesize,+exif top_right = index bottom_left = scale,frame bottom_right = status # Display scheme for gallery mode (position = content) [info.gallery] top_left = none top_right = none bottom_left = none bottom_right = name,status ################################################################################ # Viewer mode key binding configuration: key = action [parameters] ################################################################################ [keys.viewer] F1 = help Home = first_file End = last_file Prior = prev_file Next = next_file Space = next_file Shift+d = prev_dir d = next_dir Shift+o = prev_frame o = next_frame c = skip_file Shift+s = slideshow s = animation f = fullscreen Return = mode Left = step_left 10 Right = step_right 10 Up = step_up 10 Down = step_down 10 Equal = zoom +10 Plus = zoom +10 Minus = zoom -10 w = zoom width Shift+w = zoom height z = zoom fit Shift+z = zoom fill 0 = zoom real BackSpace = zoom optimal bracketleft = rotate_left bracketright = rotate_right m = flip_vertical Shift+m = flip_horizontal a = antialiasing r = reload i = info Shift+Delete = exec rm "%"; skip_file Escape = exit q = exit # Mouse related ScrollLeft = step_right 5 ScrollRight = step_left 5 ScrollUp = step_up 5 ScrollDown = step_down 5 Ctrl+ScrollUp = zoom +10 Ctrl+ScrollDown = zoom -10 Shift+ScrollUp = prev_file Shift+ScrollDown = next_file Alt+ScrollUp = prev_frame Alt+ScrollDown = next_frame ################################################################################ # Gallery mode key binding configuration: key = action [parameters] ################################################################################ [keys.gallery] F1 = help Home = first_file End = last_file Left = step_left Right = step_right Up = step_up Down = step_down Prior = page_up Next = page_down c = skip_file f = fullscreen Return = mode a = antialiasing r = reload i = info Shift+Delete = exec rm "%"; skip_file Escape = exit q = exit # Mouse related ScrollLeft = step_right ScrollRight = step_left ScrollUp = step_up ScrollDown = step_down swayimg-3.1/extra/swayimgrc.5000066400000000000000000000404461465610152200162650ustar00rootroot00000000000000.\" Swayimg configuration file format. .\" Copyright (C) 2022 Artem Senichev .TH SWAYIMGRC 5 2022-02-09 swayimg "Swayimg configuration" .SH "NAME" swayimgrc \- configuration file for the Swayimg viewer .SH "SYNOPSIS" The Swayimg configuration file is a text-based INI file used to override the default settings. .\" **************************************************************************** .\" Config file location .\" **************************************************************************** .SH "LOCATION" Swayimg searches for a config file in the following locations, in this order: .nf \- $XDG_CONFIG_HOME/swayimg/config \- $HOME/.config/swayimg/config \- $XDG_CONFIG_DIRS/swayimg/config \- /etc/xdg/swayimg/config .\" **************************************************************************** .\" Format description .\" **************************************************************************** .SH "DESCRIPTION" The structure of the INI file consists of key-value pairs for properties and sections that organize properties. .PP The basic element contained in the INI file is the key or property. Every key has a name and a value, delimited by an equals sign (=). .PP The name appears to the left of the equals sign. The value can contain any characters. .PP Keys are grouped into named sections. The section name appears on a line by itself, in square brackets ([ and ]). All keys after the section declaration are associated with that section. .PP The number sign (#) at the beginning of the line indicates a comment. Empty lines and comments are ignored. .PP Any option can be overridden using the \fI--config\fR argument in the command line, for instance: `swayimg --config="general.mode=gallery"`. .\" **************************************************************************** .\" General config section .\" **************************************************************************** .SH "SECTIONS" .SS "General" The general configuration is stored in the section \fB[general]\fR. .\" ---------------------------------------------------------------------------- .IP "\fBmode\fR = \fI[viewer|gallery]\fR" Mode used at startup, \fIviewer\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBposition\fR = \fI[parent|X,Y]\fR" Set initial position of the window (Sway only): .nf \fIparent\fR: set position from parent (currently active) window (default); \fIX,Y\fR: absolute coordinates of the top left corner, e.g. 100,200. .\" ---------------------------------------------------------------------------- .IP "\fBsize\fR = \fI[fullscreen|parent|image|W,H]\fR" Set initial size of the window: .nf \fIfullscreen\fR: use fullscreen mode; \fIparent\fR: set size from parent (currently active) window (Sway only, default); \fIimage\fR: set size from the first loaded image; \fIW,H\fR: absolute size of the window in pixels. .\" ---------------------------------------------------------------------------- .IP "\fBsigusr1\fR = \fIACTION\fR" Set the action to be performed when the SIGUSR1 signal is triggered. Default value is \fIreload\fR. .IP "\fBsigusr2\fR = \fIACTION\fR" Set the action to be performed when the SIGUSR2 signal is triggered. Default value is \fInext_file\fR. .\" ---------------------------------------------------------------------------- .IP "\fBapp_id\fR = \fINAME\fR" Set a constant window class/app_id. Setting this may break the window positioning. .\" **************************************************************************** .\" Viewer config section .\" **************************************************************************** .SS "Viewer" Configuration is defined in the \fB[viewer]\fR section. .\" ---------------------------------------------------------------------------- .IP "\fBwindow\fR = \fI#COLOR\fR" Window background color in RGB or RGBA format, default is \fI#00000000\fR. .\" ---------------------------------------------------------------------------- .IP "\fBtransparency\fR = \fI[grid|#COLOR]\fR" Background for transparent images: .nf \fIgrid\fR: draw chessboard (default); \fI#COLOR\fR: solid color in RGB or RGBA, e.g "#10ff4280". .\" ---------------------------------------------------------------------------- .IP "\fBscale\fR = \fIMODE\fR" Default image scale, valid modes are: .nf \fIoptimal\fR: 100% or less to fit to window (default); \fIwidth\fR: fit image width to window width; \fIheight\fR: fit image height to window height; \fIfit\fR: fit to window; \fIfill\fR: crop image to fill the window; \fIreal\fR: real size (100%). .\" ---------------------------------------------------------------------------- .IP "\fBfixed\fR = \fI[yes|no]\fR" Fix position of the image on the window surface, \fIyes\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBantialiasing\fR = \fI[yes|no]\fR" Enable or disable antialising (better quality, but slow rendering), \fIno\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBslideshow\fR = \fI[yes|no]\fR" Run slideshow at startup, \fIno\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBslideshow_time\fR = \fISECONDS\fR" Set slideshow image duration in seconds, default is \fI3\fR. .\" ---------------------------------------------------------------------------- .IP "\fBhistory\fR = \fISIZE\fR" Number of previously viewed images to store in cache, \fI1\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBpreload\fR = \fISIZE\fR" Number of images to preload in a separate thread, \fI1\fR by default. .\" **************************************************************************** .\" Gallery config section .\" **************************************************************************** .SS "Gallery" Configuration is defined in the \fB[gallery]\fR section. .\" ---------------------------------------------------------------------------- .IP "\fBsize\fR = \fIPIXELS\fR" Max size of the thumbnail in pixels, \fI200\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBantialiasing\fR = \fI[yes|no]\fR" Use anti-aliasing for thumbnails, \fIno\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBwindow\fR = \fI#COLOR\fR" Background color of the window, default is \fI#00000000\fR. .\" ---------------------------------------------------------------------------- .IP "\fBbackground\fR = \fI#COLOR\fR" Background color of the tile, default is \fI#202020ff\fR. .\" ---------------------------------------------------------------------------- .IP "\fBborder\fR = \fI#COLOR\fR" Border color of the selected tile, default is \fI#000000ff\fR. .\" ---------------------------------------------------------------------------- .IP "\fBshadow\fR = \fI#COLOR\fR" Shadow color of the selected tile, default is \fI#000000ff\fR. .\" **************************************************************************** .\" Image list config section .\" **************************************************************************** .SS "Image list" The image list configuration is stored in the section \fB[list]\fR. .\" ---------------------------------------------------------------------------- .IP "\fBorder\fR = \fIORDER\fR" Set order of the image list: .nf \fInone\fR: unsorted, order is system depended; \fIalpha\fR: sorted alphabetically (default); \fIrandom\fR: randomize list. .\" ---------------------------------------------------------------------------- .IP "\fBloop\fR\fR = \fI[yes|no]\fR" Looping file list mode, \fIyes\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBrecursive\fR = \fI[yes|no]\fR" Read directories recursively, \fIno\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBall\fR = \fI[yes|no]\fR" Open all files in the same directory, \fIyes\fR by default. .\" **************************************************************************** .\" Font config section .\" **************************************************************************** .SS "Font" The font configuration is stored in the section \fB[font]\fR. .\" ---------------------------------------------------------------------------- .IP "\fBname\fR\fR = \fINAME\fR" Set the font name used for text, default is \fImonospace\fR. .\" ---------------------------------------------------------------------------- .IP "\fBsize\fR = \fISIZE\fR" Set the font size (in pt), default is \fI14\fR. .\" ---------------------------------------------------------------------------- .IP "\fBcolor\fR = \fI#COLOR\fR" Set text color in RGBA format, default is \fI#ccccccff\fR. .\" ---------------------------------------------------------------------------- .IP "\fBshadow\fR = \fI#COLOR\fR" Draw text shadow with specified color, default is \fI#000000a0\fR. To disable shadow use fully transparent color \fI#00000000\fR. .\" **************************************************************************** .\" Text info config section .\" **************************************************************************** .SS "Text info: common configuration" The section \fB[info]\fR describes how to display image meta data (file name, size, EXIF etc). .\" ---------------------------------------------------------------------------- .IP "\fBshow\fR = \fI[yes|no]\fR" Enable or disable info text at startup, \fIyes\fR by default. .\" ---------------------------------------------------------------------------- .IP "\fBinfo_timeout\fR = \fISECONDS\fR" Timeout of image information displayed on the screen, 0 to always show, default is \fI5\fR. .\" ---------------------------------------------------------------------------- .IP "\fBstatus_timeout\fR = \fISECONDS\fR" Timeout of the status message displayed on the screen, default is \fI3\fR. .\" ---------------------------------------------------------------------------- .SS "Text info: viewer" The section \fB[info.viewer]\fR describes how to display image meta data (file name, size, EXIF etc) in viewer mode. Each key/value configures a set of fields and their format. .IP "\fBtop_left\fR = \fILIST\fR" Set the display scheme for the upper left corner of the window. .IP "\fBtop_right\fR = \fILIST\fR" Set the display scheme for the upper right corner of the window. .IP "\fBbottom_left\fR = \fILIST\fR" Set the display scheme for the lower left corner of the window. .IP "\fBbottom_right\fR = \fILIST\fR" Set the display scheme for the lower right corner of the window. .PP \fILIST\fR can contain any number of fields separated by commas. Plus at the beginning of a field name adds the field title to the display. Available fields: .IP "\fIname\fR" File name of the currently viewed/selected image. .IP "\fIpath\fR" Path or special source string of the currently viewed/selected image. .IP "\fIfilesize\fR" File size in human readable format. .IP "\fIformat\fR" Brief image format description. .IP "\fIimagesize\fR" Size of the image (or its current frame) in pixels. .IP "\fIexif\fR" List of EXIF data. .IP "\fIframe\fR" Current and total number of frames. .IP "\fIindex\fR" Current and total index of image in the image list. .IP "\fIscale\fR" Current image scale in percent. .IP "\fIstatus\fR" Status message. .IP "\fInone\fR" Empty field (ignored). .\" ---------------------------------------------------------------------------- .SS "Text info: gallery" The section \fB[info.gallery]\fR describes how to display image meta data (file name, size, EXIF etc) in gallery mode, same format as for viewer mode. .\" **************************************************************************** .\" Key bindings config section .\" **************************************************************************** .SS "Key bindings" The key bindings are described in sections \fB[keys.viewer]\fR and \fB[keys.gallery]\fR. Each line associates a key with a list of actions and optional parameters. Actions are separated by semicolons. One or more key modifiers (\fICtrl\fR, \fIAlt\fR, \fIShift\fR) can be specified in the key name. The key name can be obtained with the \fIxkbcli\fR tool: `xkbcli interactive-wayland`. .PP Predefined names for mouse scroll: .PP .IP "\fIScrollUp\fR: Mouse wheel up;" .IP "\fIScrollDown\fR: Mouse wheel down;" .IP "\fIScrollLeft\fR: Mouse scroll left;" .IP "\fIScrollRight\fR: Mouse scroll right." .PP .\" ---------------------------------------------------------------------------- .SS "Viewer mode actions" .IP "\fBnone\fR: can be used for removing built-in action;" .IP "\fBhelp\fR: show/hide help;" .IP "\fBfirst_file\fR: jump to the first file;" .IP "\fBlast_file\fR: jump to the last file;" .IP "\fBprev_dir\fR: jump to previous directory;" .IP "\fBnext_dir\fR: jump to next directory;" .IP "\fBprev_file\fR: jump to previous file;" .IP "\fBnext_file\fR: jump to next file;" .IP "\fBprev_frame\fR: show previous frame;" .IP "\fBnext_frame\fR: show next frame;" .IP "\fBskip_file\fR: skip the current file (remove from the image list);" .IP "\fBanimation\fR: start/stop animation;" .IP "\fBslideshow\fR: start/stop slideshow;" .IP "\fBfullscreen\fR: switch full screen mode;" .IP "\fBmode \fI[MODE]\fR\fR: switch between viewer and gallery;" .IP "\fBstep_left\fR \fI[PERCENT]\fR: move viewport left, default is 10%;" .IP "\fBstep_right\fR \fI[PERCENT]\fR: move viewport right, default is 10%;" .IP "\fBstep_up\fR \fI[PERCENT]\fR: move viewport up, default is 10%;" .IP "\fBstep_down\fR \fI[PERCENT]\fR: move viewport down, default is 10%;" .IP "\fBzoom\fR \fI[SCALE]\fR: zoom in/out/fix, \fISCALE\fR is one of \fIoptimal\fR, \fIwidth\fR, \fIheight\fR, \fIfit\fR, \fIfill\fR, \fIreal\fR, or percent, e.g. \fI+10\fR;" .IP "\fBrotate_left\fR: rotate image anticlockwise;" .IP "\fBrotate_right\fR: rotate image clockwise;" .IP "\fBflip_vertical\fR: flip image vertically;" .IP "\fBflip_horizontal\fR: flip image horizontally;" .IP "\fBreload\fR: reset cache and reload current image;" .IP "\fBantialiasing\fR: switch antialiasing (bicubic interpolation);" .IP "\fBinfo\fR \fI[MODE]\fR: switch text info mode or set specified one (\fIoff\fR/\fIviewer\fR/\fIgallery\fR);" .IP "\fBexec\fR \fICOMMAND\fR: execute an external command, use % to substitute the path to the current image, %% to escape %;" .IP "\fBstatus\fR \fITEXT\fR: print message in the status field;" .IP "\fBexit\fR: exit the application." .\" ---------------------------------------------------------------------------- .SS "Gallery mode actions" .IP "\fBnone\fR: can be used for removing built-in action;" .IP "\fBhelp\fR: show/hide help;" .IP "\fBfirst_file\fR: jump to the first file;" .IP "\fBlast_file\fR: jump to the last file;" .IP "\fBprev_file\fR: select previous file;" .IP "\fBnext_file\fR: select next file;" .IP "\fBstep_left\fR: select previous image;" .IP "\fBstep_right\fR: select next image;" .IP "\fBstep_up\fR: select image above;" .IP "\fBstep_down\fR: select image below;" .IP "\fBpage_up\fR: scroll page up;" .IP "\fBpage_down\fR: scroll page down;" .IP "\fBskip_file\fR: skip the current file (remove from the image list);" .IP "\fBfullscreen\fR: switch full screen mode;" .IP "\fBmode\fR: switch between viewer and gallery;" .IP "\fBreload\fR: reset cache and reload current image;" .IP "\fBantialiasing\fR: switch antialiasing (bicubic interpolation);" .IP "\fBinfo\fR \fI[MODE]\fR: switch text info mode or set specified one (\fIoff\fR/\fIviewer\fR/\fIgallery\fR);" .IP "\fBexec\fR \fICOMMAND\fR: execute an external command, use % to substitute the path to the current image, %% to escape %;" .IP "\fBstatus\fR \fITEXT\fR: print message in the status field;" .IP "\fBexit\fR: exit the application." .\" **************************************************************************** .\" Example .\" **************************************************************************** .SH EXAMPLES .EX # comment [list] order = random [font] size = 16 [keys] Ctrl+Alt+e = exec echo "%" > mylist.txt .EE .PP See `/usr/share/swayimg/swayimgrc` for full example. .\" **************************************************************************** .\" Cross links .\" **************************************************************************** .SH SEE ALSO swayimg(1) .\" **************************************************************************** .\" Home page .\" **************************************************************************** .SH BUGS For suggestions, comments, bug reports etc. visit the .UR https://github.com/artemsen/swayimg project homepage .UE . swayimg-3.1/extra/zsh.completion000066400000000000000000000020211465610152200170540ustar00rootroot00000000000000#compdef swayimg # zsh completion for the "swayimg" image viewer. # Copyright (C) 2022 Artem Senichev _arguments \ '(-g --gallery)'{-g,--gallery}'[start in gallery mode]' \ '(-r --recursive)'{-r,--recursive}'[read directories recursively]' \ '(-o --order)'{-o,--order=}'[set sort order for image list]:order:(none alpha random)' \ '(-s --scale=SCALE)'{-s,--scale=}'[set initial image scale]:scale:(optimal width height fit fill real)' \ '(-l --slideshow)'{-l,--slideshow}'[activate slideshow mode on startup]' \ '(-f --fullscreen)'{-f,--fullscreen}'[show image in full screen mode]' \ '(-p --position)'{-p,--position=}'[set window position]:position:(parent)' \ '(-w --size)'{-w,--size=}'[set window size]:size:(parent image)' \ '(-a --class)'{-a,--class=}'[set window class/app_id]:class' \ '(-c --config)'{-c,--config=}'[set configuration parameter]:config' \ '(-v --version)'{-v,--version}'[print version info and exit]' \ '(-h --help)'{-h,--help}'[print help and exit]' \ '*:file:_files' swayimg-3.1/meson.build000066400000000000000000000147561465610152200152160ustar00rootroot00000000000000# Rules for building with Meson project( 'swayimg', 'c', 'cpp', default_options: [ 'c_std=c99', 'warning_level=3', 'buildtype=release', ], license: 'MIT', version: '0.0.0', meson_version: '>=0.60.0', ) add_project_arguments( [ '-D_POSIX_C_SOURCE=200809' ], language: 'c', ) cc = meson.get_compiler('c') # version info version = get_option('version') if version == '0.0.0' git = find_program('git', native: true, required: false) if git.found() git_ver = run_command([git, 'describe', '--tags', '--long', '--always', '--dirty'], check: false) if git_ver.returncode() == 0 version = git_ver.stdout().strip().substring(1) endif endif endif # mandatory dependencies wlcln = dependency('wayland-client') json = dependency('json-c') xkb = dependency('xkbcommon') fontconfig = dependency('fontconfig') freetype = dependency('freetype2') threads = dependency('threads') rt = cc.find_library('rt') # optional dependencies: file formats support exr = dependency('OpenEXR', version: '>=3.1', required: get_option('exr')) heif = dependency('libheif', required: get_option('heif')) avif = dependency('libavif', required: get_option('avif')) jpeg = dependency('libjpeg', required: get_option('jpeg')) jxl = dependency('libjxl', required: get_option('jxl')) png = dependency('libpng', required: get_option('png')) rsvg = dependency('librsvg-2.0', version: '>=2.46', required: get_option('svg')) tiff = dependency('libtiff-4', required: get_option('tiff')) webp = dependency('libwebp', required: get_option('webp')) webp_demux = dependency('libwebpdemux', required: get_option('webp')) # hack to build "pkgconfigless" gif in FreeBSD gif = cc.find_library('gif', dirs: wlcln.get_variable(pkgconfig: 'libdir'), required: get_option('gif')) # optional dependencies: other features exif = dependency('libexif', required: get_option('exif')) bash = dependency('bash-completion', required: get_option('bash')) # non-Linux (BSD specific) epoll = dependency('epoll-shim', required: false) inotify = dependency('libinotify', required: false) # configuration file conf = configuration_data() conf.set('HAVE_LIBEXR', exr.found()) conf.set('HAVE_LIBGIF', gif.found()) conf.set('HAVE_LIBHEIF', heif.found()) conf.set('HAVE_LIBAVIF', avif.found()) conf.set('HAVE_LIBJPEG', jpeg.found()) conf.set('HAVE_LIBJXL', jxl.found()) conf.set('HAVE_LIBPNG', png.found()) conf.set('HAVE_LIBRSVG', rsvg.found()) conf.set('HAVE_LIBTIFF', tiff.found()) conf.set('HAVE_LIBWEBP', webp.found() and webp_demux.found()) conf.set('HAVE_LIBEXIF', exif.found()) conf.set('HAVE_INOTIFY', cc.has_header('sys/inotify.h', dependencies: inotify)) conf.set_quoted('APP_NAME', meson.project_name()) conf.set_quoted('APP_VERSION', version) configure_file(output: 'buildcfg.h', configuration: conf) # Wayland protocols wlproto = dependency('wayland-protocols') wlproto_dir = wlproto.get_variable(pkgconfig: 'pkgdatadir') wlscan = dependency('wayland-scanner', required: false, native: true) if wlscan.found() wl_scanner = find_program(wlscan.get_variable(pkgconfig: 'wayland_scanner'), native: true) else wl_scanner = find_program('wayland-scanner', native: true) endif # XDG shell Wayland protocol xdg_shell_xml = wlproto_dir / 'stable/xdg-shell/xdg-shell.xml' xdg_shell_c = custom_target( 'xdg-shell-protocol.c', output: 'xdg-shell-protocol.c', input: xdg_shell_xml, command: [wl_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], ) xdg_shell_h = custom_target( 'xdg-shell-protocol.h', output: 'xdg-shell-protocol.h', input: xdg_shell_xml, command: [wl_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], ) # install sample config install_data('extra/swayimgrc', install_dir: get_option('datadir') / 'swayimg') # man installation if get_option('man') install_man('extra/swayimg.1') install_man('extra/swayimgrc.5') endif # desktop file + icon if get_option('desktop') install_data('extra/swayimg.desktop', install_dir: get_option('datadir') / 'applications') install_data('extra/icon_64.png', rename: 'swayimg.png', install_dir: get_option('datadir') / 'icons/hicolor/64x64/apps') install_data('extra/icon_128.png', rename: 'swayimg.png', install_dir: get_option('datadir') / 'icons/hicolor/128x128/apps') install_data('extra/icon_256.png', rename: 'swayimg.png', install_dir: get_option('datadir') / 'icons/hicolor/256x256/apps') endif # zsh completion zsh = get_option('zsh') if zsh.auto() shell = find_program('zsh', required: false) zsh = zsh.disable_auto_if(not shell.found()) endif if zsh.allowed() datadir = get_option('datadir') zsh_install_dir = datadir / 'zsh' / 'site-functions' install_data('extra/zsh.completion', install_dir: zsh_install_dir, rename: '_swayimg') endif # bash completion installation if bash.found() datadir = get_option('datadir') bash_install_dir = bash.get_variable(pkgconfig: 'completionsdir', pkgconfig_define: ['datadir', datadir]) install_data('extra/bash.completion', install_dir: bash_install_dir, rename: 'swayimg') endif # unit tests if get_option('tests').enabled() subdir('test') endif # source files sources = [ 'src/action.c', 'src/application.c', 'src/config.c', 'src/event.c', 'src/fetcher.c', 'src/font.c', 'src/gallery.c', 'src/image.c', 'src/imagelist.c', 'src/info.c', 'src/keybind.c', 'src/loader.c', 'src/main.c', 'src/pixmap.c', 'src/str.c', 'src/sway.c', 'src/ui.c', 'src/viewer.c', 'src/formats/bmp.c', 'src/formats/pnm.c', 'src/formats/tga.c', xdg_shell_h, xdg_shell_c, ] if exif.found() sources += 'src/exif.c' endif if exr.found() sources += 'src/formats/exr.c' endif if gif.found() sources += 'src/formats/gif.c' endif if heif.found() sources += 'src/formats/heif.c' endif if avif.found() sources += 'src/formats/avif.c' endif if jpeg.found() sources += 'src/formats/jpeg.c' endif if jxl.found() sources += 'src/formats/jxl.c' endif if png.found() sources += 'src/formats/png.c' endif if rsvg.found() sources += 'src/formats/svg.c' endif if tiff.found() sources += 'src/formats/tiff.c' endif if webp.found() and webp_demux.found() sources += 'src/formats/webp.c' endif executable( 'swayimg', sources, dependencies: [ # runtime rt, threads, wlcln, epoll, inotify, json, xkb, fontconfig, freetype, exif, # image support exr, gif, heif, avif, jpeg, jxl, png, rsvg, tiff, webp, webp_demux, ], install: true ) swayimg-3.1/meson_options.txt000066400000000000000000000036561465610152200165060ustar00rootroot00000000000000# format support option('exr', type: 'feature', value: 'auto', description: 'Enable EXR format support') option('gif', type: 'feature', value: 'auto', description: 'Enable GIF format support') option('heif', type: 'feature', value: 'auto', description: 'Enable HEIF and AVIF format support') option('avif', type: 'feature', value: 'auto', description: 'Enable AVIF and AVIFS format support') option('jpeg', type: 'feature', value: 'auto', description: 'Enable JPEG format support') option('jxl', type: 'feature', value: 'auto', description: 'Enable JPEG XL format support') option('png', type: 'feature', value: 'auto', description: 'Enable PNG format support') option('svg', type: 'feature', value: 'auto', description: 'Enable SVG format support') option('tiff', type: 'feature', value: 'auto', description: 'Enable TIFF format support') option('webp', type: 'feature', value: 'auto', description: 'Enable WebP format support') # EXIF support option('exif', type: 'feature', value: 'auto', description: 'Enable EXIF reader support') # extra files to install option('bash', type: 'feature', value: 'auto', description: 'Install bash completion') option('zsh', type: 'feature', value: 'auto', description: 'Install zsh completion') option('man', type: 'boolean', value: true, description: 'Install man page') option('desktop', type: 'boolean', value: true, description: 'Install desktop file with icon') # project version option('version', type : 'string', value : '0.0.0', description : 'Project version') # unit tests option('tests', type: 'feature', value: 'disabled', description: 'Build unit tests') swayimg-3.1/src/000077500000000000000000000000001465610152200136265ustar00rootroot00000000000000swayimg-3.1/src/action.c000066400000000000000000000103601465610152200152470ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Actions: set of predefined commands to execute. // Copyright (C) 2024 Artem Senichev #include "action.h" #include "str.h" #include #include #include // Max number of actions in sequence #define ACTION_SEQ_MAX 32 /** Action names. */ static const char* action_names[] = { [action_none] = "none", [action_help] = "help", [action_first_file] = "first_file", [action_last_file] = "last_file", [action_prev_dir] = "prev_dir", [action_next_dir] = "next_dir", [action_prev_file] = "prev_file", [action_next_file] = "next_file", [action_skip_file] = "skip_file", [action_prev_frame] = "prev_frame", [action_next_frame] = "next_frame", [action_animation] = "animation", [action_slideshow] = "slideshow", [action_fullscreen] = "fullscreen", [action_mode] = "mode", [action_step_left] = "step_left", [action_step_right] = "step_right", [action_step_up] = "step_up", [action_step_down] = "step_down", [action_page_up] = "page_up", [action_page_down] = "page_down", [action_zoom] = "zoom", [action_rotate_left] = "rotate_left", [action_rotate_right] = "rotate_right", [action_flip_vertical] = "flip_vertical", [action_flip_horizontal] = "flip_horizontal", [action_reload] = "reload", [action_antialiasing] = "antialiasing", [action_info] = "info", [action_exec] = "exec", [action_status] = "status", [action_exit] = "exit", }; /** * Parse config line and fill the action. * @param action target action instance * @param source action command with parameters as text string * @param len length of the source string * @return true if action loaded */ static bool parse(struct action* action, const char* source, size_t len) { ssize_t action_type; const char* action_name; size_t action_len; const char* params; size_t params_len; size_t pos = 0; // skip spaces while (pos < len && isspace(source[pos])) { ++pos; } action_name = &source[pos]; // get action type action_len = pos; while (pos < len && !isspace(source[pos])) { ++pos; } action_len = pos - action_len; action_type = str_index(action_names, action_name, action_len); if (action_type < 0) { return false; } action->type = action_type; // skip spaces while (pos < len && isspace(source[pos])) { ++pos; } // rest part: parameters params = &source[pos]; params_len = len - pos; if (params_len) { action->params = str_append(params, params_len, NULL); if (!action->params) { return false; } } else { action->params = NULL; } return true; } bool action_create(const char* text, struct action_seq* actions) { struct action load[ACTION_SEQ_MAX] = { 0 }; struct str_slice slices[ARRAY_SIZE(load)]; size_t seq_len; struct action* buf; size_t buf_sz; // split line, one slice per action seq_len = str_split(text, ';', slices, ARRAY_SIZE(slices)); if (seq_len == 0) { return false; } if (seq_len > ARRAY_SIZE(slices)) { seq_len = ARRAY_SIZE(slices); } // load actions for (size_t i = 0; i < seq_len; ++i) { const struct str_slice* s = &slices[i]; if (!parse(&load[i], s->value, s->len)) { while (i) { free(load[--i].params); } return 0; } } // put loaded action to output sequence buf_sz = seq_len * sizeof(struct action); buf = realloc(actions->sequence, buf_sz); if (!buf) { for (size_t i = 0; i < ARRAY_SIZE(load); ++i) { free(load[i].params); } return 0; } memcpy(buf, load, buf_sz); actions->num = seq_len; actions->sequence = buf; return seq_len; } void action_free(struct action_seq* actions) { if (actions) { for (size_t i = 0; i < actions->num; ++i) { free(actions->sequence[i].params); } free(actions->sequence); } } const char* action_typename(const struct action* action) { if (action->type < ARRAY_SIZE(action_names)) { return action_names[action->type]; } return NULL; } swayimg-3.1/src/action.h000066400000000000000000000033071465610152200152570ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Actions: set of predefined commands to execute. // Copyright (C) 2024 Artem Senichev #pragma once #include #include /** Supported actions. */ enum action_type { action_none, action_help, action_first_file, action_last_file, action_prev_dir, action_next_dir, action_prev_file, action_next_file, action_skip_file, action_prev_frame, action_next_frame, action_animation, action_slideshow, action_fullscreen, action_mode, action_step_left, action_step_right, action_step_up, action_step_down, action_page_up, action_page_down, action_zoom, action_rotate_left, action_rotate_right, action_flip_vertical, action_flip_horizontal, action_reload, action_antialiasing, action_info, action_exec, action_status, action_exit, }; /** Single action. */ struct action { enum action_type type; ///< Action type char* params; ///< Custom parameters for the action }; /** Action sequence. */ struct action_seq { struct action* sequence; ///< Array of actions size_t num; ///< Number of actions in array }; /** * Create action sequence from config string. * @param text source config text * @param actions destination sequence of actions * @return false if format error */ bool action_create(const char* text, struct action_seq* actions); /** * Free actions sequence. * @param actions sequence to free */ void action_free(struct action_seq* actions); /** * Get action's type name. * @param action to get type * @return type name */ const char* action_typename(const struct action* action); swayimg-3.1/src/application.c000066400000000000000000000461551465610152200163100ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image viewer application: main loop and event handler. // Copyright (C) 2024 Artem Senichev #include "application.h" #include "buildcfg.h" #include "font.h" #include "gallery.h" #include "imagelist.h" #include "info.h" #include "loader.h" #include "str.h" #include "sway.h" #include "ui.h" #include "viewer.h" #include #include #include #include #include #include #include #include #include #include #include // Special ids for windows size and position #define SIZE_FULLSCREEN SIZE_MAX #define SIZE_FROM_IMAGE (SIZE_MAX - 1) #define SIZE_FROM_PARENT (SIZE_MAX - 2) #define POS_FROM_PARENT SSIZE_MAX /** Main loop state */ enum loop_state { loop_run, loop_stop, loop_error, }; /** File descriptor and its handler. */ struct watchfd { int fd; void* data; fd_callback callback; }; /* Application event queue (list). */ struct event_entry { struct event event; struct event_entry* next; }; /** Application context */ struct application { enum loop_state state; ///< Main loop state struct watchfd* wfds; ///< FD polling descriptors size_t wfds_num; ///< Number of polling FD struct event_entry* events; ///< Event queue pthread_mutex_t events_lock; ///< Event queue lock int event_signal; ///< Queue change notification struct action_seq sigusr1; ///< Actions applied by USR1 signal struct action_seq sigusr2; ///< Actions applied by USR2 signal event_handler ehandler; ///< Event handler for the current mode struct rect window; ///< Preferable window position and size char* app_id; ///< Application id (app_id name) }; /** Global application context. */ static struct application ctx; /** * Setup window position via Sway IPC. */ static void sway_setup(void) { struct rect parent; bool fullscreen; bool absolute = false; int ipc; ipc = sway_connect(); if (ipc == INVALID_SWAY_IPC) { return; // sway not available } if (!sway_current(ipc, &parent, &fullscreen)) { sway_disconnect(ipc); return; } if (fullscreen) { ctx.window.width = SIZE_FULLSCREEN; ctx.window.height = SIZE_FULLSCREEN; sway_disconnect(ipc); return; } if (ctx.window.width == SIZE_FROM_PARENT) { ctx.window.width = parent.width; ctx.window.height = parent.height; } if (ctx.window.x == POS_FROM_PARENT) { absolute = false; ctx.window.x = parent.x; ctx.window.y = parent.y; } if (!ctx.app_id) { // create unique application id struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { char app_id[64]; const uint64_t uid = ((uint64_t)ts.tv_sec << 32) | ts.tv_nsec; snprintf(app_id, sizeof(app_id), APP_NAME "_%" PRIx64, uid); str_dup(app_id, &ctx.app_id); } else { str_dup(APP_NAME, &ctx.app_id); } } // set window position via sway rules sway_add_rules(ipc, ctx.app_id, ctx.window.x, ctx.window.y, absolute); sway_disconnect(ipc); } /** Notification callback: handle event queue. */ static void handle_event_queue(__attribute__((unused)) void* data) { notification_reset(ctx.event_signal); while (ctx.events && ctx.state == loop_run) { struct event_entry* entry; pthread_mutex_lock(&ctx.events_lock); entry = ctx.events; if (ctx.events) { ctx.events = ctx.events->next; } pthread_mutex_unlock(&ctx.events_lock); ctx.ehandler(&entry->event); free(entry); } } /** * Append event to queue. * @param event pointer to the event */ static void append_event(const struct event* event) { struct event_entry* entry; // create new entry entry = malloc(sizeof(*entry)); if (!entry) { return; } memcpy(&entry->event, event, sizeof(entry->event)); entry->next = NULL; // add to queue tail pthread_mutex_lock(&ctx.events_lock); if (ctx.events) { struct event_entry* last = ctx.events; while (last->next) { last = last->next; } last->next = entry; } else { ctx.events = entry; } pthread_mutex_unlock(&ctx.events_lock); notification_raise(ctx.event_signal); } /** * Apply action. * @param action pointer to the action being performed */ static void apply_action(const struct action* action) { struct event event; switch (action->type) { case action_info: info_switch(action->params); app_redraw(); break; case action_status: info_update(info_status, "%s", action->params); app_redraw(); break; case action_fullscreen: ui_toggle_fullscreen(); break; case action_help: info_switch_help(); app_redraw(); break; case action_exit: if (info_help_active()) { info_switch_help(); // remove help overlay app_redraw(); } else { app_exit(0); } break; default: // not a general action, add to event queue event.type = event_action; event.param.action = action; append_event(&event); break; } } /** * POSIX Signal handler. * @param signum signal number */ static void on_signal(int signum) { const struct action_seq* sigact; switch (signum) { case SIGUSR1: sigact = &ctx.sigusr1; break; case SIGUSR2: sigact = &ctx.sigusr2; break; default: return; } for (size_t i = 0; i < sigact->num; ++i) { apply_action(&sigact->sequence[i]); } } /** * Load first (initial) image. * @param index initial index of image in the image list * @param force mandatory image index flag * @return image instance or NULL on errors */ static struct image* load_first_file(size_t index, bool force) { struct image* img = NULL; enum loader_status status = ldr_ioerror; if (index == IMGLIST_INVALID) { index = image_list_first(); force = false; } while (index != IMGLIST_INVALID) { status = loader_from_index(index, &img); if (force || status == ldr_success) { break; } index = image_list_skip(index); } if (status != ldr_success) { // print error message if (!force) { fprintf(stderr, "No image files was loaded, exit\n"); } else { const char* reason = "Unknown error"; switch (status) { case ldr_success: break; case ldr_unsupported: reason = "Unsupported format"; break; case ldr_fmterror: reason = "Invalid format"; break; case ldr_ioerror: reason = "I/O error"; break; } fprintf(stderr, "%s: %s\n", image_list_get(index), reason); } } return img; } /** * Custom section loader, see `config_loader` for details. */ static enum config_status load_config(const char* key, const char* value) { enum config_status status = cfgst_invalid_value; if (strcmp(key, APP_CFG_MODE) == 0) { if (strcmp(value, APP_MODE_VIEWER) == 0) { ctx.ehandler = viewer_handle; status = cfgst_ok; } else if (strcmp(value, APP_MODE_GALLERY) == 0) { ctx.ehandler = gallery_handle; status = cfgst_ok; } } else if (strcmp(key, APP_CFG_POSITION) == 0) { if (strcmp(value, APP_FROM_PARENT) == 0) { ctx.window.x = POS_FROM_PARENT; ctx.window.y = POS_FROM_PARENT; status = cfgst_ok; } else { struct str_slice slices[2]; ssize_t x, y; if (str_split(value, ',', slices, 2) == 2 && str_to_num(slices[0].value, slices[0].len, &x, 0) && str_to_num(slices[1].value, slices[1].len, &y, 0)) { ctx.window.x = (ssize_t)x; ctx.window.y = (ssize_t)y; status = cfgst_ok; } } } else if (strcmp(key, APP_CFG_SIZE) == 0) { ssize_t width, height; if (strcmp(value, APP_FROM_PARENT) == 0) { ctx.window.width = SIZE_FROM_PARENT; ctx.window.height = SIZE_FROM_PARENT; status = cfgst_ok; } else if (strcmp(value, APP_FROM_IMAGE) == 0) { ctx.window.width = SIZE_FROM_IMAGE; ctx.window.height = SIZE_FROM_IMAGE; status = cfgst_ok; } else if (strcmp(value, APP_FULLSCREEN) == 0) { ctx.window.width = SIZE_FULLSCREEN; ctx.window.height = SIZE_FULLSCREEN; status = cfgst_ok; } else { struct str_slice slices[2]; if (str_split(value, ',', slices, 2) == 2 && str_to_num(slices[0].value, slices[0].len, &width, 0) && str_to_num(slices[1].value, slices[1].len, &height, 0) && width > 0 && width < 100000 && height > 0 && height < 100000) { ctx.window.width = width; ctx.window.height = height; status = cfgst_ok; } } } else if (strcmp(key, APP_CFG_SIGUSR1) == 0) { if (action_create(value, &ctx.sigusr1)) { status = cfgst_ok; } } else if (strcmp(key, APP_CFG_SIGUSR2) == 0) { if (action_create(value, &ctx.sigusr2)) { status = cfgst_ok; } } else if (strcmp(key, APP_CFG_APP_ID) == 0) { str_dup(value, &ctx.app_id); status = cfgst_ok; } else { status = cfgst_invalid_key; } return status; } void app_create(void) { ctx.window.x = POS_FROM_PARENT; ctx.window.y = POS_FROM_PARENT; ctx.window.width = SIZE_FROM_PARENT; ctx.window.height = SIZE_FROM_PARENT; ctx.ehandler = viewer_handle; action_create("reload", &ctx.sigusr1); action_create("next_file", &ctx.sigusr2); font_create(); image_list_create(); info_create(); keybind_create(); viewer_create(); gallery_create(); // register configuration loader config_add_loader(APP_CFG_SECTION, load_config); } void app_destroy(void) { loader_destroy(); gallery_destroy(); viewer_destroy(); ui_destroy(); image_list_destroy(); info_destroy(); font_destroy(); keybind_destroy(); for (size_t i = 0; i < ctx.wfds_num; ++i) { close(ctx.wfds[i].fd); } free(ctx.wfds); while (ctx.events) { struct event_entry* entry = ctx.events; ctx.events = ctx.events->next; if (entry->event.type == event_load) { image_free(entry->event.param.load.image); } free(entry); } if (ctx.event_signal != -1) { notification_free(ctx.event_signal); } pthread_mutex_destroy(&ctx.events_lock); action_free(&ctx.sigusr1); action_free(&ctx.sigusr2); } bool app_init(const char** sources, size_t num) { bool force_load = false; struct image* first_image; struct sigaction sigact; // compose image list if (num == 0) { // no input files specified, use all from the current directory static const char* current_dir = "."; sources = ¤t_dir; num = 1; } else if (num == 1) { force_load = true; if (strcmp(sources[0], "-") == 0) { // load from stdin static const char* stdin_name = LDRSRC_STDIN; sources = &stdin_name; } } if (image_list_init(sources, num) == 0) { if (force_load) { fprintf(stderr, "%s: Unable to open\n", sources[0]); } else { fprintf(stderr, "No image files found to view, exit\n"); } return false; } // load the first image first_image = load_first_file(image_list_find(sources[0]), force_load); if (!first_image) { return false; } // setup window position and size if (ctx.window.width != SIZE_FULLSCREEN) { sway_setup(); // try Sway integration } if (ctx.window.width == SIZE_FULLSCREEN) { ui_toggle_fullscreen(); } else if (ctx.window.width == SIZE_FROM_IMAGE || ctx.window.width == SIZE_FROM_PARENT) { // fixup window size form the first image const struct pixmap* pm = &first_image->frames[0].pm; ctx.window.width = pm->width; ctx.window.height = pm->height; } if (!ctx.app_id) { str_dup(APP_NAME, &ctx.app_id); } if (!ui_init(ctx.app_id, ctx.window.width, ctx.window.height)) { return false; } // event queue notification ctx.event_signal = notification_create(); if (ctx.event_signal != -1) { app_watch(ctx.event_signal, handle_event_queue, NULL); } else { perror("Unable to create eventfd"); return false; } pthread_mutex_init(&ctx.events_lock, NULL); // initialize other subsystems font_init(); info_init(); loader_init(); viewer_init(ctx.ehandler == viewer_handle ? first_image : NULL); gallery_init(ctx.ehandler == gallery_handle ? first_image : NULL); // set mode for info if (info_enabled()) { info_switch(ctx.ehandler == viewer_handle ? APP_MODE_VIEWER : APP_MODE_GALLERY); } // set signal handler sigact.sa_handler = on_signal; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigaction(SIGUSR1, &sigact, NULL); sigaction(SIGUSR2, &sigact, NULL); return true; } void app_watch(int fd, fd_callback cb, void* data) { const size_t sz = (ctx.wfds_num + 1) * sizeof(*ctx.wfds); struct watchfd* handlers = realloc(ctx.wfds, sz); if (handlers) { ctx.wfds = handlers; ctx.wfds[ctx.wfds_num].fd = fd; ctx.wfds[ctx.wfds_num].data = data; ctx.wfds[ctx.wfds_num].callback = cb; ++ctx.wfds_num; } } bool app_run(void) { struct pollfd* fds; // file descriptors to poll fds = calloc(1, ctx.wfds_num * sizeof(struct pollfd)); if (!fds) { perror("Failed to allocate memory"); return false; } for (size_t i = 0; i < ctx.wfds_num; ++i) { fds[i].fd = ctx.wfds[i].fd; fds[i].events = POLLIN; } // main event loop ctx.state = loop_run; while (ctx.state == loop_run) { ui_event_prepare(); // poll events if (poll(fds, ctx.wfds_num, -1) < 0) { if (errno != EINTR) { perror("Error polling events"); ctx.state = loop_error; break; } } // call handlers for each active event for (size_t i = 0; i < ctx.wfds_num; ++i) { if (fds[i].revents & POLLIN) { ctx.wfds[i].callback(ctx.wfds[i].data); } } ui_event_done(); } free(fds); return ctx.state != loop_error; } void app_exit(int rc) { ctx.state = rc ? loop_error : loop_stop; } void app_switch_mode(size_t index) { const char* info_mode; const struct event event = { .type = event_activate, .param.activate.index = index, }; if (ctx.ehandler == viewer_handle) { ctx.ehandler = gallery_handle; info_mode = APP_MODE_GALLERY; } else { ctx.ehandler = viewer_handle; info_mode = APP_MODE_VIEWER; } ctx.ehandler(&event); if (info_enabled()) { info_switch(info_mode); } if (info_help_active()) { info_switch_help(); } app_redraw(); } bool app_is_viewer(void) { return ctx.ehandler == viewer_handle; } void app_reload(void) { static const struct action action = { .type = action_reload }; const struct event event = { .type = event_action, .param.action = &action, }; append_event(&event); } void app_redraw(void) { const struct event event = { .type = event_redraw, }; struct event_entry* prev = NULL; struct event_entry* it = ctx.events; // remove the same event to append the new one to tail while (it) { struct event_entry* next = it->next; if (it->event.type == event_redraw) { if (prev) { prev->next = next; } else { ctx.events = next; } free(it); break; } prev = it; it = next; } append_event(&event); } void app_on_resize(void) { const struct event event = { .type = event_resize, }; append_event(&event); } void app_on_keyboard(xkb_keysym_t key, uint8_t mods) { const struct keybind* kb = keybind_find(key, mods); if (kb) { for (size_t i = 0; i < kb->actions.num; ++i) { apply_action(&kb->actions.sequence[i]); } } else { char* name = keybind_name(key, mods); if (name) { info_update(info_status, "Key %s is not bound", name); free(name); app_redraw(); } } } void app_on_drag(int dx, int dy) { const struct event event = { .type = event_drag, .param.drag.dx = dx, .param.drag.dy = dy }; struct event_entry* it = ctx.events; // merge with existing event while (it) { if (it->event.type == event_drag) { it->event.param.drag.dx += dx; it->event.param.drag.dy += dy; return; } it = it->next; } append_event(&event); } void app_on_load(struct image* image, size_t index) { const struct event event = { .type = event_load, .param.load.image = image, .param.load.index = index, }; append_event(&event); } void app_execute(const char* expr, const char* path) { char* cmd = NULL; int rc = -1; // construct command from template while (expr && *expr) { if (*expr == '%') { ++expr; if (*expr != '%') { str_append(path, 0, &cmd); // replace % with path continue; } } str_append(expr, 1, &cmd); ++expr; } if (cmd) { rc = system(cmd); // execute if (rc != -1) { rc = WEXITSTATUS(rc); } else if (errno) { rc = errno; } } // show execution status if (!cmd) { info_update(info_status, "Error: no command to execute"); } else { size_t max_len = 30; // trim long command text if (strlen(cmd) > max_len) { const char* ellipsis = "..."; const size_t ellipsis_len = strlen(ellipsis); memcpy(&cmd[max_len - ellipsis_len], ellipsis, ellipsis_len + 1); } if (rc) { info_update(info_status, "Error %d: %s", rc, cmd); } else { info_update(info_status, "OK: %s", cmd); } } free(cmd); app_redraw(); } swayimg-3.1/src/application.h000066400000000000000000000054651465610152200163140ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image viewer application: main loop and event handler. // Copyright (C) 2024 Artem Senichev #pragma once #include "image.h" #include "keybind.h" // Configuration parameters #define APP_CFG_SECTION "general" #define APP_CFG_MODE "mode" #define APP_CFG_POSITION "position" #define APP_CFG_SIZE "size" #define APP_CFG_SIGUSR1 "sigusr1" #define APP_CFG_SIGUSR2 "sigusr2" #define APP_CFG_APP_ID "app_id" #define APP_MODE_VIEWER "viewer" #define APP_MODE_GALLERY "gallery" #define APP_FROM_PARENT "parent" #define APP_FROM_IMAGE "image" #define APP_FULLSCREEN "fullscreen" /** * Handler of the fd poll events. * @param data user data */ typedef void (*fd_callback)(void* data); /** * Create global application context. */ void app_create(void); /** * Destroy global application context. */ void app_destroy(void); /** * Initialize global application context. * @param sources list of sources * @param num number of sources in the list * @return true if application initialized successfully */ bool app_init(const char** sources, size_t num); /** * Add file descriptor for polling in main loop. * @param fd file descriptor for polling * @param cb callback function * @param data user defined data to pass to callback */ void app_watch(int fd, fd_callback cb, void* data); /** * Run application. * @return true if application was closed by user, false on errors */ bool app_run(void); /** * Handler of external event: application exit request. * @param rc result (error) code to set */ void app_exit(int rc); /** * Switch mode (viewer/gallery). * @param index index of the current image */ void app_switch_mode(size_t index); /** * Get active mode. * @return true if current mode is viewer, false for gallery */ bool app_is_viewer(void); /** * Handler of external event: reload image / reset state. */ void app_reload(void); /** * Handler of external event: redraw window. */ void app_redraw(void); /** * Handler of external event: window resized. */ void app_on_resize(void); /** * Handler of external event: key/mouse press. * @param key code of key pressed * @param mods key modifiers (ctrl/alt/shift) */ void app_on_keyboard(xkb_keysym_t key, uint8_t mods); /** * Handler of external event: mouse/touch drag. * @param dx,dy delta between old and new position */ void app_on_drag(int dx, int dy); /** * Handler of image loading completion (background thread loader). * @param image loaded image instance, NULL if load error * @param index index of the image in the image list */ void app_on_load(struct image* image, size_t index); /** * Execute system command for the specified image. * @param expr command expression * @param path file path to substitute into expression */ void app_execute(const char* expr, const char* path); swayimg-3.1/src/config.c000066400000000000000000000174251465610152200152500ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Program configuration. // Copyright (C) 2022 Artem Senichev #include "config.h" #include "str.h" #include #include #include #include /** Config file location. */ struct location { const char* prefix; ///< Environment variable name const char* postfix; ///< Constant postfix }; /** Section loader. */ struct section { const char* name; config_loader loader; }; /** Config context. */ struct config_context { struct section* sections; size_t num_sections; }; static struct config_context ctx; static const struct location config_locations[] = { { "XDG_CONFIG_HOME", "/swayimg/config" }, { "HOME", "/.config/swayimg/config" }, { "XDG_CONFIG_DIRS", "/swayimg/config" }, { NULL, "/etc/xdg/swayimg/config" } }; /** * Expand path from environment variable. * @param prefix_env path prefix (var name) * @param postfix constant postfix * @return allocated buffer with path, caller should free it after use */ static char* expand_path(const char* prefix_env, const char* postfix) { char* path; const char* prefix; size_t prefix_len = 0; size_t postfix_len = strlen(postfix); if (prefix_env) { const char* delim; prefix = getenv(prefix_env); if (!prefix || !*prefix) { return NULL; } // use only the first directory if prefix is a list delim = strchr(prefix, ':'); prefix_len = delim ? (size_t)(delim - prefix) : strlen(prefix); } // compose path path = malloc(prefix_len + postfix_len + 1 /* last null*/); if (path) { if (prefix_len) { memcpy(path, prefix, prefix_len); } memcpy(path + prefix_len, postfix, postfix_len + 1 /*last null*/); } return path; } /** * Load configuration from a file. * @param path full path to the file * @return operation complete status, false on error */ static bool load_config(const char* path) { FILE* fd = NULL; char* buff = NULL; size_t buff_sz = 0; size_t line_num = 0; ssize_t nread; char* section = NULL; fd = fopen(path, "r"); if (!fd) { return false; } while ((nread = getline(&buff, &buff_sz, fd)) != -1) { char* delim; const char* value; char* line = buff; enum config_status status; ++line_num; // trim spaces while (nread-- && isspace(line[nread])) { line[nread] = 0; } while (*line && isspace(*line)) { ++line; } // skip empty lines and comments if (!*line || *line == '#') { continue; } // check for section beginning if (*line == '[') { ssize_t len; char* new_section; ++line; delim = strchr(line, ']'); if (!delim || line + 1 == delim) { fprintf(stderr, "Invalid section define in %s:%zu\n", path, line_num); continue; } *delim = 0; len = delim - line + 1; new_section = realloc(section, len); if (new_section) { section = new_section; memcpy(section, line, len); } continue; } delim = strchr(line, '='); if (!delim) { fprintf(stderr, "Invalid key=value format in %s:%zu\n", path, line_num); continue; } // trim spaces from start of value value = delim + 1; while (*value && isspace(*value)) { ++value; } // trim spaces from key *delim = 0; while (line != delim && isspace(*--delim)) { *delim = 0; } // load configuration parameter from key/value pair status = config_set(section, line, value); if (status != cfgst_ok) { fprintf(stderr, "Invalid configuration in %s:%zu\n", path, line_num); } } free(buff); free(section); fclose(fd); return true; } void config_load(void) { // find and load first available config file for (size_t i = 0; i < sizeof(config_locations) / sizeof(config_locations[0]); ++i) { const struct location* cl = &config_locations[i]; char* path = expand_path(cl->prefix, cl->postfix); if (path && load_config(path)) { free(path); break; } free(path); } } void config_destroy(void) { if (ctx.sections) { free(ctx.sections); ctx.sections = NULL; ctx.num_sections = 0; } } enum config_status config_set(const char* section, const char* key, const char* value) { enum config_status status = cfgst_invalid_section; if (!section || !*section) { fprintf(stderr, "Empty section name\n"); return cfgst_invalid_section; } for (size_t i = 0; i < ctx.num_sections; ++i) { const struct section* sl = &ctx.sections[i]; if (strcmp(sl->name, section) == 0) { status = sl->loader(key, value); if (status != cfgst_invalid_key) { break; } } } switch (status) { case cfgst_ok: break; case cfgst_invalid_section: fprintf(stderr, "Invalid section \"%s\"\n", section); break; case cfgst_invalid_key: fprintf(stderr, "Invalid key \"%s\"\n", key); break; case cfgst_invalid_value: fprintf(stderr, "Invalid value \"%s\"\n", value); break; } return status; } bool config_command(const char* cmd) { char section[32]; char key[32]; const char* value; const char* ptr; struct str_slice slices[2]; size_t size; // split section.key and value size = str_split(cmd, '=', slices, ARRAY_SIZE(slices)); if (size <= 1) { return false; } // split section and key ptr = slices[0].value + slices[0].len; while (*ptr != '.') { if (--ptr < cmd) { return false; } } // section name size = ptr - slices[0].value; if (size > sizeof(section) - 1) { size = sizeof(section) - 1; } memcpy(section, slices[0].value, size); section[size] = 0; // key name ++ptr; // skip dot size = slices[0].len - (ptr - slices[0].value); if (size > sizeof(key) - 1) { size = sizeof(key) - 1; } memcpy(key, ptr, size); key[size] = 0; value = slices[1].value; return config_set(section, key, value) == cfgst_ok; } void config_add_loader(const char* section, config_loader loader) { const size_t new_sz = (ctx.num_sections + 1) * sizeof(struct section); struct section* sections = realloc(ctx.sections, new_sz); if (sections) { ctx.sections = sections; ctx.sections[ctx.num_sections].name = section; ctx.sections[ctx.num_sections].loader = loader; ++ctx.num_sections; } } bool config_to_bool(const char* text, bool* flag) { bool rc = false; if (strcmp(text, "yes") == 0 || strcmp(text, "true") == 0) { *flag = true; rc = true; } else if (strcmp(text, "no") == 0 || strcmp(text, "false") == 0) { *flag = false; rc = true; } return rc; } bool config_to_color(const char* text, argb_t* color) { ssize_t num; if (*text == '#' || isspace(*text)) { ++text; } if (!str_to_num(text, 0, &num, 16) || num < 0 || (uint64_t)num > (uint64_t)0xffffffff) { return false; } if (strlen(text) > 6) { // value with alpha (RRGGBBAA) *color = (num >> 8) | ARGB_SET_A(num); } else { *color = num | ARGB_SET_A(0xff); } return true; } swayimg-3.1/src/config.h000066400000000000000000000031411465610152200152430ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Program configuration. // Copyright (C) 2022 Artem Senichev #pragma once #include "pixmap.h" /** Load status. */ enum config_status { cfgst_ok, cfgst_invalid_section, cfgst_invalid_key, cfgst_invalid_value, }; /** * Custom section loader. * @param key,value configuration parameters * @return load status */ typedef enum config_status (*config_loader)(const char* key, const char* value); /** * Load configuration from file. */ void config_load(void); /** * Destroy global configuration instance. */ void config_destroy(void); /** * Set option. * @param section section name * @param key,value configuration parameters * @return load status */ enum config_status config_set(const char* section, const char* key, const char* value); /** * Execute config command to set option. * @param cmd command in format: "section.key=value" * @return false if error */ bool config_command(const char* cmd); /** * Register custom section loader. * @param name statically allocated name of the section * @param loader section data handler */ void config_add_loader(const char* section, config_loader loader); /** * Convert text value to boolean. * @param text text to convert * @param flag target variable * @return false if text has invalid format */ bool config_to_bool(const char* text, bool* flag); /** * Convert text value to ARGB color. * @param text text to convert * @param color output variable * @return false if text has invalid format */ bool config_to_color(const char* text, argb_t* color); swayimg-3.1/src/event.c000066400000000000000000000012651465610152200151170ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Events processed by the viewer and gallery. // Copyright (C) 2024 Artem Senichev #include "event.h" #include #include #include int notification_create(void) { return eventfd(0, 0); } void notification_free(int fd) { close(fd); } void notification_raise(int fd) { const uint64_t value = 1; ssize_t len; do { len = write(fd, &value, sizeof(value)); } while (len == -1 && errno == EINTR); } void notification_reset(int fd) { uint64_t value; ssize_t len; do { len = read(fd, &value, sizeof(value)); } while (len == -1 && errno == EINTR); } swayimg-3.1/src/event.h000066400000000000000000000031131465610152200151160ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Events processed by the viewer and gallery. // Copyright (C) 2024 Artem Senichev #pragma once #include "image.h" #include "keybind.h" /** Event types. */ enum event_type { event_action, ///< Apply action event_redraw, ///< Redraw window request event_resize, ///< Window resize notification event_drag, ///< Mouse or touch drag operation event_load, ///< Image loaded (preload thread notification) event_activate, ///< The mode is activating (viewer/gallery switch) }; /** Event description. */ struct event { enum event_type type; union event_params { const struct action* action; struct drag { int dx; int dy; } drag; struct activate { size_t index; } activate; struct load { struct image* image; size_t index; } load; } param; }; /** * Event handler declaration. * @param event event to handle */ typedef void (*event_handler)(const struct event* event); /** * Create notification (eventfd descriptor). * @return file descriptor or -1 on errors */ int notification_create(void); /** * Free notification instance. * @param fd file descriptor for the notification */ void notification_free(int fd); /** * Send notification through file descriptor. * @param fd file descriptor for the notification */ void notification_raise(int fd); /** * Reset notification after raising. * @param fd file descriptor for the notification */ void notification_reset(int fd); swayimg-3.1/src/exif.c000066400000000000000000000122511465610152200147260ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // EXIT reader. // Copyright (C) 2022 Artem Senichev #include "exif.h" #include #include /** * Fix orientation from EXIF data. * @param img target image instance * @param exif instance of EXIF reader */ static void fix_orientation(struct image* img, ExifData* exif) { const ExifEntry* entry = exif_data_get_entry(exif, EXIF_TAG_ORIENTATION); if (entry) { const ExifByteOrder byte_order = exif_data_get_byte_order(exif); switch (exif_get_short(entry->data, byte_order)) { case 2: // flipped back-to-front image_flip_horizontal(img); break; case 3: // upside down image_rotate(img, 180); break; case 4: // flipped back-to-front and upside down image_flip_vertical(img); break; case 5: // flipped back-to-front and on its side image_flip_horizontal(img); image_rotate(img, 90); break; case 6: // on its side image_rotate(img, 90); break; case 7: // flipped back-to-front and on its far side image_flip_vertical(img); image_rotate(img, 270); break; case 8: // on its far side image_rotate(img, 270); break; default: break; } } } /** * Add meta info from EXIF tag. * @param img target image instance * @param exif instance of EXIF reader * @param tag EXIF tag * @param name EXIF tag name */ static void add_meta(struct image* img, ExifData* exif, ExifTag tag, const char* name) { char value[64]; ExifEntry* entry = exif_data_get_entry(exif, tag); if (entry) { exif_entry_get_value(entry, value, sizeof(value)); if (*value) { image_add_meta(img, name, "%s", value); } } } /** * Append string. * @param buffer destination buffer * @param buffer_max size of the buffer * @param value value to append */ static void append(char* buffer, size_t buffer_max, const char* value) { const size_t current_len = strlen(buffer); const size_t value_len = strlen(value); if (current_len + value_len + 1 <= buffer_max) { memcpy(buffer + current_len, value, value_len + 1); } } /** * Read coordinate to string buffer. * @param exif instance of EXIF reader * @param tag location tag * @param ref location reference * @param buffer destination buffer * @param buffer_sz size of the buffer * @return number of bytes written to the buffer */ static size_t read_coordinate(ExifData* exif, ExifTag tag, ExifTag ref, char* buffer, size_t buffer_sz) { const char* delimiters = ", "; ExifEntry* entry; char value[32]; char* token; size_t index = 0; entry = exif_content_get_entry(exif->ifd[EXIF_IFD_GPS], tag); if (!entry) { return 0; } exif_entry_get_value(entry, value, sizeof(value)); if (!*value) { return 0; } buffer[0] = 0; token = strtok(value, delimiters); while (token) { append(buffer, buffer_sz, token); switch (index++) { case 0: append(buffer, buffer_sz, "°"); break; case 1: append(buffer, buffer_sz, "'"); break; case 2: append(buffer, buffer_sz, "\""); break; } token = strtok(NULL, delimiters); } entry = exif_content_get_entry(exif->ifd[EXIF_IFD_GPS], ref); if (entry) { exif_entry_get_value(entry, value, sizeof(value)); if (*value) { append(buffer, buffer_sz, value); } } return strlen(buffer); } /** * Read GPS location and add it to meta. * @param img target image instance * @param exif instance of EXIF reader */ static void read_location(struct image* img, ExifData* exif) { char latitude[32], longitude[32]; // NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange) if (read_coordinate(exif, EXIF_TAG_GPS_LATITUDE, EXIF_TAG_GPS_LATITUDE_REF, latitude, sizeof(latitude)) && read_coordinate(exif, EXIF_TAG_GPS_LONGITUDE, EXIF_TAG_GPS_LONGITUDE_REF, longitude, sizeof(longitude))) { image_add_meta(img, "Location", "%s, %s", latitude, longitude); } // NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) } void process_exif(struct image* img, const uint8_t* data, size_t size) { ExifData* exif = exif_data_new_from_data(data, (unsigned int)size); if (exif) { fix_orientation(img, exif); add_meta(img, exif, EXIF_TAG_DATE_TIME, "DateTime"); add_meta(img, exif, EXIF_TAG_MAKE, "Camera"); add_meta(img, exif, EXIF_TAG_MODEL, "Model"); add_meta(img, exif, EXIF_TAG_SOFTWARE, "Software"); add_meta(img, exif, EXIF_TAG_EXPOSURE_TIME, "Exposure"); add_meta(img, exif, EXIF_TAG_FNUMBER, "F Number"); read_location(img, exif); exif_data_unref(exif); } } swayimg-3.1/src/exif.h000066400000000000000000000005501465610152200147320ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // EXIF reader. // Copyright (C) 2022 Artem Senichev #pragma once #include "image.h" /** * Read and handle EXIF data. * @param img target image context * @param data image file data * @param size size of image data in bytes */ void process_exif(struct image* img, const uint8_t* data, size_t size); swayimg-3.1/src/fetcher.c000066400000000000000000000177461465610152200154310ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Images origin for viewer mode. // Copyright (C) 2024 Artem Senichev #include "fetcher.h" #include "application.h" #include "buildcfg.h" #include "imagelist.h" #include "loader.h" #include #include #include #ifdef HAVE_INOTIFY #include #endif /** Image cache queue. */ struct image_cache { size_t capacity; ///< Max length of the queue struct image** queue; ///< Cache queue }; /** Image fetch context. */ struct fetch { struct image* current; ///< Current image struct image_cache history; ///< Least recently viewed images struct image_cache preload; ///< Preloaded images #ifdef HAVE_INOTIFY int notify; ///< inotify file handler int watch; ///< Current file watcher #endif }; /** Global image fetch context. */ static struct fetch ctx; /** * Initialize cache queue. * @param cache context * @param capacity max size of the queue */ static void cache_init(struct image_cache* cache, size_t capacity) { cache->capacity = capacity; cache->queue = NULL; if (capacity) { cache->queue = calloc(1, capacity * sizeof(*cache->queue)); if (!cache->queue) { cache->capacity = 0; } } } /** * Reset (clear) cache queue. * @param cache context */ static void cache_reset(struct image_cache* cache) { for (size_t i = 0; i < cache->capacity; ++i) { struct image* img = cache->queue[i]; if (img) { image_free(img); cache->queue[i] = NULL; } } } /** * Free cache queue. * @param cache context */ static void cache_free(struct image_cache* cache) { cache_reset(cache); free(cache->queue); } /** * Put image handle to cache queue. * @param cache context * @param image pointer to image instance */ static void cache_put(struct image_cache* cache, struct image* image) { if (cache->capacity == 0 || !image) { return; } // check for empty entry for (size_t i = 0; i < cache->capacity; ++i) { if (!cache->queue[i]) { cache->queue[i] = image; return; } } // cache is full, remove the oldest entry from head image_free(cache->queue[0]); for (size_t i = 0; i < cache->capacity - 1; ++i) { cache->queue[i] = cache->queue[i + 1]; } // add new entry to tail cache->queue[cache->capacity - 1] = image; } /** * Take out image handle from cache queue. * @param cache context * @param index index of the image in the image list * @return image instance or NULL if image not in cache */ static struct image* cache_take(struct image_cache* cache, size_t index) { struct image* img = NULL; for (size_t i = 0; i < cache->capacity; ++i) { struct image* entry = cache->queue[i]; if (!entry) { break; // last entry } if (entry->index == index) { img = entry; // move remaining entries for (; i < cache->capacity - 1; ++i) { cache->queue[i] = cache->queue[i + 1]; } // remove last entry from queue cache->queue[cache->capacity - 1] = NULL; break; } } return img; } /** * Remove oldest entries from cache. * @param cache context * @param size number of entries to preserve */ static void cache_trim(struct image_cache* cache, size_t size) { if (size == 0) { cache_reset(cache); } else { for (size_t i = cache->capacity - 1; i > size; ++i) { image_free(cache->queue[i]); cache->queue[i] = NULL; } } } #ifdef HAVE_INOTIFY /** inotify handler. */ static void on_inotify(__attribute__((unused)) void* data) { while (true) { bool updated = false; uint8_t buffer[1024]; ssize_t pos = 0; const ssize_t len = read(ctx.notify, buffer, sizeof(buffer)); if (len < 0) { if (errno == EINTR) { continue; } return; // something went wrong } while (pos + sizeof(struct inotify_event) <= (size_t)len) { const struct inotify_event* event = (struct inotify_event*)&buffer[pos]; if (event->mask & IN_IGNORED) { ctx.watch = -1; } else { updated = true; } pos += sizeof(struct inotify_event) + event->len; } if (updated) { app_reload(); } } } #endif // HAVE_INOTIFY /** Reset preloader queue. */ static void reset_preloader(void) { size_t* preload; size_t preload_num = 0; size_t found = 0; size_t next; if (ctx.preload.capacity == 0) { return; } loader_queue_reset(); preload = malloc(ctx.preload.capacity * sizeof(*preload)); if (!preload) { return; } // reorder preloads and create list of preloads next = ctx.current->index; for (size_t i = 0; i < ctx.preload.capacity; ++i) { struct image* img; next = image_list_next_file(next); if (next == IMGLIST_INVALID) { break; } img = cache_take(&ctx.preload, next); if (img) { cache_put(&ctx.preload, img); ++found; } else { preload[preload_num++] = next; } } // remove images that don't fit into cache size cache_trim(&ctx.preload, found); // add preloads to queue for (size_t i = 0; i < preload_num; ++i) { loader_queue_append(preload[i]); } free(preload); } /** * Set image as the current one. * @param image pointer to the image instance */ static void set_current(struct image* image) { // put current image to history cache if (ctx.current) { if (ctx.history.capacity) { cache_put(&ctx.history, ctx.current); } else { image_free(ctx.current); } } ctx.current = image; reset_preloader(); #ifdef HAVE_INOTIFY // register inotify watcher if (ctx.notify >= 0) { if (ctx.watch != -1) { inotify_rm_watch(ctx.notify, ctx.watch); } ctx.watch = inotify_add_watch(ctx.notify, ctx.current->source, IN_CLOSE_WRITE | IN_MOVE_SELF); } #endif } void fetcher_init(struct image* image, size_t history, size_t preload) { cache_init(&ctx.history, history); cache_init(&ctx.preload, preload); #ifdef HAVE_INOTIFY ctx.notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); if (ctx.notify >= 0) { app_watch(ctx.notify, on_inotify, NULL); ctx.watch = -1; } #endif // HAVE_INOTIFY if (image) { set_current(image); } } void fetcher_destroy(void) { cache_free(&ctx.history); cache_free(&ctx.preload); image_free(ctx.current); } bool fetcher_reset(size_t index, bool force) { loader_queue_reset(); cache_reset(&ctx.history); cache_reset(&ctx.preload); image_free(ctx.current); ctx.current = NULL; if (force && index != IMGLIST_INVALID) { fetcher_open(index); } else { if (index == IMGLIST_INVALID) { index = image_list_first(); } while (index != IMGLIST_INVALID && !fetcher_open(index)) { index = image_list_skip(index); } } return !!ctx.current; } bool fetcher_open(size_t index) { struct image* img; if (ctx.current && ctx.current->index == index) { return true; } // check history and preload img = cache_take(&ctx.history, index); if (!img) { img = cache_take(&ctx.preload, index); } if (!img) { loader_from_index(index, &img); } if (img) { set_current(img); } return !!img; } void fetcher_attach(struct image* image, size_t index) { if (image) { cache_put(&ctx.preload, image); } else { loader_queue_reset(); image_list_skip(index); reset_preloader(); } } struct image* fetcher_current(void) { return ctx.current; } swayimg-3.1/src/fetcher.h000066400000000000000000000023141465610152200154170ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Images origin for viewer mode. // Copyright (C) 2024 Artem Senichev #pragma once #include "image.h" /** * Initialize global fetch context. * @param image initial image * @param history max number of images in history * @param preload max number of preloaded images */ void fetcher_init(struct image* image, size_t history, size_t preload); /** * Destroy global fetch context. */ void fetcher_destroy(void); /** * Reset cache and reload current image. * @param index preferable index of image in the image list * @param force flag to fail if loading specified image failed * @return true if image was reloaded */ bool fetcher_reset(size_t index, bool force); /** * Open image and set it as the current one. * @param index index of the image to fetch * @return true if image opened */ bool fetcher_open(size_t index); /** * Attach image to preload cache. * @param image loaded image instance, NULL if load error * @param index index of the image in the image list */ void fetcher_attach(struct image* image, size_t index); /** * Get current image. * @return current image or NULL if no image loaded yet */ struct image* fetcher_current(void); swayimg-3.1/src/font.c000066400000000000000000000156601465610152200147500ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Font renderer. // Copyright (C) 2022 Artem Senichev #include "font.h" #include "config.h" #include "str.h" // font realted #include #include #include FT_FREETYPE_H #include FT_GLYPH_H #define POINT_FACTOR 64.0 // default points per pixel for 26.6 format #define SPACE_WH_REL 2.0 /** Font context. */ struct font { FT_Library lib; ///< Font lib instance FT_Face face; ///< Font face instance char* name; ///< Font face name size_t size; ///< Font size (pt) argb_t color; ///< Font color argb_t shadow; ///< Font shadow color }; /** Global font context instance. */ static struct font ctx; /** * Get path to the font file by its name. * @param name font name * @param font_file output buffer for file path * @param len size of buffer * @return false if font not found */ static bool search_font_file(const char* name, char* font_file, size_t len) { FcConfig* fc = NULL; font_file[0] = 0; font_file[len - 1] = 0; if (FcInit()) { fc = FcInitLoadConfigAndFonts(); if (fc) { FcPattern* fc_name = NULL; fc_name = FcNameParse((const FcChar8*)name); if (fc_name) { FcPattern* fc_font = NULL; FcResult result; FcConfigSubstitute(fc, fc_name, FcMatchPattern); FcDefaultSubstitute(fc_name); fc_font = FcFontMatch(fc, fc_name, &result); if (fc_font) { FcChar8* path = NULL; if (FcPatternGetString(fc_font, FC_FILE, 0, &path) == FcResultMatch) { strncpy(font_file, (const char*)path, len - 1); } FcPatternDestroy(fc_font); } FcPatternDestroy(fc_name); } FcConfigDestroy(fc); } FcFini(); } return *font_file; } /** * Calc size of the surface and allocate memory for the mask. * @param text string to print * @param surface text surface * @return true if operation completed successfully */ static bool allocate_surface(const wchar_t* text, struct text_surface* surface) { const size_t space_size = ctx.face->size->metrics.x_ppem / SPACE_WH_REL; const size_t height = ctx.face->size->metrics.height / POINT_FACTOR; size_t width = 0; uint8_t* data = NULL; size_t data_size; // get total width while (*text) { if (*text == L' ') { width += space_size; } else if (FT_Load_Char(ctx.face, *text, FT_LOAD_RENDER) == 0) { width += ctx.face->glyph->advance.x / POINT_FACTOR; } ++text; } // allocate surface buffer data_size = width * height; if (data_size) { data = realloc(surface->data, data_size); if (data) { surface->width = width; surface->height = height; surface->data = data; memset(surface->data, 0, data_size); } } return !!data; } /** * Custom section loader, see `config_loader` for details. */ static enum config_status load_config(const char* key, const char* value) { enum config_status status = cfgst_invalid_value; if (strcmp(key, "name") == 0) { str_dup(value, &ctx.name); status = cfgst_ok; } else if (strcmp(key, "size") == 0) { ssize_t num; if (str_to_num(value, 0, &num, 0) && num > 0 && num < 1024) { ctx.size = num; status = cfgst_ok; } } else if (strcmp(key, "color") == 0) { if (config_to_color(value, &ctx.color)) { status = cfgst_ok; } } else if (strcmp(key, "shadow") == 0) { if (strcmp(value, "none") == 0) { ctx.shadow = 0; status = cfgst_ok; } else if (config_to_color(value, &ctx.shadow)) { status = cfgst_ok; } } else { status = cfgst_invalid_key; } return status; } void font_create(void) { // set defaults str_dup("monospace", &ctx.name); ctx.size = 14; ctx.color = ARGB(0xff, 0xcc, 0xcc, 0xcc); ctx.shadow = ARGB(0x80, 0, 0, 0); // register configuration loader config_add_loader("font", load_config); } void font_init(void) { char file[256]; const FT_F26Dot6 size = ctx.size * POINT_FACTOR; if (!search_font_file(ctx.name, file, sizeof(file)) || FT_Init_FreeType(&ctx.lib) != 0 || FT_New_Face(ctx.lib, file, 0, &ctx.face) != 0) { fprintf(stderr, "Unable to load font %s\n", ctx.name); return; } FT_Set_Char_Size(ctx.face, size, 0, 96, 0); } void font_destroy(void) { if (ctx.face) { FT_Done_Face(ctx.face); } if (ctx.lib) { FT_Done_FreeType(ctx.lib); } free(ctx.name); } bool font_render(const char* text, struct text_surface* surface) { size_t space_size; ssize_t base_offset; wchar_t* wide; wchar_t* it; size_t x = 0; if (!ctx.face) { return false; } space_size = ctx.face->size->metrics.x_ppem / SPACE_WH_REL; base_offset = (ctx.face->ascender * (ctx.face->size->metrics.y_scale / 65536.0)) / POINT_FACTOR; wide = str_to_wide(text, NULL); if (!wide) { return false; } if (!allocate_surface(wide, surface)) { free(wide); return false; } // draw glyphs it = wide; while (*it) { if (*it == L' ') { x += space_size; } else if (FT_Load_Char(ctx.face, *it, FT_LOAD_RENDER) == 0) { const FT_GlyphSlot glyph = ctx.face->glyph; const FT_Bitmap* bmp = &glyph->bitmap; const ssize_t off_y = base_offset - glyph->bitmap_top; size_t size; // calc line width, floating point math doesn't match bmp width if (x + bmp->width < surface->width) { size = bmp->width; } else { size = surface->width - x; } // put glyph's bitmap on the surface for (size_t y = 0; y < bmp->rows; ++y) { const size_t offset = (y + off_y) * surface->width + x; uint8_t* dst = &surface->data[offset + glyph->bitmap_left]; memcpy(dst, &bmp->buffer[y * bmp->pitch], size); } x += glyph->advance.x / POINT_FACTOR; } ++it; } free(wide); return true; } void font_print(struct pixmap* wnd, ssize_t x, ssize_t y, const struct text_surface* text) { if (ARGB_GET_A(ctx.shadow)) { ssize_t shadow_offset = text->height / 16; if (shadow_offset < 1) { shadow_offset = 1; } pixmap_apply_mask(wnd, x + shadow_offset, y + shadow_offset, text->data, text->width, text->height, ctx.shadow); } pixmap_apply_mask(wnd, x, y, text->data, text->width, text->height, ctx.color); } swayimg-3.1/src/font.h000066400000000000000000000017171465610152200147530ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Font renderer. // Copyright (C) 2022 Artem Senichev #pragma once #include "pixmap.h" /** Text surface: array of alpha pixels. */ struct text_surface { size_t width; ///< Width (px) size_t height; ///< Height (px) uint8_t* data; ///< Pixel data }; /** * Create font renderer. */ void font_create(void); /** * Initialize (load font). */ void font_init(void); /** * Free font resources. */ void font_destroy(void); /** * Render single text line. * @param text string to print * @param surface text surface to reallocate * @return true if operation completed successfully */ bool font_render(const char* text, struct text_surface* surface); /** * Print surface line on the window. * @param wnd destination window * @param x,y text position * @param text text surface to draw */ void font_print(struct pixmap* wnd, ssize_t x, ssize_t y, const struct text_surface* text); swayimg-3.1/src/formats/000077500000000000000000000000001465610152200153015ustar00rootroot00000000000000swayimg-3.1/src/formats/avif.c000066400000000000000000000101161465610152200163710ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // AV1 (AVIF/AVIFS) format decoder. // Copyright (C) 2023 Artem Senichev #include "../loader.h" #include #include #include #include // AVI signature static const uint32_t signature = 'f' | 't' << 8 | 'y' << 16 | 'p' << 24; #define SIGNATURE_OFFSET 4 static int decode_frame(struct image* ctx, avifDecoder* decoder) { avifRGBImage rgb; avifResult rc; struct pixmap* pm; rc = avifDecoderNextImage(decoder); if (rc != AVIF_RESULT_OK) { goto decode_fail; } memset(&rgb, 0, sizeof(rgb)); avifRGBImageSetDefaults(&rgb, decoder->image); rgb.depth = 8; rgb.format = AVIF_RGB_FORMAT_BGRA; #if AVIF_VERSION_MAJOR > 0 rc = #endif avifRGBImageAllocatePixels(&rgb); #if AVIF_VERSION_MAJOR > 0 if (rc != AVIF_RESULT_OK) { goto decode_fail; } #endif rc = avifImageYUVToRGB(decoder->image, &rgb); if (rc != AVIF_RESULT_OK) { goto fail_pixels; } pm = image_allocate_frame(ctx, decoder->image->width, decoder->image->height); if (!pm) { goto fail_pixels; } memcpy(pm->data, rgb.pixels, rgb.width * rgb.height * sizeof(argb_t)); avifRGBImageFreePixels(&rgb); return 0; fail_pixels: avifRGBImageFreePixels(&rgb); decode_fail: return -1; } static int decode_frames(struct image* ctx, avifDecoder* decoder) { avifImageTiming timing; avifRGBImage rgb = { 0 }; avifResult rc = AVIF_RESULT_UNKNOWN_ERROR; if (!image_create_frames(ctx, decoder->imageCount)) { return AVIF_RESULT_UNKNOWN_ERROR; } for (size_t i = 0; i < ctx->num_frames; ++i) { rc = avifDecoderNthImage(decoder, i); if (rc != AVIF_RESULT_OK) { break; } avifRGBImageSetDefaults(&rgb, decoder->image); rgb.depth = 8; rgb.format = AVIF_RGB_FORMAT_BGRA; #if AVIF_VERSION_MAJOR == 0 avifRGBImageAllocatePixels(&rgb); #else rc = avifRGBImageAllocatePixels(&rgb); if (rc != AVIF_RESULT_OK) { break; } #endif rc = avifImageYUVToRGB(decoder->image, &rgb); if (rc != AVIF_RESULT_OK) { break; } if (!pixmap_create(&ctx->frames[i].pm, rgb.width, rgb.height)) { break; } rc = avifDecoderNthImageTiming(decoder, i, &timing); if (rc != AVIF_RESULT_OK) { break; } ctx->frames[i].duration = (size_t)(1000.0f / (float)timing.timescale * (float)timing.durationInTimescales); memcpy(ctx->frames[i].pm.data, rgb.pixels, rgb.width * rgb.height * sizeof(argb_t)); avifRGBImageFreePixels(&rgb); rgb.pixels = NULL; } if (rgb.pixels) { avifRGBImageFreePixels(&rgb); } return rc; } // AV1 loader implementation enum loader_status decode_avif(struct image* ctx, const uint8_t* data, size_t size) { avifResult rc; avifDecoder* decoder = NULL; int ret; // check signature if (size < SIGNATURE_OFFSET + sizeof(signature) || *(const uint32_t*)(data + SIGNATURE_OFFSET) != signature) { return ldr_unsupported; } // open file in decoder decoder = avifDecoderCreate(); if (!decoder) { return ldr_fmterror; } rc = avifDecoderSetIOMemory(decoder, data, size); if (rc != AVIF_RESULT_OK) { goto fail; } rc = avifDecoderParse(decoder); if (rc != AVIF_RESULT_OK) { goto fail; } if (decoder->imageCount > 1) { ret = decode_frames(ctx, decoder); } else { ret = decode_frame(ctx, decoder); } if (ret != 0) { goto fail; } ctx->alpha = decoder->alphaPresent; image_set_format(ctx, "AV1 %dbpc %s", decoder->image->depth, avifPixelFormatToString(decoder->image->yuvFormat)); avifDecoderDestroy(decoder); return ldr_success; fail: avifDecoderDestroy(decoder); image_free_frames(ctx); return ldr_fmterror; } swayimg-3.1/src/formats/bmp.c000066400000000000000000000324041465610152200162260ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // BMP format decoder. // Copyright (C) 2020 Artem Senichev #include "../loader.h" #include #include #include // BMP type id #define BMP_TYPE ('B' | ('M' << 8)) // Compression types #define BI_RGB 0 #define BI_RLE8 1 #define BI_RLE4 2 #define BI_BITFIELDS 3 // RLE escape codes #define RLE_ESC_EOL 0 #define RLE_ESC_EOF 1 #define RLE_ESC_DELTA 2 // Default mask for 16-bit images #define MASK555_RED 0x7c00 #define MASK555_GREEN 0x03e0 #define MASK555_BLUE 0x001f #define MASK555_ALPHA 0x0000 // Sizes of DIB Headers #define BITMAPINFOHEADER_SIZE 0x28 #define BITMAPINFOV2HEADER_SIZE 0x34 #define BITMAPINFOV3HEADER_SIZE 0x38 #define BITMAPINFOV4HEADER_SIZE 0x6C #define BITMAPINFOV5HEADER_SIZE 0x7C #define BITS_PER_BYTE 8 // Bitmap file header: BITMAPFILEHEADER struct __attribute__((__packed__)) bmp_file { uint16_t type; uint32_t file_size; uint32_t reserved; uint32_t offset; }; // Bitmap info: BITMAPINFOHEADER struct __attribute__((__packed__)) bmp_info { uint32_t dib_size; int32_t width; int32_t height; uint16_t planes; uint16_t bpp; uint32_t compression; uint32_t img_size; uint32_t hres; uint32_t vres; uint32_t clr_palette; uint32_t clr_important; }; // Masks used for for 16bit images struct __attribute__((__packed__)) bmp_mask { uint32_t red; uint32_t green; uint32_t blue; uint32_t alpha; }; // Color palette struct bmp_palette { const uint32_t* table; size_t size; }; /** * Get number of the consecutive zero bits (trailing) on the right. * @param val source value * @return number of zero bits */ static inline size_t right_zeros(uint32_t val) { size_t count = sizeof(uint32_t) * BITS_PER_BYTE; val &= -(int32_t)val; if (val) --count; if (val & 0x0000ffff) count -= 16; if (val & 0x00ff00ff) count -= 8; if (val & 0x0f0f0f0f) count -= 4; if (val & 0x33333333) count -= 2; if (val & 0x55555555) count -= 1; return count; } /** * Get number of bits set. * @param val source value * @return number of bits set */ static inline size_t bits_set(uint32_t val) { val = val - ((val >> 1) & 0x55555555); val = (val & 0x33333333) + ((val >> 2) & 0x33333333); return (((val + (val >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; } /** * Get shift size for color channel. * @param val color channel mask * @return shift size: positive=right, negative=left */ static inline ssize_t mask_shift(uint32_t mask) { const ssize_t start = right_zeros(mask) + bits_set(mask); return start - BITS_PER_BYTE; } /** * Decode bitmap with masked colors. * @param img decoded image context * @param bmp bitmap info * @param mask channels mask * @param buffer input bitmap buffer * @param buffer_sz size of buffer * @return false if input buffer has errors */ static bool decode_masked(struct image* ctx, const struct bmp_info* bmp, const struct bmp_mask* mask, const uint8_t* buffer, size_t buffer_sz) { struct pixmap* pm = &ctx->frames[0].pm; const bool default_mask = !mask || (mask->red == 0 && mask->green == 0 && mask->blue == 0 && mask->alpha == 0); const uint32_t mask_r = default_mask ? MASK555_RED : mask->red; const uint32_t mask_g = default_mask ? MASK555_GREEN : mask->green; const uint32_t mask_b = default_mask ? MASK555_BLUE : mask->blue; const uint32_t mask_a = default_mask ? MASK555_ALPHA : mask->alpha; const ssize_t shift_r = mask_shift(mask_r); const ssize_t shift_g = mask_shift(mask_g); const ssize_t shift_b = mask_shift(mask_b); const ssize_t shift_a = mask_shift(mask_a); const size_t stride = 4 * ((bmp->width * bmp->bpp + 31) / 32); // check size of source buffer if (buffer_sz < pm->height * stride) { return false; } for (size_t y = 0; y < pm->height; ++y) { argb_t* dst = &pm->data[y * pm->width]; const uint8_t* src_y = buffer + y * stride; for (size_t x = 0; x < pm->width; ++x) { const uint8_t* src = src_y + x * (bmp->bpp / BITS_PER_BYTE); uint32_t m, r, g, b, a; if (bmp->bpp == 32) { m = *(uint32_t*)src; } else if (bmp->bpp == 16) { m = *(uint16_t*)src; } else { return false; } r = m & mask_r; g = m & mask_g; b = m & mask_b; r = 0xff & (shift_r > 0 ? r >> shift_r : r << -shift_r); g = 0xff & (shift_g > 0 ? g >> shift_g : g << -shift_g); b = 0xff & (shift_b > 0 ? b >> shift_b : b << -shift_b); if (mask_a) { a = m & mask_a; a = 0xff & (shift_a > 0 ? a >> shift_a : a << -shift_a); } else { a = 0xff; } dst[x] = ARGB_SET_A(a) | ARGB_SET_R(r) | ARGB_SET_G(g) | ARGB_SET_B(b); } } return true; } /** * Decode RLE compressed bitmap. * @param img decoded image context * @param bmp bitmap info * @param palette color palette * @param buffer input bitmap buffer * @param buffer_sz size of buffer * @return false if input buffer has errors */ static bool decode_rle(struct image* ctx, const struct bmp_info* bmp, const struct bmp_palette* palette, const uint8_t* buffer, size_t buffer_sz) { struct pixmap* pm = &ctx->frames[0].pm; size_t x = 0, y = 0; size_t buffer_pos = 0; while (buffer_pos + 2 <= buffer_sz) { const uint8_t rle1 = buffer[buffer_pos++]; const uint8_t rle2 = buffer[buffer_pos++]; if (rle1 == 0) { // escape code if (rle2 == RLE_ESC_EOL) { x = 0; ++y; } else if (rle2 == RLE_ESC_EOF) { // remove alpha channel argb_t* ptr = pm->data; while (ptr < pm->data + pm->width * pm->height) { *ptr |= ARGB_SET_A(0xff); ++ptr; } return true; } else if (rle2 == RLE_ESC_DELTA) { if (buffer_pos + 2 >= buffer_sz) { return false; } x += buffer[buffer_pos++]; y += buffer[buffer_pos++]; } else { // absolute mode if (buffer_pos + (bmp->compression == BI_RLE4 ? rle2 / 2 : rle2) > buffer_sz) { return false; } if (x + rle2 > pm->width || y >= pm->height) { return false; } uint8_t val = 0; for (size_t i = 0; i < rle2; ++i) { uint8_t index; if (bmp->compression == BI_RLE8) { index = buffer[buffer_pos++]; } else { if (i & 1) { index = val & 0x0f; } else { val = buffer[buffer_pos++]; index = val >> 4; } } if (index >= palette->size) { return false; } pm->data[y * bmp->width + x] = palette->table[index]; ++x; } if ((bmp->compression == BI_RLE8 && rle2 & 1) || (bmp->compression == BI_RLE4 && ((rle2 & 3) == 1 || (rle2 & 3) == 2))) { ++buffer_pos; // zero-padded 16-bit } } } else { // encoded mode if (bmp->compression == BI_RLE8) { // 8 bpp if (rle2 >= palette->size) { return false; } if (x + rle1 > pm->width || y >= pm->height) { return false; } for (size_t i = 0; i < rle1; ++i) { pm->data[y * pm->width + x] = palette->table[rle2]; ++x; } } else { // 4 bpp const uint8_t index[] = { rle2 >> 4, rle2 & 0x0f }; if (index[0] >= palette->size || index[1] >= palette->size) { return false; } if (x + rle1 > pm->width) { return false; } for (size_t i = 0; i < rle1; ++i) { pm->data[y * pm->width + x] = palette->table[index[i & 1]]; ++x; } } } } return false; } /** * Decode uncompressed bitmap. * @param img decoded image context * @param palette color palette * @param buffer input bitmap buffer * @param buffer_sz size of buffer * @param decoded output data buffer * @return false if input buffer has errors */ static bool decode_rgb(struct image* ctx, const struct bmp_info* bmp, const struct bmp_palette* palette, const uint8_t* buffer, size_t buffer_sz) { struct pixmap* pm = &ctx->frames[0].pm; const size_t stride = 4 * ((bmp->width * bmp->bpp + 31) / 32); // check size of source buffer if (buffer_sz < pm->height * stride) { return false; } for (size_t y = 0; y < pm->height; ++y) { argb_t* dst = &pm->data[y * pm->width]; const uint8_t* src_y = buffer + y * stride; for (size_t x = 0; x < pm->width; ++x) { const uint8_t* src = src_y + x * (bmp->bpp / BITS_PER_BYTE); if (bmp->bpp == 32) { dst[x] = ARGB_SET_A(0xff) | *(uint32_t*)src; } else if (bmp->bpp == 24) { dst[x] = ARGB_SET_A(0xff) | *(uint32_t*)src; } else if (bmp->bpp == 8 || bmp->bpp == 4 || bmp->bpp == 1) { // indexed colors const size_t bits_offset = x * bmp->bpp; const size_t byte_offset = bits_offset / BITS_PER_BYTE; const size_t start_bit = bits_offset - byte_offset * BITS_PER_BYTE; const uint8_t index = (*(src_y + byte_offset) >> (BITS_PER_BYTE - bmp->bpp - start_bit)) & (0xff >> (BITS_PER_BYTE - bmp->bpp)); if (index >= palette->size) { return false; } dst[x] = ARGB_SET_A(0xff) | palette->table[index]; } else { return false; } } } return true; } // BMP loader implementation enum loader_status decode_bmp(struct image* ctx, const uint8_t* data, size_t size) { const struct bmp_file* hdr; const struct bmp_info* bmp; const void* color_data; size_t color_data_sz; struct bmp_palette palette; const uint32_t* mask_location; struct bmp_mask mask; bool rc; hdr = (const struct bmp_file*)data; bmp = (const struct bmp_info*)(data + sizeof(*hdr)); // check format if (size < sizeof(*hdr) || hdr->type != BMP_TYPE) { return ldr_unsupported; } if (hdr->offset >= size || hdr->offset < sizeof(struct bmp_file) + sizeof(struct bmp_info)) { return ldr_fmterror; } if (bmp->dib_size > hdr->offset) { return ldr_fmterror; } if (!image_allocate_frame(ctx, abs(bmp->width), abs(bmp->height))) { return ldr_fmterror; } color_data = (const uint8_t*)bmp + bmp->dib_size; color_data_sz = hdr->offset - sizeof(struct bmp_file) - bmp->dib_size; palette.table = color_data; palette.size = color_data_sz / sizeof(uint32_t); // create mask if (bmp->dib_size > BITMAPINFOHEADER_SIZE) { mask_location = (const uint32_t*)(bmp + 1); } else { mask_location = (color_data_sz >= 3 * sizeof(uint32_t) ? color_data : NULL); } if (!mask_location) { mask.red = mask.green = mask.blue = mask.alpha = 0; } else { mask.red = mask_location[0]; mask.green = mask_location[1]; mask.blue = mask_location[2]; mask.alpha = bmp->dib_size > BITMAPINFOV2HEADER_SIZE ? mask_location[3] : 0; } // decode bitmap if (bmp->compression == BI_BITFIELDS || bmp->bpp == 16) { rc = decode_masked(ctx, bmp, &mask, data + hdr->offset, size - hdr->offset); image_set_format(ctx, "BMP %dbit masked", bmp->bpp); } else if (bmp->compression == BI_RLE8 || bmp->compression == BI_RLE4) { rc = decode_rle(ctx, bmp, &palette, data + hdr->offset, size - hdr->offset); image_set_format(ctx, "BMP %dbit RLE", bmp->bpp); } else if (bmp->compression == BI_RGB) { rc = decode_rgb(ctx, bmp, &palette, data + hdr->offset, size - hdr->offset); image_set_format(ctx, "BMP %dbit uncompressed", bmp->bpp); } else { rc = false; } if (rc) { if (bmp->height > 0) { image_flip_vertical(ctx); } ctx->alpha = bmp->bpp == 32; } else { image_free_frames(ctx); } return (rc ? ldr_success : ldr_fmterror); } swayimg-3.1/src/formats/exr.c000066400000000000000000000261011465610152200162430ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // EXR format decoder. // Copyright (C) 2023 Artem Senichev #include "../loader.h" #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop // EXR signature static const uint8_t signature[] = { 0x76, 0x2f, 0x31, 0x01 }; // EXR data buffer struct data_buffer { const uint8_t* data; const size_t size; }; // EXR data reader static int64_t exr_reader(__attribute__((unused)) exr_const_context_t exr, void* userdata, void* buffer, uint64_t sz, uint64_t offset, __attribute__((unused)) exr_stream_error_func_ptr_t error_cb) { const struct data_buffer* buf = userdata; if (offset + sz > buf->size) { return -1; } memcpy(buffer, buf->data + offset, sz); return sz; } /** * Decode single chunk. * @param ectx EXR context * @param chunk source chunk info * @param decoder EXR decoder instance * @param buffer data pointer for unpacked data of the current chunk * @return result code */ static exr_result_t decode_chunk(const exr_context_t ectx, const exr_chunk_info_t* chunk, exr_decode_pipeline_t* decoder, uint8_t* buffer) { exr_result_t rc; int8_t bpp = 0; // initialize decoder if (decoder->channels) { rc = exr_decoding_update(ectx, 0, chunk, decoder); } else { rc = exr_decoding_initialize(ectx, 0, chunk, decoder); if (rc == EXR_ERR_SUCCESS) { rc = exr_decoding_choose_default_routines(ectx, 0, decoder); } } if (rc != EXR_ERR_SUCCESS) { return rc; } // configure output buffer for (int16_t i = 0; i < decoder->channel_count; ++i) { bpp += decoder->channels[i].bytes_per_element; } for (int16_t i = 0; i < decoder->channel_count; ++i) { exr_coding_channel_info_t* cci = &decoder->channels[i]; cci->decode_to_ptr = buffer; cci->user_pixel_stride = bpp; cci->user_line_stride = cci->width * bpp; cci->user_bytes_per_element = decoder->channels[i].bytes_per_element; buffer += decoder->channels[i].bytes_per_element; } // decode current chunk return exr_decoding_run(ectx, 0, decoder); } /** * Decode pixel. * @param decoder EXR decoder instance * @param unpacked pointer to unpacked data * @param size max number of bytes in unpacked buffer * @return ARGB value of the pixel */ static argb_t decode_pixel(const exr_decode_pipeline_t* decoder, const uint8_t* unpacked, size_t size) { uint8_t a = 0xff, r = 0, g = 0, b = 0; for (int16_t i = 0; i < decoder->channel_count; ++i) { const exr_coding_channel_info_t* channel = &decoder->channels[i]; float intensity = 0; size_t color; // it's all a dirty hack =) switch (channel->data_type) { case EXR_PIXEL_UINT: break; // not supported case EXR_PIXEL_HALF: if (size >= sizeof(uint16_t)) { // convert half to float // todo // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Assign) const uint16_t half = *(const uint16_t*)unpacked; union { uint32_t i; float f; } hf; hf.i = (half & 0x8000) << 16; hf.i |= ((half & 0x7c00) + 0x1c000) << 13; hf.i |= (half & 0x03ff) << 13; intensity = hf.f; } break; case EXR_PIXEL_FLOAT: if (size >= sizeof(float)) { // todo // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Assign) intensity = *(const float*)unpacked; } break; default: break; // not supported } color = intensity * 0xff; if (color > 0xff) { color = 0xff; } switch (*channel->channel_name) { case 'A': a = color; break; case 'R': r = color; break; case 'G': g = color; break; case 'B': b = color; break; default: break; // not supported } unpacked += channel->bytes_per_element; if (size <= (size_t)channel->bytes_per_element) { break; } size -= channel->bytes_per_element; } return ARGB_SET_A(a) | ARGB_SET_R(r) | ARGB_SET_G(g) | ARGB_SET_B(b); } /** * Load scanlined EXR image. * @param ectx EXR context * @param pm destination pixmap * @return result code */ static exr_result_t load_scanlined(const exr_context_t ectx, struct pixmap* pm) { exr_result_t rc; int32_t scanlines; uint64_t chunk_size; uint8_t* buffer = NULL; exr_decode_pipeline_t decoder = EXR_DECODE_PIPELINE_INITIALIZER; argb_t* dst = pm->data; exr_attr_box2i_t dwnd; // temporary buffer for decoded chunk's scanlines rc = exr_get_chunk_unpacked_size(ectx, 0, &chunk_size); if (rc != EXR_ERR_SUCCESS) { return rc; } buffer = malloc(chunk_size); if (!buffer) { return EXR_ERR_OUT_OF_MEMORY; } // get image properties rc = exr_get_data_window(ectx, 0, &dwnd); if (rc != EXR_ERR_SUCCESS) { goto done; } rc = exr_get_scanlines_per_chunk(ectx, 0, &scanlines); if (rc != EXR_ERR_SUCCESS) { goto done; } // decode chunks for (int32_t y = dwnd.min.y; y <= dwnd.max.y; y += scanlines) { exr_chunk_info_t chunk; int8_t bpp = 0; rc = exr_read_scanline_chunk_info(ectx, 0, y, &chunk); if (rc != EXR_ERR_SUCCESS) { break; } rc = decode_chunk(ectx, &chunk, &decoder, buffer); if (rc != EXR_ERR_SUCCESS) { break; } // put pixels to final image for (int16_t i = 0; i < decoder.channel_count; ++i) { bpp += decoder.channels[i].bytes_per_element; } for (size_t i = 0; i < chunk_size; i += bpp) { if (dst >= pm->data + (pm->width * pm->height)) { goto done; } *dst = decode_pixel(&decoder, buffer + i, chunk_size - i); ++dst; } } done: exr_decoding_destroy(ectx, &decoder); free(buffer); return rc; } /** * Load tailed EXR image. * @param ectx EXR context * @param pm destination pixmap * @return result code */ static exr_result_t load_tailed(const exr_context_t ectx, struct pixmap* pm) { exr_result_t rc; uint64_t chunk_size; uint8_t* buffer = NULL; int32_t levels_x, levels_y; exr_decode_pipeline_t decoder = EXR_DECODE_PIPELINE_INITIALIZER; // temporary buffer for decoded chunk's scanlines rc = exr_get_chunk_unpacked_size(ectx, 0, &chunk_size); if (rc != EXR_ERR_SUCCESS) { return rc; } buffer = malloc(chunk_size); if (!buffer) { return EXR_ERR_OUT_OF_MEMORY; } rc = exr_get_tile_levels(ectx, 0, &levels_x, &levels_y); if (rc != EXR_ERR_SUCCESS) { goto done; } for (int32_t lvl_y = 0; lvl_y < levels_y; ++lvl_y) { for (int32_t lvl_x = 0; lvl_x < levels_x; ++lvl_x) { int32_t lvl_w, lvl_h; int32_t tile_w, tile_h; int tile_x, tile_y; exr_chunk_info_t chunk; exr_decode_pipeline_t decoder = EXR_DECODE_PIPELINE_INITIALIZER; rc = exr_get_level_sizes(ectx, 0, lvl_x, lvl_y, &lvl_w, &lvl_h); if (rc != EXR_ERR_SUCCESS) { goto done; } rc = exr_get_tile_sizes(ectx, 0, lvl_x, lvl_y, &tile_w, &tile_h); if (rc != EXR_ERR_SUCCESS) { goto done; } tile_y = 0; for (int64_t img_y = 0; img_y < lvl_h; img_y += tile_h) { tile_x = 0; for (int64_t img_x = 0; img_x < lvl_w; img_x += tile_w) { int8_t bpp = 0; rc = exr_read_tile_chunk_info(ectx, 0, tile_x, tile_y, lvl_x, lvl_y, &chunk); if (rc != EXR_ERR_SUCCESS) { goto done; } rc = decode_chunk(ectx, &chunk, &decoder, buffer); if (rc != EXR_ERR_SUCCESS) { goto done; } // put pixels to final image for (int16_t i = 0; i < decoder.channel_count; ++i) { bpp += decoder.channels[i].bytes_per_element; } for (int32_t y = 0; y < chunk.height; ++y) { const uint8_t* src_line = &buffer[y * chunk.width * bpp]; argb_t* dst_line = &pm->data[(img_y + y) * pm->width]; for (int32_t x = 0; x < chunk.width; ++x) { dst_line[img_x + x] = decode_pixel(&decoder, src_line + x * bpp, 42); } } ++tile_x; } ++tile_y; } } } done: exr_decoding_destroy(ectx, &decoder); free(buffer); return rc; } // EXR loader implementation enum loader_status decode_exr(struct image* ctx, const uint8_t* data, size_t size) { exr_result_t rc; exr_context_t exr; exr_context_initializer_t einit = EXR_DEFAULT_CONTEXT_INITIALIZER; struct pixmap* pm; exr_attr_box2i_t dwnd; exr_storage_t storage; struct data_buffer buf = { .data = data, .size = size, }; einit.user_data = &buf; einit.read_fn = exr_reader; // check signature if (size < sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return ldr_unsupported; } // decode rc = exr_start_read(&exr, "exr", &einit); if (rc != EXR_ERR_SUCCESS) { return ldr_fmterror; } rc = exr_get_data_window(exr, 0, &dwnd); if (rc != EXR_ERR_SUCCESS) { goto done; } pm = image_allocate_frame(ctx, dwnd.max.x - dwnd.min.x + 1, dwnd.max.y - dwnd.min.y + 1); if (!pm) { rc = EXR_ERR_OUT_OF_MEMORY; goto done; } rc = exr_get_storage(exr, 0, &storage); if (rc != EXR_ERR_SUCCESS) { goto done; } image_set_format(ctx, "EXR"); ctx->alpha = true; if (storage == EXR_STORAGE_SCANLINE) { rc = load_scanlined(exr, pm); } else if (storage == EXR_STORAGE_TILED) { rc = load_tailed(exr, pm); } else { rc = EXR_ERR_FEATURE_NOT_IMPLEMENTED; } done: exr_finish(&exr); if (rc != EXR_ERR_SUCCESS) { image_free_frames(ctx); return ldr_fmterror; } return ldr_success; } swayimg-3.1/src/formats/gif.c000066400000000000000000000102561465610152200162160ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // GIF format decoder. // Copyright (C) 2020 Artem Senichev #include "../loader.h" #include #include #include #include // GIF signature static const uint8_t signature[] = { 'G', 'I', 'F' }; // Buffer description for GIF reader struct buffer { const uint8_t* data; const size_t size; size_t position; }; // GIF reader callback, see `InputFunc` in gif_lib.h static int gif_reader(GifFileType* gif, GifByteType* dst, int sz) { struct buffer* buf = (struct buffer*)gif->UserData; if (sz >= 0 && buf && buf->position + sz <= buf->size) { memcpy(dst, buf->data + buf->position, sz); buf->position += sz; return sz; } return -1; } /** * Decode single GIF frame. * @param ctx image context * @param gif gif context * @param index number of the frame to load * @return true if completed successfully */ static bool decode_frame(struct image* ctx, GifFileType* gif, size_t index) { const SavedImage* img = &gif->SavedImages[index]; const GifImageDesc* desc = &img->ImageDesc; const ColorMapObject* color_map = desc->ColorMap ? desc->ColorMap : gif->SColorMap; GraphicsControlBlock ctl = { .TransparentColor = NO_TRANSPARENT_COLOR }; struct image_frame* frame = &ctx->frames[index]; const size_t width = (size_t)desc->Width > frame->pm.width - desc->Left ? frame->pm.width - desc->Left : (size_t)desc->Width; const size_t height = (size_t)desc->Height > frame->pm.height - desc->Top ? frame->pm.height - desc->Top : (size_t)desc->Height; DGifSavedExtensionToGCB(gif, index, &ctl); if (ctl.DisposalMode == DISPOSE_PREVIOUS && index < ctx->num_frames - 1) { struct pixmap* next = &ctx->frames[index + 1].pm; pixmap_copy(&frame->pm, next, 0, 0, false); } for (size_t y = 0; y < height; ++y) { const uint8_t* raster = &img->RasterBits[y * desc->Width]; argb_t* pixel = frame->pm.data + desc->Top * frame->pm.width + y * frame->pm.width + desc->Left; for (size_t x = 0; x < width; ++x) { const uint8_t color = raster[x]; if (color != ctl.TransparentColor && color < color_map->ColorCount) { const GifColorType* rgb = &color_map->Colors[color]; *pixel = ARGB_SET_A(0xff) | ARGB_SET_R(rgb->Red) | ARGB_SET_G(rgb->Green) | ARGB_SET_B(rgb->Blue); } ++pixel; } } if (ctl.DisposalMode == DISPOSE_DO_NOT && index < ctx->num_frames - 1) { struct pixmap* next = &ctx->frames[index + 1].pm; pixmap_copy(&frame->pm, next, 0, 0, false); } if (ctl.DelayTime != 0) { frame->duration = ctl.DelayTime * 10; // hundreds of second to ms } else { frame->duration = 100; } return true; } // GIF loader implementation enum loader_status decode_gif(struct image* ctx, const uint8_t* data, size_t size) { GifFileType* gif = NULL; struct buffer buf = { .data = data, .size = size, .position = 0, }; int err; // check signature if (size < sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return ldr_unsupported; } // decode gif = DGifOpen(&buf, gif_reader, &err); if (!gif) { return ldr_fmterror; } if (DGifSlurp(gif) != GIF_OK) { goto fail; } // allocate frame sequence if (!image_create_frames(ctx, gif->ImageCount)) { goto fail; } for (size_t i = 0; i < ctx->num_frames; ++i) { struct pixmap* pm = &ctx->frames[i].pm; if (!pixmap_create(pm, gif->SWidth, gif->SHeight)) { goto fail; } } // decode every frame for (size_t i = 0; i < ctx->num_frames; ++i) { if (!decode_frame(ctx, gif, i)) { goto fail; } } image_set_format(ctx, "GIF%s", gif->ImageCount > 1 ? " animation" : ""); ctx->alpha = true; DGifCloseFile(gif, NULL); return ldr_success; fail: DGifCloseFile(gif, NULL); image_free_frames(ctx); return ldr_fmterror; } swayimg-3.1/src/formats/heif.c000066400000000000000000000062121465610152200163610ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // HEIF and AVIF formats decoder. // Copyright (C) 2022 Artem Senichev #include "../exif.h" #include "../loader.h" #include "buildcfg.h" #include #include #include #ifdef HAVE_LIBEXIF /** * Read Exif info. * @param ctx image context * @param pih handle of HEIF/AVIF image */ static void read_exif(struct image* ctx, struct heif_image_handle* pih) { heif_item_id id; const int count = heif_image_handle_get_list_of_metadata_block_IDs(pih, "Exif", &id, 1); for (int i = 0; i < count; i++) { size_t sz = heif_image_handle_get_metadata_size(pih, id); uint8_t* data = malloc(sz); if (data) { const struct heif_error err = heif_image_handle_get_metadata(pih, id, data); if (err.code == heif_error_Ok) { process_exif(ctx, data + 4 /* skip offset */, sz); } free(data); } } } #endif // HAVE_LIBEXIF // HEIF/AVIF loader implementation enum loader_status decode_heif(struct image* ctx, const uint8_t* data, size_t size) { struct heif_context* heif = NULL; struct heif_image_handle* pih = NULL; struct heif_image* img = NULL; struct heif_error err; const uint8_t* decoded; struct pixmap* pm; int stride = 0; enum loader_status status = ldr_fmterror; if (heif_check_filetype(data, size) != heif_filetype_yes_supported) { return ldr_unsupported; } heif = heif_context_alloc(); if (!heif) { goto done; } err = heif_context_read_from_memory(heif, data, size, NULL); if (err.code != heif_error_Ok) { goto done; } err = heif_context_get_primary_image_handle(heif, &pih); if (err.code != heif_error_Ok) { goto done; } err = heif_decode_image(pih, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, NULL); if (err.code != heif_error_Ok) { goto done; } decoded = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride); if (!decoded) { goto done; } pm = image_allocate_frame(ctx, heif_image_get_primary_width(img), heif_image_get_primary_height(img)); if (!pm) { goto done; } // convert to plain image frame for (size_t y = 0; y < pm->height; ++y) { const argb_t* src = (const argb_t*)(decoded + y * stride); argb_t* dst = &pm->data[y * pm->width]; for (size_t x = 0; x < pm->width; ++x) { dst[x] = ABGR_TO_ARGB(src[x]); } } ctx->alpha = heif_image_handle_has_alpha_channel(pih); image_set_format(ctx, "HEIF/AVIF %dbpp", heif_image_handle_get_luma_bits_per_pixel(pih)); #ifdef HAVE_LIBEXIF read_exif(ctx, pih); #endif status = ldr_success; done: if (status != ldr_success) { image_free_frames(ctx); } if (img) { heif_image_release(img); } if (pih) { heif_image_handle_release(pih); } if (heif) { heif_context_free(heif); } return status; } swayimg-3.1/src/formats/jpeg.c000066400000000000000000000056031465610152200163760ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // JPEG format decoder. // Copyright (C) 2020 Artem Senichev #include "../loader.h" #include #include #include #include #include // depends on stdio.h, uses FILE but doesn't include the header #include // JPEG signature static const uint8_t signature[] = { 0xff, 0xd8 }; struct jpg_error_manager { struct jpeg_error_mgr mgr; jmp_buf setjmp; struct image* img; }; static void jpg_error_exit(j_common_ptr jpg) { struct jpg_error_manager* err = (struct jpg_error_manager*)jpg->err; char msg[JMSG_LENGTH_MAX] = { 0 }; (*(jpg->err->format_message))(jpg, msg); longjmp(err->setjmp, 1); } // JPEG loader implementation enum loader_status decode_jpeg(struct image* ctx, const uint8_t* data, size_t size) { struct pixmap* pm; struct jpeg_decompress_struct jpg; struct jpg_error_manager err; // check signature if (size < sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return ldr_unsupported; } jpg.err = jpeg_std_error(&err.mgr); err.img = ctx; err.mgr.error_exit = jpg_error_exit; if (setjmp(err.setjmp)) { image_free_frames(ctx); jpeg_destroy_decompress(&jpg); return ldr_fmterror; } jpeg_create_decompress(&jpg); jpeg_mem_src(&jpg, data, size); jpeg_read_header(&jpg, TRUE); jpeg_start_decompress(&jpg); #ifdef LIBJPEG_TURBO_VERSION jpg.out_color_space = JCS_EXT_BGRA; #endif // LIBJPEG_TURBO_VERSION pm = image_allocate_frame(ctx, jpg.output_width, jpg.output_height); if (!pm) { jpeg_destroy_decompress(&jpg); return ldr_fmterror; } while (jpg.output_scanline < jpg.output_height) { uint8_t* line = (uint8_t*)&pm->data[jpg.output_scanline * pm->width]; jpeg_read_scanlines(&jpg, &line, 1); // convert grayscale to argb if (jpg.out_color_components == 1) { uint32_t* pixel = (uint32_t*)line; for (int x = jpg.output_width - 1; x >= 0; --x) { const uint8_t src = *(line + x); pixel[x] = ((argb_t)0xff << 24) | (argb_t)src << 16 | (argb_t)src << 8 | src; } } #ifndef LIBJPEG_TURBO_VERSION // convert rgb to argb if (jpg.out_color_components == 3) { uint32_t* pixel = (uint32_t*)line; for (int x = jpg.output_width - 1; x >= 0; --x) { const uint8_t* src = line + x * 3; pixel[x] = ((argb_t)0xff << 24) | (argb_t)src[0] << 16 | (argb_t)src[1] << 8 | src[2]; } } #endif // LIBJPEG_TURBO_VERSION } image_set_format(ctx, "JPEG %dbit", jpg.out_color_components * 8); jpeg_finish_decompress(&jpg); jpeg_destroy_decompress(&jpg); return ldr_success; } swayimg-3.1/src/formats/jxl.c000066400000000000000000000110531465610152200162420ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // JPEG XL format decoder. // Copyright (C) 2021 Artem Senichev #include "../loader.h" #include #include // JPEG XL loader implementation enum loader_status decode_jxl(struct image* ctx, const uint8_t* data, size_t size) { JxlDecoder* jxl; JxlBasicInfo info = { 0 }; JxlDecoderStatus status; size_t buffer_sz; struct image_frame* frames; size_t frame_num = 0; const JxlPixelFormat jxl_format = { .num_channels = 4, // ARBG .data_type = JXL_TYPE_UINT8, .endianness = JXL_NATIVE_ENDIAN, .align = 0 }; // check signature switch (JxlSignatureCheck(data, size)) { case JXL_SIG_NOT_ENOUGH_BYTES: case JXL_SIG_INVALID: return ldr_unsupported; default: break; } // initialize decoder jxl = JxlDecoderCreate(NULL); if (!jxl) { return ldr_fmterror; } status = JxlDecoderSetInput(jxl, data, size); if (status != JXL_DEC_SUCCESS) { goto fail; } // process decoding status = JxlDecoderSubscribeEvents( jxl, JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE); if (status != JXL_DEC_SUCCESS) { goto fail; } do { JxlDecoderStatus rc; status = JxlDecoderProcessInput(jxl); switch (status) { case JXL_DEC_SUCCESS: break; // decoding complete case JXL_DEC_ERROR: goto fail; case JXL_DEC_BASIC_INFO: rc = JxlDecoderGetBasicInfo(jxl, &info); if (rc != JXL_DEC_SUCCESS) { goto fail; } break; case JXL_DEC_FULL_IMAGE: // convert ABGR -> ARGB for (size_t i = 0; i < ctx->frames[frame_num].pm.width * ctx->frames[frame_num].pm.height; ++i) { ctx->frames[frame_num].pm.data[i] = ABGR_TO_ARGB(ctx->frames[frame_num].pm.data[i]); } frame_num = ctx->num_frames; break; case JXL_DEC_FRAME: frames = realloc(ctx->frames, sizeof(*ctx->frames) * (ctx->num_frames + 1)); if (!frames) { goto fail; } ctx->frames = frames; if (!pixmap_create(&ctx->frames[frame_num].pm, info.xsize, info.ysize)) { goto fail; } ctx->num_frames += 1; if (info.have_animation) { JxlFrameHeader header; rc = JxlDecoderGetFrameHeader(jxl, &header); if (rc != JXL_DEC_SUCCESS) { goto fail; } ctx->frames[frame_num].duration = header.duration * 1000.0f * info.animation.tps_denominator / info.animation.tps_numerator; } break; case JXL_DEC_NEED_IMAGE_OUT_BUFFER: // get image buffer size rc = JxlDecoderImageOutBufferSize(jxl, &jxl_format, &buffer_sz); if (rc != JXL_DEC_SUCCESS) { goto fail; } // check buffer format if (buffer_sz != ctx->frames[frame_num].pm.width * ctx->frames[frame_num].pm.height * sizeof(argb_t)) { goto fail; } // set output buffer rc = JxlDecoderSetImageOutBuffer(jxl, &jxl_format, ctx->frames[frame_num].pm.data, buffer_sz); if (rc != JXL_DEC_SUCCESS) { goto fail; } break; default: break; } } while (status != JXL_DEC_SUCCESS); if (!ctx->frames) { goto fail; } image_set_format(ctx, "JPEG XL %ubpp", info.bits_per_sample * info.num_color_channels + info.alpha_bits); ctx->alpha = info.alpha_bits != 0; JxlDecoderDestroy(jxl); return ldr_success; fail: JxlDecoderDestroy(jxl); image_free_frames(ctx); return ldr_fmterror; } swayimg-3.1/src/formats/png.c000066400000000000000000000203611465610152200162330ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // PNG format decoder. // Copyright (C) 2020 Artem Senichev #include "../loader.h" #include #include #include #include // PNG memory reader struct mem_reader { const uint8_t* data; const size_t size; size_t position; }; // PNG reader callback, see `png_rw_ptr` in png.h static void png_reader(png_structp png, png_bytep buffer, size_t size) { struct mem_reader* reader = (struct mem_reader*)png_get_io_ptr(png); if (reader && reader->position + size < reader->size) { memcpy(buffer, reader->data + reader->position, size); reader->position += size; } else { png_error(png, "No data in PNG reader"); } } /** * Bind pixmap with PNG line-reading decoder. * @param pm pixmap to bind * @return array of pointers to pixmap data */ static png_bytep* bind_pixmap(const struct pixmap* pm) { png_bytep* ptr = malloc(pm->height * sizeof(png_bytep)); if (ptr) { for (uint32_t i = 0; i < pm->height; ++i) { ptr[i] = (png_bytep)&pm->data[i * pm->width]; } } return ptr; } /** * Decode single framed image. * @param ctx image context * @param png png decoder * @param info png image info * @return false if decode failed */ static bool decode_single(struct image* ctx, png_struct* png, png_info* info) { const uint32_t width = png_get_image_width(png, info); const uint32_t height = png_get_image_height(png, info); struct pixmap* pm = image_allocate_frame(ctx, width, height); png_bytep* bind; if (!pm) { return false; } bind = bind_pixmap(pm); if (!bind) { return false; } if (setjmp(png_jmpbuf(png))) { free(bind); return false; } png_read_image(png, bind); free(bind); return true; } #ifdef PNG_APNG_SUPPORTED /** * Decode single PNG frame. * @param ctx image context * @param png png decoder * @param info png image info * @param index number of the frame to load * @return true if completed successfully */ static bool decode_frame(struct image* ctx, png_struct* png, png_info* info, size_t index) { png_uint_32 width = 0; png_uint_32 height = 0; png_uint_32 offset_x = 0; png_uint_32 offset_y = 0; png_uint_16 delay_num = 0; png_uint_16 delay_den = 0; png_byte dispose = 0; png_byte blend = 0; png_bytep* bind; struct pixmap frame_png; struct image_frame* frame_img = &ctx->frames[index]; // get frame params if (png_get_valid(png, info, PNG_INFO_acTL)) { png_read_frame_head(png, info); } if (png_get_valid(png, info, PNG_INFO_fcTL)) { png_get_next_frame_fcTL(png, info, &width, &height, &offset_x, &offset_y, &delay_num, &delay_den, &dispose, &blend); } // fixup frame params if (width == 0) { width = png_get_image_width(png, info); } if (height == 0) { height = png_get_image_height(png, info); } if (delay_den == 0) { delay_den = 100; } if (delay_num == 0) { delay_num = 100; } // allocate frame buffer and bind it to png reader if (!pixmap_create(&frame_png, width, height)) { return false; } bind = bind_pixmap(&frame_png); if (!bind) { pixmap_free(&frame_png); return false; } // decode frame into pixmap if (setjmp(png_jmpbuf(png))) { pixmap_free(&frame_png); free(bind); return false; } png_read_image(png, bind); // handle dispose if (dispose == PNG_DISPOSE_OP_PREVIOUS) { if (index == 0) { dispose = PNG_DISPOSE_OP_BACKGROUND; } else if (index + 1 < ctx->num_frames) { struct pixmap* next = &ctx->frames[index + 1].pm; pixmap_copy(&frame_img->pm, next, 0, 0, false); } } // put frame on final pixmap pixmap_copy(&frame_png, &frame_img->pm, offset_x, offset_y, blend == PNG_BLEND_OP_OVER); // handle dispose if (dispose == PNG_DISPOSE_OP_NONE && index + 1 < ctx->num_frames) { struct pixmap* next = &ctx->frames[index + 1].pm; pixmap_copy(&frame_img->pm, next, 0, 0, false); } // calc frame duration in milliseconds frame_img->duration = (float)delay_num * 1000 / delay_den; pixmap_free(&frame_png); free(bind); return true; } /** * Decode multi framed image. * @param ctx image context * @param png png decoder * @param info png image info * @return false if decode failed */ static bool decode_multiple(struct image* ctx, png_struct* png, png_info* info) { const uint32_t width = png_get_image_width(png, info); const uint32_t height = png_get_image_height(png, info); const uint32_t frames = png_get_num_frames(png, info); uint32_t index; // allocate frames if (!image_create_frames(ctx, frames)) { return false; } for (index = 0; index < frames; ++index) { struct image_frame* frame = &ctx->frames[index]; if (!pixmap_create(&frame->pm, width, height)) { return false; } } // decode frames for (index = 0; index < frames; ++index) { if (!decode_frame(ctx, png, info, index)) { break; } } if (index != frames) { // not all frames were decoded, leave only the first for (index = 1; index < frames; ++index) { pixmap_free(&ctx->frames[index].pm); } ctx->num_frames = 1; } if (png_get_first_frame_is_hidden(png, info) && ctx->num_frames > 1) { --ctx->num_frames; pixmap_free(&ctx->frames[0].pm); memmove(&ctx->frames[0], &ctx->frames[1], ctx->num_frames * sizeof(*ctx->frames)); } return true; } #endif // PNG_APNG_SUPPORTED // PNG loader implementation enum loader_status decode_png(struct image* ctx, const uint8_t* data, size_t size) { png_struct* png = NULL; png_info* info = NULL; png_byte color_type, bit_depth; bool rc; struct mem_reader reader = { .data = data, .size = size, .position = 0, }; // check signature if (png_sig_cmp(data, 0, size) != 0) { return ldr_unsupported; } // create decoder png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { return ldr_fmterror; } info = png_create_info_struct(png); if (!info) { png_destroy_read_struct(&png, NULL, NULL); return ldr_fmterror; } // setup error handling if (setjmp(png_jmpbuf(png))) { png_destroy_read_struct(&png, &info, NULL); return ldr_fmterror; } // get general image info png_set_read_fn(png, &reader, &png_reader); png_read_info(png, info); color_type = png_get_color_type(png, info); bit_depth = png_get_bit_depth(png, info); // setup decoder if (png_get_interlace_type(png, info) != PNG_INTERLACE_NONE) { png_set_interlace_handling(png); } if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png); } if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png); if (bit_depth < 8) { png_set_expand_gray_1_2_4_to_8(png); } } if (png_get_valid(png, info, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png); } if (bit_depth == 16) { png_set_strip_16(png); } png_set_filler(png, 0xff, PNG_FILLER_AFTER); png_set_packing(png); png_set_packswap(png); png_set_bgr(png); png_set_expand(png); png_read_update_info(png, info); #ifdef PNG_APNG_SUPPORTED if (png_get_valid(png, info, PNG_INFO_acTL) && png_get_num_frames(png, info) > 1) { rc = decode_multiple(ctx, png, info); } else { rc = decode_single(ctx, png, info); } #else rc = decode_single(ctx, png, info); #endif // PNG_APNG_SUPPORTED if (!rc) { image_free_frames(ctx); } else { image_set_format(ctx, "PNG %dbit", bit_depth * 4); ctx->alpha = true; } // free resources png_destroy_read_struct(&png, &info, NULL); return rc ? ldr_success : ldr_fmterror; } swayimg-3.1/src/formats/pnm.c000066400000000000000000000213001465610152200162330ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // PNM formats decoder // Copyright (C) 2023 Abe Wieland #include "../loader.h" #include // Divide a by b, rounding to the nearest integer; evaluates b twice #define div_near(a, b) (((a) + (b) / 2) / (b)) // Divide a by b, rounding up; evaluates b twice #define div_ceil(a, b) (((a) + (b) - 1) / (b)) // PNM file types enum pnm_type { pnm_pbm, // Bitmap pnm_pgm, // Grayscale pixmap pnm_ppm // Color pixmap }; // A file-like abstraction for cleaner number parsing struct pnm_iter { const uint8_t* pos; const uint8_t* end; }; // Error conditions #define PNM_EEOF -1 #define PNM_ERNG -2 #define PNM_EFMT -3 #define PNM_EOVF -4 // Digits in INT_MAX #define INT_MAX_DIGITS 10 /** * Read an integer, ignoring leading whitespace and comments * @param it image iterator * @param digits maximum number of digits to read, or 0 for no limit * @return the integer read (positive) or an error code (negative) * * Although the specification states comments may also appear in integers, this * is not supported by any known parsers at the time of writing; thus, we don't * support it either */ static int pnm_readint(struct pnm_iter* it, size_t digits) { if (!digits) digits = INT_MAX_DIGITS; for (; it->pos != it->end; ++it->pos) { const char c = *it->pos; if (c == '#') { while (it->pos != it->end && *it->pos != '\n' && *it->pos != '\r') ++it->pos; } else if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { break; } } if (it->pos == it->end) return PNM_EEOF; if (*it->pos < '0' || *it->pos > '9') return PNM_EFMT; int val = 0; size_t i = 0; do { const uint8_t d = *it->pos - '0'; if (val > INT_MAX / 10) return PNM_ERNG; val *= 10; if (val > INT_MAX - d) return PNM_ERNG; val += d; ++it->pos; ++i; } while (it->pos != it->end && *it->pos >= '0' && *it->pos <= '9' && i < digits); return val; } /** * Decode a plain/ASCII PNM file * @param pm pixel map to write data to * @param it image iterator * @param type type of PNM file * @param maxval maximum value for each sample * @return 0 on success, error code on failure */ static int decode_plain(struct pixmap* pm, struct pnm_iter* it, enum pnm_type type, int maxval) { for (size_t y = 0; y < pm->height; ++y) { argb_t* dst = pm->data + y * pm->width; for (size_t x = 0; x < pm->width; ++x) { argb_t pix = ARGB_SET_A(0xff); if (type == pnm_pbm) { const int bit = pnm_readint(it, 1); if (bit < 0) return bit; if (bit > maxval) return PNM_EOVF; pix |= bit - 1; } else if (type == pnm_pgm) { int v = pnm_readint(it, 0); if (v < 0) return v; if (v > maxval) return PNM_EOVF; if (maxval != UINT8_MAX) v = div_near(v * UINT8_MAX, maxval); pix |= ARGB_SET_R(v) | ARGB_SET_G(v) | ARGB_SET_B(v); } else { int r = pnm_readint(it, 0); if (r < 0) return r; if (r > maxval) return PNM_EOVF; int g = pnm_readint(it, 0); if (g < 0) return g; if (g > maxval) return PNM_EOVF; int b = pnm_readint(it, 0); if (b < 0) return b; if (b > maxval) return PNM_EOVF; if (maxval != UINT8_MAX) { r = div_near(r * UINT8_MAX, maxval); g = div_near(g * UINT8_MAX, maxval); b = div_near(b * UINT8_MAX, maxval); } pix |= ARGB_SET_R(r) | ARGB_SET_G(g) | ARGB_SET_B(b); } dst[x] = pix; } } return 0; } /** * Decode a raw/binary PNM file * @param f image frame to write data to * @param it image iterator * @param type type of PNM file * @param maxval maximum value for each sample * @return 0 on success, error code on failure */ static int decode_raw(struct pixmap* pm, struct pnm_iter* it, enum pnm_type type, int maxval) { // PGM and PPM use bpc (bytes per channel) bytes for each channel depending // on the max, with 1 channel for PGM and 3 for PPM; PBM pads each row to // the nearest whole byte size_t bpc = maxval <= UINT8_MAX ? 1 : 2; size_t rowsz = type == pnm_pbm ? div_ceil(pm->width, 8) : pm->width * bpc * (type == pnm_pgm ? 1 : 3); if (it->end < it->pos + pm->height * rowsz) return PNM_EEOF; for (size_t y = 0; y < pm->height; ++y) { argb_t* dst = pm->data + y * pm->width; const uint8_t* src = it->pos + y * rowsz; for (size_t x = 0; x < pm->width; ++x) { argb_t pix = ARGB_SET_A(0xff); if (type == pnm_pbm) { const int bit = (src[x / 8] >> (7 - x % 8)) & 1; pix |= bit - 1; } else if (type == pnm_pgm) { int v = bpc == 1 ? src[x] : src[x] << 8 | src[x + 1]; if (v > maxval) return PNM_EOVF; if (maxval != UINT8_MAX) v = div_near(v * UINT8_MAX, maxval); pix |= ARGB_SET_R(v) | ARGB_SET_G(v) | ARGB_SET_B(v); } else { int r, g, b; if (bpc == 1) { r = src[x * 3]; g = src[x * 3 + 1]; b = src[x * 3 + 2]; } else { r = src[x * 3] << 8 | src[x * 3 + 1]; g = src[x * 3 + 2] << 8 | src[x * 3 + 3]; b = src[x * 3 + 4] << 8 | src[x * 3 + 5]; } if (r > maxval || g > maxval || b > maxval) return PNM_EOVF; if (maxval != UINT8_MAX) { r = div_near(r * UINT8_MAX, maxval); g = div_near(g * UINT8_MAX, maxval); b = div_near(b * UINT8_MAX, maxval); } pix |= ARGB_SET_R(r) | ARGB_SET_G(g) | ARGB_SET_B(b); } dst[x] = pix; } } return 0; } enum loader_status decode_pnm(struct image* ctx, const uint8_t* data, size_t size) { struct pnm_iter it; bool plain; enum pnm_type type; int width, height, maxval, ret; if (size < 2 || data[0] != 'P') { return ldr_unsupported; } switch (data[1]) { case '1': plain = true; type = pnm_pbm; break; case '2': plain = true; type = pnm_pgm; break; case '3': plain = true; type = pnm_ppm; break; case '4': plain = false; type = pnm_pbm; break; case '5': plain = false; type = pnm_pgm; break; case '6': plain = false; type = pnm_ppm; break; default: return ldr_unsupported; } it.pos = data + 2; it.end = data + size; width = pnm_readint(&it, 0); if (width < 0) { return ldr_fmterror; } height = pnm_readint(&it, 0); if (height < 0) { return ldr_fmterror; } if (type == pnm_pbm) { maxval = 1; } else { maxval = pnm_readint(&it, 0); if (maxval < 0) { return ldr_fmterror; } if (!maxval || maxval > UINT16_MAX) { return ldr_fmterror; } } if (!plain) { // Again, the specifications technically allow for comments here, but no // other parsers support that (they treat that comment as image data), // so we won't allow one either const char c = *it.pos; if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { return ldr_fmterror; } ++it.pos; } if (!image_allocate_frame(ctx, width, height)) { return ldr_fmterror; } ret = plain ? decode_plain(&ctx->frames[0].pm, &it, type, maxval) : decode_raw(&ctx->frames[0].pm, &it, type, maxval); if (ret < 0) { image_free_frames(ctx); return ldr_fmterror; } image_set_format(ctx, "P%cM (%s)", type == pnm_pbm ? 'B' : (type == pnm_pgm ? 'G' : 'P'), plain ? "ASCII" : "raw"); return ldr_success; } swayimg-3.1/src/formats/svg.c000066400000000000000000000072731465610152200162550ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // SVG format decoder. // Copyright (C) 2020 Artem Senichev #include "../loader.h" #include #include #include // SVG uses physical units to store size, // these macro defines default viewbox dimension in pixels #define RENDER_SIZE 1024 // Max offset of the root svg node in xml file #define MAX_OFFSET 1024 /** * Check if data is SVG. * @param data raw image data * @param size size of image data in bytes * @return true if it is an SVG format */ static bool is_svg(const uint8_t* data, size_t size) { const char svg_begin[] = " sizeof(svg_begin) && strncmp((const char*)data, svg_begin, sizeof(svg_begin) - 1) == 0) { return true; } if (size > sizeof(xml_begin) && strncmp((const char*)data, xml_begin, sizeof(xml_begin) - 1) == 0) { // search for svg node size_t pos = sizeof(xml_begin); while (pos < MAX_OFFSET && pos + sizeof(svg_begin) < size) { if (strncmp((const char*)&data[pos], svg_begin, sizeof(svg_begin) - 1) == 0) { return true; } ++pos; } } return false; } // SVG loader implementation enum loader_status decode_svg(struct image* ctx, const uint8_t* data, size_t size) { RsvgHandle* svg; gboolean has_vb_real; RsvgRectangle vb_real; RsvgRectangle vb_render; GError* err = NULL; cairo_surface_t* surface = NULL; cairo_t* cr = NULL; struct pixmap* pm; cairo_status_t status; if (!is_svg(data, size)) { return ldr_unsupported; } svg = rsvg_handle_new_from_data(data, size, &err); if (!svg) { return ldr_fmterror; } // define image size in pixels rsvg_handle_get_intrinsic_dimensions(svg, NULL, NULL, NULL, NULL, &has_vb_real, &vb_real); vb_render.x = 0; vb_render.y = 0; if (has_vb_real) { if (vb_real.width < vb_real.height) { vb_render.width = RENDER_SIZE * (vb_real.width / vb_real.height); vb_render.height = RENDER_SIZE; } else { vb_render.width = RENDER_SIZE; vb_render.height = RENDER_SIZE * (vb_real.height / vb_real.width); } } else { vb_render.width = RENDER_SIZE; vb_render.height = RENDER_SIZE; } // allocate and bind buffer pm = image_allocate_frame(ctx, vb_render.width, vb_render.height); if (!pm) { goto fail; } memset(pm->data, 0, pm->width * pm->height * sizeof(argb_t)); surface = cairo_image_surface_create_for_data( (uint8_t*)pm->data, CAIRO_FORMAT_ARGB32, pm->width, pm->height, pm->width * sizeof(argb_t)); status = cairo_surface_status(surface); if (status != CAIRO_STATUS_SUCCESS) { goto fail; } // render svg to surface cr = cairo_create(surface); if (!rsvg_handle_render_document(svg, cr, &vb_render, &err)) { goto fail; } image_set_format(ctx, "SVG"); if (has_vb_real) { image_add_meta(ctx, "Real size", "%0.2fx%0.2f", vb_real.width, vb_real.height); } ctx->alpha = true; cairo_destroy(cr); cairo_surface_destroy(surface); g_object_unref(svg); return ldr_success; fail: if (cr) { cairo_destroy(cr); } if (surface) { cairo_surface_destroy(surface); } image_free_frames(ctx); g_object_unref(svg); return ldr_fmterror; } swayimg-3.1/src/formats/tga.c000066400000000000000000000200121465610152200162130ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Truevision TGA format decoder. // Copyright (C) 2024 Artem Senichev #include "../loader.h" #include /** TGA file header. */ struct __attribute__((__packed__)) tga_header { uint8_t id_len; uint8_t clrmap_type; uint8_t image_type; uint16_t cm_index; uint16_t cm_size; uint8_t cm_bpc; uint16_t origin_x; uint16_t origin_y; uint16_t width; uint16_t height; uint8_t bpp; uint8_t desc; }; #define TGA_COLORMAP 1 // color map present flag #define TGA_UNC_CM 1 // uncompressed color-mapped #define TGA_UNC_TC 2 // uncompressed true-color #define TGA_UNC_GS 3 // uncompressed grayscale #define TGA_RLE_CM 9 // run-length encoded color-mapped #define TGA_RLE_TC 10 // run-length encoded true-color #define TGA_RLE_GS 11 // run-length encoded grayscale #define TGA_ORDER_R2L (1 << 4) // right-to-left pixel ordering #define TGA_ORDER_T2B (1 << 5) // top-to-bottom pixel ordering #define TGA_PACKET_RLE (1 << 7) // rle/raw field #define TGA_PACKET_LEN 0x7f // length mask /** * Get pixel color from data stream. * @param data pointer to stream data * @param bpp bits per pixel * @return color */ static inline argb_t get_pixel(const uint8_t* data, size_t bpp) { argb_t pixel; switch (bpp) { case 8: pixel = ARGB_SET_A(0xff) | ARGB_SET_R(data[0]) | ARGB_SET_G(data[0]) | ARGB_SET_B(data[0]); break; case 15: case 16: pixel = ARGB_SET_A(0xff) | ARGB_SET_G(data[0] & 0xf8) | ARGB_SET_B((data[0] << 5) | ((data[1] & 0xc0) >> 2)) | ARGB_SET_R((data[1] & 0x3e) << 2); break; case 24: pixel = ARGB_SET_A(0xff) | ARGB_SET_R(data[2]) | ARGB_SET_G(data[1]) | ARGB_SET_B(data[0]); break; default: pixel = *(const argb_t*)data; break; } return pixel; } /** * Decode uncompressed image. * @param pm destination pixmap * @param tga source image descriptor * @param colormap color map * @param data pointer to image data * @param size size of image data in bytes * @return true if image decoded successfully */ static bool decode_unc(struct pixmap* pm, const struct tga_header* tga, const uint8_t* colormap, const uint8_t* data, size_t size) { const uint8_t bytes_per_pixel = tga->bpp / 8 + (tga->bpp % 8 ? 1 : 0); const size_t num_pixels = pm->width * pm->height; const size_t data_size = num_pixels * bytes_per_pixel; if (data_size > size) { return false; } if (tga->bpp == 32) { memcpy(pm->data, data, data_size); } else { const uint8_t cm_bpp = tga->cm_bpc / 8 + (tga->cm_bpc % 8 ? 1 : 0); for (size_t i = 0; i < num_pixels; ++i) { const uint8_t* src = data + i * bytes_per_pixel; if (!colormap) { pm->data[i] = get_pixel(src, tga->bpp); } else { const uint8_t* entry = colormap + cm_bpp * (*src); if (entry + cm_bpp > data) { return false; } pm->data[i] = get_pixel(entry, tga->cm_bpc); } } } return true; } /** * Decode RLE compressed image. * @param pm destination pixmap * @param tga source image descriptor * @param colormap color map * @param data pointer to image data * @param size size of image data in bytes * @return true if image decoded successfully */ static bool decode_rle(struct pixmap* pm, const struct tga_header* tga, const uint8_t* colormap, const uint8_t* data, size_t size) { const uint8_t cm_bpp = tga->cm_bpc / 8 + (tga->cm_bpc % 8 ? 1 : 0); const uint8_t bytes_per_pixel = tga->bpp / 8 + (tga->bpp % 8 ? 1 : 0); const argb_t* pm_end = pm->data + pm->width * pm->height; argb_t* pixel = pm->data; size_t pos = 0; while (pixel < pm_end) { const uint8_t pack = data[pos++]; const bool is_rle = (pack & TGA_PACKET_RLE); size_t len = (pack & TGA_PACKET_LEN) + 1; while (len--) { if (pos + bytes_per_pixel > size) { return false; } if (!colormap) { *pixel = get_pixel(data + pos, tga->bpp); } else { const uint8_t* entry = colormap + cm_bpp * data[pos]; if (entry + cm_bpp > data) { return false; } *pixel = get_pixel(entry, tga->cm_bpc); } if (pixel++ >= pm_end) { break; } if (!is_rle) { pos += bytes_per_pixel; } } if (is_rle) { pos += bytes_per_pixel; } } return true; } // TGA loader implementation enum loader_status decode_tga(struct image* ctx, const uint8_t* data, size_t size) { const struct tga_header* tga = (const struct tga_header*)data; const uint8_t* colormap = NULL; size_t colormap_sz = 0; const char* type_name = NULL; bool rc = false; size_t data_offset; struct pixmap* pm; // check type if (size < sizeof(struct tga_header) || (tga->image_type != TGA_UNC_CM && tga->image_type != TGA_UNC_TC && tga->image_type != TGA_UNC_GS && tga->image_type != TGA_RLE_CM && tga->image_type != TGA_RLE_TC && tga->image_type != TGA_RLE_GS)) { return ldr_unsupported; } // check image params if (tga->width == 0 || tga->height == 0 || (tga->bpp != 8 && tga->bpp != 15 && tga->bpp != 16 && tga->bpp != 24 && tga->bpp != 32)) { return ldr_unsupported; } // get color map switch (tga->image_type) { case TGA_UNC_CM: case TGA_RLE_CM: if (!(tga->clrmap_type & TGA_COLORMAP) || !tga->cm_size || !tga->cm_bpc) { return ldr_unsupported; } colormap_sz = tga->cm_size * (tga->cm_bpc / 8 + (tga->cm_bpc % 8 ? 1 : 0)); colormap = data + sizeof(struct tga_header) + tga->id_len; break; default: if (tga->clrmap_type & TGA_COLORMAP || tga->cm_size || tga->cm_bpc) { return ldr_unsupported; } break; } // get pixel array offset data_offset = sizeof(struct tga_header) + tga->id_len + colormap_sz; if (data_offset >= size) { return ldr_unsupported; } data += data_offset; size -= data_offset; // decode image pm = image_allocate_frame(ctx, tga->width, tga->height); if (!pm) { return ldr_fmterror; } switch (tga->image_type) { case TGA_UNC_CM: case TGA_UNC_TC: case TGA_UNC_GS: rc = decode_unc(pm, tga, colormap, data, size); break; case TGA_RLE_CM: case TGA_RLE_TC: case TGA_RLE_GS: rc = decode_rle(pm, tga, colormap, data, size); break; } if (!rc) { image_free_frames(ctx); return ldr_fmterror; } // fix orientation if (!(tga->desc & TGA_ORDER_T2B)) { pixmap_flip_vertical(pm); } if (tga->desc & TGA_ORDER_R2L) { pixmap_flip_horizontal(pm); } // set image meta data switch (tga->image_type) { case TGA_UNC_CM: type_name = "uncompressed color-mapped"; break; case TGA_UNC_TC: type_name = "uncompressed true-color"; break; case TGA_UNC_GS: type_name = "uncompressed grayscale"; break; case TGA_RLE_CM: type_name = "RLE color-mapped"; break; case TGA_RLE_TC: type_name = "RLE true-color"; break; case TGA_RLE_GS: type_name = "RLE grayscale"; break; } image_set_format(ctx, "TARGA %dbpp, %s", tga->bpp, type_name); ctx->alpha = (tga->bpp == 32); return ldr_success; } swayimg-3.1/src/formats/tiff.c000066400000000000000000000074001465610152200163760ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // TIFF format decoder. // Copyright (C) 2022 Artem Senichev #include "../loader.h" #include #include // TIFF signatures static const uint8_t signature1[] = { 0x49, 0x49, 0x2a, 0x00 }; static const uint8_t signature2[] = { 0x4d, 0x4d, 0x00, 0x2a }; // Size of buffer for error messages, see libtiff for details #define LIBTIFF_ERRMSG_SZ 1024 // TIFF memory reader struct mem_reader { const uint8_t* data; size_t size; size_t position; }; // TIFF memory reader: TIFFReadWriteProc static tmsize_t tiff_read(thandle_t data, void* buffer, tmsize_t size) { struct mem_reader* mr = data; if (mr->position >= mr->size) { return 0; } if (mr->position + size > mr->size) { size = mr->size - mr->position; } memcpy(buffer, mr->data + mr->position, size); mr->position += size; return size; } // TIFF memory reader: TIFFReadWriteProc static tmsize_t tiff_write(__attribute__((unused)) thandle_t data, __attribute__((unused)) void* buffer, __attribute__((unused)) tmsize_t size) { return 0; } // TIFF memory reader: TIFFSeekProc static toff_t tiff_seek(thandle_t data, toff_t off, __attribute__((unused)) int xxx) { struct mem_reader* mr = data; if (off < mr->size) { mr->position = off; } return mr->position; } // TIFF memory reader: TIFFCloseProc static int tiff_close(__attribute__((unused)) thandle_t data) { return 0; } // TIFF memory reader: TIFFSizeProc static toff_t tiff_size(thandle_t data) { struct mem_reader* mr = data; return mr->size; } // TIFF memory reader: TIFFMapFileProc static int tiff_map(thandle_t data, void** base, toff_t* size) { struct mem_reader* mr = data; *base = (uint8_t*)mr->data; *size = mr->size; return 0; } // TIFF memory reader: TIFFUnmapFileProc static void tiff_unmap(__attribute__((unused)) thandle_t data, __attribute__((unused)) void* base, __attribute__((unused)) toff_t size) { } // TIFF loader implementation enum loader_status decode_tiff(struct image* ctx, const uint8_t* data, size_t size) { TIFF* tiff; TIFFRGBAImage timg; struct pixmap* pm; char err[LIBTIFF_ERRMSG_SZ]; struct mem_reader reader; // check signature if (size < sizeof(signature1) || size < sizeof(signature2) || (memcmp(data, signature1, sizeof(signature1)) && memcmp(data, signature2, sizeof(signature2)))) { return ldr_unsupported; } reader.data = data; reader.size = size; reader.position = 0; TIFFSetErrorHandler(NULL); TIFFSetWarningHandler(NULL); tiff = TIFFClientOpen("", "r", &reader, tiff_read, tiff_write, tiff_seek, tiff_close, tiff_size, tiff_map, tiff_unmap); if (!tiff) { return ldr_fmterror; } *err = 0; if (!TIFFRGBAImageBegin(&timg, tiff, 0, err)) { goto fail; } pm = image_allocate_frame(ctx, timg.width, timg.height); if (!pm) { goto fail; } if (!TIFFRGBAImageGet(&timg, pm->data, timg.width, timg.height)) { goto fail; } // convert ABGR -> ARGB for (size_t i = 0; i < pm->width * pm->height; ++i) { pm->data[i] = ABGR_TO_ARGB(pm->data[i]); } if (timg.orientation == ORIENTATION_TOPLEFT) { image_flip_vertical(ctx); } image_set_format(ctx, "TIFF %dbpp", timg.bitspersample * timg.samplesperpixel); ctx->alpha = true; TIFFRGBAImageEnd(&timg); TIFFClose(tiff); return ldr_success; fail: image_free_frames(ctx); TIFFRGBAImageEnd(&timg); TIFFClose(tiff); return ldr_fmterror; } swayimg-3.1/src/formats/webp.c000066400000000000000000000055341465610152200164110ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // WebP format decoder. // Copyright (C) 2020 Artem Senichev #include "../exif.h" #include "../loader.h" #include "buildcfg.h" #include #include // WebP signature static const uint8_t signature[] = { 'R', 'I', 'F', 'F' }; // WebP loader implementation enum loader_status decode_webp(struct image* ctx, const uint8_t* data, size_t size) { const WebPData raw = { .bytes = data, .size = size }; WebPAnimDecoderOptions webp_opts; WebPAnimDecoder* webp_dec = NULL; WebPAnimInfo webp_info; WebPBitstreamFeatures prop; int prev_timestamp = 0; // check signature if (size < sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return ldr_unsupported; } // get image properties if (WebPGetFeatures(data, size, &prop) != VP8_STATUS_OK) { return ldr_fmterror; } // open decoder WebPAnimDecoderOptionsInit(&webp_opts); webp_opts.color_mode = MODE_BGRA; webp_dec = WebPAnimDecoderNew(&raw, &webp_opts); if (!webp_dec) { goto fail; } if (!WebPAnimDecoderGetInfo(webp_dec, &webp_info)) { goto fail; } // allocate frame sequence if (!image_create_frames(ctx, webp_info.frame_count)) { goto fail; } // decode every frame for (size_t i = 0; i < ctx->num_frames; ++i) { uint8_t* buffer; int timestamp; struct image_frame* frame = &ctx->frames[i]; struct pixmap* pm = &frame->pm; if (!pixmap_create(pm, webp_info.canvas_width, webp_info.canvas_height)) { goto fail; } if (!WebPAnimDecoderGetNext(webp_dec, &buffer, ×tamp)) { goto fail; } memcpy(pm->data, buffer, pm->width * pm->height * sizeof(argb_t)); if (ctx->num_frames > 1) { frame->duration = timestamp - prev_timestamp; prev_timestamp = timestamp; if (frame->duration <= 0) { frame->duration = 100; } } } #ifdef HAVE_LIBEXIF const WebPDemuxer* webp_dmx = WebPAnimDecoderGetDemuxer(webp_dec); if (WebPDemuxGetI(webp_dmx, WEBP_FF_FORMAT_FLAGS) & EXIF_FLAG) { WebPChunkIterator it; if (WebPDemuxGetChunk(webp_dmx, "EXIF", 1, &it)) { process_exif(ctx, it.chunk.bytes, it.chunk.size); WebPDemuxReleaseChunkIterator(&it); } } #endif // HAVE_LIBEXIF WebPAnimDecoderDelete(webp_dec); image_set_format( ctx, "WebP %s %s%s", prop.format == 1 ? "lossy" : "lossless", prop.has_alpha ? "+alpha" : "", prop.has_animation ? "+animation" : ""); ctx->alpha = prop.has_alpha; return ldr_success; fail: if (webp_dec) { WebPAnimDecoderDelete(webp_dec); } return ldr_fmterror; } swayimg-3.1/src/gallery.c000066400000000000000000000425051465610152200154370ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Gallery mode. // Copyright (C) 2024 Artem Senichev #include "gallery.h" #include "application.h" #include "imagelist.h" #include "info.h" #include "loader.h" #include "str.h" #include "ui.h" #include #include // Scale for selected thumbnail #define THUMB_SELECTED_SCALE 1.15f /** List of thumbnails. */ struct thumbnail { struct image* image; ///< Preview image size_t width, height; ///< Real image size struct thumbnail* next; ///< Next entry }; /** Gallery context. */ struct gallery { size_t thumb_size; ///< Max size of thumbnail struct thumbnail* thumbs; ///< List of preview images bool thumb_aa; ///< Use anti-aliasing for thumbnail argb_t clr_window; ///< Window background argb_t clr_background; ///< Tile background argb_t clr_select; ///< Selected tile background argb_t clr_border; ///< Selected tile border argb_t clr_shadow; ///< Selected tile shadow size_t top; ///< Index of the first displayed image size_t selected; ///< Index of the selected image }; /** Global gallery context. */ static struct gallery ctx; /** * Reset thumbnail list and free its resources. */ static void reset_thumbnails(void) { while (ctx.thumbs) { struct thumbnail* entry = ctx.thumbs; ctx.thumbs = ctx.thumbs->next; image_free(entry->image); free(entry); } app_redraw(); } /** * Add new thumbnail from existing image. * @param image original image */ static void add_thumbnail(struct image* image) { struct thumbnail* entry = malloc(sizeof(*entry)); if (entry) { entry->width = image->frames[0].pm.width; entry->height = image->frames[0].pm.height; entry->image = image; // create thumbnail from image image_thumbnail(image, ctx.thumb_size, ctx.thumb_aa); // add to the list entry->next = ctx.thumbs; ctx.thumbs = entry; } } /** * Get thumbnail. * @param index image position in the image list * @return thumbnail instance or NULL if not found */ static struct thumbnail* get_thumbnail(size_t index) { struct thumbnail* it = ctx.thumbs; while (it) { if (it->image->index == index) { return it; } it = it->next; } return NULL; } /** * Remove thumbnail. * @param index image position in the image list */ static void remove_thumbnail(size_t index) { struct thumbnail* it = ctx.thumbs; struct thumbnail* prev = NULL; while (it) { if (it->image->index == index) { if (prev) { prev->next = it->next; } else { ctx.thumbs = it->next; } image_free(it->image); free(it); return; } prev = it; it = it->next; } } /** * Draw thumbnail. * @param window destination window * @param x,y top left coordinate * @param image thumbnail image * @param selected flag to highlight current thumbnail */ static void draw_thumbnail(struct pixmap* window, ssize_t x, ssize_t y, const struct image* image, bool selected) { const struct pixmap* thumb = image ? &image->frames[0].pm : NULL; if (!selected) { pixmap_fill(window, x, y, ctx.thumb_size, ctx.thumb_size, ctx.clr_background); if (thumb) { x += ctx.thumb_size / 2 - thumb->width / 2; y += ctx.thumb_size / 2 - thumb->height / 2; pixmap_copy(thumb, window, x, y, image->alpha); } } else { // currently selected item const size_t thumb_size = THUMB_SELECTED_SCALE * ctx.thumb_size; const size_t thumb_offset = (thumb_size - ctx.thumb_size) / 2; x = max(0, x - (ssize_t)thumb_offset); y = max(0, y - (ssize_t)thumb_offset); if (x + thumb_size >= window->width) { x = window->width - thumb_size; } pixmap_fill(window, x, y, thumb_size, thumb_size, ctx.clr_select); if (thumb) { const ssize_t thumb_w = thumb->width * THUMB_SELECTED_SCALE; const ssize_t thumb_h = thumb->height * THUMB_SELECTED_SCALE; const ssize_t tx = x + thumb_size / 2 - thumb_w / 2; const ssize_t ty = y + thumb_size / 2 - thumb_h / 2; if (ctx.thumb_aa) { pixmap_scale_bicubic(thumb, window, tx, ty, THUMB_SELECTED_SCALE, image->alpha); } else { pixmap_scale_nearest(thumb, window, tx, ty, THUMB_SELECTED_SCALE, image->alpha); } } // shadow if (ARGB_GET_A(ctx.clr_shadow)) { const argb_t base = ctx.clr_shadow & 0x00ffffff; const uint8_t alpha = ARGB_GET_A(ctx.clr_shadow); const size_t width = max(1, (double)thumb_size / 15.0 * ((double)alpha / 255.0)); const size_t alpha_step = alpha / width; for (size_t i = 0; i < width; ++i) { const ssize_t lx = i + x + thumb_size; const ssize_t ly = y + width; const ssize_t lh = thumb_size - (width - i); const argb_t color = base | ARGB_SET_A(alpha - i * alpha_step); pixmap_vline(window, lx, ly, lh, color); } for (size_t i = 0; i < width; ++i) { const ssize_t lx = x + width; const ssize_t ly = y + thumb_size + i; const ssize_t lw = thumb_size - (width - i) + 1; const argb_t color = base | ARGB_SET_A(alpha - i * alpha_step); pixmap_hline(window, lx, ly, lw, color); } } // border if (ARGB_GET_A(ctx.clr_border)) { pixmap_rect(window, x, y, thumb_size, thumb_size, ctx.clr_border); } } } /** * Draw thumbnails. * @param window destination window */ static void draw_thumbnails(struct pixmap* window) { size_t index = ctx.top; ssize_t select_x = 0; ssize_t select_y = 0; const struct thumbnail* select_th = NULL; // thumbnails layout const size_t cols = ui_get_width() / ctx.thumb_size; const size_t rows = ui_get_height() / ctx.thumb_size + 1; const size_t margin = (ui_get_width() - (cols * ctx.thumb_size)) / (cols + 1); // draw for (size_t row = 0; row < rows; ++row) { const ssize_t y = row * ctx.thumb_size + margin * (row + 1); for (size_t col = 0; col < cols; ++col) { const ssize_t x = col * ctx.thumb_size + margin * (col + 1); const struct thumbnail* th = get_thumbnail(index); // draw preview, but postpone the selected item if (index == ctx.selected) { select_x = x; select_y = y; select_th = th; } else { draw_thumbnail(window, x, y, th ? th->image : NULL, false); } // get next thumbnail index index = image_list_next_file(index); if (index == IMGLIST_INVALID || index <= ctx.top) { row = rows; // break parent loop break; } } } // draw selected thumbnail draw_thumbnail(window, select_x, select_y, select_th ? select_th->image : NULL, true); } /** * Draw gallery. */ static void redraw(void) { struct pixmap* wnd; if (image_list_first() == IMGLIST_INVALID) { printf("No more images, exit\n"); app_exit(0); return; } wnd = ui_draw_begin(); if (!wnd) { return; } pixmap_fill(wnd, 0, 0, wnd->width, wnd->height, ctx.clr_window); draw_thumbnails(wnd); info_print(wnd); ui_draw_commit(); } /** Reset loader queue. */ static void reset_loader(void) { loader_queue_reset(); if (!get_thumbnail(ctx.selected)) { loader_queue_append(ctx.selected); } // get number of thumbnails on the screen const size_t cols = ui_get_width() / ctx.thumb_size; const size_t rows = ui_get_height() / ctx.thumb_size + 1; // search for nearest to selected const size_t last = image_list_forward(ctx.top, cols * rows); const size_t max_f = image_list_distance(ctx.selected, last); const size_t max_b = image_list_distance(ctx.top, ctx.selected); size_t next_f = ctx.selected; size_t next_b = ctx.selected; for (size_t i = 0; i < max(max_f, max_b); ++i) { if (i < max_f) { next_f = image_list_next_file(next_f); if (!get_thumbnail(next_f)) { loader_queue_append(next_f); } } if (i < max_b) { next_b = image_list_prev_file(next_b); if (!get_thumbnail(next_b)) { loader_queue_append(next_b); } } } } /** Fix up top position. */ static void fixup_position(void) { const size_t cols = ui_get_width() / ctx.thumb_size; const size_t rows = ui_get_height() / ctx.thumb_size; size_t distance; // if selection is not visible, put it on the center distance = image_list_distance(ctx.top, ctx.selected); if (distance == IMGLIST_INVALID || distance > cols * rows) { const size_t center_x = cols / 2; const size_t center_y = rows / 2; ctx.top = image_list_back(ctx.selected, center_y * cols + center_x); } // remove gap at the bottom of the screen distance = image_list_distance(ctx.top, image_list_last()); if (distance < cols * (rows - 1)) { ctx.top = image_list_back(image_list_last(), cols * rows - 1); } reset_loader(); } /** * Set current selection. * @param index image index to set as selected one */ static void select_thumbnail(size_t index) { const struct thumbnail* th; ctx.selected = index; th = get_thumbnail(index); if (th) { info_reset(th->image); info_update(info_image_size, "%zux%zu", th->width, th->height); info_update(info_index, "%zu of %zu", th->image->index + 1, image_list_size()); } fixup_position(); app_redraw(); } /** * Skip current image. * @return true if next image was loaded */ static bool skip_current(void) { size_t index = image_list_skip(ctx.selected); if (index == IMGLIST_INVALID || index < ctx.selected) { index = image_list_prev_file(ctx.selected); } if (index != IMGLIST_INVALID) { remove_thumbnail(ctx.selected); if (ctx.top == ctx.selected) { ctx.top = index; } select_thumbnail(index); return true; } return false; } /** * Select closest item. * @param direction next image position in list */ static void select_nearest(enum action_type direction) { const size_t cols = ui_get_width() / ctx.thumb_size; const size_t rows = ui_get_height() / ctx.thumb_size; size_t index = ctx.selected; switch (direction) { case action_first_file: index = image_list_first(); ctx.top = index; break; case action_last_file: index = image_list_last(); ctx.top = image_list_back(index, cols * rows - 1); break; case action_prev_file: case action_step_left: if (index != image_list_first()) { index = image_list_prev_file(index); } break; case action_next_file: case action_step_right: if (index != image_list_last()) { index = image_list_next_file(index); } break; case action_step_up: index = image_list_back(index, cols); break; case action_step_down: index = image_list_forward(index, cols); break; default: break; } if (index != IMGLIST_INVALID && index != ctx.selected) { // fix up top by one line const size_t distance = image_list_distance(ctx.top, index); if (distance == IMGLIST_INVALID) { ctx.top = image_list_back(ctx.top, cols); } else if (distance >= rows * cols) { ctx.top = image_list_forward(ctx.top, cols); } select_thumbnail(index); } } /** * Scroll one page. * @param forward scroll direction */ static void scroll_page(bool forward) { const size_t distance = ui_get_width() / ctx.thumb_size + ui_get_height() / ctx.thumb_size; size_t top = ctx.top; size_t index = ctx.selected; if (forward) { top = image_list_forward(top, distance); index = image_list_forward(index, distance); } else { top = image_list_back(top, distance); index = image_list_back(index, distance); } if (index != IMGLIST_INVALID && index != ctx.selected) { ctx.top = top; select_thumbnail(index); } } /** * Apply action. * @param action pointer to the action being performed */ static void apply_action(const struct action* action) { switch (action->type) { case action_antialiasing: ctx.thumb_aa = !ctx.thumb_aa; reset_thumbnails(); break; case action_first_file: case action_last_file: case action_prev_file: case action_next_file: case action_step_left: case action_step_right: case action_step_up: case action_step_down: select_nearest(action->type); break; case action_page_up: case action_page_down: scroll_page(action->type == action_page_down); break; case action_skip_file: if (!skip_current()) { printf("No more images, exit\n"); app_exit(0); } break; case action_reload: reset_thumbnails(); fixup_position(); break; case action_exec: app_execute(action->params, image_list_get(ctx.selected)); break; case action_status: info_update(info_status, "%s", action->params); app_redraw(); break; case action_mode: app_switch_mode(ctx.selected); break; default: break; } } /** * Background loader thread callback. * @param image loaded image instance, NULL if load error * @param index index of the image in the image list */ static void on_image_load(struct image* image, size_t index) { if (image) { if (get_thumbnail(index)) { image_free(image); } else { add_thumbnail(image); if (index == ctx.selected) { select_thumbnail(ctx.selected); // update meta info } } } else { loader_queue_reset(); if (index == ctx.selected) { skip_current(); } else { image_list_skip(index); fixup_position(); } } app_redraw(); } /** * Custom section loader, see `config_loader` for details. */ static enum config_status load_config(const char* key, const char* value) { enum config_status status = cfgst_invalid_value; if (strcmp(key, "size") == 0) { ssize_t num; if (str_to_num(value, 0, &num, 0) && num >= 10 && num <= 1024) { ctx.thumb_size = num; status = cfgst_ok; } } else if (strcmp(key, "window") == 0) { if (config_to_color(value, &ctx.clr_window)) { status = cfgst_ok; } } else if (strcmp(key, "background") == 0) { if (config_to_color(value, &ctx.clr_background)) { status = cfgst_ok; } } else if (strcmp(key, "select") == 0) { if (config_to_color(value, &ctx.clr_select)) { status = cfgst_ok; } } else if (strcmp(key, "border") == 0) { if (config_to_color(value, &ctx.clr_border)) { status = cfgst_ok; } } else if (strcmp(key, "shadow") == 0) { if (config_to_color(value, &ctx.clr_shadow)) { status = cfgst_ok; } } else if (strcmp(key, "antialiasing") == 0) { if (config_to_bool(value, &ctx.thumb_aa)) { status = cfgst_ok; } } else { status = cfgst_invalid_key; } return status; } void gallery_create(void) { ctx.thumb_size = 200; ctx.top = IMGLIST_INVALID; ctx.selected = IMGLIST_INVALID; ctx.clr_background = 0xff202020; ctx.clr_select = 0xff404040; ctx.clr_border = 0xff000000; ctx.clr_shadow = 0xff000000; // register configuration loader config_add_loader("gallery", load_config); } void gallery_init(struct image* image) { ctx.top = image_list_first(); if (image) { add_thumbnail(image); select_thumbnail(image->index); } } void gallery_destroy(void) { reset_thumbnails(); } void gallery_handle(const struct event* event) { switch (event->type) { case event_action: apply_action(event->param.action); break; case event_redraw: redraw(); break; case event_activate: select_thumbnail(event->param.activate.index); break; case event_load: on_image_load(event->param.load.image, event->param.load.index); break; case event_resize: fixup_position(); break; case event_drag: break; // unused in gallery mode } } swayimg-3.1/src/gallery.h000066400000000000000000000010271465610152200154360ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Gallery mode. // Copyright (C) 2024 Artem Senichev #pragma once #include "event.h" #include "image.h" /** * Create global gallery context. */ void gallery_create(void); /** * Initialize global gallery context. * @param image initial image to open */ void gallery_init(struct image* image); /** * Destroy global gallery context. */ void gallery_destroy(void); /** * Event handler, see `event_handler` for details. */ void gallery_handle(const struct event* event); swayimg-3.1/src/image.c000066400000000000000000000077501465610152200150650ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image instance: pixel data and meta info. // Copyright (C) 2021 Artem Senichev #include "image.h" #include #include #include struct image* image_create(void) { return calloc(1, sizeof(struct image)); } void image_free(struct image* ctx) { if (ctx) { image_free_frames(ctx); free(ctx->source); free(ctx->format); while (ctx->num_info) { --ctx->num_info; free(ctx->info[ctx->num_info].value); } free(ctx->info); free(ctx); } } void image_flip_vertical(struct image* ctx) { for (size_t i = 0; i < ctx->num_frames; ++i) { pixmap_flip_vertical(&ctx->frames[i].pm); } } void image_flip_horizontal(struct image* ctx) { for (size_t i = 0; i < ctx->num_frames; ++i) { pixmap_flip_horizontal(&ctx->frames[i].pm); } } void image_rotate(struct image* ctx, size_t angle) { for (size_t i = 0; i < ctx->num_frames; ++i) { pixmap_rotate(&ctx->frames[i].pm, angle); } } void image_thumbnail(struct image* image, size_t size, bool antialias) { struct pixmap thumb; struct image_frame* frame; const struct pixmap* full = &image->frames[0].pm; const float scale_width = 1.0 / ((float)full->width / size); const float scale_height = 1.0 / ((float)full->height / size); const float scale = min(scale_width, scale_height); const size_t thumb_width = scale * full->width; const size_t thumb_height = scale * full->height; // create thumbnail if (!pixmap_create(&thumb, thumb_width, thumb_height)) { return; } if (antialias) { pixmap_scale_bicubic(full, &thumb, 0, 0, scale, image->alpha); } else { pixmap_scale_nearest(full, &thumb, 0, 0, scale, image->alpha); } image_free_frames(image); frame = image_create_frames(image, 1); if (frame) { frame->pm = thumb; } } void image_set_format(struct image* ctx, const char* fmt, ...) { va_list args; int len; char* buffer; va_start(args, fmt); // NOLINTNEXTLINE(clang-analyzer-valist.Uninitialized) len = vsnprintf(NULL, 0, fmt, args); va_end(args); if (len <= 0) { return; } ++len; // last null buffer = realloc(ctx->format, len); if (!buffer) { return; } va_start(args, fmt); vsprintf(buffer, fmt, args); va_end(args); ctx->format = buffer; } void image_add_meta(struct image* ctx, const char* key, const char* fmt, ...) { va_list args; int len; void* buffer; buffer = realloc(ctx->info, (ctx->num_info + 1) * sizeof(struct image_info)); if (!buffer) { return; } ctx->info = buffer; // construct value string va_start(args, fmt); // NOLINTNEXTLINE(clang-analyzer-valist.Uninitialized) len = vsnprintf(NULL, 0, fmt, args); va_end(args); if (len <= 0) { return; } ++len; // last null buffer = malloc(len); if (!buffer) { return; } va_start(args, fmt); vsprintf(buffer, fmt, args); va_end(args); ctx->info[ctx->num_info].key = key; ctx->info[ctx->num_info].value = buffer; ++ctx->num_info; } struct pixmap* image_allocate_frame(struct image* ctx, size_t width, size_t height) { if (image_create_frames(ctx, 1) && pixmap_create(&ctx->frames[0].pm, width, height)) { return &ctx->frames[0].pm; } image_free_frames(ctx); return NULL; } struct image_frame* image_create_frames(struct image* ctx, size_t num) { struct image_frame* frames; frames = calloc(1, num * sizeof(struct image_frame)); if (frames) { ctx->frames = frames; ctx->num_frames = num; } return frames; } void image_free_frames(struct image* ctx) { for (size_t i = 0; i < ctx->num_frames; ++i) { pixmap_free(&ctx->frames[i].pm); } free(ctx->frames); ctx->frames = NULL; ctx->num_frames = 0; } swayimg-3.1/src/image.h000066400000000000000000000063221465610152200150640ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image instance: pixel data and meta info. // Copyright (C) 2021 Artem Senichev #pragma once #include "pixmap.h" struct image_frame; struct image_info; /** Image context. */ struct image { size_t index; ///< Index of the entry in the image list char* source; ///< Image source (e.g. path to the image file) const char* name; ///< Name of the image file size_t file_size; ///< Size of image file char* format; ///< Format description struct image_frame* frames; ///< Image frames size_t num_frames; ///< Total number of frames bool alpha; ///< Image has alpha channel struct image_info* info; ///< Image meta info size_t num_info; ///< Total number of meta info entries }; /** Image frame. */ struct image_frame { struct pixmap pm; ///< Frame data size_t duration; ///< Frame duration in milliseconds (animation) }; /** Image meta info. */ struct image_info { const char* key; ///< Meta key name char* value; ///< Meta value }; /** * Create empty image instance. * @return image context or NULL on errors */ struct image* image_create(void); /** * Free image. * @param ctx image context to free */ void image_free(struct image* ctx); /** * Get image file name without path. * @param ctx image context * @return file name without path */ const char* image_file_name(const struct image* ctx); /** * Flip image vertically. * @param ctx image context */ void image_flip_vertical(struct image* ctx); /** * Flip image horizontally. * @param ctx image context */ void image_flip_horizontal(struct image* ctx); /** * Rotate image. * @param ctx image context * @param angle rotation angle (only 90, 180, or 270) */ void image_rotate(struct image* ctx, size_t angle); /** * Create thumbnail from full size image. * @param image original image * @param size thumbnail size in pixels * @param antialias use antialiasing */ void image_thumbnail(struct image* image, size_t size, bool antialias); /** * Set image format description. * @param ctx image context * @param fmt format description */ void image_set_format(struct image* ctx, const char* fmt, ...) __attribute__((format(printf, 2, 3))); /** * Add meta info property. * @param ctx image context * @param key property name (must be static) * @param fmt value format */ void image_add_meta(struct image* ctx, const char* key, const char* fmt, ...) __attribute__((format(printf, 3, 4))); /** * Create single frame, allocate buffer and add frame to the image. * @param width frame width in px * @param height frame height in px * @return pointer to the pixmap associated with the frame, or NULL on errors */ struct pixmap* image_allocate_frame(struct image* ctx, size_t width, size_t height); /** * Create list of empty frames. * @param ctx image context * @param num total number of frames * @return pointer to the frame list, NULL on errors */ struct image_frame* image_create_frames(struct image* ctx, size_t num); /** * Free image frames. * @param ctx image context */ void image_free_frames(struct image* ctx); swayimg-3.1/src/imagelist.c000066400000000000000000000261271465610152200157600ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // List of images. // Copyright (C) 2022 Artem Senichev #include "imagelist.h" #include "config.h" #include "loader.h" #include "str.h" #include "ui.h" #include "viewer.h" #include #include #include #include #include /** Context of the image list (which is actually an array). */ struct image_list { char** sources; ///< Array of entries size_t capacity; ///< Number of allocated entries (size of array) size_t size; ///< Number of entries in array enum list_order order; ///< File list order bool loop; ///< File list loop mode bool recursive; ///< Read directories recursively bool all_files; ///< Open all files from the same directory }; static struct image_list ctx = { .order = order_alpha, .loop = true, .recursive = false, .all_files = true, }; /** Order names. */ static const char* order_names[] = { [order_none] = "none", [order_alpha] = "alpha", [order_random] = "random", }; /** * Add new entry to the list. * @param source image data source to add */ static void add_entry(const char* source) { // check for duplicates for (size_t i = 0; i < ctx.size; ++i) { if (strcmp(ctx.sources[i], source) == 0) { return; } } // relocate array, if needed if (ctx.size + 1 >= ctx.capacity) { const size_t cap = ctx.capacity ? ctx.capacity * 2 : 4; char** ptr = realloc(ctx.sources, cap * sizeof(*ctx.sources)); if (!ptr) { return; } ctx.capacity = cap; ctx.sources = ptr; } // add new entry ctx.sources[ctx.size] = str_dup(source, NULL); if (ctx.sources[ctx.size]) { ++ctx.size; } } /** * Add file to the list. * @param file path to the file */ static void add_file(const char* file) { // remove "./" from file path if (file[0] == '.' && file[1] == '/') { file += 2; } add_entry(file); } /** * Add files from the directory to the list. * @param dir full path to the directory * @param recursive flag to handle directory recursively */ static void add_dir(const char* dir, bool recursive) { DIR* dir_handle; struct dirent* dir_entry; struct stat file_stat; size_t len; char* path; dir_handle = opendir(dir); if (!dir_handle) { return; } while (true) { dir_entry = readdir(dir_handle); if (!dir_entry) { break; } // skip link to self/parent dirs if (strcmp(dir_entry->d_name, ".") == 0 || strcmp(dir_entry->d_name, "..") == 0) { continue; } // compose full path len = strlen(dir) + 1 /*slash*/; len += strlen(dir_entry->d_name) + 1 /*last null*/; path = malloc(len); if (path) { // NOLINTBEGIN(clang-analyzer-security.insecureAPI.strcpy) strcpy(path, dir); strcat(path, "/"); strcat(path, dir_entry->d_name); // NOLINTEND(clang-analyzer-security.insecureAPI.strcpy) if (stat(path, &file_stat) == 0) { if (S_ISDIR(file_stat.st_mode)) { if (recursive) { add_dir(path, recursive); } } else { add_file(path); } } free(path); } } closedir(dir_handle); } /** * Get next source entry. * @param start index of the start position * @param forward step direction * @return index of the next entry or IMGLIST_INVALID if not found */ static size_t next_entry(size_t start, bool forward) { size_t index = start; if (start == IMGLIST_INVALID) { return image_list_first(); } while (true) { if (forward) { if (++index >= ctx.size) { if (!ctx.loop) { break; } index = 0; } } else { if (index-- == 0) { if (!ctx.loop) { break; } index = ctx.size - 1; } } if (index == start) { break; } if (ctx.sources[index]) { return index; } } return IMGLIST_INVALID; } /** * Get next directory entry index (works only for paths as source). * @param start index of the start position * @param forward step direction * @return index of the next entry or IMGLIST_INVALID if not found */ static size_t next_dir(size_t start, bool forward) { const char* cur_path = ctx.sources[start]; size_t cur_len; size_t index = start; if (start == IMGLIST_INVALID) { return image_list_first(); } // directory part of the current file path cur_len = strlen(cur_path) - 1; while (cur_len && cur_path[cur_len] != '/') { --cur_len; } // search for another directory in file list while (true) { const char* next_path; size_t next_len; index = next_entry(index, forward); if (index == IMGLIST_INVALID || index == start) { break; // not found } next_path = ctx.sources[index]; next_len = strlen(next_path) - 1; while (next_len && next_path[next_len] != '/') { --next_len; } if (cur_len != next_len || strncmp(cur_path, next_path, next_len)) { return index; } }; return IMGLIST_INVALID; } /** * Compare sources callback for `qsort`. * @return negative if a < b, positive if a > b, 0 otherwise */ static int compare_sources(const void* a, const void* b) { return strcoll(*(const char**)a, *(const char**)b); } /** * Shuffle the image list. */ static void shuffle_list(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); srand(ts.tv_nsec); // swap random entries for (size_t i = 0; i < ctx.size; ++i) { const size_t j = rand() % ctx.size; if (i != j) { char* swap = ctx.sources[i]; ctx.sources[i] = ctx.sources[j]; ctx.sources[j] = swap; } } } /** * Custom section loader, see `config_loader` for details. */ static enum config_status load_config(const char* key, const char* value) { enum config_status status = cfgst_invalid_value; if (strcmp(key, IMGLIST_CFG_ORDER) == 0) { const ssize_t index = str_index(order_names, value, 0); if (index >= 0) { ctx.order = index; status = cfgst_ok; } } else if (strcmp(key, IMGLIST_CFG_LOOP) == 0) { if (config_to_bool(value, &ctx.loop)) { status = cfgst_ok; } } else if (strcmp(key, IMGLIST_CFG_RECURSIVE) == 0) { if (config_to_bool(value, &ctx.recursive)) { status = cfgst_ok; } } else if (strcmp(key, IMGLIST_CFG_ALL) == 0) { if (config_to_bool(value, &ctx.all_files)) { status = cfgst_ok; } } else { status = cfgst_invalid_key; } return status; } void image_list_create(void) { // register configuration loader config_add_loader(IMGLIST_CFG_SECTION, load_config); } void image_list_destroy(void) { for (size_t i = 0; i < ctx.size; ++i) { free(ctx.sources[i]); } free(ctx.sources); ctx.sources = NULL; ctx.capacity = 0; ctx.size = 0; } size_t image_list_init(const char** sources, size_t num) { struct stat file_stat; for (size_t i = 0; i < num; ++i) { // special files if (strncmp(sources[i], LDRSRC_STDIN, LDRSRC_STDIN_LEN) == 0 || strncmp(sources[i], LDRSRC_EXEC, LDRSRC_EXEC_LEN) == 0) { add_entry(sources[i]); continue; } // file system files if (stat(sources[i], &file_stat) != 0) { continue; } if (S_ISDIR(file_stat.st_mode)) { add_dir(sources[i], ctx.recursive); continue; } if (!ctx.all_files) { add_file(sources[i]); continue; } // add all files from the same directory const char* delim = strrchr(sources[i], '/'); const size_t len = delim ? delim - sources[i] : 0; if (len == 0) { add_dir(".", ctx.recursive); } else { char* dir = malloc(len + 1); if (dir) { memcpy(dir, sources[i], len); dir[len] = 0; add_dir(dir, ctx.recursive); free(dir); } } } if (ctx.size != 0) { // sort or shuffle if (ctx.order == order_alpha) { qsort(ctx.sources, ctx.size, sizeof(*ctx.sources), compare_sources); } else if (ctx.order == order_random) { shuffle_list(); } } return ctx.size; } size_t image_list_size(void) { return ctx.size; } const char* image_list_get(size_t index) { return index < ctx.size ? ctx.sources[index] : NULL; } size_t image_list_find(const char* source) { // remove "./" from file source if (source[0] == '.' && source[1] == '/') { source += 2; } for (size_t i = 0; i < ctx.size; ++i) { if (ctx.sources[i] && strcmp(ctx.sources[i], source) == 0) { return i; } } return IMGLIST_INVALID; } size_t image_list_distance(size_t start, size_t end) { size_t index = start; size_t step = 0; if (start > end) { return IMGLIST_INVALID; } while (index != IMGLIST_INVALID && index < end) { ++step; index = next_entry(index, true); } return step; } size_t image_list_back(size_t start, size_t distance) { size_t index = start; while (distance--) { const size_t next = next_entry(index, false); if (next == IMGLIST_INVALID || next >= start) { break; } index = next; } return index; } size_t image_list_forward(size_t start, size_t distance) { size_t index = start; while (distance--) { const size_t next = next_entry(index, true); if (next == IMGLIST_INVALID || next <= start) { break; } index = next; } return index; } size_t image_list_next_file(size_t start) { return next_entry(start, true); } size_t image_list_prev_file(size_t start) { return next_entry(start, false); } size_t image_list_next_dir(size_t start) { return next_dir(start, true); } size_t image_list_prev_dir(size_t start) { return next_dir(start, false); } size_t image_list_first(void) { if (ctx.size == 0) { return IMGLIST_INVALID; } return ctx.sources[0] ? 0 : next_entry(0, true); } size_t image_list_last(void) { const size_t index = ctx.size - 1; if (ctx.size == 0) { return IMGLIST_INVALID; } return ctx.sources[index] ? index : next_entry(index, false); } size_t image_list_skip(size_t index) { if (index < ctx.size && ctx.sources[index]) { // remove current entry from list free(ctx.sources[index]); ctx.sources[index] = NULL; } return next_entry(index, true); } swayimg-3.1/src/imagelist.h000066400000000000000000000066051465610152200157640ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // List of images. // Copyright (C) 2022 Artem Senichev #pragma once #include // Configuration parameters #define IMGLIST_CFG_SECTION "list" #define IMGLIST_CFG_ORDER "order" #define IMGLIST_CFG_LOOP "loop" #define IMGLIST_CFG_RECURSIVE "recursive" #define IMGLIST_CFG_ALL "all" // Invalid index of the entry #define IMGLIST_INVALID SIZE_MAX /** Order of file list. */ enum list_order { order_none, ///< Unsorted (system depended) order_alpha, ///< Alphanumeric sort order_random ///< Random order }; /** * Create global image list context. */ void image_list_create(void); /** * Destroy global image list context. */ void image_list_destroy(void); /** * Initialize the image list: scan directories and fill the image list. * @param sources list of sources * @param num number of sources in the list * @return size of the image list */ size_t image_list_init(const char** sources, size_t num); /** * Get image list size. * @return total number of entries in the list include non-image files */ size_t image_list_size(void); /** * Get image source for specified index. * @param index index of the image list entry * @return image data source description (path, ...) or NULL if no source */ const char* image_list_get(size_t index); /** * Find index for specified source. * @param source image data source * @return index of the entry or IMGLIST_INVALID if not found */ size_t image_list_find(const char* source); /** * Get distance between two indexes. * @param start,end entry indexes * @return number of image entries between indexes */ size_t image_list_distance(size_t start, size_t end); /** * Move backward through the list. * @param start start position * @param distance number of valid entries to skip * @return index of the end entry */ size_t image_list_back(size_t start, size_t distance); /** * Move forward through the list. * @param start start position * @param distance number of valid entries to skip * @return index of the end entry */ size_t image_list_forward(size_t start, size_t distance); /** * Get next entry index. * @param start index of the start position * @return index of the entry or IMGLIST_INVALID if not found */ size_t image_list_next_file(size_t start); /** * Get previous entry index. * @param start index of the start position * @return index of the entry or IMGLIST_INVALID if not found */ size_t image_list_prev_file(size_t start); /** * Get next directory entry index (works only for paths as source). * @param start index of the start position * @return index of the entry or IMGLIST_INVALID if not found */ size_t image_list_next_dir(size_t start); /** * Get previous directory entry index (works only for paths as source). * @param start index of the start position * @return index of the entry or IMGLIST_INVALID if not found */ size_t image_list_prev_dir(size_t start); /** * Get the first entry index. * @return index of the entry or IMGLIST_INVALID if image list is empty */ size_t image_list_first(void); /** * Get the first entry index. * @return index of the entry or IMGLIST_INVALID if image list is empty */ size_t image_list_last(void); /** * Skip entry (remove from the image list). * @param index entry to remove * @return next valid index of the entry or IMGLIST_INVALID if list is empty */ size_t image_list_skip(size_t index); swayimg-3.1/src/info.c000066400000000000000000000525711465610152200147370ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image info: text blocks with image meta data. // Copyright (C) 2023 Artem Senichev #include "info.h" #include "application.h" #include "config.h" #include "font.h" #include "imagelist.h" #include "keybind.h" #include "loader.h" #include "str.h" #include "ui.h" #include #include #include #include #include #include // clang-format off /** Display modes. */ enum info_mode { mode_viewer, mode_gallery, mode_off, }; static const char* mode_names[] = { [mode_viewer] = APP_MODE_VIEWER, [mode_gallery] = APP_MODE_GALLERY, [mode_off] = "off", }; #define MODES_NUM 2 /** Field names. */ static const char* field_names[] = { [info_file_name] = "name", [info_file_path] = "path", [info_file_size] = "filesize", [info_image_format] = "format", [info_image_size] = "imagesize", [info_exif] = "exif", [info_frame] = "frame", [info_index] = "index", [info_scale] = "scale", [info_status] = "status", }; #define INFO_FIELDS_NUM ARRAY_SIZE(field_names) /** Positions of text info block. */ enum block_position { pos_center, pos_top_left, pos_top_right, pos_bottom_left, pos_bottom_right, }; /** Block position names. */ static const char* position_names[] = { [pos_center] = "center", [pos_top_left] = "top_left", [pos_top_right] = "top_right", [pos_bottom_left] = "bottom_left", [pos_bottom_right] = "bottom_right", }; #define INFO_POSITION_NUM ARRAY_SIZE(position_names) // max number of lines in one positioned block #define MAX_LINES (INFO_FIELDS_NUM + 10 /* EXIF and duplicates */) /** Scheme of displayed field (line(s) of text). */ struct field_scheme { enum info_field type; ///< Field type bool title; ///< Print/hide field title }; // Defaults static const struct field_scheme default_viewer_tl[] = { { .type = info_file_name, .title = true }, { .type = info_image_format, .title = true }, { .type = info_file_size, .title = true }, { .type = info_image_size, .title = true }, { .type = info_exif, .title = true }, }; static const struct field_scheme default_viewer_tr[] = { { .type = info_index, .title = false }, }; static const struct field_scheme default_viewer_bl[] = { { .type = info_scale, .title = false }, { .type = info_frame, .title = false }, }; static const struct field_scheme default_viewer_br[] = { { .type = info_status, .title = false }, }; static const struct field_scheme default_gallery_br[] = { { .type = info_file_name, .title = false }, { .type = info_status, .title = false }, }; // clang-format on // Space between text layout and window edge #define TEXT_PADDING 10 #define SET_DEFAULT(m, p, d) \ ctx.scheme[m][p].fields_num = ARRAY_SIZE(d); \ ctx.scheme[m][p].fields = malloc(sizeof(d)); \ if (ctx.scheme[m][p].fields) \ memcpy(ctx.scheme[m][p].fields, d, sizeof(d)) /** Key/value text surface. */ struct keyval { struct text_surface key; struct text_surface value; }; /** Info scheme: set of fields in one of screen positions. */ struct block_scheme { struct field_scheme* fields; ///< Array of fields size_t fields_num; ///< Size of array }; /** Info timeout description. */ struct info_timeout { int fd; ///< Timer FD size_t duration; ///< Timeout duration in seconds bool active; ///< Current state }; /** Info data context. */ struct info_context { enum info_mode mode; ///< Currently active mode struct info_timeout info; ///< Text info timeout struct info_timeout status; ///< Status message timeout struct text_surface* help; ///< Help layer lines size_t help_num; ///< Number of lines in help struct keyval* exif_lines; ///< EXIF data lines size_t exif_num; ///< Number of lines in EXIF data struct keyval fields[INFO_FIELDS_NUM]; ///< Info data struct block_scheme scheme[MODES_NUM][INFO_POSITION_NUM]; ///< Info scheme }; /** Global info context. */ static struct info_context ctx; /** Notification callback: handle timer event. */ static void on_timeout(void* data) { struct info_timeout* timeout = data; struct itimerspec ts = { 0 }; timeout->active = false; timerfd_settime(timeout->fd, 0, &ts, NULL); app_redraw(); } /** * Initialize timer. * @param timeout timer instance */ static void timeout_init(struct info_timeout* timeout) { timeout->fd = -1; timeout->active = true; if (timeout->duration != 0) { timeout->fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (timeout->fd != -1) { app_watch(timeout->fd, on_timeout, timeout); } } } /** * Reset/restart timer. * @param timeout timer instance */ static void timeout_reset(struct info_timeout* timeout) { timeout->active = true; if (timeout->fd != -1) { struct itimerspec ts = { .it_value.tv_sec = timeout->duration }; timerfd_settime(timeout->fd, 0, &ts, NULL); } } /** * Close timer FD. * @param timeout timer instance */ static void timeout_close(struct info_timeout* timeout) { if (timeout->fd != -1) { close(timeout->fd); } } /** * Print centered text block. * @param wnd destination window */ static void print_help(struct pixmap* window) { const size_t line_height = ctx.help[0].height; const size_t row_max = (window->height - TEXT_PADDING * 2) / line_height; const size_t columns = (ctx.help_num / row_max) + (ctx.help_num % row_max ? 1 : 0); const size_t rows = (ctx.help_num / columns) + (ctx.help_num % columns ? 1 : 0); const size_t col_space = line_height; size_t total_width = 0; size_t top = 0; size_t left = 0; // calculate total width for (size_t col = 0; col < columns; ++col) { size_t max_width = 0; for (size_t row = 0; row < rows; ++row) { const size_t index = row + col * rows; if (index >= ctx.help_num) { break; } if (max_width < ctx.help[index].width) { max_width = ctx.help[index].width; } } total_width += max_width; } total_width += col_space * (columns - 1); // top left corner of the centered text block if (total_width < ui_get_width()) { left = window->width / 2 - total_width / 2; } if (rows * line_height < ui_get_height()) { top = window->height / 2 - (rows * line_height) / 2; } // put text on window for (size_t col = 0; col < columns; ++col) { size_t y = top; size_t col_width = 0; for (size_t row = 0; row < rows; ++row) { const size_t index = row + col * rows; if (index >= ctx.help_num) { break; } font_print(window, left, y, &ctx.help[index]); if (col_width < ctx.help[index].width) { col_width = ctx.help[index].width; } y += line_height; } left += col_width + col_space; } } /** * Print info block with key/value text. * @param wnd destination window * @param pos block position * @param lines array of key/value lines to print * @param lines_num total number of lines */ static void print_keyval(struct pixmap* wnd, enum block_position pos, const struct keyval* lines, size_t lines_num) { size_t max_key_width = 0; const size_t height = lines[0].value.height; // calc max width of keys, used if block on the left side for (size_t i = 0; i < lines_num; ++i) { if (lines[i].key.width > max_key_width) { max_key_width = lines[i].key.width; } } max_key_width += height / 2; // draw info block for (size_t i = 0; i < lines_num; ++i) { const struct text_surface* key = &lines[i].key; const struct text_surface* value = &lines[i].value; size_t y = 0; size_t x_key = 0; size_t x_val = 0; // calculate line position switch (pos) { case pos_center: return; // not supported (not used anywhere) case pos_top_left: y = TEXT_PADDING + i * height; if (key->data) { x_key = TEXT_PADDING; x_val = TEXT_PADDING + max_key_width; } else { x_val = TEXT_PADDING; } break; case pos_top_right: y = TEXT_PADDING + i * height; x_val = wnd->width - TEXT_PADDING - value->width; if (key->data) { x_key = x_val - key->width - TEXT_PADDING; } break; case pos_bottom_left: y = wnd->height - TEXT_PADDING - height * lines_num + i * height; if (key->data) { x_key = TEXT_PADDING; x_val = TEXT_PADDING + max_key_width; } else { x_val = TEXT_PADDING; } break; case pos_bottom_right: y = wnd->height - TEXT_PADDING - height * lines_num + i * height; x_val = wnd->width - TEXT_PADDING - value->width; if (key->data) { x_key = x_val - key->width - TEXT_PADDING; } break; } if (key->data) { font_print(wnd, x_key, y, key); } font_print(wnd, x_val, y, value); } } /** * Import meta data from image. * @param image source image */ static void import_exif(const struct image* image) { struct keyval* line; const size_t buf_size = image->num_info * sizeof(*line); // free previuos lines for (size_t i = 0; i < ctx.exif_num; ++i) { free(ctx.exif_lines[i].key.data); free(ctx.exif_lines[i].value.data); } ctx.exif_num = 0; if (image->num_info == 0) { return; } line = realloc(ctx.exif_lines, buf_size); if (!line) { return; } memset(line, 0, buf_size); ctx.exif_num = image->num_info; ctx.exif_lines = line; for (size_t i = 0; i < ctx.exif_num; ++i) { const struct image_info* exif = &image->info[i]; char key[64]; snprintf(key, sizeof(key), "%s:", exif->key); font_render(key, &line->key); font_render(exif->value, &line->value); ++line; } } /** * Parse and load scheme from config line. * @param config line to parse * @param scheme destination scheme description * @return true if config parsed successfully */ static bool parse_scheme(const char* config, struct block_scheme* scheme) { struct str_slice slices[MAX_LINES]; size_t slices_num; struct field_scheme* fields; // split into fields slices slices_num = str_split(config, ',', slices, ARRAY_SIZE(slices)); if (slices_num > ARRAY_SIZE(slices)) { slices_num = ARRAY_SIZE(slices); } fields = realloc(scheme->fields, slices_num * sizeof(*fields)); if (!fields) { return false; } scheme->fields = fields; scheme->fields_num = 0; for (size_t i = 0; i < slices_num; ++i) { struct field_scheme* field = &scheme->fields[scheme->fields_num]; ssize_t field_idx; struct str_slice* sl = &slices[i]; // title show/hide ('+' at the beginning) field->title = (sl->len > 0 && *sl->value == '+'); if (field->title) { ++sl->value; --sl->len; } // field type field_idx = str_index(field_names, sl->value, sl->len); if (field_idx >= 0) { field->type = field_idx; scheme->fields_num++; } else if (sl->len == 4 && strncmp(sl->value, "none", sl->len) == 0) { continue; // special value, just skip } else { return false; // invalid field name } } if (scheme->fields_num == 0) { free(scheme->fields); scheme->fields = NULL; } return true; } /** Custom section loader, see `config_loader` for details. */ static enum config_status load_config_viewer(const char* key, const char* value) { const ssize_t pos = str_index(position_names, key, 0); if (pos < 0) { return cfgst_invalid_key; } if (!parse_scheme(value, &ctx.scheme[mode_viewer][pos])) { return cfgst_invalid_value; } return cfgst_ok; } /** Custom section loader, see `config_loader` for details. */ static enum config_status load_config_gallery(const char* key, const char* value) { const ssize_t pos = str_index(position_names, key, 0); if (pos < 0) { return cfgst_invalid_key; } if (!parse_scheme(value, &ctx.scheme[mode_gallery][pos])) { return cfgst_invalid_value; } return cfgst_ok; } /** Custom section loader, see `config_loader` for details. */ static enum config_status load_config_common(const char* key, const char* value) { enum config_status status = cfgst_invalid_value; if (strcmp(key, "show") == 0) { bool opt; if (config_to_bool(value, &opt)) { ctx.mode = opt ? mode_gallery : mode_off; status = cfgst_ok; } } else if (strcmp(key, "info_timeout") == 0) { ssize_t num; if (str_to_num(value, 0, &num, 0) && num >= 0 && num < 1024) { ctx.info.duration = num; status = cfgst_ok; } } else if (strcmp(key, "status_timeout") == 0) { ssize_t num; if (str_to_num(value, 0, &num, 0) && num >= 0 && num < 1024) { ctx.status.duration = num; status = cfgst_ok; } } else { status = cfgst_invalid_key; } return status; } void info_create(void) { // set defaults ctx.info.duration = 5; ctx.status.duration = 3; ctx.mode = mode_viewer; SET_DEFAULT(mode_viewer, pos_top_left, default_viewer_tl); SET_DEFAULT(mode_viewer, pos_top_right, default_viewer_tr); SET_DEFAULT(mode_viewer, pos_bottom_left, default_viewer_bl); SET_DEFAULT(mode_viewer, pos_bottom_right, default_viewer_br); SET_DEFAULT(mode_gallery, pos_bottom_right, default_gallery_br); // register configuration loader config_add_loader("info", load_config_common); config_add_loader("info.viewer", load_config_viewer); config_add_loader("info.gallery", load_config_gallery); } void info_init(void) { font_render("File name:", &ctx.fields[info_file_name].key); font_render("File path:", &ctx.fields[info_file_path].key); font_render("File size:", &ctx.fields[info_file_size].key); font_render("Image format:", &ctx.fields[info_image_format].key); font_render("Image size:", &ctx.fields[info_image_size].key); font_render("Frame:", &ctx.fields[info_frame].key); font_render("Index:", &ctx.fields[info_index].key); font_render("Scale:", &ctx.fields[info_scale].key); font_render("Status:", &ctx.fields[info_status].key); timeout_init(&ctx.info); timeout_init(&ctx.status); } void info_destroy(void) { timeout_close(&ctx.info); timeout_close(&ctx.status); for (size_t i = 0; i < ctx.exif_num; ++i) { free(ctx.exif_lines[i].key.data); free(ctx.exif_lines[i].value.data); } for (size_t i = 0; i < MODES_NUM; ++i) { for (size_t j = 0; j < INFO_POSITION_NUM; ++j) { free(ctx.scheme[i][j].fields); } } for (size_t i = 0; i < INFO_FIELDS_NUM; ++i) { free(ctx.fields[i].key.data); free(ctx.fields[i].value.data); } for (size_t i = 0; i < ctx.help_num; i++) { free(ctx.help[i].data); } free(ctx.help); } void info_switch(const char* mode) { if (!ctx.info.active) { timeout_reset(&ctx.info); return; } if (mode && *mode) { const ssize_t mode_num = str_index(mode_names, mode, 0); if (mode_num >= 0) { ctx.mode = mode_num; return; } } else { ++ctx.mode; if (ctx.mode > mode_off) { ctx.mode = 0; } } timeout_reset(&ctx.info); } void info_switch_help(void) { if (ctx.help) { for (size_t i = 0; i < ctx.help_num; i++) { free(ctx.help[i].data); } free(ctx.help); ctx.help = NULL; ctx.help_num = 0; } else { const struct keybind* kb = keybind_get(); size_t num = 0; // get number of bindings while (kb) { if (kb->help) { ++num; } kb = kb->next; } if (num == 0) { return; } // create help layer ctx.help = calloc(1, num * sizeof(*ctx.help)); if (!ctx.help) { return; } ctx.help_num = num; // fill keybinds help (they are stored in reverse order) kb = keybind_get(); while (kb) { if (kb->help) { font_render(kb->help, &ctx.help[--num]); } kb = kb->next; } } } bool info_help_active(void) { return !!ctx.help_num; } bool info_enabled(void) { return (ctx.mode != mode_off); } void info_reset(const struct image* image) { const size_t mib = 1024 * 1024; const char unit = image->file_size >= mib ? 'M' : 'K'; const float sz = (float)image->file_size / (image->file_size >= mib ? mib : 1024); font_render(image->name, &ctx.fields[info_file_name].value); font_render(image->source, &ctx.fields[info_file_path].value); font_render(image->format, &ctx.fields[info_image_format].value); info_update(info_file_size, "%.02f %ciB", sz, unit); info_update(info_image_size, "%zux%zu", image->frames[0].pm.width, image->frames[0].pm.height); import_exif(image); info_update(info_frame, NULL); info_update(info_scale, NULL); timeout_reset(&ctx.info); } void info_update(enum info_field field, const char* fmt, ...) { struct text_surface* surface = &ctx.fields[field].value; va_list args; int len; char* text; if (!fmt) { free(surface->data); memset(surface, 0, sizeof(*surface)); return; } va_start(args, fmt); // NOLINTNEXTLINE(clang-analyzer-valist.Uninitialized) len = vsnprintf(NULL, 0, fmt, args); va_end(args); if (len <= 0) { return; } text = malloc(len + 1 /* last null */); if (!text) { return; } va_start(args, fmt); vsprintf(text, fmt, args); va_end(args); font_render(text, surface); free(text); if (field == info_status) { timeout_reset(&ctx.status); } } void info_print(struct pixmap* window) { if (info_help_active()) { print_help(window); } if (ctx.mode == mode_off || !ctx.info.active) { // print only status if (ctx.fields[info_status].value.width && ctx.status.active) { const size_t btype = app_is_viewer() ? mode_viewer : mode_gallery; for (size_t i = 0; i < INFO_POSITION_NUM; ++i) { const struct block_scheme* block = &ctx.scheme[btype][i]; for (size_t j = 0; j < block->fields_num; ++j) { const struct field_scheme* field = &block->fields[j]; if (field->type == info_status) { struct keyval status = ctx.fields[info_status]; if (!field->title) { memset(&status.key, 0, sizeof(status.key)); } print_keyval(window, i, &status, 1); break; } } } } return; } for (size_t i = 0; i < INFO_POSITION_NUM; ++i) { struct keyval lines[MAX_LINES] = { 0 }; const struct block_scheme* block = &ctx.scheme[ctx.mode][i]; size_t lnum = 0; for (size_t j = 0; j < block->fields_num; ++j) { const struct field_scheme* field = &block->fields[j]; const struct keyval* origin = &ctx.fields[field->type]; switch (field->type) { case info_exif: for (size_t n = 0; n < ctx.exif_num; ++n) { if (lnum < ARRAY_SIZE(lines)) { if (field->title) { lines[lnum].key = ctx.exif_lines[n].key; } lines[lnum++].value = ctx.exif_lines[n].value; } } break; case info_status: if (origin->value.width && ctx.status.active) { if (field->title) { lines[lnum].key = origin->key; } lines[lnum++].value = origin->value; } break; default: if (origin->value.width) { if (field->title) { lines[lnum].key = origin->key; } lines[lnum++].value = origin->value; } break; } if (lnum >= ARRAY_SIZE(lines)) { break; } } if (lnum) { print_keyval(window, i, lines, lnum); } } } swayimg-3.1/src/info.h000066400000000000000000000026301465610152200147330ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image info: text blocks with image meta data. // Copyright (C) 2023 Artem Senichev #pragma once #include "image.h" /** Available info fields. */ enum info_field { info_file_name, info_file_path, info_file_size, info_image_format, info_image_size, info_exif, info_frame, info_index, info_scale, info_status, }; /** * Create global info context. */ void info_create(void); /** * Initialize global info context. */ void info_init(void); /** * Destroy global info context. */ void info_destroy(void); /** * Switch display mode. * @param mode display mode name */ void info_switch(const char* mode); /** * Enable/disable help layer. */ void info_switch_help(void); /** * Check if help layer is enabled. * @return true if help layer is visible */ bool info_help_active(void); /** * Check if info enabled. * @param true if info text layer is enabled */ bool info_enabled(void); /** * Compose info data from image. * @param image image instance */ void info_reset(const struct image* image); /** * Update info text. * @param field info field id * @param fmt text string to set, NULL to clear */ void info_update(enum info_field field, const char* fmt, ...) __attribute__((format(printf, 2, 3))); /** * Print info text. * @param window target window surface */ void info_print(struct pixmap* window); swayimg-3.1/src/keybind.c000066400000000000000000000364641465610152200154340ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Keyboard bindings. // Copyright (C) 2023 Artem Senichev #include "keybind.h" #include "application.h" #include "config.h" #include "str.h" #include #include #include // Default key bindings struct keybind_default { xkb_keysym_t key; ///< Keyboard key uint8_t mods; ///< Key modifiers struct action action; ///< Action }; // clang-format off /** Default key bindings for viewer mode. */ static const struct keybind_default default_viewer[] = { { .key = XKB_KEY_F1, .action = { action_help, NULL } }, { .key = XKB_KEY_Home, .action = { action_first_file, NULL } }, { .key = XKB_KEY_End, .action = { action_last_file, NULL } }, { .key = XKB_KEY_space, .action = { action_next_file, NULL } }, { .key = XKB_KEY_SunPageDown, .action = { action_next_file, NULL } }, { .key = XKB_KEY_SunPageUp, .action = { action_prev_file, NULL } }, { .key = XKB_KEY_c, .action = { action_skip_file, NULL } }, { .key = XKB_KEY_d, .action = { action_next_dir, NULL } }, { .key = XKB_KEY_d, .mods = KEYMOD_SHIFT, .action = { action_prev_dir, NULL } }, { .key = XKB_KEY_o, .action = { action_next_frame, NULL } }, { .key = XKB_KEY_o, .mods = KEYMOD_SHIFT, .action = { action_prev_frame, NULL } }, { .key = XKB_KEY_s, .action = { action_animation, NULL } }, { .key = XKB_KEY_s, .mods = KEYMOD_SHIFT, .action = { action_slideshow, NULL } }, { .key = XKB_KEY_f, .action = { action_fullscreen, NULL } }, { .key = XKB_KEY_Left, .action = { action_step_left, NULL } }, { .key = XKB_KEY_Right, .action = { action_step_right, NULL } }, { .key = XKB_KEY_Up, .action = { action_step_up, NULL } }, { .key = XKB_KEY_Down, .action = { action_step_down, NULL } }, { .key = XKB_KEY_equal, .action = { action_zoom, "+10" } }, { .key = XKB_KEY_plus, .action = { action_zoom, "+10" } }, { .key = XKB_KEY_minus, .action = { action_zoom, "-10" } }, { .key = XKB_KEY_w, .action = { action_zoom, "width" } }, { .key = XKB_KEY_w, .mods = KEYMOD_SHIFT, .action = { action_zoom, "height" } }, { .key = XKB_KEY_z, .action = { action_zoom, "fit" } }, { .key = XKB_KEY_z, .mods = KEYMOD_SHIFT, .action = { action_zoom, "fill" } }, { .key = XKB_KEY_0, .action = { action_zoom, "real" } }, { .key = XKB_KEY_BackSpace, .action = { action_zoom, "optimal" } }, { .key = XKB_KEY_bracketleft, .action = { action_rotate_left, NULL } }, { .key = XKB_KEY_bracketright, .action = { action_rotate_right, NULL } }, { .key = XKB_KEY_m, .action = { action_flip_vertical, NULL } }, { .key = XKB_KEY_m, .mods = KEYMOD_SHIFT, .action = { action_flip_horizontal, NULL } }, { .key = XKB_KEY_a, .action = { action_antialiasing, NULL } }, { .key = XKB_KEY_r, .action = { action_reload, NULL } }, { .key = XKB_KEY_i, .action = { action_info, NULL } }, { .key = XKB_KEY_Return, .action = { action_mode, NULL } }, { .key = XKB_KEY_Escape, .action = { action_exit, NULL } }, { .key = XKB_KEY_q, .action = { action_exit, NULL } }, { .key = VKEY_SCROLL_LEFT, .action = { action_step_right, "5" } }, { .key = VKEY_SCROLL_RIGHT, .action = { action_step_left, "5" } }, { .key = VKEY_SCROLL_UP, .action = { action_step_up, "5" } }, { .key = VKEY_SCROLL_DOWN, .action = { action_step_down, "5" } }, { .key = VKEY_SCROLL_UP, .mods = KEYMOD_CTRL, .action = { action_zoom, "+10" } }, { .key = VKEY_SCROLL_DOWN, .mods = KEYMOD_CTRL, .action = { action_zoom, "-10" } }, { .key = VKEY_SCROLL_UP, .mods = KEYMOD_SHIFT, .action = { action_prev_file, NULL } }, { .key = VKEY_SCROLL_DOWN, .mods = KEYMOD_SHIFT, .action = { action_next_file, NULL } }, { .key = VKEY_SCROLL_UP, .mods = KEYMOD_ALT, .action = { action_prev_frame, NULL } }, { .key = VKEY_SCROLL_DOWN, .mods = KEYMOD_ALT, .action = { action_next_frame, NULL } }, { .key = XKB_KEY_Delete, .mods = KEYMOD_SHIFT, .action = { action_none, NULL } }, }; /** Default key bindings for gallery mode. */ static const struct keybind_default default_gallery[] = { { .key = XKB_KEY_F1, .action = { action_help, NULL } }, { .key = XKB_KEY_Home, .action = { action_first_file, NULL } }, { .key = XKB_KEY_End, .action = { action_last_file, NULL } }, { .key = XKB_KEY_f, .action = { action_fullscreen, NULL } }, { .key = XKB_KEY_Left, .action = { action_step_left, NULL } }, { .key = XKB_KEY_Right, .action = { action_step_right, NULL } }, { .key = XKB_KEY_Up, .action = { action_step_up, NULL } }, { .key = XKB_KEY_Down, .action = { action_step_down, NULL } }, { .key = XKB_KEY_Prior, .action = { action_page_up, NULL } }, { .key = XKB_KEY_Next, .action = { action_page_down, NULL } }, { .key = XKB_KEY_c, .action = { action_skip_file, NULL } }, { .key = XKB_KEY_a, .action = { action_antialiasing, NULL } }, { .key = XKB_KEY_r, .action = { action_reload, NULL } }, { .key = XKB_KEY_i, .action = { action_info, NULL } }, { .key = XKB_KEY_Return, .action = { action_mode, NULL } }, { .key = XKB_KEY_Escape, .action = { action_exit, NULL } }, { .key = XKB_KEY_q, .action = { action_exit, NULL } }, { .key = VKEY_SCROLL_LEFT, .action = { action_step_right, NULL } }, { .key = VKEY_SCROLL_RIGHT, .action = { action_step_left, NULL } }, { .key = VKEY_SCROLL_UP, .action = { action_step_up, NULL } }, { .key = VKEY_SCROLL_DOWN, .action = { action_step_down, NULL } }, { .key = XKB_KEY_Delete, .mods = KEYMOD_SHIFT, .action = { action_none, NULL } }, }; // clang-format on // Names of virtual keys struct virtual_keys { xkb_keysym_t key; const char* name; }; static const struct virtual_keys virtual_keys[] = { { VKEY_SCROLL_UP, "ScrollUp" }, { VKEY_SCROLL_DOWN, "ScrollDown" }, { VKEY_SCROLL_LEFT, "ScrollLeft" }, { VKEY_SCROLL_RIGHT, "ScrollRight" }, }; /** Head of global key binding list. */ static struct keybind* kb_viewer; static struct keybind* kb_gallery; /** * Convert text name to key code with modifiers. * @param name key text name * @param key output keyboard key * @param mods output key modifiers (ctrl/alt/shift) * @return false if name is invalid */ static bool parse_keymod(const char* name, xkb_keysym_t* key, uint8_t* mods) { struct str_slice slices[4]; // mod[alt+ctrl+shift]+key const size_t snum = str_split(name, '+', slices, ARRAY_SIZE(slices)); if (snum == 0) { return false; } // get modifiers *mods = 0; for (size_t i = 0; i < snum - 1; ++i) { const char* mod_names[] = { "Ctrl", "Alt", "Shift" }; const ssize_t index = str_index(mod_names, slices[i].value, slices[i].len); if (index < 0) { return false; } *mods |= 1 << index; } // get key *key = xkb_keysym_from_name(slices[snum - 1].value, XKB_KEYSYM_CASE_INSENSITIVE); // check for virtual keys if (*key == XKB_KEY_NoSymbol) { for (size_t i = 0; i < ARRAY_SIZE(virtual_keys); ++i) { if (strcmp(slices[snum - 1].value, virtual_keys[i].name) == 0) { *key = virtual_keys[i].key; break; } } } // check for international symbols if (*key == XKB_KEY_NoSymbol) { wchar_t* wide = str_to_wide(slices[snum - 1].value, NULL); *key = xkb_utf32_to_keysym(wide[0]); free(wide); } return (*key != XKB_KEY_NoSymbol); } /** * Allocate new key binding. * @param key keyboard key * @param mods key modifiers (ctrl/alt/shift) * @param actions sequence of actions */ static struct keybind* create_binding(xkb_keysym_t key, uint8_t mods, struct action_seq* actions) { struct keybind* kb = calloc(1, sizeof(struct keybind)); if (!kb) { return NULL; } kb->key = key; kb->mods = mods; kb->actions = *actions; // construct help description if (kb->actions.sequence[0].type != action_none) { size_t max_len = 30; char* key_name = keybind_name(kb->key, kb->mods); if (key_name) { const struct action* action = &kb->actions.sequence[0]; // first only str_append(key_name, 0, &kb->help); str_append(": ", 0, &kb->help); str_append(action_typename(action), 0, &kb->help); if (action->params) { str_append(" ", 0, &kb->help); str_append(action->params, 0, &kb->help); } if (kb->actions.num > 1) { str_append("; ...", 0, &kb->help); } if (strlen(kb->help) > max_len) { const char* ellipsis = "..."; const size_t ellipsis_len = strlen(ellipsis); memcpy(&kb->help[max_len - ellipsis_len], ellipsis, ellipsis_len + 1); } free(key_name); } } return kb; } /** * Free key binding. * @param kb key binding */ static void free_binding(struct keybind* kb) { if (kb) { action_free(&kb->actions); free(kb->help); free(kb); } } /** * Put key binding to the global scheme. * @param kb head of binding list * @param key keyboard key * @param mods key modifiers (ctrl/alt/shift) * @param actions sequence of actions */ static void set_binding(struct keybind** head, xkb_keysym_t key, uint8_t mods, struct action_seq* actions) { struct keybind* kb; struct keybind* prev; // remove existing binding kb = *head; prev = NULL; while (kb) { if (kb->key == key && kb->mods == mods) { if (prev) { prev->next = kb->next; } else { *head = kb->next; } free_binding(kb); break; } prev = kb; kb = kb->next; } // add to head kb = create_binding(key, mods, actions); if (!kb) { action_free(actions); } else { kb->next = *head; *head = kb; } } /** * Create default binding. * @param head head of binding list * @param kb default key binding description */ static void set_default(struct keybind** head, const struct keybind_default* kb) { struct action_seq actions; if (kb->action.type == action_none && kb->key == XKB_KEY_Delete && kb->mods == KEYMOD_SHIFT) { // special action: Shift+Del actions.num = 2; actions.sequence = malloc(actions.num * sizeof(struct action)); if (actions.sequence) { actions.sequence[0].type = action_exec; actions.sequence[0].params = str_dup("rm \"%\"", NULL); actions.sequence[1].type = action_skip_file; actions.sequence[1].params = NULL; } } else { actions.num = 1; actions.sequence = malloc(actions.num * sizeof(struct action)); if (actions.sequence) { actions.sequence[0].type = kb->action.type; if (kb->action.params) { actions.sequence[0].params = str_dup(kb->action.params, NULL); } else { actions.sequence[0].params = NULL; } } } if (actions.sequence) { set_binding(head, kb->key, kb->mods, &actions); } } /** * Load binding from config parameters. * @param kb head of binding list * @param key text name of key with modifiers * @param value actions * @return load status */ static enum config_status load_binding(struct keybind** head, const char* key, const char* value) { struct action_seq actions = { 0 }; xkb_keysym_t keysym; uint8_t mods; // parse keyboard shortcut if (!parse_keymod(key, &keysym, &mods)) { return cfgst_invalid_key; } // parse actions if (!action_create(value, &actions)) { return cfgst_invalid_value; } set_binding(head, keysym, mods, &actions); return cfgst_ok; } /** Configure global key bindings, see `config_loader` for details. */ static enum config_status config_viewer(const char* key, const char* value) { return load_binding(&kb_viewer, key, value); } /** Configure global key bindings, see `config_loader` for details. */ static enum config_status config_gallery(const char* key, const char* value) { return load_binding(&kb_gallery, key, value); } void keybind_create(void) { // create default bindings for (size_t i = 0; i < ARRAY_SIZE(default_viewer); ++i) { set_default(&kb_viewer, &default_viewer[i]); } for (size_t i = 0; i < ARRAY_SIZE(default_gallery); ++i) { set_default(&kb_gallery, &default_gallery[i]); } // register configuration loaders config_add_loader("keys.viewer", config_viewer); config_add_loader("keys.gallery", config_gallery); } void keybind_destroy(void) { for (size_t i = 0; i < 2; ++i) { struct keybind* it = i ? kb_viewer : kb_gallery; while (it) { struct keybind* next = it->next; free_binding(it); it = next; } } kb_viewer = NULL; kb_gallery = NULL; } struct keybind* keybind_get(void) { return app_is_viewer() ? kb_viewer : kb_gallery; } struct keybind* keybind_find(xkb_keysym_t key, uint8_t mods) { struct keybind* it = keybind_get(); // we always use lowercase + Shift modifier key = xkb_keysym_to_lower(key); while (it) { if (it->key == key && it->mods == mods) { return it; } it = it->next; } return NULL; } char* keybind_name(xkb_keysym_t key, uint8_t mods) { char key_name[32]; char* name = NULL; // skip modifiers switch (key) { case XKB_KEY_Super_L: case XKB_KEY_Super_R: case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: case XKB_KEY_Control_L: case XKB_KEY_Control_R: case XKB_KEY_Meta_L: case XKB_KEY_Meta_R: case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: return NULL; } if (xkb_keysym_get_name(key, key_name, sizeof(key_name)) > 0) { if (mods & KEYMOD_CTRL) { str_append("Ctrl+", 0, &name); } if (mods & KEYMOD_ALT) { str_append("Alt+", 0, &name); } if (mods & KEYMOD_SHIFT) { str_append("Shift+", 0, &name); } str_append(key_name, 0, &name); } return name; } uint8_t keybind_mods(struct xkb_state* state) { uint8_t mods = 0; if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0) { mods |= KEYMOD_CTRL; } if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0) { mods |= KEYMOD_ALT; } if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0) { mods |= KEYMOD_SHIFT; } return mods; } swayimg-3.1/src/keybind.h000066400000000000000000000033071465610152200154270ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Keyboard bindings. // Copyright (C) 2023 Artem Senichev #pragma once #include "action.h" #include "config.h" #include // Key modifiers #define KEYMOD_CTRL (1 << 0) #define KEYMOD_ALT (1 << 1) #define KEYMOD_SHIFT (1 << 2) // Virtual keys used for scrolling (mouse wheel, touchpad etc) #define VKEY_SCROLL_UP 0x42000001 #define VKEY_SCROLL_DOWN 0x42000002 #define VKEY_SCROLL_LEFT 0x42000003 #define VKEY_SCROLL_RIGHT 0x42000004 /** Key binding list entry. */ struct keybind { xkb_keysym_t key; ///< Keyboard key uint8_t mods; ///< Key modifiers struct action_seq actions; ///< Sequence of action char* help; ///< Help line with binding description struct keybind* next; ///< Next entry in list }; /** * Create global default key binding scheme. */ void keybind_create(void); /** * Destroy global key binding scheme. */ void keybind_destroy(void); /** * Get head of the global binding list. * @return pointer to the list head */ struct keybind* keybind_get(void); /** * Find binding for the key. * @param key keyboard key * @param mods key modifiers (ctrl/alt/shift) * @return pointer to key binding or NULL if not found */ struct keybind* keybind_find(xkb_keysym_t key, uint8_t mods); /** * Get key name. * @param key keyboard key * @param mods key modifiers (ctrl/alt/shift) * @return text name of key, caller should free the buffer */ char* keybind_name(xkb_keysym_t key, uint8_t mods); /** * Get current key modifiers state. * @param state XKB handle * @return active key modifiers (ctrl/alt/shift) */ uint8_t keybind_mods(struct xkb_state* state); swayimg-3.1/src/loader.c000066400000000000000000000244421465610152200152460ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image loader. // Copyright (C) 2024 Artem Senichev #include "loader.h" #include "application.h" #include "buildcfg.h" #include "event.h" #include "exif.h" #include "imagelist.h" #include "str.h" #include #include #include #include #include #include #include #include #include #include #include // Construct function name of loader #define LOADER_FUNCTION(name) decode_##name // Declaration of loader function #define LOADER_DECLARE(name) \ enum loader_status LOADER_FUNCTION(name)(struct image * ctx, \ const uint8_t* data, size_t size) const char* supported_formats = "bmp, pnm, tga" #ifdef HAVE_LIBJPEG ", jpeg" #endif #ifdef HAVE_LIBPNG ", png" #endif #ifdef HAVE_LIBGIF ", gif" #endif #ifdef HAVE_LIBWEBP ", webp" #endif #ifdef HAVE_LIBRSVG ", svg" #endif #ifdef HAVE_LIBHEIF ", heif, avif" #endif #ifdef HAVE_LIBAVIF #ifndef HAVE_LIBHEIF ", avif" #endif ", avifs" #endif #ifdef HAVE_LIBJXL ", jxl" #endif #ifdef HAVE_LIBEXR ", exr" #endif #ifdef HAVE_LIBTIFF ", tiff" #endif ; // declaration of loaders LOADER_DECLARE(bmp); LOADER_DECLARE(pnm); LOADER_DECLARE(tga); #ifdef HAVE_LIBEXR LOADER_DECLARE(exr); #endif #ifdef HAVE_LIBGIF LOADER_DECLARE(gif); #endif #ifdef HAVE_LIBHEIF LOADER_DECLARE(heif); #endif #ifdef HAVE_LIBAVIF LOADER_DECLARE(avif); #endif #ifdef HAVE_LIBJPEG LOADER_DECLARE(jpeg); #endif #ifdef HAVE_LIBJXL LOADER_DECLARE(jxl); #endif #ifdef HAVE_LIBPNG LOADER_DECLARE(png); #endif #ifdef HAVE_LIBRSVG LOADER_DECLARE(svg); #endif #ifdef HAVE_LIBTIFF LOADER_DECLARE(tiff); #endif #ifdef HAVE_LIBWEBP LOADER_DECLARE(webp); #endif // list of available decoders static const image_decoder decoders[] = { #ifdef HAVE_LIBJPEG &LOADER_FUNCTION(jpeg), #endif #ifdef HAVE_LIBPNG &LOADER_FUNCTION(png), #endif #ifdef HAVE_LIBGIF &LOADER_FUNCTION(gif), #endif &LOADER_FUNCTION(bmp), &LOADER_FUNCTION(pnm), #ifdef HAVE_LIBWEBP &LOADER_FUNCTION(webp), #endif #ifdef HAVE_LIBHEIF &LOADER_FUNCTION(heif), #endif #ifdef HAVE_LIBAVIF &LOADER_FUNCTION(avif), #endif #ifdef HAVE_LIBRSVG &LOADER_FUNCTION(svg), #endif #ifdef HAVE_LIBJXL &LOADER_FUNCTION(jxl), #endif #ifdef HAVE_LIBEXR &LOADER_FUNCTION(exr), #endif #ifdef HAVE_LIBTIFF &LOADER_FUNCTION(tiff), #endif &LOADER_FUNCTION(tga), }; /** Background thread loader queue. */ struct loader_queue { size_t index; ///< Index of the image to load struct loader_queue* next; ///< Pointer to the next list entry }; /** Loader context. */ struct loader { pthread_t tid; ///< Background loader thread id struct loader_queue* queue; ///< Background thread loader queue pthread_mutex_t lock; ///< Queue access lock pthread_cond_t signal; ///< Queue notification pthread_cond_t ready; ///< Thread ready signal }; /** Global loader context instance. */ static struct loader ctx; /** * Load image from memory buffer. * @param img destination image * @param data raw image data * @param size size of image data in bytes * @return loader status */ static enum loader_status image_from_memory(struct image* img, const uint8_t* data, size_t size) { enum loader_status status = ldr_unsupported; size_t i; for (i = 0; i < ARRAY_SIZE(decoders) && status == ldr_unsupported; ++i) { status = decoders[i](img, data, size); } img->file_size = size; #ifdef HAVE_LIBEXIF process_exif(img, data, size); #endif return status; } /** * Load image from file. * @param img destination image * @param file path to the file to load * @return loader status */ static enum loader_status image_from_file(struct image* img, const char* file) { enum loader_status status = ldr_ioerror; void* data = MAP_FAILED; struct stat st; int fd; // open file and get its size fd = open(file, O_RDONLY); if (fd == -1) { return ldr_ioerror; } if (fstat(fd, &st) == -1) { close(fd); return ldr_ioerror; } // map file to memory data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (data == MAP_FAILED) { close(fd); return ldr_ioerror; } // load from mapped memory status = image_from_memory(img, data, st.st_size); munmap(data, st.st_size); close(fd); return status; } /** * Load image from stream file (stdin). * @param img destination image * @param fd file descriptor for read * @return loader status */ static enum loader_status image_from_stream(struct image* img, int fd) { enum loader_status status = ldr_ioerror; uint8_t* data = NULL; size_t size = 0; size_t capacity = 0; while (true) { ssize_t rc; if (size == capacity) { const size_t new_capacity = capacity + 256 * 1024; uint8_t* new_buf = realloc(data, new_capacity); if (!new_buf) { break; } data = new_buf; capacity = new_capacity; } rc = read(fd, data + size, capacity - size); if (rc == 0) { status = image_from_memory(img, data, size); break; } if (rc == -1 && errno != EAGAIN) { break; } size += rc; } free(data); return status; } /** * Load image from stdout printed by external command. * @param img destination image * @param cmd execution command to get stdout data * @return loader status */ static enum loader_status image_from_exec(struct image* img, const char* cmd) { enum loader_status status = ldr_ioerror; int pfd[2]; pid_t pid; if (pipe(pfd) == -1) { return ldr_ioerror; } pid = fork(); if (pid == -1) { close(pfd[1]); close(pfd[0]); return ldr_ioerror; } if (pid) { // parent close(pfd[1]); status = image_from_stream(img, pfd[0]); close(pfd[0]); waitpid(pid, NULL, 0); } else { // child const char* shell = getenv("SHELL"); if (!shell || !*shell) { shell = "/bin/sh"; } dup2(pfd[1], STDOUT_FILENO); close(pfd[1]); close(pfd[0]); execlp(shell, shell, "-c", cmd, NULL); exit(1); } return status; } enum loader_status loader_from_source(const char* source, struct image** image) { enum loader_status status; struct image* img; // create image instance img = image_create(); if (!img) { return ldr_ioerror; } img->source = str_dup(source, NULL); img->name = strrchr(img->source, '/'); if (!img->name || strcmp(img->name, "/") == 0) { img->name = img->source; } else { ++img->name; // skip slash } // decode image if (strcmp(source, LDRSRC_STDIN) == 0) { status = image_from_stream(img, STDIN_FILENO); } else if (strncmp(source, LDRSRC_EXEC, LDRSRC_EXEC_LEN) == 0) { status = image_from_exec(img, source + LDRSRC_EXEC_LEN); } else { status = image_from_file(img, source); } if (status == ldr_success) { *image = img; } else { image_free(img); } return status; } enum loader_status loader_from_index(size_t index, struct image** image) { enum loader_status status = ldr_ioerror; const char* source = image_list_get(index); if (source) { status = loader_from_source(source, image); if (status == ldr_success) { (*image)->index = index; } } return status; } /** Image loader executed in background thread. */ static void* loading_thread(__attribute__((unused)) void* data) { struct loader_queue* entry; struct image* image; do { pthread_mutex_lock(&ctx.lock); pthread_cond_signal(&ctx.ready); while (!ctx.queue) { pthread_cond_wait(&ctx.signal, &ctx.lock); if (!ctx.queue) { pthread_cond_signal(&ctx.ready); } } entry = ctx.queue; ctx.queue = ctx.queue->next; pthread_mutex_unlock(&ctx.lock); if (entry->index == IMGLIST_INVALID) { free(entry); return NULL; } image = NULL; loader_from_index(entry->index, &image); app_on_load(image, entry->index); free(entry); } while (true); return NULL; } void loader_init(void) { pthread_cond_init(&ctx.signal, NULL); pthread_cond_init(&ctx.ready, NULL); pthread_mutex_init(&ctx.lock, NULL); pthread_create(&ctx.tid, NULL, loading_thread, NULL); pthread_mutex_lock(&ctx.lock); pthread_cond_wait(&ctx.ready, &ctx.lock); pthread_mutex_unlock(&ctx.lock); } void loader_destroy(void) { if (ctx.tid) { loader_queue_reset(); loader_queue_append(IMGLIST_INVALID); // send stop signal pthread_join(ctx.tid, NULL); pthread_mutex_destroy(&ctx.lock); pthread_cond_destroy(&ctx.signal); pthread_cond_destroy(&ctx.ready); } } void loader_queue_append(size_t index) { struct loader_queue* last; struct loader_queue* request = malloc(sizeof(*request)); if (!request) { return; } request->index = index; request->next = NULL; // add to queue tail pthread_mutex_lock(&ctx.lock); last = ctx.queue; while (last && last->next) { last = last->next; } if (last) { last->next = request; } else { ctx.queue = request; } pthread_cond_signal(&ctx.signal); pthread_mutex_unlock(&ctx.lock); } void loader_queue_reset(void) { pthread_mutex_lock(&ctx.lock); while (ctx.queue) { struct loader_queue* next = ctx.queue->next; free(ctx.queue); ctx.queue = next; } pthread_cond_signal(&ctx.signal); pthread_cond_wait(&ctx.ready, &ctx.lock); pthread_mutex_unlock(&ctx.lock); } swayimg-3.1/src/loader.h000066400000000000000000000037731465610152200152570ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image loader. // Copyright (C) 2024 Artem Senichev #pragma once #include "image.h" // File name used for image, that is read from stdin through pipe #define LDRSRC_STDIN "stdin://" #define LDRSRC_STDIN_LEN (sizeof(LDRSRC_STDIN) - 1) // Special prefix used to load images from external command output #define LDRSRC_EXEC "exec://" #define LDRSRC_EXEC_LEN (sizeof(LDRSRC_EXEC) - 1) /** Loader status. */ enum loader_status { ldr_success, ///< Image was decoded successfully ldr_unsupported, ///< Unsupported format ldr_fmterror, ///< Invalid data format ldr_ioerror ///< IO errors }; /** Contains string with the names of the supported image formats. */ extern const char* supported_formats; /** * Image loader function prototype, implemented by decoders. * @param image target image instance * @param data raw image data * @param size size of image data in bytes * @return loader status */ typedef enum loader_status (*image_decoder)(struct image* image, const uint8_t* data, size_t size); /** * Initialize background thread loader. */ void loader_init(void); /** * Destroy background thread loader. */ void loader_destroy(void); /** * Load image from specified source. * @param source image data source: path to the file, exec command, etc * @param image pointer to output image instance * @return loading status */ enum loader_status loader_from_source(const char* source, struct image** image); /** * Load image with specified index in the image list. * @param index index of the entry in the image list * @param image pointer to output image instance * @return loading status */ enum loader_status loader_from_index(size_t index, struct image** image); /** * Append image to background loader queue. * @param index index of the image in the image list */ void loader_queue_append(size_t index); /** * Reset background loader queue. */ void loader_queue_reset(void); swayimg-3.1/src/main.c000066400000000000000000000141421465610152200147200ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Program entry point. // Copyright (C) 2020 Artem Senichev #include "application.h" #include "buildcfg.h" #include "config.h" #include "imagelist.h" #include "loader.h" #include "ui.h" #include "viewer.h" #include #include #include #include #include /** Command line options. */ struct cmdarg { const char short_opt; ///< Short option character const char* long_opt; ///< Long option name const char* format; ///< Format description const char* help; ///< Help string const char* section; ///< Section name of the param const char* key; ///< Key of the param const char* value; ///< Static value to set }; // clang-format off static const struct cmdarg arguments[] = { { 'g', "gallery", NULL, "start in gallery mode", APP_CFG_SECTION, APP_CFG_MODE, APP_MODE_GALLERY }, { 'r', "recursive", NULL, "read directories recursively", IMGLIST_CFG_SECTION, IMGLIST_CFG_RECURSIVE, "yes" }, { 'o', "order", "ORDER", "set sort order for image list: none/[alpha]/random", IMGLIST_CFG_SECTION, IMGLIST_CFG_ORDER, NULL }, { 's', "scale", "SCALE", "set initial image scale: [optimal]/fit/width/height/fill/real", VIEWER_CFG_SECTION, VIEWER_CFG_SCALE, NULL }, { 'l', "slideshow", NULL, "activate slideshow mode on startup", VIEWER_CFG_SECTION, VIEWER_CFG_SLIDESHOW, "yes" }, { 'p', "position", "POS", "set window position [parent]/X,Y", APP_CFG_SECTION, APP_CFG_POSITION, NULL }, { 'w', "size", "SIZE", "set window size: fullscreen/[parent]/image/W,H", APP_CFG_SECTION, APP_CFG_SIZE, NULL }, { 'f', "fullscreen", NULL, "show image in full screen mode", APP_CFG_SECTION, APP_CFG_SIZE, APP_FULLSCREEN }, { 'a', "class", "NAME", "set window class/app_id", APP_CFG_SECTION, APP_CFG_APP_ID, NULL }, { 'c', "config", "S.K=V", "set configuration parameter: section.key=value", NULL, NULL, NULL }, { 'v', "version", NULL, "print version info and exit", NULL, NULL, NULL }, { 'h', "help", NULL, "print this help and exit", NULL, NULL, NULL } }; // clang-format on /** * Print usage info. */ static void print_help(void) { puts("Usage: " APP_NAME " [OPTION]... [FILE]..."); puts("Show images from FILE(s)."); puts("If FILE is -, read standard input."); puts("If no FILE specified - read all files from the current directory.\n"); puts("Mandatory arguments to long options are mandatory for short options " "too."); for (size_t i = 0; i < sizeof(arguments) / sizeof(arguments[0]); ++i) { const struct cmdarg* arg = &arguments[i]; char lopt[32]; if (arg->format) { snprintf(lopt, sizeof(lopt), "%s=%s", arg->long_opt, arg->format); } else { strncpy(lopt, arg->long_opt, sizeof(lopt) - 1); } printf(" -%c, --%-14s %s\n", arg->short_opt, lopt, arg->help); } } /** * Parse command line arguments into configuration instance. * @param argc number of arguments to parse * @param argv arguments array * @return index of the first non option argument, or -1 if error, or 0 to exit */ static int parse_cmdargs(int argc, char* argv[]) { struct option options[1 + (sizeof(arguments) / sizeof(arguments[0]))]; char short_opts[(sizeof(arguments) / sizeof(arguments[0])) * 2]; char* short_opts_ptr = short_opts; int opt; for (size_t i = 0; i < sizeof(arguments) / sizeof(arguments[0]); ++i) { const struct cmdarg* arg = &arguments[i]; // compose array of option structs options[i].name = arg->long_opt; options[i].has_arg = arg->format ? required_argument : no_argument; options[i].flag = NULL; options[i].val = arg->short_opt; // compose short options string *short_opts_ptr++ = arg->short_opt; if (arg->format) { *short_opts_ptr++ = ':'; } } // add terminations *short_opts_ptr = 0; memset(&options[(sizeof(arguments) / sizeof(arguments[0]))], 0, sizeof(struct option)); // parse arguments while ((opt = getopt_long(argc, argv, short_opts, options, NULL)) != -1) { const struct cmdarg* arg; if (opt == '?') { return -1; } // get argument description arg = arguments; while (arg->short_opt != opt) { ++arg; } if (arg->section) { if (config_set(arg->section, arg->key, arg->value ? arg->value : optarg) != cfgst_ok) { return -1; } continue; } switch (opt) { case 'c': if (!config_command(optarg)) { fprintf(stderr, "Invalid config: \"%s\"\n", optarg); return -1; } break; case 'v': puts(APP_NAME " version " APP_VERSION "."); puts("https://github.com/artemsen/swayimg"); printf("Supported formats: %s.\n", supported_formats); return 0; case 'h': print_help(); return 0; default: return -1; } } return optind; } /** * Application entry point. */ int main(int argc, char* argv[]) { bool rc = false; int argn; setlocale(LC_ALL, ""); app_create(); config_load(); argn = parse_cmdargs(argc, argv); config_destroy(); if (argn <= 0) { // args error or requested version/help rc = (argn == 0); } else { // initialize and run main event loop handler rc = app_init((const char**)&argv[argn], argc - argn) && app_run(); } app_destroy(); return rc ? EXIT_SUCCESS : EXIT_FAILURE; } swayimg-3.1/src/pixmap.c000066400000000000000000000447521465610152200153040ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Pixel map. // Copyright (C) 2024 Artem Senichev #include "pixmap.h" #include #include /** * Alpha blending. * @param img color of image's pixel * @param wnd pointer to window buffer to put puxel */ static inline void alpha_blend(argb_t src, argb_t* dst) { const uint8_t ai = ARGB_GET_A(src); if (ai != 0xff) { const argb_t wp = *dst; const uint8_t aw = ARGB_GET_A(wp); const uint8_t target_alpha = max(ai, aw); const argb_t inv = 255 - ai; src = ARGB_SET_A(target_alpha) | ARGB_SET_R((ai * ARGB_GET_R(src) + inv * ARGB_GET_R(wp)) / 255) | ARGB_SET_G((ai * ARGB_GET_G(src) + inv * ARGB_GET_G(wp)) / 255) | ARGB_SET_B((ai * ARGB_GET_B(src) + inv * ARGB_GET_B(wp)) / 255); } *dst = src; } bool pixmap_create(struct pixmap* pm, size_t width, size_t height) { argb_t* data = calloc(1, height * width * sizeof(argb_t)); if (data) { pm->width = width; pm->height = height; pm->data = data; } return !!data; } void pixmap_free(struct pixmap* pm) { free(pm->data); } void pixmap_fill(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, size_t height, argb_t color) { const ssize_t left = max(0, x); const ssize_t top = max(0, y); const ssize_t right = min((ssize_t)pm->width, (ssize_t)width + x); const ssize_t bottom = min((ssize_t)pm->height, (ssize_t)height + y); const ssize_t fill_width = right - left; const ssize_t fill_height = bottom - top; const size_t template_sz = fill_width * sizeof(argb_t); argb_t* template = &pm->data[top * pm->width + left]; if (right < 0 || bottom < 0 || fill_width <= 0 || fill_height <= 0) { return; } // compose and copy template line for (x = 0; x < fill_width; ++x) { template[x] = color; } for (y = top + 1; y < bottom; ++y) { memcpy(&pm->data[y * pm->width + left], template, template_sz); } } void pixmap_inverse_fill(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, size_t height, argb_t color) { const ssize_t left = max(0, x); const ssize_t top = max(0, y); const ssize_t right = min((ssize_t)pm->width, (ssize_t)width + x); const ssize_t bottom = min((ssize_t)pm->height, (ssize_t)height + y); if (left > 0) { pixmap_fill(pm, 0, top, left, bottom - top, color); } if (right < (ssize_t)pm->width) { pixmap_fill(pm, right, top, pm->width - right, bottom - top, color); } if (top > 0) { pixmap_fill(pm, 0, 0, pm->width, top, color); } if (bottom < (ssize_t)pm->height) { pixmap_fill(pm, 0, bottom, pm->width, pm->height - bottom, color); } } void pixmap_hline(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, argb_t color) { if (y >= 0 && y < (ssize_t)pm->height) { const ssize_t begin = max(0, x); const ssize_t end = min((ssize_t)pm->width, x + (ssize_t)width); const size_t offset = y * pm->width; for (ssize_t i = begin; i < end; ++i) { alpha_blend(color, &pm->data[offset + i]); } } } void pixmap_vline(struct pixmap* pm, ssize_t x, ssize_t y, size_t height, argb_t color) { if (x >= 0 && x < (ssize_t)pm->width) { const ssize_t begin = max(0, y); const ssize_t end = min((ssize_t)pm->height, y + (ssize_t)height); for (ssize_t i = begin; i < end; ++i) { alpha_blend(color, &pm->data[i * pm->width + x]); } } } void pixmap_rect(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, size_t height, argb_t color) { pixmap_hline(pm, x, y, width, color); pixmap_hline(pm, x, y + height - 1, width, color); pixmap_vline(pm, x, y + 1, height - 1, color); pixmap_vline(pm, x + width - 1, y + 1, height - 1, color); } void pixmap_grid(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, size_t height, size_t tail_sz, argb_t color1, argb_t color2) { const ssize_t left = max(0, x); const ssize_t top = max(0, y); const ssize_t right = min((ssize_t)pm->width, (ssize_t)width + x); const ssize_t bottom = min((ssize_t)pm->height, (ssize_t)height + y); const ssize_t grid_width = right - left; const ssize_t grid_height = bottom - top; const size_t template_sz = grid_width * sizeof(argb_t); argb_t* templates[] = { &pm->data[top * pm->width + left], &pm->data[(top + tail_sz) * pm->width + left] }; if (right < 0 || bottom < 0 || grid_width <= 0 || grid_height <= 0) { return; } for (y = 0; y < grid_height; ++y) { const size_t shift = (y / tail_sz) % 2; argb_t* line = &pm->data[(y + top) * pm->width + left]; if (line != templates[0] && line != templates[1]) { // put template line memcpy(line, templates[shift], template_sz); } else { // compose template line for (x = 0; x < grid_width; ++x) { const size_t tail = x / tail_sz; line[x] = (tail % 2) ^ shift ? color1 : color2; } } } } void pixmap_apply_mask(struct pixmap* dst, ssize_t x, ssize_t y, const uint8_t* mask, size_t width, size_t height, argb_t color) { const ssize_t left = max(0, x); const ssize_t top = max(0, y); const ssize_t right = min((ssize_t)dst->width, x + (ssize_t)width); const ssize_t bottom = min((ssize_t)dst->height, y + (ssize_t)height); const ssize_t dst_width = right - left; const ssize_t delta_x = left - x; const ssize_t delta_y = top - y; for (ssize_t dst_y = top; dst_y < bottom; ++dst_y) { const size_t src_y = dst_y - top + delta_y; const uint8_t* mask_line = &mask[src_y * width + delta_x]; argb_t* dst_line = &dst->data[dst_y * dst->width + left]; for (x = 0; x < dst_width; ++x) { const uint8_t alpha_mask = mask_line[x]; if (alpha_mask != 0) { const uint8_t alpha_color = ARGB_GET_A(color); const uint8_t alpha = (alpha_mask * alpha_color) / 255; const argb_t clr = ARGB_SET_A(alpha) | (color & 0x00ffffff); alpha_blend(clr, &dst_line[x]); } } } } void pixmap_copy(const struct pixmap* src, struct pixmap* dst, ssize_t x, ssize_t y, bool alpha) { const ssize_t left = max(0, x); const ssize_t top = max(0, y); const ssize_t right = min((ssize_t)dst->width, x + (ssize_t)src->width); const ssize_t bottom = min((ssize_t)dst->height, y + (ssize_t)src->height); const ssize_t dst_width = right - left; const ssize_t delta_x = left - x; const ssize_t delta_y = top - y; const size_t line_sz = dst_width * sizeof(argb_t); for (ssize_t dst_y = top; dst_y < bottom; ++dst_y) { const size_t src_y = dst_y - top + delta_y; const argb_t* src_line = &src->data[src_y * src->width + delta_x]; argb_t* dst_line = &dst->data[dst_y * dst->width + left]; if (alpha) { for (x = 0; x < dst_width; ++x) { alpha_blend(src_line[x], &dst_line[x]); } } else { memcpy(dst_line, src_line, line_sz); } } } void pixmap_scale_nearest(const struct pixmap* src, struct pixmap* dst, ssize_t x, ssize_t y, float scale, bool alpha) { const ssize_t left = max(0, x); const ssize_t top = max(0, y); const ssize_t right = min((ssize_t)dst->width, x + scale * src->width); const ssize_t bottom = min((ssize_t)dst->height, y + scale * src->height); const ssize_t delta_x = left - x; const ssize_t delta_y = top - y; for (ssize_t dst_y = top; dst_y < bottom; ++dst_y) { const size_t src_y = (float)(dst_y - top + delta_y) / scale; const argb_t* src_line = &src->data[src_y * src->width]; argb_t* dst_line = &dst->data[dst_y * dst->width]; for (ssize_t dst_x = left; dst_x < right; ++dst_x) { const size_t src_x = (float)(dst_x - left + delta_x) / scale; const argb_t color = src_line[src_x]; if (alpha) { alpha_blend(color, &dst_line[dst_x]); } else { dst_line[dst_x] = ARGB_SET_A(0xff) | color; } } } } void pixmap_scale_bicubic(const struct pixmap* src, struct pixmap* dst, ssize_t x, ssize_t y, float scale, bool alpha) { const ssize_t left = max(0, x); const ssize_t top = max(0, y); const ssize_t right = min((ssize_t)dst->width, x + scale * src->width); const ssize_t bottom = min((ssize_t)dst->height, y + scale * src->height); const ssize_t delta_x = left - x; const ssize_t delta_y = top - y; size_t state_zero_x = 1; size_t state_zero_y = 1; float state[4][4][4] = { 0 }; // color channel, y, x for (ssize_t dst_y = top; dst_y < bottom; ++dst_y) { argb_t* dst_line = &dst->data[dst_y * dst->width]; const double scaled_y = (double)(dst_y - top + delta_y) / scale; const size_t fixed_y = (size_t)scaled_y; const float diff_y = scaled_y - fixed_y; const float diff_y2 = diff_y * diff_y; const float diff_y3 = diff_y * diff_y2; for (ssize_t dst_x = left; dst_x < right; ++dst_x) { const double scaled_x = (double)(dst_x - left + delta_x) / scale; const size_t fixed_x = (size_t)scaled_x; const float diff_x = scaled_x - fixed_x; const float diff_x2 = diff_x * diff_x; const float diff_x3 = diff_x * diff_x2; argb_t fg = 0; // update cached state if (state_zero_x != fixed_x || state_zero_y != fixed_y) { float pixels[4][4][4]; // color channel, y, x state_zero_x = fixed_x; state_zero_y = fixed_y; for (size_t pc = 0; pc < 4; ++pc) { // get colors for the current area for (size_t py = 0; py < 4; ++py) { size_t iy = fixed_y + py; if (iy > 0) { --iy; if (iy >= src->height) { iy = src->height - 1; } } for (size_t px = 0; px < 4; ++px) { size_t ix = fixed_x + px; if (ix > 0) { --ix; if (ix >= src->width) { ix = src->width - 1; } } const argb_t pixel = src->data[iy * src->width + ix]; pixels[pc][py][px] = (pixel >> (pc * 8)) & 0xff; } } // recalc state cache for the current area // clang-format off state[pc][0][0] = pixels[pc][1][1]; state[pc][0][1] = -0.5 * pixels[pc][1][0] + 0.5 * pixels[pc][1][2]; state[pc][0][2] = pixels[pc][1][0] - 2.5 * pixels[pc][1][1] + 2.0 * pixels[pc][1][2] - 0.5 * pixels[pc][1][3]; state[pc][0][3] = -0.5 * pixels[pc][1][0] + 1.5 * pixels[pc][1][1] - 1.5 * pixels[pc][1][2] + 0.5 * pixels[pc][1][3]; state[pc][1][0] = -0.5 * pixels[pc][0][1] + 0.5 * pixels[pc][2][1]; state[pc][1][1] = 0.25 * pixels[pc][0][0] - 0.25 * pixels[pc][0][2] - 0.25 * pixels[pc][2][0] + 0.25 * pixels[pc][2][2]; state[pc][1][2] = -0.5 * pixels[pc][0][0] + 1.25 * pixels[pc][0][1] - pixels[pc][0][2] + 0.25 * pixels[pc][0][3] + 0.5 * pixels[pc][2][0] - 1.25 * pixels[pc][2][1] + pixels[pc][2][2] - 0.25 * pixels[pc][2][3]; state[pc][1][3] = 0.25 * pixels[pc][0][0] - 0.75 * pixels[pc][0][1] + 0.75 * pixels[pc][0][2] - 0.25 * pixels[pc][0][3] - 0.25 * pixels[pc][2][0] + 0.75 * pixels[pc][2][1] - 0.75 * pixels[pc][2][2] + 0.25 * pixels[pc][2][3]; state[pc][2][0] = pixels[pc][0][1] - 2.5 * pixels[pc][1][1] + 2.0 * pixels[pc][2][1] - 0.5 * pixels[pc][3][1]; state[pc][2][1] = -0.5 * pixels[pc][0][0] + 0.5 * pixels[pc][0][2] + 1.25 * pixels[pc][1][0] - 1.25 * pixels[pc][1][2] - pixels[pc][2][0] + pixels[pc][2][2] + 0.25 * pixels[pc][3][0] - 0.25 * pixels[pc][3][2]; state[pc][2][2] = pixels[pc][0][0] - 2.5 * pixels[pc][0][1] + 2.0 * pixels[pc][0][2] - 0.5 * pixels[pc][0][3] - 2.5 * pixels[pc][1][0] + 6.25 * pixels[pc][1][1] - 5.0 * pixels[pc][1][2] + 1.25 * pixels[pc][1][3] + 2.0 * pixels[pc][2][0] - 5.0 * pixels[pc][2][1] + 4.0 * pixels[pc][2][2] - pixels[pc][2][3] - 0.5 * pixels[pc][3][0] + 1.25 * pixels[pc][3][1] - pixels[pc][3][2] + 0.25 * pixels[pc][3][3]; state[pc][2][3] = -0.5 * pixels[pc][0][0] + 1.5 * pixels[pc][0][1] - 1.5 * pixels[pc][0][2] + 0.5 * pixels[pc][0][3] + 1.25 * pixels[pc][1][0] - 3.75 * pixels[pc][1][1] + 3.75 * pixels[pc][1][2] - 1.25 * pixels[pc][1][3] - pixels[pc][2][0] + 3.0 * pixels[pc][2][1] - 3.0 * pixels[pc][2][2] + pixels[pc][2][3] + 0.25 * pixels[pc][3][0] - 0.75 * pixels[pc][3][1] + 0.75 * pixels[pc][3][2] - 0.25 * pixels[pc][3][3]; state[pc][3][0] = -0.5 * pixels[pc][0][1] + 1.5 * pixels[pc][1][1] - 1.5 * pixels[pc][2][1] + 0.5 * pixels[pc][3][1]; state[pc][3][1] = 0.25 * pixels[pc][0][0] - 0.25 * pixels[pc][0][2] - 0.75 * pixels[pc][1][0] + 0.75 * pixels[pc][1][2] + 0.75 * pixels[pc][2][0] - 0.75 * pixels[pc][2][2] - 0.25 * pixels[pc][3][0] + 0.25 * pixels[pc][3][2]; state[pc][3][2] = -0.5 * pixels[pc][0][0] + 1.25 * pixels[pc][0][1] - pixels[pc][0][2] + 0.25 * pixels[pc][0][3] + 1.5 * pixels[pc][1][0] - 3.75 * pixels[pc][1][1] + 3.0 * pixels[pc][1][2] - 0.75 * pixels[pc][1][3] - 1.5 * pixels[pc][2][0] + 3.75 * pixels[pc][2][1] - 3.0 * pixels[pc][2][2] + 0.75 * pixels[pc][2][3] + 0.5 * pixels[pc][3][0] - 1.25 * pixels[pc][3][1] + pixels[pc][3][2] - 0.25 * pixels[pc][3][3]; state[pc][3][3] = 0.25 * pixels[pc][0][0] - 0.75 * pixels[pc][0][1] + 0.75 * pixels[pc][0][2] - 0.25 * pixels[pc][0][3] - 0.75 * pixels[pc][1][0] + 2.25 * pixels[pc][1][1] - 2.25 * pixels[pc][1][2] + 0.75 * pixels[pc][1][3] + 0.75 * pixels[pc][2][0] - 2.25 * pixels[pc][2][1] + 2.25 * pixels[pc][2][2] - 0.75 * pixels[pc][2][3] - 0.25 * pixels[pc][3][0] + 0.75 * pixels[pc][3][1] - 0.75 * pixels[pc][3][2] + 0.25 * pixels[pc][3][3]; // clang-format on } } // set pixel for (size_t pc = 0; pc < 4; ++pc) { // clang-format off const float inter = (state[pc][0][0] + state[pc][0][1] * diff_x + state[pc][0][2] * diff_x2 + state[pc][0][3] * diff_x3) + (state[pc][1][0] + state[pc][1][1] * diff_x + state[pc][1][2] * diff_x2 + state[pc][1][3] * diff_x3) * diff_y + (state[pc][2][0] + state[pc][2][1] * diff_x + state[pc][2][2] * diff_x2 + state[pc][2][3] * diff_x3) * diff_y2 + (state[pc][3][0] + state[pc][3][1] * diff_x + state[pc][3][2] * diff_x2 + state[pc][3][3] * diff_x3) * diff_y3; // clang-format on const uint8_t color = max(min(inter, 255), 0); fg |= (color << (pc * 8)); } if (alpha) { alpha_blend(fg, &dst_line[dst_x]); } else { dst_line[dst_x] = ARGB_SET_A(0xff) | fg; } } } } void pixmap_flip_vertical(struct pixmap* pm) { void* buffer; const size_t stride = pm->width * sizeof(argb_t); buffer = malloc(stride); if (buffer) { for (size_t y = 0; y < pm->height / 2; ++y) { argb_t* src = &pm->data[y * pm->width]; argb_t* dst = &pm->data[(pm->height - y - 1) * pm->width]; memcpy(buffer, dst, stride); memcpy(dst, src, stride); memcpy(src, buffer, stride); } free(buffer); } } void pixmap_flip_horizontal(struct pixmap* pm) { for (size_t y = 0; y < pm->height; ++y) { argb_t* line = &pm->data[y * pm->width]; for (size_t x = 0; x < pm->width / 2; ++x) { argb_t* left = &line[x]; argb_t* right = &line[pm->width - x - 1]; const argb_t swap = *left; *left = *right; *right = swap; } } } void pixmap_rotate(struct pixmap* pm, size_t angle) { const size_t pixels = pm->width * pm->height; if (angle == 180) { for (size_t i = 0; i < pixels / 2; ++i) { argb_t* color1 = &pm->data[i]; argb_t* color2 = &pm->data[pixels - i - 1]; const argb_t swap = *color1; *color1 = *color2; *color2 = swap; } } else if (angle == 90 || angle == 270) { argb_t* data = malloc(pm->height * pm->width * sizeof(argb_t)); if (data) { const size_t width = pm->height; const size_t height = pm->width; for (size_t y = 0; y < pm->height; ++y) { for (size_t x = 0; x < pm->width; ++x) { size_t pos; if (angle == 90) { pos = x * width + (width - y - 1); } else { pos = (height - x - 1) * width + y; } data[pos] = pm->data[y * pm->width + x]; } } free(pm->data); pm->width = width; pm->height = height; pm->data = data; } } } swayimg-3.1/src/pixmap.h000066400000000000000000000131551465610152200153020ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Pixel map. // Copyright (C) 2024 Artem Senichev #pragma once #include #include #include #include /** ARGB color. */ typedef uint32_t argb_t; // shifts for each channel in argb_t #define ARGB_A_SHIFT 24 #define ARGB_R_SHIFT 16 #define ARGB_G_SHIFT 8 #define ARGB_B_SHIFT 0 // get channel value from argb_t #define ARGB_GET_A(c) (((c) >> ARGB_A_SHIFT) & 0xff) #define ARGB_GET_R(c) (((c) >> ARGB_R_SHIFT) & 0xff) #define ARGB_GET_G(c) (((c) >> ARGB_G_SHIFT) & 0xff) #define ARGB_GET_B(c) (((c) >> ARGB_B_SHIFT) & 0xff) // create argb_t from channel value #define ARGB_SET_A(a) (((argb_t)(a) & 0xff) << ARGB_A_SHIFT) #define ARGB_SET_R(r) (((argb_t)(r) & 0xff) << ARGB_R_SHIFT) #define ARGB_SET_G(g) (((argb_t)(g) & 0xff) << ARGB_G_SHIFT) #define ARGB_SET_B(b) (((argb_t)(b) & 0xff) << ARGB_B_SHIFT) #define ARGB(a, r, g, b) \ (ARGB_SET_A(a) | ARGB_SET_R(r) | ARGB_SET_G(g) | ARGB_SET_B(b)) // convert ABGR to ARGB #define ABGR_TO_ARGB(c) \ ((c & 0xff00ff00) | ARGB_SET_R(ARGB_GET_B(c)) | ARGB_SET_B(ARGB_GET_R(c))) #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) /** Size description. */ struct size { size_t width; size_t height; }; /** Rectangle description. */ struct rect { ssize_t x; ssize_t y; size_t width; size_t height; }; /** Pixel map. */ struct pixmap { size_t width; ///< Width (px) size_t height; ///< Height (px) argb_t* data; ///< Pixel data }; /** * Allocate/reallocate pixel map. * @param pm pixmap context to create * @param width,height pixmap size * @return true pixmap was allocated */ bool pixmap_create(struct pixmap* pm, size_t width, size_t height); /** * Free pixel map created with `pixmap_create`. * @param pm pixmap context to free */ void pixmap_free(struct pixmap* pm); /** * Fill area with specified color. * @param pm pixmap context * @param x,y start coordinates, left top point * @param width,height region size * @param color color to set */ void pixmap_fill(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, size_t height, argb_t color); /** * Fill whole pixmap except specified area. * @param pm pixmap context * @param x,y top left corner of excluded area * @param width,height excluded area size * @param color color to set */ void pixmap_inverse_fill(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, size_t height, argb_t color); /** * Draw horizontal line. * @param pm pixmap context * @param x,y start coordinates, left top point * @param width line size * @param color color to use */ void pixmap_hline(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, argb_t color); /** * Draw vertical line. * @param pm pixmap context * @param x,y start coordinates, left top point * @param height line size * @param color color to use */ void pixmap_vline(struct pixmap* pm, ssize_t x, ssize_t y, size_t height, argb_t color); /** * Draw rectangle with 1px lines. * @param pm pixmap context * @param x,y start coordinates, left top point * @param width,height rectangle size * @param color color to use */ void pixmap_rect(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, size_t height, argb_t color); /** * Fill pixmap with grid. * @param pm pixmap context * @param x,y start coordinates, left top point * @param width,height region size * @param tail_sz size of a single tail * @param color0 first grid color * @param color1 second grid color */ void pixmap_grid(struct pixmap* pm, ssize_t x, ssize_t y, size_t width, size_t height, size_t tail_sz, argb_t color0, argb_t color1); /** * Apply mask to pixmap: change color according alpha channel. * @param dst destination pixmap * @param x,y destination left top point * @param mask array with alpha channel mask * @param width,height mask size * @param color color to set */ void pixmap_apply_mask(struct pixmap* dst, ssize_t x, ssize_t y, const uint8_t* mask, size_t width, size_t height, argb_t color); /** * Draw one pixmap on another. * @param src source pixmap * @param dst destination pixmap * @param x,y destination left top coordinates * @param alpha flag to use alpha blending */ void pixmap_copy(const struct pixmap* src, struct pixmap* dst, ssize_t x, ssize_t y, bool alpha); /** * Draw scaled pixmap: nearest filter, poor quality but fast. * @param src source pixmap * @param dst destination pixmap * @param x,y destination left top coordinates * @param scale scale of source pixmap * @param alpha flag to use alpha blending */ void pixmap_scale_nearest(const struct pixmap* src, struct pixmap* dst, ssize_t x, ssize_t y, float scale, bool alpha); /** * Draw scaled pixmap: bicubic filter, good quality but slow. * @param src source pixmap * @param dst destination pixmap * @param x,y destination left top coordinates * @param scale scale of source pixmap * @param alpha flag to use alpha blending */ void pixmap_scale_bicubic(const struct pixmap* src, struct pixmap* dst, ssize_t x, ssize_t y, float scale, bool alpha); /** * Flip pixel map vertically. * @param pm pixmap context */ void pixmap_flip_vertical(struct pixmap* pm); /** * Flip pixel map horizontally. * @param pm pixmap context */ void pixmap_flip_horizontal(struct pixmap* pm); /** * Rotate pixel map. * @param pm pixmap context * @param angle rotation angle (only 90, 180, or 270) */ void pixmap_rotate(struct pixmap* pm, size_t angle); swayimg-3.1/src/str.c000066400000000000000000000070101465610152200146000ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // String operations. // Copyright (C) 2024 Artem Senichev #include "str.h" #include #include #include #include #include #include #include char* str_dup(const char* src, char** dst) { const size_t sz = strlen(src) + 1; char* buffer = realloc(dst ? *dst : NULL, sz); if (buffer) { memcpy(buffer, src, sz); if (dst) { *dst = buffer; } } return buffer; } char* str_append(const char* src, size_t len, char** dst) { const size_t src_len = len ? len : strlen(src); const size_t dst_len = dst && *dst ? strlen(*dst) : 0; const size_t buf_len = dst_len + src_len + 1 /* last null */; char* buffer = realloc(dst ? *dst : NULL, buf_len); if (buffer) { memcpy(buffer + dst_len, src, src_len); buffer[buf_len - 1] = 0; if (dst) { *dst = buffer; } } return buffer; } bool str_to_num(const char* text, size_t len, ssize_t* value, int base) { char* endptr; long long num; char buffer[32]; const char* ptr; if (!text) { return false; } if (!*text) { return false; } if (len == 0) { ptr = text; } else { if (len >= sizeof(buffer)) { len = sizeof(buffer) - 1; } memcpy(buffer, text, len); buffer[len] = 0; ptr = buffer; } errno = 0; num = strtoll(ptr, &endptr, base); if (!*endptr && errno == 0) { *value = num; return true; } return false; } wchar_t* str_to_wide(const char* src, wchar_t** dst) { wchar_t* buffer; size_t len; len = mbstowcs(NULL, src, 0); if (len == (size_t)-1) { return NULL; } ++len; // last null buffer = realloc(dst ? *dst : NULL, len * sizeof(wchar_t)); if (!buffer) { return NULL; } mbstowcs(buffer, src, len); if (dst) { *dst = buffer; } return buffer; } size_t str_split(const char* text, char delimeter, struct str_slice* slices, size_t max_slices) { size_t slice_num = 0; while (*text) { struct str_slice slice; // skip spaces while (*text && isspace(*text)) { ++text; } if (!*text) { break; } // construct slice if (*text == delimeter) { // empty value slice.value = ""; slice.len = 0; } else { slice.value = text; while (*text && *text != delimeter) { ++text; } slice.len = text - slice.value; // trim spaces while (slice.len && isspace(slice.value[slice.len - 1])) { --slice.len; } } // add to output array if (slices && slice_num < max_slices) { memcpy(&slices[slice_num], &slice, sizeof(slice)); } ++slice_num; if (*text) { ++text; // skip delimiter } } return slice_num; } ssize_t str_search_index(const char** array, size_t array_sz, const char* value, size_t value_len) { if (value_len == 0) { value_len = strlen(value); } for (size_t i = 0; i < array_sz; ++i) { const char* check = array[i]; if (strlen(check) == value_len && strncmp(value, check, value_len) == 0) { return i; } } return -1; } swayimg-3.1/src/str.h000066400000000000000000000042151465610152200146110ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // String operations. // Copyright (C) 2024 Artem Senichev #pragma once #include #include #include #ifndef ARRAY_SIZE #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) #endif /** String slice. */ struct str_slice { const char* value; size_t len; }; /** * Duplicate string. * @param src source string to duplicate * @param dst pointer to destination buffer * @return pointer to new string, caller must free it */ char* str_dup(const char* src, char** dst); /** * Append string. * @param src source string to append * @param dst pointer to destination buffer * @return pointer to merged string, caller must free it */ char* str_append(const char* src, size_t len, char** dst); /** * Convert text string to number. * @param text text to convert * @param len length of the source string (0=auto) * @param value output variable * @param base numeric base * @return false if text has invalid format */ bool str_to_num(const char* text, size_t len, ssize_t* value, int base); /** * Convert ansi string to wide char format. * @param src source string to encode * @param dst pointer to destination buffer * @return pointer to wide string, caller must free it */ wchar_t* str_to_wide(const char* src, wchar_t** dst); /** * Split string ("abc,def" -> "abc", "def"). * @param text source string to split * @param delimiter delimiter character * @param slices output array of slices * @param max_slices max number of slices (size of array) * @return real number of slices in source string */ size_t str_split(const char* text, char delimeter, struct str_slice* slices, size_t max_slices); /** * Search for value in string array. * @param array source array of strings * @param array_sz number of strings in array * @param value text to search * @param value_len length of the value (0=auto) * @return index of value in array or -1 if not found */ ssize_t str_search_index(const char** array, size_t array_sz, const char* value, size_t value_len); #define str_index(a, v, s) str_search_index((a), ARRAY_SIZE(a), v, s) swayimg-3.1/src/sway.c000066400000000000000000000225551465610152200147660ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Integration with Sway WM. // Copyright (C) 2020 Artem Senichev #include "sway.h" #include #include #include #include #include #include #include #include /** IPC magic header value */ static const uint8_t ipc_magic[] = { 'i', '3', '-', 'i', 'p', 'c' }; /** IPC message types (used only) */ enum ipc_msg_type { IPC_COMMAND = 0, IPC_GET_WORKSPACES = 1, IPC_GET_TREE = 4 }; /** IPC header */ struct __attribute__((__packed__)) ipc_header { uint8_t magic[sizeof(ipc_magic)]; uint32_t len; uint32_t type; }; /** * Read exactly specified number of bytes from socket. * @param fd socket descriptor * @param buf buffer for destination data * @param len number of bytes to read * @return true if operation completed successfully */ static bool sock_read(int fd, void* buf, size_t len) { while (len) { const ssize_t rcv = recv(fd, buf, len, 0); if (rcv == 0) { fprintf(stderr, "IPC error: no data\n"); return false; } if (rcv == -1) { const int ec = errno; fprintf(stderr, "IPC read error: [%i] %s\n", ec, strerror(ec)); return false; } len -= rcv; buf = ((uint8_t*)buf) + rcv; } return true; } /** * Write data to the socket. * @param fd socket descriptor * @param buf buffer of data of send * @param len number of bytes to write * @return true if operation completed successfully */ static bool sock_write(int fd, const void* buf, size_t len) { while (len) { const ssize_t rcv = write(fd, buf, len); if (rcv == -1) { const int ec = errno; fprintf(stderr, "IPC write error: [%i] %s\n", ec, strerror(ec)); return false; } len -= rcv; buf = ((uint8_t*)buf) + rcv; } return true; } /** * IPC message exchange. * @param ipc IPC context (socket file descriptor) * @param type message type * @param payload payload data * @return IPC response as json object, NULL on errors */ static struct json_object* ipc_message(int ipc, enum ipc_msg_type type, const char* payload) { struct ipc_header hdr; memcpy(hdr.magic, ipc_magic, sizeof(ipc_magic)); hdr.len = payload ? strlen(payload) : 0; hdr.type = type; // send request if (!sock_write(ipc, &hdr, sizeof(hdr)) || !sock_write(ipc, payload, hdr.len)) { return NULL; } // receive response if (!sock_read(ipc, &hdr, sizeof(hdr))) { return NULL; } char* raw = malloc(hdr.len + 1); if (!raw) { fprintf(stderr, "Not enough memory\n"); return NULL; } if (!sock_read(ipc, raw, hdr.len)) { free(raw); return NULL; } raw[hdr.len] = 0; struct json_object* response = json_tokener_parse(raw); if (!response) { fprintf(stderr, "Invalid IPC response\n"); } free(raw); return response; } /** * Send command for specified application. * @param ipc IPC context (socket file descriptor) * @param app application Id * @param command command to send * @return true if operation completed successfully */ static bool ipc_command(int ipc, const char* app, const char* command) { bool rc = false; char cmd[128]; snprintf(cmd, sizeof(cmd), "for_window [app_id=%s] %s", app, command); json_object* response = ipc_message(ipc, IPC_COMMAND, cmd); if (response) { struct json_object* val = json_object_array_get_idx(response, 0); if (val) { rc = json_object_object_get_ex(val, "success", &val) && json_object_get_boolean(val); } if (!rc) { fprintf(stderr, "Bad IPC response\n"); } json_object_put(response); } return rc; } /** * Read numeric value from JSON node. * @param node JSON parent node * @param name name of the rect node * @param value value from JSON field * @return true if operation completed successfully */ static bool read_int(json_object* node, const char* name, int* value) { struct json_object* val; if (!json_object_object_get_ex(node, name, &val)) { fprintf(stderr, "JSON scheme error: field %s not found\n", name); return false; } *value = json_object_get_int(val); if (*value == 0 && errno == EINVAL) { fprintf(stderr, "JSON scheme error: field %s not a number\n", name); return false; } return true; } /** * Read rectangle geometry from JSON node. * @param node JSON parent node * @param name name of the rect node * @param rect rectangle geometry * @return true if operation completed successfully */ static bool read_rect(json_object* node, const char* name, struct rect* rect) { int x, y, width, height; struct json_object* rn; if (!json_object_object_get_ex(node, name, &rn)) { fprintf(stderr, "Failed to read rect: node %s not found\n", name); return false; } if (read_int(rn, "x", &x) && read_int(rn, "y", &y) && read_int(rn, "width", &width) && width > 0 && read_int(rn, "height", &height) && height > 0) { rect->x = (ssize_t)x; rect->y = (ssize_t)y; rect->width = (size_t)width; rect->height = (size_t)height; return true; } return false; } /** * Get currently focused workspace. * @param node parent JSON node * @return pointer to focused workspace node or NULL if not found */ static struct json_object* current_workspace(json_object* node) { int idx = json_object_array_length(node); while (--idx >= 0) { struct json_object* focused; struct json_object* wks = json_object_array_get_idx(node, idx); if (json_object_object_get_ex(wks, "focused", &focused) && json_object_get_boolean(focused)) { return wks; } } return NULL; } /** * Get currently focused window node. * @param node parent JSON node * @return pointer to focused window node or NULL if not found */ static struct json_object* current_window(json_object* node) { static const char* nnames[] = { "nodes", "floating_nodes" }; struct json_object* focused; if (json_object_object_get_ex(node, "focused", &focused) && json_object_get_boolean(focused)) { return node; } for (size_t i = 0; i < sizeof(nnames) / sizeof(nnames[0]); ++i) { struct json_object* nodes; if (json_object_object_get_ex(node, nnames[i], &nodes)) { int idx = json_object_array_length(nodes); while (--idx >= 0) { struct json_object* sub = json_object_array_get_idx(nodes, idx); struct json_object* focus = current_window(sub); if (focus) { return focus; } } } } return NULL; } int sway_connect(void) { struct sockaddr_un sa; memset(&sa, 0, sizeof(sa)); const char* path = getenv("SWAYSOCK"); if (!path) { return INVALID_SWAY_IPC; } size_t len = strlen(path); if (!len || len > sizeof(sa.sun_path)) { fprintf(stderr, "Invalid SWAYSOCK variable\n"); return INVALID_SWAY_IPC; } int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { const int ec = errno; fprintf(stderr, "Failed to create IPC socket: [%i] %s\n", ec, strerror(ec)); return INVALID_SWAY_IPC; } sa.sun_family = AF_UNIX; memcpy(sa.sun_path, path, len); len += sizeof(sa) - sizeof(sa.sun_path); if (connect(fd, (struct sockaddr*)&sa, len) == -1) { const int ec = errno; fprintf(stderr, "Failed to connect IPC socket: [%i] %s\n", ec, strerror(ec)); close(fd); return INVALID_SWAY_IPC; } return fd; } void sway_disconnect(int ipc) { if (ipc != INVALID_SWAY_IPC) { close(ipc); } } bool sway_current(int ipc, struct rect* wnd, bool* fullscreen) { bool rc = false; // get currently focused window json_object* tree = ipc_message(ipc, IPC_GET_TREE, NULL); if (!tree) { return false; } json_object* cur_wnd = current_window(tree); if (!cur_wnd || !read_rect(cur_wnd, "window_rect", wnd)) { goto done; } // get full screen mode flag int fs_mode; *fullscreen = read_int(cur_wnd, "fullscreen_mode", &fs_mode) && fs_mode; if (*fullscreen) { rc = true; goto done; } // if we are not in the full screen mode - calculate client area offset json_object* workspaces = ipc_message(ipc, IPC_GET_WORKSPACES, NULL); if (!workspaces) { goto done; } json_object* cur_wks = current_workspace(workspaces); if (cur_wks) { struct rect workspace; struct rect global; rc = read_rect(cur_wks, "rect", &workspace) && read_rect(cur_wnd, "rect", &global); if (rc) { wnd->x += global.x - workspace.x; wnd->y += global.y - workspace.y; } } json_object_put(workspaces); done: json_object_put(tree); return rc; } bool sway_add_rules(int ipc, const char* app, int x, int y, bool absolute) { char move[64]; snprintf(move, sizeof(move), "move %s position %i %i", absolute ? "absolute" : "", x, y); return ipc_command(ipc, app, "floating enable") && ipc_command(ipc, app, move); } swayimg-3.1/src/sway.h000066400000000000000000000022371465610152200147660ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Integration with Sway WM. // Copyright (C) 2020 Artem Senichev #pragma once #include "pixmap.h" #include #define INVALID_SWAY_IPC -1 /** * Connect to Sway. * @return IPC context, INVALID_SWAY_IPC if error */ int sway_connect(void); /** * Disconnect IPC channel. * @param ipc IPC context */ void sway_disconnect(int ipc); /** * Get geometry for currently focused window. * @param ipc IPC context * @param wnd geometry of currently focused window * @param fullscreen current full screen mode * @return true if operation completed successfully */ bool sway_current(int ipc, struct rect* wnd, bool* fullscreen); /** * Add rules for Sway for application's window: * 1. Enable floating mode; * 2. Set initial position. * * @param ipc IPC context * @param app application Id * @param x horizontal window position * @param v vertical window position * @param absolute flag to use absolute position instead of relative to the * current workspace * @return true if operation completed successfully */ bool sway_add_rules(int ipc, const char* app, int x, int y, bool absolute); swayimg-3.1/src/ui.c000066400000000000000000000547711465610152200144250ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // User interface: Window management, keyboard input, etc. // Copyright (C) 2020 Artem Senichev #include "ui.h" #include "application.h" #include "buildcfg.h" #include "config.h" #include "str.h" #include "xdg-shell-protocol.h" #include #include #include #include #include #include #include // Max number of output displays #define MAX_OUTPUTS 4 // Window size #define WINDOW_MIN 10 #define WINDOW_MAX 100000 #define WINDOW_DEFAULT_WIDTH 800 #define WINDOW_DEFAULT_HEIGHT 600 // Mouse button #ifndef BTN_LEFT #define BTN_LEFT 0x110 // from #endif /** UI context */ struct ui { // wayland specific struct wl { struct wl_display* display; struct wl_registry* registry; struct wl_shm* shm; struct wl_compositor* compositor; struct wl_seat* seat; struct wl_keyboard* keyboard; struct wl_pointer* pointer; struct wl_surface* surface; struct wl_output* output; } wl; // outputs and their scale factors struct outputs { struct wl_output* output; int32_t scale; } outputs[MAX_OUTPUTS]; // window buffers struct wnd { struct wl_buffer* buffer0; struct wl_buffer* buffer1; struct wl_buffer* current; struct pixmap pm; size_t width; size_t height; int32_t scale; } wnd; // cross-desktop struct xdg { bool initialized; struct xdg_wm_base* base; struct xdg_surface* surface; struct xdg_toplevel* toplevel; } xdg; // keyboard struct xkb { struct xkb_context* context; struct xkb_keymap* keymap; struct xkb_state* state; } xkb; // key repeat data struct repeat { int fd; xkb_keysym_t key; uint32_t rate; uint32_t delay; } repeat; // mouse drag struct mouse { bool active; int x; int y; } mouse; // fullscreen mode bool fullscreen; // flag to cancel event queue bool event_handled; }; /** Global UI context instance. */ static struct ui ctx = { .wnd.scale = 1, .repeat.fd = -1, }; /** * Fill timespec structure. * @param ts destination structure * @param ms time in milliseconds */ static inline void set_timespec(struct timespec* ts, uint32_t ms) { ts->tv_sec = ms / 1000; ts->tv_nsec = (ms % 1000) * 1000000; } /** * Create window buffer. * @return wayland buffer on NULL on errors */ static struct wl_buffer* create_buffer(void) { const size_t stride = ctx.wnd.width * sizeof(argb_t); const size_t buf_sz = stride * ctx.wnd.height; struct wl_buffer* buffer = NULL; struct wl_shm_pool* pool = NULL; int fd = -1; char path[64]; struct timespec ts; void* data; // generate unique file name clock_gettime(CLOCK_MONOTONIC, &ts); snprintf(path, sizeof(path), "/" APP_NAME "_%" PRIx64, ((uint64_t)ts.tv_sec << 32) | ts.tv_nsec); // open shared mem fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd == -1) { fprintf(stderr, "Unable to create shared file: [%i] %s\n", errno, strerror(errno)); return NULL; } shm_unlink(path); // set shared memory size if (ftruncate(fd, buf_sz) == -1) { fprintf(stderr, "Unable to truncate shared file: [%i] %s\n", errno, strerror(errno)); close(fd); return NULL; } // get data pointer of the shared mem data = mmap(NULL, buf_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { fprintf(stderr, "Unable to map shared file: [%i] %s\n", errno, strerror(errno)); close(fd); return NULL; } // create wayland buffer pool = wl_shm_create_pool(ctx.wl.shm, fd, buf_sz); buffer = wl_shm_pool_create_buffer(pool, 0, ctx.wnd.width, ctx.wnd.height, stride, WL_SHM_FORMAT_ARGB8888); wl_buffer_set_user_data(buffer, data); wl_shm_pool_destroy(pool); close(fd); return buffer; } /** * Free window buffer. * @param buffer wayland buffer to free */ static void free_buffer(struct wl_buffer* buffer) { if (buffer) { const size_t stride = ctx.wnd.pm.width * sizeof(argb_t); const size_t size = stride * ctx.wnd.pm.height; void* data = wl_buffer_get_user_data(buffer); wl_buffer_destroy(buffer); munmap(data, size); } } /** * Recreate window buffers. * @return true if operation completed successfully */ static bool recreate_buffers(void) { ctx.wnd.current = NULL; // recreate buffers free_buffer(ctx.wnd.buffer0); ctx.wnd.buffer0 = create_buffer(); free_buffer(ctx.wnd.buffer1); ctx.wnd.buffer1 = create_buffer(); if (!ctx.wnd.buffer0 || !ctx.wnd.buffer1) { return false; } ctx.wnd.pm.width = ctx.wnd.width; ctx.wnd.pm.height = ctx.wnd.height; ctx.wnd.current = ctx.wnd.buffer0; app_on_resize(); return true; } // suppress unused parameter warnings #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" /******************************************************************************* * Keyboard handlers ******************************************************************************/ static void on_keyboard_enter(void* data, struct wl_keyboard* wl_keyboard, uint32_t serial, struct wl_surface* surface, struct wl_array* keys) { } static void on_keyboard_leave(void* data, struct wl_keyboard* wl_keyboard, uint32_t serial, struct wl_surface* surface) { } static void on_keyboard_modifiers(void* data, struct wl_keyboard* wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { xkb_state_update_mask(ctx.xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group); } static void on_keyboard_repeat_info(void* data, struct wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) { // save keyboard repeat preferences ctx.repeat.rate = rate; ctx.repeat.delay = delay; } static void on_keyboard_keymap(void* data, struct wl_keyboard* wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { char* keymap; xkb_state_unref(ctx.xkb.state); xkb_keymap_unref(ctx.xkb.keymap); keymap = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); ctx.xkb.keymap = xkb_keymap_new_from_string(ctx.xkb.context, keymap, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); ctx.xkb.state = xkb_state_new(ctx.xkb.keymap); munmap(keymap, size); close(fd); } static void on_keyboard_key(void* data, struct wl_keyboard* wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { struct itimerspec ts = { 0 }; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { // stop key repeat timer timerfd_settime(ctx.repeat.fd, 0, &ts, NULL); } else if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { xkb_keysym_t keysym; key += 8; keysym = xkb_state_key_get_one_sym(ctx.xkb.state, key); if (keysym != XKB_KEY_NoSymbol) { app_on_keyboard(keysym, keybind_mods(ctx.xkb.state)); // handle key repeat if (ctx.repeat.rate && xkb_keymap_key_repeats(ctx.xkb.keymap, key)) { // start key repeat timer ctx.repeat.key = keysym; set_timespec(&ts.it_value, ctx.repeat.delay); set_timespec(&ts.it_interval, 1000 / ctx.repeat.rate); timerfd_settime(ctx.repeat.fd, 0, &ts, NULL); } } } } static void on_pointer_enter(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { } static void on_pointer_leave(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface) { } static void on_pointer_motion(void* data, struct wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { const int x = wl_fixed_to_int(surface_x); const int y = wl_fixed_to_int(surface_y); if (ctx.mouse.active) { const int dx = x - ctx.mouse.x; const int dy = y - ctx.mouse.y; if (dx || dy) { app_on_drag(dx, dy); } } ctx.mouse.x = x; ctx.mouse.y = y; } static void on_pointer_button(void* data, struct wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { if (button == BTN_LEFT) { ctx.mouse.active = (state == WL_POINTER_BUTTON_STATE_PRESSED); } } static void on_pointer_axis(void* data, struct wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { xkb_keysym_t key; if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { key = value > 0 ? VKEY_SCROLL_RIGHT : VKEY_SCROLL_LEFT; } else { key = value > 0 ? VKEY_SCROLL_DOWN : VKEY_SCROLL_UP; } app_on_keyboard(key, keybind_mods(ctx.xkb.state)); } static const struct wl_keyboard_listener keyboard_listener = { .keymap = on_keyboard_keymap, .enter = on_keyboard_enter, .leave = on_keyboard_leave, .key = on_keyboard_key, .modifiers = on_keyboard_modifiers, .repeat_info = on_keyboard_repeat_info, }; static const struct wl_pointer_listener pointer_listener = { .enter = on_pointer_enter, .leave = on_pointer_leave, .motion = on_pointer_motion, .button = on_pointer_button, .axis = on_pointer_axis, }; /******************************************************************************* * Seat handlers ******************************************************************************/ static void on_seat_name(void* data, struct wl_seat* seat, const char* name) { } static void on_seat_capabilities(void* data, struct wl_seat* seat, uint32_t cap) { if (cap & WL_SEAT_CAPABILITY_KEYBOARD) { ctx.wl.keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(ctx.wl.keyboard, &keyboard_listener, NULL); } else if (ctx.wl.keyboard) { wl_keyboard_destroy(ctx.wl.keyboard); ctx.wl.keyboard = NULL; } if (cap & WL_SEAT_CAPABILITY_POINTER) { ctx.wl.pointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(ctx.wl.pointer, &pointer_listener, NULL); } else if (ctx.wl.pointer) { wl_pointer_destroy(ctx.wl.pointer); ctx.wl.pointer = NULL; } } static const struct wl_seat_listener seat_listener = { .capabilities = on_seat_capabilities, .name = on_seat_name }; /******************************************************************************* * XDG handlers ******************************************************************************/ static void on_xdg_surface_configure(void* data, struct xdg_surface* surface, uint32_t serial) { xdg_surface_ack_configure(surface, serial); if (ctx.xdg.initialized) { app_redraw(); } else { wl_surface_attach(ctx.wl.surface, ctx.wnd.current, 0, 0); wl_surface_commit(ctx.wl.surface); } } static const struct xdg_surface_listener xdg_surface_listener = { .configure = on_xdg_surface_configure }; static void on_xdg_ping(void* data, struct xdg_wm_base* base, uint32_t serial) { xdg_wm_base_pong(base, serial); } static const struct xdg_wm_base_listener xdg_base_listener = { .ping = on_xdg_ping }; static void handle_xdg_toplevel_configure(void* data, struct xdg_toplevel* lvl, int32_t width, int32_t height, struct wl_array* states) { bool reset_buffers = (ctx.wnd.current == NULL); if (width && height) { const size_t new_width = width * ctx.wnd.scale; const size_t new_height = height * ctx.wnd.scale; if (width != (int32_t)ctx.wnd.width || height != (int32_t)ctx.wnd.height) { ctx.wnd.width = new_width; ctx.wnd.height = new_height; reset_buffers = true; } ctx.xdg.initialized = true; } if (reset_buffers && !recreate_buffers()) { app_exit(1); } } static void handle_xdg_toplevel_close(void* data, struct xdg_toplevel* top) { app_exit(0); } static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = handle_xdg_toplevel_configure, .close = handle_xdg_toplevel_close, }; /******************************************************************************* * WL Output handlers ******************************************************************************/ static void on_output_geometry(void* data, struct wl_output* output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform) { } static void on_output_mode(void* data, struct wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { } static void on_output_done(void* data, struct wl_output* output) { } static void on_output_scale(void* data, struct wl_output* output, int32_t factor) { // save output scale factor for (size_t i = 0; i < MAX_OUTPUTS; ++i) { if (!ctx.outputs[i].output || ctx.outputs[i].output == output) { ctx.outputs[i].output = output; ctx.outputs[i].scale = factor; break; } } } static const struct wl_output_listener wl_output_listener = { .geometry = on_output_geometry, .mode = on_output_mode, .done = on_output_done, .scale = on_output_scale, }; static void handle_enter_surface(void* data, struct wl_surface* surface, struct wl_output* output) { int32_t scale = 1; // find scale factor for current output for (size_t i = 0; i < MAX_OUTPUTS; ++i) { if (ctx.outputs[i].output == output) { scale = ctx.outputs[i].scale; break; } } // recreate buffer if scale has changed if (scale != ctx.wnd.scale) { ctx.wnd.width = (ctx.wnd.width / ctx.wnd.scale) * scale; ctx.wnd.height = (ctx.wnd.height / ctx.wnd.scale) * scale; ctx.wnd.scale = scale; if (recreate_buffers()) { app_redraw(); } else { app_exit(1); } } } static void handle_leave_surface(void* data, struct wl_surface* surface, struct wl_output* output) { } static const struct wl_surface_listener wl_surface_listener = { .enter = handle_enter_surface, .leave = handle_leave_surface, }; /******************************************************************************* * Registry handlers ******************************************************************************/ static void on_registry_global(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { if (strcmp(interface, wl_shm_interface.name) == 0) { ctx.wl.shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, wl_compositor_interface.name) == 0) { ctx.wl.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 3); } else if (strcmp(interface, wl_output_interface.name) == 0) { if (!ctx.wl.output) { ctx.wl.output = wl_registry_bind(registry, name, &wl_output_interface, 3); wl_output_add_listener(ctx.wl.output, &wl_output_listener, data); } } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { ctx.xdg.base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(ctx.xdg.base, &xdg_base_listener, data); } else if (strcmp(interface, wl_seat_interface.name) == 0) { ctx.wl.seat = wl_registry_bind(registry, name, &wl_seat_interface, 4); wl_seat_add_listener(ctx.wl.seat, &seat_listener, data); } } static void on_registry_remove(void* data, struct wl_registry* registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { .global = on_registry_global, .global_remove = on_registry_remove }; #pragma GCC diagnostic pop // "-Wunused-parameter" // Key repeat handler static void on_key_repeat(__attribute__((unused)) void* data) { uint64_t repeats; const ssize_t sz = sizeof(repeats); if (read(ctx.repeat.fd, &repeats, sz) == sz) { app_on_keyboard(ctx.repeat.key, keybind_mods(ctx.xkb.state)); } } // Wayland event handler static void on_wayland_event(__attribute__((unused)) void* data) { wl_display_read_events(ctx.wl.display); wl_display_dispatch_pending(ctx.wl.display); ctx.event_handled = true; } bool ui_init(const char* app_id, size_t width, size_t height) { ctx.wnd.width = width; ctx.wnd.height = height; if (ctx.wnd.width < WINDOW_MIN || ctx.wnd.height < WINDOW_MIN || ctx.wnd.width > WINDOW_MAX || ctx.wnd.height > WINDOW_MAX) { ctx.wnd.width = WINDOW_DEFAULT_WIDTH; ctx.wnd.height = WINDOW_DEFAULT_HEIGHT; } ctx.wl.display = wl_display_connect(NULL); if (!ctx.wl.display) { fprintf(stderr, "Failed to open display\n"); return false; } ctx.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ctx.wl.registry = wl_display_get_registry(ctx.wl.display); if (!ctx.wl.registry) { fprintf(stderr, "Failed to open registry\n"); ui_destroy(); return false; } wl_registry_add_listener(ctx.wl.registry, ®istry_listener, NULL); wl_display_roundtrip(ctx.wl.display); ctx.wl.surface = wl_compositor_create_surface(ctx.wl.compositor); if (!ctx.wl.surface) { fprintf(stderr, "Failed to create surface\n"); ui_destroy(); return false; } wl_surface_add_listener(ctx.wl.surface, &wl_surface_listener, NULL); ctx.xdg.surface = xdg_wm_base_get_xdg_surface(ctx.xdg.base, ctx.wl.surface); if (!ctx.xdg.surface) { fprintf(stderr, "Failed to create xdg surface\n"); ui_destroy(); return false; } xdg_surface_add_listener(ctx.xdg.surface, &xdg_surface_listener, NULL); ctx.xdg.toplevel = xdg_surface_get_toplevel(ctx.xdg.surface); xdg_toplevel_add_listener(ctx.xdg.toplevel, &xdg_toplevel_listener, NULL); xdg_toplevel_set_app_id(ctx.xdg.toplevel, app_id); if (ctx.fullscreen) { xdg_toplevel_set_fullscreen(ctx.xdg.toplevel, NULL); } wl_surface_commit(ctx.wl.surface); app_watch(wl_display_get_fd(ctx.wl.display), on_wayland_event, NULL); ctx.repeat.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); app_watch(ctx.repeat.fd, on_key_repeat, NULL); return true; } void ui_destroy(void) { if (ctx.repeat.fd != -1) { close(ctx.repeat.fd); } if (ctx.xkb.state) { xkb_state_unref(ctx.xkb.state); } if (ctx.xkb.keymap) { xkb_keymap_unref(ctx.xkb.keymap); } if (ctx.xkb.context) { xkb_context_unref(ctx.xkb.context); } free_buffer(ctx.wnd.buffer0); free_buffer(ctx.wnd.buffer1); if (ctx.wl.seat) { wl_seat_destroy(ctx.wl.seat); } if (ctx.wl.keyboard) { wl_keyboard_destroy(ctx.wl.keyboard); } if (ctx.wl.pointer) { wl_pointer_destroy(ctx.wl.pointer); } if (ctx.wl.shm) { wl_shm_destroy(ctx.wl.shm); } if (ctx.xdg.toplevel) { xdg_toplevel_destroy(ctx.xdg.toplevel); } if (ctx.xdg.base) { xdg_surface_destroy(ctx.xdg.surface); } if (ctx.xdg.base) { xdg_wm_base_destroy(ctx.xdg.base); } if (ctx.wl.output) { wl_output_destroy(ctx.wl.output); } if (ctx.wl.surface) { wl_surface_destroy(ctx.wl.surface); } if (ctx.wl.compositor) { wl_compositor_destroy(ctx.wl.compositor); } if (ctx.wl.registry) { wl_registry_destroy(ctx.wl.registry); } if (ctx.wl.display) { wl_display_disconnect(ctx.wl.display); } } void ui_event_prepare(void) { ctx.event_handled = false; while (wl_display_prepare_read(ctx.wl.display) != 0) { wl_display_dispatch_pending(ctx.wl.display); } wl_display_flush(ctx.wl.display); } void ui_event_done(void) { if (!ctx.event_handled) { wl_display_cancel_read(ctx.wl.display); } } struct pixmap* ui_draw_begin(void) { if (!ctx.wnd.current) { return NULL; // not yet initialized } // switch buffers if (ctx.wnd.current == ctx.wnd.buffer0) { ctx.wnd.current = ctx.wnd.buffer1; } else { ctx.wnd.current = ctx.wnd.buffer0; } ctx.wnd.pm.data = wl_buffer_get_user_data(ctx.wnd.current); return &ctx.wnd.pm; } void ui_draw_commit(void) { wl_surface_attach(ctx.wl.surface, ctx.wnd.current, 0, 0); wl_surface_damage(ctx.wl.surface, 0, 0, ctx.wnd.width, ctx.wnd.height); wl_surface_set_buffer_scale(ctx.wl.surface, ctx.wnd.scale); wl_surface_commit(ctx.wl.surface); } void ui_set_title(const char* name) { char* title = NULL; str_append(APP_NAME ": ", 0, &title); str_append(name, 0, &title); if (title) { xdg_toplevel_set_title(ctx.xdg.toplevel, title); free(title); } } size_t ui_get_width(void) { return ctx.wnd.width; } size_t ui_get_height(void) { return ctx.wnd.height; } size_t ui_get_scale(void) { return ctx.wnd.scale; } void ui_toggle_fullscreen(void) { ctx.fullscreen = !ctx.fullscreen; if (ctx.xdg.toplevel) { if (ctx.fullscreen) { xdg_toplevel_set_fullscreen(ctx.xdg.toplevel, NULL); } else { xdg_toplevel_unset_fullscreen(ctx.xdg.toplevel); } } } swayimg-3.1/src/ui.h000066400000000000000000000025521465610152200144200ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // User interface: Window management, keyboard input, etc. // Copyright (C) 2020 Artem Senichev #pragma once #include "pixmap.h" /** * Create global UI context. */ void ui_create(void); /** * Initialize global UI context: create window, register handlers etc. * @param app_id application id, used as window class * @param width,height initial window size in pixels * @return true if window created */ bool ui_init(const char* app_id, size_t width, size_t height); /** * Destroy global UI context. */ void ui_destroy(void); /** * Prepare the window system to read events. */ void ui_event_prepare(void); /** * Event handler complete notification. */ void ui_event_done(void); /** * Begin window redraw procedure. * @return window pixmap */ struct pixmap* ui_draw_begin(void); /** * Finish window redraw procedure. */ void ui_draw_commit(void); /** * Set window title. * @param name file name of the current image */ void ui_set_title(const char* name); /** * Get window width. * @return window width in pixels */ size_t ui_get_width(void); /** * Get window height. * @return window height in pixels */ size_t ui_get_height(void); /** * Get window scale factor. * @return window scale factor */ size_t ui_get_scale(void); /** * Toggle full screen mode. */ void ui_toggle_fullscreen(void); swayimg-3.1/src/viewer.c000066400000000000000000000514021465610152200152750ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Business logic of application and UI event handlers. // Copyright (C) 2020 Artem Senichev #include "viewer.h" #include "application.h" #include "buildcfg.h" #include "config.h" #include "fetcher.h" #include "imagelist.h" #include "info.h" #include "keybind.h" #include "loader.h" #include "str.h" #include "ui.h" #include #include #include #include #include // Background grid parameters #define GRID_BKGID 0x00f1f2f3 #define GRID_STEP 10 #define GRID_COLOR1 0xff333333 #define GRID_COLOR2 0xff4c4c4c // Scale thresholds #define MIN_SCALE 10 // pixels #define MAX_SCALE 100.0 // factor /** Scaling operations. */ enum fixed_scale { scale_fit_optimal, ///< Fit to window, but not more than 100% scale_fit_window, ///< Fit to window size scale_fit_width, ///< Fit width to window width scale_fit_height, ///< Fit height to window height scale_fill_window, ///< Fill the window scale_real_size, ///< Real image size (100%) }; // clang-format off static const char* scale_names[] = { [scale_fit_optimal] = "optimal", [scale_fit_window] = "fit", [scale_fit_width] = "width", [scale_fit_height] = "height", [scale_fill_window] = "fill", [scale_real_size] = "real", }; // clang-format on /** Viewer context. */ struct viewer { ssize_t img_x, img_y; ///< Top left corner of the image size_t frame; ///< Index of the current frame argb_t image_bkg; ///< Image background mode/color argb_t window_bkg; ///< Window background mode/color bool antialiasing; ///< Anti-aliasing mode on/off bool fixed; ///< Fix image position enum fixed_scale scale_init; ///< Initial scale float scale; ///< Current scale factor of the image bool animation_enable; ///< Animation enable/disable int animation_fd; ///< Animation timer bool slideshow_enable; ///< Slideshow enable/disable int slideshow_fd; ///< Slideshow timer size_t slideshow_time; ///< Slideshow image display time (seconds) size_t history; ///< Max number of cached images size_t preload; ///< Max number of images to preload }; /** Global viewer context. */ static struct viewer ctx; /** * Fix up image position. * @param force true to ignore current config setting */ static void fixup_position(bool force) { const ssize_t wnd_width = ui_get_width(); const ssize_t wnd_height = ui_get_height(); const struct pixmap* img = &fetcher_current()->frames[ctx.frame].pm; const ssize_t img_width = ctx.scale * img->width; const ssize_t img_height = ctx.scale * img->height; if (force || ctx.fixed) { // bind to window border if (ctx.img_x > 0 && ctx.img_x + img_width > wnd_width) { ctx.img_x = 0; } if (ctx.img_y > 0 && ctx.img_y + img_height > wnd_height) { ctx.img_y = 0; } if (ctx.img_x < 0 && ctx.img_x + img_width < wnd_width) { ctx.img_x = wnd_width - img_width; } if (ctx.img_y < 0 && ctx.img_y + img_height < wnd_height) { ctx.img_y = wnd_height - img_height; } // centering small image if (img_width <= wnd_width) { ctx.img_x = wnd_width / 2 - img_width / 2; } if (img_height <= wnd_height) { ctx.img_y = wnd_height / 2 - img_height / 2; } } // don't let canvas to be far out of window if (ctx.img_x + img_width < 0) { ctx.img_x = -img_width; } if (ctx.img_x > wnd_width) { ctx.img_x = wnd_width; } if (ctx.img_y + img_height < 0) { ctx.img_y = -img_height; } if (ctx.img_y > wnd_height) { ctx.img_y = wnd_height; } } /** * Move image (viewport). * @param horizontal axis along which to move (false for vertical) * @param positive direction (increase/decrease) * @param params optional move step in percents */ static void move_image(bool horizontal, bool positive, const char* params) { const ssize_t old_x = ctx.img_x; const ssize_t old_y = ctx.img_y; ssize_t step = 10; // in % if (params) { ssize_t val; if (str_to_num(params, 0, &val, 0) && val > 0 && val <= 1000) { step = val; } else { fprintf(stderr, "Invalid move step: \"%s\"\n", params); } } if (!positive) { step = -step; } if (horizontal) { ctx.img_x += (ui_get_width() / 100) * step; } else { ctx.img_y += (ui_get_height() / 100) * step; } fixup_position(false); if (ctx.img_x != old_x || ctx.img_y != old_y) { app_redraw(); } } /** * Rotate image 90 degrees. * @param clockwise rotation direction */ static void rotate_image(bool clockwise) { struct image* img = fetcher_current(); const struct pixmap* pm = &img->frames[ctx.frame].pm; const ssize_t diff = (ssize_t)pm->width - pm->height; const ssize_t shift = (ctx.scale * diff) / 2; image_rotate(img, clockwise ? 90 : 270); ctx.img_x += shift; ctx.img_y -= shift; fixup_position(false); app_redraw(); } /** * Set fixed scale for the image. * @param sc scale to set */ static void scale_image(enum fixed_scale sc) { const struct image* img = fetcher_current(); const struct pixmap* pm = &img->frames[ctx.frame].pm; const size_t wnd_width = ui_get_width(); const size_t wnd_height = ui_get_height(); const float scale_w = 1.0 / ((float)pm->width / wnd_width); const float scale_h = 1.0 / ((float)pm->height / wnd_height); switch (sc) { case scale_fit_optimal: ctx.scale = min(scale_w, scale_h); if (ctx.scale > 1.0) { ctx.scale = 1.0; } break; case scale_fit_window: ctx.scale = min(scale_w, scale_h); break; case scale_fit_width: ctx.scale = scale_w; break; case scale_fit_height: ctx.scale = scale_h; break; case scale_fill_window: ctx.scale = max(scale_w, scale_h); break; case scale_real_size: ctx.scale = 1.0; // 100 % break; } // center viewport ctx.img_x = wnd_width / 2 - (ctx.scale * pm->width) / 2; ctx.img_y = wnd_height / 2 - (ctx.scale * pm->height) / 2; fixup_position(true); info_update(info_scale, "%.0f%%", ctx.scale * 100); app_redraw(); } /** * Zoom in/out. * @param params zoom operation */ static void zoom_image(const char* params) { ssize_t percent = 0; ssize_t fixed_scale; if (!params || !*params) { return; } // check for fixed scale type fixed_scale = str_index(scale_names, params, 0); if (fixed_scale >= 0) { scale_image(fixed_scale); } else if (str_to_num(params, 0, &percent, 0) && percent != 0 && percent > -1000 && percent < 1000) { // zoom in % const double wnd_half_w = (double)ui_get_width() / 2; const double wnd_half_h = (double)ui_get_height() / 2; const float step = (ctx.scale / 100) * percent; const double center_x = wnd_half_w / ctx.scale - ctx.img_x / ctx.scale; const double center_y = wnd_half_h / ctx.scale - ctx.img_y / ctx.scale; if (percent > 0) { ctx.scale += step; if (ctx.scale > MAX_SCALE) { ctx.scale = MAX_SCALE; } } else { const struct image* img = fetcher_current(); const struct pixmap* pm = &img->frames[ctx.frame].pm; const float scale_w = (float)MIN_SCALE / pm->width; const float scale_h = (float)MIN_SCALE / pm->height; const float scale_min = max(scale_w, scale_h); ctx.scale += step; if (ctx.scale < scale_min) { ctx.scale = scale_min; } } // restore center ctx.img_x = wnd_half_w - center_x * ctx.scale; ctx.img_y = wnd_half_h - center_y * ctx.scale; fixup_position(false); } else { fprintf(stderr, "Invalid zoom operation: \"%s\"\n", params); } info_update(info_scale, "%.0f%%", ctx.scale * 100); app_redraw(); } /** * Start/stop animation if image supports it. * @param enable state to set */ static void animation_ctl(bool enable) { struct itimerspec ts = { 0 }; if (enable) { const struct image* img = fetcher_current(); const size_t duration = img->frames[ctx.frame].duration; enable = (img->num_frames > 1 && duration); if (enable) { ts.it_value.tv_sec = duration / 1000; ts.it_value.tv_nsec = (duration % 1000) * 1000000; } } ctx.animation_enable = enable; timerfd_settime(ctx.animation_fd, 0, &ts, NULL); } /** * Start/stop slide show. * @param enable state to set */ static void slideshow_ctl(bool enable) { struct itimerspec ts = { 0 }; ctx.slideshow_enable = enable; if (enable) { ts.it_value.tv_sec = ctx.slideshow_time; } timerfd_settime(ctx.slideshow_fd, 0, &ts, NULL); } /** * Reset state to defaults. */ static void reset_state(void) { const struct image* img = fetcher_current(); const size_t total_img = image_list_size(); ctx.frame = 0; ctx.img_x = 0; ctx.img_y = 0; ctx.scale = 0; scale_image(ctx.scale_init); fixup_position(true); ui_set_title(img->name); animation_ctl(true); slideshow_ctl(ctx.slideshow_enable); info_reset(img); if (total_img) { info_update(info_index, "%zu of %zu", img->index + 1, total_img); } app_redraw(); } /** * Skip current image. * @return true if next image was loaded */ static bool skip_image(void) { size_t index = image_list_skip(fetcher_current()->index); while (index != IMGLIST_INVALID && !fetcher_open(index)) { index = image_list_next_file(index); } return (index != IMGLIST_INVALID); } /** * Switch to the next image. * @param direction next image position * @return true if next image was loaded */ static bool next_image(enum action_type direction) { size_t index = fetcher_current()->index; do { switch (direction) { case action_first_file: index = image_list_first(); break; case action_last_file: index = image_list_last(); break; case action_prev_dir: index = image_list_prev_dir(index); break; case action_next_dir: index = image_list_next_dir(index); break; case action_prev_file: index = image_list_prev_file(index); break; case action_next_file: index = image_list_next_file(index); break; default: break; } } while (index != IMGLIST_INVALID && !fetcher_open(index)); if (index == IMGLIST_INVALID) { return false; } reset_state(); return true; } /** * Switch to the next or previous frame. * @param forward switch direction */ static void next_frame(bool forward) { size_t index = ctx.frame; const struct image* img = fetcher_current(); if (forward) { if (++index >= img->num_frames) { index = 0; } } else { if (index-- == 0) { index = img->num_frames - 1; } } if (index != ctx.frame) { ctx.frame = index; info_update(info_frame, "%zu of %zu", ctx.frame + 1, img->num_frames); info_update(info_image_size, "%zux%zu", img->frames[ctx.frame].pm.width, img->frames[ctx.frame].pm.height); app_redraw(); } } /** * Animation timer event handler. */ static void on_animation_timer(__attribute__((unused)) void* data) { next_frame(true); animation_ctl(true); } /** * Slideshow timer event handler. */ static void on_slideshow_timer(__attribute__((unused)) void* data) { slideshow_ctl(next_image(action_next_file)); } /** * Draw image. * @param wnd pixel map of target window */ static void draw_image(struct pixmap* wnd) { const struct image* img = fetcher_current(); const struct pixmap* img_pm = &img->frames[ctx.frame].pm; const size_t width = ctx.scale * img_pm->width; const size_t height = ctx.scale * img_pm->height; // clear window background pixmap_inverse_fill(wnd, ctx.img_x, ctx.img_y, width, height, ctx.window_bkg); // clear image background if (img->alpha) { if (ctx.image_bkg == GRID_BKGID) { pixmap_grid(wnd, ctx.img_x, ctx.img_y, width, height, ui_get_scale() * GRID_STEP, GRID_COLOR1, GRID_COLOR2); } else { pixmap_fill(wnd, ctx.img_x, ctx.img_y, width, height, ctx.image_bkg); } } // put image on window surface if (ctx.scale == 1.0) { pixmap_copy(img_pm, wnd, ctx.img_x, ctx.img_y, img->alpha); } else if (ctx.antialiasing) { pixmap_scale_bicubic(img_pm, wnd, ctx.img_x, ctx.img_y, ctx.scale, img->alpha); } else { pixmap_scale_nearest(img_pm, wnd, ctx.img_x, ctx.img_y, ctx.scale, img->alpha); } } /** * Reload image file and reset state (position, scale, etc). */ static void reload(void) { const size_t index = fetcher_current()->index; if (fetcher_reset(index, false)) { if (index == fetcher_current()->index) { info_update(info_status, "Image reloaded"); } else { info_update(info_status, "Unable to update, open next file"); } reset_state(); } else { printf("No more images to view, exit\n"); app_exit(0); } } /** * Redraw handler. */ static void redraw(void) { struct pixmap* window = ui_draw_begin(); if (window) { draw_image(window); info_print(window); ui_draw_commit(); } } /** * Window resize handler. */ static void on_resize(void) { fixup_position(false); reset_state(); } /** * Apply action. * @param action pointer to the action being performed */ static void apply_action(const struct action* action) { switch (action->type) { case action_first_file: case action_last_file: case action_prev_dir: case action_next_dir: case action_prev_file: case action_next_file: next_image(action->type); break; case action_skip_file: if (skip_image()) { reset_state(); } else { printf("No more images, exit\n"); app_exit(0); } break; case action_prev_frame: case action_next_frame: animation_ctl(false); next_frame(action->type == action_next_frame); break; case action_animation: animation_ctl(!ctx.animation_enable); break; case action_slideshow: slideshow_ctl(!ctx.slideshow_enable && next_image(action_next_file)); break; case action_mode: app_switch_mode(fetcher_current()->index); break; case action_step_left: move_image(true, true, action->params); break; case action_step_right: move_image(true, false, action->params); break; case action_step_up: move_image(false, true, action->params); break; case action_step_down: move_image(false, false, action->params); break; case action_zoom: zoom_image(action->params); break; case action_rotate_left: rotate_image(false); break; case action_rotate_right: rotate_image(true); break; case action_flip_vertical: image_flip_vertical(fetcher_current()); app_redraw(); break; case action_flip_horizontal: image_flip_horizontal(fetcher_current()); app_redraw(); break; case action_antialiasing: ctx.antialiasing = !ctx.antialiasing; info_update(info_status, "Anti-aliasing %s", ctx.antialiasing ? "on" : "off"); app_redraw(); break; case action_reload: reload(); break; case action_exec: app_execute(action->params, fetcher_current()->source); break; default: break; } } /** * Image drag handler. * @param dx,dy delta to move viewpoint */ static void on_drag(int dx, int dy) { const ssize_t old_x = ctx.img_x; const ssize_t old_y = ctx.img_y; ctx.img_x += dx; ctx.img_y += dy; if (ctx.img_x != old_x || ctx.img_y != old_y) { fixup_position(false); app_redraw(); } } /** * Custom section loader, see `config_loader` for details. */ static enum config_status load_config(const char* key, const char* value) { enum config_status status = cfgst_invalid_value; if (strcmp(key, VIEWER_CFG_WINDOW) == 0) { if (config_to_color(value, &ctx.window_bkg)) { status = cfgst_ok; } } else if (strcmp(key, VIEWER_CFG_TRANSPARENCY) == 0) { if (strcmp(value, "grid") == 0) { ctx.image_bkg = GRID_BKGID; status = cfgst_ok; } else if (config_to_color(value, &ctx.image_bkg)) { status = cfgst_ok; } } else if (strcmp(key, VIEWER_CFG_SCALE) == 0) { const ssize_t index = str_index(scale_names, value, 0); if (index >= 0) { ctx.scale_init = index; status = cfgst_ok; } } else if (strcmp(key, VIEWER_CFG_FIXED) == 0) { if (config_to_bool(value, &ctx.fixed)) { status = cfgst_ok; } } else if (strcmp(key, VIEWER_CFG_ANTIALIASING) == 0) { if (config_to_bool(value, &ctx.antialiasing)) { status = cfgst_ok; } } else if (strcmp(key, VIEWER_CFG_SLIDESHOW) == 0) { if (config_to_bool(value, &ctx.slideshow_enable)) { status = cfgst_ok; } } else if (strcmp(key, VIEWER_CFG_SLIDESHOW_TIME) == 0) { ssize_t num; if (str_to_num(value, 0, &num, 0) && num != 0 && num <= 86400) { ctx.slideshow_time = num; status = cfgst_ok; } } else if (strcmp(key, VIEWER_CFG_HISTORY) == 0) { ssize_t num; if (str_to_num(value, 0, &num, 0) && num >= 0 && num < 1024) { ctx.history = num; status = cfgst_ok; } } else if (strcmp(key, VIEWER_CFG_PRELOAD) == 0) { ssize_t num; if (str_to_num(value, 0, &num, 0) && num >= 0 && num < 1024) { ctx.preload = num; status = cfgst_ok; } } else { status = cfgst_invalid_key; } return status; } void viewer_create(void) { // set default configuration ctx.image_bkg = GRID_BKGID; ctx.fixed = true; ctx.animation_enable = true; ctx.animation_fd = -1; ctx.slideshow_enable = false; ctx.slideshow_fd = -1; ctx.slideshow_time = 3; ctx.history = 1; ctx.preload = 1; // register configuration loader config_add_loader(VIEWER_CFG_SECTION, load_config); } void viewer_init(struct image* image) { // setup animation timer ctx.animation_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (ctx.animation_fd != -1) { app_watch(ctx.animation_fd, on_animation_timer, NULL); } // setup slideshow timer ctx.slideshow_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (ctx.slideshow_fd != -1) { app_watch(ctx.slideshow_fd, on_slideshow_timer, NULL); } fetcher_init(image, ctx.history, ctx.preload); } void viewer_destroy(void) { fetcher_destroy(); if (ctx.animation_fd != -1) { close(ctx.animation_fd); } if (ctx.slideshow_fd != -1) { close(ctx.slideshow_fd); } } void viewer_handle(const struct event* event) { switch (event->type) { case event_action: apply_action(event->param.action); break; case event_redraw: redraw(); break; case event_resize: on_resize(); break; case event_drag: on_drag(event->param.drag.dx, event->param.drag.dy); break; case event_activate: if (fetcher_reset(event->param.activate.index, false)) { reset_state(); } else { app_exit(0); } break; case event_load: fetcher_attach(event->param.load.image, event->param.load.index); break; } } swayimg-3.1/src/viewer.h000066400000000000000000000020311465610152200152740ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Business logic of application and UI event handlers. // Copyright (C) 2020 Artem Senichev #pragma once #include "event.h" #include "image.h" // Configuration parameters #define VIEWER_CFG_SECTION "viewer" #define VIEWER_CFG_WINDOW "window" #define VIEWER_CFG_TRANSPARENCY "transparency" #define VIEWER_CFG_SCALE "scale" #define VIEWER_CFG_FIXED "fixed" #define VIEWER_CFG_ANTIALIASING "antialiasing" #define VIEWER_CFG_SLIDESHOW "slideshow" #define VIEWER_CFG_SLIDESHOW_TIME "slideshow_time" #define VIEWER_CFG_HISTORY "history" #define VIEWER_CFG_PRELOAD "preload" /** * Create global viewer context. */ void viewer_create(void); /** * Initialize global viewer context. * @param image initial image to open */ void viewer_init(struct image* image); /** * Destroy global viewer context. */ void viewer_destroy(void); /** * Event handler, see `event_handler` for details. */ void viewer_handle(const struct event* event); swayimg-3.1/test/000077500000000000000000000000001465610152200140165ustar00rootroot00000000000000swayimg-3.1/test/action_test.cpp000066400000000000000000000042211465610152200170350ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2024 Artem Senichev extern "C" { #include "action.h" } #include class Action : public ::testing::Test { protected: void TearDown() override { action_free(&actions); } struct action_seq actions = { nullptr, 0 }; }; TEST_F(Action, Create) { ASSERT_TRUE(action_create("info", &actions)); ASSERT_EQ(actions.num, static_cast(1)); ASSERT_NE(actions.sequence, nullptr); ASSERT_EQ(actions.sequence[0].type, action_info); ASSERT_EQ(actions.sequence[0].params, nullptr); } TEST_F(Action, Fail) { ASSERT_FALSE(action_create("", &actions)); ASSERT_EQ(actions.num, static_cast(0)); ASSERT_EQ(actions.sequence, nullptr); ASSERT_FALSE(action_create("invalid", &actions)); ASSERT_EQ(actions.num, static_cast(0)); ASSERT_EQ(actions.sequence, nullptr); ASSERT_FALSE(action_create("info123 exec", &actions)); ASSERT_EQ(actions.num, static_cast(0)); ASSERT_EQ(actions.sequence, nullptr); } TEST_F(Action, Params) { ASSERT_TRUE(action_create("exec \t param 123 ", &actions)); ASSERT_EQ(actions.num, static_cast(1)); ASSERT_NE(actions.sequence, nullptr); ASSERT_EQ(actions.sequence[0].type, action_exec); ASSERT_STREQ(actions.sequence[0].params, "param 123"); } TEST_F(Action, Sequence) { ASSERT_TRUE(action_create("exec cmd;\nreload;\t exit;status ok", &actions)); ASSERT_EQ(actions.num, static_cast(4)); ASSERT_NE(actions.sequence, nullptr); ASSERT_EQ(actions.sequence[0].type, action_exec); ASSERT_STREQ(actions.sequence[0].params, "cmd"); ASSERT_EQ(actions.sequence[1].type, action_reload); ASSERT_EQ(actions.sequence[1].params, nullptr); ASSERT_EQ(actions.sequence[2].type, action_exit); ASSERT_EQ(actions.sequence[2].params, nullptr); ASSERT_EQ(actions.sequence[3].type, action_status); ASSERT_STREQ(actions.sequence[3].params, "ok"); } TEST_F(Action, FailSequence) { ASSERT_FALSE(action_create("exec cmd;\nreload;invalid", &actions)); ASSERT_EQ(actions.num, static_cast(0)); ASSERT_EQ(actions.sequence, nullptr); } swayimg-3.1/test/config_test.cpp000066400000000000000000000043211465610152200170260ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2024 Artem Senichev extern "C" { #include "config.h" } #include static std::map config; static enum config_status on_load(const char* key, const char* value) { config.insert(std::make_pair(key, value)); return cfgst_ok; } #define EXPECT_CONFIG(k, v) \ { \ const auto& it = config.find(k); \ const char* val = (it == config.end() ? "N/A" : it->second.c_str()); \ EXPECT_STREQ(val, v); \ } TEST(Config, Load) { setenv("XDG_CONFIG_HOME", TEST_DATA_DIR, 1); config_add_loader("test.section", on_load); config_load(); EXPECT_EQ(config.size(), static_cast(3)); auto check = [](const char* key, const char* val) { const auto& it = config.find(key); const char* real = (it == config.end() ? "N/A" : it->second.c_str()); EXPECT_STREQ(real, val); }; check("spaces", "s p a c e s"); check("nospaces", "nospaces"); check("empty", ""); config_command("\t\ntest.section.command = 42"); check("command", "42"); config_destroy(); } TEST(Config, ToBool) { bool rc; EXPECT_TRUE(config_to_bool("true", &rc)); EXPECT_TRUE(rc); EXPECT_TRUE(config_to_bool("false", &rc)); EXPECT_FALSE(rc); EXPECT_TRUE(config_to_bool("yes", &rc)); EXPECT_TRUE(rc); EXPECT_TRUE(config_to_bool("no", &rc)); EXPECT_FALSE(rc); EXPECT_FALSE(config_to_bool("", &rc)); EXPECT_FALSE(config_to_bool("abc", &rc)); } TEST(Config, ToColor) { argb_t argb; EXPECT_TRUE(config_to_color("#010203", &argb)); EXPECT_EQ(argb, 0xff010203); EXPECT_TRUE(config_to_color("#010203aa", &argb)); EXPECT_EQ(argb, 0xaa010203); EXPECT_TRUE(config_to_color("010203aa", &argb)); EXPECT_EQ(argb, 0xaa010203); EXPECT_TRUE(config_to_color("# 010203aa", &argb)); EXPECT_EQ(argb, 0xaa010203); EXPECT_FALSE(config_to_color("", &argb)); EXPECT_FALSE(config_to_color("invalid value", &argb)); } swayimg-3.1/test/data/000077500000000000000000000000001465610152200147275ustar00rootroot00000000000000swayimg-3.1/test/data/exif.jpg000066400000000000000000000032721465610152200163700ustar00rootroot00000000000000JFIF,,BExifII* (1 2i%lGooglePixel 7,,GIMP 2.99.162024:07:06 12:31:44("'0232    "*  27767767760100:   3B4"J` N@Bd2024:05:30 21:18:482024:05:30 21:18:48+03:00+03:00+03:004ddddGooglePixel 7 back camera 6.81mm f/1.85 NEM& .7, d%% d+2024:05:30C      C   }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?^yswayimg-3.1/test/data/image.avif000066400000000000000000000010301465610152200166520ustar00rootroot00000000000000ftypavifavifmif1miafmeta!hdlrpictpitm4ilocD@=8iinfinfeav01infeav01iprpipcocolrnclx av1Cispepixi8auxCurn:mpeg:mpegB:cicp:systems:auxiliary:alpha av1C ispepixiipmairefauxldmdat 62[=q5ƿ{ 8620@D tKY8Rm.nE(ozlD0R+Kswayimg-3.1/test/data/image.bmp000066400000000000000000000001261465610152200165100ustar00rootroot00000000000000BMVF8 #.#.swayimg-3.1/test/data/image.gif000066400000000000000000000001051465610152200164740ustar00rootroot00000000000000GIF89a! NETSCAPE2.0!,;swayimg-3.1/test/data/image.heif000066400000000000000000000010111465610152200166370ustar00rootroot00000000000000ftypheicmif1heicmiafmeta!hdlrpictpitmidat8ilocD@;8iinfinfehvc1infegridiprpipcoshvcCp @ p@!'Bp ꮚ"Dsispe@@ispepixiipmairefdimgCmdat7(!trڱJ2`~'F<V[ы!¥T"swayimg-3.1/test/data/image.jpg000066400000000000000000000015441465610152200165170ustar00rootroot00000000000000JFIF++CC }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ??> x_ csZuk߄5]gZu_5m[TԵ=Jy/'in.%i>?G'+-r<[evN p a0-<> ӧCNi•(FWv7 8Ӎ1,N>**:8sAfi93晎+bkkի/swayimg-3.1/test/data/image.jxl000066400000000000000000000004361465610152200165330ustar00rootroot00000000000000 )4@[c!%L2СnhCCZfeǓIAJVpe_uۘm%li+2 h_ql#9aB/, S]'JX0*P6TYC?\eYZQԱ@T{eHzTϟ'tH#ۅu!({#P[Z,-,e&t NJ0ssU(+HɃLL x¹|6yGW?hKQ'>^fK?{K?swayimg-3.1/test/data/image.png000066400000000000000000000002301465610152200165120ustar00rootroot00000000000000PNG  IHDRr $gAMA asRGB, cHRMz&u0`:pQ<IDATO" ?}l IENDB`swayimg-3.1/test/data/image.pnm000066400000000000000000000000451465610152200165240ustar00rootroot00000000000000P6 # Simple pnm 2 2 255 swayimg-3.1/test/data/image.svg000066400000000000000000000003731465610152200165350ustar00rootroot00000000000000 swayimg-3.1/test/data/image.tga000066400000000000000000000000761465610152200165110ustar00rootroot00000000000000  TRUEVISION-XFILE.swayimg-3.1/test/data/image.tiff000066400000000000000000000004251465610152200166640ustar00rootroot00000000000000II* (RS,,Backgroundswayimg-3.1/test/data/image.webp000066400000000000000000000000621465610152200166660ustar00rootroot00000000000000RIFF*WEBPVP8L/@ Hz?`"swayimg-3.1/test/data/swayimg/000077500000000000000000000000001465610152200164075ustar00rootroot00000000000000swayimg-3.1/test/data/swayimg/config000066400000000000000000000001671465610152200176030ustar00rootroot00000000000000# Swayimg test configuration file. [test.section] spaces = s p a c e s nospaces=nospaces empty= [empty_section] swayimg-3.1/test/exif_test.cpp000066400000000000000000000024141465610152200165150ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2024 Artem Senichev extern "C" { #include "exif.h" } #include #include TEST(Exif, Read) { std::ifstream file(TEST_DATA_DIR "/exif.jpg", std::ios::binary); const std::vector data((std::istreambuf_iterator(file)), (std::istreambuf_iterator())); struct image* image = image_create(); process_exif(image, data.data(), data.size()); EXPECT_EQ(image->num_info, static_cast(7)); EXPECT_STREQ(image->info[0].value, "2024:07:06 12:31:44"); EXPECT_STREQ(image->info[1].value, "Google"); EXPECT_STREQ(image->info[2].value, "Pixel 7"); EXPECT_STREQ(image->info[3].value, "GIMP 2.99.16"); EXPECT_STREQ(image->info[4].value, "1/50 sec."); EXPECT_STREQ(image->info[5].value, "f/1.9"); EXPECT_STREQ(image->info[6].value, "55°44'28.41\"N, 37°37'25.46\"E"); image_free(image); } TEST(Exif, Fail) { struct image* image = image_create(); process_exif(image, nullptr, 0); EXPECT_EQ(image->num_info, static_cast(0)); process_exif(image, reinterpret_cast("abcd"), 4); EXPECT_EQ(image->num_info, static_cast(0)); image_free(image); } swayimg-3.1/test/imagelist_test.cpp000066400000000000000000000112341465610152200175400ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2024 Artem Senichev extern "C" { #include "imagelist.h" } #include class ImageList : public ::testing::Test { protected: void TearDown() override { image_list_destroy(); } }; TEST_F(ImageList, Init) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; ASSERT_EQ(image_list_size(), static_cast(0)); ASSERT_EQ(image_list_init(sources, 3), static_cast(3)); ASSERT_EQ(image_list_size(), static_cast(3)); } TEST_F(ImageList, Find) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); const size_t idx = image_list_find("exec://cmd2"); ASSERT_EQ(idx, static_cast(1)); } TEST_F(ImageList, Skip) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); const size_t idx = image_list_skip(1); ASSERT_EQ(idx, static_cast(2)); } TEST_F(ImageList, Distance) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); ASSERT_EQ(image_list_distance(0, 0), static_cast(0)); ASSERT_EQ(image_list_distance(0, 1), static_cast(1)); ASSERT_EQ(image_list_distance(0, 2), static_cast(2)); image_list_skip(1); ASSERT_EQ(image_list_distance(0, 2), static_cast(1)); ASSERT_EQ(image_list_distance(2, 0), static_cast(IMGLIST_INVALID)); } TEST_F(ImageList, Back) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); ASSERT_EQ(image_list_back(0, 42), static_cast(0)); ASSERT_EQ(image_list_back(2, 0), static_cast(2)); ASSERT_EQ(image_list_back(2, 1), static_cast(1)); ASSERT_EQ(image_list_back(2, 2), static_cast(0)); ASSERT_EQ(image_list_back(2, 42), static_cast(0)); image_list_skip(1); ASSERT_EQ(image_list_back(2, 1), static_cast(0)); } TEST_F(ImageList, Forward) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); ASSERT_EQ(image_list_forward(0, 42), static_cast(2)); ASSERT_EQ(image_list_forward(0, 0), static_cast(0)); ASSERT_EQ(image_list_forward(0, 1), static_cast(1)); ASSERT_EQ(image_list_forward(0, 2), static_cast(2)); ASSERT_EQ(image_list_forward(0, 42), static_cast(2)); image_list_skip(1); ASSERT_EQ(image_list_forward(0, 1), static_cast(2)); } TEST_F(ImageList, Get) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); const char* src = image_list_get(1); ASSERT_STREQ(src, "exec://cmd2"); } TEST_F(ImageList, GetFirst) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); ASSERT_EQ(image_list_first(), static_cast(0)); } TEST_F(ImageList, GetLast) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); ASSERT_EQ(image_list_last(), static_cast(2)); } TEST_F(ImageList, GetNextFile) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); ASSERT_EQ(image_list_next_file(0), static_cast(1)); ASSERT_EQ(image_list_next_file(1), static_cast(2)); ASSERT_EQ(image_list_next_file(2), static_cast(0)); } TEST_F(ImageList, GePrevFile) { const char* sources[] = { "exec://cmd1", "exec://cmd2", "exec://cmd3" }; image_list_init(sources, 3); ASSERT_EQ(image_list_prev_file(0), static_cast(2)); ASSERT_EQ(image_list_prev_file(1), static_cast(0)); } TEST_F(ImageList, GetNextDir) { const char* sources[] = { "exec://cmd1/dir1/image1", "exec://cmd1/dir1/image2", "exec://cmd1/dir2/image3", "exec://cmd1/dir2/image4", "exec://cmd1/dir3/image5", }; image_list_init(sources, 5); ASSERT_EQ(image_list_next_dir(0), static_cast(2)); ASSERT_EQ(image_list_next_dir(2), static_cast(4)); ASSERT_EQ(image_list_next_dir(4), static_cast(0)); } TEST_F(ImageList, GetPrevDir) { const char* sources[] = { "exec://cmd1/dir1/image1", "exec://cmd1/dir1/image2", "exec://cmd1/dir2/image3", "exec://cmd1/dir2/image4", "exec://cmd1/dir3/image5", }; image_list_init(sources, 5); ASSERT_EQ(image_list_prev_dir(0), static_cast(4)); ASSERT_EQ(image_list_prev_dir(2), static_cast(1)); ASSERT_EQ(image_list_prev_dir(3), static_cast(1)); } swayimg-3.1/test/keybind_test.cpp000066400000000000000000000110201465610152200172000ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2024 Artem Senichev extern "C" { #include "config.h" #include "keybind.h" } #include class Keybind : public ::testing::Test { protected: void SetUp() override { keybind_create(); // register config callback keybind_destroy(); // clear default bindings } void TearDown() override { keybind_destroy(); config_destroy(); } static constexpr const char* section = "keys.viewer"; }; TEST_F(Keybind, AddOne) { ASSERT_EQ(keybind_get(), nullptr); ASSERT_EQ(config_set(section, "a", "exit"), cfgst_ok); const struct keybind* kb = keybind_get(); ASSERT_NE(kb, nullptr); ASSERT_EQ(kb->key, static_cast('a')); ASSERT_EQ(kb->mods, static_cast(0)); ASSERT_EQ(kb->actions.num, static_cast(1)); ASSERT_EQ(kb->actions.sequence[0].type, action_exit); ASSERT_STREQ(kb->help, "a: exit"); ASSERT_EQ(kb->next, nullptr); } TEST_F(Keybind, Replace) { ASSERT_EQ(config_set(section, "a", "exit"), cfgst_ok); ASSERT_EQ(config_set(section, "b", "exit"), cfgst_ok); ASSERT_EQ(config_set(section, "b", "reload"), cfgst_ok); const struct keybind* kb = keybind_get(); ASSERT_NE(kb, nullptr); ASSERT_EQ(kb->key, static_cast('b')); ASSERT_EQ(kb->actions.sequence[0].type, action_reload); ASSERT_EQ(kb->next->key, static_cast('a')); ASSERT_EQ(kb->next->actions.sequence[0].type, action_exit); ASSERT_EQ(kb->next->next, nullptr); } TEST_F(Keybind, Find) { const struct keybind* kb; ASSERT_EQ(config_set(section, "a", "exit"), cfgst_ok); ASSERT_EQ(config_set(section, "Ctrl+Alt+Shift+b", "reload"), cfgst_ok); kb = keybind_find('a', 0); ASSERT_NE(kb, nullptr); ASSERT_EQ(kb->key, static_cast('a')); ASSERT_EQ(kb->mods, static_cast(0)); ASSERT_EQ(kb->actions.sequence[0].type, action_exit); kb = keybind_find('b', KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT); ASSERT_NE(kb, nullptr); ASSERT_EQ(kb->key, static_cast('b')); ASSERT_EQ(kb->mods, static_cast(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT)); ASSERT_EQ(kb->actions.sequence[0].type, action_reload); ASSERT_EQ(keybind_find('a', KEYMOD_CTRL), nullptr); ASSERT_EQ(keybind_find('c', 0), nullptr); } TEST_F(Keybind, Mods) { ASSERT_EQ(config_set(section, "Ctrl+a", "exit"), cfgst_ok); ASSERT_EQ(config_set(section, "Alt+b", "exit"), cfgst_ok); ASSERT_EQ(config_set(section, "Shift+c", "exit"), cfgst_ok); ASSERT_EQ(config_set(section, "Alt+Ctrl+d", "exit"), cfgst_ok); ASSERT_EQ(config_set(section, "Ctrl+Shift+Alt+e", "exit"), cfgst_ok); ASSERT_NE(keybind_find('a', KEYMOD_CTRL), nullptr); ASSERT_NE(keybind_find('b', KEYMOD_ALT), nullptr); ASSERT_NE(keybind_find('c', KEYMOD_SHIFT), nullptr); ASSERT_NE(keybind_find('d', KEYMOD_CTRL | KEYMOD_ALT), nullptr); ASSERT_NE(keybind_find('e', KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT), nullptr); } TEST_F(Keybind, ActionParams) { const struct keybind* kb; ASSERT_EQ(config_set(section, "a", "exit"), cfgst_ok); kb = keybind_get(); ASSERT_NE(kb, nullptr); ASSERT_EQ(kb->key, static_cast('a')); ASSERT_EQ(kb->mods, static_cast(0)); ASSERT_EQ(kb->actions.num, static_cast(1)); ASSERT_EQ(kb->actions.sequence[0].type, action_exit); ASSERT_EQ(kb->next, nullptr); ASSERT_EQ(config_set(section, "a", "status \t params 1 2 3\t"), cfgst_ok); kb = keybind_get(); ASSERT_NE(kb, nullptr); ASSERT_EQ(kb->key, static_cast('a')); ASSERT_EQ(kb->mods, static_cast(0)); ASSERT_EQ(kb->actions.num, static_cast(1)); ASSERT_EQ(kb->actions.sequence[0].type, action_status); ASSERT_STREQ(kb->actions.sequence[0].params, "params 1 2 3"); ASSERT_EQ(kb->next, nullptr); } TEST_F(Keybind, Multiaction) { ASSERT_EQ(config_set(section, "a", "exec cmd;reload;exit"), cfgst_ok); const struct keybind* kb = keybind_get(); ASSERT_NE(kb, nullptr); ASSERT_EQ(kb->key, static_cast('a')); ASSERT_EQ(kb->mods, static_cast(0)); ASSERT_EQ(kb->actions.num, static_cast(3)); ASSERT_EQ(kb->actions.sequence[0].type, action_exec); ASSERT_EQ(kb->actions.sequence[1].type, action_reload); ASSERT_EQ(kb->actions.sequence[2].type, action_exit); ASSERT_STREQ(kb->actions.sequence[0].params, "cmd"); ASSERT_EQ(kb->next, nullptr); } swayimg-3.1/test/loader_test.cpp000066400000000000000000000037561465610152200170420ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2024 Artem Senichev extern "C" { #include "application.h" #include "buildcfg.h" #include "loader.h" #include "ui.h" #include "viewer.h" } #include // stubs for linker (application and ui are not included to tests) extern "C" { void app_watch(int, fd_callback, void*) { } void app_reload() { } void app_redraw() { } void app_on_resize() { } void app_on_keyboard(xkb_keysym_t, uint8_t) { } void app_on_drag(int, int) { } void app_exit(int) { } void app_on_load(struct image*, size_t) { } bool app_is_viewer() { return true; } } class Loader : public ::testing::Test { protected: void TearDown() override { image_free(image); } void Load(const char* file) { EXPECT_EQ(loader_from_source(file, &image), ldr_success); ASSERT_NE(image, nullptr); EXPECT_NE(image->frames[0].pm.width, static_cast(0)); EXPECT_NE(image->frames[0].pm.height, static_cast(0)); EXPECT_NE(image->frames[0].pm.data[0], static_cast(0)); } struct image* image = nullptr; }; TEST_F(Loader, External) { const enum loader_status status = loader_from_source( LDRSRC_EXEC "cat " TEST_DATA_DIR "/image.bmp", &image); EXPECT_EQ(status, ldr_success); ASSERT_NE(image, nullptr); } #define TEST_LOADER(n) \ TEST_F(Loader, n) \ { \ Load(TEST_DATA_DIR "/image." #n); \ } TEST_LOADER(bmp); TEST_LOADER(pnm); TEST_LOADER(tga); #ifdef HAVE_LIBEXR // TEST_LOADER(exr); #endif #ifdef HAVE_LIBGIF TEST_LOADER(gif); #endif #ifdef HAVE_LIBHEIF TEST_LOADER(heif); #endif #ifdef HAVE_LIBAVIF TEST_LOADER(avif); #endif #ifdef HAVE_LIBJPEG TEST_LOADER(jpg); #endif #ifdef HAVE_LIBJXL TEST_LOADER(jxl); #endif #ifdef HAVE_LIBPNG TEST_LOADER(png); #endif #ifdef HAVE_LIBRSVG TEST_LOADER(svg); #endif #ifdef HAVE_LIBTIFF TEST_LOADER(tiff); #endif #ifdef HAVE_LIBWEBP TEST_LOADER(webp); #endif swayimg-3.1/test/meson.build000066400000000000000000000031441465610152200161620ustar00rootroot00000000000000# Rules for building tests sources = [ 'action_test.cpp', 'config_test.cpp', 'imagelist_test.cpp', 'keybind_test.cpp', 'loader_test.cpp', 'pixmap_test.cpp', 'str_test.cpp', '../src/action.c', '../src/config.c', '../src/event.c', '../src/image.c', '../src/imagelist.c', '../src/keybind.c', '../src/loader.c', '../src/pixmap.c', '../src/str.c', '../src/formats/bmp.c', '../src/formats/pnm.c', '../src/formats/tga.c', ] if exif.found() sources += ['exif_test.cpp', '../src/exif.c'] endif if exr.found() sources += '../src/formats/exr.c' endif if gif.found() sources += '../src/formats/gif.c' endif if heif.found() sources += '../src/formats/heif.c' endif if avif.found() sources += '../src/formats/avif.c' endif if jpeg.found() sources += '../src/formats/jpeg.c' endif if jxl.found() sources += '../src/formats/jxl.c' endif if png.found() sources += '../src/formats/png.c' endif if rsvg.found() sources += '../src/formats/svg.c' endif if tiff.found() sources += '../src/formats/tiff.c' endif if webp.found() and webp_demux.found() sources += '../src/formats/webp.c' endif test( 'swayimg', executable( 'swayimg_test', sources, dependencies: [ dependency('gtest', main: true, disabler: true, required: true), xkb, exif, exr, gif, heif, inotify, avif, jpeg, jxl, png, rsvg, tiff, webp, webp_demux, ], include_directories: '../src', cpp_args : '-DTEST_DATA_DIR="' + meson.current_source_dir() + '/data"', ) ) configure_file(output: 'buildcfg.h', configuration: conf) swayimg-3.1/test/pixmap_test.cpp000066400000000000000000000326571465610152200170740ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2024 Artem Senichev extern "C" { #include "pixmap.h" } #include class Pixmap : public ::testing::Test { protected: void Compare(const struct pixmap& pm, const argb_t* expect) const { for (size_t y = 0; y < pm.height; ++y) { for (size_t x = 0; x < pm.width; ++x) { char expected[32], real[32]; snprintf(expected, sizeof(expected), "y=%ld,x=%ld,c=%08x", y, x, expect[y * pm.height + x]); snprintf(real, sizeof(real), "y=%ld,x=%ld,c=%08x", y, x, pm.data[y * pm.height + x]); EXPECT_STREQ(expected, real); } } } }; TEST_F(Pixmap, Create) { struct pixmap pm; ASSERT_TRUE(pixmap_create(&pm, 123, 456)); EXPECT_NE(pm.data, nullptr); EXPECT_EQ(pm.data[0], static_cast(0)); EXPECT_EQ(pm.width, static_cast(123)); EXPECT_EQ(pm.height, static_cast(456)); pixmap_free(&pm); } TEST_F(Pixmap, Fill) { const argb_t clr = 0x12345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { 0x00, 0x01, 0x02, 0x03, 0x10, clr, clr, 0x13, 0x20, clr, clr, 0x23, 0x30, 0x31, 0x32, 0x33, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_fill(&pm, 1, 1, 2, 2, clr); Compare(pm, expect); } TEST_F(Pixmap, FillOutsideTL) { const argb_t clr = 0x12345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { clr, clr, 0x02, 0x03, clr, clr, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_fill(&pm, -2, -2, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, FillOutsideBR) { const argb_t clr = 0x12345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, clr, clr, 0x30, 0x31, clr, clr, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_fill(&pm, 2, 2, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, InverseFill) { const argb_t clr = 0x12345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { clr, clr, clr, clr, clr, 0x11, 0x12, clr, clr, 0x21, 0x22, clr, clr, clr, clr, clr, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_inverse_fill(&pm, 1, 1, 2, 2, clr); Compare(pm, expect); } TEST_F(Pixmap, InverseOutsideTL) { const argb_t clr = 0x12345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { 0x00, 0x01, clr, clr, 0x10, 0x11, clr, clr, clr, clr, clr, clr, clr, clr, clr, clr, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_inverse_fill(&pm, -2, -2, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, InverseOutsideBR) { const argb_t clr = 0x12345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { clr, clr, clr, clr, clr, clr, clr, clr, clr, clr, 0x22, 0x23, clr, clr, 0x32, 0x33, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_inverse_fill(&pm, 2, 2, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, Grid) { const argb_t clr1 = 0x12345678; const argb_t clr2 = 0x87654321; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { clr2, clr2, clr1, clr1, clr2, clr2, clr1, clr1, clr1, clr1, clr2, clr2, clr1, clr1, clr2, clr2, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_grid(&pm, -10, -10, 20, 20, 2, clr1, clr2); Compare(pm, expect); } TEST_F(Pixmap, Mask) { const argb_t clr = 0xffaaaaaa; // clang-format off argb_t src[] = { 0xdddddddd, 0xcccccccc, 0xbbbbbbbb, 0xaaaaaaaa, 0x11111111, 0xff000000, 0x80000000, 0x22222222, 0x33333333, 0xaa111111, 0x00000000, 0x44444444, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, }; const uint8_t mask[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, }; const argb_t expect[] = { 0xdddddddd, 0xcccccccc, 0xbbbbbbbb, 0xaaaaaaaa, 0x11111111, 0xffaaaaaa, 0xffaaaaaa, 0x22222222, 0x33333333, 0xaa5d5d5d, 0x402a2a2a, 0x44444444, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_apply_mask(&pm, 0, 0, mask, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, MaskOutsideTL) { const argb_t clr = 0xffaaaaaa; // clang-format off argb_t src[] = { 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000, 0x00000000, 0xff000000, 0xff000000, 0x00000000, 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, }; const uint8_t mask[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, }; const argb_t expect[] = { 0xff000000, 0xffaaaaaa, 0xff000000, 0xff000000, 0xffaaaaaa, 0xffaaaaaa, 0x00000000, 0xff000000, 0xff000000, 0x00000000, 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_apply_mask(&pm, -2, -2, mask, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, MaskOutsideBR) { const argb_t clr = 0xffaaaaaa; // clang-format off argb_t src[] = { 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000, 0x00000000, 0xff000000, 0xff000000, 0x00000000, 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, }; const uint8_t mask[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, }; const argb_t expect[] = { 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000, 0x00000000, 0xff000000, 0xff000000, 0x00000000, 0xffaaaaaa, 0xffaaaaaa, 0xff000000, 0xff000000, 0xffaaaaaa, 0xff000000, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_apply_mask(&pm, 2, 2, mask, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, Copy) { // clang-format off argb_t src[] = { 0xaa, 0xbb, 0xcc, 0xdd, }; argb_t dst[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0xaa, 0xbb, 0x13, 0x20, 0xcc, 0xdd, 0x23, 0x30, 0x31, 0x32, 0x33, }; // clang-format on const struct pixmap pm_src = { 2, 2, src }; struct pixmap pm_dst = { 4, 4, dst }; pixmap_copy(&pm_src, &pm_dst, 1, 1, false); Compare(pm_dst, expect); } TEST_F(Pixmap, CopyOutsideTL) { // clang-format off argb_t src[] = { 0xaa, 0xbb, 0xcc, 0xdd, }; argb_t dst[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { 0xdd, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; // clang-format on const struct pixmap pm_src = { 2, 2, src }; struct pixmap pm_dst = { 4, 4, dst }; pixmap_copy(&pm_src, &pm_dst, -1, -1, false); Compare(pm_dst, expect); } TEST_F(Pixmap, CopyOutsideBR) { // clang-format off argb_t src[] = { 0xaa, 0xbb, 0xcc, 0xdd, }; argb_t dst[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0xaa, }; // clang-format on const struct pixmap pm_src = { 2, 2, src }; struct pixmap pm_dst = { 4, 4, dst }; pixmap_copy(&pm_src, &pm_dst, 3, 3, false); Compare(pm_dst, expect); } TEST_F(Pixmap, CopyAlpha) { // clang-format off argb_t src[] = { 0xffaaaaaa, 0x80aaaaaa, 0x40aaaaaa, 0x00aaaaaa, }; argb_t dst[] = { 0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, 0x77777777, 0x88888888, 0x99999999, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, 0xeeeeeeee, 0xffffffff, }; const argb_t expect[] = { 0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0xffaaaaaa, 0x80888888, 0x77777777, 0x88888888, 0x999d9d9d, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, 0xeeeeeeee, 0xffffffff, }; // clang-format on const struct pixmap pm_src = { 2, 2, src }; struct pixmap pm_dst = { 4, 4, dst }; pixmap_copy(&pm_src, &pm_dst, 1, 1, true); Compare(pm_dst, expect); } TEST_F(Pixmap, CopyAlphaOutsideTL) { // clang-format off argb_t src[] = { 0x00aaaaaa, 0x40bbbbbb, 0x80cccccc, 0xffdddddd, }; argb_t dst[] = { 0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, 0x77777777, 0x88888888, 0x99999999, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, 0xeeeeeeee, 0xffffffff, }; const argb_t expect[] = { 0xffdddddd, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, 0x77777777, 0x88888888, 0x99999999, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, 0xeeeeeeee, 0xffffffff, }; // clang-format on const struct pixmap pm_src = { 2, 2, src }; struct pixmap pm_dst = { 4, 4, dst }; pixmap_copy(&pm_src, &pm_dst, -1, -1, false); Compare(pm_dst, expect); } TEST_F(Pixmap, CopyAlphaOutsideBL) { // clang-format off argb_t src[] = { 0xffaaaaaa, 0x80aaaaaa, 0x40aaaaaa, 0x00aaaaaa, }; argb_t dst[] = { 0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, 0x77777777, 0x88888888, 0x99999999, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, 0xeeeeeeee, 0xffffffff, }; const argb_t expect[] = { 0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, 0x77777777, 0x88888888, 0x99999999, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, 0xeeeeeeee, 0xffaaaaaa, }; // clang-format on const struct pixmap pm_src = { 2, 2, src }; struct pixmap pm_dst = { 4, 4, dst }; pixmap_copy(&pm_src, &pm_dst, 3, 3, true); Compare(pm_dst, expect); } TEST_F(Pixmap, Rect) { const argb_t clr = 0xff345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { clr, clr, clr, clr, clr, 0x11, 0x12, clr, clr, 0x21, 0x22, clr, clr, clr, clr, clr, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_rect(&pm, 0, 0, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, RectOutsideTL) { const argb_t clr = 0xff345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { 0x00, clr, 0x02, 0x03, clr, clr, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_rect(&pm, -2, -2, 4, 4, clr); Compare(pm, expect); } TEST_F(Pixmap, RectOutsideBR) { const argb_t clr = 0xff345678; // clang-format off argb_t src[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33, }; const argb_t expect[] = { 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, clr, clr, 0x30, 0x31, clr, 0x33, }; // clang-format on struct pixmap pm = { 4, 4, src }; pixmap_rect(&pm, 2, 2, 4, 4, clr); Compare(pm, expect); } swayimg-3.1/test/str_test.cpp000066400000000000000000000047411465610152200163770ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2024 Artem Senichev extern "C" { #include "str.h" } #include #include TEST(Str, str_dup) { char* str = str_dup("Test123", NULL); ASSERT_NE(str, nullptr); EXPECT_STREQ(str, "Test123"); EXPECT_NE(str_dup("NewTest123", &str), nullptr); EXPECT_NE(str, nullptr); EXPECT_STREQ(str, "NewTest123"); free(str); } TEST(Str, str_append) { char* str = str_dup("Test", NULL); ASSERT_NE(str, nullptr); EXPECT_NE(str_append("123", 0, &str), nullptr); EXPECT_STREQ(str, "Test123"); EXPECT_NE(str_append("ABCD", 2, &str), nullptr); EXPECT_STREQ(str, "Test123AB"); free(str); } TEST(Str, str_to_num) { ssize_t num; ASSERT_TRUE(str_to_num("1234", 0, &num, 0)); ASSERT_EQ(num, 1234); ASSERT_TRUE(str_to_num("1234", 2, &num, 0)); ASSERT_EQ(num, 12); ASSERT_TRUE(str_to_num("0x1234", 0, &num, 0)); ASSERT_EQ(num, 0x1234); ASSERT_TRUE(str_to_num("1234", 0, &num, 16)); ASSERT_EQ(num, 0x1234); } TEST(Str, str_to_wide) { wchar_t* str = str_to_wide("Test", NULL); ASSERT_NE(str, nullptr); EXPECT_STREQ(str, L"Test"); EXPECT_NE(str_to_wide("NewTest123", &str), nullptr); EXPECT_NE(str, nullptr); EXPECT_STREQ(str, L"NewTest123"); free(str); } TEST(Str, str_split) { struct str_slice slices[4]; ASSERT_EQ(str_split("a,bc,def", ',', slices, ARRAY_SIZE(slices)), static_cast(3)); ASSERT_EQ(slices[0].len, static_cast(1)); ASSERT_EQ(strncmp(slices[0].value, "a", slices[0].len), 0); ASSERT_EQ(slices[1].len, static_cast(2)); ASSERT_EQ(strncmp(slices[1].value, "bc", slices[1].len), 0); ASSERT_EQ(slices[2].len, static_cast(3)); ASSERT_EQ(strncmp(slices[2].value, "def", slices[2].len), 0); ASSERT_EQ(str_split("", ';', slices, ARRAY_SIZE(slices)), static_cast(0)); ASSERT_EQ(str_split("a", ';', slices, ARRAY_SIZE(slices)), static_cast(1)); ASSERT_EQ(str_split("a;b;c;", ';', slices, ARRAY_SIZE(slices)), static_cast(3)); ASSERT_EQ(str_split("a,b,c,d,e,f", ',', slices, ARRAY_SIZE(slices)), static_cast(6)); } TEST(Str, str_index) { const char* array[] = { "param1", "param2", "param3" }; ASSERT_EQ(str_index(array, "param2", 0), 1); ASSERT_EQ(str_index(array, "param22", 0), -1); ASSERT_EQ(str_index(array, "param22", 6), 1); }