From 648d00c7d8243d71e33c33e99a6e4f509beddf98 Mon Sep 17 00:00:00 2001 From: Joe Mooring Date: Fri, 15 Dec 2023 12:04:05 -0800 Subject: [PATCH] resources/images: Create AutoOrient image filter Closes #11717 --- .../examples/landscape-exif-orientation-5.jpg | Bin 0 -> 38639 bytes .../content/en/functions/images/AutoOrient.md | 52 +++ docs/layouts/shortcodes/img.html | 379 ++++++++++++++++++ resources/image.go | 16 +- resources/images/auto_orient.go | 60 +++ resources/images/filters.go | 8 + 6 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 docs/assets/images/examples/landscape-exif-orientation-5.jpg create mode 100644 docs/content/en/functions/images/AutoOrient.md create mode 100644 docs/layouts/shortcodes/img.html create mode 100644 resources/images/auto_orient.go diff --git a/docs/assets/images/examples/landscape-exif-orientation-5.jpg b/docs/assets/images/examples/landscape-exif-orientation-5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ad64835eb2209c960dfb21c4437fa72754cd98d2 GIT binary patch literal 38639 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�=GPw2@UbQFl?)n;%hWzoOHLOhGl$o^m@}`E5N^Nv*D^ zx>3_^%nr1~ns2wU@Us1blK7hfgqA~%yXVP8yWxacAfWFdRjTK2YGBJ>BYjIsyU z4J2;s$A4F>R@`{qZ@L;}m#~j5sXbT_Wy01ffK#xZYoPkS+mLDf zjrLkzHfzN09saW1+Az(^EsTLmu7uddbCp&FxlA26Aq*D0M~OslyEhjN9={UGX(;3( zX8@{|0uPnNyVEC;)sf~>N7qo(r}FoqMK;urB<0+U)!(2DkSf3B%-O48eXCyO#E z>rp2KuJFeoo-v*gk!d&^xX~p7cJL}SAt(oChectOm=V)lH!;*{(|L=;3SO4WzPZ>x zQvgA(HP=3e0+8wX2^=s9y8W2M&MwW22f2bzqNwU}impun< zG{sN2vYHt3II%)}bDzQ`gbPKaR^#?cTxdKH?<07zj@4rChKCjkhO#b-&5OI`2+>%yixO|E>Jd7q-L&~tKZAXrXNq+PmTwZLX`?k z6xU>=&G9#Yb2mYY7iXozAx{M}4-9QR8j=oE4!4=q`OO|`DFVRB9@Yb#bPCqVx1M5x zv4$};aS_}4^uu!7g&a;#^0(y7bfRXrIrrl8oKEV}CXkTnh1NTUw7=TWHQrmDzNQ5K zQf8-`7O=@=EbphPN+J1+V%2LuXgMJQ9xXrmpaVB`V9;r>$csocuq8@~r{1;{?tv-*f45Kr3c&UZJ=g>#dVXC3|=kib* z*o1;826JF55gaR;46})EwokJwhLjWLy*nN1#MM2`$<^7@(q zA}@k0Irpoj$3{&JSMdxm2U%He9xx9;0WO?vX4^)=(=l2I?MSyuK#O@4QGp_)}u66juv^l>}IZCM!b7m z{gAY7_R7)IwE=p^%c%K-Jay{82sQNOAU9CFYNqG_*M|E1{!^mY2Oegp+>x;cLdwKA zibHN|+NA4{m=CmYI!3LU;n$NwNDy5w6dbgzAKosdK4QdooxRXods<-L0Zf%TrFJef zgw~bm|K4tPcf9t!%D(Rk6gC(gW_$%KkN%{E_p>QGr1#$v@~7ddM8%^s@#;ML=l6etRC1r{zbYsku4o7=^ZD zK$U1NE$Ax2MHprlMtt+GD<8g(;&5*RRV$>njV1z#o^As)PKx2%kF>(yP;18#| zsN5b@3{svT`WZ1Thl~|7G-$segHk(aXC1W8{8{2BGLz8#{h3t)f>emHoTn$u&IZ-X zeW6UCyOk4CMSly4Gev<&^qB-}l>OE%Ohi(_&;*+O!9`BlaXejMj<7MpAfK|Y#9{i< zP9WM3W(59lmnWras5Ylr#$iS=6WiF4BD2T)r>Pe;mU5+lTz2GM5QViqD-2?!mTskv zG%3lob>u?pQyE9VKLje$YuS9_i@&ZC=2_7|z2rGYLq1U8!_1ysDU+^W%Ky8ul`=HZvJp1rm<+i;AqYgXjwDHv$#2yoEq?j3RG zesay!L9#lf*&sY|7mrXFOC55&FxO+II-_3tw{9SzBdpiBzDOI4v%5gI-K!P-mrtM& zw5^|vgQE#6Y_lg2O7w}EVFBss!Pu23*;Bgd+1AiR^=s(f-;c$<61PQ;C%eCRB5&_X z1e2&csVim$$XeP%^-shmcMH|(8x^XHIT!96^@$MqrfEi5nE+4QDMz^J$aDcvqfBSM zv}Rk#{mEB4K1xbNQuaW?GG6$LS%?&PaPu4g419=bvh3%VF8r(+JFSH=ya1PI^YX#F z6gn#ab-4RV1ES^bYiet2il*b4@Xm;fR%2Z&B((%7+o5=8RL$0pFj7;NH3hIsO3+fe zjwEW+P$q^5nwRiX!%d_M<Zr-$3K!K?qgg5`^m4QI;JwveIig zmo(_CFJZE0U=-&t-(ufI=){PCg!->NQz_&p1Ys&RJ^18$I5WoEdq0wz0qvJ_cD$t? zUri0~qh^#jNKP_VSmgx8izFw_PlC+d+l|5`=Cyd=0Euh)^EE_fXG-d=zMc_ZSwC=1fjTE*-n|v; zq0}Lr#4Nuo&7&jzxAIm;z^Hk{AzA*&&jQMH#~83<9jp1TkxD*f^$`jcbwed;@dzC- zIEKnSFqe%Nt`y_&&v_F(30-V1HeAN_@Yraw-)>zTc=(N_S8WB1Hin7{0wApDEbCCX{X zz4o!5x$rcPc33jzI)e)LHuK&}qn}Bvm0d;58X?q!No9Q+KURsov%=EH68^LbEwpvM z!r(%_Xz(2{E&UEy(IZiPt@V-haDNAwTJpFGJsL~(MZ>poIQt1HJ&~LiRV=M#A43y7 zD@$}>MC>UY<=ZOM5Iiex$StM|xszok<=bz2jqPp?WpctyKH1p?^& zgXs>5*j4aBpDprfQomP=*#6(*k42UCqDJHQh@tx`dz)^Jx&{CU2$vdSkHVNaqc~lY zAio$u6N#3X(sU{(TRU$ty&hub!0xGDMvK>mf{{ww!xq9mQy5NpP zB#G+hw3*Lhsq)v91h-!Nd@XJYT8V7h1-U;7r;33+%f&Z`agjma+0t-wvhUoeSsEfU zfQLFd5GCNLXzJrJe4RxO%_vltSC$((fU|iE_;haj~r%2f8bpuHj zuBy_c2!q}Mu26deb2lW*+F`AYfX5V~@bS3g2A|ud0<%~g9 zy4~Zf6^kj=Lj7V_^LxiSi9Uj{7F;f^l;zb&{{%whK8?;)f#~;dP0L|YlX!_XQwe^A z8N|p%(I97&kp|122>gJ2E#=XEjWvxHVG%y-dwP1(P`Ehw7CSOFr1Z%`h11#T__W`7 zjjl-gyFbhqA=|xFdnV3tH)R7cWDK3k{J9+52S}%W+IkwfUGmm1?NRztDZR!4xQ*$k zk_cn-Vc$l0-^aI~=rSY5-=uhJ zIu&T70}&eX&hS)EzSK=lhmF$&(rH0;PhTHsXet#~bLkK^Bbgp}q5kn-jsp@LzNU^$ zok0h$+WXxt-B&uDnYK31nn(^fX8iWv=^80s*I!;;=Sz2i0slr|A&mYyU+F6F1EnM1 z}hXLkSoqqOdi6dPWjC+QbUE=Dv!l=2y5WsZExs8)4(v znNj!tt+1sd>PT;wZ~+VqU~cO4Z=j{nNPsDK=Q;qczF|XN|3P+vgfUdj(1`}!^JBND z+1Jv$=3hFmvF^L7ODZVke?P9*YI2<>dgjq1FLDngHPGT|YkoFI-b}$;{S8aJJh3{& zjz)8rS5RV3i~U((sdV1mX+?P706kU~**pY7T3~KlZkP5+XrEGVprcV6*{;4Gpjfw#wx@! z>4LI@;SZ5M)1}8XGv0E1MsfLAE_I(2Jd`&Q66d5QL0h8MI^~xOC~ju)!KHQBy8d^TeM93WjWk65T@r$Ui-CcAYR?23+rs*{+YCQu(RAe+LdLvq zl4Q6FIL0(0RcH^gfMFBu(yd*djLZbd=0;ZoOx9x5Z^)3^-^SmmZp!Ti=$AgHb9|Bg z8rk@p`S8#{<~5@StuG78NA#l48SGI-zV?Uk^~7qxH<%QDM$MaaMz1)H+EW zfo3bUQydt%)+f*pZOA;dlSpdeu7bTDm!BYed5Dsq`0t*WIWVnQQ}iKPuW zc#~FmB`+FldpT^;75paB5k|Ay3^tJZg?2!~^J{j2r3T0oJ9|57^p6vXkZwNJGNdu` z&gOZ15NGNp8QdBbYDIFcFyHzKc;{)JK|qfC0%udlPZVnVSTw+y=oS}MDH5PevO0kH zC!niOVcRCjBX*^q>{BzooAq*~+wc6SI2(MBUS97InzKvbvCNGu*_=trYB6NDQhyQo1WKVdzG>(#%O zC0{F5QGtn=iJTqZGRS3q^Qh4Zn_rjY>+B{o8c{27^c1+lhjyRO;~`;lmjCVYlF%s1 z5#4{**SD-xiVwz2DJdb8=&zA@<61;wRK5c`sTJ?;8%|xa#YOv~fJOJJL)TrowT`LU z-W9w{aOUQG6Q{w&Vw!<;`u_pKKs~=fsmk!Y;D4U=z$JCAr!2rj2r9&af9YRL*E`hj z4DqZGhSD-!&;HS~xD;NY)m|9bsU9SafsOgtXYs9iXH1)^T#It#M29XwJJboZr@8D8zjH%ZSdnclz(72xG52xb z*i+sLq;04V2m-*4=hyu0PLQktm+Z>VCv&K8_jaus_2IC2QO}7X^6mFQ+wrV4Uuq?! zB*MFvCjgP!gK2OiwN{x%gkn{kY=7#5=p0s{Jd0>ti3E=B?Q)~~N1#1|)G1p(s2tFxSqfW;?5v|4$VBn%E) zjDxiSuW~@#liwJnR_aL)h7h@s6~;e2npoYPI;1*J2hEe39tkn8Ll+Udk|JPLznJ9X z{O?Oe0^2(@g_Hr4wJv)6R<24ewiv}Fd+`m`+(9%AJo$$8$}(qKAG)}+or>C@4S^ZU z9!)EC1an!3Hz^@U$pHTVdY@w%k+n7*+;iwCmz_FDZ7uGcqd_4#(BPHdxa4N2p+LJF zib#vf$k|f0kid{n3PC5*z0mFc++|3)2+e`t8K6kSLdAP=n%0#?8NX%*NenYfrvojc z9sS6rY)-fiG36)gTV0SYqj3-{RZAaA!s&ugQwBLOHXn5NsN4%w~f!g!u>^`Scq@9CP^&zK*|vZys@JNB+RjiW#pu~Sm7Y|XY=6ta_? zWOfY3-akfF&3btCblloXKgpNl!v497aW-0{tK-?NX0dSsUUPvSp_7sTnEDM#*z z)FggzM}FIPBzCSc;@j-kC3NKv2OpJv3*bv%{73sY_>bYMS5bb>mvEytHRDd-ETA6R zg=Cs(8zR#^efwtXa!KKIduu+OY`aGgi^O0(3CJISqjc}u?d<35zZ2M5^*)~_(&>v1 z2dznZl7@^H*m>qIyRP$Kv9Rx8gr#|o8Gs19j8r!?k0Q>Ajlr%_8#;+ zRUK@<@&5qBcK-klb(oJ#a(Zq2LgLuuoho@;oygn~-voMBHwhHdymrzwJP! zoAgeF467uaD#S-KgToy-AA)D(MRFF{jxt;V!1;+jyi-A1*t2!PbwaGz+aTjT>(oFV zO~@D<0Db-I)cY3E^zJBbfy$Gdsnn%dsRz^$ayg=!(YHI-t<%oov{$!QQC_9Fo&MKo z@B(gFcx+D3xjb*i^d@yzD%Udx{$I4ok@VG?d!$&WQtBotNe+Rh2hXh_9T&? znEKFfZEdYy(&MJH9-V&~cvZ8QU4HU!zB${mQaHsg?3PH=^dHr@O2#0QdU;7`>TCxX zswjVVRg&0|;HA*q;)M{yQxgqFfQJOrE&=pzGq|SAo=j3PI1RlR$)A-?s~IzLb>@|x zEk|lRZZnD|kXIyeT%8>DOEGwBMuYC=wYWKu)==P6z7s?C(qcs!r}>x~lW08P)npog zYR@%Hyf`CzeE9dJByYX#P2K(CO;1@q_I+xR;W-AAc)jsiUF;YGn#oYmN*s^-q#o36pft)wD=G4!>?=%6?ApzLdVMogdX&Fq_-gh{h5gZ7oq*JF z{b?%|Eh!_`xxb4(F|z6%AlUTzSd?a z1*1uR>$t#S+~ngmgQor=_LWq#AW@<{l$>YfT&&X>dY-ptR7NC_IQmsf%b4Mirk9pL zzEwK_PbQ?e(UQ*WGlkpe52ag*h^*QWT-;a-xuPgSsp4Y3Ob$I)|M=wBW5Z;ASg+H`qlx-p@5lfY#~8OG&+-1en& zY-I$Gir)Us`tM8WuYGlBBFm(DSvXz6{vrnZ`u_lW z`e*GIu34pu^pN_lWO(LqF^G{^9I^Mt0Q%R>qAh!LTq$&iWmEi2dmolFMmrWti+>4x zEjM3lw@KlGNKp!_##j*9*pPPnzK5FI;y#DfI%6u{!V)PMBHSZtkL8`PPoW$8&^kxO z^ZYB-CtEvIx)P#z=;Wzv6OAl5JDsq5SJQewTIt^xbg4&#NGqb^)^!?`FyV&;9>;@0 zw;%Wvl$ZK^u6uZ%Aqx!(p7Fn~U%dx_D&>Va7OLP=~jWS#_z6KXchW7sS#1ZJ$Lg-wAG27)q zF=r<53 z^!n;M(BMttu~5Ea&l_VN)wFQO5iKU1D9<^^&-zx)2?$J#LJq+1Q98F=F&zK^KY?+* za!tBHw0p1hQwqpX;n-nCbC8FTaU-$cJlCX_Tl5$kJ>EpX-{;8swMQ_B2a{Xs1#J#c~IFV06q2yeAo5`m+ z)#@u)19)4XGZJx%PFR^AE~En4=9PsOz8xzR?~8`B<^IW&kn#t0U2&Y-t->mjj#-D|J3xcc4T2r7gSGgVi$E_0GO8JQ9nRE|^{{VSgS0b|*^Lmnatsn1g-}0iw zxO@&Ns6_1=&u){Zml+0?>9KXLjPKSuaGo!)x~tv9GO?D}1MGOg+PW@@ankL2p1!$f zJ>{o{!^45JuzZXkyt`mGe)ZhgbuWZl;O?;`T^jW)QfLLtFD70_EHn^x?SqcuvgUN^ zjd)hNb2k3~wrk0)(R$BA>Agg#z;l$((1ss{V)ORH)XlS7^lq17(v^qajdig33v6pA)~sJx4tM0IEOyv?l8xv&fDc;f|<3{lWhL<*0bq>Q~D6&IPU{xVe&Rsik=( zV)97GEX49j^{h#7?-L;;f(IjvS6T7@072gYo&p~E8% z2tAEg9}`}@OCM6{_8m|l^1R8RzP?r;fEAii$&PMNR+(tHPAW2WM5SOHElg}y4lDOTyJ%Am6*izO^l1@sJ8sP}F7j2F~+Nj2H9ZiQg1cUff@ou@N zkg+v#DRy9|B8@i`l#P=`AT}_PWafw@8|H%w?Y|X?7yx&!t(|%g6MpiY)I2<9r9}8S zr(S4<$I26c3%$M`_`7E7|HgnCYLBh z4`JdVbsgn2xlK`x_&F4N!>VIT-AtB!K3kcy;lhR61A5l9jQwCbms4Jm(x#PXf;*C= z!m_qAz6k@qE9IhJ97wUotj+hGvyyZARX)AfCF^tRxE>TJF%(-JE8l+PoK(wcQE+@R z$2@0~?^ttW@_IJmhl<&o2>d@1OSd{70Z@B-`U=+eLp!ODUWGd@`g5OtX~;ApO*G7{ zt#A+$S;_qXAMILoLoMG&MuIO6-Zn18X9cYSgxAp=~nXL!mr*c+1OIwqrK?pz7w#B z2Do6Q0U=8a>S6u4u8m~z72SamjtL)l*C_pcE$y*o60{Mjs7&sE&gZ>ldY$)BbdC}S zh%~m^0^{8CMLHFx&$E8Wu7PeK+96xx|I;+`AcZuS)q;9C3^tyJo&e(fWVb;9J|pD|0Ib zDXSs3@|=2rc=?+9Z|wQR-vim9K4|sE{jfu~_oFwVarF4*-%z>ugR4kZY&FcLS!Kp> zN!Sl%T`Tmeb1CVzA}e4ECJAHQ_a?nV7n8EoPT_uv~*^wEK zrvvATWCzd|mhP9rNw;O#?s5(*s`zK98$XOXe5l7o!<%Q@?}i5-gDRiL)}ot6Id@^U zNdx$OPtL0RC2KcO_`1jxD+E$_>4iHg1Gzu>ijS=qz()Lw-?eU=4afW)k!V+RI2{DaKcmU#pSHq4qk;WAR02h32asKtRE*2)% zH+}F8P0IK?R1#?#L;Iiyr}dz~r3EJr6C(W9(2)zO+aRRsXtjxc%Jp(I@}wl1P+RG%|$ z1}V(pNySUHk)pd(t7GLp;;JafARN}LFXKjBi{xSvszZ~v6lk-c)6c5%+3(!YrnJDTgh`MQu-qqIz|CMnEY#@ zwD=FKKf}FFSuMJRGpx{fR)MgSs6Jv>JCU4^OjkYAZtl8$(3-~TYgvkv*Ar#+^zZhq{HNy+^1$?9xJQ=hP2|ppAZR*&fvls&AYxjl&c%xGm&E#*j|KZrg2BNWlPuzSZ0OS=U$MYd(-=Sqah{OBCy9X4UDMX;N^9J%}~x^W=h9o%c2BVR!vLq-#db zD2y{hVP29jn^;Ekb!bsKY#mLcRz-qo3Me}rSpeVw4o|SoA2bikviw zgou&OmOHo1413bw55*M9x?^PFRRow4s#ZeTF{HPBW?D*NMZ-ec>e$@o=g#h z;*;q}Wt5`d@Y3b&E@O>tIu*T)f>eJQXUR|)5;2YP20pby-0E6JHT0(-C2H#$f}}~! z%cdt7q_paj>rYMIm(l^vJXnnzpWUV@IbVK$l+?d+r_9(q(?ts)(H}6$+MPzgsgS>$ znr&Zd5=SO!aB@kZMmGkDAq;UtBr5Gia8Zb)(;|`URqq=SBYO(xl1xZGbJ zqNO!W6oa(P!zhvy7kS}&17IP!4%9iu5~^(^LuA}S9CB7JDe(wMEU{Ee8;Z! zXr=2n658vPCXtF@VCg5*v8Bw~jUlkbH?lRDQ+RSjR29f`B3>wo;D}bn#}jHL1--0hqp+| z%yG87=L6L3no28`hS}o>Ux+jk2ID6u6c|@A%K&tc1j~}x)9ei^3I%k=O)+X>iagDn z1CmI`rr)Iw2-Zv3z)+=_E&$upYTYw&AcV5V9&%m%K*c_`R`4)**s{dxTUfzko(4VW za5Pc5>EXZ~_+R6o!^h^pImeKi0b+*>A)vVR<)7yL~a!EZTKZQDEl20<1Sizd%&XrYO!#pV5`<=~6z5S?k=prW4 z&hq7m%7#G_k?WQ@{Huicr{cb&@hbRtGbDE3c`cyMobn43yOH!B)E|@S%f>tw_`k0I z0OF5`B)o`~hBVO#ae}BbgOBE8lk&jrP-nRis0woJgN)-%Xwp5sv2{6st^QYQWCeGD>IOb&&xYf`3*vk3fwadDsra_fIDz0 z7%W6e>Q_P0u;+hDwGFIsg6|#FiF^B`qxHV8ni)6AuRDnsHn)u zL5)VPt+a5$A9ad7faI~KZLDL7OU_jlP6=JNsa;<5%Y3k^ff8;qcR24|DLFbqrCVa% zM*T!yBSP_K3J)aX+|=18c3gtMKVsN8pxhUl<)wGqI}QA!+OiG?;aK~Mv?1b-l&oG` zYyjBbowv;kPIHrwQBIH;?}496(V#ibF|}DDObmDn$>;E*rcQS?u8#Q4Zi%)}dZH%G zMh>Iyd8MZgtXFUk@c~Y%@44e~S(+g%3^-sm`O$H-1nH8Be((XiHs4(HRpMPSobG*v zOo(`LqqsOyO;dv`nK;ScYJ6G~F3gM*oL1mztYCU`-_o~L_p1;xySMvO5vU(33}b(K zVN{Sj)KSAQu2(oY#>TJwDbOvtw6|BCQe$t@`g#d292^szkf8E7B$2-WZfX<pmZW`k(Co0OB5Zc`i$pV# z>0YMsJIMM2P@laN(AZ;|+Ki0S8=0)G;<~hx!=4kBBxDeIRvpy$`gavpPOZ^8oQ7_t z(r)c;pLd$c^(&uKf(iPrYCDaq3(ZR=2+jF%l!jSBSEAf^zYkgv*8MxEU&^N{E{>S< z*pNsc{U(7u<8oVHJE0ATAOsp;(_6fTZ##s!AyU5{pwZ29Yk|gE(R2emn!+{uqPuh zX9UwK#3-RcxD;hvn&t#xsV6iEG7br?2PY zu63foUt?D--xi@y%9AF?#w4?NcRcJ(Yj@n%m%dpx^{h_$h#zVxdjo!jt=s)Y%?d`_ z8Q(RFXZ`E@iXN;=aHX-h8Ry!mMC{`F=fs!rb({NZYx0meF=)~@0AS$qJMmolEOJPs zOO-4Gu^Xrynl;?5B(EfhttHE`Z!Y5}bKB)hy9;|c<5Zu=O*VHsd9%4pFUrMlSFVsMb6Gl*+DLavk_MuIk zqW4mG`hvw2S{m34Z?G8H?mK^KOQ&1M)Gk!8#f7m5iJTco{*<%WJ)cdDCzt~!G8FPF zYo4POs>Z537~@|i{{VOC>P$$M9h+ED>j8fAiDyruulA@=}rPODUFLJ108j>6j#Fw^&ms_n{;Gd$!h50m5%19>Duy6sWJp(9jqC1jlGA| z)b69zRxs%bsAk(d(8bQEOlgG!u-oZU@?D5NxhAwn8YARu9!be3CYVJUc|N3?&?pB0 zXB?U|Ym@S;q$Qc~Sb?zuwhOkL8Y%(?eQRQ&v9}b|3l`B>3xVxS_<1I?;cgE!$q8cG z0m=GS`ZAup(HbwMWGA&cLOUj#Z5ov@SoUq{t857Ha$6nftqxm%#8-|J0~BK#V3e&< z?l$e(w^6q=9y9M-#@lhVP?&Wa{P(7!T#V3TP}ns>)(uPTnq-BuRF7K*~uFLvCm;k zk)<1JLu0opMHzl6=zUTAUbpbHQd~FRn%YGL&#BIn_>*2=jCyxTRzDAXIR&KTU{7%2 znSO&L{Iv?&OAmq~kgD}wt1t%|;#PD&CK2HMD?@4UN$03_ul`Ga`0FRb_{~>8>N#t> ziLY*@xx1OBxS6#{BXY<34!{ltEXlo7>hW~g`hyOOE!5VY5_zsvt82?-41u;Z(oC5G zwnChAjFPnJiZ}wjMUp;y*Qw}RJ7(Lp4DUoDgnvrVi2-3)9<{UYoK~ZnVM52h6x2I5 zDQNwvxJFJXBs@mqr(hdWk&U*epad^92^WY*Uo9mmD%(@|)G1lldPKs|q=ze$NlF2y zq!>~u8BY7vqD7lS#WfOYG^|@nfMaMcjTnAxR&U(}?~IS?p*t58aDUXOd#9;UT~;&Nz_6ibmLuBeH-GY|k3K@swK;Y90B zsx_&U=^oiXjVXUCm1RvpYGZ@xovHcY3v|+{Dkat7l1_OeKJ>Jn6e_|^OeI`lw$p>i z2E3_s^&qrHAnM2&Jnh<#2i{F_7vH$#RVXa&00vu`kQMV{F_Hl9zZj?IxU%Wy8(Ujb zaVAU1W|S2^gKT+9WxH@031I zZ#8Mwz9pXO$s&T@3pmth5y6}0Hv>TtzYP<$PmuPQ-kuHoXNV`gJejSlp3o zGpZHy$eUy2ak!%N`-_=SYShx{EYdk8vFpFKY6A6%WMYlyBXwq7!S*!ejpU!bA(l1M zkTqu{pL0Uaqdi?^dx-7_i1_k|&}=sU0JTS&c@|aS=?=`kLgf4HIwwX zfR&ahCLyKYfIC-VrVDsPg_J}W1=M!=eE$Giq!kjAG!hGQsc^bZO14f(>?>QVi;IDY zgGU>-0)x2u)amEAmPAPWHHTRNcGP`I&+Ak~VLUlx(iDKPgy1hZ`BzqeU_m3RQF-*riyLfZm&DwPPd9LBQVw{P(2@r&H?4@jN!$m)VHe0aAyS zEym;0jeQq`eIWnK_GIK5&;DFt@qccOqafV&lJfJbffM~>V^ zJh%(o)|yly10-W3ZN+3xJP#%{4#t@Z*xY!I014#qXfKA5i0)_xN0eg(dwS7OV{P&& z(iMh?Q&E6;?LdfUd{c3#sv#M&44x>$Jm)p&*NSCS5l)bs03pd6R^ku=rcx^0$F(v- zwoiDsCyY>khd3j)^z@02)VqScsi<2OBYf75L1ISYiy+&*2o7_#amJ`JeMK5L^V_{* zDE!-kYMd6Cj4?RL+cfD}Huieb+_pCFSFEhOyN>>x);f1j)SzSDx+`k{6FA0c_$J#X z8!@Yja?huh_*1S)0vDIWhB-ovw}3RW06D?SXVeODt#C=jZ_?+NrFBQVieC=i2-UB0 zx`c{J5u&gD@Uc2iZS$OphG+0Eg*7dlq43DmCXIpMhZGhm{{XyC{vlnJ-^7;^Owun! z=_n6r*TjFR01W)I^Q9l+i=@taH$*@F%U3L!Kl%Rv^zR5S*yQl7oh%Q?=7j2dU{y}1 zbp1=HHPp7&w#3G<_;$!ub}hO0^L|u|DBtp{#@e=s%9EreAlkiIxNRWUsGVnI!+H}P z=&eCxyV|2W*Jcn=kPW1qY#y()XMQj=SaqNV*_tmmU+^o zSLxuJwjtFfYqwV+kpKcU)sun!DMgW)yxZrTw)IWppT~Jv1PsM|GC^W}zLYt{POKg@ zk0T(elau_T`_>b9pYJ5Vjj@4~=IuoLB#w_ON~){(vi@WAqL`($Pb(^ET#$Ls`HBIL zUCeDfpeY)dFCb@veN77pH(;?JGif`3d+kAzXu^RAU~#!_Pt&y#?j!LeR1KqWppl$= z(Da3u5w751;j@|+8l!lia}G(v6Y40@p9&Mh2@aj|KDC9AG8JNfrfW8EtmL7eNl;Jy ztAKbWe1_>YAK~JvbnAJHk+iIj9A9*8yLx-n#I=Y>LrK5B^>3v~7@NV50T61}o(&OX z^jOs#Q!W8g050VDMtdEomwiY|tAAf3*a;jf+y(Z>%fG!o@@t5gN|?hE0scTSkC!x^q!Oi& zM_mD1CsD~gj@^N#<&D0Hd%YSz?8Ch-GgLhDHZ$_CGpK?1r`Q1OhP| zXHYx0r^=X=z|l%PNi%?h9)r}>o12dwV%{lb{w$G#M|wvpD3G%{Z`!bBb(6@)DliT)MnFMjxmZgqV3x)*dsddO z=U{g&+|Yzf>~pd2MuHL!#Bsd@8yY&_N-jnhW1eeZUNu!>GIt)Lu^VF}&D(!U+Hhr6 za4=Nk=|u`OCkqM5p+s@X*zrLQgKUydG<6t0QNQGA(2Q;)qT=PoAJqR@HBg_76R7AoeaYQMBS&eq} zqLl<~Oog!-qvn#4NIP*<zAepsaJ?Xq{;x-Oe-xb@Czi$tIBrPA!QfH|vi!G*;R#t$xW^r}l9+*D;E(2XG` zfBg6N8_%Nli}{N*~dt_{{Z5P{{X(IPA(HVFYmw~{{WXK z_o?$--zIOup~zcrxFau(5#FfVdQ_xR1Mv*vn~ROln!qQsH`Y`?u8wbb;{ zvV&apx&tsZ(^y6pHEi=y&h%Fb8NUH++Zz*H%bMr2kn>>S2!t_@v z*%oCwepE|U4IWReZy*e(-h*o(S7j$*y>03;ae|RT%u4#=gLQQUjhoGNGQ|>LGq-G< zQw4Ps^R5r$POo)&)SAM{W0D(k7?8uwCOGGOZ?|peN>q+fw?zKE@q=|GmdY667UFHB zS8T`zagq7d6-16;gr6=-f=?a&`J=}?n9PoXWjgYuaKBtuEaNvlO%gT>3oww%!-X8} z&pUHjGD){=*^VkIkRGfW02HuBEOJ=qCmYaW>SiFw@hJ@v3o9;Ix!~v9G#`jB6RBOi z(MR@pBl5VvVf*-r$<4ZPMq1?6X3M%_bd#uO}aL`)q$22U-!l=}rUAf+UMk(t)=F z`s@u%haya81ns%TDchtW%KfNH6zLi=6cf1snqEIXsFw@hCwvM}kF0RV5ijQ?ebCw5 z{HS({kF5%2H35*@kCClZ2%`c?0*s7cWSVN)MS|Zc0?f>ESmYHP0fDfl1r1w6ZqcT* zrz_6HsrNKF3ffB|2f@HPzf2G7NqMIs@>G&?LFSkiUMx&!m5$y<0H39For^}%UB>Yr zJ8I)eIaYkoSlh0np*p- zw1`m^#!lp%ZAd_+kha@$PkNvI3Pws0ImBb zo$<~MP`H5Hg&_z89!B)Fuu%hRC<>h<9{&I;DVzc}$lq+74AXJNsdbHk@9jwj1e6#) zZfVHm0^FQ(cl56G3No2QAx>M6YjzAzl<6aWHZ`nD>#4}?z~>Z4!M1G*0VHITK;R7n z3^2(g=jBeqJer6lhcp(I#FzwNW9vnROvc*v09zy9AM04an*r)phs9KdHfx89vos0|}HHAKm@9yQSG zoa5S?gFI4-e86)}!Z-J-#6~uYwG=O7xve5?xT9bZoYSE|I&CluC~S7Cw`-wJ)z06z!yNBdL|G!`vQHK21n;@8RG&vm(e$YWF8%#! zmm5;_MhJlIS++iW z)<$X=(Li+GnI}-%u+W_uq)vj>iMY~+lhR975f-ziu$GyBVNRrwjy5d<;!Z22>CxsT zB<7+;7yQOOE1d4W3v8czEd*W zMK*4xP)8?TPq9l4M|G9#B1b$*wx)*&aaoYVi5iStRAevIrWNZNfhOSIxAOk=k;N<@Ro6x3$ zWS0z17-2>ZF`nMEWV0p$cL6tU$3HJqN<9r&tS0CFVoxYtMM3ZB^QLcpBX}Y(+XqD; zf^s!Cu1z6r({64dmUfIoY6epe*N-dx}mqX<3|wWsLE@H(=mi ziPaGl!s>;mYyw6|Q=H@EaauW=E45goc@!VJr*HdKt7C5+%1v)4l^Zw&ox6**YsZSz#*XJ!SdxD6?q~}+@PlL%oxQ6<11NnXwpeYo zdN|o8)Q{vFn(G|dA~Q2*Bn)KsqMK11dO$erdr)J&Wi6*GsPE}SlJOcdoM((@6~H!C zk1d^8Be37)S&WmGa93};LWN7Rx0noUI~r|1<7vh~*m_WG;aQ_%>Jijq@cCBD)6*^D zGfM16t?((NjW7|u{{STCiZCN`r(h^B2~A)WXhKkNq;d5%i+gFUv*Jhz#=xD1^wq{d z$^IWV)}?hj^CiPZgoRO!tCr3|dXG>j!Z`3wFikHCgO}8yQ+yxdC(@KiiAHwq>svA( z2r)a3#-2JFjlofNgbXMnIIACKIvJE{1nhD5z3Lh#n>p2t5It(mwaA`w3lXSf1KbYP z%__^0Yk~b@4YpE9Dw**0M7XvcdAwy7Q<2eDOHS=()c*^ ztlQi&E2++u<35!{BN=0==hB^mOh$hyl@m!U*-_7}KN=7~Ey=2(82kr0JKY;jJ6H_lJ>tP$&2)weZ}b51Q7E09GJ z81Tb2;4(!XA-}B=D67*@WLHaOzENE7PdFe~Nn~~vIhMv!iFTuD+J_e6jg6}-G$2n) zOT{$ww4LdY89sHSCr}ieRY_MM=B3}TMB21KjE;M$NsU|AEz}zX<8fCyoc?A!*EMwc zVo9wYjJ;&}nr_OTSa}s* z#WRCaVH=9I(}v=ulZA1W!r~$Ftj3tF$K0)vcJ-`QYNOHp>R_-bj8IVyYbEe-DOkw! zCXK-x^IAJ?&OTH*oP*9qYFHHmJJFz6Yi0fZahk1fDMCmY091I~#xu=Rvypzyt+{S0 zSrG8iw6nXMu-Na4<+_Zyk@w`)Ypa$Ha@fX2K)G=2*sEcU63Z(A6XB^|wbY{N7Dnn+ zFTRyPag+1(9GW7wAKtl7jXHw$S(m5UI8$uQBz9l}HU`7xy-{rZ8!L-@l$E297bM_~&VN6MTv{53?7zA+|93+y}6hKB9z zFRr6&8RpY!G|MEbvZ{Ik-*Z$th_#ET+sSD0%C?aN%zjc>jk((Z^N;IVSkH9sR@vh^ z2ohvzZ@P4ka4Bhkw(2i=6NxR=kBC}Kup@1@6gqdYTlkp#JfCX|70Sxa5kOEg{KMaD zV~Uq0r;BmnM;@Go)2)E|x1~4Kdc1dld8sC)fulc1M6 zJ1mAp5(48acfe}iPfCnt>Jjd65DNzf%h~~n$91}!gD00N-I~us zkfe4aJ7=1m)ZOQqpn&B^-eNiSpaiT=PNZcoN7#>0IOI-$(7Yn3kimfkCTYG&a%Sgj4slK0TQcGq~ zsz8ViOJoD{u1xA;mN58}jK~xZ;?EVcw}UH!b?9%rJJB8{pE08YhTjH(Bn=tT`Kl%Y z#B;}RbfF4dd4cVk!zgG9BZVY^jPO0`WxM!cUku)CXWtb2%CTCN4XD2?O)H24#S>d7 z<+OQ@+g5@@m586bb)RFJT`%8gTMRR`Z*3+d*C&~Yz{hIjfkUY$n6V@b=jJHTV8y6! zsOFX`V~aiVew))Ue(jbw0E}SoOogU5Zlxk%#BOpi+KmgUs4hWaN=Z7Zpw-UCwuy93 zGC`_DMv7@W6#Kgoji}JECp3!BwvZ_BvwQDWi6&KwPzE#jQIeo0G+6VVDa&q;JecwyYO8JV)XaBUHjpX45z;btreL>rH~`m6Yw*b*cvU-o zffRVi;+O8l1!9vq>pNB$*yrU}tm1G6L8>uy_~RT?3lgsYRHc^7M;!u$5H}QT?OE7o z6{#Yp(v?PywJ#@nJDOe1G8KzTa1C>vV&_OBKb>2>n@SqwI<>~KM>VG!Gi7=qU(6kj z4Mezo&Nr%8l6jABrAd>MjjF89g`a%qy?VTM$TjMdEf%Y1)!cJS$-OZB=ngAlBE{Z< zd84@4&`nVjVIetTnz>+cgHYi(HEO}e1xF_e<1DGj1J;<0cLJ7!%tbjL-mFs5tpySJ zNu|p9iKmcdaX^)PJm#jJ$7n^@R!7p7k#tFbc&7D@WF9j@lzFj?)l(wPOrca}nfqA@ zu>%8&pK@`fRBz2IJ=ceY2kT6sVDh7Di?(KL_oSJ!4QkTsdd7A&c*ck~W{A0&fAb3Y zIl<}^G=Z2280AJu-0zP{`hQZmHhn7U;ePaSFb>>405^V*cSX=Ii}iY*~Wm;@aE06|8*xoMzFTX)dPKqXu8!4!ak z7I`x;E~AeS?}7N%R?TS)!m5@s$u+{pAzZ3x0J+X^KhE^b-w>-x=_SOmMi&J=N$dyV zzcnYO&pfgS(N*(?Eg?H&f%47|z9~f!ERvU=K|cOtjn4Vc&ZTxe6neMCZ6rwzwn--} z?14|98vwu)JqW@EDI3`2bEOb6i8n6ofN7f;{?&VGlQOD2fU0&sKf;u|zs=O15|z+O zqXx+v@ARQz0WGxAN?&5iN0=P_X?BUuOB}X&&T6E#k^ca~45Hq4EOI9!3QwjzIp5ZY zd1r5HV++G`;=G`<05AX>ezc)Naq7WPy0*(;XElU{O17n30i5TPSV>#OMN&oq8*+2V zreKL=)PqAgY+vk+Y(8C3al zG3Cc^tp;(6XO1g_ANrGM$dTM>jQ2k-+t%^jO>yUrU*0b}{r=T12p)UEsw}PxGAZPH zny2pgd;*^b!kop*g`k_XbO=enVF7N@JZ)B#*A+k2+&Fi!6Ya< z^Z8M&ESsrFE}AD0BV$soKqvapOj-`s=IkVbMiHn!VYUaM+nT}YjLZOWI8o)#lnS?J z(hJ72UB7{E$DO%8zUG;@NG*bZi78dq3LGvE&vJI9av4RloEG_pMh8|%11A9X$o8d& zPXIsJQRbW-M7jLUS-E-JSB`+FK2(nx-_-vAdKfT60crAq#!91Z2XA`kEe#m#nqfF7 zjp4R6GZD6ZhX7LX+W|%ux#~+B`1_XUP~d#c9^Wf?Mce= zIdTDAdtjW^5U$N5X7O2-fIWv|DWA10*8UqJsZqJ^X*Nbz8hA8BfD!|8-SfpV73|$v zG+|U6ak1atjUK5YjY$IsgT(|y(YZ$38)Q?Ku^F2{-~+xks#S}0W<`!9X%#_s*cIZm zvqwvU@~H`rF{WU#msSB{6zzewYNQ!dG&g;~-Zz)|zA zAbq>*Ih#L&Ad3ZtO;@+*7SMs@6+^|vV9W1H=O#LYQRu9ph-{#JD9VVu3I~l?@u_w; zkkDW_#dydmPa3dhqM{2L@hcuBC}ppBwB8`GzY?*kmJ(J&(V$pHZK-LW0*KE`OO^(n zC`QSBMnX}yu2ZO-16@_x95r(tNi&~sO4SY-avaUs^BSKf+*KRI!8JBfxuVIY0mrWO z>ho8sY-8%}y#@E9xS%-ptw=^L`y5bPR3ZS1^X6=b)FTVM0`b8ZUCkMO<_D!EDI+x~t7EsJ5;V)6YZ{Bn zam9O9OEIO);d6@6kp|3N$g3wKVVZ?;K3|;EH+KZH9P?8hvel;?XwNxY8G*BtM$ej- zfwrSYeAuR7L~o1l+Q*`JXrP;?@X2lf15y12eAs=UH;E&}io~3elh2^_uFvDXq_SQ? z)8fH+m6^_SrJJ@ra6dZcZhEbyrLTl!6G#R;pnup_EUx5j#Hu_NPqZ-_CoDT6#ul{% z2V&@|!v;sz8MwyX{*(y1qumu)ZS@5{VqXIk!o{ztJXff62$h)UPq_MTS(`K@9SYdk z$vdEO`B=h|GDmEGoL4Z##OKCG)OW*e`qypLBU?)wYiOrfwKwl4DX5~HpDFFePkr%R zuTh`vSCSZF!UKUJVs;-oUt=x;ib5AuiZwRClgHAv6NyR5^L8{#RdFzQ)m>0|jxrC< zwOJ*ONgGKF!$=?vy~PcNk(lLZ`kZDYW@K@sAjv1tR?TnW-HER4m|M2wagSQePdssH zNeEHCRE+-soYL$(N6^paAP+I^LuVH5I;FHd4tsc}){dcLj~;d3a>tH6tDMvOwaat8 zGWPKn!pLxV1A5ibQ=RLQ35#@yT?68k0dff)=gnXw@V^fyYAOy24 zn}V#gNaB%Qh-TBAu^-lhIU(z0f@^?-Cnp)ll}EY$)tifWF^vZ=y0VOcM6!sK8m-?z zISfCG=j}xnXyBI1G5t-#O+_pTJ0aqh|j`-W!ld-;uCwOHj(UoAwRy7{mk9ykeEREv4g@^<9 zsLZOs`iyq;skYeBKyB`Cdd1TCCtRv`c7G}Wc2Sysd1AHw<^9BR&d<+KrD5M%l3_Qa;li&TRD9phahw&AZ0;oeCXjP0>m#IOik&;>U63V&Lzpe#C zzSH0qpG~H6P_{KUr@kp!FDI5@p=OCx>ujmiK=s8C+{JZv971Gc-bnIKzVuR~uVN%e zvYAYbFmkPt^Wup#YDTh{L%{B%VlgzIIT%el+E$M7+8KF*ASW1do+kINZ1+ znqJ5EX!4upvmV^K{#++6AFMtqWx*x}is>J=1c^9MR zDbrn?9}MrtakW&p_-;wd2;$C0#~9wet!wagq&Ou8YNS0T+5v!rYNCu#zEWq(?S2Kg zoq?5k_1>>^@7eOkLolxVM;d+A0==vXWlMrMx7I+i_&U-J$|uf414965vk1Byi4glK}hmVT!xY~s<+9-IwmG2*JYJZMv0%Pk(#2!U2A8PTq zss1568rfNmcZt?J_KL@kp7gNPYjs0iM_6wZ);vR6tY(O@S7_tG&14cqY$Elo#A3S8 z0d8v`8ZLk<*aD55XaTJ51#PuzNExC72DCNidKr3TD;B(uC~@AouB8?bn(8hX7|nCN zPa0c_ya-I=Z&wW2sd598Ryu$h&MHjX6HdsXR_BaYt1YqKy;EbO>d(aqxT8Nb7d5E} zz&M~a9nYl!{praw5$#nhn*da_-LX{dI{`~R4~(^HbsDh;-KkcC0AsaMiLgar$Zdez zW5YCs%YcX8oF^%dT2|#XN55*6Iy|^Zxsyy}4)s21NJCBCa%yzv3tM1}a$+;dxuvow zlb<=Djg4tYgBpW_MMlx7ik8v~U<{Linj8ZHkocD9*!(?k@yp>$E+R$;2Pd3;0jbUS zrpiHiG>!)6sI}e4QJ!WgB-*5C)Zm`lLG;ctd(>F%ZA-@Uc!yETMWhp-O*g+{ndK8A!%Ee{S{VcWm_%tsv`Uu$UuQG_=Ye#91_`1mgtN z7~!kRFSmrCOB2~I0Je$-$#)2m{1WLOyTD78v{rg3ZvK_KN@E81YVw{ zq6(I=vqAvW3FE(~I=him9UO)I&BV@I%(1b`MY+%p_@(tGqC`E20GxsT^#1@*VI7PM zYne5nc_ek;ImX-jRP!US4I`N!8OQ1U=%6sBIO4TTIRZnnf(RJfe|~6E970M($v9*F zaYBV8d3H@r^WvDE+2V)>%HZ$j9MB5jsbJ#@xF;NOM2gjB; z=RL{wqui{PGWbjjsULhD$gVilm=Sb{{5HwoR&z#$ZO9983ZTXV0!be_NMWBNl^MzH zTR@E^#udP0u+`eQ;(%KyF5^Hlqu8GN9%|&>6)tWZ!**3r02nFBKAH8dUVC#Cs05px z^T5bGsjFdn?BW^ObCy56#^SjEwR6#J(U~Qjv+UVqv_zFmZwa&(@XfG<7!u z-^YY?8794GLW3A2DP!tK2iLuCcPz0?z{ZaO)uCfyr#x&=e_G>ix`p|;J~K-02qZB4 z_NrYf-MW?13%RC@I3Z9Rl1Hzgskqo#Med^!mWcwO&NQw``hGPc=KM4aFjaGaNZfpd zS+VFBDQhc55;THO3?pT}XXTI9uXL}0u5PFC9^NQpZHOaLBhw~~F-h)$_Yzcbu}90NKQ>K|Z9@DPwR)hXN`i=K)4*>DE89%UGljF64G2VeCDs{jbB;R%$dxxgGIU zSbwZXk-j74Rv!y>3zu0WNpc1bqm2CpU9|n5&nl5}$`Oum7kc_C#SB9VpjHf^af)>a zt|45c{hYmrz?N2^YFv@)y;*~!+FOq#V{d9{rl7Rm^;=@3kU){fFsdsCQ9v?wtqYSv zgJI0nGc5?@*R&HwBdpn_HKCB2*g(gDL|Z~@v;loY*M@1;v)g(i+NkEb(bU8Ou^Ukq zCN%oMi`useqtd&`G$OX_dr?x`)|to#z2YdLV53B~0*J6EtxVunrsR6pY&s4RMQk*R zPwhi}>r-?yT}=;8mo7Vk)CQ~!vxbZN`&P1?Ov=*!zR5_Wl{Bq;Lup+jQ*4d z*jA)XfqwM6oSI?i4mPJm*{XfvRcsd|RH**-Tj`@=OFbd6qQ#)iJf2c1OKfVUvy)gQ z(_}_9V`>HCVm-K`cNC@Eg@zBUBv!|h(JJ#|YF)!@YMto7`P8YwC)&2-F~~w)&1k=R zi7>@tBqZjq=!dbWR|Dx>hsSpkKTx)}fyihRByuv`l0ASQjdjvFx3rp2Kru$olfb|x zzDIF*(6cimJIbV~Q@dw;pGw8`GpN$`ZTe_iwlb?5Td9^7l#cG$2XnqCFwHmoUfq`8 zQpCbm3YxWJwm$({-Cjd=EEZipCx~n%kSNO%GI{sT)nlaeO-3;kfa**-tt!~MUhxrC1~B!)O#dhhK^VMgzB14`q<@sy3+wn_IL zhx^tHUuU;QlS;FVOW}=K2e+nZP$UtsGGT~pGiM_n!jEZY_p^@)j7nch7T^x}rmz{h zX=S>OM1nhX3#k=Q41xSd)Ycj(%u%YD9!B#q$;UZAGx-Xp@bvzb8p$+IcQ%q)Se*lX zg)1q$3inc^OK&d&0eS4J=}G0f3PG>(mjN1EbX+MB#DIq3*=aB-Zexf zP|g?+QVFj~)7$J(Ot7$p_i3FleXs{_){A><6etXF$}NVSWkSk1$8kE8 zUj_mE#1rmvd(*aY!354qlDWbbBPa0V<_!iWGRUDI8Ug#HZTOnCXJ&|r87%A$PNSUt zXr?HT*L{8e05Z!7RMeUVcJKLCC5|?@R*AG1Z9ZCh=@J{~D;dB*7zQV8`Sq!*YkPEp zW{fH;j570#`d2snIZ*k7MXYkl{{Xx@GOnB&O6u)b%O30x2ORwUDz&G=S6xkmN8+~s z0K_r)S5MQwW{mn;VpENW6!FI=(Ol9#YOu>A5UC-PK2=fA&bls{&~ERQ6YQ)v%lUsH z+aEgmhfnw(!pw$b48UV1u0dyQYPduL=~P(agmaVTkDv6vfhID?D+Y)tz*2HH;<|31 z@J+3=c#`GhBX&I1{{S6VWfiHoCYqAs7rkb%blXc|gpG13km#Ru55kl(lbY9AJXckn zX$>{TIH9!p??K%9)k5xb&y;#l z@;Zc8)r}Qw_upz@$N=qHnb&&jVWUO1QO#~4LE47bB8Jy~^eKl!I8oZXT}2?*T<=0_ zqesPyWfL-r(AVLlF`y zVGp%+AZ+otBywwC6rWn(!L0aVil7r(qxr0qjy=86{0wdZOSkIiE_Jk})O)1)h6vq^w+ zQg0>zF#T$k#|EI^IRt`nimXV;nROzPcBym7HwW;e-Aq9>GFf#Us7cVVMw32rYt*!_ zUZ}QJBka#XYCHN+99G0hfofgFFx*fcofBq}{ra`j&ILwJYQ2d8X)S}O%9!o@Pv6@0;!jtP%?gmXKa-K@r z6C{ZeCZcP2V>!pIZp(+PT}-_{rpa`}Iu{DR%*SN|*Ltg!B5F3yL*jpl)@Y=$w}o0G zCX*vM%V|5{e=c_4n&acQj%cnWQ!6Z5V=TT`iTXa1vR}I;8~SH4Bgr@nySj zo+-&127(Ju4i6XrG#Ii2_hNgTeJjbF+}*R)gcMaH5WyvfP>o=Scz_0tSdCA%*!?qB z?mDAftU}h^TI(u}kGiXj5)Ys`-|I>AzLjuHw=zm(Y4(m|fKCsYk9>Cb#XlX~Ju>&~ zi6m!R0>JH-17dx5#s(@*$O9NVe5<2Rgl<|LSSrVJtC}eIUPFyif$OP8YKs)#pV1Fq5O?9K;D8lb3KQ-cw7sD2j`Jc?-4TL@tB1mYEnXgI3G%zIPo#!l?!Vv=O_g+cGyecpQ|Z42 zt0ofFR58w2VwyisTWnvw{71~z-r{X8Rkbd_58?Ou*F$5_?r)EhISzBV$j`nj>9!vR zT3dOfMJJvr)K-@EtAs@u_M_nh4=FR}eK+=hG_n~kcGLz%bygn+*xAIYV+1b(t~B6m zC@ncO>s&yq>FDjP?N<@TqvR`Qm<^O-gQ<;UNj20H%_%wULnV)TFpJi<5dmGG3k57| zb%@dMsw*BK6f7o!A6g>9pjM4I6f<*{49sgn%zbFWEx4@sSgz5+`dIg@ct^c9BCmK= z)^!Sqb6N0r;+hFw(OqXyu!vTCAxA6eUI|@c0ia^Dpi#6}vpf@%QP zHGn8Zt5V$4BrSz6Lua9u&1z2j(k9z5j z2sIO}NBg!TwNVk0IE&XIj{6Fp?swj)bqLP9cBwZ5PxYxz#*G7a$lvm>P#Lqgdi6xQ HT0j5UC*8$J literal 0 HcmV?d00001 diff --git a/docs/content/en/functions/images/AutoOrient.md b/docs/content/en/functions/images/AutoOrient.md new file mode 100644 index 000000000..588f4874c --- /dev/null +++ b/docs/content/en/functions/images/AutoOrient.md @@ -0,0 +1,52 @@ +--- +title: images.AutoOrient +description: Returns an image filter that rotates and flips an image as needed per its EXIF orientation tag. +categories: [] +keywords: [] +action: + aliases: [] + related: + - functions/images/Filter + - methods/resource/Filter + returnType: images.filter + signatures: [images.AutoOrient] +toc: true +--- + +{{< new-in 0.122.0 >}} + +## Usage + +Create the filter: + +```go-html-template +{{ $filter := images.AutoOrient }} +``` + +{{% include "functions/images/_common/apply-image-filter.md" %}} + +{{% note %}} +When using with other filters, specify `images.AutoOrient` first. +{{% /note %}} + +```go-html-template +{{ $filters := slice + images.AutoOrient + (images.Process "resize 200x") +}} +{{ with resources.Get "images/original.jpg" }} + {{ with images.Filter $filters . }} + + {{ end }} +{{ end }} +``` + +## Example + +{{< img + src="images/examples/landscape-exif-orientation-5.jpg" + alt="Zion National Park" + filter="AutoOrient" + filterArgs="" + example=true +>}} diff --git a/docs/layouts/shortcodes/img.html b/docs/layouts/shortcodes/img.html new file mode 100644 index 000000000..50d4da9ed --- /dev/null +++ b/docs/layouts/shortcodes/img.html @@ -0,0 +1,379 @@ +{{- /* +Renders the given image using the given filter, if any. + +@param {string} src The path to the image which must be a remote, page, or global resource. +@param {string} [filter] The filter to apply to the image (case-insensitive). +@param {string} [filterArgs] A comma-delimited list of arguments to pass to the filter. +@param {bool} [example=false] If true, renders a before/after example. +@param {int} [exampleWidth=384] Image width, in pixels, when rendering a before/after example. + +@returns {template.HTML} + +@examples + + {{< img src="zion-national-park.jpg" >}} + + {{< img src="zion-national-park.jpg" alt="Zion National Park" >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="process" + filterArgs="resize 400x webp" + >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="colorize" + filterArgs="180,50,20" + >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + example=true + >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + example=true + exampleWidth=400 + >}} + + When using the text filter, provide the arguments in this order: + + 0. The text + 1. The horizontal offset, in pixels, relative to the left of the image (default 20) + 2. The vertical offset, in pixels, relative to the top of the image (default 20) + 3. The font size in pixels (default 64) + 4. The line height (default 1.2) + 5. The font color (default #ffffff) + + {{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Text" + filterArgs="Zion National Park,25,250,56" + example=true + >}} + + When using the padding filter, provide all arguments in this order: + + 0. Padding top + 1. Padding right + 2. Padding bottom + 3. Padding right + 4. Canvas color + + {{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Padding" + filterArgs="20,50,20,50,#0705" + example=true + >}} + +*/}} + +{{- /* Initialize. */}} +{{- $alt := "" }} +{{- $src := "" }} +{{- $filter := "" }} +{{- $filterArgs := slice }} +{{- $example := false }} +{{- $exampleWidth := 384 }} + +{{- /* Default values to use with the text filter. */}} +{{ $textFilterOpts := dict + "xOffset" 20 + "yOffset" 20 + "fontSize" 64 + "lineHeight" 1.2 + "fontColor" "#ffffff" + "fontPath" "https://github.com/google/fonts/raw/main/ofl/lato/Lato-Regular.ttf" +}} + +{{- /* Get and validate parameters. */}} +{{- with .Get "alt" }} + {{- $alt = .}} +{{- end }} + +{{- with .Get "src" }} + {{- $src = . }} +{{- else }} + {{- errorf "The %q shortcode requires a file parameter. See %s" .Name .Position }} +{{- end }} + +{{- with .Get "filter" }} + {{- $filter = . | lower }} +{{- end }} + +{{- $validFilters := slice + "autoorient" "brightness" "colorbalance" "colorize" "contrast" "gamma" + "gaussianblur" "grayscale" "hue" "invert" "none" "opacity" "overlay" + "padding" "pixelate" "process" "saturation" "sepia" "sigmoid" "text" + "unsharpmask" +}} + +{{- with $filter }} + {{- if not (in $validFilters .) }} + {{- errorf "The filter passed to the %q shortcode is invalid. The filter must be one of %s. See %s" $.Name (delimit $validFilters ", " ", or ") $.Position }} + {{- end }} +{{- end }} + +{{- with .Get "filterArgs" }} + {{- $filterArgs = split . "," }} + {{- $filterArgs = apply $filterArgs "trim" "." " " }} +{{- end }} + +{{- if in (slice "false" false 0) (.Get "example") }} + {{- $example = false }} +{{- else if in (slice "true" true 1) (.Get "example")}} + {{- $example = true }} +{{- end }} + +{{- with .Get "exampleWidth" }} + {{- $exampleWidth = . | int }} +{{- end }} + +{{- /* Get image. */}} +{{- $ctx := dict "page" .Page "src" $src "name" .Name "position" .Position }} +{{- $i := partial "inline/get-resource.html" $ctx }} + +{{- /* Resize if rendering before/after examples. */}} +{{- if $example }} + {{- $i = $i.Resize (printf "%dx" $exampleWidth) }} +{{- end }} + +{{- /* Create filter. */}} +{{- $f := "" }} +{{- $ctx := dict "filter" $filter "args" $filterArgs "name" .Name "position" .Position }} +{{- if eq $filter "autoorient" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.AutoOrient }} +{{- else if eq $filter "brightness" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Brightness (index $filterArgs 0) }} +{{- else if eq $filter "colorbalance" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage red" "argValue" (index $filterArgs 0) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage green" "argValue" (index $filterArgs 1) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage blue" "argValue" (index $filterArgs 2) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.ColorBalance (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- else if eq $filter "colorize" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "hue" "argValue" (index $filterArgs 0) "min" 0 "max" 360) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "saturation" "argValue" (index $filterArgs 1) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 2) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Colorize (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- else if eq $filter "contrast" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Contrast (index $filterArgs 0) }} +{{- else if eq $filter "gamma" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "gamma" "argValue" (index $filterArgs 0) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Gamma (index $filterArgs 0) }} +{{- else if eq $filter "gaussianblur" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "sigma" "argValue" (index $filterArgs 0) "min" 0 "max" 1000) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.GaussianBlur (index $filterArgs 0) }} +{{- else if eq $filter "grayscale" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Grayscale }} +{{- else if eq $filter "hue" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "shift" "argValue" (index $filterArgs 0) "min" -180 "max" 180) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Hue (index $filterArgs 0) }} +{{- else if eq $filter "invert" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Invert }} +{{- else if eq $filter "opacity" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "opacity" "argValue" (index $filterArgs 0) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Opacity (index $filterArgs 0) }} +{{- else if eq $filter "overlay" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $ctx := dict "src" (index $filterArgs 0) "name" .Name "position" .Position }} + {{- $overlayImg := partial "inline/get-resource.html" $ctx }} + {{- $f = images.Overlay $overlayImg (index $filterArgs 1 | float ) (index $filterArgs 2 | float) }} +{{- else if eq $filter "padding" }} + {{- $ctx = merge $ctx (dict "argsRequired" 5) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Padding + (index $filterArgs 0 | int) + (index $filterArgs 1 | int) + (index $filterArgs 2 | int) + (index $filterArgs 3 | int) + (index $filterArgs 4) + }} +{{- else if eq $filter "pixelate" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "size" "argValue" (index $filterArgs 0) "min" 0 "max" 1000) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Pixelate (index $filterArgs 0) }} +{{- else if eq $filter "process" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Process (index $filterArgs 0) }} +{{- else if eq $filter "saturation" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Saturation (index $filterArgs 0) }} +{{- else if eq $filter "sepia" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Sepia (index $filterArgs 0) }} +{{- else if eq $filter "sigmoid" }} + {{- $ctx = merge $ctx (dict "argsRequired" 2) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "midpoint" "argValue" (index $filterArgs 0) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "factor" "argValue" (index $filterArgs 1) "min" -10 "max" 10) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Sigmoid (index $filterArgs 0) (index $filterArgs 1) }} +{{- else if eq $filter "text" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $ctx := dict "src" $textFilterOpts.fontPath "name" .Name "position" .Position }} + {{- $font := or (partial "inline/get-resource.html" $ctx) }} + {{- $fontSize := or (index $filterArgs 3 | int) $textFilterOpts.fontSize }} + {{- $lineHeight := math.Max (or (index $filterArgs 4 | float) $textFilterOpts.lineHeight) 1 }} + {{- $opts := dict + "x" (or (index $filterArgs 1 | int) $textFilterOpts.xOffset) + "y" (or (index $filterArgs 2 | int) $textFilterOpts.yOffset) + "size" $fontSize + "linespacing" (mul (sub $lineHeight 1) $fontSize) + "color" (or (index $filterArgs 5) $textFilterOpts.fontColor) + "font" $font + }} + {{- $f = images.Text (index $filterArgs 0) $opts }} +{{- else if eq $filter "unsharpmask" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "sigma" "argValue" (index $filterArgs 0) "min" 0 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "amount" "argValue" (index $filterArgs 1) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "threshold" "argValue" (index $filterArgs 2) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.UnsharpMask (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- end }} + +{{- /* Apply filter. */}} +{{- $fi := $i }} +{{- with $f }} + {{- $fi = $i.Filter . }} +{{- end }} + +{{- /* Render. */}} +{{- if $example }} +

