From d6a8ab0ddb97aa5c6349066a79b150b0436f082c Mon Sep 17 00:00:00 2001 From: lacvet Date: Wed, 15 Apr 2026 21:20:55 +0900 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFAX=20Copilot=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EC=A0=90=EC=9C=A0=EC=9C=A8=EA=B3=BC=20=ED=8A=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=20DPI=20=ED=94=84=EB=A0=88=EC=9E=84=EC=9D=84?= =?UTF-8?q?=20=ED=82=A4=EC=9A=B4=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 작업 표시줄과 트레이에서 AX Copilot 아이콘이 다른 앱보다 작게 보이던 원인은 icon.ico 내부 여백이 커서 실제 도형 점유율이 낮았기 때문이다. 현재 4다이아몬드 계열 형태는 유지한 채 내부 여백을 줄이고 캔버스를 더 넓게 쓰는 새 멀티사이즈 아이콘으로 자산을 재생성했다. 아이콘 생성 경로도 함께 정리했다. tools/IconGenerator는 현재 AX 아이콘 스타일을 기본으로 생성하고 16 20 24 32 40 48 64 128 256 프레임을 포함하도록 바꿨다. src/AxCopilot/Assets/diamond_pixel.svg도 같은 비율로 맞춰 소스 SVG와 실제 ico 자산이 덜 어긋나게 정리했다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_icon_size\ -p:IntermediateOutputPath=obj\verify_icon_size\ / 경고 0 오류 0 검증: System.Drawing.Icon 확인 결과 16 20 24 32 프레임이 요청 크기 그대로 로드됨 --- README.md | 8 + docs/DEVELOPMENT.md | 7 + src/AxCopilot/Assets/diamond_pixel.svg | 32 +- src/AxCopilot/Assets/icon.ico | Bin 7526 -> 22701 bytes tools/IconGenerator/Program.cs | 417 ++++++++++++++++--------- 5 files changed, 308 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index 58e96f1..ca67b83 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # AX Commander +- 업데이트: 2026-04-15 21:19 (KST) +- AX Copilot 앱 아이콘을 더 크게 보이도록 다시 생성했습니다. `src/AxCopilot/Assets/icon.ico`의 실제 도형 점유율이 작아 작업 표시줄과 트레이에서 다른 앱보다 작게 보였는데, 같은 4다이아몬드 계열 형태를 유지한 채 내부 여백을 줄이고 캔버스를 더 넓게 쓰도록 멀티사이즈 아이콘을 다시 만들었습니다. +- 트레이 DPI 대응도 함께 보강했습니다. `tools/IconGenerator/Program.cs`가 현재 앱 아이콘 스타일을 기본으로 생성하고 `16/20/24/32/40/48/64/128/256` 프레임을 포함하도록 바뀌어, `src/AxCopilot/App.xaml.cs`의 `LoadAppIcon()`이 고DPI 트레이 크기에서도 더 알맞은 프레임을 읽게 됩니다. +- `src/AxCopilot/Assets/diamond_pixel.svg`도 같은 레이아웃으로 정리해 소스 자산과 실제 `icon.ico`가 서로 덜 어긋나게 맞췄습니다. +- 검증: + - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_icon_size\\ -p:IntermediateOutputPath=obj\\verify_icon_size\\` 경고 0 / 오류 0 + - `System.Drawing.Icon` 확인: 16/20/24/32 프레임 모두 요청 크기 그대로 로드 + - 업데이트: 2026-04-15 21:11 (KST) - AX Agent 좌측 대화 목록 선택 카드의 배경이 실제로 보이지 않던 회귀를 바로잡았습니다. `src/AxCopilot/Views/ChatWindow.xaml`에서 `ConversationItemTemplate` 루트 `Border`의 `Background`/`BorderBrush` 로컬값을 제거하고 스타일 기본값으로 내린 뒤, 선택 트리거가 `ItemSelectedBackground`를 정상 적용하도록 고쳤습니다. - 같은 파일에서 `ConversationItemsControl`의 컨테이너를 가로 `Stretch`로 맞춰 선택 배경이 제목 주변만이 아니라 행 전체 둥근 카드로 깔리게 했고, idle 심볼은 `16x16` 그리드와 내부 마진을 줘 점선 링이 살짝 잘리던 문제도 함께 줄였습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index a33f8b7..37952ed 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1577,3 +1577,10 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫 - idle 심볼은 `16x16` 영역과 내부 마진을 주도록 수정해 점선 링이 가장자리에서 약간 잘리던 문제를 줄였습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_conversation_list_selection_fix\\ -p:IntermediateOutputPath=obj\\verify_conversation_list_selection_fix\\` 경고 0 / 오류 0 - 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ConversationItemViewModelTests" -p:OutputPath=bin\\verify_conversation_list_selection_fix_tests\\ -p:IntermediateOutputPath=obj\\verify_conversation_list_selection_fix_tests\\` 통과 3 + +업데이트: 2026-04-15 21:19 (KST) +- AX Copilot 앱 아이콘이 작업 표시줄과 트레이에서 작게 보이던 문제를 자산 기준으로 조정했습니다. 기존 `src/AxCopilot/Assets/icon.ico`는 내부 여백이 커서 32px 기준 실사용 영역이 작았고, 트레이에서도 같은 아이콘이 더 축소돼 보였습니다. +- `tools/IconGenerator/Program.cs`를 현재 AX 4다이아몬드 아이콘 스타일 기준 생성기로 정리하고, `16/20/24/32/40/48/64/128/256` 프레임을 포함하는 멀티사이즈 `ico`를 만들도록 바꿨습니다. 이 변경으로 `src/AxCopilot/App.xaml.cs`의 `LoadAppIcon()`이 DPI별 트레이 크기에서도 더 맞는 프레임을 읽을 수 있습니다. +- `src/AxCopilot/Assets/icon.ico`는 내부 도형 점유율을 키운 새 아이콘으로 재생성했고, `src/AxCopilot/Assets/diamond_pixel.svg`도 같은 비율의 소스 자산으로 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_icon_size\\ -p:IntermediateOutputPath=obj\\verify_icon_size\\` 경고 0 / 오류 0 +- 검증: `System.Drawing.Icon` 확인 결과 16/20/24/32 프레임이 요청 크기 그대로 로드됨 diff --git a/src/AxCopilot/Assets/diamond_pixel.svg b/src/AxCopilot/Assets/diamond_pixel.svg index 48d7a81..0da1f27 100644 --- a/src/AxCopilot/Assets/diamond_pixel.svg +++ b/src/AxCopilot/Assets/diamond_pixel.svg @@ -1,13 +1,27 @@ - + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + diff --git a/src/AxCopilot/Assets/icon.ico b/src/AxCopilot/Assets/icon.ico index 273db748efa7d259eb97a58d93168771cfc9c82e..5cffa147771fc34172ea0e87dacf61cf0eb78152 100644 GIT binary patch literal 22701 zcmb@sWl&tt_AWd~!a#xs_rYC)ySqzp4IVrMcXtUI+&x(E;2~IWcXtni4h%Dw-#Pz! zzr45Zse9_~s;;iJS9h_YMFM7ymc?i~;~? z5di=}LI0)&umJ#d(2KyA=O6hrbO1o~`6~K%ef_(2U7+{^4E(!eJvdtB?MU9?z5T5j-*2r19hJ z@0WGGUs_#sEjfm`>e~H{`A7lvW>|+YSd^g4( zHlJJ{;j^ZbGR3Dh{ErVgf#A#w_!XZZoZk2n)^j^u7|qcx!j558x>5OPdR@31c-}Dq z@7N?ilbj##?ftvmk1|br-+J!c#&r+hTV;063HZFczxX`#2(MjpdAvK%o{8`@yjSxR zdxAsxn4Q+VJPeVH#R$(f1GOK{mSe36*o-uw0Y{d7`FBK{@HrJI+tI~yvoqgC&ZEb3Lc1phX|r?&J@L}Y%KPG24LksVmIw=}Z?*>K<waOcW3eIkJqE82(!-?kqL}rrDP7@-jK8S&ZgCv8}SRdq(qlmk$q{nvQd3RU!+Y z%0hWsogX(E;_(6)D{b7#x+kBF{;r|0C*GR^DAB~Z@BMY-RFY_{`0Ugx7E)6=lm6sh zM#)6vR5<8m9}>KB;mQwh%W)?;GFC(dO%t;ky0-GObg)^vQcotP>*OQ$iaNpSuq1cf zQx7#%74*^>|57U*g;ot2N&%xmVZT3n@`hu5?i?4TB-i7x_7cup%5d&%65m0A7h*QY z^xhm1$c5ZSvX(BEvN!rTMz1}izhzgUi5@y3v(v_|)hxAj{i2;cp3Yx7__b#GXTFBi zpci^E9@?3vQN5*j@*Qs2q>hB+hApn@in&UDL6?$KW2@Q$oBA|U4HP`b0GR0 zZ=#7&RFmEb6ett3IO^PkBbngP6s{(sExMsBW0|3h^H*hZnzMW~+Hz1=+2(RQ6`G9t z?W6X4_px;U7S&JTh_Ng*9Z;2+_iicC=0-yyZ+`P;0JrKL$_ndT_U+{9KBpJC7j~ib zlGiea^_eZzaR>hK<#{$Q7%57(AG)#WtYX{t(SP2_YFZ)lDz7ALh^MRQ@m%B<+;95S ztu#|NJK!P`Sm1OixH4NDYhBawR+FmVM+X6MQQGVhH}w3XXHrRRb9MFJ`SQ#T9%iLW-W$kC(7kdH06=MnI5zOhm%m2)6FBNbV5YyUKRoW3y~W;m)1bBE3;t;S zL34mZ_}PW1#OQuUSZ{sdek0Fk{qS3G0e;05ddM})t)XaM2A z5WeLp^6=jv90}_mgv%?Prx?o5YLE_{%gWGbeAJ%=b>WI?Yl_@XjYlR^zG0SDdB?&y zCotaOL^m*}$=SrMg5BIg`sv*vZSHkCVRiel7#_RD$pP&WY&B45^FeOqPL5{0{OJ1g z7PR~D+NJxjtqp2Z45&9QrL}qb5Qhn}pSwyO(OY@5P3)+8^>lKmM!{lf z;gJ9e6wGq*&Ey-}$wAKLiKmab1$~rZ4io^Rb{pOBF8oif(2D6M7vvz{Qm?{2lNo1k zahiCr!DF#WohNOW7g5c1#*$p`?RTJ|Eks_*bxwQ(F|few@!9rGPh;a^ji#_EKKyqG zVd1ONkFlDC1;U1LF}*|YAn!UG-lnR4Sd7aY(q$*_ouE?G^e?2xE>COVFN~7-?SfJA zvnA}}qT3(5;H+!Oni71DR*J%$NT|f~q`CcfI-<|!FWaA<#qTD5i%oy%n@pQnFsH0{ z$^?-r`|I%0)7$%%1SF%fZHv?!X~e_`L)sv3X-M*m+$FHj9o!27N}dZ%6MGc`cB=sz zS$;RiJvoMduq83d;`a$j_opMc|5_tgWIK+b)Q>Df-{h+Hm_9(7S2O~QX{Gz7R(V@U zB!^8TqlAK3KNMfa*4j~}?_*jggR;Ci=Q)?%BTn5VR~G%4va3pdy_Ny$9#X1ll{$Q2 z6ge)t6ZbXW4`OYN`_cSG_DAlV@}iFA#D0hvv-E^RVXe+{U&g87*B0LJa_QMrkjAJ5 zUhaCNvO8GaSF&Z9JnOBm3*t8*0-@dP9BFK zj3SZu`3T{sdwtE+!1NZfUVK$t%C_1Vle-TSp7KmFOKu)?xwemZuKA+SJfmmB@RO~mf=w4`3-Ul{_dq1${xU9YExb_@o~ri4ntit;q6mWKI$xme@l|LO ztF!aMAx)9Q)8!uS7y$SnAky4&Os)7IOpo$~Px&v?bMx`J`EN`QS>zwn(~~<-cU0EW zB@7}vp>w5+T~2Hdx76o7NMwBz$icb28MnNX%tOthOp9fOGgt-&sWZgY;oJ5uP~r)S zE(AM1Z{aICzJe?nNPKzAE9Y!ym#JFwajeGx% zJhzQ(r{y0bboR&q6Czl+s4&MFyEw2brp}q6k+-S*7OjRAXDVsnx-XnJi>6$io4ggo z0X`UQ;w{dJX!=OuH*(c?g~*;`e|OB+8SMhLfSAaSTnE*U6w}9(Ed5SwJvuWAu=NHL zVV2|+iWlBJ0|16sYlnycD!^6^3}hh)S1u0{fE^UJ zkn_hTdF2gHnOYklc%ZIG4ndzHGhj~|8Vb-*cVFoO&*`VgDZkk&T zlqu%dz9}9XQlaY`TAXFo^2O(irJZ%3T(>ai@-q#ub(@{M(H1!l#@PT)15}DlwutXn zg7-nU6XpAUumt8m0;+5oMK0d{U6usxmT{u??@~r)+mkFu5MbY<=GcjJJvoWI<0Yy@ zTsnX|vP<=W{1hJ}!3D!C9_vPgnB3EqKMrG8$>SDyg}=2gz;!>Un^@S-4H>E;j^-fb zf1BAZ9^tNdC{`8Q&kSgx`{qcX{O3!X@PHn=LIe{-4o-(GX%E?22VX#kvTEo0tk7Qx z<*`C}As&hW=x3vV-XyeFwTiZ`N+aH%V8=s!IRcI6g?o_-2fT)HKufe4%X zSdi%2Ye?fV&8M5CuJ6NTg&|4r{^cn3;54&39VI$8c9jXIxf_&*b9uIUzl@RZA0U>@ z9N)H^q-G!sRy%=^ORqx^<6Ju;xqsH zKK0wTooe+R*!fSzQ^rsJ!^zg#O?XgJy&iT7n!E5oO{;gpUfr-86B3wb^X^0cN13&A zZHh<3jZ(c&^ghbDS-bFDC^i$*#6@k5qgOnUuAgHH932igEuW%4mgcwn4}R+9mIh$> zm!FD*wK@J*erorB{M5X+a*8Kl(3Qa5$!!$h;$s}!X!=;2o$L~RZW5bPmg5L-2BE8) zR!&fmhqSS{?#gFHflor`I1J`+&?u+(1t`}gnrM;+uXRzU2x4lRsc zzejW+XcQG>Pm=w!TSdTpLGTxCY|Z_f(bZN!f!G?cBrp~W%CPoab9^CC>qM^B6V)) zs{SKF?;1oGV3O2qjMYdYNxIYRjQ{J}iB!?ImX5=-_Q)O@c=6UP>J4esZy3yF4;|mA ztjGOeLaU?X`_o7GIBqC)&QLA`sb;>Hdg*s~SbL$4y$;P6n<~1{*fX(@OsBPP&772% zPUgwqlrE0)!{X)M-np2wF5j0n(;6P1CI$$qGsTm%yWiVO4%^>MELS0Vd>PWH5i#-# z>{Qs$!TGZvgU?0gVc6lZ${Zs^z+af994eA?-jtB{XEL0FjE3r!#Yc!t(bM6V{L19U z;+5o=Q%67uSf98EPx7D}m}7W1$$>*(RyZC?Kv7PNwg#zcM32{ux}7LsFs7BR?=+Y4st(sF6B zwKSjhZY|0tcm|imxF@!j@m9@t#Gc=TI59PZaf6X=~!k4)S(^GzYeq1esdpFs*(XL`JwAqDF4pf}Dbk*E9ZZ<1L&eZtPJA+HzghOqYAskG~^Ca@-}4>*s8BjUX;mh(#=%+R{_UXu(PPA#+9YZP6tcFbTFUCe5-!P@nvJ$lSunHg ziv3mduV(KXynXUmTn3J-5MWcmN43%8CsPc=`8*Zjb%xL|(-};Wa_^1J*)sUONTc;Q zNB>){I21sqY8Nl7x_y#XcnsD^+L5%@8w0s6u~NIX=!G(?cOoEEXT^n zxk(iTc}S{-bRtDQV3QEh?=C)sj~5O4CB@`}(122>a)9RzgO_YpP{ya6pR^{lmm@rV zYze6KxW(O%gSgBRx&id7zzb~I-rrgk$dX5)ER2Pv%$MDZz%dunk27&ntt-kCbDav| zIakdpVQSh{j$ol*E`LAnx^&Ml6j`eh12_mkdsAZK99(KEbUoC#k$^G42OclN%y2B9 zl(FJkb6qUS>k-8M{(gHlqX11DmIC%3t~J0+<7+GZ^p@8>7Zn9`6X5Q= zJGe=)4krcOU`rnDc0za3W@ZyDgZ&=CL{EXP-v-RaUIX6m+MmmNA_Kmc)bbS?`7U+x zHt^i1>uRBeaZ_;MdN4OMG-8BuX0yvbbGMr5;rABPUmhLZz6Nya6jtAfZDcIwe^Emm z@uc;veY>z^Bls1ZlbxJb_o>)?J)Xg(ed5*P3O~b9_d_dA0HFxPE%d_%NTeY`WxcPy zffiGC!#}ZaQM*qhHLLmg*dh##@E)1Tq zqgT-4<$|CVdd|w$~zd2d(jN{dhBk)hC!f`Bict2HDZ`? zbVNYBR>>m=E9AUJa%mw8(4D2ta9MMZRAd=ZTzuNOUu}HY(bg6xGN*m4P5xzH;HTG; z5FyIPb&&tF{H#yaW?K@XOe-^2PxbC}x@QL-rlA2c*5cP|m);6P#c^q1muCTA$1*_=Iemz60MnXfgK0g_RK#sPy^Cb9lY;6{oTl4SItiJ?uXBy{>%Lgr#24Gn5 z8m%^aHl@;DYhJ(Jc_zOxq&x=A`S++-4D=z$4`OB=-TS?wX@x$)w)8zrJ;r~ZH7?DT zap>0y#t!#B`s7}hMekWN=2)R52Q$-mMckX*G?`QByR=sE^kI9(A$~_3ziL4QVT0u3 zQQwc^XE71e1zv=#&7y#VTfEWw;5|<7ji4n_F#a&kz%4%+ zPwF0`2}>ipqg?G~YZI@F#VC|%CzV*-ELmu{#0;PN8>4yb3Wn5jG)#^yn&AADg?sbh zRjS@2d<3rwtT>)ql1lFsjEWPgDG*~p6Ddby^3jO1d9b+U&k)jf>eqondUH^j>``zm*X~N~N{Q3=6MsSot1cd>D&H$TkS|p(*8M6We?Z z`p6U}?Z_08Z-g0`lyZ1q_R9G7A$n8iaG#*g)n(%^=oZ!#B>aiR@IWxq$3KF|`WrtC z@GWBJFTi`5=0AiA@fW`Mzd}Woo$JPb6Dm-W{|OadCg+*n>ZylhLyoggc_k^FDzkRk z(R3abQXIC9$0d$}%8`NvMWIx~tRFj*o<#B6v9Zi0F#3%35YIx`it6ZCf_rs3iO=W8^Y=;TNA`>|buC5NkWx2PW@)dDc#9ZlKx+ z_Aa0r^Cekruj@QG#08Qjl|NIkz*KG% zAz@fxEy-coT6I3jR~$M4i{^Mc@6!~+L%v&27`R|b2FL@=u_ZU0dTKw#jc}T5>?h%T zIOKK&bSsSm{!#0dh_1Qc2UqCD_~5C19Q%o zyjKn2m)+g#dw=2V;BfI-*rY0*nA(*2WEGNq8yJ|GB?Z*Kwe^3gz6qYp_jaZqs+KE* zZ8macC0a_Vinlk!wB+b1U^~-?y{!v+LeQLhjSgdvXvI$z&{ba59-{{D>%~fafR3ly z-2wNAq0sLiErG_GUmmhT<-z( zWfUt5!R6%I^X=%b%%5_lTpn*ITi#v_9rR&tL+lt%$a4a_vAdOF_vhP74+?}1tJFyz zv8%k_Ev2_wSFLij6;)_2Dc2)akYG!!=n*bWR}kq3CJ1_dSl_{C20xS;4GtEsk6~ko zrE}WgyJz34R#CCD6U;D|o3+OHzfE8hB)h*yYT>%3#QajtYH_`c*%xSPI$Eti8LAdA zh^d|FuLewMMVF=tg3>EnY~Hpy-E3 zXahL#_*OQIipW6*|91*SHg8BFxHj3c?S`(enUBcZv-(iF;O?&ZQlPuwld^c1i1;*2!o;D%NY@J3wqF*s(l(R7aRPLZ|cIW=h=#ZNpz8g>f&G zf^KKcX2;=&niQ^#`o=XlElz(m4K>9Q!m`O+3$(DL3X6kfz&j&ix;$KB{@849cW-^ch_=b7q-7!MW4+0>-u7)bd0?|Q*}V(=xb>0~cY+_4WJi3ZB~Qsw<85%8NH^Y50|!a6 zW`+c+zF$dZnrUl0;(HS_A-aFV29m(0T$%?mbHCjh8st3=;`dh1LLXm~DtP-q*0HDB z!llm15mZF)8-OSna*Y%2QDi5E6MFXVD8gJ=jREbOa2~kdfM!1^@2dByx4VE z5?>mDfnm}ll7P;8G4Dl=3eRxKbNtsT8*4!OMM3DdW~b4*h0cz1o|No-ntOG!prTwHoN2B*G2eHmLKH9@IJ7EKCEnz4$Z1yM0IH&y)< zJ^{f>-q9p1Nc*V{cbo8JLJ&Ue5xwmCumOK!!xZk_cHt+*i0uFWcN4z>y{1SH^n*+9 z{BMClJ$Nuu8^m9PzJ$H(qdW^~Hf#n~`FX8>>e5#i?g_61EzZQk#3nPvr{7|5)1 z{^MimWiDE(!Piu)^BHaZ3^0~5$JzcYk=oAre%+FHxnF;c{GX=3SjsdJ%z@#Fc%z1lQ8mb`t+ajN3ZNxqPQVn$U|I5OqudOW&TFY z=itJb9e?3R`_cwx{*Eg(9A9dfjQh>~GN!L9ebaEm+Tjp{C3dd_!>MAmcEIGJ292+M z=#!!py=e8T8o&+dq9aFa;M4&V_-6k7nt{5?fLwV{8>2{$SkAQo_8px?RsdoK&$^~V zWwgmXB`MXmK`v+$>1xd`ji||H0< znx7q-KPGJ&lEnYru=rJI>9#m-xF8VMAQygmutrU2ks>DIoH9g;mk5PuU&tbdWheQp zea_>&`{A;l8#x?X5n}`TL5m~%( z6Ky7@HQz~wZ@;c0mw%^X>+sQO&V@-BV}ijf?m39_j%#o!wL8o6BDsc5bRI3y%yk5A ztA&}6XI7R+e)sy4qZO<0TwrzsC zcOOJ8&Om@bXSm3O@ypWcl=+{0in|@}-y#HC%KNS=?RB5-mUOu*_9XuZ;e8Q{?B z@q}5I5TgdVo%L*ostqn_KYD)IWPXN=33*|bR`*te3Nh1*wmq_9oJP}wv#Ma>fc z4pXZs`N?KumT`NL^?M3l?0|FkT7+p}J9^JgmgAa5Hl=#VC)|=O%{@sZc@KfAi-k|Nn!sSpAv6@5Gb)<6Ol2?1zQvbdr z2Pve^WFv+s+;H23nDYdSU#ojZCxT9zB9zwMu>OaW(3*RIp3ZK3`}`?Kxex0`r9r4l za|dXazFh|>OBE>68R5ZwD7!T0ZgRf58Ps3aqPQM^zD3i5-&qsxTC2` zC)U~<2;8{-)hxTWxv?uh#_9FVZL4z}3v%C18a&r@maz3bj9Z~IrK3FrvCz#__P&J#@w3xZ zs@v1ED{MrY;e1oEU-9R8yEr}+x{4)CYs7pt_r_UF*lK01{A^$BevLGeOz>lp+Wz!OqXQ$W2VEob4%l$YzZ2hgQZ< zFJFhj#R1Adw^KHUPwU|#j$ip#GObuHb?Qo5xVE!~a8CluQP@YT-C{kB65oaUwVuMf z;EzrZAO5&%XVjJ4#(QW+3Z{S!R7Ww_q6osF@vPl0&Q~((_B5J;jkh8TV1=TDfXTfKa-elUm#HrVRu43GNAmK6U>5PH-lpY7y@4bzP|qhN9KMXTuR(M185`;4J=J*1$h+E|qa==k2EC zZzjto9;5w%zqA>@!L15r_b{6qq&(S=MFqzxvlQ#ER4690uzN(Q3an~*S@5G&XH2sPSdrQfN zr~DPw13H`Xz1ZzQU9004mb^P!{|b{S+a|d=5NyvrjKM=5sr#XcVb5??(As0D+}7z* z1n3osSnUz4;Rpo8YWI5FwB^m8+7Vo3m-{X4G9b_O4DGLi-u6|ly!r7E;ozks6D!$m z?ulH#05^m%`(MtXnJ(Q8^n398E`i$AOH<|Dqa)@GC|)GWfugD>BO-YfJHZ*3Vdp8i z#b^>Q9=5*fGfIZ@WC7M(&Lw)^!5@aJm-KQ8r9~aBuVFh=eb5DR2b6O8 zanQ-q0wd)8L^Z&i|7jUxNpjarD2L|^3zyfAR^eRG33#6R8FI&u)+a$0PAS<5u1t4| zba!TPP@qPY_)62vwJQGXYt&8l5{He+Kqe4vrItS8&-BO(<`Lq{6d`vEY@2x!pbAlT zA-#-n^b_EVG6tRK>06=oOtM&r#$RblcfWxuM3P5aq3f?P+ZQYvRRB5|(C&BgXkt-_AVd-n@aR*uW)U~fWD zK{Gu~nKJ$Kr+AM($RHw|dEUzYsK<^aKIA+Z{M3MnkhXbIf4H-Q*&|=)NwQeP>pZw* z-?F-19Zx^G;KBSpXnnf(Q_mCIru1#$VlEbqGBDg&pYYzn) zf{68efO2)u5&KjyG?LJwhnP}~2{lR{`8tvO9i)qA^38!b;~46(CwkvrWg`V1xvq29 z-wB$G7u+;Xc0W^pV(~s8xP%FHsrp=bhTa`Ku|3rZrmErP0S?Id>A))2!sd@D^kWD} zl3Om!nLo=a1i};I6}fYTchLuz5|5TU2E_tRF4RTm@18B_Hg6EpP?Qm-IcP2Cevs2A zep2~Tkus!R7IUsJ>dqo2jKGW5OZbQHr(?v`^EgNN;IACF$9gS#cRE4tS5h`&xS0w2 z6!Jli7E|9w`MO(CMU-0maWiJ-@9lSUR;xrb5dIPi1w7ghCkUof(6}Y|ReAFL!p%jt zpJgRhICMt3Mr3I41p)>yBf^M z`^<#;&94Oj*>X`@lYUIQgMcriv!5!CwcDH~L_(aNHdxwQ2Fr%uH#nMG!0ZAUAR@7y z=5d-^2kq=oIj-n2A_N)Jv@r)Ue^4^5Wp4|QGAX$K-PG624Da)we^l5ew|aPP0(SP# z;WXzM9(#dw7N61jL^X!XyJG##HDR#@7kpm`Y66)ds=yiZYYg|Fek=3EtKGkDKVKj< zNpMplN6wh9{#dL8=jRFDD{ObaIur#tFco4gVU+fr;O*c-{B$Q#-=g&~qDg#m2DbST z^l1RUUscw7g}Fd#LpYEfaYOjgBselInciab31rf;;T?=i7Ur4j+_JnZJ6q8X#-`FI~-+P-!L@3nU(CjReA24Q<} zgbV!VEALJ^`Tuj_1mT}j3E*Fa6Qj=+*8ff6gtq|FbH5y4+1?|PFk9Zi=$F9NXG>e{1f@o^FXpc;X-69D?{0{Tr6jx||jor?8K1$YG*l6dX{1cA`7*Qvt@3)8y~ioR(Z zIen{z-AmszVeQDBc?#)eA_<_07zlsFZ$Kx=h_<)r&xF5e089)8{0cX&7gn3Je7Bq3F>7*4E)W5 z2#CP?0aau4eMTZ0l!|#MM3n`jaZjDBzs|a}E5PI>9QMx$=6xa>7F#hxlPLPSgGtCf zHy5iA^&$Y+40T0`8;F7WavRSubFCIXIV?LD)Jtn2f7Fb>{bvNSJ^0$hKiAXqigg4I z^ON_1v9ICvj6C*{I5NP6J5(khkLZG| zX(@$zN+Rn4BS!J0qOMZvg#`>%379>yYbf8q4d#~y03tc`_75|=71d&SU!?Mf^|AH5 z*F^rPj2$J73^)>#k_oWWIkB6T)k2mOK#YFTgl}9uCEsR6QG`{Ssz3%A(8d^11)cf+ zBDT#g;wSWv3v!4cb%7i*pgz2Dc#_LKJP+Rmx#$+RYW_v_n~25{_VLeTtV;zi${eBF zq~#wPyCdUZApitjJs@evapK?es9q=|xd6NLW82`!1jOQ7+z=o@cW8d^(Ay?6;lK^K z2n%8BSKEtJmicMutuZz*Q4Tri&GreHd6p=cP#Vw%OzN@UfT_z}FSsIqi2}F;PzHKJ zRAyDL7n-%qN)kfSfOAxcy{H;_GjAXrdxx1nN^%cs)2Oi=IT2s(*Em6-aIBs3q|G1D zKV!xgJcx-^i|>L!NECxO(_EdL6k{A^lCV6mn!ZDpfc*-ic!KzsWLO9(AdjMY5ibZ7 zjP)y?Z>R1${*7J$d7^)kDJ$F%{IV-WH=4)^q_eO7u3%5k^5X%dFhIZkI}uu&qo*5= z2Yu25oeB3^>EMR3%DWd(su& zn)aNtf7@FS#tvg7sr|AqHX@%5rR)&_Uym70)4if(P?YmSqpjP=oI8CP6%^%_2)6+J zt_#QN-|vEJ=OGbA+_fRe8nnLyuTsOa->jBgVe-AA?9m=eM{?=$Sx0C)$C?(g-g!BQ zAZq%zKl_K`_ue@bK(hZ4p=M%+CO(Exl&r`}h5!h~p?btjdpNk}McMjIWPyr8Ni`zw z%l+EmCpvgF8Q!(;m<(+|kr7fw=q-@mxUMec%XcLWVxJ&NBolNhX+wGq#^91CK=MTf zfIa_AHVPz_0BFduY1@Fn*JNOUp2vDq5%>iw>wpa8+e5I6Sk&)O}&l~F{77NaL^hCr_(1)1Egt85ye znVI_RaijvU4*UR=`>Ms|2}tFm4Z;-t-kYGF=6YBCoNECB*~PiJ@k??9q*8Luq#=`G zXBF5R?^h`YtVUeWga(UC?HaL7<74bw!F%&=8`w4AhzpG3KSz>2z{tYl&C7hc_U+_k zrQI0YmR*tGw!lRcZ|Mz$b);v;|N1gQOyEM|c4d^{$>+Np#lhl9KB{>B(I~{m-Tgd& zBJcTAoFHPQ+{$MGfS-ujGx_kQ)XP7H0oPBPYLC(3&8_a!lLuP?p#`SHy7|5idg*0d zUC&o_Kipt;YqI;x3E&ZFA^2J9fHG5ES-629LLC%5-|cP(|D|3*9?TM|0< zE5gc^&G@HqGE#vwC41c#&&JmE8>TXMS;`IZ(=n>86R;TU;o*tpI|Osi-Zv$5H4b6) zC34Y(J(ObYaCURmfOWJ%zvaDmx5b`dQ&iCJ%`hyBFsyfJm=+`XBVHeXSD2vRreRn;QUGzxhjF?e zH!*)(A|_=Z*8oiYB|_h~`Dmn&ho$^HZk%pxBSFw%Y1*R*U571UU#Uy?pW|{>2z`}e_MeI=Z)+ZVDjtlZlYzBGe3>y? zNQRfngKIF0x0E<>T?K-1NH8=%^_!!$<0?&OXuW4|HYI0=Z~P^EuPenG=%W9rfeIes zhu)RCFEQ!gZjfcO4;j_>w4<|hEE4-LDf^QhCWIwX z`F{IY5aYM>bv|OVbC7AB#;dB^Vq6T0$4j!l1(Wytx}VUlit8a+W{tnR4xFvyxsqp>h$l%u8n*;UAD5!4;M`(gDEFh^zVv}t@+PS+vhLZ*ZrF)lMQ=w)03bCL4N&sj3z*KZ~WXc{Ic*&pe8)CHq z#4Lu%YT^BB+Lyeo?kxq83-X;3c2s5EPmNF@KS@G5JaAwC+b;F1Ao`VU?5Wu-vZNj{ za3X6q_4r&2UQ?Z_)YB;y10l8x+iOKwx%YeBnP1nW2sPZ(r3rCCCNaIAu7Tn((NQ>> z+A#`uv!C}p&@UGF*88kqU6%j_OrE^mxgK zGVz-Jt^s5zb=0a`rC_jOIF8zggHO2w5rhG|LJB#8rqvK8_NURTfC{U+JV{Ta-3^%% zt}-enOU{VMQ@E!~qAC&9JN2=C7%ujbXx#1Dm%3WMSnA1(PTiw$Dz&Sp8;|3eTHu^4 zHKJ5--^<1g&yLzjw0PPeYqFDhDkCR%$i3*+31uJVD*eLfy%?74j84UIMD!he4c)%^ zoRxpBlJ%1ttwyOBS5A&nR_$VyGB9utPM|i;;#0jRC4gv-t49(Jd@K9911dbL)biD) zR2D6W5pjqVCzvonTv_);-|fPvtdR`-o{2EV6B_OYIz@(}2BY-OD3w0zCrHolM2KZhIHXVMcU=btE zab7*G4xn=JC8`2ncN(6_kn97)_yrNCeRlNQKcGOKKw^`dZ)~Rab_U9T+V%_u#Mo`D zZ`~nj+rY575``3D0Jh8h%rYz7M{Kz61>Vl2!9_AnoQGzGc=-mmyGaDw6fXjSF^|H0*Im?3USrR^iAt}qJaFw zHYxds=S4M5g~F*J_L`-x+Q`Y6CThA=SXhO{=pF7&YmK=Zrt`A1=`GVA6FpYQR7(^t zlErRQ?oyI^%Avx6tlw1Y2-s4our{+46>3mUPq7e&wqK4#nQ={#6^hzSMw zo=t2LEkS&iEvDN#MjVl9O3dg3jPDKa>|V%t(?&2h2gSxHN~E+%DLy&)UIm7yD`E%v zpteFOL;3Y%9W6B?Uu^Yg-IyRP;)jHIac3mRc}Bj?^1RC4bWYrkod8_NGt{uyWc*SaW>;C8?-HV^p(*bqf^HsDU z5B{AU2e|&5!Vb56h(g7@$6Eg-{7+&rx4B7@)T0C|jVCGPl%Wj%SW62+Y}p#(W>z~6 zf5}>U*f8ZN>zQPTKOmFq)LA*dCE?w^*bemCx9RlMRf0#YhM9^8?(Cz;CXm97Z|`p- zpxlZ0U|1P-3Fy>+0rv zMV_ZoEW2I`D2Sln7SPQkUy(rGe?}%V zlQ%uYpw!r{$zqcRA4qp|=&f1XlCa8cm7nmqGww+R9(Tf}{nJrKClinITm$I$bVuL@ z8+3LH1SneKR_|Zwlr-rEu6l>Jt1Tiqz53%V<~+8YvCFIvGrkQ@GNt`rrCjGKdC+ z#WA&aF|GO)y;2^oildQE()&8+< z%G*iBp&n4GoDKuBWYTjFZCM`&OJ{)y=@%NQMLFri3IBAIb&?t<7B&O;GxW)y7ZQUX zv>DNKB~pm9>oj?wbznIAaEu9M`k1iID{ zfx$jTnF0!CD$80Lylx#C8sTzy24>@VYv&MW>jPqh7>NUL%e-l8dg{<$r)@(=%dPz( zBDO?gF}EK5!T$H|?3Pt`V9TXn2dg>l_Tv>{gcxl7SNd=1@j#(w4uS0vV@Jr?WK-3Ur$`~gQV#m945gJ>K&P5TYq&EdL+0i~TB&Fq!)a}-bJ=2$|tiW`l%th2Z4kvn*Kyu&zd74ab6kN5x zds{P{@@an#=)!dHv{t(LKA;DZyKA1i7w|^D>R@SmL#LG^@zdK7xC?&Xys`A*e}&O8X&@MPZ=3j!FB$;?M2PmsnbB zg-k!v17lP0WYILNd}G`iXAh}geq0;R(z?z7MpH<9K*%63>qQ#tKjCg(yx2)XE*x8Dku&P4ZqNZC88aODygj^@Jmzd0U)zpDX;j}R<}_>ZY%7OK zN?!N7l|U#7<7?|%`REY3*14H77Eh z{S2`!=~S8HH#mRYg!I!HTj|)3LC0qIAJfx5-r@;h(*QF>ZJC1Npo2oTIlG$HZHW~Z zKL!=E9r;Q43wg)p1b(i&8c#n9WPS;F9ksA2BrU?PF%dH=qCd!wM@%~YRnG(3R3JL| zG03Cg+R`u00q=!0le!9l^DAnPk+13$2&!|B`qtfEGBHY>Ly~e=^*2K~dBfGj8Gf&L z@q3#*Z%N;Iq2w`JY>RUZ_!U2?R>!t)U$2&T0frt$z7n7Xg;~u-#QH38Po zLzY41o}}>O?>ITUhSFrzJR?1!tdh+B*3Jxq4;RD=Vvjx=D1^ zcP9IhTI^en_bC8*Pv~FKj9{uf(0zN>e`${2NGj;cG{8N$!Dl1@xynVtX3v^ zX*jQ4Ub7L+Bwhu35@XT}80-eR&noyAh^#ZoQR_HqdLdFDc!Wh!fV$<54J!@VDhUT`P=#e;eeOt!Gsw ztkNk0PTujYVg4W=drHpo1!U2`b9CBzFgzkG!`9WA~(k-1)8vxMt;^?ABohIL6(punyK zru*CG-y@1TJkJNYxj;t4Qu!-Bvg|^$N=dW?w`E_#0l6Z6i!{)YF>Eb&(ii0=;_0my z-XXc2@&*NL(#_D9*OaTs;1U7fepzp-BP8Z*<-iI}#jY)U;f&Mq%vAkSel{aGux?u) z>ne!Q8lMY#9bw%W4Z>c@L2q7Gjeh|fL!AAOm0vfII1 zfYrFL@TI4dSLd_b_lu~{Od{TTw%Ks}o+NCbV%qjA&*}sEUaA|vCFYA!VF_5V1_c{l zM7^yE!GK}>tpY1`tB*0^F%hSBsE67UAk!pMpq!Z64(6_u^Ak~oI32U~XF|)hypuyc zO`&3F=E-Ywgxh0SEFLuy}`)>!z_zS_o zlpvG=J#d(fNUKae$_b~?Mt2!zOgelN4O?54bp8u(=2=5`BKTPy;{wXF%dQm@Q4r2V z9ol73GpUyBc~=&}^nxAyK^Ia%5_f<=ZO8%Ram-I3#&Fyn4Cj}{1@yn+gv}tDGD(MM zTgKS@pl=e~#?jLfpyQ7j7fQwzIj4Cp4jiD}fS=WXeSI4vky3Y1_qys*I_$@XsaFUg z@&v?$$8&=j)ir5QD1tGP?kE@y;2E% z``kmHNR#(lT?#i)n+`hfW2j@BYR6~w^paMN*+)-{53ZrIKWl6mKjnSOxM*8H2766R z!$r(PLfOC*Ey*>Xz=1A^bwMAqFZ^HYT=5eSJpEI|WpO(#M)VBs2*`po&ng zX$7J^9Dqv-1)1Vzqb&Pu(zD?hK9ap}NwzO7DCA1{h8B3_X_)RkTJ;913u+9Dn1S9CUx-x_lr_vZAle1^jKq?|wu zmPwDUKU8=04~+P`%~<2Zi3)Qe$h|fFj=qJWPOdx++qC9OKq!TK4$R{U(Y7*3N-z;2 zsO)dQyKsUqX0wSFMwaa^$Q-_-t{d!lrQ5UE#1cuD zi@gBTGA0Ox*=Yz^ooPHBw6fNF$x;NZr?6l1;KAhAg=TOmqezUZ6J=|wsjxdBiJJmx zpTX_h41@JCj_!&ErF^Ny@4+`N=em_JYQSqkjP<;V1d&H{xx`Y$Y7fLus42+GPq|}t zf>F_}Xq-dc+(lwA6~)AhHVEi-IzMDoWg9!sx`NGYhLJehdUGpg2>*6M*eup^YZ>Mj_XUAZiu@D+>>>Bu_T0e z8ExMG$b;cDPM+?hSc3*Mj-_YB+(L@9(lyh2-$=~lqOfV59BlRzsWKV8GtJLvZfh%N z#C@t}2wWERiBS zVSf{_E}84JI_%t=P?py!$?wg#Gi3pOWay9EKq;HlaIyG#zx2MZ#2g=iy`AO*;yJeG z@lk$qh^7o<9Qz~BlF3E&o0J<4{)i=66sd@h(FTXFOLi50lP0!ujTnpaOD5W=i z?JVgbF5=*)9)~a~XeG}0l<~M(dzV;5vA&LgV7N|8*;qzmQ z8l=lWNYf{ib7LzRv_*;AG%OWZ}^#&ePh|Rm$lubFBPjC&!S_{&7IoieGYQe zEQ%X<14!!ndGcK;ZlwPv5EAH}>&p0JswJ9sdpW-vF+JP;I2kt5zsTNN7!$i9j>Hys z_y3tHS1N7BT?qx}@Wcwf1b(qAu5oVbuV&-SoTPjechC54828Z$3PY3-i3vJfsrYIKWgK z4H0Y7e(&2YEe;>yjKsbDt-U7`nDXn**v8=k3qa-Xk)|;f`R$77EZD2SI^M{4Px|{n z6~B_HXmXH}^VBHbeC#&_CDH?sT5+GxxRo>9yQ@-2zU>0NfU$L$j|Y}o8Egl zBxFV~-10)RYO_JSj!xqJVlJ|uE><3A%6K>atE^**Sn3>9S6i!HcbDejzUnY}h=R1^`_I=-jp^m8 z&3E2>Gs401wW@s;yjyUE?uGNCt*>@zuQ_5BN+ufnpJ4FwMaS?8HDE?mgto54w=IWW z*o3>Ae5iXi>1-2_(0Z!|v6Osfid9gIWQKKBB9w<}Lr>YPR(;Oc?cGe&v9X8W2-Cq1 z)a)E^2xeRIham116<9W0aV2>n9x}bpF}C8|On@9UZznYO(Jek{Bf0Z`oxpGXY;2;3 zC-{Zs*FjoS@KlA7TKv{AC`nqei9~rc^F?1XGQ9|U^l2^Plsc?tu)c&Ie1-3hmycoL zLkVsH%flpcHF(5?VkpBt!il(D9%R!=pEIixJcc2ohTG6 zMHDjk(GI?02LEGd_2sC>LBWR`aAmftXl@Ny-%mXdjAIwcEoG>KiIAOUZ5?{WkN>ef zNJvp%7)M-kW;uY1*SRAUupJcyCfQlWwKh;NTYLR~8y03F3v(I-z+#^b0erUx>~7VL zyX@~$#QTE^&OARrt?#AGfBouxL#|e99&{rE({<_%y9c+^{htZoF;=ru3=Fe0bISL=tYM1lkS!K%=urFcKEwJ9@bIcL z2(KSSL0SO;l$UnCJEkZ|$F`Bx?mLLr*pgHpavoMx>H(0FE_GCVV}#00v$`Xm5hCj> ze;lu`2acaemA{!g9H@WXt(;t)FDO3jR~??gQy8v|8Pt+PZ(62^Mv6bU0F!S~jQI6> zsyyGmWERyapB)Hn&*gWaVrpegMJ3lTS-3p4PP+G-H{Y9;{U)*}n608Oz&d(bf%W>S zF3CD&J{tR(3UHTY{!xmMHimx?x3(14@LwAG_U$EKuqAF|zPx))g=JF&Jew*+y*`O= zIm$G(ui#+LM5ao7!3AxD`P9FT-1Zf@o3ko|&8^>=gFvDck}+rox^`)Se^&JmLlc~{ zw%{Ejh?IN(bL*frN*)(gRCtZXl8jzk=mrWz8B+CDdDq%A^RC&GYycnvNC7w;0I!@t2oV7I0|3Cp z^fzt?@d0>na{i5(Apr1(7y!h?{>D}y?!N#4%F2IZO|Xt11^^Kef8#Z<&Wjv$2!06u zX(<5U9t{8_=;^3aUSz%qnkh9kZW@4B@D2btj08M$9G=^N7b2vAx+?Hygmn`HP$v~_ z6#%GBCdb==J^?Z>4HF~)P<0ayqCU@ZdyoLvys2_eMt3;*nGUfEJWiJ@C`M70ch}TW z1^*)!j%F@f|eA!89w|8#k%_fgJ(h94W zTT*J(e>-9ANNsH!vJ%9d|1ihGwm07#E6`TW+x)dLJCwO|tz1tS6&PC;HTNqF%Qw4% zC#flBcQGw_LCaobUzuB?149zjV|(A(RUz&&HGP2#?K|;D?ZzZ*H#%iNu9GJ0W&qPI40WlHjgQ zx^<;`Z&Hx?Ms=nF#p_i}Lz=gY5qWf*@qkaScl0pAqvAs5x#pR42a!MTV65oD-BfVE z0L@!EH{YmQNBx_j2PYd~`ZM&)0SdoC@c$or1H#a2Xzip4BR?p^<8YVzU+Y|o{_x30 zI$~Sbny=aYWnvM0f-hR-{?~F>-ja^YaW)^i%LyMB{5)pSeSCM*q|%($CFd6p`B;M6 zuUSY~Zk`E^G2(yeM5s&l>9L7V&XOHP6`d!Q%)h9obt;QD_pnREp7CG)*m%H>=X_XaVKwR#6ai_nJ;-Ih3eT7u8+VmB$#Um%Xzc-1W9QvZ<*!;yN~ zy*F+S5%gg{XxZii0!o#k7o87l{79LSN+tN6yF(i?ZdbDQ=x{sJU7-J*aXsB^s3Mno z0Fz0pISNmTSn_pA~S9x^!i=)ao0YQp3htM6n8dw$TPk4sHQa;EkVG>go4 z_vt>3wr?kukd#fBnH}a#)mV%AuB*(Y*(jBn-rs`EHemjOG-Db|6WUB~iz`GHz2kUh zASj5dhG{L3<=wX)514on64tKw@v7hZ7l2#Y*SA8SH;}O@e4WM#`?XF3tMtb=x=JoH zen%ATVd4Ux*NUtm;`a(JpmN_WuTYpHr}IT$e|S+Qvak@^XYeD-^=CKRL99jCqJ9i% zL=sV8d5H#p>rEa~>X)rS_iQeQ?G}w;PHvZ?bwyHzop^=nVzlX2Ek4(+@n^!{m>&?o zZCV#ATnghqzGKTy+`VVPuPw+Fw&!xbpicAV?bm`G^_Ro@Z$sU9+%HGOkD_F0jE>{Z zjqkd}tDjM(V2EC|A|To;C4KEw_%xT7!1?ob-%@597`)4SXn+%tI!>#wN^5 z{(~~WH5w56qYNVmF8;q##t}go57c(D+dV#r(Z`;^gp>;FaNkhMZQOad&p72kp@M7dh!&jN0Ko}Gf8IgQv(F;geR4#eP z)k|}_;QFz(P^AQ^0k0}yvif%3R$7T)pwKs3JVD4{+Sxw18F&Qk#W981Qrm_WU&t#v z{6@_2%4&nvs3^;BEA!ZI)REV@*>O`lCmVT(sT2$3 zsA?E0>R^xup^vsE(hP58RbzePreEcdU4fhn-e&A!i>;$Ps?&m zlU*wx)mA}Fy|vG5yXv_@vEm-2wE$ntewRV%EsYUoo&@`6YW`b;6`Ug?BRqhd}~$w^5`yYrJWs=T~}h*RrW zt_YcpZ$GAIAOXL-x&qtosiR_IVs~akbtaJ=*Aq?$62fneE|BLQ^C)>@ZXfi-_N^WS z%CjeL9PMiam^=vnefT9kQT;epy|uJ^v~xJKqD`B7^x}4QM<3VGH>E)QY&W;U0>iVvME8F; zY-}5KnVeF?()Nsm7y{(8lI64BsYtZNjG`AWG%<%p%RZ%D>ATsn)Rger`dZL%run{c z?v#Ky2c9idBYaOUDl=ZyM5su5{bAYVa{UIgHPf&JPdCFT^QH_hjSejB&q2AY6r+wh zTAu+rErBbZ$U54K19NLakznzNRs$=QL;JIxretuAwF)Z5@~a;!bz7uvE<2Q;2`|R> zTUH~EjWH;%gBH_aspWYmfl;1DTu#?dh+V18JM=)HIi{YBz39&IRA0Kx+FgmT_zj?O zgib^sOOmYnLMgrI9iP`gr#|B-k&KdUPL1*W>cK#l&y}6^jHlh)u^Yhh!E{R@lW{{J zir-puMN=U1b4*`jmx#W>IGwz|U}fk2@_s;gJBGH)yhLi?%2N`rXW%tHC+`HQmdvJM%iF@V=P|Nm}lk!UyRZm1Z z^*;nBxzXpMS%lOm^Xb3|Q%8#Ta7}hy=~|s)AI3nF^oWyb**kwSN$uLuxGZ~rSe1r! ze`L!NWFGFJV}GaLlmTFC@|ML}NXufr^qVDSjNJ&*Dx@{N>N#Vix`h#rL(jPf$;y%k zC9OaAI5vIsnB%%xXb_elKTw7FDd+6_LnhF`%BTO3ND*Lt#Ggd!R*?L$`cubX z-84J7lMT(|uxlLsAF=X#znp(K6&-yg?6m*gXUyt})7cQpXUczfSHp3)W4gKZDSp#x za5RcY9JfuqJ-;y9{L5Ve`DmAbC{k-pL(8Wp zhus5T-+D*KlFSb+B)_=)3d6vE?cDT>`gHgUPj|^vKi2i*6m zRd)7pOYCcGt?<}$d-pRtyObM-^Xt?qtvGb|{pX7wj}@Vu;qQVeeu}Y;oT7%8&UF}G2D9is$aSHwQZcMeX;8c%tRUT? z9(l*&TjM^q||IHf(qU3RlyzF-Uw{4g9?so~8;mY-2YnLR`cg4~jROY($SsE+7&E8sk9;3MGdM_bY zO~p`1%Hj++uz5B7(`k6__z~;H#4v^6LIo(>`pG~au@ky_gn`^sOV_6n<<;2a=RX@b zn*0RrTkTxVX9jB-QYtJ=opD3uy~5qW!hLf*6R$e^XFrhIryJKz;e{$l z7ils?0qa|2@0Ev~O5!<-aX2+GvBR7Ph=kBy`PtTR^ zkJ7rC-|leUiW#s`tl)-rf`MA!jGVPo&WZqV94 z{7NGN`9#2wC(zyEHKzx>x4zW}cB)6BgqvEV9CPRy7kODCvlV`D-r#9Lop zvmNKQBSI!5Bt$_$Va_x?Qzai48R@7V@Fc%lTT!sF)c7avFvM{&N3K4gpdkG)EX!SX zJ(Ggc6U25^++2N&kuT7QhvLmxm5}rq^#9kS~+t`M7 z5#%|m&LG_>5|CN%z>`}nYsVk}8Ml6{784{Qx=}xjObzhrmqLr$zyPZ0TR2_{BI|g6 z6M}h~h;fi$Hd{0JVIBIY{TKqd3qT1Fpj(PACR+>|LAhUxr;P*lDHOAzxHNK_05! zkgOIAM^e35FR&ElWa>-De#YKhJYMIQ@)ygU3SI#A9-kSVc~H+)dInyjl&SIB4eaLT;A%F*{5*eXliuB!v3^HVH&Z-@AiFQ(}BFA^7r zr)dVg0gHp!FH6Vke8L|t0jRzZ;Iumg*k3$-2?PWq(|^bwSIkdrkp=P>SwGix@c^48K15YwedrZs)nh zIkNa&GzV_ZH!EB_2`^HBaS*;GrRz9z>v*YYf}`FZrqPn!c?B;dr<=GPvhz#-R0n(O z2D+1tVU>;q5?q_tm(|+Xe1BtBk$kf+_@Y{|8E(hW=muJZ<;&?N{fxWm8V3{ffy4Fl zLk9`8rwhxgyLISB7Li57SQTC~Rr~waJmQxBNq)Bb!-)1r)rI0m>zg!kH=k_0tUelj zK)VEgV0AW*KTR4LJ=QJjXC%GVyTVvN}dC3H+Up#`5trPkFFuIXhc8i-7L)$ANH;Mrpd-lny6#<|K3Dk5~D z4oXH)H_Pb4x+zBD)P7`Eag}`VT!tbtQtvG8hzXK+6PE|xRn7N1GJ{L`TzPX`-z8ac z6^@4rV~tPt5Qf{&>Vk*Iq7^DeJ$%}`*7V=Z72awR9N0e6>JBGOrN>3p!=yQLFz1sD z*^{=^qm!=E8E=`2Hj8wb>)LM2eYd7cY=87x_=>Yms_6FTG|O)73XxN3-v}3aK`eDJ zM4fh~N1Bp+?d+_KCKr_lDITg$&Syd+Esp zupQ7Ad)8^SXXSm+ia49;*_pLmrRoZ+9sLw6EFCKcguLiy3>L19Yj0?1OtE*5!w=ye z6z**nshc{~WIMdS9hSc+nfz09N+T%-7~V-+dg_b1|712i&JnYOU|v_072HkQ1=^A(|t3qZU^j6K6Q*6@P%Ue*`r)QVz-P!UqIToq?QKMD7$!Kd}d z1DF64xLVf=R*Q=blCeZ;NlipOD4O5~(+evC(^vjVCr=s}I&|$bd>?G7v^ou*e>)Id zyL#J$cQ-j>m(L=l)v0&JKM@LZJA!fzCck9E_b<(2rQGs=f=STo{liW=p$eZSqSnP; z)BLO+ru4m!#AQ2Z_vhP%ILb*rtGPlL3%C$0#6>8iT$kHBOzWabZZxv%()~Q}u^vA+ ztNpI+=IJaZW^o!Vx+wvz{O(fR&!?FhT7AmyGVo^mQb8C+aXm&HvwH1Bh(}ham)~9V z2S*7$dBP6{3n_CXKidzm6+PV98?+SvcCJ)|%;=O2jR%%9rAU2ybH77Nvieep7R5EC z#`6m0pC}~hRkJL&W4-Y4i`%8L7CO8nZCz#IC)3Xd&@`T35pemF0BR#57)kYMYahGk z8}Mmu@Po#vn~&f113zTH;MA?Pu~xI8rP_)2araG>0ak|tYkPKLw38-@gAK!_ZebqmLA&a#|&RfIU_xnn5QLRKY z&=be3#btZH?W&1jHX_l?=So)hM;}nK6v(F%pQTYlY}2+&8V5A(wmI&MgZqn}vn$Iw zp8lUw)_kq1IhGYXF1lRuI8$lb9u}s|D->G7ZH!UHRNtvXw&U!mG3xi2Af2i>8eUKW z>T9;_kPME8zTosOM?M8Dq|6`1paM!Ys{LSQ5DUl1fB@V!5r9Grp$6PiQ4(); -var outputPath = args.Length > 0 ? args[0] - : Path.Combine(AppContext.BaseDirectory, "icon.ico"); - -int[] sizes = [16, 24, 32, 48, 64, 128, 256]; -var pngList = new List(); - -foreach (var sz in sizes) +foreach (var size in options.Sizes) { - using var bmp = DrawGemDiamond(sz); - using var ms = new MemoryStream(); - bmp.Save(ms, ImageFormat.Png); - pngList.Add(ms.ToArray()); - Console.WriteLine($" {sz}x{sz} OK"); + using var bitmap = options.Style switch + { + IconStyle.LegacyGem => DrawLegacyGemDiamond(size), + _ => DrawAxDiamondPixel(size), + }; + + using var stream = new MemoryStream(); + bitmap.Save(stream, ImageFormat.Png); + pngFrames.Add(stream.ToArray()); + Console.WriteLine($" {size}x{size} OK"); + + if (options.PreviewSize == size && !string.IsNullOrWhiteSpace(options.PreviewPath)) + bitmap.Save(options.PreviewPath!, ImageFormat.Png); } -CreateIco(pngList, sizes, outputPath); -Console.WriteLine($"Icon saved: {outputPath}"); +CreateIco(pngFrames, options.Sizes, options.OutputPath); +Console.WriteLine($"Icon saved: {options.OutputPath}"); -static Bitmap DrawGemDiamond(int size) +static Bitmap DrawAxDiamondPixel(int size) +{ + var bitmap = new Bitmap(size, size, PixelFormat.Format32bppArgb); + using var g = Graphics.FromImage(bitmap); + g.Clear(Color.Transparent); + g.SmoothingMode = SmoothingMode.AntiAlias; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + + var canvas = size; + var groupSpan = canvas * 0.55f; + var tileSize = canvas * 0.245f; + var gap = Math.Max(1f, groupSpan - tileSize * 2f); + var left = (canvas - groupSpan) / 2f; + var top = left; + var radius = Math.Max(2f, tileSize * 0.16f); + var center = new PointF(canvas / 2f, canvas / 2f); + + var tiles = new[] + { + new IconTile( + new RectangleF(left, top, tileSize, tileSize), + Color.FromArgb(0x74, 0x8E, 0xFF), + Color.FromArgb(0x5B, 0x70, 0xF7)), + new IconTile( + new RectangleF(left + tileSize + gap, top, tileSize, tileSize), + Color.FromArgb(0x7C, 0xF0, 0x88), + Color.FromArgb(0x42, 0xD6, 0x63)), + new IconTile( + new RectangleF(left, top + tileSize + gap, tileSize, tileSize), + Color.FromArgb(0x64, 0xDB, 0x8B), + Color.FromArgb(0x36, 0xC6, 0x78)), + new IconTile( + new RectangleF(left + tileSize + gap, top + tileSize + gap, tileSize, tileSize), + Color.FromArgb(0xFF, 0x6C, 0x84), + Color.FromArgb(0xF2, 0x47, 0x69)), + }; + + var shadowOffset = Math.Max(0.45f, canvas * 0.02f); + using var shadowBrush = new SolidBrush(Color.FromArgb((int)(255 * 0.16f), 0x2A, 0x34, 0x56)); + foreach (var tile in tiles) + { + using var shadowPath = CreateRoundedRectangle(new RectangleF( + tile.Bounds.X + shadowOffset, + tile.Bounds.Y + shadowOffset, + tile.Bounds.Width, + tile.Bounds.Height), radius); + using var matrix = new Matrix(); + matrix.RotateAt(45f, center); + shadowPath.Transform(matrix); + g.FillPath(shadowBrush, shadowPath); + } + + foreach (var tile in tiles) + { + using var path = CreateRoundedRectangle(tile.Bounds, radius); + using var matrix = new Matrix(); + matrix.RotateAt(45f, center); + path.Transform(matrix); + + using var brush = new LinearGradientBrush(tile.Bounds, tile.Highlight, tile.Base, 45f, true); + g.FillPath(brush, path); + + using var outline = new Pen(Color.FromArgb((int)(255 * 0.26f), 255, 255, 255), Math.Max(0.7f, canvas / 60f)) + { + LineJoin = LineJoin.Round + }; + g.DrawPath(outline, path); + } + + using var shineBrush = new SolidBrush(Color.FromArgb((int)(255 * 0.18f), 255, 255, 255)); + var shineSize = Math.Max(1.2f, canvas * 0.07f); + foreach (var point in new[] + { + new PointF(canvas * 0.30f, canvas * 0.28f), + new PointF(canvas * 0.67f, canvas * 0.30f), + new PointF(canvas * 0.34f, canvas * 0.66f), + }) + { + g.FillEllipse(shineBrush, point.X, point.Y, shineSize, shineSize); + } + + return bitmap; +} + +static Bitmap DrawLegacyGemDiamond(int size) { var bmp = new Bitmap(size, size, PixelFormat.Format32bppArgb); using var g = Graphics.FromImage(bmp); @@ -33,173 +118,211 @@ static Bitmap DrawGemDiamond(int size) g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.Clear(Color.Transparent); - float s = size; - float cx = s / 2f; + var s = size; + var cx = s / 2f; + var tableL = s * 0.22f; + var tableR = s * 0.78f; + var tableY = s * 0.18f; + var girdleL = s * 0.06f; + var girdleR = s * 0.94f; + var girdleY = s * 0.40f; + var culetX = cx; + var culetY = s * 0.92f; - // ── 보석 다이아몬드 외곽 좌표 ── - // Table (평평한 윗면) - float tableL = s * 0.22f; - float tableR = s * 0.78f; - float tableY = s * 0.18f; + PointF tl = new(tableL, tableY); + PointF tr = new(tableR, tableY); + PointF gl = new(girdleL, girdleY); + PointF gr = new(girdleR, girdleY); + PointF bt = new(culetX, culetY); + PointF cm = new(cx, girdleY); + PointF ct = new(cx, tableY); - // Girdle (가장 넓은 부분) - float girdleL = s * 0.06f; - float girdleR = s * 0.94f; - float girdleY = s * 0.40f; - - // Culet (바닥 뾰족한 점) - float culetX = cx; - float culetY = s * 0.92f; - - // 주요 꼭짓점 - PointF TL = new(tableL, tableY); // 테이블 좌 - PointF TR = new(tableR, tableY); // 테이블 우 - PointF GL = new(girdleL, girdleY); // 거들 좌 - PointF GR = new(girdleR, girdleY); // 거들 우 - PointF BT = new(culetX, culetY); // 바닥 점 - - // Crown 내부 포인트 (table → girdle 사이 facet) - PointF CM = new(cx, girdleY); // 거들 중앙 - PointF CT = new(cx, tableY); // 테이블 중앙 - - // Crown facet 분할점 - PointF CL = new(s * 0.14f, girdleY); // 거들 좌측 근처 - PointF CR = new(s * 0.86f, girdleY); // 거들 우측 근처 - - // Pavilion facet 내부 교차점들 - float pavMidY = s * 0.62f; - PointF PL = new(s * 0.28f, pavMidY); // 파빌리온 좌 중간 - PointF PR = new(s * 0.72f, pavMidY); // 파빌리온 우 중간 - PointF PM = new(cx, pavMidY); // 파빌리온 중앙 - - // ── 색상 ── - var blue = Color.FromArgb(50, 110, 230); + var blue = Color.FromArgb(50, 110, 230); var blueBright = Color.FromArgb(80, 150, 255); - var green = Color.FromArgb(60, 200, 80); + var green = Color.FromArgb(60, 200, 80); var greenBright = Color.FromArgb(100, 235, 110); - var red = Color.FromArgb(230, 50, 65); + var red = Color.FromArgb(230, 50, 65); var redBright = Color.FromArgb(255, 90, 100); - var greenDk = Color.FromArgb(45, 170, 75); + var greenDk = Color.FromArgb(45, 170, 75); var greenDkBr = Color.FromArgb(80, 210, 100); - // ── Crown (상단부) 채우기 ── + FillPolygon(g, new[] { tl, gl, cm, ct }, blueBright, blue); + FillPolygon(g, new[] { tr, ct, cm, gr }, greenBright, green); + FillPolygon(g, new[] { gl, cm, bt }, redBright, red); + FillPolygon(g, new[] { cm, gr, bt }, greenDkBr, greenDk); - // Crown 좌: Blue - FillPoly(g, new[]{TL, GL, CM, CT}, blueBright, blue); - // Crown 우: Green - FillPoly(g, new[]{TR, CT, CM, GR}, greenBright, green); - - // ── Pavilion (하단부) 채우기 ── - - // Pavilion 좌: Red - FillPoly(g, new[]{GL, CM, BT}, redBright, red); - // Pavilion 우: Green (darker) - FillPoly(g, new[]{CM, GR, BT}, greenDkBr, greenDk); - - // ── Facet 내부 색상 변화 (깊이감) ── - // Crown 좌측 어두운 삼각형 using var crownShadow = new SolidBrush(Color.FromArgb(25, 0, 0, 0)); - g.FillPolygon(crownShadow, new[]{TL, GL, new PointF(cx * 0.7f, girdleY * 0.85f)}); + g.FillPolygon(crownShadow, new[] { tl, gl, new PointF(cx * 0.7f, girdleY * 0.85f) }); - // Pavilion 중앙 밝은 삼각형 (반사) using var pavHighlight = new SolidBrush(Color.FromArgb(20, 255, 255, 255)); - g.FillPolygon(pavHighlight, new[]{CM, BT, new PointF(cx - s*0.08f, pavMidY)}); + g.FillPolygon(pavHighlight, new[] { cm, bt, new PointF(cx - s * 0.08f, s * 0.62f) }); - // ── Facet 선 (흰색) ── - float lw = Math.Max(0.8f, s / 140f); - using var facetPen = new Pen(Color.FromArgb(180, 255, 255, 255), lw) + using var facetPen = new Pen(Color.FromArgb(180, 255, 255, 255), Math.Max(0.8f, s / 140f)) { LineJoin = LineJoin.Round, StartCap = LineCap.Round, EndCap = LineCap.Round }; - // Crown facet lines - // 테이블 윗변 - g.DrawLine(facetPen, TL, TR); - // 테이블 → 거들 대각선 - g.DrawLine(facetPen, TL, GL); - g.DrawLine(facetPen, TR, GR); - // 세로 중심선 (table → girdle) - g.DrawLine(facetPen, CT, CM); - // Crown 크로스 facet - g.DrawLine(facetPen, TL, CM); - g.DrawLine(facetPen, TR, CM); - // 추가 Crown facet (table 모서리 → 거들 중간) - g.DrawLine(facetPen, TL, new PointF(girdleL + (cx - girdleL) * 0.5f, girdleY)); - g.DrawLine(facetPen, TR, new PointF(cx + (girdleR - cx) * 0.5f, girdleY)); + g.DrawLine(facetPen, tl, tr); + g.DrawLine(facetPen, tl, gl); + g.DrawLine(facetPen, tr, gr); + g.DrawLine(facetPen, ct, cm); + g.DrawLine(facetPen, tl, cm); + g.DrawLine(facetPen, tr, cm); + g.DrawLine(facetPen, tl, new PointF(girdleL + (cx - girdleL) * 0.5f, girdleY)); + g.DrawLine(facetPen, tr, new PointF(cx + (girdleR - cx) * 0.5f, girdleY)); + g.DrawLine(facetPen, gl, gr); + g.DrawLine(facetPen, gl, bt); + g.DrawLine(facetPen, gr, bt); + g.DrawLine(facetPen, cm, bt); - // Girdle 수평선 - g.DrawLine(facetPen, GL, GR); - - // Pavilion facet lines - // 거들 → 바닥 점 - g.DrawLine(facetPen, GL, BT); - g.DrawLine(facetPen, GR, BT); - // 중심 → 바닥 - g.DrawLine(facetPen, CM, BT); - // Pavilion 크로스 facets - float crossY = girdleY + (culetY - girdleY) * 0.45f; + var crossY = girdleY + (culetY - girdleY) * 0.45f; PointF crossL = new(girdleL + (culetX - girdleL) * 0.45f, crossY); PointF crossR = new(girdleR - (girdleR - culetX) * 0.45f, crossY); - g.DrawLine(facetPen, GL, crossR); - g.DrawLine(facetPen, GR, crossL); - // 거들 중간점에서 바닥으로 - g.DrawLine(facetPen, new PointF(girdleL + (cx - girdleL) * 0.5f, girdleY), BT); - g.DrawLine(facetPen, new PointF(cx + (girdleR - cx) * 0.5f, girdleY), BT); + g.DrawLine(facetPen, gl, crossR); + g.DrawLine(facetPen, gr, crossL); + g.DrawLine(facetPen, new PointF(girdleL + (cx - girdleL) * 0.5f, girdleY), bt); + g.DrawLine(facetPen, new PointF(cx + (girdleR - cx) * 0.5f, girdleY), bt); - // ── 외곽선 (두꺼운 흰색) ── - float olw = Math.Max(1.2f, s / 100f); - using var outlinePen = new Pen(Color.FromArgb(220, 255, 255, 255), olw) + using var outlinePen = new Pen(Color.FromArgb(220, 255, 255, 255), Math.Max(1.2f, s / 100f)) { LineJoin = LineJoin.Round }; - g.DrawPolygon(outlinePen, new[]{TL, TR, GR, BT, GL}); + g.DrawPolygon(outlinePen, new[] { tl, tr, gr, bt, gl }); - // ── 상단 하이라이트 (테이블 면 빛 반사) ── - using var tableHL = new LinearGradientBrush( - TL, new PointF(cx, girdleY), + using var tableHighlight = new LinearGradientBrush( + tl, + new PointF(cx, girdleY), Color.FromArgb(45, 255, 255, 255), Color.Transparent); - g.FillPolygon(tableHL, new[]{TL, TR, CT}); + g.FillPolygon(tableHighlight, new[] { tl, tr, ct }); return bmp; } -static void FillPoly(Graphics g, PointF[] pts, Color c1, Color c2) +static GraphicsPath CreateRoundedRectangle(RectangleF bounds, float radius) { - float minX = pts.Min(p => p.X), maxX = pts.Max(p => p.X); - float minY = pts.Min(p => p.Y), maxY = pts.Max(p => p.Y); - var rect = new RectangleF(minX, minY, Math.Max(1, maxX - minX), Math.Max(1, maxY - minY)); - try + var path = new GraphicsPath(); + var diameter = radius * 2f; + + if (radius <= 0.01f) { - using var brush = new LinearGradientBrush(rect, c1, c2, LinearGradientMode.Vertical); - g.FillPolygon(brush, pts); - } - catch - { - using var brush = new SolidBrush(c1); - g.FillPolygon(brush, pts); + path.AddRectangle(bounds); + return path; } + + path.AddArc(bounds.Left, bounds.Top, diameter, diameter, 180, 90); + path.AddArc(bounds.Right - diameter, bounds.Top, diameter, diameter, 270, 90); + path.AddArc(bounds.Right - diameter, bounds.Bottom - diameter, diameter, diameter, 0, 90); + path.AddArc(bounds.Left, bounds.Bottom - diameter, diameter, diameter, 90, 90); + path.CloseFigure(); + return path; } -static void CreateIco(List pngs, int[] sizes, string path) +static void FillPolygon(Graphics g, PointF[] points, Color topColor, Color bottomColor) { - using var ms = new MemoryStream(); - using var bw = new BinaryWriter(ms); - bw.Write((short)0); - bw.Write((short)1); - bw.Write((short)pngs.Count); - int offset = 6 + 16 * pngs.Count; - for (int i = 0; i < pngs.Count; i++) + var minX = points.Min(p => p.X); + var maxX = points.Max(p => p.X); + var minY = points.Min(p => p.Y); + var maxY = points.Max(p => p.Y); + var rect = new RectangleF(minX, minY, Math.Max(1, maxX - minX), Math.Max(1, maxY - minY)); + + using var brush = new LinearGradientBrush(rect, topColor, bottomColor, LinearGradientMode.Vertical); + g.FillPolygon(brush, points); +} + +static void CreateIco(IReadOnlyList pngs, IReadOnlyList sizes, string outputPath) +{ + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream); + + writer.Write((short)0); + writer.Write((short)1); + writer.Write((short)pngs.Count); + + var offset = 6 + 16 * pngs.Count; + for (var i = 0; i < pngs.Count; i++) { - byte dim = (byte)(sizes[i] >= 256 ? 0 : sizes[i]); - bw.Write(dim); bw.Write(dim); - bw.Write((byte)0); bw.Write((byte)0); - bw.Write((short)1); bw.Write((short)32); - bw.Write(pngs[i].Length); bw.Write(offset); + var dimension = (byte)(sizes[i] >= 256 ? 0 : sizes[i]); + writer.Write(dimension); + writer.Write(dimension); + writer.Write((byte)0); + writer.Write((byte)0); + writer.Write((short)1); + writer.Write((short)32); + writer.Write(pngs[i].Length); + writer.Write(offset); offset += pngs[i].Length; } - foreach (var png in pngs) bw.Write(png); - File.WriteAllBytes(path, ms.ToArray()); + + foreach (var png in pngs) + writer.Write(png); + + File.WriteAllBytes(outputPath, memoryStream.ToArray()); +} + +file sealed record IconTile(RectangleF Bounds, Color Highlight, Color Base); + +file enum IconStyle +{ + AxDiamond, + LegacyGem, +} + +file sealed class IconGeneratorOptions +{ + public string OutputPath { get; init; } = ""; + public string? PreviewPath { get; init; } + public int PreviewSize { get; init; } = 256; + public int[] Sizes { get; init; } = [16, 20, 24, 32, 40, 48, 64, 128, 256]; + public IconStyle Style { get; init; } = IconStyle.AxDiamond; + + public static IconGeneratorOptions Parse(string[] args) + { + string? outputPath = null; + string? previewPath = null; + var previewSize = 256; + var style = IconStyle.AxDiamond; + + foreach (var arg in args) + { + if (arg.StartsWith("--preview=", StringComparison.OrdinalIgnoreCase)) + { + previewPath = arg["--preview=".Length..].Trim('"'); + continue; + } + + if (arg.StartsWith("--preview-size=", StringComparison.OrdinalIgnoreCase) + && int.TryParse(arg["--preview-size=".Length..], out var parsedPreviewSize)) + { + previewSize = parsedPreviewSize; + continue; + } + + if (arg.StartsWith("--style=", StringComparison.OrdinalIgnoreCase)) + { + var styleValue = arg["--style=".Length..].Trim().ToLowerInvariant(); + style = styleValue switch + { + "legacy-gem" or "gem" => IconStyle.LegacyGem, + _ => IconStyle.AxDiamond, + }; + continue; + } + + if (!arg.StartsWith("--", StringComparison.Ordinal)) + outputPath ??= arg.Trim('"'); + } + + outputPath ??= Path.Combine(AppContext.BaseDirectory, "icon.ico"); + + return new IconGeneratorOptions + { + OutputPath = outputPath, + PreviewPath = previewPath, + PreviewSize = previewSize, + Style = style, + }; + } }