pax_global_header00006660000000000000000000000064142007115600014506gustar00rootroot0000000000000052 comment=e7a87313a8330eddcd240f0661b8b060433fc997 swayimg-1.6/000077500000000000000000000000001420071156000130345ustar00rootroot00000000000000swayimg-1.6/.clang-format000066400000000000000000000011241420071156000154050ustar00rootroot00000000000000--- 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-1.6/.github/000077500000000000000000000000001420071156000143745ustar00rootroot00000000000000swayimg-1.6/.github/screenshot.png000066400000000000000000001154651420071156000172730ustar00rootroot00000000000000PNG  IHDR[H IDATxr[癮恓y9q:g]]qs>ptv{Hwؖ5ZLk'Y)AK@`XXQ,//h18DD\q//|[UpPtOl> pP2)X)ztB 3s5f psW)8hs0F8' ̳@#Dr*[wR5n!!BF %WLcg W5ֽ.p!%WS b`FpWoWe6B)_~͗_s E3?>;KKK͛7x,{i3=6~YcvA/WXvcl:hSGA"/]3s5>v"RpN  8`!pN A pՍp!K,DBp%@f[+X<.?UUF!vpE,×,R(PEQ!.%k MKaYEQy$I$jyEQDyfe t\GQ:Ʉ TUUF -˒7JeJ) p` (Mz֘rDJ,=,2M4MF8GGD 1?NӔEQiz\BM<7 E02>G E j0BD:(cAp qE3$I d.R8c#L&lg`]3 IX ÐeYqF69d#su_}_3CʸĜjN˲A9}]fmn6>BDaaȭd6Eq]7MS]1ΩN R~.9…iυy2ζvm6^E<_U$xx,c1k䐈feATUeyL G$f{pDqYiiZYr iV 󂳴ܟq,SUUgݲ,0BF;I;?Fe`c5>`$XQ c0_#3S\>KQ4M(|pIux:sz~)ݹs w tpUIF6 !\ҿSY.{ p,k~ Fxfl\P$ iB83)dj8xϝ"EQ$IEbP2)9FxY+140B8{5ϿR Tpr#,胩9:5&ԊHUUEQc0 Cq`apf X0p\)EQt]7MqFy8iR86F1oܸ>|ڵk~Yc 8 pf-ɪZ( ߒ$ ϸ8MY(w?9Y7QeqGQ亮~?cUUd29VO `.̲LӴz>6BUJrc0|yI7`.m ȻieMdQE/Ft4MkZϟ?]vZ!CF4Mt2]Ӳ,=8<)&cá˗eɑ2. &0_$"0]v;wܾ}{uu>t]Jx4L  Z,]WVV?Ǐ?xh4 {^ ˲Wx$80c#g^cO?Ν;KKKmn:?ZE~ڵkxb42}F_?޽˃ld@O8MD8?ŋ#FWi_1/sGUnnl{ȝ"Wg&ӧOM<|^i =wҒ<<  !Z%B\XX`{9 :X.prVq&,d|NUv}ڵeALsV4vm{~hp8|[6'eEx<6rN^_ЊEvyqᣩ8NvW4`3 lC^AL&o+Id..tmxn6`pe~8ASl %%l<ŋR뺣21\l#̋~V8aqs7w}ynYVѸqjt:FCn^ѣG/^x%Ii7nܐ2S\U՛7ow7…6Bn%y,߼yooxp8'|bY"qk²,68$9FlfӶmUUGѳg?={l}}]F8Nnw0E&o.'9LJG+I&x<~64y ',駟p84Ǐ'I}x-!R>~7|/>"Θ1/O>ӟQ;wܼyӲxae4n;6 ۶,J")曆 A0 8M<ϫ,_~=yX4 qVe4'ɛ7o㱜 Xe[[[Ѩ^w|ׯ_|S>(; Ό/fq=49̗,&NEUUyx<E)rggÁNjXB۶1Ţ(Fsm677GQ}h4 ÐY?X*{g: |#2j/Lm:Fyq !Gq<xw*2~yd4B$ Ð3fY_Bx1)ˢ,)?L/zo7GxVC 1B0l6 ݑZ`uAFXB^c梃S]~+uׯ$I0 ]]^^u/BUi]+to);:RRQY(H)Eĕ?7),IȲ3ʼnPSYP)hQURES(ֵ>Oyٳ7oAp7έͳe;#„\~ N~0zx/J5R-MEFTPH7ki)$LiH+U㒪DШۡ;m*=~DADiBin\y'/n߾4MoE?~?ϛz|٧R p1U[x;ȳg\mZ~ѣG^jJR:da$E(BHX$j^`B(TIJBy+JBN )c5R(8FH5sJm%9y ,}o̲ŋ[~ʍE/ֳvfr\R*K* j4)0qH$lN "2 jvH(t) !HP!J+rP|>nJ苗;[ÿҞ={6Ctu]]u]B\\H#}8#7y| if;EfddYoۊEFd$TA*]/uAD""ABZeEUTmTZYRRQ6HY_xW͗Esq犢?WT>O9_P_V85Ȳߎ|wU!VDQFUFD DQN"#JH%*E-ofqx{<4s0o=ɲV8 f0/8@X/ \ǝ݆jFVzxdY<\#RaITȨ+BX$,";BN/EP HQQɶ+{(Ϟ斞+DAyD#,"200.楃S)c.ZnӮd1Ϩ">`IDe>{#=txTޯ A$*D"HUTEiЍ}L-,o,4, _pudtGE"5v0F C/U۹2U6[ET" aշX)%gIɵƐ>My=r!F0B(S%BJEz*X~-r<:x){)U Q/ &H Byj/\9e9 ABEK]x&>ڞe)oWg/2kluiC&YY6) B(D:ABTeDUU߲Ƃ'$L"MB$XT%TETEDQ5ӛ7CIE&uڤkd8d3(482!*ʔI5TBADAu %!!&M'/{_v0U9QBOՄ(z1 $ )6 QKWTTeTTED!QJTFӷPPɵɲH5FE%sBəkl1ecinmƳTTeD1U1UY-e,{MNvbB8Wgo~4#ۦF;]݀^m.ik;ۓgZUy'Ibit(ar4͝]y MB ~A0G)$R=֓GN^M&T4\ xL&˗sc*RʲhuVW"y}bQU F!G4 WEֺ-t=}SIL!i|kƚl6 HӔg1 ! UUW.O#""TVaZW;FSUPYRQh(,((,Njsvv,%ˤh8^\Lto#yFYFq$v\R]Nu,v"%?֦%(|___/˒C,BTU(8r58Ð4MCΣj̋+ǦiZd2IӔ#%f7CeٌZE[FU(M_YRQN02J2MZ\,R4\r]tR**r* JbWVJSѼ]jhjK/L ܴFY۶h4E'N4MSjXq;80n3DN+˒Äꆡ43%UXD[JVUVRSPRPr:} NI-.8o$8r`RRRkah9㉥jwlWfnYEQIqnBaAv;it*2eysXp  2\QǶdmۖU-_R2MtԴ4ɴ0Ζ)Js*r*+ڿ #I>q]Ƒʒ҄␢P0nk5zսg4;BQE-Z@uv:Nn8hj ]$e5 jygnXBX%c8wFHzq~8yö2Sv4n42S/-%EXpPPHS"J*d080'"&cA>Do|j=vӰ(((~fnh$~#F,ms"Ge4M<ϹF5Bn9|R0?4mQUNQE6Jx[RM/4T52*RR'F ~]#EIIFEAENy#)I8⼑MԚ/]mn0,G4YDӴ7ogϞM&..dY>^Ǯ:z$>L~6Xy Wu|(#DtyaS6CUU]j)T/4 1//|[U  MzWٶ8O{FXRIgEUj6gZJR*TJJ$DU $RJS#=Zuѱ,˶m/'3TۭxUq۽qǏM|z$I:WR`>F(˯oNb>NJx8췽3ykm,lHȠZUUQQ>ITe*(T*4T(TJ1S;a뺧nւhَcYeY2Y|/,vݻqpE>f0H6B5I<Κ;Yiex^] Ǐi'1BqbN{AY=$I۶-&cHpX8F("Yf(8 Fq>ͳikS.x^}`<9Fx| 9.Ya] *z8 xÅu#t:l4MM;//'Oy\#r)k8nZ6N'I"ߠeYP{zT@PGxp{x<޿0k׮WzeYb*k,8; kupŠӋqu7n׿6Mɓ'Rm~뺮}ApIp8VIdY&zޝ;w>ׯs! E8zŅP6K)W9 dڵk~ikkk/^˙dp=婾NAfnp3Mөynƍ߿{nӱm]r/,;KdPzǙiZ׻}˗/]םa?Ω{&^#Gov>o~sΝ^P8UJۊ:_FctnÐ%_38;#/A9:c&<ϻvݻwáeY<:8=\] Bein&[ 8 N͠MhvX2pvqvv19K4|F;r+EQ\>ﯪjNDz4u9B9qee_|#oW_|^E^ a0@Fiڣ,K^|rLtqq?yf:WU;;;lY S\G73A̩8Ty$2@.uhps o|n"ai:SNYC^e#a۶yc }h4z)yj<'W۶m&0mS7CT> )WE䶺Ct]w]lj&/+?pw-A29n$Sa7JAil6E8aUU?|gLYlakFxBB ("˲dRlv]WQeP1B޽w>Dx!txѦtmO`Y^>O.#ulUU<ڶ׿S -qO8NYh19Ề +0|׬w\T'yqc793(I^!& &AXl撾#wi] yGUU.+Ҳ,q{-q66B~r |0#D)pZǜH=<@2d۶:C{5^;4CI2 ò,ɨi<}JO]WҩzY4bW( ~%Dd۶m~>xWcE~RsA{(ba] *zl\$<8> 4M4A{IaVF u]eYr0;\X|?,,00O8 ƭ!\wEi4l-6 ;+qa^vpd)#wO55[R9B$I`0`#[Gy^'L&Dlnpac`sU0r$57.+lp8\\\^l6Mu]uG2w=ԁy!igJ#D=q<ϚW6668DZJFisBcuNpUU$1MSopFLC#OqsB{mZ`aaa8z=<7+4x~Bq\_"MMxj {Iduׇ!G9q)sq]]YuH!OmΝ;կ~ > PUղ,dvc-+2IEQx~ + ([I{v2+mvH7r]///s7eY3O (o״{ND,0 -z8*WH,L&i,sZw k.rkk( zA^ @({Jh/mʕs?!Ȳ,˲.wQt]Υ^^WaArx<6 ݻ^͛7ŁX<)G4B}0\0iAE\LBp,u}iiiuuѣGnq<:'Mu.BIHm]m.Ҳ h!lO@X<_i5//|[U{qtSi+++~|ƍ^wtq_h44}%jy/}WbD(w(cKWpPtOlԧp\Pޓybm#4*R~Νׯw:;3 /Ŏ6Ntx [aݻ+++|a<ц+9jih{k'?cd.Rk墶x7OQWL(wݛ7o4M=xeϞlD$lX[G8#wضׯBh<5_Fk۵Մr!>UԃKOQqaF({x );w BI{OսHr9{sмFrnGd2EƲ遍S<*˲zRDtp5(%7k[4>˹y[nu:w刖em&,0L&d<ABѸ,w UU$r !pQCp02J|F(#Ll=s4BcIeYNg0KDih>H#q?,0Bpn)0-Gr,R;#yfF:(h67o|⢮d@l=[o~rQ%n4BI>2]>yfل NЅ,SKex~ ڍ0# % aeQqPvBp:}" C^abY`0uҒpc _a~-Bag&efjœ t{಺ $I6^a̩F4p8lۼf뺭V˲#GS 9gB f WMc0MH#ĒyVg 0d#B"c-/ϊ%fuu) G6XXio$CXUUѸ~Ç-:992e9er 0?M,/8;V8'I紷ue>VuΝO>dyy432]7XeڗpoVW-ipAp\U,+e1MS9c<aqF{)dTawoGN $mu!h/F(qDFt997>rT.8^+gR;o Iq9bn; vHDaH#<33qE)z_2y􇰾YXJ6#ɠ'CCf !iiڶ]gPD35M#"UU/Skl}!IVG$h.\r#oW_|^E^Aյ;e Yˎ6dbiV3j7B\˧=FX7?˲ݱI;ِ:7qrP;(~OB E* fN5WPp 8\xpIɄ' ^c+:'7 eL{SeR.!iFqɎo4O>GΛD1FN83aluM\;m%(isqSGϭʀ {A#-J.ʑ<̥n.#G2\7\Y,ywjK.ˑsb!FxT)K`#]S8'kzc$z4ͶF#l~An<T@ԐYZy̌PiC9bPuu&\9#\b0d k*dH&IᨳNezGuht:˲XI!F\>F%2h?e#McdƲYp#4 htݹ tp)9^ch":ȂŽ eݞ,SUZL%?Tgeۨ},Ǿw3k,7@I [Iyatɍ#M|?w kn\TZ.eͩcmcRL1Wf7fm6uZ*(Q$Lbso~xD &&@lDh۹/.Fa5~z,b ![<չ0vww<Ju$ʅ ^{\!:ίvqnOni K}:TD،9s4EQ:gw4ݱp&˲(Mzyyyy"zՈOwi0 R^ r^yнDA׆$IL&n`{~ 9RZ _D};x$ig+nva$Iz W7J mݐN3 e9N]AIӰ!~e[!^xI////O^^߱@ $+@ 0ð&LtʲBiIHy u ]xeƘƺt:^ pd2q%ݮ;Թ xK<덗׫@|_}Ms뵂^.! !`-/݇l6F;…MžGĭdY{{{_} ~t:N.^Mą}]Ғ{䦋PD(tI4݅^M"l?Ï}W o߃R a >{l1&6g$ҽaM7EfwwȲ,uWJ9ϥ~mGaq젹GNYfA{^^^26z&P (Txl֗훗x:޸n!+[/?EYF/Xaf#[ږ6M BM.Nt=zS7/޾cbㄕ ,(ū!l`6RQ|~kvkkkM?J.ˢ;GiښCijONj3jрwuy&M:2mAƘx,TAA ƣ(r3`qM>[˳׹$AªY]ae}r\h44++ f7t?x㍷~sQ]t>jcRoxfG m [0Ϯyh5}:fEQK(,Bq͸=zyyy"zE`A܉RVvH>Ɇ %XB@%WAd7fRśoo4-?6.ynK̎HoO߄';uB\# #ۇ{Ou%tgeΦlnl߸kD!c-LnyfC +%8YAu I9`;|R87?K)]=3GAk(71rAV-KI@H [dyw~3cܵ.2&FgD=!xH ADxP.ZcGĝtrW&IXA5(BDƀ+"0XǑ##~s/Ƿrscgvg0ܸqB +?\#B穠-E%?נ>ԩA1KAXv :"BƄJd(04>MJƶ9˽1e({yyy"< qػ^Ho]~R)PD(%V\x\&PEXQ_<0_oO?Xk'?y8vV]8 8PV3SD:GtA Ҁ}WV(<ߺRoGo^X$#rpN?/^^^ z(:GB~k=_T,  U` D r $@, rW{֖Ͽ'7onnnq|%O-a$M%K΅b'C҅\|tt[D(2EaqR?-I T`hVWFhh*=zyyy"zb}0[_XKʥB9h2U!v~!C &b(0Ww?@{N7[o[ FkiSlݽw^EnH3K)]EHvOv]W_r>W{k z} Zշ)bp ^?|  w޽h4*Z#RSR f@( IbddԺSՍqu^ beEv{J( XDr'B/WB0èݸÁV@LU5la ,Jbe@M-qYiz޽N><<ۻz˗WVV>qijW33[ֲOY% nm˼uY,@B/BaP'%" oߣkTRjJ!SDywW,j]ή-.WM* ꐱ *@@ 6I %V cJLJpW.`؟w71Ή0Mx_wʕ+qѫJڲ,gd2i^Zi`k5J0Bc!?Wp .HA’9P< g{yyyD/4^NIX)~',a!X+a"Y7"佨BFE c(uj`сu #$ . .+Άk_]Zt:Z_ZZzoܸEQEB zFHYYftgZk5ΏD )؂b1f( JmP J x޽qV1 X!^)&a h~xe <䑱">>i(wz} H(/Cyrb0 XIHɑ04q%RB A( !>Ye>ya)gTc&0NN a=4~?`86 e^C<4f,sV\j({B9SBDt 5$eSSL4ˑk(@"T$!,uqa Vpq++ tXu Wqa%5VSiV٥dQggM3B/ I,$2b}p* !%3`7v GJGO UàBf1^U} E!۷o߾iBNN+Guل : z1rB/nnn___3 IDATW\p+`ܽ~zM$TU9"5HA-IeM''rЀ4 Uq@It:%@vB̓[nmmmeYVe\L+~L"Zkh%t $%¨BF*W0ZهP I$Fn.RAHAPX-NgTIXHxyyrD:P\Gfm)vaf..* D(2~u ) ?&UbbsL4ͻyi~1ʲ,Rk}-)+˜L&kkkO?;ѧM-10a k9"Z1@Bňc$$]$ c%@HAD tAqu9P&̰w6#,G///OgɳSʊ^_K5<H/-q 2~Э}ɕ / ~[Dp{~N4 9 8b:WA8 )!Eu/c` kB&8F#N'bA,dD'@.xJץH׋^tɗ;'B///O^^ߙC]DZh~Ґ6"2ճI ${Tɩ3'BR`TcX@@-cyKDY"+Q#Fx2Abt:g!a점ֲ1G<JLP\[ a,}߄b1a $!QT;8: ƥjJO:y) -O=z͉^H@)!jq G򝨙YpjoJLBԭL*Ptbo=:Q7|cppp{7nܐRs]fXXvd&DJB;XT8(%Aj5goC d*<W[ì?^o"lCk\=AB >%pGQI A' H l@u>#¾B/hv*N&,ܹv]CsPHDVeo2`A'sQU9jMt@Dhƴ }+(Tu;os@ DBvצᕥ=!~'B4k+D+tvjM 3( a\j2nmHڢ(1>|}}}ssss;vdNsGBW3 f VU$e($$=EE9$Hn:š`cg///WRM+`egGTQݭ:wLj",amkKH3yC ݜ͍8WWW AMfׁVǶ&6}Mg"t(@oi@"N~&h{f a}B//׍}֠׋5(KT8V=ؼTŵJpU&V_?-z瘤g=d2y_~9 ]4>r%kFYቐvLGDr'T< " ($ј2mRZZjC^^^zwEQie<qU[5#T/1lu8POwN`uuEl*it]p`:vMϋ#!=A-"$QylTxHer?͊2]z-a  ԨBÚ ppy{[MN<1&M͍ X-w"?% A(9c!/㯿Y]'Jnznƍ;sҥ0 EY+ZFݻw~_}յ`j%!=ąXD ?j{Rj|NY}MI( ,b+JeX9^^^NS eL!kpdyLgj: \'_ Ws W\Y__zիW/_|ҥ/FQD/;e{Ν[nݼy&o@Aa5\dHQŨ5x\XM>OnD H)@N+} qF4ۄ^^^/?q5hdjw%zxQۓ—\MŃ*xa@ 7g?{~EBsDN;;;no~駟 AXom.W"',@’fg\Ea//G'D闿Ï>#o zb cHk2Og ]:(7DBgڏ/w 9J#(AE.d,B:VYAa=Z<_&'yi镕4F2˥{yyD؆B|ːq}/2F9 rS$`Lj^TވŃh\‚Z?ӝlF!BNTLP:󣗑[HZ {zyyD@~O`6Xt 9C"1AFD  qQ\VS%W~d=_ kmeo_i@A-l⓬R \%)Sʋ^^^^Og@':v^^YH"t&ttq(!Q/;:4ueGqT$r܎6{tKcƍxiQ3d:?|ݻWsNQD.Pz1)Z ɇ{j2֗xyyLzgAp@?D.$J'BQFU`p.D K;rkťh2wvvܹsݍ>oiBTUSXJUm;Y>r3ɵ{yyNDpUN1Zh@a j)!ûi Ae&ts궶?W_}u4MDH:!B8n]j:KCdyz- 'kQ^E"Y^/ ( bm'N4|z2T^j??wqoއ^cq߿>?6666778D'D?F@#ynM.;ʧfK0dY9e(>^^^.]^OSl9fn&0VFxNTs8@0Bfɭ_먻؄,2˲_ovݽt^K 1jPwy}ZkxZ4tKgO:D$5zVlA*TҠQ:/8B"pEK t{8xxgaf8]_x[)ׄ^qZkii:ͦ7|wvvHa q݌PT Xǔ¶\Û[Y?^^^D^}p^8Ok& Hi#5F6>Pj{X^^^P'=&z==.X#:YJR#e~2`W6$gv^Bg($T;GY:NM4\:{R4%,رN|رh4/?/r:N,<fٷ˒SqPui5,~ BrlwôfE3RDn1=zyyD_kܸp ח~D489$4/Mqr B@)pN6fWREQ)6C4\-ZwlWA'Ox<6_Zd60766\t8skӼZ!HD JՅu`NI2a*掺u|snW|{z݉iq+uqҥAwuPhk[IE l?v}gsm{rm iqq,w^Pҹ.j ? v~ "y61+"$(R`yVχO`m+"f[”92`S-Ǐy7G+ lӥZf)s&Md{΁<8rcF4vw{׹#>m/Ѿ2ai䝭A`eX^0{lo挮%4߅.CV]l T+sgć}ܗXekp vDhۏWm" 0HЍssL:GP# 31sVpPt䣦nҚ bS`&jSd<a{W 2ע4󼼼^9"l?Ï}*c*r{?z0^+IO% wbD] HףuUhqBcSi@GjlpС5lB)*UMI󜫗ca-100Lp]xR}ḼXsC\ek֍ʃQnr'',,l}A//J zy=1h"tmx|FQe5RЎlWQ ' _*uwu%)V= *.%Bs(*=/B$99sP($t әm[Vo mkgxͭ/$@*m%q,Da"P/؄+A Pq #G"ȮaMi<\pC!M]BJlR bk,7& O2$Iqp'Ua4Ip0N#2pUrbaG~ձ8 佰na) .rICU<tŎs1'",K9gƴw6N^^O380M>|cկK߫u{{\\]]m]XB{A 96v땤\1HxPpyA 'EAEqo7q;irҥ3M8M.gVu^[[[ؿn{e+2D2w65%A0<y)d/M]iYNC͋ Έomm/_>#|T7$Bz='-S/O* ;1.3s B///O^^/ n.s"|pTN,MJ4j;I`"e(V你#(`L RT{Yg?C90, 0ʉ|@u K^>2 ”DMtaG(i]MҎ֠[11Sib\DY<*"qjܔ8\4-L )-X4؝)-2 d)"cHD[4ď4Noo zyyjD/4^*(c$-8Xg3 yz'>eh(HmUd`ILda0V V XLȸ\XBH@yy8#P`Ŋ%+bf%,u|6vK' fQVVX`L26HHNpd9G98J9玄 8sD!80ݝCo].eǿˮ}x9L$Qxxßnvn±ȋ_n~?yxwx9JqrS m(x>LSy>l~w܊M b~zDx/7~W5SCSûe{!?F{o8ywiLS!}poxƏoݣþ8$ME8(SCL%VxS5UQEQ9lnapxnϻ=uþ8l^v҉,7ˍul) /, Z P(ta*.X{`saمݣߎ7Vv|8)BQSZ&Cs`z>?n6a+?TW(i&hϒ`i`m$j\F?Nt æ~M=j'Āx>p6&9`#=[6MF*-$ǛBkM޴'щ'j[Bz?! O8I#߻[PZRi(窟=OwމFXk=?ZW'oVw6`)JW^' V~ZM]y HAZ!\I4 !:lJS'A'$_LI9X2V;"O5ĺL֩(rXY ơimMn^̝Fhيyo規eZUɚkkR_cX'W3Ulˬ9GÛ8tj\[W׻'e;OZ YD(4;iZܐ/OZmrwj =.V GdY|YHwgua \ؚz˪%>q bܾb"+L.J7h|wwB_Aq`zfDXͅ "a"Vn6BA!A"@"XF~}~%ק8WF~{1a)K(`$vd\mR9#,Ur 0C# ǗZ l;4̜$L\ X=KjgI`ƽP(X{"`EJ֞k/{f k ^Ǿƙa5ʂHz(m,6BDDz'OGW)_p}8x e4KKYB}cϿWuʱ!{\6-V8W1~[I"g /r8]簭}YKզ5S~M/"s69>cg\ťvG,#`퓎W画ݠGuUmJJ1Z3m׶6r_m kڤk;:]5nkuȟ/|R ]iZJ&㯱 Z7*K|FPNXϑEu-Em&^M;o|靎[~V'eq{i˖"8Y~Tuk(8g1*oYD89)%owm]Z̔z/běk=LpRaSmH*!KUj]zfn]K1Ek :40bѲ]5qf4bOP̔E$| 5ٵt's`^"Y,U2nl-fdzSJ-j _Pk1!~Q>a0Ү,`l#o#k<~90FJGYb;M"]e$zKa'f2'"an&"K|3_.E=}s>(d=qpȭk)apIq/ E V\&Uz~5]S[ػ/PLs}X?6_kNX!b88Ycmm 02fݹps!bλ[W"kAv]X]d7s YjRrOM^dG__EdJ ,fգ=QK:[Gr?N6R|͢i@Fuy$2A;,u(3|kh%u%cCp9dMY!Esz"Wx1n {E4Ae ?r/)cuo?qٛ/ۮNjVk-],`gkX(;I<)W_ڬUV FM2Q#`G@Vo=K3T'H\z|}bgf]E"DixZ9sw3,NR8+b.@"lc ڊ )^j(xa0kP{픂d1"Z i a%cnN'E|L'ĸ֩]MǏv}(grXE. RRH|-4R1g)i9)Ysar N0եL1g\되;tq]: bpƉ%˲8)\D(ea-j%;$kaF[O`3,6tN\סYv8C+gMtCu4 pq05,/(ͼ藄 -^M,,NpaXDk<_GZ'Q.:Al`6www!%$ :@t]Qa#T'l3e*XĂ"SzZMr0+o=~4^JzHu?,"M<‰P!m'^^gyyy_'>?peG@'Wkjb$B$B$B$B$B$B$B$B$B$B$B$B$B$B$B$Bݶ믞Fv;fnxkIENDB`swayimg-1.6/.github/workflows/000077500000000000000000000000001420071156000164315ustar00rootroot00000000000000swayimg-1.6/.github/workflows/ci.yml000066400000000000000000000016511420071156000175520ustar00rootroot00000000000000name: CI on: [push] jobs: build: runs-on: ubuntu-20.04 steps: - name: Install dependencies run: > sudo apt install --no-install-recommends --yes build-essential meson pkg-config wayland-protocols libwayland-dev libcairo-dev libjson-c-dev libxkbcommon-dev libgif-dev libjpeg-dev librsvg2-dev libwebp-dev - name: Check out source code uses: actions/checkout@v2.2.0 with: fetch-depth: 0 - name: Get version run: echo "::set-output name=VERSION::$(git describe --tags --long --always | sed 's/^v//;s/-/./')" 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-1.6/LICENSE000066400000000000000000000020671420071156000140460ustar00rootroot00000000000000Copyright (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-1.6/README.md000066400000000000000000000046741420071156000143260ustar00rootroot00000000000000# Swayimg: image viewer for Sway/Wayland ![CI](https://github.com/artemsen/swayimg/workflows/CI/badge.svg) Now you can view images directly in the current terminal window! ![Screenshot](https://raw.githubusercontent.com/artemsen/swayimg/master/.github/screenshot.png) ## How it works The program uses [Sway](https://swaywm.org) IPC to determine the geometry of the currently focused container. This data is used to calculate the position and size of the new "overlay" window that will be used to draw the image. In the next step, _swayimg_ adds two Sway rules for the self window: "floating enable" and "move position". Then it creates a new Wayland window and draws the image from the specified file. ## 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)); - AV1 (via [libavif](https://github.com/AOMediaCodec/libavif)); - BMP (built-in limited support). _Note_: animation is not supported, only the first frame is displayed. ## Usage `swayimg [OPTIONS...] FILE...` See `man swayimg` for details. Examples: - View multiple files: `swayimg photo.jpg logo.png` - View using pipes: `wget -O- https://example.com/image.jpg 2> /dev/null | swayimg -` ### Key bindings - `Arrows` and vim-like moving keys (`hjkl`): Move view point; - `+` or `=`: Zoom in; - `-`: Zoom out; - `0`: Set scale to 100%; - `Backspace`: Reset scale to default; - `F5` or `[`: Rotate 90 degrees anticlockwise; - `F6` or `]`: Rotate 90 degrees clockwise; - `F7`: Flip vertical; - `F8`: Flip horizontal; - `i`: Show/hide image properties; - `F11` or `f`: Toggle full screen mode; - `PgDown`, `Space`, or `n`: Open next file; - `PgUp` or `p`: Open previous file; - `Esc`, `Enter`, `F10` or `q`: Exit the program. ## Configuration The viewer searches for the configuration file with name `config` in the following directories: - `$XDG_CONFIG_HOME/swayimg` - `$HOME/.config/swayimg` Sample file is available [here](https://github.com/artemsen/swayimg/blob/master/extra/swayimgrc). See `man swayimgrc` for details. ## Build and install ``` meson build ninja -C build sudo ninja -C build install ``` Arch users can install the program via [AUR](https://aur.archlinux.org/packages/swayimg). swayimg-1.6/extra/000077500000000000000000000000001420071156000141575ustar00rootroot00000000000000swayimg-1.6/extra/bash.completion000066400000000000000000000007611420071156000171730ustar00rootroot00000000000000# swayimg(1) completion _swayimg() { local cur=${COMP_WORDS[COMP_CWORD]} local opts="-g --geometry \ -b --background \ -f --fullscreen \ -s --scale \ -i --info \ -v --version \ -h --help" if [[ ${cur} == -* ]]; then COMPREPLY=($(compgen -W "${opts}" -- "${cur}")) else COMPREPLY=($(compgen -f -- "${cur}")) fi } && complete -F _swayimg swayimg # ex: filetype=sh swayimg-1.6/extra/swayimg.1000066400000000000000000000056731420071156000157340ustar00rootroot00000000000000.\" Swayimg: image viewer for Sway/Wayland .\" Copyright (C) 2021 Artem Senichev .TH SWAYIMG 1 2021-12-28 swayimg "Swayimg manual" .SH NAME swayimg \- image viewer for Sway .SH SYNOPSIS swayimg [\fIOPTIONS\fR...] \fIFILE...\fR .SH DESCRIPTION The \fBswayimg\fR utility can be used for preview images inside the currently focused window (container). .PP Use '-' as \fIFILE\fR to read image data from stdin. .PP The program uses Sway IPC to determine the geometry of the currently focused workspace and window. This data is used calculate the position and size of a new window. Then the program adds two rules to the Sway: "floating enable" and "move position" for \fBswayimg\fR application, 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\-g\fR, \fB\-\-geometry\fR\fB=\fR\fIX,Y,WIDTH,HEIGHT\fR" Set the absolute position and size of the window, for example \fI10,20,100,200\fR creates a window at position 10x20 (top left corner) with a size of 100x200. The comma can be replaced with any non-numeric character. If this parameter is not specified, the geometry of the currently focused Sway window will be used. .IP "\fB\-b\fR, \fB\-\-background\fR\fB=\fR\fIXXXXXX\fR" Set background color from RGB hex string, for example \fI1a2b3c\fR. If this parameter is not specified or contains special value \fIgrid\fR, the default grid background will be used. .IP "\fB\-f\fR, \fB\-\-fullscreen\fR" Start in full screen mode. .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. \'default\': reduce the image to fit the window, but do not enlarge it. \'fit\': reduce or enlarge the image to fit the window. \'real\': real image size (100%). .IP "\fB\-i\fR, \fB\-\-info\fR" Show image properties on startup (path, size, current scale etc). .\" keys .SH KEYBINDINGS .IP "\fBArrows\fR and vim-like moving keys (\fBh\fR,\fBj\fR,\fBk\fR,\fBl\fR)" Move the view point. .IP "\fB+\fP, \fB=\fR" Zoom in. .IP "\fB-\fP" Zoom out. .IP "\fB0\fP" Set scale to 100%. .IP "\fBBackspace\fP" Reset scale to defaults. .IP "\fBF5\fP, \fB[\fP" Rotate 90 degrees anticlockwise. .IP "\fBF6\fP, \fB]\fP" Rotate 90 degrees clockwise. .IP "\fBF7\fP" Flip vertical. .IP "\fBF8\fP" Flip horizontal. .IP "\fBi\fP" Show/hide image properties. .IP "\fBf\fP, \fBF11\fP" Toggle full screen mode. .IP "\fBPgDown\fR, \fBSpace\fR, \fBn\fR" Show next file. .IP "\fBPgUp\fR, \fBp\fR" Show previous file. .IP "\fBEsc\fP, \fBEnter\fP, \fBF10\fP, \fBq\fP" 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-1.6/extra/swayimg.desktop000066400000000000000000000002401420071156000172260ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Swayimg Comment=Image viewer for Sway/Wayland Icon=swayimg Exec=swayimg %F Categories=Graphics;Viewer StartupNotify=false swayimg-1.6/extra/swayimg.png000066400000000000000000000066021420071156000163510ustar00rootroot00000000000000PNG  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 SWAYIMG 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 .\" 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 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 character. Kay and values are case sensitive. .PP The number sign (#) at the beginning of the line indicates a comment. Empty lines and comments are ignored. .SS Properties .PP .IP "\fBscale\fR: initial image scale:" .nf \fIdefault\fR: 100% or less to fit to window; \fIfit\fR: fit to window; \fIreal\fR: real size (100%); .IP "\fBfullscreen\fR: start in dull screen mode, possible values are \fIyes\fR or \fIno\fR" .IP "\fBbackground\fR: background mode or color, possible values are \fIgrid\fR or RGB hex color (e.g. \fI1a2b3c\fR)" .IP "\fBinfo\fR: show image meta information: format, size, EXIF, and current scale, possible values are \fIyes\fR or \fIno\fR" .\" example file .SH EXAMPLES .EX # comment scale = default fullscreen = no background = grid info = no .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-1.6/meson.build000066400000000000000000000077621420071156000152120ustar00rootroot00000000000000# Rules for building with Meson project( 'swayimg', 'c', default_options: [ 'c_std=c99', 'warning_level=3', ], license: 'MIT', version: '0.0.0', ) add_project_arguments( [ '-Wno-unused-parameter', '-D_POSIX_C_SOURCE=200809' ], language: 'c', ) cc = meson.get_compiler('c') version = get_option('version') # mandatory dependencies wlcln = dependency('wayland-client') cairo = dependency('cairo') json = dependency('json-c') xkb = dependency('xkbcommon') rt = cc.find_library('rt') # optional dependencies: file formats support avif = dependency('libavif', required: get_option('avif')) gif = cc.find_library('gif', required: get_option('gif')) jpeg = dependency('libjpeg', required: get_option('jpeg')) jxl = cc.find_library('libjxl', required: get_option('jxl')) png = dependency('libpng', required: get_option('png')) rsvg = dependency('librsvg-2.0', version: '>=2.46', required: get_option('svg')) webp = dependency('libwebp', required: get_option('webp')) # optional dependencies: other features exif = dependency('libexif', required: get_option('exif')) bash = dependency('bash-completion', required: get_option('bash')) # configuration file conf = configuration_data() conf.set('HAVE_LIBAVIF', avif.found()) conf.set('HAVE_LIBGIF', gif.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_LIBWEBP', webp.found()) conf.set('HAVE_LIBEXIF', exif.found()) 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_pkgconfig_variable('pkgdatadir') wlscan = dependency('wayland-scanner', required: false, native: true) if wlscan.found() wl_scanner = find_program(wlscan.get_pkgconfig_variable('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@'], ) # 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 # bash completion installation if bash.found() datadir = get_option('datadir') bash_install_dir = bash.get_pkgconfig_variable( 'completionsdir', define_variable: ['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/image.c', 'src/main.c', 'src/sway.c', 'src/viewer.c', 'src/window.c', 'src/formats/bmp.c', xdg_shell_h, xdg_shell_c, ] if exif.found() sources += 'src/exif.c' endif if avif.found() sources += 'src/formats/avif.c' endif if gif.found() sources += 'src/formats/gif.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 webp.found() sources += 'src/formats/webp.c' endif executable( 'swayimg', sources, dependencies: [ avif, cairo, exif, gif, jpeg, json, jxl, png, rsvg, rt, webp, wlcln, xkb, ], install: true ) swayimg-1.6/meson_options.txt000066400000000000000000000025571420071156000165020ustar00rootroot00000000000000# format support option('avif', type: 'feature', value: 'auto', description: 'Enable AVIF format support') option('gif', type: 'feature', value: 'auto', description: 'Enable GIF 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('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 completions') 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-1.6/src/000077500000000000000000000000001420071156000136235ustar00rootroot00000000000000swayimg-1.6/src/canvas.c000066400000000000000000000244561420071156000152550ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2022 Artem Senichev #include "canvas.h" #include "window.h" #include #include // Text render parameters #define FONT_FAMILY "monospace" #define FONT_SIZE 16 #define LINE_SPACING 2 #define TEXT_COLOR 0xb2b2b2 #define TEXT_SHADOW 0x101010 // Background grid parameters #define GRID_STEP 10 #define GRID_COLOR1 0x333333 #define GRID_COLOR2 0x4c4c4c // Scale thresholds #define MIN_SCALE 10 // pixels #define MAX_SCALE 100.0 // factor #define ROTATE_RAD(r) ((r * 90) * 3.14159 / 180) void reset_canvas(canvas_t* canvas) { canvas->scale = 0.0; canvas->rotate = rotate_0; canvas->flip = flip_none; canvas->x = 0; canvas->y = 0; } void draw_image(const canvas_t* canvas, cairo_surface_t* image, cairo_t* cairo) { const int width = cairo_image_surface_get_width(image); const int height = cairo_image_surface_get_height(image); const int center_x = width / 2; const int center_y = height / 2; cairo_matrix_t matrix; cairo_get_matrix(cairo, &matrix); cairo_matrix_translate(&matrix, canvas->x, canvas->y); // apply scale cairo_matrix_scale(&matrix, canvas->scale, canvas->scale); // apply flip if (canvas->flip) { cairo_matrix_translate(&matrix, center_x, center_y); if (canvas->flip & flip_vertical) { matrix.yy = -matrix.yy; } if (canvas->flip & flip_horizontal) { matrix.xx = -matrix.xx; } cairo_matrix_translate(&matrix, -center_x, -center_y); } // apply rotate if (canvas->rotate) { cairo_matrix_translate(&matrix, center_x, center_y); cairo_matrix_rotate(&matrix, ROTATE_RAD(canvas->rotate)); cairo_matrix_translate(&matrix, -center_x, -center_y); } // draw cairo_set_matrix(cairo, &matrix); cairo_set_source_surface(cairo, image, 0, 0); cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); cairo_paint(cairo); cairo_identity_matrix(cairo); } void draw_grid(const canvas_t* canvas, cairo_surface_t* image, cairo_t* cairo) { const int width = canvas->scale * cairo_image_surface_get_width(image); const int height = canvas->scale * cairo_image_surface_get_height(image); cairo_translate(cairo, canvas->x, canvas->y); // rotate if (canvas->rotate == rotate_90 || canvas->rotate == rotate_270) { const int center_x = width / 2; const int center_y = height / 2; cairo_translate(cairo, center_x, center_y); cairo_rotate(cairo, ROTATE_RAD(canvas->rotate)); cairo_translate(cairo, -center_x, -center_y); } // fill with the first color cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgb(cairo, RGB_RED(GRID_COLOR1), RGB_GREEN(GRID_COLOR1), RGB_BLUE(GRID_COLOR1)); cairo_rectangle(cairo, 0, 0, width, height); cairo_fill(cairo); // draw lighter cells with the second color cairo_set_source_rgb(cairo, RGB_RED(GRID_COLOR2), RGB_GREEN(GRID_COLOR2), RGB_BLUE(GRID_COLOR2)); for (int y = 0; y < height; y += GRID_STEP) { const int cell_height = y + GRID_STEP < height ? GRID_STEP : height - y; int cell_x = y / GRID_STEP % 2 ? 0 : GRID_STEP; for (; cell_x < width; cell_x += 2 * GRID_STEP) { const int cell_width = cell_x + GRID_STEP < width ? GRID_STEP : width - cell_x; cairo_rectangle(cairo, cell_x, y, cell_width, cell_height); cairo_fill(cairo); } } cairo_identity_matrix(cairo); } void draw_text(cairo_t* cairo, int x, int y, const char* text) { // set font cairo_font_face_t* font = cairo_toy_font_face_create( FONT_FAMILY, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_face(cairo, font); cairo_set_font_size(cairo, FONT_SIZE); // shadow cairo_set_source_rgb(cairo, RGB_RED(TEXT_SHADOW), RGB_GREEN(TEXT_SHADOW), RGB_BLUE(TEXT_SHADOW)); cairo_move_to(cairo, x + 1, y + 1 + FONT_SIZE); cairo_show_text(cairo, text); // normal text cairo_set_source_rgb(cairo, RGB_RED(TEXT_COLOR), RGB_GREEN(TEXT_COLOR), RGB_BLUE(TEXT_COLOR)); cairo_move_to(cairo, x, y + FONT_SIZE); cairo_show_text(cairo, text); cairo_set_font_face(cairo, NULL); cairo_font_face_destroy(font); } void draw_lines(cairo_t* cairo, int x, int y, const char** lines) { while (*lines) { draw_text(cairo, x, y, *lines); ++lines; y += FONT_SIZE + LINE_SPACING; } } bool move_viewpoint(canvas_t* canvas, cairo_surface_t* image, move_t direction) { const int img_w = canvas->scale * cairo_image_surface_get_width(image); const int img_h = canvas->scale * cairo_image_surface_get_height(image); const int wnd_w = (int)get_window_width(); const int wnd_h = (int)get_window_height(); const int step_x = wnd_w / 10; const int step_y = wnd_h / 10; int x = canvas->x; int y = canvas->y; switch (direction) { case center_vertical: y = wnd_h / 2 - img_h / 2; break; case center_horizontal: x = wnd_w / 2 - img_w / 2; break; case step_left: if (x <= 0) { x += step_x; if (x > 0) { x = 0; } } break; case step_right: if (x + img_w >= wnd_w) { x -= step_x; if (x + img_w < wnd_w) { x = wnd_w - img_w; } } break; case step_up: if (y <= 0) { y += step_y; if (y > 0) { y = 0; } } break; case step_down: if (y + img_h >= wnd_h) { y -= step_y; if (y + img_h < wnd_h) { y = wnd_h - img_h; } } break; } if (canvas->x != x || canvas->y != y) { canvas->x = x; canvas->y = y; return true; } return false; } /** * Move view point by scale delta considering window center. * @param[in] canvas parameters of the image * @param[in] image image surface * @param[in] delta scale delta */ static void move_scaled(canvas_t* canvas, cairo_surface_t* image, double delta) { const int img_w = cairo_image_surface_get_width(image); const int img_h = cairo_image_surface_get_height(image); const int wnd_w = (int)get_window_width(); const int wnd_h = (int)get_window_height(); const int old_w = (canvas->scale - delta) * img_w; const int old_h = (canvas->scale - delta) * img_h; const int new_w = canvas->scale * img_w; const int new_h = canvas->scale * img_h; if (new_w < wnd_w) { // fits into window width move_viewpoint(canvas, image, center_horizontal); } else { // move to save the center of previous coordinates const int delta_w = old_w - new_w; const int cntr_x = wnd_w / 2 - canvas->x; const int delta_x = ((double)cntr_x / old_w) * delta_w; if (delta_x) { canvas->x += delta_x; if (canvas->x > 0) { canvas->x = 0; } } } if (new_h < wnd_h) { // fits into window height move_viewpoint(canvas, image, center_vertical); } else { // move to save the center of previous coordinates const int delta_h = old_h - new_h; const int cntr_y = wnd_h / 2 - canvas->y; const int delta_y = ((double)cntr_y / old_h) * delta_h; if (delta_y) { canvas->y += delta_y; if (canvas->y > 0) { canvas->y = 0; } } } } bool apply_scale(canvas_t* canvas, cairo_surface_t* image, scale_t op) { const int img_w = cairo_image_surface_get_width(image); const int img_h = cairo_image_surface_get_height(image); const int wnd_w = get_window_width(); const int wnd_h = get_window_height(); const bool swap = (canvas->rotate == rotate_90 || canvas->rotate == rotate_270); const double max_w = swap ? img_h : img_w; const double max_h = swap ? img_w : img_h; const double step = canvas->scale / 10.0; double scale = canvas->scale; switch (op) { case scale_fit_or100: { const double scale_w = 1.0 / (max_w / wnd_w); const double scale_h = 1.0 / (max_h / wnd_h); scale = scale_w < scale_h ? scale_w : scale_h; if (scale > 1.0) { scale = 1.0; } break; } case scale_fit_window: { const double scale_w = 1.0 / (max_w / wnd_w); const double scale_h = 1.0 / (max_h / wnd_h); scale = scale_h < scale_w ? scale_h : scale_w; break; } case scale_100: scale = 1.0; // 100 % break; case zoom_in: if (canvas->scale < MAX_SCALE) { scale = canvas->scale + step; if (scale > MAX_SCALE) { scale = MAX_SCALE; } } break; case zoom_out: scale -= step; if (scale * img_w < MIN_SCALE && scale * img_h < MIN_SCALE) { scale = canvas->scale; // don't change } break; } if (canvas->scale != scale) { // move viewpoint const double delta = scale - canvas->scale; canvas->scale = scale; if (op == scale_fit_window || op == scale_fit_or100 || op == scale_100) { move_viewpoint(canvas, image, center_vertical); move_viewpoint(canvas, image, center_horizontal); } else { move_scaled(canvas, image, delta); } return true; } return false; } void apply_rotate(canvas_t* canvas, bool clockwise) { if (clockwise) { if (canvas->rotate == rotate_270) { canvas->rotate = rotate_0; } else { ++canvas->rotate; } } else { if (canvas->rotate == rotate_0) { canvas->rotate = rotate_270; } else { --canvas->rotate; } } } void apply_flip(canvas_t* canvas, flip_t flip) { canvas->flip ^= flip; } swayimg-1.6/src/canvas.h000066400000000000000000000071541420071156000152560ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2022 Artem Senichev #pragma once #include #include #include // Grid background mode id #define BACKGROUND_GRID UINT32_MAX // Convert color components from RGB to float #define RGB_RED(c) ((double)((c >> 16) & 0xff) / 255.0) #define RGB_GREEN(c) ((double)((c >> 8) & 0xff) / 255.0) #define RGB_BLUE(c) ((double)(c & 0xff) / 255.0) /** Rotate angles. */ typedef enum { rotate_0, ///< No rotate rotate_90, ///< 90 degrees, clockwise rotate_180, ///< 180 degrees rotate_270 ///< 270 degrees, clockwise } rotate_t; /** Flags of the flip transformation. */ typedef enum { flip_none, flip_vertical, flip_horizontal, } flip_t; /** Scaling operations. */ typedef enum { scale_fit_or100, ///< Fit to window, but not more than 100% scale_fit_window, ///< Fit to window size scale_100, ///< Real image size (100%) zoom_in, ///< Enlarge by one step zoom_out ///< Reduce by one step } scale_t; /** Direction of movement. */ typedef enum { center_vertical, ///< Center vertically center_horizontal, ///< Center horizontally step_left, ///< One step to the left step_right, ///< One step to the right step_up, ///< One step up step_down ///< One step down } move_t; /** Canvas context. */ typedef struct { double scale; ///< Scale, 1.0 = 100% rotate_t rotate; ///< Rotation angle flip_t flip; ///< Flip mode flags int x; ///< X-coordinate of the top left corner int y; ///< Y-coordinate of the top left corner } canvas_t; /** Rectangle description. */ typedef struct { int x; int y; int width; int height; } rect_t; /** * Reset canvas parameters to default values. * @param[in] canvas canvas context */ void reset_canvas(canvas_t* canvas); /** * Draw image. * @param[in] canvas canvas context * @param[in] image surface to draw * @param[in] cairo paint context */ void draw_image(const canvas_t* canvas, cairo_surface_t* image, cairo_t* cairo); /** * Draw background grid for transparent images. * @param[in] canvas canvas context * @param[in] image surface to draw * @param[in] cairo paint context */ void draw_grid(const canvas_t* canvas, cairo_surface_t* image, cairo_t* cairo); /** * Draw text line. * @param[in] cairo paint context * @param[in] x left offset * @param[in] y top offset * @param[in] text text to draw */ void draw_text(cairo_t* cairo, int x, int y, const char* text); /** * Draw multiline text. * @param[in] cairo paint context * @param[in] x left offset * @param[in] y top offset * @param[in] lines array of strings, last line must be NULL */ void draw_lines(cairo_t* cairo, int x, int y, const char** lines); /** * Move view point. * @param[in] canvas canvas context * @param[in] image image surface * @param[in] direction move direction * @return true if coordinates were changed */ bool move_viewpoint(canvas_t* canvas, cairo_surface_t* image, move_t direction); /** * Apply scaling operation. * @param[in] canvas canvas context * @param[in] image image surface * @param[in] op scale operation type * @return true if scale was changed */ bool apply_scale(canvas_t* canvas, cairo_surface_t* image, scale_t op); /** * Rotate image on 90 degrees. * @param[in] canvas canvas context * @param[in] clockwise rotate direction */ void apply_rotate(canvas_t* canvas, bool clockwise); /** * Flip image. * @param[in] canvas canvas context * @param[in] flip axis type */ void apply_flip(canvas_t* canvas, flip_t flip); swayimg-1.6/src/config.c000066400000000000000000000116101420071156000152330ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2022 Artem Senichev #include "config.h" #include #include #include #include #include #include config_t config = { .scale = scale_fit_or100, .background = BACKGROUND_GRID, }; /** * Apply property to configuration. * @param[in] key property key * @param[in] value property value */ static void apply_conf(const char* key, const char* value) { const char* yes = "yes"; const char* no = "no"; if (strcmp(key, "scale") == 0) { if (strcmp(value, "default") == 0) { config.scale = scale_fit_or100; } else if (strcmp(value, "fit") == 0) { config.scale = scale_fit_window; } else if (strcmp(value, "real") == 0) { config.scale = scale_100; } } else if (strcmp(key, "fullscreen") == 0) { if (strcmp(value, yes) == 0) { config.fullscreen = true; } else if (strcmp(value, no) == 0) { config.fullscreen = false; } } else if (strcmp(key, "background") == 0) { set_background(value); } else if (strcmp(key, "info") == 0) { if (strcmp(value, yes) == 0) { config.show_info = true; } else if (strcmp(value, no) == 0) { config.show_info = false; } } } /** * Open user's configuration file. * @return file descriptior or NULL on errors */ static FILE* open_file(void) { char path[64]; size_t len; const char* postfix = "/swayimg/config"; const size_t postfix_len = strlen(postfix); const char* config_dir = getenv("XDG_CONFIG_HOME"); if (config_dir) { len = strlen(config_dir); if (len < sizeof(path)) { memcpy(path, config_dir, len + 1 /*last null*/); } else { len = 0; } } else { config_dir = getenv("HOME"); len = config_dir ? strlen(config_dir) : 0; if (len && len < sizeof(path)) { memcpy(path, config_dir, len + 1 /*last null*/); const char* dir = "/.config"; const size_t dlen = strlen(dir); if (len + dlen < sizeof(path)) { memcpy(path + len, dir, dlen + 1 /*last null*/); len += dlen; } else { len = 0; } } } if (len && len + postfix_len < sizeof(path)) { memcpy(path + len, postfix, postfix_len + 1 /*last null*/); return fopen(path, "r"); } return NULL; } void load_config(void) { char* buff = NULL; size_t buff_sz = 0; ssize_t nread; FILE* fd = open_file(); if (!fd) { return; } while ((nread = getline(&buff, &buff_sz, fd)) != -1) { const char* value; char* delim; char* line = buff; // trim spaces while (nread-- && isspace(line[nread])) { line[nread] = 0; } while (*line && isspace(*line)) { ++line; } if (!*line || *line == '#') { continue; // skip empty lines and comments } delim = strchr(line, '='); if (!delim) { continue; // invalid format: delimiter not found } // 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; } apply_conf(line, value); } free(buff); fclose(fd); } bool set_scale(const char* value) { if (strcmp(value, "default") == 0) { config.scale = scale_fit_or100; } else if (strcmp(value, "fit") == 0) { config.scale = scale_fit_window; } else if (strcmp(value, "real") == 0) { config.scale = scale_100; } else { return false; } return true; } bool set_background(const char* value) { uint32_t bkg; if (strcmp(value, "grid") == 0) { bkg = BACKGROUND_GRID; } else { bkg = strtoul(value, NULL, 16); if (bkg > 0x00ffffff || errno == ERANGE || (bkg == 0 && errno == EINVAL)) { return false; } } config.background = bkg; return true; } bool set_geometry(const char* value) { rect_t window; int* nums[] = { &window.x, &window.y, &window.width, &window.height }; const char* ptr = value; size_t idx; for (idx = 0; *ptr && idx < sizeof(nums) / sizeof(nums[0]); ++idx) { *nums[idx] = atoi(ptr); // skip digits while (isdigit(*ptr)) { ++ptr; } // skip delimiter while (*ptr && !isdigit(*ptr)) { ++ptr; } } if (window.width <= 0 || window.height <= 0 || idx != sizeof(nums) / sizeof(nums[0])) { return false; } config.window = window; return true; } swayimg-1.6/src/config.h000066400000000000000000000021011420071156000152330ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2022 Artem Senichev #pragma once #include "canvas.h" /** App configuration. */ typedef struct { scale_t scale; ///< Initial scale rect_t window; ///< Window geometry uint32_t background; ///< Background mode/color bool fullscreen; ///< Full screen mode bool show_info; ///< Show image info } config_t; extern config_t config; /** Load configuration from file. */ void load_config(void); /** * Set initial scale from one of predefined string (default, fit or real). * @param[in] value argument to parse * @return false if value format is invalid */ bool set_scale(const char* value); /** * Set background type/color from RGB hex string. * @param[in] value argument to parse * @return false if value format is invalid */ bool set_background(const char* value); /** * Set window geometry (position and size) from string "x,y,width,height". * @param[in] value argument to parse * @return false if value format is invalid */ bool set_geometry(const char* value); swayimg-1.6/src/exif.c000066400000000000000000000106241420071156000147250ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2022 Artem Senichev #include "exif.h" #include #include /** * Set orientation from EXIF data. * @param[in] img target image instance * @param[in] exif instance of EXIF reader */ static void read_orientation(image_t* 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); const ExifShort orientation = exif_get_short(entry->data, byte_order); if (orientation >= 1 && orientation <= 8) { img->orientation = (orientation_t)orientation; } } } /** * Add meta info from EXIF tag. * @param[in] img target image instance * @param[in] exif instance of EXIF reader * @param[in] tag EXIF tag * @param[in] name EXIF tag name */ static void add_meta(image_t* 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) { add_image_meta(img, name, value); } } } /** * Append string. * @param[in] buffer destination buffer * @param[in] buffer_max size of the buffer * @param[in] 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[in] exif instance of EXIF reader * @param[in] tag location tag * @param[in] ref location reference * @param[in] buffer destination buffer * @param[in] 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[in] img target image instance * @param[in] exif instance of EXIF reader */ static void read_location(image_t* 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)) { add_image_meta(img, "Location", location); } } } void read_exif(image_t* img, const uint8_t* data, size_t size) { ExifData* exif = exif_data_new_from_data(data, (unsigned int)size); if (exif) { read_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-1.6/src/exif.h000066400000000000000000000006051420071156000147300ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2022 Artem Senichev #pragma once #include "image.h" #include #include /** * Read and handle EXIF data. * @param[in] img target image instance * @param[in] data raw image data * @param[in] size size of image data in bytes */ void read_exif(image_t* img, const uint8_t* data, size_t size); swayimg-1.6/src/formats/000077500000000000000000000000001420071156000152765ustar00rootroot00000000000000swayimg-1.6/src/formats/avif.c000066400000000000000000000067011420071156000163730ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2021 Artem Senichev // // AV1 image format support (AVIF) // #include #include #include #include #include // HEIF signature static const uint8_t signature[] = { 'f', 't', 'y', 'p' }; // Ignore first 4 bytes in header #define SIGNATURE_START 4 // Number of components of rgba pixel #define RGBA_NUM 4 // AV1 loader implementation cairo_surface_t* load_avif(const uint8_t* data, size_t size, char* format, size_t format_sz) { cairo_surface_t* surface = NULL; avifResult rc; avifRGBImage rgb; avifDecoder* decoder = NULL; memset(&rgb, 0, sizeof(rgb)); // check signature if (size < SIGNATURE_START + sizeof(signature) || memcmp(data + SIGNATURE_START, signature, sizeof(signature))) { return NULL; } // open file in decoder decoder = avifDecoderCreate(); if (!decoder) { fprintf(stderr, "Error creating AV1 decoder\n"); return NULL; } rc = avifDecoderSetIOMemory(decoder, data, size); if (rc == AVIF_RESULT_OK) { rc = avifDecoderParse(decoder); } if (rc == AVIF_RESULT_OK) { rc = avifDecoderNextImage(decoder); // first frame only } if (rc != AVIF_RESULT_OK) { fprintf(stderr, "Error decoding AV1: %s\n", avifResultToString(rc)); goto done; } // setup decoder avifRGBImageSetDefaults(&rgb, decoder->image); rgb.format = AVIF_RGB_FORMAT_BGRA; avifRGBImageAllocatePixels(&rgb); // decode the frame rc = avifImageYUVToRGB(decoder->image, &rgb); if (rc != AVIF_RESULT_OK) { fprintf(stderr, "YUV to RGB failed: %s", avifResultToString(rc)); goto done; } // create image instance surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, rgb.width, rgb.height); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Unable to create surface\n"); return NULL; } snprintf(format, format_sz, "AV1 %dbit %s", rgb.depth, avifPixelFormatToString(decoder->image->yuvFormat)); // put image on to cairo surface uint8_t* sdata = cairo_image_surface_get_data(surface); if (rgb.depth == 8) { // simple 8bit image memcpy(sdata, rgb.pixels, rgb.width * rgb.height * RGBA_NUM); } else { // convert to 8bit image const size_t max_clr = 1 << rgb.depth; const size_t src_stride = rgb.width * RGBA_NUM * sizeof(uint16_t); const size_t dst_stride = cairo_image_surface_get_stride(surface); for (size_t y = 0; y < rgb.height; ++y) { const uint16_t* src_y = (const uint16_t*)(rgb.pixels + y * src_stride); uint8_t* dst_y = sdata + y * dst_stride; for (size_t x = 0; x < rgb.width; ++x) { uint8_t* dst_x = dst_y + x * RGBA_NUM; const uint16_t* src_x = src_y + x * RGBA_NUM; dst_x[0] = (uint8_t)((float)src_x[0] / max_clr * 255); dst_x[1] = (uint8_t)((float)src_x[1] / max_clr * 255); dst_x[2] = (uint8_t)((float)src_x[2] / max_clr * 255); dst_x[3] = (uint8_t)((float)src_x[3] / max_clr * 255); } } } cairo_surface_mark_dirty(surface); done: if (decoder) { avifRGBImageFreePixels(&rgb); avifDecoderDestroy(decoder); } return surface; } swayimg-1.6/src/formats/bmp.c000066400000000000000000000152121420071156000162210ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev // // BMP image format support // #include #include #include #include #include #include #include #define BITS_IN_BYTE 8 // BMP signature static const uint8_t signature[] = { 'B', 'M' }; // BITMAPFILEHEADER struct __attribute__((__packed__)) bmp_header { uint16_t type; uint32_t file_size; uint32_t reserved; uint32_t offset; }; // BITMAPCOREINFO struct __attribute__((__packed__)) bmp_info { uint32_t dib_size; uint32_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; uint32_t red_mask; uint32_t green_mask; uint32_t blue_mask; }; /** * Get number of the consecutive zero bits (trailing) on the right. * @param[in] val source value * @return number of zero bits */ static size_t right_zeros(uint32_t val) { size_t count = sizeof(uint32_t) * BITS_IN_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[in] val source value * @return number of bits set */ static 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[in] val color channel mask * @return shift size: positive=right, negative=left */ static ssize_t mask_shift(uint32_t mask) { const ssize_t start = right_zeros(mask) + bits_set(mask); return start - BITS_IN_BYTE; } // BMP loader implementation cairo_surface_t* load_bmp(const uint8_t* data, size_t size, char* format, size_t format_sz) { // check signature if (size < sizeof(struct bmp_header) + sizeof(struct bmp_info) || memcmp(data, signature, sizeof(signature))) { return NULL; } const struct bmp_header* header = (const struct bmp_header*)data; const struct bmp_info* bmp = (const struct bmp_info*)(data + sizeof(struct bmp_header)); const size_t stride = 4 * ((bmp->width * bmp->bpp + 31) / 32); // RLE is not supported yet if (bmp->compression != 0 /* BI_RGB */ && bmp->compression != 3 /* BI_BITFIELDS */) { fprintf(stderr, "BMP compression (%i) not supported\n", bmp->compression); return NULL; } // check size of input buffer if (size < header->offset + abs(bmp->height) * stride) { fprintf(stderr, "Invalid BMP format\n"); return NULL; } // create image instance const cairo_format_t fmt = bmp->bpp == 32 ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24; cairo_surface_t* surface = cairo_image_surface_create(fmt, bmp->width, abs(bmp->height)); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Unable to create surface\n"); return NULL; } snprintf(format, format_sz, "BMP %dbit", bmp->bpp); // color channels (default 5:5:5) const bool def_mask = !bmp->red_mask && !bmp->green_mask && !bmp->blue_mask; const uint32_t mask_r = def_mask ? 0x001f : bmp->red_mask; const uint32_t mask_g = def_mask ? 0x03e0 : bmp->green_mask; const uint32_t mask_b = def_mask ? 0x7c00 : bmp->blue_mask; 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); // flip and convert to argb (cairo internal format) uint8_t* dst_data = cairo_image_surface_get_data(surface); const size_t dst_stride = cairo_image_surface_get_stride(surface); for (size_t y = 0; y < abs(bmp->height); ++y) { uint8_t* dst_y = dst_data + y * dst_stride; const uint8_t* src_y = data + header->offset; if (bmp->height > 0) { src_y += (bmp->height - y - 1) * stride; } else { src_y += y * stride; // top-down format (rarely used) } for (size_t x = 0; x < bmp->width; ++x) { uint8_t a = 0xff, r = 0, g = 0, b = 0; const uint8_t* src = src_y + x * (bmp->bpp / BITS_IN_BYTE); switch (bmp->bpp) { case 32: a = src[3]; r = src[2]; g = src[1]; b = src[0]; break; case 24: r = src[2]; g = src[1]; b = src[0]; break; case 16: { const uint16_t val = *(uint16_t*)src; r = shift_r > 0 ? (val & mask_r) >> shift_r : (val & mask_r) << -shift_r; g = shift_g > 0 ? (val & mask_g) >> shift_g : (val & mask_g) << -shift_g; b = shift_b > 0 ? (val & mask_b) >> shift_b : (val & mask_b) << -shift_b; } break; default: { // indexed colors const size_t bits_offset = x * bmp->bpp; const size_t byte_offset = bits_offset / BITS_IN_BYTE; const size_t start_bit = bits_offset - byte_offset * BITS_IN_BYTE; const uint8_t val = (*(src_y + byte_offset) >> (BITS_IN_BYTE - bmp->bpp - start_bit)) & (0xff >> (BITS_IN_BYTE - bmp->bpp)); if (val < bmp->clr_palette) { const uint8_t* clr = data + sizeof(struct bmp_header) + bmp->dib_size + val * sizeof(uint32_t); r = clr[2]; g = clr[1]; b = clr[0]; } else { // color without palette? r = ((val >> 0) & 1) * 0xff; g = ((val >> 1) & 1) * 0xff; b = ((val >> 2) & 1) * 0xff; } } } uint32_t* dst_x = (uint32_t*)(dst_y + x * 4 /*argb*/); *dst_x = a << 24 | r << 16 | g << 8 | b; } } cairo_surface_mark_dirty(surface); return surface; } swayimg-1.6/src/formats/gif.c000066400000000000000000000061151420071156000162120ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev // // GIF image format support // #include #include #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; } // GIF loader implementation cairo_surface_t* load_gif(const uint8_t* data, size_t size, char* format, size_t format_sz) { cairo_surface_t* surface = NULL; // check signature if (size < sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return NULL; } struct buffer buf = { .data = data, .size = size, .position = 0, }; int err; GifFileType* gif = DGifOpen(&buf, gif_reader, &err); if (!gif) { fprintf(stderr, "Unable to open GIF decoder: [%i] %s\n", err, GifErrorString(err)); return NULL; } // decode with high-level API if (DGifSlurp(gif) != GIF_OK) { fprintf(stderr, "Unable to decode GIF: [%i] %s\n", err, GifErrorString(err)); goto done; } if (!gif->SavedImages) { fprintf(stderr, "No saved images in GIF\n"); goto done; } // create image instance surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, gif->SWidth, gif->SHeight); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Unable to create surface\n"); return NULL; } snprintf(format, format_sz, "GIF"); // we don't support animation, show the first frame only const GifImageDesc* frame = &gif->SavedImages->ImageDesc; const GifColorType* colors = gif->SColorMap ? gif->SColorMap->Colors : frame->ColorMap->Colors; uint32_t* base = (uint32_t*)(cairo_image_surface_get_data(surface) + frame->Top * cairo_image_surface_get_stride(surface)); for (int y = 0; y < frame->Height; ++y) { uint32_t* pixel = base + y * gif->SWidth + frame->Left; const uint8_t* raster = &gif->SavedImages->RasterBits[y * gif->SWidth]; for (int x = 0; x < frame->Width; ++x) { const uint8_t color = raster[x]; if (color != gif->SBackGroundColor) { const GifColorType* rgb = &colors[color]; *pixel = 0xff000000 | rgb->Red << 16 | rgb->Green << 8 | rgb->Blue; } ++pixel; } } cairo_surface_mark_dirty(surface); done: DGifCloseFile(gif, NULL); return surface; } swayimg-1.6/src/formats/jpeg.c000066400000000000000000000064071420071156000163760ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev // // JPEG image format support // #include #include #include #include #include #include #include // depends on stdio.h, uses FILE but desn'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; }; 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); fprintf(stderr, "JPEG decode failed: %s\n", msg); longjmp(err->setjmp, 1); } // JPEG loader implementation cairo_surface_t* load_jpeg(const uint8_t* data, size_t size, char* format, size_t format_sz) { cairo_surface_t* surface = NULL; struct jpeg_decompress_struct jpg; struct jpg_error_manager err; // check signature if (size < sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return NULL; } jpg.err = jpeg_std_error(&err.mgr); err.mgr.error_exit = jpg_error_exit; if (setjmp(err.setjmp)) { if (surface) { cairo_surface_destroy(surface); } jpeg_destroy_decompress(&jpg); return NULL; } 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 // create image instance surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, jpg.output_width, jpg.output_height); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Unable to create surface\n"); jpeg_destroy_decompress(&jpg); return NULL; } snprintf(format, format_sz, "JPEG %dbit", jpg.out_color_components * 8); uint8_t* raw = cairo_image_surface_get_data(surface); const size_t stride = cairo_image_surface_get_stride(surface); while (jpg.output_scanline < jpg.output_height) { uint8_t* line = raw + jpg.output_scanline * stride; jpeg_read_scanlines(&jpg, &line, 1); // convert grayscale to argb (cairo internal format) 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] = 0xff000000 | src << 16 | src << 8 | src; } } #ifndef LIBJPEG_TURBO_VERSION // convert rgb to argb (cairo internal format) 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] = 0xff000000 | src[0] << 16 | src[1] << 8 | src[2]; } } #endif // LIBJPEG_TURBO_VERSION } cairo_surface_mark_dirty(surface); jpeg_finish_decompress(&jpg); jpeg_destroy_decompress(&jpg); return surface; } swayimg-1.6/src/formats/jxl.c000066400000000000000000000113031420071156000162350ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2021 Artem Senichev // // JPEG XL image format support // #include #include #include #include #include // JPEG XL loader implementation cairo_surface_t* load_jxl(const uint8_t* data, size_t size, char* format, size_t format_sz) { cairo_surface_t* surface = NULL; uint8_t* buffer = NULL; size_t buffer_sz; JxlDecoder* jxl; JxlBasicInfo info; JxlDecoderStatus status; 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 NULL; default: break; } // initialize decoder jxl = JxlDecoderCreate(NULL); if (!jxl) { fprintf(stderr, "Unable to create JPEG XL decoder\n"); return NULL; } status = JxlDecoderSetInput(jxl, data, size); if (status != JXL_DEC_SUCCESS) { fprintf(stderr, "Unable to set JPEG XL buffer [%i]\n", status); goto error; } // process decoding status = JxlDecoderSubscribeEvents(jxl, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); if (status != JXL_DEC_SUCCESS) { fprintf(stderr, "JPEG XL event subscription failed\n"); goto error; } do { JxlDecoderStatus rc; status = JxlDecoderProcessInput(jxl); switch (status) { case JXL_DEC_SUCCESS: break; // decoding complete case JXL_DEC_ERROR: fprintf(stderr, "JPEG XL decoder failed\n"); goto error; case JXL_DEC_BASIC_INFO: rc = JxlDecoderGetBasicInfo(jxl, &info); if (rc != JXL_DEC_SUCCESS) { fprintf(stderr, "Unable to get JPEG XL info [%i]\n", rc); goto error; } 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) { fprintf(stderr, "Unable to get JPEG XL buffer size [%i]\n", rc); goto error; } // create cairo image surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, info.xsize, info.ysize); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Unable to create surface\n"); goto error; } // check buffer format buffer = cairo_image_surface_get_data(surface); if (buffer_sz != info.ysize * cairo_image_surface_get_stride(surface)) { fprintf(stderr, "Unsupported JPEG XL buffer format\n"); goto error; } // set output buffer rc = JxlDecoderSetImageOutBuffer(jxl, &jxl_format, buffer, buffer_sz); if (rc != JXL_DEC_SUCCESS) { fprintf(stderr, "Unable to set JPEG XL buffer [%i]\n", rc); goto error; } break; default: fprintf(stderr, "Unexpected JPEG XL status: %i\n", status); } } while (status != JXL_DEC_SUCCESS); if (!buffer) { // JXL_DEC_NEED_IMAGE_OUT_BUFFER was not handled, something went wrong fprintf(stderr, "Missed buffer initialization in JPEG XL decoder\n"); goto error; } // convert colors: JPEG XL -> Cairo (RGBA -> ARGB) for (size_t i = 0; i < info.xsize * info.ysize; ++i) { uint8_t* pixel = buffer + i * jxl_format.num_channels; const uint8_t tmp = pixel[0]; pixel[0] = pixel[2]; pixel[2] = tmp; } cairo_surface_mark_dirty(surface); // format description: total number of bits per pixel snprintf(format, format_sz, "JPEG XL %ubit", info.bits_per_sample * info.num_color_channels + info.alpha_bits); JxlDecoderDestroy(jxl); return surface; error: JxlDecoderDestroy(jxl); if (surface) { cairo_surface_destroy(surface); } return NULL; } swayimg-1.6/src/formats/png.c000066400000000000000000000116431420071156000162330ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev // // PNG image format support // #include #include #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"); } } /** * Apply alpha to color. * @param[in] alpha alpha channel value * @param[in] color color value * @return color with applied alpha */ static uint8_t multiply_alpha(uint8_t alpha, uint8_t color) { const uint16_t temp = (alpha * color) + 0x80; return ((temp + (temp >> 8)) >> 8); } /** * Create array with pointers to image lines. * @param[in] surface image surface * @return allocated buffer, the caller must free it */ static png_bytep* get_lines(cairo_surface_t* surface) { uint8_t* raw = cairo_image_surface_get_data(surface); const size_t stride = cairo_image_surface_get_stride(surface); const size_t height = cairo_image_surface_get_height(surface); png_bytep* lines = malloc(height * sizeof(png_bytep)); if (!lines) { return NULL; } for (size_t i = 0; i < height; ++i) { lines[i] = raw + stride * i; } return lines; } // PNG loader implementation cairo_surface_t* load_png(const uint8_t* data, size_t size, char* format, size_t format_sz) { cairo_surface_t* surface = NULL; png_struct* png = NULL; png_info* info = NULL; png_bytep* lines = NULL; size_t width, height; png_byte color_type, bit_depth; struct mem_reader reader = { .data = data, .size = size, .position = 0, }; // check signature if (png_sig_cmp(data, 0, size) != 0) { return NULL; } // create decoder png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { fprintf(stderr, "Unable to create PNG decoder\n"); return NULL; } info = png_create_info_struct(png); if (!info) { png_destroy_read_struct(&png, NULL, NULL); fprintf(stderr, "Unable to create PNG info\n"); return NULL; } // setup error handling if (setjmp(png_jmpbuf(png))) { png_destroy_read_struct(&png, &info, NULL); free(lines); if (surface) { cairo_surface_destroy(surface); } return NULL; } // get general image info png_set_read_fn(png, &reader, &png_reader); png_read_info(png, info); width = png_get_image_width(png, info); height = png_get_image_height(png, info); color_type = png_get_color_type(png, info); bit_depth = png_get_bit_depth(png, info); // setup decoder 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); // create image instance surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Unable to create surface\n"); return NULL; } snprintf(format, format_sz, "PNG %dbit", bit_depth); // allocate buffer for pointers to image lines lines = get_lines(surface); if (!lines) { png_destroy_read_struct(&png, &info, NULL); cairo_surface_destroy(surface); fprintf(stderr, "Not enough memory to decode PNG\n"); return NULL; } // read image png_read_image(png, lines); // handle transparency for (size_t y = 0; y < height; ++y) { for (size_t x = 0; x < width; ++x) { uint8_t* pixel = (uint8_t*)lines[y] + x * 4 /*argb*/; const uint8_t alpha = pixel[3]; if (alpha != 0xff) { pixel[0] = multiply_alpha(alpha, pixel[0]); pixel[1] = multiply_alpha(alpha, pixel[1]); pixel[2] = multiply_alpha(alpha, pixel[2]); } } } cairo_surface_mark_dirty(surface); // free resources png_destroy_read_struct(&png, &info, NULL); free(lines); return surface; } swayimg-1.6/src/formats/svg.c000066400000000000000000000046011420071156000162420ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev // // SVG image format support // #include #include #include #include #include #include // SVG uses physical units to store size, // these macro defines default viewport dimension in pixels #define VIEWPORT_SIZE 2048 // SVG signature static const uint8_t signature[] = { '<' }; // SVG loader implementation cairo_surface_t* load_svg(const uint8_t* data, size_t size, char* format, size_t format_sz) { cairo_surface_t* surface = NULL; RsvgHandle* svg; GError* err = NULL; gboolean has_viewport; RsvgRectangle viewport; cairo_t* cr = NULL; // check signature, this an xml, so skip spaces from the start while (size && isspace(*data) != 0) { ++data; --size; } if (size < sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return NULL; } svg = rsvg_handle_new_from_data(data, size, &err); if (!svg) { fprintf(stderr, "Invalid SVG format"); if (err && err->message) { fprintf(stderr, ": %s\n", err->message); } else { fprintf(stderr, "\n"); } return NULL; } // define image size in pixels rsvg_handle_get_intrinsic_dimensions(svg, NULL, NULL, NULL, NULL, &has_viewport, &viewport); if (!has_viewport) { viewport.x = 0; viewport.y = 0; viewport.width = VIEWPORT_SIZE; viewport.height = VIEWPORT_SIZE; } // create image instance surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, viewport.width, viewport.height); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Unable to create surface\n"); goto done; } snprintf(format, format_sz, "SVG"); // render svg to surface cr = cairo_create(surface); if (!rsvg_handle_render_document(svg, cr, &viewport, &err)) { fprintf(stderr, "Invalid SVG format"); if (err && err->message) { fprintf(stderr, ": %s\n", err->message); } else { fprintf(stderr, "\n"); } goto done; } done: cairo_destroy(cr); g_object_unref(svg); return surface; } swayimg-1.6/src/formats/webp.c000066400000000000000000000051101420071156000163740ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev // // WebP image format support // #include #include #include #include #include // WebP signature static const uint8_t signature[] = { 'R', 'I', 'F', 'F' }; /** * Apply alpha to color. * @param[in] alpha alpha channel value * @param[in] color color value * @return color with applied alpha */ static uint8_t multiply_alpha(uint8_t alpha, uint8_t color) { const uint16_t temp = (alpha * color) + 0x80; return ((temp + (temp >> 8)) >> 8); } // WebP loader implementation cairo_surface_t* load_webp(const uint8_t* data, size_t size, char* format, size_t format_sz) { // check signature if (size < sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return NULL; } // get image properties WebPBitstreamFeatures prop; VP8StatusCode status = WebPGetFeatures(data, size, &prop); if (status != VP8_STATUS_OK) { fprintf(stderr, "Unable to get WebP image properties: status %i\n", status); return NULL; } // create image instance const cairo_format_t fmt = prop.has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24; cairo_surface_t* surface = cairo_image_surface_create(fmt, prop.width, prop.height); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Unable to create surface\n"); return NULL; } snprintf(format, format_sz, "WebP %s %s%s", prop.format == 1 ? "lossy" : "lossless", prop.has_alpha ? "+alpha" : "", prop.has_animation ? "+animation" : ""); uint8_t* dst_data = cairo_image_surface_get_data(surface); const size_t stride = cairo_image_surface_get_stride(surface); const size_t len = stride * prop.height; if (!WebPDecodeBGRAInto(data, size, dst_data, len, stride)) { fprintf(stderr, "Error decoding WebP\n"); cairo_surface_destroy(surface); return NULL; } // handle transparency if (prop.has_alpha) { for (size_t i = 0; i < len; i += 4 /* argb */) { const uint8_t alpha = dst_data[i + 3]; if (alpha != 0xff) { dst_data[i + 0] = multiply_alpha(alpha, dst_data[i + 0]); dst_data[i + 1] = multiply_alpha(alpha, dst_data[i + 1]); dst_data[i + 2] = multiply_alpha(alpha, dst_data[i + 2]); } } } cairo_surface_mark_dirty(surface); return surface; } swayimg-1.6/src/image.c000066400000000000000000000173001420071156000150520ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2021 Artem Senichev #include "image.h" #include "buildcfg.h" #include "exif.h" #include #include #include #include #include #include #include #include // Number of characters in the name field in meta info #define META_NAME_LEN 10 /** * Image loader function. * @param[in] data raw image data * @param[in] size size of image data in bytes * @param[in] format buffer for format description * @param[in] format_sz size of format buffer * @return image surface or NULL if load failed */ typedef cairo_surface_t* (*image_load)(const uint8_t* data, size_t size, char* format, size_t format_sz); // Construct function name of loader #define LOADER_FUNCTION(name) load_##name // Declaration of loader function #define LOADER_DECLARE(name) \ cairo_surface_t* LOADER_FUNCTION(name)(const uint8_t* data, size_t size, \ char* format, size_t format_sz) // declaration of loaders #ifdef HAVE_LIBJPEG LOADER_DECLARE(jpeg); #endif // HAVE_LIBJPEG #ifdef HAVE_LIBPNG LOADER_DECLARE(png); #endif // HAVE_LIBPNG #ifdef HAVE_LIBWEBP LOADER_DECLARE(webp); #endif // HAVE_LIBWEBP #ifdef HAVE_LIBGIF LOADER_DECLARE(gif); #endif // HAVE_LIBGIF LOADER_DECLARE(bmp); #ifdef HAVE_LIBRSVG LOADER_DECLARE(svg); #endif // HAVE_LIBRSVG #ifdef HAVE_LIBAVIF LOADER_DECLARE(avif); #endif // HAVE_LIBAVIF #ifdef HAVE_LIBJXL LOADER_DECLARE(jxl); #endif // HAVE_LIBJXL // list of available loaders (functions from formats/*) static const image_load loaders[] = { #ifdef HAVE_LIBJPEG &LOADER_FUNCTION(jpeg), #endif // HAVE_LIBJPEG #ifdef HAVE_LIBPNG &LOADER_FUNCTION(png), #endif // HAVE_LIBPNG #ifdef HAVE_LIBWEBP &LOADER_FUNCTION(webp), #endif // HAVE_LIBWEBP #ifdef HAVE_LIBGIF &LOADER_FUNCTION(gif), #endif // HAVE_LIBGIF &LOADER_FUNCTION(bmp), #ifdef HAVE_LIBRSVG &LOADER_FUNCTION(svg), #endif // HAVE_LIBRSVG #ifdef HAVE_LIBAVIF &LOADER_FUNCTION(avif), #endif // HAVE_LIBAVIF #ifdef HAVE_LIBJXL &LOADER_FUNCTION(jxl), #endif // HAVE_LIBJXL }; const char* supported_formats(void) { return "bmp" #ifdef HAVE_LIBJPEG ",jpeg" #endif // HAVE_LIBJPEG #ifdef HAVE_LIBJXL ",jxl" #endif // HAVE_LIBJXL #ifdef HAVE_LIBPNG ",png" #endif // HAVE_LIBPNG #ifdef HAVE_LIBGIF ",gif" #endif // HAVE_LIBGIF #ifdef HAVE_LIBRSVG ",svg" #endif // HAVE_LIBRSVG #ifdef HAVE_LIBWEBP ",webp" #endif // HAVE_LIBWEBP #ifdef HAVE_LIBAVIF ",avif" #endif // HAVE_LIBAVIF ; } /** * Create image instance from memory buffer. * @param[in] path path to the image * @param[in] data raw image data * @param[in] size size of image data in bytes * @return image instance or NULL on errors */ static image_t* image_create(const char* path, const uint8_t* data, size_t size) { image_t* img; char format[32]; cairo_surface_t* surface = NULL; // decode image for (size_t i = 0; i < sizeof(loaders) / sizeof(loaders[0]); ++i) { surface = loaders[i](data, size, format, sizeof(format)); if (surface) { break; } } if (!surface) { // failed to load return NULL; } // create image instance img = calloc(1, sizeof(image_t)); if (!img) { cairo_surface_destroy(surface); fprintf(stderr, "Not enough memory\n"); return NULL; } img->surface = surface; img->path = path; // add general meta info add_image_meta(img, "File", path); add_image_meta(img, "Format", format); snprintf(format, sizeof(format), "%ix%i", cairo_image_surface_get_width(img->surface), cairo_image_surface_get_height(img->surface)); add_image_meta(img, "Size", format); #ifdef HAVE_LIBEXIF // handle EXIF data read_exif(img, data, size); #endif // HAVE_LIBEXIF return img; } image_t* image_from_file(const char* file) { image_t* img = NULL; void* data = MAP_FAILED; struct stat st; int fd; // open file fd = open(file, O_RDONLY); if (fd == -1) { fprintf(stderr, "Unable to open file %s: %s\n", file, strerror(errno)); goto done; } // get file size if (fstat(fd, &st) == -1) { fprintf(stderr, "Unable to get file stat for %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, "Unable to map shared file: [%i] %s\n", errno, strerror(errno)); goto done; } img = image_create(file, data, st.st_size); if (!img) { fprintf(stderr, "Unsupported file format: %s\n", file); } done: if (data != MAP_FAILED) { munmap(data, st.st_size); } if (fd != -1) { close(fd); } return img; } image_t* image_from_stdin(void) { image_t* img = NULL; uint8_t* data = NULL; size_t size = 0; size_t capacity = 0; while (1) { 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) { img = image_create("{STDIN}", data, size); if (!img) { fprintf(stderr, "Unsupported file format\n"); } } done: if (data) { free(data); } return img; } void image_free(image_t* img) { if (img) { for (size_t i = 0; i < sizeof(img->meta) / sizeof(img->meta[0]); ++i) { free(img->meta[i]); } if (img->surface) { cairo_surface_destroy(img->surface); } free(img); } } /** * Create meta information line. * @param[in] name property name * @param[in] value property value * @return pointer to allocated string, caller must free it */ static char* create_meta(const char* name, const char* value) { char* meta; size_t meta_len; size_t consumed = 0; const size_t name_len = strlen(name); const size_t value_len = strlen(value); // calculate entry length if (name_len < META_NAME_LEN) { meta_len = META_NAME_LEN; } else { meta_len = name_len; } meta_len += 3; // delimiter (": ") and termination null meta_len += value_len; // compose entry line "name: value" meta = malloc(meta_len); if (!meta) { fprintf(stderr, "Not enough memory\n"); return NULL; } memcpy(meta, name, name_len); consumed = name_len; meta[consumed++] = ':'; meta[consumed++] = ' '; while (consumed < META_NAME_LEN) { meta[consumed++] = ' '; } memcpy(meta + consumed, value, value_len); consumed += value_len; meta[consumed++] = 0; return meta; } void add_image_meta(image_t* img, const char* name, const char* value) { size_t index; const size_t max_index = sizeof(img->meta) / sizeof(img->meta[0]) - 1; // search for free meta entry for (index = 0; index < max_index; ++index) { if (!img->meta[index]) { img->meta[index] = create_meta(name, value); break; } } } swayimg-1.6/src/image.h000066400000000000000000000026451420071156000150650ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2021 Artem Senichev #pragma once #include // Max number of meta entries #define META_MAX_ENTRY 16 // Orientation (top left corner projection) typedef enum { ori_undefined, ori_top_left, ori_top_right, ori_bottom_right, ori_bottom_left, ori_left_top, ori_right_top, ori_right_bottom, ori_left_bottom, } orientation_t; /** Image description. */ typedef struct { const char* path; ///< Path to the file char* meta[META_MAX_ENTRY]; ///< Image meta info orientation_t orientation; ///< Image orientation cairo_surface_t* surface; ///< Image surface } image_t; /** * Load image from file. * @param[in] file path to the file to load * @return image instance or NULL on errors */ image_t* image_from_file(const char* file); /** * Load image from stdin data. * @return image instance or NULL on errors */ image_t* image_from_stdin(void); /** * Free image. * @param[in] img image instance to free */ void image_free(image_t* img); /** * Add meta property. * @param[in] img image instance * @param[in] name property name * @param[in] value property value */ void add_image_meta(image_t* img, const char* name, const char* value); /** * Get string with the names of the supported image formats. * @return image instance or NULL on errors */ const char* supported_formats(void); swayimg-1.6/src/main.c000066400000000000000000000077061420071156000147250ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev #include "buildcfg.h" #include "config.h" #include "image.h" #include "viewer.h" #include #include #include #include /** * Print help usage info. */ static void print_help(void) { // clang-format off puts("Usage: " APP_NAME " [OPTION...] FILE..."); puts(" -f, --fullscreen Full screen mode"); puts(" -s, --scale=TYPE Set initial image scale: default, fit, or real"); puts(" -b, --background=XXXXXX Set background color as hex RGB"); puts(" -g, --geometry=X,Y,W,H Set window geometry"); puts(" -i, --info Show image properties"); puts(" -v, --version Print version info and exit"); puts(" -h, --help Print this help and exit"); // clang-format on } /** * Print version info. */ static void print_version(void) { puts(APP_NAME " version " APP_VERSION "."); printf("Supported formats: %s.\n", supported_formats()); } /** * Application entry point. */ int main(int argc, char* argv[]) { const char** files = NULL; size_t files_num = 0; // clang-format off const struct option long_opts[] = { { "fullscreen", no_argument, NULL, 'f' }, { "scale", required_argument, NULL, 's' }, { "background", required_argument, NULL, 'b' }, { "geometry", required_argument, NULL, 'g' }, { "info", no_argument, NULL, 'i' }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; const char* short_opts = "fs:b:g:ivh"; // clang-format on // default config load_config(); opterr = 0; // prevent native error messages // parse arguments int opt; while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) { switch (opt) { case 'f': config.fullscreen = true; break; case 's': if (!set_scale(optarg)) { fprintf(stderr, "Invalid scale: %s\n", optarg); fprintf(stderr, "Expected 'default', 'fit', or 'real'.\n"); return EXIT_FAILURE; } break; case 'b': if (!set_background(optarg)) { fprintf(stderr, "Invalid background: %s\n", optarg); fprintf(stderr, "Expected 'grid' or RGB hex value.\n"); return EXIT_FAILURE; } break; case 'g': if (!set_geometry(optarg)) { fprintf(stderr, "Invalid window geometry: %s\n", optarg); fprintf(stderr, "Expected X,Y,W,H format.\n"); return EXIT_FAILURE; } break; case 'i': config.show_info = true; break; case 'v': print_version(); return EXIT_SUCCESS; case 'h': print_help(); return EXIT_SUCCESS; default: fprintf(stderr, "Invalid argument: %s\n", argv[optind - 1]); return EXIT_FAILURE; } } if (config.fullscreen && config.window.width) { fprintf(stderr, "Incompatible arguments: " "can not set geometry for full screen mode\n"); return EXIT_FAILURE; } if (optind == argc) { fprintf(stderr, "No files specified for viewing, " "use '-' to read image data from stdin.\n"); return EXIT_FAILURE; } if (strcmp(argv[optind], "-") != 0) { files = (const char**)&argv[optind]; files_num = (size_t)(argc - optind); } return run_viewer(files, files_num) ? EXIT_SUCCESS : EXIT_FAILURE; } swayimg-1.6/src/sway.c000066400000000000000000000216351420071156000147610ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // 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[in] fd socket descriptor * @param[out] buf buffer for destination data * @param[in] 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[in] fd socket descriptor * @param[in] buf buffer of data of send * @param[in] 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[in] ipc IPC context (socket file descriptor) * @param[in] type message type * @param[in] 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[in] ipc IPC context (socket file descriptor) * @param[in] app application Id * @param[in] 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[in] node JSON parent node * @param[in] name name of the rect node * @param[out] 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 rectange geometry from JSON node. * @param[in] node JSON parent node * @param[in] name name of the rect node * @param[out] rect rectangle geometry * @return true if operation completed successfully */ static bool read_rect(json_object* node, const char* name, rect_t* rect) { 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; } return read_int(rn, "x", &rect->x) && read_int(rn, "y", &rect->y) && read_int(rn, "width", &rect->width) && read_int(rn, "height", &rect->height); } /** * Get currently focused workspace. * @param[in] 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[in] 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) { fprintf(stderr, "SWAYSOCK variable is not defined\n"); return -1; } size_t len = strlen(path); if (!len || len > sizeof(sa.sun_path)) { fprintf(stderr, "Invalid SWAYSOCK variable\n"); return -1; } 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 -1; } 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 -1; } return fd; } void sway_disconnect(int ipc) { close(ipc); } bool sway_current(int ipc, rect_t* 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) { rect_t workspace; rect_t 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) { char move[64]; snprintf(move, sizeof(move), "move position %i %i", x, y); return ipc_command(ipc, app, "floating enable") && ipc_command(ipc, app, move); } swayimg-1.6/src/sway.h000066400000000000000000000020601420071156000147550ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev #pragma once #include "canvas.h" #include /** * Connect to Sway. * @return IPC context, -1 if error */ int sway_connect(void); /** * Disconnect IPC channel. * @param[in] ipc IPC context */ void sway_disconnect(int ipc); /** * Get geometry for currently focused window. * @param[in] ipc IPC context * @param[out] wnd geometry of currently focused window * @param[out] fullscreen current full screen mode * @return true if operation completed successfully */ bool sway_current(int ipc, rect_t* wnd, bool* fullscreen); /** * Add rules for Sway for application's window: * 1. Enable floating mode; * 2. Set initial position. * * @param[in] ipc IPC context * @param[in] app application Id * @param[in] x horizontal window position relative to current workspace * @param[in] v vertical window position relative to current workspace * @return true if operation completed successfully */ bool sway_add_rules(int ipc, const char* app, int x, int y); swayimg-1.6/src/viewer.c000066400000000000000000000221631420071156000152740ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev #include "viewer.h" #include "buildcfg.h" #include "canvas.h" #include "config.h" #include "image.h" #include "sway.h" #include "window.h" #include #include #include /** Viewer context. */ typedef struct { struct { const char** files; ///< List of files to view size_t total; ///< Total number of files in the list size_t current; ///< Index of currently displayed image in the list } file_list; image_t* image; ///< Currently displayed image canvas_t canvas; ///< Canvas context } viewer_t; static viewer_t viewer; /** * Load image from file or stdin. * @param[in] file path to the file, NULL for reading stdin * @return false if image was not loaded */ static bool load_image(const char* file) { image_t* image = NULL; char* title; if (file) { image = image_from_file(file); } else { image = image_from_stdin(); } if (!image) { return false; } image_free(viewer.image); viewer.image = image; reset_canvas(&viewer.canvas); // fix orientation switch (viewer.image->orientation) { case ori_top_right: // flipped back-to-front viewer.canvas.flip = flip_horizontal; break; case ori_bottom_right: // upside down viewer.canvas.rotate = rotate_180; break; case ori_bottom_left: // flipped back-to-front and upside down viewer.canvas.flip = flip_vertical; break; case ori_left_top: // flipped back-to-front and on its side viewer.canvas.flip = flip_horizontal; viewer.canvas.rotate = rotate_90; break; case ori_right_top: // on its side viewer.canvas.rotate = rotate_90; break; case ori_right_bottom: // flipped back-to-front and on its far side viewer.canvas.flip = flip_vertical; viewer.canvas.rotate = rotate_270; break; case ori_left_bottom: // on its far side viewer.canvas.rotate = rotate_270; break; default: break; } // set initial scale and position of the image apply_scale(&viewer.canvas, viewer.image->surface, config.scale); // change window title (includes ": " and last null = 3 bytes) title = malloc(strlen(APP_NAME) + strlen(viewer.image->path) + 3); if (title) { strcpy(title, APP_NAME); strcat(title, ": "); strcat(title, viewer.image->path); set_window_title(title); free(title); } return true; } /** * Load next image file. * @param[in] forward move direction (true=forward / false=backward). * @return false if file was not loaded */ static bool next_file(bool forward) { size_t index = viewer.file_list.current; bool initial = (viewer.image == NULL); if (viewer.file_list.total == 0) { // stdin mode, read only once return viewer.image || load_image(NULL); } while (true) { if (initial) { initial = false; } else { if (forward) { if (index < viewer.file_list.total - 1) { ++index; } else { index = 0; } } else { if (index > 0) { --index; } else { index = viewer.file_list.total - 1; } } if (index == viewer.file_list.current) { return false; // all files enumerated } } if (load_image(viewer.file_list.files[index])) { break; } } viewer.file_list.current = index; return true; } /** Draw handler, see handlers::on_redraw */ static void on_redraw(cairo_surface_t* window) { cairo_t* cairo = cairo_create(window); // clear canvas cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); cairo_paint(cairo); // image with background if (cairo_image_surface_get_format(viewer.image->surface) == CAIRO_FORMAT_ARGB32) { if (config.background == BACKGROUND_GRID) { draw_grid(&viewer.canvas, viewer.image->surface, cairo); } else { cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgb(cairo, RGB_RED(config.background), RGB_GREEN(config.background), RGB_BLUE(config.background)); cairo_paint(cairo); } } draw_image(&viewer.canvas, viewer.image->surface, cairo); // image meta information: file name, format, exif, etc if (config.show_info) { char scale[8]; snprintf(scale, sizeof(scale), "%i%%", (int)(viewer.canvas.scale * 100.0)); draw_text(cairo, 10, get_window_height() - 30, scale); draw_lines(cairo, 10, 10, (const char**)viewer.image->meta); } cairo_destroy(cairo); } /** Window resize handler, see handlers::on_resize */ static void on_resize(void) { viewer.canvas.scale = 0.0; // to force recalculate considering window size apply_scale(&viewer.canvas, viewer.image->surface, config.scale); } /** Keyboard handler, see handlers::on_keyboard. */ static bool on_keyboard(xkb_keysym_t key) { switch (key) { case XKB_KEY_SunPageUp: case XKB_KEY_p: return next_file(false); case XKB_KEY_SunPageDown: case XKB_KEY_n: case XKB_KEY_space: return next_file(true); case XKB_KEY_Left: case XKB_KEY_h: return move_viewpoint(&viewer.canvas, viewer.image->surface, step_left); case XKB_KEY_Right: case XKB_KEY_l: return move_viewpoint(&viewer.canvas, viewer.image->surface, step_right); case XKB_KEY_Up: case XKB_KEY_k: return move_viewpoint(&viewer.canvas, viewer.image->surface, step_up); case XKB_KEY_Down: case XKB_KEY_j: return move_viewpoint(&viewer.canvas, viewer.image->surface, step_down); case XKB_KEY_equal: case XKB_KEY_plus: return apply_scale(&viewer.canvas, viewer.image->surface, zoom_in); case XKB_KEY_minus: return apply_scale(&viewer.canvas, viewer.image->surface, zoom_out); case XKB_KEY_0: return apply_scale(&viewer.canvas, viewer.image->surface, scale_100); case XKB_KEY_BackSpace: return apply_scale(&viewer.canvas, viewer.image->surface, config.scale); case XKB_KEY_i: config.show_info = !config.show_info; return true; case XKB_KEY_F5: case XKB_KEY_bracketleft: apply_rotate(&viewer.canvas, false); return true; case XKB_KEY_F6: case XKB_KEY_bracketright: apply_rotate(&viewer.canvas, true); return true; case XKB_KEY_F7: apply_flip(&viewer.canvas, flip_vertical); return true; case XKB_KEY_F8: apply_flip(&viewer.canvas, flip_horizontal); return true; case XKB_KEY_F11: case XKB_KEY_f: config.fullscreen = !config.fullscreen; enable_fullscreen(config.fullscreen); return false; case XKB_KEY_Escape: case XKB_KEY_Return: case XKB_KEY_F10: case XKB_KEY_q: close_window(); return false; } return false; } bool run_viewer(const char** files, size_t total) { const struct handlers handlers = { .on_redraw = on_redraw, .on_resize = on_resize, .on_keyboard = on_keyboard }; // create unique application id char app_id[64]; struct timeval tv; gettimeofday(&tv, NULL); snprintf(app_id, sizeof(app_id), APP_NAME "_%lx", tv.tv_sec << 32 | tv.tv_usec); // setup window position via Sway IPC const int ipc = sway_connect(); if (ipc != -1) { bool sway_fullscreen = false; if (!config.window.width) { // get currently focused window state sway_current(ipc, &config.window, &sway_fullscreen); } config.fullscreen |= sway_fullscreen; if (!config.fullscreen && config.window.width) { sway_add_rules(ipc, app_id, config.window.x, config.window.y); } sway_disconnect(ipc); } // GUI prepare if (!create_window(&handlers, config.window.width, config.window.height, app_id)) { return false; } // load first file viewer.file_list.files = files; viewer.file_list.total = total; if (!next_file(true)) { destroy_window(); return false; } if (config.fullscreen) { enable_fullscreen(true); } // GUI loop show_window(); destroy_window(); image_free(viewer.image); return true; } swayimg-1.6/src/viewer.h000066400000000000000000000005631420071156000153010ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev #pragma once #include #include /** * Start viewer. * @param[in] files list of files to view * @param[in] total total number of files in the list * @return true if operation completed successfully */ bool run_viewer(const char** files, size_t total); swayimg-1.6/src/window.c000066400000000000000000000314001420071156000152740ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev #include "window.h" #include "buildcfg.h" #include "xdg-shell-protocol.h" #include #include #include #include #include #include #include /** Loop state */ enum state { state_ok, state_exit, state_error, }; /** Window context */ struct context { 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_surface* surface; } wl; struct xdg { struct xdg_wm_base* base; struct xdg_surface* surface; struct xdg_toplevel* toplevel; } xdg; struct xkb { struct xkb_context* context; struct xkb_keymap* keymap; struct xkb_state* state; } xkb; struct surface { cairo_surface_t* cairo; struct wl_buffer* buffer; } surface; struct size { size_t width; size_t height; } size; struct handlers handlers; enum state state; }; static struct context ctx; /** Redraw window */ static void redraw(void) { ctx.handlers.on_redraw(ctx.surface.cairo); wl_surface_attach(ctx.wl.surface, ctx.surface.buffer, 0, 0); wl_surface_damage(ctx.wl.surface, 0, 0, ctx.size.width, ctx.size.height); wl_surface_commit(ctx.wl.surface); } /** * Create shared memory file. * @param[in] sz size of data in bytes * @param[out] data pointer to mapped data * @return shared file descriptor, -1 on errors */ static int create_shmem(size_t sz, void** data) { char path[64]; struct timeval tv; gettimeofday(&tv, NULL); snprintf(path, sizeof(path), "/" APP_NAME "_%lx", tv.tv_sec << 32 | tv.tv_usec); int 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 -1; } shm_unlink(path); if (ftruncate(fd, sz) == -1) { fprintf(stderr, "Unable to truncate shared file: [%i] %s\n", errno, strerror(errno)); close(fd); return -1; } *data = mmap(NULL, 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 -1; } return fd; } /** * (Re)create buffer. * @return true if operation completed successfully */ static bool create_buffer(void) { // free previous allocated buffer if (ctx.surface.cairo) { cairo_surface_destroy(ctx.surface.cairo); ctx.surface.cairo = NULL; } if (ctx.surface.buffer) { wl_buffer_destroy(ctx.surface.buffer); ctx.surface.buffer = NULL; } // create new buffer const size_t stride = ctx.size.width * 4 /* argb */; const size_t buf_sz = stride * ctx.size.height; void* buf_data; const int fd = create_shmem(buf_sz, &buf_data); if (fd == -1) { return false; } struct wl_shm_pool* pool = wl_shm_create_pool(ctx.wl.shm, fd, buf_sz); close(fd); ctx.surface.buffer = wl_shm_pool_create_buffer(pool, 0, ctx.size.width, ctx.size.height, stride, WL_SHM_FORMAT_XRGB8888); wl_shm_pool_destroy(pool); ctx.surface.cairo = cairo_image_surface_create_for_data( buf_data, CAIRO_FORMAT_ARGB32, ctx.size.width, ctx.size.height, stride); return true; } /******************************************************************************* * 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) { } static void on_keyboard_keymap(void* data, struct wl_keyboard* wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { xkb_state_unref(ctx.xkb.state); xkb_keymap_unref(ctx.xkb.keymap); char* 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) { if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { xkb_keysym_t keysym = xkb_state_key_get_one_sym(ctx.xkb.state, key + 8); if (keysym != XKB_KEY_NoSymbol && ctx.handlers.on_keyboard(keysym)) { redraw(); } } } 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, }; /******************************************************************************* * 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; } } 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.surface.buffer && !create_buffer()) { ctx.state = state_error; return; } 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) { if (width && height && (width != (int32_t)ctx.size.width || height != (int32_t)ctx.size.height)) { ctx.size.width = width; ctx.size.height = height; if (create_buffer()) { ctx.handlers.on_resize(); } else { 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, }; /******************************************************************************* * 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, 1); } 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, NULL); } else if (strcmp(interface, wl_seat_interface.name) == 0) { ctx.wl.seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); wl_seat_add_listener(ctx.wl.seat, &seat_listener, NULL); } } 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 }; bool create_window(const struct handlers* handlers, size_t width, size_t height, const char* app_id) { ctx.size.width = width ? width : 800; ctx.size.height = height ? height : 600; ctx.handlers = *handlers; 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"); destroy_window(); 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"); destroy_window(); return false; } 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"); destroy_window(); return false; } xdg_surface_add_listener(ctx.xdg.surface, &xdg_surface_listener, NULL); ctx.xdg.toplevel = xdg_surface_get_toplevel(ctx.xdg.surface); xdg_toplevel_add_listener(ctx.xdg.toplevel, &xdg_toplevel_listener, NULL); xdg_toplevel_set_app_id(ctx.xdg.toplevel, app_id); wl_surface_commit(ctx.wl.surface); return true; } void show_window(void) { while (ctx.state == state_ok) { wl_display_dispatch(ctx.wl.display); } } void destroy_window(void) { if (ctx.xkb.state) { xkb_state_unref(ctx.xkb.state); } if (ctx.xkb.keymap) { xkb_keymap_unref(ctx.xkb.keymap); } if (ctx.surface.cairo) { cairo_surface_destroy(ctx.surface.cairo); } if (ctx.surface.buffer) { wl_buffer_destroy(ctx.surface.buffer); } if (ctx.wl.seat) { wl_seat_destroy(ctx.wl.seat); } if (ctx.wl.keyboard) { wl_keyboard_destroy(ctx.wl.keyboard); } if (ctx.wl.shm) { wl_shm_destroy(ctx.wl.shm); } if (ctx.xdg.base) { xdg_surface_destroy(ctx.xdg.surface); } if (ctx.xdg.base) { xdg_wm_base_destroy(ctx.xdg.base); } if (ctx.wl.compositor) { wl_compositor_destroy(ctx.wl.compositor); } if (ctx.wl.registry) { wl_registry_destroy(ctx.wl.registry); } wl_display_disconnect(ctx.wl.display); } void close_window(void) { ctx.state = state_exit; } size_t get_window_width(void) { return ctx.size.width; } size_t get_window_height(void) { return ctx.size.height; } void set_window_title(const char* title) { xdg_toplevel_set_title(ctx.xdg.toplevel, title); } void enable_fullscreen(bool enable) { if (enable) { xdg_toplevel_set_fullscreen(ctx.xdg.toplevel, NULL); } else { xdg_toplevel_unset_fullscreen(ctx.xdg.toplevel); } } swayimg-1.6/src/window.h000066400000000000000000000031441420071156000153050ustar00rootroot00000000000000// SPDX-License-Identifier: MIT // Copyright (C) 2020 Artem Senichev #pragma once #include #include #include #include #include /** UI event handlers. */ struct handlers { /** * Redraw handler. * @param[in] window surface to draw on */ void (*on_redraw)(cairo_surface_t* window); /** * Window resize handler. * @param[in] window surface to draw on */ void (*on_resize)(void); /** * Key press handler. * @param[in] key code of key pressed * @return true if state was changed and window must be redrawn */ bool (*on_keyboard)(xkb_keysym_t key); }; /** * Create window. * @param[in] handlers event handlers * @param[in] width window width * @param[in] height window height * @param[in] app_id application id * @return true if operation completed successfully */ bool create_window(const struct handlers* handlers, size_t width, size_t height, const char* app_id); /** * Show window and run event handler loop. */ void show_window(void); /** * Destroy window. */ void destroy_window(void); /** * Close window. */ void close_window(void); /** * Get window width. * @return window width */ size_t get_window_width(void); /** * Get window height. * @return window height */ size_t get_window_height(void); /** * Set window title. * @param[in] title new window title */ void set_window_title(const char* title); /** * Enable or disable full screen mode. * @param[in] enable mode */ void enable_fullscreen(bool enable);