pax_global_header00006660000000000000000000000064145571035720014523gustar00rootroot0000000000000052 comment=6e9ba50a72ab07b2172d11719d91c485028aab6c swayimg-2.1/000077500000000000000000000000001455710357200130455ustar00rootroot00000000000000swayimg-2.1/.clang-format000066400000000000000000000011241455710357200154160ustar00rootroot00000000000000--- 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-2.1/.github/000077500000000000000000000000001455710357200144055ustar00rootroot00000000000000swayimg-2.1/.github/screenshot.png000066400000000000000000012306211455710357200172750ustar00rootroot00000000000000PNG  IHDRysBIT|d IDATxwx׹vU/QuM.;$.qعv87)5q  @T!zSP]Ih?$Y" ~s;(^!B!B);B!B!9HA!B!^!A!B!BxB!BdB!B!W4Al !B!KLj*^VENk0r!ze.¾/&$NI !B!7 A"|sc#jyQ >q=R[`w;,ORB!B! qdy_|.{aa2LE8wU LB!B!P2_V|Z d@k;.mjZ|pAD3 `7;'!e]X2D 眛t4ke< EŰՐK# >.sFPh 5nț !B!ou01 M4;rm E~1h0T 6ڠ>>!I0K:oR/wf\c!ۮD=? 0%ç=]g@XPQ ϜB!B!U<[ِݹ<۪dP]| f°Ήmf0e`FB[-ܞ @ L?(t0=s;X.hks (3/Qm5pWpǴ2(C B!B Cs;XiJt+!~sB HlfPb T¢+ $^W  7B!B!l<2C^{ۋ> ?ږNvF2lffB!BW:~T2!nRp,w'C\gXU-!B!qW bvPiFC(O&&1,>CϹU+j{j&1.=0V <|C oBq=y%Ȑ9'O'NKrR"˴>f4*L/.ybcHOx˪RvtMDD6eyQW_ pl%'ɧk0h4Vݽ[Mf <㴄Bׂ &W^{`,^8}dcZIiS4F:~Xv 6rކN9GǏO~vf23Coqǹikkgڝ߭Z3 hlu;j)degڔf'Dbm.ݽg񖼹k.s'AAWcc9sFZBw}zy[ߟ#E?I\\ӵȝ{LGnN&lsAuu .t[2Ə`>tΛfzL1dB2LKjm߸i55uN?0Gu+Ǚ/)-CD|\,KINJ )):HZ[[м͜?w2Z_zlv>^X/~.VKʰd"h4iuUUfgAt˜:}wQÓt~P(N aa,7)'PZVVqMt< foA ,H2yb?y*QQzr048u0bpF#NNO BRb%NV+%WOܹ׻{M>Z}Liiˠd?5mױCU!F2ʴҮ~+(]?D455ź ")1GRRZXgN:_Ƽ 0%'~Z`@ۻͶ?evL&-˄l gywϳ,[:z}$&N9KQ 9XNEemWp9~2!/[ W Uwh0ϷG$$(g<}Mdg[=RK5lZns^*OQWWOiY9՗j(-+d;.&v?93 sc9ٙBS l8˝ !7*G_oϻԧtU/ڗjjikkG`hBrIJL@Su*oe`Z8˖.BTze=nq׎ٱs7AAh[C,*Q(|ՄowAGhos-ƶ6+ݹT*>tY=V_]41yчhm5knZB|iyw:JEE 17޳*n6ƍCfgf3kobƭݹ׃̛3/j3dgpyB!#`6CvrF.E P(GFiU*o#y8)ÒY~b:[o۩nxxO/sn\AGbb<6lf-`b9sÇ1gt~U9z p^5JGpyw&'B .uťeD#1-\.ˈtZ飸yB`m~L+j7-6kǛ4Fs%fwh4N/6l6_RÙ3ӺtUڛL4//.nHp&j]܊V%""[}q^=l6rQ>m2;g-s4Br0-)-C$>>η"KlL͢Du1ŗWۼj;W,CYW\caI,7XFp;7wkw(Jģdgt ,,[s=&Nnv&9M\>ĔI3k:サe/^,&.6úKv!B| JM2Z t++ ';rvx#fB^N,[񹸸CG Jc,m(=aLfoxF6o MOo?ΙQUDFs$q͸?lش{^գh=jY9u, 4!a=nl69Ήgلrb FZCB|,'n2њ)@l㮐`0L7xm OQTj1[,7\ PtZ---47\sR>2vǶ}jX,׭o `|}l466v>9jQQmջb@Hp0fDFï~#;?B 22ck砣V+ 7M !S/Ml B!nsgK>M] !µ&6Ns !BOpߪhnn&sBA$A!Bܐ"9b8JJӋE!C B!!=w~;B!GB!B!ėB!BdB!B!WHA!B!^!A!B!Bx.! fNFiY9-,,[MH1.\(xF؟;L&"<RG\B|9ٙLl6niZ,$j<};iڹ(`hD23u^^Qno'(7__ϝ(p#Gy=}=&!>?V+ 8ycE'^ J%c=j$AA-f*+ٷ5u%__FΥ}!bH (=?Qw2,^8!PC}s,m-nNǤ ,??eebcXp57ۜ {OGb6i44R*`:R__zَ_eά鄄;Md II .#^2xkS&O`^2|KLwpP= s:?=ޮ+(c0eJJHLZ9V, ?EO:jڽf1~,z-з2xkGk6y맘L&bcY}JzJ_(ګyB! bܷ.&Ocw^ v sg¹455pN<;}Hxxy9Y--={Gח)TVUv&ǃ2@{{;ov1cF{?F{{ӿrcWVΫռs KJdLf IDAT)`Xظy[2ɉ^g_ 2"i<*^xeV+3OjބB!z㕚 +Z˭]Ø=k:aTVUf&jjjUwsX^=O>l@6rMDx8%eWT^״̜>tZ-u<̡G{ܟ6uG ɓH͢'ٓ_C=wHNJd W:p_[}Ʀ;ģ̟;}\,."8CvM]w@Y}TWװys]+8t7WFݻBy퍷r\j5M-.555Qkz\?7;4F#էk{_ ..S\qahj)L/9}):08y { h|<皘8x(>V;}_%.6ysfhB96ki!7*2`3Hӿm tZ-mm=J1w#<1"5' 1rD*ǥ|b6lʺ [l{MTCfXAf͸mRa|ǰX46&mHRR?^Z:B!čk}2ư`,~gl"/ |m%eDEfgr-S0[,<K4oa(mj|~G MMtZj23Ƒc\}-6{ m)3whᴴ8.rm%&Ɠ1~,v葉ibatx0Tnܴ<)V{7-/󚗛Ix57`ܵ,!2";/={]OC; v\AsKG큞P(DGV{~X͟X\tj16=eK1kƭ|}-;ݹ&L&>yQW=Zg)Iܿ^~>߳J@?O< &ɶ\X╴Bׂ &W^{`,^8}dcZIiS4F:~Xv 6rކN9GǏO~vf239r2f۟t\f.mK/b,+m׻̝=M[)mZE23mDfv=w__z;1؎=/9Ο;a4Q( |w?+UU]b8.ť$'%HMmz}$77wҹ674r^W^YfWkYm -]GGNV'O}ZEnv9N鳼Z$Y-';,?HÃiZ&O#>6'O܂Z"*JO^NFG{^Fh؉o }2~pp!5hZR%FNsw MF O!)1RyV'ݽ&8y[!JI\lLP8{ :k N-B!P2ʴҮ~+(]?D455ź ")1GRR7w2m1,9 oe=nl EGa}hpU(f_ ǥhC5w(ͯGnԙHaQ -[u|Aq{ABywDvx/hTsf51X`.򲁎sE]]=eT_hٻ$<Ѽ d O`ع Z M-!Je}TTTqɤ0n2;e6Y~7nu,νܿ&-`ޜfQ!';.PJB;Gl6_W픕hbq) Yp. }d$6QqN:2,/#솇_d2熛mJW: & 1iX\( q$&ƳnfvY{%7;ǟ]կ{j?ԸSn&z45 qN %1,gBB;NW焍ihlt<8e_Z[.%N>F#>|sfMgQ^Qő\K)t4'1!7|+.BZǏQ\ZF>¥ZZ[[9"~HNK>Q( 6mǴRq}ڬoҺޚ{Ͷ?vR{M?/\qqٮ==.5ħtr+Znu?x~5mι76>MvCiy:ܬ RSSزu;=r8LKʰ&o;Β s2ua|:G˖O6z$?wW@[[;#G '9)^|2vɣZQNnF `|}l466vyjQQm_OCd2QWpݎe``!fK^5 ُ8z8c F[B!D^(A!BΞ˗_?}B!k/MlB! UwL\g|] 0!H B!!EEE2rpJ%<99bB!BqC:{v6Bq .B!B/5 2!B!+$ B!B B!B!B B!B! ]B\A̜>Ҳr<2q[XX(N‘c\P<؍?:wμvf6lqT*Q#  l1SYY;swuv =/9)̌qݦWTV%/')cZ$'OqZ֓cꩾ\,^Ju;lhj|}}?wb=rQ#S?Plv;uu-,s^ύp J/K]] ;+B!Ā (=?Qw2,^8Pߟ;q1,^82ۜHxGHNJnc04iÒyo{>m7̙5`ilQ!))eٶkאfٜ9w^ B!T$ bʤ hRRXq;[sIzQyr#9)l`O1LD|PLUUGx𧎿x_x͝A=v]}"H>Zbtt:+\-&sIlscwUG x8c0eJJHLw̒ Ïױyvf3a!<2{tN(@%B'gsߪ<1{_1Y7;--={GחmdD8c(8x79=VTV‹/cZ9}G\oJRFETh>h4:J%jJNFuZl6wdg79 vƌ1u*TvWJ~~~Jy\.h5V;κ [F}#v;Fz5oB!JMw-V֮Da̞50*Yn55ʪ9z}{{WWQZZ'~6 y C9o?&"<2+*kZQzfNFll :ںz =OYO:#GIy Kvfщ/!o;QHϝI.:pfDjG븫#@TVP(Uj %AlDB% gcf 2( dٜ9{gTv<([,&Z|} bb6 ־hjn>'z1u@^fghtmwoQCIzp)ԏsgqQr}.ZyVw1m}wS]]歟`$$q}C[QB}ü93h4rH!?B CV8 <}|:-IKÏkZZ.NѨT^ L/vWOYƐU-&:pm646x7ϝlpvokh$3:עTjRP(U:T* J RBBPP(Q*U`TRnW0jD6>rJZD%,,1X,ZK`۱X@ '""لjnadžblfV V ݆҆jh4o˭=JUO]u#<"u8O淽1r8z8;?1 rfJ?\C/Ր1>e!.6Y3naU/r9s<'O{vFC1Ѥ&:JϿ?\봎Fo=¨4 TVV˴)ud^;bcX0w?ճTU_`E,^8rdJJˈ;[`XxO/8 hPPVykr~G MMtZj23Ƒc[}-;;JKMM=p ۶?]RWW4dOm 1QNUKhP)utPtRPhP*ՎJ: (P(TQP(j0?Zm&ŢnC EVժѢQ nA "0@BQ#j`XX4mo8jd;][lV,V3 jׇ̗)RMt4>8>l6ZZ.s^%$ع{e^롙DZfC2yh7pHW:gtb28x('F]py}KIN~7y`#`lvt>,7Q#Sٸ>h#0w&k,=wbo64-]Ĭ]\<Пw?={X`b^l%#B ^ 2&^yMm;XpǴ26i t#*2ұ@m0 5A:sN?M]̌qFP+m3شe[y_ӧkw~sg $$ͣu@RCѡh|;?hP4 BZ *: +Vho(h5|hoaVGNEJD@@$`fw,L& Iv?||P*MX,6l6#РPt[;c 6XX|XmXXm&,vj)degڔ>P{;-6&ns9o}[.zWU]b8Q {ϝח^q.&&:w-GfCni9ldSsv s3qpiy/qEǴv6lJ葌I]{2a049jjjYv#cF;11w#pt'J@T_uMrno=ˆdBqSq&MtS IDAT%moB}}Ӵn7W/^RZN#88X.@RR"5u:T TIfPSȟ_y]eX}-r`ёg\6pO~Å֢jILDx:_446V`WRh2*Jmg텮BWA ]YZѨj D 6zZ: NFF]RO$ EW-+*0@?>Q)-X+ `#`[͌RaŽ ՌMQf3cuj͌3̌QN?tԾq_9G . P̛͔(-+gI]}=?31H=A^n>Kٸ>ݱs4w.g?_'Oc04u 2D# ̉Sg ]~tBYy OW aaN MF O!)1>V+%^I pE/M.uր[$B!čd52iS'i]WNonQHb݆̈́OCD#))ʛ;WUu\}8l08N'Ĺ8nwwwg9?9u8oر7| q$.i$f9?!aI }>{~Kq7rqǒ$flxh?oPH;Á:c k_E^+_>yO^'OGÿ;7qQ b!Rj4ՊMIQUӃIh&L$(fɅ$R%XFZ BV$IHSIjQIJQ`$#RytPx]]Ao(RnV@  K2,^Y/] X|d#[aQyчٰ>nmm3=Tx=Wgn={#1GCٷ$;''ڿ[nO}ys_}K.:,$Ck^u ?sמXWJ)N8X~ֳ#UTunk?6~&ٴy O?Mpx4hРA7 ~ⓟ>g얢m9K,frrIMэ{,Y`CoN\W7)A >8ҎpaYsw?z淿Ib55=rPe= 1I]S%y2,2]äiF+I )T AWCT2H^ !P3HJ(k=FqPemmlBkֆL 0f |txQDҎ~PJa)Q!e$!8bDTAaQ*ByxݕWe\o EJY… EޥןcL7z>[<ɲ9YC+.!z~6zPx;nG Ĭ6<σhغu+r(p[yͫ.cY5ùkW#u/;'Ym{}{7}'xK.~'tg:s,Kp \wW5hРA/% H,ulP!y.YLJ~l&>1^wիBdbnqVZy@ۡ ǵon+/?9\Bpǟ`.uoeI 2vRH Jv) Z:Z&I& *!o Bh2"N=d.W$c[-;$xPhMQdxHEMBa@$|hUJHx*Qv5E1%]U ,2&,B%#Ѱp0##˹;y'CB`\sziv25=,@~/F9j z:~9@ EQ2};h>zbN u8LMYw4ӝi?}{>شy [xr`Xhc>|Yվls_~>ﱍ }\d1'xFN$M6GN;$pמ5H⅑z?ƘH;ÅV`h÷c :_җR+`S0^rdo|k=FGu1)ɱ%lyBZ(UBV2y V#T)E14 kÀ`!kv3ʢC(tE$M,h%H6@@ ,B8)M{kלMƯ|;ODSJT%J)1)cTo"NP}ux~;ﺇ7\s%?lvNNh|!=#osr~P۷>ʪ3O7ûxbqw߽/ޏ9P<ߝAe.Q;B|醯?frb;1vɬ:qs#R"Ao}N-?}?̮J%,YKWc2D ]26Jy(eҡTs}д2) zjr( 0};W c0pQ+xمu{^$iO5n>۶m'RN88֮9rٱc'<0匏/Yݿ>vNbxY`٫M?5tW8U7SO=R;^ gزK/+y"[n`۶ ,[uk! n=ȼ=\uHs BDĨI FiR(%㕩 )B A @"m2*Gx7ב 4hppXH'| !qlu#>1-myw7x1b\}ի|\|;߽I[˲[o EQrrk9wB>֟|k׬ Zl8Ҏ@|wN<8&wb׮W-RUqU{A1߸/C! J%y1fUgRk[x z1FFd2_""h)N0:Z!HHՅpdH4%Y `%tŹ@Y:StN(I+!BKBS!ꦢ""Z'o):K0&5h0Ɠ$l09o!KI%'w ;v9-)f/[y qq}ԧ?7mfrÃ=Ÿx˛U}`yY6S;M r[yϿn]w?55Yu˦ȗnl&}[C<ȣƝ~].X+/h𚵎;ﺇ_w^Ct7m¦[8iɈ#BH^ V/JZ@u/R 0ZDJPbBI )$@!ʀמ2/P#Qj  _xBpJX,4hA|_]?vAV;v>+2R4;>d ,Z=qϋ Ɩ1<)6c#رs,${ce,`E=?BBKp!d$Y#s%maFR`! J)$ڐ$ 8)] w{Xփ4*HNEI-"o< 4hBo.yx <_LMM35 ~?ߧ /P=vgƞ\߽!l# B (FT@jI! "QR⅋6OCFP6H!ZeTu [*Pf YB4pH/A"BzB!C ̦aKыdC/k 4h!4hpC%[ RjUy  J&$I##Ì 14&2:V$A%BB8'(xy.sIY(D%JQGqT3j>W- ~!( dp1ڀ Ā !,1o"N*%]P E9spkp1<4Ͼ'b| > hpp uT/eB'<^z>u9|6T:_ dHUY(IM2%:5)" DtpxB!@UA 8MJ(%lO/5_ hH Ph5{ !6Ad$I\exMj Pe'PŢI(B})0hg贞?̽Ł}܎"<|esDJN윧,C$> k2k=N.b}o̪%$|@Aձ)19.VR–!$R(TH9CYKs'"dO|Go?2_ Děe$d$FHPZE⴮5!c.JT3չNsAT 2!D. \B )CVT!tpd[d<R4hРA@РA# )[hPYg30h,X0cEaxMn$!I*@FAwQ:< @/E(˘ fRY_|WJ kyB|BȂP-sxOǵe𳉆U cwrΗ- /Gr9u_B@]R1!ϻ8.+B=B|3Du 8ݤB$RN4P$D$t64f(R5R*r@ܙ 2 $P)*EI$>x\p,9\i)lI?3w=s\@hLLYA8I(""{|qJܑ[ 4h`d24hC Uy0r"Фi$2>##C tZd-C$$hR9s<tU,WJ!h0& QJ9 ,`mM08_;[)j"W]UUTL 0kӘMnRFFݎQXJ`L7P)&\ B E;>ZTHVbuB:2GSZD)hmx/~!0ZRLEjRHJUP Z"0 4 <'jrA&L$tH,0!͒JѩBCt\FB!ueP u ^=KAuZ@V N~P A O*TʉcaBUV | \J%p)SLihРA}@C24h5Bv$\r!E2&I*J贇Yd!GXpcmFFmMhmIJDV*8bx/)xW أXHN@a᜘%*BQ%ei#QF|ZP5 coZ׃317܌F IDATCIU @C@˨p2IʊHЀ&1PHaV E(KutU`xg1/[?P̒ I$ i1)&ё`HEB%DrA%+傖`beV XJHR)$2WjO"3 DeRc +-:`-l$$tTAb>YaX0\IQ rЉƥ2QI(yߐ 4h'4$C ~ *`2U/DkD-m$-jnwX`EX`cn)Tj)T$Tʃ`bqUr *WsTB>DXk)H4 JKQҌZym0PY4pH2D1>gTDD.B H+Q*f!#Za壌IlPzz$%H2Ch$ x:"ćK_{   [N4#a:CX[)L`2 6(Mh"<|!HX!YIJ0``}+-.x8U c 28Ns"be: ,YJTp2՘vBjȆ 4 .b6mm?pc\r˹{xэ{w7^s)'U/\u<#;Yz!$@|0 R(}l idbʥ,Y VKeVKn, ۘz?=ZA)Pe)@QxRPԃam*)lB-RTOY)"]Q~ #* DE( XDjpHhR:B(&jEuHYJɊpXʏ=Uheb3Q쬤, +q.~$$#k8ߥ(;)J%Chط}=W%Iychsm۹ lx}־cukY4>NiK۸n>ޞ !;o-GNs;vr_47*^˙<ýKG. UBE.TυT36( yq?4? 4x!I 'y{cٲ{W_ɺs} ʲ`1_ʽ??T?Bʔ @V)iڢbY; 0:Z'.bxx)=ZiBjd1$ DE0H$9;z~ULO{ff<3zv.E xqK>Q+Bm)U]X060j-Q>ʊUs|!(VIb`QQQgJ:ϭ$r&QZe{33}djW陂ndѢ,\0"/0F(JAu[8^)E_-*૚̽)\׿%Ko F2!MS7_+.o|šg?~LOpi_y;z/G|5diʟ{߆r5 ?yֲUqk?~|t{y nH :C۳CTcj!Q!Lnpx! ph1Q41Uo# K GJQ N `S)lI'i47V5ABă[IRPE$&H6Pd"Qi$#PG&ѩBg|:'M8d ^hHW WG?ɇ^5x导/~^۾c'xǯ'p=)ZsœON"D R$&@c> xfjaL!4h:C%bD "MEX7-DKDgsKo33~YL`j13cLM@ y.pNUDfEE2D{AXcB0xSD@lPY.m@VV QLYsQo`;ߞfm!@YưJ"P3ʲd׮򾥟LNvKvM~Z6VUDJ ioN@jvII'p^ zyq 窵kɧ 0oy|_>]gI]jvo#oxOg_;w ߻׎?6n4`r_uk9c^$]{<(fɅ4ދXMi ZC픤je$*Lh,X > ǐE|,Z_T(ʤ :hPČ1hAe I@"QB蠣A' hv$"`Id1XaCNNA` %bfEqP*No=XA(" R=.28#A5&5Vgf/B xpPHtu7p҉s嗲h|!O>4 lݺ ,K7o)^3ocӦ-|:$v8b޷?Y4>6'_m-[.+& &s;ګ$x/N9/<4qw{goN~~V>dZkjzݵk s‰'eq?}[B}r.}ֺox޴~ۓRr8g*.Moo]^SvPZqq֙pBl۶q'qw<7H $q\'V+ctduҖ!iEJAV% gEeeǼgP["6?SIG3:QA*P,/J(2$ 2hYAJJJ_ =43ΒU#P aT"areh9lCDX$1¤ tchРKdXsY1Ɇ7kl߾$M8c83{LOyi(%`xx^v_y!۷Á/z<_>{_"O>sUgyg=^b9gy/[ƢE>sΝCY9B(z0XJP$s sU}tR X)-9Joqb{GV2`B{u6AhmU:UC "ɠ>h:ab(``f-]J>{laN"&թVHЂ2P D@!DY %+ Yle=K*lס<&A/4˹՗;#z:A|^W _WMߴK ޷n9\SZ?!|Hpżo/6gndi+s,;w 7=_|ओN_?yo7sQϰ:Iӄ /c9A;{lx`#O>9B!HQ* C[äcccd33%J%*@˪)" $q6.CxU*OW2ӵtD]eE0xT(v@k7  V: eeۈۊy`$1*bXPuւ,@YUlcf+JU)>uZ #ɐ 2@J8癙)KG1R1yE~({LAҜQ%Wq_5~Z랓8txz/M c/X0:2̪.-gzz~?$3. ﳟ"W\~)MEEĵgOr=C33}}>{ c|Brsi|yۭۺ5Wp)'q_ӟ‼Yx7?֟/b3/^{w,_ԧ??Pc^˅Wo!TĂHĬA|ZT66PJ6$mnkLK3t6RU9H@q\: L(mTXk)|G J0U:Dۘ]~JPyvpP8D'H/A$$eAi"S؂^*[d6# ;˝LJTsLT%)R"DX .@DJK\lkJc)JKJcD tj>~РAP1=3k^Òŋߴ.x3ONEm,]xܷÁ02<=ow_FY}`6pOx}G<,_']FQʧXRn}|;"E"eFtheCdYVk%m¹hвT\bD2&<'",(@_[z]G^@YHR13cy&&r&[vQJT!xk%PV*AiÀ\Z !锗;ELlU&@H3d7+CkZ@EpN(}ET*'* cDDפ$,@QDDY3diFfhh`zzR7+KT@6Jnu#@3Qޯ) + O`9gs/,˽,zh<7&^+p͕+{ZI[1WU-o롵F>mrXfusرc'xUk9][n__zr ,#zMܻۙU윜yVY}љQ5H`z&Zr!!$BQD2* A+:DV +#ʊ`6E}enCkEKHTB!K?? >$H! ^ 9W9gj[DD'$$d2#)NIU:SyE(I0e.BIWZ'H4(-Q^ǐGWY'JU" C'8)PFRiRI TGHoOkT 481 .<'t>;-ۺub\` Tz[ߴ4MarrG\c7qܱGsǰuK,21ہlH޷_~ZvǾ~ׇ G(Bkyg.HQV,?^v g'g2%I:t,ꐥ4c΂=B kC?w8WKܐ皲L2%ֺ.QZOAUfYJ]dg{ߔP$*!U)m&UdT6ȂRR둖iTW*A*vY0K *H( K<xqhbQPƟ!RKE= 480 N=$.}zw{>Hx/ެDjg5ŊKndltc9[oKХCon/%37<4tw}q A'r\wwQlٴymx͛8Sy|4JuZ!t4"1vRr#ZU޳AFr@,IH@Qzhsj㽧Wo EBYB)ܕ31eDΎ99yg XQQ 1GKszBU)>m:AQY.Dk,c d+$K {F8uxmOXK,VȶˤdʲxS " 飪2eV8(tי/{}S7Mxt^.V^0`킄BOĤ9xʡs>o؝NNt)ɥ`>3%Ƙ/(IXa4h'"@bcWc81 IDATS/sЕ4Muח7q~E=b e?G?A]U<ɓs7)W3ύ/~G9mB*.RxpνF:0P©`B@!.4ZfhzBXbc}5x |ϙS6[Hhږi7eL1jVXY_&!ts^PhUeQWHVzcLE]Ԍk,-qѥ-% I i>x} zhJjtRh/  tΠA; m5j cD1033f;KK,kzӼi9'i'g(o{7\]{]v$|ϻ4Z=Q{NdqmQ:vKY>O]~K<\s=ՔŢIBJ X"U"KF09HKȡRCMhAȆ6&6l7;lDjRKJCUT1 2!+zjd)1%Avg$X4%e!։ʉ"HJϝ DFb N; UF+A%v *jWD1Hg@2B.&Si竌XRgݜJUK,suSrt.RJ= WLq\w鷿{]vc{pn{|3W(ReZ Y I.Cq :rDQ&Z lV=Au d-}-b\CVJ3B$FY`V&CLe}  UUylJIY* [kuGU^*g94S9s1F"R *B#z\wZ;CxE$X0<αc@͋+3ln'﹗'Nۅd}@"#'/_/Rt"C2*EqG d#Uxp]Ѡ * 5 @S$$1`iCK: fSŕQ:t%]yI) Fu n~>eYpe }\⹃fR`ZG/l6F* A~MX19!8TvЫqcP>ACQ" ('gbӇ}.ɖ;XX3ʲ9t:Ek>:T x12c)PjxkIyyR"1y+z=Wnx]zKoǎg>oѹLcc(n,KYLw㭬Lۈ1>b>yrMq)yxnWt~k2>XÈEkDO4XxKt>SEP$դ2. EԊ@, ]0d;0bYuqA8 -nVN{q5F3vcVIn0  e6 !Ed Q 7RN3dX2JSPJ!m! Vk̾LsV 2 ] 6Y!*h+ĉVL(-6 Bh&Jqdy:(fg@%X⹁ ?:t%G\}C;xMEB~rZ]26 _۟džpk a8v|[e5a'hWH%Oqv5UC$pVbcV (MiHzvݝ3g:Nn9vl N%ysϽ[}6_3.Ny6zA~^33FcMhFVE|̕m1CK )u|CHckmZvN۶h%㦑m+ V248W s )-, {²/imQ8'jcuZ(72%o=7,3u]iV|J"'T5cܘMZx/"r_Mϻ7skx7C N2>Ë^x{]o#&(\3V%`c$J)n˹۸K_a{YOq\{($w 7;>rMq)y28x?׽NMo?g{ 3=f^xSkTk67#/8L) U /MhiF!Bη4D̻k!.()֦4NIUa#jSQ(bdV1$Z.3Tg -Nnh@k-5R9rD9)'U+klk8Pd4UN#RJ%Ǝ z$_Mqc[`kk&7vT7yFXb%.;ЕWPuv]ѣo^%>\sj|+{ȑkwc}}ᒶw}>gr;1/y,w3}ϽzKO߃+8 ˧khRug`Vsoows*g̩G6+Y CƃNQZa4ZewWCh퀚͆cǶihۀRBXLh%1SB+yl{{- B(ѓGZ "1$RL)P5mTfI|QȄ}:ŇRM=8WBR,ٝS.WUM`žagg6Dj>͜>J%h/7&ݣƚ~liP^ /#%S+UYvUMR ;l@C!"q*/zcmqXy7=Ƌw]o*o[__?Gy(˒;^rn&~;>xlxŻk_*\] :sE5/'Nw[& .w{쥷/?8~a1Ž 2ѐPV90gX }Pz{B:i{PfK&>_,w.@.$?G?m;^?fBeR?s_A}ԓlxϣ,bkqOL(f3lG̅tK`E7t m"SƤA.1 aMrfb,~ۂ$"J䐉q7OJZd7Ƈĉ'XL&b@)Ey)G6mQJb#\ȮkrÅ84lHQ&)J)VWWX_['l6v9#&gNA@nHYb-G*U%=}sIK {e߁G)93Ę%a1V#9R+ָwRTmc^u+w|6_d>}fCԧ?{8?;Aqï]WM=8 ?o~G>If9/g'_}~η{_.Rdy\wJ5;>t8}ķ~ǫy-t Ml/3s#@pYT@^!ڮ%:TYuPh8(  1q S?eXl{ShO\9q1XkXXeɻ{ eÇ63@;| ^;Bu[SV̺u[؊UCu֚R )ln $ Ő q E;2X#Q t~L ,,I>d.t\qK,ij羃_W.7VV&g6 k債 loogsmznWLFl֬tR(G_ax_ж:7(RU9sWhk0!6}m]}Q+MJ m:9P8Ȕ r>0fP4X# hP:b9dA>$4:B~1J<ɗ!\nN0d@%=~y4(ƀMYXD%@!}!.D9=Pw|] s8B~GİK;xM;t~ m. <5A*f9N?v%ȿu3yܔR1kBlnn2?;Z.h*)%pu}5l\1`cJ¨᬴ED"mhSv]v]6ٚoqv~l6l[4RUJsجRB4$%> H]mN;'#i0YziG16L7gt;Ka%x߼zQ2,ēE}y\$z8C>G b5c5ZWTT iD%EU.PRZ"|'Ռ2T+m oK Z{y,A V*LԶ_%'7@he BvTMW;Dw!} Ym:F+JQʣA)WZ|Ft*h&1W}]d;s=uO,v1+RVm\+pC:Dz۠6fJ@(m]ǃ' G~>o8~g)8sfaFL Vs>[RGNXjM*A"ʁr]•W<K=)qEW[td-tO[ ~J[I&̝.z 0͘0qI1BŤ:i};d LBb'@Q<5h(m!Rh ┕zK+3"'|bG[%O!A*Ɏ%6-m=?Ja0eDC7[X:;5"d p4I bBR$(M$a(Έ.s@m#DK,ij Ka%к,V'ju * JՐ*%I2` B(PжB4m\AFz$:+*+ń@u$ 1[$ !!M|K$oa5ŒY7 *02UԶ,JʮY+,O X!21vc ,F?$,N&r Qc!5Ai,')&(ӗ 1lVDK,ÒdXb%.)5dԺ@QUPT& PTw#t]ȓ݀R>uMaP:Y m=m+gE:@ɪ{JrM k39,Z+BHL(BLiA\~e_&"ECBUgy|[أjiMAd,^+D&qO(%$Ɋ |gUD O={j4b Q;DB@*⡕P9hxlK F*&%TETladD5(*G1vI+A7fXCsE dUã9 tAАMI$X K,ij Ka%,v)kcjzD:>Po@HJXV fFk uHRtXDDU*FH .P195ymWÀ³k=]pp{}Ud? =kΝj>wA7Α+8+!bQcQ"G'X(y}l*>"Td(#&#MF%.+=2A*[$"WZ&1ɘjTRօ(j)@0 b$*F$)HCƍ+7p ``cjJ**SQY=](L X$f8R1$1 ݜ7Ht5G/։l Qd$vSٺj\IUMI$ BB& PCkFO45-Y!PQy7'A,lbA 3 b )!R:)!#bIR&S/Y5 IDATgv.k%XdXb%.`GCֵX#&1T{`4 j_6Fbm ɫTQ i|Bo?`H9 b2'콙bRg>ipgމ{ ѵ*K{âeQ뜩'=iX+Α9O"홰d?i |b\I?}DXOPkF#!!X`(rH(2ѠAPKBc +lIͧd\iВwnD9p  8<レUkիdĨ]$ &$q~yP OkZZR&%[!T:) RRJHJV, KiKq4#;ʡgAi!YVFQF LGV',7{عDRi!0`%,1 fMBߜiKa%ƒdXb%. Z;΍qVk*Pj5)I]eJ9Zs!~iJ]!RW(ո" ` h!EL2(b[ 2/RK3Xe{Aܔ- jNH#O66B {Q|oN;J &+ ESlrЅnQ,duFf5F LEPrIRLJ=Gm88bu eF8#ΗDK,͋%ɰKYs%eika/{/ Z}ݓf ?:ixnAf䉒QXkpN㜬jZCۑ,yxLÅ+>bJDthP>+ZoqğpXr4EEU$Hc}mʘѨTrJ_ ZTZ )uDg"w""jM #7fRMcf\TcFnDUH&nC=Bx)mIszIH'/c#}bD*;Zd!dS&V`=U" ipdNWt;J(]rC!r}frRJE+%VdE"]s@< ]"bOjnecԌ-%XKa%xPnWL(kG=Fc!a^$TMJ5JgtZ+QW2ԕ:[$d *2') FW)bYWzDZVxH}מ wYZ{'OS TTeH؄V1p4KcRd5="n }L,u*\_4<0F;vwzӓ= 6+n!z{DJ:kqD-16{OODDSbpM$) +XOJQJW8+6kdb$!-"^AY)dmU1#FňBU"84ʮ#+B- vh(t!˫Cc bj.Sx"&68mH++}UdJ 3e-)Є8S`.b/aP}c =1Esk\|F Zݏ#$ tt8rԟ# sEy%3($ToK r(|F 'ugDK,͉%ɰSUԧ?Lq/|{ΓƓm&V0z1RSX$A\/Ɯ[Oy..Ck*ht?[&*F q%v _@+YIFRV14Dh!2%bZ/i ~Xd\q@]ٝRzX8p`ƪX&Aeq2 ϭ!$m"%l0@$&5O_[:RG#}3͘癔UɁ5xl-UmVB;"Fh[t4x9spfD?&K) 5a:;NԒ O!Ĵd«^u;xD3g6WS6q늷Mo>^涗ʋ_BVW|DZcO~'O=%|;[&vvvG_#'!]>(zT 0IX&K1Չ r\PB0LF#fY`̢Q„EC&S$f3H;~цVgRN(]%BYS]EYB2-V}ޗ ^47(5)bSoZѥ7̺~J: ԁ#4LLsҊ]˙Đ>-J!S"̻9o[:ЦNȅ*WJ\#ρ1M[7?_jlCiTҤ)cj%XKaˎ:O_ꫯԳdxsdѺ,Vn-: bI bً2`kk{ @ɤȵWQU1QxR ]Q6!}JzL4 >a,|P 2$v@<$I֚ɤ`}m̕Wl X8 mAT MJGE $/}eJiqU>H R02+Nd4ua C hP:3"Ϝa}}+87c}`c2d6Եf4VBK) fHUBKak SǞ-Bց^k\;'Une ;X eaZ`Z c4I`O%"y:HQu4M _(N#ݿ󨶛ihfߠulbLK-'Omq%Zĸ_-q~ nkc?/_yc|_ʫ|%Q}\>`>o.Ŀ_btLLqc5.MGԯtvZRh0F3m3ZJ3o6S< Q9H&F=kp8`ڡ3R\w|ba4e0VI=d2IpToG!qNu9Q2af쌩QJ|) (DC1Q5J $$2j!.e9B޳4  )(T12&O=MhU zBX%ړ((H&a0D#>Y! 2ÿJA eJk9"o&1#C <姟#uk]ֺi Cw8/ʯw3O=#oU䕯Z@n؞zgO?|V yl/ܘ|nڶ?!^گ;n/cMUn-b c&h]<" !j 7eE,\4 AktV㜢Z\( ,rk@aZ挆6&^L> c1D EFyE9{o+B!UʄŐoɌmB9sfFtZ0V.d`/D<"e7GoT1kU>n)K#)/W߱]wmJD兹0ufQr5 'EC%&\)8<쮉q|? ңUAJ0y'?*7&1>9.^\?$&?mof~7>r-loo\>{C_Ͻ%} dS׳xꩧ|I^u7>"7zOq { )&kq!~ >۟Uo|DrQX .IF2@cAܦmy|ʺ$h7t=d-J2de b%#f0%ңPc2Gм}e)_f0p/6 S`fP Ze┣g-Sg;z]Nq1@$IbJsXH5X\ IHÎSY%S(=(d 2 [sk]Z׭37KQsEΟ?~zsǚ`kg?#e&{{u<üq=w8s4łs?c|5Vw/#{1h;OYjI֓ 08W`(,m``/x~#Vqb~ "FUL&L UUb涳)uL&18f3tZ0b0\n6UP`Y6X̧' z2_.9)URZrƫ>up7 {~粫=w<erg?W}-o|gr۶y{_+-OS_s .Qמr~b} wy;v~o6<رfw'ĉd zsn CLehRK 2C%ʄ5"Tܸ/;iCXѥ wc-oy#a|kpRJ($oeذ KU$Ǿ2a/H&T7+AFG46YlY^{XkO{ *+EvLi0iD%J(iG!2 ]_rH̦5h~S[;b%$tS0ꆤb\}r8<ʐܮk]dx׾77\B?| WY,3??!6'?3y5Ʊ:y/y=hmc6]|,nA+2x 0\fFT9, 2f6D B3h )_`~E%BGz^"D%Nd) OsI.dy (T͒Q52Tf6+ޞ0ĘqĨ.) KQX2;NjX%> WjW)C4)]ɓx+i-;ʪ,q2@i:uԵ`~28$!matԵBN͂Ţ.t )V(բUjg AX5߿"ēOēO]u {{ӟyܿKw7^@/7o]<;Go7M0H%?|{F)k;LS2!AK~JPPHDđ }h:u?^hCK݂n.C/I)%Q)1cÕjF0!,%].vtA<"~0Ĉ,K.K -1Ȧ^hf(TAZRhh>`)fɈ s DJx2RiQL8o!Q}M̀\bJD"B/Y%Tf0IfIR'-K>1DNka]Z-_Sj+L]\O^SO4-gΜfoo(x䞻_3qkl7?cw~;w\B|6u~>V֎X3ùJMZ$D1#jF-kUURԔB$65֕!&f \ Y+!AQ {#)72#.. ~ }}̳iY+" =}b1 yJiQNb4 2WefBU:6f1{f1yP=eĩJc0F ֊iba`\Du#l!B(NlcawfIJpي,'`L\*=KA,I xoHYRU:_nlX|LDREiNOoz5\1# 1v|ӿc=λW[lO<{3_ Ÿ,KgkoIQ8GsKJ^l8oc^{_C=c_~l…=>O^?7rOqV%:ҫ~d0 s(TD ر]=[զTPBӏ@o$}$2IMho91 0$ ^b,!~yB';"$},Fœ>㲇 0\`F"7Ckvd> 2! :j\r蠅}=I%t#(CdGG I\ "Ĉ&He\lRD騤p,0R^7dy#R !S%߃"HN~Tu뫧ƫ^s{RG,b;vR9}_Я<_c׼|g'O VQAL(55 =`0yV:dM~fu'HJi{{$mEFE,_Jk S\v$LՍ*@ UhT yba3|c Azrd1x"6, `)Zk:B `0Da1!mP7b2_ m/̅E`̻9.!*gϜ=O>4ł/~&u^G(˂o㺾z/)J)~CrʹL3r3*iXs[?RX3;0ɣ ӋK$nA)L`Y~u5zǖBctf$cT%}ύvcRf4H#U]1`֐~V։rv> U*CJJR;cE"Bf1Zb)  0< ) ̡Vj,"e@G3p}(|^R&.m#)҉ 1hM:fB v}VSh]cR)=A3B\cKYr^^W\6|~Ჳ?_>]ǐ}~r|97e:ٟtYc wy;?2lw1{_Glfٰy]=U3H$tVWYShRmW~4Hd$1lnl@i}3N T"$::b}OZԱd1~l3Q mJ!7-dF?%z/Fm߲,%~μ]0o粼os ℉d5647^CD2]! }" #+Ǥ he>UbYjlEN}ߏ` Z)|M%B4 ØGiCt%2B1zT1x-UWr _zu]Z׺nvᡇ/(Ń>C7zڶ'.9?5l@ހf<;/Յ=^ +[?_=5S0 ,(&(=<>7 f`pNgMH _7 ͽ2s}i5"h@K}"%8ĘK.`̐`HIѭ ll<}2[ RP ʈ<"GrLEdHD¹Pxѓ5#BrTɯ&z tIC>hXzj&(C"EW WpNlgěA3GjV`MYSjuia3, hZl*p6f%}ˮk^rzǷWgxZc=A^rx+=EJiа >yZU&NOWДRLݔV: kil6ND!$kYt oXa{ȼ=dce; .\ , @Şo)MSnd]FۿǿGoxݷj>yK_vxshU3ࡇd0LAMIFR!z*8dFRT<\<DIIc3`B mF!K׉mEUm%Rc j[O69SRmwgQWum`Z )d hEiei0(vH.%٬lH9:yΞf4 !`3 !< tRSZN(JI]PENP9CRkKG0~G\ZK1 9ɑϢplom]|޲\yx7/?~?MsŒ-Ǜ8b*쟲,.? ğ/3u&k_%^K?1n-9>رen۱_z'ozoy“JJEQuAaK:U]gmAQuQt ]FfA潰K)~Ӌs"iH„)P(w7JeaN4]h|K[Zz1h!FZƱ 8ec-sE=99]<., 0#`Y%ug 4n)G0*f6Z1ǯ/cF"T8&PJAT]5 )`cC Goc/w!7xL(NZ$'-AEK50ݜ0?̿2ֵu}-(8u$>_zヒrMJo?{K^۹?r #>?]wH~'_u=o~7w|!ҔG{?2[8{ib ?z_Wr:E(R A]=F%JA]8ksFga 1eihcOmgZS(\̨d{J/ma2Tf6llA M 67%"e)T..(e#0/h܈%S10?E{ɲ,$dȥ[lomRbW>ɤbH,idRd!5d1IZ4Z3Z+fP ]'r10~y}%:#?;!~)~cxǷ˦Wݯ7G?Οg4g>wS?~%?Ksn$^yZ>3x)jZRO*ʺ@i0V9])LQF017Ҵ}DzYb*5<{^fad'$B766נX -1X1De\ h$Vw=:in3G1kw E`r .0MRgY,(mх\?0&Ѥfh껓H1TNEE2‘s3,7J9;^gG [!~eɆVzd J$*R'bYE S\B j[Ok]z^J跐Go8ֵ몍K-%k-k ;;;x988+?Nnf8ibp&3hImE IDAT&!PJ&7r^ &7↲/oZ: a+ D"CMS2(jU: 67 672h&mX Fǜ`1.t4mrsxqpppGm Ή\šɐ!fCĮ(1np&^+6O.6=˥g/l@ekV"wќ=3{f~Gm՜Dh#&;Þet-s.uAYnSNPj ,cJ lJդdc-I4, #H*?),&qKCr އncJyܢu vvtll,s0B`]BV 0MX,q4#MZP.&.'vQfpRv W$R  ~0(@\Gi'xo^y=jxhXuʣ|bhX!VEk0:]m;.k0a\c=/g{,BhzzU#Qw[Yłbo[7@.ӂɬf2q8IX1֫5PAg` Y"wng 3# \Hԥ$hGC;ĔQ<d&R}UD24̛9>xt_^>~)p1p'Уmڮb"(1QQR66>|JJ^7nLFI@flXxJWbE6Z"EtQ7fʵQJ%9J1K`)5*@ [ZfGk]Zͪ5Ȱu}Uum`L`tMQlFf7l`̆$K5>ж1^Ʉ0,},!$ h2!0{ceDTvNTY0Nd2zRJBQ#&Dg.e^)R-q#M(3}?&R,$Lts{_#<|.u#њU3Fj3Ȑ"xCiKSDB Lf$c`8Z,@ cZIϢ"e@ema7W3)&( P=bĹcRERd!d)@ѓ,QJ Er-I$SzTH-؉΄Fƺֵu=ֵ*Mbm5`MMUmR&(&Lڑ8=ؠ3 .p3Fڡhmf8Z(iF1'8$|rx4t]Dcj0 iə5OO)9Q]1BSUX`pA@ж$ ]g:7DRFA3, VcL>Š1@*108>\y4=B1D[*%і4ąF@IQ1y%Ea20PZd*zuS<1tBJugO5XT Vnp;[Z(jlmmMGC9)(؉DUҠ;z(>eBӨ F37 0$>l c1P%.p&=7$Y>@:eIr jON V[68rP৉rnB W=.hcGL'pU5]DRi΄HYg;n-̉lbd3d}icR-q1gH@R 0B=D--2:O1qDi"X׺ֵ úU\)JD6SbF]oRU[&EX3Ki܌ф2ZsS$1+A)yC6r qJ ]sSN*9qĉm1gkG]kR)MCn2x`m ei8qN_`i0{b.HeFxL㒄 #]dC@W=__sB.Ge(Arqmc8ZTqR@IqVgR>B^^!^U(BC Z/1lb]/2MUR*bkdU0ʩ[b0WJ 5)SH0>r pާ@=&v1A =LB Bc@B'@Iżs|shc} ;+FAa p3"w$PŊ6$1RbZI0UJ`LT;t%pXkIJ 5ذ4K\XC ĨȡbtSb:hPliIѣ 7V!m-)ݢ'5a]ZsSka]*--kTdD)V9iY޾|(:iJ&Nc<;/T 8nķ 7>c!p䔻rTS%'OVlmԕc21Եx1>:0VoL.`0Z3LJ'}11{}1$7CH>D麞tl,kYfe)lѶut]RfZ\Aam42!qĈ=D@RdBOu! 1."PARvj(Fja6Z1z4kƸdP9AV,Fa0jS }&V 1E K賐7ILyhш?&ahos@[[]Iq=RJQʖ,X+,eƠ͒ iʽIHQKU;7:ˆ \#b2DDL+[&nf6CYQ CٕXeJ@Ca!cΛ*Ə†xq O ,hAAЕBwStic=彠ֵudX׺Jp"5MN63MYlRVnghSf}ܸ:ìj2X 1y;KĐ(,"Ys8:%ew9̙)'N>]Y0Ta:T, eVdpA$2u1{>Șn`Db4SO2u٧H c ){,brF/iܽG6 ]bvd8vV}d=4m#M:9Mk3\.c^O:C bP(㔲|Z%sȹ8ъZ5~\"x_ 6C5B/լbggb0.pK1qU`081*$PY {1aٙ]<0#B De)eUOdQGƐ|mXae9Y @by`/ϙY ڊ6hmG * I8bj4e\1Q (TMMitN ;a&EIJՄiBCe*lk1XeEӇ^eH:F!*VǓM*k5&BٗAF|}"puڬh }6\׺uk 2k]_em5blcIIUmR`l-3FchjmQ?B=îx)A {!Zr 1'(0,~c.n1<1;'=;䩂3g�ZSK]CY,P)8:%)4E|޳X@,Gv{eBKb(”_CJ8Ww5B.78dN, 0)CЕ S8FAMIWC Ay 4^XRCѸ j$) ~0%BOOkK4[(4)>뼟vdk`R2U46=j$X-KK S.HM f)I ?i3g-]ŤvC] hB` =~a)Ї*REPfŲ1u ń 5;lGu [ df9xOֵܺ?tֵ*-rdllIY( rs3nk,Pdc@ ތ| R:7iJcw<>sg8jw$]UsN9q [%٬`6a219MBX J܌sBaAAa|sp98y=m ڀ gUs"FX (qC Ml> p5mY !b{ b1_Xfa@QxL3]8< =˥iCFpllI:^)A&3HZ1jR,&1 .Ѫ >*ue 3'nL(&;)vbp V"7j:#*5^B4~l=>4}c/_8TU;RDzĢG+M{bh9{*胗4FfT@{hCGZ ƈR`PJQkg(3z08-V|&FuWeW{a$-sxvG_1b0JDJ#UTe,In P t)|X%6!P x3x--bI^c'W4vF׵uZ ZWYŔ6ɖ0M10 iB0z*\A^_X@*% Z g,߾9g8ydg`{`gbsӍ֖c:uԵ#pc ={{.t]hٻгM]OIf}t0f!K ќaFV4V ]'F"ٸ2f ISH;Sdj(˞n( I$ 1 s+$0J[{ AOO=ğk2e;b(L!T0, CӗXS\s5]Wb k?5JOnQmd{grZR崠9R+M1F F {*ez@ Ӈ~ァ66Ϝ3O I0-oh}GQiPf%I) #$b@dHvVԮvdFH=p,#q.t{4Œm#4h#&Sdp[|q[I&z>tgֵuZ zNjkk<ȣ|Sù:qbg?Ǘ5 6620p,ńbs McPb9A 4`e\kcܠz?PLꚪ0ƐR~UҙٰZ,T\Q}bds`gDZQ0f3t,;;4 {O izKib3ᏱX.qyۿ9;;''4GL \p:lmѥU̷=b𨔒d< kA 2+fyĢdU .π;㎳'dSc& >z_}GB 4JL(cѯmfL`:^ (4*?ok,hTN)Te11Ї b$$1BI%dxkٔFҐ䱒 HW<]h6tTª jMDNa "0bDhȃcU{˺ֵu]kAuŸvY>OntN=~;_ tI=eMY87$2ƀR.fʅ2Xu 9Ξ9CQ8W1v EQdNqpxqædg{[ 8y"Uejdb) MUifD28' mO .tcc\<\&|='.b6-(O&EHQ XˌV9:1ϊƕBQT(ٓcbфUY86/yX^dhKњo?3JnBaCkG{=5cIL @Dx> Öݞ<Ҷ1=c0a8?vC*LD>, ;;3 '肠KBWo,/W{xw.P%w~;o]~+.Bnoo|\W=yϻN҉fbRRM౬J\m + PJA9ޥRJl ' (E`?MW) 7aZLd:NLWE1 |(3Fa (UA< iIx @NC0>VR"GWZDFhA;NRc* KҒ62[ByjU:VuU _jqWٟs +UZk`2Y&FbLYns#-H0zA$=%}űښf˒Ʉ|AQ'̉)˒; l9?Ǩk'#[G>78WU-7Mr鐈XkPJR9\1&v MQ2bu0՜;agG|b( k&r/F% IDATRI,hmT wd<D6qTd| *<b:c:z-7~]LpmmmbcyvF@iN0fJ7DHRS 1JBPL {"_DNt*,;bܩ1F!dTq98 {@!`sV$Mt:g>ϓҜ[n>K_2Ξ{1 /x*l%|͛Oө/|w|] n6{O o_gXZuask}O9*V[Z] TPd!!2C)i >tt -f^~;}Ď(7,%C~DJ9P.*qPaNGzBυr)`x^Rz~@ aS`FE% # 4hlhV"=1x[a3 = [>: xs}CoكEL ب6dUg-ejBK-pK$*B4dfRa:3VaJ.5ф N{&cήL WU]Z *2nS?gxjը0\ 2H&\5EU9@(#]<:a34M&EH 9{v;nqc<19YnG;-7fccs!*qkk"gϝz~d 7ZM;Bp9f#BӐAIe1 `h$cwonΎvM 8](KoVdJ ayddY g-RFxrցr ;D&q&]ױ+ho3gǬ'#xE숦c~\ko93Zvw=8藥 ~ 4苀e\ݥ%D]$J'hO{^{14%֎躒/Uo|={{S~/_x+̫^r?^{{wWEԑdB9.,bZyiЅF es#wx^(>a2.de:6vo0黰&vVɤPʖ"1Jk,KQҔj9͔'N;.i9=y̳2hVgs Oq=ːܠDe L"ju;HzҊkS̠J3s~> YE6l%Z4s`C5ƴфd6EE%Z5I',('":{4P `CkH%$(Ɖقn\ժV몀 lί}1ǏGW9#@U7?~i~&|3k2oD]T'r1x!>wоI_7Rgm?|=u/z w=yO׼;n4{u<7iǟɧam5'O1*B <7캈"Mi[xTsXki}r#)Yұnxle0HcJHO#:OӨa~ {ei5EG]wewwcMkrS۲'MOl2_P8gssͩc!u>)#γwȓ֚G0_,e4͏7=eg ) MsOĤ)cWsdZU$bR#vB./_bw~ݗz^}+x>ǏS̙|泟%Y&8.48#c(jibMdσw <]XFTvQ"_3&.~A$]rbmVQQQ > F[l2zI i:7!Ti697{\Q`ׇ4 ` S6 J=$K`8J`04X!2 [B4$_S A3,ɳ79Uy;gF4]Ca 2xTX e 9y;.j}v:l7&gi#1ɼ*iRBe- ո[L6]ժV 򗽄w_|y[K~9錺nEwa~‹5 >_y5gy{O'>y{^y/{R?BB^u_|!\$ݽM$ 6@4X !612vwΟo1*K$ߙ2ppY%r l̙!R|R*#*PʡU1gywsqz;}>#\Mp6Ox霷oX ҌM&>WO|^>u{17 z>Ͻ6χ>c{;EwÏv^C9E2yD׀*} pcϙ9jI6q^:fx.DHQNؑ#mH$R~)%*SrltI1aFTA;21Yr+Da Ŕ4Q4`0 }i*?4" BРp1P1b+-!uY аmD2u#F~NUy'l$i8*WR`ੋ(6Xt 6nJ; $","I6UBh%MP$Txh6+yb3jUzu77uky1_ś|´x8u{^2_B:~Gk:oD]vcgo=n˲;[xK_«^rG/w]?w}_坿vyӖSn_z^{M<J;Μ;Oim\5*7㽱{h`Pg40_ 9_йmP啟5( GM¶blZq @^.b"YӴk<ܹm9y6kj>[+?t:ŏd6fײ?33?x[-[{޳?޿UwpxW|rm[ߪ5_JۿyUNu[OSMJ<18S4⓴Q(?S@bV/Pb]1RG+80.nj݈Ina#Sq(`G"v AFߓ&G@ٿ"^dyBybNQ$y C:$ IY=;.ѳ$Qۼ5uեzrQ74%vƨ(MIa@x+l!PläP5 `ͭ1w *Cvf0DuDk5HF$]3ʺ"M${uxp]XU]qBiKJŢ}Z"DqZIH6cF4MhD'mx/wŠ)r~gcGrM72/\ev\j᥹]+ue>o9wN1OiG:t"B ENΜ"RFllRc}}s2UX JBQ 4 0`d'[C@;i.\'^/pyo)L#ʲja{{ EBHM9=e6@SG(&n/GS[g@CVKADnѩf, RL Äe9yhP@뒔J.1a?W?wgX 0FQq}עt!d<>#[^{3< %OHԥLіB9 #Zz4*hBi,xlvEX0q6G1+Q`A0'FA#3bi҈.v(CC^ )îmCC:Bx<>vh хNYPnstpJ>M29r :7XYgɥ!^x`KHl(&ĥͰ]o3rcFnľ6ҔXcJd;QiGa J]PيVZLAvN7t,׍QKBLtF>yِ&$S.r+aUZӪdx_лབྷݐ/,=GqN<>o<ů;>!N<.ռ]Ug;z(q}m튧wẾtڊ !׼7F3h"1!$UY"!d<ڔMMkJ\x!SJkćh)&6Y[xnxG9v*wc`gw(gbzΝ]9= .0$ wvy{ cUwv9{\~v-J۟ |>e4DtI:ҤF%͕֚&,;~c>TEi &nB a4IJQRRS'f3J'#a0B a{dHb"QE=], D'y=p`GϢ]02%:ctG%A`0Fj􌾨\i%"A|aNw%2F8)mJ[m\PbM+| 8+^ 8`H',瑛 2$O tx $ Q'R6Ī!Œ vn˨[ժVW_P4O\]ؙuɓ'ݣ( A[8y8яVv%WqޞzW~9_Bk}./\ײ(1j`jd)>;LDiK, 0Bő7pH*]zvoBBQ\=s!eD>7|2{Ou&ř3skֈ!ؙL 2rjjdmmbNQ8FQRMtN=mY4:MX+2rڍ|`9jѡ-F$=ήE]=CJB7p9f D?K,^m/)b8LƂFbw$>If а[4ق٬<]' 9:*MkK|7zBiޟSǧ{n= z%gT 86:)֚6tS> YPqTȌtfEDPL㴼ovDK744criqbɣъs cbH04[^6tL)mh/`xO4Fk59Jk8kL> iͦ9h%J-$K  B Xc(bS)"* >v*[Q%X=Hm$4Dd61K@z Z+-q9a0-PJg1JD3@%v 1 r=AʂV êV+ክKf]J)C,}?-7W(8yo;\yY۵;n?aC>ǎ_?s3wu5Ge #&6D >7әM `A^?\?؀ɽ 6S-ӽC(ꮈ@C4b O,4rAgb219pȱ,-U3G:&M-F֊LA Q%ItؑGTUEYN8wy6@r?q,04EQHi`% #xLQX)WiHԼmsO!bf4dbɤpq"5=]%%F):6(ZWu#:?7z&Ck[<𣗱n]y߅JZAkjjy.@G'M6dbPR16c&qB5GSnA'fRK×4$1,҂I!}285L&YNBM(@!XrZ9`Ճ9=2A(b>.7 \Q?#,lcJk_ oK?#uÝϽo`m2OsPy^l/%f=XNҦ|6ḲH%ZZ` jtɚ[cݭbHXsa3YFh(PVD"kJI~JҔx ìRՇ$J\CGO2Ćw4a͙ws9gzHK#?+~7]nd}'N=X,lhE+a(| 2$PIֽ3n0zLFFѢXM0 wS+A i 1eTCh}KC~i$g?XZazXA [_ - ="f#H*3CʪVg<dT$ȫy9Mh\ժGlq_o@˛pٕ<{˓Ъdɢ%EBgG Il"EKJbV;i&C6ޏhp46eu%ˋl=4[cx )`wowb ﱾ6,-]Wc@+X[s~-9bbc 꺑ngV9d2o&6L J= :et(@YT<0d=ݜX hm+bscE]bt]GYܣ*FUűc.k.{d\bO\VDZz <Gom{2$|hۖdt&̦KOiDpCʒ1Zcbd5{{sP NWj"`Q~y>~~swO{7R' >~ %9gE+uD"- Lta6PQ11T[L܄23#k!k꣰Z n:H}*JPsK:} Uo>׾M}O;>'_7}=7:.Ǐ5#?o.u]+^{?͋~!>Xk8q+_2[GY(JP (Wk֑b1xHjBMv spk ZHp!"ƈ͍t+ڶeQ7 qn:}Jb,C@(˂dNQJtFt:-+b Zi k,o%uFET L^qfqv`dFKv%@db'`DY#Sz`$>)% h0H&H]hLae$4pvUZճ?RǏqO}>y?իu㍧x_7N8<~/tgc뾆|Wr|ŷҶ}kxW|!2ȏ8W2:{iOwyBX㘌F87t# DG.jW@Ⱦ,Pczjw~C2X3e\ku=Eݱt:cJ:,gݽc}/́b͍una+7uӠubcc,yk38!#8LN^p( ZX=egg%!DC{uǿ7o%_aߟdS]c/\?/|[ږ{_O$`A% #5ǏT/QS'C1a\)蔓LzG2@{?$IM`>)PV$)&"QEɑþe2kD1 NXNZi*Ui7tTTكRjs9nNÞhϦjS˴)fT LN[OP@(=aFҬLD *H T<6?dv`6]Ma9MĒi)HQ4E㥏$QGVE$0T1,4a͊ͰU꩕/!}n=U=Z__c<}~"s˪*h(k G{i~/Y~:u%Sllb2>JQnQ `Bk0!4m1X!J{ 1ѕB/ KQA&anH'u` W00Ѓ#HbPD@5 kUn<1VF,tu!6 ^ Yܹ=HL6Q`mJk}U]dL YނB%XV FcDlsVhO^jԲ7<ȥ~*2ǟ߷z>qBBߍ+}@KK1. 4FQAYRtYs}4Z$rGIƜ,ibް <jFSBllhDBb>QE[ ݂É8NJcll8Lm}/ aʹ6/ ώNP ))MIi $g 6uG묕kLFh̨ll]`8g-hy3gu[3o]ѥN-fF#~ً2G-Uب6Y/X/-h3o{u_l $5"a0 1__ZktИ֠Zj4 H%,rkvފͰUgɰU=ߟ?kuPWBQ,OY,+ʚDS6/ 1c$O 䋎~t^.tBQ&h3sdgmiPPʋ{IH)a̒2*xM"1`4C36DD @t>AfZjz`3@4N`{;II Q/V2 `|go~ٳz/#MMPwW,kX~nblz}D/W} %,yWo-M#/= mᬘ?mD)IIdȆ!}h=a6o4wh1%>49Th5" C]7<ȵOx6WT3b v, ÈZ ΌI61/Fݘq!QQ1qbdGl&e$ x)|A+P.Xe3ȘHHA"3Ž9]'O:cKӵԾaԾp8nĩI6MnZ& S 1eFBǢ[0 dd˒J$(#PS 1L1e(e?b 50zAb7z߇e3!BolO?eV/)Ѕ7L5F`HoA)E;a!&%HJbJ4 t-^RDYϳ! q_#F+N;E )RcKlaUZe dXժU+p迕*8r(*R M0nVHA9L5,a !B{/сM/Ct^ }1ФQ {6DiMq̏J\R el`{mΎgo_x\ؐ;ދ![mk4+~c1<`~JH:`}0U)B L ">0=8Bk0l%qhPʒ(QEkOZ9.ѺĚcG2SpUWJʈdawhq:BeG.1eQ Pq6t w 4loqZb.rX R^f,tL2 GEě.hhY3I1R#3G7Ym^3)&L H}00$V݈J&0 L^3ـ"a֫u/G3P`A+Lɞ)%@`bş?*ah#GCÑru/XkSF$Ԅ&6E-]|D2!4D"'Q/Vt]B;H`KKN9ZժVǫȰU= ˹¹,; $ `L~M+t sȨPh\!BQdسzҶ!3p7e)|q}*2ƀedE )rB@O0}|+:/]goD\XVnc>l\٣1m' S!\\0O_q`(H@ KS> 1r qм ж *<dHxAKX t]\H\wXR82K+VuUJve(AX d}CW>V[܈V%( #7***S MAS>9GgS@gܠlO%9K^L 0i%2&?bM nMk9ZeZcd\1r]註š -}׭DG>?aÐOG/Y pr1I#}̉K!1:UQL$=M^b`٦5rFؠv50b$Q6Rþ֧Ihh# &É-J ٳ#q+v4afXժVuYVgaIxĬmGT+H\Gۚeb^)~B7/J,SKلfLtZjhU,)lў8ix])5f}DZ|ۋEl<1_Ǧ s e4\z_yЧ`u-~e Ovp,' *FA;d{,ӗLJ7Z4٧S(Ls3 CFcC<=ؑRE낔 rD]`LNhچ*U6C3 M[+J+Gu6 *S `K*WQ؂rJ #1= C9N=bhy1&HI0[y&MUmQm^SҔћ hF"9B^WKIōh#J9^4Jd0(cdA12@dC[ٿxqX߷6zɒP¤ )SY1gܤ29.?!PPu?!bƤ Zi4&8Ψ1 o'9KD@;.+^UĪV˪ȰU=+K0bT(Z[Ck L7 zBOVC&>*3 I(|_X 3&衉6 fzZ&3(BYw1t]VJ1@ %  Я^ GmL FsEL2p?QcT)EbkY|eO9Q Y}MSkIȒQSLdc(/Gd~ld. Mf1,=!J?f0OTG2U@@R'ØcGhorUW*  `V k,)`8d=0X vi>cx ً!lܗ "9^㖍96916u[#M #F7L m'r"{yK!z1ⳑj;xp pkZ9n" yHd~1#*% &ָ%ߧª*Pl5+`- ֚`TAQkz0g\2*| ] }|4xMK 35b4F gsam+WWDKy i,r߰.qٍQgEߨʍjo'Bn`xi|+)Yḁ7Tz)|BF!F9Oz"ꗹ|)]: d,e& %.o.Ѻg5䐙1b$v3d-l~=pJڵj1F5 7je?{/ y- ˟כyiiDxI/>V׽ idV4PJP)G<:~@C`">hPɺ{)Fd6/Mzð9VKDaIX&2ȠUu6|EE_edfVwm\'Y6"0"kIm< p`8~8 . %t4Q$J)t2İP%5)TZwS=fZOm9dRNʖtbZ(`g:D o}äs~tnEh^C725.(6LQHFRr^G41fW'F `d41ERLCBEiK:S;DRJҀpmu*9aVN⏑z= :@A1?o#:iT`߮DbƄh\+[1יaXU^VT$)iGr'׭:y 'uR_vr3.3U}fM! 4q )Blh=K"}Őte%7_= }9c,? 4Urs' J {0+ &G`aDf/µk={Z)y=Z;˞Ѓ ]2UB'Ǜ6pSϼ̴66a#`/?`V7,;*X<$C*b+۰Tk˷(Hb@ X[`(@MW|唜!Rg dVrdzRr 3}SnrOA7Ɛ 7 !] xEU\(0 2ҟ\\M6FL*IWc&xy3)!BږQ1bd4Ylh CK.s/B?) - `cӸz jy25b +289v6XVSMUPu1aCZv@{a PKvD3ŗ~$.hX$> AdU`*=d8:gNˮ]SRqPP,FEk%KU5>`s颞nCzgI, 牌ai8U%PHt(UU%T`t_] C?s{[idUYsAy-7!W€^t3Q[!l=|G[RYDXOǡE.lgcF=e`\c&ՄjJUVTՐ*R"谔GĄ/缾~k2dEV"%ʢ$@W.ꢦ ShBBbA9IqK!u*./<2$ FCȒFTi9/Wua˓U2 lbHJ&N 'uR'lu2ԋR[[|#>'~S/pnvvN m|*_C/prAQI{(M6bTh [fلvn6= BM!&I{cT%42B?:gHYkmQ"w] c6K\Ҍ(e~m\Xgl+?C̳^~'u Ͱlcvi C6t>kyonnf`X,_j3W%p6^]YMf2SW%Z {b41ЦƌFcL`YekX1^A)n~FtJuf+шh$p3m^z58oE2h]6x;y[™ӧqE˷8<ӗoςed`PU]ɵ~A0W V`H* Iz\r i .:[N͌ͭ;gi|`Zeخm0ƌ<#fT`(sgBX12 "1&F4j;F*]1R5|J+r}LNUoQo2F"]ݏii0wshD_{*>a $%T/]ԅARFB(xBG8X䘘|aύSg>xLOQ颒 Mphʁ1#:3b8s.k˃HwP$T@]FUP"Nꤾzd8Ro, uoW׹p=Z1F5QE]Wꚲ*) k lQ8ƨ^&V#&n!Xʄ]@ 7dq}9&G#. CU]GYi[CQE6K(K:5)Fs~5V0Lqt"HʌsXV !og2,#4{xh;.=&up8LUw<Wn-2{9t:e1eo̩m ׮]goW*KNa4  A)V7y㾞N&((˒xb1(=s4eY=EQpzgӧwhnԈ+e (@)ZD7{OCG9<y5O|Oq_kra>bq쾺Rɐ_F "QreϦdƼ9C>xBjtw(,6tN܁_R/0Yta}y5bZM)JJj̨JhE-t1ns57֢;UKˁGB{ypj|&jƼ^rϺu3c {xhM%bhטrd~44f^ʗv9/zFm:cf{Rë {8b8l2 &N{L'HTQ_%YayӚlnN:U '903ۢ@&B' fdM  'uR'uc 'u[=\7?_2+Ei 㚺rxe14/z:2[@crf7FaŰW@kE'GdupTpM<m!ϟ{CY kIe#.SdR`Lb:((p ` , 5I)N&bYH:gI>!d٤7_ϝIp=Ko`2cwO@ؚ8sfΩ !$&G677\ݥф8<<ԩfR$Ȓ2Ds e~[6)\z{~MѨf6R\KؘrcΠA+KVtNP%Ɣ~gkMo|3Auo>ܮ{NmRGzsOODV.vt}2@hC+dp#̄UX- ⵰ N-h}~{As(C(A5,7zJ]R-(K:TEEY 4@ÊDX+=4ؽ9 ,&IQzƊgAB鬣uGt>z ŢRketsNd<K˙h3=ۡoE&!1J'(e!RY{W+V3Մ Ԇ,MWh6Q#i<8fz  пO@cd9eFf%m# bpzyn!BDZX\7#իװҶ-قY,y"r̾Rlnl} Zy35Tb!`枻رx_2 {77>>яCO |˻K.X,'?Tm^<{њk\mHkV]4LuIQZDqu]Is4~<kh\CE,~YnvyXu1hJҌꚝStU,x'Fe!zo{԰rU_ a+Aѳ"F:-Nxqn,;'l`kFVXm)LQ8"5K# ;&t 7 {=cX [X`s_l,<6_z0J 8"1ɐ"(}R .v(M;L0t-¼^!cXF0a%rBHvFBl‰!d,ThI.oڢ'uR'uR˺- Û_ӟ,/Y]NYs]u__ 3u_x1o'v%Q=g?}=oo^ϟS?$!^򶷾;z>Ky^0\p3gN-Sf\_eP=g>XRU#Ffo˚udox@w˙C!7Q$E!34{(%Α%WׯVE q-f>(d# -e8< ,=А҉1G2 ٧=CbyЃ#pmjcڶdDt2pSŐIzT?7bs2[F#KYJVAcF8IS `nCE L((qZETS9fPX4rmlq^4h:M/ 7쫥cd.})}ZgΜg: ۜ=3T@Z[r?hۖWqŵ_k ~ի d2iۛ og 1ǝw\䵯y5{sV)-_}'yj2]8ufj9sKR.,jE[\DزhyB 0\˼3̺ -F",hp3݌ku1KKFk <ɘ;︄ҚEL'S6 [`kZlѳ:@y=(a}r~Ƨؚl=fd\.}%l樔 *zNt%,.:Z0 byƕc(tdldpA)%V iaRDa#dVt\ܸ0SLt9t13P_{2O2ЩN5a,ϙ4%NxH`%tE ] p bBiK:q>˞J:BTP(c0:B,"bkKl N%tɧBVoy[9~QΟ?7|oy6Gzїtl/GJ+k-A|v[U%{7 ~s?cW^!#vx ~C5b2…{0U cڒ3;lpӶNbUCћz6<b $aL@*/oV:azcuJ CHԵzxlc,U,ѶzŐq p1fV(\Z"2FS%xs?W_L֗w'uJ%2qLVfhlkCQ(2UbXT&$\m  w,'XL6TAQ` hݰd5u?):oQy30m$T(EfQ d2"%ϖgΜp`QsNmo?u{VrƣϺ?͏llLKO[6>?ϗzdxo>!>+G =V/pZD";[;uׯp-q$y2WƉ^L*TҜ&i킅3s8a/ek5~1xA^1^T Fu]q.9&NۜdA4n\T VaeP'O`dXyV ~;6'l٨7[9nj̈( 1 wðzRӅhi|Tn 2(ϋDO4:$)&Ca%Ʋ.jJqgf^fcd#.vd1!ch3U`Qփꂢ())Ic$'=[1$pSfSHNU*p& <$ХB;wbyR'uR'북 k?};{fxG{kyk^ 3|*ϞRW^i17?y>_^mǿɟMo|.]_iV؆d:p,E!=@nJ$Aa5ei:H#X3rL"/f IDAT"R̀Z?%Y{#{MPX1cK {()|X,̐pհ*Xe3cc,EVv,Y!NYg݃ KF:|/Wԩsg+K+&Sxd ƣyMxc xKJ#NLW:9mhcLMs3}^( ܘ?{έog9$Nmoе >|G>OVo.[|HIYCYVXZjb`ü÷=xCeqG.s!O;'15 1#7d[szzq=ap0&lS&ՄD.1H2 6cdFt5ui*6M&f,ޒk') jYJ*HݑTjIx̆*T8<_x =O."Ŕ%"A5D)"Q=Pv-/A|cGHa8 EeKLTk!E\tL܄0.F<1UN"(*7@TH>Q]^_c'uR'22o{Nn0| _=\ؤ~Qbkk}_zU}]\rs2ϹzK:[YW^iu.c}v~VGfgtc4/`kpC6׋Bg z@p#Y}COytdϮNCAn6<+a4!$0iIVXh(l]&ǰ4y'b/e:Hu %ψ @n2@MM^ez:&vi2PS]p`42LĒ5eizul6#s#;Em@4JS6$Ҭow>]I)4wssϱXh–b+% WY4SeEJ#m\TeY֭7je4qpx/XB7?5'd<^{}{{.?~U1xo@%faՔʔXk ,OWt"Mƃ!ȅp }=]0- 17HVfm$-Wc6Ms)졎 DZzoх. A0Ç7_.s>ål֛L{pa:a\˚RCcB VnQcT{Oe+tE%blLX2UA`$u fZa)# B(}Ij\O pqe! гa,Z~^v1R^t7%?2P̈Z%~Zi SH8#j[g# 6djJH.KYwACXBll:jdW?;CGsC?b 8S)ϟ;.'Ow/ⓜ?wYFTc|5ӧw)Wf6[^}RݞaW %.RU67>biRρm:4GoJT 蕛Xu @Ʒb8Zz<'N'9Zk6b0ҘJZ^8X7{\zpA<1tޢJfzI Ċ-xJdb"jdjH& Z7,reMW.sǥ; bL F-3nzJ#`v:C넵@9a,JS&ac!=Hqu(xQ@?_Ez''e5"F(*1GQ]s}w+Wm9HnnQ'2&1BʌK($/^T- TmImŎQBh191wl]7oswu{=<8]Ҕ( :KxRJx oUVj,z28D/;BRAwpK .y{Aq5jI9f\=bk S gYk3h^-^PXȎGae*7 ;JWqLyXC-U Dr$  n6J6} d! 6 $j!W"<'s"9a2cj-`Neq OmM 82sl\G'b,^'uR']/~CM{q/_iZΝ;>eY _|sg^zr{wa&v}R֭lO?3z_˖4czD"%MQxPU4^㜡i md+ea)+e.pALԣĨԵzF%Kt3.rx98֎]nTᒁUNAJm䞹H _HX[cW5ĝTXRS m*v@GJ#GgcR%MRxv0P2P)2CŒUʮXCYZOmj)B2 h;sаs{v\sg֕$@ Qm B]3a71B q07{a[6H,8JT$%3tkMibqnaUB~]jz&wǃ2hY+23( ZWtgXM(,ZwPs# ƍ]lRIjQ} Qf䳶?ܘ&J2d<}=q꺠06fpLTmڲ>]YQ0)&xJRSҴ]DJ+i'!⺊ ; yo@{-`^A+r|l2 2`ɠ2Ky 'I]HfQhbmA+UҴ-Xnz\N'V_ʱbg CK&xX f>pLܘeACZ;VbAfSŎn:׺k]QZ+4b(ͮ fhPe)%Ntx%Dn0t :QMUa m7:߰v==&KY+O)bD]e҈WRV YJYa]| 8]q9:_2˜:|!҉͸IG̃2ree3h]+,Z4dX1ڬHۢ7?-~Fp͡ByMʈv Sj3777*IW_6S=(ϝ9ӗ0x4JM'SPJS?֩bgog@aiD}Qbhz67Zb9uQgh|n41 Y;tģalK-vm/g~?@Q,=25}Јn 2EBY"8&r7Z|θ\ sɿUaoEn53FS%Ui*KYUY $Bmakd:5L&ɤ`%38!,M6}ِhsLIZ Nu>nPڠG [AfN2`4xaTx?~I4͢XKb4&`,9)MC&V(U(98scz _◎]iLW& ]}J"Ԧ1 ##.XХ]-,64: _k6;\/9]l]J{}O|& NmѸ}B]J( .8%P-%<1CoT)5eY HRkh]üs\'r:0^ҔҔyUCY2ndX5ń_)uE̱4߃P Òِ% b2/R~:HfAU1 (#**6݉hVS@$ 0XLjK-tA%rDmEU5eSLa1;"0! !9V5'Q'uR_ ?(A)za@fEbom;zgYI}Uo@x6_ |/̛5_ ?n, ٞ1Bbib!M :SN[ck~rkh`2Ϸm$tosn4q#ss.{d*F UUR&Yh5˦ a1ǚMԲaL &63qSd_q^ ;' Ysf}Ν--*k\[2hA|tn _D!Kbq֌ J)˾ ?D¹MRo`<( (K|jh*b,qYy羰vCJ~?/rb<{bP%^44R7Q1bsI.x9-"E,02+#.vt񴡡-OwO3s|"Esg10&PO>yy`0ޘ%&37P!4X\Y&K(a-5sN6,=df<=̞{f֑ 0"(lAa;ZGVXe3}=e)So(kLs?nmh!76y}d1IBC$zFtb]!R.WTbszf 7UT(ۢP9iB+D:hqeAXa3(FL2T+eR2rΘc@<)}w@U*K@5FRO5''uR'W/;{,}G}{˛x^ Ixu;w}za>zKUm}sqx[1?]4 ?斷k׮/|׿羀gNi>я_,@NdžϺ-xX+f|==9RR6L&5ӍV24|b `PHItb2S}AJ%!S|>_ },=1|)?Ϋp? O Vɯ7wRQQ҈Hb36th+|>MÈERMfsGTb5&-`ʨPE>KoVffѐ49諠Ev`/ƈn s۵mˬ1o̻9vg7;=O-zj%uMֈq G/1"eK$\rPר 2Uy91+_ne2D *EQP_bq$mveФezYꁆh"X`D &'&B9gJ]R$@dr )}F>$gFhM]jlm)}uON2<ē(8s4Y%N~/CIx<{oxKnFm/uww~3!2|?A~7Lxq푔.?&`#(BPY'.4M #U*EY*+44]-a4I)ɍ9MYbPS4!&(,dմ޴ټZ~ wj3FQ]Ҷ[0a}m) E!M;'7\!dB M3+Wb:0O׽\zUbW9=v]ǓO=vL}l&I [lSuP&Eo, rV* :/0YcCU3Gժ*5H%&#NӶ4BĻ9i&Nu1颤1 IlKF,%Ei(epi (%r J(VZʒ,M e>xʲ &1x_`i;|Ƿw!?ͱl ~o~7𶷼 \޻§>0BfpUEALrÖz%O= \r20T8iWZL%0FkC1-nٞb)Ȍ: fnAsDq=:.B. 2a{Ȭ1k?9`ٓ,1):?~vA)54Za_ PZ K(qbfrHj2lZ61DBf< @DLQm lO4|8{%Ǒ,M; 4_"22{EZnrv^lEr}1@3GUGFTeDй b'ίrdBHX:!m IDATb4SB_G0ʲdblj@VJV93M3ӬPg810*nW+ CfSX%NUzr64t}jr64R^&>^/<<,?̼yw'__W*`n$ cnGUYb1)u"ߚ ΐehi1Mn̡a0`L.wA(GTI ի/ӟk91F^z}c< /}vfw;pw 5QL# Ē$jt0ZHmQjSSE%[S_Z_*hl8'(l/WTuFd;wpg%/^sWD*$\ t[^^Ϸ!)%>'>9{;~7yu3 KLi:qys~=oo_+:~Ϳ|9樃?>o}>9/Ϟ=ݞq R4J1e^f8SLk^s_߯ tB":ˁbKZ,%`٘^.|xy~5_Of;@ߚk_R鲄&sh~*[ATS4VH̒->TAg3=uQeksΔۢV.WɒVpI5 ɶ=- 4t ٕOCDM.`bMZGjɿ|xγs2%^/{atʈt05FfP]xt>BSӀZ p[2V?ӊ~!̐A"fg"HFf/FUM#B)}_[Fҽ @aM0( ֔;bY $Y#{> Kٷ>2Ǹ3ݝ B5R KKְiLRٚF !ERΛd_G"" i[Y  |,@)8Fc17TfOИ-蹧ZCk*I{>˲sNu^"ly/~㋱ҡ (jjcW:+Z h͵o#4bzA*׎+h~c-ʰ=Z=$qU$_bcתv,k&%5Qn"PD$?:ʍb5+ȳF_ͯ\Ym >_3~yfZ.>}̛͟͏I.#7s.X.RIsd猹>Fpl#S2a NߑumxT$I wK{S,"D߂xX/g@a^زJ̚{dnxfh@G//9;)=q.v|@ nȲ3aN3expMEtȏ[V ϴy"gs_'c8[sf[33(;$6:vc #P(@ò5a+&3׋BmjK;0bWFC1G0:рN;Q-Ӛy.̻RGܗ h!mL%8\]J`CۅlJ@~lS,a7F*5ֺZ<\}ަwmT#u2~ne lϥ lˌ SD1n-ԉyk׏ RZ;1uVi\m#5r%V9-PidRy*AWiӭ>ȠW9`i Ty'HծC)е|3,N-°3:(^1L""WDA̻ PhAc˻5k]\-|u{\I o462 L-ӑ| ߜN_px|4T{idYElդ9WLY%.,<^ϯzj>8þ`^&BJ!f^LpyW1P-J܁&fE!z5)W {>di%5(xf؞Og3Nq>-e9VЭ@Xa!XPYywwwG9^Ѡ$;PBp;Vʺ ϴLL3$eafa&|\XC|s_-QkCQql@JJiaXG3 5WRWTմG:Т4%j/&6ώRB KA<aFk,0ͫM btq 2PJ0iW#܏ >e;|5 ^|:Kڑ}M4M3Qj,-A+\[c?VO OcЏG{'힫IgYWLEreݎ&M/gA{`hKqII;(rvyҌVلf\1cX$b+dBH PFpK*9c4\',b# x@M}py~xgƏ.4FA8y}yWyQ$WT81-ibL~ 1EdF?ǍpwWOp<66'HNAVor;K)(Y %P8C 9s' 9/< {vFO=vab@v!Ɖx99wq?[zw!H}16r&%Է#M bB hHFGob% .2G" coKxduxo=b9|u:}31)0bCg n`p`Ž0t>;xLTOȭnu_O@[gZdL[(y)3Ox7#23hv4L㹏^>uFCREw3M-Fgc˒gcba,9)9{DfVC Ae9*FAf`,eO(i١3"T愭{(j@R (JߦOXGV96R^:R~< [3V[ǏU@gXepD>Z9 P1cl1϶e.ӀR>פ7Ƃs#Ε*RJViUR.UꔘLJXV^49P|ho(bt%MW'JW3B@oH6Sc8o~ 6 n`Fpŀ/`\ZBD؀83bn`. wr ˂W~'qwэ@~CΙ%VyxfY*/n|Nou_[@[g\ˉ)K"H!,?|Ec3G5~mQ&qB2xm Caa1|m .. ,KbѾ;Ugm:IQ#%ߛNLa1 ]֌КFcNЩbN'3l I"b@}9jvzUԌRHOaخR7Xζt5BT>F`|ěT>m?2 RN{BmAD`j(4C,Ƒ5 ŖMBϹs3QKPMY?gR*}9Ȫ烸Pr$ll\Ьh׮ԨHgƐR(ԓ##A42UyAxjeqDZԁi+Z#@c>Rcy:"Gvhx(FkRXGw&)´L i9x;T/ 6R<Aţ7AMnm)I,tMF'8o^ Ιto RGJz2Tk}px3ǿ;_D̐ME`*YĀ -q %*#iɏ*~[n ín32H)ь 'FD@@VuW!GkeZsJ) j,BeJqc웑jyG !DZEPB|0vܚPӔV7擰-e-2RI;)W{jY)6 @4I͔DWj§~ D*/ؖN_K4YL31\7Wc5ݹ6Kf3քrMϔl= wMFccWlj֘岔VO:lR [@14`*CkpJ"e[iZ4Vib!J,= rtqҼD*sy\e04&smfͫvل6",Q({tu?UoGc:o 0 գ 9`03F@y9/gt~7>7zb6 Kz +`#lsuR̄I'Æp>h&c@Bee%Z<+=R% Bݖwq߫zo tu&UQʨ ڃly1XD@ nMxnկn ín3"tKrB3)Y 2.F">_y R7%Nù0B5E0ެb@\A܁1%$ m}}y?}w7>;TcIˋ !l@9@eQdξ6RK)՗5LN8DOJp>fW~&$TS223 ,U[Ƹ05S)^%LSfWYkch2Y`/ 6¶sv\ǼƱ7n=VpI1)Q M~bU4U@0r4֥SJ4'_}iRrLĸ q$DZ1F|"f9kp%Q&PG10ipHaa5pR۽ۗbwm ;Sx~_߲ fnXjĒa8oB44 0Xrq>ryWW|}?:kF[=X [cj> lMRI5^< eU~!(%w=XD<^@-a@CcB9Jo/HI9ҕoPMi"uY:>r/ymWvG5?L| %p-W$e1G\.Jk2Kd#K C!,mns$P}3MTflheseu_ƣaB26J FV6YvT 9bƨ%WЏ[WzRLBR+x9+,\gG! "g{Er)r:-\.%qB V~ Ъw ҍ0 pf&lYi>3E$ĤP Z>8J(խ~unu_@].Gҋ9PJ3Bλ< *KkӄԆCD>wN7#IS 5鞱ZdoW^DXkal"6$wZ*B;.zޘHi*}@p pK "I%LZ` Q|>ePZf35\ s(@Y6l̸P;e!&@.%p8 TĨϙqax h8G&^fMn]Qk_k$"Ucr&m_c[BkzI{;n\g;?Su"Ң\)z<*L/Ӆq0yd'Q-`.ieMZ@1z.=ѡd@ydWl $5)2$P4oX\' \r|Z}R@֓̅-w?<=?9jDK: 4-Mb;^ͯ1>gsK`fE3֨m"EK~h; Tnbt3Ȕ'&M"*M&Rg爾⺴fqX4hɀl&Й =S h8/'^_^qxq 8Op[Z2V\~L DT l>Rʌwc9Vsfm+6mIub,\-,jڵj@ ͌PhpxW3RiJJjw2 `hI+Ӡ"Q9 MB13'l1:pm.XeD`޷ۀC&R X PH. Iʻ5\ Q&dy` (dTBxZxfׯ': ;w4bhUЦܤF7+BGJLa@e0\¾ ;I26YkhIb`[.12sڌ!5‘SZhτ%PJfnm껍{Cl§~^Xo+x;^/ w8qȜB̀s:c>}pD WvlkX|(fi셂:E>d"ғGT26Bc?L.J2 `R :™I 4"g`` @;|*h)ZG9 va pi>qƷ{S}0hR}V /T%C4D{_P, vq2ϭB$Zd)F.2d5kl+=|moSā1كK!eX"3^K< HX-i¾,yJ8x!3 qij1kV-鳇,Ldy^o~% f&Fk O 4D[w۸F~)p7RBOc1tUq:Y4RW.~5gkg7)m_?Ra(nxЀ5*rN}9**K0-sPطAIѨaY"<.s0] BTʝ@Ms_r$T/B1H8LjQdpp{uJq H1#RYNƓO>@>~S ?ƾ\x\%O\.S ?~X;#@3XUIi \_f+h-ڹsu=*Y-hQ|FF̄v_Tqd6%+RJYhKm=鬍b;p}{+4|{#S.;ag ;ˑ)MTR%Û{3VY dխ~!uXõ,6RjD 5`UDIMl 4m)u8o@#1K!_wHlӿ47զSHw`=dfhIh7joT~6 DqnOETy;.gsNS1iG`2x!=4YגA hh6L撥Nqկn ín "9CIyb(r ~QQ)jɹhKP& 15yB4sĘ& ?BJfȨ/F> m0s<T)"J-[:E1 ay^1a  w/43@3t*XV3̞V^3Ǭ lFjOcqI)S[Bg0hՍC$bB-JJiJ̳4En8XH9U&Ca7o" &c{H:X)d9"$^!7@m{!$}T95V*uc1UE`9y_ C 'N-,y!HHɎY1'S$Ld{I$ -:W|;q?L(!WқcXH@"u=`ãkF7t&_&R Gz(0;m;AmR-! 4TFi"5 ҩ#ch\:3XTv^0uYqKm>`,/ 6}ʴI5o˷]rr7s.w0ȷۭnp[r9,xG&~O.fD``e3H\Kȴ ˢ]mZqRhI%mdT6jUߦ%̊::Th%6x&g k:K5lGF*6-XFID m>>E&}]<#w-ƱyD %4غgրN̋pSγ{Qa]Le"ct/|͙۷s_z z x*QU.1 4gW,+h6@ifO,ońd}SXl CGoK%JR|Ғ8v3.BvBXUA.52%&bI(X r&DgA1e`&s WQP5lc/y>y: =%lBը2~5rr/7-JSRsˁ&ӫ3,y}E!A@K :`"5Ƨ  ҥݓFd^D0iZ}giUsW1D׿խnp[t:ryqbF3%fQY7V 35Q xrB{x_א3R$.BJ͖EYnG[|bPT:#ƭ$ΔH r2p11oK$34BnD TuWCt ('Dx#T*L{$],KR6f2=Cq WI)K3TrNn؍ cna-BvY8"·N|ͅzaj, L[o&שׂ6*jtMV:fkTR03َ9MrߪU3~)CA )s.c8%cћ`t̎Bc2t!wGBEm޽Y udbTb,,Kffvap&8dbkLJ} z08<}6a`IϳE 沑- nz4zTFkӨh qZ5&K2mtLK XŒ@5+L(S&"љtLvLps"k@.tNgs]oz 0֜u`3WIMћJPpH71a[^| &yɽ5z5"%s&gK@Vc2jf"qIӑY%.$4v/RJ抍}כVE^`{b2"WAj`)ad #{We}+Xf.02,JקyKMQmz]lTz[gY7VaqH伐lC8Q '+;@Ř )l,d1 `0Ƅ09FcTi1` &KX6sU`h@:X/qR|HcPĔ$8ǵjWiB^\_ŷD툺6?ZiՀy2d xt)ٓR/@39ƚ bT%3Kt=:Tc/>{ִuR/{XjvQJlf,46KkW`IYfm濠fJIĸRU n/ M@a#'f75ZYc :r.+*3~5Ruw6 猒ߚCM+֔RY Z [9&~<Op0l= .3EՂ:mR&U(2T!'HTo’"K\mZ&bM>V9Yj0Vjʀ(XldԸ1E؅1HGY e|D(J Q!>Wu_m@fB>[܏v[X7V֛x3q3̈́0SD)΍.f`14[kCВ"}Ui̽߼ hf_մ)QG\F|9i13L:3(5A#Rr$4Pez(ʛA y$}"c>uaYbr. B&mem ifX)Pǹjg^ ȪULSm1GJt\_tTFǶ,r54O^XTiH"y 7&ÏV&M 43tIX( dgU'JU9gÞ 9b@ 23-)bZ\ phZ[si?]'2> l=W8q d荹TFǖIR6Ш((QT]#J(*).D"%0G;fcZ ybN%H̶mTfyt*hJ7tuCW0:H(RȒk";c/{c1c|'8K"pQOy)T zzIC6&wXԺVտK@[X8ˢBQc2!dGTa_ĵZAc1<~lLr Z.t4GVB1DӪ#4YH!}4~YR7sh9Vj|g/*P&A+ ٞ~\ 64`mcN@) !R(UޡxR'ZclRm3^6+d^upx @ `R c:,KXb!WfC:焦;-ĹApd0V9I)S$W~*sAF`pO Iȩ  TUO{Y3l1MƐ>][dbkRJ.dz|-5yx,r@ 03wůD,Z3c+5xR<< oRR%T b~ 0T&gnaExƞ;ٱs;F7 x I#5NYeޓ\ @MqY&Ykc*lfԀnVZoQV dխ~u< ÎgR͛ApO+k@|ꃐRl% =_<>vVC lzBrp!oq9;Rzd涞>:kRsܨJM\n7?h}3V}}$HIp.AD<t>{w F]KO&w~諹fI5\&sniAvYXb}3y:&6yΉ5/utN$D즚Q"E3)/4V$Х&E ^?PUN0ָ: 0 'Z=6td[Z3F@dǒ#1E˙r~~u|CY/ڹb$@R(C0pap ~`X$1@G 硥'R6?%.8Zp^Μ3#^cJX'^F"~E#SS*kQ&I pΙDbVap- ޘ5$/I2;K@cR,y`dWdZ u>EJ+Շtܭnu_E@[Ztt~`{&rّ҂s3!P 2\BϽ}`@x,h8um}ϊh6 2bq{Ĭ@'x p$ȥPJ!7qԣj kDL4Jx D,BV慫X؉NZx^ /8{cbe`-׀x6$ <DDE :K1N;?u Do[V /N{ݙ7O /00".QJĻ ۫O"CFf=iE{g%'X_l4I+y\7X@JK%  Ba=?{ol[v}ְ>Ý{=nu[CK-HdKv0ؖK N9T9E !P  $$T*BB ,,[1< [CK7wks gZ{u9{>w}04jSp=ɟf89,^BNB ggq3WgA+$41N"26&u6, [m}w ΢$Q "1*^ȠևTa 04>P/[?T*BQ&ZVA8ASP~G=}1>=Q PT6_h g]FvD$)( QЊ޿v5d֌1;aG1_\e4tlG)moJ$t(@Ҵ3R14g1\鲥g.Fb"}ʄꀉ [1:&fr?lWAIz|AidoB970d)qYV_:!蔽:NOJuMʫϯ._/}eroFE!ҕD1z!3_ZflX'MHـRb,J<o Ƕ]V  WI.Y eAX MeueL ]ѽ=Q==.;LviÎRQ=@l[&adǦ"7OMH'zf1t"A8Ek .(l,ܔEG#!yd8OeD6ttymͯ*:/ /nzsý댐Ցw3bf,Z[{c 1HiO o쿐R{f -Jy1_ufYkcZ=d eҽ끆dvνZK/<)qw JHeG}v);.#S] Bl]ˢ]KLlAWy~h0tC.^ăj;&Bہ0d`1a'xRR -k%e5m+P75Uiuʈe)[A<" O+KD c/ّHAlAKtϤs`1.Ƙӧ@(*( 4a9-%3+r$h=BG- 9N"_Ň㶶-Ȱm}|>cY5)qcBh AtEġUn'kYUFÍ *s_i!6LN˞ yw79 4'!B$(J: X Eajĵ*[^=>X}Mp.d.4*c,Ea{s4IѣZ(*K&H!$bV9megL>תh"f%%F5Md6;&| 9#ʽ("PY"EAa2XGC@7IGDžo+ ,#P(PɋB@$Bh|hpQRFEEJxEd{pPpPaegel&mB;1K3lHDxKxU 2XJCѠ!IMv8aPZ 2⽐e$"HSR>\A8$9Zh]M۶.",K㉟XH 3n*I1v2.٪;& ->:1#)Is&X Ĉ^ud< ;RbU/H)_$a>馂JI|f}ֶڂ /02-0 *Btc&uX䵛͵ |Ȕ|AeO]<QC(c."c&YH<ǭi5t3};E ĉuRחQ9o(LAiD2q(lv=Δ8(S0FEEJJ~QKkpֱ4ˎ}lⓇ}+Xy=6FsFׇ %RO"cgP(O P| 2$Fkh0f,9n\_|VUZ9R3v8\ؙ;{3we.17T+JEAK5KQq$e 'y3@ٮȀDL 1|.ֶ^X@-4Qcq:y3TcIġVN .Oc%3b%A5b'#TY >c˜)Y7w~C~]GO6dF2W[L:6CӜf7㣰ip_ᓬ79bX_aJDњ"m" c5$ЈNqrQs\8Y]L Ge C %QjI5jڶWq-0JQdw:a\OL4W._" RRiUѨbTV sSj-SJm(ۡ eE 4ڈy|XGSx6 0Gl@(Zײ4,Kd.8[[sPpf|rq1fRL̔$ZQ C^'Pwpw}g~Q+"گp&;H !E*@4:)t!o!x1vl]4aQ/53,q~-h#d: UPt^TT.H)gRy\ThcZe0THv^s UA<'@Ct%WTD%*^E^lGe-Ȱm@ .EQѶ5,J #P-:6*HtTF#],Bs.L%N^v=ʕ_k"=e>(,m)KC_N%+M-9r Rk P{BL[w.3,H!vw~eQY^9Q~>*`J5D9Jՠb\4 Z7gYϒT2Li% &e:SLKD;;:[Z#252՚R)ʢ*-5TtN#C[UҮJbШE.xQ !RcY"T,Kϲ˖qGs$1Q({iNah{.jI1a\D{Ja(5{PY-! CK]L{&wU J%ZOTFV:tkJBbܨ<M^l@<u5Ծa,5sCQSJz `DV#O _71I=!/1 -%?λn]W8 kyx1 ۫DPPQ  [&ö-Ȱm@xvS%W8`L Zg6CJ4CF_0|M)I_P`lfН㿌nz&LTQHv]*>k$2Hэx')%cBt7Xc0FJM f>Cbud1d)i8F-y0RVc|c)8ttJd ItȚ`gc'0xKz KdL^2"eTy mKa2 %M`\0Ϙͮƾk k`,XZeNQ0- E(ZH+ *t]/ZaVӶ3ZQYCa `$7}| {<@6 w !^}@#NkU(M#4AS@-m,()xqdW8Ff5tv>p.=F{V(Q0)"@m%EAk1 v5u[bn~ao8^eDH@WGa74Obo(GVbBhZbOǠ L' z߃ .8w,]C=9b&|5Q·AX8@׺RCf_mB0Pܧ*vHG<>d dXaɧE2#!b CXcj#Ѱm Z ^R" n+p=wox#'xϜox\t4_Ssumȇ|O^m}evvA!D4Q5SI FZ&14Z{W8@Ёe\ %Mf`e(.HL EoF"`( eYQo `L˞m3Hy9Ng/ GkX*JSk ea&p!]\+@'" hPA'@U8>̆:! !QzKY,cGLJF$2֚VLfb4c"S"q0&2єZcC@IDl|6c"(SRݑRhCi-ʊ~VDWj4B &tG'S< (q% ѵ!zd' BP g*JbXVŨbVzfjb< ϡ\V+1Pl \hx΋٫Wcv]a(L jȌ(u٥Ff8 .8*W1r#jGzN*Ε瞷 Cf'tS(0{쬀;?")OAکޟ!1l>YR!]Ϣmwh̛G1uXv #4;$=E'=9WEցj9`lFvJ[2*K9^Y/Dh$N3yN,BYӆGlPZkLU x%ƩQ|p88/wm궂  oS/,o۹xϋ:{ ?իW ön{- U5ŚUXנx3UCYgO{JϯfT)ՙi0P>©@g.[4rIz2|NQz5Ei[Ճ }\dkf67ee3Fs#FHʈcU.XPXӧqHoR/CJbs:GDگ =8G,ILQ5ܐ =1{4MI1]M=h(F5Sj( \Qc2VPJk -$w2XtmkBFCLjT@t+e-vg}O6 t;snj"0hEzQkH]z Fst\v\-/ڽ;w0-T;+ՔI5f<3#vL`Km>Tu-.)MIe+J#q-ةy&Q]"]R9%28@[\Xt C+ȓ :HleL1tRE+ s o wƽ3~\g/ JiJ]0ŘqQQ#E%[IIl Av H\`~˶]к=J)IHY>c592hUS9`^Z BD'XH`Ch4mmyZ dF_c9}$hħ?sg0N68ֶ1rttxGL }s - "HC io! )jQFWZ5,ѡIVO~>Y?6b:I&DWt&*R\2b nLqآϵ$ $$C"U= V,/{Rtx2x߳ rl_a}cB,(5*R3=L *VP-!4J/Pjs3fֱU+ Lbb g AkFJc4S`Bj4!RH顈rGi2F9>KoXBEA”⫯"~TLj:lD13tӀs#:*a`T%*BirQ*ǥu\T 1ЦuA>Ŕq9ʊq9fZbBiʾitYkNF&Z}Ac=z^ uj:$(:%jbA- vJ1Du͠ IDAT'$D*.5vɼ*51a9-c*$Сcc(qez&´2.G 0*)-(L!:ZEF[H&y }NHr ^ PP}Ttgm΁s2ĸ%/ni݌zy!K%4ckÞ&h͎VLbJdbG$F*R(S5..tN](+1Fbϩt1w(Tb>7:1hk ֢3(ƻ&B*L5Lض DlZłf6Y)!&C*bgf0Qt41ПN:`\cFdzDUVV Z$m14*+7Zg*(J;H̰v`NHdK+,q娝ɹ%cAATH>PI]pK_ \N= دJ TZC0 iThq1%QڂB[BYmߨ",FRZOZf͜|pj~)Q:hDHL!~ [a[zm#dz{[<S+Ї?gky5gq_R?klλ^zo8w/ϾxWa~C=K;w?~{{{~Wswmw߽TeɅ7||m^u;oy>}vww6|eL9<'{osttqz׿Gył_op]EQ%!,pB !6PtR`YX뛩QJ.UEcIe2@td "L[t= }ƕ(;Va *O,u]7jc]гTg H,m}_F$2 [X1am-e=H$zDf@b HV]2ss^3[DZo:PRFI!쥡IZ Xqt|DTbvaG)&ZR"̆QL2K nFj.K2nFwEy,z2FM 1sgCac\cm`9\@J!1b2~EG@@'vC tQŴ%&/¶Ћ9PWWC28'襰wWˉg68\:O>[|A;R{**FQ<l$2гtw/|Lhy.l[MFShRYo{Ae=:fhlK4a5mh9noE)l+Fsq1v-iB-Q?H>tB^KL'JSW2TvDe*F0Ok-1&It7 !1uQst {V&70(I0`a,,mmZdxe//_z߿93<+̙&җVWUO灗WxFo}x7_X7\|'{.]J^җG^W+gկJFUx<ʕ{s.w|q]wc?W/~쇉157 |'<iﻷF E{;έ,+WV?ЃďQťWX.|k]|/ ]w^~`Z/{}zaT])֔˜Z/ Ē "Հ0l&/֍CeĻB)Ik!GN|DiB=YClic I( 7LZ1`8{%$_>{^`(J3,XK="Mj 6t ->ԄP 揩Ϯ܅&F3њٷVV!I}`D#j[W[sL.g(Ϝ?L&Ųk\1ai%#♳D;>fyJzGuZkU%JQc+K0g@LC(S !ĈkLY迍#.83Î6+ q=w>\ԌSjEgi#IK Jw`V=# BHuA##)1`[:%.h\m-G1q *:V6 1BGmpDBpZbY'i )N Pq:`Z4[1.GS"'+LZZ C#]ut.6P6҄ݼL+5ĉZf\P^X thM$[a[z!mzI;#x[5?kN;^ʻ^~۝Lϝ;OG]|˗woH^xw~;o7򯮼'>gϞu>|/c?e/Z~>wGsU)^?˟O?ࡇ?ɟ?/_6sO~f5}w7+f'>{̙ y)v{jYP#oЪEQo{a=Q:W.6OC Xt(ZLD PXP89df9,õd#/ONO)Q4X.Rw@íֳ.z`9mVdM@ 1H5%eY&FB)#cI!&y!dE I t~ e-J !2`j:eEAК&*Kw;mZ#mq|F{|L=Rĵy-{QCŹY*ܳ,"5֦{m;a"\5*?|IRuQLDe هAiT\!ȈzkZw͐N}pXpntI1f@Q!`6RhHdž64*{ä$ĈrVp$&Ym',DEL FbxLh/^ xhk'ֶ^uA7TUɯ^˩}6^p?}',0fսz<.\]N֗._{y˭ë<yK^̓߼|K+_b>[LꮫW/3ݥ,ǴE%FPAD?m~Sm$wӸ1Cqf\2l0Z$BuiLF+ݗ]wSq.f:*ح [4+WhE/|sO2HstӚt>=ɔr2*1,B=#hcĭ h&g'6 bI{|D{!~@`㖼vՓXpVTJ;ar|":KQ܅Yt>3MiN`y>emC/+ַ+72`~26{L`-)m)JcE)M@Ê#mo=׽dh;~PsX%ޝKUtНNO - r(/w*EXI0Ɗβ gCib8k-CmbU((/&(Bxnk[*[2|l~#tFZ_f?by7@;w^Rʲ^ٹ%~Oq_I.^<_S|c<Őgg˴Z.\=2`3:67 &͏7%DH{2d0} ؐ,HcB= [LrF t ͭ[vui9ŢAi kc2_5`~t 0Bc=2I=_3\, hT0Nn}i@$L'Lݕ"9Sua8.( Ęc"`m#&C$tTqIDe+2kA%2ؑ_CLPC3 !C jI5Â=uI.HtefH$*Ů֜),{E!̅,;1`BnYsVLG9o`ᇙw?sgyBn< HzAGG.~)??kQM G9dO=xo.x), Đ`"\i4KŜxFsxH=RD}n!4cq0'|2>1~ᱧjNkԈ ة4H"<` ყ S'0:L^N9<c{p!I%t,2}S$cx҈dҶ?qAA=K&A[a6S3bR#F `(MM6cA<6r@g숢3}OcT 7W5 [2/ ƭ&ok[zA-xॼEz+|6i%oW&O?}o~[yyk^Eb۶==6m}mօ ٧͠U 0!tS%U/4TFBP)0" x:6VVCn;?4Š/Y j|mM}{y_P'gMo`7>})Ҭ>NOZ1L-;; Q,4PבRy'HF(2oiiҾ/OisDd 1<,ēAɲeCL7Al]Yg-4͌]ܜS4 %=1V@=J&5p8{澷ox=av*cgOhoA;Cl*(p|Milk[ۺ}uKAoy7k,pRvڻ~^ƻ·>?pY_˷t~{+wu'/|۷~ 橧|s[_u kJ¹[BRK*AYVG,tZ,\02Ƚ: pf xQZbi){d}~j :_ՆVeCM qtjЭi {l<71 E(gώ+)+% X"WHX.%gX9P@o3{%2m_ EӴllZ œ 1p`̠pS7 8c\;cY\Q3B3c,ٯ#6Xl =8ZTd_EvFXUatbvν-WaϞ${ae$ihK˗/]bD/g=Kĝ$F(TJ(jg;SLu0|o)"-Krisł n];Q8dO䃲c+ \Ɉ2 v[/_: _: ħ?_Wb#Ƞy#bXS7=A/w IDATm}mŋϰ#l%E!lj.Uo V/ojg.knLTLnD;7&pCLy7(Sރ YBoJׁfRt8ڻV$A{ bF 00|1U/~dVFC29'>Y*/Lm#sU&,2(DR'=-T' $дk|ӶǴe}r^)Ƶx̘3}{>5cU{b%&|G_;ro'g^J_ z!BmK[״9 X>w/q='x b<񻧑DRkRĈ ^$ SuTH~}[AWׄe-B]F% D,dMf/BX櫍:pt >y~ݣÌ 3IZvev a0Y;B{tnUuC:oe@E ji5a\L%(%UGyI&^Ip81FCE>IrEҰ65[\i\FYvEP{݌I)ә;"QAwb29h|#I.!X\CZhqDs ChMضN̉!&jr=mmUAsOwwu'/ל[Fʲ[y+곟}7SOoyn˼UA9s`%Z;k{3\Ii|ü~iGGN={|>RPO/֪mVdz\[*^bLI%!ԄPbt:[!АL\ o{a.xpJlc6y1vbhږq; SK&m02؝=nLQdd?E*q pAN4r)2xk ،(x,j"X1-qI"9.MͲۚyj{tr*o4& b@XFYMJ%zmV.6SZ[o_}AΟѨկz|Ⓩ-V}#?s]\9š VuyX W^֥חI J{|6.hH#_w0korkΜ8wnٳgϕUX&"%2d"^ci`<f ȐBBc0!1"4 "yqt9p iDgއ")bmMm/ hN*!@ƒfx7Csv]ͨ TF3-#KL)l'~"MsM &ΣBQ]c0bGR$aIaR< ?4hM{`ֳkp; II t5 e;AJ*$$)>**W%J>Pq ;U)AX"]{w!]ݽ>$dOUv^z(K}n;:%4pn>ǝflF8=//]׮> JGp:eeƻۖ#nۖrTZв10҃ mk;B 0u/ Td=J 䩰"+YdgK)EKK|KiVOp <,'׮ໟd_!H:7,%nɢ],}MjS?;19F@@ȎCJ1`$H9ӠqT$e>`11/A6t&+9m/*yثܯ~g}7! MDI1<-G>&!ĘI^!=.NYK|v;hB'fSLkQ^ѵDB=$ֶ\__G!|5xg_o_o_~^ /?>0!?S?Cv|ޟkc?zo7upؿۿ[9'>/g<|5m}uT׵1+[u fuONz\Lu)| 8 % Z֯N_4zd[L/8oF%]8hs36b=JIdc׭l(Q'YkUips2)x0 Kӂɸ`4&``Rmd8C\H@׵xh[Slf.xYJ@2ހB.sfL~wBc- lXZg '/1`L!T]͎[2nkmð[Rzƨ8'ذ@aٛaJI}(0*2)#MZ(pQgaQ3J봮/zJL>Vܞ Ѣ(0!$s:QL0Ŕ|NXQ]]S霑=>Db=rd{Z`-.f2ƌǘшh-q0%.rϩ`y&!K1c1D^_u;}4,nU,o_Uf/:|6zn,f ,݂E;ĝpх Ar\ \P!Q5 S$"PB J2 f%P.z/3;ѹI< ڮ= pa]nZ crêBi# X?SJEnt ߆~+/\%CG۵Nexy-_}*ON:EFakmm O_|d?(˗X.u!?o/KDh4bow7o|I^NMF!GTbP zyyea'''8wggt.]qi>eͭ߱m)5>;w w)=j MA)V}#'ib\%MbgX2&?$>IE[ X/}и.Q=^: Yg~NۻyycO4u15''GK.m{\3@|!xrʀ JJ`4*4Ue{O. /Fڳ\Ffl9<znqDa.@Ճ& recL\"0zza/Y4)eHas|< ,LCVx7(0t5Sw͙t' UwLRJf6\>&АT*> 8'>pH 4JaA)% XeƏ>J1R1r1&ITk&ٔ4{&99yӫW9z- Ӣbr" Mkp)lwJb;/VR0reK{{ؽ=d {;trOq/'p۫kSZ3}!d[\gɰc~;׽? v%fl1c8^sk~\_s5G'!k 0ίlH C `0L q5fXC [PMEotH̾W[SjBt hZ\ֵ,[an3n,olmZ{e|jN`*$`9g \Xf.0*}g, Š_C6̫Q>~lšcۉ|u-u'L1޿uǹPkTA F TˀZ*'[ög{lc}0{ONNN999놺syumg, !pzrp0%-1 .Ahz`@)Dߞ2] {m]Rz%d=*h2ۙr1ݠFzT< Ӆfb$ zj:+3]zݙXEQr:1 J$%{b( d:5F.Q飵k#QuksA׳GNfc~F})2H% t,p]?0s{r`A,QQg;Fnϙ#M?BL}Y y@LRc!5f}^Ή@(m F#LQWtI0zӗ@f?hMT)p UJLQlޔ MC{rJ{tDst$pVtf 1&a-F \-!^*UR@BHf8r2eF=u1rro3L Iڑ]g|)c0>.~ (t7np|ѹU?e&1rs;=e֞0k3a.2 ^JD$tXm)Mћ6f>RHND߂VLwڷbnu4a՜'=ިb^`P=аXv<g$)D0ⴣI]ha^I)O ]J#&#ORt  H-ϓ\׮O7j:2$,Ym, a2fJRS~Mql{ G,/4b$&foH!>ₓǘ@=k\GӵNa-h|C6s\1L3pR#JVST+zsP@R/Q X}-.p:p|cȼ9 r>=]pjmqs׷q 0 $&v;1)E$#b[֟z uK>Zl϶0N(XScmZ7(]c T/ד[ 1ּV &:mxooS=)m9H@$/&qb(rᩉ)-7( @HY7 Ls,yѽD жm2fxu֚ݒb<)NK&Rqx\2[F6a/cJ!dK^3d@sacV C$>& Nÿ"ZE #`W쪚8gBX joC ; xVxre! H$Nc~01BURTR SV}-KlYbמ=\5-|Nb}ӬMCup:gxh{/H7^A̤ 9.kB߶E !DN&T1ý=шiww>O0|/S`Mf=,7"j԰ONε"p-M'\f$;Nl"fĺ.^[t5#[S@2fF;I^YN͐q C[y;g} Nf >v:a7t@k~ok[W 2Gvlk[ `lAخD,1-ZY59UB MvEuNw}]9甈5BcC)-bLLя*55Gjf laOxiF rӯaMn;SW9)].ʋPE2PT,K^L@dKF#ph 2FPW]5.k ;%` iáf8TTh$Q:bƾ^dQ cbW L}q).5SdS=47wݒbڗΉ~:SN&Jk :5fCU(JfAǩYk[MC}2=:>8_؈=! N^ה9+yRb łxF9wض1ZB𨮅 Dm?^pnTݚ^~SO1x;.klY0er2==k8z%\]t' V&  @h[n֧T'ANv(9N~ ח7F[a0( &ދͰ\ɠ6yD'9`c+S!F^'iO~ a.bwt.0Db.zEɀ |sFY|9.D9WOP2[c6R(Ti"r\muPkBmc'd9lƾ&3c|0 lT.0xƥWiُ]0  áa0( 1ӢϲQ *#Uu#(Ҽ0;+ÙLNJ@d=r1.\t'%#?g쏰-;!euLWג9G׶,K9 1L-ؘ IDATd"1~v_K ]D-U),Z5T%bXbʶƂQJ<$;&p@#1bF-!xhKa5`RĪ`̾ϩo&O<Ǩ҄n^&^hg3=S5ǟHT ww^e;bryr9g9Ƙ$cP+P!DBNI2 t wKhތy';7Cn47Ei=Jrsn\n0c^h|i V Uz`"/'HОU!y~/).A<s4fɢ[0w حH{H(T@^)*]Q 2*2]pD<]m1 L麟Ӷ)c;gќʑ1cM_*5?%o\H,@N^ɇA9::l%m-Ȱmm꺖1e9c ux,ZhѺX$ \_@:dVV'JM} z Jkw \|Ȍ!7Tjwl&Cʣ!o굻Unxo6 ]gz6CQK=q, (2 R* l+bp'5e)T(aLqn"}g}9T370 ŐT5\`~f9{I1N1!*.P!rϘݟ2Ry!1`겤*(*)3qذ6@X ,0x-Lh_ׄPMS`,Tm *71_UEO55)|NssDw9cЄa+%@CJ}/2 ._oe9s@e-dv6cK>4 ,^[gӴ0֌yAƗ/3N!P߼-ck@$ !y![k#e P~Z Fثv!C32%Xmq#$=m\vQ@+Uu~Ա7*˘% `Ktu-]hC swIIB2YAv@e1lk[: ö; FÉ %EQ}516P`LGe?/3"4 Ұ#^ 1Flԩ#H ޻ kiv;$mKLhBB{cɰ^JiqƏjMBҵ{nי ~12aŐFF%H|f m+lc:SURd1TUJKY &=ڬy3hQ \T(5fTؐ#TݲY; R%8/U4sA-fv1L9f!&($\28om7JOP׸>=*1`-KUIUJy:r ۨ:SrNi!8'(#U 8"FB@ {K9iқzMyZؠqYDt^ RLP\y['^a-v˲CW189g9}9\2\ۇj:az2˗N'eɵk" Q)tQ`A{O]$)CBa;*BqΥG"c-9lTfw{oQod"Vˈ'ҀVxՀy{ 0Rqw ]fXKݒ&DBf| *Ttd ̀I1fel 퀁PcʰM >:zob+KL,:=v$shQ}".Y56EVFatAHR d|6~/p[}ZJtj `Pmmk[_}mݱڶe6;`0QؒB%Ɣ(UbME3 V@CfQcL$ii%=@Bm^~T!<h4 'z/>EjxF3aV:΂+!z ˱֦q8'lV3,۵.Uy/ʋR*cCQhR c6A Y?MQhdlYUyF Ler:&Faa@h<(>  ̘C!G8GJH8 [N7?5TUE5mi j8d8 Wu(֮~ccu-t&0ڎPTP+2 fc<(Ý14cbxǼs,B8?9i?Nh{Õ ozSox}߂} :`RIJ@ ]>[H:2chلst,=M) f˗Uz,ƃ0:?:<&uĔ2Bbi@Q@UjR1(|Z#Z݄]V)g̜IXfL]CFPc `x9cH!t*4"o;,tu`:qO[D%Ĉ15FD0Q߼I}V[h5e 4z @y `CQUt%l-NKێYױ=ƪtdpB^ݗ0Iᚆv>>:98v32OPB+ЋbOߙU 䜘#2C@+JѠ9tJC$jCg#|!@şXM 19ɄOx DW@ ѬK"7N ؅NLL;1ҁ.6C#ȼu@ BdDpQ'YDTQ@Ĉ e Z1C}vFH ŐҖT10B:e{D;QIEYe$EnzO $sRNh|ڗ;!xwz~^ mX yp`+ֶu: |3?~K/z^x;_x%G5]W}y7 1ryꏾx O><##=ŧ>mmO\c X0f1&X}- k/ icb=5L`:pN\R P 41trd]ǫ uFÊjXoWo|>MzîuN= ]*KQtuH#-׷VMZ:ńR y\&9?>oYgaLr1ֈ,MFX/&^6ev^@Ԍ Sw̸;ac!7)M_(d:,)!8>ks)O>_%l~\Dy"f:CtU(k Zˬq\̩oܠ}ygc8SXVb 65f3Ö%mrzccB?){}P@'%{1gу}N>S*:[.isc#ٿ~6yW0(cG@X,ȞZI^.\L+sZ㽗eX!XHEbAK[iWW7UkBJJdVZB җiw@7K%020# 1y׳Z׊٢0#*;X#۹pfYT'C `ecRL#FEHA,H^ 2Xmd\ &4Ąkt 2'CRƒ.t 4 |Wsaѐ%l{2mmk[w; {l%Ty 󝷽dwӻpstt1oz; ?=(?[ϰo:C'>i-а眜(ƖX[c]Mh_uM%!IS{hmr!9g!H 9v]Dɏ7Ů%mk )^E'.eX͞WC^fk`Y ƌ'`YEF։Y^4eGYg8mKulyRfMMg1{2Ti4V84Yy/>\C LCŀ[aX֬_ Z˲Ln5, uMXе-LkNY`n4mUQR]%+|bw3bch*KTQ!&ǔvѶ[.1''_Q`~ q7;;~х 0zCۖZӳb(MI:y]@گẖ/>c2ۣ֢=KI=ўft\s(,u{2b4O *Lz<%@XqڈyxQFO^$Jڳ^6 GFR7tn FCLz/q2׌2+VAY8R a&mm>V.;G>%Y_ ͻ]s~u_~>>T1>[kȓF ocd[i9(Œ+0J0LlKxo&+ʹco )mb$Da8`(" 4Mhh r4jjPi՛ܣq ka@lp4H~/+öY i R1Z2P&\b3[nnٯ]e@X 8ČЬ05+jې 8Erc|%oJ(2 )c|F+ALp ]"n~{1&Y@26w` 0YqMbV%فYbHr 1wTym`\)`ȷPzAIJVG;XDO[,A=s"h22`#^{tS7=$4\*YD*Z%q(Y6 ĶmݣjA9׺|S#~~C?׾ gyn`pKGCR;D2hx9SXIhxD6Eϥ[~]>'xq v~^cPdn`]*JZ0\셻}^">KWy礩"(Ə<Λ׼_v)Sx,zjz} VĹij??w-12|a0TX1z˚Ӯå#[e;_:nb~t駙2ea11jwvv1q0@a:dRIZ%B|A{rBad-v:A1Hb+TQ:&_@ Fiho&$?+Wti{ "%HjyètX|%,%+&P%QL}d5XoH$?t<ߨ:{ɀQ2,2Bn2DQ 0L'QVTbXɠ кh  _!'Hb2'DMH>BA~--o~I7>~k7; ^z| m*Kn:1cS~`\nnevrzgf [mʺu:ф0sK `0¹c /߈\$`mLV__C `YxVDZ%HDE.|` TD'e(4ΉGDEFn8"d_I'VrUzd;S1iT/I<ěmȑlB6 J`PXC!߼_ :nxZrƛ!֤T1.([5crB0#b=+`X/46R2 Lxɛodȣ~jbEI6|z?uA ǃB% 6ImCB) acaRLvGU} ŒtCy%pQP'd7AH\v1e@|2 K^Ft1 l$öQ#?Lzt6WU?lv7|?~_ubUm_Ǔ~ ݿ|9jwwsB `v//\r[zm*ȵk3FXSB|t -0h1M`m R[b(gcԹ H>71y-A 7GfsɜPSk |6BjVF1 \WCH C;_ `02<&TᜢTo)s+VfM,S IDATHBi̳5"7+zN<.nQn`_q79| `2XS_xWZfȭ֌~wݷgЃ 6{9~;9tSԟbᕇvv0Zٍs ˚E+u!ΓOqEQ0TLgh C4餢U@W(pNnPJ˒jgJO1PLc䈵 L2Ĉ&BnJ)dОBA X@xIpg!*R6R5j>İb; CJwr {L8⻐ 0 h9[qD"ZJ]W[2-;Lq1fTJTeU H&JSb{֧mH#R,c3aI ^Ɗاe76х_cYK a]"z'mmk[Ri~<o?W7ﺀ?/gP.^O|ãWSpM~f_y;']}RUf=<( !Okj?/?ߡmQ|"^|ȗ{U4JlkخD)hp]qF4ڢhm>Rz Aͩ<Վ̸GBP4mshy%$@Ql2i.& +l)Vle a%ѥl4acV_ }VV (5i1 u-l2q;AQŲM}f"m'm ]'s]OH {B5!tWSԧT-7]FO}ŧ?C cJ^L1B5#N9Z,8\.9/S"لֺ_ÝX%Px墳cn UFP)M9h[INkֲs6rwWXX!qmF=J)LAǘ|>(I sE=sT]ǍαDR~2^@'0)Ov$|g3 i *_{O\i ;; oyJaMA6?Zc1Bi2 CiJ&Xjʴc0!3`X {&C O#`֪h#*nZ$y"8w yCv5Ms*^imh`*<D" usxֶ-Ww|=G/ ʭ[":o[Wݏ|~y;~Uǫ|+K,w9կ:??O?/?|ן{Y.=?#=m}/2( -Z ȠEc Ysc,΁BJMf1 ɔQ'F&jˬ,9:s@ 4 2GJZJ!, 𔽏thWL~a=mZWWE)K#@Ibhd6s)t]HMv\c7dAb! 4T,U_CHcra |Sa NƲ#mu-W-m}ὀ 3-)-ؓO57o[ҚAUQ67|[S^)Tj<>%r>ǸY auo߶Ǵ7o<˧HX<djjl'&rpx oAfë.f`aez=Ws,]XUa6h%l!Iajt`FC.1٥ѢXw\c#l5I.tbl%YrW o5f4ɄT *0H !,"?G ChF"@olTŏb';h4Pآg1J@ a2?``*ƥ0i1e\Wc&Xf%YB*E*ag{.x<yUb5S6:҆.8ZpO%]wsab1JJi$08$k[a[+W 2s/K|ū={jG|S__~8<<,KзGc˲}G~|󜜜b7c>qmm+^}D6SifM%HD ZL:+@nj5l|L3a c',e^t4!D)Aׅd FjCfĒJxgMcmYӻ;Tp>ӝvM76iId!( EQ"E|CA"a!H ljA`˲c;u^^VUta քA"[H,p^gi U-V& +ɇ-A\8xk#Ue8x3Fm0H&&6A౤F@\>*>\6Ҷ2Ҷ岢iu O!uufж9tQ/OhfTwч_˥hino3f23n~h\Por*i0J)i/!J)b+kVn|S\ǩzr2#ǘ[ wzJ>oо5^bkDA!|{rI=]σEבS.'UJط?m#e@1*&u4 m<줜aƎFLv`vwԊJ*/29vJaZg*a1D s dPڈgC a@Em2"1Qcn]1βݗz4d9opԈ>|bȦIb@ Jj3x6Xe |t&ԇdXoJi010HBϣ.i T,ٴ7Zy c;aROc8A2Qz p.)bO.ᣧ=]X-i}O{|tPa6] .t,ܒcwϳty<.+b/ 0d/"$aS۬w 2mp\_|ung-7~~3u—KoOqegY,?$_ аo U51SIpº&G#Jƹ z0&bn @ ٗƷH%V@ z,DI]Wc^!u-TNX'Js.^ZkFQ7ьF.`y ("HExȽ]yÀ*%q*=l`4kGw `P6GTʼKLeβLPt]ԳX,"c6ьF>L*S?! "X8f Y;Ӝ0Ou|}*k/7J1Nlo505UU\3s2])٠(m+eKXy\)%M]acd߮AhtxH͆Qze XQiL] SUf_|K$=p>{oAʫtNHzlتB5X:Ǭwu҈+3k'd&: ]Kſ= h,]ǩiU1,#iŎo0A70 *YcR C!)|nUS^/%R9n@DY/6K|\<IU<AgK'\K5lk>I)  "fYH1QiEz q҃"4r쵒床X)(LbTI_U^$#Db٣_cj U%&uͨ1FZ۠#B E7 5`G׀вtKeZZǐ>8  Q <#_Hd`Ԧ6.o^Y<>} _?o =ܳ|Ƿ}[ ۷?aSߴRΝěT[*[ *Q6/"S(T&eyAXų!Abc kB(>  `H$kЋjhpD4+ݶ*u&uҸF*%QU&Y8ٛ2hR 0)V$Cc bg{2shd>1R`L MZ`tg,=g>fYO{2#ܿ:Cutu58{2]+x:e<3hcј+TW`ww1)f{ 3tѤ{Ot=a>ǟܥ}eyքR e XNn9s2LA[+)i0;T<I |ΰ\\rBr\KlFwtLo?:"u](ck1GTy y Pb0R_)%4L5sG*ӗAʟ=:46)t9*׮anݢvf{]$.D =9yu-ߝMma(Y,[z׳h;3a=(5k ̺.a}k<@Uzu )kso7\#YCI. 7R$()&1tCQRrBQVj嵞"a<^@uCS+b-V.1JP g+B|6CzвK37gϙsaIzaz(=we-8g5{,AJDXT6æ61dVOn?ȿ9_koNm%d2|1g: ߺuG$RkߥԦ޻:9>Gèy1}tN\r-3+` KXqhZB(F6{$"mCCsJd(Rϥ. J()S+AKCddUZlL5IDRֵ0Jam3B6R"h1dKRH0!41kۼă=ө5vqHlKZZgYH^ "0,ΖtPwRy{/ѽyۏ6 tdg=od1[3_ѽǔd]:=cb`T[Sd0 M aM#}Oz4nma'cLUcU2rJӝ=?3=F~$,7O鎏ݧ?<;a1'tsELzw1ah -0t⼗E)GW`C \w:;^ 1~~I @GN3^d:} ݡ٤urIv,sBS;X/^kYK6NFGqvt%b~n=FC 51^QktJQsB!+Q ԗrc~ouʎVDTҀAl Zb`mYZQ"1S1MhF|!$2GSj3PP؀[5(GD%Gd!z݋Ca1-G::߱-X 7gϘ; :eDP*:郣€sN3aw"$ET CTK3h7Mm1u)a'oێ+;;]ҿ?r>oA)_<0pNܸ~ܸqPK5MC(ySurzLU7TUEoVqU-xӊHS/p)?\Z T Aი z_@Ip$#(>eDctDE> R }( VKfifK JŌ1Ue%MB4Ο=@~k*cj*jbty=C=0S>eTtTβP0?)0b(CO7qimkY_bvtܮF pO= /0yi&7oR]G#h$M{̍`z*h1G#iP$=`SxjjyȌ賱"Nonu%yi`H) >0;;-PT,I1K wrL8<ߧ?>&,[!)?rń< [WtJ39i^棍9g(Y &%ϜbJA>s>o>_Ovʫ?<7ý{<3bW_{_G20J}{'[[?ggw|ky/}]YMm꽮{SI47$NH+ Jn@2 v - ,c0cDG2DSc}cNHo\I2-)QLĐ5)Gfʴm(Z[4KJ$y!c=Raa/J UZ _꺢"Ź8IJeD+H$AL/䷯W:Ƭd ]`:1ԵDjL>ґ38;&}KtW|K3ֵ4Y1ay97oQ]`Sx, xG,͜l$b{=PMГܗ?^1믃EQF4ֈBa, ú\x\#,ijS)~>R,45fB4*NI,9#4E Y#P86:'HQ:5P.=5 `ESm>{>ӝ"eQNb%ٌc9 α^)fH] _8ߔBU֔qK?<=}+R{O 8S[Kc wvpqtt|.qB(b^57}k#NoX~H>p9BQVb”9Hh")&b+! 4$C @C{ \ 50$_DmNH>::SG坤8MH )D$\tx/;`pKZe`ܜu( h l~I{",;a9 Q÷ۇo—W^tOGo[79>9=>;#w? m~.]/?w-tbgϽ7?şss>?ƃ?X(w3<+>ϝK__~G뵩M}=^ XDiH+Ê/FY IDAT#sJaQ0&e|5U8[Lޫ*Ivp.uq4@2̥Ꮲm*AUdBcҐ$hyX1bwPY N% ,Cya 0! ) նaiKbP0Ƚ{riM- u!xp=z~9C*/~ R5ЌAk1تɪZxg>gctU]d" ffhjb\`}6N&QkIxļ.-֚ڊ-H6g,Nzаp) El }'MU(!D7HiUb"s=>8G) aQ4~J@ "IU>w EőB~E]Mfg3= FZbK1],hhXt]l-I)IRs"Pn+W Pc ˡ]fϐ%A$l˰4ѫs?=hj7N9j1ɽ-HjE"*9lCbT1daZcXB-@#Z5M%%C&L>IwTB+Q .: (l`rx?>`XeIkfc-9BzǷS,X jP A:eCa1ljSԻ\dO؏>t/eؿ]KWǮ,7n_W}#H_c4#?RJo~$/ ??ʏ ;g/?Cf?Owⷝk_ɟ7?wn׳zmdJE'@̋ܡȋֆhAfBh ` ѹDgb)bn!50#)f+vBϠ<ŸR "Y9˕|+ml~O?Eszk&v<`=x>ܽ w%)i\鳯^13Ж,p1%'v-q$V/p<|$uJT)aSĐp@3{ 40\d.}lP@D PٛA{PZ;9>$腤DG 1$GDr[7{9vn`>F|-,OOY/utR(9b9kg2&rf0Zc'c-/SCC a{kJ3cB%PX \ymƵk5{6W=j43xJsŁ3ģBS7x|(2 @ޕx,.h1(({4&aHtvrdG;h%*)|t I"Hu-hC?gf,܂/qg?@Hi`1=n<,5Yc5СH#gԦ6K#'pUoɄ+;x9xp$ O-&1GǸ Qè?xTu]ON'!RJq.#I.6Juֳ\ۻ M+zަw)u5hD$*4TU#uqQ{CjKbX,]'dQb< L&FEPd60LEhb4xqմ ^?>$8J Frm-W>aOpC֋u =>|[g1_b!з-w>~md$\~!#uXԣ1X=!F1Rۑ8 b>`<2(HL9bb3E汮L(#^2l ʣ'"}pgBb\(`C{ aƎ^+<\c4CgX8>==eyvF*F-*@n2ϳL.+ LРSX7L~gPӉ4Jɹ Ϫ#ٝ;ܹ3<#\5d(Eu%rwvb,W%znzyuJz|k4U#ŚJgYd!R1}_xiC5La3  Fk*[t"SD٩v)S;eb'4aloij*#j$*YR[Kf݌K\t"}L#BO;:ױ-}/=ޕLC&~mjSz'Kx?x.Ǧ,9;qvv yvOܠ{ORJstLMmX=xpY@*e<eפ:Am9x(Q3X*m{I)O+˺ܺ$CA<ZK7kHx_7H $,^d}c&h-|oҙaQBOJWxYQ9ݗ=YfX:JQW-O>U3>bM1+W;ݏ|}sQ+$wr{]}i).# I;G;TעloD~]QFzRKb{wM]rp)1%XȲlOE@(_ԥYڐP)(ʡ'$U` V@)b$" f֍zv]cE4a`I  ''GǴgDc.7  N5\/eE*P~&Mk5T[Sl3",d/YUވTݢqv>!3b@i-,9Wno/muUO2EYI-VUXeE+sVƑ1'1lc08/ ,*:x.d#6$$T9F?HpR:,r@ڜw43=c;55vُ!@:гK|0y9]ȼaBN,ݒ.tr@]yS:::7oc6M#,7MmjwܿF90h]ef_a4#$rxRQ~[dxFcT@bxЂ< `>/'o2 ҆⛠3P`2(1&BiH$ƕ E^)AjJ@r4g߽uEˬM1FGCfND$Avp 5^]r |1&b5%׸Ϯ|haqʣ[{?1PIY1fߣ};4(Ι祔r!'=dpA-[X.'dfCZL UyΫt!%9D`h[,eE+ 1ƈ^LFYQ|`H*'EOAhza)do:PW!O(ѴpPzJ4۷•4񹄈rI;s^B?o.+IAV:[dI:Hk"{S\TUacɘn-j}kEӄhvw9K|%XJk;sn4Eq6_*2H,$ZDB+Er@I* @)\0uySX%GvR Yyi aՅC^`̳Lm {y-!{to߾Cw.n@mMW@]r҄g3GzGZTfG^0Œ5.FU5XG1IGAquPdD | P2$]~d#3ÔQ(.$"!cGJZ4LJ@ջU*ʒ R)Wa]g<]T/y-p[o5?Eh[%M +v 1>>W_s舸\ح-HiV[gC9į9\mJ]KzfQU%E Pj FB$&жe=A!#*E\9?2j5& DbI &y ,ŋKKwʸpl= \+ԣ֬X Ĉk[''|9zZ+1!hu&C)9Vi,l50dRbF_Kº):{+\*lp/2{ohއy֍)r}ZI}ƊDPI,XS!"*LQA *kHxE ,A +0ŋ!6s I-fHŃ'IwH j%I۵,zIH)I*D|('GLEw m )M)i/:G:bggtG#{Cړb۞_PĮG_sDI}O֘-)p0I t}̤W#/J mdD*|7D::3g߄W@.ΕֆF׌-VA:,حw )˷.@jc!VyDa8zKlMmjS߀ڀ Ԧ޳J)q]`Š F/`&Tu w  !HC#/Qu9FJ*9"(1EIR+&G1V(L)PUhpAAeZe4ED߻ TaLaVuY5)d䲪x;L_Iw)YsH,˜}+4O?MV#)жĶş\8;&D صs{'!Xe;Mޖ'ڞ'߿0hU2z`yhc)߶WL2IJ$qUf2|1Zbp%-"ub$P0b)dj4G@آ(1%4MC]7k#ndvx Gx+VǐJBC/P\@jt;щ!$+eWJs,*ئ˽/QRl^tϢA+)D̝7׼8||)bPH+PFH4l F|Pf|Fi"DPAϲ HRUE4(2(OT&ՍDq4 Cb/<.8F3ՔTjpNM,C ,y c7ys$ܼyߖnNې GG9f2eZ$,r?+g X&āZ+$MU*a" dyDT#3$%^ F7DBT1r6y(~cWc3fa:5[vN͸3#j[ `p(>?}]Jd姰.pjِ?y[CӯoV,6}L>O$,aQXEpqҝ7ߘ2\3 SO&< α8>δ@s$̶N`܌DN AVYB4bH"(,"I'Kڄ4(a$<dXDh9j #O@8PtZEc*165HL`XC7E5Zj j̸Sq5TTgSp#$?ȐJvՆlܔ: *1# [!0Ԧ6MTO 2'c dCg 7o9)Fk>J]rzz={{0/GmGc57_}Gౖfllo\<8<ew{sc|[ #:\bHa{{Me !rN|zyޞ kUF߬. 0=>&k_;Hy^ +Ѫ%M)KjMQ Fh+#f TX k64q# b>i *2f?@lBs;j$ʔVpɲ2;K4JQM<<~O׵Y_'zSW&YtO"b(T#Ω!DPJ:-LaI+TʠMsT0"&p/tD^ 2K#PaF e %-`59G @GT#*SaMEe-ƚ @A9FKT(VY*Sхn%9gH 럯xЫMzĦ6o%6ԏ?͛7/wmKLԖdxٹeN&SʲXt}7F=]Ĺ02f>8sE-ZWyDSu1ú'׬cimq7Dg"f+=s(K< ASU40NOúcy}] kMT+1{W|}@JׇZ)ggg+R2NؚN(ueE\ۻsoiz]j1LPJ)lۖGzh(~pc'}`mW9?l0d( e] e$"M,kTݙG1#cs|ϱHnz1{1Nj5"( IdWlF=_`'!1Dֲ4pA+Fs|PS6W^|n>G bk_sk;D4ԣ"2HRQGti!K{* @%0dȚ&YLB%MS bNHZCV`R5*&T/KRC>tT`a?u**[aA-P@b sNuBDl^(k2ɷG6 æ:=;)i*> =;Ѻ%_c!$f6:%OJ=鄺>i]>Z.qvʕm_Xeͣ&Kϳܥޭ44# $ @ dd,0N898!Y  X66FHhvt^UgsoUޒQ~sj[u}|2 6 A(3hmETxBBt)e2L-;" qͨH> _(AeF8#U T۴N${Mƨi:cK4K9i?fy"c Hnmn/ dcKۡ//-!dss?`Ǖ0YǮ~;;7w!v^~)mX{q6$OC@|,(2ٝN@^݆J2XH;ys4U2֗ .ګd9WĞO5>mUfsC9d\hycɀL *=wSmm~$ԚІa.paA m-` "&M<6X8|'uѦ #CHn|LRY_'BG@k5t붆O>D@Gv;d,)%PJAsG Vz3χ:3К;@TDD=<=b^Y]2!x#ЃX]=ϩӧ/x1~+xۿs~e_{_3}{}8q#B^/Wiv]?vɧ5[ulR ,.,裟nަ]>üʱcQ9/GOg.\˹~W_Jc79m>ēG?1? 3ίj}>([[hmAX(IB06iNkxjY1&~济 ]8QJcBELqNhxasøGD  1:36*dNdYa8Xk"QaG)KR+<>(CC =Hl wc \D)77PZa&?Rvruo?FW7]=:wO>g>E!ϓsdO3V0 o,zD$A/n}Zϰ~,Mz&e| |ݵ i{Ev ҦIXom$[Z !I61 4' S* ~90-ce =P oCX<9zŜZJ2Qa0^Šu1!Xy`r}вlcHwi63!> ޥAk#@m |t^%Bk%2" &=.fR azZ.3jRŠTR?׫ [pE4`qΤc(S*a3,?nNC9{!H5 4qk1}.\-//}W]]Õ T `iV72Zk}UR Ż_5xgӧY} ~?<l]@ˌ,+BY~/UDG:BR(BXPS M@2'g ;u.L]^jS+i!="q٪be2AMfY1'Zv\;&CzmjBʒ#GX>zjcyM8pPrN+Tb@*Ƕ:?i[C08ﱉ>Ri |QbC.)+B:H-"e{DW"~[3⃏`F>~h!#%"xт 2I}! dJI&9le+ܱKH&@@gRZMB"@zd!  T%! icvmҥJ8H\lwE$KVKԵvХBtOdEM?PE 4$N:$S@gCl#H#Ђ yOi!MƋbcR]];lVl52tivŭɒ?u\_!sRjoʲ^Y246 XikL`5 {{lnnqH%YNQ}zc JJfὧn{{ 0r6[αҋ>Y?IgPz J|$H^ tV;2K DB*Ep͵5Ν<Ņ3u}QF_wඐbC1Vd?=@, dE,;CpJE9EYpUEʀ[kql39 dQ -%(,/ԣ!ly% Tdf"!Xd"$`1t1.o!-21⽈hv6J`-TCDLd(Leh&EȐ7PL@63e3L+o'"D[:2VM)w:sǽ)[6q^à A666B|/4;}wÇv^:q}[xռWpV_ȡCkU>Qη{}쳏nnűHi_[+00`SE3V&q=u5d/Lo[BIIdNZ3'o- RI,KTYXX@9[=O}ՙ3ߏK4۔߃ꈴE@( RR%`* w/P uTJ-# @@ n2JBJ8p!'5"I Z d'PȮBw`Je\B ٲ@KxB) XHTJҋlN135fl0cC=n&iK _\\׼[ĉSE[]0ER[҉bmssۏOp]wp]wzb4q? _}q}گMۛ{UwYrv⑇W*" >_nzkm}ts8}v՞/TjsƇ?ģG?F#@ih#U>s0˜1YЅ&X!PU%K7Gk4ب)IEq6K_$CGH*(JB+f m: A\ I  hJCTZGܚn Rknz{u`FԦItdY TxJC4uVл@q)Ν8hs Jtb0\ p40]";Ʉ 2y~f!`c,w]#G&MCk5_`l?4R3$_ZB%w{'"2ʲ.KhgkV zS6.7Q(qODixq&D_,IH`Q5_:t(.oP !ȒatboTO'ۛ4 .D#ZhdGBe!$ω`>ތ38o1``cK=6* 7`,{^he-{a0y담fWRa+\A IDATp1w?)I >6n;~C,/qם?Gg1>/-uÏßotl^UouxR^u#Ϳ>|nxcO?{"F[|-\V;kŅ}ecs_D3gN cv6,[i1v-Ώ0f()zz(eR'iAli*OY扙 "6]Y҅u4K15EQXgGCky{,e 6Bi!%4"!"!~gebLƋofJ[e"0az=P"}8Xl1q뫄xLi㑁jjԅY\$_^fWMW+{%`b8_u>1pc\lD&1?eiM_=z.\_%}Q-,jc;b13Via6ƛ8S%CN ~%ҶL4^g VV@)(>7 CXkp{dBW0(@W>_B\}u^ &ҥK->ū^IɎE3ɰ!lǏ{=wG?Nu&жi77WVȋb&>sy5ѝ, Xcjþcu)E4eOwQ&!|ݞZyDϪO@Bֻ2x?ɜ5A|D(O<VI̓ '"LJ@ 2W!!yMxױXc1Vfd0#K3ؑCa$x%z,LhM0D"sIM4-[YomZe롊O\1, BbUup=7RYUw bxS 5xY?w{iG(4ReoHh 8.#{4tZlyO JTNJ(z@jM4 JkDtBLj?;f.I61=ř_KnZ|S.%ֲ:rb_/%wW7lb6TJ=,S{OLmzgr{+MEg\ lIZ@bf l 9[=;ޥ΀@"[J. Weh Y1Gt"/2gAb0 ރwS6 Bv!dϵt)$|d dfb6XR Q*XdM+;iD+hV.1g/k^jdk'OCS /_u!8|y_xiտM1憚/ձqqv>[?򱛲ͽWͷ}7'4_18p`?Ǐ۾?uT.e;8>`u\…AlȲ\-^G8 @M5]h[bBoA(w2!BgOoqIη;{f{g[{? )s1LtyXxY rH{ C5&҈d3GU͢Y'OcWy^hgHg)wH۰}+#?t 1ޚ=(h'ilmmFsҚ"+++yH6Ra͈lH`c{xjLG*2y?+nÄe3i_* _\H5dB u1u3g8sF#7`U4:.cz1`{ב--u6nbŗJ)G"!%?DziF# h*e;YAhh'PNge,X:Y Ը<ƅlDPJ~DD xd  )bLX\d DekT6>7zc$0!'$9B@:FODH2x@ )xlci,͸6ԣzTk e=:n0$yD5yj52T㊥ŽiگΛOCc9zhsϿ@~E.GH3OS/}#xW>ç?ٛ6/屵Qܬ{B;EnVy/<,+n㹞o.~._pw/i_w8p`x]%5,咲 lll`ޗQ2,3E]5#b_/[ 8d! J&Zktp֣L,vy]Hg!deN@t>E1@h\dp8حKcT3Jd1̨J D`١u Ip`L9!2΄8 YY8<C]!?@5PW#PxgSn.3kk쬯4L vǎa}c36xL0P!HMIt&<ݓ]MLem8-(PYRz2.eGUu x,106<$pmsm>g{^009aWW$'C [&RwɁw"[Z5s!\ 0 Cr&!Pml/."{=A;ADsƐ"\!/moSXqjI4R# +BDpAɮJ @~|8J!" doGtJd;4|& Mv 2EoGYEGp-4fl5flpG4i%( !IhL ׼u p)xͫzӗN^ B/MK -РbhN?٪bL!g]s?"i{=Vk*vzKcQR8`i*z"HADР5OlZIRO6Ɯs`7$3BO=hD~$:{fQ D<昖|!k˻};Gfcs7=s{&UUW[kk9}comWϣb>CPJ86]c^>-Wkg2lHZL1)Bi2 bH CF$$="be4PBtHHT4uu"JU3*d6i& 3]z@B܏UC.080oo<W! 5{׼5[f )~_2w_v T Ny!Ot˟}v>',nU/ʯ)~W1os7)?|{zCd]/JֺՎ UUUOCPǖ?'xs/k+lVb]ӧH8r(VJZSDu=b<6Hᣊ7 PJ":C=B(%f=b}Vl:wCzeIQB}D)pi&-8Qc\7V2a{gQ<+&FCѭmVwؿvn4&B.@^ګL)T#R^ % N;jFkkTO>؀${ȢzhkkҌ$TL >%8Ge`gtLku?X/Lݺufh$ +eߛDt z R~dp=,~g_Igsȗ#85}gL0@b3%M5&?װoA ccCϳLd*#TFB I N9\l]} +,F0 *~yu!2!xdz^K*&.s5.$ji"{6. V@&jpǏ}d-4 d׼5[~ lefÇR7 6y]zo\dYeQvҀUZ+ۇz,8v8yޣyާ(zdGȲ%eG)M F%k)5BM J%Ic(OcKі:z2 L)BFwc<1M-Um a84Gk :4CJ%x띒]yTuGL"{[GYN++[ \0>{|3q= P f_cΊrq|a2Pjdةυ4Y6 /yͰt >x'eb {\4qdb"Y mt]hNj1U"Vz\" 6@h|\4l%pLFxq-n5yKEJ9yk^.k41gaDY1&07 zYIDA뒬@C0dH'N2 MiN#eF.D$ S"efͰ7y|H,G$6gր5$*~ Hi&8 2TEi7om4w{5Ihj ;E+Hۘ~HRJ*cبj4"!7 jql35^Goq&m<Q͙"i2Dx Uq 'έth &tMI"!dDrMe :$D 4 Dd0'FD+h m\pszᅽAƐ>*=q.p mB$h%>^ǏGD~klWd2wl>U>so-9D`As Le%Co\Ry6h/d:CRJ%$S*X$LF£ZB tøDܮWcGJ(2| tC]q-#lMd-dhn<) \tr4h 5y답 ü5[B CF ZZY,ɲ,Ѻ@<>VExd&C 5Je8gΣA❌ 1x>>l4ai\\n8cp"(@PC>O7 IDATcBa ,lg67n`pPPǨ^O7\\\D. h-,`Wh1z{b4By?q @_o|#3me Xm曉$ }YlϮm$Bf)6C;1p0$`FL5b0a=H/\O\|`)UgvW01&j͆i2ѣ,{o~{塃G^egiC=2leEIY K˂ -5> Z' tw €m '\'"'8 |Hi4Ѹ\Y׾3vRbA .R҄u, `5SGgbTe ؀NFIiD13_|yk^Y55yRB`{g^%%E7y@LQ2 ,le9RyFt r<Ǻ8{ECh: GF Hl}c(cm{1&DbE@U2~uchX$%@./C]{6S4Yf֛l`b="(2QvvЗx|¤yu(PEqU ykFuBb0w{-0FȤ!o_5]Y r0 $L6ϝ̓Ob㋏#!uɹ \p3|k^${'MvmEgE Cˍj=xѽyJZ賵LbyXل Ʋ$ 4QVELd22 J1L]ӹKFDej|rUlL 2oE^p!Jm, Mm"m2lIko|J4syk^ ׼n !0FdX\Z,eM@%5YT<ϨpL'pX[ttVB'!> εL $RN<bvpD~ 4xh 4i,ZG5>40*4F5܉QLLfRKiK˨S>} yA+a{XJlQ [vvdbay9\ޞDqK9jL3vu i/jէY SACa10֗a6`Y>ߥn 0}}콉kṡpm<}>wQuv/k,Cz ~FPUtNҊB,l3^&BBA#B* %b:ʴO)b+}!w'_g !S?.8*WL¢D̘5B4fkbecjUEU8%HYX-\_0@hMa5yk^Ayk^_eam[!طRL("E_V<1< Yј2҃mB}DCO.gqL.jm2i{s> kiG]Zb}d3P!Tx?B`)0C|޻gctM̆xcC9!fӦ-ࠄ(fTAJe`!$|%+G#ϩc>0ݩ%m`ǘnvTbgiJ:-ӡe2LZ;wS]/̹g5̘opB\h+Q/u!y.;-VJ?||0H3W'R=A*/Y~Uyti|DZ< JɨIa"ޅDd A(Q"džex-.x|pgR,ȗ*h(inm,ZiҺc0Ĩ ب uc1%p!6BiP}sc<vEZaPomS,,"wI::QR.(IIs`;dϣB`1^oNw4=v}}}1#}^F*1m(e2LOoC Inӧ9Qf /[˂3l{w/=[^AE0\~ln8Rʗn7.LWXY Z|@ZtsΣ,,`E`DcBg:i9lFgOC$r"0X'K k66& !2""D(05׼5j2k^,c we ,.-eeK (d,s$t2!)N-;DZaJu+Jh`ʋa`^ lapchKU5MYF+lCJkm늦EIK~҄4_ 0c0ؤXZDJT7ή_ ]3;L)qe+KL@4iajcguql!v>1f.`M1-=" @LBFT23 7{&n/^Vp!_\w(#dQv{z_ƌFkړa/@‡p#)QZ_2w3+Z\о^ k;M 3t,x.h|HW>ʫLcIQQ@,GX;0Nnn7.qTT|. 15uD&&ID!$IDL q{8DD 0yk^dAyk^J݁EQB`0XXd"/ɋxAj@v8B"'m1I`#nkh\46QAʀRcf & X ?{&ui:OND $AI1gDQ%[id[?yW:K-^ɲ()F1FȉQU==݃I♧nݪs~GHrrd > Z"n;ŚB/6(SPlEJ&ȔJ6U{y y#Cl`1lD jnd,կW"^ fʜ ǚO|^Ѧ&Z<ĊDCknFuPJcrc$C![2kW^+cԀIgmDI{ *`P)RbKإ3₏"-B,g(Zha8IJm45U,ݣlEEnkI?-Ӷ161l7;l)#H (g8]!mbb+ᶙDc:HmUXVS`5%̅i0e!sՕsnF)J"gd1x\!"Vnr-z:r=x 6oߢE ذn-혖xٍ)YOg475aZ&ǎڷAWW;g^IG{ZD'+g>=f=bD"Q45BEq; 浽tK7W++W QlGJ;ξ׏-* r٤\2=! $B8!DZpN_G tۏrta ƿ!Th,jpU6E!ҌU6{#Ɏ "99DQUL`XBpROXlb̚dpiF8|x9ab$Rir#e_ Z57"t Ep=3qH(~4OY *ZW I.= -(ZGdĂ$O> ? #G&];U#e]FBB=!8_\41Y|TME/Ar!)X6 j BܸЀ[na2%àhR.!22!^|"&w`|˩dLJCBBBB̔Ex"^  !s45ɐfssMqs?{9mZ+}[Sr=?oO>L]FJH&C4 +3?NK/t(zTgsW3n:FT>5XM&AUUZZ9w{ITUql+h E*j+0(⺱{n xY S]J"@J鵬B~ѣi{ۥ)R:qLmjU/W_)7rHf2 %sxRhzNATUa~<"Wa#D, 4pL 괈Q&lo5q\!8l)H( lڽ\ hIޥ.72BfN'0'MC=`(;e۞X"K2P^&?/(4NJ01BsK3DS8~T,J2AntL,N̜ Gp~9>Ѯ.T]o`h 3 m^ R 'j<\xi(ܧn& 5>7P`B79f8 mH a)aؚPU )+2H=.08ei"%L,zϨ:>tܟ"1CꚛW<rpL54R `5'!!!!!K -$ k`OM7\˖m;ցmۃ3O?CYpA}֬>_|ryN]'o~? ngs<1:y=|oc8~r̍_úpOG~7Kغ}'m@81Yݒ UQFcmM+DpWKm<G "kQf0Cħ~$=LyJ~0궫-hbhkm%0RuH;ﺇY|YEdaA#h2YjުMi'OxR&8dtuvF,2wf/LwZ `d z,AI&iZ0(eG=X9y٥aӜP/PL CQlˢpq!*DK͕@qN.[(USQTKEQ@p"ĭjpLIJ-LǢhɛyWh[TV}5^rܗTG,Y8eZ`$"$$$$dΘ 2!ذn-k:Ύv)ѣ 䓸Kho>g EG>-[˓N:c?BnwU>ǵW_]hn3:i;ﺇǎ}5uk,1NOw^r!:CüҦs/\V.?T: ygIKܺ}>Bx=sGo-|?`GO:bE^į}Üqa^v]w(Gqʲm- 6m|?Lss?:g{ .C ?0V*÷+6˛/`Y(ua-SapSUU%wr.beNQb1TLmʥ2BbMƸH`H)Q.[Jfشm: ,cBn0Y<9!97t55IR8cxD|I4M#;: xrѾ~CURÇ'`N0q@}t]p% o$2ZI&i>T) `tj=4< 3CH`:gMD\ɐٱB_kHE!.bBȔ!hjР%=7UeK=셒Sr[\94/‚]A6t$8^ GqSrle8iXcJbv?T _3:m%(ǎ!Ob5 Ysdٱk7_gA,Y3VƟٟ)ʬ:m%L*2).8< :(\WUU>ɏ̝w3͔wܦC\ǞhzggCCó:3O糿)=v۶9}թ_Ond ӹ{jJTMɝCGG{՘s~^l8kl6oSO?GաOooG].w^e[T(b@i8s&0̄ge}uEh49fphȠ*۞m`*Y q8z8 eLˢUA2ƉBbXr@k[ g`_5=cMˑ@#+xu^Jnm /` LPWAⶭoL\(z*-'G5͔wfJ@D#3j\Mԯ}AoVw4O~V?{}ǝ?;eK/~}/?ԬnِJ&G?D.;IM&lB~ΊY Jtvu$'<6굣!75g1mvM6T;t}{:u%Tj7}^dpӫdwzBQT)Kɘ#)( P"QQQ,a:cv3PSp& P: a;Gx*83ޒi yc֝}L{}ҁ >?^E p%VZu*\uCtwvV ;~Ċ?—.嚫.ǟ&zs ƹ̈́_<{ع{ϬoJع{/=b0R.Ü*w=ތ{=m3>Ԕ EHgxt5x5Xї,>w~J2^r| W^>ZZ7[OWg[Ճ_{<Gw $ f2SM'0 H%IR8hvӲȚ&F@1G4P(BTJN$0T)]!_4?0@tlaRs!%v=!`(@mkf2Y=V'8O0zc5A 8@908Nuqd^W{xKΦ4ccU- v]OSУц!98x@_;߽÷+N{dnnXDz@Md<# 6Jf2tq WE^܇D d?[{N|v=3쵫Yn _x_~׮1e O+Y 2?A>xDu62,Q}b[r=?򗌌dcsϕ}Oio'TM K'&(BL&I&1NP,iئI.3:6Rb6$Ig7ɫ ҽVk4b|l%z%6p(%%AKK3R!#vo&)ZD"N"X,R(2kjCqtiҔftt\m*Q]tr٨*cY 5$PTJdƟSjD$BKںi"ݻO='> B.AnBLhj"ґ^;U`TEŬS9~juWiinb+twurБm~m+<.?{c6NZ=REj~y]tHR[d|?1~dPOJDZߑmQJZ16oF'[-#2 Bʕinib(`&5EAUj )QVlۮIHkn7?0(K) dl))1T*EXٌ53N'haksT3u '4-^c5SoBS-=g;xcz8|7?MP[^%ػo?{:VuJB˓O=ÓO=CsSat,Wiɏ 0jێb޼>AUպAr5rHLU`@UU M PiE/o٠DEtfp[6DnX kvtD3ϘRL4)Eh2{.J/XNJ`--UEUD"e'Nz"">A`mX;TJ&B?f)Mrg]+0Bbdk++DOc?0?z}wCf5LƤ)UТ$-WQ*hJO>i9;C4-) {}?xSL4s?Q(7o/_̦[l~3<-7VWD4+!g?>!HTN5S6|-fûzf==RD<_ƗJe)mC*d+"Dx>UU?݁cnΎ&Ui3!/: ){m5k_3MTsJ*j߁*k-9=:] \ | vGUR{;D;~f_)y!("i8ҩ[/>iJ%dx %L\EUViU%0o~/BѱNy|adgl!)*-RnHHHH{OatvWf|I:LWW' rAfp P.MYl))u} F3zx/ov꺯a^O{y[nfeP?\9}faY6/ +/kz *ͱ,:^L"gct`` 'hyyNزuvfl~Y?hT²l^R}#Gp Wo''=᥽Ѩ8Ք Id 42ϫqbRO(0栀kr=o.^wz=IY0Ģ1Z:xlb痀?7=<f eQ6 Rdu=IE>5hFgG;dt:E{[k4R$M4mm ϫ( ГJěuz{?Zc9O~yl DH"w?(~vұsTM4UEhRIKBBBBB޻Te2t~gHXbe,XWM~ rgq.e /Pn&wVo{}n;w\4P*: CJ~Vzlڼn?Azdtqu8ÿ2,RztV759}թ|w>=4Ύvl>^[=?}3N?o/БDQ֭YͲeKy'8rXe)%Q>[m0z$3ٹk/Jy'>˗y湍 EYz֮9ᆥ]Se]e7^5ڽӴ ΥzG{SW.wyn㋔Jer2K/[k:RonJ1o< 81LNg|dٺm}Q,P!%I({F"j;!ѨNkKa&X HQH8ض(ĒqZZ^Bq/dDzeNʼtM*p`[6山$y=BN"ǶmFGHħCU;*LF*vXp"ڬ lm{UUAvʼnBodL#? c_'zɴ~͋DAU.͒`d^^͌ﶗ=yZqNQi1C_Dmw&2:J}\pqttsx*h^xD$38RUݷ9W_y/[;6%_&\p9\rapQ[#| &/ ?n 嚫.Ƕ9 _m5+e3Tcۮ$;:_?[o}\Gr{#~ TU+.kl/JBoGq/{X(24vhD*U%U8Cvttv!h۶SgW|GJ"H+M5)ֶmD<4m$FRǎc!Y J2iac.BV8-.TBxseiii)=>7) d2H "Vbmmh4J;d d귰tu<BBNm 1k`RyN3o|,f jn7RӳdmBժi3pVU[ vODQ0lR_`Plm%R,bLJ,6~fǡ\*V/cUCN3%,(.)Q5ҥ]t!ePiTm&}܌/"yک%̳s u*H)'>N\", 0рXFĢ^=ߵ0-,G }ǩxjLU` kU&K֛Л,$$$$,Zpj-x5Ä-&ۯt=4!P@|k0u|wJD!_ T ƀǑ5")Ab I4 Woisi(0S *6bz ?<,Z-؆v1) ΫV`b 1ǗFF0f-V97{e߯*ʬ\1j+fM#eٌ !!!!!B!$$$=-@@lo?aХ^0? !/@Fc6ʱshUk 7) I}@* x 4T\@b|z5kujKjX %h$J,/~U1l/AkV|fhDEl0 rG`[ TF*+aZR(o a&CcE!*D"Den-^ BBBBރ8L!ZZ` 39u2b~`|Wpܝ6_!BPjj"Anx\Bn'"d)HQQ*T^sEl/@nZq@7D˚㓖#LswgPRaӮ M~`4ӡ{'@ydѽƳE&ghXuJs`;f$0`Ф R;'$$$$!BBBBރ=|QNc תQӓ0TM& x(x _%~D`iD;)r҈zsB7^ye_2ḭ1(ؖiP)uϵezk+ nP )*C%P:~]Si} Gb i ALF(AF_}jv Ӳ%HLU2[fZ"Qh0NDQhRUҊ"CHHHH8] y`:ӏ+04b Ozұz}KAfJS|xdw7B V _\E8XiniWY3eLUיw٥tl؀HiNx ~]+j\K.FO&d884gml[(=¸?I0g*" ۲@ʊ/ÛM:fZTMJ CBBBB"CHHH{ۑXRb1IS6 ['ϧQ E!D}B MhL^tp 㸁*]>BmBۙgsĚ~GɄ`"ͧF׿S w{vۡz8?@y*1SLުEW28RV|Lf#.h,(T&U!1Y7 !!!!A ƔCJp[LXZ XnoȇEA(4z_xMT0!ю;2'cP^`\ 88 wolZahs3.DwwìFY Shk+-gFOާɰJ%o|ܶح}}}92^UQ& 3)ٰm4qESFssS`CgĄIӈUIHHHHȻPd yb;-F`B`vGEUQ"k9QY%$:;=jԲd6xdd[0x?? IDAT)cZZN V``\UU4]iRݓϷRm~m} |۴s(^50j!woq0M۶RF:R91Feh_HHHHH{1 K/C˛̘V.|6_?VOgּ]'aZNZxiX\ ]ǜCsv[uc+HҊB:!jNh !sn@#,]gem0U6=Ň?t d7زu{e|s ײeێ*QWnNAzQ,G{4EExr̍_úpOG4MZ[o&_v [ oy ĢQm;*5MSQ֯[3OM=|_OY{Ūm[Q0ff QJKK Z$p&C.;_T:MSX.W5/>Tb'PO`"4IK@q@F--I-n`% ö,rhii)v',OryQS*!l4D"Nks3|,t@]twv0 fB4i(ccPfR$-+Wkk7b׵D"CjN] Y0xPҬz<8AO8_T7]"Kl|SI="e>548kw{/塡I sl=ϖ~~nXx_tH,\督f=~LT~ qLn}?|N>/6EG>-[˓_ᱟM[yn?{Nlk% R G*4va$ϲm)(J"(}?qB$x Mu=3,&_(P(lE\׉:aDFGLxmsAZQhRm_CBBBBޫ̉Ȱ3ɲcn2<<Yxg:??'S*YuJTUTdHS\p9ܟyM[yne}޳ ?3O糿)=v۶9}թ_OUu>ӹ{j *y==ttW9Ʉ3K2O:3wfeGGho#N^PimXC m$@ fHI$-ˠ( %eW MpǶ]qPT-0@( $Z[ItI& uP+.*)m+-<Ʉ+>j¡aEp|ˢWؼKXoi%#8ѿg/;v=p30@9dGG/mS)R$cc,me0:Ze9hsMvڹ(^DKD#. s5אw,s&2}rUG믽.o5\K.<_TN]p? Y͜[yno7)vsϿX M9k4_ݗCu:a|[4t@091'z˫4Mkk.4MvzZ{xk-0 .ʻn㏰w~[M7^yT54Ueff믽RF[sHēhoF~`ۏ <|e-8.KKǎb?E qC!jgkh8 t0UAO:BDccT1!Zk|P(FF1f$h}!!]l\.eq׌R =G5M$[dhWƄ^O%ڳd\Ƞ:|MVT #h4(xO7Poj"*vvH9r2+ ʸd|,TC4-!ĕ~tEaDUB8i ^r1޲}퇇Ho}fsӃI Jf+_cTd<'#ssW5\m+H qD/ :U]יFud$i.nI1":||p͛7)t }-4H)qcV [`Tu-#¶m&\"~"Loq@$A~w6CE#.RRQBH)Q;5 4MCJ&v,VDQJ%e2K$Z6ٖzBg3N(əf\2˥+{uC'|=SC: JBR|OK&4 3gSVZe7_4X8-ksPPL_f hZ"ç>9>/uZ6NvuH89w q\`blrӓ<9gkn+qn;x |[ϙf==z7ᶖ ߼-k}{=OŻI+}~gssgÆֶ 6o/=G-wEl/Vs4ˆzT&P$w2$q6jۍd!d[0<|(B3 |6ֹZM`G,2 a̚q~sN%8x0Sl޼q6CEpոlj>ml?cc5MA?ҠK_}FDžo=!myWݿq,fgٳgk{17wƲz[nB<ږ c̱c'G?NJ-n}ip[L<㹎}G͛z>?W^q9o7ԵOcM8Fr%V{c;;'%W,1>;=6ϓ/;H01 c` R!,WUu,Mz)%b:~)`ZYx6LN`&9fvvQ2"v.g1$(@^=RQ4YCxgAm0۷s/1{' M.gl:\{źk$|=Ǥq=]1L'0 &' ~xa)*ea$ZO7sa #&o3r-O>ʵ׼éCwˆfk{o޼{﹫_U??Ͻɟy/ {4ȗ˔;M` I) T>92ъ%r" :z(u w5W_qg=H)}Ǹ7EQĮ{/G9^qfvџΝw}}wku;tϬiӏ|{y+޲[n9>O9k~{x˛ؚQ2py'l{\~õ|c / >iVB*2(h##I~l 0|Q!U*OUaq1|`@PZmMCNO+v.9PiU0Oq`2rL/$> C8$#YLQTUVP- +C5 0 {O"yEPTL ޳22222^!]Ȍ##rROaY&i}y:hAPV ӫo|[zPUQ |Tb?XXX\9&"Zfg!cAVk񯄢(LOMz@3Wpq"ȷ!Ft!OjW@\ݲز KVWScqKKDQD}3ۆH+`ͷį_.3Wi28 ðW|,ً DJIYHK8**hAib.ddb5mAofm acXr8`zl;k ϔ;i- ;B.~ P7uY&'1Mz4jյgo4%G^ )5SsqnK&!#tV`(`̽H!Ϸ0 27|}!dqqiAZ3?s(iz8AHr]C7i:x}nvoWu4P0ncxKR!Qc-CMSxsPK5 (i#}S!BShnQDIMą4ؓ H| d0 6BYBґesQ2g0!"{W`8#B<к\ UdR0M_f? ;LګMOr(qZe03T{̋32222^dp")iF!HD8D hi(IjG( f>Gnt؊~  0_[\?vvB0g7Vih ? he!EAM4} =aWYЙ|;E=^\qƆ1...0 yH."[Z\rA`CX({Dф揃dddddd"CFFFF ϧE8"n͠wve7*/_-Q5 '7>nYa(!񁈂)V*b.תեwM5M̱mq`Dzr'DYgC݁H=T֟(k'x`)Rv~/7sDO" 鼆cHݽ^p]VsͫfY(4L׷u(%HY{{vCXPPU eX9)####|#22222hNҔ Q@ J+Ѝ賽զ0 4"7R$_, ㋯.0iy1cGc5 ӅYAIȕIzKs4}M[(N<O}^+.aÔF6J[sxyU4LBj7l ?;=>(6PMߏP.D= W잽2!####fЈ"Rw6~d9Uh3PʸDt= 'O^eV3(^9pp@4rP?(`KDͲF{e3HVw4M=ľ'uu9ȣ/ R48*KGpjneq^nܢTQ:DV20X= Ë}0c㸸ˡfƲXdHMM 4?w)I1dn#֒St/)ViB` ((X{aRFFFF˝LdbFQ' 5g[X|PЎ Cd S薉YA7ue06"nػkVb16Aj:!Nu(9:S.#E-ޛu!%.Qb(غs ?Z#eiNv`^(rgH4xKKC&^Sem"Ѡլą31ưal @u,jDAw 1ta <UEǜ83z$aWǔX'" ANQ0Ȑ&揀'6]AjAB eFQ͐L a`$-|]w%VIORy~' LܝaEǦsM%`OɄFk\<%}]0/ ðE > 2 Mju[g(e9+MEqsףlxaV2-A- 1M͐ղDQ' &$9.*t oF/c2!ET*ʡG:Y7c7;طis3 gp5\m+mQ՘Ǟ`O4%|_44 6ok^|}|}cl?S?رk߀mwzٻw<1mzm#G BW5BUAGn+lB4MTUAtÀ0⠈LTB*V.G~Dc>BJfs>̙ 8f )mcJzݖLRЗ1 |.iH)q=zN o= p\4B*J2K p=Zf*Igf 5ދ(.i=cy!ڻ|,㒉8LzɠM\K)Qն0R]4+ko=W)K+)dR2!a(!KB`* D}J22222k2!3;3瘝Ǿv*y#2 9ظa_wXaqqrInzq[&4m__}ȰiUl: ?CyQD76FGK۾7OKdغ{O>B)q0,u vZ% =Y †t]GJ sN8H? 2H= La06>NXo0??x i+0>y1­[W-B*]'9bFukl|`R;(B>TSx3>% A%(qO(" Ek VŭV [Av޽ąA㜉 C(%N4 i[V>QjIj`[X±&}~Ng+$׍RA+BT!em,3222^d"C7`:]믽<̹VYf=sgxv0INv<|(򃧟#zm5<xaJTYt`h !ayS;winذr )q+:L8(ccG 2 BͶp.gS*i4/,2,bjr ;ևiuTˊM%nۙ !0("G>xVg~LK_zC`X$sWq QԮZQ}ape;~7mcr?Sq\?u ޯk*mc(`HI]z+z r6TNҷ@ʈE <"<(֓R Aq*yȰXAOu؜yHQf}Q;?Oy0R)i(~*!06Z" CNw㰸8\Z޻VR[X`S.Ta 0bȢXRJ}zn> jy&h'/+q){8&bA`XE~Z>iV(BzY,L2փh~g "?5nK\ƭTln"yUV $rL22222^zzWU78ǎKSXOgx8{8t9+s;V>>c/fgne y?k׷޲.y5<7m.13=Ï<9zAӚJLOq?#=wOs igĉ9ԳPﺏ'˕*2Q SQpi(f1J'"0 h' a"2JImqF?% ]}|ߏ-%aU0;ҧ'j4h>N 1 CΎk󪊷8y!aͦ{_o4=AEE4M<|ߏr9 岔5!~S-bQ44덎hʌbYFrdJRւqb|?TJ"FzjS,<AA!ǶE+Wc+C&f[1,!-IŅtEl ךݡR&73nA^,R?~WgoI2o4RA܈@a;.gF\}b1 ZfLASutMcÆ8$0Lm4 m"mg!c~hFniLMNpViH>q]`vf)%ab&KK t"H LضE>881G* U`v^Mץh9N\2aX9/ Ynn*(ٯ:ec AxIt;./b#bͲPD3 tMETss-{02####+شqo .O{]wooy>8m/_%燲Ȥl8{~><̷hϱSyǛZ(Vž897lU?t%ΣB>Oǩ|ⓟ^Ǫ^^H)֨>:v y^\v|wF4Qq,+͋=_4D1@[`./P;uů"e+Z&=t' l/Iu5N H}Y =[w)EQԿJOA95jG9;+帧! <VëTi..aG04)UkH@SU6m82BV';뺜G4r9IN/Pr9&1 4 &'&_XZkq7IJ"Z-*$ LJRX!c F y*IYkAD!z$W \+pEg3n1QUTBh1('f/nE(QU(17ϕB^Q)ˊ3&2x?i773}Sa|[4t@dxrb}#2j1N: IDAT7rM7pqL 2x~n?k믻O|—K,5gĶ6n}$` sr6v8#A %A AHE|ZWw(B?p`VT*2;;C$0 4MS A89KI^pD1U]ߓ")<<հ 4B5 TUdQ.5:nD\JH|! qN,m+RɮZ^V6lYI(#C5(~^ Hz ϣea 'K%[JUE ta5)f[ !mQ $\>KUQ0$ĭcdW-K.[uҷl.ALJ fTl0_"[l>is涞vm[1Bautvɫ_ůT]6dyQvYQd^4bibIV`X7AЖ.F2ufVk %z>$}q5G:!)ezzra?puLOOs;[眭:ĹKM{[QeTagm=g]pq:Dh:GcWJ(ɧoaF 8~=L+_shO|jCUվm۴zP C04 ĶsNf82?@+m= rDE$rCR")Q/z:Cۘ"ӿ-3 j"r6xymBQJEu"_NyO#?] Ok4bO Um RvLJEIɵ[J.%>8kL;v!w~l$%QUɏlM1 P;HzCɐf6MC"0k2Tu=bH϶!NacՓ';)9!0]Qp1###BKd}RrfZm8;oG_rٚzqхxO>ŧ?wMnoYRM7g>>}!zqх[[GGKl޴/xNq LB/e덾xqΏûPsV}\wxF̧Z_,*G#5 ö1l +40 à\ z$*um) OBoPO`mm]3ž )z.uL^,8t$@UGPRHJXIqR8D\E`PXcXa-D26;m&@a:q;Hc=P*(д@**J~V M(1RS! (ΘZ8p03S~ɹS4 Oζyů4 !nj.!_ڃ=|t 0Ɲ η}̮{S*;o~x]{h ټi#B_|ワmǾ_}o1 z0ԩ6m>|xцZrIy]C7 |7YOT~Tk `: " rU$`tĪC[gH& !|EQZ&c麆۞*޵ReDZєTY./g.x.Q _m(2PXK/dM_i8Aʬ0XaCF6|(᠖UUS ðwO+ ;'fAVYǝ C|!Z&M`HFB,EQ[+2`"J]H)ZBY&v;:J)ښ 3'04`"KeF<"kDmr##&&(NOSڼ{b2EW&0fv`((G(G)PTf9Lrau4-P8- <2a`fO7Z*dQ"1誔CEmb=3Xؓ$+;F>_q@5's[N$i)qt]òLؼbSSֶ~\y/ת4u|AH(ӝ9Vd)+c EuO&׎}FBTBJ` _NFFFFy9d8x0Io?;|(^:9 p=7oȽz}5vpo'NfgY*g E??{X*q/?^O"{+.^4Mejr<Ϸ~tco끵v~Gy=s 8ilڴzRF|=LLoA&sܴz\_y]W'"wgy]w011Ʈ{I^v&&R.7<8%~۶^?1>+_vRh4-r #"'ɚi[&VΦYܥ9L'E!G]ɷe36tR1Ce%Jh$F0M/MٸKvP$O5B*޺axFr]rW2 d4qo끵>{crӍ[DQĮ{/GkmƏ^jp]k_<|_؁Un\PTs6ӏ|{y+޲[n9>O9k~{x˛Y??#=e[yǛxDJS?ڿXPi:T}6хB,TMhq\s"H_dQ[\$Lih'd!=RJB8a#eboۆY, AlRN}}{}n茌z4MFjRRQB82:'ZNR+0@ðUkY,⶝>a˖ ZsÈť2*ERw*lycTυ"Gc|H>G)#{U.W:D,ܖ$-@ eLRx<" BTUctHZEK2zEHuFaDRo@ȖǗssK#9 [,?w\rymI3"g ~jf #5l+Ym~ K$B W#0ّ͐MClb"QbdJC_9####|@|G?Gfd)mzYeZ=O~OMS#jg[|[zPUQ |Vm#*ccIšZϽTB0>>iTk5ڪ($籸k{lo[.ઉ1t( q*U:zf#0kY߀iIdV/0''Q, EQwiƉON029IK1.cr44-]#} }a=.2fjCUT%L} B) ֟RJ,ލ ہ'&CUTTMEJ9g:"(V;x O5E* )@V~v-@03;9,,, Z}ERAT-Z 2_wxH%0$mw4?U(bMPܚt Eq&6nddr-QjRJwH| @>"C:,,1;m 4$.k(ID*4)BaAqo4ÈJ3M\g挌K&!#tV`P^ dEjyg=a\grEJšΉgæ_r\y4HSQUU 0hi -ĩV֊Q}'" 3q^)eZ%+JK+ᄉ}k=ɫ'mYZl/eMQ)%@ Ι`̈?"ꙞȯT<"#"'!IQC0 Eϣj5RtZ>AFs>V|c G?~jNiT7\1fqV@i'#<&(N7L(̓, iQȬ" #ƻbk\ ު qa CR^`g> 4aSNL'N~TXQ4RFy33xvӮM5Rx<)tI*+) @^>SHU28x9>TPBZT$C *TV=T")ro$JC~nietZ?2C;ZkV#ܿ7ݿ|?C΅`'{-}Z505eA9-gƗ߃RUYRd%&3sG"FTsDt@>j!؞\P7X c}ngd!^v$AKdL m?w)JDC! - \ A;YǍ;=IC/1Ľ}cuKjxJ1xmk- F瑸 YO΅pHF`H$`r*M!K:<ϓ8eL)QB T$C *T8#z=և#zΗ!)+[L߽9w9&MA) C@x~FǏFQVq/`dKʉscزv IIbg)453A|>Kr RR"@?/>g@^}L ƢZ,cD HRɒ"kN`Р54@h9 bd1P2KvvZ.y>`Ƞ!EA` ``*7B>:1J!pC 0b4裔sRȖP!`Fxz 4L4S*dBa쾳VPBW*B *tHڥ3 B6 @?`c Ahlڿ]Z e;8w: Y6p!aIlol`p P@B*U1d`F,jaS sɃ #ƺa\^#-+ FQ6NȉW62xW), 8!ʇLrF\xgFK@ =%FXEJZ2E.z8dMh1]n]~m 6eB 2O[Fu4Fkș%Wi'"I1ѐtucLjPJ*WtK}Pۣ0 hA@i\=[c=M*TP«PB Έa4tK7KSfJP ψ- w LRK2$r<%C Xܔ@JPLAލ޷p>j{vȣ8.0XOVcfnF$10B2!c: Z!PB T$C;Vܹ/?zG}EWx:6~e$am'%w~+~cooJN{1ư%;ywӇٽsY2Fc$bo{ ssc>]ɰީsWpjmՍ Nv,>шd4Bs=)ٱc+yt: NʃH`eaA*f  ֻ]$!xá]a mHG..Kt,-.>a4N26=!=A{F4LuؽJ0L?–/ͲcezI!hZp $k" n1sxpQWvt̤tq{̦ &Sm 9)goyF6K+.UdڢG4+S-eVpq6H)تg\7D [-_XиVj{u:l Ln= gAԂZP@=|XJDȉ-X"-ɤ *TCE2T({^O\y}<7عsǥV|2O~4j5>7>?ϯG>q>?#m{N~C'>A{/t '-}|#G^] ٙ~ n _|ؾ_Ork! :KI(Y|g}K+SHgZD&}fghZ_gWG|%:6=2d8Wݦ{QO( Y/t v{ d A,1?7G'p9 0DHIǤItڬw9t3s!,/-ȑ/u͆5€zh =Q0dA}P&2"W(jl4E-$K(=LK[@^z&rifEiPS39i#rSƢ[u+5CvBc {vS︃ڮ]xԝimdD1jjV0޳Ϣr[B' :=H)^͕ &U2߇tB *:Q-pQw؏7͗2Kݥ %qc"Fo1o>߿7#~?O/[(x[66x|]ݵ4 >J֚h4:돜B .4X]]EAQϭ-VIӢQ Fz]5r)ZRZ?!m%kٶX3F dfhj!#b;A0:=BH/ݶtE̱Fm2 4#rbrc)[}0Tk4{iu}[-|}<˷'M;j;wҸ&گ]!#$z=Dkk~:Y[$A:_ @(qT IAf\VPBW'.??W]81>OpI~?$=|fy_С|>RܷsC/rvXoݻwQ CN:#~~[\{khwZ񞻹|sttm"f4z4K{੧!w9zkr[}ر̻{+>/8tA}{9Q;'?g- _}5]:'f:׊V%''%RJ>ER {@H)FQ!/QH<E>"I/6 BtV;L;ayc8'r7}@iMyj4QSJkM}CkH~Ay#aplZ=d t{=3CL2|WzԘ p̉MsпLDhVpgn&,|3wI{>fӎxR:d0  Ğ=ou U$0 ^)m*ј(„I&kDX1ha 3TPBW.'Þݻx}o_cy'ox]|3ٱc%;n{=$iʿW ?pŗo侽>?'N+aV k?v-}<#S\V6WݟGgP"#Mɩvű'}=Wo+!vFՁ}.eN:}}{#QpHQ,H6c%ɦkjay QbqFV 0B&I8dI헲ono~&ȵ1"ęJc}BxJDi[z^N0!udp p+R"uYkiv lmD6 ,+#D/%k.6 p\B|;ǶQCK4mK٥@7ϳFQ2bK PB ]w{um? E.b2nݻvﻌGUv,sosb.5a\x\kk,3<3<7y'70dO}xw3|G~k:3[n"(۷W;Fb#NkCd-zX^^$}6F#(bH+(9y4;wn !Pdcdmꪕ}fl6h!Hl6.8V .R$RP!AMF!fgiβ_ ZYFk!Wd `ݘG~"(<K_ɜ seBpY} :9Q `݊ne~d'!/%Zhvi(F!P:#QL rrT$hPl hSoyu:a$^gJ&)F0ӷ`䦔0$'سJْN =/'rM3E51"| *Tx 'E9>;>Q!Dшe׻a ؿo/++K|O׼\};v)wH)/\x\!ԏ377—~˷}K| _bvf0 6ָZ};Wx7ೌWʟ4,M/..{N2 lS,'R+x4MIŸHWkN#IuD-xk6/a BH(phl!PZ|P>Tdlz2ǎYlvk U@Z)w`@V(PU {5zayGQ0 K.wV2VP5֓!ST%dKF4pƙNmWlΔ̩~9C޲* mZklGf1{մ] ǧo!{T1d@n,-!&$uN8YAކ-Q1R()HJPB !$C$߱?0⋇ٱp{/{>,/O9/W>U^>\yW߇؊?sWx{/þ}/~Fy24u[Bq !x-G\m?_zxJK^4b#U!z) ZyB0d[h+O٪餙JF'OPC/g)VC!AXJ{.beqp*f*YHE8%iOHgJhh lGaU5LX}V%09P"1m~Y`_ 2#6/  b;E5d.FXA6`E HPک!dIͰ5!Rx1khJ)"#xaذDbt " aaN33JNhm/U">vd_  c)_9Fd0QQ70IBdcB *\,\^d2IrI=FkZ-E2}]O|3KpQv>a"'Ot|+9y,Iڭ6w,oq0M% zXg!1M}B߷Ec6 W uLnjgeB\(`6nBP <)DvU^|߳$1)LCVC .Ev ]y&%yzt8$wl l,)%"`a>,UA r2mbEg91 S)&!L }v..!6a$RY4`0B"͛Ȗm0>ڀQ,hN0HM If8wD7!KU(D|9q+o~ks&AgFK=/W5jv/vqUR%L 2"%jCe= sN&I< %GPH'67+B)YJȟٔ߅)y;h@iH0?70A@"XB ;pI 203`W^|wq/>|.5vs.[ } }C?ܱ:K ZW~X[_'"O>up*><7t9~9|cyi'_m˷<$qZ0O|?//~AN:M\5~x#_ݶF) IDAT X};|['|gI%{X\\r(ß~poyG^soooޟ?{oo>Ed0?OzmoQljY"9 J)Fse,]ƝҬDFQG)eϟHeQDt4^Nl"ffΘ6!V €N6!8FI>stG#T! C:Qiǧ5%,O%R sԚMRY\lJ0j:g)SH.,Zfh 2,U[B ^i$$Ñ/!`ii}'WصkMŞݻ_ϛdxտz{q½ûvR%(m>#oi8u8^m}g<>Fcx;V;| ?30W]q9.n|o~˽o7 O[m'y;LLިw{0ןֵ8f#I!Q+GBlTQWfdF8y:3s!k]t@j#Z 1i4Tid}@B94c q d)RfgRd ?n:)a"622hpă3)"K2J5΋‹ACnth3WjW=S0l ґ[c=BH DlXSQǘ-aFZY m JPin,%8YVԅ&t߭H畗dzD1K}Ғ!J9L58fJ;ji[C% C J3*KTPk ygV BӦhpzume^^oZPn|e6L0eʄ18FxY)D~!=V4I<"v{l$)}`PB @9$`Tl1f+ ÔgS̗%X BeML(bJJ\8+ dd9 'u9Fk-. ڦ:V"ݻhɩcOcRK*il*Dp3)~RXy5 Δ.i 'Jz}=O0JCXy =XrcTk6C6􍡯 buY *T8?T$C *T8/ QLQgxtN0l`ij$%Zk^gml୮܅CK ]F2xZ))Ws@\0n0Zl!; Jka4 0泻g>mHG 1BC\mB!y K~~sҮԂyKY9_LV(ϟN/<?a}D 7Sc#КA*QB IT$C *T8olDqb v7\ôs-L?rJxZ2-ѐ5!m,Lvc DM4`0`fv>,nf/GII(c9-piv8ZA3u_h6\!6ES91@fŪܭ8vL*SAi ؜PV.(PZ:rA)G6`/]}UZ`1a+PO(@&8ҬI@9"V %=u $xaK=V Gʓ! `So$=zЭL+TP5dPB e k4=^&C{s cU$J+cqF$% FS(lJABe;:fsV8+E"zES}$jS|A%J ϱ{ND}De履qơCt5s(Q L%R)ߍ{RcJIOédT1TPkPB .aD7IYWB0."pkH0L em/=T+#WQ"+0@}.F]Yw_ sG' <(inh!AW*ӮD t6m3)Up敢B)d]"(y Pz'_khP o1H#]S&2AEڄu72ÍggLb RJ:{v~^6=d(z) Mjz]++,i1i,,m٧s'XKwV dc%([hb~(M4i.,qÍJdU`W 5F[RLTKpA8 eGk|3'R29J[KMiVr c9PrDN6(PKfZ;w~b8[I=vsDv7K)k>/PjD%xR33:zpyʖsu>VW9?ѩ$߄mI09ɦu`MzJ]>WPBT$Cfggx[Ћʣ]7{?psx' CvrJ)N:͓O~o}H)֛k!I=C'O]r}uh4c}yqۭlQ~ndpzAׄ,4 jҳ Dh!RXYw/&IOU^x3k&ڀ">PƗfE&x'C`AHPc5RF107%MU8ElA+u63sEiʂl[ ֵ"Qh1733N^-RiTrA"UR?r@Vǧ0];i:#XSf1mxh&(M&!?EUPJ9?z!a+ $4ùT(2!h,Qߵ)dVA i3;fv~ AR 5a7o"8g~xYm%$ʳsCvR%`]N "*TPᵊdpѱs ?;w凾]O2v|JO/L$wo|;_}qmTiUi?3\/׻7}']0_z O/o{ ssc>]ɰ^}?.dEWiƾE|gX!hxNaq*PU4VVu\q%YK IO`s=BN2lZi>SjV> xO4( %%C)1a@* fQɩSrPքQj{V2# k5jEm}ӫkrQb4aRrj#ߞY仐 ڹ7.(  S !翰8*ICniU^R-0s͵4sÙJd Z1AAcnƮoZMQ404F0 @Qa`v[n)*'n=Ʊ}/=q'Di@IQT8=_ѤF3pZkV' *TBE2T({^O\y}<7عsǥV|2O~ԭ~j5>7>?ϯG>q>?#cvɏć>sॗ]}.o+\p_nWnH2e:Gs{\ŰkaJpPR2 S2Q|'ñcs/WUw~䌦+5=MN:pT.ͼxryDq$ ~$&&s>.h>|q"FO)$!LւO8|~>$qrl,DZJɵVqGqh@T[&SJob^Cץ^I"w> )f9^d҅e_In=^2k-.?:&k llrh9j8٥1JeNJZNlTUbHcz Cr|R i<?ı~`mjZ*VRHRor5LK|ovN )!BXc$Y^ < T(u6.qKر&sqL<79Px*]6b@Jp IIs)J ׸[{[" IDAT.y$K%~j8!IBfvfF,+ UC^ p!p\$r 1m2"(_)ȅU=G,=1XQ1#5L k \uT/Vr+#֪0!L4%qJ8u i1K(IAxY% +BJ焳x"zñ#G= /s'^W.#}%v=JYz1ZO=Mnِ\B`J=I} s c`= 2hZzF idؽk' oGo+wq7p_8˥*~_G5+o4x9>7oj?pՕWpWs߷_G? >Op{|[4CkaU;>okgRܱ=7<89mxqJ*SOS(˂%%R)R}HG@4I$z2 (QFV?qsx~zHTOSNm֨ I\ZAS2r.1ZgIP,&yCaD+0$T;1%@1 HY v<#~ g*5[䆏!mw51$$.D(:M(%HgQ>jR!(>cב# :>q jb8t:ċK ap9 1KBBVEtj~NɶGV1je:  (i$$>t+_6f88+}epi۷mˏ=}{9J+ O>,}< TUW^];=6ׯ.xrUH$cd۳Ͻ`[o_*$`e't'NFq"ϣT.Q RZ֒$)όWkWyxu³΢r8ge%V4ri/%("/k ',0}0簙C$E=(IoT)\Zf:("+Djh%H*-:T;?$p.ȤA$}O-IB#)T  (P`d`$y#|ض`j yaf=w¾{Ys/0;^\ܷWџ5 Pmຽٹ>SxTy뮹kK.0 xg\Q~sGzy `y23FIt'm IJ[P |*Gy|b SS J{$uj'L;~8K9Y!7rLI%Unh6i?#J rA+aL/|p8\`N=jɞ1$IBZ% M`2NI-9K:k3"AcDx>iJʬLHs %DH3cɊ (d.oI_767Vg)gjٵs;]LMNp{d68sv2~ˍ魜w9i= Z[9g(v?g?6(ɧǿÏ>+/~8yo O> {N.<`;89r7HbġC^HqW  $aHyX`9Dنc)%(s>[G1X,ЎILOO` Ơ T(!9؎/!a"X2&ۦ{*-h v}r˶}VEozlT9BַPj^Ae`ލŜeW;C?8n'r$GvhX*a=atJOH KL+B^!2!ORqE* 0UeyH}J'T7 -8c"ksa8 z +lJ4NJ,D(b}a8hG1F4a ~BN2Gl _Q!9,fggw9S};|?oyx[R2;Y3WxNB~`jj?_o?@l{:u&'&VeLTs&Zs ^ B`j 6T} @*'%Rt9drbpAu'YaZYa)0@:0ORb4jUgRG!h QfY0 ;\?ҧ47~-EC*V(M9=mh,5PsX0EdI,UZt Ws.bu?ŗNN>k(F='()OOS޹Cw|1ρI؄a%^(F%l8ϤJ5-z7у1R(+K%0/T$}R~JI4$3?`8!jW)22BX 4Fyhh8$HO\ (P@dHClF}8k 7#`v6>9go'sog{;wȝÃWV/_N ڳg>w_7Oy\*Ou^*|u\/үRIjFYf{'zI` cE8FђCVQ3ggѭVp ]N }? 􎜣Dݐd~ ADP@&GiU!\ҸۄiA)T1GhwZh~ D0u"iXOqpbxq!\q3ɽg(b⬳ UTBm-ɇ2!&^8oq%p"J$FrG}DI gv4HLw:)sTJG,y7sR!7`CS!i#J#}9 C i3~<;pHyz=r /80 wL zp\uRš@_ǫm<'aV|s_X_)Ů;ygO:g n ^eb ;+Źr_$Ԫ5>mS׎w z}z('#J|$߇RiA߱}8I iAHp 0]e6z9}H28J%tB@R+ZR/':A✥I21$Hp+@hL W383j0Xs%+ N7& Qf6&>' WF"myݍDgYѧ=%|p23Xj? s5É`-a%4a)W8c0QDiѣi6 &CyZtςȋiul~y >LŰTJl‚@ ‰zAfgg.jk-7\w5Qs0`+B-Sc=vԼ B# \. Ok }uյ;ogv`!*2ߺSΙ£}x}ao >ݱ}=W=?_v ম}{v:@k# qa1NX0$IB<0;- ڋ -LݤAJgJ0L!"p捞BPRRH)K=I`X; /_ |"LA͎AyK)5n[k1f7.'} 38 \=|?8)a|ƈc0^ۺue rb"97aF>&%$vQݱ4VyV1XcZctIZ2{&--Sk cω-b,&#QġP1(P@UxY dv ap=8xit8Sصs7\{o3o~|ѓÏ<v~}?Ǝ4Mfr a?׏3?>&Oz\}kxgu<})};η0=~$no|o?DC|K_ᒋ/_/s߷g09)E}W'_|ORV2nn9FS괩1ÊvڄXBvSI9&i}-S;v NWME$IR,xz9{JQ*ds8S!SxkIRR), uGn>Y0#P/"ezpbē dvN(/ƛz;(EVﶺz}ٴ|\u2UðlU}#7}!o}[ۋ/kVvdu7xsiΌhPR.@*QBࣳ־tQMjZ8N-2|8bk^ ~H \Ĝ9 i C Xd8|(Bffٷ@_xI l;so^}޵3կwC?ϼo]a %}$qp-7qmosZr?Пwk2WhmX8j K_{Rs-7pWD^L<?o+|7|s[yK~]oGt{M]oO\⎷y8xNzs ǎ=Ȕ Zڝ8&A *Ql2N*luJ "1R*erI2RbAI1xa< GSVwH10`00oIBb 2Kd~րH Nyȴ c^ "Mj0,.6ضm ' L2nrK؀=C!L&f. `XXqs$оjG8[G.DAJqXkQL+W礏TP"-)IYB J&LK }ANj`evE*nX UcN˄`7q;MmT +[+ z7&I8CQ@ ""Xuxdmp{g_Vk:o{TE0:VDss  9|k d C[Rٺ$jTB*~KsaCqfS /-)bZ d1D/.߷ (P( 8U`A*fhm{Cs&h6O;r9 ciyW4z}}J!K.%2 >>u:[62aĄ!I:g- B>T XXu ژT/sBƘ|90F焅Qi7ѰJT)a@ P+=1&xAH8Q#ҔVtR觪a̬/oq$^uk|:!mFdu7R6yc)Y;]JrAw|)| QRwh=q YTtRsEO=a#h06 8FDZ>%TT*xAXxaV`\$cC፮E@U'J}&wzP@EA2(P@ tQ2AM*FC 0ܜ1ĞGW)DwxB EX'%(ԌOz섴F`jj-R,Uf)&3$9K̟TՐcR9*]_oxGX曓(xѮVMRػ!!4Q݅QJ/?okFlĨsir9{i?yBTd1^,MqO:D[﹇l{k6Ud3h?4x1n^XZyHpZ/-t:DIZ RZQ,a8=/bhP$^H6aDzY~ (P@P  (P%\ 2`MRj#UM(5jq(YA+ܖ4h)tJN0h5\ RR 3fdlJ[HT,R#JQ8OPJA*nZ*VĴCq\pJX[/ʖ(T >nTU J~Lyv'^mr@ pKC&15ă=ppr^gr5w"H O%Q*|cByD(#Y K[,hc=w@IK ô-VtlR"@)A.JR^$&N0. وO/m6%t6b[yO*TxyK?#җF9pAzu+ 8[X'7K/EV*~΁4xSOa{X&6I=f"NkRXRZM]$R k(iǭ<^J !_(9\ (d(P@/9Q̱n})OR? Fm 0=cu#H)S2ZCMԱZc2 p2 \a8H:-=nJJEzϰ"v R IldF P©xa_ gD^w@}NLP!UF̈ '>@dPnP{z}̋a3+as$v!VRrGx\N#A8X$NfsOH; 1xnާ>Ea-0{eLڅ(Yۥ{(}>qVU2S8c0QD..bN viqQfL J˚jXAX7RnF092MhHMl*d`p뤿$2DU`Xcc=)P@Q  (P`Xl0vIrXߺ11 M8Z}FcDG,, Ny/8MtG0hk4rQruU-R…8`nEӜp(oJufD0YK{ 91f 6!_Ee,&=} B8T12>!p✇W)#K%>ѣ4RA~ZZ\$ɅL cD1qG/5/uکMd |(V\H/1I C*AiN׳Ucpᓨ2/a{0lM%*q[@^(H (p0ns^c{0)J"[9y)d-j5*e !Z,-hs*U H9d/mN6 Y;pw'R12R' B˱ AT ku0e{k3bXG&U*`T!0<|U+ 1$ۍ18E8RR3=Fb*ZKE!P,A v!$%n"jC+V+!7hcx~VJU)T(kj)#7QIL J% Hˈ*3bd'zV%LPh q_@ ld(`rrz xIc--G9e|\.7<>~ ?m3Qj} w}nz W?T[kIMaŽIWq.y! 1ZKL YpfuaئT`3a)dZöD &3# AR\0F(0$(H]D Zt6uVGދ;@H爒a#OmK %`Y@4ĭvJF]!* eRRd**s`-.I0% I3baH4,=MKg 2Ϗ< `dCfUȉcUQ  0Q4F`>k;I*B2hwZ4'\@ d(pn |=믽;vlU{R^ ggcNy;gGC_\ϓoN{ coSnw-6S"ZWZ;^`1Ÿo^J䉉9Z$i (D(FRI0H ÐRc HǬt/sfkUpE䑸+Gfz/ %byRsJ a)Z.6rr |lFbDgjV2bhR)Y1$2bkB1pvS`D˴ aޱc8F!VkCJ$#VL2}^ xa9L(P@'d(pZp[,`~C{/s |R|/Ağkw2>Z(:p&qկc|EobYw`:QIr> MMF s(B*1j(Υ5Lh옝IU"˼1xB1aCn'jT2oc) Fm38+Hb*|Rf*(t8>4N:aGk r |ߐȲE6\͎ N;' #V絔 kAJz%,.?xh^kls$(%hCeF[xER$A m7HdjkN*H9`@qmzp̷B#A -+c x>dvg+P@NdxIӟ<o|íLoc|3gnnR)'yq?SC|FrܷGGf:|%֎[صk'a0<ă='2׷|_t!zt={;wǿ߸xN#ejrb> OWkk5XQj)h4Z*1lQqByJR8]j 2 CRxIzdp֦Q$CB)E5:ѹ:Lf\^)aH xg e*Q!C8J!~`Cǎ_ˈqrb)%V{tđ!|AVCz۝<01QE)Aǫa9n5.yN}j$)W81"WLMM `~~qah(Bʕ2(U0\" IKuhzFH!1a-#Ir Q 둫RO>Ilp !ؾc 8O{V*k5z>sʠ9DФQdYmҲǺ45B᐀<\$k$s$B`Ud}T0ңKR(SY@NN[];o7~8z8x[۸Aoϻ|~ ߿oTH^JyOsasW]y_{5}5\c\+?)ʽx^"ԪU>vGfgq[ޔty8v|e+Z ɜGZ91\\b\b3,GX mX#x6rq 9QG>¥U$8^-P+K':;zT1Zs*e,8bc$ } 9nL8T␨jBH$9bxX#^U nF$JaSP@ezq~F0t ZK&3c^%FFGJ5:|ÀM7 v F2I nMG@ 16~{ 眃 |Nk-,<Qk=AR*fi1Yis\ue\swX82ZO`R`ZRA8G8G A٤h`;+26mlreMZXGe8!RNu,> r `_\D$r7R"Yl6,CaGfㄏ>R y)+/27K9NRZ#ZA9Ǟ= hJu5à5H*!hu# Cm5gr2ۋDc4¡T*>~"LTk$::BEIn%?1&+< 30ёlnNkt1`(B>K ;Bu^xn/^r6Ryz~A_aA ca9BRHVbZ^ is^q!>CZ׫Ƹ1:.u*.(b߾81s(5以Y?􃌍g{i_`Raj:C̠`͘VGx&L80o{& FY+#O=AJ[1\/-!_ʖeU a)DS#lȢ7Efz!2kXj Dz<`P,$2kd ISXpNaFJzzDRDҽ60vE/{4v;wզ/)0}.OjZ&<[#2pqAAk<]cѫ;+a#G˞nKv:E4eAAAAI/2izD/9dži./ö/[ވi>wmN؎8N}zǭ|P_:u;δ9$e~aضm:!bVe۶-4'7iell[6gwh͓ǣC?\}|aJ|8oۺ۷rg_}~~rÛ&ZR0dW7P|)![s|[:&9t#=$IE)MC)VdԆGf#yٱX ADQ֦!e&-5 @)D HÚ~:&I!9:c9AaB[qk$?0Z+S-|)@ZGDIJYK[0cQpIB'8tuR 2sLcy"=cB+p͚6HjAGZ\}:a_ewfHYPPPPPp"9aƏGî={03MjϚ IDATtx˜޹adXppх!sw޽DF 3^WL;c;;Zw]\~=#\-zǭ&?k'WnwI׬߲e3uiRV熙#:v$'ܿ:v4;]_\MarC`J8ׯlW&kbn߶_) ف\lfYOE# 8U*R C*y-8MS\`]nog]>NaB5( /2KA)"D8I))2a<̾r IX0>RaVZ $!nL%D ; +%%4fo>Hk_e[GR*(KXciZCB9)\40y@VEo=֡sAnuzIXZHLbt,ژaeoID,((((88%"={Av d Z 8)XA<sY{v c)b]w̻3|4&ɗuEs>[6=<ӷ1. 7GZowٌ_~%βuڮf*w`a`rxd}IʨɼsƮ; *-3)zm do̫1r޶:!I|7(MX3*\FaVr 5:]FM e-e%ZšS 7GLyss6",T|E 0Ѝb:N'"MLi4Iʡ~[ofGs /0{ܝe|!_~Cɉ}΁RҐmʋIݘsݨh(((((8ea^f {*w~rw͛6r5WYuf[?ڵ~䘎o}3{ظae&'ګ?_#?1OE}y.b~g>gS<ؓ|GW~&X>oGO>|}qO>#>nq,i{n >w}K\|/,Qsr֎m ?S?ǾK_>$?jN?~掗ګ9>Mn*f+ZZ\% I%!֦oJ.(ϣOzs+Ŋ,"rsƼJB )@y^y^fEYP"ud ,"NO TV222$ e&PV!K$I%FFGHzU EV$6oa|4d^bV^)Q<<)p֢(v"L\L/x٫ۺQ.`h>x{eo[Te&&ƙ" h6C)5CJg=)lQ*03,Q{5s,/Ñ@d^Rpɑ @R,IA'8 {څPPPPPpzpJD/@$v/ؾ}+l N-6m[޸f͛زyS~cz~[LO|$IynsB&GNⲬ0igLvhmCwrUWpWcǟ&P G;nznxu'W|G~;o~M@V}hF/==@\▛;v 9Uf+^r}+2lݶQ F{_Rz%)a=&M}!($/0$UA$qu2 )"T(W*yڭͥ%A}!%ZS * QaHw~fYdO8c, ~Je86`aqR"}E26R^+3R }O&kt]!Þ5DO<ˮ]{9x7AHNj{N8 zF\faqieR Kk/GkMD@#3|4R,,,s;K!`bb0hZ4FJq M-A>amNj nrd!Ӂ::Z%ʠ` WJQVS.yki./@$nb12~/AւF#vib:a!(Nx{i)yXGzNR('QF>ZZHjrZ]^KD;"D?O]?psJ@86a R|I Y`kKV 0ΑjMэpRK!0ϫRJ<Z~,8ʞ1,kì0afO NuKxi6?(:ZfCgic5e<8瘟_8m86[zEƪejGYJB $pyDO~-Y]C_fU P:WR ^RJHrjFVT.L1b0pB |!$TPJ!$ZX t0z%!ISam, c |X3㌎0VR|$4MiG vvKҍ1C.xC?+ N)Q wȄ#`H',asK F2(&,r<8H%6BsHZ*˾4erV\(((((8m)DӒ8XL!ģ$jPR82!gₐ@T$RNOsr7yJ)0}qL!v*E %9RVZP8 ^DubcZ-6v;5(%yp#jmlA)4մB!ƙ fO,Wߴt!݁pV ĩ /H&9'JYc2Q"7H北'.t4KqX pdqz )D6 dJYGRp@2y,#^{ȫ lv0²~+}Aq8&iq.k1v:n)jAs8z}׺@z^f ,NHV[ :4ʩr:mMd ׏ȫ3\4.ȪDO``mDZ^Nx2ڼy1Y҄Ԛḥ:G miU+4JiBdZu'MDS D ʲ&RV)}A$p֠M&#H6@k;quu84[8*!"(BHIVTJB D)RǹzB_f-6IH]z {!#V Y ~)=&kc qHb j˅ͮYu7Cq"D)O)hCQMX(2.gFzRЍZ07I: d()((((8)DӎlfH@ha_0(%QHt$w!g?U0U^u.K14 t66FZT`@GIMyP%D/ Y;$f&TCXV<(0Wg+{b618\ zbЯ`pvep8gw]É cоCs,^ QLH!=UM6Sـ)\)g#H)UJ!)DӚBd(((((8 \H`7Ej3 b&{ !w'uk s^˅^[cv$AJT/pRGfNId~Wr)K8FZ&QE5,qWip^}"eG=Jud?b ~;E}pB8kOXax1;Y:o-֤XsXU3XcyVm-$2ԷMSٺ>S8&i,=-@,TP ysfe'JEƎ"|!WLX7"ODo?E&MZ={rE;>(<#r!jwZMFV & akU´#DǑdYg-!j447\Y8N "hq:"{Jlc$s'ܺ?Gr^ =.c蕗DwmXk32&uÔ Xk1`}Ɋ ֒`H* 6l$2Cu 8 VER8gBϋCʬbңത N;V _+B#*;MhZTO⳩~: FT"T|y82s("o``sX̘1MNnEӴ^c F' Θ~%TfXInZC=d..{.G{bB-(s33y*gB1p*pUȽj0lv/2XDYEVb8J[)OPa !F'h-0Vu)5F<~UH|_D+LAAAAAA!2] t8Q1UK"K -e>\;/*JT,ABkjn@k; VEmhIuJb&tImL '6MB!R)̞X Je 3[?K$6bt`{}3ՠ !!2$uom1J iq&#1&FtHlȄܛAT.QPPPPpS FGGȞ3|Gw7TeJ>krf<KKn\usZc s <ؓ< W]89#g}/.ёRo2;7B57O}yycY;se\|yh϶[TT+>#5Հwݷ'*6G4r D?_%&?N #ccr U*T \.SV$y/hw:8HhOF.c9 bh 'M07BRWFv k-aYj!rH3D%3f< 5:a Bi5 !{iHSCFhaL11~됔덲 N8716n}^d7h4NIr:M##uXZ^fzzW_w |[w>7M^O333M,-7PRr7C?? S{~+ڰRzpÚGgؖ/7€뮹z3߸_7-_>.aǎmz,OpgM͢TR)A}1{&`|IZ4D9!^Z $Y<}d R<G'>s /8|zjK/s}`?1|ko<1;8SiZdHF;yte\hphn~!)Je圬@~_ZNk-I&1R^HL E,Du$z‚ɿN$P ~2֧6yR)h؄F]/.#˫x+27oLIqD6:"M;hBQDvHu ۤi;hܵSʼ4T2?yM-Sx sP*Ƿ}g>:)c;c;Z/?$eڸa{l޼0_CaĎZx\tY۫s>kh=$߸| ?)x`ݵ kk5áoD䄈 W\JyOk €vl畗?2V(|I^që㶥;ON؎8xw-z˛ګ_`l0+/{5Z;Gv=c;c;<F; >^5W]==Gz?cys?͛7ݻ%s9ZC+k ÀW_-;o=yGa6[YElڸ+m9Bm xGX\\dt1B$MuDWE)Yb2  X#.J[) *#u\>m5 N3I $d-R22R:r?zћk RHF)H!HӔfEs.ňDF9AX*gm!iYJ.AqQ 5{ .w#:Ah6u &۴ĕa^Ǧ)s(=cJ%* ΰUW)BZq`EPLLZlXn4)]coAo=Qʛ&JX88hz" *RzyC5ĺL5&3MeKٴqZ]wwhڎ8v/5__{[PPPPPpp:N҄?^y̭i$d"`%\]dOm>w⩝m);UHt]n bfz k-2B|o/|Os/E1anU\ozccO 83o{?ZC\d04ux M2K9p%5Tɹ &1KA[\;n%qUsYD"uP;]ZJC>YM5ZRxB5لSv|R)m=\.Df::ʆi'>_XCkcl+Ci!j&VhcSC܍hDI1数 bb*˥Z݈VCUJ/ a,g׻`!$ *:Н.IEӥT.`uB\ch+VWjV.avqZbkѕ2LSzyL]xӣeDUeB6Ƌ8"ӱ TVs^&X͒<2{1]pn k5ZKQ$ɧR;H%|K_ /_\ -}k#-XXXZ6;;?4j ya n p֎mرٹyfft:5c9Ι<Ӎ%~xuk^vHL;%F|;ajrfT_7bW_yW]y_tadcq◿Ap;x{oz=<- k>;ox[0;7osS,-/w|s,gx^y)Ͽ AVc$B*27091Ah6%6@-H)0K % ƚZZMKMUXQPR>= e=-x*~}D!@ATP`&)!m,gbI:p8skZGaX9gS9g+mo؆i4NwNgmT 9vgP֯>f7&Đ٣)[gAAAAAiD_dh~G?'VDBZF (T+uJ~@!IvB)%Ye津Bƽ #Ll=6-=\\lH|2~ࣖaliRCd 6K &jeTg-$q'BNLBI"R(Z*T艽/f6{XcZiY\^8IAAAAA/2>?#(:CE133,/7vwc6ffxۜq9TrooK6qx#e>isyz7}A?􃌍g{C+~ɧ]?;;<6_WΗuFGFFՏ0T|(5BOrWk-=4JYII*+Dz)]6IK (TH)R"|c:RQnh.3ykj_d R kλ,Q +lЫ(WʙQh<!%aFqe>Ơ[G603ZqAOBk pixϯB%O/VT[Kj-F$a6"lYlSڲjj>=p4FkC$(i-¹/FeU &Y9gOUU2w4=?{ct?]{ضrB03=۱㜳]︕o>01W>Knpv>[6o'yqisy-l߾>{'_0|)z  iwCQ?=ʥRV"|臿/Rɢűܟ!ؽgnjgMDxh#sLdp.Z|PJ !tI $ABTIwzxk",rQ@JIrf\8UO,n@f^JJsg& zH)=?N*e\^a!}n'9$0nB@l)B~ ) (eˣ#",Gd5mgi$1~c5IKTFUAoX-0k׿lZ/.yq?cy һTH%1@(I7)Ac` ن(*E qϨEJUZ"$BxTF ip>DHbj1t+{K֪{C,.t(4+Ī"]آڳ 3ӤNJK:Ag]xB>wk&|R9aMG\/Drϱ|+7~w>{FGGF+ec:!K+skZ)sgZ~lba} `jjr$<"s4MF03}DNwY-VsʵuaƘc\0!0^ˠͣc|?Jdĩ<`^?aa| /;1 #\z~ OR2Y<6GCAV<,)qfT4t:|>tÆ)QL3o{0)5)zj0@(Iil!!CIԡbv g 0/t9 5s,ʬ *TPDㄐ ٯ8`]W3Xk /b V|oO!x&n떛c2X Ey+ ">2hPF^xyk [lf|eaNdEQw9P&|, 2FZQ7^ V=_xF]kR !rKJQr[! ?u{Oebbr=s?uX޿۶v?+q֙#;KwXkrAaA{38 9R&QJx[ ]"Җ=8-;B!B\X槐D$ΚC4uOXT$IHɖˆx#54eբDf Oy56FEt:]V,i, 1qFoF52$5!RzBvmg] qY7lq6Mldan@VVR+w%.35>> qvg@Aۥh`L3zS&h4N45ڥ~$dO Ð,uA dXk?E˄=]"OC)BP88[z>x=7޸wWH *TnqbH]޸( _{ٽgϋFUX_غe3^rѪ۷on(ܹ{q}}7M3I.b(?e~a~g?>s<Ӽs?þ=(ZqI0y2txv>7M369D >cgNs.$nPAf g.#8lBf-Z=Qe YfxO<<"DB2R)i:ȿp3ZrDN,)?c\|}:h49s0o}*TPBB2B6lbgO> 's2 '[l߲j[ضuK߼I{Ggq7{$IrUs5Wc9.E9?7syeǘr;s?y'W^qkuZ{ISaϞ} ^p߽^YO{Ǽ떷so.iʝw߭ ׼p5}7q!DCUW^ΕW\V.>ɷ}תm!;o뮹gww~t^{bwuz}3o8ߟa~Y\ljh( b1SXAPHΥjy}o]Zuv9 0&&H' &FheX琁FH5ƛEeX|+i$ 5eD OTYr,#&c޼lm9c7tZcscJJ`pL їHBdKZK$_@C5nL#г1M3x}zPC:90; E$I¾}8ņI=gg1LNM@6EqZ;&;y#k$dYڤ Y)°) ԙ4@( jhmJ1 EWDD@'V=$)i3Z(=38tRֆ<>>ٯw+TPB7ɝ'z*FFZ4ufW[jjXbbb,XZZ"^8 Hr;Rɉq($IRIrBh6fs/0>6F Ia! Cmֈ2\ )%7'ɲ#EdEѹNh8 A QV0z͓ Z~^)c'$rkD:M7ƨg\L0&-!Aף37GAL1T@k0yщR9 yaaXu[KXe, ,K1px:\N@h6@X+ T8h@$(1i u5n C}#P~?Xubw;@(QDjOy846Pm4BV"$%McOߧҏ$H9R-a{*0Wi(LLdȲ cdY~^o2ߕLeQ|.Oѥqaq-=dPc-Ղ,P⇊zTLF ‰znE(@jS$82-"1 ߎE$,ᩱHYGXER!R1c3u !ٴcR %ênUXc7EUPB PB )++M<(<Ų V[_V?WIH]vY03\4y!gKa ڕ`\8k3'&NK5ZF̈́N ([WQ@D&ͱ1jc098qŠ/:U V R.lN8*h(b+1"TbA A6$rR#'"5T =RCCT-@*RbE#a^e@0pKLh=)JD! M8I4 Re c|B:oTRޫPX!BhgcAi) *rE TB *T!AE2TPBuA X(ًL߅(ms)~hV"QA)>l ?S.wNBEDG I4iN#iƁRkt-"7(11pYojأVT7BQܖemhxw4m\P!9Pz,BEVB pX)fH(',$J;uZk YH%,jmyI0oP*@ '$2 r_?QHҮ$&ָeD *T>Q *TPa]b@yy0UH RcNd\b#)}J͉ Q9]eIQLV֤Ed HNh 5J)O.@VM۶d~&{r 7kP)+\1Px1,3|V eDž`0Z8%1$,Dpy' ! AE k5-mI[Dohm  g(R ҞH|<ѐ!eR+ 0ƖnXߋ;\/W0=RB *T$C *TX(k ^`I !hPC 5)0d?6'm)s fFjhS"AdDAf t!rgFC^ci0=s[w/o(%f勁KӔ*SWɅ*=P t!RqdYc)IZ9U`mR!*<=ZѬ^>&XYΙeM ^)RK  ȫ v`nzh5ߏW#=o:Rkm ah}E.e E7 LwAJ}^0j}n~"P兩C+A^' 8LFY-_/sx"ĕ`80XQBnȤAiczMv8*@4NN?3 $8p* '\Nl5ʿ'IBaeDіRBqR61h)ZN9I0HP()#ҥE],Hcx"¢h0:ĘD c|%)9q i^Y2HBVøy(H-[is0?뉊iRB *7T$C͛şY6oĝw}燞dn#z?yvm ׿E ##-~;N9  Qe\n[߾k>n̯_dttyظqW\v 7Vn_8_7-[ۿzL$)uwadſ_azz#i2 /8뮹{ﻟ?ӏ`̠׽W'I b2^-:秿rYW3 ϖ^Z)6l"Nn7(@_LOO" ˼^hc^#Yj?c0(, [F?c=:2if"o8|4EC2lܰR?6Rijgp\_dG+ ֢աftltUnwX\\|ga! ݇A!dɉk*&~{ xty o"uc*3\Fx574x) osǝ,[~V7OKd'(=ﺙ]uro.?ȇ_,[JFZ0t4:W2gDbyJ h5|4u>A0MNNT3zz/05I٢raPQECA<^j_[: jLMN099gasqd]\dq] `tlFdaԭd1ťEK˶;uǎe,kj<CEQx JkA,'@Y"45z`h7Sd,{'(L4MJT!6 cXk1źa k 󧵏Toǐ1EkoC&Ø pJv _B *`T$C뮽-yao|/?·>{'xT~pRCo^r Ss3]<ػ9>獯'_q&߻ex /8s9{]BVZK/ɱ.5{nk1+/畯<{ɰ@+@!^a ~{MhV"$@^_tcchtc ibXq#jiB'_qD$jt{/=bX.WbVkksEW,qk ᜠVoev~ Ɠ3hFEMl;p[^WnVZPJ|+JB> czibH/ BB!H֑.7&R9!5߾|4kFl|۶ϻ\m[pg?۲y36L-|u>wO}s˖Ga@^gsqm_!q=.B5I(ҋ8pp%'z;@=o𫤢=0r=Ѡ΍|a #H_lZYdƑ, I*M c=+1hAKVBx"fKl*A} ā?@^2Ya={Ikl{>mk-/cB(Z7кV5 D:A+ /#E%vyR` EaLL%MDQB';8+gpNgi5kAȜX䅿EJ= BViL h6FhCȲ$U$dYA6 -$a'T *ɲad( -ҋelݺi><=PB N<[Ķ[-o?orp٥ۿ gwitϏYч;w~Iv"Ƕޠԏ~w+ Xj.5?k-ڟqi'?_zu>'^*;WKm/?׃-7iY}FF-ffs5CF%YFdHt;-;٤"DYƑ%T$^eU|%߿!yl7KK)ֹ85䓶ȣVPB /-ɐ ̼r0'{}gdƍo_ʱ籭7x'{ʂ3"jn|۵~ω[~_U|7J;8"-W1z~ߗ߿{_G--Zqq^Fk[l斛o\\)|۾|[^x2xE(oH/w9ؼ}߳x/ z02nOyZ۷m),1;;G#`B V ?Vol(01999ٹ˓$"Z6 ZFd9Z +C V1DaHu Fc)PHI;uZ͈4K$Izj-oCYhRza%Ae6a`нֺ\fv Oڤ)?B#MC0&_ &dY~?F)dׯ0"QB WdYZ)I2[FkzCDp3?n7yoۻ83*B *[Zҋ9cY?ܲe33;@)>n(bllEo3;e)'q)'3s vlK59yl s|mu5f+q|N$j.8G{|Y:D8[?ǻy3k8@v6LM2@Fk[?e$oZF6r_.ZqpхqgE>O|TOe`!7q3?yu§ȼh-|_$p|)}cSHBZi8u4핳0hqHӄE:^P)W5 (履TĿ\~ll5inρP291VwZ hMAQ:D h0ԥDI Z)!D@* % B8--e]=Rj$.c/8AYMɌOp֫WHʁ1s v$i׫dYh2cJJAd)rsGcF蠞{p8gaIeYgLq/ #^R4PB NJgUW^~X}>X6;+o4%nĭ"crvνlȮgۼTc;Cۉ$80 e#Qo~r;׼\ ;n-K_!z[ /'C&z>/[v֙k%R|Η0s=cM/G|2ܽg/?$;WzQ _E9 IDATp2%@JBDW& 1zǢU"XÀ\. Sؿc3|1쟟zYft [$EdJKKH)Ȍd/j0l޴ mo/uHb_ȇ*"@-0#D'tPŵF>o'"'JEh|qE2#MbFOb,bmV^gk iD.q,Kɲ4K_COzH09Eh|D1 Bd4] ˺dYLŤik!I?ڨ1hϯHHx~~F Ðgv>SO䔓O`zz=hK59?ıH\훹cZxωy~ZW-o16:J,.DA wxx'ٻ93ɠZs?,MB~g~1ŝw=Fgl: <@ >ge^:045RwgֵDQ\i Yloi32ҤjGESRx~?fܷB Ǧh9R38#x_kl˿Y)(jz%CS%/CuL-"B02 "eA}e5L-&MkiDc1# 9pΒ4R4`0&#R8ۮPc ;3K16Y$PvȲi 3}3cB9zH+x΢e·KofQ8I,eeH"o3l٪PB ^j$CÁsݻiz#zf糜z[B0q#_⠯KN;~߹>2Iw'npG(!x9^0`׺@VFٶu O>4qyN;x \hxYw_8i6N>y;~|0M1ؾm+?!uC)bH?O{L! z }јinɇb>I20,IExUb1@hIQ!s,.׼n/|VW%B?oQ4u ڢTҾ:aX' °Q#@k0&C:4#IR8Ey4f h$pKicښsdYFdI !EѐuGVfYBz#IgZI.ÜHHqa6;e$I?kSj$I?o&O@X38r *TPa}? v>MIӌgvyghyDQdX僳_qBWrؼH8xωm[i|>o ^FQݶjR۶O^{r?<6|cc֚t Ð VRlc5WKӔV>7MoBF Q }&ot:E @)M ȲY#%MXBEcR$&Iυix,s1WHz AP'M;H=TƘ$DPO3)$,JjsI&Q4c OE"*TPJ}K]fzz#۷oeNgr WUټ0ּ떛c2X 3~w>' gy:O>!e7^ o:(~Ш׹kı(v/5ufl̓O>s x+.dպ( אexxٺ={qmW{?O氎 ۷mͽ œXy~՚O;u۶ne=gw3c12W0?W$Ryܥ%2(B 4 H)4P+IˋBR'"j J~Enݡ^Q6 R( VN:9r0 SJ: 2C)&0cU QT'_'$\PCZGA ~( ٶm | Gyxp-SS~Ds ž1>PiMzYB##^R{8#&X$njTݡ(C+ 굈 9h]Ø4/FѨufH!%fZ-baaqͶHVC/Y6\NG1X)Cj:a!&҈S߳|_"DgC8 n5FFZ(nd~~VI7FNEz)Nھݓoq.dfb|VA1@Đ/2 0X&2#Iڹs)} HHT܌_ 0A cbnM\ex{SB *aBa;w>[.g8×VXزe7\U˷m¶[GM2޳??w[n'I뮹cq)> /8Td1qhq`aqť<Ηk(o)}nO~f?{ɕW\_rgسgߑ}oz+W]y9W^qYg_%]&j+~?@{xwuz}3o8\r/$C ~\r\~1<#|o?R{6x3<'1$J+V)1 4?U AjdHqڝEj!" ISI#DZ-(E1(VUch21 iØ|➽05hYLZkcn~9TlR Xd82 woyECn/1>>+|@ln904C ?1tR:6i`hTx+(a$cݽgwn].aXpjx '8biig}닏A,i:$"$ -i RyjRR#,UYH>:8Wn2 % BGn,ZB *y9⿬PaadE^gvn~eQjf~Z+&&&Ȳcc4 $܋&{('B$ennB&''B?Ӻtf~RJ7n N採 0w?>)'G DaHeΕ׺Vl۶ Sc616`dH+bd$Bah1z?[$ ,ڴ:2/{7&&IXZR5nM)=guYITY1)iHĬ1gP EC|?t !FFQMQE-jj(a8B4Gv0oK)? UE^{ߋ$e}KwHҎ.ҏYXK&徂 Z_("|J*tH%\I\c`ȨT0B&Zy/0V'Qjp0lY,/-102?nCi۳t;nϱh8x c] *TPa}O/?1J KK.~Grfar;#"m1̬ax#ZsfWc4HKD9M}@輸uyDrWo#eVa#dZe[`p: <$] A6%xc#Iܼ^`XǗ`0 Dh ʤ! (%1\q/WEA20]4$!Ib,&3= |4ONʲ^^P\ oV8Sxy[qupdY;'zd*"0uyB{J09k>ӴRAYC ŸHs\B *gT$C *T~/&I,YJǢEeR#ZE<,#*N[-^ ˼7R R8Q*BoTtLK %Cb~YpqdZȲ.A(Hzek`5pǒex@71&71YbLDf|F P|(ZIFˍ Mٮe I0y'j4$ e><~:hu)s,B $A0?{o/Yu_~nݾ= ! (,aͲl)r^/#'/q>N|9I;-ۉmQ4F%4$KbF s}|STՙjS^H$I1]iG%$Ҫ-986 )C2G ʤ7Oj?5jԨq&jԨQ1n1g]4=їAIW˘K@@TH{BdD!d fLAR Ąz_"QpE$IFQ(Z.r R_{F?+h4dH$~\Z| T -E?&7袏=M^o.V߼wE?~7Y })-Q|%Q+ˆEFL4BR-*ρd E յ4GttQF5V$C5j8fsu>FXyQв`]LT+@05jAʐR)N)A'E4EdTQJ6bDhPlP:B{,A1`0E}5)}*[%>ǘi j("}HHTZņ$CҰ16,C*G%ct?R.w&zvs|>8W!y$I7s&GRA1R>Dd7톾Ȁ IM"(>J傯ȅ@4kԨQF#dQFh|lL`p>RzGz;b1(A*K Q/\\lk,;HP*!6 |B i:U@iZPEUcP >Ihi:IO(ybm})^g11{eaDKY` }"Cfar CR&*f%+Ol5jԨqt&jԨQ1~OkcU넨-C*PLW˂1bAĢNE#$ jbh"eBHA!Ʋ,W0J0VE"TAr]{,U\34Q_ABT uea! &g5Zi1ÑZ/ fЕ8z`"a\y_+Ik (lqBA&D0d4DsgTE&&YPeeXF5j8ZQ 5jԨQ|^?Ghil-du  BU *~*!3و J"M[E"aOCY;gl+ii6QsgX +٣^_y +Z(PΛH$I4IBe$I %ռф{`Lbz~_ G.T7A-$2PPޡO|=D<^D=f`Ko [/A=+0[&4+ m7o'PԨQFG3jF5jSpαЧ(4xvhckJ@]J;_ Pz,xFB )$I2ҴE4)S%ʈeq[PV+ M$iZ+vwEH_('^=8*}]l9=X[lNY:5=+[%`e{Ʌ.ucz<4y>}1T/5BxC|d9`Zv=x(0L044&I$6} d1kԨQF5PF59u4X1\(DUcBCǦZt} kI҈&} ҴA42ǀAI,)JcVIgS&9?D1=P{XJ5qP3'Ih6ɓM BD#HcECYkG/Y}Q raY7ÿA-gjj֬Yͅ˛r#_k#$y9{+XQJq3K7'>>kګ{4F ;x=¶;9~|ȶo|E2lr<\u^CN2w!֮]֚Y |ey=|}o^xGH=3^hm : ~D+!ai(%cA,R(g,+} 'id5W$.L$24@0ŠIk3^u}A)ukɲ c RBcЕA<&kLHx\5^!% r0%M5jԨqQ 5 ^{ydYI'gŽjxۮglogYn*R\9gos7~[j.{#E3N\w>)8SG^>V~SX{wo>uϾj֮]z1&TEFM yE>W^xOn[zwZhT Ys ew4 J L (4J%80PF JO4sA%(eK A^F#ӑ/̡䠇@~Z-&'ݞ,MYz%֮( 8W/F*5 }a`KٲP;$ ߇})_OJ׾Ruˆ?_4V7T"  @$'C+dɋ"ħ>yVZɹgo9vΑY[_ΧK_ZE0y'?9v{~9On|0&5E) C[ p( K^h͌vA&z]ffqߐ'@xh'`!,^">B~iVsѺOQtֆb4oxMtp0gOP'(R*Q(nٹ9 )%Zvރ':BV*^+2Mi,$m4W°gP$ nu=L,jcxl+-cJeJxЯM,z&#6s>h]Ph^@^t)Z^l )bj{1f ҋaQg5jԨq(7K_'D.VZΧ7|]vl6wo[o{ny{l۶o c{$Wm֯[\r17ne3w;t" /^_|z茏pr҉'\[nsP|_੝O/wffvmh6lNl߻e(;Ni|[wBwgō_۽u:cs]wvws`vn"ωa? >ן`=ys t}es A@X {&PʓH@5Mh$R:ڭB\4qTsuVcfjzf3A5]`vv.ѪYc`hJ|gBbq8V4 jm ]  }$A *!Ι!Ň9|,둦=Rݤ(R$IF$QM gɅQZx{9Y 5jԨQ!!y:SS3<|CY#-9U73?@WR9I]x>7Lxv$p4<Ϲ5kVgdmbb30teqzzzi2#sd #@pg#.I24 ?lv<$C??`acR89nyN2`IVZβ1 ODl_AI0?QT&XЅCk*BֶQ E 䂔))@S&؞P&A19-p.&)~HU=,sSBd($pRHQ>zPHY~0IP(3hPVC+ʕ+:G%OamX>YN,w?˰xg^Oqz<ଡh2ݵkLӄ בiUx$EIAuL)#0X<$Թ}fQLNHlH2֔lӱ!QU+jԨQƱCF2/?_暫ڸ dK.޿Uqکf*vú5kc؎mX6?-?*!ȯ9ӫp|+V,?ߪq|3_dɶ kV㜫o|:6M_u*k\1/yY"/DqNb뙯מzI]x ˖?ˑ?χ{Yn5c&FB__PK$ݢyW"Qk/LCDJ9bT2z@nӾ &5cLH$09i`ލ1}p(#(&DH&&&zh]N,KpNh,C11^2$ !B&a\ H$~>M:c dseZy"{& `(6v>~_]97nEL1pgzj'X|L"c 9X{FaIP ( \lIa@H|tYLP1$k߄hMQa7T),NQF*Oگ՛ٶkמb'mh01Y۴ǟ [g˖ڽkv+ہ|KylG&?K+=[ׇ /,>_@P2^7^{7d[ ejj,8--o~G}blHV8p$✳rYgpکh4C?Sz838z0ưgr1ʹ*nC!@UhWE'{*B0 L9T3QIJ's* +qK?%$Ю|QeEG@0y? bPK;[NԨQFG3*]_'}{7wAOuk0;; +7|b[6?ukA^ہ|DZiZOB6<Í|~WG+$JqFM)oN>/V<;\SUŪe]|+fXPPau?%֮]Cp}p=ktx; 9ܹY֬^A$"PC86(ş;.e|#cbl )4SOd4#IdYF֢Ĕe0\$:cTV!D|HЌuh=ˮ]>JC(In!y{Xb=F,$I; -IlH׀BқABXυ4mƵTzoƘiJ9A#P { 0qNfZ&3;7saV@I۸' !HpHITFfCb"Cc!xOE4TH" h]= eM |qQFG?*, >{vQB³v]Y,'cOe]quہ|DZI\uyuWWCp6C~z_zIE2XaezfNJB&pj|J9< h@X<+d$ $iFJ)$1Bƹ>Ib"EkIL(=BhHk ('C1}g-E1AqA$IVcjj]@* OT4iTFáR1T}d,KbCAfx|HrXe il#ù<@Ԡ22RPT_hαvV^s 37?#f{S(UC} $LR*j)) >wx\/ s1\2uNc#"cĺ2 UDZB7֨QF8dƏ/Olκkt]}qڭ/?e4O"/B)|aK[ ]C|<ӵyG|7޴v[n!O=uzy,.8 B Gs!mJཀྵ٨;X@\K@ &ѴOD黏c,`f377bHTƍzLOTmAB"d9g2s)*1IS&$a%DH2JP R3#M[o"'I2ػ=f1P:2jg,lXGyڶk*luExý=Pm3r^{?^k~X};mWg?.۴J8"eᯈرhmsMQt6A~n $ RQalf#,|=acgzcz c0fy_8ù.wBǖk^j6N,K*$Jeu2ZR)IIDM١jjhth4h44ci,k!2IdY,mh$ PPI"*mlYKje(F&lܰe\1u"<~,h{!  ˖j#"?3$4$*C$z[$ƍB bWڋDULGăR2jkAWF5j8"J'5idw*}S}֙l߱cȺG/6nXy瞽țzOr]gu\w핼oaL̰zJ.8sw2=3C_ϧ<~i|W??y$QY{{ݼr9៰}S\ӯ#I~O4M6/{Ǘ6O_w~_[neI'xgm} {L\|o>O1lFs'M_;v9px!~xZ~ k׬u]U+0SO7q˭q–? ~o~;}?ϯگp?3֦ey s8_z`=IDB[RHG1qV"d!4JZmVp!!}ڨDa=G5GPAPJ2\HYF'JN?XK W-^nN9zj18/I l`tw`OjɠjHhpYEj $%&(A&ֆ|"nffgQJqq%8s&v{{}A3wȝ`rr1t<ݮXM֢3AIQ2m*LPK aG%Y4 $UPwl(;BdhPȸ] cQF ΧBz*xb[g/qt`Æu\s{mߴq6n{p1}S?sM;g?OQh.|eXkIQ'o⬭gT c,{26??]o9.|.yk}9zWÏ_y?UWڮ|owx[/Z糟"_i? t\|\tg_||[:{>7\;OG,<_0nv&W]z~W箻=y s>^ O?X :x$R:$7o j^vZ#djzqGݢR27PPD"KRSEKV^BFr<!ln5RH) ZBvh )i$و@* IDAT@:%2qùړ96ѷ dž,EɧdYF(} BHclڸ' _dX\ZU\ܢO\l۾5L DV9 4|R )ʹSi%LHpBH|;JAE cha̡3QF5^li??FڭS{[6 ^+$QXc sssi//R\Q܏X m Sϛ._5SGrJʕ+hdYpҟs](āAec^2̦Mذ~%c4ZXB%h4p)d#d\.[4Eq)=R "WEu VY%I<"JmFIL%ĉ31|@H$"E*'M&ci$ic!3i$m4TU W(`m=KQ,Pyy|~o~~no7۰vH޳emكa#ؼ8&' FA)΅Q5ϔ#HMT*-Mb8{!ڐM{xgpGR5@ qNaBIQH}Bn31;W0;㙝{i٬QF/mGFPbnnXW XvH/ZeaaVK9O?}${Y(op!9V+;v6ywjԨQF 5PF5^JI4dƈki\{S&d,\CEB3 U0bBB]v(C ǕR {813 Jn8 })0 _)J iJ]; /óTex #PϭD* ~/A. L9~U?eZIh|'Qe!CBI4u4d=EQ 5jԨQAM2ԨQF$f|EI%ܑ$)=i*IT(B|,DCXBVP{sH^&~]beajxT r K#E{P8^2 WBY$E0!d㪱րaL6!5{ rǹ|ja<.1,0nWJ4U@Ah"A)IQ8$8(2ET " ͠T8KHP8eܰà}! \UЖl؛Jd$8 70xBqHFAW@:X5UsEbh]`^$I|*d@ 3333A"}wa0XP th6ۨs ecHAP)*yXWT>(@b) JEUZ+1&J᣺d@UCTch3D:X5pyxԨQFjF5jdQ>YHA M=II( HTT_@ 91TB+<>XBR"<^^JJb FXezE(el++ Hi`m>z1!D@ ~p3h v{e"G]Ƈ݁xDLpd FJDA+Pa} E !R<*Ue[@L#IR4hE.G &\GDŽ]E2B&Qx!ᣊ@.*D1c%sCA 0G^QP*AbE A0ĐT( ,H*4"٠ ):(b`4Ѻ1\cLpBFIf6>m0oH1#٨(֚<5fQq5jԨqL&jԨQKvMlf$@Ʉ\ PErAEsHijU HC&8/@YPglTC >1 ([AT)C>SEFerp!pxg((c&a!#I,. Roms1h[X*%0D1_G,!b%H<:BD7_. Cƌ1Z(<c4IP`G""|ض[T!xnMqzﱶT<@L )C?2 E1s@$x1F6a!R/@`Ϡ"0hdI8G)_mt ,ij6ǪRG>`Ԩ16{REBj19fPM6" AAk=ݮ#5y^,)C5jԨ&j(X\r1۶wusXr?uk{y'pG|ڭ{'VgaU~ Km1zA wOl*Tz peXaGygdP&HJXrE4O1F y^0;7;Q!eP8'q ET˘nY ]eb}PNij%TQdihhYX9ُ E([TTmBGq1~k#MŴRRx%!Rl4@4(ՠ4""0H(Q4dž Qm+e;HMW R:O,eO1?i?^v=K<׼xrv6܁Lw忩^O,[=k~7snKgx5k׮ _ _ȄF&^w }OI{yꩧzl[8kWNWb%hT ǠX64MT/bbvߠRJ L@ Uy&d";Rx*q>8=8hM 2JS&m/`'&_ݻ(3׾Ngwzޮ4>a}|nh-sb_Xv W\3r<Ķ_w[@I[i*6sX ް @koB(c&Ae 4<~#=Q@y^;op\YLjL6KYc YIƚ:\TXT [*O76`pNGGb8f$Xss 1@YF3n$1 i"v`@4U4Q $9P%e rlM 0Ƒe4dHJJS #2{Ew!OQhZ B+I)rA?wnr^5jԨQX!!y:SS3<|CY#-9Ug;]zV[Rv-KdyĻc@$fOC~ 0L0H,ɋZ.z.gqnUwKRZ,SUw=ޫRy  E(f;# \~%Ik۩tn۱ҵi ],p ֚={!dYy2ܸ/˵eᴳfdQ9N8_<>Vut`Ͼ8饣c֔pɪ5x O$+W,k1E\.%Vp r/W(KnyJq׏}{{pJ7jY!`BS ug *FJs$10IbVy a*M$B qE$`X,3)Kō]͟~;n\%.;w1cF{u_;T߾cIm۩tn|_ f; G>EK/`e(iؿ u5hm1$s}8&Zɪ)uV 4lJqu,dmUAT>]Pm!m[ vUw8PWWOkk  IqD.W3oK 5BLj0 R Me4MPT 1hRȰhS9o1ެ2QET$CA)E֓EGfe;dw2DTE+HRHS#6Hi"4U*#IeM9r eX,[)4>%7l6W]Qx9#`3ڪ۞̶ N綝n46ff}Lx^PZi9^ZZ>W+rͮMFlU"Y&{,:k!.+.4M'$^wͻxNH8w0\ahR;`B>ׄ扝4B d^gӕQ*# E|RG\" C`X,3Ȱz,ZpB;=У.3jTõGع\.GSS#`̟7y}6JRudm29v\W#١LZ,δ/Q9L$iz 4) IDATaTEjghjjLdHMm C˜tٝM_eb|w򥬼xϿ2/x2V,_C.͍=?FߌG>cŲ}v?Ѓ,].pHS3˴zZPS%*S.R7 J)O>Gk41f;,6!Shd SD3Yąy~߾1 ,GCA]6H/g!Q$}h= t0Ro"%˓ sD}DQ Ҙc")#%%F\c1IS;HSD)ϼC>Ϯm,RI,cdrxaK# WPJddFg *4B pyL1Yjr 4$IZM”L4Y5 (4C"aX,3p9ɧ)Y Č65|F͋͝/Œ6vq6vimƂGT7Ox^͙֟R"~F-;| x9cJt$ ~yjya Kڽ;V. G? 5sF'ٵ ^g N{Asύ.4Uh),\T$cY.?4rх@b7 R/Rƙ06Ƈ P0fUc8qj,!⹵Y$f%Q@u|̌{*\|'y BeģO͌Gknj6DA'$IJK%4"AdT.Q_Wy?$ NTJT;5"Z$qL02ENFS2ȼFx ${ *KmTci3DMtyHNJ*02H#<X$le*bX4"wB;EP!t$b$ Cmɖۙ7wy7M@z߃ pWf!tv| ^+ƋrDEשs8N׭%jڬDc 㣔ղ2 Zy^xz x~#ߎD!IӁbepp$Ihlh&Omm-TG_PVRTER@])$)#q4"9%d(L8DQD5uC)Sj21uطbfS*ƒy{VTC*Rd'$CL\qbء\&bX,3`N֚]v3rm۾9qӚp6~d8dm2m;y,\0w~3//[[S׸Ipg24551[VŃCgyq޽!MkqS,4ڐ$)=Ϝ9̐1gNgVor|w>/ⓟ1dݧsfw2wlay,8;?mwJT24TdP6+pxմ8M7}~|_" ,u53,Uf΍)Bb峋@{YW ZS sq0xx^ƃ5SI O2FciNՀ1IHf`}(()} |&Z[86MD9e * Et<)Gee5h8_ MϡTA¡Rqd*T*jH"eE(TP88Ua eT.iIR5LC* M+ '8vrIQ(j b9s2ca]ho#MR*زu55,rSdh9s8E8Ïi ^ŝwk455RG,:klY3cmo _2[nsFdxCCmmM Wg#R,Ð[#8l]Վym:w$SQ):2XA}}zjQZgB D p3'+Uh$1 ,rf}g]1{v۷L4RU/#2{24ۀ2F|޹gOqd /[:Y35s&7o.BKp946ZW+v2ޥ,< gvg[-@H֭ K/Yyغ\.䖛GɺZ{^>2sΎ̕j3KG9g-[MP(3@$NH$$4u4$uT& ijj$ri--#APq +aXG֒5ffZ°߯̀ܤ] <~υMn 6ð8dn}}]_#1_* JIӈ4Hh q\ %DH"q<$#dR.Ah(*P.Ef$)GH!"e Ed$)%He(%Լ QF%3%5$^fh`XbR.`(x࡟O?z{ؼe+Kgtmڂ{Moe7xɅ ovmb=\+}77n""raHg,RV<舂{,9O>{r\l)-џ?{ۿƛkhmmae?`.]8INjM6nkz k3i*hoΕu?U£x;ǟ_$b9,ϛ}[|~~'G OoR_WK\>̀s2^u1 w}&ؑ"h[&BO%G2G8ȶISs,`zk3'}8NIR'w0zPJ3"|?z.8O樭mk2 *^ ywVo޺sgKf͚k;lyg,:;fU?Գv~ݷrFzW=?!IRb57^ R)7w˗-F!9jgZ&֚~|K/+/SJk3rF ŗoNuՙ>~Q !=\ի.R7[Do|;n+._^R]~@7xg~y7^ `"6|bib>>"xחѸ"Y 308 ZVko`.D efى1Zsc(oR aXʪ8~@MM8I AfBhL* &ER1E2>j <_ofUk$)w줹zF\BX 8 `/mӑ2e`pa9&;<,EqzFfQ_WO_ BHo^( 0e>,Ô!DhM(LD5FRBP&Ҭ"Iq2$q,"A9QJKDQlL -by|]\ѷXN#ꩭ0s|>G>?lx} !đ gZ&yLki&̅$IJ__BͤiJooQ<ϣ\ AooqO%f\2T(04T8>68I0D1eUN470\!s=P !0 @y0T"uVD/g*guk &\ѤOurAaP6f'r(`)aD144-'EҴ Q.ǃD ~=HY<,aM94M _uGn٤BTE)40o K"K,$s8TJ%puD_`DSR{ T'86EE rRHJ+.ZȪ`Jg0PchLzDG2Q$ Q#MISdX,+2X,r{)A)=jD%Od :uu6Q*BZHBB&0cy)JJ"T({H Di$EPF é(cʺ9j ZADP 8#  RD2x1$IQQ!*oD*mbX,#"bX,G!I}'@Hifr*V_a f &<|t6P<2CW8NT-geEdfA Pn(m"ypII A)#H"&M#DZ IJQ&a2\DیUbUU黓ycjdFf').΢t&4hԤ4BCb4!M5IHr J%RV`X,e$VdX,eH-"DQDJ)]rT&1R\A*R.Rw B8^fȨ@GTYiFct]-pT" DHcҴEҴh}h ԃQ G{ W"ÖkoAJNfYd iv L1Q &EVHe1)d&4(4E$U)1DeI9RrbX,`EbX!BkРĥf+Q J̻Oh|JQL.d*TfH6Hx^V! <7K NVBW `@9F)*C8'5pt݇]0QMy ]Nq1,<,r"6TDUUcG\"D!K(1 L!Ici!IaX,+2X,rbQ C 3QT3H p4R:xd )E5~fz8pjFiR&Z4M"B2IR II,b+ILdJ)J8h2 *HAadBEPjFMTD.E yG(rIRA:Y-b +2XNMMUWsn^zynΤ6w^qk׭g9ڟښV\΂3T(óϿHmdz4MG]?g֛o3qy(GmϱpuWSSS3j-[YIs\Ö޳^|y0 Ybg-O}}RJakԶSuO'>TNLD1ULh'rp}<73Bh4% CIHr9I 5ySFSı=S<Co*Y8IHc4Fɘ4NAd!Fҕ/}F"Qڿ2 楍0ÌMoCJɛԆau]Ns)c300TU)58>;;hjlu= {ts),D38Y5  $HQ (bX,Ŋ )gv>2s {p<tOǬ|?Icc}} 6K/Y>}?\ݾc7-q}FoZ|?O<̔ ^}M_0oޜ1˯rG4Ms]/[ʍ_+k6RJ`jd@Sj*!% |&uWj\W$ALEp 8P>zMBS(pLHJc4-E3HSqDoF7ϴq5MD%4pI0= RD|LdD0T*Yl)\Mr})`8AJqImme{,]BgFRJRaJOF&M$·bX,VdL ^E,\8+~k̜9T77'uu=7^]![ְ7Ȇ/+/}>o={Z>gu?~gI{sSc#_q|gxG-?_}ܱ֛hooG~FLr|wpryWf8{:Ns.I[`f MyDet>B p"8xu5Is46ꣾri<7"֊JPa Ce~"Fk8!#43X,f>P7_R9"4ߴ IDAT6KvV|#@kolg=$ 4g4K/\x>hr<PtU0Ws}{^TQ_߀(+‚ BhX $YnX,eX2%x5PՓO߻o|˧Uӝ\.sk׍ Q/[ϫ CC ݷBMM wǣOUJQ;9XBxx8G?WwW&{O'É|Dϻ OcAYDY e" yEguU+!ພf<ϛN_~vډ_Ph-H!:;fຒbT-ר21B#e)L 4#4z#eqQکSY*1`ޣ(0s%uyV CSLB9YlYЀyb$F cM*4q2bL!S"2|}X,fY ꫘:s}|>ǿyuṿd_uRv*8v|Vv={Ofh7Y=yׯVgXsKWd7xl ﹗={Ǟ]Nh3۸_d֝gdz?wuu䕵tzyyqxNǑd8ρP.+MnS=+9sϝHcmXO84 U&msLDPo_L__ pB0Tu,M08Ѓ8RβT.ۏ!ZR))eu_gX,9cM88NNe.}޳7jP̟7榦,55?͖;GDAsAز\טTM[յ ;;Yx1F\qQ*d֌xMhj u6+o$FXHSgR^ \|M{ykyͶX,Ĕ .g? z{s!(IwGKֶSܶck6m9Yz>5{CJɒeq_׫y#9k1ŋs۬3>u1m8ym:==:s;aN3zXb9])2r9.Y=']d(|/BXw33Ht2 Ma oG1&M4O&fE޳I.5HS.K\qM28XX 8h}8%RF-*C)e6H455 5-G=gtvqppq;Ff0hs9vVfi*Qþ( F5"*j3~3%=4]}mTDZ~Q.G?p %V7|c϶bX,g*S.1?ٷ{m7qd_c.fhwx畗 Ww:m;m;}q _a`rr!ȇh\gq'zu ^h!>{ ۟L|wc.z;wl^'~n¢Dhooc V+(lڼW+rͮM AwMi:f;nsUukHW5_)8(eNB#GV0ptPJ#C:VʦdBH$qʘXCM!CVXz~2^i1]%aEb W]̢E '=Jooߨe=;@5\{d};whjjd``ٝl۾0o\FT\MRT͌(NErı2KpF{*h;w)&%IXbXp9ɧ)Y Č65|F͋͝/Œ6v]dm2y;T:AuYC}w>ٜi,(?{hԲs>?̧=gߔ =7vjdϐr=tumϿYra|?F{{qf&6lx6L8Dp݇T@Lkkoy!jZuTxJ2w յP(`O;︅ፍ;0FD0Z`hqSq,RYBePZU#aZא>pE !2@a-$iX[ԟ  1='5bTEuwsv:!t$b$ Cmɖۙ7wy7MLss /Ws׏SqO'>ZC(V_r%a0\i<ψR̝[n8C!$X[S0liIbXjTE4M'ౢf׮ho\.mNMkqxܓնɜǶp|}ͼZ~p׿1hmƟON]&֟Hg,6oJ'cn=8sٶ}++MkqV"|m>;OkkM$ sfw2wlaX{:Ns$)}}~3fK}HӐ haڴrPAߟTHDp]Lpp]r٬J$ʢ J(%3aAv0\BŠu(r*T<hqA!LE*$iAS* VXuobX,S˔? wbF{i*8}RĖۨEg˅l"G˙y.q~Qr91*iT=JδLV;nWصym)G젾_=̔ݷen>6ME%0dVnpغ4M3&t$IܿRud۱rd8q,>r9g<<*R==:1jkj_;;زeDY5.dar[n!$ֿ>j={3gȄν7;;pݩ ,qqd8`yx5E ;h DXTl-x;pط/eb+1wι^=/e(p E >}'8 ٲ@60("h&Vt8ѴN(^ŵW_Ň?qݶm;Xtւ͙~ìZD6bX,$aǎ]M'<'3+kV,]wOdcLV\qٳ;㶛߷o1뵯[n>^fh`XbR.`(x࡟O?z{ؼe+Kgtmڂ{Moe7xɅ ovmb=\+}77n""raHg,RV<3@ʶLc}F_Mzz{Ys5u20gv^I${76nkz k3i*hoΕu?U£x;ǟ_$b9,ϛ}[|~~'G OoR_WK\>̀s2^u1 w}&ut2 Qsp&]wOW~y%ϱp|/}O=tJDOr456h>B=K'xع{"̚5VDc?q2mX,i)݇8L;7oܹyɇZN f5cߟ|I vo[4_$Ix5H)dPw6x,_ 8qi Zko/N)Ʈ˽?cdpp:gT_y{`e^uqu)%^{{~|߸[qM\qj.w<>ֿ?}۸k3kK1xYsõ~֬}uq`ˎ\&zO'~vEooӦ˿l̗u{4My=?plN!]ٹk7sfw>җV.쒪87ޟ>{NV-b9mq.}[Z, e>#6{GKK B3?<4B$Bhii&BwB8Ӧ C  Gu]ۦ'ɸ! CSVLdρ{̜1r9wb%<'C]]-MնbrLqhkNTfps\fobX,o^oEbX,bX,7/?5ƏbX,bX<`X,bX,eJ"bX,bX,) bX,bX+2X,bX,b`X,bX,eJ"bX,bX,) bX,bXT7rfo\u;w楗מLiZxvzn~sܼ3g֛o#GT.Z!+W,㬅󩯯CJ^6lx76vyuh;IE޽y>3NQ6oʺM墥|˓>x˵3/:14vNj2TPSnWqokgT7bX,$"eʙ9O~̜9-niZyu ctDn?O=3Jdhh?ѧioo#MS\˖ru7r>ysZ300H d ngΏګiԲ_<͛k;l˯rXugǬ1v(r4"D<;SoN.t|# go ]C-VdX,-,SeW g3g8Ͳ |3$IF}֛hoo;踊q?{l v 1=@/#|$?Bлƽ"jɪ+iyZk%]dyst;wޙ;;3n&4B`=ql۱'6&+ײaV;#Xzb1.QQQ)%6s箽+0l~9S?\]\x{Q^^alRʛc$'26/RDG-=@ } 3gL%Ş}w[$-ԲhmmOכ<6Z8HhmmAד8x=8(>ʪ5MW+;E2ib9W_am^765q¥nF;ͲI)o֦xxxQ[yϺ}N^@  dX||[Xf=Æ1eDZQ멪Mw,S?܇XBIIpl@Z>s).)j¤DD˩9zҀ5y?.Q# @^nq&urbc0:)_eQ[IJSoo/尺~dlRx.y<)cyG_@Ւ@Vf_/ohk:2"YIgaa^8T*7hnngggt:B.@~_Ruh2)xɬAPAuMÍ R']8+E>k˛鉌 =d=|Pʟ{35e49?yhcKs<èW*zwww2!??]}ߐ!Qg %2"Q#/6yPΞ/Ql <,QU6B".6JMkNN ';Sgޱl @0XOȈpfN^ 9w2{tr3ټe;%e2aX5|R d...<}\Bc/%5e4Yi: 4?>cYagzJl)3 f֌:f.\D*g5mqxyzkljvL&#<,WR; "eL2PSSkO1-:5/OO޷f>a#nϜ>#nfY8~/ĉFg >6'}/-gt:<=cٲunZ <ȧ_,`!t: IDATnn zQfN{l.CfL!.6[w웕Ɣ%0>?qyp"|2;rB׊KJM~'%%0jc >l|֑Y /͊&6V=6@GƂywY0XK~>zVo..Ό9%sߧĨwV&Me)wt9B`;MaMz**h{3{O2/iQ*L UUլ\WWWP('ܽ#H50wc7!HLis>I^䋯 |7W%@ :)LYxͺ֙\tiavKJQ(T6bcBUu !!g%xlںz3a<KQ 8}Ud25LF@?3Oal^6%eFg2HO!a _eM 6|z-G\N|\,A+(>Oרּ5ym77i)=_h; 5=5CQT8y82SLc<|O!)6ef1ww\!/]rݰzlKtn%R󥤤lzo_F݇U@ U0FF\ڽ%T*wYА`e 3$Lq PMJ?d͌+ 9[Sy!^^ܿ^J>+{na쌏;;VY#AWg'g;Jt @p;al?|_.[aQ k,7VUZJHH0Jer+E%\\D̐hBB8}1d([2kT͹'''9׺9`K@@VSVvN{e2=??_S 6Ywa= d;s%;2:NǑc'@J˱Wzzc x{yQQyXbr0@NNO>JK~e)z%uLn@ 'JJQ QWs4d2!pcdq2l9gm_900W^u 'GKTd.fZm2Cɐ!Q^]{ $kn--*I3}̧ua6졿RT鱄vjjkquu!::L1Itt$=uHJ GO*].r"eMJ2>@ ~q^TRJhH0UU¥Wpwg(rQ0x5r2t9ݴ?^ԮCkۍ9Ǎ7/ WWWƍÝk7vLDd8667 rGd|/O (67';vX5=2"9)Jͪ5Y5뺖3wwo!> Ir؊v[RTohn9kOƪ1ɤEņM[3Zj|9r2P*8{b@ t k(.)%$$YNGvfmTLpa3= 20GR ^|qq)ޏ%*2Kn ׍*SߌFYӹ{LB ڧL;B8y. 9w9Fa`YU"|a⮬"*2ד8AJ^̈C:s4'>.ӧaܷ{0W7{ /~d9qwF`H̲9Y=g~O>lNu&rȈp r/:*Yj"Ld(.8K!eNcVTJŲ[0w:9%>v$sK!,4zr3ty%+j֬$9=jkx2I ^3Aٙ!LcVkA9?y#=-Ÿ@Rci-=Rh4Z?Yt<24>j9u,˾Yi̴eQ&[w=kjlW w˼9w?.qy9׮U>a]œ8y? 0scǟ_ݍY32Yz?iszиX 444EHURS\RJmmCv>WJosς9̽{^˼g]Nyט>u$aRYa3+V5+_cc$'%ܸoWvnURZFIiQzR^[,7zgϱ5d4vDW wwj8tsSp2k+..hhllD4klqihi~OA[[5uv; 32r9MM466ɉ ZںKn rF'qqq&,4JMMeGx6<<;@ j+V2Lnk->:rQhZzX7tTͭrzŭFg[ZZha#tl蝹rU}z^ڵ*´RQQiU@ n@ @ 02@ e@ !K@ eKo@ tBd@ @`A @ ]F@ @ vA@ @ ؅n ^ALz*ގG L&T8(l%]\8;* c9v$/8&$&dLr"~k4_eoAMٳQSSǾ+%aafgr.\gaElRSFw^V~;Tkpwgɜ;_ș[=&̫ŵkUf6ٶcwX}n dRrm@.ٖzǚƌiqIIFzj񵷷zFd9} nnn&t:ZZZ(.)%z}C=nrO&1;9{(n41J`xKϭL_QM?AX}se} '2"lfT*Fd6||_]RZƛodbFlt7:2ٳiM 2-b|~ZrW0sVoW[K!m7sۍeyAL}.6 ղ1{,RSFWٲm'Z{<}Y帻‹TTTY{s^61xOYb5-*|F]]\x{Q^^aΜ=k} __r2X0.\^&aZT*|}}3:N*ìSǷֱ~ {f@QWWϺ 9zF~Ӗ'N^̌9y +W3jjj?D3~\E J1wz,b!PY^>ppsS%8~|PRR;לHO!*2?? _G\srr"7;Ԕdhh()-c=qxyyT6p|! cdJb]TUUL?f9f0f<ν 8r83ƚ| csHNJVCYy;wvY`Xh&B.r.- d23IKCpP :jmN-mʤ ty[`,cs5rKg]3qvvU?w6FY-km&aѽ>RMJّr47jz cpכ%RFs!\tv;grs@@@ZZ=Ƒ'eM]wlUe'"<&;c=f>Eg,ܷx!~|gTWX~Ǧiط WJ*9ʯ:ooTjz{{; 2Qۃ!xs)kFL-)m:QuAִ RɌi9|8'K/DW޷FD5aZc+msV8 gg;+^au T6܌8x`Ls<èW*zwww2!??]')iFPP{]g"#%Y BR_$2"Q#~h|NJ^K>Y^gu+ #)aa!||Iy'psSP[WZfa9Y~n41 =]C+(bMh4v csFBANv5&F)&B΋?M\l \VMA^Ncselݾ$Lʘdzz=W+j$'%ƹ7YgIP*ZQ^g82lN--::Xd@j`,1a$K_̥E?aڙ6 9n 70l3{&N"$$'{T})&HAJHKM] y񹧨C3Iw/l|^Jht`, M康DPOJ5F''3gyo)zGRO e˼Shu:DE0jCblSX} &';'Os><{|op¹X֮uFQޱk/'M`\V^;;nho^2.-CUk:|.H7kuo*RS 2kdgi2 Un,{l}O",.)%44;3= Ү[[||;g07N$'3-va۰oV6f|pp?|ܷx!'NF1poˎZÆ+ٲmg5Ih:J.oؽF'>89Yl޲W:Jk[O|l ? ~t)xG9} wF΢h[FNNN$'%0oΝLc[K\~& L?,_*%%XoR9} q1Zi``>8'N\\\xVUͫX 9=Ԕdeuq5}$GfFV\k0#KmJHo}{^,oM| a#2$1S`q_k\*._!l֬v'(teIuqm-:Qu}mN .H7kuo[['/'jjMWzZ @:5/kRǞXW89Ø;Yrk :*%:M[3{RGT6|tjVZ+Igi;9x?}oWuXVBλ}eAneMd2F<Y|NS˛3PQqܹͤnndeqQEK6ߤ2ںz]ΤԲb\\ITO}8WxoV6QڭmZ1%̌T,vI-E8.XmՉִRǚkpƞ،*+}q%CkSJ~?JLB2P_᫟VryJr%( |}}P*JQ 1 `ZZZL-*n E;yxxtGiYg=2"L 56R[[71pLi/gr. e%Ç3$b=Z 5CQT8yb9sr2Q(lᣄ0bmIϗ23O~~~ku}arvvr߯xTfZ^v=aʐwUN)Gjّ3UU5]:Y7'mt`,`~ڒPA[[˨or+.H;RH /Z1(ҧ0`HO~F IDAT!+3s^ٟ)HKBO1du o2HCGL5vf#,w>Ə)o~Lʢ3Gm-:Qu}mN :ִ6ߤ3gϣT6jb &2"-JR19²Y7jF-C\ya} 5 s/…kӊhgPxBCihh$"?_,sˬSe22~32t6e]0zsffw&Ez bI 2+ pddLmq;z=WjWAd/E ZҨkUըխT6 ˹RT¥E  $$ȸ_ +8dBTj899qucIWRpP/()ˌKz`dD7,\Rtg~+ǰaNN$koog՚YɮqɆM[-w(!``֌̛s'NNN:r$R-[[9_xbz- D X53^,!\`۵-q),>S&Oˋk<{Xr(~QZZTO-t:;m@E5v[hQ(**hhǮL0qcYVs}˗x$'oCa-qTu_\mcL!#= /5L3u]ai[D(rw3z2BCQ]{+9k2`wԑGϳ!V!NGss3u{=ת> Vwm8fGafY777 xn7d2CY`\)*6qJСqLj{})X[ m%:v+\-mKuA*͖>%ߧZ\]]4'ųzyyIcbGcCnV6uu69m/.)qyT.tsO `qG`9}cMmIIieW[&Gv^  ||Gi跍1Xs&o鍽.'l.*)%4$v תiii+x3|P 9Eggfp?ªx*5>di;^yZCg^3[S9А>׏t\jE%Ϝ #*ALƂywJf՚;oGK´i~ovhHEu|kkkuZ݊zjlad2oٔ{Z28 βNLzZZTlش`~+Vea1/@Td^^pDJVvw[D¨]0$:ۣ}b;wii]q=FXSaN_;2=5G5 MPP i)h4Z:җN)EF>*_nN&rܪ# SV~HMX%'%wΚ|3GeeQfϣ6prQQDEFt`0SrsZ\\\g2"# 摇1<䣌f,oQZN_~NWkeǀ5r-mkt`,Z)Wΰq]EGE#KJ7 GfkBJ@ `u-4.LpP 'OeZ>|/<4ŏرk/UUո]1l(cxٷj R0y'8q4<Hʘdo DGFSgQ]]L&#,,TZ[ػr sGSu q6F?!DneDwp9lɣN9-*ɲ72&YvGE5$&$)qgΞ7qvI5CXhJ%AfgKW*Qƽ}[$eL2?ٳo?*#o{t4~=Xoİ'eɢ|}OoHѽ|KZv XS-R6F rm~ښrzFF''sOQ%eh5267 Fe#T!} ))8*8|aO8R6ju+fl^~%?s?fJJhpDŽ|dN|tǘ7ӗ[uR׎J6ִ)@]]= /0qXǿ޳YK0z83;Oe؇Um_j2@:yx CDqi76l6>\Nzʟ7s{,z/G {瑞bj4Zj(pW7]8sg;*m;vU#3Rښ0MDFpZS˿]媢+L4LpH"F˱'Yf]`KJ3Zd{sk-kÜ8y? 0sz24w3{9?fLeݳ]w{5 o۱e|k|iY9o,7 t v<˖N~~.3OAgGqς9̘6V~-ꞷQRZFtT$z;`~7G`L4s(߽3BXԲcr`mԶ!\[c_b76>{߮1.%7 GfKBJ@ иX 44>qh:cnOi3 ŤUVzNXK_Klm-Ŗ`vTzHѽٷ ='N,%޻ 6R}>:^>oS]wၟzzwgh4466H;ӞxF5.܌-BK;JƢ7oo/|}ioo"g..΄R{]*K/K KBhmkv/[fr9Dzj^˵w.nn n%Ƀd2P,>J`}EJirm)Ru`V6Xʵ-@PVޢ`_{(٬S8d}0e3`臺8;STHmeI]E_;2=5Auuu'O>+#,+εwFg|}vWZڀhiiv[oh4Z/ZT*Z{LHkk+N466hݠRhQ4~Ù0t:$IRfڮўmV ^jz @o*Xʁ-OOHmm!)m0k[Akk+V髺`%} GӁlq8/6;V_;2=!RtǾwPΒ3O}Oj:X`\4-')pf}%m!`3@ZfϚNZh?⪕H3^^ܿ^O.჏?L$'v /(~ԣ@`΢}ARbeeW9rr㘔0R&!D98 r0e77^^=\#B_[Ohh0#ɉ;?y0.d0̫@ 6 @ ahhjrwY@ 2m@0Pq@ @ ad@ @`A @ ]FEdA  @ bǏL6s 9s|_tτ;q˗[lmݳ1m2MMͼ[\VGR:__&M̧Coc\NVFCcDR]S˩Sg9{ %7;p% d]%%="6f)]?.+ʁ^̹kq# 6}m gM}(@ Y\}YhhhFꊯ $t a!<䣄R02􀷷?sNg''R9} GzVg2̘:qV {m̗>r[0bilݶSd0kK1m / #@ [KyOYb5-*-ՌF.w%>>tN:MXXh5{bZm؄FaLP(,g?.m;vd2z]wws#+3;N]{W`:{?=@ [A`ʆ[:s_r/y9YW|E%HOՊJVYo40WߠILe/"Æ%  9;vNN/[Y_ @ 鲒A&NZTUUSV^K$5%5JJرs%e&ιk&|jf>r56saͺ]ɺ?6D|h5Ws^[޿*6lJDx3M&""Jš#رsYWoo/&M'>cR+8Hccɳr5b8^ޞfkܩ3gٻɵΧUk3lhS&O$(0\.0ib>(rkj9x(GM4ؘ.q9{{ \7c-~9ARV~uG1s#iniaٻ0䁓)DEF烳}Nǻ,9-^x{yq$m446nqqu/>ˁGo,Äcijn2ˤ9|8܃LG~iSzan˓ܻ#ǎ-AfTǻ)E?yzK())ckHfLcy[x!Z/Znf kc:^JCGٹ{]Qm5M 3Q( \D3thYif_s<èW*zwww2!??]ONL@PN}}Φ9gr)BByKJB!&.6F]B /'9|2ne"[VF:/qe^x):J"I5q]:DÇO榠Z͈3:sfv&V݈N 2"\C+<,@wɻ)9{{: 91Cȯ~2MMS$)ZV%9)4Ν_xEGGȐ遺[# /\N 9{= zzeaa$%",42ƚ<䷇;}z 3ЉG/Q:77O=(3Oa=v cM,g>Aw9|8QF͹1ILSRh=fIMu:[a5ٳ?6e߬!0~;vZ]̉I_ ނލ _]XKJ 1LOek4;Eť4}"0 ZS=Ź}- vޱxA\{Ohffuxj@ +L|2dfR_dkP^t RGT64jUUլ\WWW:ۆ_.[񿲣;yʮT*0IMMm]=߮ZgOMM-+Vř}Ė;3M|z-Ç5y>"e|BS˛7v>A[{}a!Ӭ /͊&6V=\3Q(wju+koB&0Kk :*cG:-ۉ gB~^řQ#dB^x`7§_|o_G?GlLt񸻹 &3U{ "Ɍ<S칞$=0@Z_\RjH51ɉ__l|ǩ.2S.EJ_zR:t'ܽXYz=2Q#Qm&A_aߏ2.QZVnҨ0 7h\>mxF2~~V^^q.^Ů}O%Ç3$bYCVK*{QUUha rvߵ0q9WȥW\7^r>>6SGR}@p{abdpvr~pvv&>..;:..ٓl o셕\v:Z_Ejjj))-Z%e:u֮z~*bc]׼"SO8ܪhw6n0!,tڹ1gL- ?FHH0m+Sg8vT-qYBws ÿ́js2^/)K>/)qOXh{ bn!:* uh+Rt}8~,i)F&R~:=V_Fn%$$\Ε.]."fH RL:2SpjGˌ8zC-SB_Xē={L8BAnK $ETEt u,)gYUqw\-˱K UwuFq)fp ! JHB @B@Hw/OTG9{y>o}Cn?ެeS %%&hiZl\{ktؐ}yiSuU:x>󍋛/rd=u ;HI=ԶO%>wNWSS4eJO[ĤDݽeQ2+C>\zIҮ{=gf*=YM2 +ꉷsN +*59\[BE ھ@ueUkjLiTI n;Q_f6q-2 H2ج6EG9).߷Ym1f'6[b݌;@^u*9v\/YI2jуjkdX}CXF̶Iw~̐jg_ӔVCc>g(}Ӝ>pmZvb=ۦw}_=D_z.(+U***R9Y3l?v"ǹgtuu܍ܟmF2ob|Eed;xZ:YVqt  uםr8Uxq#N0ĝCp#/~ͺfVjJJ> 2+N)%ޥ/[g~BAU(55Y)Ƀ]w$,狐=Fege^ゾIn.VZW=kF@pb=U,H<7m9?!.n~>/W˗-n^-ICD0s)!!^SѣGgD>aQv_z3Zd_ gʘj5j(M:E7-]s ?SfFz1SќY;g} ""1!^SRxZGMX,81Q9Y[ssJO)}n~ď݉ 8F{\k 51)Qg(>nrsӣw}=9#6nD0&s~2`7#gؼ9zԑ9q>Gz*jMO].TũJmtqiS.ۻO{QC\k שݻfǪM:QZ[W,SB|nK/O?ߩ3STP6[fΘWM֛oMӐ]CMqEck)NNc9&,L_|[nFYUU=p@uKKwBK_ zxbvUwO'?Eji=He-2?m4Z o H2;|֯[[W,Wfs]ħRz _FkWtL#CVwՊ٥ݣ?րQWߠg߬e7ݨh鍋9:\T?'.2ywߡn5XeF[sqދ.n!Or] V$sΏ&MJҪ+>N)-+ה):pp`Јx]PPؠUEG57[ߤJ=}ͦOv|nԟ0Ѭk1ayoإ3$$D iرc-өVۏʹؘv55T(4tƏ/á69CoCFDuP(|Lx&w~bX/kUmȿf}WzRoyK1jM.H\-z7ݮA[_ 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 - name: Check out source code uses: actions/checkout@v3 with: fetch-depth: 0 - name: Get version run: echo "VERSION=$(git describe --tags --long --always | sed 's/^v//;s/-/./')" >> $GITHUB_OUTPUT id: version - name: Configure run: meson -Dversion=${{steps.version.outputs.VERSION}} --buildtype release --prefix /usr ./build - name: Build run: ninja -C ./build - name: Install run: DESTDIR=install ninja -C ./build install - name: Check run: ./build/install/usr/bin/swayimg --version swayimg-2.1/LICENSE000066400000000000000000000020671455710357200140570ustar00rootroot00000000000000Copyright (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-2.1/README.md000066400000000000000000000047101455710357200143260ustar00rootroot00000000000000# Swayimg: image viewer for Sway/Wayland Swayimg is a 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. ![Screenshot](https://raw.githubusercontent.com/artemsen/swayimg/master/.github/screenshot.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). ## 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 - ``` ## 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 ![CI](https://github.com/artemsen/swayimg/workflows/CI/badge.svg) The project uses Meson build system: ``` meson build ninja -C build sudo ninja -C build install ``` swayimg-2.1/extra/000077500000000000000000000000001455710357200141705ustar00rootroot00000000000000swayimg-2.1/extra/bash.completion000066400000000000000000000011311455710357200171740ustar00rootroot00000000000000# swayimg(1) completion _swayimg() { local cur=${COMP_WORDS[COMP_CWORD]} local opts="-r --recursive \ -o --order \ -s --scale \ -l --slideshow \ -f --fullscreen \ -p --position \ -g --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-2.1/extra/swayimg.1000066400000000000000000000106131455710357200157330ustar00rootroot00000000000000.\" 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 .SH DESCRIPTION .PP If no input files or directories are specified, the viewer will try to read all files in the current directory. .PP Use '-' as \fIFILE\fR to read image data from stdin. .PP In a sway 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. The program uses sway's IPC to determine the geometry of the currently focused workspace and window. This data is used to calculate the position and size of a new window. Then the program adds two rules to the sway application \fBswayimg\fR: "floating enable" and "move position". This creates a new Wayland window and draws an image from the specified file. .\" options .SH OPTIONS .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\-r\fR, \fB\-\-recursive\fR" Read directories recursively. .IP "\fB\-o\fR, \fB\-\-order\fR\fB=\fR\fIORDER\fR:" Set the order of the image file list: .nf \fInone\fR: unsorted; \fIalpha\fR: sorted alphabetically (default); \fIrandom\fR: randomize list. .IP "\fB\-s\fR, \fB\-\-scale\fR\fB=\fR\fISCALE\fR" Set the default scale of the image. \fISCALE\fR is one of the named values. \fIoptimal\fR: 100% or less to fit to window; \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" Activate slideshow mode on startup. .IP "\fB\-f\fR, \fB\-\-fullscreen\fR" Start in full screen mode. .IP "\fB\-p\fR, \fB\-\-position\fR\fB=\fR\fIPOS\fR" Set the absolute position of the window: .nf \fIparent\fR: set position from parent (currently active) window (default); \fIX,Y\fR: absolute coordinates of the top left corner. .IP "\fB\-g\fR, \fB\-\-size\fR\fB=\fR\fISIZE\fR" Set window size: .nf \fIparent\fR: set size from parent (currently active) window (default); \fIimage\fR: set size from the first image; \fIWIDTH,HEIGHT\fR: absolute size of the window. .IP "\fB\-a\fR, \fB\-\-class\fR" Set a constant window class/app_id. Setting this may break the window layout. .IP "\fB\-c\fR, \fB\-\-config\fR\fB=\fR\fISECTION.KEY=VALUE\fR" Set a configuration parameter, see `man swayimgrc` for a list of sections and its parameters. .\" keys .SH KEYBINDINGS Default key bindings can be overridden with the configuration file. .IP "\fBF1\fR" Show/hide help. .IP "\fBHome\fR" Open the first file. .IP "\fBEnd\fR" Open the last file. .IP "\fBPgDown\fR, \fBSpace\fR" Open next file. .IP "\fBPgUp\fR" Open previous file. .IP "\fBd\fR" Open file from next directory. .IP "\fBShift+d\fR" Open file from previous directory. .IP "\fBo\fR" Show next frame. .IP "\fBShift+o\fR" Show previous frame. .IP "\fBs\fR" Start/stop animation. .IP "\fBShift+s\fR" Start/stop slideshow. .IP "\fBf\fR" Toggle full screen mode. .IP "\fBArrows\fR" Move the view point. .IP "\fB+\fR, \fB=\fR" Zoom in. .IP "\fB\-\fR" Zoom out. .IP "\fBw\fR" Zoom the image to fit the window width, this will crop the image. .IP "\fBShift+w\fR" Zoom the image to fit the window height, this will crop the image. .IP "\fBz\fR" Zoom the image to fit window. .IP "\fBShift+z\fR" Zoom the image to fill the window, this will crop the image. .IP "\fB0\fR" Set scale to 100%. .IP "\fBBackspace\fR" Set optimal scale to fit the window, but not more 100%. .IP "\fB[\fR" Rotate the image 90 degrees anticlockwise. .IP "\fB]\fR" Rotate the image 90 degrees clockwise. .IP "\fBm\fR" Flip the image vertically. .IP "\fBShift+m\fR" Flip the image horizontally. .IP "\fBa\fR" Enable/disable anti-aliasing. .IP "\fBr\fR" Reset cache and reload the current image. .IP "\fBi\fR" Switch the text info mode. .IP "\fBe\fR" Execute external command: prints path to the current file. .IP "\fBEsc\fR, \fBq\fR" Exit the program. .SH ENVIRONMENT .IP \fISWAYSOCK\fR Path to the socket file used for Sway IPC. .\" related man pages .SH SEE ALSO swayimgrc(5) .\" link to homepage .SH BUGS For suggestions, comments, bug reports etc. visit the .UR https://github.com/artemsen/swayimg project homepage .UE . swayimg-2.1/extra/swayimg.desktop000066400000000000000000000007501455710357200172450ustar00rootroot00000000000000[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 NoDisplay=true swayimg-2.1/extra/swayimg.png000066400000000000000000000066021455710357200163620ustar00rootroot00000000000000PNG  IHDR@@iq pHYs   4IDATx[yluS<$J)2(Leَ&'n4-)U 0 @[5@Q(:-j@.*((ٲRc)kٝ4XdJSxpw{{ߛ}oلu5?WCt5C^FA6,94yhzgz/3[>Er݁ɵm)n\ 3Aϯ; k 7J/mZx?t;}Ǻ}3@X{^f_Z{k+݀Eŭ,Els?镽==G>z% }E *B%([ccgs ~ $o-$A ҋb0, FXY6?z{FRl8abXlP2K`Qu#}hl| qd2`B7ch̜=C=fl4!&Eh ZJyۂxI@ ޖ>B 0&j-PW1%0DQ#vI+e7~vbƷ]\ce]n]ŋ"._  <~_>޺v×P.0*sYJ?u|F [L>Gӏ2x!MG?nܘ3p.+,JX(JhmkB__-)tLEa;pEfX=xnēcMvqߣU; s9 |M:] aCTdgRB8b.D!EDv +? l,˫ |χJ}% ܂ Idw./L,"קNQmaow( \S\)3CuQ*h>[Y^7#A㉺dca3 [±?ť*& 4% ipea)yeֿԢ\,1c(/W3)L O2_ 'jՏ#d%q 3Y$$%"a~nH$$,/loV_d/ڐNauLBhroc'߹z Tk- icxBy"DrP宁Ҕ7@ҡu$]d"Ro`0!>IlS$*.QDSc~ܞt bq~݅Q6@n2+<O< p)\c5tU6d;0K$!47-Bh5 9`9V9 v<-f@aq.#Ԃ\O })pΖ9I$p _8T#eџfߣZt`e,j vX6ԝ$亼Jp])sB 4yi{ͼ K> "y1$$jn|{08lJB:+*h{y$ _K\J UCgCO *>w굣W!"EALa UF6:n6Jd9GTkp%a<#G4\~fӄ4﮺8t2V^]yB qόzg Y1(PlW,a@D 1pA|m#KRн{m26lrzц"}}Q?{e^?g`3IՃvۢ@ȗ'/ʫ*;Qa˦%7;D:OV eA }N@, -.2bc#o'󋞋9?B:Y?4(1)#"mh !le 7gf[F,HUؽ,t5A*vSw)le8tiuSJoo#iCN6{w)RÑzƑ U12)30NdL9D V{'m@S&YaKz/nO\EnC@N*&jU ,T+\wymS(}dgL &Y,S!<Mp̺ ) súYxa={Drz-ZMBX@TB~%66G@6F.gX:5vՏ .TH SWAYIMGRC 5 2022-02-09 swayimg "Swayimg configuration" .SH NAME swayimgrc \- Swayimg configuration file .\" possible file locations .SH SYNOPSIS .SY \fI$XDG_CONFIG_HOME\fR/swayimg/config .SY \fI$HOME\fR/.config/swayimg/config .SY \fI$XDG_CONFIG_DIRS\fR/swayimg/config .SY \fR/etc/xdg/swayimg/config .\" format description .SH DESCRIPTION .SS General format description The Swayimg configuration file is a text-based INI file used to override the default settings. .PP The structure of the INI file consists of key-value pairs for properties and sections that organize properties. 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 (=). 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. .SS [general] section .IP "\fBscale\fR: initial image scale:" .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 "\fBfullscreen\fR: start in full screen mode, \fIyes\fR or [\fIno\fR];" .IP "\fBantialiasing\fR: enable/disable anti-aliasing, \fIyes\fR or [\fIno\fR];" .IP "\fBtransparency\fR: background for transparent images:" .nf \fInone\fR: transparent; \fIgrid\fR: show grid as background (default); \fIXXXXXX\fR: hexadecimal RGB color. .IP "\fBposition\fR: window position:" \fIparent\fR: set position from parent (currently active) window (default); \fIX,Y\fR: absolute coordinates of the top left corner. .IP "\fBsize\fR: window size:" \fIparent\fR: set size from parent (currently active) window (default); \fIimage\fR: set size from the first image; \fIWIDTH,HEIGHT\fR: absolute size of the window. .IP "\fBbackground\fR: window background:" .nf \fInone\fR: transparent (default); \fIXXXXXX\fR: hexadecimal RGB color. .IP "\fBslideshow\fR: run slideshow at startup, \fIyes\fR or [\fIno\fR];" .IP "\fBslideshow_time\fR: slideshow image duration in seconds, default is \fI3\fR;" .IP "\fBapp_id\fR: set a constant window class/app_id. Setting this may break the window layout;" .SS [list] section: image list configuration .IP "\fBorder\fR: order of the image list:" .nf \fInone\fR: unsorted; \fIalpha\fR: sorted alphabetically (default); \fIrandom\fR: randomize list. .IP "\fBloop\fR: looping file list mode, [\fIyes\fR] or \fIno\fR;" .IP "\fBrecursive\fR: read directories recursively, \fIyes\fR or [\fIno\fR];" .IP "\fBall\fR: open all files in the same directory, [\fIyes\fR] or \fIno\fR;" .SS [font]: font configuration .PP .IP "\fBname\fR: font name used for printing text, default is \fImonospace\fR;" .IP "\fBsize\fR: font size (in pt), default is \fI14\fR;" .IP "\fBcolor\fR: color in RGB hex format, default is \fI#cccccc\fR;" .IP "\fBshadow\fR: drop shadow, \fInone\fR or color in RGB hex format, default is \fI#000000\fR;" .SS [info] section: text info layout .IP "\fBmode\fR: startup mode, \fIoff\fR, \fIbrief\fR, or [\fIfull\fR];" .IP "\fBfull.topleft\fR: comma delimited list of content for the \fIfull\fR mode, top left corner of the window:" .nf \fIname\fR: file name; \fIpath\fR: full path; \fIfilesize\fR: file size; \fIformat\fR: image format; \fIimagesize\fR: image size; \fIexif\fR: EXIF data; \fIframe\fR: current/total frame index; \fIindex\fR: current/total file index; \fIscale\fR: current scale in percent; \fIstatus\fR: status message; \fInone\fR: empty field (ignored); .IP "\fBfull.topright\fR: \fIfull\fR mode, top right corner of the window;" .IP "\fBfull.bottomleft\fR: \fIfull\fR mode, bottom left corner of the window;" .IP "\fBfull.bottomright\fR: \fIfull\fR mode, bottom right corner of the window;" .IP "\fBbrief.topleft\fR: \fIbrief\fR mode, top right corner of the window;" .IP "\fBbrief.topright\fR: \fIbrief\fR mode, top right corner of the window;" .IP "\fBbrief.bottomleft\fR: \fIbrief\fR mode, bottom left corner of the window;" .IP "\fBbrief.bottomright\fR: \fIbrief\fR mode, bottom right corner of the window;" .SS [keys]: key bindings .PP The key bindings are described in the "keys" section. Each line associates a key with some action and optional parameters. The key name can be obtained with the \fIxkbcli\fR tool: `xkbcli interactive-wayland`. One or more key modifers (Ctrl, Alt, Shift) can be specified in the key name. .PP Valid action are: .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 "\fBanimation\fR: start/stop animation;" .IP "\fBslideshow\fR: start/stop slideshow;" .IP "\fBfullscreen\fR: switch fullscreen mode;" .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/ou/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/\fIbrief\fR/\fIfull\fR);" .IP "\fBexec\fR \fICOMMAND\fR: execute an external command, use % to substitute the path to the current image, %% to escape %;" .IP "\fBexit\fR: exit the application." .\" [mouse] .SS [mouse]: mouse/touchpad configuration .PP Same format as in [keys]. .PP Valid keys: .IP "\fBScrollUp\fR: mouse wheel up or touch scroll up;" .IP "\fBScrollDown\fR: mouse wheel down or touch scroll down;" .IP "\fBScrollLeft\fR: touch scroll left;" .IP "\fBScrollRight\fR: touch scroll right;" .\" example file .SH EXAMPLES .EX # comment [general] window = #112233 [list] order = random [font] size = 16 [keys] e = exec echo "%" > mylist.txt .EE .\" related man pages .SH SEE ALSO swayimg(1) .\" link to homepage .SH BUGS For suggestions, comments, bug reports etc. visit the .UR https://github.com/artemsen/swayimg project homepage .UE . swayimg-2.1/extra/zsh.completion000066400000000000000000000017251455710357200170740ustar00rootroot00000000000000#compdef swayimg # zsh completion for the "swayimg" image viewer. # Copyright (C) 2022 Artem Senichev _arguments \ '(-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)' \ '(-g --size)'{-g,--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-2.1/meson.build000066400000000000000000000142351455710357200152140ustar00rootroot00000000000000# Rules for building with Meson project( 'swayimg', 'c', default_options: [ 'c_std=c99', 'warning_level=3', ], license: 'MIT', version: '0.0.0', meson_version: '>=0.59.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')) gif = cc.find_library('gif', required: get_option('gif')) heif = dependency('libheif', required: get_option('heif')) avif = dependency('libavif', required: get_option('avif')) jpeg = dependency('libjpeg', required: get_option('jpeg')) 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')) # optional dependencies: other features exif = dependency('libexif', required: get_option('exif')) bash = dependency('bash-completion', required: get_option('bash')) # Arch specific: https://bugs.archlinux.org/task/73931 jxl_feature = get_option('jxl') jxl = dependency('libjxl', required: false) if not jxl.found() and (jxl_feature.auto() or jxl_feature.enabled()) jxl = cc.find_library('libjxl', required: jxl_feature) endif # 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 = join_paths(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: join_paths(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: join_paths(get_option('datadir'), 'applications')) install_data('extra/swayimg.png', install_dir: join_paths(get_option('datadir'), 'icons/hicolor/64x64/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 = join_paths(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 # source files sources = [ 'src/canvas.c', 'src/config.c', 'src/font.c', 'src/image.c', 'src/imagelist.c', 'src/info.c', 'src/keybind.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/loader.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-2.1/meson_options.txt000066400000000000000000000034661455710357200165130ustar00rootroot00000000000000# 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') swayimg-2.1/src/000077500000000000000000000000001455710357200136345ustar00rootroot00000000000000swayimg-2.1/src/canvas.c000066400000000000000000000406531455710357200152630ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Canvas used to render images and text to window buffer. // Copyright (C) 2022 Artem Senichev #include "canvas.h" #include "config.h" #include "str.h" #include #include // Background modes #define COLOR_TRANSPARENT 0xff000000 #define BACKGROUND_GRID 0xfe000000 // Text rendering parameters #define TEXT_COLOR 0x00cccccc #define TEXT_SHADOW 0x00000000 #define TEXT_NO_SHADOW 0xff000000 #define TEXT_PADDING 10 // space between text layout and window edge #define TEXT_LINESP 4 // line spacing factor // Background grid parameters #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 #define max(x, y) ((x) > (y) ? (x) : (y)) #define min(x, y) ((x) < (y) ? (x) : (y)) /** Scaling operations. */ enum canvas_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 /** Canvas context. */ struct canvas { argb_t image_bkg; ///< Image background mode/color argb_t window_bkg; ///< Window background mode/color bool antialiasing; ///< Anti-aliasing (bicubic interpolation) argb_t font_color; ///< Font color argb_t font_shadow; ///< Font shadow color enum canvas_scale initial_scale; ///< Initial scale float scale; ///< Current scale factor struct rect image; ///< Image position and size struct size window; ///< Output window size size_t wnd_scale; ///< Window scale factor (HiDPI) }; static struct canvas ctx = { .image_bkg = BACKGROUND_GRID, .window_bkg = COLOR_TRANSPARENT, .font_color = TEXT_COLOR, .font_shadow = TEXT_SHADOW, }; /** * Fix viewport position to minimize gap between image and window edge. */ static void fix_viewport(void) { const struct rect img = { .x = ctx.image.x, .y = ctx.image.y, .width = ctx.scale * ctx.image.width, .height = ctx.scale * ctx.image.height }; if (img.x > 0 && img.x + img.width > ctx.window.width) { ctx.image.x = 0; } if (img.y > 0 && img.y + img.height > ctx.window.height) { ctx.image.y = 0; } if (img.x < 0 && img.x + img.width < ctx.window.width) { ctx.image.x = ctx.window.width - img.width; } if (img.y < 0 && img.y + img.height < ctx.window.height) { ctx.image.y = ctx.window.height - img.height; } if (img.width <= ctx.window.width) { ctx.image.x = ctx.window.width / 2 - img.width / 2; } if (img.height <= ctx.window.height) { ctx.image.y = ctx.window.height / 2 - img.height / 2; } } /** * Set fixed scale for the image. * @param sc scale to set */ static void set_scale(enum canvas_scale sc) { const float scale_w = 1.0 / ((float)ctx.image.width / ctx.window.width); const float scale_h = 1.0 / ((float)ctx.image.height / ctx.window.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.image.x = ctx.window.width / 2 - (ctx.scale * ctx.image.width) / 2; ctx.image.y = ctx.window.height / 2 - (ctx.scale * ctx.image.height) / 2; fix_viewport(); } /** * Zoom in/out. * @param percent percentage increment to current scale */ static void zoom(ssize_t percent) { const size_t old_w = ctx.scale * ctx.image.width; const size_t old_h = ctx.scale * ctx.image.height; const float step = (ctx.scale / 100) * percent; if (percent > 0) { ctx.scale += step; if (ctx.scale > MAX_SCALE) { ctx.scale = MAX_SCALE; } } else { const float scale_w = (float)MIN_SCALE / ctx.image.width; const float scale_h = (float)MIN_SCALE / ctx.image.height; const float scale_min = max(scale_w, scale_h); ctx.scale += step; if (ctx.scale < scale_min) { ctx.scale = scale_min; } } // move viewport to save the center of previous coordinates const size_t new_w = ctx.scale * ctx.image.width; const size_t new_h = ctx.scale * ctx.image.height; const ssize_t delta_w = old_w - new_w; const ssize_t delta_h = old_h - new_h; const ssize_t cntr_x = ctx.window.width / 2 - ctx.image.x; const ssize_t cntr_y = ctx.window.height / 2 - ctx.image.y; ctx.image.x += ((float)cntr_x / old_w) * delta_w; ctx.image.y += ((float)cntr_y / old_h) * delta_h; fix_viewport(); } /** * Draw text surface on window. * @param wnd destination window * @param x,y text position * @param text text surface to draw */ static void draw_text(struct pixmap* wnd, size_t x, size_t y, const struct text_surface* text) { if (ctx.font_shadow != TEXT_NO_SHADOW) { size_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.font_shadow); } pixmap_apply_mask(wnd, x, y, text->data, text->width, text->height, ctx.font_color); } /** * 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, CANVAS_CFG_ANTIALIASING) == 0) { if (config_to_bool(value, &ctx.antialiasing)) { status = cfgst_ok; } } else if (strcmp(key, CANVAS_CFG_SCALE) == 0) { const ssize_t index = str_index(scale_names, value, 0); if (index >= 0) { ctx.initial_scale = index; status = cfgst_ok; } } else if (strcmp(key, CANVAS_CFG_TRANSPARENCY) == 0) { if (strcmp(value, "grid") == 0) { ctx.image_bkg = BACKGROUND_GRID; status = cfgst_ok; } else if (strcmp(value, "none") == 0) { ctx.image_bkg = COLOR_TRANSPARENT; status = cfgst_ok; } else if (config_to_color(value, &ctx.image_bkg)) { status = cfgst_ok; } } else if (strcmp(key, CANVAS_CFG_BACKGROUND) == 0) { if (strcmp(value, "none") == 0) { ctx.window_bkg = COLOR_TRANSPARENT; status = cfgst_ok; } else if (config_to_color(value, &ctx.window_bkg)) { status = cfgst_ok; } } else { status = cfgst_invalid_key; } return status; } /** * Custom section loader, see `config_loader` for details. */ static enum config_status load_font_config(const char* key, const char* value) { enum config_status status = cfgst_invalid_value; if (strcmp(key, "color") == 0) { if (config_to_color(value, &ctx.font_color)) { status = cfgst_ok; } } else if (strcmp(key, "shadow") == 0) { if (strcmp(value, "none") == 0) { ctx.font_shadow = TEXT_NO_SHADOW; status = cfgst_ok; } else if (config_to_color(value, &ctx.font_shadow)) { status = cfgst_ok; } } else { status = cfgst_invalid_key; } return status; } void canvas_init(void) { // register configuration loader config_add_loader(GENERAL_CONFIG_SECTION, load_config); config_add_loader(FONT_CONFIG_SECTION, load_font_config); } bool canvas_reset_window(size_t width, size_t height, size_t scale) { const bool first = (ctx.window.width == 0); ctx.window.width = width; ctx.window.height = height; ctx.wnd_scale = scale; font_set_scale(scale); fix_viewport(); return first; } void canvas_reset_image(size_t width, size_t height) { ctx.image.x = 0; ctx.image.y = 0; ctx.image.width = width; ctx.image.height = height; ctx.scale = 0; set_scale(ctx.initial_scale); } void canvas_swap_image_size(void) { const ssize_t diff = (ssize_t)ctx.image.width - ctx.image.height; const ssize_t shift = (ctx.scale * diff) / 2; const size_t old_width = ctx.image.width; ctx.image.x += shift; ctx.image.y -= shift; ctx.image.width = ctx.image.height; ctx.image.height = old_width; fix_viewport(); } void canvas_draw_image(struct pixmap* wnd, const struct image* img, size_t frame) { const struct pixmap* pm = &img->frames[frame].pm; const ssize_t scaled_x = ctx.image.x + ctx.scale * pm->width; const ssize_t scaled_y = ctx.image.y + ctx.scale * pm->height; const ssize_t wnd_x0 = max(0, ctx.image.x); const ssize_t wnd_y0 = max(0, ctx.image.y); const ssize_t wnd_x1 = min((ssize_t)wnd->width, scaled_x); const ssize_t wnd_y1 = min((ssize_t)wnd->height, scaled_y); const size_t width = wnd_x1 - wnd_x0; const size_t height = wnd_y1 - wnd_y0; // clear window background const argb_t wnd_color = (ctx.window_bkg == COLOR_TRANSPARENT ? 0 : ARGB_SET_A(0xff) | ctx.window_bkg); if (height < wnd->height) { pixmap_fill(wnd, 0, 0, wnd->width, wnd_y0, wnd_color); pixmap_fill(wnd, 0, wnd_y1, wnd->width, wnd->height - wnd_y1, wnd_color); } if (width < wnd->width) { pixmap_fill(wnd, 0, wnd_y0, wnd_x0, height, wnd_color); pixmap_fill(wnd, wnd_x1, wnd_y0, wnd->width - wnd_x1, height, wnd_color); } if (img->alpha) { // clear image background if (ctx.image_bkg == BACKGROUND_GRID) { pixmap_grid(wnd, wnd_x0, wnd_y0, width, height, GRID_STEP * ctx.wnd_scale, GRID_COLOR1, GRID_COLOR2); } else { const argb_t color = (ctx.image_bkg == COLOR_TRANSPARENT ? wnd_color : ARGB_SET_A(0xff) | ctx.image_bkg); pixmap_fill(wnd, wnd_x0, wnd_y0, width, height, color); } } // put image on window surface pixmap_put(wnd, wnd_x0, wnd_y0, pm, ctx.image.x, ctx.image.y, ctx.scale, img->alpha, ctx.antialiasing); } void canvas_draw_text(struct pixmap* wnd, enum info_position pos, const struct info_line* lines, size_t lines_num) { size_t max_key_width = 0; const size_t height = lines[0].value.height + lines[0].value.height / TEXT_LINESP; // 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 info_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 info_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 info_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 info_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) { draw_text(wnd, x_key, y, key); x_key += key->width; } draw_text(wnd, x_val, y, value); } } void canvas_draw_ctext(struct pixmap* wnd, const struct text_surface* lines, size_t lines_num) { const size_t line_height = lines[0].height + lines[0].height / TEXT_LINESP; const size_t row_max = (wnd->height - TEXT_PADDING * 2) / line_height; const size_t columns = (lines_num / row_max) + (lines_num % row_max ? 1 : 0); const size_t rows = (lines_num / columns) + (lines_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 >= lines_num) { break; } if (max_width < lines[index].width) { max_width = lines[index].width; } } total_width += max_width; } total_width += col_space * (columns - 1); // top left corner of the centered text block if (total_width < ctx.window.width) { left = wnd->width / 2 - total_width / 2; } if (rows * line_height < ctx.window.height) { top = wnd->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 >= lines_num) { break; } draw_text(wnd, left, y, &lines[index]); if (col_width < lines[index].width) { col_width = lines[index].width; } y += line_height; } left += col_width + col_space; } } bool canvas_move(bool horizontal, ssize_t percent) { const ssize_t old_x = ctx.image.x; const ssize_t old_y = ctx.image.y; if (horizontal) { ctx.image.x += (ctx.window.width / 100) * percent; } else { ctx.image.y += (ctx.window.height / 100) * percent; } fix_viewport(); return (ctx.image.x != old_x || ctx.image.y != old_y); } bool canvas_drag(int dx, int dy) { const ssize_t old_x = ctx.image.x; const ssize_t old_y = ctx.image.y; ctx.image.x += dx; ctx.image.y += dy; fix_viewport(); return (ctx.image.x != old_x || ctx.image.y != old_y); } void canvas_zoom(const char* op) { ssize_t percent = 0; if (!op || !*op) { return; } for (size_t i = 0; i < sizeof(scale_names) / sizeof(scale_names[0]); ++i) { if (strcmp(op, scale_names[i]) == 0) { set_scale(i); return; } } if (str_to_num(op, 0, &percent, 0) && percent != 0 && percent > -1000 && percent < 1000) { zoom(percent); return; } fprintf(stderr, "Invalid zoom operation: \"%s\"\n", op); } float canvas_get_scale(void) { return ctx.scale; } bool canvas_switch_aa(void) { ctx.antialiasing = !ctx.antialiasing; return ctx.antialiasing; } swayimg-2.1/src/canvas.h000066400000000000000000000046531455710357200152700ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Canvas used to render images and text to window buffer. // Copyright (C) 2022 Artem Senichev #pragma once #include "image.h" #include "info.h" // Configuration parameters #define CANVAS_CFG_ANTIALIASING "antialiasing" #define CANVAS_CFG_SCALE "scale" #define CANVAS_CFG_TRANSPARENCY "transparency" #define CANVAS_CFG_BACKGROUND "background" /** * Initialize canvas context. */ void canvas_init(void); /** * Reset window parameters. * @param width,height new window size * @param scale window scale factor * @return true if it was the first resize */ bool canvas_reset_window(size_t width, size_t height, size_t scale); /** * Reset image position, size and scale. * @param width,height new image size */ void canvas_reset_image(size_t width, size_t height); /** * Recalculate position after rotating image on 90 degree. */ void canvas_swap_image_size(void); /** * Draw image on window. * @param wnd destination window * @param img image to draw * @param frame frame number to draw */ void canvas_draw_image(struct pixmap* wnd, const struct image* img, size_t frame); /** * Print information text block. * @param wnd destination window * @param pos block position * @param lines array of lines to print * @param lines_num total number of lines */ void canvas_draw_text(struct pixmap* wnd, enum info_position pos, const struct info_line* lines, size_t lines_num); /** * Print text block at the center of window. * @param wnd destination window * @param lines array of lines to print * @param lines_num total number of lines */ void canvas_draw_ctext(struct pixmap* wnd, const struct text_surface* lines, size_t lines_num); /** * Move viewport. * @param horizontal axis along which to move (false for vertical) * @param percent percentage increment to current position * @return true if coordinates were changed */ bool canvas_move(bool horizontal, ssize_t percent); /** * Move viewport. * @param dx,dy delta between current and new position * @return true if coordinates were changed */ bool canvas_drag(int dx, int dy); /** * Zoom image. * @param op zoom operation name */ void canvas_zoom(const char* op); /** * Get current scale. * @return current scale, 1.0 = 100% */ float canvas_get_scale(void); /** * Switch antialiasing. * @return current state */ bool canvas_switch_aa(void); swayimg-2.1/src/config.c000066400000000000000000000163511455710357200152530ustar00rootroot00000000000000// 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; ++line; delim = strchr(line, ']'); if (!delim || line + 1 == delim) { fprintf(stderr, "Invalid section define in %s:%lu\n", path, line_num); continue; } *delim = 0; len = delim - line + 1; section = realloc(section, len); memcpy(section, line, len); continue; } delim = strchr(line, '='); if (!delim) { fprintf(stderr, "Invalid key=value format in %s:%lu\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:%lu\n", path, line_num); } } free(buff); free(section); fclose(fd); return true; } void config_init(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_free(void) { free(ctx.sections); } 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; char* ptr; const char* it = cmd; // get section name ptr = section; while (*it != '.') { if (!*it || ptr >= section + sizeof(section) - 1) { goto format_error; } *ptr++ = *it++; } *ptr = 0; // last null ++it; // skip delimiter // get key ptr = key; while (*it != '=') { if (!*it || ptr >= key + sizeof(key) - 1) { goto format_error; } *ptr++ = *it++; } *ptr = 0; // last null ++it; // skip delimiter // get value value = it; // load setting return config_set(section, key, value) == cfgst_ok; format_error: fprintf(stderr, "Invalid format: \"%s\"\n", cmd); return false; } 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 == '#') { ++text; } if (str_to_num(text, 0, &num, 16) && num >= 0 && num <= 0xffffffff) { *color = num; return true; } return false; } swayimg-2.1/src/config.h000066400000000000000000000033101455710357200152470ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Program configuration. // Copyright (C) 2022 Artem Senichev #pragma once #include "pixmap.h" // Name of the general configuration section #define GENERAL_CONFIG_SECTION "general" /** 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); /** * Initialize configuration: set defaults and load from file. */ void config_init(void); /** * Free configuration instance. */ void config_free(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-2.1/src/exif.c000066400000000000000000000122261455710357200147360ustar00rootroot00000000000000// 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) { size_t pos; char location[64]; pos = read_coordinate(exif, EXIF_TAG_GPS_LATITUDE, EXIF_TAG_GPS_LATITUDE_REF, location, sizeof(location)); if (pos) { strcat(location, ", "); pos = strlen(location); if (read_coordinate(exif, EXIF_TAG_GPS_LONGITUDE, EXIF_TAG_GPS_LONGITUDE_REF, location + pos, sizeof(location) - pos)) { image_add_meta(img, "Location", "%s", location); } } } 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-2.1/src/exif.h000066400000000000000000000005501455710357200147400ustar00rootroot00000000000000// 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-2.1/src/font.c000066400000000000000000000125131455710357200147500ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Text 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 SPACE_WH_REL 2 #define GLYPH_GW_REL 4 // Defaults #define DEFALT_FONT "monospace" #define DEFALT_SIZE 14 #define DEFALT_SCALE 1 /** 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) size_t scale; ///< Scale factor (HiDPI) }; 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; } /** * 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 { status = cfgst_invalid_key; } return status; } void font_create(void) { // set defaults str_dup(DEFALT_FONT, &ctx.name); ctx.size = DEFALT_SIZE; ctx.scale = DEFALT_SCALE; // register configuration loader config_add_loader(FONT_CONFIG_SECTION, load_config); } void font_init(void) { char file[256]; const FT_F26Dot6 size = ctx.size * ctx.scale * 64; if (!search_font_file(ctx.name, file, sizeof(file))) { return; } if (FT_Init_FreeType(&ctx.lib) != 0) { return; } if (FT_New_Face(ctx.lib, file, 0, &ctx.face) != 0) { return; } FT_Set_Char_Size(ctx.face, size, 0, 96, 0); } void font_free(void) { if (ctx.face) { FT_Done_Face(ctx.face); } if (ctx.lib) { FT_Done_FreeType(ctx.lib); } free(ctx.name); } void font_set_scale(size_t scale) { ctx.scale = scale; } bool font_render(const char* text, struct text_surface* surface) { size_t x; wchar_t* wide; const wchar_t* ptr; const size_t space_size = ctx.face->size->metrics.y_ppem / SPACE_WH_REL; wide = str_to_wide(text, NULL); if (!wide) { return false; } // get total width x = 0; ptr = wide; while (*ptr) { if (*ptr == L' ') { x += space_size; } else if (FT_Load_Char(ctx.face, *ptr, FT_LOAD_RENDER) == 0) { x += ctx.face->glyph->advance.x >> 6; // why 6? from freetype tutorial! } ++ptr; } // allocate surface buffer surface->width = x; surface->height = ctx.face->size->metrics.y_ppem; surface->data = calloc(1, surface->height * surface->width); if (!surface->data) { free(wide); return false; } // draw glyphs x = 0; ptr = wide; while (*ptr) { if (*ptr == L' ') { x += space_size; } else if (FT_Load_Char(ctx.face, *ptr, FT_LOAD_RENDER) == 0) { const FT_GlyphSlot glyph = ctx.face->glyph; const FT_Bitmap* bmp = &glyph->bitmap; size_t y = ctx.face->size->metrics.y_ppem - glyph->bitmap_top; // it's a hack, but idk how to up the baseline const size_t baseline_offset = ctx.size / 3; if (y > baseline_offset) { y -= baseline_offset; } else { y = 0; } for (size_t glyph_y = 0; glyph_y < bmp->rows; ++glyph_y) { uint8_t* dst; if (glyph_y + y >= surface->height) { break; // it's a hack too =) } dst = &surface->data[(glyph_y + y) * surface->width + x + glyph->bitmap_left]; memcpy(dst, &bmp->buffer[glyph_y * bmp->pitch], bmp->width); } x += glyph->advance.x >> 6; } ++ptr; } free(wide); return true; } swayimg-2.1/src/font.h000066400000000000000000000016041455710357200147540ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Text renderer. // Copyright (C) 2022 Artem Senichev #pragma once #include #include #include // Configuration section #define FONT_CONFIG_SECTION "font" /** 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 text renderer. */ void font_create(void); /** * Initialize (load font). */ void font_init(void); /** * Free font resources. */ void font_free(void); /** * Set font scaling in HiDPI mode. * @param scale scale factor */ void font_set_scale(size_t scale); /** * Render single text line. * @param text string to print * @param surface array of alpha pixels * @return false on error */ bool font_render(const char* text, struct text_surface* surface); swayimg-2.1/src/formats/000077500000000000000000000000001455710357200153075ustar00rootroot00000000000000swayimg-2.1/src/formats/avif.c000066400000000000000000000100761455710357200164040ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // AV1 (AVIF/AVIFS) format decoder. // Copyright (C) 2023 Artem Senichev #include "loader.h" #include "src/image.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; avifRGBImageAllocatePixels(&rgb); 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: image_print_error(ctx, "AV1 decode failed: %s", avifResultToString(rc)); return -1; } static int decode_frames(struct image* ctx, avifDecoder* decoder) { avifImageTiming timing; avifRGBImage rgb; avifResult rc; if (!image_create_frames(ctx, decoder->imageCount)) { goto decode_fail; } for (size_t i = 0; i < ctx->num_frames; ++i) { rc = avifDecoderNthImage(decoder, i); if (rc != AVIF_RESULT_OK) { goto decode_fail; } avifRGBImageSetDefaults(&rgb, decoder->image); rgb.depth = 8; rgb.format = AVIF_RGB_FORMAT_BGRA; avifRGBImageAllocatePixels(&rgb); rc = avifImageYUVToRGB(decoder->image, &rgb); if (rc != AVIF_RESULT_OK) { goto fail_pixels; } if (!pixmap_create(&ctx->frames[i].pm, rgb.width, rgb.height)) { goto fail_pixels; } rc = avifDecoderNthImageTiming(decoder, i, &timing); if (rc != AVIF_RESULT_OK) { goto fail_pixels; } 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); } return 0; fail_pixels: avifRGBImageFreePixels(&rgb); decode_fail: return -1; } // 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) { image_print_error(ctx, "unable to create av1 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); if (rc != AVIF_RESULT_OK) { image_print_error(ctx, "error decoding av1: %s\n", avifResultToString(rc)); } image_free_frames(ctx); return ldr_fmterror; } swayimg-2.1/src/formats/bmp.c000066400000000000000000000347651455710357200162500ustar00rootroot00000000000000// 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) { image_print_error(ctx, "not enough bmp data"); 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 { image_print_error(ctx, "%d image cannot be masked", bmp->bpp); 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) { image_print_error(ctx, "unexpected end of RLE stream"); 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) { image_print_error(ctx, "unexpected end of RLE stream"); return false; } if (x + rle2 > pm->width || y >= pm->height) { image_print_error(ctx, "pixel position out of bmp image"); 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) { image_print_error(ctx, "color out of bmp palette"); 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) { image_print_error(ctx, "color out of bmp palette"); return false; } if (x + rle1 > pm->width || y >= pm->height) { image_print_error(ctx, "pixel position out of bmp image"); 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) { image_print_error(ctx, "color out of bmp palette"); return false; } if (x + rle1 > pm->width) { image_print_error(ctx, "pixel position out of bmp image"); return false; } for (size_t i = 0; i < rle1; ++i) { pm->data[y * pm->width + x] = palette->table[index[i & 1]]; ++x; } } } } image_print_error(ctx, "RLE decode failed"); 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) { image_print_error(ctx, "not enough data for bitmap image"); 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) { image_print_error(ctx, "color out of bmp palette"); return false; } dst[x] = ARGB_SET_A(0xff) | palette->table[index]; } else { image_print_error( ctx, "color for bmp %dbit images not supported", bmp->bpp); 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)) { image_print_error(ctx, "invalid bmp header"); return ldr_fmterror; } if (bmp->dib_size > hdr->offset) { image_print_error(ctx, "invalid bmp header size"); 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 { image_print_error(ctx, "compression %d not supported", bmp->compression); 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-2.1/src/formats/exr.c000066400000000000000000000251361455710357200162600ustar00rootroot00000000000000// 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 buffer pointer to unpacked data * @return ARGB value of the pixel */ static argb_t decode_pixel(const exr_decode_pipeline_t* decoder, const uint8_t* unpacked) { 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: { // convert half to float 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: 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; } 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) { return rc; } rc = exr_get_scanlines_per_chunk(ectx, 0, &scanlines); if (rc != EXR_ERR_SUCCESS) { return rc; } // 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) { *dst = decode_pixel(&decoder, buffer + i); if (++dst >= pm->data + (pm->width * pm->height)) { break; } } } 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) { return rc; } 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); } } ++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; image_print_error(ctx, "unsupported storage format %d", storage); } done: exr_finish(&exr); if (rc != EXR_ERR_SUCCESS) { image_free_frames(ctx); return ldr_fmterror; } return ldr_success; } swayimg-2.1/src/formats/gif.c000066400000000000000000000101721455710357200162210ustar00rootroot00000000000000// 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]; DGifSavedExtensionToGCB(gif, index, &ctl); if (ctl.DisposalMode == DISPOSE_PREVIOUS && index < ctx->num_frames - 1) { struct pixmap* next = &ctx->frames[index + 1].pm; pixmap_copy(next, 0, 0, &frame->pm, frame->pm.width, frame->pm.height); } for (int y = 0; y < desc->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 (int x = 0; x < desc->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(next, 0, 0, &frame->pm, frame->pm.width, frame->pm.height); } 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) { image_print_error(ctx, "unable to open gif decoder: [%d] %s", err, GifErrorString(err)); return ldr_fmterror; } if (DGifSlurp(gif) != GIF_OK) { image_print_error(ctx, "unable to decode gif image"); 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-2.1/src/formats/heif.c000066400000000000000000000065071455710357200163760ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // HEIF and AVIF formats decoder. // Copyright (C) 2022 Artem Senichev #include "../exif.h" #include "buildcfg.h" #include "loader.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; if (heif_check_filetype(data, size) != heif_filetype_yes_supported) { return ldr_unsupported; } heif = heif_context_alloc(); if (!heif) { image_print_error(ctx, "unable to create heif context"); return ldr_fmterror; } err = heif_context_read_from_memory(heif, data, size, NULL); if (err.code != heif_error_Ok) { goto decode_fail; } err = heif_context_get_primary_image_handle(heif, &pih); if (err.code != heif_error_Ok) { goto decode_fail; } err = heif_decode_image(pih, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, NULL); if (err.code != heif_error_Ok) { goto decode_fail; } decoded = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride); if (!decoded) { err.message = "no decoded data"; goto decode_fail; } pm = image_allocate_frame(ctx, heif_image_get_primary_width(img), heif_image_get_primary_height(img)); if (!pm) { goto alloc_fail; } // 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] = ARGB_SET_ABGR(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 heif_image_release(img); heif_image_handle_release(pih); heif_context_free(heif); return ldr_success; decode_fail: image_print_error(ctx, "heif decode failed: %s", err.message); alloc_fail: if (img) { heif_image_release(img); } if (pih) { heif_image_handle_release(pih); } heif_context_free(heif); return ldr_fmterror; } swayimg-2.1/src/formats/jpeg.c000066400000000000000000000055551455710357200164120ustar00rootroot00000000000000// 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); image_print_error(err->img, "failed to decode jpeg: %s", 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] = (0xff << 24) | src << 16 | 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] = (0xff << 24) | src[0] << 16 | 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-2.1/src/formats/jxl.c000066400000000000000000000102021455710357200162430ustar00rootroot00000000000000// 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; JxlDecoderStatus status; size_t buffer_sz; struct pixmap* pm = NULL; 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) { image_print_error(ctx, "unable to create jpeg xl decoder"); return ldr_fmterror; } status = JxlDecoderSetInput(jxl, data, size); if (status != JXL_DEC_SUCCESS) { image_print_error(ctx, "unable to set jpeg xl buffer: error %d", status); goto fail; } // process decoding status = JxlDecoderSubscribeEvents(jxl, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); if (status != JXL_DEC_SUCCESS) { image_print_error(ctx, "jpeg xl event subscription failed"); goto fail; } do { JxlDecoderStatus rc; status = JxlDecoderProcessInput(jxl); switch (status) { case JXL_DEC_SUCCESS: break; // decoding complete case JXL_DEC_ERROR: image_print_error(ctx, "failed to decode jpeg xl"); goto fail; case JXL_DEC_BASIC_INFO: rc = JxlDecoderGetBasicInfo(jxl, &info); if (rc != JXL_DEC_SUCCESS) { image_print_error( ctx, "unable to get jpeg xl info: error %d", rc); goto fail; } break; case JXL_DEC_FULL_IMAGE: break; // frame decoded, nothing to do case JXL_DEC_NEED_IMAGE_OUT_BUFFER: // get image buffer size rc = JxlDecoderImageOutBufferSize(jxl, &jxl_format, &buffer_sz); if (rc != JXL_DEC_SUCCESS) { image_print_error( ctx, "unable to get jpeg xl buffer: error %d", rc); goto fail; } pm = image_allocate_frame(ctx, info.xsize, info.ysize); if (!pm) { goto fail; } // check buffer format if (buffer_sz != pm->width * pm->height * sizeof(argb_t)) { image_print_error(ctx, "unsupported jpeg xl buffer format"); goto fail; } // set output buffer rc = JxlDecoderSetImageOutBuffer(jxl, &jxl_format, pm->data, buffer_sz); if (rc != JXL_DEC_SUCCESS) { image_print_error( ctx, "unable to set jpeg xl buffer: error %d", rc); goto fail; } break; default: image_print_error(ctx, "unexpected jpeg xl status %d", status); } } while (status != JXL_DEC_SUCCESS); if (!pm) { image_print_error(ctx, "jxl frame is empty"); goto fail; } // convert ABGR -> ARGB for (size_t i = 0; i < pm->width * pm->height; ++i) { pm->data[i] = ARGB_SET_ABGR(pm->data[i]); } 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-2.1/src/formats/loader.c000066400000000000000000000065061455710357200167300ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image loader: interface and common framework for decoding images. // Copyright (C) 2022 Artem Senichev #include "loader.h" #include "buildcfg.h" #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" #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); #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 }; enum loader_status load_image(struct image* ctx, const uint8_t* data, size_t size) { enum loader_status status = ldr_unsupported; for (size_t i = 0; i < sizeof(decoders) / sizeof(decoders[0]); ++i) { switch (decoders[i](ctx, data, size)) { case ldr_success: return ldr_success; case ldr_unsupported: break; case ldr_fmterror: status = ldr_fmterror; break; } } return status; } void image_print_error(const struct image* ctx, const char* fmt, ...) { va_list args; if (ctx) { fprintf(stderr, "%s: ", ctx->file_name); } va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); } swayimg-2.1/src/formats/loader.h000066400000000000000000000025441455710357200167330ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image loader: interface and common framework for decoding images. // Copyright (C) 2022 Artem Senichev #pragma once #include "../image.h" /** Loader status. */ enum loader_status { ldr_success, ///< Image was decoded successfully ldr_unsupported, ///< Signature not recognized by any decoder ldr_fmterror ///< Decoder found, but data has invalid format }; /** Contains string with the names of the supported image formats. */ extern const char* supported_formats; /** * Image loader function prototype, implemented by decoders. * @param ctx image context * @param data raw image data * @param size size of image data in bytes * @return loader status */ typedef enum loader_status (*image_decoder)(struct image* ctx, const uint8_t* data, size_t size); /** * Load image from memory buffer. * @param ctx image context * @param data raw image data * @param size size of image data in bytes * @return loader status */ enum loader_status load_image(struct image* ctx, const uint8_t* data, size_t size); /** * Print decoding problem description. * @param ctx image context * @param fmt text format */ void image_print_error(const struct image* ctx, const char* fmt, ...) __attribute__((format(printf, 2, 3))); swayimg-2.1/src/formats/png.c000066400000000000000000000210111455710357200162320ustar00rootroot00000000000000// 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 decode buffer. * @param buffer png buffer to reallocate * @param pm pixmap to bind * @return false if decode failed */ static bool bind_pixmap(png_bytep** buffer, const struct pixmap* pm) { png_bytep* ptr = realloc(*buffer, pm->height * sizeof(png_bytep)); if (!ptr) { return false; } *buffer = ptr; for (uint32_t i = 0; i < pm->height; ++i) { ptr[i] = (png_bytep)&pm->data[i * pm->width]; } return true; } /** * 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* rdrows = NULL; if (!pm) { return false; } if (!bind_pixmap(&rdrows, pm)) { return false; } if (setjmp(png_jmpbuf(png))) { free(rdrows); return false; } png_read_image(png, rdrows); free(rdrows); 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) { bool rc = false; struct image_frame* frame = &ctx->frames[index]; png_byte dispose = 0, blend = 0; png_uint_16 delay_num = 0, delay_den = 0; png_uint_32 x = 0, y = 0, width = 0, height = 0; png_bytep* rdrows = NULL; struct pixmap frame_pm = { 0, 0, NULL }; if (setjmp(png_jmpbuf(png))) { goto done; } // 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, &x, &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; } frame->duration = (float)delay_num * 1000 / delay_den; // decode frame into pixmap if (!pixmap_create(&frame_pm, width, height) || !bind_pixmap(&rdrows, &frame_pm)) { goto done; } png_read_image(png, rdrows); // 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(next, 0, 0, &frame->pm, frame->pm.width, frame->pm.height); } } // put frame on final pixmap switch (blend) { case PNG_BLEND_OP_SOURCE: pixmap_copy(&frame->pm, x, y, &frame_pm, frame_pm.width, frame_pm.height); break; case PNG_BLEND_OP_OVER: pixmap_over(&frame->pm, x, y, &frame_pm, frame_pm.width, frame_pm.height); break; } // handle dispose if (dispose == PNG_DISPOSE_OP_NONE && index + 1 < ctx->num_frames) { struct pixmap* next = &ctx->frames[index + 1].pm; pixmap_copy(next, 0, 0, &frame->pm, frame->pm.width, frame->pm.height); } rc = true; done: pixmap_free(&frame_pm); free(rdrows); return rc; } /** * 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) { image_print_error(ctx, "unable to initialize png decoder"); return ldr_fmterror; } info = png_create_info_struct(png); if (!info) { image_print_error(ctx, "unable to create png object"); 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); image_print_error(ctx, "failed to decode png"); 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-2.1/src/formats/pnm.c000066400000000000000000000230531455710357200162500ustar00rootroot00000000000000// 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 /** * Return string for error conditions above * @param err error code * @return string representing that error code */ static const char* pnm_strerror(int err) { switch (err) { case PNM_EEOF: return "unexpected end of image"; case PNM_ERNG: return "integer too large"; case PNM_EFMT: return "digit expected"; case PNM_EOVF: return "pixel value above maxval"; default: return "unknown error"; } } // 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) { image_print_error(ctx, "%s", pnm_strerror(width)); return ldr_fmterror; } height = pnm_readint(&it, 0); if (height < 0) { image_print_error(ctx, "%s", pnm_strerror(height)); return ldr_fmterror; } if (type == pnm_pbm) { maxval = 1; } else { maxval = pnm_readint(&it, 0); if (maxval < 0) { image_print_error(ctx, "%s", pnm_strerror(maxval)); return ldr_fmterror; } if (!maxval || maxval > UINT16_MAX) { image_print_error(ctx, "invalid maxval"); 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') { image_print_error(ctx, "single whitespace character expected"); 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_print_error(ctx, "%s", pnm_strerror(ret)); 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-2.1/src/formats/svg.c000066400000000000000000000102101455710357200162440ustar00rootroot00000000000000// 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) { image_print_error(ctx, "invalid svg format: %s", err && err->message ? err->message : "unknown error"); 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) { const char* desc = cairo_status_to_string(status); image_print_error(ctx, "unable to create cairo surface: %s", desc ? desc : "unknown error"); goto fail; } // render svg to surface cr = cairo_create(surface); if (!rsvg_handle_render_document(svg, cr, &vb_render, &err)) { image_print_error(ctx, "unable to decode svg: %s", err && err->message ? err->message : "unknown error"); 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-2.1/src/formats/tiff.c000066400000000000000000000077041455710357200164130ustar00rootroot00000000000000// 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) { image_print_error(ctx, "unable to open tiff decoder"); return ldr_fmterror; } *err = 0; if (!TIFFRGBAImageBegin(&timg, tiff, 0, err)) { image_print_error(ctx, "unable to initialize tiff decoder: %s", 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)) { image_print_error(ctx, "unable to decode tiff"); goto fail; } // convert ABGR -> ARGB for (size_t i = 0; i < pm->width * pm->height; ++i) { pm->data[i] = ARGB_SET_ABGR(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-2.1/src/formats/webp.c000066400000000000000000000061271455710357200164160ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // WebP format decoder. // Copyright (C) 2020 Artem Senichev #include "../exif.h" #include "buildcfg.h" #include "loader.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) { image_print_error(ctx, "unable to get webp properties"); return ldr_fmterror; } // open decoder WebPAnimDecoderOptionsInit(&webp_opts); webp_opts.color_mode = MODE_BGRA; webp_dec = WebPAnimDecoderNew(&raw, &webp_opts); if (!webp_dec) { image_print_error(ctx, "unable to decode webp image"); goto fail; } if (!WebPAnimDecoderGetInfo(webp_dec, &webp_info)) { image_print_error(ctx, "unable to get 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)) { image_print_error(ctx, "failed to decode webp frame"); 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-2.1/src/image.c000066400000000000000000000136201455710357200150640ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image instance: pixel data and meta info. // Copyright (C) 2021 Artem Senichev #include "image.h" #include "buildcfg.h" #include "exif.h" #include "formats/loader.h" #include #include #include #include #include #include #include #include #include /** * Create image instance from memory buffer. * @param path path to the image * @param data raw image data * @param size size of image data in bytes * @return image instance or NULL on errors */ static struct image* image_create(const char* path, const uint8_t* data, size_t size) { struct image* ctx; enum loader_status status; ctx = calloc(1, sizeof(*ctx)); if (!ctx) { fprintf(stderr, "Not enough memory\n"); return NULL; } // save common file info ctx->file_path = path; ctx->file_name = strrchr(path, '/'); if (!ctx->file_name) { ctx->file_name = path; } else { ++ctx->file_name; // skip slash } ctx->file_size = size; // decode image status = load_image(ctx, data, size); if (status != ldr_success) { if (status == ldr_unsupported) { image_print_error(ctx, "unsupported format"); } image_free(ctx); return NULL; } #ifdef HAVE_LIBEXIF process_exif(ctx, data, size); #endif return ctx; } struct image* image_from_file(const char* file) { struct image* ctx = NULL; void* data = MAP_FAILED; struct stat st; int fd; // open file fd = open(file, O_RDONLY); if (fd == -1) { fprintf(stderr, "%s: %s\n", file, strerror(errno)); goto done; } // get file size if (fstat(fd, &st) == -1) { fprintf(stderr, "%s: %s\n", file, strerror(errno)); goto done; } // map file to memory data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (data == MAP_FAILED) { fprintf(stderr, "%s: %s\n", file, strerror(errno)); goto done; } ctx = image_create(file, data, st.st_size); done: if (data != MAP_FAILED) { munmap(data, st.st_size); } if (fd != -1) { close(fd); } return ctx; } struct image* image_from_stdin(void) { struct image* ctx = NULL; uint8_t* data = NULL; size_t size = 0; size_t capacity = 0; while (true) { if (size == capacity) { const size_t new_capacity = capacity + 256 * 1024; uint8_t* new_buf = realloc(data, new_capacity); if (!new_buf) { fprintf(stderr, "Not enough memory\n"); goto done; } data = new_buf; capacity = new_capacity; } const ssize_t rc = read(STDIN_FILENO, data + size, capacity - size); if (rc == 0) { break; } if (rc == -1 && errno != EAGAIN) { perror("Error reading stdin"); goto done; } size += rc; } if (data) { ctx = image_create("{STDIN}", data, size); } done: free(data); return ctx; } void image_free(struct image* ctx) { if (ctx) { image_free_frames(ctx); 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_set_format(struct image* ctx, const char* fmt, ...) { va_list args; int len; char* buffer; va_start(args, fmt); 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); 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; } else { image_print_error(ctx, "not enough memory"); } 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-2.1/src/image.h000066400000000000000000000061001455710357200150640ustar00rootroot00000000000000// 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 { const char* file_path; ///< Full path to the image file const char* file_name; ///< File 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 }; /** * Load image from file. * @param file path to the file to load * @return image context or NULL on errors */ struct image* image_from_file(const char* file); /** * Load image from stdin data. * @return image context or NULL on errors */ struct image* image_from_stdin(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); /** * 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 sinlge 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-2.1/src/imagelist.c000066400000000000000000000417571455710357200157740ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // List of images. // Copyright (C) 2022 Artem Senichev #include "imagelist.h" #include "buildcfg.h" #include "config.h" #include "str.h" #include "ui.h" #include "viewer.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_INOTIFY #include #endif /** Number of entries added on reallocation. */ #define ALLOCATE_SIZE 32 /** Name used for image, that is read from stdin through pipe. */ #define STDIN_FILE_NAME "*stdin*" /** Single list entry. */ struct entry { size_t index; ///< Entry index in the list char path[1]; ///< Path to the file, any size array }; /** Image list context. */ struct image_list { struct entry** entries; ///< Array of entries size_t alloc; ///< Number of allocated entries (size of array) size_t size; ///< Number of files in array size_t index; ///< Index of the current file entry struct image* prev; ///< Previous image handle (cached) struct image* current; ///< Current image handle struct image* next; ///< Next image handle (preload) pthread_t preloader; ///< Preload thread 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 #ifdef HAVE_INOTIFY int notify; ///< inotify file handler int watch; ///< Current file watcher #endif // HAVE_INOTIFY }; static struct image_list ctx = { .order = order_alpha, .loop = true, .recursive = false, .all_files = true, #ifdef HAVE_INOTIFY .notify = -1, .watch = -1, #endif // HAVE_INOTIFY }; /** Order names. */ static const char* order_names[] = { [order_none] = "none", [order_alpha] = "alpha", [order_random] = "random", }; /** * Add file to the list. * @param file path to the file */ static void add_file(const char* file) { struct entry* entry; size_t entry_sz; size_t path_len = strlen(file); // remove "./" from the start if (file[0] == '.' && file[1] == '/') { file += 2; path_len -= 2; } // search for duplicates for (size_t i = 0; i < ctx.size; ++i) { if (strcmp(ctx.entries[i]->path, file) == 0) { return; } } // relocate array, if needed if (ctx.index >= ctx.alloc) { const size_t num = ctx.alloc + ALLOCATE_SIZE; struct entry** ptr = realloc(ctx.entries, num * sizeof(struct entry*)); if (!ptr) { return; } ctx.alloc = num; ctx.entries = ptr; } // add new entry entry_sz = sizeof(struct entry) + path_len; entry = malloc(entry_sz); if (entry) { memcpy(entry->path, file, path_len + 1); entry->index = ctx.index; ctx.entries[ctx.index] = entry; ++ctx.index; ++ctx.size; } } /** * 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) { strcpy(path, dir); strcat(path, "/"); strcat(path, dir_entry->d_name); if (stat(path, &file_stat) == 0) { if (S_ISDIR(file_stat.st_mode)) { if (recursive) { add_dir(path, recursive); } } else if (file_stat.st_size) { add_file(path); } } free(path); } } closedir(dir_handle); } /** * Peek next file entry. * @param start index of the start position * @param forward step direction * @return index of the next entry or SIZE_MAX if not found */ static size_t peek_next_file(size_t start, bool forward) { size_t index = start; 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.entries[index]) { return index; } } return SIZE_MAX; } /** * Peek next directory entry. * @param file path to extarct source directory * @param start index of the start position * @param forward step direction * @return index of the next entry or SIZE_MAX if not found */ static size_t peek_next_dir(const char* file, size_t start, bool forward) { const char* cur_path = file; size_t cur_len; size_t index = start; // 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 = peek_next_file(index, forward); if (index == SIZE_MAX || index == start) { break; // not found } next_path = ctx.entries[index]->path; 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 SIZE_MAX; } /** * Peek first/last entry. * @param first direction, true=first, false=last * @return index of the next entry or SIZE_MAX if not found */ static size_t peek_edge(bool first) { size_t index = first ? 0 : ctx.size - 1; if (index == ctx.index || ctx.entries[index]) { return index; } return peek_next_file(ctx.index, first); } /** * Sort the image list alphabetically. */ static void sort_list(void) { for (size_t i = 0; i < ctx.size; ++i) { for (size_t j = i + 1; j < ctx.size; ++j) { if (strcoll(ctx.entries[i]->path, ctx.entries[j]->path) > 0) { struct entry* swap = ctx.entries[i]; ctx.entries[i] = ctx.entries[j]; ctx.entries[j] = swap; } } ctx.entries[i]->index = i; } } /** * 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) { struct entry* swap = ctx.entries[i]; ctx.entries[i] = ctx.entries[j]; ctx.entries[j] = swap; } } } /** Image preloader executed in background thread. */ static void* preloader_thread(__attribute__((unused)) void* data) { size_t index = ctx.index; while (true) { struct image* img; index = peek_next_file(index, true); if (index == SIZE_MAX) { break; // next image not found } if ((ctx.next && ctx.next->file_path == ctx.entries[index]->path) || (ctx.prev && ctx.prev->file_path == ctx.entries[index]->path)) { break; // already loaded } pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); img = image_from_file(ctx.entries[index]->path); if (img) { image_free(ctx.next); ctx.next = img; break; } else { // not an image, remove entry from list free(ctx.entries[index]); ctx.entries[index] = NULL; } pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); } return NULL; } /** * Stop or restart background thread to preload adjacent images. * @param restart action: true=restart, false=stop preloader */ static void preloader_ctl(bool restart) { if (ctx.preloader) { pthread_cancel(ctx.preloader); pthread_join(ctx.preloader, NULL); ctx.preloader = 0; } if (restart) { pthread_create(&ctx.preloader, NULL, preloader_thread, NULL); } } #ifdef HAVE_INOTIFY /** * Register watcher for current file. */ static void watch_current(void) { if (ctx.notify >= 0) { if (ctx.watch != -1) { inotify_rm_watch(ctx.notify, ctx.watch); ctx.watch = -1; } ctx.watch = inotify_add_watch(ctx.notify, ctx.current->file_path, IN_CLOSE_WRITE | IN_MOVE_SELF); } } /** * Notify handler. */ static void on_notify(void) { 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) { viewer_reset(); } } } #endif // HAVE_INOTIFY /** * 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_init(void) { // register configuration loader config_add_loader(IMGLIST_CFG_SECTION, load_config); #ifdef HAVE_INOTIFY ctx.notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); if (ctx.notify >= 0) { ui_add_event(ctx.notify, on_notify); } #endif // HAVE_INOTIFY } bool image_list_scan(const char** files, size_t num) { const char* force_start = NULL; if (num == 0) { // no input files specified, use all from the current directory add_dir(".", ctx.recursive); } else if (num == 1 && strcmp(files[0], "-") == 0) { // pipe mode add_file(STDIN_FILE_NAME); force_start = STDIN_FILE_NAME; } else { for (size_t i = 0; i < num; ++i) { struct stat file_stat; if (stat(files[i], &file_stat) == -1) { fprintf(stderr, "%s: [%i] %s\n", files[i], errno, strerror(errno)); } else { if (S_ISDIR(file_stat.st_mode)) { add_dir(files[i], ctx.recursive); } else { if (!ctx.all_files) { add_file(files[i]); } else { // add all files from the same directory const char* delim = strrchr(files[i], '/'); const size_t len = delim ? delim - files[i] : 0; if (len == 0) { add_dir(".", ctx.recursive); } else { char* dir = malloc(len + 1); if (dir) { memcpy(dir, files[i], len); dir[len] = 0; add_dir(dir, ctx.recursive); free(dir); } } if (!force_start) { force_start = files[i]; } } } } } } if (ctx.size == 0) { // empty list image_list_free(); return false; } // sort or shuffle if (ctx.order == order_alpha) { sort_list(); } else if (ctx.order == order_random) { shuffle_list(); } // set initial index if (!force_start) { ctx.index = 0; } else { if (force_start[0] == '.' && force_start[1] == '/') { force_start += 2; } for (size_t i = 0; i < ctx.size; ++i) { if (strcmp(ctx.entries[i]->path, force_start) == 0) { ctx.index = i; break; } } } // load the first image if (strcmp(ctx.entries[ctx.index]->path, STDIN_FILE_NAME) == 0) { ctx.current = image_from_stdin(); } else { ctx.current = image_from_file(ctx.entries[ctx.index]->path); } if (!ctx.current && ((force_start && num == 1) || !image_list_jump(jump_next_file))) { image_list_free(); return false; } preloader_ctl(true); #ifdef HAVE_INOTIFY if (ctx.watch == -1) { watch_current(); } #endif // HAVE_INOTIFY return true; } void image_list_free(void) { preloader_ctl(false); for (size_t i = 0; i < ctx.size; ++i) { free(ctx.entries[i]); } free(ctx.entries); image_free(ctx.prev); image_free(ctx.current); image_free(ctx.next); memset(&ctx, 0, sizeof(ctx)); } size_t image_list_size(void) { return ctx.size; } struct image_entry image_list_current(void) { struct image_entry entry = { .index = ctx.index, .image = ctx.current }; return entry; } bool image_list_reset(void) { // reset cache preloader_ctl(false); if (ctx.prev) { image_free(ctx.prev); ctx.prev = NULL; } if (ctx.next) { image_free(ctx.next); ctx.next = NULL; } // reload current image image_free(ctx.current); ctx.current = image_from_file(ctx.entries[ctx.index]->path); if (ctx.current) { preloader_ctl(true); return true; } // open nearest image return image_list_jump(jump_next_file) || image_list_jump(jump_prev_file); } bool image_list_jump(enum list_jump jump) { struct image* image = NULL; size_t index = ctx.index; preloader_ctl(false); while (!image) { switch (jump) { case jump_first_file: index = peek_edge(true); break; case jump_last_file: index = peek_edge(false); break; case jump_next_file: index = peek_next_file(index, true); break; case jump_prev_file: index = peek_next_file(index, false); break; case jump_next_dir: index = peek_next_dir(ctx.entries[ctx.index]->path, index, true); break; case jump_prev_dir: index = peek_next_dir(ctx.entries[ctx.index]->path, index, false); break; } if (index == SIZE_MAX) { return false; } if (ctx.next && ctx.next->file_path == ctx.entries[index]->path) { image = ctx.next; ctx.next = NULL; } else if (ctx.prev && ctx.prev->file_path == ctx.entries[index]->path) { image = ctx.prev; ctx.prev = NULL; } else { image = image_from_file(ctx.entries[index]->path); if (!image) { // not an image, remove entry from list free(ctx.entries[index]); ctx.entries[index] = NULL; } } } image_free(ctx.prev); ctx.prev = ctx.current; ctx.current = image; ctx.index = index; preloader_ctl(true); #ifdef HAVE_INOTIFY watch_current(); #endif // HAVE_INOTIFY return true; } swayimg-2.1/src/imagelist.h000066400000000000000000000033331455710357200157650ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // List of images. // Copyright (C) 2022 Artem Senichev #pragma once #include "image.h" // 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" /** Image entry. */ struct image_entry { size_t index; ///< Entry index in the list struct image* image; ///< Image handle }; /** Order of file list. */ enum list_order { order_none, ///< Unsorted (system depended) order_alpha, ///< Alphanumeric sort order_random ///< Random order }; /** Movement directions. */ enum list_jump { jump_first_file, jump_last_file, jump_next_file, jump_prev_file, jump_next_dir, jump_prev_dir }; /** * Initialize the image list. */ void image_list_init(void); /** * Free the image list. */ void image_list_free(void); /** * Scan directories and fill the image list. * @param files list of input files * @param num number of files in the file list * @return false if no one images loaded */ bool image_list_scan(const char** files, 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 current entry in the image list. * @return current entry description */ struct image_entry image_list_current(void); /** * Reset cache and reload current image. * @return false if reset failed (no more images) */ bool image_list_reset(void); /** * Move through image list. * @param jump position to set * @return false if iterator can not be moved */ bool image_list_jump(enum list_jump jump); swayimg-2.1/src/info.c000066400000000000000000000341161455710357200147400ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image info: text blocks with image meta data. // Copyright (C) 2023 Artem Senichev #include "info.h" #include "canvas.h" #include "config.h" #include "imagelist.h" #include "str.h" #include #include #include #include // clang-format off // Section name in the config file #define CONFIG_SECTION "info" /** Display modes. */ enum info_mode { info_mode_full, info_mode_brief, info_mode_off, }; static const char* mode_names[] = { [info_mode_full] = "full", [info_mode_brief] = "brief", [info_mode_off] = "off", }; #define MODES_NUM 2 /** Available 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, }; #define INFO_FIELDS_NUM 10 /** 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", }; /** Block position names. */ static const char* position_names[] = { [info_top_left] = "topleft", [info_top_right] = "topright", [info_bottom_left] = "bottomleft", [info_bottom_right] = "bottomright", }; // Defaults static const enum info_field default_full_top_left[] = { info_file_name, info_image_format, info_file_size, info_image_size, info_exif, }; static const enum info_field default_full_top_right[] = { info_index, }; static const enum info_field default_full_bottom_left[] = { info_scale, info_frame, }; static const enum info_field default_bottom_right[] = { info_status, }; static const enum info_field default_brief_top_left[] = { info_index, }; // clang-format on #define SET_DEFAULT(m, p, d) \ ctx.blocks[m][p].scheme_sz = sizeof(d) / sizeof(d[0]); \ ctx.blocks[m][p].scheme = malloc(sizeof(d)); \ memcpy(ctx.blocks[m][p].scheme, d, sizeof(d)) /** Single info block. */ struct info_block { struct info_line* lines; enum info_field* scheme; size_t scheme_sz; }; /** Info data context. */ struct info_context { enum info_mode mode; const char* file; struct info_line* exif_lines; size_t exif_num; size_t frame; size_t frame_total; size_t index; size_t width; size_t height; size_t scale; struct info_line fields[INFO_FIELDS_NUM]; struct info_block blocks[MODES_NUM][INFO_POSITION_NUM]; }; static struct info_context ctx; /** * Check if field is visible. * @param field field to check * @return true if field is visible */ static bool is_visible(enum info_field field) { if (ctx.mode == info_mode_off) { return false; } for (size_t i = 0; i < INFO_POSITION_NUM; ++i) { const struct info_block* block = &ctx.blocks[ctx.mode][i]; for (size_t j = 0; j < block->scheme_sz; ++j) { if (field == block->scheme[j]) { return true; } } } return false; } /** * Update field. * @param text text to set * @param surface field's surface */ static void update_field(const char* text, struct text_surface* surface) { if (surface->data) { free(surface->data); surface->data = NULL; } font_render(text, surface); } /** * Import meta data from image. * @param image source image */ static void import_exif(const struct image* image) { struct info_line* line; 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, image->num_info * sizeof(struct info_line)); if (!line) { return; } 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; } } /** * Custom section loader, see `config_loader` for details. */ static enum config_status load_config(const char* key, const char* value) { enum info_mode mode; struct info_block* block = NULL; struct str_slice slices[INFO_FIELDS_NUM]; size_t num_slices; enum info_field scheme[INFO_FIELDS_NUM]; size_t scheme_sz = 0; ssize_t index; if (strcmp(key, "mode") == 0) { index = str_index(mode_names, value, 0); if (index < 0) { return cfgst_invalid_value; } ctx.mode = index; return cfgst_ok; } // parse key (mode.position) if (str_split(key, '.', slices, 2) != 2) { return cfgst_invalid_key; } // get mode index = str_search_index(mode_names, MODES_NUM, slices[0].value, slices[0].len); if (index < 0) { return cfgst_invalid_value; } mode = index; // get position and its block index = str_index(position_names, slices[1].value, slices[1].len); if (index < 0) { return cfgst_invalid_value; } block = &ctx.blocks[mode][index]; // split into list fileds num_slices = str_split(value, ',', slices, sizeof(slices) / sizeof(slices[0])); if (num_slices > sizeof(slices) / sizeof(slices[0])) { num_slices = sizeof(slices) / sizeof(slices[0]); } for (size_t i = 0; i < num_slices; ++i) { index = str_index(field_names, slices[i].value, slices[i].len); if (index >= 0) { scheme[scheme_sz++] = index; } else { if (slices[i].len == 0 || (slices[i].len == 4 && strncmp(slices[i].value, "none", 4) == 0)) { continue; // skip empty fields } return cfgst_invalid_value; } } // set new scheme if (scheme_sz) { block->scheme = realloc(block->scheme, scheme_sz * sizeof(enum info_field)); memcpy(block->scheme, scheme, scheme_sz * sizeof(enum info_field)); } else { free(block->scheme); block->scheme = NULL; } block->scheme_sz = scheme_sz; return cfgst_ok; } void info_create(void) { // set defaults ctx.mode = info_mode_full; ctx.frame = UINT32_MAX; ctx.index = UINT32_MAX; SET_DEFAULT(info_mode_full, info_top_left, default_full_top_left); SET_DEFAULT(info_mode_full, info_top_right, default_full_top_right); SET_DEFAULT(info_mode_full, info_bottom_left, default_full_bottom_left); SET_DEFAULT(info_mode_full, info_bottom_right, default_bottom_right); SET_DEFAULT(info_mode_brief, info_top_left, default_brief_top_left); SET_DEFAULT(info_mode_brief, info_bottom_right, default_bottom_right); // register configuration loader config_add_loader(CONFIG_SECTION, load_config); } void info_init(void) { update_field("File name:", &ctx.fields[info_file_name].key); update_field("File path:", &ctx.fields[info_file_path].key); update_field("File size:", &ctx.fields[info_file_size].key); update_field("Image format:", &ctx.fields[info_image_format].key); update_field("Image size:", &ctx.fields[info_image_size].key); } void info_free(void) { 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.blocks[i][j].lines); free(ctx.blocks[i][j].scheme); } } for (size_t i = 0; i < INFO_FIELDS_NUM; ++i) { free(ctx.fields[i].key.data); free(ctx.fields[i].value.data); } } void info_set_mode(const char* mode) { // reset state to force refresh ctx.file = NULL; ctx.index = UINT32_MAX; ctx.frame = UINT32_MAX; if (mode && *mode) { const size_t num_modes = sizeof(mode_names) / sizeof(mode_names[0]); for (size_t i = 0; i < num_modes; ++i) { if (strcmp(mode, mode_names[i]) == 0) { ctx.mode = i; return; } } } ++ctx.mode; if (ctx.mode > info_mode_off) { ctx.mode = 0; } } void info_update(size_t frame_idx) { const struct image_entry entry = image_list_current(); const struct image* image = entry.image; char buffer[64]; if (ctx.file != image->file_path) { if (is_visible(info_file_name)) { update_field(image->file_name, &ctx.fields[info_file_name].value); } if (is_visible(info_file_path)) { update_field(image->file_path, &ctx.fields[info_file_path].value); } if (is_visible(info_file_size)) { 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); snprintf(buffer, sizeof(buffer), "%.02f %ciB", sz, unit); update_field(buffer, &ctx.fields[info_file_size].value); } if (is_visible(info_image_format)) { update_field(image->format, &ctx.fields[info_image_format].value); } if (is_visible(info_exif)) { import_exif(image); } ctx.frame = UINT32_MAX; // force refresh frame info ctx.file = image->file_path; } if (is_visible(info_frame) && (ctx.frame != frame_idx || ctx.frame_total != image->num_frames)) { ctx.frame = frame_idx; ctx.frame_total = image->num_frames; snprintf(buffer, sizeof(buffer), "%lu of %lu", ctx.frame + 1, ctx.frame_total); update_field(buffer, &ctx.fields[info_frame].value); } if (is_visible(info_index) && ctx.index != entry.index) { ctx.index = entry.index; snprintf(buffer, sizeof(buffer), "%lu of %lu", ctx.index + 1, image_list_size()); update_field(buffer, &ctx.fields[info_index].value); } if (is_visible(info_scale)) { const size_t scale_percent = canvas_get_scale() * 100; if (ctx.scale != scale_percent) { ctx.scale = scale_percent; snprintf(buffer, sizeof(buffer), "%ld%%", ctx.scale); update_field(buffer, &ctx.fields[info_scale].value); } } if (is_visible(info_image_size)) { const struct pixmap* pm = &image->frames[frame_idx].pm; if (ctx.width != pm->width || ctx.height != pm->height) { ctx.width = pm->width; ctx.height = pm->height; snprintf(buffer, sizeof(buffer), "%lux%lu", ctx.width, ctx.height); update_field(buffer, &ctx.fields[info_image_size].value); } } } void info_set_status(const char* fmt, ...) { struct text_surface* surface = &ctx.fields[info_status].value; free(surface->data); surface->data = NULL; if (fmt) { va_list args; int len; void* buffer; va_start(args, fmt); len = vsnprintf(NULL, 0, fmt, args); va_end(args); if (len <= 0) { return; } buffer = malloc(len + 1 /* last null */); if (!buffer) { return; } va_start(args, fmt); vsprintf(buffer, fmt, args); va_end(args); update_field(buffer, surface); free(buffer); } } size_t info_height(enum info_position pos) { const struct info_block* block; size_t lines_num; if (ctx.mode == info_mode_off) { return 0; } block = &ctx.blocks[ctx.mode][pos]; lines_num = block->scheme_sz; for (size_t i = 0; i < block->scheme_sz; ++i) { switch (block->scheme[i]) { case info_exif: --lines_num; lines_num += ctx.exif_num; break; case info_frame: if (ctx.frame_total == 1) { --lines_num; } break; case info_status: if (!ctx.fields[info_status].value.data) { --lines_num; } break; case info_index: if (image_list_size() == 1) { --lines_num; } break; default: break; } } return lines_num; } const struct info_line* info_lines(enum info_position pos) { const size_t lines_num = info_height(pos); struct info_block* block; struct info_line* line; if (ctx.mode == info_mode_off) { return 0; } block = &ctx.blocks[ctx.mode][pos]; line = realloc(block->lines, lines_num * sizeof(struct info_line)); if (!line) { return NULL; } block->lines = line; for (size_t i = 0; i < block->scheme_sz; ++i) { switch (block->scheme[i]) { case info_exif: memcpy(line, ctx.exif_lines, ctx.exif_num * sizeof(*line)); line += ctx.exif_num; break; case info_frame: if (ctx.frame_total != 1) { memcpy(line, &ctx.fields[block->scheme[i]], sizeof(*line)); ++line; } break; case info_status: if (ctx.fields[info_status].value.data) { memcpy(line, &ctx.fields[block->scheme[i]], sizeof(*line)); ++line; } break; case info_index: if (image_list_size() > 1) { memcpy(line, &ctx.fields[block->scheme[i]], sizeof(*line)); ++line; } break; default: memcpy(line, &ctx.fields[block->scheme[i]], sizeof(*line)); ++line; break; } } return block->lines; } swayimg-2.1/src/info.h000066400000000000000000000024401455710357200147400ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Image info: text blocks with image meta data. // Copyright (C) 2023 Artem Senichev #pragma once #include "font.h" /** Info block position. */ enum info_position { info_top_left, info_top_right, info_bottom_left, info_bottom_right, }; #define INFO_POSITION_NUM 4 /** Info line. */ struct info_line { struct text_surface key; struct text_surface value; }; /** * Create info context. */ void info_create(void); /** * Initialize info context. */ void info_init(void); /** * Free info context. */ void info_free(void); /** * Set the display mode. * @param mode display mode name */ void info_set_mode(const char* mode); /** * Refresh info data. * @param frame_idx index of the current frame */ void info_update(size_t frame_idx); /** * Set status text. * @param fmt message format description */ void info_set_status(const char* fmt, ...) __attribute__((format(printf, 1, 2))); /** * Get number of lines in the specified block. * @param pos block position * @return number of lines */ size_t info_height(enum info_position pos); /** * Get list of text lines (key->val). * @param pos block position * @return pointer to the lines array */ const struct info_line* info_lines(enum info_position pos); swayimg-2.1/src/keybind.c000066400000000000000000000256621455710357200154400ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Keyboard bindings. // Copyright (C) 2023 Artem Senichev #include "keybind.h" #include "config.h" #include "str.h" #include #include #include #include // Section names in the config file #define CONFIG_SECTION_KEYS "keys" #define CONFIG_SECTION_MOUSE "mouse" /** Action names. */ static const char* action_names[] = { [kb_none] = "none", [kb_help] = "help", [kb_first_file] = "first_file", [kb_last_file] = "last_file", [kb_prev_dir] = "prev_dir", [kb_next_dir] = "next_dir", [kb_prev_file] = "prev_file", [kb_next_file] = "next_file", [kb_prev_frame] = "prev_frame", [kb_next_frame] = "next_frame", [kb_animation] = "animation", [kb_slideshow] = "slideshow", [kb_fullscreen] = "fullscreen", [kb_step_left] = "step_left", [kb_step_right] = "step_right", [kb_step_up] = "step_up", [kb_step_down] = "step_down", [kb_zoom] = "zoom", [kb_rotate_left] = "rotate_left", [kb_rotate_right] = "rotate_right", [kb_flip_vertical] = "flip_vertical", [kb_flip_horizontal] = "flip_horizontal", [kb_reload] = "reload", [kb_antialiasing] = "antialiasing", [kb_info] = "info", [kb_exec] = "exec", [kb_exit] = "exit", }; // Default key bindings // clang-format off static const struct key_binding default_bindings[] = { { .key = XKB_KEY_F1, .action = kb_help }, { .key = XKB_KEY_Home, .action = kb_first_file }, { .key = XKB_KEY_End, .action = kb_last_file }, { .key = XKB_KEY_space, .action = kb_next_file }, { .key = XKB_KEY_SunPageDown, .action = kb_next_file }, { .key = XKB_KEY_SunPageUp, .action = kb_prev_file }, { .key = XKB_KEY_d, .action = kb_next_dir }, { .key = XKB_KEY_d, .mods = KEYMOD_SHIFT,.action = kb_prev_dir }, { .key = XKB_KEY_o, .action = kb_next_frame }, { .key = XKB_KEY_o, .mods = KEYMOD_SHIFT,.action = kb_prev_frame }, { .key = XKB_KEY_s, .action = kb_animation }, { .key = XKB_KEY_s, .mods = KEYMOD_SHIFT,.action = kb_slideshow }, { .key = XKB_KEY_f, .action = kb_fullscreen }, { .key = XKB_KEY_Left, .action = kb_step_left }, { .key = XKB_KEY_Right, .action = kb_step_right }, { .key = XKB_KEY_Up, .action = kb_step_up }, { .key = XKB_KEY_Down, .action = kb_step_down }, { .key = XKB_KEY_equal, .action = kb_zoom, .params = "+10" }, { .key = XKB_KEY_plus, .action = kb_zoom, .params = "+10" }, { .key = XKB_KEY_minus, .action = kb_zoom, .params = "-10" }, { .key = XKB_KEY_w, .action = kb_zoom, .params = "width" }, { .key = XKB_KEY_w, .mods = KEYMOD_SHIFT, .action = kb_zoom, .params = "height" }, { .key = XKB_KEY_z, .action = kb_zoom, .params = "fit" }, { .key = XKB_KEY_z, .mods = KEYMOD_SHIFT,.action = kb_zoom, .params = "fill" }, { .key = XKB_KEY_0, .action = kb_zoom, .params = "real" }, { .key = XKB_KEY_BackSpace, .action = kb_zoom, .params = "optimal" }, { .key = XKB_KEY_bracketleft, .action = kb_rotate_left }, { .key = XKB_KEY_bracketright, .action = kb_rotate_right }, { .key = XKB_KEY_m, .action = kb_flip_vertical }, { .key = XKB_KEY_m, .mods = KEYMOD_SHIFT, .action = kb_flip_horizontal }, { .key = XKB_KEY_a, .action = kb_antialiasing }, { .key = XKB_KEY_r, .action = kb_reload }, { .key = XKB_KEY_i, .action = kb_info }, { .key = XKB_KEY_e, .action = kb_exec, .params = "echo \"Image: %\"" }, { .key = XKB_KEY_Escape, .action = kb_exit }, { .key = XKB_KEY_q, .action = kb_exit }, { .key = VKEY_SCROLL_LEFT, .action = kb_step_right, .params = "5" }, { .key = VKEY_SCROLL_RIGHT, .action = kb_step_left, .params = "5" }, { .key = VKEY_SCROLL_UP, .action = kb_step_down, .params = "5" }, { .key = VKEY_SCROLL_DOWN, .action = kb_step_up, .params = "5" }, { .key = VKEY_SCROLL_UP, .mods = KEYMOD_CTRL, .action = kb_zoom, .params = "+10" }, { .key = VKEY_SCROLL_DOWN, .mods = KEYMOD_CTRL, .action = kb_zoom, .params = "-10" }, { .key = VKEY_SCROLL_UP, .mods = KEYMOD_SHIFT, .action = kb_prev_file }, { .key = VKEY_SCROLL_DOWN, .mods = KEYMOD_SHIFT, .action = kb_next_file }, { .key = VKEY_SCROLL_UP, .mods = KEYMOD_ALT, .action = kb_prev_frame }, { .key = VKEY_SCROLL_DOWN, .mods = KEYMOD_ALT, .action = kb_next_frame }, }; // 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" }, }; struct key_binding* key_bindings; size_t key_bindings_size; /** * Set key binding. * @param key keyboard key * @param mods key modifiers (ctrl/alt/shift) * @param action action to set * @param params additional parameters (action specific) */ static void keybind_set(xkb_keysym_t key, uint8_t mods, enum kb_action action, const char* params) { char key_name[32]; struct key_binding* new_binding = NULL; for (size_t i = 0; i < key_bindings_size; ++i) { struct key_binding* binding = &key_bindings[i]; if (binding->key == key && binding->mods == mods) { new_binding = binding; // replace existing break; } else if (binding->action == kb_none && !new_binding) { new_binding = binding; // reuse existing } } if (action == kb_none) { // remove existing binding if (new_binding) { new_binding->action = action; free(new_binding->params); new_binding->params = NULL; free(new_binding->help); new_binding->help = NULL; } return; } if (!new_binding) { // add new (reallocate) const size_t sz = (key_bindings_size + 1) * sizeof(struct key_binding); struct key_binding* bindings = realloc(key_bindings, sz); if (!bindings) { return; } new_binding = &bindings[key_bindings_size]; memset(new_binding, 0, sizeof(*new_binding)); key_bindings = bindings; ++key_bindings_size; } // set new parameters new_binding->key = key; new_binding->mods = mods; new_binding->action = action; if (new_binding->params && (!params || !*params)) { free(new_binding->params); new_binding->params = NULL; } else if (params && *params) { str_dup(params, &new_binding->params); } // construct help description if (xkb_keysym_get_name(key, key_name, sizeof(key_name)) > 0) { if (mods & KEYMOD_CTRL) { str_append("Ctrl+", 0, &new_binding->help); } if (mods & KEYMOD_ALT) { str_append("Alt+", 0, &new_binding->help); } if (mods & KEYMOD_SHIFT) { str_append("Shift+", 0, &new_binding->help); } str_append(key_name, 0, &new_binding->help); str_append(" ", 1, &new_binding->help); str_append(action_names[action], 0, &new_binding->help); if (new_binding->params) { str_append(" ", 1, &new_binding->help); str_append(new_binding->params, 0, &new_binding->help); } } } /** * Get a keysym from its name. * @param key keyboard key * @param mods key modifiers (ctrl/alt/shift) * @return false if name is invalid */ static bool get_key_from_name(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); 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; } } } return (*key != XKB_KEY_NoSymbol); } /** * Custom section loader, see `config_loader` for details. */ static enum config_status load_config(const char* key, const char* value) { enum kb_action action_id = kb_none; size_t action_len; const char* params; xkb_keysym_t keysym; uint8_t mods; const char* action_end; ssize_t index; // get action length action_end = value; while (*action_end && !isspace(*action_end)) { ++action_end; } action_len = action_end - value; // get action id form its name index = str_index(action_names, value, action_len); if (index < 0) { return cfgst_invalid_value; } action_id = index; // get parameters params = action_end; while (*params && isspace(*params)) { ++params; } if (!*params) { params = NULL; } // convert key name to code if (!get_key_from_name(key, &keysym, &mods)) { return cfgst_invalid_key; } keybind_set(keysym, mods, action_id, params); return cfgst_ok; } void keybind_init(void) { // set defaults for (size_t i = 0; i < ARRAY_SIZE(default_bindings); ++i) { const struct key_binding* kb = &default_bindings[i]; keybind_set(kb->key, kb->mods, kb->action, kb->params); } // register configuration loader config_add_loader(CONFIG_SECTION_KEYS, load_config); config_add_loader(CONFIG_SECTION_MOUSE, load_config); } void keybind_free(void) { for (size_t i = 0; i < key_bindings_size; ++i) { free(key_bindings[i].params); free(key_bindings[i].help); } free(key_bindings); } 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; } const struct key_binding* keybind_get(xkb_keysym_t key, uint8_t mods) { // we always use lowercase + Shift modifier key = xkb_keysym_to_lower(key); for (size_t i = 0; i < key_bindings_size; ++i) { struct key_binding* binding = &key_bindings[i]; if (binding->key == key && binding->mods == mods) { return binding; } } return NULL; } swayimg-2.1/src/keybind.h000066400000000000000000000035621455710357200154400ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Keyboard bindings. // Copyright (C) 2023 Artem Senichev #pragma once #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 /** Available actions. */ enum kb_action { kb_none, kb_help, kb_first_file, kb_last_file, kb_prev_dir, kb_next_dir, kb_prev_file, kb_next_file, kb_prev_frame, kb_next_frame, kb_animation, kb_slideshow, kb_fullscreen, kb_step_left, kb_step_right, kb_step_up, kb_step_down, kb_zoom, kb_rotate_left, kb_rotate_right, kb_flip_vertical, kb_flip_horizontal, kb_reload, kb_antialiasing, kb_info, kb_exec, kb_exit, }; /** Key binding. */ struct key_binding { xkb_keysym_t key; ///< Keyboard key uint8_t mods; ///< Key modifiers enum kb_action action; ///< Action char* params; ///< Custom parameters for the action char* help; ///< Binding description }; extern struct key_binding* key_bindings; extern size_t key_bindings_size; /** * Initialize default key bindings. */ void keybind_init(void); /** * Free key binding context. */ void keybind_free(void); /** * Get current key modifiers state. * @param state XKB handle * @return active key modifiers (ctrl/alt/shift) */ uint8_t keybind_mods(struct xkb_state* state); /** * Get key binding description. * @param key keyboard key * @param mods key modifiers (ctrl/alt/shift) * @return pointer to the binding or NULL if not found */ const struct key_binding* keybind_get(xkb_keysym_t key, uint8_t mods); swayimg-2.1/src/main.c000066400000000000000000000175311455710357200147330ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Program entry point. // Copyright (C) 2020 Artem Senichev #include "buildcfg.h" #include "canvas.h" #include "config.h" #include "font.h" #include "formats/loader.h" #include "imagelist.h" #include "info.h" #include "keybind.h" #include "sway.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[] = { { '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", GENERAL_CONFIG_SECTION, CANVAS_CFG_SCALE, NULL }, { 'l', "slideshow", NULL, "activate slideshow mode on startup", GENERAL_CONFIG_SECTION, VIEWER_CFG_SLIDESHOW, "yes" }, { 'f', "fullscreen", NULL, "show image in full screen mode", GENERAL_CONFIG_SECTION, UI_CFG_FULLSCREEN, "yes" }, { 'p', "position", "POS", "set window position [parent]/X,Y", GENERAL_CONFIG_SECTION, UI_CFG_POSITION, NULL }, { 'g', "size", "SIZE", "set window size: [parent]/image/W,H", GENERAL_CONFIG_SECTION, UI_CFG_SIZE, NULL }, { 'a', "class", "NAME", "set window class/app_id", GENERAL_CONFIG_SECTION, UI_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) { char buf_lopt[32]; 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]; strcpy(buf_lopt, arg->long_opt); if (arg->format) { strcat(buf_lopt, "="); strcat(buf_lopt, arg->format); } printf(" -%c, --%-18s %s\n", arg->short_opt, buf_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)) { 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; } /** * Setup window position via Sway IPC. */ static void sway_setup(void) { struct rect parent; bool fullscreen; bool rc = false; const int ipc = sway_connect(); if (ipc != INVALID_SWAY_IPC && sway_current(ipc, &parent, &fullscreen)) { if (fullscreen && !ui_get_fullscreen()) { ui_toggle_fullscreen(); } if (!ui_get_fullscreen()) { const bool absolute = ui_get_x() != POS_FROM_PARENT && ui_get_y() != POS_FROM_PARENT; if (!absolute) { ui_set_position(parent.x, parent.y); } if (ui_get_width() == SIZE_FROM_PARENT || ui_get_height() == SIZE_FROM_PARENT) { ui_set_size(parent.width, parent.height); } rc = sway_add_rules(ipc, ui_get_appid(), ui_get_x(), ui_get_y(), absolute); } } sway_disconnect(ipc); if (!rc) { // set fixed app_id config_set(GENERAL_CONFIG_SECTION, "app_id", APP_NAME); // fixup window size if (ui_get_width() == SIZE_FROM_PARENT || ui_get_height() == SIZE_FROM_PARENT) { const struct pixmap* pm = &image_list_current().image->frames[0].pm; ui_set_size(pm->width, pm->height); } } } /** * Application entry point. */ int main(int argc, char* argv[]) { bool rc = false; int argn; setlocale(LC_ALL, ""); keybind_init(); font_create(); info_create(); image_list_init(); canvas_init(); ui_init(); viewer_init(); config_init(); font_init(); info_init(); // parse command arguments argn = parse_cmdargs(argc, argv); if (argn <= 0) { rc = (argn == 0); goto done; } // compose file list if (!image_list_scan((const char**)&argv[argn], argc - argn)) { fprintf(stderr, "No images to view, exit\n"); goto done; } // set window size form the first image if (ui_get_width() == SIZE_FROM_IMAGE || ui_get_height() == SIZE_FROM_IMAGE) { const struct pixmap* pm = &image_list_current().image->frames[0].pm; ui_set_size(pm->width, pm->height); } sway_setup(); // run ui event loop rc = ui_run(); done: config_free(); viewer_free(); ui_free(); image_list_free(); info_free(); font_free(); keybind_free(); return rc ? EXIT_SUCCESS : EXIT_FAILURE; } swayimg-2.1/src/pixmap.c000066400000000000000000000415551455710357200153100ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Pixel map. // Copyright (C) 2024 Artem Senichev #include "pixmap.h" #include #include #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) /** * 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 = 256 - ai; src = ARGB_SET_A(target_alpha) | ARGB_SET_R((ai * ARGB_GET_R(src) + inv * ARGB_GET_R(wp)) >> 8) | ARGB_SET_G((ai * ARGB_GET_G(src) + inv * ARGB_GET_G(wp)) >> 8) | ARGB_SET_B((ai * ARGB_GET_B(src) + inv * ARGB_GET_B(wp)) >> 8); } *dst = src; } /** * Put one pixmap on another: nearest filter, fast but ugly. */ static void put_nearest(struct pixmap* dst, ssize_t dst_x, ssize_t dst_y, const struct pixmap* src, ssize_t src_x, ssize_t src_y, float src_scale, bool alpha) { const size_t max_dst_x = min(dst->width, src_x + src_scale * src->width); const size_t max_dst_y = min(dst->height, src_y + src_scale * src->height); const size_t dst_width = max_dst_x - dst_x; const size_t dst_height = max_dst_y - dst_y; const size_t delta_x = dst_x - src_x; const size_t delta_y = dst_y - src_y; for (size_t y = 0; y < dst_height; ++y) { const size_t scaled_src_y = (float)(y + delta_y) / src_scale; const argb_t* src_line = &src->data[scaled_src_y * src->width]; argb_t* dst_line = &dst->data[(y + dst_y) * dst->width + dst_x]; for (size_t x = 0; x < dst_width; ++x) { const size_t src_x = (float)(x + delta_x) / src_scale; const argb_t color = src_line[src_x]; if (alpha) { alpha_blend(color, &dst_line[x]); } else { dst_line[x] = ARGB_SET_A(0xff) | color; } } } } /** * Put one pixmap on another: bicubic filter, nice but slow. */ static void put_bicubic(struct pixmap* dst, ssize_t dst_x, ssize_t dst_y, const struct pixmap* src, ssize_t src_x, ssize_t src_y, float src_scale, bool alpha) { size_t state_zero_x = 1; size_t state_zero_y = 1; float state[4][4][4]; // color channel, y, x const size_t max_dst_x = min(dst->width, src_x + src_scale * src->width); const size_t max_dst_y = min(dst->height, src_y + src_scale * src->height); const size_t dst_width = max_dst_x - dst_x; const size_t dst_height = max_dst_y - dst_y; const size_t delta_x = dst_x - src_x; const size_t delta_y = dst_y - src_y; for (size_t y = 0; y < dst_height; ++y) { argb_t* dst_line = &dst->data[(dst_y + y) * dst->width + dst_x]; const float scaled_y = (float)(y + delta_y) / src_scale - 0.5; 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 (size_t x = 0; x < dst_width; ++x) { const float scaled_x = (float)(x + delta_x) / src_scale - 0.5; 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[x]); } else { dst_line[x] = ARGB_SET_A(0xff) | fg; } } } } 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, size_t x, size_t y, size_t width, size_t height, argb_t color) { size_t max_y; size_t template_sz; argb_t* template; if (width == 0 || x >= pm->width || height == 0 || y >= pm->height) { return; } width = min(width, pm->width - x); height = min(height, pm->height - y); // compose template line template = &pm->data[y * pm->width + x]; template_sz = width * sizeof(argb_t); for (size_t i = 0; i < width; ++i) { template[i] = color; } // put template line max_y = y + height; for (size_t i = y + 1; i < max_y; ++i) { memcpy(&pm->data[i * pm->width + x], template, template_sz); } } void pixmap_grid(struct pixmap* pm, size_t x, size_t y, size_t width, size_t height, size_t tail_sz, argb_t color1, argb_t color2) { argb_t* templates[2]; size_t template_sz; if (width == 0 || x >= pm->width || height == 0 || y >= pm->height) { return; } width = min(width, pm->width - x); height = min(height, pm->height - y); template_sz = width * sizeof(argb_t); templates[0] = &pm->data[y * pm->width + x]; templates[1] = &pm->data[(y + tail_sz) * pm->width + x]; for (size_t i = 0; i < height; ++i) { const size_t shift = (i / tail_sz) % 2; argb_t* line = &pm->data[(y + i) * pm->width + x]; if (line != templates[0] && line != templates[1]) { // put template line memcpy(line, templates[shift], template_sz); } else { // compose template line for (size_t j = 0; j < width; ++j) { const size_t tail = j / tail_sz; line[j] = (tail % 2) ^ shift ? color1 : color2; } } } } void pixmap_apply_mask(struct pixmap* dst, size_t x, size_t y, const uint8_t* mask, size_t width, size_t height, argb_t color) { size_t mask_width; size_t mask_height; if (width == 0 || x >= dst->width || height == 0 || y >= dst->height) { return; } mask_width = min(width, dst->width - x); mask_height = min(height, dst->height - y); for (size_t mask_y = 0; mask_y < mask_height; ++mask_y) { argb_t* dst_line = &dst->data[(y + mask_y) * dst->width + x]; const uint8_t* mask_line = &mask[mask_y * width]; for (size_t mask_x = 0; mask_x < mask_width; ++mask_x) { const uint8_t alpha = mask_line[mask_x]; if (alpha != 0) { alpha_blend(ARGB_SET_A(alpha) | color, &dst_line[mask_x]); } } } } void pixmap_copy(struct pixmap* dst, size_t x, size_t y, const struct pixmap* src, size_t width, size_t height) { size_t len; if (width == 0 || x >= dst->width || height == 0 || y >= dst->height) { return; } width = min(width, dst->width - x); height = min(height, dst->height - y); len = width * sizeof(argb_t); for (size_t i = 0; i < height; ++i) { argb_t* dst_ptr = &dst->data[(i + y) * dst->width + x]; const argb_t* src_ptr = &src->data[i * src->width]; memcpy(dst_ptr, src_ptr, len); } } void pixmap_over(struct pixmap* dst, size_t x, size_t y, const struct pixmap* src, size_t width, size_t height) { width = min(width, dst->width - x); height = min(height, dst->height - y); if (width == 0 || x >= dst->width || height == 0 || y >= dst->height) { return; } for (size_t i = 0; i < height; ++i) { argb_t* dst_line = &dst->data[(i + y) * dst->width + x]; const argb_t* src_line = &src->data[i * src->width]; for (size_t x = 0; x < width; ++x) { alpha_blend(src_line[x], &dst_line[x]); } } } void pixmap_put(struct pixmap* dst, ssize_t dst_x, ssize_t dst_y, const struct pixmap* src, ssize_t src_x, ssize_t src_y, float src_scale, bool alpha, bool antialiasing) { if (antialiasing) { put_bicubic(dst, dst_x, dst_y, src, src_x, src_y, src_scale, alpha); } else { put_nearest(dst, dst_x, dst_y, src, src_x, src_y, src_scale, alpha); } } 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-2.1/src/pixmap.h000066400000000000000000000116741455710357200153140ustar00rootroot00000000000000// 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) (((a)&0xff) << ARGB_A_SHIFT) #define ARGB_SET_R(r) (((r)&0xff) << ARGB_R_SHIFT) #define ARGB_SET_G(g) (((g)&0xff) << ARGB_G_SHIFT) #define ARGB_SET_B(b) (((b)&0xff) << ARGB_B_SHIFT) // convert RGBA to ARGB #define ARGB_SET_ABGR(c) \ ((c & 0xff00ff00) | ARGB_SET_R(ARGB_GET_B(c)) | ARGB_SET_B(ARGB_GET_R(c))) // alpha blending (a=alpha, s=target alpha, b=background, f=foreground) #define ARGB_ALPHA_BLEND(a, s, b, f) \ ARGB_SET_A(s) | \ ARGB_SET_R((a * ARGB_GET_R(f) + (256 - a) * ARGB_GET_R(b)) >> 8) | \ ARGB_SET_G((a * ARGB_GET_G(f) + (256 - a) * ARGB_GET_G(b)) >> 8) | \ ARGB_SET_B((a * ARGB_GET_B(f) + (256 - a) * ARGB_GET_B(b)) >> 8) /** 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 }; // Attach buffer to pixel map #define PIXMAP_ATTACH(d, w, h) \ { \ .width = w, .height = h, .data = d \ } /** * 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 pixmap 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, size_t x, size_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, size_t x, size_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, size_t x, size_t y, const uint8_t* mask, size_t width, size_t height, argb_t color); /** * Put one pixmap on another. * @param dst destination pixmap * @param x,y destination left top point * @param src source pixmap * @param width,height source pixmap area to copy */ void pixmap_copy(struct pixmap* dst, size_t x, size_t y, const struct pixmap* src, size_t width, size_t height); /** * Put one pixmap on another with alpha blending. * @param dst destination pixmap * @param x,y destination left top point * @param src source pixmap * @param width,height source pixmap area to copy */ void pixmap_over(struct pixmap* dst, size_t x, size_t y, const struct pixmap* src, size_t width, size_t height); /** * Put one scaled pixmap on another. * @param dst destination pixmap * @param dst_x,dst_y destination left top point * @param src source pixmap * @param src_x,src_y left top point of source pixmap * @param src_scale scale of source pixmap * @param alpha flag to use alpha blending * @param antialiasing flag to use antialiasing */ void pixmap_put(struct pixmap* dst, ssize_t dst_x, ssize_t dst_y, const struct pixmap* src, ssize_t src_x, ssize_t src_y, float src_scale, bool alpha, bool antialiasing); /** * 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-2.1/src/str.c000066400000000000000000000065661455710357200146250ustar00rootroot00000000000000// 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 ? strlen(*dst) : 0; const size_t buf_len = dst_len + src_len + 1 /* last null */; char* buffer = realloc(*dst, buf_len); if (buffer) { memcpy(buffer + dst_len, src, src_len); buffer[buf_len - 1] = 0; *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 (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-2.1/src/str.h000066400000000000000000000042151455710357200146170ustar00rootroot00000000000000// 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-2.1/src/sway.c000066400000000000000000000222611455710357200147660ustar00rootroot00000000000000// 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) { struct json_object* focused; if (json_object_object_get_ex(node, "focused", &focused) && json_object_get_boolean(focused)) { return node; } struct json_object* nodes; if (json_object_object_get_ex(node, "nodes", &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-2.1/src/sway.h000066400000000000000000000022371455710357200147740ustar00rootroot00000000000000// 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-2.1/src/ui.c000066400000000000000000000653671455710357200144360ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // User interface: Window management, keyboard input, etc. // Copyright (C) 2020 Artem Senichev #include "ui.h" #include "buildcfg.h" #include "config.h" #include "keybind.h" #include "str.h" #include "viewer.h" #include "xdg-shell-protocol.h" #include #include #include #include #include #include #include #include #include // Max number of output displays #define MAX_OUTPUTS 4 // Mouse button #ifndef BTN_LEFT #define BTN_LEFT 0x110 // from #endif /** Loop state */ enum state { state_ok, state_exit, state_error, }; /** Custom event */ struct custom_event { int fd; fd_event handler; }; /** 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; ssize_t x; ssize_t y; size_t width; size_t height; int32_t scale; } wnd; // cross-desktop struct xdg { 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; // app_id name (window class) char* app_id; // fullscreen mode bool fullscreen; // custom events struct custom_event* events; size_t num_events; // global state enum state state; }; static struct ui ctx = { .wnd.scale = 1, .wnd.x = POS_FROM_PARENT, .wnd.y = POS_FROM_PARENT, .wnd.width = SIZE_FROM_PARENT, .wnd.height = SIZE_FROM_PARENT, .repeat.fd = -1, .state = state_ok, }; /** * 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 "_%lx", (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; } /** * Recreate window buffers. * @return true if operation completed successfully */ static bool recreate_buffers(void) { ctx.wnd.current = NULL; // first buffer if (ctx.wnd.buffer0) { wl_buffer_destroy(ctx.wnd.buffer0); } ctx.wnd.buffer0 = create_buffer(); if (!ctx.wnd.buffer0) { return false; } // second buffer if (ctx.wnd.buffer1) { wl_buffer_destroy(ctx.wnd.buffer1); } ctx.wnd.buffer1 = create_buffer(); if (!ctx.wnd.buffer1) { return false; } ctx.wnd.current = ctx.wnd.buffer0; viewer_on_resize(ctx.wnd.width, ctx.wnd.height, ctx.wnd.scale); 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) { viewer_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) { viewer_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; } viewer_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.wnd.current && !recreate_buffers()) { ctx.state = state_error; return; } ui_redraw(); } 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) { const int32_t cur_width = (int32_t)(ctx.wnd.width / ctx.wnd.scale); const int32_t cur_height = (int32_t)(ctx.wnd.height / ctx.wnd.scale); if (width && height && (width != cur_width || height != cur_height)) { ctx.wnd.width = width * ctx.wnd.scale; ctx.wnd.height = height * ctx.wnd.scale; if (!recreate_buffers()) { ctx.state = state_error; } } } static void handle_xdg_toplevel_close(void* data, struct xdg_toplevel* top) { ctx.state = state_exit; } 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()) { ui_redraw(); } else { ctx.state = state_error; } } } 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" static bool create_window(void) { if (ctx.wnd.width < 10 || ctx.wnd.height < 10) { // fixup window size ctx.wnd.width = 640; ctx.wnd.height = 480; } 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_free(); 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_free(); 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_free(); 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, ctx.app_id); if (ctx.fullscreen) { xdg_toplevel_set_fullscreen(ctx.xdg.toplevel, NULL); } wl_surface_commit(ctx.wl.surface); ctx.repeat.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); return true; } /** * 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, UI_CFG_APP_ID) == 0) { str_dup(value, &ctx.app_id); status = cfgst_ok; } else if (strcmp(key, UI_CFG_FULLSCREEN) == 0) { if (config_to_bool(value, &ctx.fullscreen)) { status = cfgst_ok; } } else if (strcmp(key, UI_CFG_SIZE) == 0) { ssize_t width, height; if (strcmp(value, "parent") == 0) { ctx.wnd.width = SIZE_FROM_PARENT; ctx.wnd.height = SIZE_FROM_PARENT; status = cfgst_ok; } else if (strcmp(value, "image") == 0) { ctx.wnd.width = SIZE_FROM_IMAGE; ctx.wnd.height = SIZE_FROM_IMAGE; 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.wnd.width = width; ctx.wnd.height = height; status = cfgst_ok; } } } else if (strcmp(key, UI_CFG_POSITION) == 0) { if (strcmp(value, "parent") == 0) { ctx.wnd.x = POS_FROM_PARENT; ctx.wnd.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.wnd.x = (ssize_t)x; ctx.wnd.y = (ssize_t)y; status = cfgst_ok; } } } else { status = cfgst_invalid_key; } return status; } void ui_init(void) { struct timespec ts; // create unique application id if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { char app_id[64]; const uint64_t timestamp = (ts.tv_sec << 32) | ts.tv_nsec; snprintf(app_id, sizeof(app_id), APP_NAME "_%lx", timestamp); str_dup(app_id, &ctx.app_id); } else { str_dup(APP_NAME, &ctx.app_id); } // register configuration loader config_add_loader(GENERAL_CONFIG_SECTION, load_config); } void ui_free(void) { free(ctx.app_id); 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); } if (ctx.wnd.buffer0) { wl_buffer_destroy(ctx.wnd.buffer0); } if (ctx.wnd.buffer1) { wl_buffer_destroy(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); } } bool ui_run(void) { const size_t num_fds = ctx.num_events + 2; // wayland + key repeat const size_t idx_wayland = num_fds - 1; const size_t idx_krepeat = num_fds - 2; struct pollfd* fds; if (!create_window()) { return false; } // file descriptors to poll fds = calloc(1, num_fds * sizeof(struct pollfd)); if (!fds) { fprintf(stderr, "Not enough memory\n"); return false; } for (size_t i = 0; i < ctx.num_events; ++i) { fds[i].fd = ctx.events[i].fd; fds[i].events = POLLIN; } fds[idx_wayland].fd = wl_display_get_fd(ctx.wl.display); fds[idx_wayland].events = POLLIN; fds[idx_krepeat].fd = ctx.repeat.fd; fds[idx_krepeat].events = POLLIN; // main event loop while (ctx.state == state_ok) { // prepare to read wayland events while (wl_display_prepare_read(ctx.wl.display) != 0) { wl_display_dispatch_pending(ctx.wl.display); } wl_display_flush(ctx.wl.display); // poll events if (poll(fds, num_fds, -1) <= 0) { wl_display_cancel_read(ctx.wl.display); continue; } // read and handle wayland events if (fds[idx_wayland].revents & POLLIN) { wl_display_read_events(ctx.wl.display); wl_display_dispatch_pending(ctx.wl.display); } else { wl_display_cancel_read(ctx.wl.display); } // read and handle key repeat events from timer if (fds[idx_krepeat].revents & POLLIN) { uint64_t repeats; const ssize_t sz = sizeof(repeats); if (read(ctx.repeat.fd, &repeats, sz) == sz) { const uint8_t mods = keybind_mods(ctx.xkb.state); while (repeats--) { viewer_on_keyboard(ctx.repeat.key, mods); } } } // read custom events for (size_t i = 0; i < ctx.num_events; ++i) { if (fds[i].revents & POLLIN) { ctx.events[i].handler(); } } } free(fds); return ctx.state != state_error; } void ui_stop(void) { ctx.state = state_exit; } void ui_redraw(void) { struct pixmap wnd = PIXMAP_ATTACH(NULL, ctx.wnd.width, ctx.wnd.height); if (!ctx.wnd.current) { return; // 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; } // draw to window buffer wnd.data = wl_buffer_get_user_data(ctx.wnd.current); viewer_on_redraw(&wnd); // show window buffer 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); } const char* ui_get_appid(void) { return ctx.app_id; } 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); } } void ui_set_position(ssize_t x, ssize_t y) { ctx.wnd.x = x; ctx.wnd.y = y; } ssize_t ui_get_x(void) { return ctx.wnd.x; } ssize_t ui_get_y(void) { return ctx.wnd.y; } void ui_set_size(size_t width, size_t height) { ctx.wnd.width = width; ctx.wnd.height = height; } size_t ui_get_width(void) { return ctx.wnd.width; } size_t ui_get_height(void) { return ctx.wnd.height; } 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); } } } bool ui_get_fullscreen(void) { return ctx.fullscreen; } void ui_add_event(int fd, fd_event handler) { const size_t new_sz = (ctx.num_events + 1) * sizeof(struct custom_event); struct custom_event* events = realloc(ctx.events, new_sz); if (events) { ctx.events = events; ctx.events[ctx.num_events].fd = fd; ctx.events[ctx.num_events].handler = handler; ++ctx.num_events; } } swayimg-2.1/src/ui.h000066400000000000000000000037431455710357200144310ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // User interface: Window management, keyboard input, etc. // Copyright (C) 2020 Artem Senichev #pragma once #include "pixmap.h" // Configuration parameters #define UI_CFG_APP_ID "app_id" #define UI_CFG_FULLSCREEN "fullscreen" #define UI_CFG_SIZE "size" #define UI_CFG_POSITION "position" // Special ids for windows size and position #define SIZE_FROM_IMAGE 0 #define SIZE_FROM_PARENT 1 #define POS_FROM_PARENT 0xffffffff /** * Initialize User Interface context. */ void ui_init(void); /** * Free UI context. */ void ui_free(void); /** * Run event handler loop. * @return true if window was closed by user (not an error) */ bool ui_run(void); /** * Stop event handler loop. */ void ui_stop(void); /** * Redraw window. */ void ui_redraw(void); /** * Get app id (window class name). * @return app id */ const char* ui_get_appid(void); /** * Set window title. * @param name file name of the current image */ void ui_set_title(const char* name); /** * Set window position. * @param x,y new window coordinates */ void ui_set_position(ssize_t x, ssize_t y); /** * Get window x position. * @return window x position */ ssize_t ui_get_x(void); /** * Get window y position. * @return window y position */ ssize_t ui_get_y(void); /** * Set window size. * @param width,height window size in pixels */ void ui_set_size(size_t width, size_t height); /** * 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); /** * Toggle full screen mode. */ void ui_toggle_fullscreen(void); /** * Check if full screen mode is active. * @return current mode */ bool ui_get_fullscreen(void); /** Custom event handler. */ typedef void (*fd_event)(void); /** * Add custom event handler. * @param fd file descriptor for polling * @param handler callback */ void ui_add_event(int fd, fd_event handler); swayimg-2.1/src/viewer.c000066400000000000000000000277031455710357200153120ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Business logic of application and UI event handlers. // Copyright (C) 2020 Artem Senichev #include "viewer.h" #include "buildcfg.h" #include "canvas.h" #include "config.h" #include "imagelist.h" #include "info.h" #include "keybind.h" #include "str.h" #include "ui.h" #include #include #include #include #include #include #include /** Viewer context. */ struct viewer { size_t frame; ///< Index of the current frame struct text_surface* help; ///< Help lines size_t help_sz; ///< Number of lines in help 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) }; static struct viewer ctx = { .animation_enable = true, .animation_fd = -1, .slideshow_enable = false, .slideshow_fd = -1, .slideshow_time = 3 }; static void switch_help(void) { if (ctx.help) { for (size_t i = 0; i < ctx.help_sz; i++) { free(ctx.help[i].data); } free(ctx.help); ctx.help = NULL; ctx.help_sz = 0; } else { ctx.help_sz = 0; ctx.help = calloc(1, key_bindings_size * sizeof(struct text_surface)); if (ctx.help) { for (size_t i = 0; i < key_bindings_size; i++) { if (key_bindings[i].help) { font_render(key_bindings[i].help, &ctx.help[ctx.help_sz++]); } } } } } /** * Switch to the next or previous frame. * @param forward switch direction * @return false if there is only one frame in the image */ static bool next_frame(bool forward) { size_t index = ctx.frame; const struct image_entry entry = image_list_current(); if (forward) { if (++index >= entry.image->num_frames) { index = 0; } } else { if (index-- == 0) { index = entry.image->num_frames - 1; } } if (index == ctx.frame) { return false; } ctx.frame = index; return true; } /** * Start 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_entry entry = image_list_current(); const size_t duration = entry.image->frames[ctx.frame].duration; enable = (entry.image->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 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 after loading new file. */ static void reset_state(void) { const struct image_entry entry = image_list_current(); const struct pixmap* pm = &entry.image->frames[0].pm; ctx.frame = 0; canvas_reset_image(pm->width, pm->height); ui_set_title(entry.image->file_name); animation_ctl(true); slideshow_ctl(ctx.slideshow_enable); } /** * Load next file. * @param jump position of the next file in list * @return false if file was not loaded */ static bool next_file(enum list_jump jump) { if (!image_list_jump(jump)) { return false; } reset_state(); return true; } /** * Execute system command for the current image. * @param expr command expression */ static void execute_command(const char* expr) { const char* path = image_list_current().image->file_path; char* cmd = NULL; int rc = EINVAL; // 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 { rc = errno ? errno : EINVAL; } } // show execution status if (!cmd) { info_set_status("Error: no command to execute"); } else { if (strlen(cmd) > 30) { // trim long command strcpy(&cmd[27], "..."); } if (rc) { info_set_status("Error %d: %s", rc, cmd); } else { info_set_status("OK: %s", cmd); } } free(cmd); if (!image_list_reset()) { printf("No more images, exit\n"); ui_stop(); return; } reset_state(); } /** * Move viewport. * @param horizontal axis along which to move (false for vertical) * @param positive direction (increase/decrease) * @param params optional move step in percents */ static bool move_viewport(bool horizontal, bool positive, const char* params) { ssize_t percent = 10; if (params) { ssize_t val; if (str_to_num(params, 0, &val, 0) && val > 0 && val <= 1000) { percent = val; } else { fprintf(stderr, "Invalid move step: \"%s\"\n", params); } } if (!positive) { percent = -percent; } return canvas_move(horizontal, percent); } /** * Animation timer event handler. */ static void on_animation_timer(void) { next_frame(true); animation_ctl(true); ui_redraw(); } /** * Slideshow timer event handler. */ static void on_slideshow_timer(void) { slideshow_ctl(next_file(jump_next_file)); ui_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_key; if (strcmp(key, VIEWER_CFG_SLIDESHOW) == 0) { status = config_to_bool(value, &ctx.slideshow_enable) ? cfgst_ok : cfgst_invalid_value; } 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 { status = cfgst_invalid_value; } } return status; } void viewer_init(void) { // setup animation timer ctx.animation_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (ctx.animation_fd != -1) { ui_add_event(ctx.animation_fd, on_animation_timer); } // setup slideshow timer ctx.slideshow_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (ctx.slideshow_fd != -1) { ui_add_event(ctx.slideshow_fd, on_slideshow_timer); } // register configuration loader config_add_loader(GENERAL_CONFIG_SECTION, load_config); } void viewer_free(void) { for (size_t i = 0; i < ctx.help_sz; i++) { free(ctx.help[i].data); } if (ctx.animation_fd != -1) { close(ctx.animation_fd); } if (ctx.slideshow_fd != -1) { close(ctx.slideshow_fd); } } void viewer_reset(void) { if (image_list_reset()) { reset_state(); info_set_status("Image reloaded"); ui_redraw(); } else { printf("No more images, exit\n"); ui_stop(); } } void viewer_on_redraw(struct pixmap* window) { const struct image_entry entry = image_list_current(); info_update(ctx.frame); canvas_draw_image(window, entry.image, ctx.frame); // put text info blocks on window surface for (size_t i = 0; i < INFO_POSITION_NUM; ++i) { const size_t lines_num = info_height(i); if (lines_num) { const enum info_position pos = (enum info_position)i; const struct info_line* lines = info_lines(pos); canvas_draw_text(window, pos, lines, lines_num); } } if (ctx.help) { canvas_draw_ctext(window, ctx.help, ctx.help_sz); } // reset one-time rendered notification message info_set_status(NULL); } void viewer_on_resize(size_t width, size_t height, size_t scale) { canvas_reset_window(width, height, scale); reset_state(); } void viewer_on_keyboard(xkb_keysym_t key, uint8_t mods) { bool redraw = false; const struct key_binding* kbind = keybind_get(key, mods); if (!kbind) { return; } // handle action switch (kbind->action) { case kb_none: break; case kb_help: switch_help(); redraw = true; break; case kb_first_file: redraw = next_file(jump_first_file); break; case kb_last_file: redraw = next_file(jump_last_file); break; case kb_prev_dir: redraw = next_file(jump_prev_dir); break; case kb_next_dir: redraw = next_file(jump_next_dir); break; case kb_prev_file: redraw = next_file(jump_prev_file); break; case kb_next_file: redraw = next_file(jump_next_file); break; case kb_prev_frame: case kb_next_frame: animation_ctl(false); redraw = next_frame(kbind->action == kb_next_frame); break; case kb_animation: animation_ctl(!ctx.animation_enable); break; case kb_slideshow: slideshow_ctl(!ctx.slideshow_enable && next_file(jump_next_file)); redraw = true; break; case kb_fullscreen: ui_toggle_fullscreen(); break; case kb_step_left: redraw = move_viewport(true, true, kbind->params); break; case kb_step_right: redraw = move_viewport(true, false, kbind->params); break; case kb_step_up: redraw = move_viewport(false, true, kbind->params); break; case kb_step_down: redraw = move_viewport(false, false, kbind->params); break; case kb_zoom: canvas_zoom(kbind->params); redraw = true; break; case kb_rotate_left: image_rotate(image_list_current().image, 270); canvas_swap_image_size(); redraw = true; break; case kb_rotate_right: image_rotate(image_list_current().image, 90); canvas_swap_image_size(); redraw = true; break; case kb_flip_vertical: image_flip_vertical(image_list_current().image); redraw = true; break; case kb_flip_horizontal: image_flip_horizontal(image_list_current().image); redraw = true; break; case kb_antialiasing: info_set_status("Anti-aliasing %s", canvas_switch_aa() ? "on" : "off"); redraw = true; break; case kb_reload: viewer_reset(); break; case kb_info: info_set_mode(kbind->params); redraw = true; break; case kb_exec: execute_command(kbind->params); redraw = true; break; case kb_exit: if (ctx.help) { switch_help(); // remove help overlay redraw = true; } else { ui_stop(); } break; } if (redraw) { ui_redraw(); } } void viewer_on_drag(int dx, int dy) { if (canvas_drag(dx, dy)) { ui_redraw(); } } swayimg-2.1/src/viewer.h000066400000000000000000000021201455710357200153010ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Business logic of application and UI event handlers. // Copyright (C) 2020 Artem Senichev #pragma once #include "keybind.h" #include "pixmap.h" // Configuration parameters #define VIEWER_CFG_SLIDESHOW "slideshow" #define VIEWER_CFG_SLIDESHOW_TIME "slideshow_time" /** * Initialize viewer context. */ void viewer_init(void); /** * Free viewer context. */ void viewer_free(void); /** * Reset state: reload image file, set initial scale etc. */ void viewer_reset(void); /** * Redraw handler. * @param window pixel map of window */ void viewer_on_redraw(struct pixmap* window); /** * Window resize handler. * @param width,height new window size * @param scale window scale factor */ void viewer_on_resize(size_t width, size_t height, size_t scale); /** * Key press handler. * @param key code of key pressed * @param mods key modifires (ctrl/alt/shift) */ void viewer_on_keyboard(xkb_keysym_t key, uint8_t mods); /** * Image drap handler. * @param dx,dy delta to move viewpoint */ void viewer_on_drag(int dx, int dy);