From f499662afc3ea63f87092f4a0f2b4166413afba8 Mon Sep 17 00:00:00 2001 From: pxlvxl Date: Wed, 23 Feb 2022 11:16:23 -0500 Subject: [PATCH] Various changes - added `/:paletteSlug/:resolution` functionality for localhost testing - created `currFile.sublayers` for *things that should zoom with the canvas layers* - `currFile.layers` now solely contains the canvas layers - added `getProjectData` to `FileManager`'s exported methods --- - added `FileManager.localStorageSave` (it's basically just: localStorage.setItem("lpe-cache",FileManager.getProjectData())) - added `FileManager.localStorageCheck` (it's basically just: `!!localStorage.getItem("lpe-cache")`) - added `FileManager.localStorageLoad` (it's basically just: `return localStorage.getItem("lpe-cache")`) - added `FileManager.localStorageReset` (for debugging purity) --- - calling `FileManager.localStorageSave()` on mouse up (we should stress test this) --- - changed lpe file format to `{canvasWidth:number,canvasHeight:number,selectedLayer:number,colors:[],layers:[]}` - added backward compatibility for the old lpe file format --- - added some canvas utility functions in `canvas_util` - added Unsettled's color similarity utility functions in `color_util2` --- - html boilerplate - wang tiles - - POC - tiny text boilerplate - POC - tiny text font scraper --- - WIP - added two optional url route parameters `/:paletteSlug/:resolution/:prefillWidth/:prefillBinaryStr` - WIP POC - hbs_parser.js (outputs tree data about hbs file relationships) --- css/_canvas.scss | 2 +- hbs_parser.js | 52 ++++ images/icons_14x14.png | Bin 0 -> 1964 bytes images/le_button_18x19.png | Bin 0 -> 301 bytes images/lospec_mock1.png | Bin 0 -> 49164 bytes images/lospec_mock2.png | Bin 0 -> 9794 bytes images/rotate_test_8x8_x16.png | Bin 0 -> 859 bytes images/sked_tree_32x32.png | Bin 0 -> 872 bytes images/sked_x1.png | Bin 0 -> 23559 bytes images/sked_x10.png | Bin 0 -> 91241 bytes images/test_8x8.png | Bin 0 -> 149 bytes images/wang_tilesets_32x32.png | Bin 0 -> 2030 bytes js/Color.js | 2 +- js/ColorModule.js | 12 +- js/ColorPicker.js | 2 +- js/Dialogue.js | 7 +- js/EditorState.js | 6 +- js/Events.js | 3 +- js/File.js | 65 +++-- js/FileManager.js | 183 ++++++++++--- js/History.js | 22 +- js/Input.js | 2 +- js/LayerList.js | 200 ++++++++------ js/PresetModule.js | 4 +- js/Settings.js | 6 +- js/Startup.js | 173 ++++++------ js/Tool.js | 11 +- js/ToolManager.js | 2 +- js/TopMenuModule.js | 11 +- js/Util.js | 2 +- js/canvas_util.js | 211 +++++++++++++++ js/color_utils.js | 385 +++++++++++++++++++++++++++ js/color_utils3.js | 338 +++++++++++++++++++++++ js/data/consts.js | 2 - js/layers/Layer.js | 62 ++++- js/pixel-editor.js | 111 ++++---- js/tools/BrushTool.js | 2 +- js/tools/EllipseTool.js | 2 +- js/tools/EraserTool.js | 2 +- js/tools/EyeDropperTool.js | 2 +- js/tools/FillTool.js | 10 +- js/tools/LassoSelectionTool.js | 2 +- js/tools/LineTool.js | 2 +- js/tools/MagicWandTool.js | 12 +- js/tools/PanTool.js | 2 +- js/tools/RectangleTool.js | 2 +- js/tools/RectangularSelectionTool.js | 2 +- js/tools/SelectionTool.js | 46 ++-- js/tools/ZoomTool.js | 5 +- poc_pages/pixelate_font.html | 73 +++++ poc_pages/rotation_POC_latest.html | 269 +++++++++++++++++++ poc_pages/rotation_POC_old.html | 268 +++++++++++++++++++ poc_pages/tiny_text.html | 45 ++++ poc_pages/wang_tiles_16.html | 130 +++++++++ server.js | 20 +- 55 files changed, 2385 insertions(+), 387 deletions(-) create mode 100644 hbs_parser.js create mode 100644 images/icons_14x14.png create mode 100644 images/le_button_18x19.png create mode 100644 images/lospec_mock1.png create mode 100644 images/lospec_mock2.png create mode 100644 images/rotate_test_8x8_x16.png create mode 100644 images/sked_tree_32x32.png create mode 100644 images/sked_x1.png create mode 100644 images/sked_x10.png create mode 100644 images/test_8x8.png create mode 100644 images/wang_tilesets_32x32.png create mode 100644 js/canvas_util.js create mode 100644 js/color_utils.js create mode 100644 js/color_utils3.js create mode 100644 poc_pages/pixelate_font.html create mode 100644 poc_pages/rotation_POC_latest.html create mode 100644 poc_pages/rotation_POC_old.html create mode 100644 poc_pages/tiny_text.html create mode 100644 poc_pages/wang_tiles_16.html diff --git a/css/_canvas.scss b/css/_canvas.scss index 1b8d602..6e3eee1 100644 --- a/css/_canvas.scss +++ b/css/_canvas.scss @@ -24,12 +24,12 @@ height: 400px; position: fixed; display: none; - box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64); background-color: transparent; } #checkerboard { z-index: 1; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.64); } #pixel-canvas { diff --git a/hbs_parser.js b/hbs_parser.js new file mode 100644 index 0000000..c70e81f --- /dev/null +++ b/hbs_parser.js @@ -0,0 +1,52 @@ +const fs = require('fs'); +const path = require('path'); +const HANDLEBARS = require('handlebars'); +const sass = require('sass'); + +const result = sass.compile('./css/pixel-editor.scss'); +fs.writeFileSync("./css/pixel-editor.css",result.css); + +// fs.readFile('/css/pixel-editor.scss', function(err, scssFile) { +// compiler.compile(scssFile.toString(), function(err, css) { +// }); +// }); + +const hbsArr = [ + ...fs.readdirSync("./views/").filter(n=>n.slice(-3)==="hbs").map(n=>"./views/" + n), + ...fs.readdirSync("./views/components/").filter(n=>n.slice(-3)==="hbs").map(n=>"./views/components/" + n), + ...fs.readdirSync("./views/logs/").filter(n=>n.slice(-3)==="hbs").map(n=>"./views/logs/" + n), + ...fs.readdirSync("./views/popups/").filter(n=>n.slice(-3)==="hbs").map(n=>"./views/popups/" + n), +]; +const HBS_STR_MAP = {}; +const HBS_SPEC_MAP = {}; +const HBS_TEMPLATE_MAP = {}; +const HBS_META_DATA = hbsArr.reduce((r,filePath,i)=>{ + + const fileStr = fs.readFileSync(filePath,"utf8"); + const sp0 = fileStr.split("{{> ").slice(1); + const partialArr = sp0.map(n=>n.split("}}")[0]); + const sp1 = fileStr.split("{{").slice(1); + + if(sp0.length || sp1.length) { + const dblCurlsArr = sp1.map(n=>n.split("}}")[0]); + + HBS_STR_MAP[filePath] = fileStr; + + HBS_SPEC_MAP[filePath] = HANDLEBARS.precompile(fileStr); + // HBS_TEMPLATE_MAP[filePath] = HANDLEBARS.template(HBS_SPEC_MAP[filePath]); + + r[filePath] = { + fileStr, + filePath, + dblCurlsArr, + partialArr + }; + } + + return r; +},{}); + +fs.writeFileSync( + "./js/HBS_META_DATA.js", + "const HBS_META_DATA = " + JSON.stringify(HBS_META_DATA,null,4) +); \ No newline at end of file diff --git a/images/icons_14x14.png b/images/icons_14x14.png new file mode 100644 index 0000000000000000000000000000000000000000..25c3b15a4028a4c0b1df9f5a223de6e9a4b1414d GIT binary patch literal 1964 zcmV;d2UGZoP)XR7YW*BK`II*{@}7`3xc2pL9t4~l~!Y=A_zq{ z{!|yW7KAPuwNN)UT5yvz7%K#^iqLw$$@?Z}&OLKy?#m-W29h&#=KGm*@140XFMY0{ zT|YE>W%kfDOUSKv`Pe^vxUYa3Ak^p&nM8*`Kix4)K$Gd9QSloM0}xWe?4N@tlj3?J zXjFXgl7<1;L$h1nyUGpd6=9q+12NUW<($9ooyx$uurSkCtFXijM2gA??z68P^eM{> z6H$e|FnjKahrHnpPwjEDht4`y=9AKJrDh|Ww!1f;-{WM!rXaxw|>Oy-}B(OJ9Ydk_vc?1_tUT+X74|GhnxNTKJ(GnmxM$_a9>+L=wLQ9JmRCQ z6cy|H-3QY2n>)PDz`EPqb(>E6{enc3*^!NR`={~ctJk>~{{6!t!u0%^6K-(B&2Dl2 z*P0nL!&3^g{+ou}!tWEvH=1F7-^E3QUGGlA1j2H;B!3>Q# zeHn>E^p{j7&~G9T%S*~JOr}* zZ^Zv#tj$Vun&*!zX(ml!XbD5a%3Ha}5CF>zIaV4G#K01_a^Zc5$*FCVZc@BrUVr!V zAZZ=MVX{TX8`5n1=%Sl)%cj(^%&gUynz&`o|j<@l7A9Ca|$I4kG3 z1twCaS|nPT7+B&~&To8(jvPMfP#GI4EHxviB${uF5~5y=RgIM^(e5e6-$U#%s_uRA zVe`1NFgmB-k$5+1OxTAT4!r0+HHiyxo%#8_`$GzyAHM(5jUPYiz|${ZuT}BBd|;>B zyyZ^M3x*g$tf@jS$}>B^V3y*s!tu&T7 zeCzPub>?HgzFp6}>dj@uJq1Vd%vD(xf?RvB8Y<*dt1Gm$YdQV&)c`8d79Y{OA$P;^ zrri>|;&nx;mf01rD^ll1wC1)ZM0Y%2q2CsKbg^Ib0j%_*A$dshEYk2KfuF%5#|2h0o(#fjt(;`y5q@gBooHHd3ivgBn@=Qa3@Kq$f6XjQu5a4&DBqJwV39t4()TGCL znhu&Y=c>a*)hXrG4CU%k(FeDWIm=X+LV`-I307m@G(&nF$Niz%9~V` z(u|@OC&x=xNHL0+n&o&=h4M|oJ`Q7=QcOo+<)O{7#jAZ<)?!U#m{7blW-n>Vup2Wh zYVB8;x?*M?Tf{nLOmHG;o~0I68(B}F#nZWHTj)uQwNWW1wV-T;mSIW^*wh>s+PEz& zft(v$@~SdEQD*QsyBKGc;^eBry(?4IPcsV^C`q0))!&9z?^qn+j>h=*6mE z(L;ktzSRN18={SwUYs67GgP=zGl-QmZ@5}!SWu{tt8051$xB5P6U`xkrxCF>86kT( zST&=jHTR-otB`10Z~+?FW@}JGM`%K)_RwfE5UrdRlw;x**ZOZq8ps4xYP z;w2d1fy41gyo_PvOW~$I$teuYFi%rnPQ>u{TS_36r-+=Fi-FnjwHw2q3&i)#P?~WO zG~pQ0ZwrDYpfm>&V$o-lTFfE(dzhM(K=w7ZjTtn|jgJWmX#>o$5yLTJ6CU$5LrcUG zvT!*w(-285mJ*V<6b5=l4PsI-MJmBaZnQ0OtCZq7tTd5*|4|=giXn2WBZ$mN5prct z_Q45bfcX=Wi4JknK=u=~+B1>DnR#g-`zBZ2BKwV^MW9B7rSrE$C!8?Q%atw#EiC4( zyj*LvEhw0ZxkbSYT>eNS_Ip|(MVsRCp<1D^GI>c@sU@IsdTeo!t9>z~P4RJ0H+=ll zt&|r|8d;6(L)bDv9yp1W{S?m|Vp3ib*~c7O%2Z-ye~DG+1w#TX66QWSb}VU%`BGkF zm?A;&{;^w=rrq+O8#Ue|M7D2E{~X=7h|n1{@dosbmI7QkF(;>gagR()n4fD(dHzYm zw;1||CJ;g&{|#ki4ppOK8FRcML%DhiB5Jdy%S~SN&`9A_pO+c)c#v?JWz6wf8%jZK yhHq`r8%IEF-UCcl}t_i4RnmpQZQ zg7kA?ZF6o{^1NyNHbvs_ht2;Lm><_GSX4aIb7i|ZTf_KI{lp^&3jP?mJ~N1TWn>VT z#`Lwc->M^EMqk^73=TmqMyF4UGc!zDI9eGLe_k{(k#cHrYM5Zdy`s={F54}~S0)$M z#9aC(Tx4qaY1)2`oH>rVi3_JW&c0tH{5Y>EOJe_Y&A-dtqHp}1!|j}~L*9}5qh`eZ zB4Oso`=59IUwKjN-CdzG1^@1d)_fD3Vr+6rGB;S5XX(XN%>e?v*zJGf)WK?U!K0V$ h*016X- literal 0 HcmV?d00001 diff --git a/images/lospec_mock1.png b/images/lospec_mock1.png new file mode 100644 index 0000000000000000000000000000000000000000..e37efd28f35b17d194b9338e17f92926a3818896 GIT binary patch literal 49164 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
ietO)$)M?f z*HReVXrZ;jdDNP#4pt-5wFFnjRVD(KatfR%nL+3O!w#QaLNmE5j&!>+wL+LkIfZ;z zK>xs`b@?+}!zTY2oha5^w~A-A(4T}lJ3L=$#Ro7yx#>@hx~<8sm&Ve1RStgmjhlD# zpBe?id6$BY+2XT{+sq|c@pI3x>WI)|;H3+i+%6gwm3TV>ZRl7w4W*Li33d@-7555` z{gfpWxe5itAfj}jVD&=_-Onz2xMcK6(hJ=60wLY5!t+~-y zqN6foVejqA@gKeTGE~L->a~|dyRgi!%%TofTXtG|Y92sb>*u!Ppr2Z~Xy9xs5d!?b zCA`wQjKiL=!+*MJ%$nu5kDukm(TSlHk4`5G=NmVH!THgxWcSk zzvsH%^x5t3ZjVn$?q@Wv5RH_|sGdbk4c*@;ll-bh--ET_cD_R$deYdrL?{02tP-!u zTp?sY!ASUFL{_H~p*W$~H82;0a6`@c{8GgUpqoGP9sBgrsqmnyC=#u7TKN$8LmL=G z_!@65ZVhN+TF2rP99Z6yOXN~!Z+{1mViR>%tjZC;CIhFrd}Y-qjVfFI)GVHB6@3Xt zmNYpQT^UG<8b*Q*|Io6DZUg{3>r zqWMb*9N)q$9IiMa8hr2Xz9H2`jyv^wf^}*TR7Ugwhof{^xb~>R1Vs@@L+qED!(W;E zZ#!cn%e~&lqvSFXRvu_PZ!;}!WVnXH>31DF=lNP=4nL(ab<7av}Rqw`$(WnqqtC~fu>sTepKZtj?y?rKfcme?Hq zXO%Gm<^QYHH9l^hWZA3ja-8P2ur3lEGzm***TB}!Q!8x1CB|4i0sq4w&FR;52UqY~&=ffI3MnAvHy*PT8SCpA&@ znO!IL?=Z-(QgSN}Xq&Uc=j2jEg{nt+%y?vhks9LDz-{&80~p6955hYW?P^9|gV1rw7faiVxp);-4y=935VZz)Z^Ro*8KW%|c*XPaJxVpF_Pn zce8O!j@=sZUZ8}5nF`0nt8;=d)0MxWhmEl(2e(+vIEmDTsnz%}k1YCjvM{6AN)=NB zeD*DEdBwo4XHE`vJ{H{1*1w`Mw<@W|LfWEr0PI(Kbl-2^;OjqbtvLwrOCqfUCTyGb z9M@Wr5uN^EEsRE565QQDb;42$pSu&oDuBt!>;YTP?n|(~#_9>^snkDQFdd(qbdS{OvOcqy9WK?@;@2$(#F z_^0kv(xsR&Nk4>h$p`;fh17e{m7swDbY5SQiq+${s5N*TDwmMGyi^oxkyRQyQ z<0aRihr7v=xnN8)6w1>M89Bf~?fE^4Tt=T?oLHu4Is}wYtVkgohIbN=22P0E2qj!Q zySFaEq9iCp<8_a}e?cg5$_=d&2>BV|Vv@a7}w3d85FL#pFq+2 z1uyfbxE&TTnjgTZ;MMz*)=P;bA<^c_EzexggrNi25tIZ8+3>V8Ox#XNjAA-KYsx)0 z?!}3!%%umkJnOuuoa5U_tmaIrC{MR}zWhBbSAQ#|8#5PT_wwL4yTz{UqE#0YGVmwX zL)L!xOy7;MGmG?!6-J92-iN_=6AsuerO6MsrH^F+ZGLi(mN$P+mq1#klwlkp(&Y=O zU<5Zdqa4=9awqTdL4ExzCpLE6`~icnFWTM`u5(vfG|~srmP65QognyOK4qOmke{+L0Q3(u<4N$J zqZB+>Yf$gk@9vyJ_vv?Y+dC3zL+E2V5;Cf$S6z5|{ZC zOIue7R{dpqmbj=k+2)cpCOmsGiRUlVf0xt1J$;um(-LtmUb(LX<@A#1LBqMyW-j8) zr+iQS!n>AlZ_Y^G9HZD@l1zKRV1+r778xO=yZ{aq1~W}{a)cBmg`qly7F2dZC(BVWBKtBT zhQ<-!q zP(Pxem)U@;n%}?k9PIq^q^;;G#+ia&>!OG_y_9y zBJn53tk}T}e@1Yt6`SQ_C2ZQny0r35V^)P{%d5`d9M6a9mR61jou7_6dX1)q@_OpC zwEV~4bylmnOeHnXvfhHv#Q*4a%RZ||Nyn%DMp39~9g(CO%D>P<+k(QfzCIKf`JenU zoMUV|!vI5zCV;Pfiw_8pNhM(87bL8eMu+Sxx$xAWs=a`NZwo{=_6o_)YHwoM^)Q0z zemSjC^=T*TS~{M`Z>M=Q=2o;TDq0b?1t(Hg92gkO$n{I0k2mnd+Ee(^$6*Iyl$qGn z{Bn8hgE`*mFY}M^FTwJM<(8f%z@1(LfWC9`Rw=3mUIc8RI!72q*71_}JB3omo8P<* zd@(wtf(7qaC&%=ntw((ASSy+|kB>Z@TYNT)q&tzv0G3e`_Un1#{yMbhNoVO!cG_xe zlQlQO`USWoqetM4Za|Rw^mKfw{tBOnFC1Lv4S4ktBar*wVdDj&Q#Fg%c+p0@w6`vnv$^M4R zts|b;@W3p$z5MO1VV^msYKbx(oRn$jgJJ4BZouv>&R)3)O@Iy<5B>YF!Jc@kU|fyl zMW+TO>O8%cfs+`%ZGGA@cc63Z)-8=#eaA;`qgC5!j2?1hPirwLt)VYWMRvOUF2ajn zI@vCRTbp|4vqzO^RfBA~YME+PKG{3_f$ZV7#vG;8k??)=M7M6YXj)XRYuB3wPyqMG zq|!|bUhh&*Iu0RJEAsW}&kOMR2UYNjD<>3OQQ32U`YGX>Ka91|SJ>n3S zOLgv$r7Om*N9yuwGhfZWrgJs+I*Q>zC}VH)(T;1{zNjduy(|B!8e0e0hSgpsecgJx8wP(7;s8 zueM^>E>&dhR!8MfJrg6LF3qWA6m4#W@asVIc(?xZo_zknM)C-I(i&#%__z&#<0?0e z3u!pId$xT}ALmAE$Ety`SL6;JBZnT!_EAxlzzy3mIad^q+AeBW@;=S0dS~aO5`-UV zQEBQQN@i3Q^fp}&WQeKbJr{euJyGKvuL^I$s2CJwcQ;mrpXi!#hhe6C^{MET`_$F7 zwgn|}mJOX=QZjMoF#~j(Sq``Ix>uW_3#FWlmtlAq2uEQjMEz;cK9BYD54Wd)MUmF^ zqu$t|5;N&r&z$HH3ah_$_d)FIn?;97%a(MXK16a<^krBdFbpv4E$wb3uAggV?7hCN zz1kgu?@RTvSfifa8%#FdBiE4T)crsX zm5CWA%1RE?9?~-z)V1t1{wdL^*ddOeBfC-NryT-rxA}c4iWlU8`8mkjWTA;5Pz8gl zj<^&%rnycz`;SzSjWcQh3zhqtnN_4eI#%M{KH2oOXO26*Tp9B2F+|7GazK$ z=cr-fHct=sNuj7Q;q#n7|1cSX)sG^M0%}k$kW>p@($K1Uo)N~sE3073b7(=Wq=18XT_qMvgf#NrOhIY*7VfIf1u2~8 z@HW%mEl2|ewwC5IX>Q$3j#r|LGWlOb=o6;lMh9BtQWjwGX!2Ve@Zc<5g#PwI2o-J;_%2Z%@@Q8&znF$Pz0VSYm z@Is+DCR!BsRXuHeuwr9WQd>7&(S1DKtJVf~CUPRuTDbQ*IX3FoHRnz1~NJ5G6fsR52q#sgy*B>8q`85?G-1iAoZhQkSYoQsZGmy)(I)yx2}Ya0`X^Jub4;=7@770O z$dVT#43~@Zt>Me?miKK|hxu^V+Py?td^sz?x;Wfg1mPc=n z^=QOfIXcet_IVI>i6rgqdHWV;C&)IqSl8+--atTqsFUXckMUI2N{78|J3fW#@*UAY zqHo_;iGD27Kr@#Y9qT0hkbU{_E?2zG&ohT(M;E={Rl22^7y0WOQ{gFCSK;ZkS}5_{ z5eCG>oIPJaBoKuFsU`o~b)p9f3IBp+Q+wvxSw)AX?%fDhV%gS{yL{YOFL2iNBZV(y z^>{TGRG!ju#w|c5)c*ZL;Qq6*`^!6R|DUKveSr^c_H`N0YE9(?DAG!FOPAuDSavV) zhIAde9=3Cu8=EouEPJ3SP2Ha%+lBYkTitJ!))<+;PP|8bmIpq5lkKI^Mco_AeHu*P z?L6?7v?c4(%INYQ^^)y-A^^(TWi8{N$m0maq+eXFebE#5Q3%=*0n6*Ct0tfoLlcWu zhk_H+y)A;{bBYcdI&um*DFu$yl&DP@+YY~L)WsR(PBz;*7^k3L-Ji25#Op9<|7_N; z*13y{d+*lwZflX(?Detge>HZ?dTYy#GYvj1URR%a0l%e>7Oe48)3VEL6L;FyzX%v` z`rtc^0l)AtjGU8^JnD}N3prvlJ)hik^&}p&VNbEN_q^bAsC+Ef8{(-HOWeh}Zrsh( z1Vp?!zzpYTxuFr^t_&1jdY-GG>u^ySa{T>N#oZ}w5pG`XC*|}qUGG*b0Qs_ez^7-d zy4#T2Favfcpo3KPI8nw z_UjGJ__5lAw|z9<{7wRlFLIR6K#&G$rT^jgHst=R#ha?RZ8Q~^sfL4Z zd)1YA(be51E%i;8$40B?Mtd2|#=8{Tgzo1D_Fu5;ivztc@_P!tO3g;5!0B*L4}_sI z-&9zW|G|5DaPyTlVg6*Eq_`(0H`S7PPFdwj_-`!Q{cAlTH z3wfzPfg{NoSMh{jZY3(bjVnReR`h2tov@Q zZPmIu*SdoQP&u`1&{gimptD8p&?pDXVl%8s+|8i9z1B?1X1N=Wj0TgI`PZLLRkZBm z%+>YL1wv7TAG)b>@I={Qc0g$nktOT-Dk^r@DN{$&mMEo~sQeFvKM8y1Y1QsN^zx3p zBapJx+^$SRK^imx~%YxL%HE7gK>c4BIB9l3^Uq4ftXs2>7O&n9< zqfb-?R<5uSUCN#9z0q;JH8xc8Fnf4Qi23SE8?rz30;nH*v2;ixfX#l3Q!?I%8`>(J zDwZ+k^3-#jjh|_=vd9tl?s~M&mY5}!RI0UEBgxq{1}Odo^-yaG>UiOgeYXnrh9uVX z{-R_b869C-@1>x-;uEP{xC0Wk=)v=s5ppLYs}6dDo{0w9bu$yw=bLYDMhlGu7hxdU zPexW}gA#gLLg(JAZ2ATQjab#=pxZ>(M*QCNm!Qj#t&z?`5+)i%zPnR`PCtVr-B}J}Q-_!jU_D>xQ5C z9P*>vMGY8SIgeQ(L7ozwJ9IT9h{|bD4@vKBjv9Xpr%iqx#Qqp#1PpwZ=b~LGGu$5u zd)T7+K)^Yy7zo3Zs|!?7_*Q_qr>r;0y}R#m`ko^5MZ~Tr@sPK{LA&Qyx9gT_)u-&E znDW?4N~;E~hih#%HqzTg+BNyQL9264a|>t$>HCBdq4{pu#R*(aEl#lz539$Y?5=F!2Y9ab;p9eXjpxizeV9!1==yw%?# z^mu2!s;%br)V!M9Xj`WKTnY&~tg8|eD^Vxih*bzV!VU=ApI4R$lg`cAhY}olbHX;M zqzw$W#-7IF5H@~bhjf}~A19S#RpiEEW?I*?`nVaK0!IB1Q=b}PUClGVYf->)O{bl} zVzi~jbebEv2|s4HCc#H?>SFB^Qi%=Ey3^lenFm80>x))$c~}wLI2~^n=*F_Od`TF@ zD(2Hjoogx;bmkK1_T(hnn*D9qFBdaH4JkXZ_VLBJCFtZvU~){08UYuP-U6j6=()2S zx}iCuM9?6mrhD&d8wxytb6dWoc2Poi?wj`N&P<$TdLCwkvq?id`j(cu)1K7BwAUwv zKcsZZn5bRvHTTxo@s(@q(ta1;FT4PNp3!`)ruTNi?-O5}0@o}B!wTaQ_95(L`oi_V zb}t58MB0|&wSN3ECa_)GLWZ07=>o-r0X;Lqyr~VGKCV@CbcA(+StJ?idEA8>O(39G zarJ7);>Vd}gCDkOT%gObqnpS*j+o8GNL}YtF073l=jvW(6!|1%;u028LydsLoAmhTWn55qdZOjK~E$v zF98Ny?!1|V=CCM;oBDlT0xq;X0eSCR&*%eM$tqIZ9rRc_oQKLXV-m6);@rD?N#uH_{KOEnhc>orB^8I ze$Mvk-j)wm+^*QS5eSV&jP=Ckfo z){B#SCfa-69KN}GZy5~A1|ohWAf4Q0du8hq?cX@;sFv5nCSvB^0f*Z4u`>R-o@(3zHV(0 z?RfzDUDi$gKum$;8X zcg=)8iqaZ9>GdKKE!wcUlfx62Qm4^D_ciu;G1o)AujVPg%j6-W3b^g#LQGFzj@OMV zS<3cluU37Zx-M8pIo@3{3AlyLoL&J~iOQ zsN%Jh%>(iZ%KWYb^F=|8EDg1Y2-7Y}SzOD=x+3UG5Otxbk=sPV3LXWteQWBMr!p9$yw+vx3;gJ=cUMPJNQgyQ2zW+qN;b0^K?h*wjf2 z(2kREApJGTJr%a7-<>uZ#O3|rb>LY|#oa*)RlN&3vufuL4LqM~D$AU|uq2T=Uf+b3h$8QA$fJt7p9b;*R>UpGU51Q^_S~ z!=&1C$Wu&vpYdq}^dy`){S?z?Jl~&n2$z871efLJ+UZ!gf)w@Qk1`ZbrDfaIrijV? z#-7poROZoqiX?XB*H2eF7orQ2Ig1FfR|pde!+O*w+n+an!AH8ge0ecq}ZjL2qL`0%{&(5u{}AnuQZ12!Se01Vy^n3TQ54li>wa%`Siy zxSy_Z2|a3+w*Xc^Hr`fiVt$xJX#w*;pCv7c`i$l-OpRl6JKX|a+MKj^1T0${RZHP7 zfo&(IlgNj$yNz2iL`S6*w_ot7cGOkxE2E4W7c~B*jVQ50iI-(FqRX9BN@9~ojUUTY zV#j2@)iUq{!Q7HBPJbPoUPCkIzWShIMAS8!XVRRVmc!!FqIvl@yq$4tEcG*87h~ED zSmx^f58VatO*Yi=y9x~6V_gN%lq4j)d)9scf2->nynhO7pTRO-Vv@9Sh`p+b20d)o z2Onf}hcCV5LSKZFp*ML%wb-lfbfshU%I&q>;aG*DQN;#m`6-soP%M5nMWUtGh9Q1( zDWQ^hRd0mq7xESqmWM3k;~OPf*zP@w%9c-mPaP1e`8}cHXVjx^0!ExU1Ig$TTem<~ z+*RGFjhgdL!fq~M{OPmeO#+J4pU((sYf{Ea4DumQ(U0pq;nz53NcL)F=zu(kf_r^htvvM_D9kF z>o`N9h*S+s@@|%qQnM)1n;rS{`{&VHyd~D3?wHl8M|N_Fpf?yqe%e-TcS?;ivo_H; z{he|VEC1R3*>qW(-}nYM6|z>)4JT97fS-Z^i``0{G0|U6<;jC{AC9X>V?IF}>+>Br z_UTr-}~+Qcui3-AB+s@gB;V1huzb!>RP75&Q(5HbFKVLmDTkI35}$M({a zF!-`~yD-Ry=@l$RHE0Me%Z;}7dY`c|LFx&ITj+8|WQ#Pkw(l;lZTl(aBo>-R#CsC+6f19mzp;JOX!+IMG^ zP5$B`?XWzkh#hvc%Fh8sNNa(V@#{s$IfEYR{=K};t15l(Qtd26V+uZaF%OjZgqiTm&%xPRKO9LnnFohYq2?o}su&|JnVVae+uDa zidf1VB(NBn!(V=B^m9PBTvijbG{TM-Xg0m93MuS+eo;~<_iE0erzQt3@d(B1GR+E?qo1&GM*1?NKA235`|zUhdtD;=mXw zh@FOe9ten?fkyUHNiv(JtyJ@FOHfcKsu!!T3Z@nDT3C=U={ooeMU48EbTTyIMiudE zNc{OxR!ef9pP{K1@ya4-rib`t!Pkob_~`d;t3s_=0U})eyvw%eU)({+s`PSxgjR`b zVz80@nDF?Gtt0MX=1`fP1^Jo3)A2(uJU`E}m``Bm2jdu-@y9`C9fFU;nc2RIY@ zZ|FJ^b`@SO?`3p&D?ZmN8MqTh!jJLhK~cEVcXHIR%@E4@2QVhihw>D%ikN!Z{E1eE z9p?Cfu+FYN0QJM;6xKnH!TM*kS3qu!@1<8dJ z2!doLsbOiM<7g2lgX7~i7=Of{LG{ISEQ#rH8=C)kK~4zUv#FRBzX^|jou>Mlcf{OyMP5wAdBJkzGF;s6#VUTiByF+g)9#vlDl z)=iwNkZ=kF;!4-u{V2oIQ>ScW47@g%N*VNd-rYBok@kSRZ}Ay!ZJ6oHT&;LLd;(pk zDRr!@6^ed+H&~CcH=1vmQTFWS<%s=wDfZ(WlSR>)(Yu@u@eCShBTH`kyd!PN@E>Fy z$1}5rFXt_Q(WXjlu>Z@|J~`h^4RC?3%FXEEX~GQHfPfvz0wl?Y(8=5LE)8$(a8g=% zx=2pO$e#XUH=5z6ipm!fn$b+~^}h>ejf6DM^HJW{$VCkzrcONbgglzvp)Mi2As=1w zPynbAuif?eO=4fo-UvJ_V8J2uql_gTM%v%)4%NT-?4?OF7E%w2pPYF4(>$!rq4tR4 zr)B)>qvMPrrQWFzK^tO>QURRkToXr=gq?IvGBo22zi=ILeUY}VP)Fx!nY=0ZwJidBJ4<5!7s}Z?=^A+<4I`@2ib7q5?-uyUyip%Y)o4%h~3I*`3ujP7p=6w<$Ehx$LDn_#T}ZqC1(^RMUpZK390&-uTF zwSK4g6_X|@T{T|6b`M%K{AY)7{?{Qc?@pa`n1C4HD6CPWs5meQT2L?7A4Nm`I#4El z)N;b3X>cuuA=^>~^ly}bLMrH)yq8$9=c+@_@f{d4eeSpd{nQ)qspn+VFr@W$#?Fg@ z+;+ybUI1h|1|ox?X9z9AhuPfo*-hVq9B_X7o&H>ubIt>_zhMS?J}B>ZkpaQLhCmcn zzc6R>GvC-(usmC2Kl1jRpXxjhf zFR|=S>6C;rzLhE52%$ZeWYR*ZrX}5z0U@`kydXFcN{skm4)}uW(O+S8(Kh641g> z0%liYxDiEEi$N5*#}&RUpru0-RGi1b0$wI{N%W9uQp1UCp)if3=$n3w_QH4VwYqu) z{W4cD9M^)E=q|@?!OSDU8wFQagg&{9_bj z2gWMM{)L1wwEO?v*HE_YwOPt#&IN zblJtAcJ0_6AVBR>1532kU7$2E6hSE7K4*1VXTBru8FsLx`nw%$=@5LxTHA#JN8KN& z?~kMxw*Hu;Grx7B!3qiQg}8gJE~NbL9f zn!G!AA73>M)e;KL68a2)jEapQA_q2?vGEdNP-- ze($d=4thUi?1XU6#H9wCguJ}h-6})?4umsHFJ`TXMwGIc#gJ<5W-cj4q|bKsvJ~}j zxc!VB<$`D+dG~>$A9Zh>h7kVnXb@<*Jq=$TZ2u5E9!r-^cGy?loiuUalztItT2Gzo z#_*nef7|Q76wyx>Y*dNm8tdq{XpqMAjk>desHyJJf@mVh*^GFY7j&HX4KDbAa!5Y& zXVe4A!SuG|&gUQoCUFAPB4$2A;4=f?$WIp~;69HnYPr)vr1@^56|AM8N-qGdw{U+6 z6neg)TzzjCdCXvsMX)x!kfMLKz_1{6e@Njna&OxyqgxOQzY%=~Cf(zSwIOLY&NAkK zpILP)0EBxn4Ad0PL%!A+A*u)f2z$ZjzwrzJ1VOlyFoIdhF@q!3ia$3&C2`f?tR#vi zZB#rn(a=HOg{<*C3zq-uuW`7`n`u6~ky%f5|@zl9^;i2|^@ z=zdOb{JE8`j$!YQ-_vUp1z+fLWlHiOT$pD7cg4;c5CD>1f$eRu4t_fU$UrX21oZQ* zXNad}`Qk#5qaItWoAWPC4)P1c{n5hJQA3#db`GjK#1JVO9*yCVZ(w@R zWdNDY?4=KyNbf>=7Kkn+-oj$Goxg@e`FT68KxWumt!j>bnk57b%Vqq#iar_L2XK8C zvhuxmnJNexnRgM2SKs2V5qF>OL#U{baHveWYjseNjh%wC&+xBn;a%t7kUE6!zhj09 zupsP!-ZvB#NNs``WgOp3qNcs7QhupGi1NgttvWk0;Zr+xRRoAz+reU^h7ANhqe!?7 z?;%5N87||bS{J{S6L$WVcm&6q+@q$U7okiCY;^NCv~QoV3zRg05|uWN0@sV}gmT!< z_2?kA7s0L?Tg5*6nmAzCZ$12F;Q|!HhhILWNC?f`?l+-~k6rTc!QbqdGk^eWH2iKU z_`e!V{z(A(C(lHjFWru)XF|f2k`82ErEw{Ut=n5b0oC>&od&!+Es1|x>TA#22*g6+ z3cVeLI=%pP2DKb~Q8(TxIw0^F(Cv~3MVMIK)OU&n%2fz^z#evT98qRnmIls2|Dy^e zJ!J+K}XSn z-EQXmGZUpLfgp!`&x1-c|DDdiyYsKH{i8#E|N73qk?nsOsJXc(yTZjLO`7y`IJr7* zp10kk5GuwfD0_@1nQ)@e!4k$o&>eF>>3*^`|yS%$0? zF?L2M290&>pIfib`}_U=1>c|EKX^QDbIyJ4`<&->uIF{G=N)5aqR+~5mW7Uvj`ijZ zJ##ubdd1^E$O+&V#XY%oIyw=$n|eC81L>EuSt)Y*cZjA-lF1o^+TL>MkQ;ebIyVdk z3{HV#g;`mX^(v~(4cKp3K20vL(c$FMDgVwXE;>rbVOUiq#vC~)0;&n)T{d<%m*~__ zP5gTMyo9Qfn+W-6gs9k@}0cF=xf^G zEn|WdBA-Y{A6L&bZjmfqeA6a3&*ym*>w*^{puJG03~_| z-mnTEvTGS3(%8`P7#~TduR4wQ_noDmLZS(_5X$DcL)%v#IY868`2D*SrUV@B=TBXy zQGe=WQOI525l`VNxd0dV{*aHi#nE&re$+6#`_D04A*3$^V9KUXi2^Aiq}zB_H{OgZ-;aH7DA5| zvh04l;{9$Ft$fK1aV<*Va#CAO?eQKsdOYP>{aAIqNg+7Pu4<=Ysm%RH1^Jy%ed-8$ zHJIS3ch93L zvaqv>&8Qmkt4GViPGcWX?Q+fjCo9J`cuIptR95dDF3xY)S{9vG1x0qh&Pcr$eF|xR z(4|Wc6YSs8*f{YDm)8hH^8!R7wpj@zS{n_jHN7$fl|$Ht zeZCPNc(B^rp_&C7-G-WOhl*SO_pUN*kzTa%yHK2r zW4e8yjyN*0iCfJ7sNhb!Buk!;ImtO>#FMR=Z?mfMw7*(;fSkW;6NUfo?CJ8=3XX%518$F@A(vr9+bLma{q8BF9vXe8J8RRxV2Z z5fw>h%nO4|uO)~0$EUx*Dv`XO#rYYD8sJ;E`H5S>TdiDt9o!8UUNj}roG0+X48Js* z@wZC+G~wKag=2TZblm=g|9S_X9q&cu1_fq5i&3%TKmGk{zL2$1c@0dlt&#f86+O8T zt$(T6JtBH$LCZcyhi?1_4^q)_nB|lTr*bL5Y(eAC#z2PC*;l5TmX=%a*r)|P%)XbI z#*bms?dXN6@3vt&jraE#u<2gG(=9=-+lo#y1ncTkcT0RieAmy3JCd$UNjf@EDRbiM z4%PVG+=GMdy$@OS%?Hao)uZxFHG2Uo!p*IS*B zZ0S$%FNg1fN3<);w3?&Ev4$3((%l!LwblwL3h~Y_Xr7>vM#sK8+e{ z-uo#65BhWv^i5d{a|WP`BL{zMk#`K&Bo?7BwE43)Gf|`dF|GNulXhOJHKdDmN^Rt# zD%r0bq#K*cqsPiWj!|ea)2xaK1yY9OSB~<;!xifFMz@Q%8>iXw$n?~f&-DV-vmbZK zYALay5ZNg0B@T|F0)F``2LrDJ@_x6Z)R#p+2<(q@$p^T2>0G%uBEQ|^z1cI97zgal zuUFp=o&$ADeTCRiBKz1+d*6#N{vAk3Co|3?xAJDzIB-MoAxiiXzYf>xDHJF?N#U8P$}r^Uh-vjli#-sBWNp^+Yewy;h~V zF|CBdzt0;FE&twMt{|8gF7%`yw)H$M{qsjD>cnk~|Kr;H!nFq-b#K@DO!i;KJXp{~ zlCo*Ks0%q0G5x9Ou)A0d>Lx#NrSjF3gny+@HwP`kRc&?X9%Gt1BPFT2zdaFa&%8h0 z4RUf=4BVwq#f&cB6)up3uanOREjQY|VC>;4du;0Th9?XI@;#dUx&vOkc7vn5Fxq-J z|L&Fe#m6P!-NXAZZy)=8O0GRD=i7py9KwP8V_RR%ppVeBZF!*@8KZqZ3{HJG+a6rk zzOeXBcsph9G$xFoB|G7&F}G!N{iRPzs7~Xbp8R>{4?r%{2!Y>owVSy+y&$CIw}QPu zobHNqVLp3PIvYga2-hw9Leiq0`usY&Jy}r!cUSnc0M?Oi4tF>kf^ODB1wn7lKjlo@D6luJG_H$ zPO}gR@nYezM#{3qND4!F01Zs>BZ^ zDczZ~m2BFUq8)uOC*=C7n{ui~`3Uc~O0eLbS>Gkl&n zs8-k|)z{5g@Ld9N7d;W>y{$(|N#I!o-V<1pt(^4Uu?kjbhA6zcALz6&2C<1;-P&zSIq1mZOR_V9}|9>DsyE8OEk>(5l8F^yxQdmljQhS|Dd_8 z&cxkBj=pku$)Wj4V#SPv!KBya(nBD@6qh%>QITJ!bX(4C zv2ZVLNBnSeX=@MpTzZA}^pbYE*_2lrVnr&S>*> zM|xfMXbT=`42#)MqG#RG>q!=HiMDVAr(GvF%8y<_RtJ0dRzG!N z?3!1)R}-&J+g<#|tuDRc?SZug-Qzd>`5@+(qslJ1h1jGuUulsIF-oTMCXa<+Q4uyX zxGy0V914&GF4-PQtI&E3U!n|hRI_VE=bX34vbut3N%FDycLp^+GTKpj?$dMs=fsdG zya8Ho2v7d#=kQqX6PogQNU1+XrSA7HSot`8mlJqiJG6yS^i4*u^SH0@8!LpbR`}T? zF9>4uTfj)f7m{Tcd+rDw_0&Z_{F{@!tLV)Mrnn~S`OL}>dau;!OY3Vn)>)n{& z-bmQu~ zlWajllD7V7N-CcM&=Xv|6np;N^ITz$k2UM!>bZi&-v< zHA&Iwi)T$0 zv8`h{z+`T4M+KC&_QM5oLRiv4-SNF?RSaPeQk$M+!9Vq!IPf=}w*6-(%0Qj5;HUR3pC!JmM6xIB8)*$|%&SkJQeKWrG!U0CcI^Wv^&BSceQGQeWz}S+1P+-#hQ7Z!4IK77>a_qb;it0=G<-d!ty}5>yTDM?IYWM!*+!Kg zTtihpv$n8`UjhLM%{}{|1JC-ayZIM>+vjktg~z+rzFI~|wKw-%J8^w8PaXPRCsa)3 ztq)$w0ux1RBi4X)h3$q@qe{>xcXlamS`va12pRG-9Y8YPuj}C?x94(PILe=2G=#=R)lZOQ5AAK)t{+P5r{rOsP$%0GRP|oAiy1+} zaqkM>B03XHfp@s{l}{Gig07QD=VyXwh($9#wqRQIH>uc~;DvfL$unWRvA0arch4c< z(!kF?ua)&XhC8H@g|eB8J_{MUauNa-SR|!}LA}#xIp@wX2d9NPBgYrG8Z28{{>(wy zm~vtg7iOJ;l16)r6ER=zt(P`3%HiYACLPvgF@z5fb!?oj>3o@+)atjX^i zh9U9;+LVQ?5bO#9jS8U~L#vpFRphF)RPL$T6%3mb>#h?x66(fm^V>LIi8|yzqDHy> z#nT-uJ)gnDi1$fYk;{bd<=51Jy?&p`8QrI~%*8!M&lP4TVsT6hSqo>e^>bgeP#{EC7+{k;ekc9I2v5Mp-eICsAO=o&5?@A?ev5x zEMp3Kq86Tb<&tPt<-br%^=su-Xi=wJ!R4s8xKN!-QG`!|Vv#v9J~7AUm!eA8b%MEi z-Qrre>iCr$^014Yx}&w0MTyf=7i^os_rrqnMfqZQV9XEKjxAC6MmFjn^(hJZaMHB2 zqF5IFk&=(D;1Q>!ni(eQ!G#}~n(#w3GH)~E*%u3x+UU4UmKmu=QmQx{2fD+E~ zJX~&@9}>pb^7XPGkE$uV7uPtH%440gC+<)t)i7K%DV;XrYeu+V7tO_Ux|pECM4mfS zVRJEmwtWkx0WbsMFt}n~EsYdlMo@E}+$&7sh%jQW9yiOD? z0%_VL=ZF6Wii}x}?kjwFI_W$0AWXQ9Llp?dPw zQ+v@KX;H*1HYDygcpprgc{XIDQ?orld{Uec@@b%W0ziS~uvx=siQ*ln*xMc+DA_Ch zJn~LGK#&ji{d?Jtk*wlf*Uh$xV&;*by8mabN=9J6<4JX%5&wJ%t_pR^j>&0MIibq% zOKv9erN>EUL#{IUWTR!fEipw)(0+Lf?T@CSP<4)URx?% z=Q0d`*H{VaX8yS@DCX!;blGWNa*+D0({#2fHU0i+i-l0%-gU3-XzHb;vp&LGR>fCg z(z!ODhlj>&@+!iicSZ(x-5Bq=11Jj;BdAJDzQw#*DO{TL7Evtiw4rBV{I%wy*%pJl zrW0T>cGSn>75{?`RZ!rcWZ1X<^UoPzze?tg;p?dO+an_Y@`kihwwdEfmb%0X&?w>R zzWD~q@AT&?pvdXlot&*zam9dE&fB~KYYPlw02y5|{- zp5j~K*iIy2*U0ZsY1Y7H5J-Je1>w9|->Jym6dc4TTf2)%<1dyM1+LEiRg3w#&Q9u% zZY{n(vj%rdU=?+_%_&ri6yjFqx`%A)EpbHcQM;-+i@j$Y?~`+Q$=8h#0gkWh1Y(fn zMUaYwX3p|6T`k75S64sv+gdx;31_@xHqmn3eqtQ*Y5nYs2yk?A{?KXIHt+ZS(&zz4 zd9%~FPk;BS+)YC9{ruO|9Rr%lQ8a*~ioxM;CEU#d)YGd_Cj5wR{3%BFA_C#baYj_Oq0|9tt0HG)%N|B~2#PBN^* z-0WB;lw#Xw{;mb~L5{7K+LY!tZKu-FHF`5t48)upGYD?vnqTC zQXrTJfemKlSJKZAejgC}G`~`Uh&*j9abF{eCN7k(APg=0B#kupRi`26-%&@qeATV< ziLw8|nAwNi=MV1tW-~=ZxmZVxU%()`pAC$GKmgU1-R+2cM$Cd7kt&y|BOES|8A+Xgkn3>c>E0|NkP5%4}N0l(UoqJxz_l z`CN;FSC_)r?8dm)Hb=e%-#FXIR9yy`A{J4A7Xo?9LAn$6U^8`3-%~gI{_2~$xujK& zv7!34v%Zo_KZcpDgd5vfaNkPJBmct+8lZx#MGLK_vr{jb-@=zzdp|W|<)xo(~7eiqwoySCNWUEhICm_?&dGvSO@@JpzT4n+G49#Iu zndI{T%erKqJ>*l6;v3{vb}~F z{mZ9)rhpZ5jOfY0fimIDzudf-8+MQ&vT1iCUjBy^ak6i8@~~pFYVBW*8O|_uLzf8$ zVEfSQ$(kKQ7-^f2LOUTyR(4G*TnAE+Ps_NQI#D$x`pWfj{AJ^OEI@kWY(0f9wQz6b z!=_Czr*P!Cc|+E**!1P1+Zf=iSI9w#;yG$hNbUy>V5@YIpSdzj%?wI1c%x(@QSGu@ z5q%R0|0a##!^i|-farvj#ve+raO5x%4S#;sM2$X$DMDhoxM~CjbkbRocc_aWEt(** zxa0g>Rt0A}wO#*5Vf#-h0U04lJavZBDKNeQ`ln_KjD)eJZaLih8 zlyD)JTm!ih-W5l94*A0xqbf7?-Xc>N90h7usspt>QC(j1o2_$EJ>A9mNVTdOIFrL} zsF+5T)^mPyK2_X^fhlfPrKS9vvdiW)_vXL&E_*J0;3p4ob(d_%q#P4n0vcs+*P&@K z`m+G2G;puD zDwQbM-Tk~MKL7p8F&O_PyzJG4&^!hh5-Uh#Nop%fd^hJq-+cSc9q3=B{IR zR{U#ygc0<)&F8%6Z*?l!^j263yi-h4U_ef~VrX!AwW@sipA_oRu*%2ZeD&K*DV6(^9> zks)um76%W5iyk$aHG7-^$}kGYnLhXp*VXo1gJUlWh&wPH$-nk6XXJOP0nYFN@Lp-x z05kcojq7u#^AbPsU~}+Udf36KJ)n!R)qlDuPNkGzYY86U9lhYZjHxiQK32jXc!1}y zg_+PZfUF-0?e8ww-c88H1{akUy?)eK8hjhj(B-E=CD}A~zWrI}_4kHQSyFmJREGL- zxsncS3yL%dy6Lv=)|z}CsS?>MeBCfnU+Z5_e40y9j2go_YL;r`y_ z>{DSD?dH4R#$z{SZX5K|eQD^8rbLcH!Xof4Lxu?b%1+7}9UbGr@wWhk&>5nB?vK|G zT2)O}24^}(Rhw4odpCXR9>1HkCJdd77PRs*S~fThm!V>??|~XU08+rShzf&Ay|rOj%Y`{~;lOQT)8fZPgH@f>goUiof2Z(& z!r1gqUl|At1lY#^o`6moYyT(qHoskg2-Hjtyvd9Ug+xFEEH5bTIXcQ4LHe{12N6$>+es)2u zTo`0kOA=p_xGTYtZa^TbeK7#XLPKv%_)fX1=GJcyWW96R7(_q$@{EUb2_+XMf$+RM z@Xx|qh`=OU10pHh#=@v_AL literal 0 HcmV?d00001 diff --git a/images/rotate_test_8x8_x16.png b/images/rotate_test_8x8_x16.png new file mode 100644 index 0000000000000000000000000000000000000000..550777badba9e9fc7e6715a73eaadfbbf12d22f0 GIT binary patch literal 859 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU{>;UaSW+od^_i~uS}o_>-`0u z2P7@n)13qanw%Xcv^`E}j8L{@J)O;*+sX6FbM2+I6C>t5tMMwoY%qQ6wc{Tzr)Mym zI~R-pUfswb(dWd#a^M47+wu2I^rCs#AN+ETJ8=6X<3F4EISlWvvo^5addip(FYt?> zA!0$S9K$!(h5s4I6|CFa%sFqtynp}ZfB0VC&KCD>X5;LmJbU!FIfysBT^`QfQ0FA3 zzz|ViRm$)}J%y2};VXH9U!VSX|BZiA)84ajA9BA%{g>T8yg?E|9ms+ zE`}cYy;clggf}rT9{6>6HM4`gBDVm;8hi2t^^f1q`SP71-TcFWxZ}yc*MC=+Zs6R- zu)$%s4Z|C5r~ix$+*?jFAK0hxMV@TI^~b+6udu)G$6v6EaZ9{7!`0{F><#tKQxq5i zYU>Iaewe#(GLb9z@#&8uhOgh5@63Di@A`kXqx1C|D)z`;V@`1Iew5BaRkl5&pRwF# Uv8N(SftiNE)78&qol`;+0B>YyrvLx| literal 0 HcmV?d00001 diff --git a/images/sked_tree_32x32.png b/images/sked_tree_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf4582b41daac83ea62bdcd0f51df8c5d9b6518 GIT binary patch literal 872 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 S?BLD-00005oyGP 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 literal 0 HcmV?d00001 diff --git a/images/test_8x8.png b/images/test_8x8.png new file mode 100644 index 0000000000000000000000000000000000000000..6b6f319e7ad5d54ff0f12a5ca90212e63821b614 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqF`h1tArg|wDG3Qb_>WjH3j0Z} zVCgoT)U`X{-0{sEfy+!*FiEUsNHs8YkaXDkKb^_2!)(UM5Ag?PJhV4HeCB@-&#{DA x3ph3M1X`kb(+v!m{&IXz6PU79QiY3^p^r!6@&PTKsX%)eJYD@<);T3K0RTdEFCG8@ literal 0 HcmV?d00001 diff --git a/images/wang_tilesets_32x32.png b/images/wang_tilesets_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..c7e0d4f4228453bd32797c0e729c1a5b30aca447 GIT binary patch literal 2030 zcmVnXJ(T4Z$s|4&-&K4&ieMbcb{`-CP%y7?&dE)E?D1CTTP(qgBj0)fMmMqr3jm&y!rVmiXP1pQfp3TvR6-7o+aM-9HTPRxJS8 z(LkKfl};y>d2@QY+i$%#ErO^oBKl1W;0b$J8O-@rj!l|t^B!l+`I$!_jb>gwn@qU@Wx>{CKMIvuYmu|( zr;YBzTQ?VdeD?6sVguaDdk*Vly*$W?pUJ~i9v*LBckW+fZKl!EL*^O>#o6nsO|ZJkRm`&4a4%Plo1SP~bkl0_ z#zl1^dKOSLzG?wTjwa&VLSi;=yr&_D9h>Gwb)s?6xM*B74r1K6D8E<$nr|6U(8G&r zy=pYa5IvVy-?%8>()%%K@#yCTh|R~jCyB8*uXtk-D~&vcG-=GHInIWO#r%q2tDhF2 zYA(j&oRc$REY1(?@^!?M9Tx#}m8n?us`!&$b3Kk`M*P|wjcH0W{w(omZ{lfhgIZ&< zrC+ICJn?0-t!Du8);tIF^wgLhglM#7f~3|!gSOJIF;Csg+`~uusqg#KYxAYt1e@7` z$O{$?$9a%+r*2wePIl81Gp|mBkMo@G@w@;8);6BaTMYf$8fv^%@wumAQC=kO<`@@2 zlj=lt%o#pGsuSTB3qW8rTINT(9v@ARXX9#Y*tlr!NZ;cZUxr5J*7B`)f%?oxLzu$w z^(WdtzaZwAk@dtU)2Hu%idQ=?fXzu{+`^NaO-6m>y}J2)eviK>Pra6Qajt!%-S6y| z;nDW=r`)2S=Y25019~UkH`=3z+-3B=`}lwAIe?v!_da z0$Zf_uKjnJK1%=yGIB33-Ho*q8$3-ba%u5CP$Ysn^7`?Sb;ttQpalC38 zt<0$fD?XdImRRYh1@NlHggB4Q#(4hD9*xF%rH_1UJl1y}%^Hg+AC1PjeALL7f1F^V z2K|^A7e_pKz)JR=7>Sf z8yDf?U>{%s#mF(Yj`+OzP0s=D?3GD2wHA-@9*@xF zb!ft4aMXOoXY($KSk<4u4%4JfMI%uZ=bFN(I%8Y(){Kh~jmT%z6uB-&j5*IbkLBkD zfY5+w@(6Q&J|5}iZAvsg!knLvd-@Z;&p+6m{q^)B`R=RdevG`}_Xl15p1-qO{tzUA*%&+Eo(bS2R##l6;V-;sk#Js5LM04jyVFRM( zd*9_p-}SB4!x#PYfay7)YVh9f@L$<(hd)a{pLg@===TEo*U`Dg<6Zyv0{KcteZHEJ zbKcXFv-&xJot!&{=n2M6T$}F#XuRvF?=?8CUGE>(UW4-<;HD)ajtvmUyqJrt6I*<1 z5Aa-K)*BbEir@4cfVp_vqX`Wg7juKg!D{eMz?_(KjEnLvh;dO~G!Dk`OrHZ>9I<6S znxMJdWRKTa)Tvh|8b7xHRbw_ya>d6Y6_4Jy7&Y;uP&H=LXevI|s(AFq#i+6Vyaz;* zdDV2z=^a=4Xu9HKy{hTRkLHzL(`epF literal 0 HcmV?d00001 diff --git a/js/Color.js b/js/Color.js index 71827f7..7f09f85 100644 --- a/js/Color.js +++ b/js/Color.js @@ -30,7 +30,7 @@ class Color { this.hsv = Color.rgbToHsv(this.rgb); break; default: - console.error("Unsupported color mode " + fmt); + //console.error("Unsupported color mode " + fmt); break; } } diff --git a/js/ColorModule.js b/js/ColorModule.js index 9b90237..9087397 100644 --- a/js/ColorModule.js +++ b/js/ColorModule.js @@ -57,7 +57,7 @@ const ColorModule = (() => { if (newColorHex == colors[i].jscolor.toString()) { //if the color isnt the one that has the picker currently open if (!colors[i].parentElement.classList.contains('jscolor-active')) { - //console.log('%cColor is duplicate', colorCheckingStyle); + //////console.log('%cColor is duplicate', colorCheckingStyle); //show the duplicate color warning duplicateColorWarning.style.visibility = 'visible'; @@ -251,7 +251,7 @@ const ColorModule = (() => { //loop through colors for (var i = 0; i < colors.length; i++) { - //console.log(color,'=',colors[i].jscolor.toString()); + //////console.log(color,'=',colors[i].jscolor.toString()); if (color == colors[i].jscolor.toString()) { //set color to the color button @@ -346,7 +346,7 @@ const ColorModule = (() => { * @param {*} paletteColors The colours of the palette */ function createColorPalette(paletteColors) { - console.log("creating palette"); + ////console.log("creating palette"); //remove current palette while (colorsMenu.childElementCount > 1) colorsMenu.children[0].remove(); @@ -358,7 +358,7 @@ const ColorModule = (() => { for (var i = 0; i < paletteColors.length; i++) { var newColor = new Color("hex", paletteColors[i]); var newColorElement = ColorModule.addColor(newColor.hex); - + ////console.log('newColor.hex === ',newColor.hex); var newColRgb = newColor.rgb; var lightestColorRgb = lightestColor.rgb; @@ -382,6 +382,8 @@ const ColorModule = (() => { //set as current color updateCurrentColor(darkestColor.hex); + + ////console.log('getCurrentPalette() === ',getCurrentPalette()); } /** Creates the palette with the colours used in all the layers @@ -421,7 +423,7 @@ const ColorModule = (() => { //create palette from colors array createColorPalette(colorPaletteArray); - console.log("Done 2"); + ////console.log("Done 2"); } function updateCurrentColor(color, refLayer) { diff --git a/js/ColorPicker.js b/js/ColorPicker.js index 5f971a1..0f47dda 100644 --- a/js/ColorPicker.js +++ b/js/ColorPicker.js @@ -696,7 +696,7 @@ const ColorPicker = (() => { } break; default: - console.log("How did you select the " + currentPickingMode + ", hackerman?"); + ////console.log("How did you select the " + currentPickingMode + ", hackerman?"); break; } diff --git a/js/Dialogue.js b/js/Dialogue.js index 7ace4b8..b5d7061 100644 --- a/js/Dialogue.js +++ b/js/Dialogue.js @@ -5,7 +5,7 @@ const Dialogue = (() => { let currentOpenDialogue = ""; let dialogueOpen = true; - const popUpContainer = document.getElementById("pop-up-container"); + const popUpContainer = document.getElementById("pop-up-container") ?? document.createElement("div"); const cancelButtons = popUpContainer.getElementsByClassName('close-button'); Events.onCustom("esc-pressed", closeDialogue); @@ -31,6 +31,9 @@ const Dialogue = (() => { * @param {*} trackEvent Should I track the GA event? */ function showDialogue (dialogueName, trackEvent) { + + + if (typeof trackEvent === 'undefined') trackEvent = true; // Updating currently open dialogue @@ -83,4 +86,4 @@ const Dialogue = (() => { } })(); -console.log("Dialog: " + Dialogue); \ No newline at end of file +////console.log("Dialog: " + Dialogue); \ No newline at end of file diff --git a/js/EditorState.js b/js/EditorState.js index 56f16db..95d712f 100644 --- a/js/EditorState.js +++ b/js/EditorState.js @@ -9,8 +9,10 @@ const EditorState = (() => { return pixelEditorMode; } - function switchMode(newMode) { - if (!firstFile && newMode == "Basic" && !confirm('Switching to basic mode will flatten all the visible layers. Are you sure you want to continue?')) { + function switchMode(newMode, skipConfirm = false) { + ////console.trace(); + const switchText = 'Switching to basic mode will flatten all the visible layers. Are you sure you want to continue?'; + if (!firstFile && newMode == "Basic" && !skipConfirm && !confirm(switchText)) { return; } //switch to advanced mode diff --git a/js/Events.js b/js/Events.js index 780e44e..b4058f8 100644 --- a/js/Events.js +++ b/js/Events.js @@ -100,8 +100,9 @@ const Events = (() => { */ function on(event, elementId, functionCallback, ...args) { //if element provided is string, get the actual element + if(!elementId)return; const element = Util.getElement(elementId); - + if(!element)return; element.addEventListener(event, function (e) { functionCallback(...args, e); diff --git a/js/File.js b/js/File.js index b20a652..28be431 100644 --- a/js/File.js +++ b/js/File.js @@ -2,11 +2,12 @@ class File { // Canvas, canvas state canvasSize = []; zoom = 7; - canvasView = document.getElementById("canvas-view"); + canvasView = document.getElementById("canvas-view") ?? document.createElement("canvas"); inited = false; // Layers layers = []; + sublayers = []; currentLayer = undefined; VFXLayer = undefined; TMPLayer = undefined; @@ -129,11 +130,9 @@ class File { // Save all imageDatas for (let i=0; i { // Binding the browse holder change event to file loading @@ -6,6 +7,7 @@ const FileManager = (() => { Events.on('change', browseHolder, loadFile); Events.on('change', browsePaletteHolder, loadPalette); + Events.on("click", "save-project-confirm", saveProject); function openSaveProjectWindow() { //create name @@ -20,7 +22,6 @@ const FileManager = (() => { } Util.setValue('lpe-file-name', fileName); - Events.on("click", "save-project-confirm", saveProject); Dialogue.showDialogue('save-project', false); } @@ -42,6 +43,7 @@ const FileManager = (() => { function saveProject() { // Get name + // debugger; let fileName = Util.getValue("lpe-file-name") + ".lpe"; let selectedPalette = Util.getText('palette-button'); //set download link @@ -55,6 +57,9 @@ const FileManager = (() => { if (typeof ga !== 'undefined') ga('send', 'event', 'Pixel Editor Save', selectedPalette, currFile.canvasSize[0]+'/'+currFile.canvasSize[1]); /*global ga*/ + + + LayerList.closeOptionsMenu(); // is this the right place for this? } function exportProject() { @@ -117,7 +122,39 @@ const FileManager = (() => { //open file selection dialog document.getElementById('open-image-browse-holder').click(); } - + function localStorageCheck() { + return !!localStorage.getItem("lpe-cache"); + } + function localStorageSave() { + const lpeStr = getProjectData(); + const lpe = JSON.parse(lpeStr); + //console.log('LPE saved === ',lpe); + if(lpe.colors.length < 1)lpe.colors.push("#000000"); + if(!lpe.canvasWidth)lpe.canvasWidth = 16; + if(!lpe.canvasHeight)lpe.canvasHeight = 16; + localStorage.setItem("lpe-cache", JSON.stringify(lpe)); + } + function localStorageReset() { + localStorage.setItem("lpe-cache", JSON.stringify({ + "canvasWidth":16, + "canvasHeight":16, + "editorMode":"Advanced", + "colors":["#000000","#0b6082","#1d8425","#cc1919"], + "selectedLayer":0, + "layers":[ + {"canvas":{},"context":{"mozImageSmoothingEnabled":false},"isSelected":true,"isVisible":true,"isLocked":false,"oldLayerName":null,"menuEntry":{},"id":"layer0","name":"Layer 0","src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAHZJREFUOE9jZKAQMFKon4EuBvxHcyWKpdhcgK4BpB+nS9ElYJqJ9hqyQpI1ozsNZABRNnMnNIEt+7qgjhGrBpgCWOCBFKJHN0gNTgOQFSPbhi5OlAHYEhpBL+DThO4tgoGGHB7YwgKvAbj8j+xCgi4glNkoNgAA3JApEbHObDkAAAAASUVORK5CYII="}, + {"canvas":{},"context":{"mozImageSmoothingEnabled":false},"isSelected":false,"isVisible":true,"isLocked":false,"oldLayerName":null,"menuEntry":{},"id":"layer1","name":"Layer 1","src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAGNJREFUOE9jZKAQMCLrl21R/Q/iP665jSKOzw7qGgCyCeQKsl0AM4AUb2D1KymuoJ0BxHoDZ3QRG6V445uYsCBoACGvEExxhFxBlAH4XEHQAEKpkygDkFMoumuINgCWI9HDBAChJjwRzAXQUwAAAABJRU5ErkJggg=="}, + {"canvas":{},"context":{"mozImageSmoothingEnabled":false},"isSelected":false,"isVisible":true,"isLocked":false,"oldLayerName":null,"menuEntry":{},"id":"layer2","name":"Layer 2","src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAGNJREFUOE9jZKAQMJKi/4yk5H+YepPnz8F68RqArAFdI4yP1QBsNuFyKYYBMM0wJxLyIlYDiNWMNQxALhimBuCKUoKBSChKcRpASCPOhESsRoIGEBuVKF4g1XaMhERqMgYZAACIaEgR0hnFxgAAAABJRU5ErkJggg=="} + ] + })); + } + function localStorageLoad() { + ////console.log("loading from localStorage"); + ////console.log('JSON.parse(localStorage.getItem("lpe-cache") ?? "{}") === ',JSON.parse(localStorage.getItem("lpe-cache") ?? "{}")); + const lpe = JSON.parse(localStorage.getItem("lpe-cache") ?? "{}"); + //console.log('LPE loaded === ',lpe); + return lpe; + } function loadFile() { let fileName = document.getElementById("open-image-browse-holder").value; // Getting the extension @@ -139,7 +176,6 @@ const FileManager = (() => { browseHolder.value = null; } - function openFile() { //load file var fileReader = new FileReader(); @@ -165,59 +201,88 @@ const FileManager = (() => { }; fileReader.readAsDataURL(browseHolder.files[0]); } - - function openProject() { - let file = browseHolder.files[0]; - let reader = new FileReader(); - + function openProject(lpeData) { + // Getting all the data - reader.readAsText(file, "UTF-8"); - // Converting the data to a json object and creating a new pixel (see _newPixel.js for more) - reader.onload = function (e) { - let dictionary = JSON.parse(e.target.result); - Startup.newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary); - - for (let i=0; dictionary['color' + i] != null; i++) { - ColorModule.addColor(dictionary['color'+i]); + if(lpeData){ + _parseLPE(lpeData); + } else { + let file = uri ?? browseHolder.files[0]; + let reader = new FileReader(); + reader.readAsText(file, "UTF-8"); + // Converting the data to a json object and creating a new pixel (see _newPixel.js for more) + reader.onload = function (e) { + let dictionary = JSON.parse(e.target.result); + _parseLPE(dictionary); } - - // Removing the default colours - ColorModule.deleteColor(ColorModule.getCurrentPalette()[0]); - ColorModule.deleteColor(ColorModule.getCurrentPalette()[0]); + } + + function _parseLPE(dictionary) { + Startup.newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary, true); } } + function loadFromLPE(dictionary) { + ColorModule.resetPalette(); + //console.log('dictionary === ',dictionary); + + dictionary = FileManager.upgradeLPE(dictionary); + + EditorState.switchMode(dictionary.editorMode ?? 'Advanced'); + + // I add every layer the file had in it + // dictionary.layers.forEach((layerData,i)=>{ + // let layerImage = layerData.src; + // if (layerData != null) { + // // Setting id + // let createdLayer = LayerList.addLayer(layerData.id, false, layerData.name); + // // Setting name + // createdLayer.menuEntry.getElementsByTagName("p")[0].innerHTML = layerData.name; + + // // Adding the image (I can do that because they're sorted by increasing z-index) + // let img = new Image(); + // img.onload = function() { + // createdLayer.context.drawImage(img, 0, 0); + // createdLayer.updateLayerPreview(); + // }; + + // img.src = layerImage; + + // // Setting visibility and lock options + // if (!layerData.isVisible) { + // createdLayer.hide(); + // } + // if (layerData.isLocked) { + // createdLayer.lock(); + // } + // } + // }) + if(dictionary.colors)ColorModule.createColorPalette(dictionary.colors); + } function getProjectData() { // use a dictionary let dictionary = {}; // sorting layers by increasing z-index - let layersCopy = currFile.layers.slice(); - layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1); - // save canvas size + let layersCopy = currFile.layers.filter(n=>!!n.menuEntry).slice(); + // layersCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1); dictionary['canvasWidth'] = currFile.canvasSize[0]; dictionary['canvasHeight'] = currFile.canvasSize[1]; - // save editor mode dictionary['editorMode'] = EditorState.getCurrentMode(); - // save palette - for (let i=0; i{ + //console.log('n.name === ',n.name); + if(n.isSelected)dictionary.selectedLayer = i; + return { + ...n, + src: n.canvas.toDataURL(), + }; + }); return JSON.stringify(dictionary); } - function loadPalette() { if (browsePaletteHolder.files && browsePaletteHolder.files[0]) { //make sure file is allowed filetype @@ -268,9 +333,45 @@ const FileManager = (() => { browsePaletteHolder.value = null; } + function upgradeLPE(dictionary) { + ////console.log('dictionary === ',dictionary); + if(dictionary.color0 && !dictionary.colors) { + dictionary.colors = []; + let colorIdx = 0; + while(dictionary[`color${colorIdx}`]) { + dictionary.colors.push(dictionary[`color${colorIdx}`]); + delete dictionary[`color${colorIdx}`]; + colorIdx++; + } + dictionary.layers = Object.keys(dictionary).reduce((r,k,i)=>{ + if(k.slice(0,5) === "layer"){ + if(dictionary[k].isSelected){ + dictionary.selectedLayer = r.length; + } + r.push({ + ...dictionary[k], + src: dictionary[`${k}ImageData`] + }); + + delete dictionary[k]; + delete dictionary[`${k}ImageData`]; + } + return r; + },[]); + } + return dictionary; + } return { + loadFromLPE, + getProjectData, + localStorageReset, + localStorageCheck, + localStorageSave, + localStorageLoad, + upgradeLPE, saveProject, + openProject, exportProject, openPixelExportWindow, openSaveProjectWindow, diff --git a/js/History.js b/js/History.js index f619b3e..1d54ec3 100644 --- a/js/History.js +++ b/js/History.js @@ -38,12 +38,12 @@ const History = (() => { } function undo () { - console.log("undoing"); + ////console.log("undoing"); undoOrRedo('undo'); } function redo () { - console.log("redoing"); + ////console.log("redoing"); undoOrRedo('redo'); } @@ -186,13 +186,13 @@ class HistoryState { this.nFlattened = nFlattened; this.undo = function() { - for (let i=0; i this.index + 1) { + if (currFile.layers.length > this.index + 1) { currFile.layers[this.index + 1].selectLayer(); } else { @@ -412,7 +416,7 @@ class HistoryState { //find new color in palette and change it back to old color let colors = document.getElementsByClassName('color-button'); for (let i = 0; i < colors.length; i++) { - //console.log(newColorValue, '==', colors[i].jscolor.toString()); + //////console.log(newColorValue, '==', colors[i].jscolor.toString()); if (newColorValue == colors[i].jscolor.toString()) { colors[i].jscolor.fromString(oldColorValue); break; @@ -429,7 +433,7 @@ class HistoryState { //find old color in palette and change it back to new color let colors = document.getElementsByClassName('color-button'); for (let i = 0; i < colors.length; i++) { - //console.log(oldColorValue, '==', colors[i].jscolor.toString()); + //////console.log(oldColorValue, '==', colors[i].jscolor.toString()); if (oldColorValue == colors[i].jscolor.toString()) { colors[i].jscolor.fromString(newColorValue); break; diff --git a/js/Input.js b/js/Input.js index 8450ce7..954a766 100644 --- a/js/Input.js +++ b/js/Input.js @@ -163,7 +163,7 @@ const Input = (() => { spacePressed = true; break; case 46: - console.log("Pressed del"); + ////console.log("Pressed del"); Events.emit("del"); break; } diff --git a/js/LayerList.js b/js/LayerList.js index 9c52c47..2f47a2d 100644 --- a/js/LayerList.js +++ b/js/LayerList.js @@ -1,46 +1,36 @@ const LayerList = (() => { - let layerList = document.getElementById("layers-menu"); - let layerListEntry = layerList.firstElementChild; + // let layerListEntry = layerList.firstElementChild; + let layerListEntry = document.getElementById("default-layer-list-item"); let renamingLayer = false; let dragStartLayer; - - // Binding the right click menu Events.on("mousedown", layerList, openOptionsMenu); - // Binding the add layer button to the right function Events.on('click',"add-layer-button", addLayer, false); - // Listening to the switch mode event so I can change the layout - Events.onCustom("switchedToAdvanced", showMenu); - Events.onCustom("switchedToBasic", hideMenu); + Events.onCustom("switchedToAdvanced", showLayerList); + Events.onCustom("switchedToBasic", hideLayerList); - // Making the layers list sortable new Sortable(layerList, { animation: 100, filter: ".layer-button", draggable: ".layers-menu-entry", onStart: layerDragStart, - onEnd: layerDragDrop + onEnd: layerDragEnd }); - - function showMenu() { + function showLayerList() { layerList.style.display = "inline-block"; document.getElementById('layer-button').style.display = 'inline-block'; } - function hideMenu() { + function hideLayerList() { if (EditorState.documentCreated()) { - // Selecting the current layer currFile.currentLayer.selectLayer(); - // Flatten the layers flatten(true); } - layerList.style.display = "none"; document.getElementById('layer-button').style.display = 'none'; } + function addLayer(id, saveHistory = true, layerName) { - function addLayer(id, saveHistory = true) { - // layers.length - 3 - let index = currFile.layers.length - 3; + let index = currFile.layers.length; // Creating a new canvas let newCanvas = document.createElement("canvas"); // Setting up the new canvas @@ -49,12 +39,16 @@ const LayerList = (() => { newCanvas.style.zIndex = Layer.maxZIndex; newCanvas.classList.add("drawingCanvas"); - if (!layerListEntry) return console.warn('skipping adding layer because no document'); + if (!layerListEntry) return //console.warn('skipping adding layer because no document'); // Clone the default layer let toAppend = layerListEntry.cloneNode(true); + toAppend.style.display = "flex"; + //console.log('toAppend === ',toAppend); // Setting the default name for the layer - toAppend.getElementsByTagName('p')[0].innerHTML = "Layer " + Layer.layerCount; + const _layerName = layerName ?? "Layer " + currFile.layers.length; + //console.log('_layerName === ',_layerName); + toAppend.getElementsByTagName('p')[0].innerHTML = _layerName; // Removing the selected class toAppend.classList.remove("selected-layer"); // Adding the layer to the list @@ -65,10 +59,11 @@ const LayerList = (() => { newLayer.context.fillStyle = currFile.currentLayer.context.fillStyle; newLayer.copyData(currFile.currentLayer); - currFile.layers.splice(index, 0, newLayer); + // currFile.layers.splice(index, 0, newLayer); + currFile.layers.push(newLayer); // Insert it before the Add layer button - layerList.insertBefore(toAppend, layerList.childNodes[0]); + layerList.insertBefore(toAppend, document.getElementById("add-layer-li")); if (id != null && typeof(id) == "string") { newLayer.setID(id); @@ -76,11 +71,11 @@ const LayerList = (() => { // Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history if (saveHistory) { new HistoryState().AddLayer(newLayer, index); + FileManager.localStorageSave(); } return newLayer; } - /** Merges topLayer onto belowLayer * * @param {*} belowLayer The layer on the bottom of the layer stack @@ -97,7 +92,7 @@ const LayerList = (() => { toMergeImageData.data[i+2], toMergeImageData.data[i+3] ]; - let currentUnderlyingPixel = [ + let currentUnderlyingPixel = [ //TODO: I'd be curious to know if this array slows this function down belowImageData.data[i], belowImageData.data[i+1], belowImageData.data[i+2], belowImageData.data[i+3] ]; @@ -115,34 +110,54 @@ const LayerList = (() => { // Putting the top data into the belowdata belowLayer.putImageData(toMergeImageData, 0, 0); } - /** Sets the z indexes of the layers when the user drops the layer in the menu * * @param {*} event */ - function layerDragDrop(event) { - let oldIndex = event.oldDraggableIndex; - let newIndex = event.newDraggableIndex; + function layerDragEnd(event) { + // let oldIndex = event.oldDraggableIndex; + // let newIndex = event.newDraggableIndex; - let movedZIndex = dragStartLayer.canvas.style.zIndex; + // let movedZIndex = dragStartLayer.canvas.style.zIndex; - if (oldIndex > newIndex) - { - for (let i=newIndex; ioldIndex; i--) { - getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i - 1].id).canvas.style.zIndex; - } - } + // if (oldIndex > newIndex) + // { + // for (let i=newIndex; ioldIndex; i--) { + // getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i - 1].id).canvas.style.zIndex; + // } + // } - getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex; + // getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex; Events.simulateMouseEvent(window, "mouseup"); - } + + const tempLayerCache = currFile.layers.reduce((r,n,i) => { + r[n.id] = n; + return r; + },{}); + + let selectedId; + const idArr = [...document.querySelectorAll(".layers-menu-entry")].map(elm => { + if([...elm.classList].includes("selected-layer")) { + selectedId = elm.id; + } + return elm.id; + }); + let selectedIdx = idArr.indexOf(selectedId); + idArr.forEach((id,i)=>{ + currFile.layers[i] = tempLayerCache[id]; + currFile.layers[i].isSelected = i===selectedIdx; + }); + + FileManager.localStorageSave(); + + } /** Saves the layer that is being moved when the dragging starts * * @param {*} event @@ -150,21 +165,27 @@ const LayerList = (() => { function layerDragStart(event) { dragStartLayer = getLayerByID(layerList.children[event.oldIndex].id); } - - // Finds a layer given its id function getLayerByID(id) { + //console.log(`getLayerByID(${id})`); + // for (let i=0; i { return null; } - function startRenamingLayer(event) { let p = currFile.currentLayer.menuEntry.getElementsByTagName("p")[0]; @@ -189,7 +209,6 @@ const LayerList = (() => { renamingLayer = true; } - function duplicateLayer(event, saveHistory = true) { function getMenuEntryIndex(list, entry) { for (let i=0; i { newCanvas.style.zIndex = parseInt(currFile.currentLayer.canvas.style.zIndex) + 2; newCanvas.classList.add("drawingCanvas"); - if (!layerListEntry) return console.warn('skipping adding layer because no document'); + if (!layerListEntry) return //console.warn('skipping adding layer because no document'); // Clone the default layer let toAppend = currFile.currentLayer.menuEntry.cloneNode(true); @@ -248,41 +267,52 @@ const LayerList = (() => { new HistoryState().DuplicateLayer(newLayer, currFile.currentLayer); } } + function clearLayers() { + //console.log('currFile.layers === ',currFile.layers); - function deleteLayer(saveHistory = true) { - // Cannot delete all the layers - if (currFile.layers.length != 4) { - let layerIndex = currFile.layers.indexOf(currFile.currentLayer); - let toDelete = currFile.layers[layerIndex]; - let previousSibling = toDelete.menuEntry.previousElementSibling; - // Adding the ids to the unused ones - Layer.unusedIDs.push(toDelete.id); - - // Selecting the next layer - if (layerIndex != (currFile.layers.length - 4)) { - currFile.layers[layerIndex + 1].selectLayer(); - } - // or the previous one if the next one doesn't exist - else { - currFile.layers[layerIndex - 1].selectLayer(); - } - - // Deleting canvas and entry - toDelete.canvas.remove(); - toDelete.menuEntry.remove(); - - // Removing the layer from the list - currFile.layers.splice(layerIndex, 1); - - if (saveHistory) { - new HistoryState().DeleteLayer(toDelete, previousSibling, layerIndex); - } + currFile.layers.forEach(()=>deleteLayer()); + //console.log('currFile.layers.length === ',currFile.layers.length); + for(let i = 0; i < currFile.layers.length;i++){ + const layer = currFile.layers[i]; + //console.log('i === ', i); + //console.log('layer === ',layer); } + } + function deleteLayer(saveHistory = true) { + //console.log('deleting layer: ', currFile.currentLayer.name, currFile.currentLayer); + //console.trace(); + deleteLayerDirectly(currFile.currentLayer, saveHistory); // Closing the menu closeOptionsMenu(); } + function deleteLayerDirectly(layer, saveHistory = true) { + let layerIndex = currFile.layers.indexOf(layer); + let toDelete = currFile.layers[layerIndex]; + let previousSibling = toDelete.menuEntry.previousElementSibling; + // Adding the ids to the unused ones + Layer.unusedIDs.push(toDelete.id); + if(layer.isSelected) { + // Selecting the nearest layer + const nearestLayer = (currFile.layers[layerIndex + 1] ?? currFile.layers[layerIndex - 1]); + if(nearestLayer){ + nearestLayer.selectLayer(); + //console.log('changing to nearest layer'); + } + } + + // Deleting canvas and entry + toDelete.canvas.remove(); + toDelete.menuEntry.remove(); + + // Removing the layer from the list + currFile.layers.splice(layerIndex, 1); + + if (saveHistory) { + new HistoryState().DeleteLayer(toDelete, previousSibling, layerIndex); + } + } function merge(saveHistory = true) { // Saving the layer that should be merged let toMerge = currFile.currentLayer; @@ -312,7 +342,6 @@ const LayerList = (() => { currFile.currentLayer.updateLayerPreview(); } } - function flatten(onlyVisible) { if (!onlyVisible) { // Selecting the first layer @@ -366,7 +395,6 @@ const LayerList = (() => { currFile.currentLayer.updateLayerPreview(); } } - function openOptionsMenu(event) { if (event.which == 3) { let selectedId; @@ -385,21 +413,17 @@ const LayerList = (() => { getLayerByID(selectedId).selectLayer(false); } } - function closeOptionsMenu(event) { Layer.layerOptions.style.visibility = "hidden"; currFile.currentLayer.rename(); renamingLayer = false; } - function getLayerListEntries() { return layerList; } - function isRenamingLayer() { return renamingLayer; } - return { addLayer, mergeLayers, @@ -407,7 +431,9 @@ const LayerList = (() => { getLayerByName, renameLayer: startRenamingLayer, duplicateLayer, + clearLayers, deleteLayer, + deleteLayerDirectly, merge, flatten, closeOptionsMenu, diff --git a/js/PresetModule.js b/js/PresetModule.js index 0b8d5eb..4e10c16 100644 --- a/js/PresetModule.js +++ b/js/PresetModule.js @@ -6,7 +6,7 @@ const PresetModule = (() => { }; function instrumentPresetMenu() { - console.info("Initializing presets.."); + //console.info("Initializing presets.."); // Add a button for all the presets available const presetsMenu = document.getElementById('preset-menu'); Object.keys(presets).forEach((presetName,) => { @@ -17,7 +17,7 @@ const PresetModule = (() => { presetsMenu.appendChild(button); button.addEventListener('click', () => { - console.log("Preset: " + presetName); + ////console.log("Preset: " + presetName); //change dimentions on new pixel form Util.setValue('size-width', presets[presetName].width); Util.setValue('size-height', presets[presetName].height); diff --git a/js/Settings.js b/js/Settings.js index bb9e63d..62f1aa6 100644 --- a/js/Settings.js +++ b/js/Settings.js @@ -14,7 +14,7 @@ const Settings = (() => { settingsFromCookie = Cookies.get('pixelEditorSettings'); if(!settingsFromCookie) { - console.log('settings cookie not found'); + ////console.log('settings cookie not found'); settings = { switchToChangedColor: true, @@ -27,8 +27,8 @@ const Settings = (() => { }; } else{ - console.log('settings cookie found'); - console.log(settingsFromCookie); + ////console.log('settings cookie found'); + ////console.log(settingsFromCookie); settings = JSON.parse(settingsFromCookie); } diff --git a/js/Startup.js b/js/Startup.js index 999bb0a..ff5f03f 100644 --- a/js/Startup.js +++ b/js/Startup.js @@ -1,6 +1,8 @@ const Startup = (() => { let splashPostfix = ''; + let cacheIntervalIdx; + Events.on('click', 'create-button', create, false); Events.on('click', 'create-button-splash', create, true); @@ -15,7 +17,10 @@ const Startup = (() => { var height = Util.getValue('size-height' + splashPostfix); var selectedPalette = Util.getText('palette-button' + splashPostfix); - newPixel(width, height); + newPixel({ + canvasWidth: width, + canvasHeight: height, + }); resetInput(); //track google event @@ -25,15 +30,16 @@ const Startup = (() => { /** Creates a new, empty file * - * @param {*} width Start width of the canvas - * @param {*} height Start height of the canvas * @param {*} fileContent If fileContent != null, then the newPixel is being called from the open menu + * @param {*} skipModeConfirm If skipModeConfirm == true, then the mode switching confirmation will be skipped */ - function newPixel (width, height, fileContent = null) { + function newPixel (fileContent = null, skipModeConfirm = false) { + //console.log('called newPixel'); + //console.trace(); // The palette is empty, at the beginning ColorModule.resetPalette(); - initLayers(width, height); + initLayers(fileContent); initPalette(); // Closing the "New Pixel dialogue" @@ -46,64 +52,91 @@ const Startup = (() => { // Now, if I opened an LPE file if (fileContent != null) { - loadFromLPE(fileContent); - // Deleting the default layer - LayerList.deleteLayer(false); - // Selecting the new one - currFile.layers[1].selectLayer(); + FileManager.loadFromLPE(fileContent); } - - EditorState.switchMode(EditorState.getCurrentMode()); + ////console.log('ColorModule.getCurrentPalette() === ',ColorModule.getCurrentPalette()); + + EditorState.switchMode(EditorState.getCurrentMode(), skipModeConfirm); // This is not the first Pixel anymore EditorState.created(); - } - function initLayers(width, height) { - // Setting the general canvasSize + ////console.log('ColorModule.getCurrentPalette() === ',ColorModule.getCurrentPalette()); + ////console.trace(); + } + function clearLayers() { + for(let i = 0; i < currFile.layers.length;i++) { + currFile.layers[i].delete(i); + } + for(let i = 0; i < currFile.sublayers.length;i++) { + currFile.sublayers[i].delete(i); + } + } + function initLayers(lpe) { + //console.group('called initLayers'); + //console.log('currFile.layers === ',currFile.layers); + + const width = lpe.canvasWidth; + const height = lpe.canvasHeight; + clearLayers(); + + // debugger; + // currFile.canvasSize = [width, height]; - // If this is the first pixel I'm creating since the app has started - if (EditorState.firstPixel()) { - // Creating the first layer - currFile.currentLayer = new Layer(width, height, 'pixel-canvas', ""); + if( lpe.layers && lpe.layers.length ) { + currFile.currentLayer = new Layer(width, height, `pixel-canvas`,"","layer-li-template"); currFile.currentLayer.canvas.style.zIndex = 2; - } - else { - // Deleting all the extra layers and canvases, leaving only one - let nLayers = currFile.layers.length; - for (let i=2; i < currFile.layers.length - nAppLayers; i++) { - let currentEntry = currFile.layers[i].menuEntry; - let associatedLayer; + currFile.sublayers.push(currFile.currentLayer); - if (currentEntry != null) { - // Getting the associated layer - associatedLayer = LayerList.getLayerByID(currentEntry.id); + let selectedIdx = lpe.selectedLayer ?? 0; - // Deleting its canvas - associatedLayer.canvas.remove(); - - // Adding the id to the unused ones - Layer.unusedIDs.push(currentEntry.id); - // Removing the entry from the menu - currentEntry.remove(); + lpe.layers.forEach((layerData, i) => { + //console.log('lpe.layers[i] === ', i); + let layerImage = layerData.src; + if (layerData != null) { + // Setting id + let createdLayer = LayerList.addLayer(layerData.id, false, layerData.name); + if(i===selectedIdx)createdLayer.selectLayer(); + // Setting name + createdLayer.menuEntry.getElementsByTagName("p")[0].innerHTML = layerData.name; + + // Adding the image (I can do that because they're sorted by increasing z-index) + let img = new Image(); + img.onload = function() { + createdLayer.context.drawImage(img, 0, 0); + createdLayer.updateLayerPreview(); + }; + + img.src = layerImage; + + // Setting visibility and lock options + if (!layerData.isVisible) { + createdLayer.hide(); + } + if (layerData.isLocked) { + createdLayer.lock(); + } } - } + }); - // Removing the old layers from the list - for (let i=2; i { // Tmp layer to draw previews on currFile.TMPLayer = new Layer(width, height, 'tmp-canvas'); - if (EditorState.firstPixel()) { - // Adding the first layer and the checkerboard to the list of layers - currFile.layers.push(currFile.checkerBoard); - currFile.layers.push(currFile.currentLayer); - currFile.layers.push(currFile.TMPLayer); - currFile.layers.push(currFile.pixelGrid); - currFile.layers.push(currFile.VFXLayer); - } + currFile.sublayers.push(currFile.checkerBoard); + currFile.sublayers.push(currFile.TMPLayer); + currFile.sublayers.push(currFile.pixelGrid); + currFile.sublayers.push(currFile.VFXLayer); } function initPalette() { @@ -168,42 +197,6 @@ const Startup = (() => { } } - function loadFromLPE(fileContent) { - // I add every layer the file had in it - for (let i=0; i { currTool.onRightDrag(mousePos, mouseEvent.target); break; default: - console.log("wtf"); + ////console.log("wtf"); break; } } diff --git a/js/TopMenuModule.js b/js/TopMenuModule.js index 3b70d64..1e33237 100644 --- a/js/TopMenuModule.js +++ b/js/TopMenuModule.js @@ -1,7 +1,7 @@ const TopMenuModule = (() => { - const mainMenuItems = document.getElementById('main-menu').children; - let infoList = document.getElementById('editor-info'); + const mainMenuItems = document.getElementById('main-menu')?.children ?? []; + let infoList = document.getElementById('editor-info') ?? document.createElement("div"); let infoElements = {}; initMenu(); @@ -113,7 +113,12 @@ const TopMenuModule = (() => { } function updateField(fieldId, value) { - document.getElementById(fieldId).value = value; + const elm = document.getElementById(fieldId); + if(elm) { + elm.value = value; + } else { + //console.warn('elm === ', elm); + } } function addInfoElement(fieldId, field) { diff --git a/js/Util.js b/js/Util.js index a6ba566..cc0aaf8 100644 --- a/js/Util.js +++ b/js/Util.js @@ -83,7 +83,7 @@ class Util { return document.getElementById(elementOrElementId); } else { - console.log("Type not supported: " + typeof(elementOrElementId)); + ////console.log("Type not supported: " + typeof(elementOrElementId)); } } diff --git a/js/canvas_util.js b/js/canvas_util.js new file mode 100644 index 0000000..95b8cd5 --- /dev/null +++ b/js/canvas_util.js @@ -0,0 +1,211 @@ +function drawTinyNumber(ctx,str,x,y) { + const CHARS = [ + `0111110111011111`, + `0110111001101111`, + `1111001111001111`, + `1111011100111111`, + `1001100111110011`, + `1111110000111111`, + `1100111110111111`, + `1111001101100110`, + `1110101111010111`, + `1111101111110011`, + ] + .map(n=>n.split("").map(Number)) + ; + str.split("").reduce((xo,n)=>{ + let bitArr = CHARS[n]; + let cw = bitArr.length / 4; + bitArr.forEach((bit,i)=>{ + const _x = x + xo + (i%cw); + const _y = y + Math.floor(i/cw); + if(bit)ctx.fillRect(_x,_y,1,1); + }); + xo+=cw+1; + return xo; + },0); +} +function drawTinyText( ctx, str, x = 0, y = 0, font = "Verdana", w = 16, h = 16, xo = 0, yo = 0 ) { + for(let i = 0; i < 4;i++){ + drawTinyTextOne( ctx, str, x, y, font, w, h, xo+0, yo+i ); + } +} +function drawTinyTextOne( ctx, str, x = 0, y = 0, font = "Verdana", w = 16, h = 16, xo = 0, yo = 0 ) { + const CHARS = generateCharsFromFont(font, w, h, 8, 8, undefined, undefined, xo, yo) + .map(n=>n.split("").map(Number)) + ; + ////console.log('CHARS === ',CHARS); + str.split("").reduce((_xo,n)=>{ + const code = n.charCodeAt(0) - 33; + // ////console.log('n,code === ',n,code); + let charWidth = CHARS[code].length / w; + CHARS[code].forEach((bit,i)=>{ + const _x = x + _xo + (i%charWidth); + const _y = y + Math.floor(i/charWidth); + + // ////console.log('bit === ',bit); + if(bit)ctx.fillRect(_x,_y,1,1); + }); + _xo+=charWidth+1; + return _xo; + },0); +} +function generateCharsFromFont(font, charW = 7, charH = 7, sampleScale = 8, scale = 8, previewDiv, debugDiv, xo = 0, yo = 0) { + return [...Array(94)].map((_,i)=>{ + const char = String.fromCharCode(i+33); + const canvas = document.createElement('canvas'); + if(debugDiv)debugDiv.appendChild(canvas); + const sz = sampleScale; + const w = charW * sz; + const h = charH * sz; + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + ctx.font = `${h}px ` + font; + ctx.shadowColor="black"; + ctx.shadowBlur=sz*2; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.fillStyle = "black"; + ctx.fillText(char,w/2,h); + ctx.strokeStyle = "black"; + ctx.strokeText(char,w/2,h); + const imageData = ctx.getImageData(0,0,w,h); + // ////console.log('imageData === ',imageData); + let ret = ''; + ctx.fillStyle = "red"; + const previewCanvas = document.createElement('canvas'); + previewCanvas.width = charW; + previewCanvas.height = charH; + const ctx2 = previewCanvas.getContext('2d'); + if(previewDiv)previewDiv.appendChild(previewCanvas); + + for(let y = scale/2; y < h;y+=scale) { + for(let x = scale/2; x < w;x+=scale) { + const _x = (x-(scale/2))/scale; + const _y = (y-(scale/2))/scale; + const _imageData = ctx.getImageData(x+xo,y+yo,1,1); + let specResult = _imageData.data[3] > 128; + ctx2.fillStyle = "black"; + if(specResult) { + ctx2.fillRect(_x,_y,1,1); + ret += "1"; + } else { + ret += "0"; + } + ctx.fillStyle = specResult ? "#00ff00" : "#ff0000"; + ctx.fillRect(x,y,1,1); + } + } + return ret; + }) +} +function pixelButtonMeta(x, y, img, options) { + return Object.entries(options).reduce((r,n,i)=>{ + const [k,v] = n; + + }) +} +function pixelButton(x,y,xo,yo,img,colors=["#112","#334","#556","#778","#99A","#BBC"]) { + const canvas = document.createElement('canvas'); + const w = img.width+4; + const h = img.height+5; + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + ctx.fillStyle = colors[0]; + ctx.fillRect(x+0+xo,y+yo,img.width+4,img.height+5); + ctx.fillStyle = colors[1]; + ctx.fillRect(x+0+xo,y+yo,img.width+4,img.height+4); + ctx.fillStyle = colors[3]; + ctx.fillRect(x+1+xo,y+yo,img.width+2,img.height+4); + ctx.fillStyle = colors[3]; + ctx.fillRect(x+0+xo,y+yo,img.width+4,img.height+2); + ctx.fillStyle = colors[2]; + ctx.fillRect(x+1+xo,y+yo,img.width+2,img.height+2); + ctx.drawImage(img,x+2,y+2); + return canvas; +} +function scaleImageData(imageData, scale) { + if (scale === 1) return imageData; + var scaledImageData = document.createElement("canvas").getContext("2d").createImageData(imageData.width * scale, imageData.height * scale); + for (var row = 0; row < imageData.height; row++) { + for (var col = 0; col < imageData.width; col++) { + var sourcePixel = [ + imageData.data[(row * imageData.width + col) * 4 + 0], + imageData.data[(row * imageData.width + col) * 4 + 1], + imageData.data[(row * imageData.width + col) * 4 + 2], + imageData.data[(row * imageData.width + col) * 4 + 3] + ]; + for (var y = 0; y < scale; y++) { + var destRow = row * scale + y; + for (var x = 0; x < scale; x++) { + var destCol = col * scale + x; + for (var i = 0; i < 4; i++) { + scaledImageData.data[(destRow * scaledImageData.width + destCol) * 4 + i] = + sourcePixel[i]; + } + } + } + } + } + return scaledImageData; +} + +function imageChopper(img,tileHeight,tileWidth) { + const c = document.createElement('canvas'); + const w = c.width = img.width; + const h = c.height = img.height; + const ctx = c.getContext('2d'); + ctx.drawImage(img, 0, 0); + const arr = []; + for (let y = 0; y < h; y += tileHeight) { + for (let x = 0; x < w; x += tileWidth) { + const imageData = ctx.getImageData(x, y, tileWidth, tileHeight); + const tileCanvas = document.createElement('canvas'); + tileCanvas.width = tileWidth; + tileCanvas.height = tileHeight; + const tileCtx = tileCanvas.getContext('2d'); + tileCtx.putImageData(imageData,0,0); + arr.push(tileCanvas); + } + } + return arr; +} +function imageDataToCanvas(imageData, x = 0, y = 0) { + const canvas = document.createElement('canvas'); + canvas.width = imageData.width; + canvas.height = imageData.height; + const ctx = canvas.getContext('2d'); + ctx.putImageData(imageData, x, y); + return canvas; +} + +function tilesToCanvas(arr,columns,tilesData) { + const canvas = document.createElement('canvas'); + const rows = Math.floor(arr.length / columns); + if(rows !== (arr.length / columns)){ + debugger; + //console.error("wtf this should never happen..."); + } + const w = tilesData[0].width * columns; + const h = tilesData[0].height * rows; + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + + /* first draw the tiles... */ + arr.forEach((tileIdx,i) => { + if(tileIdx >= 0) { + const c = tilesData[tileIdx]; + const x = i%columns; + const y = Math.floor(i/columns); + document.body.appendChild(c); + ctx.drawImage(c, x * c.width, y * c.width); + } + }); + + /* then draw tile fringe? */ // TODO + + return canvas; +} \ No newline at end of file diff --git a/js/color_utils.js b/js/color_utils.js new file mode 100644 index 0000000..7629115 --- /dev/null +++ b/js/color_utils.js @@ -0,0 +1,385 @@ +// UTILITY + +let firstColor = "#000000", secondColor = "#000000"; +let log = document.getElementById("log"); + +// CONSTS + +// Degrees to radiants +let degreesToRad = Math.PI / 180; +// I'm pretty sure that precision is necessary +let referenceWhite = {x: 95.05, y: 100, z: 108.89999999999999}; + +// COLOUR SIMILARITY + +// Min distance under which 2 colours are considered similar +let distanceThreshold = 10; + +// Threshold used to consider a colour "dark" +let darkColoursThreshold = 50; +// Threshold used to tell if 2 dark colours are similar +let darkColoursSimilarityThreshold = 40; + +// Threshold used to consider a colour "light" +let lightColoursThreshold = 190; +// Threshold used to tell if 2 light colours are similar +let lightColoursSimilarityThreshold = 30; + + +// document.getElementById("color1").addEventListener("change", updateColor); +// document.getElementById("color2").addEventListener("change", updateColor); + +function updateColor(e) { + ////console.log(e); + + switch (e.target.id) { + case "color1": + firstColor = e.target.value; + break; + case "color2": + secondColor = e.target.value; + break; + default: + break; + } + + updateWarnings(); +} + +function updateWarnings() { + let toSet = ""; + ////console.log("colors: " + firstColor + ", " + secondColor); + toSet += similarColours(firstColor, secondColor) ? 'Colours are similar!' + '\n' : ""; + + log.innerHTML = toSet; +} + +/**********************SECTION: COLOUR SIMILARITY*********************************/ + +function similarColours(rgb1, rgb2) { + let ret = differenceCiede2000(rgb1, rgb2); + const lightInRange = lightColoursCheck(rgb1, rgb2); + const darkInRange = darkColoursCheck(rgb1, rgb2); + + // if((ret < distanceThreshold && lightColoursCheck(rgb1, rgb2)) || darkColoursCheck(rgb1, rgb2)){ + // return ret; + // } + // return 100; + if((ret < distanceThreshold && lightInRange) || darkInRange) { + // ////console.log('GOOD ret === ',ret); + return ret; + } else { + // ////console.log('BAD ret === ',ret); + } + return ret; +} + +function lightColoursCheck(c1, c2) { + let rDelta = Math.abs(c1.r - c2.r); + let gDelta = Math.abs(c1.g - c2.g); + let bDelta = Math.abs(c1.b - c2.b); + + // Checking only if the colours are dark enough + if (c1.r > lightColoursThreshold && c1.g > lightColoursThreshold && c1.b > lightColoursThreshold && + c2.r > lightColoursThreshold && c2.g > lightColoursThreshold && c2.b > lightColoursThreshold) { + return rDelta < lightColoursSimilarityThreshold && gDelta < lightColoursSimilarityThreshold && + bDelta < lightColoursSimilarityThreshold; + } + + return true; +} + +function darkColoursCheck(c1, c2) { + let rDelta = Math.abs(c1.r - c2.r); + let gDelta = Math.abs(c1.g - c2.g); + let bDelta = Math.abs(c1.b - c2.b); + + // Checking only if the colours are dark enough + if (c1.r < darkColoursThreshold && c1.g < darkColoursThreshold && c1.b < darkColoursThreshold && + c2.r < darkColoursThreshold && c2.g < darkColoursThreshold && c2.b < darkColoursThreshold) { + return rDelta < darkColoursSimilarityThreshold && gDelta < darkColoursSimilarityThreshold && + bDelta < darkColoursSimilarityThreshold; + } + + return false; +} + +// Distance based on CIEDE2000 (https://en.wikipedia.org/wiki/Color_difference#CIEDE2000) +function differenceCiede2000(c1, c2) { + var kL = 1, kC = 1, kH = 0.9; + var LabStd = RGBtoCIELAB(c1); + var LabSmp = RGBtoCIELAB(c2); + + var lStd = LabStd.l; + var aStd = LabStd.a; + var bStd = LabStd.b; + var cStd = Math.sqrt(aStd * aStd + bStd * bStd); + + var lSmp = LabSmp.l; + var aSmp = LabSmp.a; + var bSmp = LabSmp.b; + var cSmp = Math.sqrt(aSmp * aSmp + bSmp * bSmp); + + var cAvg = (cStd + cSmp) / 2; + + var G = 0.5 * (1 - Math.sqrt(Math.pow(cAvg, 7) / (Math.pow(cAvg, 7) + Math.pow(25, 7)))); + + var apStd = aStd * (1 + G); + var apSmp = aSmp * (1 + G); + + var cpStd = Math.sqrt(apStd * apStd + bStd * bStd); + var cpSmp = Math.sqrt(apSmp * apSmp + bSmp * bSmp); + + var hpStd = Math.abs(apStd) + Math.abs(bStd) === 0 ? 0 : Math.atan2(bStd, apStd); + hpStd += (hpStd < 0) * 2 * Math.PI; + + var hpSmp = Math.abs(apSmp) + Math.abs(bSmp) === 0 ? 0 : Math.atan2(bSmp, apSmp); + hpSmp += (hpSmp < 0) * 2 * Math.PI; + + var dL = lSmp - lStd; + var dC = cpSmp - cpStd; + + var dhp = cpStd * cpSmp === 0 ? 0 : hpSmp - hpStd; + dhp -= (dhp > Math.PI) * 2 * Math.PI; + dhp += (dhp < -Math.PI) * 2 * Math.PI; + + var dH = 2 * Math.sqrt(cpStd * cpSmp) * Math.sin(dhp / 2); + + var Lp = (lStd + lSmp) / 2; + var Cp = (cpStd + cpSmp) / 2; + + var hp; + if (cpStd * cpSmp === 0) { + hp = hpStd + hpSmp; + } else { + hp = (hpStd + hpSmp) / 2; + hp -= (Math.abs(hpStd - hpSmp) > Math.PI) * Math.PI; + hp += (hp < 0) * 2 * Math.PI; + } + + var Lpm50 = Math.pow(Lp - 50, 2); + var T = 1 - + 0.17 * Math.cos(hp - Math.PI / 6) + + 0.24 * Math.cos(2 * hp) + + 0.32 * Math.cos(3 * hp + Math.PI / 30) - + 0.20 * Math.cos(4 * hp - 63 * Math.PI / 180); + + var Sl = 1 + (0.015 * Lpm50) / Math.sqrt(20 + Lpm50); + var Sc = 1 + 0.045 * Cp; + var Sh = 1 + 0.015 * Cp * T; + + var deltaTheta = 30 * Math.PI / 180 * Math.exp(-1 * Math.pow((180 / Math.PI * hp - 275)/25, 2)); + var Rc = 2 * Math.sqrt( + Math.pow(Cp, 7) / (Math.pow(Cp, 7) + Math.pow(25, 7)) + ); + + var Rt = -1 * Math.sin(2 * deltaTheta) * Rc; + + return Math.sqrt( + Math.pow(dL / (kL * Sl), 2) + + Math.pow(dC / (kC * Sc), 2) + + Math.pow(dH / (kH * Sh), 2) + + Rt * dC / (kC * Sc) * dH / (kH * Sh) + ); +} + +/**********************SECTION: COLOUR CONVERSIONS****************************** */ + +/** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {Array} The RGB representation + */ +function hslToRgb(h, s, l){ + var r, g, b; + + h /= 360; + s /= 100; + l /= 100; + + if(s == 0){ + r = g = b = l; // achromatic + }else{ + var hue2rgb = function hue2rgb(p, q, t){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; +} + +function hsvToRgb(h, s, v) { + var r, g, b; + + h /= 360; + s /= 100; + v /= 100; + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + + return [ r * 255, g * 255, b * 255 ]; +} + +function hslToHex(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const toHex = x => { + const hex = Math.round(x * 255).toString(16); + return hex.length === 1 ? '0' + hex : hex; + }; + + return `${toHex(r)}${toHex(g)}${toHex(b)}`; +} + +function rgbToHsl(col) { + let r = col.r; + let g = col.g; + let b = col.b; + + r /= 255, g /= 255, b /= 255; + + let max = Math.max(r, g, b), min = Math.min(r, g, b); + let myH, myS, myL = (max + min) / 2; + + if (max == min) { + myH = myS = 0; // achromatic + } + else { + let d = max - min; + myS = myL > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: myH = (g - b) / d + (g < b ? 6 : 0); break; + case g: myH = (b - r) / d + 2; break; + case b: myH = (r - g) / d + 4; break; + } + + myH /= 6; + } + + return {h: myH, s: myS, l: myL }; +} + + function rgbToHsv(col) { + let r = col.r; + let g = col.g; + let b = col.b; + + r /= 255, g /= 255, b /= 255; + + let max = Math.max(r, g, b), min = Math.min(r, g, b); + let myH, myS, myV = max; + + let d = max - min; + myS = max == 0 ? 0 : d / max; + + if (max == min) { + myH = 0; // achromatic + } + else { + switch (max) { + case r: myH = (g - b) / d + (g < b ? 6 : 0); break; + case g: myH = (b - r) / d + 2; break; + case b: myH = (r - g) / d + 4; break; + } + + myH /= 6; + } + + return {h: myH, s: myS, v: myV}; + } + + function RGBtoCIELAB(rgbColour) { + // Convert to XYZ first via matrix transformation + let x = 0.412453 * rgbColour.r + 0.357580 * rgbColour.g + 0.180423 * rgbColour.b; + let y = 0.212671 * rgbColour.r + 0.715160 * rgbColour.g + 0.072169 * rgbColour.b; + let z = 0.019334 * rgbColour.r + 0.119193 * rgbColour.g + 0.950227 * rgbColour.b; + + let xFunc = CIELABconvF(x / referenceWhite.x); + let yFunc = CIELABconvF(y / referenceWhite.y); + let zFunc = CIELABconvF(z / referenceWhite.z); + + let myL = 116 * yFunc - 16; + let myA = 500 * (xFunc - yFunc); + let myB = 200 * (yFunc - zFunc); + + return {l: myL, a: myA, b: myB}; + +} +function CIELABconvF(value) { + if (value > Math.pow(6/29, 3)) { + return Math.cbrt(value); + } + + return 1/3 * Math.pow(6/29, 2) * value + 4/29; +} + +function colorToRGB(color) { + if(window.colorCache && window.colorCache[color]){ + return window.colorCache[color]; + } + if (!window.cachedCtx) { + window.cachedCtx = document.createElement("canvas").getContext("2d"); + window.colorCache = {}; + } + let ctx = window.cachedCtx; + ctx.fillStyle = color; + return hexToRgb(ctx.fillStyle); + + function hexToRgb(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + }; + } +} \ No newline at end of file diff --git a/js/color_utils3.js b/js/color_utils3.js new file mode 100644 index 0000000..b9859a6 --- /dev/null +++ b/js/color_utils3.js @@ -0,0 +1,338 @@ +// Min distance under which 2 colours are considered similar +let distanceThreshold = 10; + +// Threshold used to consider a colour "dark" +let darkColoursThreshold = 50; +// Threshold used to tell if 2 dark colours are similar +let darkColoursSimilarityThreshold = 40; + +// Threshold used to consider a colour "light" +let lightColoursThreshold = 190; +// Threshold used to tell if 2 light colours are similar +let lightColoursSimilarityThreshold = 30; + +let referenceWhite = { + x: 95.05, + y: 100, + z: 108.89999999999999 +}; +const example = { + "red": [ + "#bf6f4a", + "#e07438", + "#c64524", + "#ff5000" + ], + "green": [ + "#99e65f", + "#5ac54f", + "#33984b" + ], + "blue": [ + "#0069aa", + "#0098dc", + "#00cdf9" + ], + "cyan": [ + "#0069aa", + "#0098dc", + "#00cdf9", + "#0cf1ff" + ], + "yellow": [ + "#ffa214", + "#ffc825", + "#ffeb57" + ], + "magenta": [ + "#db3ffd" + ], + "light": [ + "#ffffff", + "#f9e6cf", + "#fdd2ed" + ], + "dark": [ + "#131313", + "#1b1b1b", + "#272727", + "#3d3d3d", + "#5d5d5d" + ], + "brown": [ + "#e69c69", + "#f6ca9f", + "#f9e6cf", + "#edab50", + "#e07438", + "#ed7614", + "#ffa214", + "#ffc825", + "#ffeb57" + ], + "neon": [ + "#ff0040", + "#ff5000", + "#ed7614", + "#ffa214", + "#ffc825", + "#0098dc", + "#00cdf9", + "#0cf1ff", + "#7a09fa", + "#3003d9" + ] +}; +const COLOR_META = { + red: { color: "#ff0000", flux:{ h:25, v:40, s:40} }, + green: { color: "#00ff00", flux:{ h:35} }, + blue: { color: "#0077dd", flux:{ h:25, v:30, s:30} }, + cyan: { color: "#00ffff", flux:{ h:25, v:40, s:40} }, + yellow: { color: "#ffff00", flux:{ h:25, v:40, s:40} }, + magenta: { color: "#ff00ff", flux:{ h:15, v:40, s:40} }, + light: { color: "#ffffff", flux:{ v:10, s:30} }, + dark: { color: "#000000", flux:{ v:30, v:40, s:20} }, + brown: { color: "#ffaa00", flux:{ h:20} }, + neon: { color: "#00ffff", flux:{ s:20, v:20} }, +}; +Object.keys(COLOR_META).forEach(metaName=>{ + COLOR_META[metaName].colorMeta = colorMeta(COLOR_META[metaName].color); +}); +function paletteMeta(colorArr) { + const colorMetaArr = colorArr.map(colorMeta); + //////console.log('colorMetaArr === ',colorMetaArr); + + const ret = {}; + Object.keys(COLOR_META).forEach(metaName=>{ + const {color,colorMeta,flux} = COLOR_META[metaName]; + + const fluxKeys = Object.keys(flux); + + ret[metaName] = colorArr.filter((c,i)=>{ + const colorMeta2 = colorMetaArr[i]; + return fluxKeys.filter(k=>{ + return (colorMeta[k] + flux[k]) > colorMeta2[k] + && + (colorMeta[k] - flux[k]) < colorMeta2[k] + ; + }).length === fluxKeys.length; + }); + + }); + //////console.log(JSON.stringify(ret,null,4)); + return ret; +} +function colorMeta(colorStr) { + const rgb = colorToRGB(colorStr); + const hsv = rgb2hsv(rgb.r, rgb.g, rgb.b); + const lab = rgb2lab(rgb.r, rgb.g, rgb.b); + const cie = {c:lab.l,i:lab.a,e:lab.b}; + return { + ...rgb, + ...hsv, + ...cie + }; +} +function rgb2hex(r, g, b) { + return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); +} +function rgb2hsv(r, g, b) { + let rabs, gabs, babs, rr, gg, bb, h, s, v, diff, diffc, percentRoundFn; + rabs = r / 255; + gabs = g / 255; + babs = b / 255; + v = Math.max(rabs, gabs, babs), + diff = v - Math.min(rabs, gabs, babs); + diffc = c => (v - c) / 6 / diff + 1 / 2; + percentRoundFn = num => Math.round(num * 100) / 100; + if (diff == 0) { + h = s = 0; + } else { + s = diff / v; + rr = diffc(rabs); + gg = diffc(gabs); + bb = diffc(babs); + + if (rabs === v) { + h = bb - gg; + } else if (gabs === v) { + h = (1 / 3) + rr - bb; + } else if (babs === v) { + h = (2 / 3) + gg - rr; + } + if (h < 0) { + h += 1; + }else if (h > 1) { + h -= 1; + } + } + return { + h: Math.round(h * 360), + s: percentRoundFn(s * 100), + v: percentRoundFn(v * 100) + }; +} + +function similarColors(rgb1, rgb2) { + let ret = differenceCiede2000(rgb1, rgb2) + //////console.log(ret); + return (ret < distanceThreshold && lightColoursCheck(rgb1, rgb2)) || darkColoursCheck(rgb1, rgb2); +} +function lightColoursCheck(rgb1, rgb2) { + let rDelta = Math.abs(rgb1.r - rgb2.r); + let gDelta = Math.abs(rgb1.g - rgb2.g); + let bDelta = Math.abs(rgb1.b - rgb2.b); + + // Checking only if the colours are dark enough + if (rgb1.r > lightColoursThreshold && rgb1.g > lightColoursThreshold && rgb1.b > lightColoursThreshold && + rgb2.r > lightColoursThreshold && rgb2.g > lightColoursThreshold && rgb2.b > lightColoursThreshold) { + return rDelta < lightColoursSimilarityThreshold && gDelta < lightColoursSimilarityThreshold && + bDelta < lightColoursSimilarityThreshold; + } + + return true; +} +function darkColoursCheck(rgb1, rgb2) { + let rDelta = Math.abs(rgb1.r - rgb2.r); + let gDelta = Math.abs(rgb1.g - rgb2.g); + let bDelta = Math.abs(rgb1.b - rgb2.b); + + // Checking only if the colours are dark enough + if (rgb1.r < darkColoursThreshold && rgb1.g < darkColoursThreshold && rgb1.b < darkColoursThreshold && + rgb2.r < darkColoursThreshold && rgb2.g < darkColoursThreshold && rgb2.b < darkColoursThreshold) { + return rDelta < darkColoursSimilarityThreshold && gDelta < darkColoursSimilarityThreshold && + bDelta < darkColoursSimilarityThreshold; + } + + return false; +} +// Distance based on CIEDE2000 (https://en.wikipedia.org/wiki/Color_difference#CIEDE2000) +function differenceCiede2000(rgb1, rgb2) { + var kL = 1, + kC = 1, + kH = 0.9; + var LabStd = rgb2lab(rgb1); + var LabSmp = rgb2lab(rgb2); + + var lStd = LabStd.l; + var aStd = LabStd.a; + var bStd = LabStd.b; + var cStd = Math.sqrt(aStd * aStd + bStd * bStd); + + var lSmp = LabSmp.l; + var aSmp = LabSmp.a; + var bSmp = LabSmp.b; + var cSmp = Math.sqrt(aSmp * aSmp + bSmp * bSmp); + + var cAvg = (cStd + cSmp) / 2; + + var G = 0.5 * (1 - Math.sqrt(Math.pow(cAvg, 7) / (Math.pow(cAvg, 7) + Math.pow(25, 7)))); + + var apStd = aStd * (1 + G); + var apSmp = aSmp * (1 + G); + + var cpStd = Math.sqrt(apStd * apStd + bStd * bStd); + var cpSmp = Math.sqrt(apSmp * apSmp + bSmp * bSmp); + + var hpStd = Math.abs(apStd) + Math.abs(bStd) === 0 ? 0 : Math.atan2(bStd, apStd); + hpStd += (hpStd < 0) * 2 * Math.PI; + + var hpSmp = Math.abs(apSmp) + Math.abs(bSmp) === 0 ? 0 : Math.atan2(bSmp, apSmp); + hpSmp += (hpSmp < 0) * 2 * Math.PI; + + var dL = lSmp - lStd; + var dC = cpSmp - cpStd; + + var dhp = cpStd * cpSmp === 0 ? 0 : hpSmp - hpStd; + dhp -= (dhp > Math.PI) * 2 * Math.PI; + dhp += (dhp < -Math.PI) * 2 * Math.PI; + + var dH = 2 * Math.sqrt(cpStd * cpSmp) * Math.sin(dhp / 2); + + var Lp = (lStd + lSmp) / 2; + var Cp = (cpStd + cpSmp) / 2; + + var hp; + if (cpStd * cpSmp === 0) { + hp = hpStd + hpSmp; + } else { + hp = (hpStd + hpSmp) / 2; + hp -= (Math.abs(hpStd - hpSmp) > Math.PI) * Math.PI; + hp += (hp < 0) * 2 * Math.PI; + } + + var Lpm50 = Math.pow(Lp - 50, 2); + var T = 1 - + 0.17 * Math.cos(hp - Math.PI / 6) + + 0.24 * Math.cos(2 * hp) + + 0.32 * Math.cos(3 * hp + Math.PI / 30) - + 0.20 * Math.cos(4 * hp - 63 * Math.PI / 180); + + var Sl = 1 + (0.015 * Lpm50) / Math.sqrt(20 + Lpm50); + var Sc = 1 + 0.045 * Cp; + var Sh = 1 + 0.015 * Cp * T; + + var deltaTheta = 30 * Math.PI / 180 * Math.exp(-1 * Math.pow((180 / Math.PI * hp - 275) / 25, 2)); + var Rc = 2 * Math.sqrt( + Math.pow(Cp, 7) / (Math.pow(Cp, 7) + Math.pow(25, 7)) + ); + + var Rt = -1 * Math.sin(2 * deltaTheta) * Rc; + + return Math.sqrt( + Math.pow(dL / (kL * Sl), 2) + + Math.pow(dC / (kC * Sc), 2) + + Math.pow(dH / (kH * Sh), 2) + + Rt * dC / (kC * Sc) * dH / (kH * Sh) + ); +} +function rgb2lab(r, g, b) { + // Convert to XYZ first via matrix transformation + let x = 0.412453 * r + 0.357580 * g + 0.180423 * b; + let y = 0.212671 * r + 0.715160 * g + 0.072169 * b; + let z = 0.019334 * r + 0.119193 * g + 0.950227 * b; + + let xFunc = CIELABconvF(x / referenceWhite.x); + let yFunc = CIELABconvF(y / referenceWhite.y); + let zFunc = CIELABconvF(z / referenceWhite.z); + + let myL = 116 * yFunc - 16; + let myA = 500 * (xFunc - yFunc); + let myB = 200 * (yFunc - zFunc); + + return { + l: myL, + a: myA, + b: myB + }; + +} + +function CIELABconvF(value) { + if (value > Math.pow(6 / 29, 3)) { + return Math.cbrt(value); + } + return 1 / 3 * Math.pow(6 / 29, 2) * value + 4 / 29; +} + +function colorToRGB(color) { + if(window.colorCache && window.colorCache[color]){ + return window.colorCache[color]; + } + if (!window.cachedCtx) { + window.cachedCtx = document.createElement("canvas").getContext("2d"); + window.colorCache = {}; + } + let ctx = window.cachedCtx; + ctx.fillStyle = color; + return hexToRgb(ctx.fillStyle); + + function hexToRgb(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + }; + } +} \ No newline at end of file diff --git a/js/data/consts.js b/js/data/consts.js index f3903f8..c049d25 100644 --- a/js/data/consts.js +++ b/js/data/consts.js @@ -3,7 +3,5 @@ const MAX_Z_INDEX = 5000; // Index of the first layer the user can use in the layers array const firstUserLayerIndex = 2; -// Number of layers that are only used by the editor -const nAppLayers = 3; const MIN_ZOOM_LEVEL = 0.5; \ No newline at end of file diff --git a/js/layers/Layer.js b/js/layers/Layer.js index 501953b..43e653a 100644 --- a/js/layers/Layer.js +++ b/js/layers/Layer.js @@ -18,7 +18,7 @@ class Layer { // TODO: this is simply terrible. menuEntry can either be an HTML element, a string or undefined. // If it's an HTML element it is added, if it's a string nothing happens, if it's undefined the // first menuEntry is used (the one that appears on load) - constructor(width, height, canvas, menuEntry) { + constructor(width, height, canvas, menuEntry, id) { // REFACTOR: the canvas should actually be a Canvas instance this.canvas = Util.getElement(canvas); this.canvas.width = width; @@ -37,18 +37,24 @@ class Layer { else if (menuEntry !== undefined) this.menuEntry = menuEntry; - let id = Layer.unusedIDs.pop(); + let hadId = false; + if(typeof id !== "undefined"){ + hadId = true; + } else { + id = Layer.unusedIDs.pop(); + } if (id == null) { id = Layer.currentID; Layer.currentID++; } - this.id = "layer" + id; + this.id = hadId ? id : "layer" + id; // Binding the events if (this.menuEntry !== undefined) { this.name = this.menuEntry.getElementsByTagName("p")[0].innerHTML; + this.menuEntry.id = "layer" + id; this.menuEntry.onmouseover = () => this.hover(); @@ -69,16 +75,58 @@ class Layer { this.menuEntry.getElementsByTagName("canvas")[0].getContext('2d').imageSmoothingEnabled = false; } + if(hadId){ + this.menuEntry.classList.remove("layers-menu-entry"); + } else { + if(this.menuEntry)this.menuEntry.classList.add("layers-menu-entry"); + } + this.initialize(); + } hasCanvas() { return this.menuEntry != null; } - tryDelete() { + delete(layerIndex) { + //console.log('layerIndex === ',layerIndex); + let toDelete = currFile.layers[layerIndex]; + let previousSibling; + if(toDelete){ + //console.log('toDelete === ',toDelete); + previousSibling = toDelete.menuEntry.previousElementSibling; + //console.log('previousSibling === ',previousSibling); + // Adding the ids to the unused ones + // Deleting canvas and entry + toDelete.canvas.remove(); + toDelete.menuEntry.remove(); + } + + Layer.unusedIDs.push(this.id); + + if(this.isSelected) { + // Selecting the nearest layer + const nearestLayer = (currFile.layers[layerIndex + 1] ?? currFile.layers[layerIndex - 1]); + if(nearestLayer){ + nearestLayer.selectLayer(); + //console.log('changing to nearest layer'); + } + } + + + // Removing the layer from the list + currFile.layers.splice(layerIndex, 1); + + if(toDelete){ + new HistoryState().DeleteLayer(toDelete, previousSibling, layerIndex); + } + } + + tryDelete() { //TODO: quote yoda if (Input.getLastTarget() != this.menuEntry && Input.getLastTarget().parentElement != this.menuEntry) return; + LayerList.deleteLayer(); } @@ -121,7 +169,7 @@ class Layer { hover() { // Hides all the layers but the current one - for (let i=1; i response.json()) - .then(data => { - //palette loaded successfully - palettes[paletteSlug] = data; - palettes[paletteSlug].specified = true; + let args = window.location.pathname.split('/'); + let paletteSlug = args[2]; + let dimensions = args[3]; + // let prefillWidth = args[4] ?? 9; // TODO + // let prefill = args[5] ?? "110101111110100110111100110110101111"; + // let customColors = args[6] ?? ""; // ex: "#ffffff,#000000" + // console.log('prefill === ',prefill); + if(paletteSlug && dimensions) { - //refresh list of palettes - document.getElementById('palette-menu-splash').refresh(); - - //if the dimentions were specified - if (dimentions && dimentions.length >= 3 && dimentions.includes('x')) { - let width = dimentions.split('x')[0]; - let height = dimentions.split('x')[1]; - - //create new document - Startup.newPixel(width, height); - } - //dimentions were not specified -- show splash screen with palette preselected - else { - //show splash - Dialogue.showDialogue('new-pixel', false); - } - }) - //error fetching url (either palette doesn't exist, or lospec is down) - .catch((error) => { - console.warn('failed to load palette "'+paletteSlug+'"', error); - //proceed to splash screen - Dialogue.showDialogue('splash', false); - }); - } -}; + //fetch palette via lospec palette API + fetch('https://lospec.com/palette-list/'+paletteSlug+'.json') + .then(response => response.json()) + .then(data => { + //palette loaded successfully + palettes[paletteSlug] = data; + palettes[paletteSlug].specified = true; + //refresh list of palettes + document.getElementById('palette-menu-splash').refresh(); + + //if the dimensions were specified + if (dimensions && dimensions.length >= 3 && dimensions.includes('x')) { + let width = dimensions.split('x')[0]; + let height = dimensions.split('x')[1]; + const layers = []; + let selectedLayer; + Startup.newPixel({ + canvasWidth: width, + canvasHeight: height, + selectedLayer, + colors: data.colors.map(n=>"#"+n), + layers + }); + } + //dimensions were not specified -- show splash screen with palette preselected + else { + //show splash + Dialogue.showDialogue('new-pixel', false); + } + }) + //error fetching url (either palette doesn't exist, or lospec is down) + .catch((error) => { + //console.warn('failed to load palette "'+paletteSlug+'"', error); + //proceed to splash screen + Dialogue.showDialogue('splash', false); + }); + } else { + if(FileManager.localStorageCheck()) { + //load cached document + const lpe = FileManager.localStorageLoad(); + + Startup.newPixel(lpe); + } + //check if there are any url parameters + else if (window.location.pathname.replace('/pixel-editor/','').length <= 1) { + //show splash screen + Dialogue.showDialogue('splash', false); + } + } +} //prevent user from leaving page with unsaved data -window.onbeforeunload = function() { - if (EditorState.documentCreated) - return 'You will lose your pixel if it\'s not saved!'; +// window.onbeforeunload = function() { +// if (EditorState.documentCreated) +// return 'You will lose your pixel if it\'s not saved!'; - else return; -}; +// else return; +// }; // Compatibility functions function closeCompatibilityWarning() { diff --git a/js/tools/BrushTool.js b/js/tools/BrushTool.js index 8003484..23da542 100644 --- a/js/tools/BrushTool.js +++ b/js/tools/BrushTool.js @@ -12,7 +12,7 @@ class BrushTool extends ResizableTool { this.addTutorialKey("Left drag", " to draw a stroke"); this.addTutorialKey("Right drag", " to resize the brush"); this.addTutorialKey("+ or -", " to resize the brush"); - this.addTutorialImg("brush-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/brush-tutorial.gif"); } onStart(mousePos, cursorTarget) { diff --git a/js/tools/EllipseTool.js b/js/tools/EllipseTool.js index 68fb4c9..6d6a19d 100644 --- a/js/tools/EllipseTool.js +++ b/js/tools/EllipseTool.js @@ -25,7 +25,7 @@ class EllipseTool extends ResizableTool { this.addTutorialKey("Left drag", " to draw an ellipse"); this.addTutorialKey("Right drag", " to resize the brush"); this.addTutorialKey("+ or -", " to resize the brush"); - this.addTutorialImg("ellipse-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/ellipse-tutorial.gif"); } changeFillType() { diff --git a/js/tools/EraserTool.js b/js/tools/EraserTool.js index dfbf841..c691caa 100644 --- a/js/tools/EraserTool.js +++ b/js/tools/EraserTool.js @@ -12,7 +12,7 @@ class EraserTool extends ResizableTool { this.addTutorialKey("Left drag", " to erase an area"); this.addTutorialKey("Right drag", " to resize the eraser"); this.addTutorialKey("+ or -", " to resize the eraser"); - this.addTutorialImg("eraser-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/eraser-tutorial.gif"); } onStart(mousePos) { diff --git a/js/tools/EyeDropperTool.js b/js/tools/EyeDropperTool.js index ef9ceef..76c9b6d 100644 --- a/js/tools/EyeDropperTool.js +++ b/js/tools/EyeDropperTool.js @@ -14,7 +14,7 @@ class EyeDropperTool extends Tool { this.addTutorialKey("Aòt + left drag", " to preview the picked colour"); this.addTutorialKey("Left click", " to select a colour"); this.addTutorialKey("Alt + click", " to select a colour"); - this.addTutorialImg("eyedropper-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/eyedropper-tutorial.gif"); } onStart(mousePos, target) { diff --git a/js/tools/FillTool.js b/js/tools/FillTool.js index 0043f66..6738e63 100644 --- a/js/tools/FillTool.js +++ b/js/tools/FillTool.js @@ -8,7 +8,7 @@ class FillTool extends DrawingTool { this.addTutorialTitle("Fill tool"); this.addTutorialKey("F", " to select the fill tool"); this.addTutorialKey("Left click", " to fill a contiguous area"); - this.addTutorialImg("fill-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/fill-tutorial.gif"); } onStart(mousePos, target) { @@ -25,7 +25,7 @@ class FillTool extends DrawingTool { static fill(cursorLocation, context) { //changes a pixels color function colorPixel(tempImage, pixelPos, fillColor) { - //console.log('colorPixel:',pixelPos); + //////console.log('colorPixel:',pixelPos); tempImage.data[pixelPos] = fillColor.r; tempImage.data[pixelPos + 1] = fillColor.g; tempImage.data[pixelPos + 2] = fillColor.b; @@ -34,13 +34,13 @@ class FillTool extends DrawingTool { //change x y to color value passed from the function and use that as the original color function matchStartColor(tempImage, pixelPos, color) { - //console.log('matchPixel:',x,y) + //////console.log('matchPixel:',x,y) let r = tempImage.data[pixelPos]; let g = tempImage.data[pixelPos + 1]; let b = tempImage.data[pixelPos + 2]; let a = tempImage.data[pixelPos + 3]; - //console.log(r == color[0] && g == color[1] && b == color[2]); + //////console.log(r == color[0] && g == color[1] && b == color[2]); return (r == color[0] && g == color[1] && b == color[2] && a == color[3]); } @@ -52,7 +52,7 @@ class FillTool extends DrawingTool { //this is an array that holds all of the pixels at the top of the cluster let topmostPixelsArray = [[Math.floor(cursorLocation[0]/currFile.zoom), Math.floor(cursorLocation[1]/currFile.zoom)]]; - //console.log('topmostPixelsArray:',topmostPixelsArray) + //////console.log('topmostPixelsArray:',topmostPixelsArray) //the offset of the pixel in the temp image data to start with let startingPosition = (topmostPixelsArray[0][1] * currFile.canvasSize[0] + topmostPixelsArray[0][0]) * 4; diff --git a/js/tools/LassoSelectionTool.js b/js/tools/LassoSelectionTool.js index f767e12..e60b125 100644 --- a/js/tools/LassoSelectionTool.js +++ b/js/tools/LassoSelectionTool.js @@ -15,7 +15,7 @@ class LassoSelectionTool extends SelectionTool { this.addTutorialKey("CTRL+C", " to copy a selection") this.addTutorialKey("CTRL+V", " to paste a selection") this.addTutorialKey("CTRL+X", " to cut a selection") - this.addTutorialImg("lassoselect-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/lassoselect-tutorial.gif"); } onStart(mousePos, mouseTarget) { diff --git a/js/tools/LineTool.js b/js/tools/LineTool.js index 67662b5..d596cd6 100644 --- a/js/tools/LineTool.js +++ b/js/tools/LineTool.js @@ -12,7 +12,7 @@ class LineTool extends ResizableTool { this.addTutorialKey("Left drag", " to draw a line"); this.addTutorialKey("Right drag", " to resize the brush"); this.addTutorialKey("+ or -", " to resize the brush"); - this.addTutorialImg("line-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/line-tutorial.gif"); } onStart(mousePos) { diff --git a/js/tools/MagicWandTool.js b/js/tools/MagicWandTool.js index d695ada..d07c20e 100644 --- a/js/tools/MagicWandTool.js +++ b/js/tools/MagicWandTool.js @@ -13,7 +13,7 @@ class MagicWandTool extends SelectionTool { this.addTutorialKey("CTRL+C", " to copy a selection"); this.addTutorialKey("CTRL+V", " to paste a selection"); this.addTutorialKey("CTRL+X", " to cut a selection"); - this.addTutorialImg("magicwand-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/magicwand-tutorial.gif"); } onEnd(mousePos, mouseTarget) { @@ -22,7 +22,7 @@ class MagicWandTool extends SelectionTool { !Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) return; - + ////console.log('this.moveTool === ',this.moveTool); this.switchFunc(this.moveTool); this.moveTool.setSelectionData(this.getSelection(), this); } @@ -52,8 +52,10 @@ class MagicWandTool extends SelectionTool { this.outlineData = new ImageData(currFile.canvasSize[0], currFile.canvasSize[1]); this.previewData = selectedData; this.drawSelectedArea(); - this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2, - this.boundingBox.minY + (this.boundingBox.maxY - this.boundingBox.minY) / 2]; + this.boundingBoxCenter = [ + this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2, + this.boundingBox.minY + (this.boundingBox.maxY - this.boundingBox.minY) / 2 + ]; // Cut the selection this.cutSelection(); @@ -61,7 +63,7 @@ class MagicWandTool extends SelectionTool { currFile.TMPLayer.context.putImageData(this.previewData, 0, 0); // Draw the bounding box - this.drawBoundingBox(); + this.drawBoundingBox(1, 1); return selectedData; } diff --git a/js/tools/PanTool.js b/js/tools/PanTool.js index 1a952a0..8bd930e 100644 --- a/js/tools/PanTool.js +++ b/js/tools/PanTool.js @@ -10,7 +10,7 @@ class PanTool extends Tool { this.addTutorialKey("P", " to select the lasso selection tool"); this.addTutorialKey("Left drag", " to move the viewport"); this.addTutorialKey("Space + drag", " to move the viewport"); - this.addTutorialImg("pan-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/pan-tutorial.gif"); } onStart(mousePos, target) { diff --git a/js/tools/RectangleTool.js b/js/tools/RectangleTool.js index 7ecd78a..2393ea8 100644 --- a/js/tools/RectangleTool.js +++ b/js/tools/RectangleTool.js @@ -23,7 +23,7 @@ class RectangleTool extends ResizableTool { this.addTutorialKey("Left drag", " to draw a rectangle"); this.addTutorialKey("Right drag", " to resize the brush"); this.addTutorialKey("+ or -", " to resize the brush"); - this.addTutorialImg("rectangle-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/rectangle-tutorial.gif"); } changeFillType() { diff --git a/js/tools/RectangularSelectionTool.js b/js/tools/RectangularSelectionTool.js index f7bee0b..de5ff53 100644 --- a/js/tools/RectangularSelectionTool.js +++ b/js/tools/RectangularSelectionTool.js @@ -14,7 +14,7 @@ class RectangularSelectionTool extends SelectionTool { this.addTutorialKey("CTRL+C", " to copy a selection"); this.addTutorialKey("CTRL+V", " to paste a selection"); this.addTutorialKey("CTRL+X", " to cut a selection"); - this.addTutorialImg("rectselect-tutorial.gif"); + this.addTutorialImg("/images/ToolTutorials/rectselect-tutorial.gif"); } onStart(mousePos, mouseTarget) { diff --git a/js/tools/SelectionTool.js b/js/tools/SelectionTool.js index 35a7799..2f31a61 100644 --- a/js/tools/SelectionTool.js +++ b/js/tools/SelectionTool.js @@ -44,8 +44,10 @@ class SelectionTool extends Tool { this.currSelection = {}; this.moveOffset = [0, 0]; - this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1), - Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)); + this.updateBoundingBox( + Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1), + Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1) + ); } onDrag(mousePos) { @@ -66,8 +68,10 @@ class SelectionTool extends Tool { if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) { - this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1), - Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)); + this.updateBoundingBox( + Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1), + Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1) + ); } } @@ -81,8 +85,10 @@ class SelectionTool extends Tool { let mouseY = mousePos[1] / currFile.zoom; if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) { - this.updateBoundingBox(Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1), - Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1)); + this.updateBoundingBox( + Math.min(Math.max(mouseX, 0), currFile.canvasSize[0]-1), + Math.min(Math.max(mouseY, 0), currFile.canvasSize[1]-1) + ); } this.boundingBoxCenter = [this.boundingBox.minX + (this.boundingBox.maxX - this.boundingBox.minX) / 2, @@ -211,6 +217,7 @@ class SelectionTool extends Tool { } } } + ////console.log('this.currSelection === ',this.currSelection); // Save the selection outline this.outlineData = currFile.VFXLayer.context.getImageData(this.boundingBox.minX, @@ -246,16 +253,25 @@ class SelectionTool extends Tool { this.boundingBox.minY + this.moveOffset[1]); } - drawBoundingBox() { + drawBoundingBox(xo = 0, yo = 0) { currFile.VFXLayer.context.fillStyle = "red"; - currFile.VFXLayer.context.fillRect(this.boundingBox.minX + this.moveOffset[0], - this.boundingBox.minY + this.moveOffset[1], 1, 1); - currFile.VFXLayer.context.fillRect(this.boundingBox.minX+ this.moveOffset[0], - this.boundingBox.maxY + this.moveOffset[1], 1, 1); - currFile.VFXLayer.context.fillRect(this.boundingBox.maxX+ this.moveOffset[0], - this.boundingBox.minY + this.moveOffset[1], 1, 1); - currFile.VFXLayer.context.fillRect(this.boundingBox.maxX+ this.moveOffset[0], - this.boundingBox.maxY + this.moveOffset[1], 1, 1); + + currFile.VFXLayer.context.fillRect( + this.boundingBox.minX + this.moveOffset[0] - xo, + this.boundingBox.minY + this.moveOffset[1] - yo, + 1, 1); + currFile.VFXLayer.context.fillRect( + this.boundingBox.minX + this.moveOffset[0] - xo, + this.boundingBox.maxY + this.moveOffset[1] + yo, + 1, 1); + currFile.VFXLayer.context.fillRect( + this.boundingBox.maxX + this.moveOffset[0] + xo, + this.boundingBox.minY + this.moveOffset[1] - yo, + 1, 1); + currFile.VFXLayer.context.fillRect( + this.boundingBox.maxX + this.moveOffset[0] + xo, + this.boundingBox.maxY + this.moveOffset[1] + yo, + 1, 1); } isBorderOfBox(pixel) { diff --git a/js/tools/ZoomTool.js b/js/tools/ZoomTool.js index a945bb6..7da5acc 100644 --- a/js/tools/ZoomTool.js +++ b/js/tools/ZoomTool.js @@ -75,8 +75,11 @@ class ZoomTool extends Tool { } } - for (let i=1; i + + + + + + Document + + + + + +
+ +
+
+ +
+
+ +
+ + + + + + diff --git a/poc_pages/rotation_POC_latest.html b/poc_pages/rotation_POC_latest.html new file mode 100644 index 0000000..628e5d8 --- /dev/null +++ b/poc_pages/rotation_POC_latest.html @@ -0,0 +1,269 @@ + + + + + + + Document + + + + + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/poc_pages/rotation_POC_old.html b/poc_pages/rotation_POC_old.html new file mode 100644 index 0000000..8a59661 --- /dev/null +++ b/poc_pages/rotation_POC_old.html @@ -0,0 +1,268 @@ + + + + + + + Document + + + + + + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/poc_pages/tiny_text.html b/poc_pages/tiny_text.html new file mode 100644 index 0000000..edee703 --- /dev/null +++ b/poc_pages/tiny_text.html @@ -0,0 +1,45 @@ + + + + + + + + Document + + + + + + + + + + + + + + + + diff --git a/poc_pages/wang_tiles_16.html b/poc_pages/wang_tiles_16.html new file mode 100644 index 0000000..138f095 --- /dev/null +++ b/poc_pages/wang_tiles_16.html @@ -0,0 +1,130 @@ + + + Basic three.js template + + + + + + + + + + + \ No newline at end of file diff --git a/server.js b/server.js index 3fcb6b0..30534a5 100644 --- a/server.js +++ b/server.js @@ -33,13 +33,25 @@ app.use('/', express.static(FULLBUILDPATH, { } })); - - //ROUTE - match / or any route with just numbers letters and dashes, and return index.htm (all other routes should have been handled already) app.get('/', (req, res, next) => { - console.log('root') + //console.log('root') res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) { - console.log('sent file'); + //console.log('sent file'); + return next(); + }); +}); +app.get('/pixel-editor', (req, res, next) => { + //console.log('root') + res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) { + //console.log('sent file'); + return next(); + }); +}); +app.get('/pixel-editor/?:palette/?:resolution/?:patternWidth/?:patternBinStr', (req, res, next) => { + //console.log('root') + res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) { + //console.log('sent file'); return next(); }); });