From cd34ca9a032e7cac8cfa42077063dd8c9795c34b Mon Sep 17 00:00:00 2001 From: Rick Companje Date: Sun, 27 Apr 2014 20:12:44 +0200 Subject: [PATCH] added the scan&trace experiments --- js/ContourFinder.js | 222 ++++++++++++++++++++++++++++++++++++ js/Scan.js | 192 +++++++++++++++++++++++++++++++ js/buttonbehaviors.js | 9 +- js/main.js | 6 +- less/buttons.less | 2 +- less/popup.less | 19 +++ www/img/buttons/btnScan.png | Bin 0 -> 12263 bytes www/index.html | 9 ++ 8 files changed, 455 insertions(+), 4 deletions(-) create mode 100644 js/ContourFinder.js create mode 100644 js/Scan.js create mode 100644 www/img/buttons/btnScan.png diff --git a/js/ContourFinder.js b/js/ContourFinder.js new file mode 100644 index 0000000..345f978 --- /dev/null +++ b/js/ContourFinder.js @@ -0,0 +1,222 @@ +function ContourFinder() { + + this.pixelsWidth; // pixels width + this.pixelsHeight; // pixels height + this.pixels; // pixels (single array of r,g,b,a values of image) + this.fColor; // foreground color + this.bColor; // background color + this.threshold; + this.maxContourPoints = 500*10; //was 500*4 + this.allpoints = []; + + this.findContours = function(image,foregroundColor,backgroundColor,threshold) { + var w = this.pixelsWidth = image.width; + var h = this.pixelsHeight = image.height; + this.fColor = foregroundColor; + this.bColor = backgroundColor; + this.threshold = threshold; + + // create a new pixel array + var imageCtx = image.getContext('2d'); + + + alert(imageCtx); + + var imageData = imageCtx.getImageData(0,0,w, h); + //console.log("imageData: ",imageData); + var pixels = this.pixels = imageData.data; + //console.log("pixels: ",pixels); + var prevValue = 0; + + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + var index = y*w*4+x*4; //(y*w + x)*4; + + /*r = pixels[index+0]; + g = pixels[index+1]; + b = pixels[index+2]; + a = pixels[index+3];*/ + var factor = ((pixels[index] *.3 + pixels[index+1]*.59 + pixels[index+2]*.11) ) + //console.log(index+": "+r+" "+" "+g+" "+b+" "+a); + + //var value = g; + var value = (factor > threshold)? 255 : 0; // threshold + + //console.log(" > "+value); + + pixels[index+0] = value; + pixels[index+1] = value; + pixels[index+2] = value; + //pixels[index+3] = value; + } + } + + //console.log("pixels: ",pixels); + + // copy the image data back onto the canvas + //imageCtx.putImageData(imageData, 0, 0); // at coords 0,0 + //return; + + var counter = 0; + + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + var index = y*w*4+x*4; + + r = pixels[index+0]; + g = pixels[index+1]; + b = pixels[index+2]; + a = pixels[index+3]; + + var value = g; + value = (value > threshold)? 255 : 0; + // if we enter a foreGround color and red isn't 0 (already stored as contour) + if(prevValue == backgroundColor && value == foregroundColor && r != 0) { + var points = this.followContour([x,y]); + this.allpoints.push(points); + counter++; + } + + //r = 255; + pixels[index+0] = r; + pixels[index+1] = g; + pixels[index+2] = b; + pixels[index+3] = a; + prevValue = value; + } + } + + // console.log("counter: " +counter); + + // console.log(this.getPoints(points)); + // console.log("======================================"); + + + /*for (var i = 0, n = pixels.length; i < n; i += 4) { + var grayscale = pixels[i ] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11; + + alpha = pixels[i+3]; + //console.log("alpha: ",alpha); + var value = (alpha > threshold)? 255 : 0; + //console.log("value: ",value); + /*pixels[i ] = value; // red + pixels[i+1] = value; // green + pixels[i+2] = value; // blue + pixels[i+3] = value; // alpha*/ + /* + if(alpha > threshold) { + pixels[i ] = 255; // red + } + }*/ + + /*for (var y = 0; y < height; y++) { + inpos = y * width * 4; // *4 for 4 ints per pixel + outpos = inpos + w2 * 4 + for (var x = 0; x < w2; x++) { + r = imageData.data[inpos++] / 3; // less red + g = imageData.data[inpos++] / 3; // less green + b = imageData.data[inpos++] * 5; // MORE BLUE + a = imageData.data[inpos++]; // same alpha + + b = Math.min(255, b); // clamp to [0..255] + + imageData.data[outpos++] = r; + imageData.data[outpos++] = g; + imageData.data[outpos++] = b; + imageData.data[outpos++] = a; + } + }*/ + + // copy the image data back onto the canvas + imageCtx.putImageData(imageData, 0, 0); // at coords 0,0 + } + this.followContour = function(startPoint) { + //console.log("followContour @",startPoint); + points = []; // start new contour + points.push(startPoint); + var w = this.pixelsWidth; + var h = this.pixelsHeight; + + //console.log("w :",w," h: ",h); + + var point = startPoint; + var numPoints = 0; + + // define neighborhood (array of 4, with: + // x offset, y offset, index offset + var neighborhood = [ + [1,0,1,3], // east + [0,1,w,0], // south + [-1,0,-1,1], // west + [0,-1,-w,2] // north + ]; + + var prevIndex; + var nextNeighbor = 0; // starting point for neighborhood search (index for neighborhood array) + do { + //console.log(" point: ",point[0],point[1]); + var x = point[0]; + var y = point[1]; + + + // go clockwise trough neighbors (starting at east side) + var index = y*w*4+x*4; + this.pixels[index] = 0; // r + this.pixels[index+2] = 0; // b + var newPoint; + //console.log(" index: ",index); + var i = nextNeighbor; + //console.log(" nextNeighbor: ",nextNeighbor); + for(var j=0;j "; + } + return log; + } +} diff --git a/js/Scan.js b/js/Scan.js new file mode 100644 index 0000000..2207724 --- /dev/null +++ b/js/Scan.js @@ -0,0 +1,192 @@ +var scanPopup; +var canvasScan,canvasScanCtx; +var contourFinder; + +function initScan() { + // $("body").append(''); + // $("#svgfont").load("img/font.svg?"); + + scanPopup = new Popup($("#popupScan"),$("#popupMask")); + $("#btnScanOk").on("onButtonClick",scanPopup.commit); + // $("#btnWordArtCancel").on("onButtonClick",wordArtPopup.cancel); + $("#scanPopup").bind("onPopupCancel", onScanCancel); + $("#scanPopup").bind("onPopupCommit", onScanOk); + + contourFinder = new ContourFinder(); + + canvasScan = $("#canvasScan"); + canvasScanCtx = canvasScan[0].getContext('2d'); + +} + +function showScanDialog() { + buttonGroupAdd.hide(); + scanPopup.open(); + // console.log("canvas: ", canvasScanCtx); + // alert("hoi"); + loadImage(); + + // $("#txtWordArt").focus(); + // $("#txtWordArt").val(""); //clear textbox +} + +function loadImage() { + var img = new Image(); + img.src = "/doodle3d-contours/images/foto-(7).png"; + img.onload = function() { + // alert("hoi"); + canvasScanCtx.drawImage(img, 0, 0); + + var foregroudColor = 255; + var backgroundColor = 0; + //var threshold = 160; //doodle3D logo + var threshold = 62; //arcade + + contourFinder.findContours(canvasScan[0],foregroudColor,backgroundColor,threshold); + + for (var i=0; i{3E;H!w==h_*D5Ii`3L@?lSxZ@EE0m=a zENy`TrF5aC%l+^B-JIm)<|a+jGz}#mPo9J{xk>JM@B6*qa?UX+ib4Skn77On093#N zfC^XuPyq`7>VmNbg_Ix@p9?-2Ce`@)h6V)>#s32q092z^Q?Do|&ngt%P(cOd^%SGt zw~dRlJce~oNT8sgpa7@>fO2eLquSb9I(qCF{m&O!R9U!>Lb?}H-H`-}O6W^9rXTq+ zZ1z33^F4qT5fMQlAtC0o7sPj9w*Zt{5ug;!sIIP#|6g5Q&G#65JjT6jc`J)r-2O1yRplf8hV$cpBPdvT5ffbvr|s;H>2xGLDeqE%FKppHQTaJXt6+H2rQ&**RX_oUPgbU62< z2B!Kd29yAt9$c_qDI?eoCZ#6QpbkU$xlRf3G@yMtg$IXUP@wW_KnZOm18KwROgolw z6+p5b%=t&7@0XXc1PPDbNLOWLE7`}Ay_%yoqs$tYNrYlVu+gW9R zHZ3GmG+=$da3In%zXX)hFqZ}Q zqc6N>(MXQ85g6d^d&kj(H;?i&t*c1iM^WA5dis1(9>sM^pt9qIHowWDyHxksE4?pW zJz})Q6$+hY)>?iKGQR^9z;vXrP!rr+Keb^cz5AFv@nY)St*hC+`S-D@;sHVntR-vUqG2FwM@RuTr#+1cQ}0Xa2w|GH`ekSK*?Z2Qc^^3d}3Ac zddCe@Ew6=6Y9AX$breM5)`a1C3#@vjo<3O{O*N%^=-vNYYZtj1F_*}3`5SBL0o5X< z7d79iKIR)V?7}2{# zu-2U;@8yz@$@HMqU3lk>NPxWG3UtP(+2W*vR&L zzwbbSA$y5)C)W~h8Z(o+MI`#n4Yn9VK{p*eR!DPRxnBiSg-tXU0FzlGs@m$hiE-+q zINLu=)R;#PDvh2#wVe;@vlZQgu^BMLFH}$H(}`%O?etj*K@2`qP~9 z0CI2XgX1_WFY1VT5nsgmeG%miZJ?BiK@6<8DBpflp8yKF>6iCiOL;l@hN81`$B+PK zTmaQoqZ8FzccRCq(*w^hp)DIKSUQMADQhqFg8eH0J?dYSOnn>1&}F0lsvwW$E4FHO z4Fy-XEORye;ojdE7I&?M-2g+m8y(JHdWdxvG#13XoO+P;RnGwv=!sdADK1iVrKSf= z-i5g@HI(Y=jualD1&cqj6O%BlCKmvcr$aewTCzoBMZ}!3sTa!n>R{dmo`mt@?E5Jq z;$;Qx%FV@A%?c=yB0wfwpHD&2^p+_@0$}nIxU`^#=<}~P+vrpQEUIWnzbslszaD=J zCq|n~TZJ^w&wEWdckUdm+>pijCY!PfYICY}=o-c4lz@33RP)|gX;Xm4J=hDG_XTHu zppO08(d41c!&F`e6cpLH`WpIZ;X-=-qq&?S(AyoqxrAm;P2}2KZ~zI-3Mzdh2>a)Y zswyc zhVemX?78}v6u$;kjml0hFMZCYbys$GvmYI2?ywA4OM(jh`6$lURMwC;5f$RSpYWOj zFoB)+Z9HI?A^@1Ky7D5LJSvdH^d+d!j=Uu&nnk(n{)DbwxnV2ks^X%a_R64mEe{1i zeYfo^dVAg%mPwa#;~1EUlX3wtwMf3|Xi+7t+j88l9T`3DOFsS&F9>1^a=)mo&qI05 z8Z%5P`W z+<$#dUo6{Bqeu3leto*yyl(z~*4e%HSGT-MKYsTVb?gx1@c4%dPEgi*Ywfd7N@p6- zzq?mmCOig$q_b7AbfzkqBS19oA((>eOBs+(0}@&QP++IBs?&B0eSw|2bc>?aXj$2N z`5uE@a-y8U^!=BQ+r94ff6lV`|0}O9qdCtlp^}m_P60J##*55ovf(qHpL2tkU8c#> z9Xfxhl?;$YU%TonN}TeH5)%{ScDqYRQ`u?pr%QM>IN7kik|*qR;$%5xZ`@Bs#ig`! z*U#4D^@yj`e%)x~@IFol!xskTxn@eb8C(Zx)~#b)df%|2+Da@C^Zip3oVrJQ5Fcch zx$LutXxwE341)w=&W`QKFlDWC$6M*`x4xqlKWw7s<~&W?cIML?&;5nmUH>?wsqD11 zBAW%B5aUK`F*Rl@SMR2`=V!9}Xw2sPWjft{>sYsd1)zc%Dl4lkE(u zN0*KRcLcjAJ(&*we8S8g{akwQz%)&(6c(Lw`Fp?p#vT0n!#|fWkf&VtPdb!4*XFki z=Ch3|)9J2R6AZ!UXf!pgJDLBNd*C>e1lF5ySD=S9m7P##krx2Ns&KBZ#e|6Ig;&0y zxvzh3c`iAro#9n#0hD_3-v3|~?cDsb*MJI_a{Y`EbnP|6odQ*`CfHJ>4WMy^ZQhbc z?|-=3<~?oOM$zntudy5#;BeJ${MBgbJNBcy?tPaTe9DzWtsplz9)7RTcCWpBo6D>P zQ0>hhz<>ftK%ieS_GKkB>=9G50}6>uuoITuky8b5cdpu-^M0@alT7)t(3M#LA-FTZ z2_|{`sf8{vo^UC+J@n{8%GvsNhm4iGw}|ft4eUYh%wNHed-m*P`8(W_k=^vGF+8Yn zk6;?TzH&8}1wbuaw9W=lxM<{y5TCJkf3f8^HRfXVhWcN1c`UaSV_Bsksq-l@v6&eP z?(Sr5fyPc!W*i|XQU$Ii2lZ5qlX2bj5j1>Qio>V|qB!3mf|5`0eGkpI0Ub{GefL~J zlP9Gcf(75@BXytvZbVp)0QJ*$O}ygoOO|fp;|k^h+X{E{;r#n3I{r?(_d^Q`z`N>Z zh2ebQ*tkgG!o}y}cjCO^oZw!7b>X5c2B0USi`g|Bvuf1Hy@0-1vx;V2?>b9)l!rn= z`J;6UESXke86#ZRPZ&UXz!8SyWWevsV8USIl2>l zf8T$ZzT5hZ%Viml7^57^N%^vL6LK4IE+)n1wV zr45)M;2(ZqicBjQUHi@HTJP} zdxHn{;NL9(>kZRZXPdVai0)hQ_h?!uvr{T6dKcq^>*nP8#m|K zL?n7J;fg~k3SE8#f6oUSqyC>NI^!b;USOyv{76StpA+BnG6=K_wTNEIG z2p5r@EXd{Gq&FN}00MR}|CrOXgfoN21fYJ%+-c{|I(LfmYU0zw znAj&xUI&!eRL91mOX;$ULtQ#Xilj4^eY3?TJcd31egE^t8(jtyOm?AMw2z&dMOd0!&Lj`IE_FxzOJOk+B(Jqa|4pPc5Z`L6w=&5zIc=bCq=TNqRA_e$EF$V) z7xBq^Y z%_XA5?i~!1rt){zyTPVdhPk+X>_nYq4R&t;id%iL2a63u1vhQ&a9p);a{!jO@-PT! zwZ+v4mO(iKj0JK0=o25=yAT4t211aJxY#z|mbr2l-{bqE zHwwQ6odN>bFu5HE?g__fjBSnV`^+7Nx=Exan#T=Vz1nAA=2NiAW&r`uH5A$zP!K+M zrn61SMN88+eO3#A@yt`dqKcAtcyPc9xEcj0kf;=4x4xZLi1F9o{MMWh&0ul=>zQwQ z%m%Rhd`*O-pJ`W4fuSH5c_pK$O^fQR(W{(c7uk3B-0wjPK*ev?r(&AZ(17>Z@-y_) z@k%m<4{jPmv6#$h;DcJCmgFRt3&QS#>+Lu0=Bse_lh!Ekwwo`r$y^IV>j?uuz48Gd zxFnQxI7g(qQ6r`TLm(Q2@Yk11gl*nxPKG9TqJ0O74F$QV@yX^aYb`SDc@7esj!eFY za8Wr*t$<0rZDM02*oWVIP1(ie6cKf?=c2qpX;83#ot?XjZ@x(g0EKW5;$(`Xq+Zk_ zJQi+7M2r9>m`*U&Rj+b%57DBzb=ly95>zY6J zsP?^02K~VVgoc2Q$!Qi$=t1RnEl+Ou#RptIZkSgDxwGoO+^Zx8S%_UDHz`1c z{1sf*6OUfYOa-@Sl$R1LgLYk`=Mh1P3_03=Wz#p>=~)OR5L_tw-zWN}%a+Yc@eWFF%-YJdtsCFPx^!)bPrC9Xj|1^#R3e%o_+Cin~b#jHDqm&LP)1jok@gaO;>bre z)-iz3ft$PL>I}<-Od}SACW4*=nDp!=%0qG|gM2|aN+ZY@1U^$-C`c2Ci$k!7tIc*I zA0~>+B84C`T(et6J5}yjU0#y_Rb6f0@8fCy3A#Zv(B1#Y`*it)!4A6(p&P&uVo*@3 zx#icFxcpfdEF?t*qlgH|)k^=)yYkJ^F(9@o&!i-eGT$(bcYZf4(xXgVBs$48ALiU?2yEw1wXAetr9MV>5Qdyu6!d zz20coDH6oqvbi5-arY+19-D|Sd1o0VCca|wXyJQ+4k-hyt|I%>rY0`IR1ZHm)u#PK z2ukP|k;`cb5a|Ur<^k%n^XN5l#oeI!&eg%iXMs`iSx9^$!!9O!BH9$6C)n@w=}Man zyt6jxrkaTEv0ZPIT%KsE3yVGGaD%|643-{f{%XGbZul%`pC>e;&sh4fH3wE!waek_ z+$z0@SuU(cRJA`aJ&6K#0h%h#+s4It4k&bKiWOD_L&4?k+I~22axV}&oO{xx*SQ2J zG}Unq%Dgl&SPK0yVyaERTD$22x9CzrUDqQ(iKaSvQ4FmA^y=f&DX)s*e;6Ut^TKgoga3rsMw|<0DJIx?DA?A6Metent$OHR+2gg&hiClViSX_ zM~tTE=;)?Ld17VXfOfjT1o^?@2~g#=CQGQu?vB|h;O;IY+rDLw z%dd3`s0M|Ub9D{8y(k1ZQUw8a3Yf=$GO6L>!Zo`s6FVDgT?p8PVIxP3^bAn7wY5~7 zm%|I9d%NTku=CU61~@X&AA`051iEt+#(_(JYy)7T=aABl+;2@OO7Nz-$Q%k8^!xXQ%Be4Z#h3 zFV@ktni5DIG-BoDXKCAxpXujo;^BJ+JFR#j*ehZ z4IR>x+kT^>!u{$uah5zenwtbdLqk0SloG5^-{HN@bvzBJ&KlHxWi{vOxKlACB$(&k z1qb`rRSnSOA1YDV>4bXnLsSNer|75%n|)L2>gws}u`*hdwU@Fs>}LkU`$k@zLQgz4 zT?JN>!)$a*=qHSQ|54*;X!J*EjZFp@Hz4;NT#Nj0~eT5n)tjo`Q~6=9_OAO#}KT*Z``& zUg1rWwrtJkU9B*=AZJe@_mmeLIjOQ#j~2gBkRd;LO3OW>kGxm6v2@*xYuy7B+Eq;E zGk8vwP+e2fxQF-zW&6d|Ut3#GnLp;x=U;55kkDXSwPp{+#I&&tytv0Nz!fi^!!0N% z$RXeaD2yJ<|2F~`p2AB*LR3JtQ30jiPM1~Dneu8Lki7ik6sPJXXr@k`VyRPnEQN=K zQvZJ4Xw1kybhf&dvNs-}f4%hsm8#5(-`LXj!1If3+#PIqYND1haI)oDi2cdY&KLra zlV=r9Y|7Ts*>Zp)Le(C@>fqI?uJ2T7B~?^Z+YYq(p&|jw1l^*L4e)G$BE+zWHx%hu zN+WC*Wa7_iP#@RS)Tu7HmM0eg&}Yw{qj%q5MN5}$Wl(kL97l=W+w(niQ%JD3*3zNF zB@Dvqv$b?e1!1)cG5`}ltEF6@U?&(_R=`fq^pjJUSKFYKUG!$V)}rr!D9$ae@^*1i zJj3IfsTq`#+{wm8iF=umWX96FvWA8RW|(uebsWwMV#1%pR1Fj!9%6YfUMquJW-+`M z5gov^Y0E)gWFPJ?zGDpk4Vo%v&k?GutYI)kMTYS)gVftOm6gg=K;e`wS+<2vsZ7#QeB(4sw3KjGO)&v9YDi|dcGAc zK=kv^Cs`OnNLN#LEGlJY0>HNK{F!#_DqtWE9@vAfnw(BoUOt!^&Sw^3?}?mc$en5^ z?Sxu^B9fc(jF&A`p!(S*F7J)k9O9g>?jTJJdjuxcW$xZnMDKsFntoWZi#3nXL%0uB zqe#34qQ8TBliei#4q%6=>=hmsVtGyz=R_&JI@7FM+p4ZPnEx(=t-AUgolwC8CPTCb zm;LGDjkNTupIE%759&dssw>CZ1(-FxcV98HF@WVO_a{$EKvNy`Q%FdN`+zcQC?!#l^VogxLrKot3?x z9((c=4(Y)ny7R-YX)u407!^2hPgu4GfWqSTJ(J=oi&a?(c`5)N0f;Y+=MDUzJx)zcrHppp=%VU3tavDMggT>FecUYDgCQ+Xl>y>OK&;TVhhZEv4;Ik z10?hlGSwHQCsE%%UG0Q@U0nkmFDYZ7oGCxc|Bu4jR%!^391@r#SQSL=+ePthpvq`k z0##dF^{4HJ>Fe*d(^}PKefz@>2G;Z`88m%bhTGeawFH>p@-P<<>4L_RnQj3}*ua4U zDP;XTOS2u&D=(nn%jiyad!7Ib$;N`hle|JNqOrt;_I95E_w~WY>v%O7L}9S-@K72v zvM;+xFb&*ObW}J41Z!$Sq@!Gd@cz;<{kW3=I_isMTh(YSkN*8mCZ{`o{mW4{k)QvW zViV=L65Y8AD3cPb4V{+QOlmQP@`f-+M3uX@m@E28Jv!LAA-Ju@OE+^U50C&LVJZNi zw;+(S)leRc2Ic^u5E+8Ckch+r`Q5s<<-bFmBk>Ah{?7cB^wqc9D0{;J8b3CTv*n&# zNIvod%upxF!fEzBx4XrVE<-OoSUvfwIKz~5VJ+Fc>1jy<1M{;gw6Q!P)==uMvXj0C zX`jkY>$3Op@voemPM2Mh%76lEee_8dmwR}C6)jpUD?7`_K%(^*m-VL`e>I9O8q$;7 zQE)?EX~ggp^_crrH+qIw(sLD1a_-BQ424X2oY^%is=G6J8&I(O#y!mUAHB4Q(+Ask zZ6lhN++qwRioWi?4-5od1O`E5C7&&nPPY7%&ygFVwCiUKXVDJ8;J#nquKa${afvcY z$%!+Rf4GD;ZazqhKHtbf{qaYpQSX$_X0dJJVla*&-sd7wSeV$C422!1N-367RRZbK z**!1h4xm(V9Ux1AB1gA`JkMjrl68g-l^%QaKyG0a&J$=PcC1q8- z;}pdBi?i=0p`#JM|`Z6exG*R&vQiQ)Px^DG#}$AB^pvAvPYs;H=8mj+kWtxG%3 zcH5jVbE!wZL11Ggz5%HTTnvWQOGRY)I|u6u%KoOImaRI*J5Iqk_vB)9=P97L>2AuH zsWfJqbsP5iOAq-JP+D%to~=9?B>h0u)}iB|UUO3sp)U7$gVDfJaEGY7A+afcHzYy9 ziiiRoB{&`ZR^Pe00-l6|%(%N^++#q&CZ%6Vk4*au#dpzmkz_69Lza?%r?)R$6nukJ z*1A_xXt5~I)AOv=0ghqP&*SQe2>If=e(eX|!t>#VsUoOPMQkXxX>h*o`?GP;?}$Shbr=Fc}vm zb2KR28rAD@W(e|ZAYMb?Io*8Y zO*|_GJ?Ch;^VKwUc@-rAt@|BEylss64U)rGzN?vFHY{*Rmzz+5{3 zSiWI$!DmZ0(VBI8xzk|mrK#-hgh5xWTARzoXn<+(z(npnZ)))1Et|hIpOSheF_=bN zHi09%CP@a*2I7AHb)liJD$m{e65WyWFy$?;FqdMQH;n+1j5ZNWpF+TzHJcLoWi zUPo6w2CjEX7yA8O6L>IDGMT?%HLcCsN5eCEQF5}@j*IqR2yrkEYIe{_zr1!h_cDc< z%PMGnLAL$JRePBE#*FGq2?_3I$UV!qxnl>4gLxq_fC=oRc%Yq{1Sr}4MMuw7K$A!# zU>o+iNu8T@iWLStDXAlGzJp$-ojFHn(u7Lx4sg{_PQh`oI~4X@(Yro?6RzvAKTTz@ zU}nvpy+xMJN%4N92GGU}w!CJ_P`YF0SWcG0;3mKLatke7l+C%V8?GP8U~{*I=c`VG zf6jiA(*#WcQ_}%u9$-WTcRjJnhDN*h3ZqE_yEO}2p>CF;>XfVv2UrYi8|d_R>BNKK z(ENs5-oazEdhK4$AEDz?4=U8)W_5~VW6ZUynn; zy?y!BR{D_3G!-;`Y6jhY%cWfA@zgCh2dL^5t!Ym*(2)xbbj=PZuvGVixym1Ye;@tp z->L9Mr&s^&{d=Dg!v zunkiM^o(nUaj8TkCI$G=If*`|foa|8YE^s#P$&|EIYP`WzyPDx?yG@m;iC1NHOCyg zUyL0HPGsJmhIEef|N!n$I}15KYcOcm5;XxTSE(FY%A@xFDK z1aQgde$0SyV{+L8U_~z!KG!IHfiwde_Lh9PnI{`Sz)!t;C^J+4CV)w}J#X80>x&so zkSm;X@&Y?~4^TKz*dwMPsShhyWuMG@Gb2BBAU}>ebUjXIsyflksfk_#6`I|U@4-%+ zxvI87-x3Bc%teiY-t9YYAIqUUbP~k(zWqm4y_8GCR7M(B}6Habh3CLO&Qx+y&}B#k?8w|c>egm>p2R$Y)mS%h^x^einn*nyo}u$Tpy0H zSY;SY&;%2K@sfLXC;v0HX+c__JSm)C(Hy!!rBMW zYRIyq)wn@d8}!6b_=TZf*CTNvgDE=pNs~!=)oUE+RX~BIhV^+yK|bTF&)&0a)Cf%# z*5w%gkXc@AcR}kdYpGQ^v_5-3Emxzyn{OQB)+}4NKSWNdwiatmrk8IN6-CZ z9j6E&!e?Hufr-Cc6C=B30~8E-3`Bh!wth=aY3PN;6MM~4WSuRfkNt*2L_KMeHf zk$t!W605Pmb)i2T&1|7=3;8-XSW;F=zfFeY_0Cx%gT3KzoDm}eu^iA!o-4A zur(jv=hbw=y7l|$rMX|RFvntD$Z{h(6YCqmU5G|E`LRCFBhG~==O9l7214T0vdBh* zbzuzv8tW6lC8E@#KMsHhH&|F)$~p^O4C2_(=3yjGO%xIqSB6nQF?T(ImR>U?O85ljE@z4SehTZ@m6;p8aN77@BmQdfX< zw5ZaCmE?0Sz{I7MX3V%Z>s%gudF)&yHow+>U=qFi$(MAxe3*HyPVayUB6#xUgE{0! z$0d51tP?`XZaE)!d#aqh-lXgO7M}su!3r*zObW0!h7Ym$hr1~NG&llG>$V)Hvg1c> zzyx->`nuaSX7WO5r9}aSlbb$hFwImSFEBqRK=t{eJgP2_rP;qr^BSm7Cqpewo>Jb@ z*1UB%g*Qk1`kSA)GNA=O1r{(39)1ZIeJvu~OUcU?0~8DsM28F+!mD>o|MjgFP>CO% zJ~)o98r|KiBE6*-JN1oeU|O(XsRc;l-0C$Gz~pJeoVym(tXHRcMPSwh?z<->aD;&$ zDi+$Oz`@3m3xRxrt_0QnptC4d$blu{tPm4>;BFWJej0A=U?bf6N^3hb&YFS@`4 zyVcFKPhr)*oKcd&?y=Nyvd@a7uOF-d7Ezev|g+)cQV95`Zmy>N1BFjrz16=37wz9f6p%=l<;=&QDpa4kB zNPYky|JG3YFQ-!Yq=}QbsEpblbX9I{t_4(jZM9%QfnRGY^l-=9EsSB!c%wr=Fp>a~ zU?k`yOM=qRbdvvTCOU>Q4__*QnaAMuWpH zX-AU=CiwijihX(k9AWccw`xF9CVB9lyR>Oc(a}^_S4X>c?P?WmH~#DLbkUy(lSO)A z!Qzi>lA-#DFtv9lUTYi7fO}tT!-hq>VDe6wvTL@}N1{gw^WsFO=Xs=;{65NaCmO7% zsG!qjWpvvgAG1V*azrQ_R`11vT383^)xS+@HXsQw>7!8q3CUYoAF)RAQPW!hP;M}n z5bZEW!d=+-Nf+}@-vHO*1$TNKNJdN~_L<|*U!J<8veNm+My(!DvbzwBCF+8rXGqUn zV2_$I>pms`7eIC}=O1pJ1?*`EufOJ0-kYGcD#`Pt_PVg0kCP;T|w!?dh=>A!p>Bk;(Oc7yh67muNin57G@pYq9elP>lkvp+P}dzH-${ zF6qby76Y^B?{muAP$l*TG)fDMA3?)M52wh`NCr_*Qx<*-0P6g;s;VmG?AfzaT3SlQ zM~f*cGKx-|JV6~hbmV(s0(3|;%Kw+2n>b-&07w@)plo!PYG5q@3|3iLNhtH+R%hOt zqvS-1d2tKt+D!2s + @@ -126,6 +127,14 @@ + +