From 7d261a147dbda19f14362df2046bca4626b2e429 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Mon, 13 Jun 2016 23:03:41 +0100 Subject: [PATCH] Ensure scaling factors are calculated independently #452 Fixes bug introduced in v0.15.0 where, if the shrink operation rounded up along one dimension, it could then also round up the reduce operation on the same axis, creating a small stretch effect. --- docs/changelog.md | 4 ++ src/pipeline.cc | 72 +++++++++++++----------- test/fixtures/expected/crop-entropy.jpg | Bin 8660 -> 8715 bytes test/fixtures/expected/embed-16bit.png | Bin 999 -> 789 bytes 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 510c73fa..9ace370c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -18,6 +18,10 @@ Requires libvips v8.3.1 [#443](https://github.com/lovell/sharp/pull/443) [@lemnisk8](https://github.com/lemnisk8) +* Ensure scaling factors are calculated independently to prevent rounding errors. + [#452](https://github.com/lovell/sharp/issues/452) + [@puzrin](https://github.com/puzrin) + * Add --sharp-cxx11 flag to compile with gcc's new C++11 ABI. [#456](https://github.com/lovell/sharp/pull/456) [@kapouer](https://github.com/kapouer) diff --git a/src/pipeline.cc b/src/pipeline.cc index d671bd62..f9b01456 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -219,34 +219,46 @@ class PipelineWorker : public AsyncWorker { // Scaling calculations double xfactor = 1.0; double yfactor = 1.0; + int targetResizeWidth = baton->width; + int targetResizeHeight = baton->height; if (baton->width > 0 && baton->height > 0) { // Fixed width and height - xfactor = static_cast(inputWidth) / (static_cast(baton->width) + 0.1); - yfactor = static_cast(inputHeight) / (static_cast(baton->height) + 0.1); + xfactor = static_cast(inputWidth) / static_cast(baton->width); + yfactor = static_cast(inputHeight) / static_cast(baton->height); switch (baton->canvas) { case Canvas::CROP: - xfactor = std::min(xfactor, yfactor); - yfactor = xfactor; + if (xfactor < yfactor) { + targetResizeHeight = static_cast(round(static_cast(inputHeight) / xfactor)); + yfactor = xfactor; + } else { + targetResizeWidth = static_cast(round(static_cast(inputWidth) / yfactor)); + xfactor = yfactor; + } break; case Canvas::EMBED: - xfactor = std::max(xfactor, yfactor); - yfactor = xfactor; + if (xfactor > yfactor) { + targetResizeHeight = static_cast(round(static_cast(inputHeight) / xfactor)); + yfactor = xfactor; + } else { + targetResizeWidth = static_cast(round(static_cast(inputWidth) / yfactor)); + xfactor = yfactor; + } break; case Canvas::MAX: if (xfactor > yfactor) { - baton->height = static_cast(round(static_cast(inputHeight) / xfactor)); + targetResizeHeight = baton->height = static_cast(round(static_cast(inputHeight) / xfactor)); yfactor = xfactor; } else { - baton->width = static_cast(round(static_cast(inputWidth) / yfactor)); + targetResizeWidth = baton->width = static_cast(round(static_cast(inputWidth) / yfactor)); xfactor = yfactor; } break; case Canvas::MIN: if (xfactor < yfactor) { - baton->height = static_cast(round(static_cast(inputHeight) / xfactor)); + targetResizeHeight = baton->height = static_cast(round(static_cast(inputHeight) / xfactor)); yfactor = xfactor; } else { - baton->width = static_cast(round(static_cast(inputWidth) / yfactor)); + targetResizeWidth = baton->width = static_cast(round(static_cast(inputWidth) / yfactor)); xfactor = yfactor; } break; @@ -259,23 +271,23 @@ class PipelineWorker : public AsyncWorker { } } else if (baton->width > 0) { // Fixed width - xfactor = static_cast(inputWidth) / (static_cast(baton->width) + 0.1); + xfactor = static_cast(inputWidth) / static_cast(baton->width); if (baton->canvas == Canvas::IGNORE_ASPECT) { - baton->height = inputHeight; + targetResizeHeight = baton->height = inputHeight; } else { // Auto height yfactor = xfactor; - baton->height = static_cast(round(static_cast(inputHeight) / yfactor)); + targetResizeHeight = baton->height = static_cast(round(static_cast(inputHeight) / yfactor)); } } else if (baton->height > 0) { // Fixed height - yfactor = static_cast(inputHeight) / (static_cast(baton->height) + 0.1); + yfactor = static_cast(inputHeight) / static_cast(baton->height); if (baton->canvas == Canvas::IGNORE_ASPECT) { - baton->width = inputWidth; + targetResizeWidth = baton->width = inputWidth; } else { // Auto width xfactor = yfactor; - baton->width = static_cast(round(static_cast(inputWidth) / xfactor)); + targetResizeWidth = baton->width = static_cast(round(static_cast(inputWidth) / xfactor)); } } else { // Identity transform @@ -429,19 +441,13 @@ class PipelineWorker : public AsyncWorker { // Swap input output width and height when rotating by 90 or 270 degrees std::swap(shrunkWidth, shrunkHeight); } - xresidual = static_cast(baton->width) / static_cast(shrunkWidth); - yresidual = static_cast(baton->height) / static_cast(shrunkHeight); - if (baton->canvas == Canvas::EMBED) { - xresidual = std::min(xresidual, yresidual); - yresidual = xresidual; - } else if (baton->canvas == Canvas::IGNORE_ASPECT) { - if (!baton->rotateBeforePreExtract && - (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) { - std::swap(xresidual, yresidual); - } - } else { - xresidual = std::max(xresidual, yresidual); - yresidual = xresidual; + xresidual = static_cast(targetResizeWidth) / static_cast(shrunkWidth); + yresidual = static_cast(targetResizeHeight) / static_cast(shrunkHeight); + if ( + !baton->rotateBeforePreExtract && + (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270) + ) { + std::swap(xresidual, yresidual); } } @@ -673,10 +679,10 @@ class PipelineWorker : public AsyncWorker { // use gravity in ovelay if(overlayImageWidth <= baton->width) { - across = static_cast(ceil(static_cast(baton->width) / overlayImageWidth)); + across = static_cast(ceil(static_cast(image.width()) / overlayImageWidth)); } if(overlayImageHeight <= baton->height) { - down = static_cast(ceil(static_cast(baton->height) / overlayImageHeight)); + down = static_cast(ceil(static_cast(image.height()) / overlayImageHeight)); } if(across != 0 || down != 0) { int left; @@ -684,10 +690,10 @@ class PipelineWorker : public AsyncWorker { overlayImage = overlayImage.replicate(across, down); // the overlayGravity will now be used to CalculateCrop for extract_area std::tie(left, top) = CalculateCrop( - overlayImage.width(), overlayImage.height(), baton->width, baton->height, baton->overlayGravity + overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity ); overlayImage = overlayImage.extract_area( - left, top, baton->width, baton->height + left, top, image.width(), image.height() ); } // the overlayGravity was used for extract_area, therefore set it back to its default value of 0 diff --git a/test/fixtures/expected/crop-entropy.jpg b/test/fixtures/expected/crop-entropy.jpg index 40107d2d074f7a9d1319d3b7896a6a765330e0fd..af12e8a37989445a0b316cb22744311d3316bbcd 100644 GIT binary patch delta 8624 zcmV;hAy3}aLyJU^rGEkf0|5a5000000000000svH1p)&I0RaER06-7`0s#X90s{sE z1_K8L0RR920s{mE5dab)1~C&sQ3n(vGGTFnk)ahMLQ=88Bs0+n7A10mqVP0BL}KCb z|HJ?q0RRI50RaI30000000000009C61ONpF|HJ?&5dZ=L0)GMm0s#d80{{R300001 z0ucit5!KKI%@w`rDxfV_GXI+ZRkz^yO6)BIiBc zO|28XLWGhgTpgu{&BJG&+J(?}Nv)Nz6tdP;fSxQym@JY810dk}XSme=0BW@Z!Jvk| z1d_6uS_$RYRoX&H!9CdgB=h7(qJ3y_8^{HK^&7L>%Rm;G1d|_xwmiopO;2x28`0ZZ2$^DzR;P?@ z`ATFtC(N)B{p&1GY8uvS5H6D+B4awUufJgYB|InfP;k@W{s^^a2|eLHS~+fhvuG)*Dd;iG^F zCBK^k%k86XhW1?yeMHv|sI8LuSqzH4CuY4GYJM2T4pBS&rwjV%XQ1n*rN3L)PwuXd;u(CG zpCQ{!Uuk_4Uq#VU+$p1Jt<>)hJ?S=8Ab%eYS0BO1`MJ`Qrs_6UEQ`B-{HVr8bH|Z^z@|^v2ThUgyD{E`$W2`jR zx(cZ#2`JjqCVmie+sN~&U1dVn$=|8$@fH+#ig?>U;^6ZK`H0R(r)^K5(dCxxmEAEz zY_`=3d8b1QGY^6o_Z*T(B)0?c)Ys|6y4+}@is#~1P*Xgx#y%Jzm$XZa_J24zIr9fY z9RW0LY@MlLl4XKl-p&*#!5P{|elUA#-iD~iW2c5w9FM1Xq*04go)HIXbk% zLrnFM_?`!Y;MJmKmSpXWd4CnpbM+cf(Nk0V%EEh~CJUe1D|HM3e%r}}fkFvl1bm69<46aN6VzJGh$%=rLyF6$js z-9<%ho|z>v0WpFpLo4lG0M8i7AC{;rSBn0Xfl}cNHLymWB-7IkvaFfs#0P>$ZalSR z)3s@It*p4cLN!FYkq9KTY7SR-rsMc(m+1ry6>{4;?c}bbYI-}F1LoWn)Zw`vxCciU zJC!{R7?Nt1j*&=dOMm=8f#vniag9@7?9pDWwbbvJXNF?X&KCkXI6V4~Txb#-%#~I~ zp}WqpRnb;M42t;6;m1FK9$ob}Yh&L=JqT_QrF|_mJdi~*s_busl~PYR`{e18)4jD} zq*cg2F1>qS*WV^u&>Mu(3QJYx1BNDRt38TgBl&U~@{ntw`7P%3L?c-2%EBqUwNPI0YWZ**## z?P5Uh;6}$_W+eW)ud<2iQls4l>D60!x-W*V2bBf{GIAT}My~8u zD|IblsgBhYRpGue{{Sp!J=J>;zJ_{E<>1sL5vf|BLlD4*KZcm}7l`VrjFoVW(v5|X zd4FjfiX?%)r|PS%i^G=XZw*T;GH;CJraK-zc{&2S^)uCY($dru86bo_5H!a2pFk_D zQ%7x`WdS^JfxaYV9}@mYNJTl$!-n%uU$%r=YR<@^Pa;VFK zhmOP7k~OQ`#B>!=-U7`>JOy1_1Hg~I2kH3hsNpAHAB#VVry@koe!8xssA@W;WPg#> z!Qih4R`bsShk_?8iJhd5NcGiXg;!?5E#yhly($%KWAvrFA**Sa)KyEsZ1SXy9`wDY zrdsGEk~tQhFNwD|1-!Jr==zE1X%v|p!CsV`{x?p&q$h?R ze|;8pk4uV*p>lz|IHp$dz#rwhjOXlh9cM;|sAw{~Pl}`Q&YV4IK=g4{Re!`;qMDiG zFoW|p-Z;>cX_JbyPg<3$dwsXV5=hn3%^`Om<)C0sS0J9>s-NABBcWx@s$LO;E$N{G`#D`=Uc?DI608zdyqi%`)ZNUvUUnoNOEv{ z_tq43&q*@A#gzfh)*sVeF@MOx*1roUAZJ|m1nbakVZ29omEdP+Kp+rET!a0zM_@G+ zcglFJU`RxvcdBksGs(`BtxY6OyOHB&UEW~h$o;h7_9c5!1x1#%{z2nY6Y2a*{q(TH zr0UB*C8g_QX0lHdR3C|sc7S~}w4{lt>Z)j%;bqK;{{T9karhCYEq_GK;WDQH_b0j1 z`>UXq3Ma3o&y7bYoMX*hG*36x|mbQmM@H1dj(CFLHI4v{LlL zE|*^kYAa-jpV~8cgnu~PQ2;!BIsTfX_OWV1(&44_D~{vH{YJgF)*E%g z3WkkkNZb;PG6?iK%B~|6&l>`zSd0$bYBY(tPB}U>Aj#`U=zlJhc8jcRBcz#4F_aAW zPu+J9Pc2v3X=-|Bsx1{202p3H#EZ(ZHwT}`Qazf}uTfZUeLo@n<3uW{^ZYU|f6rCa zH<_sI)7URjF@=x#StK77JfpY$C$@wei~fd^v~(p=2@wYe%T!k=QhF+w*aEK`1Nm!J zzUC@z)UwLpvwy~?XgMU~Tk3!G&Y#5N9C>S8x3V$NwCJvx6UA|PBi!*y(8hn15Dww> z)puRTEiJOyLs26<(KL)nJ;xY2Vq2>rveiix>xz1eZyaLiG-BW zLztOPLFb)gN%Jn+;v0!05zkX3vRo@3B+HfCm+V0Q0DtNa1J|8pYNe;AndJ>E^En2h zVsg3kKzIXQb%i`MT|l(aZ;B{sJ>-QvZViRxb{vfBRP_Ri)i86jUEkR%Il8bz#!h7QNLpD>A~MeB@`J^wbLW6V#SQQrOAK#(vtf>BWj0j4dlu zV6X$lJb(SMof&CpR86&t&u3k4Pc?dmfx`{Ravn3mBU3%7h-a};RzOwOT81kAWNpv- zYIn72No2B8Rn#F8Q$E!2`515J^c-k1?^i4Im71Dt0Iw{HJw`wu(CTY#NO!A47ulSK zTNO052ls#!$v%fh?&zT8f`0l;S+2D<`#dX5-+#MNfY>Ypz35m%TDSK z^KH-NooQ z{6FUKz|}qG;Zac!g>LaL@00>Yr+d%k6KH9#vS}0^q*Hu}iFD~bWz|Qt9jGwsG?IgCF zbt*#y0EqIcUS-H6V18Krb?1JjA7-vg?BwaKtXxMBd1WVrQO>2(^wrWgNuHT$!muHx za54C3i`s><8(&pV?k8*$pB)rFSvPkFAAelxcR|$3P%P>y5Vs;l-TLcKB;&~fDK|!` z`g-wO)Avf6%IRtlQ<(q>@NQBF8T|Ace~8vysBM<^66{Fnqucy$d*lA2r`J(U;q;B> zYIt_Ebc}W$&E@^{qxM&3O3!K58fj&W#T4m0j$4c_4nI6-W82u-6)Hi)TdX#^h<_+0 zr}x=tQ{CK$+ZxC|a<~~MJZI^vN;7LgzLwD+;OA9$sc7Kq%e9U>Mb2utYFvKcZ`=vo z8}SE6{Wa2*hKXmcq^ybGKe}crSMT-LrCqN^NMnDtfr`6_h6~4Urj3B%W!H6APh4e}YUQY>kc{K`F~R(Fkec05O)RM_ zFvRG^U08xa9$K7~zQH!}x@hWSWu$0Hnv>y>?sU!$wN-SjqN1HrS_CK?at0?V-o5p$ zW2qO%90g)OFi&lLJhcK{{_^5aFpq(ae6`nQbSS;EP^As-o=D&kM}IURs)M^E z9!b?#M)gRx1*fQ?S;pb8p~D=XJrMh0Mv|g|+D4S5XXb3xTl#IoZ zki>m;>)_rMWg+iFa`>%T(syU@%BxVxfPB6j={f9Lt||_?ptw~L?)1vWKz#BrH2ka) z(sX^?yky3dQkN^w8&0{3#eal=$r9n&cIWBmrh}zr{{R&w$yz!$rn;ic_X$M|(Jfr; z43aRzNwN)geI0c8yz5f6$a%Beka~n#Bla3<~I1=Ja~75QZT zAU_^;&;&(s>DkN=x%O@Y(@XyVX%sQm^fVRILZ0z&s8bu419G>_xbr%y+on$>6tQ1u z*;MN_bFSE<0EJ@gS71K6lTQ!$IU5kZ#@n$Z-LwwP$F6j+uIbhkvIuJDsHiM3WtVWq zIrIH>qMCy9Yn5lVvwtv;EKo=eNAmaQ?XR1JAm8pI&bl=FX_Q5GqmDFC5-o)1h8aBQ zAEzoQX>M@ITMIBOHab2EA77}|mtI`ty3ZX$h65Nyae|}J{j~zcZCj9JSK4Z*kZO*Bv?jSM= zdEpZr>e`0=M}HDBRK=MH_z?NFqsNP`&75sm#^|fdO=}TG*phLuFQb1DKTQx_I!4pg zHV@hNc$_+9qeUTBiM>ac=cVn2({z@D$BLBRM;NfEVPcn*$6}c{6vgr9$cM#;!f(B<}2s{ zyxaZu@kbM@G({Dq+uVWQ^U)=0#Wj5+K!}l3#84^@LXbGm;j8+4zfD052B*U^s_c1C zWZ-d;_St?2ZzRPK(M9VJ;w z%L1o3`yA?q_j{|3&m=S~u)q|s1#oyB^e=0?&r?Y~Leoj$MA(jOZ4c@-?3a3~xLlb0 zSCtBgoQ$wMd^p#s7Z|NAn)yaDEWx1-Pv{l$ z$NsHSc+Ujv2f>CaV{YsB(|W|#1?h0@?xh-^H2@Lflk_K$bK6-fB&AKBv+_K31(O_a zqK=EBYh|@0@<{1ZG2AKYH{%|p=ehf6R;pIF*3U^%0EOdXq$$YA^g3YK{h?AVzinMS zwSN^693zqjA z7* zapW~Y*A>+?70hZFcy7Cppu&JVkHh=vJULcRc2gJb`ZBy-Y&9tbvN>LiCKf36aetn` zaop*BSh7%6ymHgLOsq&UAybU^0P+U6bk)X+KBlIrxsCMEkKPl{C$anq(Qmbmp7*+V z?=V%BT1ZdAMnkVBApSb{Al$dv9Pzcr^e<~>rmc`kRZSFW1l6@uSZg5?db+Bo8h*(%;(ET$Q(qXP77{PNNI; zI?3WM`9Wgw&e&f+#lib&vDDQKAF+76Ni#8mH90)su>RUqN^uxITeRHkY7<}V^oW!f zVy-rfB>e(i4OaGenrP!$MM9?JVn6_LIT|XsTdKN*)qh+p)J;-VJX)$n zapOh@c0hi0`*JiZ(elGxZ%EOHyg~xJeq-yR>n&^9?vPc+U3@mf6*whN1b$k@k=(e< z`hN+0IF!6)*J$3i(%aYpRqxZ>Hj-8rVqE_J;@JRaV z&#x?hu`Xhwo>r;1)qfV2DZ=mpJga&Uol{!l70o3wE@faa=dz6JQqrW)oy^?4mp2{X zulpIj8CzLvp4nYoBUnr-h{#yG4T%& zjTZ;uU~t$zVE59a+F(<4fl(cE5E*{w9A{lIZ>y+y-mV*TlC_@kPmG?eUQ~sL(%kKwPaHZw2Aw|ag3fb zu77zIjsXV;oqstxOX27XRsbcP&m%v?b+Z$zXp>h#ohPy0n_G|VT3GKtz9v*LRDgsY zcp-C-$3&x|tORYE%{-iQx~5O;b+4p4mf`MIR7n(5K}L4PyM|nnF`rKQ+tyuAaIkds zP25sLQ4K!RByqPpNyd59({8GpGQF@Gflv~qOx#+qN9MNF7KuBz#M zlXN{3v(s3s;$|2nYh*lrCqhtvv^6^5OJtWGWH{&b)rBXw+eK|eFg2RFWtG(i#!@m* z=dQP2kQBGd-Gn_a(Q!2|zT6Zvk^(}=0RVrluI;@mXSW7nMI^Gh2P`=w(;6d^F&r^Q zum(Ye#((@{_0)_@v;(_SkjvYyLB zN$#&xB|aL0W^z0aQr<@(<5tJ*qA#XfbR5E3+nr4~Ro>$fvE+BpAAMW(H3ePIr|z_p zLReK($fu4^86T+D%O$6$sxBr5BBp7-#LUSi3~jImP)Cs4t4TUOk0H^jksBLV$3GDI zXMa<3i;-0pk;IBZl9G@acscUMtSr|Gn?=eSg|eYys(|Gi0f#-o2gvB2{neGW+x2}K z(c5H>8QwK7P6;3qIXaIYTRG-iJ)y_e3~8wnXG~U8^`kgdz1u>cFvE}gbsl?PTpD+O z+SHPT8QQZbE%Y2{Df&KClq{3gkllhIUw_nVWZfq!#2qB6MeH|9a&-3<-}pT@*ZLDZ zldfl{LnTV&frR(O$ohI|P1p6e8O z_s~^GvhJpqc9U%!#FE6VV&wjMJksy{7mIxvbcL>_)n66Hsom)rScuGXFL2%;!S+y>%%_TXyD(`l-LmX6tQj0%`397s0?3xnT2dcuyxmpW=% z`gkCMIyPyilg?5yyn2zJ%TjWF(=84PGE!FGui&@ntKH5^tpqj69A>3u0(D#*s3#d8 zO2G zT?e`}j|#-ms39@89ktNP+jbbu zW-3gw#*7G@w*=!nXgFUZio8Ou%%U=zil04gj`2+%NvH|59s)N%ZZ#!)vW~SwDj4Cp zblr+st7;)?IZL332Fn6S9QOP*cGr|O8_U!)vle1N8;Seq3!>`i?|-%hp;;794MRvt zJ4sw%YU-nBH1Rf4V@XYHo;(`2gy$PxZ~Ws=Nq^wGXQ|Qj z@y-&WwrJVB%m@1F-hY##ttiVqOYGEIT`q$;!~+e9f!oI z-*KYnFR-XYsutQ7EMlgIz)^)lsz@HB>LbxwmQ0#@5{HaBFMqy?;-_ag5vE&=DAqR{ zZ5)a2l`%kh1+oKw{M77fK(%G2)lo>%tpy-2!DRq(^g53fo_~&-m?b5;odDw)AF0tq zmikvy5s7vWga9}r%UDNeL;)a8iVp*Jj(`lsW}@a0jad~wDo8V6Dy8BR{q-VRp>MWh&NA)m^cupZ*Erkm^$NbCMBS01vOlqSBmdc= Cl!R6Q delta 8568 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 b#}W7!*w~X!nrse>00000NkvXXu0mjfFY0mS delta 957 zcmV;u148_j2ImKmIDZ3~Nkl4in>sMQ##jbWOrDU7w$bizpvbfEt zh{-aIB$^3i#BgRz^uPl%k{Km2Xjrn$iA$J~1&A6Nv%%0Aae6@Ax(U@G8$#u$0xMks zX=&;I@Ao|(VRqnXlkp_)*^}$Np8Mbi0NUzBA`t)p5y3t>%71lrck_72TyAROFLrft z>wZ560R8>_$i~lUVdCyx$%7vek>ppe_wc1_*Lts8ESEPdmS>14@4=ljXV3D3gM%y^ z|Do4xj?HFcRn^r35pnlCp2L%FcXP-2^DjteX39f08>W0d+}XXmk)1m?b)%zWU0GXu z=|FY$Fk@^k8-E)BvI^AH)X?1Q?CI_=dmEC$jZ!x7E!5-Vkj7$y319&Li{Y>+i^nS` zXJ_F*_1^RLQM)kE-+#NTy!=#UW#yv@0KoFfa$RCQQR;VjlSZ$XkDASF;m}hOcRB-; z!+}>;RyeOn;xA?86itP3@AokW z2?kyXl|B!}kE>89ijXA|h^@LXG*FF!D;0F}rUE4;<``ovkd2*_fZnW^8A1kN4Y*S} zfssNxK7ZD+m(BXCR~fKJzg{QUdaySKuf3p*zP(>{}^mZ|{<*tlXG z4!HxtbP&;G3>-@#7*b+#@0)LR<*0Otfk$a4jYTa5Ps0@O|7#K06 z(n)wcJ5f;Z8+1CK04SMW&(AZa9tPgX%FnvcInpU%j3pZnHGY3Wegeh`qXY~Y9y};b zh$k{gB>n+oYvgp+at9Bp(bQD(DFD>f?ab-EtX151tB(f&-=DsE%U2fK@y!mx=Sa1G za(|lIK5V55Q+)<#5{@0~p|P>sZv%i<`wmBxz3Q{~z-K`7v(1^>H)|!D{hBL8ROK9Y zc8PDUwauRPrmw=N~P&Ke!QI@9DF~+7~7r%Hplvdl?MPKV)Ik; zS9`j9sHLTa+S)#vAfl%Mps?_z&C7G<=QboTG&BSN2&@MFOKo9s;dQIk`Uer^1Hgp~ fegBu}mi&JKz6;)mnlEx+00000NkvXXu0mjfW6#a&