Original

+ {{ $alt }} +

Processed

+ {{ $alt }} +{{- else -}} + {{ $alt }} +{{- end }} + +{{- define "validate-arg-count" }} + {{- $msg := "When using the %q filter, the %q shortcode requires an args parameter with %d %s. See %s" }} + {{- if lt (len .args) .argsRequired }} + {{- $text := "values" }} + {{- if eq 1 .argsRequired }} + {{- $text = "value" }} + {{- end }} + {{- errorf $msg .filter .name .argsRequired $text .position }} + {{- end }} +{{- end }} + +{{- define "validate-arg-value" }} + {{- $msg := "The %q argument passed to the %q shortcode is invalid. Expected a value in the range [%v,%v], but received %v. See %s" }} + {{- if or (lt .argValue .min) (gt .argValue .max) }} + {{- errorf $msg .argName .name .min .max .argValue .position }} + {{- end }} +{{- end }} + +{{- define "partials/inline/get-resource.html" }} + {{- $r := "" }} + {{- $u := urls.Parse .src }} + {{- $msg := "The %q shortcode was unable to resolve %s. See %s" }} + {{- if $u.IsAbs }} + {{- with resources.GetRemote $u.String }} + {{- with .Err }} + {{- errorf "%s" }} + {{- else }} + {{- /* This is a remote resource. */}} + {{- $r = . }} + {{- end }} + {{- else }} + {{- errorf $msg $.name $u.String $.position }} + {{- end }} + {{- else }} + {{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }} + {{- /* This is a page resource. */}} + {{- $r = . }} + {{- else }} + {{- with resources.Get $u.Path }} + {{- /* This is a global resource. */}} + {{- $r = . }} + {{- else }} + {{- errorf $msg $.name $u.Path $.position }} + {{- end }} + {{- end }} + {{- end }} + {{- return $r}} +{{- end -}} diff --git a/resources/image.go b/resources/image.go index cb0181a5f..6c34795f8 100644 --- a/resources/image.go +++ b/resources/image.go @@ -279,8 +279,8 @@ func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) { } return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) { - filters := gfilters - for j, f := range gfilters { + var filters []gift.Filter + for _, f := range gfilters { f = images.UnwrapFilter(f) if specProvider, ok := f.(images.ImageProcessSpecProvider); ok { processSpec := specProvider.ImageProcessSpec() @@ -293,10 +293,14 @@ func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) { if err != nil { return nil, err } - // Replace the filter with the new filters. - // This slice will be empty if this is just a format conversion. - filters = append(filters[:j], append(pFilters, filters[j+1:]...)...) - + filters = append(filters, pFilters...) + } else if orientationProvider, ok := f.(images.ImageFilterFromOrientationProvider); ok { + tf := orientationProvider.AutoOrient(i.Exif()) + if tf != nil { + filters = append(filters, tf) + } + } else { + filters = append(filters, f) } } return i.Proc.Filter(src, filters...) diff --git a/resources/images/auto_orient.go b/resources/images/auto_orient.go new file mode 100644 index 000000000..194efefb5 --- /dev/null +++ b/resources/images/auto_orient.go @@ -0,0 +1,60 @@ +// Copyright 2023 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package images + +import ( + "image" + "image/draw" + + "github.com/disintegration/gift" + "github.com/gohugoio/hugo/resources/images/exif" +) + +var _ gift.Filter = (*autoOrientFilter)(nil) + +var transformationFilters = map[int]gift.Filter{ + 2: gift.FlipHorizontal(), + 3: gift.Rotate180(), + 4: gift.FlipVertical(), + 5: gift.Transpose(), + 6: gift.Rotate270(), + 7: gift.Transverse(), + 8: gift.Rotate90(), +} + +type autoOrientFilter struct{} + +type ImageFilterFromOrientationProvider interface { + AutoOrient(exifInfo *exif.ExifInfo) gift.Filter +} + +func (f autoOrientFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) { + panic("not supported") +} + +func (f autoOrientFilter) Bounds(srcBounds image.Rectangle) image.Rectangle { + panic("not supported") +} + +func (f autoOrientFilter) AutoOrient(exifInfo *exif.ExifInfo) gift.Filter { + if exifInfo != nil { + if orientation, ok := exifInfo.Tags["Orientation"].(int); ok { + if filter, ok := transformationFilters[orientation]; ok { + return filter + } + } + } + + return nil +} diff --git a/resources/images/filters.go b/resources/images/filters.go index 2a1f41a03..572a10d71 100644 --- a/resources/images/filters.go +++ b/resources/images/filters.go @@ -174,6 +174,14 @@ func (*Filters) Padding(args ...any) gift.Filter { } } +// AutoOrient creates a filter that rotates and flips an image as needed per +// its EXIF orientation tag. +func (*Filters) AutoOrient() gift.Filter { + return filter{ + Filter: autoOrientFilter{}, + } +} + // Brightness creates a filter that changes the brightness of an image. // The percentage parameter must be in range (-100, 100). func (*Filters) Brightness(percentage any) gift.Filter {