From a9901ed590d7426e08d6809a5425dca6a51e0aa4 Mon Sep 17 00:00:00 2001 From: Unbewohnte Date: Sun, 6 Nov 2022 16:49:18 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + README.md | 51 +++++++++ d2d.go | 211 ++++++++++++++++++++++++++++++++++++ examples/example1.go | 75 +++++++++++++ examples/example2.go | 37 +++++++ examples/example_image1.png | Bin 0 -> 36670 bytes examples/example_image2.png | Bin 0 -> 10511 bytes go.mod | 3 + shapes/circle.go | 85 +++++++++++++++ shapes/line.go | 96 ++++++++++++++++ shapes/rectangle.go | 52 +++++++++ shapes/triangle.go | 100 +++++++++++++++++ 12 files changed, 712 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 d2d.go create mode 100644 examples/example1.go create mode 100644 examples/example2.go create mode 100644 examples/example_image1.png create mode 100644 examples/example_image2.png create mode 100644 go.mod create mode 100644 shapes/circle.go create mode 100644 shapes/line.go create mode 100644 shapes/rectangle.go create mode 100644 shapes/triangle.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f10030c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +example1 +example2 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec5caf8 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# d2d - draw 2D +## the most minimal, probably most lacking, but simple 2d drawing Go package + +![Example image](https://unbewohnte.su:3000/Unbewohnte/d2d/examples/example_image2.png) + +## What can it do ? + +Draw + +- Points +- Lines +- Rectangles (Filled and empty) +- Circles (Filled and empty) + +of any color that satisfies basic stdlib's color.Color interface + +## Installation + +`go get unbewohnte.su:3000/Unbewohnte/d2d` - if it doesn't work, your best choice is to just download it manually or copy-paste the code into your project + +## Usage + +The main part of the package - `Canvas` struct that is essentially just a wrapper to `draw.Image`. Though the drawing can be done via direct work with shapes, canvas comes with additional quality of life drawing functions such as `Border`, `FillWhole`, `Grid`, `FloodFill`. + + +### Example + +``` +canvas := d2d.NewCanvas(1920, 1080) +canvas.FillWhole(color.RGBA{60, 180, 30, 255}) +canvas.Border(5, color.Black) +canvas.DrawFilledCircle(shapes.NewCircle(image.Pt(500, 500), 80), color.Black) +canvas.DrawLine(shapes.NewLine(image.Pt(0, 0), image.Pt(1920, 1080)), color.Black) +canvas.DrawFilledTriangle( + shapes.NewTriangle( + image.Pt(900, 20), + image.Pt(1400, 400), + image.Pt(500, 300), + ), + color.Black, +) + +err := canvas.SaveAsPNG("image.png") +if err != nil { + panic(err) +} +``` + +## License + +MIT \ No newline at end of file diff --git a/d2d.go b/d2d.go new file mode 100644 index 0000000..8bec553 --- /dev/null +++ b/d2d.go @@ -0,0 +1,211 @@ +/* +The MIT License (MIT) + +Copyright © 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +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. +*/ + +package d2d + +import ( + "image" + "image/color" + "image/draw" + "image/png" + "os" + "unbewohnte/d2d/shapes" +) + +// Image wrapper +type Canvas struct { + innerImage draw.Image +} + +// Creates new canvas with provided resolution +func NewCanvas(width uint16, height uint16) *Canvas { + return &Canvas{ + innerImage: image.NewNRGBA(image.Rect(0, 0, int(width), int(height))), + } +} + +// Returns a pointer to the wrapped draw.Image +func (c *Canvas) InnerImage() *draw.Image { + return &c.innerImage +} + +// Fills the whole canvas with color +func (c *Canvas) FillWhole(color color.Color) { + for y := 0; y < c.innerImage.Bounds().Dy(); y++ { + for x := 0; x < c.innerImage.Bounds().Dx(); x++ { + c.innerImage.Set(x, y, color) + } + } +} + +// Swaps old neighboring colors with a new color +func (c *Canvas) FloodFill(pt image.Point, oldColor color.Color, newColor color.Color) { + if c.innerImage.At(pt.X, pt.Y) == c.innerImage.ColorModel().Convert(oldColor) { + c.innerImage.Set(pt.X, pt.Y, newColor) + c.FloodFill(image.Pt(pt.X-1, pt.Y), oldColor, newColor) + c.FloodFill(image.Pt(pt.X+1, pt.Y), oldColor, newColor) + c.FloodFill(image.Pt(pt.X, pt.Y-1), oldColor, newColor) + c.FloodFill(image.Pt(pt.X, pt.Y+1), oldColor, newColor) + } +} + +// Save image data as PNG file +func (c *Canvas) SaveAsPNG(filepath string) error { + file, err := os.Create(filepath) + if err != nil { + return err + } + + err = png.Encode(file, c.innerImage) + if err != nil { + return err + } + + return nil +} + +// Returns the bottom-right point of canvas +func (c *Canvas) Bounds() image.Point { + return image.Pt(c.innerImage.Bounds().Dx(), c.innerImage.Bounds().Dy()) +} + +// Scales up image by magnitude +func (c *Canvas) ScaleUp(magnitude uint8) { + var scaledCanvasImage *image.NRGBA = image.NewNRGBA( + image.Rect( + 0, + 0, + c.innerImage.Bounds().Dx()*int(magnitude), + c.innerImage.Bounds().Dy()*int(magnitude), + ), + ) + var basePixelColor color.Color + for y0 := 0; y0 < c.innerImage.Bounds().Dy(); y0++ { + for x0 := 0; x0 < c.innerImage.Bounds().Dx(); x0++ { + basePixelColor = c.innerImage.At(x0, y0) + + for y := int(magnitude) * y0; y < int(magnitude)*(y0+1); y++ { + for x := int(magnitude) * x0; x < int(magnitude)*(x0+1); x++ { + scaledCanvasImage.Set(x, y, basePixelColor) + } + } + } + } + + c.innerImage = scaledCanvasImage +} + +// Draws a point on canvas +func (c *Canvas) DrawPoint(pt image.Point, color color.Color) { + c.innerImage.Set(pt.X, pt.Y, color) +} + +// Draws a circle on canvas +func (c *Canvas) DrawCircle(circle shapes.Circle, color color.Color) { + circle.Draw(c.innerImage, color) +} + +// Draws a filled circle on canvas +func (c *Canvas) DrawFilledCircle(circle shapes.Circle, color color.Color) { + circle.DrawFilled(c.innerImage, color) +} + +// Draws a rectangle on canvas +func (c *Canvas) DrawRectangle(rectangle shapes.Rectangle, color color.Color) { + rectangle.Draw(c.innerImage, color) +} + +// Draws a filled rectangle on canvas +func (c *Canvas) DrawFilledRectangle(rectangle shapes.Rectangle, color color.Color) { + rectangle.DrawFilled(c.innerImage, color) +} + +// Draws a line on canvas +func (c *Canvas) DrawLine(line shapes.Line, color color.Color) { + line.Draw(c.innerImage, color) +} + +// Draws a triangle on canvas +func (c *Canvas) DrawTriangle(triangle shapes.Triangle, color color.Color) { + triangle.Draw(c.innerImage, color) +} + +// Draws a filled triangle on canvas +func (c *Canvas) DrawFilledTriangle(triangle shapes.Triangle, color color.Color) { + triangle.DrawFilled(c.innerImage, color) +} + +// Draws a grid on canvas +func (c *Canvas) Grid(border shapes.Rectangle, spacing uint16, lineWidth uint16, color color.Color) { + if lineWidth == 0 { + return + } + + for x := border.UpperLeft.X; x < border.BottomRight.X; x += int(lineWidth + spacing) { + c.DrawFilledRectangle( + shapes.NewRectangle( + image.Pt(x, border.UpperLeft.Y), + image.Pt(x+int(lineWidth), border.BottomRight.Y), + ), + color, + ) + } + + for y := border.UpperLeft.Y; y < border.BottomRight.Y; y += int(lineWidth + spacing) { + c.DrawFilledRectangle( + shapes.NewRectangle( + image.Pt(border.UpperLeft.X, y), + image.Pt(border.BottomRight.X, y+int(lineWidth)), + ), + color, + ) + } +} + +// Draws a canvas border +func (c *Canvas) Border(width uint16, color color.Color) { + // upper part + c.DrawFilledRectangle( + shapes.NewRectangle( + image.Pt(0, 0), + image.Pt(c.innerImage.Bounds().Dx(), int(width)), + ), + color, + ) + + // left part + c.DrawFilledRectangle( + shapes.NewRectangle( + image.Pt(0, 0), + image.Pt(int(width), c.innerImage.Bounds().Dy()), + ), + color, + ) + + // right part + c.DrawFilledRectangle( + shapes.NewRectangle( + image.Pt(c.innerImage.Bounds().Dx()-int(width), 0), + image.Pt(c.innerImage.Bounds().Dx(), c.innerImage.Bounds().Dy()), + ), + color, + ) + + // bottom part + c.DrawFilledRectangle( + shapes.NewRectangle( + image.Pt(0, c.innerImage.Bounds().Dy()-int(width)), + image.Pt(c.innerImage.Bounds().Dx(), c.innerImage.Bounds().Dy()), + ), + color, + ) +} diff --git a/examples/example1.go b/examples/example1.go new file mode 100644 index 0000000..1d46099 --- /dev/null +++ b/examples/example1.go @@ -0,0 +1,75 @@ +package main + +import ( + "image" + "image/color" + "unbewohnte/d2d" + "unbewohnte/d2d/shapes" +) + +const ( + width int = 600 + height int = 600 +) + +func main() { + var canvas = d2d.NewCanvas(uint16(width), uint16(height)) + canvas.FillWhole(color.RGBA{237, 223, 123, 255}) + + canvas.DrawFilledRectangle( + shapes.NewRectangle( + image.Pt(0+width/10, 0), + image.Pt(width/10+10, height), + ), + color.RGBA{255, 190, 11, 255}, + ) + + canvas.DrawFilledRectangle( + shapes.NewRectangle( + image.Pt(width-width/10, 0), + image.Pt(width-width/10+10, height), + ), + color.RGBA{255, 190, 11, 255}, + ) + + var j uint8 = 0 + j = 0 + for i := width / 2; i > 1; i-- { + canvas.DrawFilledCircle( + shapes.NewCircle( + image.Pt(width/2, height/2), + i, + ), + color.RGBA{131, 56 + j, 236, 255}, + ) + j++ + } + + for i := width / 3; i > 1; i-- { + canvas.DrawFilledCircle( + shapes.NewCircle( + image.Pt(width/2, height/2), + i, + ), + color.RGBA{255, 190, 11 + j, 255}, + ) + j++ + } + + j = 0 + for i := width / 5; i > 1; i-- { + canvas.DrawFilledCircle( + shapes.NewCircle( + image.Pt(width/2, height/2), + i, + ), + color.RGBA{251, 86 + j, 7, 255}, + ) + j++ + } + + err := canvas.SaveAsPNG("example_image1.png") + if err != nil { + panic(err) + } +} diff --git a/examples/example2.go b/examples/example2.go new file mode 100644 index 0000000..e0b527a --- /dev/null +++ b/examples/example2.go @@ -0,0 +1,37 @@ +package main + +import ( + "image" + "image/color" + "unbewohnte/d2d" + "unbewohnte/d2d/shapes" +) + +const ( + width int = 1400 + height int = 800 +) + +func main() { + var canvas = d2d.NewCanvas(uint16(width), uint16(height)) + canvas.FillWhole(color.RGBA{237, 223, 123, 255}) + + canvas.Border(5, color.Black) + canvas.DrawPoint(image.Pt(500, 700), color.Black) + canvas.DrawFilledCircle(shapes.NewCircle(image.Pt(500, 500), 80), color.Black) + canvas.DrawFilledRectangle(shapes.NewRectangle(image.Pt(20, 20), image.Pt(300, 300)), color.Black) + canvas.DrawLine(shapes.NewLine(image.Pt(0, 0), image.Pt(1920, 1080)), color.Black) + canvas.DrawFilledTriangle( + shapes.NewTriangle( + image.Pt(900, 20), + image.Pt(1400, 400), + image.Pt(500, 300), + ), + color.Black, + ) + + err := canvas.SaveAsPNG("example_image2.png") + if err != nil { + panic(err) + } +} diff --git a/examples/example_image1.png b/examples/example_image1.png new file mode 100644 index 0000000000000000000000000000000000000000..ab4cbb5d2cbd739fe037463b4766a2fb344e966b GIT binary patch literal 36670 zcmX6^RajeH)5YCggS!*7xO=hU?q00Li(8T6PH`#j(&BExT?zz(777F}_UHZni(F(s zd*2das0yL4g4W2ZybqEUyCxhhX^M4-Emf!iR(#4+j?try?)&!7u-; z$D;nG+w*S?u^*BHf@1QzElR;abi>}}IqnlZZ_uAFcCZB*dqT26xEVBrn}Lq)w(ZQ} z>dJzDa&f2jVUEco^yQi3|EgH4o`l~Xo@hF{Cf@Gv-!Pruo(kXGc6&LEnr$aiE4SvQ z-VRTx4VxkL(AAw6@Tr-|)WzFfR*kRKMC#K70yvean(W6DSqDqmF12~KX|sGf@*tDi zr(nyaX;fLxjaAc7Q|Mu{bSLCoem5+-brqqQ`5fNJ(G;rx-{MHr;C5g{V@A{{lRFb; zq?)W`%FQ>f~HLYZD3!`VTMcYaJpfu#OK zK`AdMhFYdxVn(#4UXc&zAV@CRBB9AcX{)dS};n0F0GHhgF)z71q{v*d)a!XC2jU~S4 zgx-G#K=6lZ+HN9%O(yC0QfvM@q6q$jgI7H&+GZpbDR8b^B%j!IsI{9!l3o!*mVo{i>Hv0sFOX?>Z*2(iZq)iEuVe+*< z=qxX!8*N2Dg*B=MB-d8YUIgohb`JmN7*{K~et3=&@WCANAbiQy*)QQ6gV4^%{~oJs ziCq{l2HOzch#L--r@HJ;$O=k0#Fi1+1(Vv# z{qyD%e|z7&qL$_OpG|2|5L_;Bxbe8b<)>qtE0m;a6fmSvrX_aA^WXnLjoM>87zC#l z!Zst(Co@{OzasA%Aj_(Eb%i!AhF5l&%)h37G3mg~I_uhi1VsOWb3!4|xW#jl0^m@)43n33*u zY)GjP6IxULq+}j8<4Kq(%u1wLsz~3^v3Zb+)tvOFd4{&XU$Eq$eFl3>ba3MJ);{1F zq79PaKW-oo8l=Ma4U;6p#9+1~CA#}M$BtwlyTGM*ni9=$OacJ($e>cM(SsNJzx@~ioOib~?2mFU)3h^KDoSS8b zHer-?vqeIaFZVqhPo5aYk6tCDWy zg#xD=n}q~8zvA?a5aYk51AJed85%ceNPkn>20u-oSw%%(!!$LJ-p*@5W!r90qK9gg zVmRuSDqP0gCO`=Gf5H~fZ51L7;>7T<)Mu(KQ?ya6W%v)h6(V!sJQX4nAc$gzD%#T( zr#ywDTeJ`#%c$s8m_vC88Sof+v}?FKrW390{2bclaZN^WYSYyxT9V1(tcO7P)$b_g zMcZVDVCnUeLR6eow~PDqdwqBOgjm(olaW(Ut^<}W39rV}*6H!=vo)BhIa5(-%3pM; z3uzPUA@A2ljrQkqP`puen<~9qZWRqT!YyJx@!RB z;F$|yfZWF``pa=^+IK~>3-a{d)kfK&6qq+aRwt=GvjJ{p*Jyg}|a64stEdJZ2$Uc_k8T@QgkiiHk(UWZU znoh$t6B4v1jDUCPxTrm8b;Bo@5~$n@F1~HPX~ODhG;5d1T^=iu5rea>hHH8&5`*R? zlGN2^PT}-M3KX%0RlA)*uOu z4I_Q|HHA4ZM@zTi&sUU1mb(=B>*RGlBeoo1B1+L5;(4gikfv4Bnz=*>se$8~90}SV zre3PJN+cse5QiOSpzD|NoW(X98;ClX#kIB5(2yH5as;X{P=c>H4FPBgJbiMT>F!6( zd0sJ~$RGC}E4)b^5L#AOd5ResG&Z_Nn}Re_vDdph0t@+sxYF!RW;pe+9MqY{^Axm8 z*`}+R4hFZ({(Opclr4e#gvaU{kRyQ z$7iv`_CaFi)Z#=MOZ(+gMzUs-X_WaBmVaAKsh zbwY}H-7K9=ykB7nrlMsDT5c5%oN{$!571}>{z3ahVE_bub4GgQMP@O=Y5wdf=qFqaO~;eDOi4)Z`;cG)n*o{VaW9VQ zc@v~PlTgjxILidMtnK|2(OCb&ctZ{^5 z6I+bjHOdG%@3?3daf<_EWMd~L<_u75_3t0w@%fNsNm~t%GmvEqsQ`F(D0Y^TsLWF6 z3a%g%l7HzL`Wuz-a`i#nm&I1Wy^GuSO>?kX0LN2`VjmlIgq(?_$6y%r0r!Du6(^Y# z^2E}9t59wk8vj`03&>>TiRgKn z`|R}R8(ALRX9*}11H`x1=j2vGe@aRp|SN+-P zoNpVtzoT{oK)dNG8j*#SHf|{wn5nwoO|Lz<;O)4>HoqoHJvLCi`m{N9Id)wn*S~9@ zm>O8AwSB&Z!SLw{QcEMj)G)fhr?;ZPTjn>9kj7`7u7)@mPW<_gB#-U?BgwMKzn;6J zjFgZe%BQ;Xvh2PJiOV{fNvK$hIp1(Iy{uj{k!}46RN;DKj(Gbg_YeO-=z^bZuFu#? z@%Y8sE*}jV#)LKPyT}}33g>2NRyFwl;0#-h$|TqE2ahfL05p42Go!!Hp^pXOBCR}@ zjL2$}pdnxN@Ly0j)H@OK`b=9L0uK|cx2FSt_bfoFo+Is1OeV5X?GL;~4|PZ|Uz(-; zNFu`hT9HSYxZ_&hbJ`b?y$_n@rUAce-YW=r}&LUh)uK2I6LNVKWxs(bJi#0(Mi zqI1N|GtEW`l7S{%R(9GhHeFG5`J9FI1A^_F@Fq}e*Zg1?R2*mON_HeYJU9Q)~p)0Dy+LanTe9N$-J6mF6`45+X6e_(myJo7IfKou7i zhcc{p73uJs0L=&g#G)MpHeXW97^)0B2Ot9m9!sW_THnh&F}|pA-VUuHK(NM~``1{> zC!Teb2*QkytQwjPkJEi$D4E}OSt%>*nJdOdE9}4yhDO=wkZF1?gcldIw|m853j|`< z0MHH>pPrv4C`5nZW+I^z_sLlI(H`o955BhWLDfqB*?7Q%%SRo?*I8Eg>-7FE+BdMg zLlr#0>xzYXNby)c*`qYC_u7RVb%87!7QV@MLH*9}0mV`2J)^;VzNK|{wkH+DCvEg# zy6a9zF8B0d2`2$`ELe%6KN{y>Xqo$y%Gl;opEHO7Z<2^;tR^db+mk#S5eCqr^&>sp zR4Ad*iJLCex?T`7$s8!4PJ#&G_zAsx>bbW9cZFWYhf58MQqVJS=Fr_*#q4=;gr#5j z!$b8JK{gSEoK2$RN-&v}iPT!2h}on*25^`?tun`GUI}2(GkwF9azSs7E#L zPMWxEqZD|UdAs!ud3f1*rT>z+nzD*YSF-B^N<%SY81eTQCvzZEAsNb%q>XH?p-+|T ze|?@DA4m<+5kN6UiDSO&?!xDQDn)n8?;6E6p<Ssfn<6^^tbklhe^B0k6krw_wlP5yd5G1zs(2&9*A6xq|1%~t9(`)_dwty(H!;^N9 zI)FJMv|iZe-xJk_Tad+ILDU02E6^(bMxu4yo&Qz88QPe!*wIC6yTo`|U=8+h|F=@q zc_K?9^hjzClk~Vwb$^eMs*HKrKuFZ1#|*nGG3+{|T$D@ur4o_aNPD&IJrb>OjFn9R9T#Ir|(xITe0+*&L}T25Rxbuy^#O!%>qoLp#G7WPsy+ln<%!pR1BSwjX(I z#KIsU4^AH5lX`ry@@alvq1|q3nksgC)r%NhMJm3!=3{(RWjzkJ2#wbhIYU+Gon$P` z3L$znCyr$Eluo%id%Oh!?%D1oG=p7|UDp_pT%kM@gQQ5s&y$t~L8Ll!@p33$%tMOw z$*2V^Lj&$%b9f|i2lGia?2TWawkbf3y!|8Ru5SUHL4tU%Cn0ZA5w=!Pv-dItPVle9 zvV8Vhs`61GjL*g~V~&l%1fD;=*AI`HH0r*S)W-%0|D)(YF>Av*mEXr+?<2#sz=-VA zxs16Cqg5G5h2t9?_xCV_o*)#*#EQLswKSovO-I8I>^TSbr4Gj+WYcvEM!=I-T11K#f;A!fvKXTzgFiX8_pzIC8z zoH=1lLHdOM@ymeYLocOefOuHRNw%A07`e}wL}nn&p(NDe`Zu3W#y4068mB6MtE81C zzF(|d{|zbw&hJ2RaVIpMDz001X}lzseh89|9xhN6DF`|sg3t-$;tUlgG_wujB~NhT z`DC;J?X%Q@u>L?^_;W?gM{FqM@4z5vfi-ND)DnCy;}abP|8jOqpa}y4DIHcm=d{L! zn^QrPIbupycf}SXLop+TGrdE{<+wv4E@5!5Ik$>rK8k**|JAZZ8F>s(P7p`|_JdyK z*M!r|_DS>gcf&o0=1?PW<83R?eDFt!ZLhNqy_o0$)yZrfEyH4Pxh`UmGD53UaNH)T zm_nuZJ(0jqK}sC6LP;bP>0_N=oe!zB0^i@+oUP#*^I-4L-kc+R!08ms#>K;wlTHg! z7x6pFi=z3jm)5Y2+VLC`^44e3fg(ku1un^CnaC3G8Az%$lcHLY)z&etDPn%)Jrx+| z^T&4YZtjS6(Mf>fPj}=L`dczk1Q8ehDPQA#o-i1RgOB*aY%>ZW-_OqZ@O&`a_xsc1 zMaZkLR$#^!LF91~ihsL*)ymg!?g!+~*jsZulsFuu02yU_60|mJg2*70%Q}jfZT4EM zKQ6^&_Exksqja|5eA@2w1;;M_38IN=-pBfqq6wIFhQ#-Pk^O4Y%Fd0wX?}&GKUK3g zU2t#jihZteL-@rI?*0y%L;TwstpuL@( zQeRF1hyxQ1G2?8PAw;fe_C@Q6Hcek+6@k>?x{?wIceY*&V#GDa+443ZS6-aru&91s(lmK2 zhy@_BJ}5iH92(_uTy8^*FqEuj-H_}r{`Nc~fq8@k{$gIZc3otlJ>@*HQ}j#Esh1hK?`K3)JFoVO1nJDq^9~tyy&D$GF3bz! z5{f+t>lzxtO}g^EdZdwQnLJY~y?Y{fiSt9c^xrN@4mW1D_)y)s8MeVvx)fJYpdB7$ zLL_LoqcX~w$nR2CCfTkYCppVezQ3w4MBaNBJymSt{d75oaFVZ9DjbV8i{xe2Hyr1d z;e-N(=mBPxfjQH_k@nO0@aNaBjnkD7?Re^Nld|dtUBAe-ncvisa&q29%<&S;rG|f? z0Q-U)OSAZh2HY3^&+U5)^>)4=E)SMv77l3^eOH)Fc7*Ah%U|kdltZA zl@c-7zx`kqpF5Dw?<9Y0NQ9l>>ejhSZ*(x3rG>YYJN=?SkB6kU$6}#C;>n!s9$%uU zdzTg@e>|Cm6i!=qJsL7I$4gGck2173802`|42p>mn~szLC*Xscrk8v&NP( zGHnA|yAZIrKmaiU!!OljV>;cN>iDPM`->O(V}u)fXQuwE&<_x3MKx>G9oRhJ!O+Jj zBn1%lyAF@zZwqei3h&-RK20^^dGPJ0{37v}e$Twfl~_TdFfAH;{@8A-mk`u;2?WY( zV;~715Prxh49d{Iu^<00mU5d5u?#7&Z37x&Q)^0zr^=$pS0|6AxMJFKt4NfO;?QvE zfSnb)4PwfH?k3mGYA#;QKl8(pHp=@noq1R#KVj_I8t=W&*7(r&tNrM)PvJp*L=jWU zewV*W|BP?m)Y}5+BTAL!y(vDLAknyfXiWS4qC;^rVw_5Uu38s{QHjoJmNl&rax>}8 zmn-F*!Bp5pKa)MF+shS4Q5zzB1?%SZX56U=5#52GG0V?!a-~=&&qQXjRV}+c@A9_G zbhUx~LK|MA3jEsgey+s#TSc4Z+BOuZn0O)ZH{a(N+*#2u<{o~)sz(?OxTh6z; zTopr@BH^H=jq3r&)`F>cH$6!jtxS)r=TDAsF;DEH2M53Dk5`tjzc^)y%S+%?)Hno3 zUb^bXpDvcQwXhW>9E34cb0HvRDV{y*xKdvsd`Wuw+z5En9^GK;ZRh3yykRZ9O zloNE2XHP3k;LoSKwx3_hiuXxE7H0@pW+;8Elrz&zv@VAVIGqj5L()pzFF&y^1aa_$ zgn#-QoBWgUtJ63p4a~FriJ+w7-55ogI-YB&ObZsv;oc8t6Q(C0|6GV;vO!yp+m($R z_5B&w&}D*6I#~Qtw6oJcVd@KymO0C61fB$g9AM`Tbam9+{a+HS;1`PVm0--23-gVa z;Y-jlNE77~k~{rECYBhtTa@8Wj#AGo!=*n8?FTQBgu%}x1D;I$AHNUA%dko@5K80@ zq>ZgzyG;zlU01emI~{_8ba{uQ78~4dD%a8b<}*+#(Uqdi#JBj>?a9is;>#%MUUWe4 z{E|XPLk)J|Tl`{=Y>dYvYsU8b_RrXVbOHDK<+L|+*vMwR>H>cUSPTmL{hGX=t&L9^qrli4||w5E(T)(}k4sLi3Du1AC$ z0xSnU+ol_*fE-n)Q5*&AaPes}oia8E`SuWbqHxF4rFe!6QWRu(q$JaV)DNMRHqfLY zzk;Bz*=8?GAa#7J-S9z|iZ*&!l=3xUcjx7=o!(~gXWindyh=}A^NQyH>kp+{JUtJPIXGfG>FWml6{H^R3$@-OKLj=LpfU zHFy#ho18RSfTYkHcU5>Q=jli0Z2?QdFzRe^v)~|Z8bon~!jos=dvlUpRY_bbWjA~> zIE~QQ3Mj@;l*n0+c_UDtsvAxW5*fJLg(A^k5WTh1nk`NCChQZ0NCQ`+yf;31nigB)8_ zvfIsUF?4{jZi#LDDOS3vZm*|qJ>%!Re&53w<7=me-(8Q5+{}1`-mpE*Zpo_D?%p9E z_f1aJB!_uZ;j zOIHb#A(k$3rE;JHx=qB}EEEn?k?vFaSDxbTFcYWySI_?H@_iD569e$VXmIReqzbvVAY^I09qa^aLE#FZ}zP5$`Ut05?ip z1u3&x@w!~^gsb15l`7=ytQ_Z1QU)OnB1ipf&elDk4Sd6LuTb=~)|rPnn*y z0^3Ei$B2^&(Nvn-?QM^U$Z6j~hry`YCL?49QWtRT+b5}r05aJ&G;xO@&V+-By~BH6 zrd;u7@qbn_U>GqBVd>Ek7K_A%Qg{1hv}x*89~;~C0)8_&DiFN88`nWV^8KH5wKACZG z;yZYjd_g?WtD#4u;2?G2kQ!35&fM*C@x2;UJC@0(B=4*&b)qx6dtXIGNQ9GwR@Xo+ z8eY+KPXwhf#WKBW)0+{$|Aciys-A_kGed)w>WYI>t`h>|v*SkYZ~s;a^1(z$9@VYqZh z8`{}~Y%jafnZ|IV@SU!uw3djSyt0r=0p%#Dd_du%leEUNH#}UO-?MXIL;vLScX)#? zxEvKk91$|~J!yK>VqbSTPDn-NbCjPHp0`LDvouT(%3Y_5<_V6hAQhIWm>;fi)eR1& zMnt@&KJQu!w`e@8vN1;G+c`8aaAjJzDfW6OCqCHKfY?R^*83*ZgtH0Ba6X;pl&l2<0!WVlP22 zT_@KiHQoy*Lp0F-lZncB;t!PQHlL(PgFi~)3>d4_&&$z@;cTqt^r0`QqC)R;|I%0F z@j+fW`0W{9@D|=~lV#5FWf$;J+&4uNA{pHmN{p?n2n~+mIlPMaV(Rt_lT#`9r5b4H z#dSd4V_oNh^5r0rlzrTv`jF-ro{+Di_I8<@+b7wa7ArcUsq}EcfK5=tPZ~Jd)qTU@ z4F0pui!8`(J&TrKwOWp1%8Pq@ufjCO=<^rBb{WOQZx|)r3(%t_qwc&yMrNr|+`M9t z*S7QTy$m=xHu)jIr%qo%2t13iE?!i{9_%PpG79NQqw!M&#FR`S&U{=n;T!*?opWYu zxG8$i<@5K!J5E?0hxdZ@vj(aVkCeQz36_hW`mZ47#tX<>O*tmovIR+9C;fN<*=ucl zWrKYA^rsbe`&>Ue2_cLihZ3D6E(MbH>p1_J5xjykpVy_6 zc!Tc^sT?d9hFlI{D^&o1ULiVw>q4=*TOZf=v>KzR33g{@eZ&3&#YC+h45+=q5z$Cv z^U_Tj5Bx408V|2|8b#mOC)RJTwpzgB<`^|rS@r3l{PCff@|Q!8Zp5_u0q;ZGLc!f= zkvQua2|j@ZvmxaVuMR$je89 z7$njBe@(oZZ|ns$`S^WGp&@R#@btNpnBW)}g@{(72rcB#hk+~lIRT4nV%-9iUI;-) z<*2r%tCf3@d!1yJiZ|=H33(sDQ#em1D8d7r!;eq)6y9`F#IKo`amq83z4$i7uN=UV zHTDO-?~droO{!do^$ZXHS8wq>UTAyRb(m7B=*MnE?M1wi!@9yG37Jt*J() z{QUp8b>bE-eq6~X94Pb+iBQ*}o(T^e`o+ZG`_)QqR^H?mBR5LOlMb}S883N=!sGz$Wn{jn|UjU%pG5)TSB-Z znkqf(4+th&8}@kKpba7a8s5rWdvde+;Y107&D(#@{g~(D`d{lx?cKX>`8O-3MP?hk zAMEq-jGt+yIOI%l#n>ajIOC%&!pLwQ5$A8FLCXF`wsF$vlH^QA zj6go7oUswz<%V1ylImLp3Js$lxCVOUXRfN@gE;tBe_9cRxFdq}43je4yt77+4Qh$w z-7>~o>WP-tNEm3)rBY#qC|cj$l6$6o(U~tXO8mB^nrnQPRahd&4W|(@aR#LTubf=n z4|#86-K>i97;WZTaJA37;X%n!W_Cf?s|%)oxEu#=gKc5#Sp^>!C=MLjfFmBgapBnx z=2WSLp7hn1T_lnE@||C=w&CO49Lc_6A{LsJ_6O6^(%@8&93eQZ?z%^h^XUF*oj{E$ z#|KZmF4|lOU5YEg5$`4vU5SU`*@u2{>;(#&OWk;e_FAs9mn4H|lGp;>xm^3?nvFp1j4 z?M(o|%$L>>1~5z7g_7d#j#{`D0f(Z%PLLxucefzxolSHAZKJmo^(h+$MwzGQ3?)9a zTYUxPSQ(E04WRH@e!RanE`BW(MuluO=pQEv|p4kpGOU zxjiW`yChyl|2V~58Jvt*h5FxBCiJhuS+U6cDL+q?CQLJP$IcXI`{lr*X#A({8&h9-Y4;cDYn=!gHSntVj|KV!bptj z-SZ*oK8cUm{HiJ5a9*Jv6x*%za{`HnVTHo1SaVI0K|7f_O7^GpgGtOOndHDldv;dY zgTJ@_AQe*lM{7#DPZjUv4%HCVWmI)j1@BL+ zep`+^=6!7ht-jH>KQ9uvgA8+pR1oV(TD=5C*?r?n>}_--utWKb6m%N%Lqh%0k#gnp zu_Mo0Lykvc>RoH(d&-_i>4FcDKcpz+e`UL};S3@cx^tS*oNm?MoxsBuf@D zpVHhn)XIL68eG}QBm>&pyK^J8>ZWU-X3J$W_de{`W>|WYQn*@auap_Vi^EqY8~Y8$ z{p5Uee^0Ge9LGj@Ui)f40iQvzF1d^)K_qR1lPVHVV(#N`+>g7TCT!ES@zKET@_DvQ zIg|2BV=GI4V582v;DMISU&l%6mY)SW1SXYSOZXaj1c0zCXPjZ+Zs;J88|K8R{~c$lvI!?Io@{{kktJ<0nBeyXhZCHH2Q1T|oVKg^R5$Lml2A3Y+XUNK( zPmXQ@gNdxvym?*f$ZX6brX_9VR>I_KRPz~Ih~3@c_rJfaQep#Clp^;ytDAB2BS2Mq zsPet1&wX8Ph+7KMrR3S86>r0&G9rT69mFMrwMa{`?58OLrEdrbKz> z9eS*$pF`qyNCL=cuYN$@qIO>#DvUXo*b8>PY2>EJ%h_fw`%+Baj=uW0I(H|9PK-XI z0)-OJX0*y~&To<`?n+wgX;?MisX;{n^#jL4iuOfXNwI_ceC4yW6V!kHGOZ*_ikjjB z06=qoLv;M0ByBbuf^wF}T<@SXCMn1+#AlK+YTIF+H)!0JvBq2k5&U1^d@k8VEurld ziYurq1!gMfEcmec(yN zd64Y(oLC=32|9nDUPvs_#wTWdNIb_FA0(+h;2>uu`r)d35Y2Tq#Yu(0T8K6C%m=D0 zq+plRN`vYD>K`vXL^xlub>B6jFRXGg{G>BlQAoL(3mNw%ph1iJtgVX>c;QSSl9i)7 zgUeZdDn5C>ZaqVP(K@Vf@wqhzwHK>%vvL}IqIKqTuX;}CuJIeQ=NvNejB#j1msA$) z{7o}WhsBKl4o4iS`fb?C{`6<%V}rS@z-%Y&LeW+gPEqL6UhWUKCu_?NmOX z+g@9*k=3*?>DvRkJH;3jM-gMHxH7?z@?j}X1QD8h$Q0%0r1ejw-Nk~0>hY7~zu(e5 zqXv2YV#&hZMsar#hN33@RP~<%&h5YQ8Tf{ zZRiUNf-_r)_)93Vo5~XSGT(kfZzs z_`mxF5+(-v`OVEmf!x{5D7c>a+cXet=*2%Jyq6Xf%qT`R+z6HFeAXqG=E{9}T0NhFNgz zYtEQ~JtNNJuI7gThx#xcB`vZ$ZEm3ZCqKG7$-Ly6($`q6JOKExDLU#d3UP$D-z@uj zvSv(G_rC-Zw5=l3Z#SLiqJ`A>3Emy>?$B2(q*es!qYTNOcrT|K)V(xAMFQ+-$+Q?Y zTU&F1T$gI-sng=<)Osg&<2bUJmRT`fFNBX{e@I+~l~_bf{#82)1jP3@|@KZ(Q=kL(1&Bg@ZZM z^tdJTohi;3LYq$7X_0dx6Kz@pg%$5aRA1B3jFiodo%9%Snhp^j$lK1Xh@zUVP(1cj zZiG&z!%KOeOBe=kwr$1G1wi?`vOr&+Fm~Ofj0!~{Z{m5k^nkPot0Hd@+7ImaEmlRF zn1Prf8i$uC;j(#AwvMbiz=^&@{mlLbiM>y{zi>o$9GSfaqqcHqv zuGrJK4sWO&f0!rPU+-SO zJ**>mA0qezPdteNTKCRX)W9LAo=QPnMj6`96x00zl8`S%Na&kem}bDrXU}hqbC4aU zho27yf_8(t8SwK4%c~5)lGvDcPUJ1Ktq9YL$9(RD?-&Mgx(t09VG0x3fQzxY1Rn4B z0>p>>HHscGLh2*FGRI|oa53pIoa%&18=}*v)lYQ&-msA^Ksyu%T#J!FR}EEO+bEdn zZEuPjXN8K?XY73k%+*`rK`J8)jvsL+|FMb2GmY+`?0_w6d0Z`L3>yH32#Jnx2~v?3 zi=*ENp7cRnZXaR`FY?E2WC3nGn0e2CeI3rgWvi(X>z{kb=8jmNvJ$VnBzAN`hOVif z=ChJzQ%?gUrI6Ks+De&i{>p94Tw=XfCvKiBXQ4*A6T4#yB85fhvBrIrT~Y&$v}Ih> zM*lUAT5$jBU_-{;!>Fqv$3XKI4N3cQKEirJ$L*zv+WJ*XY%Tw- zp_8+?!gMhoKvz82IKxqqc`^9CK<0&z|A(CGlJ7>zQUaGGDjIt7**WcXGN{lT`T`5Z z)+TUj5N@MzP#!N4erC}5#x{ngRmzv^6?qlT;&9v9QSayrW8;KcJeC1kC(6jg-Kta<$e+BQxLr~^4Q*ofI z`jl&3uGXqt!saNK?6EpDX-*(_z~J>U+c7(wb{r0Ebo=KE(e&Q}jHFO~h@AA{SqRB3 zszWMxm+axIHAS+}g45t$8>z{`iw74`YS@0qr!J)j-2j?c^=m+#q?-?zcHJFR;ZvWg zs>?t$oAfnqMg!DE^${)xHm9akbMbW8yoqQMJ3=Sy4<%8{VS8K5uF(6z2> zZ6`&MeJ?|s?v9svrnM@!5-1#`K_w?HY$DBNbfAYYUCb&@yN`iEeh6lt2dOup&QxFg zeTNz5{e=_o){U1a@MA(gGDuJ25e;}iWi>y;$(GX|)dfBw*#d&MAT-@7 z^FXjfv@hWg(E0j8R6AU7wM1p|7pUG;x8S^4T_*}>-ODu!@jP>|2s+Ngs|jY^1Q>sD zsdZD^D*R%f79esUT>kLzliZcd*;G|=M?a_SoEFc`4;W)r{dH|_v%8z7MKh%1`&+Rj z?s&E2ki-M!0&HxqCr_x!I19ibm>BG$#ZdsIXV$=QuDgs~s2DTM)Y&~MC^@ow<`RQeW*orENLta-An*O=!WQu;YW`I<2G zhI;bRKvC;wM`hf(4oE*(3V-dLIG=5oxcsH{c4HnV=i@Lw#m%Nx1tH-N}{MUuN?spa!imZ zY{`!vaml!2kp?atS>&B)hS zJoQw9s&lCwy~dyJhN~LH!P}{NYg4qV-hM90pF&E6k!F?-wuGxDP^8-GKc@5>@b+C~ zO3u`!_Mzy+bqQl4%AdhYX>aVO@?nyxaY2I{R?s!06O48k7@zveYLrb3@~Xc^Vk79S z%~J_1)yV)Q`~CT)ZR0pnP}yk^_$y6lM5hthG>3VgHxMC$9yw8_34O@UfmXZV^X?yS z@!Jfw@7|ed7WZ*o{VQtkc`o=_;nEv-ExFdfM<;7C^04DYm5bd#U-OeiZzn~3JU3Np z71Sttk>gxxXkR@(!2z()@SCcXxTV~hc=5VjT&^KS&?6v^M1GTT#3)EUkVyJFw#hH< zW-U~P>7fv*dEl?T^vSL0hrjx+zXnyi1^ybZn}c%G%kYkbPvmrbc0cc$Q#!JayL@(7 zA#;L;`w`rx*)F=om&_YiH|=f>W%160vV3n zZv;o=wBHljyk+;m-zJ`YO#c1I^{tA0)fO@ZQDP!kJ_X*PO}L`T2TSUq~O?WoGQH{I3i>k$%A zcJ?InusZ(|*MeO4cs{7ICG_{NUy*#Cz9J5^F=YSujC7To=!ql$#|u{!)O7!qSx%}c zGo5dz(T8xo-uisXA}K&t=q~ngxYS9tc3F3(rgY$A>UxOAydy!jm(yUC|%?;(PQAkVibMt>9M zcQX?7Wvg*S>;!0_GIW?Z+_(7xYxr_SKBS=8t= zbFXuraBiODID5YoumI0RL8pE4t}C2=5i&@$W^$;Noke=ihuXHrEFLU5f`K7s#$Ycr z;Hv{k=sy9_9`1I(uMc)!N_R;z8#~YZMC}6bL6vt1aycU>&vGX5@93RPo~NUsh*L1n z&O*?zxu_dhn+mtrj)z#M&=Aj%r<5+NW-CdzUFYJ2{Q69_^*6hY1%ih^DOE3o-HQCv z*}YHLvs=cndD%;VUn2P~l3Ya+NIz^<(#e$;i-kO3=sYjtws%iLgQ`(w=8+BV`qQ0P zs+{?2AmMC6zcZ$cDnRXpP{AW_+2V)k?)G0As?Xes{6G)DPkX7U4g*OoSz^i3On8n>_p_-ITJX%>pu)KmB~wU|$K z`$}1g3KY4!%!gCDu#h_xL``ragcIs=Sw4drqbIN~e!X<>S>;Tqh<@5}76zgu(!{(` zFS&}zXfFmGuMyf%5GKL~iIUVIy-o^1B-bSWzzUEzkr*r)&%pJX^7yQ}S9JcHxH;TX zj?o~#*u4Cmw_7!@Kb{kE+pE_tqwLDu_SHkGK)*FD)y=dZ<-X62YsN*y)weg1h(+bO zH)X2$P)}jmixoiJ6wlSxa7jcaj`}ua6* zI(3sRJwZwiaI7iCGK*#kZg_BKKkQLZoGAPqPwbe0#^dSPAie>B|F$K#s|U zJYnxmC6aE9Ky$wu&U(jZQI&3E>`brEefXz@DMU*Hgm^p+x7p1XSN32WZFfo9W0{Fm zc*=`?;Tg#f7PMbHM+{*acoqP~8X!6W*WbOh?`XU&Cat}$_52_T4p!OuIPRXf{Vi)X zBnv?K-sfQ%@|d0AqFX`!SEd%$=2l1xZ%BW~6|?7(0zyt+%O?vQZU7~lobsLg3&gW$ z+){XG`kowik7l;`*(=4OoTl;qAck{3uO?-6-KovKaLp*btx|j@@`TJKdyDF^CxA1? z*&&fiw5j+Y8w2rh9(jBBkAKeNh+yi|RJv4?(DXd|1k;mQ*H0~}pwa{y$d~#GCzs8j z8b;FpBkL{0;%d4!ZQR}6-J#Lo9^7u+8wu{g-Q6961qlwp3GR(M1cJK-cbn#Zp7)z? zW`6hXeblO|U3y*TQs^bJ1|1(70lQ20{dA|BNRZNndtIz-d)T9Yo_K1G<%DE#pSJ~r zVy{7WuhRKM-!d{n?wf2RjqS_7s@dLGZjt9oy1s}QpO5*H5K%8 zI_#3OrL<+6P|@>>UwJby{1w-gN@Ed__ms=*myNV;$Pd2{T(dextsmsh=jKRFH+v0D<}QH`}M(!{Jyi{Twx_Mx>#!f1uMl@2iU5QShPfqZJvl zvzr$c#Ib|+D9qmi^B!Y*A;$`XaMjo!eQVn8`}Q}(H1yVqU1EJ%-lO=6>t^98f|pjzw~47vT!bXpXhRudi%mnpXfIDnsr$KbbnrlzBMV|ieRHe zg~yf>4^NqHI#w969+Q!Mfl9h&Ay~Z{!53&?qVC2|16ZT>^{|<|_$D|vvl_|?-PDSx zA+%>WW2%3n>~panVio0x>Y%>PV)N2(YK;5X$4RxYL@=IN_OhpXK)Q=PQrRtXslM59 zOzfZg?!nk0er^P~Q{<2AA4tjKW59{&#|p5;*lntWyy8eME8TU=aXAfo)-EmXXg@)W z1^ohnUS^1eg0h0Nsn^i34lB!9i+oN;yl`p> zrE&cb3iLd*!5~tv{3#znRa+gxq(6Lh)WZ|w#-=u4%QAtp;gkw3xdk6WSiIzhNRM=> z{9I#`o=h=CzEgMd%0+TD=!n=T*m+D^oGEw_a7d8}kXIp~7dgxg->cG4_ z=~gxMy`q{I3qmD4dHQ4lG!OpVCO{#rpoKd&Xr1b2DOT8Yn8Iv>F%C>4;62Az4Wl${2tb{zvMZbzP5(`fMV87nQnC zLHY^0R9ejtjwDv+0FuG~5P|!n-3FWCo%Q$s4t8<5-VEhVdwdf!b-X?*Pp`X5$K0~` zdC`_qW4y9J*=$$I@#7&UtONW>8LC7$%sO14_wDw>`ob9T;vsI37|j~uAsl41419T9 z+WrMB@SM%PB8b?<%D^#`vHMT=!JR#dkCa2nw_A_^rko!wNhli0=Xu{u& z53O`|r0OhcXq+M{bx(-+i(K#5pob3EKj7**XA?&7acvwRje{FW95j&m1^$SSCbb?%ES)c&(V*oob~tH(mTBrx}N%6K_3(r znVPT?TUSr<;mbcP3V z1%L;cqaP^w{sU;D;I4j~THdPov;|}OTx@069g_;|q* zk=M*H3bme0tNQVa972dpogxwpON4{`HEaj1f&`d3VlG#1!rjboS7L}}xUB!P_#<-N zSzSGbZ05@S09i0Dr&#&p&x8zTTt`OZd(*TE>2CiPf(?t4!Qevwr+MqgUtG#L5<=L( zPnT77o9cZqc4!@0!GM)?@EdlkEm-3|9;w2ep;f3!{|JFC87~NosYy5Zz}KXsq|OOm zQ6UGEg5Qqv0bf!0r0@*Q3E|=c!^Xg-%LVqhXRt8hzI-nb4d9Y7TitoSo;f5(jsutF z->0#*Us>$}dpa-4B8cwrh``T+9lNv&2s0dJZ$?(XNS^ASYiXx13RzONzv0X!(<&5v zl=){0#6@HC897q2;-~8PJQKXYKTx~>sjQAH6NtnB685{&SEIX#l(4g71|7PIKqcti zzTM;xf(b1wI$K<@g?e?q@=af+L7jmMGZ+ZN6^9Z|((v_y{;E3eOJhzL zl^p>T>d->)UUJSyXPWF2{ND1Bnl4qHg(pwVQiT|x$Ao_o=S(nv+;R!?uF+bFW@sSM^Yo}&V>2R! z{k&ehlJfH+C>!#RUPd+>BG(OR!p8tP{@ZWNH5dD}ccw;`t4;c6n&sTF3K3_mwKq+N z;SyK*y;#^(-^Uo7H1qEmOiG5ik3Jm@8Vwa7d_`}$laeci-xnu%0sN-7Ab!tM8BXjj zEd@z}PSv!#6p2~`5j;D`UiQZx&$ITl7)r!-n)H#)cFbS;XmRX4pnQVJ9i4TXne&78 zrZq~#j8C;KFOGk>oBfpDbZ4=-!1f4qQ}n{;4dM%ZuKz4xaK%tEwJd7CdiUJ@YVqw= zALNVw&-gm--4GwAAWXF~5g@>x>K=oH>Y8F$Sv&#KsYEv0=Z@8S@RN4;`>OH!m1$PF zMf{$;MvsI0YyNi~>@#r!j2G~J;n`uVlN=i}xYcQOFUw-q{p95q=6cQ6lv6MSkL8>O zJWcJVZ@1Uw%adJQbMcTybP4~4S>E(KEVj078ka2lM-ECjgC)f~fuJtx+o*`7?fs5T z1ITZ!uM&)5Ns)-3K?L$^UT8C{T#NlEYeE%5fDV>meVa2%_gEuR9F+ulI^sopVNhSG zR_zrn*Igy9I;vfjm+@ZWWXhm#ZN0nf#=qY16W_Z%m1@^0 zcHeaqy)3BS;RYGsu%X3$8&oBq=@!BZKeH7zr*kd|O?G!vsT*Wy)v~q3AwXwwuf*Krx#xqBb6Iz@2??y zvGH@!O~DK)FYhnwvSQEy7?eX^Kn@2~}e(PUt>vlovOZE~ZIE!p4(Z6ikC2EJO z+>9li|9t&PCM`Nmp7 zVZnp})9~J##%(2gv}^*DP!ZrvA1t?;$F9eUX&wh0F*m6KIDWN`$&I+>|FFq)gE&|c zmUtevST~55m3#t}3fxusXXKIkuhEU45;>mpU{op|pha8CuyDcEq*+U7K8%QMZz2`0)7~{4F*N?h$H}5zPd|pF%yFUQ^ab;U-ing_UeHGPEcILgn`JC+jaqh6=ZbIKRhf!eh`D4W5qAl)!W-OAZJ5viY4V) z{~QGbQMz+F4Vh36q#Zp%yk$<}IQw{mrdVE}grxTFlB0lI%D7nU^11fnN+bsfn$b^1 zn@^(z_`km!?p4D-zxhBNgx8S}U?yv0Uz1Xc?;cpdo%%geD%|Ybq~3&dIO28+?3^g+ z(NK5m^zZJEoGmCScHOUX`THYTFJmOJjVTff-N&W(GN|K80JZ%5rKHmp5DWzg6(`Oh;zKS@L zA@f*sU|2H0mwc-*?7w?Y(bz%gFj?iEZMpKZOiS|@cxx)ts~_?(EUlm=-+RnpM7@Wv z;KPzKG$QNGmyfOJJwZm$Mx@Ff0?L=TQh#)xioAGX+N!pwK`yF!R-0|;_Dbu&YFDVj zu@6*nv|k~G8w6o@Uw49+MPGEQSa*Qv+;E?qZ-o}Wa7vzZ{d}epHKOueE0Z}`DXht1 zI2v9$c3q1)S#6}OB%n3MfeZu`dvMs>V0UZ3@Zo|_UB822Urbou30-sJ$NxQ=xF6U} zrJynFS$JN&PC%^ea&0A7sVsh96TA)SI2CzPW$O19 zjdY8f2ZcR*Sr>Kf2tK9U)%hk@1F^L`Y<1YR;6V2$p>k{IrB=~0!?(J8iMuF+K0}fR zCRrf{u$lOWulfgRT!qE#c=;C_54bv_h}}_ACrC5!$RN-dn?x zn4~rtUs$Eg_HWP!`nWNY2Wc#h{K5CPAP$ESo@d>zPTz?2=G526TJO%a3$i**8(tvm z#i?a@!rj2RLi3)6C9@pTwi&X`L<8awG=RXP>F{sUn%XQ`BkIlcFUj}CPmTbp@yvr= zh`#4n_(4sd=gug!0Li2mqIziY6>Q4_vPQbJi;RX)= zg6|U@N#uktL4oKmX|s$gtL|Q0u3PVHQGHUO{CM1EQh@J;YBBg(1$7HJ;z@TuP1(?7 z%v3%BrBG88I0bfFWMoG(*OtHPU-Ft-dUT>zk_>34y|mo2b%&>|Q%#1%x?^$sL;Uyb z80Kod$W};4wXv_a!W2Cg;L3aQ=~I_@>a=)aIXoW%53!R+;(Ny0a&>Q@v(Z4LY7DGl5CvQC2_h%E79`a+2Pd3#Q9C+* zO0RLb2YG)rA%yi`losuSNLBFmp6Lh_FuWh_qj;yfN^{Om=AwFdWn8*HFJC!GS+E$T z>kGo+hf)mup~)$3YIl*_->bD!8)P3C2@k7KfaqYPG@R7cIJYn1(-dIt7F;HCDSbb} zYRQKmxghUN0WH!MKybG;(FAG0LNzW*IvN_dTP^<9>GP9`f)&fD^c9C%6o5nnEtLWm zl7sICWWi_B_&A+b<}@ndHq9t>i=nY3H}i+I8y$0aB_g7Z*!zM^j?#LTzQY-0uwnmm zzu|lGtzHrzy<&NxGk9>hYy7y=2D0eRUkcGW0Jc;EN$AiuYM%+;fPb1B@iH(# z22VQFQ})-o)G6*3qp)l{s>f~|Cg?Dc6WY1Zh<4G$VqvcEWh^*LKcqvc+LKxWgUAM4 zpV`_3mZ}JWEto_a)=OCqO}Q~)Rw$^g%2I8int@5UIi=Knlh2qN-ra9wPsdAjPRBsv zU&k-Xg;owvB3+T!rL!zoQL$V;#V@5Gv+WX-hsJiegv-C1A(cd+!QQu^lhR6j)>F}D zYljmkggtUSVruAkGt>89WbW*0p&qn#C&kX=;WyMON$<%LjkJc89B_||q%9Hl`7>y& zW?#9~ncjzl!XB!3=-1%Xk4uWotYe(5!l*W~wInHrWTSiVacM$iV~zCjJIfk}REK@5NDg_PMm0&HsCkXlhOr+JDzH z7k)+R&v#QB-_C2mPx+Wdz7W$~JO({PvEjY0j9`B1W7ae*66*Z0sf-~~#zsX3S5S3( zlC?r+fD#%zrBxn~i!m%F(=%O>hDJ;!W;L1ba#UYf@tSbIX|Kz_SBI)Qc1V6uX1NmI zR75De*%5=9vu@si;_z+Uv85!TB5SE)1oMhWGVsR9BFTebuU{B*kV6%}zQ0y(~6QJQwbx zz(7F{`L_QM8_V&6QxfA_y{Wfe+Tt2`#&!{!C!*lp+`KHp2-ppKs@Z6+J5UteP!g*A z{xUQo46p~X_1rK{C%f8@v{v`!;b&$^3kf*)S4LX>m0R#YQB4#oEMu}<&b?P`(qI8- z)|<@m3@dpJEJ+(a?;V#_YM;Z8sFXvSo_DgJ3jNkBwolMC%{C1Y!#(mNJJ`scdC4ZE z-4p^zl(u4d6##K=@*MnjKgSfI8)~2pa^U+IJeTSfG-J=!1As3Sr)_+o)U&danYIc zcSE=A>l7VNvMsjHEdNzr9K?F)8ySmvXDyyOdj|h=Q%3VHW@2iYS(Mdd<$OCQsCh>3 zb>kFXNC80Uvdxj6844!N;`F;JnzPtJha5~Hu;O9ybUIUKrJ!YmHBC5IUHH-pAJD%O zuz1}7r9=0{B#Qmw>02$;*9IqJp*@}hPxXDwkvSmm+t(*HQxco%Mh!)j*qxK>vSYE# z%m`qDNzzxG`Hi&;KW!eceC+9H?Lb*Vw7%3{(9kZ=Z?>kScc5|o2TV~xx{!)uk`-E*)M2V0eu*4G1E)xRN znE3Z%(NKx`O}!2Wl+YT>KnRCK1;TuYxpbfurs?o%XxN(NQ`A{_t=tw&4{t$w)fH3F zzUqq}1#Gh~*#J2|H|DQF>$j1qoQkerCoC|NxAhFKgP~e5*@~GX+G*n!wOKClbyi=} zQx#YH)-K#C*C;yv3im0rM>pLzKQ&SsF|cH@?%L5*el97;jP9H0^TXWLHwKw!Zp&Lz zCD41xWX`R{V!SSzc=~dUCL=B^;yGPDW}@I-+GS(emNb2fi*FwXtC+hgom?H6BZLJP zj|5U`v2JzBt+_*LJG>h(4mw%PqkSx%8MD`famj)p(4Zl=zo@nI9FqFeVs2@vN&jE^ zr)UU5DATj`)Em6Ny{n1MW#n0$_I2U~(H`*CSXNRYl4n~J5<|ovN`?8DJknO5hA*Y= zd8zO557Sx0$d2? z0!qgzJ#X3W%0A14{9ItTLQ<1%2IO;T@|g_mlc@Oe1(LxY#D%RTp0Wp zsRM*mI#OrO^!p!X<26dGR;vDJ7pM+G6vpX@t^rR;Fej>o;{D+x-X3MN1+Qz#qqqvxocTV_*NH> zYp_%Oc|cBR#_E*$;WHoD1iE zmQTYb14?)FUrft8p2fx0=oWfbr5wZRpFc4(Y1o9zZ@f{#4(KMYkv=h12E5e@kMD*o zS;Nsn$Xamfs;8vr4NjO=Y~e+(w*32}iroJFlnSZ*- z`g+@0pozt-{OKo!Z)W_C7;B_8`k<^ZE2$0_A;OB(#)g3Dh5UdFZ8 zqVxB*hc@bhICXI|h(G8m$6rlYI@g2Pw>!#xg#t456*bX1o6r0-x~bu869PC7i70~# zruAX6U(&3W5ed?)LTZ!^ETKC*R0iS*K#)qF;Y6D8 zP8dt!>?fU_Q9~g!1V!JWLwoZxqs}bKc9iw9dD)<4zX>#rdqsi=5@v5h#hw^1oe}($ zA3m$QgT*Js{0t2411P*#E^(Z^Je(F7(LdCp#*Vi>8|te#NZ5>ZpHIJMSbS)R%?=hC zFYhj@?_t79$(Va{={q5e-P%|$&5^UN9bH?(mmibMi&F$k{bg0UgDDvp?rKme6Kk2E zP(=x;Ri(B}d~ySW7%b7*8vF|tx^U^c<(eEk_P(oB!rQCpaaop5P#aV(wB6`{Y`LN9 z-Hl`SR%1(Fyz{mL-gY(;j%A$An4=D{o!7@B2YcvQ$jIX$dj?`rcAK{0Y&p*YhyIZ% zu<{J(a#v$bGqODqOXFfn{mxID=A=CxivIOk!JVHSof^Gp2c`o zh1GsN*1XNo)b{(!aQ9f+mP&Ob;H-M%PK48S> zpaxco+<7p0pV8p;^{16eqq#TPa~>+0&%b64uRlL8FDQJ6l)t_!{_vWaglsd_Loo*t zewc9b?yXi#v`G`|6x;bY{ch^!&0@6ukeAdnGb9211);%VbC6aeUKKtt(uNwiP97af z=mBKS_n-fkt6gH@8pHlNCC|0!8TVD3zs0dCIKD2=nkrEB5p$a?lN{9Azs}!GQhVVp zHxLq48WrVX(n}X|`*aG@oSE1X%Jq5eSp z@ZRM3QVM))3f7IgBW zP9!=6m&_lNDcouOuR+Y-Uj)Zw>i;l^^_!_{Rz5K?454IJdxG2f{1p0~I5Qq$)leXc zS&YW4>mnzBo|xngSK43fofIdz*O<75Guxaq{eCEn-qe`bH$42Ah)c2KXf>j!Z0EWR zWw9l{`U)@y6TBiFJ0xA@!I?azjcZ7x`RHo_Y_L-(umY;!z$v3ovVaC>L4tSXGgr_! z{LITenkS^+ZM2s^eg7D1>W@>0yJQ;YlPzOojOA}@OixU*3_U9c$O&H&6oLv)?1p3z z08qVw0f(ApS}%=PHaL6zKUsr{DtY?{>$30}5Tk^6o+N*Kk#)RBuxed=K0Q=!^YP_= z89CHciGa();Q<6~FCAP5&GrC_*6F2(MY*0NounAxyeO{vA@gx5n1TwJNnk?FxHKVw z_DS%PYp*!kJLXP{xz zzCoR?mm>n_C~Q`%JZ*Vt=bJB0ol>3{TrVfVVh}>8)=TP6m2AKXUAazicBUq!ET!b8 zSV;2vB+Hds@vz-=8N+9s=jN?-12|vS!^Qd4q45iNqCZh!%VLnwQS9f0^fnpcR=wK= zAMMe`ZvuM>i0#yeYk9m7@(d#=X5fy)88U*%tfW3OdLUf<-In)q@qe241k0GHNm~|K zTTlT{x*u%$c8PN>6Y4U4cK}Oh!c;pTR;p6BEz}G6(tyt4C2E|fb3hTLfPe5{gIfu} zXf7^eeQxfEFXrP4umvc_iJ`TkpcAI6mC8PrW;VTc?v=@@z6E8=-iEnsdSi>@LKiUm zM{BI`PCrB`$*@4EDmDDQ8yLPf-8mqJ5LkxE8QH_}PFJ|8Whj;1Uu0!#!6fI`I;vTz zggr!Or^U^uLGc16#a`K}K1dGzLAzNoKoi9@&2$SUosJyLC&GUp&?@sq$BVWHv|OPD zJCIDm3CKJJslwg1-`bw!B0+QaOt^@^ zNuGkq`HgNYNainF@}^hFM*@=wojVp0NUOFfH*TWzEHr{0(L%i;7aoK`$Cg=!|=`Im=fz!_8T^=rp&CTfwjJ3 z!VzNxCOSw(*XuzpdrosKBF{Qzo0hbdWIPf%l)wk|2nWVej9`ACOmV8_QrcW-Hwz3K>LotvCXim&HH-S?hD}1eet{Q2%4DvUiq@ zDH1bd8rX(4yV(KXU8`rkdlgmIaN_nls^0eup}`2 zhiHHgN{NArWU)5mF69#j7zIl6!nNy)Yj{BoYQf~C+0B(Qe>m7CQISW%C9_*!5iw8< z4_PBir!2QDiaCP6!Jr@kDI~kZDYx^(TWFvvokpByG%E?Mz^~^Wdgx$a>I*qAj!oUhxVoB!l@9mHPBc*b+>)gS=QO5}ZV-j8-hxg!fjFTs?;O`a1e9y^ zz=<@@G+#1&$my==Bx$vAdZVleDq{;5H5@EY(gK}}x^Z+h@~q!9dwb}8flL`9-?@2+ z-`WVfM*gwBTc$|t^)48ITkhhyu#8s~GW^^G*0_h6M0tv}hLHDnb3iGrB*E?RyQyJ> z#Fa}7)-D|cv6h3Fp`V=6bjJt|c^|*dh$wt76UZ{Y7_W-dlJ*B}Cg`Bya+9}iEW0p7 zUfkUkL8=;g+SWBWBNDItjdF1=whB@W%Kt2w?E9aJuViS|g{zL!qSfPV`rk|mCd@M< z=vg`@3ry2qm4xvAF32R~^Gy}>)zlV7;8p>%N_gPbWwYjL5)d4knt=-1&4mIK_Joaoq+ixx!1%dOL>pyj#!#QKM zzuN46>12zs>dnJhvP^G1#rH8bG8JQmM%*(r#sKr|2^@u9z?j(%7F{&h9$=*VVUfGQ z`~aEE<_K8!wV-C%`Ois2>as>{PG5z@3`w|_+a)?6K^s_+rTOY~C_&w`UaB3Hp34gQ z91F_C!cd;uVREdNoA@rS$)EWqDj}1+A*6-OdprxD+B0rfg3}#Pl$R?*`*=rT`4YcK zS+f=yZcfjS-3>Ck(nwg8x#cTPA%jRRFc&a<=M(} z`0W|&)~*Z&#%6e(X2Bsc!W9x#T6US*r4cVej}*_nw^17IpfrABM)fWGV|B*U#Uf)L4URdi^UZHqm0DXs^<(h|<{xIprFReEw>n3pVcB#Vjg}PO=-0#% zp$2Idy(!^OHzI7zW}6Nw@gibp(=zsVB>CK?yvyKnvPR_!eh|!uhrE)fgM7EdG^6rJ z5scI_pJH0T$d>ucO3h|h9fO!F(F0ko3l%n>xU3m z?X7)ITWTkXrlV_@!^iN44GVI_h%JVzQFQAJF^G^E|ym7o$!12pNHW+4)it0$>_m%wtWoQp3p zc4X84;O@bH8R=NUhe$A6$-NPR;_JlSF*2(f{DxvHc+fRCNgAMh89oBJPg+uQ)U%(D zjI4y)s}JY#?&T(0cTL(+UbdrJw@pFI^nGnWQZn^bozK81xqQ}@|!4I5u= zI``O))Q`2;SBd~;Yipx4mOA=q*O1J7X8{Piq$Sonv<_So{geMCg$+bvJ##w~5ckv{ zUGt05J*j55)A=}30Rb9dP{{LVV+=g_Fj{XHlE(jl=diXql8t^$69GnbhHgZPyIo7X zC>m!I{_*EEq^@w{_+>d)`W76!n3{XeIV@oVmDzz4{o2=CkGa)&fo!a9{Q-!h?NF^J zz4r7Teu-R6XV)^d+j0v3$A=fXAMrHV<6oQ_1-KlEKf6}eu{P=DOEPIsC3nQ>Z>B$tQkhD>~p z-}k2yssP?&vwa#sG&*bx$XW@7!p7;J^^a$yckb^O&zz`_Cya_?qd zH&;)9y{e-eY~hy{TR7Ja%xfos@ZpJ3{r~JMiaR%eI!?fNLrk;LAO4E`{JK(y?NuWT z^;y58>kn3g)g1OOJ3iIbjq-%Xl)C;A8Y+nJP4R&EvVcF8Ti(+%-K>BIFasOx&)v|T z<1$02z*|I{!dAwo=86Us*us~C+ehlpOosGEwwgwEl@BlF1O^RUO z;HrY=JLPbC3oC&6fpE{H@QB6fygKhvyfg>k5%P$X^`K}~Le;n-0^7Ye7Py;*BK(rm5vbZz;h>#PQ`H4>r$V7R5RC9qunEG*$U?P?^ zlV~=wy2$gyZdQ000vv3PzN{|R=jK~E#>dPWAa6mO>hiFQEupw0)5~LVAls41*ZpBf zIAdHBg>Xi0h|F*qw8(AG3?7WiP3GH6l}vd5+C=fF|Cy1S@+4V*wPu`7zi(zn<#%{& zKo3v1jEZ18tS&709anD7A=eH&J2gO}hxp!{>LLVNt539sEK*81Z=vk|FT)gibO$qW zy__QZTa2D-R}>%WvXlutok|9I{@8F}lw{!}IMYa$3?ywnJX&${A-0{ka+JGiur&=9crtlv`aBgP~XH?ICQTh$(knVk>h_|kP3 zddfQe;1EQBDGqT_2@X!n5OM1t*rNbod`)`5@+r*V*41S~_xokv#7DZ1Ws&CEM*FD_ zZQY`ye^{o>O2+I~{U0*|cvuqGD^UWt*W#!jr1~6#Gp^+G(1M z3lhRva}u6FrfUg~1+RL7gp#~Qs=xvzszH0DK`m`YhG z@>Pq;;^ZuWqhfhr%qKM50Pfh{*5(c7)_1jbZoR3G&oCdI;rjZnAybFWzLIw=wh@u$ zjF?Mlodz~uv21Q20mF6VxAfLb=CeA6V9^Q{hm-X{9($Uu7=tK}MkVD`eM_|f6{v$> zvje~?CUW$v$3lHtfy}x9KDoE!JNFs1C3fk5N!%d14V?KWhT;N`u3uL@_*)+^;Cqm^ zDD}05C1l~{yrPUGxB;W8wy#$?r^Z1S^Rzc$RzyuTZ`%{gM$mJz<32QxU2bGZdKcr5 zqwACzp1nuk%t-VEXC6%Y!p&y;?ly0DqTl3I=+X_^HoV#5nqXi5)iYhJFb5oAhSfyE zAUVrRhXsRX0PckFa!ynJmZ5PGX@u7MbPSM;xd~+vn$|IJ)y6U*KSZ}YHCmXfG+7)> zG;NkEqw4=WZG)D@K;_1gc$Gn4+zAf(s*#SK*!2e5Vrw^48kp5sy}|;RAtM;DvDG(R zy?)uC@+}2^4mwV79b~F$U`H&8#zSBtgY1FlVs0+6bomBvUt!?tx$>BE-Ihu zVGkbyH){neR(3f9zoC?8V$WCEbD;iSySf=0bpLfkZhA*odD>fdIqCT$4H!EnbTw8B zW?=8vTPQC^CVdwn&CVs-VH|12pQZlFY6DkfNtcBgYOFHxlGsB@ch+eS!rUjC-c9Yv zqu(s}T*ck=gw37ZU@B5wy%NP~Bo$k7WhQ93>!cD9-4x`7lbKnXJZv5t|A8MTeEw$H z?Bq0CGDE&?MBxY8o8D!z0#f4Ocjp%{8j$W)CLxN|np~cAf=ec<{pgSX zuQpF0_8n!&tWy8FWpCCjB^-$f5d^r7oAXZLtTH_Ysk^Wn8nZk$K^xLOXVGZ-ee0vH z`ug{;DgsO=d?3qRW-PFJ%qP=pHsmYo+x$3FgKVf(5Dxw$g`*h%2yrWwsCFq6Qlq4# zwt>}9q$R~6>RV>ybnN?IT+HU|2>OJyGL&+@w7W7v44kxjph*8xJIaWVaTx1SvuIy0 zF6PkQ3vqFcZDP-@f_~am{?n8JYr7>8z=#jXy_4qwHzCOlJook)t!lZH??-F0HDSQH zac4*SYX6kkP|gRdw1hbZ?GX_J{uU*Btu4AXh!%Dal3ovW=dVO3Muh&RypYax!uUI+ zK8ve0Ep#DdbHh!rr(XGS3O1UvuOwODqIVvSUK3Wq%Y(^E!FpddN7+?T2xti9fV1N% zZl`Lw`VMv6Pm*1DpB)=XM%g{nYlH9f@oBYW`R?V*ZKYFn5tM3@x>jd@fCWehoXZ^s zjTnc)KzfpWfEPF1l`n4dKd~xXo8Z(NEs&-!mZm5X?EQUAFtZoTeTDd;ko&9{(b8C< zbXx`#EYcK+gTI|T5n;^`iet`-4xvngNY+aad%Fqiv=v0u^TjD7rHCWgK4p8ezu+M_ zlQ9FNxWyj)mIUMl+0l4Zy>1B~;$+RpsiIrpz$r{n$|y_2zS8f{0a?_FsAEq6iNH;q|MOFX-h=8KS+a=DCWyUKMM@qPMbjahdN% zb{T&V!h9xVvQdeiB*a`t59Q%QPO(ySafU#y#L$5erAO;DK*30g*j5Nm3^L9Ppm}_v zP7K|)ErD;*ZPG7Z#>aHGdd)x#lx7Q1%-|8IR7XdsXUhjA0629*^&| z^y*UPp*_=I8=u)@ISk z^d7KVDPbPEXi9LtV2lL<&7dBv^v2!~p*JQafy$AuyFPM=2$1+@FLQNT5n{ zrm03(Uv=odU8{Hyws9h$JDV=;sm6XH^YQU2(GOY1iUbzCd|s_~UQnTf-y$@Q&{SoI z@|sKvL}BQE!g{#0g308*hVZHW?Qz+K1I_`}Ed`^$O9kVg(N~@>Y32dL7jO+u zb=Jf)`v+CxPctHhA^c^S1r|nLnhb(Ya1X=*U9TyBO4;WAI5d_2!MSoTKIGP;Wg)1e zcdRhX1BSkhC$fa5V7|NjmQUrJZYfw*m57o8x`E@R&*~%CUYtKH(77##4^TM1q!>i# zlk@vtI(H_N%UR_PsL44_w++~y@JYs6?wS*frWjD0S>x0XvdQeAK}gkD+%e}L3vkn! zQ{8y5;A~{ZMVxjjjE;E&Mj%pgk%wP1;Ap=u!y&yYPMjgg$~$5ZsWv{Ashu&wB&${p zn#RU(0y4lU#<0LC#!AFI{hdmgxZMctyzR$vr@&{58#t%3sw(;CPpL;d6mAy@G{z{Q zaw~>m{l4L~UkNu~lH%F1(YlBb(9tKY4zW0OJ##MDz+__Y!3!C81^=W(k|-#R;P{_l2^%0DP2O%99)3~?H8^QpgsTmku@CXG+_&@a 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 literal 0 HcmV?d00001 diff --git a/examples/example_image2.png b/examples/example_image2.png new file mode 100644 index 0000000000000000000000000000000000000000..b505869eef4d33b7fa1cdcbe1759520bb3d9afb5 GIT binary patch literal 10511 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 P;C0>${5&qt6D9p02bZog literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..836a19c --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module unbewohnte/d2d + +go 1.18 diff --git a/shapes/circle.go b/shapes/circle.go new file mode 100644 index 0000000..2f473d5 --- /dev/null +++ b/shapes/circle.go @@ -0,0 +1,85 @@ +/* +The MIT License (MIT) + +Copyright © 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +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. +*/ + +package shapes + +import ( + "image" + "image/color" + "image/draw" +) + +type Circle struct { + Center image.Point + Radius int +} + +func NewCircle(center image.Point, radius int) Circle { + if radius < 0 { + radius = -radius + } + + return Circle{ + Center: center, + Radius: radius, + } +} + +func (c *Circle) Draw(canvas draw.Image, color color.Color) { + var ( + x int = c.Radius - 1 + y int = 0 + dx int = 1 + dy int = 1 + err int = dx - (c.Radius * 2) + ) + + for x > y { + canvas.Set(c.Center.X+x, c.Center.Y+y, color) + canvas.Set(c.Center.X+y, c.Center.Y+x, color) + canvas.Set(c.Center.X-y, c.Center.Y+x, color) + canvas.Set(c.Center.X-x, c.Center.Y+y, color) + canvas.Set(c.Center.X-x, c.Center.Y-y, color) + canvas.Set(c.Center.X-y, c.Center.Y-x, color) + canvas.Set(c.Center.X+y, c.Center.Y-x, color) + canvas.Set(c.Center.X+x, c.Center.Y-y, color) + + if err <= 0 { + y++ + err += dy + dy += 2 + } + if err > 0 { + x-- + dx += 2 + err += dx - (c.Radius * 2) + } + } +} + +func (c *Circle) DrawFilled(canvas draw.Image, color color.Color) { + c.Draw(canvas, color) + var ( + dx int + dy int + ) + + for y := c.Center.Y - c.Radius; y < c.Center.Y+c.Radius; y++ { + dy = y - c.Center.Y + for x := c.Center.X - c.Radius; x < c.Center.X+c.Radius; x++ { + dx = x - c.Center.X + if (dx*dx + dy*dy) < (c.Radius * c.Radius) { + canvas.Set(x, y, color) + } + } + } +} diff --git a/shapes/line.go b/shapes/line.go new file mode 100644 index 0000000..db94b8b --- /dev/null +++ b/shapes/line.go @@ -0,0 +1,96 @@ +/* +The MIT License (MIT) + +Copyright © 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +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. +*/ + +package shapes + +import ( + "image" + "image/color" + "image/draw" +) + +type Line struct { + Start image.Point + End image.Point +} + +func NewLine(start image.Point, end image.Point) Line { + return Line{ + Start: start, + End: end, + } +} + +func (l *Line) Draw(canvas draw.Image, color color.Color) { + var ( + x0 = l.Start.X + x1 = l.End.X + y0 = l.Start.Y + y1 = l.End.Y + dx int = x1 - x0 + sx int + dy int = y1 - y0 + sy int + err int + e2 int + ) + + // abs dx + if dx < 0 { + dx = -dx + } + + // -abs dy + if dy < 0 { + dy = -dy + } + dy = -dy + + // error + err = dx + dy + + // sx + if x0 < x1 { + sx = 1 + } else { + sx = -1 + } + + //sy + if y0 < y1 { + sy = 1 + } else { + sy = -1 + } + + for { + canvas.Set(x0, y0, color) + if x0 == x1 && y0 == y1 { + break + } + e2 = 2 * err + if e2 >= dy { + if x0 == x1 { + break + } + err = err + dy + x0 = x0 + sx + } + if e2 <= dx { + if y0 == y1 { + break + } + err = err + dx + y0 = y0 + sy + } + } +} diff --git a/shapes/rectangle.go b/shapes/rectangle.go new file mode 100644 index 0000000..6b263c7 --- /dev/null +++ b/shapes/rectangle.go @@ -0,0 +1,52 @@ +/* +The MIT License (MIT) + +Copyright © 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +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. +*/ + +package shapes + +import ( + "image" + "image/color" + "image/draw" +) + +type Rectangle struct { + UpperLeft image.Point + BottomRight image.Point +} + +func NewRectangle(upperLeft image.Point, bottomRight image.Point) Rectangle { + return Rectangle{ + UpperLeft: upperLeft, + BottomRight: bottomRight, + } +} + +func (r *Rectangle) Draw(canvas draw.Image, color color.Color) { + for y := r.UpperLeft.Y; y <= r.BottomRight.Y; y++ { + canvas.Set(r.UpperLeft.X, y, color) + canvas.Set(r.BottomRight.X, y, color) + } + + for x := r.UpperLeft.X; x <= r.BottomRight.X; x++ { + canvas.Set(x, r.UpperLeft.Y, color) + canvas.Set(x, r.BottomRight.Y, color) + } +} + +func (r *Rectangle) DrawFilled(canvas draw.Image, color color.Color) { + for y := 0; y < r.BottomRight.Y-r.UpperLeft.Y; y++ { + for x := 0; x < r.BottomRight.X-r.UpperLeft.X; x++ { + canvas.Set(x+r.UpperLeft.X, y+r.UpperLeft.Y, color) + } + } + +} diff --git a/shapes/triangle.go b/shapes/triangle.go new file mode 100644 index 0000000..59849b6 --- /dev/null +++ b/shapes/triangle.go @@ -0,0 +1,100 @@ +/* +The MIT License (MIT) + +Copyright © 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +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. +*/ + +package shapes + +import ( + "image" + "image/color" + "image/draw" + "math" +) + +type Triangle struct { + p0 image.Point + p1 image.Point + p2 image.Point +} + +func NewTriangle(p0 image.Point, p1 image.Point, p2 image.Point) Triangle { + return Triangle{ + p0: p0, + p1: p1, + p2: p2, + } +} + +func (t *Triangle) Draw(canvas draw.Image, color color.Color) { + line0 := NewLine(t.p0, t.p1) + line0.Draw(canvas, color) + + line1 := NewLine(t.p1, t.p2) + line1.Draw(canvas, color) + + line11 := NewLine(t.p2, t.p1) + line11.Draw(canvas, color) + + line2 := NewLine(t.p2, t.p0) + line2.Draw(canvas, color) +} + +func max(a int, b int) int { + if a > b { + return a + } + + return b +} + +func min(a int, b int) int { + if a < b { + return a + } + + return b +} + +func area(p0 image.Point, p1 image.Point, p2 image.Point) float32 { + return float32( + math.Abs( + float64((p0.X*(p1.Y-p2.Y) + p1.X*(p2.Y-p0.Y) + p2.X*(p0.Y-p1.Y))) / 2.0, + ), + ) +} + +func (t *Triangle) isPointInside(pt image.Point) bool { + var ( + a = area(t.p0, t.p1, t.p2) + a1 = area(pt, t.p1, t.p2) + a2 = area(t.p0, pt, t.p2) + a3 = area(t.p0, t.p1, pt) + ) + + return (a == a1+a2+a3) +} + +func (t *Triangle) DrawFilled(canvas draw.Image, color color.Color) { + t.Draw(canvas, color) + + maxX := max(t.p0.X, max(t.p1.X, t.p2.X)) + maxY := max(t.p0.Y, max(t.p1.Y, t.p2.Y)) + minX := min(t.p0.X, min(t.p1.X, t.p2.X)) + minY := min(t.p0.Y, min(t.p1.Y, t.p2.Y)) + + for y := minY; y < maxY; y++ { + for x := minX; x < maxX; x++ { + if t.isPointInside(image.Pt(x, y)) { + canvas.Set(x, y, color) + } + } + } +}