From b6b8610331dbc816f5b692aa73c788b1a596f4aa Mon Sep 17 00:00:00 2001 From: aarya-16 <145715221+aarya-16@users.noreply.github.com> Date: Sat, 1 Nov 2025 03:26:13 +0530 Subject: [PATCH 1/2] Add files via upload --- Hirst Spot Painting Generator/README.md | 93 ++++++++++++++++++ .../hirst-painting.jpg | Bin 0 -> 31603 bytes Hirst Spot Painting Generator/main.py | 67 +++++++++++++ Hirst Spot Painting Generator/palette.py | 80 +++++++++++++++ Hirst Spot Painting Generator/renderer.py | 76 ++++++++++++++ .../requirements.txt | 2 + 6 files changed, 318 insertions(+) create mode 100644 Hirst Spot Painting Generator/README.md create mode 100644 Hirst Spot Painting Generator/hirst-painting.jpg create mode 100644 Hirst Spot Painting Generator/main.py create mode 100644 Hirst Spot Painting Generator/palette.py create mode 100644 Hirst Spot Painting Generator/renderer.py create mode 100644 Hirst Spot Painting Generator/requirements.txt diff --git a/Hirst Spot Painting Generator/README.md b/Hirst Spot Painting Generator/README.md new file mode 100644 index 0000000..74f95e7 --- /dev/null +++ b/Hirst Spot Painting Generator/README.md @@ -0,0 +1,93 @@ +# Hirst Spot Painting Generator + +A small Python project that generates Hirst-style spot paintings. The code supports both an interactive Turtle-based renderer and a headless Pillow renderer that can export PNG images. + +Quick overview +- Interactive renderer: opens a Turtle window and draws a grid of colored dots. +- Headless renderer: creates PNG images using Pillow (no GUI required). +- Palette extraction: uses `colorgram.py` when available, falls back to Pillow's adaptive quantization, and finally a built-in palette. + +Features +- Generate spot paintings with configurable rows/columns, dot size, spacing and background color. +- Export to PNG (`--export`) for headless workflows. +- Deterministic output with `--seed`. +- Safe dry-run (`--no-window`) to print configuration without opening a GUI. + +Files of interest +- `main.py` — CLI entrypoint (flags: `--rows`, `--cols`, `--dot-size`, `--spacing`, `--bg-color`, `--image`, `--export`, `--no-window`, `--seed`). +- `palette.py` — palette extraction utilities (tries colorgram -> Pillow -> built-in fallback). +- `renderer.py` — rendering backends (Turtle interactive and Pillow PNG export). + +Requirements +- Python 3.8+ (virtualenv recommended) +- Pillow (for PNG export and palette fallback) +- colorgram.py (optional; install only if you prefer it) + +Install +1. Create and activate a virtual environment (PowerShell example): + +```powershell +python -m venv venv +& "${PWD}\venv\Scripts\Activate.ps1" +``` + +2. Install dependencies: + +```powershell +pip install -r requirements.txt +``` + +Basic usage examples (PowerShell) + +- Dry-run (prints configuration and palette, no GUI): + +```powershell +python .\main.py --no-window +``` + +- Interactive Turtle window: + +```powershell +python .\main.py +``` + +- Export a PNG (headless). Requires Pillow in your venv: + +```powershell +python .\main.py --export my_painting.png --no-window +``` + +- Reproducible output with a seed: + +```powershell +python .\main.py --export seed_painting.png --seed 42 --no-window +``` + +Changing layout +- Example: 12 rows, 8 columns, larger dots and more spacing: + +```powershell +python .\main.py --rows 12 --cols 8 --dot-size 24 --spacing 60 --export big.png --no-window +``` + +Notes and troubleshooting +- If PNG export fails with an error about Pillow, run: + +```powershell +pip install pillow +``` + +- If you want to use `colorgram.py` for palette extraction, install it in the venv: + +```powershell +pip install colorgram.py +``` + +Development notes +- The project is modular: `palette.py` encapsulates color extraction strategies and `renderer.py` contains both interactive and headless renderers. This makes it easy to extend or replace backends. +- Recommended next steps: add a small test suite that validates PNG export and palette extraction, or add a simple GUI to tweak parameters. + +License +- This repo doesn't include an explicit license. If you plan to publish, consider adding an appropriate LICENSE file. + +Enjoy creating art! \ No newline at end of file diff --git a/Hirst Spot Painting Generator/hirst-painting.jpg b/Hirst Spot Painting Generator/hirst-painting.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6cb98d58ed3a265c3c4ca39ed524871a85315bf9 GIT binary patch literal 31603 zcmb5VcQjmI*fxBSh!9ai1W^)1C&B0~5`PR{@BKhyXRh z2e_U9UI3&wZj#=-K}vd)^wup>GIDBi^4qt`>F!e9p=O|a@PL8t{(UAEE;c4+j)(W} zvkS0uJmKc$<7H$Md@9KEl#7R#=f9H>-MV#)oQ(V)Ir%*vru$4h|G&%iFMx)OsD*@` zgy=CqOhZIMLv;NIU?G?a5ZxsF{NF-EOhPb}?8a?EwHh@*OhiI_gXG37!XR!C6Ws&| z)i-EvJ|d-k_WYKv)xF2AAIa!M;&V!WbBKy5LEPv$-@03W3K^Wc&jos+w^Q~~KcQup za3<#e?dSjYLfH84OoW~Yl^~k`x*;JZxkYk=nB;$K`9Iw}qkT^LzrDog4AO~|a{Qj# zp;r=x?B2f~2ksD>64Q{-01Ck2O_zCvwdS7Bm0q>iK;AVVN_V6zkSH`uOm_37@LP4F z8&y0cIqLs%iRt7zCy9e20Wx}800<_SAwmrly#byo1(OmHZW#M5MSwGmgGCWw2ewE6 zdLWYQk@_T{uk(=|xc!puPIRz{;%xvReE(mCo$xP>5eOC`7=N4df2)W^=)VS&=?0UL z{3BNU%gzZ9Js?zU5CecBIdCJ~N;xPa{Tg_wcRrS_M{e2Pe_3<&J_*w1G9N@3NJnY% z|8D#Vr_t4=(FN#986eM}%goMAI?a(?0e-`W*0*II!0WomyN&~1@hkn$_^VF zP16cItW)~lWqH2rH}jO;M4y!W8HL&MJ5-I(O^nl_c`J9_I(G+lxubY+2KS5bK|hdW zV%Gr4+lt6d+u7BeIsP?O!8Qef=HF{(v+^jNo#trjLPI+5lp*o8h5&~dA^!6p$Ceyb zPFuWY11O8>T)qMLa_H<{Zo^=R#W?a-WE5$_Ik>hoRm*!pR=$7PkzMJ>?@%3p^E*4) zjm~{M!$=)0#r(y)iM2gXs1MuPbYr!Dsk8Smj2FLVGc&OCX<#)Tbp2z*>NY^iMg&O*~`f1?Tc zPdDROoKz&s)_C}x3T1)aw0-%U+;N}pKn<9~_lBhhpS*fKs`Zd(h8zqV4@(#ivRNnkcLh5GHW_iGZFt7T+ zLcOnyZu`?W1M0Dj7H$`Ho|K}N{WR8M+@a|*1%^SuXF!s73Xx%v>Q!asx7_(Sw z>yiRo3ij>yhUtydt7i9WU^#U%7Tg@;apt!6DX9@Mu{A7QXk6LU!SPp|Pa5g1Fz$x6 zydw%LuSdC4xkH6Y1GG}o=lKOS%;}I{eB`pb%g>U)(K+?O20sygd6Rmz6&QMH7_|FY!ZU}# zOztm_k(~YcccN3iP*T^ja}mG!PE4Y*hYw4}m=<#5i4MfK?m4p#2bs>7_X5a}DZb zR;y^b>|J@jyYktoV9(K0+$4)g?#I|U;gxz%Xc!J{ol`D(2zqwd|$!8Ar*fgfLUdH8tP0ou6T@Q||*8tMn*I>1# z{yfc(!oK^gzVnQ$dC~{o=#UD+8`hxtD)H)luqT&m>WAGj{2dcxxcP2=@qO_4Xr?eX zcl`y;h`H}pf^>k;r!dy0jf0sxV@lI~prB2;iGz*klr`bU2aow37eX9jV;H&%w>&LB zUFaEsanMz-WV52;^s{Es{xb9^+pW{gcTGpZPVB1tRk^zoXiZY> z4_08#&xP;ab_Zgk-?9kRtjD1=-gXw0Ps=TS_k}fn$+|Y zREU6S{;uWI5XKQvSxbP56-k-UL@&UtWV&+6u+ykA$>_jwbHy z%lXWC-4}W~C(Uq3XYHc_4#RSc7a4B)iVdG;DQIb?ypD8r0xf&F3?>}q!Z5yVK~&Oo z#k3vjdBYb>=N~gsh93%Y#O)Me^#(S6E~Vy)$Lf^qXza!JRDyl+E17HzeY0)rtwE{P5)l22|AMKA z6}X-#=zVQ58;IIBmh5juH$xNL%28PptRb_GV~5zv#0eJ7VSj~Uqk-1CPvffC$j|z| zQsKaj;3HK_@2G+<1N2(bBKC38I@d*iSy0-)S8G|EXz7OMLA=P`&D_*(8(ZVn0-u0b z)3$2&9NR(4x@mcp8P+VH3I08ijh*@p>xWH^(Ld`N(mj0iL}Rzz1)K&07R^14Ma;|T z3?mtPBySlcOZRK1_ew46?_G6o#r{$SuhM6-yc^QJ2KGAkWxg<_|K;iDuf+%6ZOHWF z2D`{nQOp9Qk4A!l8>us_1c}BqCNDqD5;r~l zY8VIhrfg3>ynIp|kH#IM#Fkx}F09&(zp!IhnvJ+&8zODM!WW;x3^DIv zBwxcOOYuIR`#$1n$6HZUI1r{hsDz<-c08I;x=*((3f9P zYN;J){rOJvP6yyP0btp2{!XT$-g3G_csEDsj7$osov&_3v)&@Sqfj-TPD&rqvv}N- zd%1iKpqn-}32!5oo-JAB;Z4(HL62$swgYE(pKIU*k^du$Y2_$Bb}V6S)kOf!GX&Y{ zc3aFq=XYsRY~(gS>sb)5^ZkzGyfW{ZyPtBtP@AGN7>$ZZ6Fb`K8$OfYtvIoZgxaTy zIKIR=@VSPTM>0@tRAvpWUVeZ%o=Zk9?{~P>m49EbNOw)7XTiv;48v7b$bqTE)6?&#a*H>qKA39$G!2N|f3Tv0qZ7!=E2cvik!sJw=aQ?^G zGp&Zl%q_BG3o8!|=Y<|)T8}^`gB7QHhPr-cM&|Czh+$SA>0$^tw|rkSCrmoAs1d0O zPiD?QuJDRq1CZPl@d-Ac)sd>fs$8^c_2lw84)$)b2N7$r+Aw458T653ckGD9JMd_` z^y_Q+OAg(J!Ab|FtTb1C$8oBxOtr@JZAPEA<5x%AfU6v<(43~AMWjxqLAM3m=Llvv z6gP7s=G~~Sj7jv_9@w`0v3Y^Bcs%?t1C0VN^UFNhXE@$GW)k!q0rB|R_5?+zodwu2 zW%cg#T@ukPXw8u;B@l(NlsH@i1vEivX)(eJTvy{4V`R?^njLnBQxBhsV$NqygY>jZ z1sHBV(TIGmnZQmo$SOakkNk5MfREBI7Hov4I@@={j>TY6+6ysttl7I1;Z|+35{7-U zr46~4n2H%_asS|fY&Xowq7PqLImJ6rqG6VZlFKk2UNcybw&<^u6K<6lsHxC*-+uAl z*YjOM-xxo?=|+!x)07r^_;E+x`jf`wO3nHSKZmDDcr*PNNo6l5xbf0KK;PJoUML@~ z&g?}##1)|qvSSpk<{m+{mI;_os!S&)HL2W122fD=7^W}88I1)s#x7T&HU#a5u7TZ_ zXXo*l1m!n1dy;v3{SW)RtVwPH)DtJ+ZFt}N$TwLmy^R{0^Yeo>W%A#{%YJ$@4i`G< zPGF;AOelxYq@_Gv!jyqGU4#)y%o}~PXNn)V0ytimv@sdn*4AwFA1$B|nl90XR_}E; zC!S;~JXqo_Qg`{XQqq$Mulcd2m1hf9uk7WuE87Fr24LsUx)D>0!%1>^97**Ha>i%| zvDpNO&(_SA2Nh%IPVR;&2I4u64+(yCoLfF~8n~ZV!NPhCe1>TT z2&;Zy@iEvM9s9H`<-_Sm@k!EQCQQwJ1Tb4v-(S#h%r$*AMadkgi_y?YRXt(qecLuOre7T==(KBEGFzNc$C>LoBnoNd z)yZGOu8*CZP%@jd$@pna$Yf|9cW@dla!2|Ike2=shp8>$vyZH-_ye7MpK6}k}uII1) z{jRtjD@)6E<-f4J>qz8d5F7SDohhhTR_CMp1qM$-v{G{H=cp#DEkn@kO%`c{;L^#I z>RGwr91w`tqr;O7yL;c^32#r zP`5eVdC>`Z?b79=6y|Oy%b= z-ft1kYgg=J`ND8yx)RsR?581G;%;b`lyh@iz4CkT&IgG%X=ej`N&7ZkPnFC4%fd5gqcCnk&>jSwC)Dn1=C72~n4 zO`m#PBi8!ZTHU2bessK$fAefov_HD#jvY#A7F&(vJEWI(42(Ha_JD#baFPPmEsrWv zjO*-O9g@|X61*)rJhZLB(+LO_v4tUi(mX1+q4$|FnKE8IhSvZ?{du_o#N5q|2NQZDE$|Am+(xP4+AE8O_(gJ*00DH?QE%(d5y}=LTMAdj_u; zVp_qDFDfg{b|l&0$o3itQt4-Olm;DrEHqn#FV$CJd1N&t|L_Un*w0a`&hH(0U*zmX za>kSvJHO9OxOjy~nqNdzYYX@=BvRz2n+g<>wTgC_ZHGwa{#I^JUy6NtY!Tjw%-!B{ z!U$qlEbtXPExayiVlmP1BwCQHmASy>(6QZrI_m)ZwPbEqxzm&Inq}=jO z{)PME>k7 zqAbzJiEetXJ}*H@-faK+LNY7W2%gVP9>4ZQxndxeDKOxt=gJVpM~Bt3O=qs*d?~0z z*>c^Y@mZtuUeH=%DI8fDAhqjzNr&6-?bABB)%d?jV!5rp(%DdZED;=fmJN z#pu>0?A2_Zt#A9L>`?}$PH=E~M!8!`CYBTX&{g}$aL{|JY-ZIXGc0pD$4{wS3(0pp zDzb>w;1}#3?ewzyc_BIH;Qy$_(AOj(^N$|d)xaauH~_V?(L@?$Q5z!@WIb*9I~_IT zj^32vNE8arl`IeV6s$;*NUnXcsk{VMNEdmAxNr*E{B+UsD5cP1#3J4@?UM-le9iZ_ z!k^!ycj}Xbz6V>hz2qUvu#WcTR-Z5MAFVLw|68Plj~$NdI7>xHN$Hw#^T_8nh-@<9 zc(nA90_t9>N)CZkj#Z~;VL0Tbd||rS(ivg(y4k=p5697F@=H@RpiC7$spfRW|BS_a zND*lYzhdQYj{Y$2($9vMt`5E6EFH$Tub7uyJu;zs_5;za?r-E?zSbf(_n?LES!7G( zYJAqM7vz;bBLN1iTA-!FwvDs(ZsCn4GWr`7PuQ&hfbzy|(o7TALa+{+tc%vHEILKc z;uW6t=?=FVb{)JDA%sF0C1otBrjF|*Wy2mWD{#lXsEL2>jO5XOozS_2Z@RH-!^P8H zm1xKh13`4V&tukAb|@f)Jl$p^`8v7dZYVpgE{mpv<_+J6xr=Qkup9d!u{#mBRvWl% zHY-dehn71k3b}hBsAKi5zLza`LPVbFzE%XD>kzQPU|^^=hkU0^ZKZo)!U3wMbuRs- zxXWB?Qa%r%Gq6#~fU{sdhL2qX*=cU{Tz45uG2#m{HNsKykCVFFLkg$_oB@O`?o!vh^_16>-i~U#H()vy>b-9PPu$-=l(c0f&0*w* zY~>=B9^|@F3agb5KVE>UQesxP(-hKbKcNjXt^s%6${~SGHUn~~g8Vh`0T4`QCtx2$ zKnt`g`ZuuNC%J=Cnuk{kE!_D6xO1EtPGWShd#DL_a8)T%~b%H!s zazDQH#1_bQIy-HGx+l~Q^s=V4-TQo9f=wDV;(b%fp-Q*6^7Y)k5Bg{({ORYmB3fux zhF3K(iz(b*=KK1otrbQ;e)lM%T)^o)y2{JtK7Oo`<;VUh-5b{Q;WkA6BLJx)V3P*q zC9Cm$b%jH;%Rxl1X7e&^%D#z5#c;?BWGM`DEc@CHc`t=r1K$>bWg*!|d84T&QE|UF zk2?d*uqxe}>SzH!sIo9uy@ZUPug9Zqu;d}=cj<72jG)YP4wYZ;)-}-a_Xo^Y-k3&F zeCa2^2>>_j9xlk=88~Tg5rnuA5TL0|zG}2Aeje@gey&{J(lBlxq$Ie-oOjdIrZu<|AU;KNQ2 z5e_k&Ltjw_UbgC8J0As&BA}yB)^(fcf$sQO$ypA=u`OejwdM(d4X_E)`D~Ma4LAy` z3HP)y8U|ItH@lie+5UbZ5eO!`$r~y13&<5wB>n-A%Y>>B={)8Eh`-HdLjd^y zDJ62>1mMt5M~((*?y8)w{ho6n&a=VH>m=hX)GJFRp>spfnnXAnI#i z;!-Vytm!-PSDTOIb;0_s&Y%ShLn!ZpZZY1xX8C@SD_??z_eP^JV&0CSHN@K)xu|a! zWPZ+X;O&uM~cSt z8tR*WPe@nC`u0LQq0&xB4BJw&Ctj1arbV7#n7Jk|?ERF^urnG}cjQd9{R-dbG_A9& z{yr^y9qS?`0hg&0OC=O*te>qEH49n&)CiR8iq>K&HXDGb1n!{JS5U$v>cW&b=;W;i^lO`W( zEwUVUywb_yO&K`^764wtbyxTJ7i{HfNXLUMH2i=9Q^57oTO!R(?Q~otV#YnQO_0@h zCxX5h#P-d*E#+pTd>(3d6iZ>y4#_p{TE%N?M`6^L<{r2m3)G<@_gO?eu(I`YTo%QsXq$eGF~o9)td#qdfZ8z|_Y3 z5}sp^at+5hoM=Al`4a50e@QW8XmO$7SuEU_HJj(4j{hV`-|yb1lLeJ|Jk^CzeGK-# zgUQHvuAJ+XFCuxv+flo*cG$&!8hW_m1XD5_Kx@tAHH%%uS)BN^(bd$4ZNhlCnB%6* z^ej6K1h34m-VKecPY9*C#WGtA1TB;rM)gV@1*&objCNv#)r~&QPuCgRi4Q^K4$@28 zjo^_iYb{ynL_ZY;@d5Vn3yocd7qgV|ph}0bLN9~BmabS9lcpMi2d&)YJ$tbmh%|9j z3CsH8c!7RySTI_Y)x8*7FmE0Nnb-2&HX2z!_p3*xRS!{AJg!&v_+6>Ba$7+DR8Rvk z$B4y@hzf!I*ckj3!z>+6vNpFMo-N0pxy?lTfqliH7C9cYiL5{jc`Qwf#=Ubv zg~;+OL=}_Fult=Da9=T+=pqADbjvJF66v0r|egCtiEUJyH4s; z19OfV85LG!qxG%lPM6>O>n{K3!$W{vS0d7`Ypf?aVAy%kD^S~su}zs zQiXaWG=9pyjeV%bkxJ9ESl?K5z7bk(UwX*rLaVmHMffpqM^N=Kd=0F(l$8iYnHqQG zWf-;X^>%fe;5^O-(RSz=Q!VGlOdr@j-V;S~GJ$?Q5>`$3e8zZ57^B1odebm&svgL# z_2OY|cmJXtSktq*_2PBt(p^S_k!&X~*2tfhtn15u>EPxnoIt+C+A(#BlZosB{`sSP^9FPmzUU{+djF>75Rw^iW859Cm$bG@(T{_nN0fq%p3(T7bBj!>G6(a5~+#YH<4JVOEHWUrzPi-0Vq*Jv2&~I1#7L zAuFcDM^HQHPn%oXr$XCg(~r{rhzYGQdC?rvTXYLgrT)%%EY)Yx4$a%GKe!*r-dK^k z?Pj4Jv=_RnWF9+cNt@vn2I6*CA0J6^7`9k<@lowwbYopXy`I)<%wak>39?&sMBO5t z?*c^00f3e&O1G(M_&t7V#!M-!QgYeg$!-Wf0|BN@T19%lw~nRt^XqqxlgaPQouZ|i4)rt&;9zS2H!)`YwZ_=ZoM|K_r@tU{56 zy>F(>rnrCQk4t<|x3O1r{M!X^XbNc2&=50A|9}7w?#F zqA+F@A9kaLbJyo%?M?oBQLczcG3cC}+%%aC9IFGEVaqT9z) z_D-5Q{k6af^E^AFRDF2a;;q6X!`)O>@w#SZ>^^d;%`pmoxQMRNrMIQFpfEXF>JW^}MDmGD1&tT)U`CT7ke~*zf08RLt(tN;;HhTf9mz&1 z@5^g>o>px_#S_lG0|F>hB?BXFm7L@u8&#d`x|S`PH0P$$^z(Uz(9Go{#qP=E(17Ji zN@pL7@g$S|DDhXR-27BDc%4*8(M$(rz?}%C^bNOl#{#3LZbXqy^7u@N z@MvWsCL|?ZrRE%An7IUpwZh^P79L&);4HPeX3-gu9({(Q8Y-W}w5w@|Y_z^gxPtXa*EabTBXqH=4>MCrq!>#@nj-SU8Xgn_C2 zkg=Tm3tf`{q1b1~+sCXW-(TY;HmWYV%0~WHIeqOmJqV1L$+5rL@$R=@ z$%*+lj;vW<>be&hJEq-FB%tbFouTjoA{8y(y8rr7S%sCZYKm@8-eT3E+`??krIuz( zEMsl)X~88#Y$D*)G;Y;f8MR@_*PUAb0GTqe>PMK9YBBlC&0T#&;$9cJKyT^>VT!tU z-`8u_M)*Z@y1k93PmL!2@eRSQxQ^Y8h=i@U&MVOBqqA(ic>Jc4AeuX><=HPf17%k$ z#Y$*g3VT5>bam@;cgtG{=r{aAlY!!ys`{KyyM$ZRQin_FXdIqK&pIPF2&F`;+Ke3C@;ZfG=C*2zRYLW7hgYE(?v`#Gw zA_yB-J67^hKVy9|YtHAp@?iB0#^YX4{nWj~UU^^rg7(!>*IH@%`zky7`A%gVlD?1a z1R&vA3$HJY0iq=Mw_s_?5v!}}fxVw=9o^>!x zL)HOWg-xtlKF{~2?15162cPIt?2gVae)e#YQ~_J=A^s;S^w#|vy8BvzH|d#m17t(O zv59D@-mN8W4y`nGx6Zt3?K|HlcciLj8au0gnMXI$+}|iHCcn)m5zCg3)A$;|1Q8Fb zty`rIGDZt^v7zgC7IQ15<>Z6j%ciU^j5Va6JkqvPDFQySGwEkv&4HFQ6w*N-phK4D zVMf=$U4?WqQNF^XJ;KuWTXDGF11#|0c zw0li-%^hBxesA>bI$^b3G&#;EW zvd<7>X9%XoX3IuaaEapRf-T#ySzup(_XVr2`{HjAcuc@UX*PtLE9_Jt%nO zM?zqYUp5yiLe|t>Uc;Aik$J4Em_4<(oN7!vnxzX{w8V-ulsrL1a>W(~aa7Ba%;Kxh z{&>e;WeX~W->qFaAs6YLA+mIk)Z(4Dd=Qh6mg|{yWgJE?q4=7!Xd2L6fz`>qE1pg3B_a|6i z-HG~}U$9SgN9-+h?mE3>1ehR-Y%SqRk2N@lH%=U+t|aCkdAIhXx1tJtCBzST%!a~D zM8Tq=Bl?MWxU@@D17&$VhCk~;y`kXNrXx2W!8 z+q=w5*H2}St3>qS-W_e3QLx$V2LlBQ6#NdmeRSwaW#K=Q9xfy`k$6`EKTumSyn1}x zSTyrnu=(CiQv(+kfi)E7wUV53h3jtrL}F2qKyf9-xuXmdZ3)c6ENShz^TZ{U*@gZyX=B(AP$(x7|0k^K$o@49bZJKox*wi0;7N3`DOqn%J zEv>gT6GKN2w@)AHA}T?LE;~A;%T~PvsABvC@v?j#%+@@3DCs_K0r6OgEtF$A)TUZ= z&J!TnUyrL%Fqn;fjLyaHB(yq*^(ol7YRiD-Yw-1=F@3zM_D$9YFQO!^RpuM{Oahy& zH1@`}l8%ztGHj}&PFU{!u~hK}S1h&4KIao-?ywFHn3L9PSqL-4_EUQDYvVu#`WKXPIOV-6N)Qfa7fI?~3-=c% zHd5W2o#f*@0*YNE!z*&8bie8@eOYiFu1Be})>xW2Y~gC(kcC-V1$f<0^gi@A-49 zhaenIv#z+(s2(8x@e80PbDicNLc++VOk3d?(Dri}{2HkJ+ZnU>)?!Sr5y40A9b~s` zaJxo67pCv!^wwCDQm%B_C~MvM?+U!?Bh-KDWhT>hptsiv!n&~KI7+?`dHYnh!l!X1 zyu@CcI?l|0boQUpJgsR1^BqIeFA$3@@y$4e7XiqYQMZoDLKT`T=c1X4-n-*n4!Pwf zWox{c)ZaAFxx)1?NXY}$-m4p1&^DiJ1&TGd%Ftrx$2f;5a^WJ5MVHo_a!UDr5BZf|Ag z{K7b&ok7OpJc5}Al8menh@X)a30>=V1m-=<}@TAL8>@$&Y}{=C8&WPZ-H-WAC+GN zrQ;dGtxPVIV|CI2+?(9za*0WJ;5+|S&JJp;V z`;@wXrd`BCji6EP`qbX^933%QXKjJ~&OQUq6^WGNH%}FHM5Kd>e(qL%WaSue__xmo z8#dtSy#@qEwG-F&W!L;1T^-#m#?KO~>yh%tdI?hsI99YHH`{y@-LParw85s7UQNW- z7O#`!#d(Q}14vNj(BguykV?WOjlO_VSOAW1`%x8J7dWz@Fd6qnH zMo&&yw4RzZ+X>g_R1fdWd`1e$nCQ5iE*V=roGfy-7LayXh3R59IqQ;NesF%dm|)!R z{_}!=&JK+~IMl3@tqVXbqk4W8;5OrwSC(DQZiQ9wc4eCTHaI`K1_qTS+s-6#h%!t* zq#6NECjWp0(=0_l{`sSx)5+uVNq_?q7Fy5bq-`4deRZN?CG`ZPcuT|Wd)R{;Uqxm^ zNn#v5!O1~RH{p5&fCQQD@&b+xb$H(L0}_JIeqmidh+>p7)r7MRoFRHIp3g;g6%`9C z;27#PlP(!xJF~qAbL%{nzB>9tWBE9=xpKom_N1LAxGsNp!zMMON{VOgSTFSzR8!&B ziHPrfb?3gaEwYI~bj5d8q#HRoZ3~ZUdp5)fD!huh)o*kUJ<-XWwkrQ%=U@YQR+#){ z!FFl)@M+-)q;vDe-%h}W5HQHTk`l!IR&UV zngEJ}z7E*}i4TR47Gdn{-wCu2C*3`+8w9;bN3svS03Gn^F?}>Z{fiNJ^Yp-od`4S)nfPCuj#D|Y`*x!)u+ye-v0~udMM2OgP z83_k)egz%{6Lt{_5u+LzSuQUpK}q;qk^O)65ON*<69UmBMgI>mLJAN@Mqp6Bsz0E8 zqR#$3jR&~L!AYw_=p1-Lr1l$NzkPF+F!NjCyg(yQ%Ehdem$yoCt(;)|3JgE_n3#8dQ z^nT9nwsFjO%BnMZmBiF$3Xh+gBJLWW3P_vLPgZY^j8YHSm=I`6^tUB1F!HsCa@CTX zBhwFY9Nd?s&wp0C!D-`B9g(dDelYJCX(-(&wQ3T&`{44egZ{gVWN`mNNbO!}7W%+G z9KOi#^Vi9z{|0`8=&R~h-t@C&r)m?7q-M&eqXhHmzrUNXn4(9ct^&gKCql;hk9ZrD znX^>&tWpZyxyw-WJEEw95mWHfgoA&Bs_iB=$f3#9V)2X1sJ@Y$M!Hjs3$Iz;_g$_L zVf4Y`n!OpMHm_hXRo~L%HMDK+69cC(wb@rdZG%WOn8emJ9bAhUUlSWHn#6a$6Z?L- zvc4Ki6V%QW)-&@Ho}+c3LrAYk(-**xPV-x9=C6;y{niQvx;x!GT`Wab-1D0|U;?Pa zg7fBe2|0x*5Cv4(AiwZ8;T3odZ2#*_Nj2o>&2z{Hl z@N@l%X@wIBx?YbT^E-n$NFn(EVakA-1bvNvuKs~QKSk%=gGbve?j&@(*u>vpv=KaK z0kvNVwa2t+3f@Idl7jOK_G%G92tfu?AsBj`kdDI6G&Zp=>gvBM~DRXI7pq*M!Mea@@EY*RvEmXNhRB5HcS?lRwk~ zgEprbMxd%G9}bgzK|4H%!lCp(DdhH!9(KzNer-9Rr;#CV1NNpvWse(@#d@rgi zK|aI*?uT&8@>2Zo-U1vzZYPfQvG>IH50$-I3fj??F_3fzk9;jY{oHzRfaq#jUlz9M ztC^##A-FFk*-mTgd%komKd$Ovy2AXBh3_okT;~X! z7$kOd9=o=)(k{fd!2kI*c4ZjkBZzV4EG4LSf0(ni+O2|>9hnY{^iw+CCAEumua5rt z`ylu~qlhS_57!0|e3f}QV1ALLDzykM`qW>h8!@jEzs=R9G7X`)DW&5oI|%s>ah=S1 z3oN?b{P;B=o}k5n(Os?>MvtZL+2)h?P^b}z!SLCx3+{o1U8*?{gjfIBPpYh;$oOHU@|xn-d9&gl6J4KWT!{_4NL<-Q=r8Dbl}fLeGiV z38qPe#JF}augcQDDXu*KH<*-2fK<(J4$TK0g(nC6p%;0{3nbDA^B9g1f@uC+{jS&c#zBIjmJFQR85R z$urgEuC<{|V~=5D8)Vj9hfmVtD;;TdF(w}-FX4XD$IrC4)UyK<7pl@simVFlkB@W{ zJF4;)O!((hu7Qu~f_z*2g@_Zr)r1YNCuC0JPbyK4Rn(1x+dR`IoeNn>| zi{YqNULGrABBIxd)GcwlmCaRzK-T+z0$J{l2XKdZR4QkXIE_Q#mBmj&NPCl?Lk#fJ zxc|I$ch@pzvq53D8}qioKr`hs2180nPckTh`QK*_{Jdk>Tx97=TH;>lD4|!^T$>6~ z@4dS1sadA#6CeVLtd=#;S?%(!s;Kl5l6TK2&939wU-xSkYWpWNcV9}gaB9Ih)aP}3 z=iRY=4!jwkh)cTQ`B(tki{TmNYk)PNo}T4l*BwN#3whd=_`2LGUfkl(I?D~ zXG)9`T%1?}g(p``@*7X-VHmlGX^k()61pBJs9niD4{J2A2;&<(2R+VfqO|Gh;3c?E zU~qfIWhQTbmtYdufyT}MF{HEH;W!q?LheEx^{3Ie3K>&>r}mw8q2Gm)EWaL>Z)Q@X zQMI2ns!aS~7Ttkl0O&a^%mi$CC&S6q5n(B1F&C=T&|MoC%EACdp_>oyQio zNXKaUtq%0p%E1pedK|y;zK|Pi1gOkF-vKp7dCGTJOwO4m%c#8~&T9Z}9)JAs#UFUF zGz`O4VW(TYtJdhEJ&ucJbGJlK$!KaMBM6xV;9Gt^u0CFn<#`0mLAJBG)0c@CH9`69 zaD7*3o07%m?T?>vgq#k8cZ7LouR%3rrC?()+V}Jfp7tk&+52S@hwGpV(%KK)C}q`Q zhp$Ie3F6xu9~hvHxaqm}rJWPnpW#??S`4`KlyZ9Z7QU^__KKs`tzKQkgo~JCaP1+fa6UfdbFuaB%e`nWgWix<9b$ z;M1h?OfgQq|~vt|}eD*V-Ih3`7UeVst5^Eguf&t_s zsg(AxDHfHz^h}($f^Qf@)xAw#muA~tnQrDYR?6KM`k11Vh(8iIn;FoihEBh|o`0#O z+oQBI*iDm?#+JzisYs2d;%GO<#H}d{VUI^%$)#m;z zOOM0bPxF4bwC~L~D%#}Zn!fj1{bHxzMJN}Mg5}|(FJ)z2RwRB!O(a6ZtXcdQeOmQpCtP2r$BO56{$lbbh9Gg&oCwyHRl(%b9nfb3P2W`Aiv$vgY7RvFde*KEn@ zQlSZZvaVV6x7MZV>b(u!JUH&H2Q)9B&i2rcBHzqZ9bH^lk(xsFxl`7<=Nnjm5p^p( zlAB(#ripRl%yv`eoZLt(aS1-We0H=Z@!!`~k}eD``gHbUjKXvnyR>HhnL zkVg4f3(nDDn4>e@2!HKbs#bd!Qr&g}yQ!@(bnc_@WsriuWNgb3QiFy{d)?n(EL3vE zkk)Gm^L|UM`p`g-nY(u&_NmiD%i;#jwu7A}W&97dZfP|+($-a@j7yB@djIS#XPCYk z$8>)D^y&8R+f~!Zr)ZEyvPQ!B)2PJL$5OYnPZs7W+Y^N^rCM>9SqQ_2qp8%>h64eQ z8&JaXfA=pq$WWaRrDQvg12M;TqlCm$P^KLOyDq_(ntHF0*TcJy_JlJ*-f+Z%5M^a1 zC?v-rx@O0L?|YHjNYcv0DWd6osPmDsq_^}CFt0w-}wl{k+z2^ZIgdrd!6y_@xqWL-1`t(=~mbJShM zz%v@F8WFXDU3VEg=CaGChdMgQ2G%`YGlgB)rdR`X<4pKj)V66uFFtlY_yquB1bm0l z^4#hS9-c|H;H*%^hK4*!LG7{~%q**=lEq3*Cx5|n;`B=bl*Ro-&x=K8_1nN^gk%ZvX(7NsWwO(g zWcw$V(Z?N$=HS=%<`ctp)rCqGK4@&5K)h#|wym!A-&U+(kdc!#+qnP>SDm@rj!jz4 zdYxQ*ydR$d!^S?`{i>26)pRnw%7rlUErMg#Jjds!tEVUZud3$>@w(N9uV96wCmHeDS#ST221na#{y14_{nT2<${H$_?ZWH?3tL;Um zYu}0un}r(=LRMXcX-+gAnttz^rj6mpQ;nO9B&-ST2CW4!e(`%gnmb?nw35QkUAaYe zFHWd6ODc7v%kIfscct!ZOED1yVu*|ywB0=C{si<@vHmTjs~=V;;u8bv<~o=R69B+9a7!h z-6@Q$_vY|Ulr!;5-z6ij$4!RiU5;quT=Jw*m~cJYV1j&gApVqQRXnekfK^LPIQTI9 zaxktcdtS-**_m=_VE8T;KfbQ~Zq#i%MwuiCDe%Vm!?YANmUcWpB|J(H5}u_C z*g{hkd(!ICX}4{s;iK+YC&<9duJqiaDCCHgzYyMUf3Z^)kT~oai-3psrW) zpSS%83IaKD8mk#|Dtqe)RJ*wHu6xUs-0(>s;K#S#4YhuA4#k>y9KPiy%{Q&dRu@7ioCOOjx}(FK4pmSU zS&i`!NcddZWj!-5)ljP^>Nyb_u$59AI`ZStp-+Nn@Zd;&%^<+VexZg7(6bT;yI64D zqKnndmPUq!MAV!2pGH3Zv_&Q}1$oDk1Fem~RhKDx!HJNBLNndh2qtf|UYH<5G}B2A^;qNM#GF0=eLm$2bEg+1DD9HE^{Vh_Pe)}0JZg}aHgK$e zO+nrhTuF>Q$vO7^D9z+xs$^GY>QC1&s0U7Rp4t@C^R<>$Li1zpaK151~u z`wB(~5;R@j9OPK>(9Q(^_<)nM9K5ERkq*^Q@@d9PAi}OEb@#6@-taQ=lW(qABdb`= z_>@n;U3DGY8#5SZcQGB)jHPItYmabY3n{@tz54Upnw)suRi#v=`G~j#pUqv7H8D+@ zO||R^bd*Rfrc{e%>S^VXaLnkTERdjKr3?)A=Z~tTwmv1ATE)vHwhyak1bp4`kK0j{ zoyO4(%oMZi4s9Pu3ae?7>TbbNTyshS0m3Qi_9Thbv|C~$*9Gg;FFg-p5NZY-I<7zf zq-!hTE@1j$(1Xj|pgr)F0h}}*i{XzS{{f+Q?j%}~l;=*KKC@+UyV>LWUgMs$7>Iq= zrr;R9Zv1%mFutE3N`~(;A5QQuNIH+^`X2in>kM*md{a;^@w(J{sOk_All<5qC%s@~ z(CiPWT}Z;wNQuEK*xOJ)jNYO*enf|$p(02cQ>2(DLZZ^jIx@IUz_x{bb+heDI%%G8 z_k@gFxfJ)-?4xcdc`)hFKk}{D%n7H0+6<#P)Xj8 z^`9L*;hLEGR|mJY8$v1fQ9D;e#`MBef4a^cl>qE>J#&aA;{ya&Ql>lGQQh%+3q19PYBv5}}eN-yMA=c`bG`r;`Jqxm)oM63?$F&p~R#vba8G|A>!o zzw`RtR-Q>Q&Hr(5JHxxtE^NN^$YcSn!3vg!ojXLAsP*6zn-X zp5bk5&l()Xyh%BscTne zA7))SZESnTFMfIY?-jVndIucd;tY#4K^EoJMSZ*U$SZo=udJVna^Sq_tgBO3x>uJ@nKuS; zmJe`0DpmtY7F`lqw;ElmkEPe?hSY0w@;BU@g>@EEVK}6=vNf%DyL{)N+kX1W#S4yV z6^4CjqL0~C)oB5?{+H6Ds9&(B09^W!A9Rk?FU0R~y*JBUD}xb-U|UGueG`!|3lq6! zs{7?Zd}N0Q!h&Lc!&3#87?op*vE7XW5?1AkxAb81iHY9>G%v5_sssATbwUNyT|Yiz zbMZi`d?#YvB<|_5AQ$_9h;4Pg+=H!xjo1$zp+gTGsy^Akw6{sbR^ z8dz>)JjbS^eACq`zLU}PZA@;a{1?}o{$jxoFJAE`#58Ouf3IhYeNim9WrAXvY1cW8 z=twzDZq3DzYQ;Z~&?h)_MGa`8uUsYNe(t{EEc%Z*I#>4?OK2#06sU;RF_m2Aus#`) z0$Z{<&pUj7rO#8ZSr47&En$uXAp&R4L zvaadtU{K&Jp?Fxc-rd>+Sowf?V^0l30?k3b+m_5jEh&hnp+12PAH zmw~wS{NK0#Ue2&zxOn^A?H3@i^8%o=_rouow+1}}bT(q#prEs~9RF=~|KG0!9C-52 zie-y^0@fm7r*c)hKrTZY?CZeFwxb_;lu8qdP@dB2rQfpM6dDxN8U%PCPd?L+d!7f5 z4@fL9LC0@j{)g$&ZO;o2fy)ORChJuY3=mXY1l@Va6Y_)~sM`;LkN%F^H9+TSXg&cz zeH^govzO1lVR;U${a-;t^YGs?3CrPKcMnPDHQlVa0w8ny~iW`p$KEHk%P^}jR*god6VtSXFt7zT@D|& z1<7S{72-{i*IjA`MDS*2twj{ATf7UWm%4wZ3{){LRrw??J4DmWtGcJvs`^xVN4|4k zRS%bhU`DY>&0K7RLD`X<-s#7Zq3U37-y3#|+-NLWyfNO9M2|W7Fz$K!;N|urKY36Z z6RzM|j&Ge$2mGGqD>q_-3nLDNU-oU`;9`Luty->IqQ}qs-_}pm>^0>i;aw*6TYph< zW|sL|8HA5V$N}KlXvTN8_!09OPi3HMwQ$isRybAX3!ihcqt+jg`XY6&*bH@0Gikq$ zR^pV`Ei8))EWUI03w9%n1dFT+jYCx8)93%$uUN)VrEo!cOqEwdJ0X9{nX(@8e`UYC;^(c{~v zn`>Msmz7&T-hgN@>+*M9cqw@kF%0d^sWmUd3>-f?MAk?ximKCT#V;631 zKxR&cl~Nh65g?K`mg%-#kNfBH8Xp(9rkx)%6Yjh_h`!csH4?z!qjaYMMse+KpJdzk=&D8| zYQ5E!r`yR=VQ{x==XAI4HZ%X3lBFeh2=I3vqv~qY0l}@~=Hf)GZS=K{9V@nR!~7Zh z!{@boftFVi1!b_w)XpB1vH6HYs@15!(P+P&kon`pK-l=OPnN;>U&%%m{*hLpjRkJEz>T{yaW^Tv~H5b!mQ5x5r%|AwfTWx)!a1 zZ8qu2EfaVZTN3qkN^RzbyD)5qNYc7riCr_mw3qH63@8l>mR)%g>EIAK?@?n`&fHV2 ztiHV}PjJYp*4T3L`1k-uQnP{!Fu*t(J~%P+CMTgFmO8oP1o0J3v@`0kB>Tq51l^}7lM37oX1PR^`0)cG!V)g|ouSKuRjQs&=BJbzC zm=(5MhQ#?E@q6YE&DEAhBy#JBxXfHH7>H($& zMAsuXaNa+Fe>2;W2#eb*YYeb@3?HrcH7oEm8d!#X&D)|YpX9x?^b1>P7$8r!$X8YnLrUsn5d_HA&6{KD)}`?Nl6!$3MLrO)s1*0^1&?l$3=EfD9`o| zif+w6>-B;D`Hsunw!ai67n?^zOx^Y4-fThx{ZeI2^&_TS-~S%s@5CCV%}}b;$ZF++ zk2KQ~vX62?ZfPe@XlZ^);5qPZEst6iZ!w&aq#qc#CGmNv&mLZA$7#=*_AS4+k^tVe zfiVgcr4(k74Jc~42Gg6Fmj^4NlOJBN1=@W3siI|YK)8*3r{!)mwn)ky(w-+*W{B~susF*AbTtNv z5X4lrv3q)cs5IN`S{U^6=*>4y6uzNZ-`GN|P{XTOU0&P2q1qT*QH>e&LQizvDy`8z z8BuDX)zn5zBHf#+;dV7|CQaQ^;;4jsU}WlFkYpEH3~7`XP=}ee-h-riv&fW0Dx{0x6Px1BCxV0?R2qEf zU&;U{9xK*+_Aw04pE6Ck*E#mEjArFgBwsWp=eO=Cbqcm9-KZXl7F~Tt{G+ zm}E8GGl23;oBcg6$sz45lo6Tr9nx}BaqGBDW_xNw+=_SXB5mI8gpY^#Pgtwl^D-xg zdwsed)xI9mSFWEHrxySO#?+`K!>Ilmg#XaEA4?zlhH%*uLMOgZPd~zu=p3qOHjGt} zAl^&Jo)uv<>x)a{mf4PjS&8!?;g9;tvuOSE^`oqsxlM2ODkK-Z2HCAZ&#)V(VAZft z1vMs@KyC@7Mc?jV1wPQ6Ghq8M`UaEG%=DJyN&i{}eQHgzYl&WelF!cY?KKFl!P&vUy=vUvJ z>+rnu?R*`Xo#MuYQg|Wn6m94B;bsvrM+Vc^V}8OLv2~Fgp+4@dh$Riepp~HSOn_Zp zyIxxC-N{fWu}*S; z54%x}aprL8B`6mT#HJxCm5#YSSy&z@q<-&*m^-U|P>DM!Dkg7Mka?!2V@*ZNM}EKD zbV`fglJh?cbIZ%5O<)NfpNaywSzD8aZjOQmrynaD_%63QR_^)hSQM&fY8NN1VfdI< zVkI`}p{IFix9DUKsv6g$v2QGd6G05OyV=Feu3mlA@OobDgm7=ya#MQX_w2F!MJl9S zolJ`=`9icmO!RDe^%3EFm1vOd6 zd2(gqDsju;N}pjJ#I?9pEGqzf~QeDGZFm3%Q^6I zY4ikbaek)_DZ2E5`B!R}?~FQw!JyY0wu=v8LWW-u30(F zK_#X;{UD#J+2<%x2sZNZXYZ-X{leb56@cy^9!8cW3}_b8dThzATAAa(PSLd; z86)PRoBqgoKTYOK8qUw#zl1+LQy=7ihEK~Y`Nxi1=Zj*j8poGfzs}+Wif4B#{>Oum zkfHuu2Zy|sqncoiTtD8Ds3Qf$CHTX^Ch5+ZEyj(xK1Q=JiCiZXpq&pEV zBpi8KoqIRvMwihuRB5W4IN`_ewdI=CY3VMxuQ98^{B=^Tij!DxF&CHkg^tE^l)im# z%i6fk)T*$Zp*W~tO$sl{QWyJZak|Sx&c7eqi*HxH=sYa&D#3nkG(yY&xPi~}HN)F& z?duP|S}hdcDle#QB`R~z&xl+q@8P?Via&8U5cP2M{d_vGNO5I7UxT=$n~T($IP#gQ zE#3SB;B2jM{M1<&@uJ3)7+FPsIuUx$+08kn(L?TA`N0+DTou@(ex8=1DC4E zG?)>LYDmb>EPAEV%2ytH&m++tr3 zxMruma4hJ%;GLe2^MHU7>YwC}iLSe)A1=XeN$#J}*3#{mp&?Kno#dBTF0Cw-Mw!F3 zsd_R3&CSWzJq?AH)#^oTPG=Xq92W%z8QXUKhkm9{Ws$9}*71EN$w#DwCd!aU(@w|@ z=Dh{Sb3hevJ1v|EyH2hECt>9Cn@=}KH65%(@(Mh*f`6DQ8tZOcNnE*ZpCx*4W6xJS z1l<_O%|G8JT;-dVGWCy4L9HhE%C^f!%KLe(HQ6;fFFtQ)86%SKM>DfoUzE3fE+!tM zy4iL$M^^DeqC3H4w3+D6)}&CiEfsj)OQpEtr$hxR0(~s1gYV=}N^~eG#r!B#yq}>P z{}iYMwRBQ&e;TvVo`@)cCTkvl`@v{l9T|lxlL0>bxLm1;mTetovE$QOaslY<8E{+A zsp9MMEN=$x%B*fj8Hh2#J#T0vb$jJh-7Cdd0y(ai4qlJe{OgPYwSy|*vzwbQSTbC0 zsi}s+woX$560I09 zoKhz7xTixm<*+M#UP&{VNah_$TYFb-b)YFY0w7|sTn#VVs!CR{OI=)yHOo({tK#<* zbLBN}WOBca|DmnS-9{B!9_`L9=d{9CMr(B13$=bR*0+qt`gB6v@=lz;)R9MFxwEiY zpGY^%69hShcQ8vLi&MQWlYVPmwWeZ5+i6d7+t(QFn0!)`u_ev=o3~V8#mM7*Sr(XO zKcXowEp2Y@8;A&y%NSTmi*#C%GMd;MCb<03PV>>V7$i?$Y^PKMaC0cVtX6ZI=V<;a z=Q7fatBG6bQ!L)2#FT9aes8zr{+fZyF~Hdrw)+%oNfbXgLAqfMF&!i1y@If6rCY=O z6eEV&!}llcRPWyV^QQ6st}(M8_;HPpk<`=brg7CA1LuaG;sdrnAV(U&o-aje6zsn0 zQvjqUD2WelH+&@>E+klgWNZws1ZLKTux9B+u%B*H`C+@p`-;u$Eudea} zPjH^#y|@;o=NaX-k&%s(0=V<%_;ZCwMH4;(Ct&U-WeNg zuXCt(fnm~Cp(rJu+UZWsqEY=0D5Guctn>QulCXlG#^aOO2}XB6*Rq^TmkWBD0NCW8 z18rT3tb3ruNh;V8cntPGp76Q0%As7v*ch1HWNgmWMDjoTBit>eCIF|Ekx&(d8GQ@z zXUfzwzE=IX>8U~xFngASwa)Y))VWGD0-A|qx<0_G^7M9&Wr8D>w@+YUM|>^xtsExss(9r%XFxflE`+zRd(NZMrf7#lA@shYyKiuvrAM!vZr%z z|7LI8_m=jmtf%uMT)rfKpTrJj^;er^Yf9amybIjFvxIJ&$$a_`)usIp)%}a`(tTXQ zHwOp-iN*rVAlt5T#|KtR5g@@5`UfOtdp|1TMbtYG=%D}rqWgUV@xJ|bJ}4N%4f>P} z+;bqVV9+O~q9}m!FUl>%!~zOE^YGq9x`$`k`9UAqP0xTX-hXz6x#=NK5C~*zdtQSd z#QcI(7UyzUCKPmEt*qag1wbVJ2ZR4O{^=YWOFJ&p_XN2Hn)YQCo1`XtTr<&hoOgC~rN&Ji*8W&6$$Jcd6g+Q3X!=iqfDb z`>9WCq8~LK;1mQ;UX(c;6jiFV@%|N#r(T;HFBRdRXl``Hv(i8b=v6}POR%q62 zWh30lNX{t}tt$^n8{mzt=P>bFEAC4RXv{M)@1^2p^)ZuU!U1_04(Y&Ct7l{i+3Ipp zMl{-$JNUJFIGF7^)a*B2Ui&e}VMPlI_M{}Y!7Ob8YOS~@Dj*<~qi#7#sGE@MagJN* z2s)n>1e|QKGa4@~C&x_ShBfsiI+LSu3+eDuy(r^CgwBR?EZDKa6gE?@l$T992>4=T zNsQl#_1xnY>HSXHAiZ@x_SZ5*sl;_mGLRRpWY}lM9P0~I>woUO!_ecLdh8wZ=+pjD zN`%7W967h6p)!1DY?ReWXS6QER9}pb`1;G%=#z^Z@eP`eoYFt#teD6>^*v~T3A6f4 z!AVAM5hR_HKYPw|tqMUnGtoJk`c74Nt7kX*NaAk9Sn|QAMh-H|vgW#$9V0D6e}okg-`+ zj;hg%JJCLZ&eqCDd`{8@cO2y)x0-0`hPOT}Vk<Y)_T?Ce7{#t0PL!6n{N>{~9ku2sjEL zl0msrow}BN<%6@X&bJ&I4`Yzh+&(I-c6f<(LIX;Zgu3mSdF{2o;@S4h=@YY{WGtFfPI{~9VG>Y z@-LW)N?m-Y1jlS!_f-2`=Faa8Z0C7|9LlR~+YMGr9Dt;%nH`9muyOMW{|23X2qfX6 z&T3rrXH3~V93*BcPI$p$oe;GL=gUgzg=cPQ$gJ%Dcp8-w@ipZXtBd|QSWb!-AvuT? zk)bQ~xv$OL?2}$wxIuU^*U>@W(=`kt5_kaS6m(u(VzW{>DIy5?{`2QFGdFsHz8YvZ zEVS80x3&V;hZeryiNFhnh)}OEaTr-uh85W+NASM2eU>zR_R}6H2x&fub#yeUM{f4w zE(x@^iQqJyxoWMf>J#DPzpEmf>10RgJ4>WIE`{&8R?T$VT#T#6b z(E3TLfvqN;8jP?kS>u$o+c3M4Mc#%K^v}W+Br<_s7?|^em8xdJlj|>i!FNTYX+TNV zdBT#T50CUQG}JY$4|d94R>A1lo#yRYiFdlj$H%8S>3WQHr23orkF*`@_b(RihZmb0 zX85P03xTwq^_9wl#Wk5K|7b&>nB1u>O3KZ*LaR0*Yf!t!*y(PML21-SEoG`*DWFct}jdYvi8VM zIZSXN)|BwgOz51wH*n1_^YOBbr)Y6F?uyHVMZ$Bp0~vPOYs8!jR}|C0D+~RBuz841 zg+^bjFm{JOt>(qt5tsKya4c#grq;4+urBA5Y5!b~w%gY<4|m>$vK~&_cE>&FJUc9m zSLO$TSlN`1$ppA6JGEvG9_5J;E!x0p*^~-iBOMn}om!DvMrFg_nMjh1sBy!j{Uh7S zedTb`eRVTz0n(Km;`~@9`lozzi89}UFJo8*%ED8u!}34tMC_Z%Ic;ZfbNo5wES(lhSVG6X;F!%5J%qU7TING@}ea+m}a?6{DC^ zzni~7GU7mD`<&CzjssXK5xzU?e&JoSdqUUb=|X;OAE`fr8~(6es3>i-JV5Vp@NBN^^AN=#Iy z(!lk#vg6JB0;M)Kg$FX9xJ0eW4VVu-!_a%Y_761bmkBm1%tsl!*4L+cy9TVVZ8y8= zZEEb>32eDJn13D1imJA%8SC)D{!WCrLcLXmzx*Do9BY?1X;x8uFv4D$R+g0cG-st3 zz2@exmXU>>Ff42MG{o!R(qa|AWr@tZfgZJM^kr|gwYlZN>>NLp=h-AsQFB*M-z1Kx z7&$%l?%8ZKI2Ub02kuPKTu4yr=p)0FVi~8qe(Lf?)ZHDz{g}M-vPz)SY^ZQYZr@i^ zQc7zY8|tfPt-x)qhBNn^n{^jdMVT?3l(^Z_#sTv+fk02w#3T1(jCK}r$Q@Zho81BZ zNe(hA#ie#R#L>>Ot_JoNx5o|SCXEsb4HE2Dc_`35m_J<7C%r$vNrkg<`d3w6-sTZU zGk>+RukeDYw@W~Sy`FcHB^brta+!ImSU$PcPr_Br8GrM0U0q%D?4v;x*&U|cHE>ek zxJw<1m$K`znYK?g{Q;d_6M^9&FiP+bffSet!fDJH=Zw~=;BeJXGsr%_Z`bqWjrEu?WQpD-90#V8*@JCEkN@jP9&w)e-EN zf{ztE(5)4%n_QFiP5GRkIwc%npQ0;IMa0;Bc|VfS*{wcMEI}k19i-O=obdG=DduWB*5u0+#u+ow|%fa&6FkVgF zTjRA*AMuC{*77VTxl9Gn6yOSOu>pw?p>`blksWu$#ih#-U@u~KB4v~5( zaa`1b)pcaEG;i3}iqo+&)@YytY*bD}6B<3dEE*%Mo;)Q7n+{|oxou~vZ zUo>&ivGSEJ1B?!H)L8IEDolD>JscT1DD`2z5YZEux+!ZP%Nza+n-9A{s25N~w1PTN z16ylO>J7sStF;kzh&9oU+2Nn{3SECdhbpfv%!m^En5Um8hFQ8Jlivk}`z#az+4>>a_7 zQmsawgv{yW&Q7`h8F(7mFji8wxkb+(?62n&Vyu?yAkwLv^%PL$-x6=y#&*-xZMh1q z*jLFS!qaXzHz}yYNBYvrYm=*o2U@yB1MR1F)Hh`E#7-6uxhhmhY%BwCv_M+DhMbEt z=Ec0i;um9`x6noUD+e5^c8A-dwv}zA-sp9z=UPu_mqWKzLmY-@S>>&jJqftIB;4#% zaTAo3=!Wz>Jx{hF=6RfU0J=?tknw$*i!xKZ{W&`SPZLc><_hT@#S znD6-$av*)++%UP^yuerI*WAN%-JRyrNdI&wZcSR{ zSoXPT=0%d6J<=5O&#gOEyb^$S6ih|x$10S3$jmcu8C_uIZDm|=i?D}!C%F~$5R4Li zoYH+<=F`AGBI(x^CZV`cpX>UjYR-~(kxhtuug5Ei90ayGPd=KSLH3e^f^Uk6G{$@ zydUgBly&C=?>7JR$*QtJ<_ty)Mroan-Ml1~0FZTRc)swyt??Z}uZzcfTlA7c2H16! zE5To0OVo*Gy!DyI5Bdr9^F=}LbnY7)f zL)f7aQXFciQX~QC!9$t#L<>a3AJEQ~&gCCTPR~|ER_&YDq=@@@Q)*iJU7bF;W^GR7; ziF{k$YMr+=5GL#!ijzE1(wH)ST>K@M{4C0@tt9_2Z{*=c(}&yw>=*k8dFcX5vHYVO zV;ysHA&f=OoKNj)q%Nko=oWbKg3S({B<1RUeMs|R|JliGox{H^9;Scc_fS7UFH-NQ@syDUoTN-%OkBFE44zx$xO!h)MoU2A zbCnuri9O=hmza-ss`f=U%!;_<*PpujI}5$S^GtP+Hez_vs{++_k+U6BOe!}!^{~JL7fyE{0A(v1r5wa{GB@G@o2^p4%a~gY zf17^;5#>Ow8PVUjzY9E)4>Di}vEGnodVP{qs|KNnNX{J9Y#aeB)0%DRw+8S+P1YAP zdUj&Ts@iUCuiUR=za7@+j#c643j|lXl6tM!%g{0Z6iW5=6{j_hc_BQafGih&rhwV| zZHlFt_*uBc_07_lqFygPfM(9-mV;2;aoNi~VU}l9?@0p8@;@0D@|qrk(>-Q`j0eaZ|R{l5Uh@xNP;h z;-`=ZL@ULp!X`y{Z@y*Y*!UY&B|E$?YLF+YR-Uq}znj1twi|e@4V)RuX=5$Tu~Z?b zs)%)zlmv`(kQwY7At$QrFB4z3))m_V;GgU=g+ zoZPlLzh7`zWN}p1yVO`ApgXE9s?m!6xpVGfZvcNCiV3Q$piCW!C?^W6CH*yFKhl`4 zVLp7z>l|npFuB{X!?)C!r}S>&fl1qY*~0+@+zG3mthCY{WpMrO=O9q!Unoy01jLC7 z-#6;E(9EqSta0z69=UJFQDe^eH-S6Ta%a_~pRLM?wFk17eQmS6r}7`SvN#kgrFIWH9rVa4 z6!m`y5@XAw)Qn8r)PzCYydD-UtNd$~>)g7s#o)5uSCUCPv`-8R(Z)IJdGl0;HLorC zWIWIteW155#F5R7X6>3c+$k8ie!E7y@o{faxrJ0{CU#6^l=#*_pzqCysB$d+>)qfx zV2`nhpfi8b&e5IvyJN-`yVCa^^t^E}L9a5m3t|y*&X~)>8F~Xl$C7yDW)gqR_S|kd zYv*2Z{%D%r<<@KFwbspfo}%grFJ>`yFHY48@;=Zid^Zte?Wj%HMOwRi@}Iy%G^1Ck z%`<+c9WuV5tON~n3VZs?H9D=8=7UBlmyboS;`}OSJE&D}B6^T3V*7yc3?Mr^0yPH^tq(%gR8qR<&v|WA3?B|o0>R15RoTPF@ zPCy;`8zqj!-#GXw2jbeQv?t6;$^bqiriITYy1l=Uh`B&vyOwk0B^P~y^l`@rWYp}~~p zC4u2GYhb}VzY*f#FOi_1c$guVt+fFjQc~~~L`VM|z?sQ`Y|1OK3!VBvqw1~fIJpu&GBCtXO)EcHBb`bE7XFdRQdvKF4=;4JYsywVL zKR>aA{Ds8;B>dkv|3Aq8{}e!Hfc57t_|wq-Enfwlf6fBDGyg68zc?QR0)>E>-WLUd zzS*9=`~Ef^2n4M9cdKWhhrl1e$3LSj7uY-qxC7dW~bi+;^Jk(pC~?MUh-j}M4l zjA{Tn6Z7rrIr{wbZ$AG;(*KQ!Z=iQYAR1fH1-|DXeh^Tvn1ERofRD}x0Wbd@GO#r; z-@xAhjw}dt{=Z+K@psMN05*f(28KhRpoeFG_sj3j((*q)O9Q$M List[Tuple[int, int, int]]: + palette = [] + if colorgram is None: + return palette + try: + colors = colorgram.extract(image_path, count) + for c in colors: + r = c.rgb.r + g = c.rgb.g + b = c.rgb.b + palette.append((r, g, b)) + except Exception: + return [] + return palette + + +def extract_with_pillow(image_path: str, count: int = 10) -> List[Tuple[int, int, int]]: + if Image is None: + return [] + try: + img = Image.open(image_path).convert('RGBA') + # Resize to speed up palette extraction + img_thumb = img.copy() + img_thumb.thumbnail((200, 200)) + # Convert to palette using adaptive quantization + paletted = img_thumb.convert('P', palette=Image.ADAPTIVE, colors=count) + palette = paletted.getpalette() or [] + result = [] + for i in range(0, min(len(palette), count * 3), 3): + r = palette[i] + g = palette[i + 1] + b = palette[i + 2] + result.append((r, g, b)) + return result + except Exception: + return [] + + +def extract_palette(image_path: str = 'hirst-painting.jpg', count: int = 10) -> List[Tuple[int, int, int]]: + """Try multiple strategies and return a palette list of (r,g,b) tuples. + + Order: colorgram -> Pillow -> fallback built-in palette. + """ + # 1) try colorgram + pal = extract_with_colorgram(image_path, count) + if pal: + return pal[:count] + + # 2) try Pillow + pal = extract_with_pillow(image_path, count) + if pal: + return pal[:count] + + # 3) fallback + return FALLBACK_PALETTE[:count] diff --git a/Hirst Spot Painting Generator/renderer.py b/Hirst Spot Painting Generator/renderer.py new file mode 100644 index 0000000..54df23f --- /dev/null +++ b/Hirst Spot Painting Generator/renderer.py @@ -0,0 +1,76 @@ +"""Rendering utilities. + +Provides two rendering backends: +- Turtle-based interactive renderer (opens a window) +- Pillow-based headless renderer that can export PNG files + +The main script can choose which backend to use. Pillow is optional. +""" +from typing import List, Tuple, Optional + +try: + from PIL import Image, ImageDraw +except Exception: + Image = None # type: ignore + ImageDraw = None # type: ignore + +import turtle as t +import random + + +def draw_turtle_grid(rows: int, cols: int, dot_size: int, spacing: int, bg_color: str, palette: List[Tuple[int, int, int]]): + """Open a Turtle window and draw the grid. This call blocks until the window is closed.""" + t.colormode(255) + screen = t.Screen() + screen.bgcolor(bg_color) + + rex = t.Turtle() + rex.hideturtle() + rex.penup() + + start_x = -((cols - 1) * spacing) / 2 + start_y = -((rows - 1) * spacing) / 2 + + for row in range(rows): + for col in range(cols): + x = start_x + col * spacing + y = start_y + row * spacing + rex.goto(x, y) + rex.dot(dot_size, random.choice(palette)) + + screen.mainloop() + + +def render_png(path: str, rows: int, cols: int, dot_size: int, spacing: int, bg_color: str, palette: List[Tuple[int, int, int]], margin: Optional[int] = None): + """Render the grid into a PNG file using Pillow. + + If Pillow is not installed, raise ImportError. + """ + if Image is None or ImageDraw is None: + raise ImportError('Pillow is required for PNG export. Install with: pip install pillow') + + # compute canvas size. margin makes circles fully visible on edges + if margin is None: + margin = int(dot_size * 1.5) + + width = spacing * (cols - 1) + margin * 2 + dot_size + height = spacing * (rows - 1) + margin * 2 + dot_size + + img = Image.new('RGB', (width, height), color=bg_color) + draw = ImageDraw.Draw(img) + + start_x = margin + dot_size // 2 + start_y = margin + dot_size // 2 + + for r in range(rows): + for c in range(cols): + cx = start_x + c * spacing + cy = start_y + r * spacing + color = random.choice(palette) + left = cx - dot_size // 2 + top = cy - dot_size // 2 + right = cx + dot_size // 2 + bottom = cy + dot_size // 2 + draw.ellipse([left, top, right, bottom], fill=tuple(color)) + + img.save(path, format='PNG') diff --git a/Hirst Spot Painting Generator/requirements.txt b/Hirst Spot Painting Generator/requirements.txt new file mode 100644 index 0000000..2786f1a --- /dev/null +++ b/Hirst Spot Painting Generator/requirements.txt @@ -0,0 +1,2 @@ +Pillow>=9.0.0 +colorgram.py \ No newline at end of file From 24ee0b5ba2c3adb25bb4485b9dac8e82da42125c Mon Sep 17 00:00:00 2001 From: aarya-16 <145715221+aarya-16@users.noreply.github.com> Date: Sat, 1 Nov 2025 03:27:17 +0530 Subject: [PATCH 2/2] Added .gitignore --- Hirst Spot Painting Generator/.gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Hirst Spot Painting Generator/.gitignore diff --git a/Hirst Spot Painting Generator/.gitignore b/Hirst Spot Painting Generator/.gitignore new file mode 100644 index 0000000..cff0408 --- /dev/null +++ b/Hirst Spot Painting Generator/.gitignore @@ -0,0 +1,8 @@ +__pycache__ +*.png + +# Virtual environment +venv/ + +# VS Code workspace settings +.vscode/