From 78f011e3fd1a23de13bf700c334fa98818fe16d0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 12 Oct 2017 16:49:35 +1100 Subject: [PATCH] Fix 16bit decode and add tests --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 43 +++++++++++-------- tests/ImageSharp.Tests/TestImages.cs | 4 +- tests/Images/Input/Bmp/test16-inverted.bmp | Bin 0 -> 16438 bytes tests/Images/Input/Bmp/test16.bmp | Bin 0 -> 16438 bytes 4 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 tests/Images/Input/Bmp/test16-inverted.bmp create mode 100644 tests/Images/Input/Bmp/test16.bmp diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 39bab442f7..04176e0333 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -12,22 +12,25 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Performs the bmp decoding operation. /// + /// + /// A useful decoding source example can be found at + /// internal sealed class BmpDecoderCore { /// /// The mask for the red part of the color for 16 bit rgb bitmaps. /// - private const int Rgb16RMask = 0x00007C00; + private const int Rgb16RMask = 0x7C00; /// /// The mask for the green part of the color for 16 bit rgb bitmaps. /// - private const int Rgb16GMask = 0x000003E0; + private const int Rgb16GMask = 0x3E0; /// /// The mask for the blue part of the color for 16 bit rgb bitmaps. /// - private const int Rgb16BMask = 0x0000001F; + private const int Rgb16BMask = 0x1F; /// /// RLE8 flag value that indicates following byte has special meaning @@ -233,6 +236,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp return padding; } + /// + /// Performs final shifting from a 5bit value to an 8bit one. + /// + /// The masked and shifted value + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetBytesFrom5BitValue(int value) + { + return (byte)((value << 3) | (value >> 2)); + } + /// /// Looks up color values and builds the image from de-compressed RLE8 data. /// Compresssed RLE8 stream is uncompressed by @@ -416,36 +430,31 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void ReadRgb16(PixelAccessor pixels, int width, int height, bool inverted) where TPixel : struct, IPixel { - // We divide here as we will store the colors in our floating point format. - const int ScaleR = 8; // 256/32 - const int ScaleG = 4; // 256/64 - const int ComponentCount = 2; - + int padding = CalculatePadding(width, 2); + int stride = (width * 2) + padding; var color = default(TPixel); var rgba = new Rgba32(0, 0, 0, 255); - using (var row = new PixelArea(width, ComponentOrder.Xyz)) + using (var buffer = new Buffer(stride)) { for (int y = 0; y < height; y++) { - row.Read(this.currentStream); - + this.currentStream.Read(buffer.Array, 0, stride); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); int offset = 0; for (int x = 0; x < width; x++) { - short temp = BitConverter.ToInt16(row.Bytes, offset); + short temp = BitConverter.ToInt16(buffer.Array, offset); - rgba.R = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); - rgba.G = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); - rgba.B = (byte)((temp & Rgb16BMask) * ScaleR); + rgba.R = GetBytesFrom5BitValue((temp & Rgb16RMask) >> 10); + rgba.G = GetBytesFrom5BitValue((temp & Rgb16GMask) >> 5); + rgba.B = GetBytesFrom5BitValue(temp & Rgb16BMask); color.PackFromRgba32(rgba); pixelRow[x] = color; - offset += ComponentCount; + offset += 2; } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8be892233f..9137049ee1 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -140,8 +140,10 @@ namespace SixLabors.ImageSharp.Tests public const string RLEInverted = "Bmp/RunLengthEncoded-inverted.bmp"; public const string Bit8 = "Bmp/test8.bmp"; public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; + public const string Bit16 = "Bmp/test16.bmp"; + public const string Bit16Inverted = "Bmp/test16-inverted.bmp"; - public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted }; + public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted, Bit16, Bit16Inverted }; } public static class Gif diff --git a/tests/Images/Input/Bmp/test16-inverted.bmp b/tests/Images/Input/Bmp/test16-inverted.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6ad83f8546a453712d28620a85fbe1fcd5efd5a7 GIT binary patch literal 16438 zcmdtpc{D5c|3B~`7zBf05X&GK1cP7@%ODs8gJ2NLAQ%MW%Dv`Zy6B#}v`zPPZ@Rln z+jLKRyQRCkv`zPPukP;BHr?~f)!*m-Ztgk1^F7yJ-;X>`Cd)a`KOXN_%sb10H{_M^ z)-QR^V~X>-=)4|uI2>*~aQ%5+*}pfgN0^86XxeLr-C1|NyUE?^Zg+<~Fb~Ru^DuarJ!~Eh4<65r?+$nf_@JlI3-T5LaXyK@DSqjK zEdShqg1}<1ET}TLCZt~26xtfr5#9~;MGQucM2(B4qGw~a#%|~F;`n7ipWr<3HsL|& zQxOl26PF=Y81sYj&e&=G01TCcmqF=UMh$F7{LF;Nldsj+?hE;0 zev}{QXYe!o+58-SWxR4e4pay#!78B~sutD6)g}@tb?Nn44Y`Vf#^R>3=1Q`rrM|VP ztyS64-rdpHIY^CkjdxG=%&NBbZg=O!JNpBI3&1;s&qJS!+~IgJjtodHK<|(~k9{t4 z$K&NVF`&4ByhHgs^|{KOj#uN%faU`B4(;>Y=cnht5&h=zZ%n`L7r+8sKns=xMuA1J zDggWuf5M;f*ZD8|ulTR|g8^s&8NdeU155$d0DAxwhy_xCT%aM)9B2!41orXz`2zqg z7zBre!;o4u5;vMSmcpcuXHDcz7HEp6%BCx4YS{YOrY)^=9op`#ecJ}-N4W9rQ#)oC zwk~e>;3e=Yoc#;IJB2SmUx+;51aSpIOD;t3l)iv{A@jf!$M%%bzk3i!{Ci0H;o&nZl1km>#f`QyhMH_FzD>x zC43S3QpAT7#g)jQWC^`X`XctFjE^VED~UnH5_y;MMe0ixpH5U)GJ~2W_Ac#<+?R{X zQrGO2x@Mo$HT$J!#<~A(D!;3J?^*VJF7{LF;PeY&AugnaOG2a2B3u;$p-3nZ%7p4d zmqS-V*FwQCG>i;m!}MXMFl(4S3<}4>scA54<3F5qwE#fnFB968CE2Ybmd%zmfH3 z?n=R-;=^S}Dv#E@RsVL=JFV7^ce~%~dw=kQkq^f|n)-Oww)KwXa|!bP-bNn{jRM5`ho8i^*NnP^?~a`Z~{S~M7g#*i^=j6TK`V~w%LK(SaX z70bmMV$HF(SV!!qywCWb178Tf1iun~4Xuj4iTgJ3yOi(Kf5`eV_osrj;$vmUD^Jv% ztpB;`m)2i9?A^ch{XY1|$e-hXP5nLl&sN8FZ(cIL8c+)^0q+sM?Cg8P$>M56Ex81} zNBT0hD)YvZ<<*2*aS3^k@?~mO60ig+flDwXm=kOXjs%{Y zo2R>vhd)2aGt?`>I~ss}#J-51M1cBB1F%3DSRParTpQ9LYz}P;>kRLK`Xh!SN24Z0 z)6rXEw#DuMcq#lEU_@{!c(3pk=o=9Lr-*Bi5y_?Kz0y~(Z)5Z zI&mq{m}p5{O$5Y z9Onjm#``4rCkBZ_lOmF%Q(#1#ilj->QHeAI%amo6qgC0pISskZ(zd+L{GNh-Y^ZRw zXrg#pwxwiS=?)(r!mkBJ1($*M315Z275O;(waBRCGW0&_tJt?P9~_a_5~GUC$orJ9 zQs1h4XhdDhjA|}p@6*1@ed{v+bJZW+RQ_|}>Dp=cvL)wFp_a}LOO|QWK-qSRWr40 z!))`Gwz*Di&({8JL-V8D#P;bOTNbu0?(pNK@#_Faa0U2)@O9{Wksq8Uu5{8oJ#T|w7SFdNM#v)OEYwkg}1ZO?{supBCf%Q57bb8I<|ob%ky z_q@R8LjR?pi$X7sxFq^g_%iY3$Q2SDx>LFf+b!Euer45FwO2Rjo3ClRw)484>-%pQ zx^eWT3B&ZwTW;BQ>ka`gonH@(3v}Rv!Z)BFL;^TnT#t-9`wvRrz& z4=UfFeozVMbag#5uFpUXGo zoAYh?j{Mu)Zuh*y=T85-f{dYeN8A&AFMOZ)e&hkkgXpq!FSbv%zx+VeL$wb#Jko4x zd$jYhp2z#27;3|`056~mmI{mo zmV(s+07EbWV=x`I?0jdvhJl4>Az8>4>I+SU)JFe%st?iH>;KZye146y>4lh>VaiGM#K$ zw&Fauf+c7PS;Cg+OH3u!5_<_$ij`8OT&bbdTxu(Ilz!^=ndj#|U-*9+^i}BB5v$SP zz~73$L%x^%fc_}`30spLD?eU!qV{CN&&|KI{n}~o`K|x=p+83docL?{?=AmqbL(~8da2Z}kmo1eU%PeK9Wk5MnPLwm{y7J}nmGZT65JzzmXK_7l z!mYR+hbpiNs)DO9RG2Gl6^;s?yPKE0uSWns*fY#4(mMu-_et_i^~(@s`{xA|29|($ zkUW?OQ3%OUWf&E%f(9anBgdj9MKjTJG4rttATNvG1WXC`fcu4qp<^Nt&Js5vQ<6RC ze(7QCm<+_T5bl~_+%+NinQ`ua zo67HM4?4>p>|#H)4o-h1T!~lGl}nYzN=xNxB~XP_5mii;u4=hzrE0ATl%sM|&dT+2 zliVt|JAdNEs;O$O+E8t-wpBZ-wzZsYoTnQc!9)XUFf?!nKj7&?e zL=Q-hV8>-a&VDm7t+P{V!cFBrcm2A*2Cl(t z=$fS(V~wR|wFam~YKdB=R#&@RyHdMW3lbv zvU{dyPBq`V5X{TwlfaDND)1rUQRsvy7|s@x$c*GF^dad{?1U^B&vy1_6jzZCDUVVo zRKaw%nq+1)SFsOik8&qY&wnHO&Ewyge%)UW*W>kc{ZhTL-cr9>4>TYRL<7^HYgler zX;^Cj6{vz#unN7xq_8UN3aAllq#C(KL!-IT*63*LbMN;W@TCI=gNMR~Bh@h@@uNv& zsZ7Rr_C(%fp{8UCpO()MtYVhjqMW0&s;vXthUdq)$?Y>c<`(7`7eaVB{1$)}Tn#=f zdQ8hu#$7Isn=g6GIvoc*iGhm~(pCsiSIj=F_mHCM9_Yv1BduFrp? z`px0rxPIN=1UKPLbkkCkvB}c3+5|Ks%|tWPtZQCwUTI!y21%48NtV=;CeljUNvH*D zp<1{WLyNh^*5YV6&;5L_3w$pOSPH%8K1JM(rG?k?GbUn#$e zxLTnnuTfr0U8lN!;D+HF$8MT5%-lS8%lxeiLS8Pv6_^$1!AFE|LqCgzaIUx&nU(0# zN2G6KKg)!8uDq3)b@m@ozD@nC64JTqR%TYCXCKkN&HcQHI`^kDlPz~mj@&i5@-ySy z|2CE1)t-HpJ;%j!rCXO;jjfi})mESlX(QU0HeK6t+e+J78>mE;q>@$Y zl_sTCX;(t+SUc6uwHw;a?Y4GD`)%&Gd)?uCXTV*-#<061?}@oL{=TI9Qy<89Fnc*~ zZ{fa@{rCa-L&U?1M@Ws_zv`o zC=`~8+mJ1iYfzK)9qbobC@z(^5nB}3IQ#EVzo~ahdZO~? zfA0Eqe+S%wchDV69mWnz$7%=AiF6X3OsB4MxpSp+trMhBilkUdPnjqyWv8Gntc&X6 zx(r?BE?bwQ>!AB{UeEi!5b$E~OJSDCmt$Uue>Lf~)YmiK$bK_#rSMS6Vf={vDDjr! zZSozZm3mk8-oX3AAB=rC`O(bBbGG?U7Q%RWd?hd^xE6d=XoY?ig~54ZB{CDT?;a5vsfcQ17t zyDil;3i~>8HRhZ6Z>u-fDqJf$h98%oAWkZNCVx@>O4(Jv4g5a* z$Jn2ff6e?o_s_gzA)J@bZwIu3>%hl^??QG_IGitTN3@db(8r|jVs=?Lo-c1Fw2JG< z$CU3n`{8uHx}DK#u45n5zRTIy=f6??=J0P^zwYmY`|v)xZ>i7NXX#t*1NxDEqMzy4 z^)L6Y^sn`U1Ly!bzz*mKOas;d`v62^G(~f?fi}}N+ClR?+`QfWJOcS4p5b0m-myS} zPqJ^CU#1|(KR=)-uoSEast&FTX%x1Ewug0v_d;}pI+BUfh}dXt3>Uiy@e24Iz*fQa z;N!ygpx;CgTp;d1wo0x?AD6y|{U(F(0(l3qRdGG}xbi*fHx)z|s5_Xgn(NueweNAi zEt1YpCugRGa7`=W8t3MAMx6WKrt-VmTh6k#o+1Bt6`cM-cn}|?2bTtogO1j#J}LYF`a={67m6ul zUUDP)r1S&q4_PE$D5r>d#f{{X$`7bNRFQO{vp=u7k$qD80r$u0`ENwOdHfsGulvX0 zaeSN}Um7=#TgF$%feB=Sm|!M!6U!4T6KfOTBsxh>vXlBr)1-CMJ_%_s4W;2U28~%` z(>OGJ9{t_}esthq$WZujlsa}KVKjLxjmaF(naH0k(v(hBOjpm;v5m7WTiWNkw7pyD zZR&Z3(`;vVXcxG}#VB48zYE|5H-S$HKZO1iMZrblE`*cZggzzx5c^XWg%`=Y2u^Vm z`IPcQ>Q7Y^U8L@E_HSaJ(tgPOxjz4m>NkgfJ%`IOcT@0 zv~GHNdS!ZT8k|9A$QgDT>@S%JFP3)`+Z6`#Y2`=MUn&t@tnOyE zJNr*-KjQvcWS!qlotasMYqls{Gp9H+&h!8Oc=>PgyV_^ZvTt#*pIV21@s3mfD9Upz zp348bJ$3KY^XMEo$Ij{JOmo&b`y8ajw3L?98nkAuP3zF!=5f3C9e#HP-W6gDzdP!l z*n1Q1OTIttfy@VUmh<-(?JM11aiIF4x`!JdX)(1w+VxoPq)IH1& z&CSmK$K2m8^FLR;_ALJ??Z21*tjMWH|1JJ=*PreGkNV%&!Rh(Wlk@Dne%>^1owv_J z9L7-`#~C;?XX6~)L67IWpZ9wq@Wqgq!YxrR$G(#AYVvDouV=oI^Je}^(V^1A6-TO% z*1gsEcFQ~M)~p%PN{_Mx-0=d90=od^2)&=_lw1_QIi`=4N(Y$DLo-aQ2_{{rrzb^v64EZYj z>!{V(ZxX&u{x0qN%pY=o%>Suqt@K#M@#+(GCmVlm`KA5WE_?59^zZ6Fm_Idtv43m- z;T(&xyi$HIuqe0{d{+1gQF1H#tn?GiA&bRJ<-NqB;#Trm^#ag&S#up9?qkAI^HsGmAB3V+z>ayjd9bvt+=hZ+1x;P z)SYx^-3{(0cZ<8-9rD0DC=brV=wbG-dN@3sKb`LmcnJ8Qr_c-X76EZSiM}a*>4Gf( z+<=0>Vz4ZzGPowBUf2}c8rBis4fRC~Mvg>{i>9JyW46Wa;PK-4Wk8?c9Pk$5Gteg@ z9vmkwL;57=ptne$!9J1k@HlxH(Wf|vyhZs8^@)l{$EnMhKFvAoE!tzyV`f1Vc+dyKe-N0J|E`ee44N0FY{OV>wLfy@gzJMPrc`g=bER@ z6ZAs8NH5mQ;AQf%c-fs5W8RcE=WX;hdt1F7-f=vb9}gr55<#&r2}%~F#36~PDQW5H zS(4n0g3RKqGPE+gCZ|5PN!ps%k>6d=hYc2v6pa^8$!1HomF{qJ&X)uIf^)%Jh0j8t ziripWT#och&P8vPK8t-SbHibIInl2;m%LT^EcL0%jfU0bOuyz__Ezn)+@~)6pRIoH zmhzvye!~yI01nWA4p;_Ofpq}zL3{`w#z*h7;rZ&SP)WKF57_s{hUCw~l{v`VGGT7T^L}pc5<$Rt4(< zz#s7^{270}|BC;bzs(;EKm*7CHoy>I3a|v&1E4@GkP74ije+JsYoH^rkJry10BFG= zI3yf~)S{8N(ZsP7CVf0>B6qStQ#@5RT{%<3*3UN0wa#~FySMdiA6yvW#&=BZoL$_u zw8Mj!z^`!5pAX(Hd>;B-8jSNJFe-zw(q*Z>ql-FH%{F+d(*a?ckp?M{7PWZIe&-n z1?USAA5IikB7+hgdWZA{>Xr4^}b z_DNl{U+S6z($nMI|F)Fh)xPfx`+gVu$#ro0g|HA8(n6hZS-2`(7XqP3C=tqp>O)sT z*FtTfU>F)khOuFWFjJT%%pL}XW8qXd7j6tUhg-uP;kWQ^<=+O}F1Q0+7TyWnCAvHA zp2T}o?n}QvYbAGI!T#a{Wd|!CsClsdp(a!7!yS)wKic=$;Nv4tj6XSLo_%WD(>pwQ zVty4cB)Gsi|049I$P*TetB@hd1?aN$MeIwNCoYy(5kra#$Yte=)R!txTCA>OhBOzj z%i0&YFJ1aSTm8W;ktruL=X{7gg#;=VlBcJ0Y;*cWF#AD zh%`l7BJGh-6c$BAaZ$!7bCfm85%mo3S^jgt^MV(^7lo_POQM(KUP*j4<+b$Jv);&E zD>zhqxa>&f(V92w-)efh)za}!_q%=X4Zc6}!T5($AI(~~eZ0eqm&BI?!-5OJJDu}i ziM-$>u^bteT!`K&UB$kVdErTNIWerbki1j5N`0mBqLb8eW>|9}d#84l`|4Exo6&C_ z|K{`?ei1CfMYKpKS{AK})=(T8DG#G=%kTGnGA;uJAiLu8(u~;k> z%f%XF&9T;4N9-rOPx+q#p9{VKzZ8B2t&6^n`zG<*l<(5N&-x+v#{ygNv9jZpCu)AG z|GDXx)?Yj9-M{tyKKRGTpW}Z`{XP57HpdQcUNXNLPzx>s?-IV`ocD&4#np&fauIr$ z^d)Rv=8Y%Is|mH@BJwWfOVql`n@(0&GiuF6>|NTIxb==+o{>|$*{Bdv`9!JON z;+Er9C4#HG5}AJ*AOF$i^;o{FH>Ku z06Ilo!;EMyX7AR%%zeGIhPmbt=98l_F7}h_;PfZLiFhKN zs7qWwu!+cG>J@NlMG3wBukRr`4S&Xrjog2W3oBfn(Ro9 zbAvtOeG>c=gT$dp5y{ahFd|Mx(j@7qM4EwR%CgGQs_fdFhTLXpTV7{=PeDI6R5)5R zQ9Lc1E7@MU(}#!fYk^V0CEz{6SD>HU6j>v0?QN<Ul~*tDvzj+u7PXC z1X3rdM;oLHtWnlf-dsi2wluUhx3wud+j~0tJBO&zu8HpHo;lU_-krX@R6YTW2`&Zi z6}}36EAoX?o%3UoOVN9!uVUZIeDPE{L5wLbCGS2M={>|w({ONEyo=&Ih(wEa$)7R4h2_hjRj6^S4k*rB<5-)$@KFv?BrnBFw+!_y)fdU=!@Y?#FrwMN%ZJ0>27R~Y;XDHRaewr*KoDwEgCt0$w`59vBzs!TW`;L*I)8aJsl28F$X#FMS>RUM9fP<@Ln4LQmeW ze4YAUC7{#Q^~|_N&)%tCUea(nQM;ATysKpdYt>;mh!vWkDXyZ?qWZ=4o-hA zoQvnuxw_ot+|}ImTtJFQ2`MAhJKtAXliH+U9-2qyv3Z6(Q=TQyo(JV)`BXlaZ_GF6 zTk{?Hx47Nvd7IDe{&xf|hu#@+SM=TRJ>q+j`y}_HE7E<~e%XQYgH;dIKG^V3v#IUj z&PRG4?SE|O@zEzHo}4z%J+=Mmo&G!tzX6yK>;hMWZ$LkY{9%c>0hy5Oa?Zbj{UGzl zCGrMhLa~cnQNBU_pz^0B>IP;)vx{BPzQO(A(*N1&f7(+1v)6C<3*Z90fG*G#EElX6 ztQP9G~(U)DAZEJO>*LblLQXezW6+6$o~tcWV&ii}0(B5RSO=oz{zq8=o*`EdlZxHsKIIzq zqbh*TP%D^8&2Dy|c8&Y-RR5dNZyo>U^c((SxEL>{i*?1z#jC~Z#efWv5i&-mm#xUw zoYz*c1T7&;*b+mDsl-xZFM&$2QmT|IHI|x7t)-6APuxEB{LJTb|1W~R4E-u%J^E|- z8}YZucarbXAEZBGHrcWA<5eeWe`@%-`Ioj|JMBHc_5VKf$LOCEe@*{A_s@36&Olx! zzY)*~_JI3^haj6M5Y7}gA{xmabieeFb3PEyls6I@#U66M@(^WH1=5-7Mnl`WU8maUfoO za61lFU=>saS7EF$S6C|?6+Cx0FLz&$0DiD%m{+8C3=r>=e2?2@0;;^L1gV)eC zx|-#h)tdDhpcbhmYMEMn?Mm%ht*sU$P=X{_!a$e^3t=aqI;@VWd5Xfuk|w-aP7*DOR*;!3{c`a28BssQP>qwBi2YYa*f7DbECD<(b(tS?=|2{2Mh)eg$+llV@Bdflg3h+ zjPdM=yvagM$rL^ z`biW5=ZISnR&pi!p!7}bCs_!dBX4oeUr9cwe3SY~6+-8zTNqYzCHtWEP41_S{x_@N z8vf1eH~dX-6W&BO>6(_CR-4wFfM%qbXl9!A%`44o&9-KcL`jllNdsvjEu@`y$ca@XX@U6U(6Jx*=N{uT>Tk%%9RoA-Qy4t$l3bY|@L>tqlZ(C_wYqPb1N>oWIS*1Z~ zQd*RDCDe|!Q|(;4vEAHmZFjWa;(n{wZN9e$+!4GSc4y>WF?Yw`lXP$DeHr&>ujK73 z++T74KPZ2Icu?^WX;MB+J)(Ma;IZMy$DWvca>hLW)WXw?p*$(S4VV*L1)79!L%)bZ zVX3$cnUh?Fnxt=IzsN#ysl1JtQ(Wbof1CP66-rChZOokJD%PZZoBPG3|FhLgx0L_v z^&9>UxC8H?J9Hh(9jhJd9Y812Npv!u`p%WkwN6_nNTC!-v6O)_Q5MQhL0woE)x~ug zyUbnIE=Sih?$3HX=lguX3&Agjtwz2S^K$$vNw229mhpP_8+mJmhe{6PN90F|Hx+M@ zZ!0a-JF0gF-Wz^@?1RY?X?fqB8z;KM=-^s6Wg&J!zQ8Q*9BkoRMut>hSfTz-Q1N%1rJi}F{>uKI1@_u)Ur z{+#@4=I{A`795M=ynKE;pcPyLJ|cVvvWvpud~rLXm0W{9B7Fz5%fj(|c{`z1Tthyh ze8)K-PUoxJ8Lj3T_7UwnoPDGJ&FZ&?fAjhce;?e3_tAa2zU98vzV$w!AL%FhnSOo$ zO8;8Ftsfjf2gm_-z%XDMungD-AR417nxl=hnYPjnn&;u>?e6Ch$Pe)h_lok41rmIc zebf9h1v&ot0Y!nOU`0@Ma9v2FuqCuTtSh`1q9fFiOq52%Mr&iZ*d>Tp!0!OI39bbn z6}}7oCW7DsaR;(ZaxMC(^j+*X8H5+eJBV$HYsp8I?^3_1Ai6-^!EDo9%RZ`om-}sr zbiO({JuQT5S_#)UH@DN`-2b+e-__o7hQ0MP`M;~+^bf*=_#i!~8(bb-9b6v-hL9m* zh#ArktqiRV*@nPjbeJ4whYiD~Vau?67*bBjEOsu?UL)z$E5FJzsn+UOx{UsS6oLvrhJe3T@~S+?_{=Xu45n5 zzQ_IU(*N1&mBd!^pS^y=KLU^7BlL)FWO-zDWPJn}MMjBHW>i1AGP*Wu8wJPEF>;I@ zGmM$WEMxXDh`|_&;TR)hW~_{ZDf1}z#{DV+D?_TnDeNIEZqNuU7 zsiL`>tZQj(ZE0&)cD46*(4A_E>C$wwJz5plyA;VQ(R%h?_+<+BJn~wMJy<;Cm&b7PyL~aqzj$%3!3ZM$F=Wsf1K)nGy1LL-<*EKKMs%M zW z$`7bNRZ(=2y309#1N(&b1Mbg_{x_@N8vf1eH~drZ6h1{y>86&aR;SjdfN5l!m}aK+ z(<{?!)3#}F2Av^i*cro&X~r^RpMhA6rC5$NvS!xGI@ohO&hE4RVtFNfLveD3TRr}Rl*YsXXU#GsFxj|!OZ`9tz-Ml2? z74y4+9Refxr0_%NFOdi?7Iz~%Bu4Z}>4(@~G7(-Z?Ido`Y<#H2)c$bSBfXE(kEtJLp3pqWnzc`HPcKFD zWc(grr{G4=Ec^)iTNDk;#68GP$&IL4`VscGEE<=|dx)Ki8%eYBBkFHeG%Zv2FgrCj zI_E#){&wmAZ1vhR{3o^l9{*X9laKye{AaH})Bhj!zt4kH{V$LU?1Evzv|w4VFF+i| zQ5?q^IWuSF9NaS=&w4-S_k7?BAuoonM!gjKa>6Ugucp10`FhS9`D;aoN)J~YsXkiw zX5(8eZ?{{z-sydpeoy^A^MU3=_9Ly8`*R_Y&B3|*pDF^ig;*r%NH|D5W7Gx{_Ar+0ty{{Mac{}=c- zr~lV~=I{NPkI_YPkzF(_niegK_C;t3TcVb@CF7EL$?CjbeB$w`_h){e2YwOqW%yT7 z>#<)ae3Sfb+IN}X=lqcWW09@&SjF+`6Lmi|{@n6Q`>$R0-rwlo)qgO5YW`yX*8ame zmSTCO{9a&5a5MO{@MFj!iiJzXy~vW}X7p+4$CyJFi-t3 jOPZV6r?nqD=TBbyH>>|o|Nr*>|2zM0_itW*(*J(|%(#k? literal 0 HcmV?d00001