diff --git a/app/asset/css/tabs.css b/app/asset/css/tabs.css deleted file mode 100644 index 0b756c1d..00000000 --- a/app/asset/css/tabs.css +++ /dev/null @@ -1,216 +0,0 @@ -/* code tabs*/ -[x-cloak], [hidden] { display: none !important; } -[data-btn] { - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - inline-size: var(--btn-w, min-content); - max-block-size: var(--btn-h, var(--ui-size, 2.75rem)); - min-inline-size: var(--btn-min-w, var(--ui-size, 2.75rem)); - padding-block: var(--btn-py, 0.875rem); - padding-inline-start: var(--btn-pl, var(--btn-px, 1rem)); - padding-inline-end: var(--btn-pr, var(--btn-px, 1rem)); - font-size: var(--btn-fs, 0.875rem); - font-weight: var(--btn-fw, 600); - line-height: var(--btn-lh, 1); - text-align: center; - text-transform: var(--btn-tt, none); - writing-mode: horizontal-tb; - white-space: nowrap; - border-radius: var(--btn-br, var(--ui-br, 0.25rem)); - color: var(--btn-txt, #475569); - background-color: var(--btn-bkg, #fff); - transition-property: color, background-color, border-color, - text-decoration-color, fill, stroke; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 100ms; - z-index: var(--btn-z, 0); - appearance: none; - outline: 2px solid rgba(255, 255, 255, 0); - outline-offset: 2px; - user-select: none; - touch-action: manipulation; -} - -[data-btn]:focus-visible { - --btn-z: 10; - --btn-txt: var(--btn-txt-f, #0f172a); - --btn-bkg: var(--btn-bkg-f, #f8fafc); - box-shadow: var(--btn-ring-i, inset) 0 0 0 - var(--btn-ring-w, var(--ui-ring-w, 2px)) - var(--btn-ring-c, var(--ui-ring-c, dodgerblue)); -} - -[data-btn]:hover { - --btn-txt: var(--btn-txt-h, #0f172a); - --btn-bkg: var(--btn-bkg-h, #f1f5f9); -} - -[data-btn]:active { - --btn-txt: var(--btn-txt-a, #0f172a); - --btn-bkg: var(--btn-bkg-a, #e2e8f0); - box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); - transform: translateY(1px); -} - -[data-btn]:not(:has(svg, span)) { - --btn-py: 0.875rem; - --btn-px: 1rem; - --btn-pl: var(--btn-px); - --btn-pr: var(--btn-px); -} - -[data-btn]:has(svg, span) { - --btn-py: 0; - --btn-px: 0; -} - -[data-btn] svg { - --is: calc( - var(--btn-min-w, var(--ui-size)) - (var(--btn-bw, var(--ui-bw)) * 2) - ); - flex-shrink: 0; - inline-size: var(--is); - block-size: var(--is); - padding: var(--btn-p); - stroke: var(--ic, currentColor); -} - -:where([data-btn]) [data-label]:not([hidden], .hidden, .sr-only), -:where([data-btn]) svg { - --btn-p: 0.75rem; -} - -[data-label]:is(:last-child):not([hidden], .hidden, .sr-only) { - padding-inline-end: calc(var(--btn-p) * 1.25); -} - -[data-label]:is(:first-child):not([hidden], .hidden, .sr-only) { - padding-inline-start: calc(var(--btn-p) * 1.25); -} - -[data-btn*="border"] { - border-style: solid; - border-width: var(--btn-bw, var(--ui-bw, 1px)); - border-color: var(--btn-bc, var(--ui-bc, #cbd5e1)); -} - -[role="group"] { - --r: var(--btn-br, var(--ui-br, 0.25rem)); -} - -[role="group"] [data-btn]:not(:first-child, :last-child) { - --btn-br: 0; -} - -[role="group"] [data-btn]:first-child { - border-start-start-radius: var(--r); - border-end-start-radius: var(--r); - border-start-end-radius: 0; - border-end-end-radius: 0; -} - -[role="group"] [data-btn]:last-child { - border-start-start-radius: 0; - border-end-start-radius: 0; - border-start-end-radius: var(--r); - border-end-end-radius: var(--r); -} - -[role="group"] > [data-btn*="border"] + [data-btn*="border"] { - margin-left: calc(var(--btn-bw, var(--ui-bw, 1px)) * -1); -} - -@media (prefers-color-scheme: dark) { - [data-btn] { - --btn-bc: #475569; - --btn-txt: #e2e8f0; - --btn-bkg: #334155; - --btn-txt-f: #fff; - --btn-bkg-f: #475569; - --btn-txt-h: #fff; - --btn-bkg-h: #475569; - --btn-txt-a: #f1f5f9; - --btn-bkg-a: rgb(15 23 42 / 10%); - } -} - -[data-btn*="ghost"] { - --btn-txt: #94a3b8; - --btn-bkg: transparent; - font-weight: 400; -} - -[data-btn*="ghost"]:hover { - --btn-txt-h: #334155; - --btn-bkg-h: #e2e8f0; -} - -@media (prefers-color-scheme: dark) { - [data-btn*="ghost"]:hover { - --btn-txt-h: #cbd5e1; - --btn-bkg-h: rgb(15 23 42 / 40%); - } -} - -/* scrollbar-width: auto | thin | none */ -.scrollbar { - --scrollbar-color-thumb: #0ea5e9; - --scrollbar-color-thumb-h: #38bdf8; - --scrollbar-color-track: #e2e8f0; - --scrollbar-width: thin; - --scrollbar-width-legacy: 17px; -} - -@media (prefers-color-scheme: dark) { - .scrollbar { - --scrollbar-color-track: rgb(0 0 0 / 0.2); - } -} - -@supports (scrollbar-width: auto) { - .scrollbar { - scrollbar-width: var(--scrollbar-width); - scrollbar-color: var(--scrollbar-color-thumb) var(--scrollbar-color-track); - scrollbar-gutter: stable; - } -} - -@supports selector(::-webkit-scrollbar) { - .scrollbar::-webkit-scrollbar { - width: var(--scrollbar-width-legacy); - height: var(--scrollbar-width-legacy); - } -} - -.scrollbar::-webkit-scrollbar-corner { - background-color: var(--scrollbar-color-track); -} - -.scrollbar::-webkit-scrollbar-track { - border-bottom-right-radius: 0.25rem; - background-color: var(--scrollbar-color-track); -} - -.scrollbar::-webkit-scrollbar-thumb { - border: 4px solid transparent; - border-radius: 100vh; - background-color: var(--scrollbar-color-thumb); - background-clip: content-box; -} - -.scrollbar::-webkit-scrollbar-thumb:hover, -.scroller:hover::-webkit-scrollbar-thumb { - border: 4px solid transparent; - background-color: var(--scrollbar-color-thumb-h); -} - - .scrollbar:hover { - /* - ** Invalidates styles on hover, thereby - ** enforcing a style recomputation. - ** This is needed to work around a bug in Safari. - */ - --fix: ; -} \ No newline at end of file diff --git a/app/asset/img/codemo-logo-128.png b/app/asset/img/codemo-logo-128.png deleted file mode 100644 index 48598007..00000000 Binary files a/app/asset/img/codemo-logo-128.png and /dev/null differ diff --git a/app/asset/img/codemo-logo-256.png b/app/asset/img/codemo-logo-256.png deleted file mode 100644 index fee5e5b0..00000000 Binary files a/app/asset/img/codemo-logo-256.png and /dev/null differ diff --git a/app/asset/img/codemo-logo.png b/app/asset/img/codemo-logo.png deleted file mode 100644 index 607bcb52..00000000 Binary files a/app/asset/img/codemo-logo.png and /dev/null differ diff --git a/app/asset/lib/gitoken.min.js b/app/asset/lib/gitoken.min.js new file mode 100644 index 00000000..cfbb59e8 --- /dev/null +++ b/app/asset/lib/gitoken.min.js @@ -0,0 +1,3 @@ +!function(t,r){"object"==typeof exports?module.exports=exports=r():"function"==typeof define&&define.amd?define([],r):t.CryptoJS=r()}(this,function(){var t=t||function(t,r){var e=Object.create||function(){function t(){}return function(r){var e;return t.prototype=r,e=new t,t.prototype=null,e}}(),i={},n=i.lib={},o=n.Base=function(){return{extend:function(t){var r=e(this);return t&&r.mixIn(t),r.hasOwnProperty("init")&&this.init!==r.init||(r.init=function(){r.$super.init.apply(this,arguments)}),r.init.prototype=r,r.$super=this,r},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var r in t)t.hasOwnProperty(r)&&(this[r]=t[r]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}}}(),s=n.WordArray=o.extend({init:function(t,e){t=this.words=t||[],e!=r?this.sigBytes=e:this.sigBytes=4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var r=this.words,e=t.words,i=this.sigBytes,n=t.sigBytes;if(this.clamp(),i%4)for(var o=0;o>>2]>>>24-o%4*8&255;r[i+o>>>2]|=s<<24-(i+o)%4*8}else for(var o=0;o>>2]=e[o>>>2];return this.sigBytes+=n,this},clamp:function(){var r=this.words,e=this.sigBytes;r[e>>>2]&=4294967295<<32-e%4*8,r.length=t.ceil(e/4)},clone:function(){var t=o.clone.call(this);return t.words=this.words.slice(0),t},random:function(r){for(var e,i=[],n=function(r){var r=r,e=987654321,i=4294967295;return function(){e=36969*(65535&e)+(e>>16)&i,r=18e3*(65535&r)+(r>>16)&i;var n=(e<<16)+r&i;return n/=4294967296,n+=.5,n*(t.random()>.5?1:-1)}},o=0;o>>2]>>>24-n%4*8&255;i.push((o>>>4).toString(16)),i.push((15&o).toString(16))}return i.join("")},parse:function(t){for(var r=t.length,e=[],i=0;i>>3]|=parseInt(t.substr(i,2),16)<<24-i%8*4;return new s.init(e,r/2)}},h=a.Latin1={stringify:function(t){for(var r=t.words,e=t.sigBytes,i=[],n=0;n>>2]>>>24-n%4*8&255;i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var r=t.length,e=[],i=0;i>>2]|=(255&t.charCodeAt(i))<<24-i%4*8;return new s.init(e,r)}},l=a.Utf8={stringify:function(t){try{return decodeURIComponent(escape(h.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return h.parse(unescape(encodeURIComponent(t)))}},f=n.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new s.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=l.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(r){var e=this._data,i=e.words,n=e.sigBytes,o=this.blockSize,a=4*o,c=n/a;c=r?t.ceil(c):t.max((0|c)-this._minBufferSize,0);var h=c*o,l=t.min(4*h,n);if(h){for(var f=0;f>>6-s%4*2;i[o>>>2]|=(a|c)<<24-o%4*8,o++}return n.create(i,o)}var e=t,i=e.lib,n=i.WordArray,o=e.enc;o.Base64={stringify:function(t){var r=t.words,e=t.sigBytes,i=this._map;t.clamp();for(var n=[],o=0;o>>2]>>>24-o%4*8&255,a=r[o+1>>>2]>>>24-(o+1)%4*8&255,c=r[o+2>>>2]>>>24-(o+2)%4*8&255,h=s<<16|a<<8|c,l=0;l<4&&o+.75*l>>6*(3-l)&63));var f=i.charAt(64);if(f)for(;n.length%4;)n.push(f);return n.join("")},parse:function(t){var e=t.length,i=this._map,n=this._reverseMap;if(!n){n=this._reverseMap=[];for(var o=0;o>>32-o)+r}function i(t,r,e,i,n,o,s){var a=t+(r&i|e&~i)+n+s;return(a<>>32-o)+r}function n(t,r,e,i,n,o,s){var a=t+(r^e^i)+n+s;return(a<>>32-o)+r}function o(t,r,e,i,n,o,s){var a=t+(e^(r|~i))+n+s;return(a<>>32-o)+r}var s=t,a=s.lib,c=a.WordArray,h=a.Hasher,l=s.algo,f=[];!function(){for(var t=0;t<64;t++)f[t]=4294967296*r.abs(r.sin(t+1))|0}();var u=l.MD5=h.extend({_doReset:function(){this._hash=new c.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(t,r){for(var s=0;s<16;s++){var a=r+s,c=t[a];t[a]=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8)}var h=this._hash.words,l=t[r+0],u=t[r+1],d=t[r+2],v=t[r+3],p=t[r+4],_=t[r+5],y=t[r+6],g=t[r+7],B=t[r+8],w=t[r+9],k=t[r+10],S=t[r+11],m=t[r+12],x=t[r+13],b=t[r+14],H=t[r+15],z=h[0],A=h[1],C=h[2],D=h[3];z=e(z,A,C,D,l,7,f[0]),D=e(D,z,A,C,u,12,f[1]),C=e(C,D,z,A,d,17,f[2]),A=e(A,C,D,z,v,22,f[3]),z=e(z,A,C,D,p,7,f[4]),D=e(D,z,A,C,_,12,f[5]),C=e(C,D,z,A,y,17,f[6]),A=e(A,C,D,z,g,22,f[7]),z=e(z,A,C,D,B,7,f[8]),D=e(D,z,A,C,w,12,f[9]),C=e(C,D,z,A,k,17,f[10]),A=e(A,C,D,z,S,22,f[11]),z=e(z,A,C,D,m,7,f[12]),D=e(D,z,A,C,x,12,f[13]),C=e(C,D,z,A,b,17,f[14]),A=e(A,C,D,z,H,22,f[15]),z=i(z,A,C,D,u,5,f[16]),D=i(D,z,A,C,y,9,f[17]),C=i(C,D,z,A,S,14,f[18]),A=i(A,C,D,z,l,20,f[19]),z=i(z,A,C,D,_,5,f[20]),D=i(D,z,A,C,k,9,f[21]),C=i(C,D,z,A,H,14,f[22]),A=i(A,C,D,z,p,20,f[23]),z=i(z,A,C,D,w,5,f[24]),D=i(D,z,A,C,b,9,f[25]),C=i(C,D,z,A,v,14,f[26]),A=i(A,C,D,z,B,20,f[27]),z=i(z,A,C,D,x,5,f[28]),D=i(D,z,A,C,d,9,f[29]),C=i(C,D,z,A,g,14,f[30]),A=i(A,C,D,z,m,20,f[31]),z=n(z,A,C,D,_,4,f[32]),D=n(D,z,A,C,B,11,f[33]),C=n(C,D,z,A,S,16,f[34]),A=n(A,C,D,z,b,23,f[35]),z=n(z,A,C,D,u,4,f[36]),D=n(D,z,A,C,p,11,f[37]),C=n(C,D,z,A,g,16,f[38]),A=n(A,C,D,z,k,23,f[39]),z=n(z,A,C,D,x,4,f[40]),D=n(D,z,A,C,l,11,f[41]),C=n(C,D,z,A,v,16,f[42]),A=n(A,C,D,z,y,23,f[43]),z=n(z,A,C,D,w,4,f[44]),D=n(D,z,A,C,m,11,f[45]),C=n(C,D,z,A,H,16,f[46]),A=n(A,C,D,z,d,23,f[47]),z=o(z,A,C,D,l,6,f[48]),D=o(D,z,A,C,g,10,f[49]),C=o(C,D,z,A,b,15,f[50]),A=o(A,C,D,z,_,21,f[51]),z=o(z,A,C,D,m,6,f[52]),D=o(D,z,A,C,v,10,f[53]),C=o(C,D,z,A,k,15,f[54]),A=o(A,C,D,z,u,21,f[55]),z=o(z,A,C,D,B,6,f[56]),D=o(D,z,A,C,H,10,f[57]),C=o(C,D,z,A,y,15,f[58]),A=o(A,C,D,z,x,21,f[59]),z=o(z,A,C,D,p,6,f[60]),D=o(D,z,A,C,S,10,f[61]),C=o(C,D,z,A,d,15,f[62]),A=o(A,C,D,z,w,21,f[63]),h[0]=h[0]+z|0,h[1]=h[1]+A|0,h[2]=h[2]+C|0,h[3]=h[3]+D|0},_doFinalize:function(){var t=this._data,e=t.words,i=8*this._nDataBytes,n=8*t.sigBytes;e[n>>>5]|=128<<24-n%32;var o=r.floor(i/4294967296),s=i;e[(n+64>>>9<<4)+15]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8),e[(n+64>>>9<<4)+14]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),t.sigBytes=4*(e.length+1),this._process();for(var a=this._hash,c=a.words,h=0;h<4;h++){var l=c[h];c[h]=16711935&(l<<8|l>>>24)|4278255360&(l<<24|l>>>8)}return a},clone:function(){var t=h.clone.call(this);return t._hash=this._hash.clone(),t}});s.MD5=h._createHelper(u),s.HmacMD5=h._createHmacHelper(u)}(Math),function(){var r=t,e=r.lib,i=e.WordArray,n=e.Hasher,o=r.algo,s=[],a=o.SHA1=n.extend({_doReset:function(){this._hash=new i.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,r){for(var e=this._hash.words,i=e[0],n=e[1],o=e[2],a=e[3],c=e[4],h=0;h<80;h++){if(h<16)s[h]=0|t[r+h];else{var l=s[h-3]^s[h-8]^s[h-14]^s[h-16];s[h]=l<<1|l>>>31}var f=(i<<5|i>>>27)+c+s[h];f+=h<20?(n&o|~n&a)+1518500249:h<40?(n^o^a)+1859775393:h<60?(n&o|n&a|o&a)-1894007588:(n^o^a)-899497514,c=a,a=o,o=n<<30|n>>>2,n=i,i=f}e[0]=e[0]+i|0,e[1]=e[1]+n|0,e[2]=e[2]+o|0,e[3]=e[3]+a|0,e[4]=e[4]+c|0},_doFinalize:function(){var t=this._data,r=t.words,e=8*this._nDataBytes,i=8*t.sigBytes;return r[i>>>5]|=128<<24-i%32,r[(i+64>>>9<<4)+14]=Math.floor(e/4294967296),r[(i+64>>>9<<4)+15]=e,t.sigBytes=4*r.length,this._process(),this._hash},clone:function(){var t=n.clone.call(this);return t._hash=this._hash.clone(),t}});r.SHA1=n._createHelper(a),r.HmacSHA1=n._createHmacHelper(a)}(),function(r){var e=t,i=e.lib,n=i.WordArray,o=i.Hasher,s=e.algo,a=[],c=[];!function(){function t(t){for(var e=r.sqrt(t),i=2;i<=e;i++)if(!(t%i))return!1;return!0}function e(t){return 4294967296*(t-(0|t))|0}for(var i=2,n=0;n<64;)t(i)&&(n<8&&(a[n]=e(r.pow(i,.5))),c[n]=e(r.pow(i,1/3)),n++),i++}();var h=[],l=s.SHA256=o.extend({_doReset:function(){this._hash=new n.init(a.slice(0))},_doProcessBlock:function(t,r){for(var e=this._hash.words,i=e[0],n=e[1],o=e[2],s=e[3],a=e[4],l=e[5],f=e[6],u=e[7],d=0;d<64;d++){if(d<16)h[d]=0|t[r+d];else{var v=h[d-15],p=(v<<25|v>>>7)^(v<<14|v>>>18)^v>>>3,_=h[d-2],y=(_<<15|_>>>17)^(_<<13|_>>>19)^_>>>10;h[d]=p+h[d-7]+y+h[d-16]}var g=a&l^~a&f,B=i&n^i&o^n&o,w=(i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22),k=(a<<26|a>>>6)^(a<<21|a>>>11)^(a<<7|a>>>25),S=u+k+g+c[d]+h[d],m=w+B;u=f,f=l,l=a,a=s+S|0,s=o,o=n,n=i,i=S+m|0}e[0]=e[0]+i|0,e[1]=e[1]+n|0,e[2]=e[2]+o|0,e[3]=e[3]+s|0,e[4]=e[4]+a|0,e[5]=e[5]+l|0,e[6]=e[6]+f|0,e[7]=e[7]+u|0},_doFinalize:function(){var t=this._data,e=t.words,i=8*this._nDataBytes,n=8*t.sigBytes;return e[n>>>5]|=128<<24-n%32,e[(n+64>>>9<<4)+14]=r.floor(i/4294967296),e[(n+64>>>9<<4)+15]=i,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=o.clone.call(this);return t._hash=this._hash.clone(),t}});e.SHA256=o._createHelper(l),e.HmacSHA256=o._createHmacHelper(l)}(Math),function(){function r(t){return t<<8&4278255360|t>>>8&16711935}var e=t,i=e.lib,n=i.WordArray,o=e.enc;o.Utf16=o.Utf16BE={stringify:function(t){for(var r=t.words,e=t.sigBytes,i=[],n=0;n>>2]>>>16-n%4*8&65535;i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var r=t.length,e=[],i=0;i>>1]|=t.charCodeAt(i)<<16-i%2*16;return n.create(e,2*r)}};o.Utf16LE={stringify:function(t){for(var e=t.words,i=t.sigBytes,n=[],o=0;o>>2]>>>16-o%4*8&65535);n.push(String.fromCharCode(s))}return n.join("")},parse:function(t){for(var e=t.length,i=[],o=0;o>>1]|=r(t.charCodeAt(o)<<16-o%2*16);return n.create(i,2*e)}}}(),function(){if("function"==typeof ArrayBuffer){var r=t,e=r.lib,i=e.WordArray,n=i.init,o=i.init=function(t){if(t instanceof ArrayBuffer&&(t=new Uint8Array(t)),(t instanceof Int8Array||"undefined"!=typeof Uint8ClampedArray&&t instanceof Uint8ClampedArray||t instanceof Int16Array||t instanceof Uint16Array||t instanceof Int32Array||t instanceof Uint32Array||t instanceof Float32Array||t instanceof Float64Array)&&(t=new Uint8Array(t.buffer,t.byteOffset,t.byteLength)),t instanceof Uint8Array){for(var r=t.byteLength,e=[],i=0;i>>2]|=t[i]<<24-i%4*8;n.call(this,e,r)}else n.apply(this,arguments)};o.prototype=i}}(),function(r){function e(t,r,e){return t^r^e}function i(t,r,e){return t&r|~t&e}function n(t,r,e){return(t|~r)^e}function o(t,r,e){return t&e|r&~e}function s(t,r,e){return t^(r|~e)}function a(t,r){return t<>>32-r}var c=t,h=c.lib,l=h.WordArray,f=h.Hasher,u=c.algo,d=l.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),v=l.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),p=l.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),_=l.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),y=l.create([0,1518500249,1859775393,2400959708,2840853838]),g=l.create([1352829926,1548603684,1836072691,2053994217,0]),B=u.RIPEMD160=f.extend({_doReset:function(){this._hash=l.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,r){for(var c=0;c<16;c++){var h=r+c,l=t[h];t[h]=16711935&(l<<8|l>>>24)|4278255360&(l<<24|l>>>8)}var f,u,B,w,k,S,m,x,b,H,z=this._hash.words,A=y.words,C=g.words,D=d.words,R=v.words,E=p.words,M=_.words;S=f=z[0],m=u=z[1],x=B=z[2],b=w=z[3],H=k=z[4];for(var F,c=0;c<80;c+=1)F=f+t[r+D[c]]|0,F+=c<16?e(u,B,w)+A[0]:c<32?i(u,B,w)+A[1]:c<48?n(u,B,w)+A[2]:c<64?o(u,B,w)+A[3]:s(u,B,w)+A[4],F|=0,F=a(F,E[c]),F=F+k|0,f=k,k=w,w=a(B,10),B=u,u=F,F=S+t[r+R[c]]|0,F+=c<16?s(m,x,b)+C[0]:c<32?o(m,x,b)+C[1]:c<48?n(m,x,b)+C[2]:c<64?i(m,x,b)+C[3]:e(m,x,b)+C[4],F|=0,F=a(F,M[c]),F=F+H|0,S=H,H=b,b=a(x,10),x=m,m=F;F=z[1]+B+b|0,z[1]=z[2]+w+H|0,z[2]=z[3]+k+S|0,z[3]=z[4]+f+m|0,z[4]=z[0]+u+x|0,z[0]=F},_doFinalize:function(){var t=this._data,r=t.words,e=8*this._nDataBytes,i=8*t.sigBytes;r[i>>>5]|=128<<24-i%32,r[(i+64>>>9<<4)+14]=16711935&(e<<8|e>>>24)|4278255360&(e<<24|e>>>8),t.sigBytes=4*(r.length+1),this._process();for(var n=this._hash,o=n.words,s=0;s<5;s++){var a=o[s];o[s]=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8)}return n},clone:function(){var t=f.clone.call(this);return t._hash=this._hash.clone(),t}});c.RIPEMD160=f._createHelper(B),c.HmacRIPEMD160=f._createHmacHelper(B)}(Math),function(){var r=t,e=r.lib,i=e.Base,n=r.enc,o=n.Utf8,s=r.algo;s.HMAC=i.extend({init:function(t,r){t=this._hasher=new t.init,"string"==typeof r&&(r=o.parse(r));var e=t.blockSize,i=4*e;r.sigBytes>i&&(r=t.finalize(r)),r.clamp();for(var n=this._oKey=r.clone(),s=this._iKey=r.clone(),a=n.words,c=s.words,h=0;h>>24)|4278255360&(o<<24|o>>>8),s=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8);var a=e[n];a.high^=s,a.low^=o}for(var c=0;c<24;c++){for(var d=0;d<5;d++){for(var v=0,p=0,_=0;_<5;_++){var a=e[d+5*_];v^=a.high,p^=a.low}var y=u[d];y.high=v,y.low=p}for(var d=0;d<5;d++)for(var g=u[(d+4)%5],B=u[(d+1)%5],w=B.high,k=B.low,v=g.high^(w<<1|k>>>31),p=g.low^(k<<1|w>>>31),_=0;_<5;_++){var a=e[d+5*_];a.high^=v,a.low^=p}for(var S=1;S<25;S++){var a=e[S],m=a.high,x=a.low,b=h[S];if(b<32)var v=m<>>32-b,p=x<>>32-b;else var v=x<>>64-b,p=m<>>64-b;var H=u[l[S]];H.high=v,H.low=p}var z=u[0],A=e[0];z.high=A.high,z.low=A.low;for(var d=0;d<5;d++)for(var _=0;_<5;_++){var S=d+5*_,a=e[S],C=u[S],D=u[(d+1)%5+5*_],R=u[(d+2)%5+5*_];a.high=C.high^~D.high&R.high,a.low=C.low^~D.low&R.low}var a=e[0],E=f[c];a.high^=E.high,a.low^=E.low}},_doFinalize:function(){var t=this._data,e=t.words,i=(8*this._nDataBytes,8*t.sigBytes),o=32*this.blockSize;e[i>>>5]|=1<<24-i%32,e[(r.ceil((i+1)/o)*o>>>5)-1]|=128,t.sigBytes=4*e.length,this._process();for(var s=this._state,a=this.cfg.outputLength/8,c=a/8,h=[],l=0;l>>24)|4278255360&(u<<24|u>>>8),d=16711935&(d<<8|d>>>24)|4278255360&(d<<24|d>>>8),h.push(d),h.push(u)}return new n.init(h,a)},clone:function(){for(var t=o.clone.call(this),r=t._state=this._state.slice(0),e=0;e<25;e++)r[e]=r[e].clone();return t}});e.SHA3=o._createHelper(d),e.HmacSHA3=o._createHmacHelper(d)}(Math),function(){function r(){return s.create.apply(s,arguments)}var e=t,i=e.lib,n=i.Hasher,o=e.x64,s=o.Word,a=o.WordArray,c=e.algo,h=[r(1116352408,3609767458),r(1899447441,602891725),r(3049323471,3964484399),r(3921009573,2173295548),r(961987163,4081628472),r(1508970993,3053834265),r(2453635748,2937671579),r(2870763221,3664609560),r(3624381080,2734883394),r(310598401,1164996542),r(607225278,1323610764),r(1426881987,3590304994),r(1925078388,4068182383),r(2162078206,991336113),r(2614888103,633803317),r(3248222580,3479774868),r(3835390401,2666613458),r(4022224774,944711139),r(264347078,2341262773),r(604807628,2007800933),r(770255983,1495990901),r(1249150122,1856431235),r(1555081692,3175218132),r(1996064986,2198950837),r(2554220882,3999719339),r(2821834349,766784016),r(2952996808,2566594879),r(3210313671,3203337956),r(3336571891,1034457026),r(3584528711,2466948901),r(113926993,3758326383),r(338241895,168717936),r(666307205,1188179964),r(773529912,1546045734),r(1294757372,1522805485),r(1396182291,2643833823),r(1695183700,2343527390),r(1986661051,1014477480),r(2177026350,1206759142),r(2456956037,344077627),r(2730485921,1290863460),r(2820302411,3158454273),r(3259730800,3505952657),r(3345764771,106217008),r(3516065817,3606008344),r(3600352804,1432725776),r(4094571909,1467031594),r(275423344,851169720),r(430227734,3100823752),r(506948616,1363258195),r(659060556,3750685593),r(883997877,3785050280),r(958139571,3318307427),r(1322822218,3812723403),r(1537002063,2003034995),r(1747873779,3602036899),r(1955562222,1575990012),r(2024104815,1125592928),r(2227730452,2716904306),r(2361852424,442776044),r(2428436474,593698344),r(2756734187,3733110249),r(3204031479,2999351573),r(3329325298,3815920427),r(3391569614,3928383900),r(3515267271,566280711),r(3940187606,3454069534),r(4118630271,4000239992),r(116418474,1914138554),r(174292421,2731055270),r(289380356,3203993006),r(460393269,320620315),r(685471733,587496836),r(852142971,1086792851),r(1017036298,365543100),r(1126000580,2618297676),r(1288033470,3409855158),r(1501505948,4234509866),r(1607167915,987167468),r(1816402316,1246189591)],l=[];!function(){for(var t=0;t<80;t++)l[t]=r()}();var f=c.SHA512=n.extend({_doReset:function(){this._hash=new a.init([new s.init(1779033703,4089235720),new s.init(3144134277,2227873595),new s.init(1013904242,4271175723),new s.init(2773480762,1595750129),new s.init(1359893119,2917565137),new s.init(2600822924,725511199),new s.init(528734635,4215389547),new s.init(1541459225,327033209)])},_doProcessBlock:function(t,r){for(var e=this._hash.words,i=e[0],n=e[1],o=e[2],s=e[3],a=e[4],c=e[5],f=e[6],u=e[7],d=i.high,v=i.low,p=n.high,_=n.low,y=o.high,g=o.low,B=s.high,w=s.low,k=a.high,S=a.low,m=c.high,x=c.low,b=f.high,H=f.low,z=u.high,A=u.low,C=d,D=v,R=p,E=_,M=y,F=g,P=B,W=w,O=k,U=S,I=m,K=x,X=b,L=H,j=z,N=A,T=0;T<80;T++){var Z=l[T];if(T<16)var q=Z.high=0|t[r+2*T],G=Z.low=0|t[r+2*T+1];else{var J=l[T-15],$=J.high,Q=J.low,V=($>>>1|Q<<31)^($>>>8|Q<<24)^$>>>7,Y=(Q>>>1|$<<31)^(Q>>>8|$<<24)^(Q>>>7|$<<25),tt=l[T-2],rt=tt.high,et=tt.low,it=(rt>>>19|et<<13)^(rt<<3|et>>>29)^rt>>>6,nt=(et>>>19|rt<<13)^(et<<3|rt>>>29)^(et>>>6|rt<<26),ot=l[T-7],st=ot.high,at=ot.low,ct=l[T-16],ht=ct.high,lt=ct.low,G=Y+at,q=V+st+(G>>>0>>0?1:0),G=G+nt,q=q+it+(G>>>0>>0?1:0),G=G+lt,q=q+ht+(G>>>0>>0?1:0);Z.high=q,Z.low=G}var ft=O&I^~O&X,ut=U&K^~U&L,dt=C&R^C&M^R&M,vt=D&E^D&F^E&F,pt=(C>>>28|D<<4)^(C<<30|D>>>2)^(C<<25|D>>>7),_t=(D>>>28|C<<4)^(D<<30|C>>>2)^(D<<25|C>>>7),yt=(O>>>14|U<<18)^(O>>>18|U<<14)^(O<<23|U>>>9),gt=(U>>>14|O<<18)^(U>>>18|O<<14)^(U<<23|O>>>9),Bt=h[T],wt=Bt.high,kt=Bt.low,St=N+gt,mt=j+yt+(St>>>0>>0?1:0),St=St+ut,mt=mt+ft+(St>>>0>>0?1:0),St=St+kt,mt=mt+wt+(St>>>0>>0?1:0),St=St+G,mt=mt+q+(St>>>0>>0?1:0),xt=_t+vt,bt=pt+dt+(xt>>>0<_t>>>0?1:0);j=X,N=L,X=I,L=K,I=O,K=U,U=W+St|0,O=P+mt+(U>>>0>>0?1:0)|0,P=M,W=F,M=R,F=E,R=C,E=D,D=St+xt|0,C=mt+bt+(D>>>0>>0?1:0)|0}v=i.low=v+D,i.high=d+C+(v>>>0>>0?1:0),_=n.low=_+E,n.high=p+R+(_>>>0>>0?1:0),g=o.low=g+F,o.high=y+M+(g>>>0>>0?1:0),w=s.low=w+W,s.high=B+P+(w>>>0>>0?1:0),S=a.low=S+U,a.high=k+O+(S>>>0>>0?1:0),x=c.low=x+K,c.high=m+I+(x>>>0>>0?1:0),H=f.low=H+L,f.high=b+X+(H>>>0>>0?1:0),A=u.low=A+N,u.high=z+j+(A>>>0>>0?1:0)},_doFinalize:function(){var t=this._data,r=t.words,e=8*this._nDataBytes,i=8*t.sigBytes;r[i>>>5]|=128<<24-i%32,r[(i+128>>>10<<5)+30]=Math.floor(e/4294967296),r[(i+128>>>10<<5)+31]=e,t.sigBytes=4*r.length,this._process();var n=this._hash.toX32();return n},clone:function(){var t=n.clone.call(this);return t._hash=this._hash.clone(),t},blockSize:32});e.SHA512=n._createHelper(f),e.HmacSHA512=n._createHmacHelper(f)}(),function(){var r=t,e=r.x64,i=e.Word,n=e.WordArray,o=r.algo,s=o.SHA512,a=o.SHA384=s.extend({_doReset:function(){this._hash=new n.init([new i.init(3418070365,3238371032),new i.init(1654270250,914150663),new i.init(2438529370,812702999),new i.init(355462360,4144912697),new i.init(1731405415,4290775857),new i.init(2394180231,1750603025),new i.init(3675008525,1694076839),new i.init(1203062813,3204075428)])},_doFinalize:function(){var t=s._doFinalize.call(this);return t.sigBytes-=16,t}});r.SHA384=s._createHelper(a),r.HmacSHA384=s._createHmacHelper(a)}(),t.lib.Cipher||function(r){var e=t,i=e.lib,n=i.Base,o=i.WordArray,s=i.BufferedBlockAlgorithm,a=e.enc,c=(a.Utf8,a.Base64),h=e.algo,l=h.EvpKDF,f=i.Cipher=s.extend({cfg:n.extend(),createEncryptor:function(t,r){return this.create(this._ENC_XFORM_MODE,t,r)},createDecryptor:function(t,r){return this.create(this._DEC_XFORM_MODE,t,r)},init:function(t,r,e){this.cfg=this.cfg.extend(e),this._xformMode=t,this._key=r,this.reset()},reset:function(){s.reset.call(this),this._doReset()},process:function(t){return this._append(t),this._process()},finalize:function(t){t&&this._append(t);var r=this._doFinalize();return r},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){function t(t){return"string"==typeof t?m:w}return function(r){return{encrypt:function(e,i,n){return t(i).encrypt(r,e,i,n)},decrypt:function(e,i,n){return t(i).decrypt(r,e,i,n)}}}}()}),u=(i.StreamCipher=f.extend({_doFinalize:function(){var t=this._process(!0);return t},blockSize:1}),e.mode={}),d=i.BlockCipherMode=n.extend({createEncryptor:function(t,r){return this.Encryptor.create(t,r)},createDecryptor:function(t,r){return this.Decryptor.create(t,r)},init:function(t,r){this._cipher=t,this._iv=r}}),v=u.CBC=function(){function t(t,e,i){var n=this._iv;if(n){var o=n;this._iv=r}else var o=this._prevBlock;for(var s=0;s>>2];t.sigBytes-=r}},y=(i.BlockCipher=f.extend({cfg:f.cfg.extend({mode:v,padding:_}),reset:function(){f.reset.call(this);var t=this.cfg,r=t.iv,e=t.mode;if(this._xformMode==this._ENC_XFORM_MODE)var i=e.createEncryptor;else{var i=e.createDecryptor;this._minBufferSize=1}this._mode&&this._mode.__creator==i?this._mode.init(this,r&&r.words):(this._mode=i.call(e,this,r&&r.words),this._mode.__creator=i)},_doProcessBlock:function(t,r){this._mode.processBlock(t,r)},_doFinalize:function(){var t=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){t.pad(this._data,this.blockSize);var r=this._process(!0)}else{var r=this._process(!0);t.unpad(r)}return r},blockSize:4}),i.CipherParams=n.extend({init:function(t){this.mixIn(t)},toString:function(t){return(t||this.formatter).stringify(this)}})),g=e.format={},B=g.OpenSSL={stringify:function(t){var r=t.ciphertext,e=t.salt;if(e)var i=o.create([1398893684,1701076831]).concat(e).concat(r);else var i=r;return i.toString(c)},parse:function(t){var r=c.parse(t),e=r.words;if(1398893684==e[0]&&1701076831==e[1]){var i=o.create(e.slice(2,4));e.splice(0,4),r.sigBytes-=16}return y.create({ciphertext:r,salt:i})}},w=i.SerializableCipher=n.extend({cfg:n.extend({format:B}),encrypt:function(t,r,e,i){i=this.cfg.extend(i);var n=t.createEncryptor(e,i),o=n.finalize(r),s=n.cfg;return y.create({ciphertext:o,key:e,iv:s.iv,algorithm:t,mode:s.mode,padding:s.padding,blockSize:t.blockSize,formatter:i.format})},decrypt:function(t,r,e,i){i=this.cfg.extend(i),r=this._parse(r,i.format);var n=t.createDecryptor(e,i).finalize(r.ciphertext);return n},_parse:function(t,r){return"string"==typeof t?r.parse(t,this):t}}),k=e.kdf={},S=k.OpenSSL={execute:function(t,r,e,i){i||(i=o.random(8));var n=l.create({keySize:r+e}).compute(t,i),s=o.create(n.words.slice(r),4*e);return n.sigBytes=4*r,y.create({key:n,iv:s,salt:i})}},m=i.PasswordBasedCipher=w.extend({cfg:w.cfg.extend({kdf:S}),encrypt:function(t,r,e,i){i=this.cfg.extend(i);var n=i.kdf.execute(e,t.keySize,t.ivSize);i.iv=n.iv;var o=w.encrypt.call(this,t,r,n.key,i);return o.mixIn(n),o},decrypt:function(t,r,e,i){i=this.cfg.extend(i),r=this._parse(r,i.format);var n=i.kdf.execute(e,t.keySize,t.ivSize,r.salt);i.iv=n.iv;var o=w.decrypt.call(this,t,r,n.key,i);return o}})}(),t.mode.CFB=function(){function r(t,r,e,i){var n=this._iv;if(n){var o=n.slice(0);this._iv=void 0}else var o=this._prevBlock;i.encryptBlock(o,0);for(var s=0;s>>2]|=n<<24-o%4*8,t.sigBytes+=n},unpad:function(t){var r=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=r}},t.pad.Iso10126={pad:function(r,e){var i=4*e,n=i-r.sigBytes%i;r.concat(t.lib.WordArray.random(n-1)).concat(t.lib.WordArray.create([n<<24],1))},unpad:function(t){var r=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=r}},t.pad.Iso97971={pad:function(r,e){r.concat(t.lib.WordArray.create([2147483648],1)),t.pad.ZeroPadding.pad(r,e)},unpad:function(r){t.pad.ZeroPadding.unpad(r),r.sigBytes--}},t.mode.OFB=function(){var r=t.lib.BlockCipherMode.extend(),e=r.Encryptor=r.extend({processBlock:function(t,r){var e=this._cipher,i=e.blockSize,n=this._iv,o=this._keystream;n&&(o=this._keystream=n.slice(0),this._iv=void 0),e.encryptBlock(o,0);for(var s=0;s>>8^255&n^99,o[e]=n,s[n]=e;var p=t[e],_=t[p],y=t[_],g=257*t[n]^16843008*n;a[e]=g<<24|g>>>8,c[e]=g<<16|g>>>16,h[e]=g<<8|g>>>24,l[e]=g;var g=16843009*y^65537*_^257*p^16843008*e;f[n]=g<<24|g>>>8,u[n]=g<<16|g>>>16,d[n]=g<<8|g>>>24,v[n]=g,e?(e=p^t[t[t[y^p]]],i^=t[t[i]]):e=i=1}}();var p=[0,1,2,4,8,16,32,64,128,27,54],_=n.AES=i.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var t=this._keyPriorReset=this._key,r=t.words,e=t.sigBytes/4,i=this._nRounds=e+6,n=4*(i+1),s=this._keySchedule=[],a=0;a6&&a%e==4&&(c=o[c>>>24]<<24|o[c>>>16&255]<<16|o[c>>>8&255]<<8|o[255&c]):(c=c<<8|c>>>24,c=o[c>>>24]<<24|o[c>>>16&255]<<16|o[c>>>8&255]<<8|o[255&c],c^=p[a/e|0]<<24),s[a]=s[a-e]^c}for(var h=this._invKeySchedule=[],l=0;l>>24]]^u[o[c>>>16&255]]^d[o[c>>>8&255]]^v[o[255&c]]}}},encryptBlock:function(t,r){this._doCryptBlock(t,r,this._keySchedule,a,c,h,l,o)},decryptBlock:function(t,r){var e=t[r+1];t[r+1]=t[r+3],t[r+3]=e,this._doCryptBlock(t,r,this._invKeySchedule,f,u,d,v,s);var e=t[r+1];t[r+1]=t[r+3],t[r+3]=e},_doCryptBlock:function(t,r,e,i,n,o,s,a){for(var c=this._nRounds,h=t[r]^e[0],l=t[r+1]^e[1],f=t[r+2]^e[2],u=t[r+3]^e[3],d=4,v=1;v>>24]^n[l>>>16&255]^o[f>>>8&255]^s[255&u]^e[d++],_=i[l>>>24]^n[f>>>16&255]^o[u>>>8&255]^s[255&h]^e[d++],y=i[f>>>24]^n[u>>>16&255]^o[h>>>8&255]^s[255&l]^e[d++],g=i[u>>>24]^n[h>>>16&255]^o[l>>>8&255]^s[255&f]^e[d++];h=p,l=_,f=y,u=g}var p=(a[h>>>24]<<24|a[l>>>16&255]<<16|a[f>>>8&255]<<8|a[255&u])^e[d++],_=(a[l>>>24]<<24|a[f>>>16&255]<<16|a[u>>>8&255]<<8|a[255&h])^e[d++],y=(a[f>>>24]<<24|a[u>>>16&255]<<16|a[h>>>8&255]<<8|a[255&l])^e[d++],g=(a[u>>>24]<<24|a[h>>>16&255]<<16|a[l>>>8&255]<<8|a[255&f])^e[d++];t[r]=p,t[r+1]=_,t[r+2]=y,t[r+3]=g},keySize:8});r.AES=i._createHelper(_)}(),function(){function r(t,r){var e=(this._lBlock>>>t^this._rBlock)&r;this._rBlock^=e,this._lBlock^=e<>>t^this._lBlock)&r;this._lBlock^=e,this._rBlock^=e<>>5]>>>31-n%32&1}for(var o=this._subKeys=[],s=0;s<16;s++){for(var a=o[s]=[],f=l[s],i=0;i<24;i++)a[i/6|0]|=e[(h[i]-1+f)%28]<<31-i%6,a[4+(i/6|0)]|=e[28+(h[i+24]-1+f)%28]<<31-i%6;a[0]=a[0]<<1|a[0]>>>31;for(var i=1;i<7;i++)a[i]=a[i]>>>4*(i-1)+3;a[7]=a[7]<<5|a[7]>>>27}for(var u=this._invSubKeys=[],i=0;i<16;i++)u[i]=o[15-i]},encryptBlock:function(t,r){this._doCryptBlock(t,r,this._subKeys)},decryptBlock:function(t,r){this._doCryptBlock(t,r,this._invSubKeys)},_doCryptBlock:function(t,i,n){this._lBlock=t[i],this._rBlock=t[i+1],r.call(this,4,252645135),r.call(this,16,65535),e.call(this,2,858993459),e.call(this,8,16711935),r.call(this,1,1431655765);for(var o=0;o<16;o++){for(var s=n[o],a=this._lBlock,c=this._rBlock,h=0,l=0;l<8;l++)h|=f[l][((c^s[l])&u[l])>>>0];this._lBlock=c,this._rBlock=a^h}var d=this._lBlock;this._lBlock=this._rBlock,this._rBlock=d,r.call(this,1,1431655765),e.call(this,8,16711935),e.call(this,2,858993459),r.call(this,16,65535),r.call(this,4,252645135),t[i]=this._lBlock,t[i+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});i.DES=s._createHelper(d);var v=a.TripleDES=s.extend({_doReset:function(){var t=this._key,r=t.words;this._des1=d.createEncryptor(o.create(r.slice(0,2))),this._des2=d.createEncryptor(o.create(r.slice(2,4))),this._des3=d.createEncryptor(o.create(r.slice(4,6)))},encryptBlock:function(t,r){this._des1.encryptBlock(t,r),this._des2.decryptBlock(t,r),this._des3.encryptBlock(t,r)},decryptBlock:function(t,r){this._des3.decryptBlock(t,r),this._des2.encryptBlock(t,r),this._des1.decryptBlock(t,r)},keySize:6,ivSize:2,blockSize:2});i.TripleDES=s._createHelper(v)}(),function(){function r(){for(var t=this._S,r=this._i,e=this._j,i=0,n=0;n<4;n++){r=(r+1)%256,e=(e+t[r])%256;var o=t[r];t[r]=t[e],t[e]=o,i|=t[(t[r]+t[e])%256]<<24-8*n}return this._i=r,this._j=e,i}var e=t,i=e.lib,n=i.StreamCipher,o=e.algo,s=o.RC4=n.extend({_doReset:function(){for(var t=this._key,r=t.words,e=t.sigBytes,i=this._S=[],n=0;n<256;n++)i[n]=n;for(var n=0,o=0;n<256;n++){var s=n%e,a=r[s>>>2]>>>24-s%4*8&255;o=(o+i[n]+a)%256;var c=i[n];i[n]=i[o],i[o]=c}this._i=this._j=0},_doProcessBlock:function(t,e){t[e]^=r.call(this)},keySize:8,ivSize:0});e.RC4=n._createHelper(s);var a=o.RC4Drop=s.extend({cfg:s.cfg.extend({drop:192}),_doReset:function(){s._doReset.call(this);for(var t=this.cfg.drop;t>0;t--)r.call(this)}});e.RC4Drop=n._createHelper(a)}(),t.mode.CTRGladman=function(){function r(t){if(255===(t>>24&255)){var r=t>>16&255,e=t>>8&255,i=255&t;255===r?(r=0,255===e?(e=0,255===i?i=0:++i):++e):++r,t=0,t+=r<<16,t+=e<<8,t+=i}else t+=1<<24;return t}function e(t){return 0===(t[0]=r(t[0]))&&(t[1]=r(t[1])),t}var i=t.lib.BlockCipherMode.extend(),n=i.Encryptor=i.extend({processBlock:function(t,r){var i=this._cipher,n=i.blockSize,o=this._iv,s=this._counter;o&&(s=this._counter=o.slice(0),this._iv=void 0),e(s);var a=s.slice(0);i.encryptBlock(a,0);for(var c=0;c>>0>>0?1:0)|0,r[2]=r[2]+886263092+(r[1]>>>0>>0?1:0)|0,r[3]=r[3]+1295307597+(r[2]>>>0>>0?1:0)|0,r[4]=r[4]+3545052371+(r[3]>>>0>>0?1:0)|0,r[5]=r[5]+886263092+(r[4]>>>0>>0?1:0)|0,r[6]=r[6]+1295307597+(r[5]>>>0>>0?1:0)|0,r[7]=r[7]+3545052371+(r[6]>>>0>>0?1:0)|0,this._b=r[7]>>>0>>0?1:0;for(var e=0;e<8;e++){var i=t[e]+r[e],n=65535&i,o=i>>>16,s=((n*n>>>17)+n*o>>>15)+o*o,h=((4294901760&i)*i|0)+((65535&i)*i|0);c[e]=s^h}t[0]=c[0]+(c[7]<<16|c[7]>>>16)+(c[6]<<16|c[6]>>>16)|0,t[1]=c[1]+(c[0]<<8|c[0]>>>24)+c[7]|0,t[2]=c[2]+(c[1]<<16|c[1]>>>16)+(c[0]<<16|c[0]>>>16)|0,t[3]=c[3]+(c[2]<<8|c[2]>>>24)+c[1]|0,t[4]=c[4]+(c[3]<<16|c[3]>>>16)+(c[2]<<16|c[2]>>>16)|0,t[5]=c[5]+(c[4]<<8|c[4]>>>24)+c[3]|0,t[6]=c[6]+(c[5]<<16|c[5]>>>16)+(c[4]<<16|c[4]>>>16)|0,t[7]=c[7]+(c[6]<<8|c[6]>>>24)+c[5]|0}var e=t,i=e.lib,n=i.StreamCipher,o=e.algo,s=[],a=[],c=[],h=o.Rabbit=n.extend({_doReset:function(){for(var t=this._key.words,e=this.cfg.iv,i=0;i<4;i++)t[i]=16711935&(t[i]<<8|t[i]>>>24)|4278255360&(t[i]<<24|t[i]>>>8);var n=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],o=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]];this._b=0;for(var i=0;i<4;i++)r.call(this);for(var i=0;i<8;i++)o[i]^=n[i+4&7];if(e){var s=e.words,a=s[0],c=s[1],h=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8),l=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8),f=h>>>16|4294901760&l,u=l<<16|65535&h;o[0]^=h,o[1]^=f,o[2]^=l,o[3]^=u,o[4]^=h,o[5]^=f,o[6]^=l,o[7]^=u;for(var i=0;i<4;i++)r.call(this)}},_doProcessBlock:function(t,e){var i=this._X;r.call(this),s[0]=i[0]^i[5]>>>16^i[3]<<16,s[1]=i[2]^i[7]>>>16^i[5]<<16,s[2]=i[4]^i[1]>>>16^i[7]<<16,s[3]=i[6]^i[3]>>>16^i[1]<<16;for(var n=0;n<4;n++)s[n]=16711935&(s[n]<<8|s[n]>>>24)|4278255360&(s[n]<<24|s[n]>>>8),t[e+n]^=s[n]},blockSize:4,ivSize:2});e.Rabbit=n._createHelper(h)}(),t.mode.CTR=function(){var r=t.lib.BlockCipherMode.extend(),e=r.Encryptor=r.extend({processBlock:function(t,r){var e=this._cipher,i=e.blockSize,n=this._iv,o=this._counter;n&&(o=this._counter=n.slice(0),this._iv=void 0);var s=o.slice(0);e.encryptBlock(s,0),o[i-1]=o[i-1]+1|0;for(var a=0;a>>0>>0?1:0)|0,r[2]=r[2]+886263092+(r[1]>>>0>>0?1:0)|0,r[3]=r[3]+1295307597+(r[2]>>>0>>0?1:0)|0,r[4]=r[4]+3545052371+(r[3]>>>0>>0?1:0)|0,r[5]=r[5]+886263092+(r[4]>>>0>>0?1:0)|0,r[6]=r[6]+1295307597+(r[5]>>>0>>0?1:0)|0,r[7]=r[7]+3545052371+(r[6]>>>0>>0?1:0)|0,this._b=r[7]>>>0>>0?1:0;for(var e=0;e<8;e++){var i=t[e]+r[e],n=65535&i,o=i>>>16,s=((n*n>>>17)+n*o>>>15)+o*o,h=((4294901760&i)*i|0)+((65535&i)*i|0);c[e]=s^h}t[0]=c[0]+(c[7]<<16|c[7]>>>16)+(c[6]<<16|c[6]>>>16)|0,t[1]=c[1]+(c[0]<<8|c[0]>>>24)+c[7]|0,t[2]=c[2]+(c[1]<<16|c[1]>>>16)+(c[0]<<16|c[0]>>>16)|0,t[3]=c[3]+(c[2]<<8|c[2]>>>24)+c[1]|0,t[4]=c[4]+(c[3]<<16|c[3]>>>16)+(c[2]<<16|c[2]>>>16)|0,t[5]=c[5]+(c[4]<<8|c[4]>>>24)+c[3]|0,t[6]=c[6]+(c[5]<<16|c[5]>>>16)+(c[4]<<16|c[4]>>>16)|0,t[7]=c[7]+(c[6]<<8|c[6]>>>24)+c[5]|0}var e=t,i=e.lib,n=i.StreamCipher,o=e.algo,s=[],a=[],c=[],h=o.RabbitLegacy=n.extend({_doReset:function(){var t=this._key.words,e=this.cfg.iv,i=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],n=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]];this._b=0;for(var o=0;o<4;o++)r.call(this);for(var o=0;o<8;o++)n[o]^=i[o+4&7];if(e){var s=e.words,a=s[0],c=s[1],h=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8),l=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8),f=h>>>16|4294901760&l,u=l<<16|65535&h;n[0]^=h,n[1]^=f,n[2]^=l,n[3]^=u,n[4]^=h,n[5]^=f,n[6]^=l,n[7]^=u;for(var o=0;o<4;o++)r.call(this)}},_doProcessBlock:function(t,e){var i=this._X;r.call(this),s[0]=i[0]^i[5]>>>16^i[3]<<16,s[1]=i[2]^i[7]>>>16^i[5]<<16,s[2]=i[4]^i[1]>>>16^i[7]<<16,s[3]=i[6]^i[3]>>>16^i[1]<<16;for(var n=0;n<4;n++)s[n]=16711935&(s[n]<<8|s[n]>>>24)|4278255360&(s[n]<<24|s[n]>>>8),t[e+n]^=s[n]},blockSize:4,ivSize:2});e.RabbitLegacy=n._createHelper(h)}(),t.pad.ZeroPadding={pad:function(t,r){var e=4*r;t.clamp(),t.sigBytes+=e-(t.sigBytes%e||e)},unpad:function(t){for(var r=t.words,e=t.sigBytes-1;!(r[e>>>2]>>>24-e%4*8&255);)e--;t.sigBytes=e+1}},t}); +//# sourceMappingURL=crypto-js.min.js.map \ No newline at end of file diff --git a/app/template/classless-css-master.zip b/app/template/classless-css-master.zip new file mode 100644 index 00000000..5da375c9 Binary files /dev/null and b/app/template/classless-css-master.zip differ diff --git a/app/template/dropin-minimal-css-gh-pages.zip b/app/template/dropin-minimal-css-gh-pages.zip new file mode 100644 index 00000000..b000dd75 Binary files /dev/null and b/app/template/dropin-minimal-css-gh-pages.zip differ diff --git a/app/web-tools/dpaint/README.md b/app/web-tools/dpaint/README.md new file mode 100644 index 00000000..8e8193ae --- /dev/null +++ b/app/web-tools/dpaint/README.md @@ -0,0 +1,80 @@ +# DPaint.js +Webbased image editor modeled after the legendary [Deluxe Paint](https://en.wikipedia.org/wiki/Deluxe_Paint) with a focus on retro Amiga file formats. +Next to modern image formats, DPaint.js can read and write Amiga icon files and IFF ILBM images. + +![DPaint.js Logo](./_img/dpaint-logo.png?raw=true) + +Online version available at https://www.stef.be/dpaint/ + +![DPaint.js UI](./_img/ui.png?raw=true) + +## Main Features + - Fully Featured image editor with a.o. + - Layers + - Selections + - Masking + - Transformation tools + - Effects and filters + - Multiple undo/redo + - Copy/Paste from any other image program or image source + - Customizable dither tools + - Heavy focus on colour reduction with fine-grained dithering options + - Amiga focus + - Read/write/convert Amiga icon files (all formats) + - Reads IFF ILBM images (all formats including HAM and 24-bit) + - Writes IFF ILBM images (up to 256 colors) + - Read and write directly from Amiga Disk Files (ADF) + - Embedded Amiga Emulator to preview your work in the real Deluxe Paint. + - Limit the palette to 12 bit for Amiga OCS/ECS mode, or 9 bit for Atari ST mode. + - Deluxe Paint Legacy + - Supports PBM files as used by the PC version of Deluxe Paint (Thanks to [Michael Smith](https://github.com/michaelshmitty)) + - Supports Deluxe Paint Atari ST compression modes (Thanks to [Nicolas Ramz](https://github.com/warpdesign)) +## Free and Open +It runs in your browser, works on any system and works fine on touch-screen devices like iPads. +It is written in 100% plain JavaScript and has no dependencies. +It's 100% free, no ads, no tracking, no accounts, no nothing. +All processing is done in your browser, no data is sent to any server. + +The only part that is not included in this repository is the Amiga Emulator Files. +(The emulator is based on the [Scripted Amiga Emulator](https://github.com/naTmeg/ScriptedAmigaEmulator)) + +## Building +DPaint.js doesn't need building. +It also has zero dependencies so there's no need to install anything. +DPaint.js is written using ES6 modules and runs out of the box in modern browsers. +Just serve "index.html" from a webserver and you're good to go. + +There's an optional build step to create a compact version of DPaint.js if you like. +I'm using [Parcel.js](https://parceljs.org/) for this. +For convenience, I've included a "package.json" file. +open a terminal and run `npm install` to install Parcel.js and its dependencies. +Then run `npm run build` to create a compact version of DPaint.js in the "dist" folder. + +## Documentation +Documentation can be found at https://www.stef.be/dpaint/docs/ + +## Running offline +Dpaint.js is a web application, not an app that you install on your computer. +That being said: DPaint.js has no online dependencies and runs fine offline if you want. +One caveat: you have to serve the index.html file from a webserver, not just open it in your browser. +A quick way to do this is - for example - using the [Spark](https://github.com/rif/spark/releases) app. +[Download the binary](https://github.com/rif/spark/releases) for your platform, drop the Spark executable in the folder where you downloaded the Dpaint.js source files and run it. +If you then point your browser to http://localhost:8080/ it should work. + +## Contributing +Current version is still alpha. +I'm sure there are bugs and missing features. +Bug reports and pull requests are welcome. + +### Missing Features +Planned for the next release, already in the works: + - Color Cycling (done) + - Animation support (GIf and Amiga ANIM files) + - Shading/transparency tools that stay within the palette. (done) + +Planned for a future release if there's a need for it. + - Support for non-square pixel modes such as HiRes and Interlaced + - PSD import and export + - SpriteSheet support + - Write HAM,SHAM and Dynamic HiRes images + diff --git a/app/web-tools/dpaint/TODO.txt b/app/web-tools/dpaint/TODO.txt new file mode 100644 index 00000000..87fb42e1 --- /dev/null +++ b/app/web-tools/dpaint/TODO.txt @@ -0,0 +1,64 @@ +- Improve Undo/redo +- Add/Remove from selection +- invert selection +- resize sidebar +- dragging of panels? +- improve rotate layer/selection +- polygon mask - deduplicate points +- pen support (TouchData force => https://developer.mozilla.org/en-US/docs/Web/API/Touch/force ) +- small screen support +- copy image larger than canvas => keep entire image? +- deselect after resize +- show grid function +- ctrl-mousewheel should be zoom +- save to JPG including EXIF data +- EXIF editor? +- add close buttons to second image panel +- selection tool should hide the paint-shape cursor +- open recent files? + + +- filters: e.g. https://medium.com/skylar-salernos-tech-blog/mimicking-googles-pop-filter-using-canvas-blend-modes-d7da83590d1a + + +- load palette directly from Lospec? https://lospec.com/palette-list/load?colorNumberFilterType=any&colorNumber=8&page=0&tag=&sortingType=default + +- Export to TIFF: https://github.com/motiz88/canvas-to-tiff + +Bugs: + + +Changing the palette color depth squashes all layers? +Transforming a mask clears the layer ? +resizing sizebox negatively, makes the sizebox 0 width/height +resize image to larger doesn't clear all cached data (like the drawlayer?) +copy/paste/undo shortcut doesn't work on Firefox +fill tool is active when scrollbar is clicked +when aplying color palette changes - they get applied to the top layer instead of the active layer +scrolling removes selection mask +transform layer in touch screens? +Drawing a line over the edge wraps around to the other side + + +TODO before release: +(why was there a check for "meta key down"? rotate brush stuff? +eydropper should not beactive while drawing a line) +); + +Animated GIF feature previews +- color cycling +- pressure sensitivity +- Palette lock +- 12 and 9 bit color depths + + +Amiga Specific + - 12 bit color depth + - Color Cycle with IFF export + + + + + + + diff --git a/app/web-tools/dpaint/_data/palettes/Amstrad-CPC.json b/app/web-tools/dpaint/_data/palettes/Amstrad-CPC.json new file mode 100644 index 00000000..00a31ce4 --- /dev/null +++ b/app/web-tools/dpaint/_data/palettes/Amstrad-CPC.json @@ -0,0 +1,32 @@ +{ + "type": "palette", + "palette": [ + "#040404", + "#808080", + "#ffffff", + "#800000", + "#ff0000", + "#ff8080", + "#ff7f00", + "#ffff80", + "#ffff00", + "#808000", + "#008000", + "#01ff00", + "#80ff00", + "#80ff80", + "#01ff80", + "#008080", + "#01ffff", + "#80ffff", + "#0080ff", + "#0000ff", + "#00007f", + "#7f00ff", + "#8080ff", + "#ff80ff", + "#ff00ff", + "#ff0080", + "#800080" + ] +} diff --git a/app/web-tools/dpaint/_data/palettes/Atari-2600-NTSC.json b/app/web-tools/dpaint/_data/palettes/Atari-2600-NTSC.json new file mode 100644 index 00000000..4460002e --- /dev/null +++ b/app/web-tools/dpaint/_data/palettes/Atari-2600-NTSC.json @@ -0,0 +1,133 @@ +{ + "type": "palette", + "palette": [ + "#000000", + "#444400", + "#702800", + "#841800", + "#880000", + "#78005c", + "#480078", + "#140084", + "#000088", + "#00187c", + "#002c5c", + "#00402c", + "#003c00", + "#143800", + "#2c3000", + "#442800", + "#404040", + "#646410", + "#844414", + "#983418", + "#9c2020", + "#8c2074", + "#602090", + "#302098", + "#1c209c", + "#1c3890", + "#1c4c78", + "#1c5c48", + "#205c20", + "#345c1c", + "#4c501c", + "#644818", + "#6c6c6c", + "#848424", + "#985c28", + "#ac5030", + "#b03c3c", + "#a03c88", + "#783ca4", + "#4c3cac", + "#3840b0", + "#3854a8", + "#386890", + "#387c64", + "#407c40", + "#507c38", + "#687034", + "#846830", + "#909090", + "#a0a034", + "#ac783c", + "#c06848", + "#c05858", + "#b0589c", + "#8c58b8", + "#6858c0", + "#505cc0", + "#5070bc", + "#5084ac", + "#509c80", + "#5c9c5c", + "#6c9850", + "#848c4c", + "#a08444", + "#b0b0b0", + "#b8b840", + "#bc8c4c", + "#d0805c", + "#d07070", + "#c070b0", + "#a070cc", + "#7c70d0", + "#6874d0", + "#6888cc", + "#689cc0", + "#68b494", + "#74b474", + "#84b468", + "#9ca864", + "#b89c58", + "#c8c8c8", + "#d0d050", + "#cca05c", + "#e09470", + "#e08888", + "#d084c0", + "#b484dc", + "#9488e0", + "#7c8ce0", + "#7c9cdc", + "#7cb4d4", + "#7cd0ac", + "#8cd08c", + "#9ccc7c", + "#b4c078", + "#d0b46c", + "#dcdcdc", + "#e8e85c", + "#dcb468", + "#eca880", + "#eca0a0", + "#dc9cd0", + "#c49cec", + "#a8a0ec", + "#90a4ec", + "#90b4ec", + "#90cce8", + "#90e4c0", + "#a4e4a4", + "#b4e490", + "#ccd488", + "#e8cc7c", + "#ececec", + "#fcfc68", + "#fcbc94", + "#fcb4b4", + "#ecb0e0", + "#d4b0fc", + "#bcb4fc", + "#a4b8fc", + "#a4c8fc", + "#a4e0fc", + "#a4fcd4", + "#b8fcb8", + "#c8fca4", + "#e0ec9c", + "#fce08c", + "#ffffff" + ] +} diff --git a/app/web-tools/dpaint/_data/palettes/Atari-2600-PAL.json b/app/web-tools/dpaint/_data/palettes/Atari-2600-PAL.json new file mode 100644 index 00000000..9ea32c23 --- /dev/null +++ b/app/web-tools/dpaint/_data/palettes/Atari-2600-PAL.json @@ -0,0 +1,109 @@ +{ + "type": "palette", + "palette": [ + "#000000", + "#805800", + "#445c00", + "#703400", + "#006414", + "#700014", + "#005c5c", + "#70005c", + "#003c70", + "#580070", + "#002070", + "#3c0080", + "#000088", + "#404040", + "#947020", + "#5c7820", + "#885020", + "#208034", + "#882034", + "#207474", + "#842074", + "#1c5888", + "#6c2088", + "#1c3c88", + "#542094", + "#20209c", + "#6c6c6c", + "#a8843c", + "#74903c", + "#a0683c", + "#3c9850", + "#a03c50", + "#3c8c8c", + "#943c88", + "#3874a0", + "#803ca0", + "#3858a0", + "#6c3ca8", + "#3c3cb0", + "#909090", + "#bc9c58", + "#8cac58", + "#b48458", + "#58b06c", + "#b4586c", + "#58a4a4", + "#a8589c", + "#508cb4", + "#9458b4", + "#5074b4", + "#8058bc", + "#5858c0", + "#b0b0b0", + "#ccac70", + "#a0c070", + "#c89870", + "#70c484", + "#c87084", + "#70b8b8", + "#b470b0", + "#68a4c8", + "#a470c8", + "#6888c8", + "#9470cc", + "#7070d0", + "#c8c8c8", + "#dcc084", + "#b0d484", + "#dcac84", + "#84d89c", + "#dc849c", + "#84c8c8", + "#c484c0", + "#7cb8dc", + "#b484dc", + "#7ca0dc", + "#a884dc", + "#8484e0", + "#dcdcdc", + "#ecd09c", + "#c0e89c", + "#ecc09c", + "#9ce8b4", + "#ec9cb4", + "#9cdcdc", + "#d09cd0", + "#90ccec", + "#c49cec", + "#90b4ec", + "#b89cec", + "#9c9cec", + "#ececec", + "#fce0b0", + "#d4fcb0", + "#fcd4b0", + "#b0fcc8", + "#fcb0c8", + "#b0ecec", + "#e0b0e0", + "#a4e0fc", + "#d4b0fc", + "#a4c8fc", + "#c8b0fc", + "#b0b0fc" + ] +} diff --git a/app/web-tools/dpaint/_data/palettes/Atari-GTIA.json b/app/web-tools/dpaint/_data/palettes/Atari-GTIA.json new file mode 100644 index 00000000..472a3bcd --- /dev/null +++ b/app/web-tools/dpaint/_data/palettes/Atari-GTIA.json @@ -0,0 +1,261 @@ +{ + "type": "palette", + "palette": [ + "#000000", + "#111111", + "#222222", + "#333333", + "#444444", + "#555555", + "#666666", + "#777777", + "#888888", + "#999999", + "#aaaaaa", + "#bbbbbb", + "#cccccc", + "#dddddd", + "#eeeeee", + "#ffffff", + "#190700", + "#2a1800", + "#3b2900", + "#4c3a00", + "#5d4b00", + "#6e5c00", + "#7f6d00", + "#907e09", + "#a18f1a", + "#b3a02b", + "#c3b13c", + "#d4c24d", + "#e5d35e", + "#f7e46f", + "#fff582", + "#ffff96", + "#310000", + "#3f0000", + "#531700", + "#642800", + "#753900", + "#864a00", + "#975b0a", + "#a86c1b", + "#b97d2c", + "#ca8e3d", + "#db9f4e", + "#ecb05f", + "#fdc170", + "#ffd285", + "#ffe39c", + "#fff4b2", + "#420404", + "#4f0000", + "#600800", + "#711900", + "#822a0d", + "#933b1e", + "#a44c2f", + "#b55d40", + "#c66e51", + "#d77f62", + "#e89073", + "#f9a183", + "#ffb298", + "#ffc3ae", + "#ffd4c4", + "#ffe5da", + "#410103", + "#50000f", + "#61001b", + "#720f2b", + "#83203c", + "#94314d", + "#a5425e", + "#b6536f", + "#c76480", + "#d87591", + "#e986a2", + "#fa97b3", + "#ffa8c8", + "#ffb9de", + "#ffcaef", + "#fbdcf6", + "#330035", + "#440041", + "#55004c", + "#660c5c", + "#771d6d", + "#882e7e", + "#993f8f", + "#aa50a0", + "#bb61b1", + "#cc72c2", + "#dd83d3", + "#ee94e4", + "#ffa5e4", + "#ffb6e9", + "#ffc7ee", + "#ffd8f3", + "#1d005c", + "#2e0068", + "#400074", + "#511084", + "#622195", + "#7332a6", + "#8443b7", + "#9554c8", + "#a665d9", + "#b776ea", + "#c887eb", + "#d998eb", + "#e9a9ec", + "#fbbaeb", + "#ffcbef", + "#ffdff9", + "#020071", + "#13007d", + "#240b8c", + "#351c9d", + "#462dae", + "#573ebf", + "#684fd0", + "#7960e1", + "#8a71f2", + "#9b82f7", + "#ac93f7", + "#bda4f7", + "#ceb5f7", + "#dfc6f7", + "#f0d7f7", + "#ffe8f8", + "#000068", + "#000a7c", + "#081b90", + "#192ca1", + "#2a3db2", + "#3b4ec3", + "#4c5fd4", + "#5d70e5", + "#6e81f6", + "#7f92ff", + "#90a3ff", + "#a1b4ff", + "#b2c5ff", + "#c3d6ff", + "#d4e7ff", + "#e5f8ff", + "#000a4d", + "#001b63", + "#002c79", + "#023d8f", + "#134ea0", + "#245fb1", + "#3570c2", + "#4681d3", + "#5792e4", + "#68a3f5", + "#79b4ff", + "#8ac5ff", + "#9bd6ff", + "#ace7ff", + "#bdf8ff", + "#ceffff", + "#001a26", + "#002b3c", + "#003c52", + "#004d68", + "#065e7c", + "#176f8d", + "#28809e", + "#3991af", + "#4aa2c0", + "#5bb3d1", + "#6cc4e2", + "#7dd5f3", + "#8ee6ff", + "#9ff7ff", + "#b0ffff", + "#c1ffff", + "#01250a", + "#023610", + "#004622", + "#005738", + "#05684d", + "#16795e", + "#278a6f", + "#389b80", + "#49ac91", + "#5abda2", + "#6bceb3", + "#7cdfc4", + "#8df0d5", + "#9effe5", + "#affff1", + "#c0fffd", + "#04260d", + "#043811", + "#054713", + "#005a1b", + "#106b1b", + "#217c2c", + "#328d3d", + "#439e4e", + "#54af5f", + "#65c070", + "#76d181", + "#87e292", + "#98f3a3", + "#a9ffb3", + "#baffbf", + "#cbffcb", + "#00230a", + "#003510", + "#044613", + "#155613", + "#266713", + "#377813", + "#488914", + "#599a25", + "#6aab36", + "#7bbc47", + "#8ccd58", + "#9dde69", + "#aeef7a", + "#bfff8b", + "#d0ff97", + "#e1ffa3", + "#001707", + "#0e2808", + "#1f3908", + "#304a08", + "#415b08", + "#526c08", + "#637d08", + "#748e0d", + "#859f1e", + "#96b02f", + "#a7c140", + "#b8d251", + "#c9e362", + "#daf473", + "#ebff82", + "#fcff8e", + "#1b0701", + "#2c1801", + "#3c2900", + "#4d3b00", + "#5f4c00", + "#705e00", + "#816f00", + "#938009", + "#a4921a", + "#b2a02b", + "#c7b43d", + "#d8c64e", + "#ead760", + "#f6e46f", + "#fffa84", + "#ffff99" + ] +} diff --git a/app/web-tools/dpaint/_data/palettes/CGA.json b/app/web-tools/dpaint/_data/palettes/CGA.json new file mode 100644 index 00000000..d4d3c45e --- /dev/null +++ b/app/web-tools/dpaint/_data/palettes/CGA.json @@ -0,0 +1,21 @@ +{ + "type": "palette", + "palette": [ + "#000000", + "#0000aa", + "#00aa00", + "#00aaaa", + "#aa0000", + "#aa00aa", + "#aa5500", + "#aaaaaa", + "#555555", + "#5555ff", + "#55ff55", + "#55ffff", + "#ff5555", + "#ff55ff", + "#ffff55", + "#ffffff" + ] +} diff --git a/app/web-tools/dpaint/_data/palettes/MSX.json b/app/web-tools/dpaint/_data/palettes/MSX.json new file mode 100644 index 00000000..cb3daa99 --- /dev/null +++ b/app/web-tools/dpaint/_data/palettes/MSX.json @@ -0,0 +1,20 @@ +{ + "type": "palette", + "palette": [ + "#000000", + "#cacaca", + "#ffffff", + "#b75e51", + "#d96459", + "#fe877c", + "#cac15e", + "#ddce85", + "#3ca042", + "#40b64a", + "#73ce7c", + "#5955df", + "#7e75f0", + "#64daee", + "#b565b3" + ] +} diff --git a/app/web-tools/dpaint/_data/palettes/TED-Plus4-C16.json b/app/web-tools/dpaint/_data/palettes/TED-Plus4-C16.json new file mode 100644 index 00000000..bbec1b21 --- /dev/null +++ b/app/web-tools/dpaint/_data/palettes/TED-Plus4-C16.json @@ -0,0 +1,124 @@ +{ + "type": "palette", + "palette": [ + "#000000", + "#202020", + "#404040", + "#606060", + "#808080", + "#9f9f9f", + "#bfbfbf", + "#dfdfdf", + "#5d0800", + "#7d2819", + "#9c4839", + "#bc6859", + "#dc8879", + "#fca899", + "#ffc8b9", + "#ffe8d9", + "#003746", + "#035766", + "#237786", + "#4397a6", + "#63b7c6", + "#82d7e6", + "#a2f7ff", + "#c2ffff", + "#5d006d", + "#7d128d", + "#9c32ac", + "#bc52cc", + "#dc71ec", + "#fc91ff", + "#ffb1ff", + "#ffd1ff", + "#004e00", + "#036e00", + "#238e13", + "#43ad33", + "#63cd53", + "#82ed72", + "#a2ff92", + "#c2ffb2", + "#20116d", + "#40318d", + "#6051ac", + "#8071cc", + "#9f90ec", + "#bfb0ff", + "#dfd0ff", + "#fff0ff", + "#202f00", + "#404f00", + "#606f13", + "#808e33", + "#9fae53", + "#bfce72", + "#dfee92", + "#ffffb2", + "#004600", + "#036619", + "#238639", + "#43a659", + "#63c679", + "#82e699", + "#a2ffb9", + "#c2ffd9", + "#5d1000", + "#7d3000", + "#9c5013", + "#bc6f33", + "#dc8f53", + "#fcaf72", + "#ffcf92", + "#ffefb2", + "#3e1f00", + "#5e3f00", + "#7e5f13", + "#9e7f33", + "#be9f53", + "#debf72", + "#fedf92", + "#fffeb2", + "#013e00", + "#215e00", + "#417e13", + "#619e33", + "#81be53", + "#a1de72", + "#c1fe92", + "#e1ffb2", + "#5d0120", + "#7d2140", + "#9c4160", + "#bc6180", + "#dc809f", + "#fca0bf", + "#ffc0df", + "#ffe0ff", + "#003f20", + "#035f40", + "#237f60", + "#439e80", + "#63be9f", + "#82debf", + "#a2fedf", + "#00306d", + "#03508d", + "#2370ac", + "#4390cc", + "#63afec", + "#82cfff", + "#a2efff", + "#3e016d", + "#5e218d", + "#7e41ac", + "#9e61cc", + "#be81ec", + "#dea1ff", + "#fec1ff", + "#ffe1ff", + "#ffffff" + ] +} diff --git a/app/web-tools/dpaint/_font/amiga-topaz.otf b/app/web-tools/dpaint/_font/amiga-topaz.otf new file mode 100644 index 00000000..0fe7231a Binary files /dev/null and b/app/web-tools/dpaint/_font/amiga-topaz.otf differ diff --git a/app/web-tools/dpaint/_font/licence.txt b/app/web-tools/dpaint/_font/licence.txt new file mode 100644 index 00000000..2e30d2c0 --- /dev/null +++ b/app/web-tools/dpaint/_font/licence.txt @@ -0,0 +1,14 @@ +The FontStruction “Amiga Topaz” +(https://fontstruct.com/fontstructions/show/675155) by Patrick H. Lauke is +licensed under a Creative Commons Attribution license +(http://creativecommons.org/licenses/by/3.0/). + +Derived from the original Topaz-8 Amiga font, included with Workbench 1.x by Bob Burns + +------- +Topaz-8 TTF Font +by Danny Amor +Freeware +https://www.fontsaddict.com/font/topaz-8.html + +Derived from the original Topaz-8 Amiga font, included with Workbench 2.x/3.x by Peter J Cherna diff --git a/app/web-tools/dpaint/_font/topaz-8.ttf b/app/web-tools/dpaint/_font/topaz-8.ttf new file mode 100644 index 00000000..9bfd1c43 Binary files /dev/null and b/app/web-tools/dpaint/_font/topaz-8.ttf differ diff --git a/app/web-tools/dpaint/_img/add.svg b/app/web-tools/dpaint/_img/add.svg new file mode 100644 index 00000000..32a75d65 --- /dev/null +++ b/app/web-tools/dpaint/_img/add.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/amigatick.png b/app/web-tools/dpaint/_img/amigatick.png new file mode 100644 index 00000000..eadde70d Binary files /dev/null and b/app/web-tools/dpaint/_img/amigatick.png differ diff --git a/app/web-tools/dpaint/_img/base.fla b/app/web-tools/dpaint/_img/base.fla new file mode 100644 index 00000000..18e3c71c Binary files /dev/null and b/app/web-tools/dpaint/_img/base.fla differ diff --git a/app/web-tools/dpaint/_img/brushes.png b/app/web-tools/dpaint/_img/brushes.png new file mode 100644 index 00000000..38f01688 Binary files /dev/null and b/app/web-tools/dpaint/_img/brushes.png differ diff --git a/app/web-tools/dpaint/_img/caret.svg b/app/web-tools/dpaint/_img/caret.svg new file mode 100644 index 00000000..fa27f004 --- /dev/null +++ b/app/web-tools/dpaint/_img/caret.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/web-tools/dpaint/_img/check.svg b/app/web-tools/dpaint/_img/check.svg new file mode 100644 index 00000000..57da18d4 --- /dev/null +++ b/app/web-tools/dpaint/_img/check.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/checkers.png b/app/web-tools/dpaint/_img/checkers.png new file mode 100644 index 00000000..dcde898e Binary files /dev/null and b/app/web-tools/dpaint/_img/checkers.png differ diff --git a/app/web-tools/dpaint/_img/circle.svg b/app/web-tools/dpaint/_img/circle.svg new file mode 100644 index 00000000..4af87e30 --- /dev/null +++ b/app/web-tools/dpaint/_img/circle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/circle_fill.svg b/app/web-tools/dpaint/_img/circle_fill.svg new file mode 100644 index 00000000..e592edb0 --- /dev/null +++ b/app/web-tools/dpaint/_img/circle_fill.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/cursors/cross.png b/app/web-tools/dpaint/_img/cursors/cross.png new file mode 100644 index 00000000..7c0d344a Binary files /dev/null and b/app/web-tools/dpaint/_img/cursors/cross.png differ diff --git a/app/web-tools/dpaint/_img/cursors/pipette.png b/app/web-tools/dpaint/_img/cursors/pipette.png new file mode 100644 index 00000000..1bf69f91 Binary files /dev/null and b/app/web-tools/dpaint/_img/cursors/pipette.png differ diff --git a/app/web-tools/dpaint/_img/cursors/rotatene.png b/app/web-tools/dpaint/_img/cursors/rotatene.png new file mode 100644 index 00000000..d8d2388b Binary files /dev/null and b/app/web-tools/dpaint/_img/cursors/rotatene.png differ diff --git a/app/web-tools/dpaint/_img/cursors/rotatenw.png b/app/web-tools/dpaint/_img/cursors/rotatenw.png new file mode 100644 index 00000000..ad3d8205 Binary files /dev/null and b/app/web-tools/dpaint/_img/cursors/rotatenw.png differ diff --git a/app/web-tools/dpaint/_img/cursors/rotatese.png b/app/web-tools/dpaint/_img/cursors/rotatese.png new file mode 100644 index 00000000..22ba561e Binary files /dev/null and b/app/web-tools/dpaint/_img/cursors/rotatese.png differ diff --git a/app/web-tools/dpaint/_img/cursors/rotatesw.png b/app/web-tools/dpaint/_img/cursors/rotatesw.png new file mode 100644 index 00000000..298ba799 Binary files /dev/null and b/app/web-tools/dpaint/_img/cursors/rotatesw.png differ diff --git a/app/web-tools/dpaint/_img/cycle.svg b/app/web-tools/dpaint/_img/cycle.svg new file mode 100644 index 00000000..7d8990fb --- /dev/null +++ b/app/web-tools/dpaint/_img/cycle.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/disk.svg b/app/web-tools/dpaint/_img/disk.svg new file mode 100644 index 00000000..a55737ac --- /dev/null +++ b/app/web-tools/dpaint/_img/disk.svg @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/download.svg b/app/web-tools/dpaint/_img/download.svg new file mode 100644 index 00000000..ecd7d2b7 --- /dev/null +++ b/app/web-tools/dpaint/_img/download.svg @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/dpaint-about.png b/app/web-tools/dpaint/_img/dpaint-about.png new file mode 100644 index 00000000..10f82914 Binary files /dev/null and b/app/web-tools/dpaint/_img/dpaint-about.png differ diff --git a/app/web-tools/dpaint/_img/dpaint-logo.png b/app/web-tools/dpaint/_img/dpaint-logo.png new file mode 100644 index 00000000..cb82ef77 Binary files /dev/null and b/app/web-tools/dpaint/_img/dpaint-logo.png differ diff --git a/app/web-tools/dpaint/_img/editcolor.svg b/app/web-tools/dpaint/_img/editcolor.svg new file mode 100644 index 00000000..a5a30695 --- /dev/null +++ b/app/web-tools/dpaint/_img/editcolor.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/eraser.svg b/app/web-tools/dpaint/_img/eraser.svg new file mode 100644 index 00000000..5092197d --- /dev/null +++ b/app/web-tools/dpaint/_img/eraser.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/eye.svg b/app/web-tools/dpaint/_img/eye.svg new file mode 100644 index 00000000..0bd83196 --- /dev/null +++ b/app/web-tools/dpaint/_img/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/web-tools/dpaint/_img/favicon.ico b/app/web-tools/dpaint/_img/favicon.ico new file mode 100644 index 00000000..b0aec845 Binary files /dev/null and b/app/web-tools/dpaint/_img/favicon.ico differ diff --git a/app/web-tools/dpaint/_img/favicon.png b/app/web-tools/dpaint/_img/favicon.png new file mode 100644 index 00000000..8c0a2f23 Binary files /dev/null and b/app/web-tools/dpaint/_img/favicon.png differ diff --git a/app/web-tools/dpaint/_img/fill.svg b/app/web-tools/dpaint/_img/fill.svg new file mode 100644 index 00000000..8a1e5b11 --- /dev/null +++ b/app/web-tools/dpaint/_img/fill.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/flask.svg b/app/web-tools/dpaint/_img/flask.svg new file mode 100644 index 00000000..7ce340dc --- /dev/null +++ b/app/web-tools/dpaint/_img/flask.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/floppy.png b/app/web-tools/dpaint/_img/floppy.png new file mode 100644 index 00000000..bc324115 Binary files /dev/null and b/app/web-tools/dpaint/_img/floppy.png differ diff --git a/app/web-tools/dpaint/_img/folder.svg b/app/web-tools/dpaint/_img/folder.svg new file mode 100644 index 00000000..e891ea11 --- /dev/null +++ b/app/web-tools/dpaint/_img/folder.svg @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/frame.png b/app/web-tools/dpaint/_img/frame.png new file mode 100644 index 00000000..22c2c3be Binary files /dev/null and b/app/web-tools/dpaint/_img/frame.png differ diff --git a/app/web-tools/dpaint/_img/fullscreen.svg b/app/web-tools/dpaint/_img/fullscreen.svg new file mode 100644 index 00000000..741fc7e4 --- /dev/null +++ b/app/web-tools/dpaint/_img/fullscreen.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/gradient.svg b/app/web-tools/dpaint/_img/gradient.svg new file mode 100644 index 00000000..565908c7 --- /dev/null +++ b/app/web-tools/dpaint/_img/gradient.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/hamburger.svg b/app/web-tools/dpaint/_img/hamburger.svg new file mode 100644 index 00000000..365fee41 --- /dev/null +++ b/app/web-tools/dpaint/_img/hamburger.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/app/web-tools/dpaint/_img/hand.svg b/app/web-tools/dpaint/_img/hand.svg new file mode 100644 index 00000000..6b344fbb --- /dev/null +++ b/app/web-tools/dpaint/_img/hand.svg @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/icon192.png b/app/web-tools/dpaint/_img/icon192.png new file mode 100644 index 00000000..630f005b Binary files /dev/null and b/app/web-tools/dpaint/_img/icon192.png differ diff --git a/app/web-tools/dpaint/_img/icon48.png b/app/web-tools/dpaint/_img/icon48.png new file mode 100644 index 00000000..60def192 Binary files /dev/null and b/app/web-tools/dpaint/_img/icon48.png differ diff --git a/app/web-tools/dpaint/_img/icon512.png b/app/web-tools/dpaint/_img/icon512.png new file mode 100644 index 00000000..74372bd3 Binary files /dev/null and b/app/web-tools/dpaint/_img/icon512.png differ diff --git a/app/web-tools/dpaint/_img/iff.png b/app/web-tools/dpaint/_img/iff.png new file mode 100644 index 00000000..8eeccdea Binary files /dev/null and b/app/web-tools/dpaint/_img/iff.png differ diff --git a/app/web-tools/dpaint/_img/image.svg b/app/web-tools/dpaint/_img/image.svg new file mode 100644 index 00000000..5983cfa8 --- /dev/null +++ b/app/web-tools/dpaint/_img/image.svg @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/web-tools/dpaint/_img/json.svg b/app/web-tools/dpaint/_img/json.svg new file mode 100644 index 00000000..1e11e98d --- /dev/null +++ b/app/web-tools/dpaint/_img/json.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/layers.svg b/app/web-tools/dpaint/_img/layers.svg new file mode 100644 index 00000000..573cab18 --- /dev/null +++ b/app/web-tools/dpaint/_img/layers.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/layers_mask.svg b/app/web-tools/dpaint/_img/layers_mask.svg new file mode 100644 index 00000000..f1a506cc --- /dev/null +++ b/app/web-tools/dpaint/_img/layers_mask.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/line.svg b/app/web-tools/dpaint/_img/line.svg new file mode 100644 index 00000000..ac9e390f --- /dev/null +++ b/app/web-tools/dpaint/_img/line.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/link.svg b/app/web-tools/dpaint/_img/link.svg new file mode 100644 index 00000000..7be4dc7e --- /dev/null +++ b/app/web-tools/dpaint/_img/link.svg @@ -0,0 +1,12 @@ + + + diff --git a/app/web-tools/dpaint/_img/lock_closed.svg b/app/web-tools/dpaint/_img/lock_closed.svg new file mode 100644 index 00000000..11c52d13 --- /dev/null +++ b/app/web-tools/dpaint/_img/lock_closed.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/lock_open.svg b/app/web-tools/dpaint/_img/lock_open.svg new file mode 100644 index 00000000..a8521d94 --- /dev/null +++ b/app/web-tools/dpaint/_img/lock_open.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/magicwand.svg b/app/web-tools/dpaint/_img/magicwand.svg new file mode 100644 index 00000000..f7a5fb3d --- /dev/null +++ b/app/web-tools/dpaint/_img/magicwand.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/mui.png b/app/web-tools/dpaint/_img/mui.png new file mode 100644 index 00000000..1dd5078f Binary files /dev/null and b/app/web-tools/dpaint/_img/mui.png differ diff --git a/app/web-tools/dpaint/_img/nofill-white.svg b/app/web-tools/dpaint/_img/nofill-white.svg new file mode 100644 index 00000000..2689bff5 --- /dev/null +++ b/app/web-tools/dpaint/_img/nofill-white.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/nofill.svg b/app/web-tools/dpaint/_img/nofill.svg new file mode 100644 index 00000000..dd58dfe9 --- /dev/null +++ b/app/web-tools/dpaint/_img/nofill.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/os3.png b/app/web-tools/dpaint/_img/os3.png new file mode 100644 index 00000000..f12608c5 Binary files /dev/null and b/app/web-tools/dpaint/_img/os3.png differ diff --git a/app/web-tools/dpaint/_img/os4.png b/app/web-tools/dpaint/_img/os4.png new file mode 100644 index 00000000..919860fc Binary files /dev/null and b/app/web-tools/dpaint/_img/os4.png differ diff --git a/app/web-tools/dpaint/_img/palette.svg b/app/web-tools/dpaint/_img/palette.svg new file mode 100644 index 00000000..75321447 --- /dev/null +++ b/app/web-tools/dpaint/_img/palette.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/patterns/cross.png b/app/web-tools/dpaint/_img/patterns/cross.png new file mode 100644 index 00000000..a6ba1133 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/cross.png differ diff --git a/app/web-tools/dpaint/_img/patterns/cross2.png b/app/web-tools/dpaint/_img/patterns/cross2.png new file mode 100644 index 00000000..b07fe420 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/cross2.png differ diff --git a/app/web-tools/dpaint/_img/patterns/dots.png b/app/web-tools/dpaint/_img/patterns/dots.png new file mode 100644 index 00000000..c55f6632 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/dots.png differ diff --git a/app/web-tools/dpaint/_img/patterns/gradient.png b/app/web-tools/dpaint/_img/patterns/gradient.png new file mode 100644 index 00000000..8cc810b9 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/gradient.png differ diff --git a/app/web-tools/dpaint/_img/patterns/grid.png b/app/web-tools/dpaint/_img/patterns/grid.png new file mode 100644 index 00000000..808c7d75 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/grid.png differ diff --git a/app/web-tools/dpaint/_img/patterns/lines_diag.png b/app/web-tools/dpaint/_img/patterns/lines_diag.png new file mode 100644 index 00000000..eeaef5da Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/lines_diag.png differ diff --git a/app/web-tools/dpaint/_img/patterns/lines_hor.png b/app/web-tools/dpaint/_img/patterns/lines_hor.png new file mode 100644 index 00000000..02d2d025 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/lines_hor.png differ diff --git a/app/web-tools/dpaint/_img/patterns/lines_ver.png b/app/web-tools/dpaint/_img/patterns/lines_ver.png new file mode 100644 index 00000000..89057585 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/lines_ver.png differ diff --git a/app/web-tools/dpaint/_img/patterns/longgrid.png b/app/web-tools/dpaint/_img/patterns/longgrid.png new file mode 100644 index 00000000..a84d50d0 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/longgrid.png differ diff --git a/app/web-tools/dpaint/_img/patterns/plus.png b/app/web-tools/dpaint/_img/patterns/plus.png new file mode 100644 index 00000000..94ab47f8 Binary files /dev/null and b/app/web-tools/dpaint/_img/patterns/plus.png differ diff --git a/app/web-tools/dpaint/_img/pencil.svg b/app/web-tools/dpaint/_img/pencil.svg new file mode 100644 index 00000000..3b2ceddc --- /dev/null +++ b/app/web-tools/dpaint/_img/pencil.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/pipette.svg b/app/web-tools/dpaint/_img/pipette.svg new file mode 100644 index 00000000..75e8b22b --- /dev/null +++ b/app/web-tools/dpaint/_img/pipette.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/pipette_white.svg b/app/web-tools/dpaint/_img/pipette_white.svg new file mode 100644 index 00000000..972b6c43 --- /dev/null +++ b/app/web-tools/dpaint/_img/pipette_white.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/pixelgrid.svg b/app/web-tools/dpaint/_img/pixelgrid.svg new file mode 100644 index 00000000..29234e56 --- /dev/null +++ b/app/web-tools/dpaint/_img/pixelgrid.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/png.svg b/app/web-tools/dpaint/_img/png.svg new file mode 100644 index 00000000..7818c060 --- /dev/null +++ b/app/web-tools/dpaint/_img/png.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/poly.svg b/app/web-tools/dpaint/_img/poly.svg new file mode 100644 index 00000000..98d053e6 --- /dev/null +++ b/app/web-tools/dpaint/_img/poly.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/psd.svg b/app/web-tools/dpaint/_img/psd.svg new file mode 100644 index 00000000..aac61abc --- /dev/null +++ b/app/web-tools/dpaint/_img/psd.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/redo.svg b/app/web-tools/dpaint/_img/redo.svg new file mode 100644 index 00000000..73466fe8 --- /dev/null +++ b/app/web-tools/dpaint/_img/redo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/rotate1.svg b/app/web-tools/dpaint/_img/rotate1.svg new file mode 100644 index 00000000..ba38cf14 --- /dev/null +++ b/app/web-tools/dpaint/_img/rotate1.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/select.svg b/app/web-tools/dpaint/_img/select.svg new file mode 100644 index 00000000..9619b446 --- /dev/null +++ b/app/web-tools/dpaint/_img/select.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/smudge.svg b/app/web-tools/dpaint/_img/smudge.svg new file mode 100644 index 00000000..55a5e346 --- /dev/null +++ b/app/web-tools/dpaint/_img/smudge.svg @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/split.svg b/app/web-tools/dpaint/_img/split.svg new file mode 100644 index 00000000..f74c3948 --- /dev/null +++ b/app/web-tools/dpaint/_img/split.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/spray.svg b/app/web-tools/dpaint/_img/spray.svg new file mode 100644 index 00000000..d8f8b8c5 --- /dev/null +++ b/app/web-tools/dpaint/_img/spray.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/square.svg b/app/web-tools/dpaint/_img/square.svg new file mode 100644 index 00000000..a5175ccf --- /dev/null +++ b/app/web-tools/dpaint/_img/square.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/square_fill.svg b/app/web-tools/dpaint/_img/square_fill.svg new file mode 100644 index 00000000..a8670e17 --- /dev/null +++ b/app/web-tools/dpaint/_img/square_fill.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/stamp.svg b/app/web-tools/dpaint/_img/stamp.svg new file mode 100644 index 00000000..172d2901 --- /dev/null +++ b/app/web-tools/dpaint/_img/stamp.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/swap.svg b/app/web-tools/dpaint/_img/swap.svg new file mode 100644 index 00000000..f0c40351 --- /dev/null +++ b/app/web-tools/dpaint/_img/swap.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/text.svg b/app/web-tools/dpaint/_img/text.svg new file mode 100644 index 00000000..4a86cdd0 --- /dev/null +++ b/app/web-tools/dpaint/_img/text.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/trashcan.svg b/app/web-tools/dpaint/_img/trashcan.svg new file mode 100644 index 00000000..3721fa9d --- /dev/null +++ b/app/web-tools/dpaint/_img/trashcan.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/ui.png b/app/web-tools/dpaint/_img/ui.png new file mode 100644 index 00000000..79374c44 Binary files /dev/null and b/app/web-tools/dpaint/_img/ui.png differ diff --git a/app/web-tools/dpaint/_img/undo.svg b/app/web-tools/dpaint/_img/undo.svg new file mode 100644 index 00000000..d51b8802 --- /dev/null +++ b/app/web-tools/dpaint/_img/undo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/warning.svg b/app/web-tools/dpaint/_img/warning.svg new file mode 100644 index 00000000..31e478db --- /dev/null +++ b/app/web-tools/dpaint/_img/warning.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/zoom.svg b/app/web-tools/dpaint/_img/zoom.svg new file mode 100644 index 00000000..94d895f8 --- /dev/null +++ b/app/web-tools/dpaint/_img/zoom.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_img/zoomout.svg b/app/web-tools/dpaint/_img/zoomout.svg new file mode 100644 index 00000000..1fc18667 --- /dev/null +++ b/app/web-tools/dpaint/_img/zoomout.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/web-tools/dpaint/_script/alchemy/displace.js b/app/web-tools/dpaint/_script/alchemy/displace.js new file mode 100644 index 00000000..32cb38df --- /dev/null +++ b/app/web-tools/dpaint/_script/alchemy/displace.js @@ -0,0 +1,57 @@ +let process = function(source,target){ + let expose = { + colsWidth:{min:0,max:32,value:0}, + rowWidth:{min:0,max:32,value:2}, + horizontalShift:{min:-10,max:10,value:-1}, + verticalShift:{min:-10,max:10,value:0}, + rippleSpeed:{min:0,max:100,value:0}, + rippleSize:{min:-50,max:50,value:0}, + + }; + + let w = source.width; + let h = source.height; + target.clearRect(0,0,target.canvas.width,target.canvas.height); + + if (expose.colsWidth.value){ + let cols = Math.ceil(w/expose.colsWidth.value); + for (let i=0;ioutline(target,color)); + } + + if (mode==="COLOR"){ + colors=[ + [255,255,255], + [239, 231, 20], + [221, 187, 68], + [221, 187, 68,255] + ] + colors.forEach(color=>outline(target,color)); + } + + function outline(ctx,color,dotted){ + let w = ctx.canvas.width; + let h = ctx.canvas.height; + let data = ctx.getImageData(0,0,w,h); + let d = data.data; + let target = []; + + if (color.length>3) dotted=true; + + function checkPixel(index){ + if (d[index+3] === 0){ + target.push(index); + } + } + + for (let y=1; y{ + d[index] = color[0]; + d[index+1] = color[1]; + d[index+2] = color[2]; + d[index+3] = 255; + + if (dotted){ + let x = (index/4)%w; + let y = Math.floor((index/4)/w); + if (x%2){ + d[index+3] = y%2?0:255; + }else{ + d[index+3] = y%2?255:0; + } + } + }); + target=[]; + ctx.putImageData(data,0,0); + }else{ + return target; + } + + } + +} + +export default process; diff --git a/app/web-tools/dpaint/_script/alchemy/lines.js b/app/web-tools/dpaint/_script/alchemy/lines.js new file mode 100644 index 00000000..7f9a4f0f --- /dev/null +++ b/app/web-tools/dpaint/_script/alchemy/lines.js @@ -0,0 +1,59 @@ +let process = function(source,target){ + let expose = { + lineCount:{min:1,max:1000,value:500}, + curve:{min:0,max:50,value:0}, + horizontalShift:{min:5,max:100,value:35}, + verticalShift:{min:-50,max:50,value:2}, + }; + + let w = source.width; + let h = source.height; + target.clearRect(0,0,target.canvas.width,target.canvas.height); + target.drawImage(source,0,0); + + function lineSet(color,alpha){ + let x = Math.floor(Math.random()*w)-expose.horizontalShift.value/2+5; + let y = Math.floor(Math.random()*h); + + let x_offset = expose.horizontalShift.value + Math.random()*10; + let y_offset = -(expose.verticalShift.value) + Math.random()*2; + + if (expose.curve.value){ + let hx = (w/2-x)/2; + let hy = y/h; + + y_offset = Math.random()*hx * hy * expose.curve.value/10; + y_offset = Math.round(y_offset); + } + + let dist = 4; + for (let i=0;i<4;i++){ + let d = dist*i; + line(x,y+d,x+x_offset,y+y_offset+d,toRGBA(color,alpha/(1<=0 && y>=0) { + target.drawImage(this.element, -(x-1), -(y-1)); + } + } + } + target.globalAlpha = 1; + } + + function smoove(color,alpha){ + let x = Math.floor(Math.random()*w); + let y = Math.floor(Math.random()*h); + + let x_len = 35 + Math.random()*10; + let y_len = -2 + Math.random()*2; + + let dist = 4; + for (let i=0;i<4;i++){ + let d = dist*i; + line(x,y+d,x+x_len,y+y_len+d,toRGBA(color,alpha/(1<0 && y>0 && x= 0 && scy < h && scx >= 0 && scx < w) { + srcOff = (scy * w + scx) * 4; + wt = weights[cy * katet + cx]; + + r += srcBuff[srcOff] * wt; + g += srcBuff[srcOff + 1] * wt; + b += srcBuff[srcOff + 2] * wt; + a += srcBuff[srcOff + 3] * wt; + } + } + } + + dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix); + dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix); + dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix); + dstBuff[dstOff + 3] = srcBuff[dstOff + 3]; + } else { + dstBuff[dstOff] = srcBuff[dstOff]; + dstBuff[dstOff + 1] = srcBuff[dstOff + 1]; + dstBuff[dstOff + 2] = srcBuff[dstOff + 2]; + dstBuff[dstOff + 3] = srcBuff[dstOff + 3]; + } + } + } + + ctx.putImageData(dstData, 0, 0); + } + +} + +export default process; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/alchemy/web.js b/app/web-tools/dpaint/_script/alchemy/web.js new file mode 100644 index 00000000..dd3a5b50 --- /dev/null +++ b/app/web-tools/dpaint/_script/alchemy/web.js @@ -0,0 +1,62 @@ +let process = function(source,target){ + let expose = { + starPointCount:{min:1,max:100,value:10}, + starSize:{min:1,max:100,value:25}, + sizeVariation:{min:1,max:1000,value:100}, + dabbleCount:{min:1,max:1000,value:200}, + transparency:{min:0,max:100,value:5} + }; + + let ctx=source.getContext("2d"); + let w = source.width; + let h = source.height; + target.clearRect(0,0,target.canvas.width,target.canvas.height); + target.drawImage(source,0,0); + + let transparency = expose.transparency.value/100; + + let data = ctx.getImageData(0,0,w,h); + let d = data.data; + + for (let i = 0; i{ + setTimeout(()=>{ + if (urlParams.has("play")){ + EventBus.trigger(COMMAND.CYCLEPALETTE); + } + if (urlParams.has("zoom")){ + EventBus.trigger(COMMAND.ZOOMFIT); + } + if (urlParams.has("palette")){ + EventBus.trigger(COMMAND.PALETTEFROMIMAGE); + } + },200); + }).catch((err)=>{}); + } + }else{ + // show about dialog on first run + if (window.localStorage.getItem("dp_about")!=="true"){ + setTimeout(()=>{ + EventBus.trigger(COMMAND.ABOUT); + window.localStorage.setItem("dp_about","true"); + },200); + } + + + if (urlParams.has("gallery")){ + setTimeout(()=>{ + EventBus.trigger(COMMAND.TOGGLEGALLERY,true); + },200); + + } + + // check for local autoSave + ImageFile.restoreAutoSave(); + } + + if (window.self !== window.top && window.parent){ + // we are running in an iframe, let's see if we can contact the host + console.log("contacting host"); + import("./host/host.js").then(host=>{ + window.host = host.default; + window.host.init(); + }); + } + + EventBus.on(COMMAND.OPEN,function(){ + ImageFile.openLocal(); + }) + + EventBus.on(COMMAND.LOADPALETTE,()=>{ + Palette.openLocal(); + }) + + EventBus.on(COMMAND.LOADBRUSH,()=>{ + Brush.openLocal(); + }) + + EventBus.on(COMMAND.ADF,()=>{ + var input = document.createElement('input'); + input.type = 'file'; + input.onchange = function(e){ + let files = e.target.files; + import("./ui/components/fileBrowser.js").then(FileBrowser=>{ + FileBrowser.default.openAdf(files); + }); + }; + input.click(); + }); + + EventBus.on(COMMAND.DELUXE,()=>{ + import("./ui/components/uae.js").then(UAE=>{ + UAE.default.preview(); + }); + }); + + EventBus.on(COMMAND.TOGGLEGALLERY,(andOpen)=>{ + import("./ui/components/gallery.js").then(Gallery=>{ + Gallery.default.toggle(andOpen); + }); + }); + + EventBus.on(COMMAND.ABOUT,()=>{ + Modal.show(DIALOG.ABOUT,me.version); + }) + + EventBus.on(COMMAND.FULLSCREEN,()=>{ + let elm = document.body; + if (!elm) return; + if (elm.requestFullscreen) { + elm.requestFullscreen().catch( + (err)=>{ + console.error("fullscreen failed"); + console.error(err); + } + ); + } else if (elm.webkitRequestFullscreen) { /* Safari */ + elm.webkitRequestFullscreen(); + } + }) + + EventBus.on(COMMAND.TOGGLEOVERRIDE,()=>{ + window.override = !window.override; + document.body.classList.toggle("override",window.override); + EventBus.trigger(EVENT.layersChanged); + }); + + } + + window.addEventListener('DOMContentLoaded', (event) => { + if (window.location.protocol==="http:" && window.location.hostname.indexOf("stef.be")>=0){ + window.location.href = window.location.href.replace("http:","https:"); + }else{ + me.init(); + } + }); + + + // prevent pinch-zoom for iOS Safari + if (window.GestureEvent) { + document.documentElement.addEventListener('gesturestart', (e)=>{e.preventDefault()}, {passive: false, capture:true}); + } + +/* + window.test = function(){ + let canvas = ImageFile.getCanvas(); + let ctx = canvas.getContext("2d"); + + let imagaDate = canvas.getContext("2d").getImageData(0,0,canvas.width,canvas.height); + + let transformed = bayer(imagaDate,128); + //let transformed = atkinson(imagaDate); + ctx.putImageData(transformed,0,0); + + + //effects.outline(ctx,[0,0,0]); + //effects.feather(ctx,-1); + + //StackBlur.canvasRGBA(canvas,0 ,0,canvas.width,canvas.height,10); + EventBus.trigger(EVENT.layerContentChanged); + + + } + + + function bayer(image, threshold) { + const thresholdMap = [ + [15, 135, 45, 165], + [195, 75, 225, 105], + [60, 180, 30, 150], + [240, 120, 210, 90], + ]; + + for (let i = 0; i < image.data.length; i += 4) { + const luminance = (image.data[i] * 0.299) + (image.data[i + 1] * 0.587) + (image.data[i + 2] * 0.114); + + const x = i / 4 % image.width; + const y = Math.floor(i / 4 / image.width); + const map = Math.floor((luminance + thresholdMap[x % 4][y % 4]) / 2); + //console.error(map); + let value = map < threshold ? 0 : 255; + image.data.fill(value, i, i + 3); + } + + return image; + } + + function floydsteinberg(image) { + const width = image.width; + const luminance = new Uint8ClampedArray(image.width * image.height); + + for (let l = 0, i = 0; i < image.data.length; l++, i += 4) { + luminance[l] = (image.data[i] * 0.299) + (image.data[i + 1] * 0.587) + (image.data[i + 2] * 0.114); + } + + for (let l = 0, i = 0; i < image.data.length; l++, i += 4) { + const value = luminance[l] < 129 ? 0 : 255; + const error = Math.floor((luminance[l] - value) / 16); + image.data.fill(value, i, i + 3); + + luminance[l + 1] += error * 7; + luminance[l + width - 1] += error * 3; + luminance[l + width] += error * 5; + luminance[l + width + 1] += error * 1; + } + + return image; + } + + function atkinson(image) { + const width = image.width; + const luminance = new Uint8ClampedArray(image.width * image.height); + + for (let l = 0, i = 0; i < image.data.length; l++, i += 4) { + luminance[l] = (image.data[i] * 0.299) + (image.data[i + 1] * 0.587) + (image.data[i + 2] * 0.114); + } + + for (let l = 0, i = 0; i < image.data.length; l++, i += 4) { + const value = luminance[l] < 129 ? 0 : 255; + const error = Math.floor((luminance[l] - value) / 8); + image.data.fill(value, i, i + 3); + + luminance[l + 1] += error; + luminance[l + 2] += error; + luminance[l + width - 1] += error; + luminance[l + width] += error; + luminance[l + width + 1] += error; + luminance[l + 2 * width] += error; + } + + return image; + } + + window.rot = function(){ + import("./paintTools/rotSprite.js").then(async rotSprite=>{ + let canvas = ImageFile.getActiveLayer().getCanvas(); + let c = duplicateCanvas(canvas,true); + let result = await rotSprite.default(c,90); + console.log(result); + console.error("ok"); + }); + } + */ + + return me; +}(); + diff --git a/app/web-tools/dpaint/_script/enum.js b/app/web-tools/dpaint/_script/enum.js new file mode 100644 index 00000000..3bb42ead --- /dev/null +++ b/app/web-tools/dpaint/_script/enum.js @@ -0,0 +1,152 @@ +export let COMMAND = { + NEW: 1001, + OPEN: 1002, + LINE: 1003, + SQUARE: 1004, + ZOOMIN: 1005, + ZOOMOUT: 1006, + SELECT: 1007, + DRAW: 1008, + SPLITSCREEN: 1009, + UNDO: 1010, + REDO: 1011, + STAMP: 1012, + TOLAYER: 1013, + CLEARSELECTION: 1014, + ERASEELECTION: 1015, + NEWLAYER: 1016, + SAVE: 1017, + PALETTEFROMIMAGE: 1018, + PALETTEREDUCE: 1019, + ROTATE: 1020, + CLEAR: 1021, + CROP: 1022, + INFO: 1023, + ERASE: 1024, + DELETELAYER: 1025, + CIRCLE: 1026, + TRIM: 1027, + TRANSFORMLAYER: 1028, + ADDFRAME: 1029, + DELETEFRAME: 1030, + IMPORTLAYER: 1031, + RESIZE: 1032, + RESAMPLE: 1033, + EFFECTS: 1034, + GRADIENT: 1035, + DUPLICATELAYER: 1036, + COLORMASK: 1037, + EDITPALETTE: 1038, + MERGEDOWN: 1039, + FLATTEN: 1040, + LAYERUP: 1041, + LAYERDOWN: 1042, + SAVEPALETTE: 1043, + LOADPALETTE: 1044, + LAYERMASK: 1045, + LAYERMASKHIDE: 1046, + DELETELAYERMASK: 1047, + APPLYLAYERMASK: 1048, + POLYGONSELECT: 1049, + ENDPOLYGONSELECT: 1050, + TOSELECTION: 1051, + CUTTOLAYER: 1052, + SWAPCOLORS: 1053, + ABOUT: 1054, + FLOODSELECT: 1055, + FLOOD: 1056, + SELECTALL: 1057, + COPY: 1058, + PASTE: 1059, + FULLSCREEN: 1060, + PAN: 1061, + COLORPICKER: 1062, + TOGGLEPALETTES: 1063, + ADF: 1064, + DELUXE: 1065, + SAVEDISK: 1066, + SAVEGENERIC: 1067, + SAVEFILETOADF: 1068, + DUPLICATEFRAME: 1069, + SMUDGE: 1070, + CYCLEPALETTE: 1071, + LOCKPALETTE: 1072, + FLIPHORIZONTAL: 1073, + FLIPVERTICAL: 1074, + PRESENTATION: 1075, + TOGGLEGRID: 1076, + TOGGLEMASK: 1077, + TOGGLEOVERRIDE: 1078, + TOGGLEINVERT: 1079, + ZOOMFIT: 1080, + COLORSELECT: 1081, + ALPHASELECT: 1082, + TOGGLESIDEPANEL: 1083, + TOGGLEGALLERY: 1084, + INITSELECTION: 1085, + DISABLELAYERMASK: 1086, + ENABLELAYERMASK: 1087, + TOGGLEDITHER: 1088, + SPRAY: 1089, + TEXT: 1090, + BRUSHROTATERIGHT: 1091, + BRUSHROTATELEFT: 1092, + BRUSHFLIPHORIZONTAL: 1093, + BRUSHFLIPVERTICAL: 1094, + SAVEBRUSH: 1095, + LOADBRUSH: 1096, + TOGGLEPIXELGRID: 1097, + COLORDEPTH24: 1098, + COLORDEPTH12: 1099, + COLORDEPTH9: 1100, + ADDPALETTE: 1101, + NEXTPALETTE: 1102, + PREVPALETTE: 1103 + +}; + +export let EVENT = { + drawColorChanged: 1, + backgroundColorChanged: 2, + drawCanvasOverlay: 3, + hideCanvasOverlay: 4, + imageContentChanged: 5, + imageSizeChanged: 6, + layerContentChanged: 7, + selectionChanged: 8, + sizerChanged: 9, + toolDeActivated: 10, + layersChanged: 11, + modifierKeyChanged: 12, + toolChanged: 13, + toolOptionsChanged: 14, + brushOptionsChanged: 15, + colorCount: 16, + layerContentHistory: 17, + imageHistory: 18, + selectionHistory: 19, + paletteHistory: 20, + historyChanged: 21, + endPolygonSelect: 22, + framesChanged: 23, + UIresize: 24, + paletteChanged: 25, + colorCycleChanged: 26, + colorCycleToggled: 27, + colorRangeChanged: 28, + colorRangesChanged: 29, + gridOptionsChanged: 30, + layerPropertyHistory: 31, + layerHistory: 32, + sizerStartChange: 33, + panelResized: 34, + fontStyleChanged: 35, + colorDepthChanged: 36, + previewModeChanged: 37, +}; + +export let ANIMATION = { + CYCLE: 1, + SPRAY: 2, + TEXT: 3, +} diff --git a/app/web-tools/dpaint/_script/fileformats/adf.js b/app/web-tools/dpaint/_script/fileformats/adf.js new file mode 100644 index 00000000..bcab651c --- /dev/null +++ b/app/web-tools/dpaint/_script/fileformats/adf.js @@ -0,0 +1,1190 @@ +/* + Amiga filesystem implementation in javascript. + + Read and write files and folders from/to Amiga Disk Format files. + Currently only standard Amiga DD disks are supported. + + I once reverse engineered it from scratch for my Emerald Mine Level editor at http://www.steffest.com/DXboulder/ + But since I lost that source code (no GIT in 1995) now I took the info from http://lclevy.free.fr/adflib/adf_info.html + Thanks Laurent Clevy. + + OFS and FFS are supported + + There are some sane limitations in place to keep things performant: + - max size for a single file is 20MB + - max size for a hardfile is about 2GB, depending on the browser + + MIT License + + Copyright (c) 2019-2023 Steffest - dev@stef.be + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ + +import BinaryStream from "../util/binarystream.js"; + +var ADF = function(){ + var me = {}; + var disk; + + var SectorSize = 512; // the size in bytes of one sector; + var SectorCount = 1760; + var rootSector = SectorCount/2; + + me.loadDisk = function(url,next){ + + var onLoad = function(buffer){ + disk = BinaryStream(buffer,true); + + // standard DD disk that can store 880kb have a disk.length == 901120 + // those disks have 1760 sectors of 512 bytes each + + var passed = false; + + SectorCount = disk.length/SectorSize; + if ((parseInt(SectorCount) === SectorCount) && SectorCount%2 === 0){ + rootSector = SectorCount/2; + var info = me.getInfo(); + console.log(info); + if (info.diskFormat === "DOS"){ + passed = true; + } + } + + if (passed){ + if (next) next(true); + }else{ + console.error("this does not seem to be an uncompressed ADF file"); + if (next) next(false,disk); + } + }; + + if (typeof url == "string"){ + //loadFile(url,function(buffer){ + // onLoad(buffer); + //}); + onLoad(url); + } + + if (typeof url == "object"){ + onLoad(url); + } + + }; + + me.setDisk = function(_disk){ + disk = _disk; + return me.getInfo(); + }; + + me.getInfo = function(){ + disk.goto(0); + + var info = {}; + info.diskFormat = disk.readString(3); + var diskType = disk.readUbyte(); + info.diskType = (diskType%2) == 0 ? "OFS" : 'FFS'; + + // read rootblock + disk.goto(rootSector * SectorSize); + info.inforootBlockType = disk.readLong(); + if (info.inforootBlockType !== 2){ + info.diskType = "UNKNOWN"; + info.diskFormat = "UNKNOWN"; + } + + disk.goto((rootSector * SectorSize) + SectorSize - 80); + var nameLength = disk.readUbyte(); + info.label = disk.readString(nameLength); + + disk.info = info; + + if (!disk.bitmap){ + me.getFreeSize(); + } + + return info; + }; + + me.isFFS = function(){ + return disk.info.diskType === "FFS"; + }; + + me.getSectorType = function(sector){ + if (sector === 0) return "BOOTBLOCK"; + if (sector === rootSector) return "ROOTBLOCK"; + if (disk.bitmapBlocks.indexOf(sector)) return "BITMAP BLOCK"; + + disk.goto(sector * SectorSize); + var long = disk.readLong(); + if (long === 2) return "HEADER"; + if (long === 8) return "DATA BLOCK"; + if (long === 16) return "LIST (File extension block)"; + if (long === 33) return "DIRCACHE (Directory cache block)"; + + return "EMPTY (or this is not a DOS disk)" + }; + + me.readFileAtSector = function(sector,includeContent){ + var file = readHeaderBlock(sector); + file.sector = sector; + + if (includeContent){ + file.content = new Uint8Array(file.size); + var index = 0; + + // there are 2 ways to read a file in OFS: + // 1 is to read the list of datablock pointers and collect each datablock + // 2 is to follow the linked list of datablocks + + // the second one seems somewhat easier to implement + // because otherwise we have to collect each extension block first + var block = file; + if (me.isFFS()){ + var sectors = block.pointers.slice().reverse(); + + // let's set a sane max file size of 20MB + while (block.dataBlockExtension && sectors.length<40960){ + block = readExtensionBlock(block.dataBlockExtension); + sectors = sectors.concat(block.pointers.slice().reverse()); + console.log("appending block"); + } + var maxSize = file.size; + + sectors.forEach(function(fileSector){ + if (fileSector){ + block = readDataBlock(fileSector,maxSize); + file.content.set(block.content,index); + index += block.dataSize; + maxSize -= block.dataSize; + } + }); + }else{ + + var nextBlock = block.firstDataBlock; + while (nextBlock !== 0){ + block = readDataBlock(nextBlock); + file.content.set(block.content,index); + index += block.dataSize; + nextBlock = block.nextDataBlock; + } + } + + } + + return file; + }; + + me.readFolderAtSector = function(sector){ + //console.error("readFolderAtSector " + sector); + + var directory = readHeaderBlock(sector); + directory.folders = []; + directory.files = []; + directory.sector = sector; + + // NOTE: block.pointers DO NOT hold the list of all files + // the index in de pointerslist is determined by the name of the item + // multiple files/folders with the same name are linked to each other + var entries = []; + directory.pointers.forEach(function(sector){ + if (sector){ + entries.push({ + sector: sector, + name: getFileNameAtSector(sector), + typeString: getFileTypeAtSector(sector) + }) + } + }); + + // NOTE: entries.length may change in the loop if we find chained files + for (var i = 0; i< entries.length; i++){ + var entry = entries[i]; + + if (entry.typeString == "FILE"){ // TODO: can files only be linked to blocks? + var file = me.readFileAtSector(entry.sector,false); + directory.files.push(file); + if (file.linkedSector) entries.push( + { + sector: file.linkedSector, + name: getFileNameAtSector(file.linkedSector), + typeString: getFileTypeAtSector(file.linkedSector) + } + ); + }else{ + directory.folders.push(entry); + var folderHeader = readHeaderBlock(entry.sector); + if (folderHeader.linkedSector) entries.push( + { + sector: folderHeader.linkedSector, + name: getFileNameAtSector(folderHeader.linkedSector), + typeString: getFileTypeAtSector(folderHeader.linkedSector) + } + ); + } + } + + return directory; + }; + + me.deleteFileAtSector = function(sector){ + + console.log("delete File At Sector " + sector); + + var fileHeaderBlock = readHeaderBlock(sector); + var folderSector = fileHeaderBlock.parent; + var linkedFile = fileHeaderBlock.linkedSector; + var i,max; + + // remove file from Folder; + var folderHeaderBlock = readHeaderBlock(folderSector); + + var index = getNameHashIndex(fileHeaderBlock.name); + // can we always rely on the file being linked at the nameHash position on the hashtable? + // seems so - otherwise the disk in invalid. + + var hashTableItem = folderHeaderBlock.pointers[index]; + + if (hashTableItem === sector){ + console.log("file found in main pointer list"); + if (linkedFile){ + // link this file to the main index list + folderHeaderBlock.pointers[index] = linkedFile; + }else{ + // remove file from hash table + folderHeaderBlock.pointers[index] = 0; + } + //rewrite folderHeader + writeHeaderBlock(folderSector,folderHeaderBlock); + }else{ + console.log("file not found, checking linked lists"); + + while (hashTableItem){ + var linkedHeaderBlock = me.readHeaderBlock(hashTableItem); + if (linkedHeaderBlock.linkedSector === sector){ + console.log("file found, linked to " + hashTableItem); + if (linkedFile){ + // link this file to the current file + linkedHeaderBlock.linkedSector = linkedFile; + }else{ + // deleted file was last in the link list + linkedHeaderBlock.linkedSector = 0; + } + writeHeaderBlock(hashTableItem,linkedHeaderBlock); + break; + }else{ + hashTableItem = linkedHeaderBlock.linkedSector; + } + } + } + + var sectors=[sector]; + sectors = sectors.concat(fileHeaderBlock.pointers); + var block = fileHeaderBlock; + while (block.dataBlockExtension && sectors.length<2000){ + block = readExtensionBlock(block.dataBlockExtension); + sectors = sectors.concat(block.pointers); + } + + // clear file blocks + sectors.forEach(function(index){ + if (index){ + //clearSector(index); // actually this is not really needed, maybe skip if this hurts performance + //console.error(index); + disk.bitmap[index] = 1; + } + }); + + // update bitmap + // TODO: with HDF, how do we know which bitmapblock to use? + writeBitmapBlock(disk.bitmapBlocks[0],disk.bitmap); + + console.log("File deleted"); + + }; + + me.deleteFolderAtSector = function(sector){ + + console.log("delete folder At sector " + sector); + // note: the folder needs to be empty before we can delete it + // recursive delete not yet implemented + + var folderHeaderBlock = readHeaderBlock(sector); + var parentSector = folderHeaderBlock.parent; + var linkedFile = folderHeaderBlock.linkedSector; + var i,max; + + // check if folder is empty + var isEmpty = true; + for (i=0,max=folderHeaderBlock.pointers.length;i { + if (file.name === name){ + console.warn("File already exists"); + // TODO Delete existing file ? + } + }); + + + // check if it will fit + var freeBlocks = getFreeBlocks(); + + var fileSize = data.length; + var dataBlockSize = me.isFFS() ? SectorSize : SectorSize-24; + var dataBlockCount = Math.ceil(fileSize/dataBlockSize); + var headerBlockCount = Math.ceil(dataBlockCount/72); + + var totalBlocks = dataBlockCount + headerBlockCount; + + console.log("File wil need " + totalBlocks + " blocks"); + + if (totalBlocks>freeBlocks){ + console.error("Not enough space on device"); + return false; + } + + // create file, starting with the main header block + var sector = getEmptyBlock(); + clearSector(sector); + var header = createFileHeaderBlock(sector,name,data,folder); + disk.bitmap[sector] = 0; // mark as used + + var headerBlocks = [header]; + if (headerBlockCount>1){ + console.log("Creating " + (headerBlockCount-1) + " Extension blocks"); + for (i = 1; i50 MB only + + disk.goto((sector * SectorSize) + SectorSize - 40); + block.lastDiskChangeDays = disk.readLong(); // days since 1 jan 78 + block.lastDiskChangeMinutes = disk.readLong(); // minutes pas midnight + block.lastDiskChangeTicks = disk.readLong(); // in 1/50s of a seconds, past lastt minute + + block.parent = 0; + block.typeString = "ROOT"; + + }else{ + disk.goto((sector * SectorSize) + SectorSize - 188); + block.size = disk.readLong(); // filesize for files, not used for folders + var dataLength = disk.readUbyte(); + block.comment = dataLength ? disk.readString(dataLength) : ""; + + + disk.goto((sector * SectorSize) + SectorSize - 16); + block.linkedSector = disk.readLong(); // sector of entry in the same folder + block.parent = disk.readLong(); + block.dataBlockExtension = disk.readLong(); + block.typeString = disk.readLong() == 4294967293 ? "FILE" : "DIR"; + // 4294967293 == -3 , should we read as signed ? + // this value is 2 for folders and 1 for the root folder + } + + + disk.goto((sector * SectorSize) + SectorSize - 92); + block.lastChangeDays = disk.readLong(); // days since 1 jan 78 + block.lastChangeMinutes = disk.readLong(); // minutes pas midnight + block.lastChangeTicks = disk.readLong(); // in 1/50s of a seconds, past lastt minute + + dataLength = disk.readUbyte(); + block.name = dataLength ? disk.readString(dataLength) : ""; // max 30 + + return block; + } + + me.rewriteBlock = function(sector){ + var block = readHeaderBlock(sector); + writeHeaderBlock(sector,block); + var newBlock = readHeaderBlock(sector); + }; + + function writeHeaderBlock(sector,block){ + disk.goto(sector * SectorSize); + + disk.writeUint(2); // ID for header block + disk.writeUint(block.typeString === "ROOT" ? 0 : sector); // self pointer, should be the same as the initial sector, unused for root block + disk.writeUint(block.DataBlockCount || 0); // the amount of datablocks for files, unused for folders + disk.writeUint(block.dataSize || 0); + disk.writeUint(block.firstDataBlock || 0); // should be the same as the first block in the dataBlock List for files, not used for folders + disk.writeUint(0); // Checksum, this will be calculated later + + for (var i = 0; i< 72; i++){ + disk.writeUint(block.pointers[i] || 0); + } + + var blockTypeId = 2; // folder + + if (block.typeString === "ROOT"){ + blockTypeId = 1; + + disk.goto((sector * SectorSize) + SectorSize - 200); + disk.writeUint(block.bm_flag); + for (i = 0; i<25; i++){ + disk.writeUint(block.bitmapBlocks[i] || 0); + } + + disk.goto((sector * SectorSize) + SectorSize - 96); + disk.writeUint(block.bitmap_ext); + + disk.goto((sector * SectorSize) + SectorSize - 40); + disk.writeUint(block.lastDiskChangeDays); // days since 1 jan 78 + disk.writeUint(block.lastDiskChangeMinutes); + disk.writeUint(block.lastDiskChangeTicks); + + }else{ + + if (block.typeString === "FILE"){ + blockTypeId = 4294967293; // -3 + } + + disk.goto((sector * SectorSize) + SectorSize - 188); + disk.writeUint(block.size || 0); // filesize for files, not used for folders + + if (block.comment){ + disk.writeUbyte(block.comment.length); + disk.writeString(block.comment); // max 79 chars + }else{ + disk.writeUbyte(0); + } + + + disk.goto((sector * SectorSize) + SectorSize - 16); + disk.writeUint(block.linkedSector); + disk.writeUint(block.parent); + disk.writeUint(block.dataBlockExtension); + } + + disk.goto((sector * SectorSize) + SectorSize - 92); + disk.writeUint(block.lastChangeDays); // days since 1 jan 78 + disk.writeUint(block.lastChangeMinutes); + disk.writeUint(block.lastChangeTicks); + + if (block.name){ + disk.writeUbyte(block.name.length); + disk.writeString(block.name); // TODO max length of name? + }else{ + disk.writeUbyte(0); + } + + disk.goto((sector * SectorSize) + SectorSize - 4); + disk.writeUint(blockTypeId); + + // update checksum + block.checkSum = calculateChecksum(sector); + disk.goto(sector * SectorSize + 20); + disk.writeUint(block.checkSum); + + return block; + } + + function createFileHeaderBlock(sector,name,data,folder){ + + var dataBlockSize = me.isFFS() ? SectorSize : SectorSize-24; + var dataBlockCount = Math.ceil(data.length/dataBlockSize); + + var header = { + type: 2, + typeString: "FILE", + sector: sector, + pointers: [], + size: data.length, + linkedSector: 0, + parent:folder, + dataBlockExtension:0, + DataBlockCount:dataBlockCount, + dataSize:0, + firstDataBlock:0, + name: name + }; + for (var i=0;i<72;i++){header.pointers[i] = 0} + + return header; + } + + function createFolderHeaderBlock(sector,name,folder){ + var header = { + type: 2, + typeString: "DIR", + sector: sector, + pointers: [], + linkedSector: 0, + parent:folder, + dataBlockExtension:0, // FFS Directory cache block + name: name + }; + for (var i=0;i<72;i++){header.pointers[i] = 0} + + return header; + } + + function readExtensionBlock(sector){ + var block = {}; + disk.goto(sector * SectorSize); + block.type = disk.readLong(); // should be 16 for LIST block + + block.headerSector = disk.readLong(); + block.DataBlockCount = disk.readLong(); + + disk.goto(sector * SectorSize + 20); + block.checkSum = disk.readLong(); + + disk.goto(sector * SectorSize + 24); + block.pointers = []; + for (var i = 0; i< 72; i++){ + block.pointers.push(disk.readLong() || 0); + } + disk.goto((sector * SectorSize) + SectorSize - 8); + block.dataBlockExtension = disk.readLong(); + + + console.log("Extension block " + sector,block.DataBlockCount); + return block; + } + + function writeExtensionBlock(sector,block){ + disk.goto(sector * SectorSize); + disk.writeUint(16); //LIST block + disk.writeUint(sector); + disk.writeUint(block.pointers.length); + disk.writeUint(0); + disk.writeUint(0); + disk.writeUint(0); // checksum + + for (var i = 0; i< 72; i++){ + disk.writeUint(block.pointers[i] || 0); + } + disk.goto((sector * SectorSize) + SectorSize - 12); + disk.writeUint(block.parent); + disk.writeUint(block.dataBlockExtension); + disk.writeUint(4294967293); + + + + // update checksum + block.checkSum = calculateChecksum(sector); + disk.goto(sector * SectorSize + 20); + disk.writeUint(block.checkSum); + + return block; + } + + function createExtensionBlock(sector,parent){ + var header = { + type: 16, + typeString: "EXTENSION", + sector: sector, + pointers: [], + dataBlockExtension: 0, + parent:parent, + DataBlockCount:0 + }; + for (var i=0;i<72;i++){header.pointers[i] = 0} + + return header; + } + + function readBitmapBlock(sector){ + var block = {}; + disk.goto(sector * SectorSize); + block.checkSum = disk.readLong(); + + block.longs = []; + block.map = [1,1]; + for (var i = 1; i<= 55; i++){ + var b = disk.readLong(); + block.longs.push(b); + + for (var j = 0; j<32; j++){ + block.map.push((b>>>j) & 1); + } + } + + return block; + } + + function writeBitmapBlock(sector,bitmapData){ + disk.goto(sector * SectorSize); + disk.writeUint(0); // checksum will be calculated later + + var index = 2; // ignore first 2 , these are the bootblock bits + for (var i = 1; i<= 54; i++){ + var value = 0; + for (var j = 0; j<31; j++){ + value += (bitmapData[index]<>>0); + index++; + disk.writeUint(value); + } + + // last long used only the first 30 bits + value = 0; + for (j = 0; j<30; j++){ + value += (bitmapData[index]<>> 0; + } + if (cs > 0xffffffff) cs -= 0x100000000; + } + cs = -cs; + if (cs < 0) cs += 0x100000000; + + return cs; + }; + + me.getFreeSize = function(){ + // Free/used blocks are stored in bitmap blocks; + // pointers to the bitmap blocks are stored in the rootblock; + // this is the same for harddisks, but for >50MB ones - that have more then 25 bitmap blocks, there are "bitmap extension blocks" - of which the first one is linked in the bitmap_ext field of the Rootblock; + + var rootBlock = readHeaderBlock(rootSector); + + disk.bitmapBlocks = rootBlock.bitmapBlocks; + var bitmapBlock = readBitmapBlock(rootBlock.bitmapBlocks[0]); + // NOTE: ADF disks fit in 1 bitmap block in the "map" ? + // if we have more, should we concatenate them + disk.bitmap = bitmapBlock.map; + + var count = 0; + var countIndex = 0; + + for (var ri = 0; ri0){ + var _bitmapBlock = readBitmapBlock(sector); + var max = _bitmapBlock.map.length; + for (let i = 0;i> 2) - 56); + // hash should be between 0 and 71 now ... + return hash; + } + + return me; +}(); + +export default ADF; + diff --git a/app/web-tools/dpaint/_script/fileformats/amigaIcon.js b/app/web-tools/dpaint/_script/fileformats/amigaIcon.js new file mode 100644 index 00000000..c747559a --- /dev/null +++ b/app/web-tools/dpaint/_script/fileformats/amigaIcon.js @@ -0,0 +1,1006 @@ +/* + + MIT License + + Copyright (c) 2019-2023 Steffest - dev@stef.be + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +import BinaryStream from "../util/binarystream.js"; +import zlib_closure from "../util/zlib.js"; +zlib_closure.call(window); // meh ... is there a better way to import closure compiled code? they are designed to hook themselves as global var. + +var Icon = function(){ + // Detect and decode Amiga .info icon files + // icon format info on + // http://krashan.ppa.pl/articles/amigaicons/ + // http://www.evillabs.net/index.php/Amiga_Icon_Formats + // all Amiga icons formats are supported except newIcons + + var me = {}; + + me.fileTypes={ + ICON: {name: "Icon file", actions:["show"], inspect: true} + }; + + var WB13Palette = [ + [85,170,255], + [255,255,255], + [0,0,0], + [255,136,0] + ]; + + var MUIPalette = [ + [149,149,149], + [0,0,0], + [255,255,255], + [59,103,162], + [123,123,123], + [175,175,175], + [170,144,124], + [255,169,151] + ]; + + me.parse = function(file,next){ + var icon = {}; + icon.info = {}; + + var magicBytes = file.readDWord(0); + var PNGID = 2303741511; + if (magicBytes === PNGID){ + //DualPNG + console.log("DualPNG icon"); + icon.PNGIcon = { + canvas:[] + }; + + // look for second PNG + file.goto(8); + var found = false; + var imageCount = 1; + var buffer2; + + // read next chunk size + var dw = file.readDWord(); + while (!file.isEOF(8)){ + // 4 byte Chunk Type ID and 4 byte CRC is not included + dw += 8; + file.jump(dw); + + // read next chunk size or next PNG ID + dw = file.readDWord(); + if (dw === PNGID){ + found = true; + break; + } + } + if (found){ + imageCount=2; + buffer2 = file.buffer.slice(file.index-4); + } + + var done=function(){ + if (next){ + if (icon.PNGIcon.canvas.length>(imageCount-1)){ + next(icon); + } + } + }; + + function toCanvas(buffer,index){ + var blob = new Blob( [ buffer], { type: "image/png" } ); + var urlCreator = window.URL || window.webkitURL; + var imageUrl = urlCreator.createObjectURL( blob ); + var img = new Image(); + img.onload = function(){ + icon.width = img.width; + icon.height = img.height; + var canvas = document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + canvas.getContext("2d").drawImage(img,0,0); + icon.PNGIcon.canvas[index] = canvas; + done(); + }; + img.src = imageUrl; + } + + toCanvas(file.buffer,0); + if (imageCount>1) toCanvas(buffer2,1); + + }else{ + //Amiga Icon + file.goto(2); + icon.version = file.readWord(); + icon.nextGadget = file.readDWord(); + icon.leftEdge = file.readWord(); + icon.topEdge = file.readWord(); + icon.width = file.readWord(); + icon.height = file.readWord(); + icon.flags = file.readWord(); + icon.activation = file.readWord(); + icon.gadgetType = file.readWord(); + icon.gadgetRender = file.readDWord(); + icon.selectRender = file.readDWord(); + icon.gadgetText = file.readDWord(); //Unused. Usually 0. + icon.mutualExclude = file.readDWord(); //Unused. Usually 0. + icon.specialInfo = file.readDWord(); //Unused. Usually 0. + icon.gadgetID = file.readWord(); //Unused. Usually 0. + icon.userData = file.readDWord(); // Used for icon revision. 0 for OS 1.x icons. 1 for OS 2.x/3.x icons. + icon.type = file.readUbyte(); /* + A type of icon: + 1 – disk or volume. + 2 – drawer (folder). + 3 – tool (executable). + 4 – project (data file). + 5 – trashcan. + 6 – device. + 7 – Kickstart ROM image. + 8 – an appicon (placed on the desktop by application). + */ + icon.info.type = getIconType(icon.type); + + icon.padding = file.readUbyte(); + icon.hasDefaultTool = file.readDWord(); + icon.hasToolTypes = file.readDWord(); + icon.currentX = file.readDWord(); + icon.currentY = file.readDWord(); + icon.hasDrawerData = file.readDWord(); // unused + icon.hasToolWindow = file.readDWord(); // I don't think this is used somewhere? + icon.stackSize = file.readDWord(); + + // total size 78 bytes + + var offset = 78; + + var drawerData = {}; + if (icon.hasDrawerData){ + // skip for now + offset += 56; + } + icon.drawerData = drawerData; + + + icon.img = readIconImage(file,offset); + + if (icon.selectRender) icon.img2 = readIconImage(file); + + if (icon.hasDefaultTool) icon.defaultTool = readText(file); + + icon.toolTypes = []; + let newIconFlags = 0; + if (icon.hasToolTypes){ + icon.toolTypeCount = file.readDWord(); + if (icon.toolTypeCount){ + icon.toolTypeCount = (icon.toolTypeCount/4) - 1; // seriously ... who invents this stuff? ... + + for (var i = 0; i< icon.toolTypeCount; i++){ + let toolType = readText(file); + if (toolType){ + let p = toolType.substr(0,4); + if ((p === "IM1=") || (p === "IM2=")) newIconFlags++; + icon.toolTypes.push(toolType); + } + } + } + } + + if (newIconFlags>2){ + icon.newIcon = decodeNewIcon(icon.toolTypes) + } + + + if (icon.hasToolWindow) icon.hasToolWindow = readText(file); + + if (icon.hasDrawerData && icon.userData){ + // OS2.x+ drawers + + icon.drawerData2 = {}; + icon.drawerData2.flags = file.readDWord(); + icon.drawerData2.ViewModes = file.readWord(); + } + + + if (file.index{ + let bits = ""; + for (let i = 0;i{ + if (toolType.indexOf("IM1=")===0 || toolType.indexOf("IM2=")===0){ + let data = toolType.substr(4); + let imageIndex = toolType.indexOf("IM1=")===0 ? 0 : 1; + let decoded = decodeData[imageIndex]; + let state = newIcon.states[imageIndex]; + if (!decoded.firstLine){ + decoded.firstLine=true; + newIcon.transparency = data.charCodeAt(0) === 66 // B + newIcon.width = data.charCodeAt(1)-33; + newIcon.height = data.charCodeAt(2)-33; + state.colorCount = (data.charCodeAt(3)-33 << 6) + data.charCodeAt(4)-33; + + state.bitCount = 1; + while ((1 << state.bitCount) < state.colorCount){state.bitCount++} + + data = data.substr(5); + decoded.paletteBits = decodeBits(data); + let bitCount=8; + let max = Math.floor((decoded.paletteBits.length/bitCount) / 3) ; + for (let i=0;i RLE control chars are 8 bits, but the data elements are n bits, determined by state.depth + + var max = (state.imageSize-1) * 8; + var bitIndex = 0; + + while (bitIndex < max) { + var b = file.readBits(8,bitIndex,imageDataOffset); + bitIndex += 8; + + if (b > 128) { + var b2 = file.readBits(state.depth,bitIndex,imageDataOffset); + bitIndex += state.depth; + for (var k = 0; k < 257 - b; k++) state.pixels.push(b2); + } + if (b < 128) { + for (k = 0; k <= b; k++){ + state.pixels.push(file.readBits(state.depth,bitIndex,imageDataOffset)); + bitIndex += state.depth; + } + } + } + }else{ + // note: uncompressed data is BYTE aligned, even if state.depth < 8 + for (var i = 0; i < state.imageSize; i++){ + state.pixels.push(file.readUbyte()) + } + } + + if (state.paletteSize){ + file.goto(paletteDataOffset); + var rgb = []; + + var bitsPerColorByte = 8; + + if (state.paletteCompression){ + var max = (state.paletteSize-1) * 8; + var bitIndex = 0; + + while (bitIndex < max) { + var b = file.readBits(8,bitIndex,paletteDataOffset); + bitIndex += 8; + + if (b > 128) { + var b2 = file.readBits(bitsPerColorByte,bitIndex,paletteDataOffset); + bitIndex += bitsPerColorByte; + for (var k = 0; k < 257 - b; k++) rgb.push(b2); + } + if (b < 128) { + for (k = 0; k <= b; k++){ + rgb.push(file.readBits(bitsPerColorByte,bitIndex,paletteDataOffset)); + bitIndex += bitsPerColorByte; + } + } + } + }else{ + for (i = 0; i < state.paletteSize; i++){ + rgb.push(file.readUbyte()) + } + } + + if (rgb.length>2){ + for (i = 0, max = rgb.length; i0){ + state.palette = img.states[img.states.length-1].palette; + } + } + + img.states.push(state); + + + break; + case "ARGB": + // zlib compressed + // found some info/structure on https://amigaworld.net//modules/newbb/viewtopic.php?viewmode=flat&order=0&topic_id=34625&forum=15&post_id=639101#639062 + + console.log("decoding ARGB data"); + + var state = {}; + + state.rgba = true; + state.pixels = []; + state.palette = []; + + + for (var offset = 0; offset<10;offset++){ + // no idea what this data structure is ... + // first DWORD always seem to be 1? + state.dummy = file.readUbyte(); + //console.log(state.dummy); + } + + var size = chunk.size-offset; + var data = new Uint8Array(size); + for (var i = 0; i { + name = name || ""; + let ext = name.split(".").pop().toLowerCase(); + let file; + + if (ext === "info") { + file = BinaryStream(data.slice(0, data.byteLength), true); + file.goto(0); + // Note: this can be Async! + AmigaIcon.parse(file, function (icon) { + if (icon) { + let canvas = AmigaIcon.getImage(icon); + let canvas2 = AmigaIcon.getImage(icon, 1); + next({ + image: [canvas, canvas2], + type: AmigaIcon.getType(icon), + }); + } else { + detectIFF(); + } + }); + } else if (ext === "gif"){ + // Note: GIFs are always little-endian + // see https://www.w3.org/Graphics/GIF/spec-gif89a.txt + file = BinaryStream(data.slice(0, data.byteLength), false); + file.goto(0); + let result = GIF.detect(file); + if (result) { + next(GIF.toFrames(file)); + }else{ + next(false); + } + } else if (ext === "png"){ + // check if it's an indexed PNG + // note: PNGs are always big-endian + file = BinaryStream(data.slice(0, data.byteLength), true); + file.goto(0); + let result = PNG.detect(file); + if (result){ + PNG.parse(file).then(next); + }else{ + next(false); + } + } else { + file = BinaryStream(data.slice(0, data.byteLength), true); + file.goto(0); + detectIFF(); + } + + function detectIFF() { + let fileType = IFF.detect(file); + if (fileType) { + let data = IFF.parse(file, true, fileType); + let img; + if (data && data.frames && data.frames.length) { + img = data.frames.map((frame) => { + //TODO: maybe defer rendering all frames until needed? + return IFF.toCanvas(frame); + }); + //img = IFF.toCanvas(data.frames[0]); + }else{ + if (data && data.width) img = IFF.toCanvas(data); + } + if (img) { + next({ + image: img, + type: "IFF", + data: data, + }); + } else { + next(false); + } + } else { + next(false); + } + } + }); + }; + return me; +})(); + +export default FileDetector; diff --git a/app/web-tools/dpaint/_script/fileformats/generate.js b/app/web-tools/dpaint/_script/fileformats/generate.js new file mode 100644 index 00000000..3801b0cc --- /dev/null +++ b/app/web-tools/dpaint/_script/fileformats/generate.js @@ -0,0 +1,497 @@ +import Modal, {DIALOG} from "../ui/modal.js"; +import IFF from "./iff.js"; +import ImageFile from "../image.js"; +import ImageProcessing from "../util/imageProcessing.js"; +import Icon from "./amigaIcon.js"; +import Palette from "../ui/palette.js"; +import IndexedPng from "./png.js"; +import GIF from "./gif.js"; + +let Generate = function(){ + let me = {}; + + me.validate = (config)=>{ + let result = { + valid: true, + errors: [] + }; + if (config.maxColors){ + let colors = ImageProcessing.getColors(ImageFile.getCanvas(),config.maxColors).length; + if (ImageFile.getCurrentFile().frames.length>1 && config.checkAllFrames){ + colors = Math.max(colors,ImageProcessing.getColors(ImageFile.getCanvas(1),config.maxColors).length); + } + if (colors > config.maxColors){ + result.valid = false; + result.errors.push("Please reduce the number of colors to maximum " + config.maxColors + "."); + } + } + + if (config.maxWidth && config.maxHeight){ + let width = ImageFile.getCurrentFile().width + let height = ImageFile.getCurrentFile().height; + if (width > config.maxWidth || height > config.maxHeight){ + result.valid = false; + result.errors.push("Please reduce the image size to maximum " + config.maxWidth + "x" + config.maxHeight + " pixels."); + } + } + + return result; + } + + // returns a blob + me.file = async type=>{ + switch (type){ + case "classicIcon": + return await me.classicIcon(); + case "colorIcon": + return me.colorIcon(); + case "PNGIcon": + return await me.PNGIcon(); + case "IFF": + return me.iff(); + case "BitPlanes": + return me.planes(); + case "BitMask": + return me.mask(); + case "PNG": + return await new Promise(resolve => ImageFile.getCanvas().toBlob(resolve)); + case "PNG8": + return me.png8(); + case "GIF": + return me.gif(); + case "DPAINT": + return me.dPaint(); + case "DPAINTINDEXED": + return me.dPaint(true); + default: + console.error("Unknown file type",type); + } + } + + me.iff=(maxColors,tooManyColorMessage)=>{ + maxColors = maxColors || 256; + let check = me.validate({ + maxColors: maxColors + }) + + if (!check.valid){ + Modal.show(DIALOG.OPTION,{ + title: "Save as IFF", + text: [tooManyColorMessage||"Sorry, this image can't be saved as IFF."].concat(check.errors), + buttons: [{label:"OK"}] + }); + return; + } + + let buffer = IFF.write(ImageFile.getCanvas()); + return new Blob([buffer], {type: "application/octet-stream"}); + } + + me.planes=()=>{ + let maxColors = 32; + let check = me.validate({ + maxColors: maxColors + }) + + if (!check.valid){ + Modal.show(DIALOG.OPTION,{ + title: "Save as BitPlane data", + text: ["Sorry, this image can't be saved as Bitplane data."].concat(check.errors), + buttons: [{label:"OK"}] + }); + return; + } + + return IFF.toBitPlanes(ImageFile.getCanvas()); + + } + + me.mask=()=>{ + return IFF.toBitMask(ImageFile.getCanvas()); + } + + me.png8=()=>{ + let maxColors = 256; + + let check = me.validate({ + maxColors: maxColors + }); + if (!check.valid){ + return { + result: "error", + title: "Save as PNG8", + messages: ["Sorry, this image can't be saved as PNG8."].concat(check.errors) + } + } + + let buffer = IndexedPng.write(ImageFile.getCanvas()); + return { + result: "ok", + file: new Blob([buffer], {type: "application/octet-stream"}) + }; + } + + me.gif=()=>{ + let maxColors = 256; + let check = me.validate({ + maxColors: maxColors + }); + if (!check.valid){ + return { + result: "error", + title: "Save as GIF", + messages: ["Sorry, this image can't be saved as GIF."].concat(check.errors) + } + } + + let buffer = GIF.write(ImageFile.getCurrentFile().frames); + return { + result: "ok", + file: new Blob([buffer], {type: "application/octet-stream"}) + }; + } + + me.dPaint=(indexed)=>{ + let exportResult = ImageFile.export(indexed); + let result = {result:"ok"}; + + if (indexed && exportResult.errorCount){ + let pixels = " pixel"; + if (exportResult.errorCount>1) pixels += "s"; + result.messages = [exportResult.errorCount + pixels + " could not be converted to index colors because there was no matching color found in the palette."]; + result.result = "warning"; + } + result.file = new Blob([JSON.stringify(exportResult)], { type: 'application/json' }); + return result; + } + + me.classicIcon=(config)=>{ + return new Promise(next=>{ + config = config || {}; + config.title = "Save as Amiga Classic Icon"; + + let check = me.validate({maxColors: 32, checkAllFrames: true}); + + if (!check.valid){ + Modal.show(DIALOG.OPTION,{ + title: config.title, + text: ["Sorry, this image has too many colors. Please reduce them to 16 or even better: 8 or less using the MUI palette"], + buttons: [{label:"OK"}] + }); + next(); + } + + check = me.validate({maxWidth: 1024, maxHeight: 1024}); + if (!check.valid){ + Modal.show(DIALOG.OPTION,{ + title: config.title, + text: ["Sorry, this image is too big. Please reduce it to 1024x1024 pixels or less."], + buttons: [{label:"OK"}] + }); + next(); + } + + + if (!config.skipColorCheck){ + check = me.validate({maxColors: 8}) + + if (!check.valid){ + Modal.show(DIALOG.OPTION,{ + title: config.title, + text: "Are you sure you want to save this image as Amiga Classic Icon? It has more than 8 colors which means it probably won't display correctly", + onOk:()=>{ + config.skipColorCheck = true; + me.classicIcon(config).then(next); + } + }); + return; + } + } + + if (!config.skipSizeCheck){ + check = me.validate({maxWidth: 256, maxHeight: 256}); + + if (!check.valid){ + Modal.show(DIALOG.OPTION,{ + title: config.title, + text: "Are you sure you want to save this image as Amiga Classic Icon? It's kind of big... Although technically possible, it's not recommended to use icons bigger than 256x256 pixels", + onOk:()=>{ + config.skipSizeCheck = true; + me.classicIcon(config).then(next); + } + }); + return; + } + } + + + + let canvas1 = ImageFile.getCanvas(0); + let canvas2 = ImageFile.getCanvas(1) || canvas1; + let ctx1 = canvas1.getContext("2d"); + let ctx2 = canvas2.getContext("2d"); + let w = canvas1.width; + let h = canvas1.height; + + let r,g,b,alpha; + + let icon = Icon.create(w,h); + + // discard ColorIcon + icon.colorIcon = undefined; + icon.width = w; + icon.height = h; + icon.img.width = w; + icon.img.height = h; + icon.img.depth = 3; // 8 colors + icon.img.planePick = 7 // color count - 1 (?) + icon.img.pixels = []; + icon.img2.width = w; + icon.img2.height = h; + icon.img2.depth = 3; // 8 colors + icon.img2.planePick = 7 // color count - 1 (?) + icon.img2.pixels = []; + + function fillPixels(_ctx,pixels){ + // canvas colours to pixel array + + let MUIColors = [ + "#959595", + "#000000", + "#ffffff", + "#3b67a2", + "#7b7b7b", + "#afafaf", + "#aa907c", + "#ffa997" + ]; + + let additionalColors = ["#000000"]; + + + let data = _ctx.getImageData(0, 0, w, h).data; + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + let index = (x + y * w) * 4; + + r = data[index]; + g = data[index+1]; + b = data[index+2]; + alpha = data[index+3]; + + if(alpha>100){ + let rgb = rgbToHex(r,g,b); + let colorIndex = MUIColors.indexOf(rgb); + if (colorIndex<0){ + console.warn("No MUI color: " + rgb); + + // making some assumptions here: + // get from palette + colorIndex = Palette.getColorIndex([r,g,b],true); + + // check if already in list + if (colorIndex<0) colorIndex = additionalColors.indexOf(rgb); + + if (colorIndex<0){ + // add to list + additionalColors.push(rgb); + colorIndex = additionalColors.length-1; + } + + if (colorIndex>15){ + console.error("Too many colors: " + rgb); + colorIndex = 0; + } + } + pixels.push(colorIndex); + }else{ + pixels.push(0); + } + } + } + } + + fillPixels(ctx1,icon.img.pixels); + fillPixels(ctx2,icon.img2.pixels); + + let buffer = Icon.write(icon); + next(new Blob([buffer], {type: "application/octet-stream"})); + }); + } + + me.colorIcon=()=>{ + let check = me.validate({ + maxColors: 256, + checkAllFrames: true, + maxWidth: 256, + maxHeight: 256 + }) + + if (!check.valid){ + Modal.show(DIALOG.OPTION,{ + title: "Save as Amiga Color Icon", + text: ["Sorry, this image can't be saved as Amiga Color Icon."].concat(check.errors), + buttons: [{label:"OK"}] + }); + return; + } + + // save as ColorIcon + let canvas1 = ImageFile.getCanvas(0); + var palette = []; + var pixels = []; + var ctx = canvas1.getContext("2d"); + + let canvas2 = ImageFile.getCanvas(1) || canvas1; + var palette2 = []; + var pixels2 = []; + var ctx2 = canvas2.getContext("2d"); + + var w = canvas1.width; + var h = canvas1.height; + + // for images < 8 colors we use the transparentColor as actual color; + // for >=8 we add the transparent color as extra color (image processing should have filtered that out already) + //if (IconEditor.getPalette().length > 7){ + palette.push(Palette.getBackgroundColor()); + palette2.push(Palette.getBackgroundColor()); + //} + + + var r,g,b,alpha; + var colorLookup = {}; + var colorLookup2 = {}; + + function matchColor(r,g,b,_palette){ + for (var i=1,max=_palette.length;i100){ + let rgb = rgbToHex(r,g,b); + let colorIndex = colorLookup[rgb]; + if (typeof colorIndex === "undefined"){ + colorIndex = matchColor(r,g,b,palette); + if (colorIndex<0){ + palette.push([r,g,b]); + colorIndex = palette.length-1; + colorLookup[rgb] = colorIndex; + } + } + pixels.push(colorIndex); + }else{ + pixels.push(0); + } + } + } + + var data2 = ctx2.getImageData(0, 0, w, h).data; + colorLookup2 = {}; + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + let index = (x + y * w) * 4; + + r = data2[index]; + g = data2[index+1]; + b = data2[index+2]; + alpha = data2[index+3]; + + if(alpha>100){ + let rgb = rgbToHex(r,g,b); + let colorIndex = colorLookup2[rgb]; + if (typeof colorIndex === "undefined"){ + colorIndex = matchColor(r,g,b,palette2); + if (colorIndex<0){ + palette2.push([r,g,b]); + colorIndex = palette2.length-1; + colorLookup2[rgb] = colorIndex; + } + } + //console.error(colorIndex); + pixels2.push(colorIndex); + }else{ + pixels2.push(0); + } + } + } + + var icon = Icon.create(w,h); + + icon.colorIcon.MaxPaletteSize = palette.length; + var state = icon.colorIcon.states[0]; + state.NumColors = palette.length; + state.paletteSize = state.NumColors * 3; + state.palette = palette.slice(); + state.pixels = pixels.slice(); + + icon.colorIcon.states.push( + { + transparentIndex: 0, + flags:3,// ? Bit 1: transparent color exists - Bit 2: Palette Exists + imageCompression:0, + paletteCompression:0, + depth:8, // number of bits to store each pixel + imageSize: pixels2.length + } + ); + let state2 = icon.colorIcon.states[1]; + state2.NumColors = palette2.length; + state2.paletteSize = state2.NumColors * 3; + state2.palette = palette2.slice(); + state2.pixels = pixels2.slice(); + + + let buffer = Icon.write(icon); + + return new Blob([buffer], {type: "application/octet-stream"}); + } + + me.PNGIcon=(config)=>{ + return new Promise(next=>{ + let canvas1 = ImageFile.getCanvas(0); + let canvas2 = ImageFile.getCanvas(1); + if (canvas2){ + canvas1.toBlob(function(blob1) { + canvas2.toBlob(function(blob2) { + next(new Blob([blob1,blob2], {type: "application/octet-stream"})); + }); + }); + }else{ + canvas1.toBlob(function(blob1) { + next(new Blob([blob1], {type: "application/octet-stream"})); + }); + } + }); + } + + function rgbToHex(r, g, b) { + return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + } + + return me +}(); + +export default Generate; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/fileformats/gif.js b/app/web-tools/dpaint/_script/fileformats/gif.js new file mode 100644 index 00000000..1866e60a --- /dev/null +++ b/app/web-tools/dpaint/_script/fileformats/gif.js @@ -0,0 +1,377 @@ +import BinaryStream from "../util/binarystream.js"; +import Palette from "../ui/palette.js"; +import LZW from "../util/lzw.js"; +import ImageFile from "../image.js"; + +const GIF = (()=>{ + const me = {}; + + me.detect = function (file) { + const id = file.readString(3, 0); + if (id === "GIF") { + let version = file.readString(3); + return version === "87a" || version === "89a"; + } + return false; + }; + + me.parse = function (file) { + let img = {}; + file.goto(3); + img.version = file.readString(3); + + //Logical Screen Descriptor + img.width = file.readShort(); + img.height = file.readShort(); + let packed = file.readUbyte(); + img.gctFlag = (packed & 0b10000000) !== 0; + img.colorResolution = (packed & 0b01110000) >> 4; + img.sortFlag = (packed & 0b00001000) !== 0; + img.gctSize = packed & 0b00000111; + img.colorCount = 1 << (img.gctSize + 1); + img.bgColorIndex = file.readUbyte(); + img.pixelAspectRatio = file.readUbyte(); + if (img.gctFlag) { + img.palette = []; + for (let i = 0; i < img.colorCount; i++) { + let r = file.readUbyte(); + let g = file.readUbyte(); + let b = file.readUbyte(); + img.palette.push([r, g, b]); + } + } + + function parseBlock(){ + let block = {}; + block.id = file.readUbyte(); + switch (block.id) { + case 0x21: + //Extension + block.label = file.readUbyte(); + switch (block.label) { + case 0xF9: + //Graphics Control Extension + console.log("Graphics Control Extension Found"); + block.size = file.readUbyte(); + let packed = file.readUbyte(); + img.disposalMethod = (packed & 0b00011100) >> 2; + //disposalMethod: 0: unspecified, 1: do not dispose, 2: restore to background, 3: restore to previous + img.userInputFlag = (packed & 0b00000010) !== 0; + img.transparentColorFlag = (packed & 0b00000001) !== 0; + img.delayTime = file.readShort(); + img.transparentColorIndex = file.readUbyte(); + file.readUbyte(); //block terminator + break; + case 0xFF: + //Application Extension + console.log("Application Extension Found"); + block.size = file.readUbyte(); + block.app = file.readString(11); + let subBlockSize = file.readUbyte(); + if (block.app === "NETSCAPE2.0") { + file.jump(1); + img.loopCount = file.readShort(); + file.readUbyte(); //block terminator + } else { + file.jump(subBlockSize); + file.readUbyte(); //block terminator + } + break; + case 0xFE: + case 0x01: + //0xFE: Comment Extension + //0x01: Plain Text Extension + // ignore for now + console.log("Comment Extension found, skipping"); + file.jump(1); + let size = file.readUbyte(); + file.jump(size); + file.readUbyte(); //block terminator + break; + default: + console.error("Unknown GIF block label: " + block.label); + } + break; + case 0x2C: + //Image Descriptor + console.log("Image Descriptor Found"); + img.frames = img.frames || []; + let frame = {}; + frame.left = file.readShort(); + frame.top = file.readShort(); + frame.width = file.readShort(); + frame.height = file.readShort(); + let packed = file.readUbyte(); + + block.lctFlag = (packed & 0b10000000) !== 0; + block.interlaceFlag = (packed & 0b01000000) !== 0; + block.sortFlag = (packed & 0b00100000) !== 0; + block.reserved = (packed & 0b00011000) >> 3; + block.lctSize = packed & 0b00000111; + block.colorCount = 1 << (block.lctSize + 1); + + if (block.lctFlag) { + frame.palette = []; + for (let i = 0; i < block.colorCount; i++) { + let r = file.readUbyte(); + let g = file.readUbyte(); + let b = file.readUbyte(); + frame.palette.push([r, g, b]); + } + } + + block.lzwMinCodeSize = file.readUbyte(); + + let lzwData = []; + let size; + do { + size = file.readUbyte(); + for (let i = 0; i < size; i++) lzwData.push(file.readUbyte()); + } while (size > 0); + + frame.pixels = LZW.decode(lzwData, block.lzwMinCodeSize, frame.width*frame.height); + if (!frame.palette) frame.palette = img.palette; + if (img.transparentColorFlag){ + frame.transparentColorIndex = img.transparentColorIndex; + } + img.frames.push(frame); + + break; + case 0x3B: + //Trailer + console.log("EOF Found"); + break; + default: + console.error("Unknown GIF block: ", block); + } + return block; + } + + let block = parseBlock(); + while (block.id !== 0x3B) { + block = parseBlock(); + } + + + return img; + } + + me.toCanvas = function (img) { + const canvas = document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext("2d"); + let imageData = new ImageData(img.width, img.height); + + img.pixels.forEach((pixel,index)=>{ + let color = img.palette[pixel] || [0,0,0]; + imageData.data[index*4] = color[0]; + imageData.data[index*4+1] = color[1]; + imageData.data[index*4+2] = color[2]; + imageData.data[index*4+3] = img.transparentColorIndex === pixel ? 0 : 255; + }); + ctx.putImageData(imageData, 0, 0); + return canvas; + } + + me.toFrames = function (file) { + let data = me.parse(file); + let img; + // gifs should have at least one frame + if (data && data.frames && data.frames.length) { + img = []; + data.frames.forEach((frame,index) => { + let frameCanvas = GIF.toCanvas(frame); + + let canvas = document.createElement("canvas"); + canvas.width = data.width; + canvas.height = data.height; + let ctx = canvas.getContext("2d"); + ctx.fillStyle = frame.palette[data.bgColorIndex || 0]; + if (data.disposalMethod === 0 || data.disposalMethod === 1){ + if (index>0) ctx.drawImage(img[index-1], 0, 0); + } + if (data.disposalMethod === 2){ + // restore to background color + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + if (data.disposalMethod === 3){ + if (index>0) ctx.drawImage(img[0], 0, 0); + } + + ctx.drawImage(frameCanvas, frame.left || 0, frame.top || 0); + img.push(canvas); + }); + } + if (img){ + return { + image: img, + type: "GIF", + data: data, + } + } + } + + + me.write = function(canvas) { + // write GIF file + // https://www.w3.org/Graphics/GIF/spec-gif89a.txt + + let frames = [canvas]; + if (Array.isArray(canvas)){ + frames = []; + canvas.forEach((c,index)=>{ + frames.push(ImageFile.getCanvas(index)); + }); + } + + // TODO: scan for transparent pixels and add transparent to palette + // TODO: set loop count in UI + // TODO: set frame delay in UI + + + let delayTime = 0; + if (frames.length > 1) delayTime = 15; + + let encodedFrames = []; + let palette = Palette.get(); + + // add transparent color to palette as color 0; + palette.unshift([0,0,0]); + + let colorDepth = 1; + while (1 << colorDepth < palette.length) colorDepth++; + let gctSize = colorDepth - 1; + let colorCount = 1 << colorDepth; + + frames.forEach((frame,index)=>{ + let pixels = ImageFile.generateIndexedPixels(index,true); + encodedFrames.push(LZW.encode(pixels,frame.width, frame.height, colorDepth)); + }); + + + let headerSize = 13 + colorCount*3; + let gceSize = 8; + let imageDescriptorSize = 10; + let applicationExtensionSize = 19; + + + let totalSize = headerSize + 1 + applicationExtensionSize; + encodedFrames.forEach((frame)=>{ + totalSize += gceSize + imageDescriptorSize + frame.length; + }); + console.log("Total GIF size: ", totalSize); + let file = new BinaryStream(new ArrayBuffer(totalSize),false); + + //Header + file.writeString("GIF89a"); + + // Logical Screen Descriptor + file.writeWord(frames[0].width); + file.writeWord(frames[0].height); + + file.writeUbyte((0x80 | // 1 : global color table flag = 1 (gct used) + 0x70 | // 2-4 : color resolution = 7 + 0x00 | // 5 : gct sort flag = 0 + gctSize)); // 6-8 : gct size + + file.writeUbyte(0); //background color index + file.writeUbyte(0); //pixel aspect ratio + + //Global Color Table + for (let i = 0; i < palette.length; i++) { + file.writeUbyte(palette[i][0]); + file.writeUbyte(palette[i][1]); + file.writeUbyte(palette[i][2]); + } + let remaining = colorCount - palette.length; + for (var i = 0; i < remaining*3; i++) file.writeUbyte(0); + + + // write application extension to enable loop + file.writeUbyte(0x21); //extension introducer + file.writeUbyte(0xFF); //application extension label + file.writeUbyte(0x0B); //block size + file.writeString("NETSCAPE2.0"); + file.writeUbyte(0x03); //sub-block size + file.writeUbyte(0x01); //sub-block id + file.writeWord(0); //loop count + file.writeUbyte(0x00); //block terminator + + + frames.forEach((frame,index)=>{ + ////Graphics Control Extension + file.writeUbyte(0x21); //extension introducer + file.writeUbyte(0xF9); //graphic control label + file.writeUbyte(0x04); //block size + + var transp; + var disp; + let transparent = 0; + let dispose = 0; + + if (transparent === null) { + transp = 0; + disp = 0; // dispose = no action + } else { + transp = 1; + disp = 2; // force clear if using transparent color + } + // TODO: what was this for, again? + // disp should remain at 2 when transparency is used, no ? + //if (dispose >= 0) { + //disp = dispose & 7; // user override + //} + disp <<= 2; + + // packed fields + let packed = 0 | // 1:3 reserved + disp | // 4:6 disposal + 0 | // 7 user input - 0 = none + transp; // 8 transparency flag + file.writeUbyte(packed); + + file.writeWord(delayTime); //delay time x 1/100 sec + file.writeUbyte(0); //transparent color index + file.writeUbyte(0); //block terminator + + + + //Image Descriptor + file.writeUbyte(0x2C); //image separator + file.writeWord(0); //image left position + file.writeWord(0); //image top position + file.writeWord(frame.width); //image width + file.writeWord(frame.height); //image height + + if (index === 0) { + // no local color table + file.writeUbyte(0); + }else{ + // on second frame, if a local color table exists + /* + file.writeUbyte(0x80 | // 1 local color table 1=yes + 0 | // 2 interlace - 0=no + 0 | // 3 sorted - 0=no + 0 | // 4-5 reserved + gctSize); // 6-8 size of color table + + // and write palette*/ + + file.writeUbyte(0); + } + + file.writeByteArray(encodedFrames[index]); + }) + + //Trailer + file.writeUbyte(0x3B); + + return file.buffer; + + } + + return me; +})(); + +export default GIF; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/fileformats/iff.js b/app/web-tools/dpaint/_script/fileformats/iff.js new file mode 100644 index 00000000..15fa36e7 --- /dev/null +++ b/app/web-tools/dpaint/_script/fileformats/iff.js @@ -0,0 +1,971 @@ +/* + + MIT License + + Copyright (c) 2019-2023 Steffest - dev@stef.be + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +import BinaryStream from "../util/binarystream.js"; +import ImageProcessing from "../util/imageProcessing.js"; +import Palette from "../ui/palette.js"; +import Color from "../util/color.js"; + +const FILETYPE = { + IFF: { name: "IFF file" }, + PBM: { name: "PBM Image" }, + ILBM: { name: "ILBM Image", actions: ["show"], inspect: true }, + ANIM: { name: "IFF ILBM Animation" }, +}; + +const IFF = (function () { + // Detect and Decode IFF Files + // handles ILBM images, including EHB (Extra Half-Bright) and HAM (Hold and Modify) + // and ANIM animations + // TODO: Brushes and other masked images + + // image format info on https://en.wikipedia.org/wiki/ILBM + + const me = {}; + + me.fileTypes = { + IFF: { name: "IFF file" }, + ILBM: { name: "ILBM Image", actions: ["show"], inspect: true }, + ANIM: { name: "IFF ILBM Animation" }, + }; + + me.parse = function (file, decodeBody, fileType,parent) { + let img = { + palette: [], + }; + let index = 12; + + function readChunk() { + const chunk = {}; + chunk.name = file.readString(4); + chunk.size = file.readDWord(); + return chunk; + } + + while (index < file.length - 4) { + file.goto(index); + const chunk = readChunk(); + + switch (chunk.name) { + case "BMHD": + img.width = file.readWord(); + img.height = file.readWord(); + img.x = file.readShort(); + img.y = file.readShort(); + img.numPlanes = file.readUbyte(); + img.mask = file.readUbyte(); + img.compression = file.readUbyte(); + img.pad = file.readUbyte(); + img.transparentColor = file.readWord(); + img.xAspect = file.readUbyte(); + img.yAspect = file.readUbyte(); + img.pageWidth = file.readWord(); + img.pageHeight = file.readWord(); + if (img.numPlanes && img.numPlanes < 9) { + img.colors = 1 << img.numPlanes; + } + if (img.numPlanes == 24) { + img.trueColor = true; + } + break; + case "CMAP": + for (var i = 0, max = chunk.size / 3; i < max; i++) { + img.palette.push([ + file.readUbyte(), + file.readUbyte(), + file.readUbyte(), + ]); + } + break; + case "CRNG": + img.colourRange = img.colourRange || []; + file.readShort(); // padding + let CRNGrange = { + rate: file.readShort(), // 16384 = 60 steps/second + flags: file.readShort(), + low: file.readUbyte(), + high: file.readUbyte() + } + CRNGrange.fps = CRNGrange.rate/16384*60; + CRNGrange.active = CRNGrange.flags & 1; + CRNGrange.reverse = CRNGrange.flags & 2; + img.colourRange.push(CRNGrange); + break; + case "DRNG": { + // Dpaint IV enhanced color cycle chunk. + // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.DRNG + img.colourRange = img.colourRange || []; + const range = { + min: file.readUbyte(), + max: file.readUbyte(), + rate: file.readShort(), + flags: file.readShort(), + numberOfDColors: file.readUbyte(), + colors: [], + numberOfDIndexes: file.readUbyte(), + indexes: [], + }; + for (let i = 0; i < range.numberOfDColors; i++) { + // true color RGB values. (Is this used? I've never seen it in the wild) + range.colors.push({ + index: file.readUbyte(), + red: file.readUbyte(), + green: file.readUbyte(), + blue: file.readUbyte(), + }); + } + + for (let i = 0; i < range.numberOfDIndexes; i++) { + // index values + range.indexes.push({ + index: file.readUbyte(), + colorIndex: file.readUbyte(), + }); + } + img.colourRange.push(range); + break; + } + case "CCRT": + // Graphicraft Color Cycle chunk + // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CCRT + // examples: https://amiga.lychesis.net/applications/Graphicraft.html + img.colourRange = img.colourRange || []; + let CCRTRange = { + direction: file.readWord(), + low: file.readUbyte(), + high: file.readUbyte(), + seconds: file.readLong(), + microseconds: file.readLong(), + padding: file.readWord() + } + CCRTRange.active = CCRTRange.direction !== 0; + CCRTRange.fps = 1/(CCRTRange.seconds + CCRTRange.microseconds/1000000); + img.colourRange.push(CCRTRange); + break; + case "CAMG": + var v = file.readLong(); + img.interlaced = v & 0x4; + img.ehb = v & 0x80; + img.ham = v & 0x800; + img.hires = v & 0x8000; + break; + case "BODY": + img.body = []; + + // adjust EHB and HAM palette here as the order of CMAP and CAMG is not defined; + if (img.ehb) { + for (i = 0; i < 32; i++) { + const c = img.palette[i]; + img.palette[i + 32] = [ + c[0] >> 1, + c[1] >> 1, + c[2] >> 1, + ]; + } + } + img.colorPlanes = img.numPlanes; + if (img.ham) { + img.hamPixels = []; + img.colorPlanes = 6; // HAM8 + if (img.numPlanes < 7) img.colorPlanes = 4; // HAM6 + } + + // some images have bad CAMG blocks? + if (!img.hires && img.width >= 640) img.hires = true; + if (img.hires && !img.interlaced && img.height >= 400) { + img.interlaced = true; + } + + if (decodeBody) { + if (fileType === FILETYPE.PBM) { + let pixelData = []; + + if (img.compression) { + // Decompress the data + for (let i = 0; i < chunk.size; i++) { + const byte = file.readUbyte(); + + if (byte > 128) { + const nextByte = file.readUbyte(); + for (let i = 0; i < 257 - byte; i++) { + pixelData.push(nextByte); + } + } else if (byte < 128) { + for (let i = 0; i < byte + 1; i++) { + pixelData.push(file.readUbyte()); + } + } else { + break; + } + } + } else { + // Just copy the data + // FIXME: Use BinaryStream.readBytes() ? + for (let i = 0; i < chunk.size; i++) { + pixelData.push(file.readUbyte()); + } + } + + // Rearrange pixel data in the right format for rendering? + // FIXME: Figure out why this needs to happen + let pixels = []; + for (let y = 0; y < img.height; y++) { + pixels[y] = []; + for (let x = 0; x < img.width; x++) { + pixels[y][x] = pixelData[y * img.width + x]; + } + } + + img.pixels = pixels; + } else { + const pixels = []; + const planes = []; + let lineWidth = (img.width + 15) >> 4; // in words + lineWidth *= 2; // in bytes + + if (img.compression < 2) { + for (let y = 0; y < img.height; y++) { + pixels[y] = []; + if (img.ham) img.hamPixels[y] = []; + + for (let plane = 0; plane < img.numPlanes; plane++) { + planes[plane] = planes[plane] || []; + planes[plane][y] = planes[plane][y] || []; + const line = []; + if (img.compression) { + + // RLE compression + let pCount = 0; + while (pCount < lineWidth) { + var b = file.readUbyte(); + if (b === 128) break; + if (b > 128) { + let b2 = file.readUbyte(); + for (var k = 0; k < 257 - b; k++) { + line.push(b2); + pCount++; + } + } else { + for (k = 0; k <= b; k++) { + line.push(file.readUbyte()); + } + pCount += b + 1; + } + } + } else { + for (let x = 0; x < lineWidth; x++) { + line.push(file.readUbyte()); + } + } + + // add bitplane line to pixel values; + for (b = 0; b < lineWidth; b++) { + const val = line[b]; + for (i = 7; i >= 0; i--) { + let x = b * 8 + (7 - i); + const bit = val & (1 << i) ? 1 : 0; + if (plane < img.colorPlanes) { + var p = pixels[y][x] || 0; + pixels[y][x] = p + (bit << plane); + planes[plane][y][x] = bit; + } else { + p = img.hamPixels[y][x] || 0; + img.hamPixels[y][x] = p + (bit << (plane - img.colorPlanes)); + } + } + } + } + } + } else { + // Atari ST ByteRun2 compression: each bitplane is stored in column + for (let plane = 0; plane < img.numPlanes; plane++) { + planes[plane] = planes[plane] || []; + const lines = []; + // each plane is stored in a 'VDAT' chunk inside the 'BODY' chunk + readChunk(); + const count = file.readWord() - 2; + + const cmds = file.readBytes(count) + let x = 0; + let y = 0; + let dataCount = 0; + for (let i = 0; i < count && x < lineWidth; ++i) { + const cmd = cmds[i] + + if (cmd === 0) { + dataCount = file.readWord() + while (dataCount-- > 0 && x < lineWidth) { + lines[x + y * lineWidth] = file.readUbyte(); + lines[x + y++ * lineWidth + 1] = file.readUbyte(); + if (y >= img.height) { + y = 0; + x += 2; + } + } + } else if (cmd < 0) { + dataCount = -cmd; + while (dataCount-- > 0 && x < lineWidth) { + lines[x + y * lineWidth] = file.readUbyte(); + lines[x + y++ * lineWidth + 1] = file.readUbyte(); + if (y >= img.height) { + y = 0; + x += 2; + } + } + } + else if (cmd === 1) { + dataCount = file.readWord(); + let repeat = file.readWord(); + + while (dataCount-- > 0 && x < lineWidth) { + lines[x + y * lineWidth] = repeat >> 8; + lines[x + y++ * lineWidth + 1] = repeat & 0xFF; + if (y >= img.height) { + y = 0; + x += 2; + } + } + } else { + dataCount = cmd; + let repeat = file.readWord(); + while (dataCount-- > 0 && x < lineWidth) { + lines[x + y * lineWidth] = repeat >> 8; + lines[x + y++ * lineWidth + 1] = repeat & 0xFF; + if (y >= img.height) { + y = 0; + x += 2; + } + } + } + } + + for (let y = 0; y < img.height; ++y) { + planes[plane][y] = planes[plane][y] || []; + pixels[y] = pixels[y] || []; + for (b = 0; b < lineWidth; b++) { + const val = lines[y * lineWidth + b]; + for (i = 7; i >= 0; i--) { + let x = b * 8 + (7 - i); + const bit = val & (1 << i) ? 1 : 0; + + var p = pixels[y][x] || 0; + pixels[y][x] = p + (bit << plane); + planes[plane][y][x] = bit; + } + } + } + } + } + img.pixels = pixels; + img.planes = planes; + } + } + + break; + case "FORM": // ANIM or other embedded IFF structure + img.frames = img.frames || []; + if (img.animFrameCount && img.frames.length >= img.animFrameCount){ + console.log("ANIM: frame count exceeded, skipping frame"); + break; + } + let buffer = new ArrayBuffer(chunk.size+8); + const view = new DataView(buffer); + file.readUBytes(chunk.size+8,file.index-8,view); + let subFile = new BinaryStream(buffer,true); + let subImg = me.parse(subFile, true,fileType,img); + if (subImg){ + img.frames.push(subImg); + if (img.frames.length === 1) { + img.width = subImg.width; + img.height = subImg.height; + img.numPlanes = subImg.numPlanes; + img.palette = subImg.palette; + img.animFrameCount = subImg.animFrameCount; + } + } + //console.error(subImg); + break; + case "ANHD": // https://wiki.amigaos.net/wiki/ANIM_IFF_CEL_Animations#ANHD_Chunk + img.animHeader = { + compression: file.readUbyte(), + mask: file.readUbyte(), + width: file.readWord(), + height: file.readWord(), + x: file.readWord(), + y: file.readWord(), + absTime: file.readDWord(), + relTime: file.readDWord(), + interleave: file.readUbyte(), + pad: file.readUbyte(), + bits: file.readUbyte(), + future: file.readUbyte(), + } + break; + case "DPAN": + img.animVersion = file.readWord(); + img.animFrameCount = file.readWord(); + break; + case "DLTA": // https://wiki.amigaos.net/wiki/ANIM_IFF_CEL_Animations#DLTA_Chunk + + if (!parent){ + console.error("Error: DLTA chunk without parent structure"); + return; + } + + img.animHeader = img.animHeader || {}; + + let sourceFrameIndex = Math.max(parent.frames.length - 2,0); + let frame = parent.frames[sourceFrameIndex]; + if (!frame){ + console.error("Error: No frame to apply DLTA chunk to"); + return; + } + // copy reference frame; + img.width = frame.width; + img.height = frame.height; + img.palette = frame.palette; + img.planes = []; + + // TODO: this is slow... + frame.planes.forEach(plane=>{ + let newPlane = []; + plane.forEach(line=>newPlane.push(line.slice())); + img.planes.push(newPlane); + }); + + if (sourceFrameIndex>0){ + //parent.frames[sourceFrameIndex-1].planes = undefined; + } + + + switch (img.animHeader.compression) { + case 0: // No compression + break; + case 1: // XOR compression + console.warn("unhandled ANIM compression: XOR"); + break; + case 2: // long Delta compression + console.warn("unhandled ANIM compression: long Delta"); + break; + case 3: // short Delta compression + console.warn("unhandled ANIM compression: short Delta"); + break; + case 4: // Generalized Delta compression + console.warn("unhandled ANIM compression: Generalized Delta"); + break; + case 5: // Byte Vertical Delta compression + // This is the default compression method for Deluxe Paint Animations. + + let startIndex = file.index; + let pointers = []; + for (let i =0;i<8;i++) pointers.push(file.readDWord()); + let colCount = parent.width + 15 >>> 4 << 1; + let bitPlaneCount = Math.min(parent.numPlanes,8); + + for (let bitPlaneIndex = 0; bitPlaneIndex < bitPlaneCount; bitPlaneIndex++) { + let pointer = pointers[bitPlaneIndex]; + if (pointer){ + //console.log('handling bitPlaneIndex ' + bitPlaneIndex + ' at ' + pointer); + file.goto(startIndex+pointer); + + for (let colIndex = 0; colIndex < colCount; colIndex++) { + let opCount = file.readUbyte(); + if (opCount === 0) continue; + //console.warn(opCount + " opCounts in column " + colIndex); + let destinationIndex = 0; + for (let opIndex = 0; opIndex < opCount; opIndex++) { + let opCode = file.readUbyte(); + //console.warn("opCode", opCode); + if (opCode === 0) { + //Same ops + let copyCount = file.readUbyte(); + let byteToCopy = file.readUbyte(); + //console.warn("copy",byteToCopy,copyCount); + for (let i = 0; i < copyCount; i++) { + let y = destinationIndex; + //data[(destinationIndex * bitPlaneCount + bitPlaneIndex) * colCount + colIndex] = byteToCopy; + for (let bi = 7; bi >= 0; bi--) { + let x = (colIndex*8) + 7 - bi; + const bit = byteToCopy & (1 << bi) ? 1 : 0; + img.planes[bitPlaneIndex][y][x] = bit; + } + destinationIndex++ + } + } else if (opCode < 128) { + // Skip ops: jump over opCode pixels + destinationIndex += opCode; + //console.warn("skip to line",destinationIndex); + } else { + // Uniq ops: read opCode pixels + opCode -= 128; + for (let i = 0; i < opCode; i++) { + let b = file.readUbyte(); + let y = destinationIndex; + let bits = []; + + if (destinationIndex < img.height) { + for (let bi = 7; bi >= 0; bi--) { + let x = (colIndex*8) + 7 - bi; + const bit = b & (1 << bi) ? 1 : 0; + img.planes[bitPlaneIndex][y][x] = bit; + bits.push(bit); + } + + destinationIndex++; + } + } + } + } + } + } + } + break; + case 6: // Stereo op 5 compression + console.warn("unhandled ANIM compression: Stereo op 5"); + break; + case 7: // short/long Vertical Delta mode + console.warn("unhandled ANIM compression: short/long Vertical Delta"); + break; + default: + console.error(`unhandled ANIM compression: ${img.animHeader.compression}`); + } + + let now = performance.now(); + // planes to pixels + img.pixels = []; + for (let y = 0; y < img.height; y++) { + let line = []; + for (let x = 0; x < img.width; x++) { + let pixel = 0; + for (let bitPlaneIndex = 0; bitPlaneIndex < parent.numPlanes; bitPlaneIndex++) { + let bit = img.planes[bitPlaneIndex][y][x]; + pixel += bit << bitPlaneIndex; + } + line.push(pixel); + } + img.pixels.push(line); + } + //console.log("to pixels", performance.now() - now); + + break; + default: + console.warn(`unhandled IFF chunk: ${chunk.name}`); + break; + } + + index += chunk.size + 8; + if (chunk.size % 2 === 1) index++; + } + + return img; + }; + + me.detect = function (file) { + const id = file.readString(4, 0); + if (id === "FORM") { + const size = file.readDWord(); + if (size + 8 <= file.length) { + // the size check isn't always exact for images? + const format = file.readString(4); + if (format === "ILBM") { + return FILETYPE.ILBM; + } + if (format === "PBM ") { + return FILETYPE.PBM; + } + if (format === "ANIM") { + return FILETYPE.ANIM; + } + return FILETYPE.IFF; + } + } + }; + + me.inspect = function (file) { + let result = ""; + const info = me.parse(file, false); + if (info.width && info.height) result = `${info.width}x${info.height}`; + if (info.ham) { + result += ` HAM${info.numPlanes < 7 ? "6" : "8"}`; + } + if (info.trueColor) { + result += " 24-bit"; + } else if (info.colors) { + result += ` ${info.colors} colours`; + } else if (info.palette) { + result += `palette with ${info.palette.length} colours`; + } + return result; + }; + + me.handle = function (file, action) { + if (action === "show") { + const img = me.parse(file, true); + //if (AdfViewer) AdfViewer.showImage(me.toCanvas(img)); + } + }; + + me.toCanvas = function (img) { + const canvas = document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext("2d"); + let pixelWidth = 1; + if (img.interlaced && !img.hires) { + canvas.width *= 2; + pixelWidth = 2; + } + //const ctx = canvas.getContext("2d"); + //let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + if (!img.ham && !img.trueColor && img.colourRange && img.colourRange.length > 0){ + // Executive decision right here: + // we're checking for duplicate colors in the palette that are inside the color range + // if we find any, we're going to slighty alter them to make them unique + // this is definitely a hack, but otherwise we have to implement full color index tracking through ALL image operations in ALL layers, which is definitely not worth it + + img.palette.forEach((color, index) => { + if (img.colourRange.some(range => range.low <= index && index <= range.high)) { + let colorIndex = img.palette.findIndex((c, i) => i !== index && Color.equals(c, color)); + let offsetIndex = 0; + let direction = [1,1,1]; + if (color[0] > 128) direction[0] = -1; + if (color[1] > 128) direction[1] = -1; + if (color[2] > 128) direction[2] = -1; + let count = 0; + + while (colorIndex !== -1 && count < 150) { + color[offsetIndex] += direction[offsetIndex]; + if (color[offsetIndex] < 0) color[offsetIndex] = 0; + if (color[offsetIndex] > 255) color[offsetIndex] = 255; + offsetIndex = (offsetIndex + 1) % 3; + colorIndex = img.palette.findIndex((c, i) => i !== index && Color.equals(c, color)); + count++; + } + img.palette[index] = color; + } + }) + } + + let imageData = new ImageData(img.width, img.height); + for (let y = 0; y < img.height; y++) { + let prevColor = [0, 0, 0]; + for (let x = 0; x < img.width; x++) { + let pixel = img.pixels[y][x]; + let color = img.palette[pixel] || [0, 0, 0]; + if (img.ham) { + const modifier = img.hamPixels[y][x]; + if (modifier) { + pixel <<= 8 - img.colorPlanes; // should the remaining (lower) bits also be filled? + color = prevColor.slice(); + if (modifier === 1) color[3] = pixel; + if (modifier === 2) color[0] = pixel; + if (modifier === 3) color[1] = pixel; + } + } + if (img.trueColor) { + // bits are stored like R0-R7,G0-B7,B0-B7 + // when reading out we just stack them on top, that's why the BlUE values are in 0xff0000 and the RED values are in 0x0000ff + color = [ + pixel & 0x0000ff, + (pixel & 0x00ff00) >> 8, + (pixel & 0xff0000) >> 16, + ]; + } + prevColor = color; + + for (let i = 0; i < pixelWidth; i++) { + const index = (y * canvas.width + x * pixelWidth + i) * 4; + imageData.data[index] = color[0]; + imageData.data[index + 1] = color[1]; + imageData.data[index + 2] = color[2]; + imageData.data[index + 3] = 255; + } + + } + } + ctx.putImageData(imageData, 0, 0); + return canvas; + }; + + // creates an ArrayBuffer with the binary data of the image; + me.write = function (canvas) { + let colorCycle = Palette.getColorRanges() || []; + let lockPalette = colorCycle.length || Palette.isLocked(); + let colors = lockPalette?Palette.get():ImageProcessing.getColors(canvas, 256); + + let bitplaneCount = 1; + while (1 << bitplaneCount < colors.length) bitplaneCount++; + while (colors.length < 1 << bitplaneCount) colors.push([0, 0, 0]); + + const w = canvas.width; + const h = canvas.height; + const pixels = canvas.getContext("2d").getImageData(0, 0, w, h).data; + + const bytesPerLine = Math.ceil(w / 16) * 2; + const bodySize = bytesPerLine * bitplaneCount * h; + + let colorCycleSize = 8 + 8; // header + data + let fileSize = 40 + 8 + 8; + fileSize += colors.length * 3; + fileSize += bodySize; + let colorRangeCount = colorCycle.length; + if (colorRangeCount){ + // we need at least 4 CRNG chunks to store, otherwise Deluxe Paint will inject default values + colorRangeCount = Math.max(4,colorRangeCount); + fileSize += (colorCycleSize * colorRangeCount); + } + if (fileSize & 1) fileSize++; + + const file = BinaryStream(new ArrayBuffer(fileSize), true); + + file.goto(0); + file.writeString("FORM"); + file.writeDWord(fileSize - 8); + + file.writeString("ILBM"); + + file.writeString("BMHD"); + file.writeDWord(20); + file.writeWord(w); + file.writeWord(h); + file.writeWord(0); + file.writeWord(0); + file.writeUbyte(bitplaneCount); + file.writeUbyte(0); + file.writeUbyte(0); + file.writeUbyte(0); + file.writeWord(0); + file.writeUbyte(1); + file.writeUbyte(1); + file.writeWord(w); + file.writeWord(h); + + // palette + file.writeString("CMAP"); + file.writeDWord(colors.length * 3); + colors.forEach((color) => { + file.writeUbyte(color[0]); + file.writeUbyte(color[1]); + file.writeUbyte(color[2]); + }); + + // color cycling + if (colorRangeCount){ + for (let i = 0; i < colorRangeCount; i++){ + let range = colorCycle[i] || {active:0,reverse:0,low:0,high:0,fps:0}; + + file.writeString("CRNG"); + file.writeDWord(8); + + file.writeWord(0); // padding + + let rate = Math.floor(range.fps * 16384 / 60); + let flags = range.active?1:0; + flags += range.reverse?2:0; + + file.writeWord(rate); + file.writeWord(flags); + file.writeUbyte(range.low); + file.writeUbyte(range.high); + } + } + + // body + file.writeString("BODY"); + file.writeDWord(bodySize); + const bitplaneLines = []; + + function getIndex(color) { + let index = colors.findIndex( + (c) => + c[0] === color[0] && c[1] === color[1] && c[2] === color[2] + ); + if (index < 0) { + index = 0; + console.error("color not found in palette", color); + } + return index; + } + + for (let y = 0; y < h; y++) { + for (var i = 0; i < bitplaneCount; i++) { + bitplaneLines[i] = new Uint8Array(bytesPerLine); + } + for (let x = 0; x < w; x++) { + let colorIndex = 0; + const pixel = (x + y * w) * 4; + const color = [ + pixels[pixel], + pixels[pixel + 1], + pixels[pixel + 2], + ]; + + // should we use an alpha threshold? + // const a = pixels[pixel + 3]; + + colorIndex = getIndex(color); + for (i = 0; i < bitplaneCount; i++) { + if (colorIndex & (1 << i)) { + bitplaneLines[i][x >> 3] |= 0x80 >> (x & 7); + } + } + } + for (i = 0; i < bitplaneCount; i++) { + for (let bi = 0; bi < bytesPerLine; bi++) { + file.writeUbyte(bitplaneLines[i][bi]); + } + } + } + + return file.buffer; + }; + + me.toBitPlanes = function (canvas) { + let addExtraPlane = false; + + let colors = Palette.isLocked()?Palette.get():ImageProcessing.getColors(canvas, 256); + let bitplaneCount = 1; + while (1 << bitplaneCount < colors.length) bitplaneCount++; + while (colors.length < 1 << bitplaneCount) colors.push([0, 0, 0]); + + const w = canvas.width; + const h = canvas.height; + const pixels = canvas.getContext("2d").getImageData(0, 0, w, h).data; + + const bytesPerLine = Math.ceil(w / 16) * 2; + const bitPlaneSize = bytesPerLine * h; + let fileSize = bitPlaneSize * bitplaneCount; + + if (addExtraPlane) { + fileSize += bitPlaneSize; + } + + const file = BinaryStream(new ArrayBuffer(fileSize), true); + file.goto(0); + const bitplanes = []; + + function getIndex(color) { + let index = colors.findIndex( + (c) => + c[0] === color[0] && c[1] === color[1] && c[2] === color[2] + ); + if (index < 0) { + index = 0; + console.error("color not found in palette", color); + } + return index; + } + for (var i = 0; i < bitplaneCount; i++) { + bitplanes[i] = new Uint8Array(bitPlaneSize); + } + + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + let colorIndex = 0; + const pixel = (x + y * w) * 4; + const color = [ + pixels[pixel], + pixels[pixel + 1], + pixels[pixel + 2], + ]; + colorIndex = getIndex(color); + for (i = 0; i < bitplaneCount; i++) { + if (colorIndex & (1 << i)) { + let index = (y * bytesPerLine) + (x >> 3); + bitplanes[i][index] |= 0x80 >> (x & 7); + } + } + } + + + } + + for (i = 0; i < bitplaneCount; i++) { + for (let bi = 0; bi < bitPlaneSize; bi++) { + file.writeUbyte(bitplanes[i][bi]); + } + } + + if (addExtraPlane){ + for (let bi = 0; bi < bitPlaneSize; bi++) { + file.writeUbyte(255); + } + } + + return { + width: w, + height: h, + palette: colors, + planes: file.buffer + } + + } + + me.toBitMask = function (canvas) { + let bitplaneCount = 1; + const w = canvas.width; + const h = canvas.height; + const pixels = canvas.getContext("2d").getImageData(0, 0, w, h).data; + + const bytesPerLine = Math.ceil(w / 16) * 2; + const bitPlaneSize = bytesPerLine * h; + let fileSize = bitPlaneSize * bitplaneCount; + + const file = BinaryStream(new ArrayBuffer(fileSize), true); + file.goto(0); + const bitplanes = [new Uint8Array(bitPlaneSize)]; + + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + let colorIndex = 0; + const pixel = (x + y * w) * 4; + let alpha = pixels[pixel + 3]; + if (alpha > 128) { + colorIndex = 1; + } + + if (colorIndex) { + let index = (y * bytesPerLine) + (x >> 3); + bitplanes[0][index] |= 0x80 >> (x & 7); + } + } + } + + for (let bi = 0; bi < bitPlaneSize; bi++) { + file.writeUbyte(bitplanes[0][bi]); + } + + return { + planes: file.buffer + } + + } + + return me; +})(); + +export default IFF; diff --git a/app/web-tools/dpaint/_script/fileformats/png.js b/app/web-tools/dpaint/_script/fileformats/png.js new file mode 100644 index 00000000..eaf1ffb9 --- /dev/null +++ b/app/web-tools/dpaint/_script/fileformats/png.js @@ -0,0 +1,199 @@ +/* + + Simple PNG write support for indexed palette images + Copyright (c) 2023 Steffest - dev@stef.be + + spec -> https://www.w3.org/TR/png/ + + */ + + +import BinaryStream from "../util/binarystream.js"; +import Palette from "../ui/palette.js"; +import crc32 from "../util/crc32.js"; + +import zlib_closure from "../util/zlib.js"; +zlib_closure.call(window); + +const PLTE = [80,76,84,69]; +const IHDR = [73,72,68,82]; +const IDAT = [73,68,65,84]; +const IEND = [73,69,78,68]; + +let IndexedPng = function(){ + let me = {}; + + let pngHeader = new Uint8Array([137,80,78,71,13,10,26,10]); + + me.write=function(canvas){ + let bitDepth = 8; + let colorType = 3; // indexed color + let compressionMethod = 0; + let filterMethod = 0; + let interlaceMethod = 0; + + let header = getHeaderChunk(canvas.width, canvas.height, bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod); + let palette = getPaletteChunk(); + let data = getDataChunk(canvas); + + let pngSize = pngHeader.length + chunkSize(header) + chunkSize(palette) + chunkSize(data) + chunkSize([]); + let arrayBuffer = new ArrayBuffer(pngSize); + let file = new BinaryStream(arrayBuffer, true); + file.writeByteArray(pngHeader); + writeChunk(file, IHDR, header); + writeChunk(file, PLTE, palette); + writeChunk(file, IDAT, data); + writeChunk(file, IEND, []); + + return file.buffer; + + } + + function writeChunk(stream, type, data){ + let len = data.length; + stream.writeUint(len); + stream.writeByteArray(type); + stream.writeByteArray(data); + stream.writeUint(crc32.get(type.concat(Array.from(data)))); + } + + function readChunk(file,includeData){ + let index = file.index; + let data; + let len = file.readUint(); + let type = file.readUBytes(4); + if (includeData) data = file.readUBytes(len); + let crc = file.readUint(); + + file.goto(index + 4 + 4 + len + 4); + + return {type, data}; + } + + function chunkSize(data){ + return data.length + 12; + } + + function getHeaderChunk(width, height, bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod){ + let byteArr = new ArrayBuffer(13); + let data = new BinaryStream(byteArr, true); + data.writeUint(width); + data.writeUint(height); + data.writeUbyte(bitDepth); + data.writeUbyte(colorType); + data.writeUbyte(compressionMethod); + data.writeUbyte(filterMethod); + data.writeUbyte(interlaceMethod); + return new Uint8Array(data.buffer); + } + + function readHeaderChunk(file){ + let width = file.readUint(); + let height = file.readUint(); + let bitDepth = file.readUbyte(); + let colorType = file.readUbyte(); + let compressionMethod = file.readUbyte(); + let filterMethod = file.readUbyte(); + let interlaceMethod = file.readUbyte(); + file.jump(4); // skip CRC + return {width, height, bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod}; + } + + function getPaletteChunk(){ + let palette = Palette.get(); + let data = new Uint8Array(palette.length*3); + for (let i = 0; i < palette.length; i++) data.set(palette[i], i*3); + return data; + } + + function getDataChunk(canvas){ + let w = canvas.width; + let h = canvas.height; + + // convert canvas to indexed color + // put scanline filter method in first byte of each scanline + // zlib compress the whole thing + + let imageData = canvas.getContext("2d").getImageData(0, 0, w, h); + let pixels = imageData.data; + + let data = new Uint8Array(w * h + h); + for (let y = 0; y < h; y++){ + let scanLineIndex = y * (w + 1); + data[scanLineIndex] = 0; // no filter + for (let x = 0; x < w; x++){ + let i = (y * w + x) * 4; + let r = pixels[i]; + let g = pixels[i + 1]; + let b = pixels[i + 2]; + let color = Palette.getColorIndex([r, g, b],true); + data[scanLineIndex + x + 1] = color; + } + } + + let zData = new Zlib.Deflate(data).compress(); + return zData; + } + + function isArrayEqual(a,b){ + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++){ + if (a[i] !== b[i]) return false; + } + return true; + } + + // detect 8-bit indexed PNG + me.detect = file=>{ + let header = file.readUBytes(8, 0); + let isIndexedPng = isArrayEqual(header, pngHeader); + if (isIndexedPng){ + // according to specs the IHDR chunk should be the first chunk + let chunk = readChunk(file); + if (isArrayEqual(chunk.type, IHDR)){ + file.goto(8 + 4 + 4); + let header = readHeaderChunk(file); + isIndexedPng = header.colorType === 3; + } + } + return isIndexedPng; + } + + me.parse = file=>{ + return new Promise((next)=>{ + let result = {data:{}}; + file.goto(8 + 4 + 4); + let header = readHeaderChunk(file); + + // find palette chunk + let paletteFound = false; + let palette; + while (!paletteFound && file.index < file.length - 12){ + let index = file.index; + let chunk = readChunk(file, false); + if (isArrayEqual(chunk.type, PLTE)){ + paletteFound = true; + file.goto(index); + chunk = readChunk(file, true); + palette = []; + for (let i = 0; i < chunk.data.length; i+=3){ + palette.push([chunk.data[i], chunk.data[i+1], chunk.data[i+2]]); + } + result.data.palette = palette; + } + } + + // use the browser's built-in PNG parser + var image = new Image(); + image.src = URL.createObjectURL(new Blob([file.buffer], {type: "image/png"})); + image.onload = function(){ + result.image = image; + next(result); + } + }); + } + + return me; +}(); + +export default IndexedPng; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/host/amibase.js b/app/web-tools/dpaint/_script/host/amibase.js new file mode 100644 index 00000000..88cfb3f1 --- /dev/null +++ b/app/web-tools/dpaint/_script/host/amibase.js @@ -0,0 +1,169 @@ +/* + Helper library to integrate your app with AmiBase; + www.amibase.com + + Copyright (c) 2023 Steffest + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ + +let Amibase = ()=>{ + let me = {}; + let isAmiBased = false; + let windowId; + let menu; + let messageHandler; + let callbacks = {}; + + me.init = function(){ + let messageTimeOut; + + return new Promise((next)=>{ + if (window.self !== window.top && window.parent){ + // we are running in an iframe, let's see if we can contact the host + console.log("registering with host"); + window.parent.postMessage({ + command: "register", + url: window.location.href + },"*"); + messageTimeOut = setTimeout(function(){ + console.log("no host found"); + next(false); + },500); + }else{ + next(); + } + + window.addEventListener("message", function (event) { + // We got a message from outside our window; + if (event && event.data){ + let message = event.data; + if (message.registered) { + // look at that! AmiBase has replied + console.log("registered with host"); + clearTimeout(messageTimeOut); + windowId = message.id; + isAmiBased = true; + next(true); + }else if (message.message === "callback"){ + // this is a callback from a previous request we initiated + let callback = callbacks[message.data.id]; + if (callback){ + callback(message.data.data); + delete callbacks[message.data.id]; + } + }else{ + // this is a message from the host + console.log(event.data); + if (messageHandler) messageHandler(message); + } + } + }, false); + }); + + } + + me.iAmReady = function(){ + // Tell AmiBase we are ready to receive messages + if (isAmiBased){ + window.parent.postMessage({ + command: "ready", + windowId: windowId + },"*"); + } + }; + + me.setMenu = function(_menu){ + // Sets the AmiBase menu to ours if our window is active + menu = _menu; + if (isAmiBased){ + window.parent.postMessage({ + command: "setMenu", + windowId: windowId, + data: menu + },"*"); + } + }; + + + me.requestFileOpen = async function(andOpen){ + // Request a file from AmiBase - this will pop up a file dialog and return the path of the selected file (including the file content if andOpen is true) + let path = await callAmiBase("requestFileOpen"); + let result = {path: path}; + if (!andOpen) return result; + result.data = await callAmiBase("readFile",{path: path, asBinary: false}); + return result; + } + + me.requestFileSave = async function(path){ + // Request to save a file to AmiBase - this will pop up a file dialog and return the path of the selected file + let savePath = await callAmiBase("requestFileSave",{path: path}); + let result = {path: savePath}; + return result; + } + + // get the content of a file based on its amiBase path + me.readFile = async function(path,asBinary){ + return await callAmiBase("readFile",{path: path, asBinary: asBinary}); + }; + + me.writeFile = async function(path,data,asBinary){ + // write a file to the AmiBase file system + let messageData = { + path: path, + data: data, + asBinary: asBinary + } + return await callAmiBase("writeFile",messageData); + } + + me.activateWindow = async function(){ + return await callAmiBase("activateWindow"); + } + + me.setMessageHandler = function(handler){ + messageHandler = handler; + }; + + function callAmiBase(command,data){ + return new Promise(next=>{ + if (!isAmiBased) return next(); + let message ={ + command: command, + windowId: windowId, + data: data, + callbackId: getId() + } + callbacks[message.callbackId] = next; + window.parent.postMessage(message,"*"); + }); + } + + // generate a (very temporary) unique-ish ID, so we can match the callback to the request + function getId(){ + return ("" + new Date().getTime() + Math.random()).replace(".",""); + } + + return me; +} + +export default Amibase(); \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/host/host.js b/app/web-tools/dpaint/_script/host/host.js new file mode 100644 index 00000000..10f89ad9 --- /dev/null +++ b/app/web-tools/dpaint/_script/host/host.js @@ -0,0 +1,64 @@ +import AmiBase from "./amibase.js"; +import ImageFile from "../image.js"; +import Amibase from "./amibase.js"; + +let Host = function(){ + let me = {}; + let currentFile; + + me.init = function(){ + AmiBase.init().then(isAmiBase=>{ + if (isAmiBase){ + console.log("AmiBase is available"); + AmiBase.setMessageHandler(message=>{ + console.error(message); + + var command = message.message; + if (!command) return; + if (command.indexOf("amibase_")>=0) command=command.replace("amibase_",""); + + switch(command){ + case 'dropFile': + case 'openFile': + // amiBase requests to open a file in Monaco + let file = message.data; + currentFile = file; + if (file && file.data){ + // we already have the content of the file + loadFile(file.data,file.path); + }else{ + // we need to request the file from AmiBase + AmiBase.readFile(currentFile.path,true).then(data=>{ + loadFile(data,currentFile.path); + }); + } + break; + } + }); + AmiBase.iAmReady(); + } + }); + } + + me.saveFile = function(blob,fileName){ + AmiBase.requestFileSave(currentFile.path).then(file=>{ + if (file && file.path){ + AmiBase.writeFile(file.path,blob,true).then(result=>{ + console.log(result); + AmiBase.activateWindow(); + }); + } + }); + } + + function loadFile(data,path){ + let fileName = path.split("/").pop(); + console.log("load file",typeof data); + ImageFile.handleBinary(data,fileName,"file",true); + //SaveDialog.setFile(file); + } + + return me; +}(); + +export default Host; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/image.js b/app/web-tools/dpaint/_script/image.js new file mode 100644 index 00000000..4f710b3f --- /dev/null +++ b/app/web-tools/dpaint/_script/image.js @@ -0,0 +1,1148 @@ +import FileDetector from "./fileformats/detect.js"; +import EventBus from "./util/eventbus.js"; +import {COMMAND,EVENT} from "./enum.js"; +import Historyservice from "./services/historyservice.js"; +import Layer from "./ui/layer.js"; +import Modal,{DIALOG} from "./ui/modal.js"; +import SidePanel from "./ui/sidepanel.js"; +import {duplicateCanvas, indexPixelsToPalette, releaseCanvas} from "./util/canvasUtils.js"; +import Palette from "./ui/palette.js"; +import SaveDialog from "./ui/components/saveDialog.js"; +import HistoryService from "./services/historyservice.js"; +import ImageProcessing from "./util/imageProcessing.js"; +import Brush from "./ui/brush.js"; +import storage from "./util/storage.js"; + +let ImageFile = function(){ + let me = {}; + let activeLayer; + let activeLayerIndex = 0; + let activeFrameIndex = 0; + let cachedImage; + let currentFile = { + name: "Untitled", + layers: [], + }; + let autoSaveTimer; + + me.getCurrentFile = function(){ + return currentFile; + }; + + me.getName = function(){ + return currentFile.name || "Untitled"; + }; + + me.setName = function(name){ + currentFile.name = name; + }; + + me.getOriginal = function(){ + if (!cachedImage) { + console.error("caching image"); + cachedImage = document.createElement("canvas"); + let img = me.getCanvas(); + cachedImage.width = img.width; + cachedImage.height = img.height; + cachedImage.getContext("2d").drawImage(img, 0, 0); + } + return cachedImage; + }; + + me.restoreOriginal = function(){ + if (cachedImage) { + let ctx = me.getActiveContext(); + ctx.clearRect(0, 0, currentFile.width, currentFile.height); + ctx.drawImage(cachedImage, 0, 0); + EventBus.trigger(EVENT.imageContentChanged); + } + }; + + me.getCanvas = function(frameIndex){ + let frame = + typeof frameIndex === "number" + ? currentFile.frames[frameIndex] + : currentFrame(); + if (!frame) return; + if (frame.layers.length === 1) { + if (typeof frameIndex === "number") { + return frame.layers[0].render(); + } else { + if (activeLayer && activeLayer.visible) { + return activeLayer.render(); + } + } + } else { + let canvas = document.createElement("canvas"); + let ctx = canvas.getContext("2d"); + canvas.width = currentFile.width; + canvas.height = currentFile.height; + frame.layers.forEach((layer) => { + if (layer.visible) { + ctx.globalAlpha = layer.opacity / 100; + let blendMode = layer.blendMode || "normal"; + if (blendMode === "normal") blendMode = "source-over"; + ctx.globalCompositeOperation = blendMode; + ctx.drawImage(layer.render(), 0, 0); + ctx.globalAlpha = 1; + ctx.globalCompositeOperation = "source-over"; + } + }); + return canvas; + } + }; + + me.getContext = function(){ + if (currentFrame().layers.length === 1 && activeLayer) { + return activeLayer.getContext(); + }else{ + return me.getCanvas().getContext("2d"); + } + }; + + me.getActiveContext = function(){ + if (activeLayer) return activeLayer.getContext(); + }; + + me.getActiveLayerIndex = function(){ + return activeLayerIndex; + }; + + me.getActiveLayer = function(){ + return activeLayer; + }; + + me.getLayer = function(index){ + let frame = currentFile.frames[activeFrameIndex]; + return frame ? frame.layers[index] : undefined; + }; + + me.getLayerIndexesOfType = function(type){ + let frame = currentFile.frames[activeFrameIndex]; + let result = []; + if (frame) { + frame.layers.forEach((layer, index) => { + if (layer.type === type) result.push(index); + }); + } + return result; + }; + + me.getActiveFrameIndex = function(){ + return activeFrameIndex; + }; + + me.getActiveFrame = function(){ + return currentFrame(); + }; + + me.render = function(){ + if (currentFrame().layers.length>1){ + + } + } + + me.openLocal = function(target){ + stop(); + var input = document.createElement("input"); + input.type = "file"; + input.onchange = function (e) { + handleUpload(e.target.files, target || "file"); + }; + input.click(); + }; + + me.openUrl = function(url,useProxy){ + stop(); + return new Promise((resolve,reject)=>{ + let fileName = url.substring(url.lastIndexOf("/")+1); + let extension = fileName.substring(fileName.lastIndexOf(".")+1).toLowerCase(); + fetch(url).then(response=>{ + if (extension === "json"){ + response.json().then(json=>{ + me.handleJSON(json); + resolve(); + }) + }else{ + response.blob().then(blob=>{ + blob.arrayBuffer().then(buffer=>{ + me.handleBinary(buffer, fileName, "file",true); + resolve(); + }) + }) + } + }).catch(err=>{ + if (!useProxy){ + // probably a CORS error + url = "https://www.stef.be/bassoontracker/api/proxy/?"+encodeURIComponent(url); + me.openUrl(url,true).then(resolve).catch(reject); + }else{ + console.error(err); + reject(err); + } + }) + }); + } + + me.save = function(){ + Modal.show(DIALOG.SAVE); + }; + + me.resize = function(properties){ + if (!properties) { + Modal.show(DIALOG.RESIZE); + } else { + let w = properties.width; + let h = properties.height; + let anchor = properties.anchor || "topleft"; + let pW = currentFile.width; + let pH = currentFile.height; + currentFile.width = w; + currentFile.height = h; + let aX = Math.round((w - pW) / 2); + let aY = Math.round((h - pH) / 2); + if (anchor.indexOf("top") >= 0) aY = 0; + if (anchor.indexOf("bottom") >= 0) aY = h - pH; + if (anchor.indexOf("left") >= 0) aX = 0; + if (anchor.indexOf("right") >= 0) aX = w - pW; + console.log("Resizing image to " +w + "x" + h); + currentFile.frames.forEach(frame=>{ + frame.layers.forEach(layer=>{ + // TODO: what about mask canvas and dither canvas ? + let canvas = layer.getCanvas(); + let ctx = layer.getContext(); + let d = duplicateCanvas(canvas, true); + canvas.width = w; + canvas.height = h; + ctx.drawImage(d, aX, aY); + releaseCanvas(d); + }); + }); + EventBus.trigger(EVENT.imageSizeChanged); + } + }; + + me.resample = function(properties){ + if (!properties) { + Modal.show(DIALOG.RESAMPLE); + } else { + let w = properties.width; + let h = properties.height; + if (w === currentFile.width && h === currentFile.height) return; + let quality = properties.quality || "pixelated"; + HistoryService.start(EVENT.imageHistory); + currentFile.width = w; + currentFile.height = h; + let todo = 0; + let done = 0; + currentFile.frames.forEach((frame) => { + todo += frame.layers.length; + }); + + currentFile.frames.forEach((frame) => { + frame.layers.forEach((layer) => { + let canvas = layer.getCanvas(); + let ctx = layer.getContext(); + + if (quality === "pixelated") { + let d = duplicateCanvas(canvas, true); + canvas.width = w; + canvas.height = h; + ctx.webkitImageSmoothingEnabled = false; + ctx.mozImageSmoothingEnabled = false; + ctx.imageSmoothingEnabled = false; + ctx.drawImage(d, 0, 0, d.width, d.height, 0, 0, w, h); + releaseCanvas(d); + done++; + if (done >= todo) { + HistoryService.end(); + EventBus.trigger(EVENT.imageSizeChanged); + } + } else { + let imageData = ctx.getImageData( + 0, + 0, + canvas.width, + canvas.height + ); + let result; + if (imageData.width > w && imageData.height > h) { + result = ImageProcessing.downScale(imageData, w, h); + } else { + result = ImageProcessing.biCubic(imageData, w, h); + } + canvas.width = w; + canvas.height = h; + ctx.putImageData(result, 0, 0); + done++; + if (done >= todo) { + HistoryService.end(); + EventBus.trigger(EVENT.imageSizeChanged); + } + } + }); + }); + } + }; + + me.activateLayer = function(index){ + activeLayerIndex = index; + activeLayer = currentFrame().layers[activeLayerIndex]; + EventBus.trigger(EVENT.layersChanged); + }; + + me.toggleLayer = function(index){ + currentFrame().layers[index].visible = + !currentFrame().layers[index].visible; + EventBus.trigger(EVENT.layersChanged); + EventBus.trigger(EVENT.imageContentChanged); + }; + + me.duplicateLayer = function(index){ + if (typeof index !== "number") index = activeLayerIndex; + let layer = currentFrame().layers[index]; + let newLayer = Layer( + currentFile.width, + currentFile.height, + layer.name + " duplicate" + ); + newLayer.opacity = layer.opacity; + newLayer.blendMode = layer.blendMode; + newLayer.drawImage(layer.getCanvas()); + currentFrame().layers.splice(index + 1, 0, newLayer); + me.activateLayer(index + 1); + }; + + me.flipLayer = function(index, horizontal){ + if (typeof index !== "number") index = activeLayerIndex; + let layer = currentFrame().layers[index]; + if (layer) { + let canvas = duplicateCanvas(layer.getCanvas(), true); + let ctx = layer.getContext(); + layer.clear(); + if (horizontal) { + ctx.translate(canvas.width, 0); + ctx.scale(-1, 1); + }else{ + ctx.translate(0, canvas.height); + ctx.scale(1, -1); + } + ctx.drawImage(canvas, 0, 0); + ctx.setTransform(1, 0, 0, 1, 0, 0); + releaseCanvas(canvas); + + EventBus.trigger(EVENT.layerContentChanged); + } + } + + me.setLayerOpacity = function(value){ + if (activeLayer) { + activeLayer.opacity = value; + EventBus.trigger(EVENT.imageContentChanged); + } + }; + + me.setLayerBlendMode = function(value){ + if (activeLayer) { + activeLayer.blendMode = value; + EventBus.trigger(EVENT.imageContentChanged); + } + }; + + me.getLayerBoundingRect = function(layerIndex){ + let layer = activeLayer; + if (typeof layerIndex === "number") { + layer = currentFrame().layers[layerIndex]; + } + + let ctx = layer.getContext(); + let canvas = ctx.canvas; + let w = canvas.width, + h = canvas.height, + pix = { x: [], y: [] }, + imageData = ctx.getImageData(0, 0, canvas.width, canvas.height), + x, + y, + index; + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + index = (y * w + x) * 4; + if (imageData.data[index + 3] > 0) { + pix.x.push(x); + pix.y.push(y); + } + } + } + pix.x.sort(function (a, b) { + return a - b; + }); + pix.y.sort(function (a, b) { + return a - b; + }); + let n = pix.x.length - 1; + + w = 1 + pix.x[n] - pix.x[0]; + h = 1 + pix.y[n] - pix.y[0]; + + return { x: pix.x[0], y: pix.y[0], w: w, h: h }; + }; + + me.activateFrame = function(index){ + let frame = currentFile.frames[activeFrameIndex]; + if (frame) frame.activeLayerIndex = activeLayerIndex; + + frame = currentFile.frames[index]; + if (frame) activeLayerIndex = frame.activeLayerIndex || 0; + activeFrameIndex = index; + cachedImage = undefined; + activeLayer = currentFrame().layers[activeLayerIndex]; + EventBus.trigger(EVENT.layersChanged); + EventBus.trigger(EVENT.imageContentChanged); + EventBus.trigger(EVENT.framesChanged); + }; + + me.nextFrame = function(offset){ + offset = offset || 1; + let frame = activeFrameIndex + offset; + if (frame < 0) frame = currentFile.frames.length - 1; + if (frame >= currentFile.frames.length) frame = 0; + me.activateFrame(frame); + } + + me.clone = function(indexed){ + let struct = { + type: "dpaint", + version: "1", + image: {}, + }; + + struct.image.name = currentFile.name; + struct.image.width = currentFile.width; + struct.image.height = currentFile.height; + struct.image.activeLayerIndex = activeLayerIndex; + struct.image.activeFrameIndex = activeFrameIndex; + struct.image.frames = []; + struct.errorCount = 0; + + currentFile.frames.forEach((frame) => { + let _frame = { + layers: [], + activeLayerIndex: frame.activeLayerIndex || 0 + }; + frame.layers.forEach((layer) => { + let _layer = layer.clone(true, indexed); + struct.errorCount += (_layer.conversionErrors || 0); + _frame.layers.push(_layer); + }); + struct.image.frames.push(_frame); + }); + + if (currentFile.colorRange) struct.image.colorRange = currentFile.colorRange; + + return struct; + }; + + me.restore = function(data){ + let image = data.image; + currentFile.width = image.width; + currentFile.height = image.height; + let mockImage = new Image(currentFile.width, currentFile.height); + newFile(mockImage, currentFile.name, currentFile.type); + currentFile.name = image.name || "Untitled"; + image.frames.forEach((_frame, frameIndex) => { + let frame = currentFile.frames[frameIndex]; + if (!frame) { + addFrame(); + frame = currentFile.frames[frameIndex]; + } + frame.activeLayerIndex = _frame.activeLayerIndex || 0; + _frame.layers.forEach((_layer, layerIndex) => { + let layer = frame.layers[layerIndex]; + if (!layer) { + layer = Layer(currentFile.width, currentFile.height); + frame.layers.push(layer); + } + layer.restore(_layer).then(() => { + + if (frame.activeLayerIndex === layerIndex && image.activeFrameIndex === frameIndex) { + me.activateFrame(frameIndex); + me.activateLayer(layerIndex); + } + EventBus.trigger(EVENT.layersChanged); + EventBus.trigger(EVENT.imageSizeChanged); + }); + }); + }); + + if (image.colorRange) currentFile.colorRange = image.colorRange; + + if (data.palette) Palette.set(data.palette); + + if (data.paletteList){ + Palette.setPaletteList(data.paletteList); + Palette.setPaletteIndex(data.paletteIndex); + } + + }; + + me.export = function(indexed){ + let struct = me.clone(indexed); + + struct.palette = Palette.get(); + let paletteList = Palette.getPaletteList(); + if (paletteList.length>1){ + struct.paletteList = paletteList; + struct.paletteIndex = Palette.getPaletteIndex(); + } + + if (currentFile.colorRange) struct.colorRange = currentFile.colorRange; + if (currentFile.indexedPixels){ + struct.indexedPixels = currentFile.indexedPixels; + }else{ + if (indexed){ + struct.indexedPixels = me.generateIndexedPixels(); + } + } + console.log(struct); + return struct; + } + + me.autoSave = function(){ + let data = me.export(); + storage.putFile("autosave",data); + } + window.autoSave = me.autoSave; + + me.restoreAutoSave = function(){ + storage.getFile("autosave").then(data=>{ + if (data) me.restore(data); + }); + } + + function autoSave(){ + if (autoSaveTimer) clearTimeout(autoSaveTimer); + autoSaveTimer = setTimeout(me.autoSave,1000); + } + + me.addLayer = addLayer; + me.removeLayer = removeLayer; + me.moveLayer = moveLayer; + + me.hasMultipleFrames = function(){ + return currentFile.frames.length > 1; + } + + function handleUpload(files,target){ + stop(); + if (files.length) { + var file = files[0]; + var detectType; + var isText; + var fileName = file.name.split("."); + var ext = fileName.pop().toLowerCase(); + fileName = fileName.join("."); + + if (ext === "info") detectType = true; + if (ext === "gif") detectType = true; + if (ext === "png") detectType = true; + if (ext === "json") isText = true; + + var reader = new FileReader(); + reader.onload = function(){ + if (detectType) { + me.handleBinary(reader.result, file.name, target,true); + } else if (isText) { + let data = {}; + if (ext === "json") { + try { + data = JSON.parse(reader.result); + } catch (e) { + console.error("Can't parse JSON"); + } + } + if (data) { + me.handleJSON(data,target); + } + } else { + // load as Image, fallback to detectType if it fails + var image = new Image(); + image.onload = function(){ + URL.revokeObjectURL(this.src); + handleOpenedImage(image,fileName,target) + }; + image.onerror = function(){ + URL.revokeObjectURL(this.src); + detectType = true; + reader.readAsArrayBuffer(file); + }; + image.setAttribute("crossOrigin", ""); + image.src = reader.result; + } + }; + if (isText) { + reader.readAsText(file); + } else if (detectType) { + reader.readAsArrayBuffer(file); + } else { + reader.readAsDataURL(file); + } + SaveDialog.setFile(); + } + } + me.handleUpload = handleUpload; + + me.handleBinary = function (data,name,target,stillTryImage){ + let now = performance.now(); + + name = name || ""; + let fileName = name.split("."); + fileName = fileName.join("."); + console.log("Loading file: ", fileName); + + FileDetector.detect(data, name).then((result) => { + console.log(" FileDetector: ", result); + if (result) { + currentFile.originalType = result.type; + currentFile.originalData = result.data; + if (result.data) { + if ( + result.data.xAspect && + result.data.yAspect && + result.data.xAspect !== result.data.yAspect + ) { + console.warn( + "Aspect ratio is not square! -> " + + result.data.xAspect / result.data.yAspect + ); + } + + if (result.data.palette && target==="file") { + Palette.set(result.data.palette); + } + + if (result.data.colourRange) { + console.log( + "Image has color cycling: ", + result.data.colourRange + ); + } + } + let image = result.image; + handleOpenedImage(image,fileName,target); + + let time = performance.now() - now; + console.log("File loaded in " + time + "ms"); + } else { + if (stillTryImage) { + // happens when the file is not coming from a file upload + var image = new Image(); + image.onload = function(){ + URL.revokeObjectURL(this.src); + handleOpenedImage(image,fileName,target); + }; + image.onerror = function(){ + URL.revokeObjectURL(this.src); + console.error("File is not a default image type"); + }; + image.setAttribute("crossOrigin", ""); + var arrayBufferView = new Uint8Array(data); + var blob = new Blob([arrayBufferView], { + type: "image/png", + }); + image.src = URL.createObjectURL(blob); + } + } + }); + }; + + me.handleJSON = function(data,target){ + if (data.type === "dpaint") { + + if (target==="file"){ + if (data.palette) Palette.set(data.palette); + + if (data.paletteList){ + Palette.setPaletteList(data.paletteList); + Palette.setPaletteIndex(data.paletteIndex); + } + + if (data.colorRange){ + currentFile.colorRange = data.colorRange; + } + } + + switch (target){ + case "frame": + break; + case "brush": + Brush.import(data); + break; + default: + me.restore(data); + } + } + if (data.type === "palette") { + Palette.set(data.palette); + } + } + + function handleOpenedImage(image,fileName,target){ + switch (target){ + case "frame": + if (Array.isArray(image)) { + drawFrame(image[0], fileName); + } else { + drawFrame(image, fileName); + } + break; + case "brush": + Brush.import(image); + break; + default: + if (Array.isArray(image)) { + newFile(image[0],fileName,currentFile.originalType,currentFile.originalData); + EventBus.hold(); + for (let i = 1; i < image.length; i++) addFrame(image[i]); + EventBus.release(); + EventBus.trigger(EVENT.framesChanged); + } else { + newFile(image,fileName,currentFile.originalType,currentFile.originalData) + } + } + } + + function newFile(image,fileName,type,originalData){ + Historyservice.clear(); + cachedImage = undefined; + let w = 320; + let h = 256; + if (image) { + w = image.width; + h = image.height; + } + currentFile = { + width: w, + height: h, + name: fileName || "Untitled", + frames:[{ + layers:[] + }], + colorRange:[] + } + if (type) currentFile.originalType = type; + if (originalData){ + if (originalData.palette) currentFile.palette = originalData.palette; + if (originalData.colourRange) currentFile.colorRange = originalData.colourRange; + if (originalData.pixels) currentFile.indexedPixels = originalData.pixels; + currentFile.originalData = originalData; + } + activeFrameIndex = 0; + activeLayerIndex = 0; + addLayer(); + activeLayer = currentFrame().layers[0]; + activeLayer.clear(); + if (image) { + activeLayer.getContext().drawImage(image, 0, 0); + } + EventBus.trigger(EVENT.imageSizeChanged); + } + + function addLayer(index,name,options){ + let newLayer = Layer( + currentFile.width, + currentFile.height, + name || "Layer " + (currentFrame().layers.length + 1) + ); + let newIndex = currentFrame().layers.length; + if (options){ + if (options.locked) newLayer.locked = true; + if (options.internal) newLayer.internal = true; + } + + if (typeof index === "undefined") { + currentFrame().layers.push(newLayer); + } else { + currentFrame().layers.splice(index, 0, newLayer); + newIndex = index; + } + EventBus.trigger(EVENT.layersChanged); + return newIndex; + } + + function removeLayer(index){ + if (typeof index === "undefined") index = activeLayerIndex; + if (currentFrame().layers.length > 1) { + currentFrame().layers.splice(index, 1); + if (activeLayerIndex >= currentFrame().layers.length) { + activeLayerIndex--; + } + me.activateLayer(activeLayerIndex); + EventBus.trigger(EVENT.imageContentChanged); + } + } + + function moveLayer(fromIndex,toIndex){ + if (currentFrame().layers.length > 1) { + if (toIndex >= currentFrame().layers.length) { + toIndex = currentFrame().layers.length - 1; + } + if (toIndex < 0) toIndex = 0; + if (toIndex !== fromIndex) { + let layer = currentFrame().layers[fromIndex]; + currentFrame().layers.splice(fromIndex, 1); + currentFrame().layers.splice(toIndex, 0, layer); + } + me.activateLayer(toIndex); + EventBus.trigger(EVENT.imageContentChanged); + } + } + + function addFrame(image){ + let layer = Layer(currentFile.width, currentFile.height, "Layer 1"); + currentFile.frames.push({ + layers: [layer], + }); + if (image) { + if (image.placeholder){ + layer.placeholder = true; + }else{ + if (image.width) layer.getContext().drawImage(image, 0, 0); + } + } + EventBus.trigger(EVENT.imageSizeChanged); + } + + function removeFrame(){ + if (currentFile.frames.length > 1) { + HistoryService.start(EVENT.imageHistory); + currentFile.frames.splice(activeFrameIndex, 1); + if (activeFrameIndex >= currentFile.frames.length) { + activeFrameIndex--; + } + Historyservice.end(); + me.activateFrame(activeFrameIndex); + EventBus.trigger(EVENT.imageSizeChanged); + } + } + + function drawFrame(image,fileName){ + let layerIndex = me.addLayer(0, fileName); + let layer = me.getLayer(layerIndex); + layer.clear(); + layer.drawImage(image); + me.activateLayer(layerIndex); + EventBus.trigger(EVENT.layerContentChanged); + } + + function currentFrame(){ + return currentFile.frames[activeFrameIndex]; + } + + function stop(){ + if (Palette.isCycling()) EventBus.trigger(COMMAND.CYCLEPALETTE); + } + + me.duplicateFrame = function(index){ + HistoryService.start(EVENT.imageHistory); + if (typeof index !== "number") index = activeFrameIndex; + let layers = currentFrame().layers; + let newFrame = { layers: [] }; + layers.forEach((layer) => { + let newLayer = Layer( + currentFile.width, + currentFile.height, + layer.name + ); + newLayer.opacity = layer.opacity; + newLayer.blendMode = layer.blendMode; + newLayer.drawImage(layer.getCanvas()); + newFrame.layers.push(newLayer); + }); + currentFile.frames.splice(index + 1, 0, newFrame); + Historyservice.end(); + EventBus.trigger(EVENT.imageSizeChanged); + }; + + me.moveFrame = (fromIndex,toIndex) => { + if (currentFile.frames.length > 1) { + if (toIndex >= currentFile.frames.length) { + toIndex = currentFile.frames.length - 1; + } + if (toIndex < 0) toIndex = 0; + if (toIndex !== fromIndex) { + let frame = currentFile.frames[fromIndex]; + currentFile.frames.splice(fromIndex, 1); + currentFile.frames.splice(toIndex, 0, frame); + } + me.activateFrame(toIndex); + EventBus.trigger(EVENT.imageContentChanged); + } + }; + + me.mergeDown = function (index,skipHistory){ + if (typeof index !== "number") index = activeLayerIndex; + let layer = currentFrame().layers[index]; + let belowLayer = currentFrame().layers[index - 1]; + if (layer && belowLayer) { + if (!skipHistory) HistoryService.start(EVENT.imageHistory); + if (layer.hasMask) { + layer.removeMask(true); + } + let ctx = belowLayer.getContext(); + ctx.globalAlpha = layer.opacity; + let blendMode = layer.blendMode || "normal"; + if (blendMode === "normal") blendMode = "source-over"; + ctx.globalCompositeOperation = blendMode; + belowLayer.drawImage(layer.getCanvas(), 0, 0); + ctx.globalAlpha = 1; + ctx.globalCompositeOperation = "source-over"; + currentFrame().layers.splice(index, 1); + if (!skipHistory) Historyservice.end(); + me.activateLayer(index - 1); + EventBus.trigger(EVENT.layerContentChanged); + } + }; + + me.paste = function(image){ + let w = ImageFile.getCurrentFile().width; + let h = ImageFile.getCurrentFile().height; + + function doPaste() { + // check if a mask is active on the current layer + let layer = me.getActiveLayer(); + if (layer.hasMask && layer.isMaskActive()) { + console.log("Pasting into mask"); + }else{ + let index = me.addLayer(); + me.activateLayer(index); + } + + me.getActiveLayer().drawImage(image, 0, 0); + EventBus.trigger(EVENT.layerContentChanged); + } + + if (image && (image.width > w || image.height > h)) { + Modal.show(DIALOG.OPTION, { + title: "Paste Image", + width: 320, + text: "The image you are pasting is larger than the current canvas. What do you want to do?", + buttons: [ + { + label: "Keep the canvas at " + w + "x" + h + " pixels", + onclick: doPaste, + }, + { + label: + "Enlarge the canvas to " + + image.width + + "x" + + image.height + + " pixels", + onclick: () => { + me.resize({ + width: image.width, + height: image.height, + }); + doPaste(); + }, + }, + { label: "Cancel" }, + ], + }); + } else { + doPaste(); + } + }; + + me.addRange = function(){ + currentFile.colorRange = currentFile.colorRange || []; + currentFile.colorRange.push({ + active: true, + high:1, + low:0, + fps:10 + }); + EventBus.trigger(EVENT.colorRangesChanged); + } + + me.generateIndexedPixels = function(frameIndex,oneDimensional){ + console.log("generate indexed pixels for frame " + frameIndex); + let now = performance.now(); + let ctx = me.getCanvas(frameIndex).getContext("2d"); + let colors = Palette.get(); + + let indexed = indexPixelsToPalette(ctx,colors,oneDimensional); + + currentFile.indexedPixels = indexed.pixels; + let time = performance.now() - now; + console.log("Indexed pixels generated in " + time + "ms"); + if (indexed.notFoundCount){ + console.warn("Indexed pixels: " + indexed.notFoundCount + " colors not found in palette"); + } + return currentFile.indexedPixels; + + } + + + EventBus.on(COMMAND.NEW, function(){ + stop(); + newFile(); + }); + + EventBus.on(COMMAND.SAVE, function(){ + me.save(); + }); + + EventBus.on(COMMAND.RESIZE, function(){ + me.resize(); + }); + + EventBus.on(COMMAND.RESAMPLE, function(){ + me.resample(); + }); + + EventBus.on(COMMAND.INFO, function(){ + SidePanel.showInfo(currentFile); + }); + + EventBus.on(COMMAND.NEWLAYER, function(){ + SidePanel.show(); + let newIndex = addLayer(activeLayerIndex+1); + HistoryService.add(EVENT.layerPropertyHistory,{ + index:-1, + currentIndex:activeLayerIndex + },{ + index:newIndex + }); + }); + + EventBus.on(COMMAND.DELETELAYER, function(){ + HistoryService.start(EVENT.imageHistory); + removeLayer(); + HistoryService.end(); + }); + + EventBus.on(COMMAND.DUPLICATELAYER, function(){ + HistoryService.start(EVENT.imageHistory); + me.duplicateLayer(); + HistoryService.end(); + }); + + EventBus.on(COMMAND.FLIPHORIZONTAL, function(){ + HistoryService.start(EVENT.layerContentHistory); + me.flipLayer(undefined,true); + HistoryService.end(); + }); + EventBus.on(COMMAND.FLIPVERTICAL, function(){ + HistoryService.start(EVENT.layerContentHistory); + me.flipLayer(undefined,false); + HistoryService.end(); + }); + + EventBus.on(COMMAND.LAYERUP, function(index){ + if (typeof index === "undefined") index = activeLayerIndex; + let fromIndex = index; + let toIndex = fromIndex + 1; + moveLayer(fromIndex, toIndex); + }); + + EventBus.on(COMMAND.LAYERDOWN, function(index){ + if (typeof index === "undefined") index = activeLayerIndex; + let fromIndex = index; + let toIndex = fromIndex - 1; + moveLayer(fromIndex, toIndex); + }); + + EventBus.on(COMMAND.MERGEDOWN, function(index){ + HistoryService.start(EVENT.imageHistory); + me.mergeDown(index); + HistoryService.end(); + }); + + EventBus.on(COMMAND.FLATTEN, function(){ + HistoryService.start(EVENT.imageHistory); + currentFrame().layers.forEach((layer) => { + if (layer.hasMask) { + layer.removeMask(true); + EventBus.trigger(EVENT.layersChanged); + } + }); + + if (currentFrame().layers.length > 1) { + let canvas = me.getCanvas(); + currentFrame().layers.splice(0, currentFrame().layers.length - 1); + let layer = currentFrame().layers[0]; + if (layer) { + layer.clear(); + layer.drawImage(canvas, 0, 0); + layer.opacity = 100; + layer.blendMode = "normal"; + layer.visible = true; + } + me.activateLayer(0); + EventBus.trigger(EVENT.imageContentChanged); + } + HistoryService.end(); + }); + + EventBus.on(COMMAND.ADDFRAME, function(){ + HistoryService.start(EVENT.imageHistory); + SidePanel.show(); + addFrame(); + HistoryService.end(); + }); + + EventBus.on(COMMAND.DELETEFRAME, function(){ + HistoryService.start(EVENT.imageHistory); + removeFrame(); + HistoryService.end(); + }); + + EventBus.on(COMMAND.DUPLICATEFRAME, function(){ + HistoryService.start(EVENT.imageHistory); + me.duplicateFrame(); + HistoryService.end(); + }); + + EventBus.on(COMMAND.IMPORTLAYER, function(){ + var input = document.createElement("input"); + input.type = "file"; + input.onchange = function (e) { + handleUpload(e.target.files, "frame"); + }; + input.click(); + }); + + EventBus.on(EVENT.layerContentChanged, function(options){ + options = options || {}; + if (!options.keepImageCache) cachedImage = undefined; + if (activeLayer) activeLayer.update(); + me.render(); + EventBus.trigger(EVENT.imageContentChanged); + }); + + EventBus.on(EVENT.layersChanged, () => { + cachedImage = undefined; + autoSave(); + }); + + EventBus.on(EVENT.imageContentChanged, () => { + autoSave(); + }); + + EventBus.on(EVENT.imageSizeChanged,()=>{ + autoSave(); + }); + + EventBus.on(EVENT.historyChanged,()=>{ + autoSave(); + }); + + + window.getCurrentFile = me.getCurrentFile + + return me; +}(); + +export default ImageFile; diff --git a/app/web-tools/dpaint/_script/paintTools/rotSprite.js b/app/web-tools/dpaint/_script/paintTools/rotSprite.js new file mode 100644 index 00000000..6bef0fdf --- /dev/null +++ b/app/web-tools/dpaint/_script/paintTools/rotSprite.js @@ -0,0 +1,338 @@ +// Based on https://github.com/adnanlah/rotsprite-webgl/blob/master/src/utils/RotspriteAlgoWebGL.ts +// (c) 2022 Abdelrahman Adnan Lahrech +// MIT License +// additional changes by Steffest + +let vshader = ` + attribute vec4 position; + attribute vec2 texCoord; + + uniform mediump int isUpscale; // acts like a boolean + uniform mediump vec2 resolution; + uniform vec2 translation; + uniform mat4 scale; + uniform mat4 rotation; + + varying vec2 v_texCoord; + void main() { + if (isUpscale == 0) { + // Rotate, translate and downscale + vec2 zeroToOne = vec2(rotation * position) / resolution; + vec2 zeroToTwo = zeroToOne * 2.0; + vec2 clipSpace = zeroToTwo - 1.0; + vec4 rotatedPos = vec4(clipSpace + 1.0, 1.0, 1.0); + gl_Position = (scale * vec4(clipSpace + translation, 1.0, 1.0)); + } else { + gl_Position = position; + }; + v_texCoord = texCoord; + } +` +let fshader = ` + precision mediump float; + + uniform sampler2D sampler; + uniform int isUpscale; + uniform vec2 resolution; + + varying vec2 v_texCoord; + + void main() { + vec4 P = texture2D(sampler, v_texCoord); + if (isUpscale > 0) { + // EPX Scale + vec4 A; vec4 B; vec4 C; vec4 D; + vec4 color = P; + float pixw = 1.0 / resolution.x; + float pixh = 1.0 / resolution.y; + + if (gl_FragCoord.y > 1.0) { + vec2 newTexCoord = vec2(v_texCoord.x, v_texCoord.y - (2.0*pixh)); + A = texture2D(sampler, newTexCoord); + } else { + A = P; + }; + if (gl_FragCoord.x < (resolution.x-2.0)) { + vec2 newTexCoord = vec2(v_texCoord.x + (2.0*pixw), v_texCoord.y); + B = texture2D(sampler, newTexCoord); + } else { + B = P; + }; + if (gl_FragCoord.x > 1.0) { + vec2 newTexCoord = vec2(v_texCoord.x - (2.0*pixw), v_texCoord.y); + C = texture2D(sampler, newTexCoord); + } else { + C = P; + }; + if (gl_FragCoord.y < (resolution.y-2.0)) { + vec2 newTexCoord = vec2(v_texCoord.x, v_texCoord.y + (2.0*pixh)); + D = texture2D(sampler, newTexCoord); + } else { + D = P; + }; + + // Shift gl_FragCoord by 0.5 pixel + vec4 FragCoordShifted = gl_FragCoord - vec4(0.5, 0.5, 0.0, 0.0); + + // Override color value + if (mod(FragCoordShifted.x, 2.0) == 0.0 && mod(FragCoordShifted.y, 2.0) == 0.0) { + if ((C == A) && (C != D) && (A != B)) { + color = A; + }; + + } else if (mod(FragCoordShifted.x, 2.0) != 0.0 && mod(FragCoordShifted.y, 2.0) == 0.0) { + if ((A == B) && (A != C) && (B != D)) { + color = B; + }; + } else if (mod(FragCoordShifted.x, 2.0) == 0.0 && mod(FragCoordShifted.y, 2.0) != 0.0) { + if ((D == C) && (D != B) && (C != A)) { + color = C; + }; + } else if (mod(FragCoordShifted.x, 2.0) != 0.0 && mod(FragCoordShifted.y, 2.0) != 0.0) { + if ((B == D) && (B != A) && (D != C)) { + color = D; + }; + }; + + gl_FragColor = color; + } else { + gl_FragColor = P; + } + } +` + +const RotSprite = async (inCanvas, degrees,) => { + const SCALE = 8; + + let canvas = document.createElement('canvas'); + canvas.width = inCanvas.width; + canvas.height = inCanvas.height; + + const gl = canvas.getContext('webgl', {antialias: false}); + if (!gl) throw new Error('No GL'); + const program = compile(gl, vshader, fshader); + if (!program) throw new Error('No program'); + + console.log('MAX_TEXTURE_SIZE is: ', gl.getParameter(gl.MAX_TEXTURE_SIZE)); + + // Locations + const resolution = gl.getUniformLocation(program, 'resolution'); + const translation = gl.getUniformLocation(program, 'translation'); + const scale = gl.getUniformLocation(program, 'scale'); + const rotation = gl.getUniformLocation(program, 'rotation'); + const isUpscale = gl.getUniformLocation(program, 'isUpscale'); + const position = gl.getAttribLocation(program, 'position'); + const texCoord = gl.getAttribLocation(program, 'texCoord'); + const sampler = gl.getUniformLocation(program, 'sampler'); + + // Enable EPX Scale + gl.uniform1i(isUpscale, 1); + + // Create the buffer object + const verticesTexCoords = new Float32Array([ + -1, 1, 0.0, 1.0, -1, -1, 0.0, 0.0, 1, 1, 1.0, 1.0, 1, -1, 1.0, 0.0, + ]); + const N = 4; + const FSIZE = verticesTexCoords.BYTES_PER_ELEMENT; + const vertexTexCoordBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer); + gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW); + + // Use every 1st and 2nd float for position + gl.vertexAttribPointer(position, 2, gl.FLOAT, false, FSIZE * 4, 0); + gl.enableVertexAttribArray(position); + + // Use every 3rd and 4th float for texCoord + gl.vertexAttribPointer(texCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2); + gl.enableVertexAttribArray(texCoord); + + // Texture + const texture = gl.createTexture(); + + // Reset flip + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Stretch/wrap options + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + // Pass texture 0 to the sampler + gl.uniform1i(sampler, 0); + + let naturalWidth = canvas.width; + let naturalHeight = canvas.height; + + if (naturalWidth > 512 || naturalHeight > 512) { + throw new Error('Size exceeded'); + } + let currW = 0; + let currH = 0; + + for (let i = 2; i <= SCALE; i *= 2) { + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + gl.RGBA, + gl.UNSIGNED_BYTE, + inCanvas, + ); + + + currW = naturalWidth * i; + currH = naturalHeight * i; + + gl.uniform2f(resolution, currW, currH); + + gl.canvas.width = currW; + gl.canvas.height = currH; + gl.viewport(0, 0, currW, currH); + + gl.drawArrays(gl.TRIANGLE_STRIP, 0, N); + } + + const verticesTexCoordsInPixels = new Float32Array([ + -currW / 2, + currH / 2, + 0.0, + 1.0, + -currW / 2, + -currH / 2, + 0.0, + 0.0, + currW / 2, + currH / 2, + 1.0, + 1.0, + currW / 2, + -currH / 2, + 1.0, + 0.0, + ]); + + // buffer object + const vertexTexCoordBuffer2 = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer2); + gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoordsInPixels, gl.STATIC_DRAW); + + // Updating position attribute + gl.vertexAttribPointer(position, 2, gl.FLOAT, false, FSIZE * 4, 0); + gl.enableVertexAttribArray(position); + + // Flip the image's y axis if scale == 4 + // if (SCALE == 4) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); + // Upload image + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, gl.canvas); + + // Rotating the image + gl.uniform1i(isUpscale, 0); + + const cosA = Math.cos((degrees * Math.PI) / 180); + const sinA = Math.sin((degrees * Math.PI) / 180); + + const rotCornerX = Math.round((currW / 2) * cosA + (currH / 2) * sinA); + const rotCornerY = Math.round((currH / 2) * cosA - (currW / 2) * sinA); + + const rotCorner2X = Math.round((-currW / 2) * cosA + (currH / 2) * sinA); + const rotCorner2Y = Math.round((currH / 2) * cosA - (-currW / 2) * sinA); + + const maxPosX = Math.max(Math.abs(rotCornerX), Math.abs(rotCorner2X)); + const maxPosY = Math.max(Math.abs(rotCornerY), Math.abs(rotCorner2Y)); + + const rotW = maxPosX * 2; + const rotH = maxPosY * 2; + + // Rotate matrix + const r_matrix = new Float32Array([ + cosA, + -sinA, + 0.0, + 0.0, + sinA, + cosA, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ]); + gl.uniformMatrix4fv(rotation, false, r_matrix); + + // Translation vector + const translateX = -(currW - rotW / SCALE - naturalWidth) / naturalWidth; + const translateY = -(currH - rotH / SCALE - naturalHeight) / naturalHeight; + gl.uniform2f(translation, translateX, translateY); + + // Scale matrix + const S = 1 / SCALE; + const s_matrix = new Float32Array([ + S, + 0.0, + 0.0, + 0.0, + 0.0, + S, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ]); + gl.uniformMatrix4fv(scale, false, s_matrix); + + gl.canvas.width = Math.round(rotW / SCALE); + gl.canvas.height = Math.round(rotH / SCALE); + + //gl.canvas.width = gl.canvas.width*0.7; + + gl.drawArrays(gl.TRIANGLE_STRIP, 0, N); + + // export image data + //return gl.canvas.toDataURL(); + return gl.canvas; +} + +const compile = (gl, vshader, fshader) => { + const vs = gl.createShader(gl.VERTEX_SHADER); + if (vs) { + gl.shaderSource(vs, vshader); + gl.compileShader(vs); + } + + const fs = gl.createShader(gl.FRAGMENT_SHADER); + if (fs) { + gl.shaderSource(fs, fshader); + gl.compileShader(fs); + } + + const program = gl.createProgram(); + if (program && vs && fs) { + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + gl.useProgram(program); + + console.log('vertex shader: ', gl.getShaderInfoLog(vs) || 'OK'); + console.log('fragment shader: ', gl.getShaderInfoLog(fs) || 'OK'); + console.log('program: ', gl.getProgramInfoLog(program) || 'OK'); + } + + return program; +} + +export default RotSprite; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/paintTools/smudge.js b/app/web-tools/dpaint/_script/paintTools/smudge.js new file mode 100644 index 00000000..6c7266d8 --- /dev/null +++ b/app/web-tools/dpaint/_script/paintTools/smudge.js @@ -0,0 +1,275 @@ +import ImageFile from "../image.js"; +import EventBus from "../util/eventbus.js"; +import {EVENT} from "../enum.js"; +import Brush from "../ui/brush.js"; +import Palette from "../ui/palette.js"; +import Color from "../util/color.js"; +import {duplicateCanvas} from "../util/canvasUtils.js"; +import DitherPanel from "../ui/toolPanels/ditherPanel.js"; +import ToolOptions from "../ui/components/toolOptions.js"; +import effects from "../ui/effects.js"; + +// somewhat based on https://stackoverflow.com/questions/28197378/html5-canvas-javascript-smudge-brush-tool + +let Smudge = function(){ + let me = {}; + + let lastForce = 1; + let alpha = 0.5; + let hardness = 0.01; + let radius = 10; + const brushCtx = document.createElement('canvas').getContext('2d'); + let composeCtx = document.createElement('canvas').getContext('2d'); + let featherGradient; + let ctx; + let lastX; + let lastY; + let doBlur = false; + let doSharpen = false; + let filters; + let dither = false; + let workingCtx; + + me.start = function(touchData){ + touchData.isSmudging = true; + touchData.drawLayer = ImageFile.getActiveLayer(); + ctx = touchData.drawLayer.getContext(); + let {x,y} = touchData; + + lastX = x; + lastY = y; + lastForce = touchData.force || 1; + + + let settings = Brush.getSettings(); + alpha = settings.opacity/100; + hardness = 1-(settings.softness/10); + radius = settings.width; + if (isNaN(hardness)) hardness = 0.01; + if (radius<2) radius = 2; + + updateBrushSettings(); + doBlur = ToolOptions.getSmudgeAction() === "blur"; + doSharpen = ToolOptions.getSmudgeAction() === "sharpen"; + if (doBlur || doSharpen){ + filters = effects.getFilters(brushCtx); + workingCtx = duplicateCanvas(ctx.canvas,true).getContext("2d"); + } + dither = DitherPanel.getDitherState(); + } + + me.draw = function(touchData){ + if (!touchData.isSmudging) { + return; + } + let {x,y,force} = touchData; + force = force || 1; + let tempCtx; + if (Palette.isLocked()){ + let tempCanvas = duplicateCanvas(brushCtx.canvas); + tempCtx = tempCanvas.getContext("2d"); + } + + let w = brushCtx.canvas.width; + let h = brushCtx.canvas.height; + + + const line = setupLine(lastX, lastY,x, y); + for (let more = true; more;) { + more = advanceLine(line); + if (doBlur) more = false; + + let x = line.position[0] - brushCtx.canvas.width / 2; + let y = line.position[1] - brushCtx.canvas.height / 2; + x = Math.floor(x); + y = Math.floor(y); + + + + if (doBlur || doSharpen){ + brushCtx.clearRect(0,0,w,h); + brushCtx.drawImage(workingCtx.canvas, x,y,w,h,0,0,w,h); + if (doBlur){ + let s = ToolOptions.getStrength() * 10; + if (s>w) s = w; + if (s<1) s = 1; + filters.blur(s); + } + if (doSharpen) filters.sharpen(ToolOptions.getStrength()); + feather(brushCtx); + composeCtx.clearRect(0,0,w,h); + composeCtx.globalAlpha = ToolOptions.getStrength(); + composeCtx.drawImage(brushCtx.canvas, 0, 0); + composeCtx.globalAlpha = 1; + }else{ + composeCtx.globalAlpha = ToolOptions.getStrength(); + composeCtx.globalCompositeOperation='copy'; + composeCtx.drawImage(composeCtx.canvas, 0,0); + composeCtx.globalCompositeOperation = "source-over"; + + composeCtx.globalAlpha = alpha * lerp(lastForce, force, line.u); + composeCtx.drawImage(brushCtx.canvas, 0, 0); + composeCtx.globalAlpha = 1; + } + + + if (dither){ + let pattern = DitherPanel.getDitherPattern(); + composeCtx.globalCompositeOperation = "destination-in"; + composeCtx.drawImage(pattern,x,y,w,h,0,0,w,h); + composeCtx.globalCompositeOperation = "source-over"; + ctx.drawImage(composeCtx.canvas, x, y); + } + + + if (Palette.isLocked()){ + tempCtx.clearRect(0,0,w,h); + tempCtx.drawImage(ctx.canvas,x,y,w,h,0,0,w,h); + tempCtx.drawImage(composeCtx.canvas,0,0); + + // reapply dither + let composeData; + if (dither){ + composeData = composeCtx.getImageData(0,0,w,h); + } + + Palette.applyToCanvas(tempCtx.canvas,true,composeData); + + ctx.drawImage(tempCtx.canvas,x,y); + + }else{ + ctx.drawImage(composeCtx.canvas, x, y); + } + + updateBrush(line.position[0], line.position[1]); + + } + lastX = x; + lastY = y; + lastForce = force; + EventBus.trigger(EVENT.layerContentChanged); + } + + function createFeatherGradient(radius, hardness) { + const innerRadius = Math.min(radius * hardness, radius - 1); + const gradient = brushCtx.createRadialGradient( + 0, 0, innerRadius, + 0, 0, radius); + gradient.addColorStop(0, 'rgba(0, 0, 0, 0)'); + gradient.addColorStop(1, 'rgba(0, 0, 0, 1)'); + return gradient; + } + + function updateBrushSettings() { + featherGradient = createFeatherGradient(radius/2, hardness); + brushCtx.canvas.width = radius; + brushCtx.canvas.height = radius; + composeCtx.canvas.width = radius; + composeCtx.canvas.height = radius; + } + + function feather(ctx) { + // feather the brush + ctx.save(); + ctx.fillStyle = featherGradient; + ctx.globalCompositeOperation = 'destination-out'; + const {width, height} = ctx.canvas; + ctx.translate(width / 2, height / 2); + ctx.fillRect(-width / 2, -height / 2, width, height); + ctx.restore(); + ctx.globalCompositeOperation = 'source-over'; + } + + function updateBrush(x, y) { + let width = brushCtx.canvas.width; + let height = brushCtx.canvas.height; + let srcX = x - width / 2; + let srcY = y - height / 2; + // draw it in the middle of the brush + let dstX = (brushCtx.canvas.width - width) / 2; + let dstY = (brushCtx.canvas.height - height) / 2; + + // clear the brush canvas + brushCtx.clearRect(0, 0, brushCtx.canvas.width, brushCtx.canvas.height); + + // clip the rectangle to be + // inside + if (srcX < 0) { + width += srcX; + dstX -= srcX; + srcX = 0; + } + const overX = srcX + width - ctx.canvas.width; + if (overX > 0) { + width -= overX; + } + + if (srcY < 0) { + dstY -= srcY; + height += srcY; + srcY = 0; + } + const overY = srcY + height - ctx.canvas.height; + if (overY > 0) { + height -= overY; + } + + if (width <= 0 || height <= 0) { + return; + } + + brushCtx.drawImage( + ctx.canvas, + srcX, srcY, width, height, + dstX, dstY, width, height); + + feather(brushCtx); + } + + function lerp(a, b, t) { + return a + (b - a) * t; + } + + function setupLine(x, y, targetX, targetY) { + const deltaX = targetX - x; + const deltaY = targetY - y; + const deltaRow = Math.abs(deltaX); + const deltaCol = Math.abs(deltaY); + const counter = Math.max(deltaCol, deltaRow); + const axis = counter == deltaCol ? 1 : 0; + + // setup a line draw. + return { + position: [x, y], + delta: [deltaX, deltaY], + deltaPerp: [deltaRow, deltaCol], + inc: [Math.sign(deltaX), Math.sign(deltaY)], + accum: Math.floor(counter / 2), + counter: counter, + endPnt: counter, + axis: axis, + u: 0, + }; + }; + + function advanceLine(line) { + --line.counter; + line.u = 1 - line.counter / line.endPnt; + if (line.counter <= 0) { + return false; + } + const axis = line.axis; + const perp = 1 - axis; + line.accum += line.deltaPerp[perp]; + if (line.accum >= line.endPnt) { + line.accum -= line.endPnt; + line.position[perp] += line.inc[perp]; + } + line.position[axis] += line.inc[axis]; + return true; + } + + return me; +}(); + +export default Smudge; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/paintTools/spray.js b/app/web-tools/dpaint/_script/paintTools/spray.js new file mode 100644 index 00000000..a21fd63e --- /dev/null +++ b/app/web-tools/dpaint/_script/paintTools/spray.js @@ -0,0 +1,61 @@ +import Palette from "../ui/palette.js"; +import ImageFile from "../image.js"; +import EventBus from "../util/eventbus.js"; +import {ANIMATION, EVENT} from "../enum.js"; +import Animator from "../util/animator.js"; +import ToolOptions from "../ui/components/toolOptions.js"; +import Brush from "../ui/brush.js"; + +let Spray = (()=>{ + + let me={}; + let currentData; + let speed = 4; + let size = 20; + let useOpacity = true; + + me.start=function(touchData){ + size = parseInt(ToolOptions.getSpread()) + 1; + speed = Math.floor(ToolOptions.getStrength()*20) + 1; + useOpacity = ToolOptions.usePressure(); + if (!useOpacity) Brush.setPressure(1); + + let {x,y} = touchData; + let color = touchData.button?Palette.getBackgroundColor():Palette.getDrawColor(); + + touchData.isSpraying = true; + touchData.drawLayer = ImageFile.getActiveLayer(); + touchData.drawLayer.draw(x,y,color,touchData); + currentData = touchData; + + EventBus.trigger(EVENT.layerContentChanged); + + Animator.start(ANIMATION.SPRAY,()=>{ + let {x,y} = currentData; + let color = currentData.button?Palette.getBackgroundColor():Palette.getDrawColor(); + + for (let i = 0; i < speed; i++){ + let angle = Math.random() * Math.PI * 2; + let radius = Math.sqrt(Math.random()) * size; + let _x = Math.round(x + radius * Math.cos(angle)); + let _y = Math.round(y + radius * Math.sin(angle)); + if (useOpacity) Brush.setPressure(Math.random()); + currentData.drawLayer.draw(_x,_y,color,currentData); + } + + EventBus.trigger(EVENT.layerContentChanged); + },50); + + + } + + me.stop=function(){ + if (!currentData) return; + currentData.isSpraying = false; + Animator.stop(ANIMATION.SPRAY); + } + + return me; +})(); + +export default Spray; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/paintTools/text.js b/app/web-tools/dpaint/_script/paintTools/text.js new file mode 100644 index 00000000..74231970 --- /dev/null +++ b/app/web-tools/dpaint/_script/paintTools/text.js @@ -0,0 +1,157 @@ +import Input from "../ui/input.js"; +import ImageFile from "../image.js"; +import EventBus from "../util/eventbus.js"; +import {COMMAND, EVENT, ANIMATION} from "../enum.js"; +import Animator from "../util/animator.js"; +import ToolOptions from "../ui/components/toolOptions.js"; +import Palette from "../ui/palette.js"; + +let Text = (()=>{ + let me = {}; + let currentText = {}; + let cursorOn; + let isActive ; + + let fonts = [ + {name:"Arial"}, + {name:"Courier New"}, + {name:"Georgia"}, + {name:"GillSans-UltraBold"}, + {name:"Times New Roman"}, + {name:"Topaz Serif", url:"_font/amiga-topaz.otf"}, + {name:"Topaz Sans", url:"_font/topaz-8.ttf"}, + {name:"Verdana"} + ] + + // TODO: load Amiga fonts + // see https://github.com/smugpie/amiga-bitmap-font-tools + + // TODO: implement bitmap fonts + // https://github.com/ianhan/BitmapFonts + // https://www.spriters-resource.com/amiga_amiga_cd32/gods/sheet/111137/ + + me.start=async (touchData)=>{ + if (isActive) me.stop(); + Input.setActiveKeyHandler(keyHandler); + currentText.layerIndex = ImageFile.addLayer(ImageFile.getActiveLayerIndex()+1,"Text"); + ImageFile.activateLayer(currentText.layerIndex); + currentText.layer = ImageFile.getLayer(currentText.layerIndex); + currentText.ctx = currentText.layer.getContext(); + currentText.x = touchData.x; + currentText.y = touchData.y; + currentText.text = ""; + currentText.fontSize = ToolOptions.getFontSize(); + currentText.ctx.font = currentText.fontSize + "px " + ToolOptions.getFont(); + currentText.color = Palette.getDrawColor(); + cursorOn = true; + isActive = true; + let font = fonts.find(f=>f.name === ToolOptions.getFont()); + await loadFont(font); + drawText(); + Animator.start(ANIMATION.TEXT,()=>{ + cursorOn = !cursorOn; + drawText(); + },2); + } + + me.stop=(commit)=>{ + Input.setActiveKeyHandler(null); + Animator.stop(ANIMATION.TEXT); + currentText.text = currentText.text || ""; + if (currentText.text.length === 0 && currentText.layerIndex){ + ImageFile.removeLayer(currentText.layerIndex); + }else{ + currentText.layer.clear(); + cursorOn = false; + drawText(); + } + currentText = {}; + isActive = false; + if (commit) EventBus.trigger(COMMAND.DRAW); + } + + me.getFonts = ()=>{ + return fonts.map(f=>f.name); + } + + function loadFont(font){ + return new Promise((next)=>{ + if (font && font.url && !font.loaded){ + const fontFace = new FontFace(font.name, 'url('+font.url+')'); + fontFace.load().then(loadedFont=>{ + document.fonts.add(loadedFont); + font.loaded = true; + console.log("Font " + font.name + " loaded"); + next(); + }).catch(err=>{ + console.error("Error loading font " + font.name,err); + next(); + }); + }else{ + next(); + } + }); + } + + function keyHandler(code,key){ + switch (code){ + case "enter": + case "escape": + me.stop(true); + break; + case "backspace": + currentText.text = currentText.text.slice(0,-1); + drawText(); + return true; + default: + if (key.length > 1) return false; + let char=key; + if (Input.isMetaDown()) char = char.toUpperCase(); + currentText.text += char; + drawText(); + return true; + } + } + + function drawText(){ + currentText.layer.clear(); + currentText.ctx.fillStyle = currentText.color; + currentText.ctx.fillText(currentText.text,currentText.x,currentText.y); + if (cursorOn){ + let w = Math.ceil(currentText.ctx.measureText(currentText.text).width); + currentText.ctx.beginPath(); + currentText.ctx.strokeStyle = currentText.color; + currentText.ctx.lineWidth = 2; + currentText.ctx.moveTo(currentText.x + w,currentText.y-currentText.fontSize+2); + currentText.ctx.lineTo(currentText.x + w,currentText.y+1); + currentText.ctx.stroke(); + } + EventBus.trigger(EVENT.layerContentChanged); + } + + EventBus.on(EVENT.toolChanged,()=>{ + if (isActive) me.stop(); + }); + + EventBus.on(EVENT.fontStyleChanged,async font=>{ + if (isActive && currentText.ctx){ + let f = fonts.find(f=>f.name === font.name); + await loadFont(f); + currentText.ctx.font = font.size + "px " + font.name; + currentText.fontSize = font.size; + drawText(); + } + }); + + EventBus.on(EVENT.drawColorChanged,()=>{ + if (isActive){ + currentText.color = Palette.getDrawColor(); + drawText(); + } + }); + + + return me; +})(); + +export default Text; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/services/historyservice.js b/app/web-tools/dpaint/_script/services/historyservice.js new file mode 100644 index 00000000..1c34df18 --- /dev/null +++ b/app/web-tools/dpaint/_script/services/historyservice.js @@ -0,0 +1,200 @@ +import EventBus from "../util/eventbus.js"; +import {COMMAND, EVENT} from "../enum.js"; +import ImageFile from "../image.js"; +import {duplicateCanvas} from "../util/canvasUtils.js"; + +let HistoryService = function(){ + let me = {}; + + let maxHistory = 20; + let history = []; + let future = []; + let currentHistory; + + // TODO: maybe add a "framehistory" type to store the current frame instead of the whole image? + + me.start = function(type,data){ + console.log("start his"); + currentHistory={type,data:{}}; + let index = ImageFile.getActiveLayerIndex(); + if (typeof data === "number") index = data; + + switch (type){ + case EVENT.layerContentHistory: + currentHistory.data.layerIndex = index; + currentHistory.data.from = duplicateCanvas(ImageFile.getActiveContext().canvas,true); + break; + case EVENT.layerPropertyHistory: + currentHistory.data.layerIndex = index; + currentHistory.data.from = getLayerProperties(index); + break; + case EVENT.layerHistory: + currentHistory.data.layerIndex = index; + currentHistory.data.from = ImageFile.getActiveLayer().clone(); + break; + case EVENT.imageHistory: + currentHistory.data.from = ImageFile.clone(); + // TODO: this also clears all masks and selections + // and it doesn't hold the current layer so future undo actions wont work ... + // FIXME + break; + default: + console.error("History type " + type + " not handled"); + } + } + + me.end=function(){ + if (currentHistory){ + console.log("end his"); + switch (currentHistory.type){ + case EVENT.layerContentHistory: + currentHistory.data.to = duplicateCanvas(ImageFile.getActiveContext().canvas,true) + break; + case EVENT.layerPropertyHistory: + currentHistory.data.to = getLayerProperties(currentHistory.data.layerIndex); + break; + case EVENT.layerHistory: + currentHistory.data.to = ImageFile.getLayer(currentHistory.data.layerIndex).clone(); + break; + case EVENT.imageHistory: + currentHistory.data.to = ImageFile.clone(); + break; + } + + history.unshift(currentHistory); + if (history.length>maxHistory) history.pop(); + future=[]; + currentHistory = undefined; + EventBus.trigger(EVENT.historyChanged,[history.length,future.length]); + } + } + + me.neverMind = function(){ + currentHistory = undefined; + } + + me.add = function(type,from,to){ + history.unshift({type,data:{from,to}}); + if (history.length>maxHistory) history.pop(); + future=[]; + EventBus.trigger(EVENT.historyChanged,[history.length,future.length]); + } + + me.clear = function(){ + history = []; + future = []; + } + + function getLayerProperties(index){ + let layer = ImageFile.getLayer(index); + return { + name: layer.name, + visible: layer.visible, + hasMask: layer.hasMask, + maskActive: layer.isMaskActive(), + index: index + } + } + + EventBus.on(COMMAND.UNDO,()=>{ + if (history.length){ + let historyStep = history.shift(); + let layer; + let target; + switch (historyStep.type){ + case EVENT.layerContentHistory: + layer = ImageFile.getLayer(historyStep.data.layerIndex); + layer.clear(); + layer.drawImage(historyStep.data.from); + EventBus.trigger(EVENT.layerContentChanged); + break; + case EVENT.imageHistory: + ImageFile.restore(historyStep.data.from); + EventBus.trigger(COMMAND.CLEARSELECTION); + break; + case EVENT.layerPropertyHistory: + target = historyStep.data.from; + let source = historyStep.data.to; + if (target.index<0){ + // add new layer + ImageFile.removeLayer(target.index); + ImageFile.activateLayer(target.currentIndex); + }else{ + layer = ImageFile.getLayer(target.index); + if (typeof target.name === "string") layer.name = target.name; + if (typeof target.visible === "boolean") layer.visible = target.visible; + if (source.hasMask && source.maskActive !== target.maskActive) layer.toggleMask(); + console.error(layer); + } + EventBus.trigger(EVENT.layersChanged); + break; + case EVENT.layerHistory: + layer = ImageFile.getLayer(historyStep.data.layerIndex); + layer.restore(historyStep.data.from); + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.layersChanged); + break; + default: + console.error("History type " + historyStep.type + " not handled"); + } + + future.unshift(historyStep); + if (future.length>maxHistory) future.pop(); + EventBus.trigger(EVENT.historyChanged,[history.length,future.length]); + + } + }) + + EventBus.on(COMMAND.REDO,()=>{ + if (future.length){ + let historyStep = future.shift(); + let layer; + let target; + let source; + //console.log(historyStep); + switch (historyStep.type){ + case EVENT.layerContentHistory: + layer = ImageFile.getActiveLayer(); + layer.clear(); + layer.drawImage(historyStep.data.to); + EventBus.trigger(EVENT.layerContentChanged); + break; + case EVENT.imageHistory: + ImageFile.restore(historyStep.data.to); + EventBus.trigger(COMMAND.CLEARSELECTION); + break; + case EVENT.layerPropertyHistory: + target = historyStep.data.to; + source = historyStep.data.from; + if (source.index<0){ + ImageFile.activateLayer(source.currentIndex); + ImageFile.addLayer(source.currentIndex); + }else{ + layer = ImageFile.getLayer(target.index); + if (typeof target.name === "string") layer.name = target.name; + if (typeof target.visible === "boolean") layer.visible = target.visible; + if (source.hasMask && source.maskActive !== target.maskActive) layer.toggleMask(); + } + EventBus.trigger(EVENT.layersChanged); + break; + case EVENT.layerHistory: + layer = ImageFile.getLayer(historyStep.data.layerIndex); + layer.restore(historyStep.data.to); + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.layersChanged); + break; + default: + console.error("History type " + historyStep.type + " not handled"); + } + + history.unshift(historyStep); + if (history.length>maxHistory) history.pop(); + EventBus.trigger(EVENT.historyChanged,[history.length,future.length]); + } + }) + + + return me; +}() + +export default HistoryService; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/brush.js b/app/web-tools/dpaint/_script/ui/brush.js new file mode 100644 index 00000000..2ed84275 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/brush.js @@ -0,0 +1,463 @@ +import {$div} from "../util/dom.js"; +import EventBus from "../util/eventbus.js"; +import {COMMAND, EVENT} from "../enum.js"; +import Palette from "./palette.js"; +import Color from "../util/color.js"; +import Editor from "./editor.js"; +import ImageFile from "../image.js"; +import ToolOptions from "./components/toolOptions.js"; +import {duplicateCanvas, releaseCanvas} from "../util/canvasUtils.js"; +import ImageProcessing from "../util/imageProcessing.js"; +import Modal from "./modal.js"; + +var Brush = function(){ + var me = {}; + var container; + var width = 1; + var height = 1; + var brushType = "square"; + var presets = []; + var brushIndex = 0; + let dynamicSettings; + let opacity = 100; + let pressure = 1; + + var brushCanvas = document.createElement("canvas"); + var brushBackCanvas = document.createElement("canvas"); + let brushCtx = brushCanvas.getContext("2d"); + let brushBackCtx = brushBackCanvas.getContext("2d"); + let brushAlphaLayer; + + me.init = function(parent){ + container = $div("brushes info","",parent); + container.info = "Select the brush for drawing"; + for (var i = 0; i<10;i++){ + let b = $div("brush","",container,(e)=>{ + let index = e.target.index || 0; + me.set("preset",index); + let currentTool = Editor.getCurrentTool(); + if (!(currentTool === COMMAND.DRAW || currentTool === COMMAND.ERASE)){ + EventBus.trigger(COMMAND.DRAW); + } + }) + let x = -(i % 5)*11 + "px"; + let y = (Math.floor(i / 5) * -11) + "px"; + b.style.backgroundPosition = x + " " + y; + b.index = i; + if (!i) b.classList.add("active"); + presets.push(b); + } + + EventBus.on(EVENT.drawColorChanged,()=>{ + if (brushType === 'canvas'){ + if (brushIndex>=0){ + generateBrush(); + }else{ + generateStencil(); + } + } + }) + EventBus.on(EVENT.backgroundColorChanged,()=>{ + if (brushType === 'canvas' && brushIndex>=0){ + generateBrush(); + } + }) + } + + me.get = function(){ + + } + + me.getOpacity = ()=>{ + return opacity/100; + } + + me.getSettings=()=>{ + let settings = { + width:width, + height:height, + opacity: opacity + }; + if (dynamicSettings){ + settings.softness = dynamicSettings.softness; + } + return settings; + } + + me.set = function(type,index){ + if (type === "preset"){ + presets.forEach((b,i)=>{ + b.classList.toggle("active",i===index); + }); + brushIndex = index; + switch (index){ + case 0: + brushType = "square"; + width = height = 1; + break; + case 1: + case 2: + case 3: + case 4: + brushType = "canvas"; + let size = (index*2) + 1; + if (brushIndex === 3) size = 5; + if (brushIndex === 4) size = 7; + width = height = size; + generateBrush(); + break; + case 5: + case 6: + case 7: + case 8: + brushType = "square"; + width = height = (9-index)*2 + 1; + break; + case 9: + brushType = "canvas"; + width = height = 7; + generateBrush(); + break; + } + } + + if (type === "canvas"){ + brushType = type; + brushIndex = -1; + brushCanvas.width = brushBackCanvas.width = index.width; + brushCanvas.height = brushBackCanvas.height = index.height; + brushCtx.clearRect(0,0,brushCanvas.width,brushCanvas.height); + brushCtx.drawImage(index,0,0); + brushBackCtx.clearRect(0,0,brushCanvas.width,brushCanvas.height); + brushBackCtx.drawImage(index,0,0); + // TODO: What do we draw when grabbing a stencil with right-click draw ? + brushAlphaLayer = undefined; + } + + if (type === "dynamic"){ + width = index.width; + height = index.height; + opacity = index.opacity || 100; + + if (!index.softness){ + brushType = "square"; + }else{ + brushIndex = 100; + brushType = "canvas"; + dynamicSettings = index; + generateBrush(); + + } + } + + //EventBus.trigger(EVENT.brushOptionsChanged); + } + + me.setPressure = (p)=>{ + pressure = p; + } + + me.getPressure = ()=>{ + return pressure; + } + + + me.draw = function(ctx,x,y,color,onBackground,blendColor){ + + let w,h,p; + let useCustomBlend = blendColor && Palette.isLocked(); + let useOpacity = ToolOptions.usePressure() && !useCustomBlend; + let finalColor = color; + + if (brushType === "canvas"){ + w = brushCanvas.width; + h = brushCanvas.height; + }else{ + w = width; + h = height; + } + + if (w>1) x -= Math.floor((w-1)/2); + if (h>1) y -= Math.floor((h-1)/2); + + if (useOpacity){ + p = ctx.globalAlpha; + ctx.globalAlpha = pressure; + } + + + if (brushType === "canvas"){ + if (onBackground){ + ctx.drawImage(brushBackCanvas,x,y); + }else{ + ctx.drawImage(brushCanvas,x,y); + } + }else{ + if (blendColor && Palette.isLocked()){ + let c = Color.fromString(color); + let imageCtx = ImageFile.getContext(); + let data = imageCtx.getImageData(x,y,w,h); + let opacity2 = opacity/100; + if (ToolOptions.usePressure()){ + opacity2 *= pressure; + } + + + for (let i = 0; i1) opacity2 = 1; + targetColor = Color.blend(sourceColor,c,opacity2); + finalColor = Palette.matchColor(targetColor); + } + + data.data[i] = finalColor[0]; + data.data[i+1] = finalColor[1]; + data.data[i+2] = finalColor[2]; + data.data[i+3] = 255; + } + ctx.putImageData(data,x,y); + }else{ + ctx.fillStyle = color; + ctx.fillRect(x,y,w,h); + } + + } + + if (useOpacity){ + ctx.globalAlpha = p; + } + + return{ + x:x, + y:y, + width:w, + height:h, + color:finalColor + }; + + } + + me.rotate = (left)=>{ + if (brushType === "canvas"){ + ImageProcessing.rotate(brushCanvas,left); + width = brushCanvas.width; + height = brushCanvas.height; + brushAlphaLayer = undefined; + EventBus.trigger(EVENT.drawCanvasOverlay); + + } + } + + me.flip = (horizontal)=>{ + let canvas = duplicateCanvas(brushCanvas, true); + brushCtx.clearRect(0,0,canvas.width,canvas.height); + if (horizontal) { + brushCtx.translate(canvas.width, 0); + brushCtx.scale(-1, 1); + }else{ + brushCtx.translate(0, canvas.height); + brushCtx.scale(1, -1); + } + brushCtx.drawImage(canvas, 0, 0); + brushCtx.setTransform(1, 0, 0, 1, 0, 0); + releaseCanvas(canvas); + brushAlphaLayer = undefined; + + //brushBackCtx.clearRect(0,0,brushCanvas.width,brushCanvas.height); + //brushBackCtx.drawImage(brushCanvas,0,0); + generateBackBrush(); + window.debug = true; + + EventBus.trigger(EVENT.drawCanvasOverlay); + + } + + me.export = ()=>{ + let currentFile = ImageFile.getCurrentFile(); + let struct = { + type: "dpaint", + version: "1", + image: {}, + }; + struct.image.name = currentFile.name + "_brush"; + struct.image.width = brushCanvas.width; + struct.image.height = brushCanvas.height; + struct.image.frames = [{ + layers: [{ + name: "brush", + opacity:1, + visible:true, + hasMask: false, + canvas: brushCanvas.toDataURL() + }] + }]; + + return struct; + } + + me.import = (data)=>{ + if (!data) return; + if (data.image && data.type === "dpaint"){ + let frame = data.image.frames[0]; + let layer = frame.layers[0]; + let canvas = new Image(); + canvas.onload = ()=>{ + me.set("canvas",canvas); + EventBus.trigger(EVENT.drawCanvasOverlay); + } + canvas.src = layer.canvas; + }else{ + if (data.width && data.height){ + me.set("canvas",data); + EventBus.trigger(EVENT.drawCanvasOverlay); + } + } + } + + me.openLocal = ()=>{ + ImageFile.openLocal("brush"); + } + + function generateBrush(){ + brushCanvas.width = brushBackCanvas.width = width; + brushCanvas.height = brushBackCanvas.height = height; + brushCtx.clearRect(0,0,width,height); + brushCtx.fillStyle = Palette.getDrawColor(); + + switch (brushIndex){ + case 1: + brushCtx.fillRect(0,1,3,1); + brushCtx.fillRect(1,0,1,3); + break; + case 2: + brushCtx.fillRect(0,2,5,1); + brushCtx.fillRect(2,0,1,5); + brushCtx.fillRect(1,1,3,3); + break; + case 3: + brushCtx.fillRect(0,1,5,3); + brushCtx.fillRect(1,0,3,5); + break; + case 4: + brushCtx.fillRect(0,2,7,3); + brushCtx.fillRect(2,0,3,7); + brushCtx.fillRect(1,1,5,5); + break; + case 9: + brushCtx.fillRect(2,0,1,1); + for (var y=1;y<6;y++){ + var c = 0; + if (y%2===1) c=1; + if (y===4) c=2; + for (var x=0;x<5;x+=2){ + brushCtx.fillRect(x+c,y,1,1); + } + } + brushCtx.fillRect(4,6,1,1); + break; + case 100: + // dynamic Brush + let cx = dynamicSettings.width/2; + let cy = dynamicSettings.height/2; + let wx = dynamicSettings.width/2; + let wh = dynamicSettings.height/2; + + let color = Palette.getDrawColor(); + if (color === "transparent") color="black"; + brushCtx.fillStyle = color; + + if (dynamicSettings.softness){ + let opacityStep = 1/wx/dynamicSettings.softness; + let opacity = opacityStep; + let c=Color.fromString(Palette.getDrawColor()); + for (let radius=wx;radius>0;radius--){ + brushCtx.globalAlpha = Math.min(opacity,1); + + brushCtx.beginPath(); + brushCtx.ellipse(cx, cy,radius, radius, 0, 0, 2 * Math.PI); + brushCtx.fill(); + opacity+=opacityStep; + + } + + // hmm... this doesn't preserve the color 100% + // reset color + // TODO: better way of generating transparent brushes; + let data = brushCtx.getImageData(0,0,width,height); + let d=data.data; + for (let i = 0, max = d.length; i{ + me.rotate(); + }); + + EventBus.on(COMMAND.BRUSHROTATELEFT,()=>{ + me.rotate(true); + }); + + EventBus.on(COMMAND.BRUSHFLIPVERTICAL,()=>{ + me.flip(false); + }); + + EventBus.on(COMMAND.BRUSHFLIPHORIZONTAL,()=>{ + me.flip(true); + }); + + return me; +}(); + +export default Brush; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/canvas.js b/app/web-tools/dpaint/_script/ui/canvas.js new file mode 100644 index 00000000..29ee4137 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/canvas.js @@ -0,0 +1,972 @@ +import Input from "./input.js"; +import EventBus from "../util/eventbus.js"; +import {COMMAND, EVENT} from "../enum.js"; +import {$div} from "../util/dom.js"; +import Palette from "./palette.js"; +import Brush from "./brush.js"; +import Editor from "./editor.js"; +import Selection from "./selection.js"; +import ImageFile from "../image.js"; +import Resizer from "./components/resizer.js"; +import ToolOptions from "./components/toolOptions.js"; +import StatusBar from "./statusbar.js"; +import SelectBox from "./components/selectbox.js"; +import ImageProcessing from "../util/imageProcessing.js"; +import DitherPanel from "./toolPanels/ditherPanel.js"; +import Color from "../util/color.js"; +import {duplicateCanvas} from "../util/canvasUtils.js"; +import HistoryService from "../services/historyservice.js"; +import Cursor from "./cursor.js"; +import Smudge from "../paintTools/smudge.js"; +import Spray from "../paintTools/spray.js"; +import Text from "../paintTools/text.js"; +import historyservice from "../services/historyservice.js"; +import GridOverlay from "./components/gridOverlay.js"; +import PaletteDialog from "./components/paletteDialog.js"; + +let Canvas = function(parent){ + let me = {}; + let canvas; + let ctx; + let overlayCanvas; + let overlayCtx; + let touchData={}; + let zoom=1; + let prevZoom; + var panelParent; + let selectBox; + let resizer; + let gridOverlay; + let drawFunction; + let containerTransform = {x:0,y:0,startX:0,startY:0}; + let currentCursorPoint; + + canvas = document.createElement("canvas"); + overlayCanvas = document.createElement("canvas"); + resizer = Resizer(parent); + selectBox = SelectBox(parent,resizer); + + + canvas.width = 200; + canvas.height = 200; + overlayCanvas.width = 200; + overlayCanvas.height = 200; + ctx = canvas.getContext("2d",{willReadFrequently: true, antialias:false, desynchronized: false}); + overlayCtx = overlayCanvas.getContext("2d",{willReadFrequently: true}); + + let wrapper = $div("canvaswrapper"); + let container = $div("canvascontainer"); + let visualAids = $div("visualaids"); + gridOverlay = GridOverlay(visualAids); + + + overlayCanvas.className = "overlaycanvas"; + canvas.className = "maincanvas info"; + container.appendChild(canvas); + container.appendChild(overlayCanvas); + container.appendChild(selectBox.getBox()); + container.appendChild(visualAids); + wrapper.appendChild(container); + + panelParent = parent.getViewPort(); + panelParent.appendChild(wrapper); + + panelParent.addEventListener('scroll',(e)=>{handle('scroll', e)},false); + + panelParent.classList.add("handle"); + panelParent.onDragStart = function (e) {handle('down', e)} + panelParent.onDrag = function (x,y,touchData,e) {handle('move', e)} + panelParent.onDragEnd = function (e) {handle('up', e)} + panelParent.onDoubleClick = function(e){ + if (Editor.getCurrentTool() === COMMAND.POLYGONSELECT){ + selectBox.endPolySelect(true); + } + } + + canvas.addEventListener("pointermove", function (e) {handle('over', e)}, false); + canvas.addEventListener("pointerenter", function (e) { + Input.setPointerOver("canvas"); + }, false); + canvas.addEventListener("pointerleave", function (e) { + Input.removePointerOver("canvas"); + hideOverlay(); + }, false); + + //canvas.addEventListener("touchmove",(e)=>{ + // console.error("m3",e.targetTouches[0].force); + //}); + + canvas.addEventListener("touchend",(e)=>{ + // This is needed to avoid the "selection lens" on mobile safari on double tap/hold + e.preventDefault(); + + touchData.touchUpTime = performance.now(); + }); + + EventBus.on(EVENT.hideCanvasOverlay,()=>{ + overlayCanvas.style.opacity = 0; + }); + + EventBus.on(EVENT.drawCanvasOverlay,(point)=>{ + let onBackGround = (Input.isControlDown() || Input.isMetaDown()); + if (!point) onBackGround = false; + point = point || currentCursorPoint; + if (!point) return; + if (!Input.hasPointerEvents()) return; + overlayCanvas.style.opacity = 1; + overlayCtx.clearRect(0,0, canvas.width, canvas.height); + overlayCtx.globalAlpha = Brush.getOpacity(); + let color = Palette.getDrawColor(); + if (window.override){ + let c = ImageFile.getActiveLayer().getCanvasType(); + let p = c.getContext("2d").getImageData(point.x,point.y,1,1).data; + color = "rgb("+p[0]+","+p[1]+","+p[2]+")"; + Palette.setColor(p,false,true); + } + + Brush.draw(overlayCtx,point.x,point.y,color,onBackGround); + overlayCtx.globalAlpha = 1; + currentCursorPoint = point; + }); + + EventBus.on(EVENT.drawColorChanged,()=>{ + if (overlayCanvas.style.opacity>0 && currentCursorPoint){ + overlayCtx.globalAlpha = Brush.getOpacity(); + Brush.draw(overlayCtx,currentCursorPoint.x,currentCursorPoint.y,Palette.getDrawColor(),(Input.isControlDown() || Input.isMetaDown())); + overlayCtx.globalAlpha = 1; + } + }); + + EventBus.on(COMMAND.INITSELECTION,(tool)=>{ + if (!parent.isVisible()) return; + selectBox.activate(tool); + }); + + EventBus.on(COMMAND.ENDPOLYGONSELECT,(fromClick)=>{ + selectBox.endPolySelect(fromClick); + }); + + EventBus.on(EVENT.endPolygonSelect,()=>{ + touchData.isPolySelect = false; + }); + + EventBus.on(EVENT.imageSizeChanged,()=>{ + if (!parent.isVisible()) return; + let c = ImageFile.getCanvas(); + canvas.width = overlayCanvas.width = c.width; + canvas.height = overlayCanvas.height = c.height; + me.update(); + me.zoom(1); + gridOverlay.update(); + }) + + EventBus.on(EVENT.UIresize,()=>{ + if (!parent.isVisible()) return; + me.zoom(1); + }); + + EventBus.on(EVENT.panelResized,()=>{ + if (!parent.isVisible()) return; + me.zoom(1); + }); + + EventBus.on(EVENT.imageContentChanged,()=>{ + me.clear(); + let c = ImageFile.getCanvas(); + if (c) ctx.drawImage(c,0,0); + }) + + EventBus.on(COMMAND.TOGGLEGRID,()=>{ + if (!parent.isVisible()) return; + gridOverlay.toggle(); + }); + + EventBus.on(EVENT.gridOptionsChanged,()=>{ + gridOverlay.update(); + }); + + EventBus.on(COMMAND.COLORSELECT,()=>{ + if (!parent.isVisible()) return; + selectBox.activate(COMMAND.COLORSELECT); + selectBox.colorSelect(Palette.getDrawColor()); + }); + + EventBus.on(COMMAND.ALPHASELECT,()=>{ + if (!parent.isVisible()) return; + selectBox.activate(COMMAND.ALPHASELECT); + selectBox.alphaSelect(); + }); + + EventBus.on(COMMAND.TOSELECTION,()=>{ + if (!parent.isVisible()) return; + //This is different from COMMAND.SELECTALL as this selects the actual pixels? + let layer = ImageFile.getActiveLayer(); + selectBox.activate(COMMAND.TOSELECTION); + selectBox.applyCanvas(layer.getCanvas()); + }); + + EventBus.on(EVENT.toolChanged,(tool)=>{ + if (!Editor.usesBrush(tool)) hideOverlay(); + }); + + me.clear = function(){ + ctx.clearRect(0,0,canvas.width,canvas.height); + } + + me.set = function(image,reset){ + if (reset){ + canvas.width = overlayCanvas.width = image.width; + canvas.height = overlayCanvas.height = image.height; + zoom = 1; + me.zoom(1); + } + me.clear(); + } + + me.update = function(){ + me.clear(); + ctx.drawImage(ImageFile.getCanvas(),0,0); + } + + me.zoom = function(amount,event){ + hideOverlay(); + + var z = prevZoom || zoom; + prevZoom = undefined; + const rect = panelParent.getBoundingClientRect(); + if (!event) event = {clientX: rect.width/2, clientY: rect.height/2}; + + // zoom around point + var x = Math.floor((event.clientX - rect.left)) + panelParent.scrollLeft; + var y = Math.floor((event.clientY - rect.top)) + panelParent.scrollTop; + //console.error(x,y); + + zoom=zoom*amount; + let _w = Math.floor(canvas.width * zoom); + let _h = Math.floor(canvas.height * zoom); + let _z = (zoom/z - 1); + + + canvas.style.width = _w + "px"; + canvas.style.height = _h + "px"; + overlayCanvas.style.width = _w + "px"; + overlayCanvas.style.height = _h + "px"; + + + panelParent.scrollLeft += _z*x; + panelParent.scrollTop += _z*y; + + if (selectBox.isActive()) selectBox.zoom(zoom); + if (resizer.isActive()) resizer.zoom(zoom); + gridOverlay.zoom(zoom); + + } + + me.getZoom = function(){ + return zoom; + } + + me.setZoom = function(amount,center){ + prevZoom = zoom; + zoom = amount; + me.zoom(1,center); + } + + me.getCanvas = function(){ + return canvas; + } + + // TODO: resize and selectbox should be part of the editPanel? + me.getResizer = function(){ + return resizer; + } + + me.startSelect = function(){ + touchData.isSelecting = true; + } + + function draw() { + // button=0 -> left, button=2: right + let color = touchData.button?Palette.getBackgroundColor():Palette.getDrawColor(); + if (window.override) color = "white"; + if (Editor.getCurrentTool() === COMMAND.ERASE) color = "transparent"; + let {x,y} = touchData; + + //TODO: should we cancel the draw if the coordinates are the same as the previous draw? + if (touchData.previousDrawPoint && (x === touchData.previousDrawPoint.x && y === touchData.previousDrawPoint.y)){ + return; + } + + + touchData.drawLayer = ImageFile.getActiveLayer(); + touchData.drawLayer.draw(x,y,color,touchData); + + if (touchData.previousDrawPoint){ + // fill in gaps + let p1 = touchData.previousDrawPoint + let delta = {x: x - p1.x, y: y - p1.y}; + if (Math.abs(delta.x) > 1 || Math.abs(delta.y) > 1){ + let steps = Math.max(Math.abs(delta.x), Math.abs(delta.y)); + delta.x /= steps; + delta.y /= steps; + for (let i = 0; i < steps; i++){ + let _x = p1.x + Math.round(delta.x*i); + let _y = p1.y + Math.round(delta.y*i); + touchData.drawLayer.draw(_x,_y,color,touchData); + // TODO: avoid duplicate _x,_y draws + // check how this affect transparency, especially in locked palette mode + } + } + } + touchData.previousDrawPoint = {x,y}; + + touchData.isDrawing = true; + EventBus.trigger(EVENT.layerContentChanged); + } + + + let defaultDrawFunction = function(){ + let box = resizer.get(); + let w = box.width; + let h = box.height; + let x = box.left; + let y = box.top; + drawFunction(canvas.getContext("2d"),x,y,w,h); + } + + function handle(action,e){ + e.preventDefault(); + var point; + switch (action){ + case "down": + if (Input.isMultiTouch()) return; + // let the editPanel parent handle this for pan/zoom + + if (touchData.touchUpTime){ + let delta = (performance.now() - touchData.touchUpTime); + if (delta<90){ + // this is a tap on touchscreen on the canvas without dragging + // pointer event handling seems a bit mixed up here + // (Touchup on the canvas happens before the touchdown on its parent element?) + touchData.touchUpTime = undefined; + setTimeout(()=>{ + handle("up",e); + console.error("synth up"); + },10); + } + } + + + point = getCursorPosition(canvas,e,true); + let isOnCanvas = e.target && e.target.classList.contains("maincanvas"); + touchData.isdown = true; + touchData.button = e.button; + Brush.setPressure(e.pressure); + //navigator.clipboard.writeText(point.x + "," + point.y); + if (e.metaKey || e.ctrlKey) touchData.button = 3; + if (Input.isSpaceDown() || e.button===1 || Editor.getCurrentTool() === COMMAND.PAN){ + Cursor.override("pan"); + touchData.startDragX = e.clientX; + touchData.startDragY = e.clientY; + touchData.startScrollX = panelParent.scrollLeft; + touchData.startScrollY = panelParent.scrollTop; + containerTransform.startX = containerTransform.x; + containerTransform.startY = containerTransform.y; + return; + }else if ((Input.isShiftDown() || Input.isAltDown()) && Editor.canPickColor() || Editor.getCurrentTool() === COMMAND.COLORPICKER){ + Cursor.override("colorpicker"); + Cursor.attach("colorpicker"); + var pixel = ctx.getImageData(point.x, point.y, 1, 1).data; + if (PaletteDialog.getPaletteClickAction() === "pick"){ + PaletteDialog.updateColor(pixel); + }else{ + Palette.setColor(pixel,!!e.button,true); + } + + return; + } + + let currentTool = Editor.getCurrentTool(); + switch (currentTool){ + case COMMAND.DRAW: + case COMMAND.ERASE: + if (!isOnCanvas) return; + HistoryService.start(EVENT.layerContentHistory); + draw(); + break; + case COMMAND.SMUDGE: + HistoryService.start(EVENT.layerContentHistory); + Smudge.start(touchData); + if (!isOnCanvas) return; + break; + case COMMAND.SPRAY: + HistoryService.start(EVENT.layerContentHistory); + Spray.start(touchData); + break; + case COMMAND.TEXT: + Text.start(touchData); + break; + case COMMAND.SELECT: + touchData.isSelecting = true; + selectBox.boundingBoxSelect(point); + break; + case COMMAND.POLYGONSELECT: + touchData.isPolySelect = true; + selectBox.polySelect(point); + break; + case COMMAND.FLOODSELECT: + let c = selectBox.floodSelect(ImageFile.getActiveLayer().getCanvas(),point); + selectBox.activate(COMMAND.FLOODSELECT); + selectBox.applyCanvas(c); + break; + case COMMAND.FLOOD: + HistoryService.start(EVENT.layerContentHistory); + let cf = selectBox.floodSelect(ImageFile.getActiveLayer().getCanvas(),point,Color.fromString(e.button?Palette.getBackgroundColor():Palette.getDrawColor())); + ImageFile.getActiveLayer().drawImage(cf) + HistoryService.end(); + EventBus.trigger(EVENT.layerContentChanged); + break; + case COMMAND.CIRCLE: + case COMMAND.SQUARE: + touchData.isSelecting = true; + + resizer.init({ + x: point.x, + y: point.y, + width: 0, + height: 0, + rotation: 0, + hot: true, + aspect: 1, + canRotate: false, + }); + HistoryService.start(EVENT.layerContentHistory); + + if (currentTool === COMMAND.CIRCLE){ + drawFunction = function(ctx,x,y,w,h,button){ + ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); + let color = button?Palette.getBackgroundColor():Palette.getDrawColor(); + let cx = x + w/2; + let cy = y + h/2; + let wx = w/2; + let wh = h/2; + let isOdd = false; + ctx.fillStyle = color; + + if (ToolOptions.isSmooth()){ + ctx.imageSmoothingEnabled = ToolOptions.isSmooth(); + if (!ToolOptions.isFill()){ + ctx.strokeStyle = color; + ctx.lineWidth = ToolOptions.getLineSize(); + isOdd = ctx.lineWidth%2===1; + } + ctx.beginPath(); + if (isOdd) ctx.translate(0.5,0.5); + ctx.ellipse(cx, cy, wx, wh, 0, 0, 2 * Math.PI); + if (isOdd) ctx.translate(-0.5,-0.5); + if (ToolOptions.isFill()){ + ctx.fill(); + }else{ + ctx.stroke(); + } + }else{ + cx = Math.floor(cx); + cy = Math.floor(cy); + wx = Math.floor(wx); + wh = Math.floor(wh); + + let isFill = ToolOptions.isFill(); + let size = ToolOptions.getLineSize(); + + // calculate the size of the circle for 1 quadrant + // then scale it to fit the ellipse box + // then draw the other 3 quadrants + // ... does this make sense? + + let r = Math.max(wx,wh); + let xScale = wx/r; + let yScale = wh/r; + + // shift the quadrants if the width or height is even + let offsetX = 1-w%2; + let offsetY = 1-h%2; + + for (let x = -r; x <= 0; x += 1) { + for (let y = -r; y <= 0; y += 1) { + let distance = Math.round(Math.sqrt(x * x + y * y)) + let _x = Math.floor(x*xScale); + let _y = Math.floor(y*yScale); + + let draw = isFill?distance<=r:distance===r; + + if (draw){ + ctx.fillRect(cx + _x, cy + _y, size, size); + ctx.fillRect(cx - _x - offsetX, cy + _y, size, size); + ctx.fillRect(cx + _x, cy - _y - offsetY, size, size); + ctx.fillRect(cx - _x - offsetX, cy - _y - offsetY, size, size); + } + } + } + } + } + } + + if (currentTool === COMMAND.SQUARE){ + drawFunction = function(ctx,x,y,w,h,button){ + ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); + let color = button?Palette.getBackgroundColor():Palette.getDrawColor(); + if (ToolOptions.isFill()){ + ctx.fillStyle = color; + ctx.fillRect(x,y,w,h); + }else{ + ctx.strokeStyle = color; + ctx.lineWidth = ToolOptions.getLineSize(); + let isOdd = ctx.lineWidth%2===1; + ctx.beginPath(); + if (isOdd) ctx.translate(0.5,0.5); + ctx.rect(x,y,w,h); + if (isOdd) ctx.translate(-0.5,-0.5); + ctx.closePath(); + ctx.stroke(); + } + } + } + + resizer.setOnUpdate(()=>{ + let box = resizer.get(); + let w = box.width; + let h = box.height; + let x = box.left; + let y = box.top; + ImageFile.getActiveLayer().drawShape(drawFunction,x,y,w,h); + EventBus.trigger(EVENT.layerContentChanged); + }); + break; + case COMMAND.LINE: + case COMMAND.GRADIENT: + // TODO move this as well to the drawLayer of the active layer ? + if (currentTool === COMMAND.LINE && !isOnCanvas) return; + HistoryService.start(EVENT.layerContentHistory); + let layerIndex = ImageFile.addLayer(ImageFile.getActiveLayerIndex()+1); + let drawLayer = ImageFile.getLayer(layerIndex); + touchData.hotDrawFunction = function(x,y){ + drawLayer.clear(); + let ctx = drawLayer.getContext(); + //let lineWidth = ToolOptions.getLineSize() / (window.devicePixelRatio || 1) + let lineWidth = ToolOptions.getLineSize(); + ctx.lineWidth = lineWidth; + ctx.lineCap = "square"; + ctx.strokeStyle = Palette.getDrawColor(); + ctx.imageSmoothingEnabled = false; + + if (Input.isShiftDown()){ + // snap to x or y axis + let w = Math.abs(x-point.x); + let h = Math.abs(y-point.y); + let ratio = Math.min(w/h,h/w); + if (ratio<=1 && ratio>0.5){ + let d = Math.min(w,h); + x = point.x + d*(xdy ? dx : -dy)/2; + + let lineStart = 0-Math.floor(lineWidth/2); + let lineEnd = parseInt(lineWidth)+lineStart; + + while (true) { + for(let i=lineStart;i -dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } + ctx.putImageData(imgData,0,0); + + function drawPixel(x,y){ + let n=(y*canvas.width+x)*4; + data[n]=color[0]; + data[n+1]=color[1]; + data[n+2]=color[2]; + data[n+3]=255; + } + } + + + + + function bLine(x0, y0, x1, y1,ctx,color,thickness) { + let dx = Math.abs(x1 - x0); + let dy = Math.abs(y1 - y0); + let sx = (x0 < x1) ? 1 : -1; + let sy = (y0 < y1) ? 1 : -1; + let err = dx - dy; + let e2; + let x = x0; + let y = y0; + let angle = Math.atan2(dy, dx); + let thicknessX = thickness * Math.cos(angle); + let thicknessY = thickness * Math.sin(angle); + + //color = "black"; + + while (true) { + for (let i = Math.floor(-thicknessX/2); i <= Math.ceil(thicknessX/2); i++) { + for (let j = Math.floor(-thicknessY/2); j <= Math.ceil(thicknessY/2); j++) { + let xi = Math.round(x + i * Math.cos(angle) - j * Math.sin(angle)); + let yi = Math.round(y + i * Math.sin(angle) + j * Math.cos(angle)); + ctx.fillStyle = color; + ctx.fillRect(xi, yi, 1, 1); + } + } + + if ((x == x1) && (y == y1)) break; + e2 = err * 2; + if (e2 > -dy) { err -= dy; x += sx; } + if (e2 < dx) { err += dx; y += sy; } + } + + function drawPixel(x,y){ + let n=(y*canvas.width+x)*4; + data[n]=color[0]; + data[n+1]=color[1]; + data[n+2]=color[2]; + data[n+3]=255; + } + } + + return me; +}; + +export default Canvas; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/colorPicker.js b/app/web-tools/dpaint/_script/ui/components/colorPicker.js new file mode 100644 index 00000000..83f94129 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/colorPicker.js @@ -0,0 +1,106 @@ +import dom from "../../util/dom.js"; +import SidePanel from "../sidepanel.js"; +import Palette from "../palette.js"; + +let ColorPicker = function(){ + let me = {}; + let ctx; + let ctx2; + let rgbaColor = 'rgba(255,0,0,1)'; + let w = 120; + let cx = w-1; + let cy = 0; + let dot; + let line; + + me.generate = (parent)=> { + let container = dom(".colorpicker",{parent}); + + let canvas = dom("canvas.handle",{width: w, height:w, parent:container, onDrag:(x,y,d)=>{ + let box = canvas.getBoundingClientRect(); + cx = d.startX+x - box.left; + cy = d.startY+y - box.top; + setColor(d.button); + }, onDragStart:(e)=>{ + let box = canvas.getBoundingClientRect(); + cx= e.clientX - box.left; + cy= e.clientY - box.top; + setColor(e.button); + }}); + ctx = canvas.getContext("2d"); + fillGradient(); + + let colorStrip = dom("canvas.handle",{width: 20, height:w, parent:container, onDrag:(x,y,d)=>{ + let box = colorStrip.getBoundingClientRect(); + let cy = d.startY+y - box.top; + setStrip(cy,d.button); + }, onDragStart:(e)=>{ + let box = colorStrip.getBoundingClientRect(); + let cy = e.clientY - box.top; + setStrip(cy,e.button); + }}); + ctx2 = colorStrip.getContext("2d"); + fillStrip(); + + dot = dom(".dot",{parent:container}); + line = dom(".line",{parent:container}); + + } + + function fillStrip(){ + let grd1 = ctx2.createLinearGradient(0, 0, 0, w); + grd1.addColorStop(0, 'rgba(255, 0, 0, 1)'); + grd1.addColorStop(0.17, 'rgba(255, 255, 0, 1)'); + grd1.addColorStop(0.34, 'rgba(0, 255, 0, 1)'); + grd1.addColorStop(0.51, 'rgba(0, 255, 255, 1)'); + grd1.addColorStop(0.68, 'rgba(0, 0, 255, 1)'); + grd1.addColorStop(0.85, 'rgba(255, 0, 255, 1)'); + grd1.addColorStop(1, 'rgba(255, 0, 0, 1)'); + ctx2.fillStyle = grd1; + ctx2.fillRect(0, 0, 20, w); + } + + function fillGradient() { + ctx.fillStyle = rgbaColor; + ctx.fillRect(0, 0, w, w); + + let grdWhite = ctx.createLinearGradient(0, 0, w, 0); + grdWhite.addColorStop(0, 'rgba(255,255,255,1)'); + grdWhite.addColorStop(1, 'rgba(255,255,255,0)'); + ctx.fillStyle = grdWhite; + ctx.fillRect(0, 0, w, w); + + let grdBlack = ctx.createLinearGradient(0, 0, 0,w); + grdBlack.addColorStop(0, 'rgba(0,0,0,0)'); + grdBlack.addColorStop(1, 'rgba(0,0,0,1)'); + ctx.fillStyle = grdBlack; + ctx.fillRect(0, 0, w, w); + } + + function setColor(button){ + if (cx<0) cx = 0; + if (cx>w) cx = w-1; + if (cy<0) cy = 0; + if (cy>w) cy = w-1; + + dot.style.left = cx+"px"; + dot.style.top = cy+"px"; + + let imageData = ctx.getImageData(cx, cy, 1, 1).data; + Palette.setColor(imageData,button,true) + } + + function setStrip(sy,button){ + if (sy>w) sy = w-1; + if (sy<0) sy = 0; + line.style.top = sy+"px"; + let imageData = ctx2.getImageData(0, sy, 1, 1).data; + rgbaColor = 'rgba(' + imageData[0] + ',' + imageData[1] + ',' + imageData[2] + ',1)'; + fillGradient(); + setColor(button); + } + + return me; +}(); + +export default ColorPicker; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/colorRange.js b/app/web-tools/dpaint/_script/ui/components/colorRange.js new file mode 100644 index 00000000..f910729f --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/colorRange.js @@ -0,0 +1,187 @@ +import ImageFile from "../../image.js"; +import $, {$checkbox, $div} from "../../util/dom.js"; +import Input from "../input.js"; +import Animator from "../../util/animator.js"; +import Palette from "../palette.js"; +import EventBus from "../../util/eventbus.js"; +import {EVENT} from "../../enum.js"; +import Color from "../../util/color.js"; +import PaletteDialog from "./paletteDialog.js"; + +let ColorRange = function(){ + let me = {}; + + let colorRangeContext = [] + let playButton; + let container; + let currentRange = undefined; + let rangesPanel; + + me.render = (parent)=>{ + container = parent || container; + container.innerHTML = ""; + colorRangeContext = []; + let image = ImageFile.getCurrentFile(); + let buttons = $(".bottom",{parent:container},$(".button.small.add",{ + onclick:()=>{ + ImageFile.addRange(); + } + },"Add Range")); + if (image.colorRange && image.colorRange.length){ + $(".captions",{parent:container},$(".a","Active"),$(".b","Speed (fps)"),$(".c","Colors")); + rangesPanel = $(".ranges",{parent:container,onClick:deActivateRange}) + + image.colorRange.forEach((range,index)=>{ + let rangeContainer = $div("range" + (currentRange===index?" active":""),"",rangesPanel,()=>{ + deActivateRange(); + currentRange = index; + rangeContainer.classList.add("active"); + EventBus.trigger(EVENT.colorRangeChanged); + }); + $checkbox("",rangeContainer,"",(checked)=>{ + range.active = checked; + colorRangeContext[index].canvas.classList.toggle("inactive",!checked); + },range.active) + let slider = $("input.slider",{type:"range",parent:rangeContainer,min:-60,max:60,value:(range.fps * (range.reverse?-1:1)),oninput:()=>{ + range.fps = Math.abs(slider.value); + speed.value = slider.value; + range.reverse = slider.value<0; + }}); + + let speed = $("input.speed",{ + type:"text", + parent:rangeContainer, + value:range.fps * (range.reverse?-1:1), + oninput:()=>{ + range.fps = Math.abs(speed.value); + slider.value = speed.value; + range.reverse = speed.value<0; + }, + onkeydown: (e)=>{ + e.stopPropagation(); + } + }); + + if (range.high){ + range.low = range.low || 0; + let length = range.high - range.low + 1; + let canvas = $("canvas",{ + width:length*10, + height:20, + parent:rangeContainer, + onClick:()=>{ + canvas.classList.toggle("active"); + if (canvas.classList.contains("active")){ + deActivateRange(); + PaletteDialog.setPaletteClickAction("rangefrom"); + currentRange = index; + canvas.classList.add("active"); + rangeContainer.classList.add("active"); + let dupe = $div("dragelement tooltip","Select range: From color"); + Input.setDragElement(dupe,true); + EventBus.trigger(EVENT.colorRangeChanged); + }else{ + PaletteDialog.clearPaletteClickAction(); + } + } + }); + drawRange(canvas.getContext("2d"),range); + colorRangeContext[index] = (canvas.getContext("2d")); + } + }); + + playButton = $div("button small" + (Animator.isRunning()?" active":""),Animator.isRunning()?"Stop":"Play",buttons,Palette.cycle); + }else{ + $div("norange","No ranges specified",container); + } + } + + me.cleanUp = ()=>{ + colorRangeContext = []; + currentRange = undefined; + EventBus.trigger(EVENT.colorRangeChanged); + } + + me.getActiveRange = ()=>{ + return currentRange; + } + + me.setFrom = (index)=>{ + let range = ImageFile.getCurrentFile().colorRange[currentRange]; + range.low = index; + updateRange(range); + } + + me.setTo = (index)=>{ + let range = ImageFile.getCurrentFile().colorRange[currentRange]; + range.high = index; + updateRange(range,true); + } + + function deActivateRange(){ + currentRange = undefined; + if (rangesPanel){ + let r = rangesPanel.querySelector(".range.active") + if (r){ + r.classList.remove("active") + r.querySelector("canvas").classList.remove("active") + } + PaletteDialog.clearPaletteClickAction(); + } + EventBus.trigger(EVENT.colorRangeChanged); + } + + function updateRange(range,done){ + if (range.low>range.high){ + if (done){ + let temp = range.low; + range.low = range.high; + range.high = temp; + }else{ + range.high = range.low; + } + } + let ctx = colorRangeContext[currentRange]; + if (ctx){ + let length = range.high - range.low + 1; + ctx.canvas.width = length*10; + if (done) ctx.canvas.classList.remove("active"); + drawRange(ctx,range); + } + EventBus.trigger(EVENT.colorRangeChanged); + } + + function drawRange(ctx,range){ + for (let i=range.low;i<=range.high;i++){ + let index = i-(range.index || 0); + if (index{ + if (playButton){ + let isActive = Animator.isRunning(); + playButton.classList.toggle("active",isActive); + playButton.innerHTML = isActive ? "Stop" : "Play"; + } + }); + + EventBus.on(EVENT.colorRangesChanged,()=>{ + me.render() + }) + + EventBus.on(EVENT.colorCycleChanged,(index)=>{ + if (colorRangeContext[index]){ + let range = ImageFile.getCurrentFile().colorRange[index]; + drawRange(colorRangeContext[index],range); + } + }); + + return me; +}(); + +export default ColorRange; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/contextMenu.js b/app/web-tools/dpaint/_script/ui/components/contextMenu.js new file mode 100644 index 00000000..6bdacea0 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/contextMenu.js @@ -0,0 +1,37 @@ +import {$div} from "../../util/dom.js"; +import Cursor from "../cursor.js"; +import EventBus from "../../util/eventbus.js"; + +let ContextMenu = (()=>{ + let menu; + let me = {} + + me.show = (items)=>{ + if (!menu){ + menu = $div("contextmenu"); + document.body.appendChild(menu); + } + + menu.innerHTML = ""; + items.forEach(item=>{ + $div("contextmenuitem",item.label,menu,()=>{ + if (item.command) EventBus.trigger(item.command); + if (item.action) item.action(); + me.hide(); + }) + }) + + let position = Cursor.getPosition(); + menu.style.left = position.x + "px"; + menu.style.top = position.y + "px"; + menu.classList.add("active"); + } + + me.hide = ()=>{ + if (menu) menu.classList.remove("active"); + } + + return me; +})(); + +export default ContextMenu; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/ditherDialog.js b/app/web-tools/dpaint/_script/ui/components/ditherDialog.js new file mode 100644 index 00000000..cafcf841 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/ditherDialog.js @@ -0,0 +1,285 @@ +import {$elm, $title, $div} from "../../util/dom.js"; +import ditherPanel from "../toolPanels/ditherPanel.js"; +import Storage from "../../util/storage.js"; + +let DitherDialog = (()=>{ + let me = {} + let patternCanvas; + let patternCtx; + let canvas; + let ctx; + let preview; + let previewCtx; + let gridSize = 4; + let gridButtons = []; + let presetPanel; + let deleteButton; + let saveButton; + let touchData = {}; + let userPresetIndex; + + me.render = function (container,modal) { + container.innerHTML = ""; + gridButtons = []; + let ditherPanel = $div("ditheredit","",container); + + presetPanel = $div("presets","",ditherPanel); + listPresets(); + + let editPanel = $div("editpreset","",ditherPanel); + $title("2","Draw pattern",editPanel); + canvas = $elm("canvas","",editPanel,"handle info"); + canvas.width = 240; + canvas.height = 240; + ctx = canvas.getContext("2d"); + canvas.info = "Left click to draw black - Right click to draw white" + let toolBar = $div("subtoolbar","",editPanel); + $elm("label","Grid size",toolBar); + gridButtons.push($div("button small",9,toolBar,()=>{ + setGridSize(3,true); + })); + gridButtons.push($div("button small active",16,toolBar,()=>{ + setGridSize(4,true); + })); + gridButtons.push($div("button small",25,toolBar,()=>{ + setGridSize(5,true); + })); + $div("button left","Clear",toolBar,()=>{ + ctx.fillStyle = "white"; + ctx.fillRect(0,0,canvas.width,canvas.height); + drawGridLines(); + patternCtx.clearRect(0,0,patternCanvas.width,patternCanvas.height); + updatePattern(); + }); + + + patternCanvas=document.createElement("canvas"); + patternCanvas.width = 8; + patternCanvas.height = 8; + patternCtx=patternCanvas.getContext("2d"); + patternCtx.fillStyle = "white"; + patternCtx.fillRect(0,0,patternCanvas.width,patternCanvas.height); + + + canvas.onClick = (e)=>{ + touchData.box = canvas.getBoundingClientRect(); + let s = 240/gridSize/2; + let x = Math.floor((e.clientX - touchData.box.left)/s); + let y = Math.floor((e.clientY - touchData.box.top)/s); + touchData.rightButton = e.button; + putPixeltoPattern(x,y); + } + + canvas.onDrag = (dx,dy,_touchData,e)=>{ + let s = 240/gridSize/2; + let x = Math.floor((e.clientX - touchData.box.left)/s); + let y = Math.floor((e.clientY - touchData.box.top)/s); + if (x!==touchData.x || y!==touchData.y){ + putPixeltoPattern(x,y); + } + } + + let previewPanel = $div("previewpreset","",ditherPanel); + $title("2","Preview",previewPanel); + + preview = document.createElement("canvas"); + previewCtx = preview.getContext("2d"); + preview.width = 200; + preview.height = 240; + previewPanel.appendChild(preview); + + let buttons = $div("subtoolbar","",previewPanel); + deleteButton = $div("button left hidden","Remove preset",buttons,()=>{ + Storage.remove("dither",userPresetIndex); + listPresets(); + loadPreset(0); + }); + saveButton = $div("button","Save as new preset",buttons,()=>{ + let pattern = []; + let data = patternCtx.getImageData(0,0,patternCanvas.width,patternCanvas.height).data; + for (let i = 0; i64) size=10; + let canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + let ctx = canvas.getContext("2d"); + ctx.fillStyle = "black"; + for (let y = 0; ybutton.classList.remove("active")); + let button = gridButtons[size-3]; + if (button) button.classList.add("active"); + + if (force){ + ctx.fillStyle = "white"; + ctx.fillRect(0,0,canvas.width,canvas.height); + ctx.fillStyle = "black"; + gridSize = size; + patternCanvas.width = gridSize*2; + patternCanvas.height = gridSize*2; + + drawGridLines(); + updatePattern(); + } + + } + + function listPresets(){ + presetPanel.innerHTML = ""; + $title("2","Presets",presetPanel); + + for (let i = 0; i<8; i++){ + $div("preset p"+i,"",presetPanel,()=>{ + loadPreset(i); + ditherPanel.setDitherPattern(i+1); + }) + } + + let userPresets = Storage.get("dither"); + if (userPresets && userPresets.forEach){ + userPresets.forEach((preset,index)=>{ + let tile = $div("preset user","",presetPanel,()=>{ + loadPreset(preset); + userPresetIndex = index; + }) + tile.appendChild(userPresetToCanvas(preset)); + }) + } + } + + + return me; +})(); + +export default DitherDialog; + diff --git a/app/web-tools/dpaint/_script/ui/components/effectDialog.js b/app/web-tools/dpaint/_script/ui/components/effectDialog.js new file mode 100644 index 00000000..634f8b81 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/effectDialog.js @@ -0,0 +1,334 @@ +import {$checkbox, $div, $elm, $input} from "../../util/dom.js"; +import Effects from "../effects.js"; +import modal from "../modal.js"; +import ImageFile from "../../image.js"; +import Canvas from "../canvas.js"; +import {duplicateCanvas, releaseCanvas} from "../../util/canvasUtils.js"; +import EventBus from "../../util/eventbus.js"; +import {EVENT} from "../../enum.js"; +import SyntaxEdit from "./syntaxEdit.js"; +import Palette from "../palette.js"; +import ImageProcessing from "../../util/imageProcessing.js"; +import HistoryService from "../../services/historyservice.js"; + +var EffectDialog = function() { + let me = {}; + let effects = []; + let previewCanvas; + let livePreview = true; + let codePanel; + let currentSource; + let currentRecipeSource; + let mainPanel; + + me.render = function (container,modal) { + if (!previewCanvas){ + previewCanvas = document.createElement("canvas"); + previewCanvas.width = 200; + previewCanvas.height = 200; + }else{ + previewCanvas.getContext("2d").clearRect(0,0,previewCanvas.width,previewCanvas.height); + } + + container.innerHTML = ""; + mainPanel = $div("effects panel form","",container); + + let tabs = $div("tabs","",mainPanel); + let generalTab = $div("tab active","General",tabs,()=>{ + generalTab.classList.add('active'); + alchemyTab.classList.remove('active'); + sliders.classList.add("active"); + alchemy.classList.remove('active'); + if (codePanel){ + codePanel.innerHTML = ""; + codePanel = undefined; + } + }); + let alchemyTab = $div("tab","Alchemy",tabs,()=>{ + generalTab.classList.remove('active'); + alchemyTab.classList.add('active'); + sliders.classList.remove("active"); + alchemy.classList.add('active'); + }); + + + let sliders = $div("sliders active","",mainPanel); + + currentSource = duplicateCanvas(ImageFile.getActiveLayer().getCanvas(),true); + HistoryService.start(EVENT.layerContentHistory); + + Effects.setSrcTarget(currentSource,previewCanvas.getContext("2d")) + + createSlider(sliders,"Brightness",0,-50,50,(value)=>{ + Effects.setBrightness(value); update(); + }); + createSlider(sliders,"Contrast",0,-50,50,(value)=>{ + Effects.setContrast(value); update(); + }); + createSlider(sliders,"Saturation",0,-50,50,(value)=>{ + Effects.setSaturation(value); update(); + }); + createSlider(sliders,"Hue",0,-180,180,(value)=>{ + Effects.setHue(value); update(); + }); + createSlider(sliders,"Blur",0,0,100,(value)=>{ + Effects.setBlur(value); update(); + }); + createSlider(sliders,"Sharpen",0,0,100,(value)=>{ + Effects.setSharpen(value); update(); + }); + createSlider(sliders,"Sepia",0,0,100,(value)=>{ + Effects.setSepia(value); update(); + }); + createSlider(sliders,"Invert",0,0,100,(value)=>{ + Effects.setInvert(value); update(); + }); + createSlider(sliders,"Cyan/Red",0,-100,100,(value)=>{ + Effects.setColorBalance("red",value); update(); + }); + createSlider(sliders,"Magenta/Green",0,-100,100,(value)=>{ + Effects.setColorBalance("green",value); update(); + }); + createSlider(sliders,"Yellow/Blue",0,-100,100,(value)=>{ + Effects.setColorBalance("blue",value); update(); + }); + + + /* alchemy */ + codePanel = undefined; + let alchemy = $div("alchemy","",mainPanel); + + let recipes=[ + {name: "Dabble", file:"dots"}, + {name: "Speckles", file:"speckles"}, + {name: "Lines", file:"lines"}, + {name: "Glow", file:"glow"}, + {name: "Frost", file:"web"}, + {name: "Displace", file:"displace"}, + {name: "Offset", file:"offset"}, + //{name: "BeachShadow", file:"beachshadow"}, + //{name: "Texture", file:"texture"}, + ] + + recipes.forEach(recipe=>{ + $div("recipe",recipe.name,alchemy,()=>{ + loadRecipe(recipe.file,recipe.name); + }) + }) + + + + /* previewPanel */ + + let previewPanel = $div("previewpanel","",mainPanel); + let p = $div("preview","",previewPanel); + p.appendChild(previewCanvas); + $checkbox("Preview",previewPanel,"",checked=>{ + livePreview = checked; + update(); + },livePreview); + + let buttons = $div("buttons","",mainPanel); + $div("button ghost left","Reset",buttons,()=>{ + Effects.hold(); + effects.forEach(slider=>{ + slider.value = 0; + slider.oninput(); + }); + Effects.apply(); + }); + $div("button ghost","Cancel",buttons,()=>{ + let t = ImageFile.getActiveLayer().getContext(); + t.clearRect(0,0,t.canvas.width,t.canvas.height); + t.drawImage(currentSource,0,0); + Effects.clear(); + modal.hide(); + releaseCanvas(currentSource); + EventBus.trigger(EVENT.layerContentChanged); + HistoryService.neverMind(); + }); + $div("button primary","Apply",buttons,()=>{ + if (codePanel){ + + }else{ + let t = ImageFile.getActiveLayer().getContext(); + Effects.setSrcTarget(currentSource,t); + Effects.apply(); + } + Effects.clear(); + releaseCanvas(currentSource); + modal.hide(); + EventBus.trigger(EVENT.layerContentChanged); + + if (Palette.isLocked()){ + Palette.apply(); + } + HistoryService.end(); + }); + + Effects.clear(); + + } + + function createSlider(parent,label,value,min,max,onInput,isCustom,onChange){ + let result = $div("slider"); + let labelElm = $elm("label",label,result); + let range = $input("range",value,result); + let valueElm = $input("text",value,result); + range.min = min||0; + range.max = max||100; + range.className = label.toLowerCase().split("/")[0]; + range.ondblclick = ()=>{ + range.value = value; + range.oninput(); + } + range.oninput = ()=>{ + valueElm.value = range.value; + if (onInput) onInput(range.value); + } + valueElm.onkeydown = modal.inputKeyDown; + valueElm.oninput = ()=>{ + let p = parseInt(valueElm.value); + if (isNaN(p)) p=0; + if (pmax) p=max; + range.value = p; + if (onInput) onInput(range.value); + } + valueElm.onchange = ()=>{ + if (onChange) onChange(); + } + if (onChange) range.onchange = onChange; + if (parent) parent.appendChild(result); + if (!isCustom) effects.push(range); + return result; + } + + function update(){ + if (livePreview){ + Effects.setSrcTarget(currentSource,ImageFile.getActiveLayer().getContext()); + Effects.apply(); + Effects.setSrcTarget(currentSource,previewCanvas.getContext("2d")); + EventBus.trigger(EVENT.layerContentChanged); + } + } + + async function loadRecipe(recipe,name){ + let textarea; + let button; + let process; + if (codePanel){ + codePanel.remove(); + codePanel = undefined; + } + + + codePanel = $div("code","",mainPanel); + textarea = SyntaxEdit(codePanel,(value)=>{ + console.log("updating function"); + let f = new Function("source", "target", 'return ' + value); + process = f(); + }) + + + let run = $div("button primary","Run",codePanel,()=>{ + if (typeof process === "function"){ + let source = currentSource; + //let target = ImageFile.getActiveLayer().getContext(); + let target = previewCanvas.getContext("2d"); + process(source,target); + if(livePreview){ + target = ImageFile.getActiveLayer().getContext(); + process(source,target); + } + EventBus.trigger(EVENT.layerContentChanged); + } + }) + + + process = (await import("../../alchemy/"+recipe+".js")).default + if (typeof process === "function"){ + let value = process.toString(); + + let exposeIndex = value.indexOf("expose="); + let exposeIndexEnd = 0; + if (exposeIndex<0) exposeIndex = value.indexOf("expose ="); + if (exposeIndex>=0){ + let expose = value.substring(exposeIndex); + let bracketCount = 0; + let bracketFound = false; + for (let i=1;i=0) exposeIndexEnd = endOfLine; + expose = expose.substring(0,i+1); + break; + } + } + try { + let exposed = eval(expose); + if (exposed && typeof exposed==="object" && Object.keys(exposed).length>0){ + renderRecipeParams (exposed); + } + } catch (e){ + console.error(e); + } + } + + textarea.setValue(value); + currentRecipeSource = value; + + if (exposeIndex>0 && exposeIndexEnd>0){ + currentRecipeSource = value.substring(0,exposeIndex)+ "###exposed###" + value.substring(exposeIndex+exposeIndexEnd); + } + + } + + function renderRecipeParams(params){ + let paramPanel = $div("params","

Parameters for '" + name + "'

",codePanel); + let keys = Object.keys(params); + if (keys.length>5) paramPanel.classList.add("columns"); + + keys.forEach(key=>{ + let param = params[key]; + // parent,label,value,min,max,onInput,isCustom + let slider = createSlider(paramPanel,camelCaseToSpace(key),param.value,param.min,param.max,(value)=>{ + params[key].value = parseFloat(value); + let expose = "expose = {\n"; + Object.keys(params).forEach(key=>{ + expose += " "+key+": "+ JSON.stringify(params[key])+",\n"; + }); + expose += " };"; + textarea.setValue(currentRecipeSource.replace("###exposed###",expose)); + },true,()=>{ + textarea.onChange(); + run.onClick(); + }); + if (param.main) slider.classList.add("main"); + }); + + + } + } + + function camelCaseToSpace(str){ + let result = ""; + for (let i=0;i{ + if (files){ + var file = files[0]; + var reader = new FileReader(); + reader.onload = function(){ + Adf.loadDisk(reader.result,result=>{ + if (result){ + diskInfo = Adf.getInfo(); + if (!container) generate(); + me.show(); + let root = Adf.readRootFolder(); + listFolder(root); + }else{ + Modal.alert("Sorry, this doesn't seem to be an ADF file.","Error reading disk file"); + } + }); + + } + reader.readAsArrayBuffer(file); + } + } + + function generate(){ + let parent = document.querySelector(".container"); + container = $(".filebrowser", + {parent:parent}, + $(".caption","File Browser",$(".close",{ + onclick:()=>{ + me.hide(); + },info:"Close File Browser"},"x")), + listContainer = $(".list") + ); + } + + function listFolder(root){ + listContainer.innerHTML = ""; + + listContainer.appendChild($(".disk",diskInfo.label || "Disk",$(".download",{ + onclick:()=>{ + EventBus.trigger(COMMAND.SAVEDISK,[Adf.getDisk().buffer,diskInfo.label || "Disk.adf"]) + }, + info:"Download disk as ADF file" + }))); + + if (root.sector !== 880){ + $(".listitem.folder", + { + parent:listContainer, + onclick:()=>{listFolder(Adf.readFolderAtSector(root.parent))} + }, "..." + ); + } + + root.folders.forEach(folder=>{ + $(".listitem.folder",{ + parent:listContainer, + onclick:()=>{ + let _folder = Adf.readFolderAtSector(folder.sector) + listFolder(_folder); + }}, + folder.name + ); + }); + + root.files.forEach(file=>{ + let ext = file.name.split(".").pop(); + let isImage = ext==="info" || ext==="iff" || ext==="ilbm" || ext==="pic" || ext==="png" || ext==="jpg" || ext==="jpeg" || ext==="gif"; + if (!isImage){ + let f = Adf.readFileAtSector(file.sector,true); + let bs = new BinaryStream(f.content.buffer,true); + let id = bs.readWord(0); + if (id === 0xE310) isImage = true; // icon + isImage = isImage || IFF.detect(bs); + } + $(".listitem.file" + (isImage ? ".image" : ""),{ + parent:listContainer, + onclick:()=>{ + if (isImage){ + openFile(file); + }else{ + Modal.show(DIALOG.OPTION,{ + title: "File Browser", + text: "This file is not an image file. Do you want to download it?", + buttons: [ + {label:"Yes",onclick:()=>{EventBus.trigger(COMMAND.SAVEGENERIC,[Adf.readFileAtSector(file.sector,true).content.buffer,file.name])}}, + {label:"No"} + ] + }) + } + } + }, + file.name + ); + }); + } + + function openFile(file){ + currentFile = file; + let f = Adf.readFileAtSector(file.sector,true); + ImageFile.handleBinary(f.content.buffer,file.name,"file",true); + SaveDialog.setFile(file); + } + + EventBus.on(COMMAND.SAVEFILETOADF,([currentFile,name])=>{ + let currentSector = currentFile.sector; + if (currentSector){ + let f = Adf.readFileAtSector(currentSector); + if (f && f.name === currentFile.name){ + let ext = currentFile.name.split(".").pop(); + let newExt = name.split(".").pop(); + if (ext !== newExt) name += "." + ext; + let f = ImageFile.getCurrentFile(); + let type = f.originalType || "colorIcon"; + Generate.file(type).then(data=>{ + if (data){ + console.log("save file to adf",currentFile,name); + Adf.deleteFileAtSector(currentSector); + let sector = Adf.writeFile(name,data,currentFile.parent); + + // update current file in dpaint.js + let f = Adf.readFileAtSector(sector,false); + currentFile=f; + SaveDialog.setFile(f); + Modal.hide(); + } + }); + }else{ + Modal.alert("Sorry, this doesn't seem to be the ADF disk this file was opened from.","Error saving file"); + } + } + }); + + + return me; +} + +export default FileBrowser(); \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/gallery.js b/app/web-tools/dpaint/_script/ui/components/gallery.js new file mode 100644 index 00000000..ac1e6503 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/gallery.js @@ -0,0 +1,119 @@ +import $ from "../../util/dom.js"; +import EventBus from "../../util/eventbus.js"; +import {COMMAND} from "../../enum.js"; +import ImageFile from "../../image.js"; +import Modal from "../modal.js"; + +let Gallery = (()=>{ + let me = {}; + let container; + let listContainer; + let firstItem; + let openFirstItem; + + me.toggle = function(andOpen){ + if (!container) generate(); + container.classList.toggle("active"); + let isActive = container.classList.contains("active"); + document.body.classList.toggle("withfilebrowser",isActive); + if(isActive && andOpen){ + if (firstItem){ + openItem(firstItem); + }else{ + openFirstItem = true; + } + } + } + + + function generate(){ + let parent = document.querySelector(".container"); + container = $(".filebrowser.gallery", + {parent:parent}, + $(".caption","Gallery",$(".close",{ + onclick:()=>{ + EventBus.trigger(COMMAND.TOGGLEGALLERY); + },info:"Close Gallery"},"x")), + listContainer = $(".list") + ); + list(); + } + + function list(){ + fetch("./gallery/gallery.json").then(response=>response.json()).then(data=>{ + listContainer.innerHTML = ""; + + data.forEach(item=>{ + if (item.url){ + if (!firstItem) firstItem = item; + let thumb; + $(".item",{ + parent:listContainer, + onClick:()=>{ + openItem(item); + }}, + thumb = $(".thumb"), + $(".fileinfo", + $(".title",item.title), + item.artistUrl + ? $("a.artist",{href:item.artistUrl, target:"_blank"},item.artist) + : $(".artist",item.artist), + $(".year",item.year) + )) + ; + thumb.style.backgroundImage = "url('" + item.image +"')"; + }else{ + $(".section",{parent:listContainer} + ,item.title ? $(".title",item.title): "" + ,$(".description",item.description)); + } + }); + if (openFirstItem){ + openItem(firstItem); + openFirstItem = false; + } + }).catch(err=>{ + console.error(err); + }); + } + + function openItem(item){ + ImageFile.openUrl(item.url).then(()=>{ + setTimeout(()=>{ + EventBus.trigger(COMMAND.ZOOMFIT); + if (item.cycle){ + setTimeout(()=>{ + EventBus.trigger(COMMAND.CYCLEPALETTE); + },200); + } + if (item.generatePalette){ + EventBus.trigger(COMMAND.PALETTEFROMIMAGE); + } + + // update the url + let url = new URL(window.location.href); + url.searchParams.delete("gallery"); + url.searchParams.set("file",item.url); + url.searchParams.set("zoom","1"); + if (item.cycle){ + url.searchParams.set("play","1"); + }else{ + url.searchParams.delete("play"); + } + if (item.generatePalette){ + url.searchParams.set("palette","1"); + }else{ + url.searchParams.delete("palette"); + } + url.search = decodeURIComponent(url.search); + window.history.pushState({},"",url); + },200); + }).catch((err)=>{}); + } + + + return me; + +}); + +export default Gallery(); \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/gridOverlay.js b/app/web-tools/dpaint/_script/ui/components/gridOverlay.js new file mode 100644 index 00000000..60589bdb --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/gridOverlay.js @@ -0,0 +1,77 @@ +import {$div} from "../../util/dom.js"; +import ImageFile from "../../image.js"; +import GridPanel from "../toolPanels/gridPanel.js"; + +let GridOverlay = ((parent)=>{ + let container = parent; + let me={}; + let visible = false; + let grid; + let zoom = 1; + let linesVertical = []; + let linesHorizontal = []; + + me.toggle=()=>{ + visible = !visible; + if (visible && !grid){ + grid = $div("grid","",container); + } + grid.style.display = visible?"block":"none"; + me.update(); + } + + me.update = (rebuild)=>{ + let options = GridPanel.getOptions(); + visible = options.visible; + if (!visible) return; + if (!grid){ + grid = $div("grid","",container); + } + + let w = ImageFile.getCurrentFile().width; + let h = ImageFile.getCurrentFile().height; + let size = options.size || 16; + grid.innerHTML = ""; + grid.style.opacity = options.opacity/100; + grid.style.filter = "brightness(" + (options.brightness) + "%)"; + + linesVertical = []; + linesHorizontal = []; + + for (let x=size;x{ + zoom = factor; + let options = GridPanel.getOptions(); + visible = options.visible; + if (!visible) return; + + let size = options.size || 16; + console.log("zooming grid",size,zoom); + linesVertical.forEach((line,index)=>{ + line.style.left = (size*zoom*(index+1)) + "px"; + console.log("line",line.style.left); + }); + linesHorizontal.forEach((line,index)=>{ + line.style.top = (size*zoom*(index+1)) + "px"; + }); + + + } + + return me; + +}); + +export default GridOverlay; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/optionDialog.js b/app/web-tools/dpaint/_script/ui/components/optionDialog.js new file mode 100644 index 00000000..c546ee65 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/optionDialog.js @@ -0,0 +1,35 @@ +import $,{$setTarget} from "../../util/dom.js"; +import ImageFile from "../../image.js"; + +let OptionDialog = function() { + let me = {}; + + me.render = function (container,modal,data) { + container.innerHTML = ""; + let panel = $(".optiondialog",{parent: container}); + + $setTarget(panel) + //if (data.title) $("h3",data.title); + if (data.text){ + if (typeof data.text === "string") data.text=[data.text]; + data.text.forEach(text=>{$("p",text);}); + } + + if (data.buttons){ + data.buttons.forEach(button=>{ + $(".button.large",{onclick: ()=>{ + if (button.onclick) button.onclick(); + modal.hide() + }},button.label); + }) + }else{ + $(".buttons.relative", + $(".button.ghost",{onclick: modal.hide},"Cancel"), + $(".button.primary",{onclick: ()=>{modal.hide(); if (data.onOk) data.onOk()}},"OK") + ); + } + } + return me; +}(); + +export default OptionDialog; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/paletteDialog.js b/app/web-tools/dpaint/_script/ui/components/paletteDialog.js new file mode 100644 index 00000000..3ad692d6 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/paletteDialog.js @@ -0,0 +1,631 @@ +import $, {$checkbox, $div, $elm, $setTarget, $input} from "../../util/dom.js"; +import ImageFile from "../../image.js"; +import Palette from "../palette.js"; +import Color from "../../util/color.js"; +import canvas from "../canvas.js"; +import {COMMAND, EVENT} from "../../enum.js"; +import EventBus from "../../util/eventbus.js"; +import Input from "../input.js"; +import ColorRange from "./colorRange.js"; + +var PaletteDialog = function() { + let me = {}; + let colorCanvas; + let colorCanvasCtx; + let paletteCanvas; + let paletteCanvasCtx; + let sliders = []; + let tabs = {}; + let panels = {}; + let buttons; + let inputHex; + let depthInfo; + let colorHighlight; + let colorPicker; + let lockToImage = true; + let highlight = false; + let pixelCount; + let currentIndex; + let colorSize = 30; + let palettePage = 0; + let swapButton; + let spreadButton; + let pickButton; + let paletteClickAction = ""; + let withActions = false; + let panelContainer; + let hsv = false; + let isActive = false; + + me.render = function (container,modal) { + container.innerHTML = ""; + sliders = []; + paletteClickAction = ""; + + let currentColor = Palette.getDrawColor(); + + let palettePanel; + let subPanel; + let optionsPanel; + let contextMenu; + + $setTarget(container); + panelContainer = $(".palette.panel.form" + (withActions? ".withactions" : ""), + palettePanel=$(".palettepanel"), + $(".actions", + $(".caption.sub",{onClick:()=>{ + withActions=false; + panelContainer.classList.toggle("withactions",withActions); + }},"Actions"), + $(".nav",$(".zoomin",{ + onClick:()=>{colorSize = Math.min(colorSize*2,60); renderPalette(palettePanel);}, + info: "Draw bigger color squares" + }), + $(".zoomout",{ + onClick:()=>{colorSize = Math.max(colorSize/2,15); renderPalette(palettePanel);}, + info: "Draw smaller color squares" + })), + swapButton = $(".button.small",{ + onClick:()=>{ + let active = paletteClickAction === "swap"; + active = !active; + paletteClickAction = active?"swap":""; + swapButton.classList.toggle("highlight",active); + if (active){ + let dupe = $div("dragelement tooltip","Swap with"); + Input.setDragElement(dupe,true); + }else{ + Input.removeDragElement(); + } + }, + info:"Swap a color in the palette" + },"Swap"), + spreadButton = $(".button.small",{ + onClick:()=>{ + let active = paletteClickAction === "spread"; + active = !active; + paletteClickAction = active?"spread":""; + spreadButton.classList.toggle("highlight",active); + if (active){ + let dupe = $div("dragelement tooltip","Spread to"); + Input.setDragElement(dupe,true); + }else{ + Input.removeDragElement(); + } + }, + info:"Create a color gradient between 2 colors in the palette." + },"Spread"), + $(".button.small",{onClick:()=>{Palette.addColor(Color.fromString(inputHex.value))},"info":"Add a new color"},"Add"), + $(".button.small",{onClick:()=>{Palette.removeColor(currentIndex)},"info":"Remove the selected color"},"Remove"), + pickButton = $(".button.small",{ + onClick:()=>{ + let active = paletteClickAction === "pick"; + active = !active; + paletteClickAction = active?"pick":""; + pickButton.classList.toggle("highlight",active); + if (active){ + let dupe = $div("dragelement tooltip","Pick color from image"); + Input.setDragElement(dupe,true); + EventBus.trigger(COMMAND.COLORPICKER); + }else{ + Input.removeDragElement(); + } + }, + info:"Pick a color from the image" + },"Pick"),$(".spacer"), + $(".button.small",{onClick:()=>{contextMenu.classList.toggle("active")}},"Sort",contextMenu = $(".contextmenu", + $(".item",{onClick:()=>{Palette.reverse(); contextMenu.classList.remove("active")},"info":"Reverse the colors in the palette"},"Reverse"), + $(".item",{onClick:()=>{Palette.sortByHue(); contextMenu.classList.remove("active")},"info":"Sort by hue"},"Hue"), + $(".item",{onClick:()=>{Palette.sortBySaturation(); contextMenu.classList.remove("active")},"info":"Sort by saturation"},"Saturation"), + $(".item",{onClick:()=>{Palette.sortByLightness(); contextMenu.classList.remove("active")},"info":"Sort by lightness"},"Lightness"), + $(".item",{onClick:()=>{Palette.sortByUseCount(); contextMenu.classList.remove("active")},"info":"Sort by how much a color is used in the image (optimized for compression)"},"Use Count"), + $(".item",{onClick:()=>{Palette.sortByUseCount(true); contextMenu.classList.remove("active")},"info":"optimize and test compression results"},"File Size"))), + $(".button.small",{onClick:()=>{EventBus.trigger(COMMAND.LOADPALETTE)},"info":"Open palette from disk"},"Load"), + $(".button.small",{onClick:()=>{EventBus.trigger(COMMAND.SAVEPALETTE)},"info":"Save palette to disk"},"Save"), + ), + $(".mainpanel", + $('.tabs',panels.colortab = $(".caption.sub",{onClick:toggleRangePanel},"Color"),panels.rangestab = $(".caption.sub.inactive",{onClick:toggleRangePanel},"Ranges")), + panels.color = $(".colorpanel", + $(".sliders", + colorCanvas = $("canvas",{ + width:60, + height:30, + onclick:()=>{colorPicker.click();}, + info: "Click to open Color Picker" + }), + colorPicker = $("input.masked",{ + type:"color", + value:Color.toHex(currentColor), + oninput: ()=>{ + inputHex.value = colorPicker.value; + updateColor(inputHex.value); + } + }), + pixelCount = $(".pixelcount"), + inputHex = $("input.hex",{ + type:"text", + value:Color.toHex(currentColor), + oninput: ()=>{ + updateColor(inputHex.value); + }, + onkeydown: modal.inputKeyDown + }), $(".tabs", + tabs.rgb = $(".tab",{onClick:toggleHSV, info:"Choose RGB color model"}, $("span","R G B")), + tabs.hsv = $(".tab.inactive",{onClick:toggleHSV, info:"Choose HSV color model"},$("span","H S V")), + subPanel=$(".panel")) + ), + depthInfo = $(".depthinfo"), + buttons = $(".buttons"), + optionsPanel=$(".options") + ), + panels.ranges = $(".rangepanel") + ), + ); + + renderPalette(palettePanel); + colorCanvasCtx = colorCanvas.getContext("2d"); + colorCanvasCtx.fillStyle = currentColor; + colorCanvasCtx.fillRect(0,0,60,30); + + + let RGBValues = Color.fromString(currentColor); + let colorDepth = Palette.getColorDepth()/3; + ["red","green","blue"].forEach((color,index)=>{ + let slider = $div("slider","",subPanel); + let range = document.createElement("input"); + + range.type = "range"; + range.className = "slider " + color; + range.max = 255; + let multiplier = 1; + if (colorDepth === 4){ + range.max = 15; + multiplier = 16; + } + if (colorDepth === 3){ + range.max = 7; + multiplier = 32; + } + range.value = Math.floor(RGBValues[index]/multiplier); + range.addEventListener("wheel",function(e){ + range.value = parseInt(range.value) + Math.sign(e.deltaY); + range.oninput(); + }); + slider.appendChild(range); + let label = $elm("span",color,slider,"label"); + let input = document.createElement("input"); + input.type = "text"; + input.className = "rangevalue"; + input.value = range.value; + input.onkeydown = modal.inputKeyDown; + slider.appendChild(input); + sliders.push({ + range: range, + input: input, + label: label + }) + + range.oninput = ()=>{ + let value = range.value; + let colorDepth = Palette.getColorDepth()/3; + input.value = range.value; + //let multiplier = 1; + //if (colorDepth === 4) multiplier = 16; + //if (colorDepth === 3) multiplier = 32; + + let newColor = [sliders[0].input.value,sliders[1].input.value,sliders[2].input.value]; + if (colorDepth<8) newColor = Color.to24bit(newColor,colorDepth); + if (hsv){ + newColor = Color.fromHSV(sliders[0].range.value*multiplier/360,sliders[1].range.value*multiplier/100,sliders[2].range.value*multiplier/100); + if (multiplier>1) newColor = Color.setBitDepth(newColor,colorDepth); + } + updateColor(newColor); + } + + input.oninput = ()=>{ + let value = parseInt(input.value); + let colorDepth = Palette.getColorDepth()/3; + let max=255; + let multiplier = 1; + if (colorDepth === 4){ + max = 15; + multiplier = 16; + } + if (colorDepth === 3){ + max = 7; + multiplier = 32; + } + if (isNaN(value)) value=0; + if (value<0) value=0; + if (value>max) value=max; + range.value = input.value = value; + console.log("colorDepth",colorDepth,value,max); + let newColor = [sliders[0].range.value*multiplier,sliders[1].range.value*multiplier,sliders[2].range.value*multiplier]; + if (hsv){ + newColor = Color.fromHSV(sliders[0].range.value/360,sliders[1].range.value/100,sliders[2].range.value/100); + } + updateColor(newColor); + } + + //currentIndex = 0; + }) + + $div("button small revert","Revert",buttons,()=>{ + setColor(Palette.get()[currentIndex],currentIndex); + }); + $div("button small apply","Apply",buttons,()=>{ + let color = Color.fromString(inputHex.value); + Palette.updateColor(currentIndex,color); + let colorsPerPage = paletteCanvas.width / colorSize * paletteCanvas.height / colorSize; + let start = palettePage * colorsPerPage; + drawColor(color,currentIndex-start); + colorCanvasCtx.fillStyle = Color.toString(color); + colorCanvasCtx.fillRect(0,0,60,30); + + if (lockToImage){ + let currentHighLight = ImageFile.getLayerIndexesOfType("pixelSelection"); + if (currentHighLight.length){ + let newIndex = ImageFile.getActiveLayerIndex()+1 + ImageFile.moveLayer(currentHighLight[0],newIndex); + EventBus.trigger(COMMAND.MERGEDOWN,newIndex); + } + } + }); + + + $checkbox("Update image with color changes",optionsPanel,"",(checked)=>{ + lockToImage = checked; + },lockToImage) + $checkbox("HighLight pixels that use selected color",optionsPanel,"",(checked)=>{ + highlight = checked; + setPixelHighLights(); + },highlight) + + if (hsv){ + hsv = false; + toggleHSV(); + } + + setColorDepth(); + + isActive = true; + } + + me.onClose = function(){ + removePixelHighLigts(); + me.clearPaletteClickAction(); + paletteCanvas = null; + ColorRange.cleanUp(); + isActive = false; + } + + me.setPaletteClickAction = (action)=>{ + paletteClickAction = action; + } + + me.getPaletteClickAction = ()=>{ + return isActive?paletteClickAction:""; + } + + me.clearPaletteClickAction = function(){ + paletteClickAction=""; + Input.removeDragElement(); + } + + me.updateColor = function(color){ + color = Color.toHex(color); + inputHex.value = color; + updateColor(color); + } + + function setColorDepth(){ + let depth = Palette.getColorDepth(); + depthInfo.innerHTML = "Color depth: " + depth + " bits"; + depthInfo.classList.toggle("active",depth<24); + + let colorDepth = depth/3; + sliders.forEach((slider,index)=>{ + slider.range.max = Math.pow(2,colorDepth)-1; + }); + } + + function toggleHSV(){ + hsv = !hsv; + tabs.rgb.classList.toggle("inactive",hsv); + tabs.hsv.classList.toggle("inactive",!hsv); + + let color = Color.fromString(inputHex.value); + let labels = ["Red","Green","Blue"]; + let max = [255,255,255]; + let colorDepth = Palette.getColorDepth()/3; + if (colorDepth === 4) max = [15,15,15]; + if (colorDepth === 3) max = [7,7,7]; + if (hsv){ + color = Color.toHSV(color,true); + labels = ["Hue","Sat.","Value"]; + max = [360,100,100]; + } + sliders.forEach((slider,index)=>{ + slider.range.max = max[index]; + slider.range.value = slider.input.value = color[index]; + slider.label.innerText = labels[index]; + slider.range.classList.toggle("hsv",hsv); + }) + } + + function toggleRangePanel(){ + if (panels.colortab.classList.contains("inactive")){ + panels.colortab.classList.remove("inactive"); + panels.rangestab.classList.add("inactive"); + panels.color.style.display = "block"; + panels.ranges.style.display = "none"; + ColorRange.cleanUp(); + }else{ + panels.colortab.classList.add("inactive"); + panels.rangestab.classList.remove("inactive"); + panels.color.style.display = "none"; + panels.ranges.style.display = "block"; + ColorRange.render(panels.ranges); + } + } + + + + function renderPalette(parent){ + parent.innerHTML = ""; + paletteCanvas = $("canvas",{width:120,height:240}); + paletteCanvasCtx = paletteCanvas.getContext("2d"); + let colors = Palette.get(); + colorHighlight = $div("highlight","",parent); + + let colorsPerPage = paletteCanvas.width / colorSize * paletteCanvas.height / colorSize; + let pageCount = Math.ceil(colors.length/colorsPerPage); + if (palettePage>=pageCount) palettePage = pageCount-1; + + let isInRange = ()=>{return false;} + if (ColorRange.getActiveRange() !== undefined){ + let range = ImageFile.getCurrentFile().colorRange[ColorRange.getActiveRange()]; + isInRange = (index)=>{ + return index >= range.low && index <= range.high; + } + } + + let start = palettePage * colorsPerPage; + let end = start + colorsPerPage; + for (let i=start;i{ + withActions=!withActions; + panelContainer.classList.toggle("withactions",withActions); + }, + info:"Show palette and color actions"},"Palette")); + parent.appendChild(paletteCanvas); + + + + if (colors.length>colorsPerPage){ + $(".nav",{parent:parent}, + $(".prev"+(palettePage?".active":""),{onClick:()=>{palettePage--;renderPalette(parent);}}), + $(".page",""+(palettePage+1)), + $(".next"+(palettePage{palettePage++;renderPalette(parent);}}) + ); + }else{ + palettePage = 0; + } + + setColorSelection(); + } + + + function drawColor(color,index,highlight){ + let colorsPerRow = paletteCanvas.width / colorSize; + let x = (index%colorsPerRow) * colorSize; + let y = Math.floor(index/colorsPerRow) * colorSize; + let c = Color.toString(color); + paletteCanvasCtx.fillStyle = c; + paletteCanvasCtx.fillRect(x,y,colorSize,colorSize); + if (highlight){ + paletteCanvasCtx.beginPath(); + paletteCanvasCtx.strokeStyle = "rgba(255,255,255,0.8)"; + paletteCanvasCtx.lineWidth = 1; + paletteCanvasCtx.rect(x+1.5,y+1.5,colorSize-3,colorSize-3); + paletteCanvasCtx.closePath(); + paletteCanvasCtx.stroke(); + } + if (c === Palette.getDrawColor()){ + currentIndex = index; + colorHighlight.style.left = x + "px"; + colorHighlight.style.top = y + "px"; + } + } + + function setColor(color,index){ + if (index>=Palette.get().length) return; + currentIndex = index; + Palette.setColorIndex(index); + } + + function setColorSelection(){ + let colorsPerRow = paletteCanvas.width / colorSize; + let colorsPerPage = colorsPerRow * paletteCanvas.height / colorSize; + let visualIndex = currentIndex - (palettePage * colorsPerPage); + if ((visualIndex<0) || (visualIndex>=colorsPerPage)){ + colorHighlight.style.display = "none"; + }else{ + colorHighlight.style.display = "block"; + colorHighlight.style.left = (visualIndex%colorsPerRow * colorSize)+ "px"; + colorHighlight.style.top = (Math.floor(visualIndex/colorsPerRow) * colorSize) + "px"; + colorHighlight.style.width = colorHighlight.style.height = colorSize + "px"; + } + + } + + function setPixelHighLights(){ + removePixelHighLigts(); + if (highlight){ + EventBus.trigger(COMMAND.COLORMASK); + } + } + + function removePixelHighLigts(){ + let currentHighLight = ImageFile.getLayerIndexesOfType("pixelSelection"); + currentHighLight.forEach(layerIndex=>{ + ImageFile.removeLayer(layerIndex); + }) + } + + function updateColor(color){ + if (typeof color === "string" && color.substr(0,1) === "#"){ + color = color + "000000"; + color=color.substr(0, 7); + color = Color.fromString(color); + let sliderValues = color; + if (hsv) sliderValues = Color.toHSV(color,true); + sliders.forEach((slider,index)=>{ + slider.range.value = slider.input.value = sliderValues[index]; + }) + }else{ + inputHex.value = Color.toHex(color); + } + colorCanvasCtx.fillStyle = Color.toString(color); + colorCanvasCtx.fillRect(30,0,30,30); + + if (lockToImage){ + let currentHighLight = ImageFile.getLayerIndexesOfType("pixelSelection"); + if (!currentHighLight.length){ + EventBus.trigger(COMMAND.COLORMASK,true); + currentHighLight = ImageFile.getLayerIndexesOfType("pixelSelection"); + } + let colorLayer = ImageFile.getLayer(currentHighLight[0]); + if (colorLayer){ + colorLayer.fill(color); + EventBus.trigger(EVENT.imageContentChanged); + }else{ + console.error("colorLayer not found"); + } + } + buttons.classList.add("active"); + //buttons.forEach(button=>{button.classList.add("active")}) + } + + function setColorRanges(){ + if (paletteCanvas){ + let color = Palette.getDrawColor(); + currentIndex = Palette.getDrawColorIndex(); + + colorCanvasCtx.fillStyle = Color.toString(color); + colorCanvasCtx.fillRect(0,0,60,30); + inputHex.value = colorPicker.value = Color.toHex(color); + + color = hsv ? Color.toHSV(color,true) : Color.fromString(color); + + let colorDepth = Palette.getColorDepth()/3; + let multiplier = 1; + if (colorDepth === 4) multiplier = 16; + if (colorDepth === 3) multiplier = 32; + + sliders.forEach((slider,_index)=>{ + let v = Math.floor(color[_index]/multiplier); + slider.range.value = v; + slider.input.value = v; + }); + setPixelHighLights(); + setColorSelection(); + buttons.classList.remove("active"); + } + } + + EventBus.on(EVENT.colorCount,(count)=>{ + if (highlight) pixelCount.innerHTML = "Used on
" + count + " pixels"; + }) + + EventBus.on(EVENT.paletteChanged,()=>{ + if (paletteCanvas){ + console.log("render palette") + renderPalette(paletteCanvas.parentNode); + } + }) + + EventBus.on(EVENT.colorCycleChanged,(index)=>{ + if (!paletteCanvas) return; + + let image = ImageFile.getCurrentFile(); + let range = image.colorRange[index]; + + for (let i=range.low;i<=range.high;i++){ + let index = i-(range.index || 0); + if (index=start && i{ + if (paletteCanvas) renderPalette(paletteCanvas.parentNode); + }); + + EventBus.on(EVENT.drawColorChanged,()=>{ + setColorRanges(); + }); + + EventBus.on(EVENT.colorDepthChanged,()=>{ + if (isActive){ + setColorDepth(); + setColorRanges(); + } + }); + + EventBus.on(EVENT.paletteChanged,()=>{ + if (isActive) renderPalette(paletteCanvas.parentNode); + }); + + + return me; +}(); + +export default PaletteDialog; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/paletteList.js b/app/web-tools/dpaint/_script/ui/components/paletteList.js new file mode 100644 index 00000000..f760b557 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/paletteList.js @@ -0,0 +1,103 @@ +import $ from "../../util/dom.js"; +import Palette from "../palette.js"; +import Color from "../../util/color.js"; +import Eventbus from "../../util/eventbus.js"; +import {COMMAND, EVENT} from "../../enum.js"; +let PaletteList = function(){ + let me = {}; + let container; + let parent; + let inner; + let showGeneral = true; + let showPlatform = false; + + me.init = function(_parent){ + parent = _parent; + Eventbus.on(COMMAND.TOGGLEPALETTES,me.toggle); + } + + me.toggle = function(){ + if (document.body.classList.contains("withpalettelist")){ + me.hide(); + }else{ + me.show(); + } + } + + me.show = ()=>{ + if (!container) generate(); + document.body.classList.add("withpalettelist"); + container.classList.add("active"); + } + + me.hide = ()=>{ + document.body.classList.remove("withpalettelist"); + container.classList.remove("active"); + } + + function generate(){ + if (!container){ + container = $(".palettelist",$(".caption","Palettes", + $(".close",{ + onClick:me.hide + },"x") + ),inner = $(".inner")); + parent.appendChild(container); + } + inner.innerHTML = ""; + + let list = Palette.getPaletteMap(); + + inner.appendChild($(".group" + (showGeneral?".active":""),{onClick:()=>{showGeneral=!showGeneral; generate()}},"General")); + + if (showGeneral){ + for (let key in list){ + if (!list[key].platform) renderPalette(list[key]); + } + } + + inner.appendChild($(".group" + (showPlatform?".active":""),{onClick:()=>{showPlatform=!showPlatform; generate()}},"Retro platforms")); + if (showPlatform){ + for (let key in list){ + if (list[key].platform) renderPalette(list[key]); + } + } + + inner.appendChild($(".buttons", + $(".button",{onClick:()=>{Eventbus.trigger(COMMAND.LOADPALETTE)}}, "Load Palette"), + $(".button",{onClick:()=>{Eventbus.trigger(COMMAND.SAVEPALETTE)}}, "Save Palette") + )); + } + + function renderPalette(palette){ + let preset = palette.palette; + if (preset){ + let canvas,ctx,colors; + $(".palette", + {parent:inner,onClick:()=>{ + Palette.set(colors); + Eventbus.trigger(EVENT.paletteChanged); + }}, + $(".caption",palette.label), + canvas = $("canvas") + ); + + Palette.loadPreset(palette).then(_colors=>{ + colors = _colors; + canvas.width = 80; + let size = colors.length>32 ? 5:10; + let colorsPerRow = Math.floor(canvas.width/size); + canvas.height = Math.ceil(colors.length/colorsPerRow) * size; + ctx = canvas.getContext("2d"); + colors.forEach((color,index)=>{ + ctx.fillStyle = Color.toString(color); + ctx.fillRect((index%colorsPerRow)*size,Math.floor(index/colorsPerRow)*size,size,size); + }); + }); + } + } + + return me; +}(); + +export default PaletteList; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/resampleDialog.js b/app/web-tools/dpaint/_script/ui/components/resampleDialog.js new file mode 100644 index 00000000..32eee857 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/resampleDialog.js @@ -0,0 +1,85 @@ +import $,{$div, $elm, $title} from "../../util/dom.js"; +import ImageFile from "../../image.js"; +import UserSettings from "../../userSettings.js"; + +var ResampleDialog = function() { + let me = {}; + let lockAspectRatio = true; + let aspectRatio = 1; + + me.render = function (container,modal) { + let image = ImageFile.getCurrentFile(); + aspectRatio = image.width/image.height; + container.innerHTML = ""; + let inputW; + let inputH; + let lock; + let qbuttons; + let qualitySelect; + let resampleQuality = UserSettings.get("resampleQuality") || "pixelated"; + + $("h3",{parent:container},"Resize Image to:"); + $(".panel.form",{parent:container}, + $("span.label","width"), + inputW = $("input",{type:"number",value:image.width, onkeydown:modal.inputKeyDown, oninput:()=>{ + if (lockAspectRatio){ + let w = parseInt(inputW.value); + if (isNaN(w)) w=image.width; + inputH.value = Math.round(w/aspectRatio); + }}}), + $("span","pixels"), + $("br"), + $("span.label","height"), + inputH = $("input",{type:"number",value:image.height, onkeydown:modal.inputKeyDown, oninput:()=>{ + if (lockAspectRatio){ + let h = parseInt(inputH.value); + if (isNaN(h)) w=image.height; + inputW.value = Math.round(h*aspectRatio); + }}}), + $("span","pixels"), + lock = $(".lock.active",$(".link",{onClick:()=>{ + lockAspectRatio = !lockAspectRatio; + lock.classList.toggle("active",lockAspectRatio); + }})), + qbuttons = $(".quick"), + qualitySelect = $("select.resize",$("option",{selected:resampleQuality==="pixelated"},"Pixelated"),$("option",{selected:resampleQuality==="smooth"},"Smooth")) + ); + + $(".buttons",{parent:container}, + $(".button.ghost",{onClick:modal.hide},"Cancel"), + $(".button.primary",{onClick:()=>{ + let w = parseInt(inputW.value); + if (isNaN(w)) w = image.width; + let h = parseInt(inputH.value); + if (isNaN(h)) w = image.height; + if (w<1)w=1; + if (h<1)h=1; + let quality = qualitySelect.value === "Pixelated" ? "pixelated" : "smooth"; + UserSettings.set("resampleQuality",quality); + modal.hide(); + ImageFile.resample({width:w,height: h,quality:quality}); + }},"Update") + ); + + + let labels = ["x2","/2","x3","/3"]; + for (let i = 0;i<4;i++){ + $div("button calc",labels[i],qbuttons,()=>{ + let w = parseInt(inputW.value); + let h = parseInt(inputH.value); + if (isNaN(w)) w=image.width; + if (isNaN(h)) w=image.height; + if (i===0){w*=2;h*=2;} + if (i===1){w/=2;h/=2;} + if (i===2){w*=3;h*=3;} + if (i===3){w/=3;h/=3;} + inputW.value = Math.round(w); + inputH.value = Math.round(h); + }); + } + + } + return me; +}(); + +export default ResampleDialog; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/resizeDialog.js b/app/web-tools/dpaint/_script/ui/components/resizeDialog.js new file mode 100644 index 00000000..a1105e9e --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/resizeDialog.js @@ -0,0 +1,104 @@ +import {$div, $elm, $title} from "../../util/dom.js"; +import ImageFile from "../../image.js"; + +var ResizeDialog = function() { + let me = {}; + let lockAspectRatio = true; + let aspectRatio = 1; + let anchorPoint = "center"; + + me.render = function (container,modal) { + let image = ImageFile.getCurrentFile(); + aspectRatio = image.width/image.height; + container.innerHTML = ""; + $title(3, "Resize Canvas to:", container); + let panel = $div("panel form","",container); + let inputW = document.createElement("input"); + inputW.value = image.width; + inputW.type = "number"; + let inputH = document.createElement("input"); + inputH.type = "number"; + inputH.value = image.height; + inputW.onkeydown = modal.inputKeyDown; + inputH.onkeydown = modal.inputKeyDown; + inputW.oninput = ()=>{ + if (lockAspectRatio){ + let w = parseInt(inputW.value); + if (isNaN(w)) w=image.width; + inputH.value = Math.round(w/aspectRatio); + } + } + inputH.oninput = ()=>{ + if (lockAspectRatio){ + let h = parseInt(inputH.value); + if (isNaN(h)) w=image.height; + inputW.value = Math.round(h*aspectRatio); + } + } + + $elm("span","width",panel,"label"); + panel.appendChild(inputW); + $elm("span","pixels",panel); + $elm("br","",panel); + + $elm("span","height",panel,"label"); + panel.appendChild(inputH); + $elm("span","pixels",panel); + + let lock = $div("lock active","",panel); + $div("link","",lock,()=>{ + lockAspectRatio = !lockAspectRatio; + lock.classList.toggle("active",lockAspectRatio); + }); + + let qbuttons=$div("quick","",panel); + let labels = ["x2","/2","x3","/3"]; + for (let i = 0;i<4;i++){ + $div("button calc",labels[i],qbuttons,()=>{ + let w = parseInt(inputW.value); + let h = parseInt(inputH.value); + if (isNaN(w)) w=image.width; + if (isNaN(h)) w=image.height; + if (i===0){w*=2;h*=2;} + if (i===1){w/=2;h/=2;} + if (i===2){w*=3;h*=3;} + if (i===3){w/=3;h/=3;} + inputW.value = Math.round(w); + inputH.value = Math.round(h); + }); + } + + + let anchor=$div("anchor center","",panel); + let zones = ["top left","top","top right","left","center","right","bottom left","bottom","bottom right"]; + for (let i = 0; i<9; i++){ + $div("hotspot","",anchor,()=>{ + anchorPoint = zones[i]; + anchor.className="anchor " + anchorPoint; + }) + } + $div("page","",anchor); + $title(3, "Anchor:", anchor); + let directions=["top","right","bottom","left"]; + for (let i = 0; i<4; i++){ + $div("arrow " + directions[i],"",anchor); + } + + let buttons = $div("buttons","",container); + $div("button ghost","Cancel",buttons,modal.hide); + $div("button primary","Update",buttons,()=>{ + let w = parseInt(inputW.value); + if (isNaN(w)) w = image.width; + let h = parseInt(inputH.value); + if (isNaN(h)) w = image.height; + if (w<1)w=1; + if (h<1)h=1; + modal.hide(); + ImageFile.resize({width:w,height: h, anchor: anchorPoint}); + }); + + } + return me; +}(); + +export default ResizeDialog; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/resizer.js b/app/web-tools/dpaint/_script/ui/components/resizer.js new file mode 100644 index 00000000..f28bd17d --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/resizer.js @@ -0,0 +1,340 @@ +import EventBus from "../../util/eventbus.js"; +import {COMMAND, EVENT} from "../../enum.js"; +import {$div} from "../../util/dom.js"; +import Editor from "../editor.js"; +import Input from "../input.js"; +import Cursor from "../cursor.js"; +import StatusBar from "../statusbar.js"; + +var Resizer = function(editor){ + let me = {}; + let currentSize; + let previousSize; + let sizeBox; + let updateHandler; + let dots = []; + let rotateDots = []; + let touchData = {}; + let aspectRatio = 1; + let canRotate = false; + + me.init = function(options){ + options = options || {}; + if (!sizeBox) createSizeBox(editor.getViewPort()); + sizeBox.classList.add("active"); + sizeBox.classList.toggle("hot",!!options.hot); + if (options.aspect) { + aspectRatio = options.aspect; + console.log("setting AR to " + aspectRatio) + } + canRotate = !!options.canRotate; + sizeBox.classList.toggle("canrotate",canRotate); + previousSize = undefined; + currentSize = undefined; + + setSize(options.x,options.y,options.width,options.height,options.rotation); + } + + function setSize(x,y,w,h,rotation){ + previousSize = Object.assign({},currentSize); + currentSize = { + left: Math.round(x), + top: Math.round(y), + width: Math.round(w), + height: Math.round(h), + rotation: rotation + } + if (!rotation){ + sizeBox.style.transform = "none"; + } + + updateSizeBox(); + EventBus.trigger(EVENT.sizerChanged,{ + from: previousSize, + to: currentSize + }); + } + + me.setOnUpdate = function(handler){ + updateHandler = handler; + } + + me.commit = function(){ + if (sizeBox) sizeBox.classList.remove("hot"); + updateHandler = undefined; + } + + me.get = function(){ + return currentSize; + } + + me.move = function(x,y){ + if (me.isActive() && currentSize){ + EventBus.trigger(EVENT.sizerStartChange); + //TODO: this still crops the canvas if we move outside the canvas + + previousSize = Object.assign({},currentSize); + currentSize.left += x; + currentSize.top += y; + updateSizeBox(); + EventBus.trigger(EVENT.sizerChanged,{ + from: previousSize, + to: currentSize + }); + } + } + + me.zoom = function(zoom){ + updateSizeBox(); + } + + me.isActive = function(){ + return sizeBox && sizeBox.classList.contains("active"); + } + + me.remove = function(){ + previousSize = undefined; + if(sizeBox) sizeBox.classList.remove("active"); + } + + + EventBus.on(EVENT.modifierKeyChanged,function(){ + if (sizeBox && sizeBox.classList.contains("active") && currentSize){ + updateSizeBox(); + } + }); + + EventBus.on(COMMAND.CLEARSELECTION,()=>{ + me.remove(); + }) + + EventBus.on(EVENT.imageSizeChanged,()=>{ + me.remove(); + }); + + function createSizeBox(parent){ + sizeBox = $div("sizebox","",parent,e=>{ + resizeBox(e); + }); + sizeBox.classList.toggle("canrotate",canRotate); + + for (let i = 0; i< 8; i++){ + let dot = $div("sizedot","",sizeBox,function(e){ + resizeBox(e); + }); + + dot.onDragStart = function(){ + EventBus.trigger(EVENT.sizerStartChange); + } + dot.onDrag = function(x,y){ + handleDrag(x,y,dot) + } + dot.onDragEnd = function(x,y){ + sizeBox.classList.remove("hot"); + } + dot.index = i; + dots.push(dot); + } + + for (let i = 0; i< 4; i++){ + let rotateDot = $div("rotatedot d"+(i+1),"",sizeBox,function(e){ + + }); + rotateDot.onDragStart = function(){ + let x = currentSize.left + currentSize.width/2; + let y = currentSize.top + currentSize.height/2; + touchData.rotateCenter = [x,y]; + touchData.startAngle = currentSize.rotation; + if (i===0) touchData.rotateStart= [currentSize.left,currentSize.top]; + if (i===1) touchData.rotateStart= [currentSize.left+currentSize.width,currentSize.top]; + if (i===2) touchData.rotateStart= [currentSize.left+currentSize.width,currentSize.top+currentSize.height]; + if (i===3) touchData.rotateStart= [currentSize.left,currentSize.top+currentSize.height]; + touchData.isRotating = true; + + } + rotateDot.onDrag = function(x,y,_touchData){ + touchData.rotateEnd = [touchData.rotateStart[0]+x,touchData.rotateStart[1]+y]; + let a ={x:touchData.rotateStart[0],y:touchData.rotateStart[1]}; + let b ={x:touchData.rotateCenter[0],y:touchData.rotateCenter[1]}; + let c ={x:touchData.rotateEnd[0],y:touchData.rotateEnd[1]}; + let angle = angle_between_points(c,b,a); + angle += touchData.startAngle||0; + if (Input.isShiftDown() && Input.isPointerDown()){ + angle = Math.round(angle/15) * 15; + } + currentSize.angle=angle; + currentSize.rotation=angle; + sizeBox.style.transform = "rotate("+angle+"deg)"; + + updateSizeBox(); + + } + rotateDot.onDragEnd = function(x,y){ + currentSize.rotation = currentSize.angle; + delete currentSize.angle; + touchData.isRotating = false; + } + rotateDot.onpointerenter = function(){ + Cursor.set("rotate"); + } + rotateDot.onpointerleave = function(){ + Cursor.reset(); + } + rotateDot.index = i; + rotateDots.push(rotateDot); + } + + sizeBox.onDragStart = function(x,y){ + EventBus.trigger(EVENT.sizerStartChange); + } + sizeBox.onDrag = function(x,y){ + handleDrag(x,y,{index:8}) + } + sizeBox.onDragEnd = function(x,y){ + sizeBox.classList.remove("hot"); + } + } + + function updateSizeBox(){ + if (!currentSize) return; + var parent = editor.getCanvas(); + let viewport = editor.getViewPort(); + var rect = parent.getBoundingClientRect(); + var rect2 = viewport.getBoundingClientRect(); + let zoom = editor.getZoom(); + + if (Input.isShiftDown() && Input.isPointerDown() && !touchData.isRotating){ + // aspect ratio lock + currentSize._width = currentSize._width||currentSize.width; + currentSize._height = currentSize._height||currentSize.height; + //let size = Math.min(currentSize._width,currentSize._height); + let w = currentSize._width; + let h = w / aspectRatio; + if (w>currentSize.width || h>currentSize.height){ + h = currentSize.height; + w = h * aspectRatio; + } + w = Math.round(w); + h = Math.round(h); + currentSize.width = w; + currentSize.height = h; + } + + let wz = currentSize.width*zoom; + let hz = currentSize.height*zoom; + + let xz = (rect.left - rect2.left + viewport.scrollLeft + currentSize.left*zoom); + let yz = (rect.top - rect2.top + viewport.scrollTop + currentSize.top*zoom); + + sizeBox.style.left = xz + "px"; + sizeBox.style.top = yz + "px"; + sizeBox.style.width = wz + "px"; + sizeBox.style.height = hz + "px"; + + dots[1].style.left = Math.round(wz/2) + "px"; + dots[2].style.left = wz + "px"; + + dots[3].style.left = wz + "px"; + dots[3].style.top = Math.round(hz/2) + "px"; + + dots[4].style.left = wz + "px"; + dots[4].style.top = hz + "px"; + + dots[5].style.left = dots[1].style.left; + dots[5].style.top = hz + "px"; + + dots[6].style.top = hz + "px"; + + dots[7].style.top = dots[3].style.top; + + rotateDots[1].style.left = dots[2].style.left; + rotateDots[2].style.top = dots[4].style.top; + rotateDots[2].style.left = dots[4].style.left; + rotateDots[3].style.top = dots[6].style.top; + + let text = "x:" +currentSize.left + " y:" + currentSize.top + " w:" + currentSize.width + " h:" + currentSize.height; + if (currentSize.rotation) text += " " + Math.round(currentSize.rotation) + "°"; + StatusBar.setToolTip(text); + + if (updateHandler) updateHandler(); + + } + + function resizeBox(){ + touchData.isResizing = true; + touchData.isdown = true; + touchData.startSelectWidth = currentSize.width; + touchData.startSelectHeight = currentSize.height; + touchData.startSelectLeft = currentSize.left; + touchData.startSelectTop = currentSize.top; + sizeBox.classList.add("hot"); + } + + function handleDrag(x,y,dot){ + let zoom = Editor.getActivePanel().getZoom(); + let l = touchData.startSelectLeft; + let t = touchData.startSelectTop; + let w = touchData.startSelectWidth; + let h = touchData.startSelectHeight; + + let xz = x/zoom; + let yz = y/zoom; + + switch (dot.index){ + case 0: + l+=xz; + t+=yz; + h-=yz; + w-=xz; + break; + case 1: + t+=yz; + h-=yz; + break; + case 2: + t+=yz; + w+=xz; + h-=yz; + break; + case 3: + w+=xz; + break; + case 4: + w+=xz; + h+=yz; + break; + case 5: + h+=yz; + break; + case 6: + l+=xz; + w-=xz; + h+=yz; + break; + case 7: + l+=xz; + w-=xz; + break; + case 8: + l+=xz; + t+=yz; + break; + } + + setSize(l,t,w,h,currentSize?currentSize.rotation:0); + + } + + function angle_between_points( p1, p2, p3 ){ + // p2 is the center point; + const ab = { x: p2.x - p1.x, y: p2.y - p1.y }; + const bc = { x: p3.x - p2.x, y: p3.y - p2.y }; + const radians = Math.atan2(bc.y, bc.x) - Math.atan2(ab.y, ab.x); + const degrees = -(radians * 180 / Math.PI -180) % 360; + return degrees; + } + + return me; +}; + +export default Resizer \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/saveDialog.js b/app/web-tools/dpaint/_script/ui/components/saveDialog.js new file mode 100644 index 00000000..17a1ee95 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/saveDialog.js @@ -0,0 +1,406 @@ +import $ from "../../util/dom.js"; +import ImageFile from "../../image.js"; +import saveAs from "../../util/filesaver.js"; +import Modal, {DIALOG} from "../modal.js"; +import Palette from "../palette.js"; +import EventBus from "../../util/eventbus.js"; +import {COMMAND, EVENT} from "../../enum.js"; +import Generate from "../../fileformats/generate.js"; +import Brush from "../brush.js"; + +var SaveDialog = function(){ + let me ={}; + let nameInput; + let currentFile; + + let filetypes = { + IFF:{ + description: 'IFF ILBM Image', + accept: { + 'image/x-ilbm': ['.iff'], + } + }, + PLANES:{ + description: 'Binary Bitplane Data', + accept: { + 'application/octet-stream': ['.planes'], + } + }, + MASK:{ + description: 'Binary Bitplane Mask', + accept: { + 'application/octet-stream': ['.mask'], + } + }, + PNG:{ + description: 'PNG Image', + accept: { + 'image/png': ['.png'], + } + }, + GIF:{ + description: 'GIF Image', + accept: { + 'image/gif': ['.gif'], + } + }, + ICO:{ + description: 'Amiga Icon', + accept: { + 'application/octet-stream': ['.info'], + } + }, + PALETTE:{ + description: 'DPaint.js Palette', + accept: { + 'application/json': ['.json'], + } + }, + DPAINTJS:{ + description: 'DPaint.js File', + accept: { + 'application/json': ['.json'], + } + }, + INDEXED:{ + description: 'DPaint.json with indexed color pixels', + accept: { + 'application/json': ['.json'], + } + }, + ADF:{ + description: 'Amiga Disk File', + accept: { + 'application/octet-stream': ['.adf'], + } + }, + FILE:{ + description: 'File' + }, + } + + function saveFile(blob,fileName,type) { + return new Promise(async (resolve,reject)=>{ + if (window.host && window.host.saveFile){ + window.host.saveFile(blob,fileName); + resolve(); + return; + } + + if (window.showSaveFilePicker){ + window.showSaveFilePicker({ + suggestedName: fileName, + types: [type], + }).then(async handle => { + const writableStream = await handle.createWritable(); + await writableStream.write(blob); + await writableStream.close(); + resolve(); + }).catch(async err => { + console.error(err); + await saveAs(blob,fileName); + resolve(); + }); + }else{ + await saveAs(blob,fileName); + resolve(); + } + }); + } + + me.render = function(container){ + let submenu; + container.innerHTML = ""; + let mainPanel; + container.appendChild( + mainPanel = $(".saveform", + $(".name",$("h4","Name"),nameInput = $("input",{type:"text",value:ImageFile.getName()})), + $("h4","Save as"), + $(".moremenu", + $(".item.png",{onClick:(e,elm)=>{waitFor(writePNG8,elm)}},"PNG 8-bit",$(".subtitle","PNG with indexed colors"),$(".info","Max 256 colors, no layers, only the current frame gets saved.")), + $(".item.gif",{onClick:(e,elm)=>{waitFor(writeGIF,elm)}},"GIF",$(".subtitle","(also animated)"),$(".info","Max 256 colors, no layers, frames get saved as animation.")), + $(".item.index",{onClick:(e,elm)=>{waitFor(writeINDEXED,elm)}},"Indexed",$(".subtitle","Dpaint.json with indexed colors"),$(".info","Map each pixel to the current palette. Used e.g. for the Magrathea Living Worlds app.")), + $(".item.planes",{onClick:writePLANES},"Planes",$(".subtitle","Binary bitplane data"),$(".info","Converts the current image to binary bitplane data. (Demoscene stuff)")), + $(".item.mask",{onClick:writeMASK},"Mask",$(".subtitle","Binary bitplane mask"),$(".info","Converts the current image to a binary bitplane mask. (Demoscene stuff)")), + ), + $(".platform.general", + $("h4.general","General"), + renderButton("png","PNG Image","PNG file","Full color and transparency, no layers, only the current frame gets saved.",writePNG), + renderButton("json","DPaint.JSON","JSON file","The internal format of Dpaint.. All features supported",writeJSON), + renderButton("psd","PSD","Coming soon ...","Working on it!"), + $(".button.more",{onclick:()=>{ + //submenu.classList.toggle("active"); + mainPanel.classList.toggle("hasmore"); + }},"More") + //renderButton("planes","BIN","Bitplanes","Binary bitplane data",writePLANES) + ), + $(".platform.amiga", + $("h4.amiga","Amiga"), + currentFile ? renderButton("adf","ADF","Save to ADF","Save Back to ADF. (Download the ADF afterwards when you're done editing)",writeADF) : null, + renderButton("iff","IFF Image","Amiga IFF file","Maximum 256 colors, only the current frame gets saved.",writeIFF), + renderButton("mui","Amiga Classic Icon","OS1.3 Style","For all Amiga's. Use MUI palette for best compatibility",writeAmigaClassicIcon), + renderButton("os3","Amiga Color Icon","OS3.2 Style","Also called 'Glowicons'. For modern Amiga systems and/or with PeterK's Icon Library. Max 256 colors.",writeAmigaColorIcon), + renderButton("os4","Amiga Dual PNG Icon","OS4 Style","For modern Amiga systems and/or with PeterK's Icon Library. Full colors.",writeAmigaPNGIcon) + ) + )); + + nameInput.onkeydown = function(e){ + e.stopPropagation(); + } + nameInput.onchange = function(){ + ImageFile.setName(getFileName()); + } + + // check if we have a server to post back to + let postBack; + let urlParams = new URLSearchParams(window.location.search); + let url; + if (urlParams.has("putback")){ + url = urlParams.get("putback"); + if (url) postBack = {method: "PUT", url: url} + } + if (urlParams.has("postback")){ + url = urlParams.get("postback"); + if (url) postBack = {method: "POST", url: url} + } + + if (postBack){ + try { + let url = new URL(postBack.url,window.location.href); + postBack.domain = url.hostname; + postBack.url = postBack.url = url.href; + }catch{ + postBack = undefined; + } + } + if (postBack) addPostBackOverlay(postBack,container); + } + + me.setFile = function (file){ + currentFile = file; + } + + function renderButton(type,title,subtitle,info,onClick){ + return $(".button",{onclick:onClick}, + $(".icon."+type), + $(".title",title), + $(".subtitle",subtitle), + $(".info",info) + ); + } + + function addPostBackOverlay(postBack,container){ + let overridePanel = $(".saveoverlay", + + $(".info","This editor is configured to save files back to",$("b",postBack.domain), $(".textlink",{onClick:()=>{overridePanel.remove()}},"More options")), + $(".spinner"), + $(".buttons", + $(".button.ghost",{onclick:()=>{ + Modal.hide(); + }},"Cancel"), + $(".button.primary",{onclick:async ()=>{ + overridePanel.classList.add("loading"); + let blob = await Generate.file("PNG"); + if (blob){ + fetch(postBack.url,{ + method: postBack.method, + body: blob + }).then((response)=>{ + if (response.ok){ + Modal.hide(); + }else{ + Modal.alert("Error saving file to server"); + } + }).catch((err)=>{ + Modal.alert("Error saving file to server"); + }); + } + }},"Save") + ) + ); + + container.appendChild(overridePanel); + } + + function waitFor(action,elm){ + let spinner = $(".spinner"); + elm.appendChild(spinner); + elm.classList.add("loading"); + + setTimeout(()=>{ + action().then((result)=>{ + spinner.remove(); + elm.classList.remove("loading"); + if (result && result.messages && result.messages.length){ + if (result.result === "warning" && result.file){ + result.messages.unshift("Some issues were found while generating the file:"); + result.messages.push("You might want to check the file."); + } + Modal.show(DIALOG.OPTION,{ + title: result.title || result.result || "Alert", + text: result.messages, + buttons: [{label:"OK"}] + }); + }else{ + Modal.hide(); + } + }); + },50); + } + + function getFileName(){ + let name = nameInput ? nameInput.value.replace(/[ &\/\\#,+()$~%.'":*?<>{}]/g, ""):""; + return name || "Untitled" + } + + async function writeIFF(){ + let blob = await Generate.file("IFF"); + if (blob){ + let fileName = getFileName() + '.iff'; + await saveFile(blob,fileName,filetypes.IFF); + Modal.hide(); + } + } + + async function writePLANES(){ + let result = await Generate.file("BitPlanes"); + console.error(result); + if (result && result.planes){ + let blob = new Blob([result.planes], {type: "application/octet-stream"}); + let fileName = getFileName() + '.planes'; + await saveFile(blob,fileName,filetypes.PLANES); + + fileName = getFileName() + '.palette.txt'; + let content = "{"; + for (let i=0;i{ + c = Math.round(c/16); + if (c>15) c=15; + content += c.toString(16); + }) + content += ","; + } + content = content.slice(0,-1) + "}"; + let blob2 = new Blob([content], {type: "application/octet-stream"}); + await saveFile(blob2,fileName,filetypes.FILE); + Modal.hide(); + } + } + + async function writeMASK(){ + let result = await Generate.file("BitMask"); + console.error(result); + if (result && result.planes){ + let blob = new Blob([result.planes], {type: "application/octet-stream"}); + let fileName = getFileName() + '.mask'; + await saveFile(blob,fileName,filetypes.MASK); + Modal.hide(); + } + return result; + } + + async function writeINDEXED(){ + let result = await Generate.file("DPAINTINDEXED"); + if (result.file){ + await saveFile(result.file,getFileName() + '.json',filetypes.INDEXED); + } + return result; + } + + function writeADF(){ + EventBus.trigger(COMMAND.SAVEFILETOADF,[currentFile,getFileName()]); + } + + async function writePNG(){ + let blob = await Generate.file("PNG"); + if (blob){ + await saveFile(blob,getFileName() + ".png",filetypes.PNG); + Modal.hide(); + } + } + + async function writePNG8(){ + let result = await Generate.file("PNG8"); + if (result.file){ + await saveFile(result.file,getFileName() + ".png",filetypes.PNG); + } + return result; + } + + async function writeGIF(){ + let result = await Generate.file("GIF"); + if (result.file){ + await saveFile(result.file,getFileName() + ".gif",filetypes.GIF); + } + return result; + } + + async function writeJSON(){ + let result = await Generate.file("DPAINT"); + if (result.file){ + await saveFile(result.file,getFileName() + '.json',filetypes.DPAINTJS); + Modal.hide(); + } + } + + async function writeAmigaClassicIcon(config){ + let blob = await Generate.file("classicIcon"); + if (blob){ + await saveFile(blob,getFileName() + '.info',filetypes.ICO); + Modal.hide(); + } + } + + async function writeAmigaPNGIcon(){ + let blob = await Generate.file("PNGIcon"); + if (blob){ + saveFile(blob, getFileName() + '.info',filetypes.ICO).then(()=>{ + Modal.hide(); + }); + } + } + + async function writeAmigaColorIcon(){ + let blob = await Generate.file("colorIcon"); + if (blob){ + await saveFile(blob,getFileName() + '.info',filetypes.ICO); + Modal.hide(); + } + } + + EventBus.on(COMMAND.SAVEPALETTE,()=>{ + let struct = { + type: "palette", + palette: Palette.get() + } + let blob = new Blob([JSON.stringify(struct,null,2)], { type: 'application/json' }) + saveFile(blob,getFileName() + '_palette.json',filetypes.PALETTE).then(()=>{ + + }); + }); + + EventBus.on(COMMAND.SAVEBRUSH,()=>{ + let struct = Brush.export(); + let blob = new Blob([JSON.stringify(struct,null,2)], { type: 'application/json' }) + saveFile(blob,getFileName() + '_brush.json',filetypes.PALETTE).then(()=>{ + + }); + }); + + EventBus.on(COMMAND.SAVEDISK,([data,fileName])=>{ + let blob = new Blob([data], {type: "application/octet-stream"}) + saveFile(blob,fileName,filetypes.ADF).then(()=>{}); + }) + + EventBus.on(COMMAND.SAVEGENERIC,([data,fileName])=>{ + let blob = new Blob([data], {type: "application/octet-stream"}) + saveFile(blob,fileName,filetypes.FILE).then(()=>{}); + }) + + + return me; +}(); + +export default SaveDialog; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/selectbox.js b/app/web-tools/dpaint/_script/ui/components/selectbox.js new file mode 100644 index 00000000..eedbf37b --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/selectbox.js @@ -0,0 +1,651 @@ +import {$div} from "../../util/dom.js"; +import Selection from "../selection.js"; +import EventBus from "../../util/eventbus.js"; +import {COMMAND, EVENT} from "../../enum.js"; +import Editor from "../editor.js"; +import ImageFile from "../../image.js"; +import Input from "../input.js"; +import {duplicateCanvas, releaseCanvas, outLineCanvas} from "../../util/canvasUtils.js"; +import Color from "../../util/color.js"; +import ToolOptions from "./toolOptions.js"; + +/* + SelectBox follows changes in the selection. + If the resize is active, the resize triggers a change in the selection, this triggers a change in the selectbox. + + There are 3 different types of selection: + - rectangle selection, this is also calculated as a bounding box for the other types + - polygon selection, this is a series of points + - canvas selection, this is a selection of pixels + */ + +let SelectBox = ((editor,resizer)=>{ + let me = {}; + + let box = $div("selectbox"); + let canvas; + let ctx; + let selectionPoints = []; + let selectionTransform; + let selecting; + let dots; + let shape; + let selectionTool; + let timeout; + + let border = $div("border","/svg>"); + box.appendChild(border); + + let content = $div("content"); + box.appendChild(content); + + me.getBox = ()=>{ + return box; + } + + me.activate = (tool)=>{ + if (tool) selectionTool = tool; + let currentSelection = Selection.get(); + box.classList.add("active"); + + if (currentSelection){ + switch (tool){ + case COMMAND.SELECT: + if (editor.isActive()){ + resizer.init({ + x:currentSelection.left, + y:currentSelection.top, + width:currentSelection.width, + height:currentSelection.height, + rotation:0, + aspectRatio:1, + canRotate: false + }); + } + break; + case COMMAND.POLYGONSELECT: + if (currentSelection.points){ + clearTimeout(timeout); + resizer.remove(); + me.polySelect(); + } + break; + case COMMAND.FLOODSELECT: + resizer.remove(); + break; + case COMMAND.COLORSELECT: + resizer.remove(); + break; + case COMMAND.TOSELECTION: + resizer.remove(); + break; + } + } + } + + me.deActivate = ()=>{ + box.classList.remove("active","capture"); + cleanUp(); + } + + me.isActive = ()=>{ + return box.classList.contains("active"); + } + + me.boundingBoxSelect = (point)=>{ + EventBus.trigger(COMMAND.CLEARSELECTION); + me.activate(COMMAND.SELECT); + resizer.init({ + x: point.x, + y: point.y, + width: 0, + height: 0, + rotation: 0, + hot: true, + aspect: 1, + canRotate: false, + }); + } + + me.polySelect = (point)=>{ + if (!dots) dots = $div("dots","",content); + + if (!selecting){ + let currentSelection = Selection.get(); + if (currentSelection && currentSelection.points && currentSelection.points.length){ + selectionPoints = currentSelection.points; + }else{ + selectionPoints = []; + } + Input.setActiveKeyHandler(keyHandler); + } + selecting = true; + border.classList.remove("active","filled"); + box.classList.add("capture","active"); + + if (point){ + selectionPoints.push(point); + if (selectionPoints.length===1) selectionPoints.push({x:point.x,y:point.y}); + } + + updateBoundingBox(); + drawPolyShape(); + } + + me.endPolySelect = (fromClick)=>{ + selecting = false; + timeout = setTimeout(()=>{ + EventBus.trigger(EVENT.endPolygonSelect); + Input.setActiveKeyHandler(); + selectionPoints = []; + if (dots){ + dots.innerHTML = ""; + dots = undefined; + } + box.classList.remove("capture"); + },fromClick?100:0); + } + + // Don't overuse this function, it's expensive + me.applyCanvas = _canvas=>{ + let selectedCanvas = duplicateCanvas(_canvas,true); + let selectedCtx = selectedCanvas.getContext("2d"); + + selectedCtx.globalCompositeOperation = "source-in"; + selectedCtx.fillStyle = "white"; + selectedCtx.fillRect(0,0,_canvas.width,_canvas.height); + selectedCtx.globalCompositeOperation = "source-over"; + + // generate SVG outline..; Expensive! move to webworker? + let outline = outLineCanvas(selectedCtx,false); + + Selection.set({ + left:outline.box.x, + top: outline.box.y, + width: outline.box.w, + height: outline.box.h, + canvas: selectedCanvas, + outline: outline.lines + }) + } + + + me.floodSelect = function(canvas,point,fillColor){ + fillColor = fillColor||[0,0,0]; + let w = canvas.width; + let h = canvas.height; + let imageData = canvas.getContext("2d").getImageData(0,0,w,h); + let tolerance = ToolOptions.getTolerance(); + fillColor[3] = 255; + + let c = duplicateCanvas(canvas).getContext("2d"); + let target = c.getImageData(0,0,w,h); + + let done = {}; + let check = []; + let ind = getIndex(point); + put(ind); + let color = getColor(ind); + + while (check.length){ + let i = check.shift(); + let x = i%w; + let y = (i-x)/w; + if (x>0) checkIndex(i-1); + if (x0) checkIndex(i-w); + if (y=imageData.data.length){ + console.error("invalid index " + index) + }else{ + return Color.toHex([r,g,b,a]); + } + + } + + function getIndex(p) { + return p.y*w + p.x; + } + + function checkIndex(ind){ + if (!done[ind]){ + let c = getColor(ind); + let passed = c === color; + if (!passed && tolerance){ + let distance = Color.distance(c,color); + passed = distance <= tolerance*2; + } + if (passed) put(ind); + } + } + + function put(ind){ + target.data[ind*4] = fillColor[0]; + target.data[ind*4 + 1] = fillColor[1]; + target.data[ind*4 + 2] = fillColor[2]; + target.data[ind*4 + 3] = 255; + done[ind] = true; + check.push(ind) + } + } + + + me.colorSelect = function(color){ + let canvas = ImageFile.getActiveLayer().getCanvas(); + + let w = canvas.width; + let h = canvas.height; + let imageData = canvas.getContext("2d").getImageData(0,0,w,h); + + let c = duplicateCanvas(canvas).getContext("2d"); + let target = c.getImageData(0,0,w,h); + color = Color.fromString(color); + + for (let x = 0;x{ + resizer.zoom(); + renderSelection(); + } + + me.updatePoint =(point,index)=>{ + if (typeof index === "undefined") index = selectionPoints.length-1; + let p = selectionPoints[index]; + if (p){ + p.x = point.x; + p.y = point.y; + } + updateBoundingBox(); + drawPolyShape(); + } + + function drawPolyShape(){ + let zoom = Editor.getActivePanel().getZoom(); + let drawOutline = ToolOptions.showSelectionOutline(); + let drawFill = ToolOptions.showSelectionMask(); + let generateSVG = drawOutline || drawFill; + + if (dots) dots.innerHTML = ""; + let w = ImageFile.getCurrentFile().width; + let h = ImageFile.getCurrentFile().height; + + let path = ""; + + selectionPoints.forEach((point,index)=>{ + if (dots){ + let dot = $div("sizedot","",dots,()=>{ + + }); + dot.onDragStart = (x,y)=>{ + point.startX = point.x; + point.startY = point.y; + } + dot.onDrag = (x,y)=>{ + point.x = Math.round(point.startX + x/zoom); + point.y = Math.round(point.startY + y/zoom); + drawPolyShape(); + } + dot.onDragEnd = ()=>{ + updateBoundingBox(); + } + dot.style.left = point.x*zoom + "px"; + dot.style.top = point.y*zoom + "px"; + } + + + if (generateSVG) path += point.x + " " + point.y + " "; + + }); + + if (generateSVG){ + if (!shape) shape = $div("shape","",content); + // close the path + if (selectionPoints.length>1){ + path += selectionPoints[0].x + " " + selectionPoints[0].y + " "; + } + + let svg = ""; + + let className = ""; + if (drawOutline) className = "white"; + if (drawFill) className += " filled"; + + + svg += ""; + if (drawOutline) svg += ""; + svg += ""; + + shape.innerHTML = svg; + }else{ + if (shape){ + shape.innerHTML = ""; + shape = undefined; + } + } + + } + + function drawOutline(selection){ + if (!selection) return; + let lines = selection.outline; + if (!lines) return; + + if (!shape) shape = $div("shape","",content); + let w = ImageFile.getCurrentFile().width; + let h = ImageFile.getCurrentFile().height; + + let svg = ""; + if (lines.length>6000){ + console.warn("too many lines, displaying bounding box instead"); + + svg += ''; + svg += ''; + + }else{ + // draw lines + lines.forEach(h=>{ + let x = h[0]; + let y = h[1]; + let x2 = h[2]; + let y2 = h[3]; + svg += ''; + svg += ''; + }); + + } + + svg += "" + shape.innerHTML = svg; + } + + + function keyHandler(code){ + //console.error(code); + if (selecting){ + switch (code){ + case "keyj": + EventBus.trigger(COMMAND.TOLAYER) + return true; + case "keyk": + EventBus.trigger(COMMAND.CUTTOLAYER) + return true; + case "escape": + me.endPolySelect(); + return true; + case "enter": + me.endPolySelect(); + return true; + } + } + } + + function cleanUp(){ + selectionPoints = []; + selectionTransform = undefined; + if (dots){ + dots.innerHTML = ""; + dots = undefined; + } + if (shape){ + shape.innerHTML = ""; + shape = undefined; + } + if (canvas){ + canvas.remove(); + releaseCanvas(canvas); + canvas = undefined; + } + content.innerHTML = ""; + border.classList.remove("active"); + border.classList.remove("filled"); + + if (selecting){ + selecting = false; + me.endPolySelect(); + } + } + + function updateBoundingBox(){ + let x = ImageFile.getCurrentFile().width; + let y = ImageFile.getCurrentFile().height; + let x2 = 0; + let y2 = 0; + if (selectionPoints && selectionPoints.length){ + selectionPoints.forEach(point=>{ + if (point.xx2) x2 = point.x; + if (point.y>y2) y2 = point.y; + }); + // warning; this updates the selection, which triggers a change in the selectbox + Selection.set({left: x, top: y, width: x2-x, height: y2-y, points: selectionPoints}); + } + } + + EventBus.on(EVENT.sizerStartChange,()=>{ + if (me.isActive() && editor.isActive()){ + let selection = Selection.get(); + if (selection.canvas){ + selectionTransform = { + left: selection.left, + top: selection.top, + width: selection.width, + height: selection.height, + canvas: duplicateCanvas(selection.canvas,true) + } + if (selection.outline){ + selectionTransform.outline = selection.outline.map(a=>a.slice()); + } + } + } + }); + + EventBus.on(EVENT.sizerChanged,(change)=>{ + if (me.isActive() && editor.isActive()){ + let fromSize = change.from; + let currentSize = change.to; + if (currentSize){ + let selection = Selection.get(); + let handled = false; + if (fromSize && selection){ + let translate = {x:0,y:0,scale:1}; + translate.x = currentSize.left - fromSize.left; + translate.y = currentSize.top - fromSize.top; + translate.scaleX = currentSize.width/fromSize.width; + translate.scaleY = currentSize.height/fromSize.height; + if (isNaN(translate.scaleX)) translate.scaleX = 1; + if (isNaN(translate.scaleY)) translate.scaleY = 1; + + if (selection.points && selection.points.length){ + selectionPoints = selection.points; + if (translate.x || translate.y){ + selectionPoints.forEach(point=>{ + point.x += translate.x; + point.y += translate.y; + }); + } + if (translate.scaleX!==1 || translate.scaleY!==1){ + let startX = currentSize.left; + let startY = currentSize.top; + + selectionPoints.forEach(point=>{ + point.x = startX + Math.round((point.x-startX)*translate.scaleX); + point.y = startY + Math.round((point.y-startY)*translate.scaleY); + }); + } + + currentSize.points = selectionPoints; + Selection.set(currentSize); + handled = true; + }else if (selection.canvas){ + if (translate.scaleX!==1 || translate.scaleY!==1 || translate.x || translate.y){ + // scale the original selection canvas and outline + let deltaScaleX = currentSize.width/selectionTransform.width; + let deltaScaleY = currentSize.height/selectionTransform.height; + + if (selection.outline && selectionTransform.outline){ + for (let i = 0;i{ + if (!editor.isVisible()) return; + if (!me.isActive()) return; + renderSelection(); + }); + + EventBus.on(EVENT.toolChanged,(tool)=>{ + if (me.isActive()){ + selectionTransform = undefined; + box.classList.remove("capture"); + resizer.remove(); + } + }); + + + return me; +}); + +export default SelectBox; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/syntaxEdit.js b/app/web-tools/dpaint/_script/ui/components/syntaxEdit.js new file mode 100644 index 00000000..71499ea8 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/syntaxEdit.js @@ -0,0 +1,60 @@ +let SyntaxEdit = function(parent,onChange){ + let me = {}; + + let regexes={ + string1: {reg: /"(.*?)"/g, style:"string", wrap:'"'}, + string2: {reg: /'(.*?)'/g, style:"string", wrap:"'"}, + numbers: {reg: /(\b\d+\b)/g, style:"number"}, + hex: {reg: /(\B#\w+)/g, style:"number"}, + reserved: {reg: /\b(new|var|let|if|do|function|while|switch|for|foreach|in|continue|break)(?=[^\w])/g, style:"reserved"}, + globals: {reg: /\b(document|window|Array|String|Object|Number|Math|\$)(?=[^\w])/g, style:"globals"}, + js: {reg: /\b(getElementsBy(TagName|ClassName|Name)|getElementById|typeof|instanceof)(?=[^\w])/g, style:"js"}, + //methods: {reg:/((?<=\.)\w+)/g, style:"method"}, + // Note: Safari doesn't support lookbehind in regexes ... + htmlTags: {reg: /(<[^\&]*>)/g, style:"html"}, + blockComments: {reg: /(\/\*.*\*\/)/g, style:"comment"}, + inlineComments: {reg: /(\/\/.*)/g, style:"comment"}, + source: {reg: /\b(source|\$)(?=[^\w])/g, style:"source"}, + target: {reg: /\b(target|\$)(?=[^\w])/g, style:"target"}, + } + + let editor = document.createElement("code"); + let textarea = document.createElement("textarea"); + editor.contentEditable = "true"; + editor.spellcheck = false; + editor.onkeydown = (e)=>{ + e.stopPropagation(); + } + editor.onblur = function(){ + onChange(editor.innerText); + highlight(); + } + parent.appendChild(editor); + + me.setValue=(value)=>{ + editor.innerText = value; + highlight(); + } + + me.onChange =()=>{ + onChange(editor.innerText); + } + + function highlight(){ + textarea.innerText=editor.innerText; + let text = textarea.innerHTML; + + for (let key in regexes){ + let reg = regexes[key]; + let wrap = reg.wrap || ""; + text = text.replace(reg.reg,''+wrap+'$1'+wrap+''); + } + + editor.innerHTML = text; + } + + + return me; +} + +export default SyntaxEdit; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/toolOptions.js b/app/web-tools/dpaint/_script/ui/components/toolOptions.js new file mode 100644 index 00000000..f809487a --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/toolOptions.js @@ -0,0 +1,476 @@ +import {$checkbox, $div, $elm, $input} from "../../util/dom.js"; +import {COMMAND, EVENT} from "../../enum.js"; +import EventBus from "../../util/eventbus.js"; +import ImageFile from "../../image.js"; +import BrushPanel from "../toolPanels/brushPanel.js"; +import Brush from "../brush.js"; +import DitherPanel from "../toolPanels/ditherPanel.js"; +import Text from "../../paintTools/text.js"; + +let ToolOptions = function(){ + let me = {} + let smooth = false; + let pixelPerfect = false; + let fill = false; + let lineSize = 1; + let tolerance = 0; + let strength = 50; + let spread = 20; + let mask = false; + let selectionOutline = true; + let selectionMask = false; + let pressure = false; + + let smoothCheckbox; + let pixelPerfectCheckbox; + let maskCheckbox; + let selectSection; + let selectionOutlineCheckbox; + let selectionMaskCheckbox; + let ditherSection; + let ditherCheckbox; + let invertCheckbox; + let pressureCheckbox; + let pressureOpacityCheckbox; + let fillCheckbox; + let lineSizeRange; + let toleranceRange; + let strengthRange; + let spreadRange; + let brushOptionGroup; + let brushSettings={}; + let smudgeAction = "Smudge"; + let smudgeSelect; + let fontOptionGroup; + let fontSettings={}; + + me.isSmooth = ()=>{ + return smooth; + } + + me.isPixelPerfect = ()=>{ + return pixelPerfect; + } + + me.isFill = ()=>{ + return fill; + } + + me.showMask = ()=>{ + return mask; + } + + me.showSelectionOutline = ()=>{ + return selectionOutline; + } + + me.showSelectionMask = ()=>{ + return selectionMask; + } + + me.usePressure = ()=>{ + return pressure; + } + + me.setFill = (state)=>{ + fill = !!state; + if (fillCheckbox){ + let cb = fillCheckbox.querySelector("input"); + if (cb) cb.checked = fill; + } + EventBus.trigger(EVENT.toolOptionsChanged); + } + + me.getLineSize = ()=>{ + return lineSize; + } + + me.getTolerance = ()=>{ + return tolerance; + } + + me.getStrength = ()=>{ + return strength/100; + } + + me.getSpread = ()=>{ + return spread; + } + + me.getSmudgeAction = ()=>{ + return smudgeAction.toLowerCase(); + } + + me.getFont = ()=>{ + return fontSettings.name; + } + + me.getFontSize = ()=>{ + return fontSettings.size; + } + + me.getOptions = (command)=>{ + let options = $div("options"); + switch (command){ + case COMMAND.DRAW: + options.appendChild(label("Brush:")); + options.appendChild(brushSetting(true)); + options.appendChild(ditherSetting()); + options.appendChild(pressureSetting()); + break; + case COMMAND.ERASE: + options.appendChild(label("Erase:")); + options.appendChild(brushSetting(true)); + options.appendChild(ditherSetting()); + break; + case COMMAND.SMUDGE: + options.appendChild(smudgeLabel()); + options.appendChild(brushSetting()); + options.appendChild(strengthSetting()); + options.appendChild(ditherSetting()); + break; + case COMMAND.SPRAY: + options.appendChild(label("Spray:")); + options.appendChild(spreadSetting()); + options.appendChild(strengthSetting()); + options.appendChild(pressureOpacitySetting()); + break; + case COMMAND.LINE: + options.appendChild(label("Line:")); + options.appendChild(smoothSetting()); + options.appendChild(lineSetting()); + break; + case COMMAND.SQUARE: + options.appendChild(label("Rectangle:")); + options.appendChild(fillSetting()); + options.appendChild(lineSetting()); + break; + case COMMAND.CIRCLE: + options.appendChild(label("Circle:")); + options.appendChild(fillSetting()); + options.appendChild(smoothSetting()); + options.appendChild(lineSetting()); + break; + case COMMAND.GRADIENT: + options.appendChild(label("Gradient:")); + options.appendChild(ditherSetting()); + break; + case COMMAND.SELECT: + case COMMAND.POLYGONSELECT: + case COMMAND.FLOODSELECT: + options.appendChild(selectSetting()); + if (command === COMMAND.FLOODSELECT) options.appendChild(toleranceSetting()); + break; + case COMMAND.FLOOD: + options.appendChild(label("Fill:")); + options.appendChild(toleranceSetting()); + break; + case COMMAND.TRANSFORMLAYER: + options.appendChild(label("Transform rotation:")); + options.appendChild(smoothSetting()); + options.appendChild(pixelPerfectSetting()); + break; + case COMMAND.TEXT: + options.appendChild(label("Font:")); + options.appendChild(fontSetting()); + break; + } + + let activeLayer = ImageFile.getActiveLayer(); + if (activeLayer.isMaskActive()){ + options.appendChild(maskSetting()); + } + return options; + } + + function smoothSetting(){ + if (!smoothCheckbox) smoothCheckbox=$checkbox("Smooth","","",(checked)=>{ + smooth = checked; + }); + return smoothCheckbox; + } + + function pixelPerfectSetting(){ + if (!pixelPerfectCheckbox) pixelPerfectCheckbox=$checkbox("Pixel Optimized","","",(checked)=>{ + pixelPerfect = checked; + }); + return pixelPerfectCheckbox; + } + + function fillSetting(){ + if (!fillCheckbox) fillCheckbox=$checkbox("Fill","","",(checked)=>{ + fill = checked; + EventBus.trigger(EVENT.toolOptionsChanged); + }); + return fillCheckbox; + } + + function lineSetting(){ + if (!lineSizeRange){ + lineSizeRange = $div("range"); + $elm("label","Size:",lineSizeRange); + let range = document.createElement("input"); + range.type="range"; + range.min=1; + range.max=10; + range.value = 1; + lineSizeRange.appendChild(range); + let value = $elm("span","1px",lineSizeRange); + range.oninput = function(){ + value.innerText = range.value + "px"; + lineSize = range.value; + } + + } + return lineSizeRange; + } + + function brushSetting(withOpacity){ + let brushOpacityRange; + if (!brushOptionGroup){ + let settings = Brush.getSettings(); + brushOptionGroup = $div("optionsgroup"); + let brushSizeRange = $div("range","",brushOptionGroup); + $elm("label","Size:",brushSizeRange); + brushSettings.sizeRange = $input("range",settings.width,brushSizeRange) + brushSettings.sizeRange.min=1; + brushSettings.sizeRange.max=100; + brushSettings.sizeInput = $elm("span", settings.width+"px",brushSizeRange); + brushSettings.sizeRange.oninput = function(){ + BrushPanel.set({size:brushSettings.sizeRange.value}); + } + + + brushOpacityRange = $div("range opacity","",brushOptionGroup); + $elm("label","Opacity:",brushOpacityRange,"inline"); + brushSettings.opacityRange = $input("range",settings.opacity,brushOpacityRange) + brushSettings.opacityRange.min=1; + brushSettings.opacityRange.max=100; + brushSettings.opacityInput = $elm("span",settings.opacity + "%",brushOpacityRange); + brushSettings.opacityRange.oninput = function(){ + BrushPanel.set({opacity:brushSettings.opacityRange.value}); + } + + EventBus.on(EVENT.brushOptionsChanged,()=>{ + let settings = Brush.getSettings(); + brushSettings.sizeRange.value = settings.width; + brushSettings.sizeInput.innerText = settings.width + "px" ; + brushSettings.opacityRange.value = settings.opacity; + brushSettings.opacityInput.innerText = settings.opacity + "%" ; + if (ditherCheckbox) ditherCheckbox.setState(DitherPanel.getDitherState()); + if (invertCheckbox) invertCheckbox.setState(DitherPanel.getDitherInvertState()); + }) + + } + + if (!brushOpacityRange) brushOpacityRange = brushOptionGroup.querySelector(".opacity"); + if (withOpacity){ + brushOpacityRange.style.display = "block"; + }else { + brushOpacityRange.style.display = "none"; + } + + return brushOptionGroup; + } + + function maskSetting(){ + if (!maskCheckbox) maskCheckbox=$checkbox("Show Mask","","mask",(checked)=>{ + mask = checked; + EventBus.trigger(EVENT.layerContentChanged); + }); + return maskCheckbox; + } + + function selectSetting(){ + if (!selectSection){ + selectSection = $div("optionsgroup"); + selectionMaskCheckbox=$checkbox("Show Mask",selectSection,"mask",(checked)=>{ + selectionMask = checked; + EventBus.trigger(EVENT.selectionChanged); + }); + selectionOutlineCheckbox=$checkbox("Show Outline",selectSection,"",(checked)=>{ + selectionOutline = checked; + EventBus.trigger(EVENT.selectionChanged); + }); + } + selectionOutlineCheckbox.setState(selectionOutline); + selectionMaskCheckbox.setState(selectionMask); + return selectSection; + } + + function ditherSetting(){ + if (!ditherSection){ + ditherSection = $div("inline flex"); + ditherCheckbox=$checkbox("Dither",ditherSection,"inline info",(checked)=>{ + DitherPanel.setDitherState(checked); + }); + ditherCheckbox.info="Toggle Brush Dither Pattern"; + + invertCheckbox=$checkbox("Invert",ditherSection,"info",(checked)=>{ + DitherPanel.setDitherInvertState(checked); + }); + invertCheckbox.info="I Invert Brush Dither Pattern"; + } + ditherCheckbox.setState(DitherPanel.getDitherState()); + invertCheckbox.setState(DitherPanel.getDitherInvertState()); + return ditherSection; + } + + function pressureSetting(){ + if (!pressureCheckbox) pressureCheckbox=$checkbox("Pressure","","pressure info",(checked)=>{ + pressure = checked; + }); + pressureCheckbox.info="Toggle brush pressure sensitivity"; + pressureCheckbox.setState(pressure); + return pressureCheckbox; + } + + function pressureOpacitySetting(){ + if (!pressureOpacityCheckbox) pressureOpacityCheckbox=$checkbox("Opacity","","pressure info inline",(checked)=>{ + pressure = checked; + }); + pressureOpacityCheckbox.info="Use random opacity when spraying"; + pressureOpacityCheckbox.setState(pressure); + return pressureOpacityCheckbox; + } + + function strengthSetting(){ + if (!strengthRange){ + strengthRange = $div("range"); + $elm("label","Strength:",strengthRange,"inline"); + let range = document.createElement("input"); + range.type="range"; + range.min=0; + range.max=100; + range.value = strength; + strengthRange.appendChild(range); + let value = $elm("span",strength,strengthRange); + range.oninput = function(){ + value.innerText = range.value; + strength = range.value; + } + + } + return strengthRange; + } + + function spreadSetting(){ + if (!spreadRange){ + spreadRange = $div("range"); + $elm("label","Spread:",spreadRange,"inline"); + let range = document.createElement("input"); + range.type="range"; + range.min=1; + range.max=100; + range.value = spread; + spreadRange.appendChild(range); + let value = $elm("span",spread,spreadRange); + range.oninput = function(){ + value.innerText = range.value; + spread = range.value; + } + + } + return spreadRange; + } + + + + function toleranceSetting(){ + if (!toleranceRange){ + toleranceRange = $div("range"); + $elm("label","Tolerance:",toleranceRange); + let range = document.createElement("input"); + range.type="range"; + range.min=0; + range.max=100; + range.value = 0; + toleranceRange.appendChild(range); + let value = $elm("span","0",toleranceRange); + range.oninput = function(){ + value.innerText = range.value; + tolerance = range.value; + } + + } + return toleranceRange; + } + + function smudgeLabel(){ + let options = ["Smudge","Blur","Sharpen"]; + if (!smudgeSelect){ + smudgeSelect = $elm("select","",null,"inline"); + options.forEach((option)=>{ + let opt = document.createElement("option"); + opt.value = option; + opt.innerText = option; + smudgeSelect.appendChild(opt); + }); + smudgeSelect.onchange = function(){ + smudgeAction = smudgeSelect.value; + } + } + return smudgeSelect; + } + + function fontSetting(){ + let options = Text.getFonts(); + if (!fontOptionGroup){ + fontOptionGroup = $div("optionsgroup"); + + + let fontSelect = $elm("select","",fontOptionGroup,"inline"); + options.forEach((option)=>{ + let opt = document.createElement("option"); + opt.value = option; + opt.innerText = option; + fontSelect.appendChild(opt); + }); + fontSettings.name = options[0]; + fontSelect.onchange = function(){ + fontSettings.name = fontSelect.value; + EventBus.trigger(EVENT.fontStyleChanged,fontSettings); + } + + let fontSizeRange = $div("range","",fontOptionGroup); + $elm("label","Size:",fontSizeRange,"inline"); + let range = document.createElement("input"); + range.type="range"; + range.min=5; + range.max=100; + range.value = 32; + fontSizeRange.appendChild(range); + fontSettings.size = range.value; + let value = $elm("span",fontSettings.size+"px",fontSizeRange); + range.oninput = function(){ + value.innerText = range.value + "px"; + fontSettings.size = range.value; + EventBus.trigger(EVENT.fontStyleChanged,fontSettings); + } + } + return fontOptionGroup; + } + + function label(text){ + let label = document.createElement("span"); + label.className = "tool"; + label.innerText = text; + return label; + } + + + + function lineWidthSetting(){ + + } + + EventBus.on(COMMAND.TOGGLEMASK,()=>{ + mask = !mask; + if (window.override) mask=false; + EventBus.trigger(EVENT.layerContentChanged); + }); + + return me; +}(); + +export default ToolOptions; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/components/uae.js b/app/web-tools/dpaint/_script/ui/components/uae.js new file mode 100644 index 00000000..0917a044 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/components/uae.js @@ -0,0 +1,101 @@ +import $ from "../../util/dom.js"; +import Adf from "../../fileformats/adf.js"; +import Modal from "../modal.js"; +import Generate from "../../fileformats/generate.js"; +import EventBus from "../../util/eventbus.js"; +import {COMMAND} from "../../enum.js"; + +let UAE = function(){ + let me = {}; + let container; + let currentTranslate = [0,0]; + let currentSize = [720,588]; + let currentPosition = [(window.innerWidth-currentSize[0])>>1,(window.innerHeight-currentSize[1])>>1]; + + me.preview = function(){ + window.getUaeContent = function(){ + return new Promise((next)=>{ + fetch("uae/data/boot.adf").then(r=>r.arrayBuffer()).then(buffer=>{ + Adf.loadDisk(buffer,result=>{ + if (result){ + let diskInfo = Adf.getInfo(); + let root = Adf.readRootFolder(); + if (root && root.files){ + let pictureFile = root.files.find(f=>f.name === "picture.iff"); + if (pictureFile){ + let image = Generate.iff(32,"Sorry, Deluxe Paint II supports a maximum of 32 colors."); + if (image){ + image.arrayBuffer().then(buffer=>{ + Adf.deleteFileAtSector(pictureFile.sector); + let sector = Adf.writeFile("picture.iff",buffer,pictureFile.parent); + if (sector){ + next(Adf.getDisk().buffer); + //EventBus.trigger(COMMAND.SAVEDISK,[Adf.getDisk().buffer,diskInfo.label || "Disk.adf"]) + } + }); + + }else{ + me.hide(); + } + } + } + }else{ + Modal.alert("Sorry, The Amiga Dpaint boot disk is not found","Error reading disk file"); + } + }); + }) + }) + } + if (container) container.remove(); + container = $(".uae", + $(".caption.handle",{ + onDragStart:()=>{ + container.classList.add("dragging"); + }, + onDrag:(x,y)=>{ + x += currentPosition[0]; + y += currentPosition[1]; + currentTranslate = [x,y]; + container.style.transform = "translate("+x+"px,"+y+"px)"; + }, + onDragEnd:()=>{ + container.classList.remove("dragging"); + currentPosition = [currentTranslate[0],currentTranslate[1]]; + }},"Amiga Preview",$(".close",{ + onclick:()=>{ + me.hide(); + },info:"Close Amiga Preview"},"x") + ),$(".resizer.handle",{ + onDragStart:()=>{ + container.classList.add("dragging"); + }, + onDrag:(x,y)=>{ + x += currentSize[0]; + y += currentSize[1]; + container.style.width = x+"px"; + container.style.height = y+"px"; + }, + onDragEnd:()=>{ + container.classList.remove("dragging"); + currentSize = [container.clientWidth,container.clientHeight]; + } + })); + container.style.transform = "translate("+currentPosition[0]+"px,"+currentPosition[1]+"px)"; + container.style.width = currentSize[0]+"px"; + container.style.height = currentSize[1]+"px"; + let iframe = $("iframe",{ + src:"uae/index.html" + }) + container.appendChild(iframe); + document.body.appendChild(container); + } + + me.hide = function(){ + if (container) container.remove(); + } + + + return me; +}(); + +export default UAE; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/cursor.js b/app/web-tools/dpaint/_script/ui/cursor.js new file mode 100644 index 00000000..94158445 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/cursor.js @@ -0,0 +1,98 @@ +import {$div} from "../util/dom.js"; +import Eventbus from "../util/eventbus.js"; +import Input from "./input.js"; +import {COMMAND, EVENT} from "../enum.js"; +import Editor from "./editor.js"; + +var Cursor = function(){ + var me = {} + var cursor; + var cursorMark; + var toolTip; + let position = {x:0,y:0} + let defaultCursor = "default"; + let currentCursor = undefined; + let overrideCursor = undefined; + + me.init = function(){ + cursor = $div("cursor"); + cursorMark = $div("mark","",cursor); + toolTip = $div("tooltip","",cursor); + document.body.appendChild(cursor); + + document.body.addEventListener("pointermove", function (e) { + position = {x:e.clientX,y:e.clientY}; + cursor.style.left = e.clientX + "px"; + cursor.style.top = e.clientY + "px"; + }, false); + + Eventbus.on(EVENT.drawColorChanged,(color)=>{ + cursorMark.style.borderColor = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ")"; + }) + } + + me.set = function(name){ + currentCursor = name; + setCursor(); + } + + me.reset = function(){ + currentCursor = undefined; + setCursor(); + } + + me.override = function(name){ + overrideCursor = name; + setCursor(); + } + + me.hasOverride = function(name){ + return overrideCursor === name; + } + + me.resetOverride = function(name){ + overrideCursor = undefined; + setCursor(); + } + + me.attach = function(name){ + cursor.style.display = "block"; + cursorMark.style.display = "block"; + } + + + me.getPosition = ()=>{ + return position; + } + + function setCursor(){ + document.body.classList.forEach((c)=>{ + if (c.startsWith("cursor-")) document.body.classList.remove(c); + }); + let cursorName = overrideCursor || currentCursor || defaultCursor; + document.body.classList.add("cursor-" + cursorName); + } + + Eventbus.on(EVENT.modifierKeyChanged,()=>{ + /*if ((Input.isShiftDown() || Input.isAltDown()) && !Input.isMetaDown() && Editor.canPickColor()){ + me.override("colorpicker"); + }else{ + me.resetOverride(); + }*/ + + if ((Input.isShiftDown() || Input.isAltDown()) && Editor.canPickColor(Input.isPointerDown())){ + me.override("colorpicker"); + }else{ + me.resetOverride(); + } + + + if (Input.isSpaceDown()){ + me.override("pan"); + } + }) + + return me; +}(); + +export default Cursor; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/editor.js b/app/web-tools/dpaint/_script/ui/editor.js new file mode 100644 index 00000000..38090485 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/editor.js @@ -0,0 +1,530 @@ +import Canvas from "./canvas.js"; +import {$div} from "../util/dom.js"; +import EventBus from "../util/eventbus.js"; +import {COMMAND, EVENT} from "../enum.js"; +import EditPanel from "./editpanel.js"; +import ImageProcessing from "../util/imageProcessing.js"; +import ImageFile from "../image.js"; +import Selection from "./selection.js"; +import Palette from "./palette.js"; +import Color from "../util/color.js"; +import Modal, {DIALOG} from "./modal.js"; +import {releaseCanvas, duplicateCanvas} from "../util/canvasUtils.js"; +import Input from "./input.js"; +import HistoryService from "../services/historyservice.js"; +import Cursor from "./cursor.js"; +import ToolOptions from "./components/toolOptions.js"; + +var Editor = function(){ + var me = {}; + let panels=[]; + let divider; + var container; + var zoomFactor = 1.1; + var activePanel; + let resizer; + var currentTool = COMMAND.DRAW; + let previousTool; + var touchData = {}; + let rotSprite; + var state= { + splitPanel: false + } + + me.init=function(parent){ + container = $div("editor splitpanel","",parent); + panels.push(EditPanel(container,"left")); + divider = $div("splitter","",container,(e)=>{ + touchData.totalWidth = container.getBoundingClientRect().width; + touchData.startWith = panels[0].getWidth(); + touchData.startWith2 = panels[1].getWidth(); + touchData.startX = parseInt(divider.style.marginLeft) || -3; + }) + panels.push(EditPanel(container,"right")); + activePanel = panels[0]; + resizer = activePanel.getResizer(); + + divider.onDrag = function(x,y){ + let w = touchData.startWith+x; + let w2 = touchData.startWith2-x; + let min = 120; + + if (w{ + ImageProcessing.rotate(layer.getCanvas()); + }) + let w = ImageFile.getCurrentFile().width; + ImageFile.getCurrentFile().width = ImageFile.getCurrentFile().height; + ImageFile.getCurrentFile().height = w; + EventBus.trigger(EVENT.imageSizeChanged); + }); + EventBus.on(COMMAND.CLEAR,function(){ + var s = Selection.get(); + let layer = ImageFile.getActiveLayer(); + if (!layer) return; + HistoryService.start(EVENT.layerContentHistory); + if (s){ + if (s.canvas || s.points){ + let canvas = Selection.getCanvas(); + let layerCtx = layer.getContext(); + layerCtx.globalCompositeOperation = "destination-out"; + layerCtx.drawImage(canvas,0,0); + layerCtx.globalCompositeOperation = "source-over"; + releaseCanvas(canvas); + }else{ + // rectangular selection + layer.getContext().clearRect(s.left,s.top,s.width,s.height); + } + }else{ + layer.clear(); + } + HistoryService.end(); + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.imageContentChanged); + }); + EventBus.on(COMMAND.CROP,function(){ + var s = Selection.get(); + if (s){ + HistoryService.start(EVENT.imageHistory); + ImageFile.getCurrentFile().frames.forEach(frame=>{ + frame.layers.forEach(layer=>{ + let c = layer.getCanvas(); + let ctx = c.getContext("2d"); + let canvas = document.createElement("canvas"); + canvas.width = s.width; + canvas.height = s.height; + canvas.getContext("2d").clearRect(0,0,s.width,s.height); + canvas.getContext("2d").drawImage(c,s.left,s.top,s.width,s.height,0,0,s.width,s.height); + + c.width = s.width; + c.height = s.height; + ctx.clearRect(0,0,s.width,s.height); + ctx.drawImage(canvas,0,0); + releaseCanvas(canvas); + }) + }); + ImageFile.getCurrentFile().width = s.width; + ImageFile.getCurrentFile().height = s.height; + Selection.move(0,0,s.width,s.height); + HistoryService.end(); + EventBus.trigger(EVENT.imageSizeChanged); + } + }); + EventBus.on(COMMAND.TRIM,()=>{ + let ctx = ImageFile.getActiveContext(); + let canvas = ctx.canvas; + let box = ImageFile.getLayerBoundingRect(); + if (!box.w || !box.h) return; + let cut = ctx.getImageData(box.x, box.y, box.w, box.h); + if (box.w === canvas.width && box.h === canvas.height && box.x === 0) return; + + HistoryService.start(EVENT.imageHistory); + canvas.width = box.w; + canvas.height = box.h; + ctx.putImageData(cut, 0, 0); + ImageFile.getCurrentFile().width = canvas.width; + ImageFile.getCurrentFile().height = canvas.height; + HistoryService.end(); + EventBus.trigger(EVENT.imageSizeChanged); + }) + + EventBus.on(COMMAND.TRANSFORMLAYER,()=>{ + + let box = ImageFile.getLayerBoundingRect(); + if (!box.w || !box.h) return; + + previousTool = currentTool; + currentTool = COMMAND.TRANSFORMLAYER; + + resizer.init({ + x: box.x, + y: box.y, + width: box.w, + height: box.h, + rotation: 0, + aspect: box.w/box.h, + canRotate: true + }); + + touchData.transformBox = box; + touchData.transformCanvas = document.createElement("canvas"); + touchData.transformCanvas.width = box.w; + touchData.transformCanvas.height = box.h; + let ctx = touchData.transformCanvas.getContext("2d"); + ctx.imageSmoothingEnabled = false; + ctx.drawImage(ImageFile.getActiveContext().canvas,box.x,box.y,box.w,box.h,0,0,box.w,box.h); + + HistoryService.start(EVENT.layerContentHistory); + touchData.transformLayer = ImageFile.getActiveLayer(); + resizer.setOnUpdate(updateTransform); + }); + + EventBus.on(COMMAND.COLORMASK,()=>{ + let ctx = ImageFile.getActiveContext(); + let color = Palette.getDrawColor(); + let w = ImageFile.getCurrentFile().width; + let h = ImageFile.getCurrentFile().height; + let data = ctx.getImageData(0,0,w,h).data; + let layerIndex = ImageFile.addLayer(); + let layer = ImageFile.getLayer(layerIndex); + layer.type = "pixelSelection"; + let ctx2 = ImageFile.getLayer(layerIndex).getContext(); + ctx2.fillStyle = "red"; + let count = 0; + for (let y = 0;y{ + HistoryService.start(EVENT.layerHistory); + let layer = ImageFile.getActiveLayer(); + layer.addMask(!!hide); + HistoryService.end(); + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.layersChanged); + }); + EventBus.on(COMMAND.LAYERMASKHIDE,()=>{ + EventBus.trigger(COMMAND.LAYERMASK,true); + + setTimeout(()=>{ + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.layersChanged); + },1000) + }); + EventBus.on(COMMAND.DELETELAYERMASK,()=>{ + let layer = ImageFile.getActiveLayer(); + layer.removeMask(); + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.layersChanged); + }); + EventBus.on(COMMAND.DISABLELAYERMASK,()=>{ + let layer = ImageFile.getActiveLayer(); + layer.enableMask(false); + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.layersChanged); + }); + EventBus.on(COMMAND.ENABLELAYERMASK,()=>{ + let layer = ImageFile.getActiveLayer(); + layer.enableMask(true); + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.layersChanged); + }); + EventBus.on(COMMAND.APPLYLAYERMASK,()=>{ + let layer = ImageFile.getActiveLayer(); + layer.removeMask(true); + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(EVENT.layersChanged); + }); + EventBus.on(COMMAND.EDITPALETTE, ()=>{ + Modal.show(DIALOG.PALETTE); + }) + EventBus.on(COMMAND.EFFECTS, ()=>{ + Modal.show(DIALOG.EFFECTS); + }) + EventBus.on(EVENT.toolChanged,(tool)=>{ + me.commit(); + Cursor.reset(); + if (tool === COMMAND.SELECT || tool === COMMAND.FLOODSELECT || tool === COMMAND.POLYGONSELECT){ + currentTool = tool; + Cursor.set("select"); + EventBus.trigger(COMMAND.INITSELECTION,tool); + } + }); + + } + + me.setActivePanel = function(panel){ + activePanel = panels[panel]; + resizer = activePanel.getResizer(); + } + + me.getActivePanel = function(){ + return activePanel; + } + + me.getCurrentTool = function(){ + return currentTool; + } + + me.splitPanel = function(){ + state.splitPanel = !state.splitPanel; + if (divider.style.display === "block"){ + panels[0].setWidth(100,true); + panels[1].hide(); + divider.style.display = "none"; + EventBus.trigger(EVENT.UIresize); + + }else{ + panels[0].setWidth("calc(50% - 4px)"); + panels[1].setWidth("calc(50% - 4px)"); + panels[1].show(); + divider.style.display = "block"; + EventBus.trigger(EVENT.imageSizeChanged); + } + } + + me.isStateActive = function(name){ + return !!state[name]; + } + + me.commit = function(){ + if (currentTool === COMMAND.TRANSFORMLAYER){ + console.log("commit layer"); + resizer.commit(); + updateTransform(true).then(()=>{ + clearTransform(); + EventBus.trigger(COMMAND.CLEARSELECTION); + currentTool = undefined; + HistoryService.end(); + if (previousTool) EventBus.trigger(previousTool); + }); + } + if (currentTool === COMMAND.POLYGONSELECT){ + EventBus.trigger(COMMAND.ENDPOLYGONSELECT); + } + } + + me.reset = function(){ + if (currentTool === COMMAND.TRANSFORMLAYER){ + resizer.commit(); + resetTransform(); + clearTransform(); + currentTool = undefined; + HistoryService.neverMind(); + } + EventBus.trigger(COMMAND.CLEARSELECTION); + } + + me.arrowKey = function(direction){ + let x = 0; + let y = 0; + switch (direction){ + case "left": x=-1; break; + case "right": x=1; break; + case "up": y=-1; break; + case "down": y=1; break; + } + if (Input.isMetaAndShiftDown()){ + switch (direction){ + case "left": + EventBus.trigger(COMMAND.BRUSHFLIPHORIZONTAL) + break; + case "right": + EventBus.trigger(COMMAND.BRUSHROTATERIGHT); + break; + case "up": + EventBus.trigger(COMMAND.BRUSHFLIPVERTICAL); + break; + //case "down": y=1; break; + } + return; + } + if (Input.isMetaDown()){ + x*=10; + y*=10; + } + if (me.canDrawColor()){ + if (Input.isMetaDown() && ImageFile.hasMultipleFrames()){ + if (x>0) ImageFile.nextFrame(); + if (x<0) ImageFile.nextFrame(-1); + return; + } + if (x>0) Palette.next(); + if (x<0) Palette.prev(); + return; + } + resizer.move(x,y); + } + + me.setZoom = function(factor,center){ + activePanel.setZoom(factor,center); + } + + me.canPickColor = (isDown)=>{ + console.log("can pick color"); + if (isDown && !Cursor.hasOverride("colorpicker")){ + return false; + } + return ( + currentTool === COMMAND.DRAW || + currentTool === COMMAND.CIRCLE || + currentTool === COMMAND.SQUARE || + currentTool === COMMAND.LINE || + currentTool === COMMAND.TEXT || + currentTool === COMMAND.SPRAY || + currentTool === COMMAND.FLOOD); + } + + me.canDrawColor = ()=>{ + return (currentTool === COMMAND.DRAW || currentTool === COMMAND.SQUARE || currentTool === COMMAND.GRADIENT || currentTool === COMMAND.LINE || currentTool === COMMAND.CIRCLE || currentTool === COMMAND.SPRAY || currentTool === COMMAND.ERASE); + } + + me.usesBrush = (tool)=>{ + let t = tool || currentTool; + return (t === COMMAND.DRAW || t === COMMAND.SPRAY || t === COMMAND.ERASE); + } + + async function updateTransform(final,onDone){ + if (!touchData.transformLayer) return; + console.log("update transform layer"); + let d = resizer.get(); + touchData.transformLayer.clear(); + if (d.width === 0 || d.height === 0) return; + let ctx = touchData.transformLayer.getContext(); + + let smooth = ToolOptions.isSmooth(); + let pixelOptimized = ToolOptions.isPixelPerfect(); + if (pixelOptimized) smooth = false; + + ctx.imageSmoothingEnabled = smooth; + + + let angled = d.rotation && (d.rotation % 90 !== 0); + let useHQ = final && pixelOptimized && angled && touchData.transformCanvas.width<=512 && touchData.transformCanvas.height<=512; + + if (useHQ){ + if (!rotSprite){ + rotSprite = await import("../paintTools/rotSprite.js"); + rotSprite = rotSprite.default; + } + let rotated = await rotSprite(touchData.transformCanvas,d.rotation); + let rotateScaleX = touchData.transformCanvas.width / rotated.width; + let rotateScaleY = touchData.transformCanvas.height / rotated.height; + + let w = d.width / rotateScaleX; + let h = d.height / rotateScaleY; + + let x = d.left + (d.width - w) / 2; + let y = d.top + (d.height - h) / 2; + + if (Palette.isLocked()){ + // rotated is a WEBGL canvas + rotated = duplicateCanvas(rotated,true); + Palette.applyToCanvas(rotated); + } + + ctx.drawImage(rotated,x,y,w,h); + }else{ + if (d.rotation){ + console.log("rotate " + d.rotation); + + let dw = (d.left + d.width/2); + let dh = (d.top + d.height/2); + ctx.translate(dw,dh); + ctx.rotate((d.rotation * Math.PI) / 180); + ctx.translate(-dw,-dh); + } + + ctx.drawImage(touchData.transformCanvas,d.left,d.top,d.width,d.height); + ctx.setTransform(1, 0, 0, 1, 0, 0); + + if (angled && final && Palette.isLocked()){ + Palette.applyToCanvas(ctx.canvas,true); + } + } + EventBus.trigger(EVENT.layerContentChanged); + } + + function resetTransform(){ + touchData.transformLayer.clear(); + let ctx = touchData.transformLayer.getContext(); + let box = touchData.transformBox; + ctx.imageSmoothingEnabled = false; + ctx.drawImage(touchData.transformCanvas,box.x,box.y,box.w,box.h); + ctx.setTransform(1, 0, 0, 1, 0, 0); + EventBus.trigger(EVENT.layerContentChanged); + } + + function clearTransform(){ + touchData.transformBox = undefined; + if (touchData.transformCanvas) releaseCanvas(touchData.transformCanvas); + touchData.transformLayer = undefined; + } + + + return me; +}(); + +export default Editor; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/editpanel.js b/app/web-tools/dpaint/_script/ui/editpanel.js new file mode 100644 index 00000000..52d98558 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/editpanel.js @@ -0,0 +1,415 @@ +import {$div} from "../util/dom.js"; +import EventBus from "../util/eventbus.js"; +import {COMMAND, EVENT} from "../enum.js"; +import Canvas from "./canvas.js"; +import Editor from "./editor.js"; +import ImageFile from "../image.js"; +import ToolOptions from "./components/toolOptions.js"; +import Input from "./input.js"; +import Brush from "./brush.js"; +import BrushPanel from "./toolPanels/brushPanel.js"; + +var EditPanel = function(parent,type){ + var me = {}; + var zoomLevels = [0.1,0.25,0.5,1,2,3,4,6,8,10,15,20,30,50,100]; + let startZoom = 1; + let startScale = 1; + let isZooming = false; + let touchData = {}; + + var panel = $div("panel " + type,"",parent); + + var toolbar = $div("toolbar","",panel); + var viewport = $div("viewport","",panel); + let windowContainer; + let windowCanvasList = []; + let tileContainer; + let toolPanel; + + let currentView = "editor"; + + var thisPanel = type === "left" ? 0:1; + if (thisPanel === 1) panel.style.display = "none"; + + me.getViewPort = function(){ + return viewport; + } + var canvas = Canvas(me); + + me.getCanvas = function(){ + return canvas.getCanvas(); + } + me.getZoom = function(){ + return canvas.getZoom(); + } + me.getResizer = function(){ + return canvas.getResizer(); + } + + me.getIndex = function(){ + return thisPanel; + } + + viewport.addEventListener("wheel",function(e){ + e.preventDefault(); + if (Input.isShiftDown()){ + let point = canvas.getCursorPosition(e); + let settings = Brush.getSettings(); + let offset = e.deltaY>0?-1:1; + BrushPanel.set({size:settings.width+offset}); + EventBus.trigger(EVENT.drawCanvasOverlay,point); + //console.error(settings); + }else if (Input.isControlDown()){ + let point = canvas.getCursorPosition(e); + let settings = Brush.getSettings(); + let offset = e.deltaY>0?-5:5; + BrushPanel.set({opacity:settings.opacity+offset}); + EventBus.trigger(EVENT.drawCanvasOverlay,point); + }else{ + if (e.deltaY>0){ + EventBus.trigger(COMMAND.ZOOMOUT,e); + }else if (e.deltaY<0){ + EventBus.trigger(COMMAND.ZOOMIN,e); + } + } + }); + + if ("gesturestart" in window){ + // TODO: I probably shouldn't bother with gestures, as it's not supported on Android. + // better to implement a generic solution with touch events + // besides ... we still need to track touches to get the center of the gesture + viewport.addEventListener("gesturestart", function (e) { + e.preventDefault(); + e.stopPropagation(); + Input.holdPointerEvents(); + startScale = e.scale || 1; + startZoom = canvas.getZoom(); + touchData.startScrollX = viewport.scrollLeft; + touchData.startScrollY = viewport.scrollTop; + touchData.startDragX = e.clientX; + touchData.startDragY = e.clientY; + //console.log("gesturestart"); + }); + + viewport.addEventListener("gesturechange", function (e) { + e.preventDefault(); + e.stopPropagation(); + let scale = startScale * (e.scale || 1); + let touches = Input.getTouches(); + + if (touches.length>1){ + + if (scale>1.2 || scale<0.8 || isZooming){ // avoid zoom jittering when 2-finger pan is used + + let zoom = scale * startZoom; + canvas.setZoom(zoom,e); + syncZoomLevel(); + isZooming = true; + + // TODO also apply panning ? + + }else{ + // pan + let dx = (touchData.startDragX-e.clientX); + let dy = (touchData.startDragY-e.clientY); + + viewport.scrollLeft = touchData.startScrollX + dx; + viewport.scrollTop = touchData.startScrollY + dy; + } + } + + //console.log("gesturechange"); + + }); + + viewport.addEventListener("gestureend", function (e) { + e.preventDefault(); + e.stopPropagation(); + Input.releasePointerEvents(); + isZooming = false; + }); + }else{ + + + viewport.addEventListener("touchstart", function (e) { + if (e.touches.length>1){ + e.preventDefault(); + e.stopPropagation(); + Input.holdPointerEvents(); + touchData.startScrollX = viewport.scrollLeft; + touchData.startScrollY = viewport.scrollTop; + touchData.startDragX = e.touches[0].clientX; + touchData.startDragY = e.touches[0].clientY; + touchData.startZoom = canvas.getZoom(); + touchData.startScale = 1; + + + let dx = e.touches[0].clientX - e.touches[1].clientX; + let dy = e.touches[0].clientY - e.touches[1].clientY; + touchData.startDistance = Math.sqrt(dx*dx+dy*dy); + } + }); + + viewport.addEventListener("touchmove", function (e) { + if (e.touches.length>1){ + e.preventDefault(); + e.stopPropagation(); + + let dx = e.touches[0].clientX - e.touches[1].clientX; + let dy = e.touches[0].clientY - e.touches[1].clientY; + let distance = Math.sqrt(dx*dx+dy*dy); + let scale = distance / touchData.startDistance; + + if (scale>1.1 || scale<0.9 || isZooming) { // avoid zoom jittering when 2-finger pan is used + let zoom = touchData.startZoom * scale; + syncZoomLevel(); + isZooming = true; + canvas.setZoom(zoom); + }else{ + // pan + dx = (touchData.startDragX-e.touches[0].clientX); + dy = (touchData.startDragY-e.touches[0].clientY); + + viewport.scrollLeft = touchData.startScrollX + dx; + viewport.scrollTop = touchData.startScrollY + dy; + } + } + }); + + viewport.addEventListener("touchend", function (e) { + e.preventDefault(); + e.stopPropagation(); + Input.releasePointerEvents(); + isZooming = false; + }); + } + + viewport.addEventListener("pointerenter", function (e) { + Input.setPointerOver("viewport"); + }, false); + viewport.addEventListener("pointerleave", function (e) { + Input.removePointerOver("viewport"); + }, false); + + panel.addEventListener("pointermove",()=>{ + Editor.setActivePanel(thisPanel) + //activeCanvas = type==="left"?canvas:canvas2; + }); + + function syncZoomLevel(){ + let z = Math.floor(canvas.getZoom()*100); + zoombutton.innerHTML = z + "%"; + } + + me.clear = function(){ + canvas.clear(); + } + + me.zoom = function(zoomFactor,center){ + canvas.zoom(zoomFactor,center); + syncZoomLevel(); + } + + me.zoomToFit = function(){ + const rect = viewport.getBoundingClientRect(); + let sx = rect.width/canvas.getCanvas().width; + let sy = rect.height/canvas.getCanvas().height; + canvas.setZoom(Math.min(sx,sy)); + canvas.resetPan(); + syncZoomLevel(); + } + + me.setZoom = function(factor){ + canvas.setZoom(factor); + syncZoomLevel(); + } + + me.getWidth = function(){ + return panel.getBoundingClientRect().width; + } + + me.setWidth = function(w,percentage){ + let p = percentage?"%":"px"; + if (typeof w === "string") p=""; + panel.style.width = w + p; + } + + me.setView = function(type){ + console.log("setting view to " + type); + currentView = type; + viewport.classList.toggle("hidden",type !== "editor"); + if (windowContainer) windowContainer.classList.toggle("hidden",type !== "icons"); + if (tileContainer) tileContainer.classList.toggle("hidden",type !== "tiles"); + + if (type === "icons"){ + if (!windowContainer) generateWindows(); + windowContainer.classList.remove("hidden"); + updateWindows(); + } + + if (type === "tiles"){ + if (!tileContainer) generateTiles(); + tileContainer.classList.remove("hidden"); + updateTiles(); + } + + EventBus.trigger(EVENT.previewModeChanged,type); + } + + me.show = function(){ + panel.style.display = "block"; + } + + me.hide = function(){ + panel.style.display = "none"; + } + + me.isVisible = function(){ + return panel.style.display !== "none"; + } + + me.isActive = function(){ + return me.isVisible() && Editor.getActivePanel().getIndex() === thisPanel; + } + + // setup toolbar + let zoomoutButton = $div("button info","-",toolbar,()=>{ + var z = canvas.getZoom(); + for (var i=zoomLevels.length-1;i>=0;i--){ + var zoom = zoomLevels[i]; + if (zoom{ + canvas.setZoom(1); + syncZoomLevel(); + }) + let zoominButton = $div("button info","+",toolbar,()=>{ + var z = canvas.getZoom(); + for (var i=0;iz) break; + } + canvas.setZoom(zoom); + syncZoomLevel(); + }) + let zoomFitButton = $div("button expand info","",toolbar,me.zoomToFit); + zoomoutButton.info = "Zoom out"; + zoominButton.info = "Zoom in"; + zoombutton.info = "Reset zoom to 100%"; + zoomFitButton.info = "Zoom to fit screen"; + + if (thisPanel === 0){ + $div("button closepresentation","x",toolbar,()=>{ + EventBus.trigger(COMMAND.PRESENTATION); + }); + toolPanel = $div("toolpanel","",toolbar); + EventBus.on(EVENT.toolChanged,(tool)=>{ + tool = tool || Editor.getCurrentTool(); + toolPanel.innerHTML = ""; + toolPanel.appendChild(ToolOptions.getOptions(tool)); + }) + EventBus.on(COMMAND.TRANSFORMLAYER,()=>{ + toolPanel.innerHTML = ""; + toolPanel.appendChild(ToolOptions.getOptions(COMMAND.TRANSFORMLAYER)); + }) + } + if (thisPanel === 1){ + let viewPanel = $div("viewstyle","",toolbar); + let b1 = $div("button info editor active","E",viewPanel,()=>{me.setView('editor')}); + let b2 = $div("button info icons","I",viewPanel,()=>{me.setView('icons')}); + let b3 = $div("button info tiles","T",viewPanel,()=>{me.setView('tiles')}); + b1.info = "View in editor"; + b2.info = "Preview as icon"; + b3.info = "Preview as tile"; + + $div("button right","x",toolbar,()=>{ + EventBus.trigger(COMMAND.SPLITSCREEN); + }); + + EventBus.on(EVENT.previewModeChanged,(mode)=>{ + b1.classList.toggle("active",mode === "editor"); + b2.classList.toggle("active",mode === "icons"); + b3.classList.toggle("active",mode === "tiles"); + }); + } + + function generateWindows(){ + windowContainer = $div("windowContainer","",panel); + + for (let i = 1; i<4; i++){ + let window = $div("window","",windowContainer); + let colorbar = $div("colorbar","",window); + let windowCanvas=document.createElement("canvas"); + windowCanvas.width = 280; + windowCanvas.height = 142; + window.appendChild(windowCanvas); + windowCanvasList.push(windowCanvas.getContext("2d")); + } + } + + function updateWindows(){ + let icon = ImageFile.getCanvas(); + let colors = ["#959595","#3A67A3","#000000"]; + + windowCanvasList.forEach((ctx,index)=>{ + ctx.fillStyle = colors[index]; + ctx.fillRect(0,0,280,142); + let x = 10; + let y = 10; + while (y<140) { + while (x<280) { + ctx.drawImage(icon,x,y); + x = x + icon.width + 10; + } + x = 10; + y = y+icon.height+10; + } + + + }) + } + + function generateTiles(){ + tileContainer = $div("tileContainer","",panel); + } + + function updateTiles(){ + let tile = ImageFile.getCanvas(); + let imageDataURL = tile.toDataURL(); + tileContainer.style.backgroundImage = "url('"+imageDataURL+"')"; + } + + EventBus.on(EVENT.imageSizeChanged,()=>{ + setTimeout(syncZoomLevel,20); + }) + + EventBus.on(EVENT.imageContentChanged,()=>{ + if (currentView === "icons"){ + updateWindows(); + } + if (currentView === "tiles"){ + updateTiles(); + } + }) + + EventBus.on(COMMAND.PRESENTATION,()=>{ + if (me.isVisible()){ + me.zoomToFit(); + } + }) + + EventBus.on(COMMAND.ZOOMFIT,()=>{ + if (me.isVisible()){ + me.zoomToFit(); + } + }) + + return me; +}; + + + +export default EditPanel; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/effects.js b/app/web-tools/dpaint/_script/ui/effects.js new file mode 100644 index 00000000..da311dab --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/effects.js @@ -0,0 +1,336 @@ +import * as StackBlur from "../util/stackBlur.js"; + +let Effects = function(){ + let me = {} + let filters = {}; + let _src, _target; + let maskCanvas; + let doApply = true; + + let customFilters = { + sharpen : function(value){ + sharpen(_target,_target.canvas.width,_target.canvas.height,value); + }, + blur : function(value){ + StackBlur.canvasRGBA(_target.canvas,0,0,_target.canvas.width,_target.canvas.height,value); + }, + red : function(value){ + if (value){ + overlayColor(value>0?"#F50A0A":"#0AF5F5",Math.abs(value/3)); + } + }, + green : function(value){ + if (value){ + overlayColor(value>0?"#0AF50A":"#F50AF5",Math.abs(value/3)); + } + }, + blue : function(value){ + if (value){ + overlayColor(value>0?"#0A0AF5":"#F5F50A",Math.abs(value/3)); + } + } + } + + me.clear = ()=>{ + filters = {}; + maskCanvas = null; + } + + me.setBrightness = (value,src,target)=>{ + me.setSrcTarget(src,target); + value = parseInt(value); + if (isNaN(value)) value=50; + filters.brightness = (value+50)/50; + if (filters.brightness === 1) delete filters.brightness; + applyFilters(); + } + + me.setContrast = (value,src,target)=>{ + me.setSrcTarget(src,target); + value = parseInt(value); + if (isNaN(value)) value=50; + filters.contrast = (value+50)/50; + applyFilters(); + } + + me.setSaturation = (value,src,target)=>{ + me.setSrcTarget(src,target); + value = parseInt(value); + if (isNaN(value)) value=50; + filters.saturate = (value+50)/50; + applyFilters(); + } + + me.setHue = (value,src,target)=>{ + me.setSrcTarget(src,target) + filters["hue-rotate"] = value + "deg"; + applyFilters(); + } + + me.setBlur = (value,src,target)=>{ + me.setSrcTarget(src,target) + //filters.blur = (value/5) + "px"; + filters.blur = (value/2); + applyFilters(); + } + + me.setSharpen = (value,src,target)=>{ + me.setSrcTarget(src,target) + filters.sharpen = (value/100); + applyFilters(); + } + + me.setSepia = (value,src,target)=>{ + me.setSrcTarget(src,target) + filters.sepia = (value/100); + applyFilters(); + } + + me.setInvert = (value,src,target)=>{ + me.setSrcTarget(src,target) + filters.invert = (value/100); + applyFilters(); + } + + me.setColorBalance = (channel,value,src,target)=>{ + me.setSrcTarget(src,target); + createMask(); + value = parseInt(value); + filters[channel] = value/100; + applyFilters(); + } + + me.setSrcTarget = (src,target)=>{ + if (src) _src = src; + if (target) _target = target; + } + + me.hold = ()=>{ + doApply = false; + } + + me.apply = ()=>{ + doApply = true; + applyFilters(); + } + + me.feather = function(ctx,amount){ + let w = ctx.canvas.width; + let h = ctx.canvas.height; + let data = ctx.getImageData(0,0,w,h); + let d = data.data; + let target = []; + + if (amount>0){ + function onEdge(index){ + return d[index+3] === 0; + } + + for (let y=0; y{ + d[index+3] = d[index+3]>>1; + }); + ctx.putImageData(data,0,0); + }else{ + let alphaIncrease = -amount*100; + for (let y=0; y0){ + alpha = Math.min(255,alpha+=alphaIncrease); + d[index+3] = alpha; + } + } + } + ctx.putImageData(data,0,0); + } + + } + + me.outline = function(ctx,color){ + let w = ctx.canvas.width; + let h = ctx.canvas.height; + let data = ctx.getImageData(0,0,w,h); + let d = data.data; + let target = []; + + function checkPixel(index){ + if (d[index+3] === 0){ + target.push(index); + } + } + + for (let y=1; y{ + d[index] = color[0]; + d[index+1] = color[1]; + d[index+2] = color[2]; + d[index+3] = 255; + }); + target=[]; + ctx.putImageData(data,0,0); + }else{ + return target; + } + + } + + me.getFilters = (target)=>{ + _target = target; + return customFilters; + } + + function applyFilters(){ + if (!doApply) return; + + let filter = ""; + if (_src && _target){ + _target.filter = "none"; + _target.clearRect(0,0,_target.canvas.width,_target.canvas.height); + _target.drawImage(_src, 0, 0); + + for (let key in filters){ + if (typeof filters[key] !== "undefined"){ + if (customFilters[key]){ + customFilters[key](filters[key]) + }else{ + filter += key + "(" + filters[key] + ") "; + } + } + } + + _target.filter = filter; + _target.drawImage(_target.canvas, 0, 0); + _target.filter = "none"; + + } + + } + + function sharpen(ctx, w, h, mix){ + var x, sx, sy, r, g, b, a, dstOff, srcOff, wt, cx, cy, scy, scx, + weights = [0, -1, 0, -1, 5, -1, 0, -1, 0], + katet = Math.round(Math.sqrt(weights.length)), + half = (katet * 0.5) | 0, + dstData = ctx.createImageData(w, h), + dstBuff = dstData.data, + srcBuff = ctx.getImageData(0, 0, w, h).data, + y = h; + while (y--) { + x = w; + while (x--) { + sy = y; + sx = x; + dstOff = (y * w + x) * 4; + r = 0; + g = 0; + b = 0; + a = 0; + if(x>0 && y>0 && x= 0 && scy < h && scx >= 0 && scx < w) { + srcOff = (scy * w + scx) * 4; + wt = weights[cy * katet + cx]; + + r += srcBuff[srcOff] * wt; + g += srcBuff[srcOff + 1] * wt; + b += srcBuff[srcOff + 2] * wt; + a += srcBuff[srcOff + 3] * wt; + } + } + } + + dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix); + dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix); + dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix); + dstBuff[dstOff + 3] = srcBuff[dstOff + 3]; + } else { + dstBuff[dstOff] = srcBuff[dstOff]; + dstBuff[dstOff + 1] = srcBuff[dstOff + 1]; + dstBuff[dstOff + 2] = srcBuff[dstOff + 2]; + dstBuff[dstOff + 3] = srcBuff[dstOff + 3]; + } + } + } + + ctx.putImageData(dstData, 0, 0); + } + + function overlayColor(color,amount){ + if (_target && amount){ + _target.globalCompositeOperation = "overlay"; + _target.globalAlpha = amount; + _target.fillStyle = color; + _target.fillRect(0,0,_target.canvas.width,_target.canvas.height); + _target.globalCompositeOperation = "source-over"; + _target.globalAlpha = 1; + + if (maskCanvas){ + _target.globalCompositeOperation = "destination-in"; + _target.drawImage(maskCanvas,0,0); + _target.globalCompositeOperation = "source-over"; + } + } + } + + function createMask(){ + if (!_src){ + console.error("no source for mask"); + return; + } + + if (!maskCanvas){ + maskCanvas = document.createElement("canvas"); + maskCanvas.width = _src.width; + maskCanvas.height = _src.height; + let maskCtx = maskCanvas.getContext("2d"); + let maskData = _src.getContext("2d").getImageData(0,0,maskCanvas.width,maskCanvas.height); + maskCtx.fillStyle = "black"; + maskCtx.fillRect(0,0,maskCanvas.width,maskCanvas.height); + let mask = maskData.data; + for (let i = 0; i1; + } + + function onPointerDown(e){ + if (holdPointerEvents) return; + let target = e.target.closest(".handle"); + + if (e.pointerType === "touch" && target && target.classList.contains("viewport")){ + // add a small delay so we still can capture multi touch gestures + setTimeout(function(){ + handlePointerDown(e,target); + },100); + return; + } + + handlePointerDown(e,target); + } + + function onTouchStart(e){ + touchData.touches = e.touches; + if (e.touches.length>1){ + console.log("multitouch detected, holding pointer events"); + holdPointerEvents = true; + EventBus.trigger(EVENT.hideCanvasOverlay); + } + } + + function onTouchMove(e){ + touchData.touches = e.touches; + } + + function onTouchEnd(e){ + touchData.touches = e.touches; + if (e.touches.length===0) holdPointerEvents = false; + } + + function handlePointerDown(e,target){ + if (holdPointerEvents) return; + document.body.classList.add("pointerdown"); + console.log(target); + + if (!target || !target.classList.contains("menuitem")){ + Menu.close(); + } + if (!target || !target.classList.contains("contextmenuitem")){ + ContextMenu.hide(); + } + + if (target){ + + let resizer = Editor.getActivePanel().getResizer(); + if (resizer.isActive()){ + let sizeTarget = target.closest(".sizebox"); + if (!sizeTarget){ + Editor.commit(); + } + } + + if (target.onClick){ + // on touch devices this is not a click, which might interfere with events that require "user input" + // like the file input dialog to open a file + + if ((e.pointerType==="touch" || e.pointerType==="pen") && target.waitForClick){ + touchData.waitForClick = { + target: target, + event: e, + time: performance.now() + } + }else { + target.onClick(e,target); + } + } + + + if (target.onDoubleClick){ + let now = performance.now(); + if (target.prevNow){ + if((now - target.prevNow)<400){ + target.onDoubleClick(); + } + } + target.prevNow = now; + } + if (target.onDrag){ + touchData.target = target; + touchData.onDrag = target.onDrag; + touchData.isDragging = true; + touchData.startX = e.clientX; + touchData.startY = e.clientY; + touchData.button = e.button; + e.preventDefault(); + if (target.onDragStart) target.onDragStart(e); + } + if ((e.button || e.ctrlKey) && target.onContextMenu){ + target.onContextMenu(e); + } + + } + } + + function onPointerMove(e){ + if (holdPointerEvents) return; + if (!e.shiftKey) keyDown["shift"]=false; + if (!e.altKey) keyDown["alt"]=false; + if (!e.ctrlKey) keyDown["control"]=false; + if (!e.metaKey) keyDown["meta"]=false; + + let infoTarget = e.target.closest(".info"); + if (infoTarget){ + let tooltip = infoTarget.info; + if (infoTarget.infoOnMove) tooltip = infoTarget.infoOnMove(e) + infoTarget.info; + if (infoTarget.info) Statusbar.setToolTip(tooltip); + }else{ + Statusbar.setToolTip(""); + } + + if (touchData.isDragging && touchData.onDrag){ + let x = e.clientX-touchData.startX; + let y = e.clientY-touchData.startY; + touchData.onDrag(x,y,touchData,e); + } + if (touchData.dragElement){ + touchData.dragElement.classList.add("active"); + touchData.dragElement.style.left = e.clientX + "px"; + touchData.dragElement.style.top = e.clientY + "px"; + } + + // Meh ... + if (!me.isShiftDown() && !me.isAltDown() && !me.isSpaceDown() && !document.body.classList.contains("cursor-pan")){ + Cursor.resetOverride(); + } + } + + function onPointerUp(e){ + if (e.button===1 && document.body.classList.contains("cursor-pan")){ + Cursor.resetOverride(); + } + + if (touchData.waitForClick){ + let now = performance.now(); + if ((now - touchData.waitForClick.time)<2000){ + touchData.waitForClick.target.onClick(touchData.waitForClick.event); + }else{ + console.log("waitForClick timeout"); + } + touchData.waitForClick = undefined; + } + + + if (touchData.isDragging){ + if (touchData.target && touchData.target.onDragEnd){ + touchData.target.onDragEnd(e); + } + } + touchData.isDragging = false; + document.body.classList.remove("pointerdown"); + + if (document.body.classList.contains("colorpicker") && !me.isShiftDown() && !me.isAltDown()){ + //Cursor.reset(); + } + + } + + function onKeyDown(e){ + let code = limitKeyCode(e.code); + let key = e.key; + if (key) key = key.toLowerCase(); + + if (me.isShiftDown() && !e.shiftKey) modifierKeyUp("shift"); + if (me.isControlDown() && !e.ctrlKey) modifierKeyUp("control"); + if (me.isAltDown() && !e.altKey) modifierKeyUp("alt"); + if (keyDown["meta"] && !e.metaKey) modifierKeyUp("meta"); + + if (Editor.getCurrentTool() !== COMMAND.TEXT){ + if (code === "keyv" && Input.isMetaDown()){ + // allow default paste + return; + } + + if (code === "keyc" && Input.isMetaDown()){ + // allow default copy + return; + } + } + + e.preventDefault(); + e.stopPropagation(); + // console.log(e); + + keyDown[code] = true; + if (modifiers.indexOf(code)>=0){ + document.body.classList.add(code.toLowerCase()); + EventBus.trigger(EVENT.modifierKeyChanged); + } + + + if (activeKeyHandler){ + let handled = activeKeyHandler(code,key); + if (handled) return; + } + + //console.log(code); + switch (code){ + case "delete": + case "backspace": + EventBus.trigger(COMMAND.CLEAR); + break; + case "escape": + // TODO should we tie this to the selected tool? + Menu.close(); + ContextMenu.hide(); + Editor.reset(); + if (UI.inPresentation()) EventBus.trigger(COMMAND.PRESENTATION); + break; + case "tab": + //EventBus.trigger(COMMAND.SPLITSCREEN); + EventBus.trigger(COMMAND.CYCLEPALETTE); + break; + case "enter": + Editor.commit(); + break; + case "numpad4": + ImageFile.nextFrame(-1); + break; + case "numpad6": + ImageFile.nextFrame(); + break; + case "numpad7": + Palette.prev(); + break; + case "numpad9": + Palette.next(); + break; + case "intlbackslash": + if (me.isShiftDown()){ + ImageFile.nextFrame(); + }else{ + ImageFile.nextFrame(-1); + } + break; + case "arrowleft": + case "arrowup": + case "arrowright": + case "arrowdown": + Editor.arrowKey(code.replace("arrow","")); + break; + case "pageup": + EventBus.trigger(COMMAND.NEXTPALETTE); + break; + case "pagedown": + EventBus.trigger(COMMAND.PREVPALETTE); + break; + } + + if (me.isMetaDown()){ + + if (me.isMetaAndShiftDown()){ + switch (key){ + case "a": EventBus.trigger(COMMAND.LAYERMASK); break; + case "h": EventBus.trigger(COMMAND.LAYERMASKHIDE); break; + case "l": EventBus.trigger(COMMAND.TOSELECTION); break; + case "p": EventBus.trigger(COMMAND.COLORSELECT); break; + case "arrowdown": EventBus.trigger(COMMAND.MERGEDOWN); break; + } + }else{ + switch (key){ + case "a": EventBus.trigger(COMMAND.SELECTALL); break; + case "b": EventBus.trigger(COMMAND.STAMP); break; + case "d": EventBus.trigger(COMMAND.DUPLICATELAYER); break; + case "e": EventBus.trigger(COMMAND.EFFECTS); break; + case "g": EventBus.trigger(COMMAND.TOGGLEGRID); break; + case "i": EventBus.trigger(COMMAND.IMPORTLAYER); break; + case "j": EventBus.trigger(COMMAND.TOLAYER); break; + case "k": EventBus.trigger(COMMAND.CUTTOLAYER); break; + case "l": EventBus.trigger(COMMAND.TOLAYER); break; + case "n": EventBus.trigger(COMMAND.NEW); break; + case "o": EventBus.trigger(COMMAND.OPEN); break; + case "p": EventBus.trigger(COMMAND.RESIZE); break; + case "_r": EventBus.trigger(COMMAND.ROTATE); break; + case "r": EventBus.trigger(COMMAND.RESAMPLE); break; + case "s": EventBus.trigger(COMMAND.SAVE); break; + case "y": EventBus.trigger(COMMAND.REDO); break; + case "z": EventBus.trigger(COMMAND.UNDO); break; + } + } + }else{ + switch (key){ + case "a": EventBus.trigger(COMMAND.TOGGLEMASK); break; + case "b": EventBus.trigger(COMMAND.DRAW); break; + case "c": EventBus.trigger(COMMAND.CIRCLE); break; + case "d": EventBus.trigger(COMMAND.TOGGLEDITHER); break; + case "e": EventBus.trigger(COMMAND.ERASE); break; + case "f": EventBus.trigger(COMMAND.FLOOD); break; + case "g": EventBus.trigger(COMMAND.GRADIENT); break; + case "h": EventBus.trigger(COMMAND.PAN); break; + case "i": EventBus.trigger(COMMAND.TOGGLEINVERT); break; + case "k": EventBus.trigger(COMMAND.COLORPICKER); break; + case "l": EventBus.trigger(COMMAND.LINE); break; + case "m": EventBus.trigger(COMMAND.SMUDGE); break; + case "p": EventBus.trigger(COMMAND.SPRAY); break; + case "q": EventBus.trigger(COMMAND.TOGGLEOVERRIDE); break; + case "r": EventBus.trigger(COMMAND.SQUARE); break; + case "s": EventBus.trigger(COMMAND.SELECT); break; + case "t": EventBus.trigger(COMMAND.TEXT); break; + case "v": EventBus.trigger(COMMAND.TRANSFORMLAYER); break; + case "w": EventBus.trigger(COMMAND.FLOODSELECT); break; + case "x": EventBus.trigger(COMMAND.SWAPCOLORS); break; + case "y": EventBus.trigger(COMMAND.POLYGONSELECT); break; + case "z": EventBus.trigger(COMMAND.SPLITSCREEN); break; + case "-": EventBus.trigger(COMMAND.ZOOMOUT); break; + case "+": EventBus.trigger(COMMAND.ZOOMIN); break; + } + } + + } + + function onKeyUp(e){ + let code = limitKeyCode(e.code); + keyDown[code] = false; + if (modifiers.indexOf(code)>=0){ + modifierKeyUp(code); + } + } + + function modifierKeyUp(code){ + keyDown[code] = false; + document.body.classList.remove(code); + EventBus.trigger(EVENT.modifierKeyChanged); + } + + function limitKeyCode(code){ + if ((code === "ShiftLeft") || (code === "ShiftRight")) code = "shift"; + if ((code === "AltLeft") || (code === "AltRight")) code = "alt"; + if ((code === "ControlLeft") || (code === "ControlRight")) code = "control"; + if ((code === "MetaLeft") || (code === "MetaRight")) code = "meta"; + return code.toLowerCase(); + } + + function handlePaste(e){ + + function pasteImage(blob){ + let img = new Image(); + img.onerror = ()=>{ + console.error("error pasting image"); + } + img.onload = ()=>{ + console.log("pasted image", img.width, img.height); + ImageFile.paste(img); + } + img.src = URL.createObjectURL(blob); + } + + if (!e){ + // "paste" selected from menu; + + let blob; + const queryOpts = { name: 'clipboard-read', allowWithoutGesture: true }; + navigator.permissions.query(queryOpts).then(permissionStatus=>{ + // Will be 'granted', 'denied' or 'prompt': + console.log(permissionStatus.state); + getClipboardContents(); + + // Listen for changes to the permission state + permissionStatus.onchange = () => { + console.log(permissionStatus.state); + getClipboardContents(); + }; + + + async function getClipboardContents() { + if (permissionStatus.state === "granted") { + try { + const clipboardItems = await navigator.clipboard.read(); + for (const clipboardItem of clipboardItems) { + for (const type of clipboardItem.types) { + if (!blob && type.indexOf('image') !== -1){ + blob = await clipboardItem.getType(type); + pasteImage(blob); + } + } + } + } catch (err) { + console.error(err.name, err.message); + } + } + } + }) + } + console.log("paste",e); + if (e && e.clipboardData){ + const clipboardItems = e.clipboardData.items; + console.log("paste " + clipboardItems.length + " items"); + let list = [].slice.call(clipboardItems); + list.forEach(item=>{ + console.log(item.type); + }); + + const items = list.filter(function (item) { + // Filter the image items only + return item.type.indexOf('image') !== -1; + }); + if (items.length === 0) { + return; + } + + const item = items[0]; + // Get the blob of image + console.log("paste " + item.type) + const blob = item.getAsFile(); + pasteImage(blob) + } + } + + function handleCopy(e){ + if (e){ + // Check what we need to copy + console.log("copy from ", e.target,e); + + // allow default copy for input fields + if (e.target.tagName.toLowerCase() === "input") return; + if (e.target.closest("code")) return; + } + + let canvas = Selection.toCanvas() || ImageFile.getActiveContext().canvas; + if (canvas && ClipboardItem){ + canvas.toBlob((blob) => { + // note: As to date, FireFox doesn't support ClipboardItem. + let data = [new ClipboardItem({ [blob.type]: blob })]; + + navigator.clipboard.write(data).then( + () => { + console.log("copied"); + }, + (err) => { + console.error("error"); + console.error(err); + //onError(err); + } + ); + }); + } + } + + function handleCut(){ + console.log("cut"); + } + + function handleUndo(){ + console.log("undo"); + } + + function handleDelete(){ + console.log("delete"); + } + + function handleDragEnter(e) { + e.stopPropagation(); + e.preventDefault(); + } + + function handleDragOver(e){ + e.stopPropagation(); + e.preventDefault(); + } + + function handleDrop(e){ + e.preventDefault(); + //console.error("Drop"); + //console.error(e); + + var dt = e.dataTransfer; + var files = dt.files; + + ImageFile.handleUpload(files,"file") + + } + + return me; +}(); + +export default Input; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/layer.js b/app/web-tools/dpaint/_script/ui/layer.js new file mode 100644 index 00000000..1fbc7074 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/layer.js @@ -0,0 +1,397 @@ +import Color from "../util/color.js"; +import ToolOptions from "./components/toolOptions.js"; +import {duplicateCanvas, indexPixelsToPalette, releaseCanvas} from "../util/canvasUtils.js"; +import Brush from "./brush.js"; +import HistoryService from "../services/historyservice.js"; +import DitherPanel from "./toolPanels/ditherPanel.js"; +import historyservice from "../services/historyservice.js"; +import Palette from "./palette.js"; +import ImageFile from "../image.js"; + +let Layer = function(width,height,name){ + let me = { + visible:true, + opacity:100, + name: name, + blendMode: "normal", + hasMask: false, + locked: false, + } + + let canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + let ctx = canvas.getContext("2d",{willReadFrequently:true}); + //note: willReadFrequently forces the canvas to remain on the CPU instead of the GPU + // this also "fixes" a bug in Chrome where multiple calls to getImageData() on the same canvas clears the canvas incorrectly + + let mask; + let maskCtx; + let maskActive; + let maskEnabled; + let alphaLayer; + let alphaCtx; + let combined; + let drawLayer; + let drawCtx; + let drawMask; + let drawMaskCtx; + let isDrawing; + let drawOpacity; + let currentColor; + + me.getCanvas = function(){ + if (maskActive){ + return mask; + }else{ + return canvas; + } + } + + me.getCanvasType = function(maskType){ + return (maskType) ? mask : canvas; + } + + me.getContext = function(){ + if (maskActive){ + return maskCtx; + }else{ + return ctx; + } + } + + me.render = function(){ + if ((mask && maskEnabled) || isDrawing){ + if (!combined) combined = duplicateCanvas(canvas); + let combinedCtx = combined.getContext("2d",{willReadFrequently:true}); + combinedCtx.clearRect(0,0,combined.width,combined.height); + + combinedCtx.globalCompositeOperation = "source-over"; + combinedCtx.drawImage(canvas,0,0); + + + if (isDrawing && drawLayer){ + if (mask && maskActive){ + // temporary composite alphaLayer + if (!drawMask){ + drawMask = duplicateCanvas(mask); + drawMaskCtx = drawMask.getContext("2d"); + } + drawMaskCtx.clearRect(0 ,0,drawMask.width,drawMask.height); + drawMaskCtx.drawImage(mask,0,0); + drawMaskCtx.globalAlpha = drawOpacity; + drawMaskCtx.drawImage(drawLayer,0,0); + drawMaskCtx.globalAlpha = 1; + me.update(drawMaskCtx); + }else{ + combinedCtx.globalAlpha = drawOpacity; + if(currentColor==="transparent"){ + combinedCtx.globalCompositeOperation = "destination-out"; + } + combinedCtx.drawImage(drawLayer,0,0); + combinedCtx.globalCompositeOperation = "source-over"; + combinedCtx.globalAlpha = 1; + } + } + + + if (mask){ + if (maskActive && ToolOptions.showMask()){ + combinedCtx.fillStyle = "red"; + combinedCtx.globalAlpha = 0.7; + combinedCtx.fillRect(0,0,combined.width,combined.height); + combinedCtx.globalAlpha = 1; + } + + combinedCtx.globalCompositeOperation = "destination-in"; + combinedCtx.drawImage(alphaLayer,0,0); + combinedCtx.globalCompositeOperation = "source-over"; + } + + return combined; + }else{ + return canvas; + } + } + + me.clear = function(){ + if (maskActive){ + maskCtx.clearRect(0,0, canvas.width, canvas.height); + }else{ + ctx.clearRect(0,0, canvas.width, canvas.height); + } + } + + me.drawImage = function(image,x,y){ + x=x||0;y=y||0; + let _ctx = me.getContext(); + _ctx.imageSmoothingEnabled = false; + _ctx.drawImage(image,x,y); + me.update(); + } + + me.draw = function(x,y,color,touchData){ + if (!drawLayer){ + drawLayer=duplicateCanvas(canvas); + drawCtx = drawLayer.getContext("2d"); + } + if (!touchData.isDrawing){ + drawOpacity = Palette.isLocked() ? 1 : Brush.getOpacity(); + } + isDrawing = true; + currentColor = color; + let drawColor = color; + if (color === "transparent"){ + drawColor = "black"; + } + + //Brush.draw(me.getContext(),x,y,color,true); + let b = Brush.draw(drawCtx,x,y,drawColor,touchData.button,true); // TODO: color should not be part of the brush? + + if (DitherPanel.getDitherState()){ + let pattern = DitherPanel.getDitherPattern(); + + drawCtx.globalCompositeOperation = touchData.button ? "destination-out" : "destination-in"; + drawCtx.drawImage(pattern,0,0); + drawCtx.globalCompositeOperation = "source-over"; + } + } + + me.drawShape = function(drawFunction,x,y,w,h){ + if (!drawLayer){ + drawLayer=duplicateCanvas(canvas); + drawCtx = drawLayer.getContext("2d"); + } + isDrawing = true; + + drawFunction(drawCtx,x,y,w,h); + } + + me.commitDraw = function(){ + let _ctx = me.getContext(); + _ctx.globalAlpha = drawOpacity; + if(currentColor==="transparent"){ + _ctx.globalCompositeOperation = "destination-out"; + } + _ctx.drawImage(drawLayer,0,0); + _ctx.globalCompositeOperation = "source-over"; + _ctx.globalAlpha = 1; + drawCtx.clearRect(0,0,drawLayer.width,drawLayer.height); + isDrawing = false; + currentColor=""; + historyservice.end(); + } + + me.fill = function(color){ + color = Color.fromString(color); + let imageData = ctx.getImageData(0,0,canvas.width, canvas.height); + let data = imageData.data; + let max = data.length>>2; + for (let i = 0; i100){ + imageData.data[index] = color[0]; + imageData.data[index+1] = color[1]; + imageData.data[index+2] = color[2]; + } + } + ctx.putImageData(imageData,0,0); + } + + me.addMask = function(hide){ + if (!mask){ + mask = duplicateCanvas(canvas); + alphaLayer = duplicateCanvas(canvas); + if (!combined) combined = duplicateCanvas(canvas); + + maskCtx = mask.getContext("2d"); + alphaCtx = alphaLayer.getContext("2d"); + maskCtx.fillStyle = alphaLayer.fillStyle = hide?"black":"white"; + maskCtx.fillRect(0,0,mask.width,mask.height); + alphaCtx.fillRect(0,0,mask.width,mask.height); + me.hasMask = true; + maskEnabled = true; + + if (!me.isMaskActive()){ + me.toggleMask(); + me.update(maskCtx); + me.toggleMask(); + } + } + } + + me.removeMask = function(andApply){ + if (mask){ + if (andApply){ + ctx.globalCompositeOperation = "destination-in"; + ctx.drawImage(alphaLayer,0,0); + ctx.globalCompositeOperation = "source-over"; + } + releaseCanvas(mask); + if (drawMask) releaseCanvas(drawMask); + //releaseCanvas(alphaLayer); + maskCtx = undefined; + mask = undefined; + me.hasMask = false; + maskActive = false; + } + } + + me.enableMask = function(state){ + maskEnabled = !!state; + if (!maskEnabled) maskActive = false; + } + + me.toggleMask = function(){ + maskActive = !maskActive; + } + + me.isMaskActive = ()=>{ + return maskActive; + } + + me.isMaskEnabled = ()=>{ + return maskEnabled; + } + + me.update = (_maskCtx)=>{ + _maskCtx = _maskCtx||maskCtx; + if (maskActive){ + // move mask mayer to alpha layer + let img = _maskCtx.getImageData(0, 0, canvas.width, canvas.height); + for (let i =0, max=img.data.length; i{ + let struct = { + name: me.name, + blendMode: me.blendMode, + opacity: me.opacity, + visible: me.visible, + hasMask: me.hasMask + }; + if (!forSerialization) indexed=false; + + if (indexed){ + let indexed = me.generateIndexedPixels(); + struct.indexedPixels = indexed.pixels; + struct.conversionErrors=indexed.notFoundCount; + }else{ + struct.canvas = forSerialization ? canvas.toDataURL() : duplicateCanvas(canvas); + } + + if (me.hasMask){ + struct.mask = forSerialization ? mask.toDataURL() : duplicateCanvas(mask); + } + + return struct; + } + + me.restore = (struct)=> { + return new Promise((next)=>{ + + me.name = struct.name; + me.blendMode = struct.blendMode; + me.opacity = struct.opacity; + me.visible = !!struct.visible; + me.hasMask = !!struct.hasMask; + + let canvasRestored = true; + let maskRestored = true; + + if (mask) releaseCanvas(mask); + if (alphaLayer) releaseCanvas(alphaLayer); + if (combined) releaseCanvas(combined); + + mask = undefined; + alphaLayer=undefined; + maskActive = false; + + let isDone = ()=>{ + if (canvasRestored && maskRestored){ + if (struct.mask){ + let a = maskActive; + maskActive = true; + me.update(); + maskActive = a; + } + next(); + } + } + + if (struct.canvas){ + canvasRestored = false; + if (typeof struct.canvas === "string"){ + let img = new Image(); + img.onload = ()=>{ + ctx.drawImage(img,0,0); + canvasRestored = true; + isDone(); + } + img.src = struct.canvas; + }else{ + ctx.drawImage(struct.canvas,0,0); + } + }else if (struct.indexedPixels){ + console.log("restoring indexed pixels") + canvasRestored = false; + let imgData = ctx.createImageData(canvas.width,canvas.height); + let colors = Palette.get(); + let w = canvas.width; + let h = canvas.height; + let indexed = struct.indexedPixels; + for (let y = 0; y=0){ + let color = colors[index] || [0,0,0]; + imgData.data[offset] = color[0]; + imgData.data[offset+1] = color[1]; + imgData.data[offset+2] = color[2]; + imgData.data[offset+3] = 255; + }else{ + imgData.data[offset] = 0; + imgData.data[offset+1] = 0; + imgData.data[offset+2] = 0; + imgData.data[offset+3] = 0; + } + } + } + ctx.putImageData(imgData,0,0); + canvasRestored = true; + } + if (struct.mask){ + maskRestored = false; + me.addMask(); + if (typeof struct.mask === "string"){ + let img = new Image(); + img.onload = ()=>{ + maskCtx.drawImage(img,0,0); + maskRestored = true; + isDone(); + } + img.src = struct.mask; + }else{ + maskCtx.drawImage(struct.mask,0,0); + } + } + isDone(); + }); + } + + me.generateIndexedPixels = function(){ + return indexPixelsToPalette(ctx,Palette.get()); + + } + + return me; +} + +export default Layer; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/menu.js b/app/web-tools/dpaint/_script/ui/menu.js new file mode 100644 index 00000000..e4ab0644 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/menu.js @@ -0,0 +1,254 @@ +import {$div,$link} from "../util/dom.js"; +import {COMMAND} from "../enum.js"; +import EventBus from "../util/eventbus.js"; + +let Menu = function(){ + let me = {} + let container; + let activeMenu; + let isMenuActive; + let isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; + let refs = {}; + + let items=[ + {label: "File", items:[ + {label: "New", command: COMMAND.NEW,shortKey: "meta+N"}, + {label: "Open", command: COMMAND.OPEN,shortKey: "meta+O",needsRealClick: true}, + {label: "Save", command: COMMAND.SAVE,shortKey: "meta+S"}, + {label: "Import", command: COMMAND.IMPORTLAYER,shortKey: "meta+I",needsRealClick: true}, + {label: "Info", command: COMMAND.INFO}, + ]}, + {label: "Edit", items:[ + {label: "Copy", command: COMMAND.COPY,shortKey: "meta+C"}, + {label: "Paste", command: COMMAND.PASTE,shortKey: "meta+V"}, + {label: "Undo", command: COMMAND.UNDO,shortKey: "meta+Z"}, + {label: "Redo", command: COMMAND.REDO,shortKey: "meta+Y"}, + ]}, + {label: "Image", items:[ + {label: "Rotate", command: COMMAND.ROTATE}, + {label: "Clear", command: COMMAND.CLEAR}, + {label: "Crop", command: COMMAND.CROP}, + {label: "Trim", command: COMMAND.TRIM}, + {label: "Flatten",command: COMMAND.FLATTEN}, + {label: "Image size", command: COMMAND.RESAMPLE,shortKey: "meta+R"}, + {label: "Canvas Size", command: COMMAND.RESIZE,shortKey: "meta+P"}, + ]}, + {label: "Tools", items:[ + {label: "Draw",command: COMMAND.DRAW,shortKey: "B"}, + {label: "Select",command: COMMAND.SELECT,shortKey: "S"}, + {label: "Line",command: COMMAND.LINE,shortKey: "L"}, + {label: "Rectangle",command: COMMAND.SQUARE,shortKey: "R"}, + {label: "Circle",command: COMMAND.CIRCLE,shortKey: "C"}, + {label: "Gradient",command: COMMAND.GRADIENT,shortKey: "G"}, + {label: "Erase",command: COMMAND.ERASE,shortKey: "E"}, + {label: "Smudge",command: COMMAND.SMUDGE,shortKey: "M"}, + {label: "Spray",command: COMMAND.SPRAY,shortKey: "P"}, + {label: "Text",command: COMMAND.TEXT,shortKey: "T"}, + {label: "Hand",command: COMMAND.PAN,shortKey: "H"}, + {label: "Color Picker",command: COMMAND.COLORPICKER,shortKey: "K"} + ]}, + {label: "Layer", items:[ + {label: "New",command: COMMAND.NEWLAYER}, + {label: "Transform",items:[ + {label: "Free Transform",command: COMMAND.TRANSFORMLAYER,shortKey: "T / V"}, + {label: "Flip Horizontal",command: COMMAND.FLIPHORIZONTAL}, + {label: "Flip Vertical",command: COMMAND.FLIPVERTICAL} + ]}, + {label: "Duplicate",command: COMMAND.DUPLICATELAYER,shortKey: "meta+D"}, + {label: "Effects",command: COMMAND.EFFECTS,shortKey: "meta+E"}, + {label: "Move Up",command: COMMAND.LAYERUP}, + {label: "Move Down",command: COMMAND.LAYERDOWN}, + {label: "Merge Down",command: COMMAND.MERGEDOWN, shortKey: "meta+Shift+↓"}, + {label: "Add Mask",items:[ + {label: "Show All",command: COMMAND.LAYERMASK, shortKey: "meta+Shift+A"}, + {label: "Hide All",command: COMMAND.LAYERMASKHIDE, shortKey: "meta+Shift+H"}, + ]} + ]}, + {label: "Selection", items:[ + {label: "Select",items:[ + {label: "All",command: COMMAND.SELECTALL,shortKey: "meta+A"}, + {label: "Pixels in Current Layer",command: COMMAND.TOSELECTION, shortKey: "meta+Shift+L"}, + {label: "Pixels in Current Color",command: COMMAND.COLORSELECT, shortKey: "meta+Shift+P"}, + {label: "Transparent pixels",command: COMMAND.ALPHASELECT}, + ]}, + {label: "Deselect",command: COMMAND.CLEARSELECTION,shortKey: "Esc"}, + {label: "Copy To Layer",command: COMMAND.TOLAYER,shortKey: "meta+L"}, + {label: "Cut To Layer",command: COMMAND.CUTTOLAYER,shortKey: "meta+K"}, + {label: "Copy To Brush",command: COMMAND.STAMP,shortKey: "meta+B"} + ]}, + {label: "Brush", items:[ + {label: "Load Brush",command: COMMAND.LOADBRUSH}, + {label: "Save Brush",command: COMMAND.SAVEBRUSH}, + {label: "Transform",items:[ + {label: "Rotate Right",command: COMMAND.BRUSHROTATERIGHT,shortKey: "meta+Shift+→"}, + {label: "Rotate Left",command: COMMAND.BRUSHROTATELEFT}, + {label: "Flip Horizontal",command: COMMAND.BRUSHFLIPHORIZONTAL,shortKey: "meta+Shift+←"}, + {label: "Flip Vertical",command: COMMAND.BRUSHFLIPVERTICAL,shortKey: "meta+Shift+↑"} + ]}, + {label: "From Selection",command: COMMAND.STAMP,shortKey: "meta+B"} + ]}, + {label: "Palette", items:[ + {label: "Edit",command: COMMAND.EDITPALETTE}, + {label: "From Image",command: COMMAND.PALETTEFROMIMAGE}, + {label: "Reduce",command: COMMAND.PALETTEREDUCE}, + {label: "Show Presets",command: COMMAND.TOGGLEPALETTES}, + {label: "Save Palette",command: COMMAND.SAVEPALETTE}, + {label: "Load Palette",command: COMMAND.LOADPALETTE}, + {label: "Toggle Color Cycle",command: COMMAND.CYCLEPALETTE,shortKey: "tab"}, + {label: "Color Depth",items:[ + {label: "24bit",info:"16 Million",command: COMMAND.COLORDEPTH24,checked:true,ref:true}, + {label: "12bit",info:"4096 - Amiga OCS",command: COMMAND.COLORDEPTH12,checked:false,ref:true}, + {label: "9bit",info:"512 - Atari ST",command: COMMAND.COLORDEPTH9,checked:false,ref:true}, + ]}, + ]}, + {label: "View", items:[ + {label: "Grid",command: COMMAND.TOGGLEGRID,shortKey: "D",checked:false}, + {label: "Split Screen",command: COMMAND.SPLITSCREEN,shortKey: "Z", checked: false}, + {label: "Tool Options",command: COMMAND.TOGGLESIDEPANEL, checked: false}, + {label: "Gallery",command: COMMAND.TOGGLEGALLERY, checked: false}, + {label: "Presentation mode",command: COMMAND.PRESENTATION, checked: false}, + {label: "Full Screen",command: COMMAND.FULLSCREEN,needsRealClick: true, checked: false}, + ]}, + {label: "Amiga", items:[ + {label: "Open ADF image",command: COMMAND.ADF, needsRealClick: true}, + {label: "Preview in Deluxe Paint",command: COMMAND.DELUXE}, + ]}, + {label: "Help", items:[ + {label: "About DPaint.js",command: COMMAND.ABOUT}, + {label: "Documentation",action: ()=>{ + window.open('https://www.stef.be/dpaint/docs/'); + },needsRealClick: true}, + {label: "SourceCode on GitHub",action: ()=>{ + window.open('https://github.com/steffest/dpaint-js'); + },needsRealClick: true} + ]} + ] + + me.init = function(parent){ + container = $div("menu","",parent); + generate(); + } + + me.activateMenu = function(index){ + if(typeof activeMenu === "number" && activeMenu !== index) me.deActivateMenu(activeMenu); + + let item = items[index]; + if (item && item.element){ + item.element.classList.toggle("active"); + if (item.element.classList.contains("active")){ + activeMenu=index; + isMenuActive=true; + }else{ + isMenuActive=false; + } + } + } + + me.deActivateMenu = function(index){ + if (typeof index === "undefined") index=activeMenu; + + let item = items[index]; + if (item && item.element){ + item.element.classList.remove("active"); + activeMenu = undefined; + isMenuActive = false; + } + } + + me.close = function(){ + if (container) container.classList.remove("active"); + me.deActivateMenu(); + } + + + function buildMenuItem(item,parent){ + let menuItem = $link("handle",item.label,parent,(e) =>{ + if (item.command){ + EventBus.trigger(item.command); + me.deActivateMenu(); + } + if (item.action){ + me.deActivateMenu(); + item.action(); + } + }); + if (item.items){ + menuItem.classList.add("caret"); + let sub = $div("menuitem subsub","",menuItem); + item.items.forEach(subitem=>{ + buildMenuItem(subitem,sub); + }); + } + if (item.needsRealClick) menuItem.waitForClick = true; + if (item.shortKey){ + let k = item.shortKey; + if (k.length>2){ + menuItem.classList.add("wide"); + let meta = isMac?"Cmd":"Ctrl"; + if (k.indexOf("+N")>1) meta = isMac?"Ctrl":"Alt"; + k = k.replace("meta",meta); + if (k.length>8) menuItem.classList.add("ultra"); + } + $div("shortkey",k,menuItem); + } + if (item.info){ + menuItem.classList.add("hasinfo"); + $div("info",item.info,menuItem); + } + if (typeof item.checked !== "undefined"){ + parent.classList.add("checkable"); + EventBus.on(item.command,(e)=>{ + item.checked = !item.checked; + menuItem.classList.toggle("checked"); + }); + } + if (item.checked){ + menuItem.classList.add("checked"); + } + if (item.ref){ + refs[item.command] = menuItem; + } + } + + function generate(){ + $div("hamburger menuitem","DPaint.js",container,()=>{ + container.classList.toggle("active"); + }) + items.forEach((item,index)=>{ + item.element = $link("menuitem main handle",item.label,container,(e) =>{ + me.activateMenu(index); + }); + item.element.addEventListener("pointerenter",(e)=>{ + if (isMenuActive && activeMenu!==index){ + me.activateMenu(index); + } + }) + if (item.items){ + let sub = $div("menuitem sub","",item.element); + item.items.forEach(subitem=>{ + buildMenuItem(subitem,sub); + }); + } + }) + } + + EventBus.on(COMMAND.COLORDEPTH24,()=>{ + refs[COMMAND.COLORDEPTH12].classList.remove("checked"); + refs[COMMAND.COLORDEPTH9].classList.remove("checked"); + }); + EventBus.on(COMMAND.COLORDEPTH12,()=>{ + console.log(refs); + refs[COMMAND.COLORDEPTH24].classList.remove("checked"); + refs[COMMAND.COLORDEPTH9].classList.remove("checked"); + }); + EventBus.on(COMMAND.COLORDEPTH9,()=>{ + refs[COMMAND.COLORDEPTH12].classList.remove("checked"); + refs[COMMAND.COLORDEPTH24].classList.remove("checked"); + }); + + return me; + + +}(); + +export default Menu; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/modal.js b/app/web-tools/dpaint/_script/ui/modal.js new file mode 100644 index 00000000..c8ab8c5e --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/modal.js @@ -0,0 +1,221 @@ +import $,{$div} from "../util/dom.js"; +import UI from "./ui.js"; +import Input from "./input.js"; +import SaveDialog from "./components/saveDialog.js"; +import ResizeDialog from "./components/resizeDialog.js"; +import ResampleDialog from "./components/resampleDialog.js"; +import PaletteDialog from "./components/paletteDialog.js"; +import EffectDialog from "./components/effectDialog.js"; +import DitherDialog from "./components/ditherDialog.js"; +import OptionDialog from "./components/optionDialog.js"; +import EventBus from "../util/eventbus.js"; +import {COMMAND} from "../enum.js"; + +export let DIALOG={ + SAVE: 1, + RESIZE: 2, + RESAMPLE: 3, + PALETTE: 4, + EFFECTS: 5, + ABOUT: 6, + DITHER: 7, + OPTION:8 +} + +var Modal = function(){ + let me = {}; + let blanket; + let modalWindow; + let caption; + let inner; + let currentTranslate = [0,0]; + let currentDialog; + let notification; + + let dialogs={ + 1: {title: "Save File", fuzzy: true,width:400,height:"auto", handler: SaveDialog, position: [0,0]}, + 2: {title: "Canvas Size", fuzzy: true, handler: ResizeDialog, position: [0,0],width:406,height:220}, + 3: {title: "Image Size", fuzzy: true, handler: ResampleDialog, position: [0,0],width:326,height:220}, + 4: {title: "Palette Editor", handler: PaletteDialog, width:450,height:304, position: [0,0]}, + 5: {title: "Effects", handler: EffectDialog, position: [0,0],width:500,height:530}, + 6: {title: "About", action: showAbout, position: [0,0],width:750,height:470}, + 7: {title: "DitherPattern", handler: DitherDialog, position: [0,0],width:662,height:326}, + 8: {title: "Request", fuzzy: true, handler: OptionDialog, position: [0,0],width:300,height:"auto"} + } + + me.show = function(type,data){ + data = data || {}; + let dialog = dialogs[type]; + if (dialog && dialog.fuzzy){ + UI.fuzzy(true); + me.showBlanket(); + } + if (!modalWindow){ + modalWindow = $div("modalwindow","",document.body); + let titleBar = $div("caption","",modalWindow); + caption = $div("handle","Title",titleBar); + $div("button","x",titleBar,()=>{ + me.hide(); + }); + inner = $div("inner","",modalWindow); + caption.onDrag = function(x,y){ + x += currentDialog.position[0]; + y += currentDialog.position[1]; + currentTranslate = [x,y]; + modalWindow.style.transform = "translate("+x+"px,"+y+"px)"; + } + caption.onDragEnd = function(){ + currentDialog.position = [currentTranslate[0],currentTranslate[1]]; + } + } + modalWindow.classList.add("active"); + Input.setActiveKeyHandler(keyHandler); + + if (dialog){ + let width = data.width || dialog.width || 440; + let height = data.height || dialog.height || 260; + + let maxWidth = window.innerWidth - 20; + let maxHeight = window.innerHeight - 20; + if (width > maxWidth) width = maxWidth; + if (height > maxHeight) height = maxHeight; + + modalWindow.style.width = width + "px"; + modalWindow.style.height = height + "px"; + let top = 'calc(50vh - ' + (height>>1) + 'px)'; + if (height==="auto"){ + modalWindow.style.height = "auto"; + top = 'calc(50vh - 150px)'; + inner.classList.remove("full"); + }else{ + inner.classList.add("full"); + } + modalWindow.style.top = top; + modalWindow.style.marginLeft = -(width>>1) + "px"; + currentDialog = dialog; + let x = currentDialog.position[0]; + let y = currentDialog.position[1]; + modalWindow.style.transform = "translate("+x+"px,"+y+"px)"; + caption.innerHTML = data.title || dialog.title; + if (dialog.handler){ + dialog.handler.render(inner,me,data); + } + if (dialog.action){ + dialog.action(data); + } + + }else{ + console.error("no handler"); + } + + } + + me.hide = function(){ + UI.fuzzy(false); + me.hideBlanket(); + if (modalWindow) modalWindow.classList.remove("active"); + Input.setActiveKeyHandler(null); + if (currentDialog && currentDialog.handler && currentDialog.handler.onClose){ + currentDialog.handler.onClose(); + } + currentDialog = undefined; + } + + me.isVisible = function(){ + + } + + me.inputKeyDown = function(e){ + e.stopPropagation(); + } + + me.showBlanket = function(){ + if (!blanket){ + blanket = $div("blanket","",document.body); + } + blanket.classList.add("active"); + } + + me.hideBlanket = function(){ + if (blanket) blanket.classList.remove("active"); + } + + me.showNotification = function(data){ + let delay = 0; + if (!notification){ + notification = $div("notificationbox","",document.body); + delay = 10; + } + notification.innerHTML = ""; + notification.appendChild($div("title",data.title)); + notification.appendChild($div("text",data.text)); + setTimeout(()=>{ + notification.classList.add("active"); + },delay); + + setTimeout(()=>{ + notification.classList.remove("active"); + },3000); + } + + me.hideNotification = function(){ + if (notification) notification.classList.remove("active"); + } + + me.alert = (message, title)=>{ + Modal.show(DIALOG.OPTION,{ + title: title || "Alert", + text: message, + buttons: [{label:"OK"}] + }); + } + + me.softAlert = (message, title)=>{ + Modal.showNotification({ + title: title || "Alert", + text: message + }); + } + + function keyHandler(code){ + switch (code){ + case "escape": + me.hide(); + return true; + case "enter": + let button = inner.querySelector(".button.primary"); + if (button && button.onClick){ + button.onClick(); + }else{ + button = inner.querySelector(".button"); + if (button){ + if (button.onClick){ + button.onClick(); + }else{ + me.hide(); + } + } + } + return true; + } + } + + function showAbout(version){ + inner.innerHTML = ""; + let gallery; + + inner.appendChild($(".about", + $("img",{src:"./_img/dpaint-about.png",onclick:()=>me.hide()}), + $(".text.version","version " + version), + $(".text.info","Webbased image editor modeled after the legendary",$("br"),"Deluxe Paint with a focus on retro Amiga file formats."), + $(".text.copyright.link",{onClick:()=>window.open("https://www.stef.be/")},"© 2023-2024 - Steffest"), + $(".text.github.link",{onClick:()=>window.open("https://github.com/steffest/dpaint-js")},"Open Source - Plain JavaScript - Fork me on GitHub"), + $(".text.nobullshit","No cookies - No tracking - No ads - No accounts"), + $(".text.contrib",$("i","With contributions from"),"Michael Smith and Nicolas Ramz"), + )); + } + + return me; +}(); + +export default Modal \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/palette.js b/app/web-tools/dpaint/_script/ui/palette.js new file mode 100644 index 00000000..d297839a --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/palette.js @@ -0,0 +1,1131 @@ +import $, {$checkbox, $div, $input} from "../util/dom.js"; +import EventBus from "../util/eventbus.js"; +import {ANIMATION, COMMAND, EVENT} from "../enum.js"; +import Color from "../util/color.js"; +import ImageProcessing from "../util/imageProcessing.js"; +import ImageFile from "../image.js"; +import SidePanel from "./sidepanel.js"; +import {duplicateCanvas} from "../util/canvasUtils.js"; +import Animator from "../util/animator.js"; +import ColorRange from "./components/colorRange.js"; +import Modal from "./modal.js"; + +let Palette = function(){ + let me = {}; + var container; + var paletteCanvas; + var paletteCtx; + var paletteNav; + var size = 14; + var currentPalette; + let paletteList = []; + let paletteListIndex = 0; + var alphaThreshold = 44; + let useAlphaThreshold = true; + var ditherIndex = 0; + var targetColorCount = 32; + let palettePageIndex = 0; + let palettePageCount = 1; + let currentHeight; + let colorLayers = {}; + let cycleButton; + let lockButton; + let paletteToolsPanel; + let isLocked = false; + let hasDuplicates = false; + let paletteListIndexElm; + + var drawColor = "black"; + var backgroundColor = "white"; + var drawColorIndex = 1; + var backColorIndex = 0; + let colorDepth = 24; + + var colors = [ + [149,149,149], + [0,0,0], + [255,255,255], + [59,103,162], + [123,123,123], + [175,175,175], + [170,144,124], + [255,169,151] + ] + + + var muiPalette = [ + [149,149,149], + [0,0,0], + [255,255,255], + [59,103,162], + [123,123,123], + [175,175,175], + [170,144,124], + [255,169,151] + ]; + + var grayPalette = [ + [0,0,0], + [36,36,36], + [72,72,72], + [108,108,108], + [144,144,144], + [180,180,180], + [216,216,216], + [255,255,255] + ] + + + // https://pixeljoint.com/forum/forum_posts.asp?TID=12795 + var db16Palette = [ + [20,12,28], + [68,36,52], + [48,52,109], + [78,74,78], + [133,76,48], + [52,101,36], + [208,70,72], + [117,113,97], + [89,125,206], + [210,125,44], + [133,149,161], + [109,170,44], + [210,170,153], + [109,194,202], + [218,212,94], + [222,238,214] + ]; + + // DawnBringer's 32 Col Palette + //http://pixeljoint.com/forum/forum_posts.asp?TID=16247 + var db32Palette = [ + [0,0,0], + [34,32,52], + [69,40,60], + [102,57,49], + [143,86,59], + [223,113,38], + [217,160,102], + [238,195,154], + [251,242,54], + [153,229,80], + [106,190,48], + [55,148,110], + [75,105,47], + [82,75,36], + [50,60,57], + [63,63,116], + [48,96,130], + [91,110,225], + [99,155,255], + [95,205,228], + [203,219,252], + [255,255,255], + [155,173,183], + [132,126,135], + [105,106,106], + [89,86,82], + [118,66,138], + [172,50,50], + [217,87,99], + [215,123,186], + [143,151,74], + [138,111,48] + ]; + + // https://fulifuli.tumblr.com/post/141892525920/grafxkids-today-land-palette-release + var gfxkPalette = [[17,10,3],[17,10,3],[99,13,38],[71,18,92],[37,30,106],[62,40,40],[14,53,62],[40,53,74],[152,30,130],[33,74,167],[181,45,27],[133,62,51],[21,105,72],[28,100,112],[109,93,91],[69,105,117],[10,152,216],[238,98,131],[202,117,56],[219,110,83],[240,117,8],[30,182,120],[89,179,45],[126,174,163],[176,162,141],[249,178,167],[245,185,125],[73,227,218],[251,197,49],[211,229,73],[239,239,233],[255,255,255]]; + + var paletteMap = { + optimized: {label: "Optimized", palette: null}, + current: {label: "Current", palette: null}, + mui: {label: "MUI", palette:muiPalette}, + grays: {label: "Grays 8", palette:grayPalette}, + db8: {label: "DawnBringer 8", palette:[[20, 12, 28],[85, 65, 95],[100, 105, 100],[215, 115, 85],[80, 140, 215],[100, 185, 100],[230, 200, 110],[220, 245, 255]]}, + db16: {label: "DawnBringer 16", palette:db16Palette}, + db32: {label: "DawnBringer 32", palette:db32Palette}, + gfxk16: {label: "Grafxkid 16", palette:[[26, 28, 44],[93, 39, 93],[177, 62, 83],[239, 125, 87],[255, 205, 117],[167, 240, 112],[56, 183, 100],[37, 113, 121],[41, 54, 111],[59, 93, 201],[65, 166, 246],[115, 239, 247],[244, 244, 244],[148, 176, 194],[86, 108, 134],[51, 60, 87]]}, + //gfxk16ocs: {label: "Grafxkid 16 OCS", palette:[ "#000000" , "#443344" , "#BB2244" , "#EE7755" , "#FFCC77" , "#AAFF77","#33BB66","#227777","#112266","#3355CC","#44AAFF","#77EEFF","#FFFFFF","#99BBCC","#556688","#222244"]}, + gfxk: {label: "Grafxkid 32", palette:gfxkPalette}, + tech16ocs: {label: "Tech 16 OCS", palette:["#000000","#222222","#444444","#666666","#888888","#AAAAAA","#CCCCCC","#EEEEEE","#225577","#5599dd","#773333","#bb4433","#447733","#44bb44","#ddaa33","#996622"]}, + pico8: {label: "PICO-8", palette:[[0, 0, 0],[29, 43, 83],[126, 37, 83],[0, 135, 81],[171, 82, 54],[95, 87, 79],[194, 195, 199],[255, 241, 232],[255, 0, 77],[255, 163, 0],[255, 236, 39],[0, 228, 54],[41, 173, 255],[131, 118, 156],[255, 119, 168],[255, 204, 170]]}, + dga:{label: "DGA-16",palette:["#010101","#031b75","#108c00","#17bbd3","#720c0a","#6c1c9e","#b25116","#b8b0a8","#4a4842","#0b63c4","#9bce00","#73f5d5","#e89e00","#ff7bdb","#fef255","#fffffe"]}, + micro: {label: "BBC Micro", platform: true, palette:[[0,0,0],[255,0,0],[0,255,0],[255,255,0],[0,0,255],[255,0,255],[0,255,255],[255,255,255]]}, + pepto: {label: "Pepto (C64)", platform: true, palette:[[0, 0, 0],[255, 255, 255],[104,55,43],[112 ,164 ,178 ],[111 ,61 ,134 ],[88 ,141 ,67],[53 ,40 ,121],[184 ,199 ,111],[111 ,79 ,37],[67 ,57 ,0],[154 ,103 ,89],[68 ,68 ,68],[108 ,108 ,108],[154 ,210 ,132],[108 ,94 ,181],[149 ,149 ,149]]}, + spectrum: {label: "ZX Spectrum", platform: true, palette:[[0,0,0],[0,0,128],[0,0,255],[128,0,0],[255,0,0],[128,0,128],[255,0,255],[0,128,0],[0,255,0],[0,128,128],[0,255,255],[128,128,0],[255,255,0],[128,128,128],[255,255,255]]}, + msx:{label: "MSX",platform:true,palette:"MSX.json"}, + cga:{label: "CGA (IBM PC)",platform:true,palette:"CGA.json"}, + amstrad: {label: "Amstrad CPC", platform: true, palette:"Amstrad-CPC.json"}, + ted:{label: "C= TED/+4/16",platform:true,palette:"TED-Plus4-C16.json"}, + atari2600pal:{label: "Atari 2600 PAL",platform:true,palette:"Atari-2600-PAL.json"}, + atari2600ntsc:{label: "Atari 2600 NTSC",platform:true,palette:"Atari-2600-NTSC.json"}, + atari:{label: "Atari GTIA",platform:true,palette:"Atari-GTIA.json"}, + }; + var targetPalette = null; + + me.init = function(parent,paletteParent){ + container = $div("palette","",parent); + + let display = $div("display","",container); + let colorPicker = $input("color","",display,()=>{ + me.setColor(colorPicker.value,colorPicker.isBack,true); + }); + let front = $div("front info","",display,()=>{ + colorPicker.value = Color.toHex(me.getDrawColor()); + colorPicker.isBack = false; + colorPicker.click(); + }); + front.info = "Left click drawing color - click to pick color"; + let back = $div("back info","",display,()=>{ + colorPicker.value = Color.toHex(me.getBackgroundColor()); + colorPicker.isBack = true; + colorPicker.click(); + }); + back.info = "Right click drawing color - click to pick color"; + + let swapColors = $div("button swapcolors info","",display,()=>{ + EventBus.trigger(COMMAND.SWAPCOLORS); + }); + swapColors.info = "X Swap foreground and background color" + + let noColor = $div("button transparentcolors info","",display,(e)=>{ + me.setColor("transparent",e.button,true); + }); + noColor.info = "Select transparent color, left click to select front, right click to select back"; + + let paletteToolsButton = $(".togglepanel.showpalettetools",{ + parent: container, + onClick: ()=>{ + paletteToolsButton.classList.toggle("active"); + paletteToolsPanel.classList.toggle("hidden"); + me.set(currentPalette); + }, + info:"Show palette tools" + },"Palette"); + + paletteToolsPanel = $('.palettebuttons.hidden',{parent: paletteParent}, + $(".button.edit",{onClick: ()=>{EventBus.trigger(COMMAND.EDITPALETTE)}, info:"Edit palette"},$(".icon")), + cycleButton = $(".button.cycle",{onClick: ()=>{EventBus.trigger(COMMAND.CYCLEPALETTE)}, info:"tab Toggle Color Cycle"},$(".icon")), + lockButton = $(".button.lock",{onClick: ()=>{EventBus.trigger(COMMAND.LOCKPALETTE)}, info:"Lock Palette"},$(".icon")), + $(".button.plus",{onClick: ()=>{EventBus.trigger(COMMAND.ADDPALETTE)}, info:"Add new Palette"},$(".icon")), + ); + + + + $(".paletteListNav",{parent: paletteToolsPanel},$(".nav", + $(".prev.active",{onClick: ()=>{me.setPaletteListIndex(paletteListIndex,-1)},info:"Pg Up Previous Palette"}), + paletteListIndexElm = $(".page","" + (paletteListIndex+1)), + $(".next.active",{ + onClick: ()=>{me.setPaletteListIndex(paletteListIndex,1)},info:"Pg Dn Next Palette" + })) + ); + + paletteCanvas = $("canvas.info.palettecanvas",{ + parent: paletteParent, + info: "Color palette, left click to select front, right click to select back", + infoOnMove:(e)=>{ + const rect = paletteCanvas.getBoundingClientRect(); + const x = Math.floor(e.clientX - rect.left); + const y = Math.floor(e.clientY - rect.top); + let p = paletteCtx.getImageData(x,y,1,1).data; + let color = "r: " + p[0] + " g: " + p[1] + " b: " + p[2] + " "; + return color; + }, + onClick: function(e){ + const rect = paletteCanvas.getBoundingClientRect(); + const x = Math.floor(e.clientX - rect.left); + const y = Math.floor(e.clientY - rect.top); + let p = paletteCtx.getImageData(x,y,1,1).data; + let index = Math.floor(x/size) + Math.floor(y/size) * 4 + palettePageIndex * 4 * Math.floor(currentHeight/size); + if (e.button){ + backColorIndex = index; + }else { + drawColorIndex = index; + } + me.setColor([p[0],p[1],p[2]],e.button,false); + }, + onDoubleClick: function(e){ + EventBus.trigger(COMMAND.EDITPALETTE); + } + }) + + paletteNav = $div("palettenav","",paletteParent); + + paletteCtx = paletteCanvas.getContext("2d"); + me.set(colors); + + EventBus.on(EVENT.drawColorChanged,(color)=>{ + color = Color.fromString(color); + if (color.length > 3 && color[3] === 0) color = "transparent"; + if (color === "transparent"){ + front.style.backgroundColor = color; + front.classList.add("nofill"); + }else{ + front.style.backgroundColor = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ")"; + front.classList.remove("nofill"); + } + }); + EventBus.on(EVENT.backgroundColorChanged,(color)=>{ + color = Color.fromString(color); + if (color.length > 3 && color[3] === 0) color = "transparent"; + if (color === "transparent"){ + back.style.backgroundColor = color; + back.classList.add("nofill"); + }else{ + back.style.backgroundColor = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ")"; + back.classList.remove("nofill"); + } + + + }); + + me.setColor(colors[1],false,true); + me.setColor(colors[2],true,true); + } + + + + me.setColor=function(color,back,matchIndex){ + if (back){ + backgroundColor = Color.toString(color); + EventBus.trigger(EVENT.backgroundColorChanged,color); + if (matchIndex){ + backColorIndex = me.getColorIndex(color,false); + } + }else{ + drawColor = Color.toString(color); + if (matchIndex){ + drawColorIndex = me.getColorIndex(color,false); + } + drawPalette(); + EventBus.trigger(EVENT.drawColorChanged,color); + } + } + + me.getDrawColor = function(){ + return drawColor; + } + + me.getBackgroundColor = function(){ + return backgroundColor; + } + + me.next = function(){ + me.setColorIndex(drawColorIndex+1); + } + + me.prev = function(){ + me.setColorIndex(drawColorIndex-1); + } + + me.setColorIndex = function(index){ + if (currentPalette){ + drawColorIndex = index; + let rangeIndex = ColorRange.getActiveRange(); + if (rangeIndex !== undefined){ + let range = ImageFile.getCurrentFile().colorRange[rangeIndex]; + if (range){ + if (drawColorIndex < range.low) drawColorIndex = range.high; + if (drawColorIndex > range.high) drawColorIndex = range.low; + } + } + + if (drawColorIndex < 0) drawColorIndex = currentPalette.length-1; + if (drawColorIndex >= currentPalette.length) drawColorIndex = 0; + + me.setColor(currentPalette[drawColorIndex],false,false); + } + } + + me.getDrawColorIndex = function(){ + return drawColorIndex; + } + + me.getBackColorIndex = function(){ + return backColorIndex; + } + + me.isLocked = function(){ + return isLocked; + } + + me.set = function(palette){ + let cols = 4; + let rows = Math.ceil(palette.length/cols); + paletteCanvas.width = cols*size; + paletteCanvas.height = rows*size; + palettePageIndex = 0; + palettePageCount = 1; + paletteNav.innerHTML = ""; + let pageNumber,pageNext,pagePrev; + + + let box = paletteCanvas.getBoundingClientRect(); + let parentBox = paletteCanvas.parentElement.getBoundingClientRect(); + let availableHeight = parentBox.height + parentBox.top - box.top - 2; + currentHeight = availableHeight; + if (availableHeight < paletteCanvas.height){ + let rows = Math.floor((availableHeight-22)/size); + paletteCanvas.height = rows*size; + palettePageCount = Math.ceil(palette.length/(cols*rows)); + paletteNav.appendChild($(".nav", + pagePrev = $(".prev",{onClick:()=>{setPage(-1)}}), + pageNumber = $(".page","1"), + pageNext = $(".next.active",{onClick:()=>{setPage(1)}}))); + } + currentPalette = palette; + paletteList[paletteListIndex] = currentPalette.slice(); + scanDuplicates(); + drawPalette(); + + function setPage(index){ + palettePageIndex += index; + if (palettePageIndex < 0) palettePageIndex = 0; + if (palettePageIndex >= palettePageCount) palettePageIndex = palettePageCount-1; + pageNumber.innerHTML = palettePageIndex+1; + pagePrev.classList.toggle("active",palettePageIndex > 0); + pageNext.classList.toggle("active",palettePageIndex < palettePageCount-1); + drawPalette(); + } + } + + function drawPalette(){ + let cols = 4; + let rows = Math.floor(paletteCanvas.height/size); + let start = palettePageIndex * cols * rows; + let end = start + cols * rows; + if (end > currentPalette.length) end = currentPalette.length; + paletteCtx.clearRect(0,0,paletteCanvas.width,paletteCanvas.height); + + for (let i=start;i256 && !targetPalette){ + ImageFile.restoreOriginal(); + }else{ + let base = ImageFile.getOriginal(); + let c = duplicateCanvas(base,true); + if (targetColorCount === 2 && ditherIndex){ + ImageProcessing.bayer(c.getContext("2d"),Math.floor(alphaThreshold*2.56),false); + let layer = ImageFile.getActiveLayer(); + layer.clear(); + layer.drawImage(c,0,0); + EventBus.trigger(EVENT.layerContentChanged,{keepImageCache:true}); + }else{ + ImageProcessing.reduce(c,targetPalette || targetColorCount,alphaThreshold,ditherIndex,useAlphaThreshold); + } + + SidePanel.show("reduce"); + } + } + + me.apply = function(){ + // TODO: this squashes all the layers together :-/ + // probably not what we want ? + let base = ImageFile.getOriginal(); + let c = duplicateCanvas(base,true); + ImageProcessing.reduce(c,currentPalette,alphaThreshold,0,useAlphaThreshold); + } + + me.getColorIndex = function(color,forceMatch){ + var index = currentPalette.findIndex((c)=>{return c[0] === color[0] && c[1] === color[1] && c[2] === color[2]}); + if (index<0 && forceMatch) index=0; + return index; + } + + me.getColor = index=>currentPalette[index]; + + me.matchColor = function(color){ + // find the closest color in the palette + var index = currentPalette.findIndex((c)=>{return c[0] === color[0] && c[1] === color[1] && c[2] === color[2]}); + + if (index<0){ + var min = 100000; + var match = 0; + for (var i=0;i index2){ + let t = index1; + index1 = index2; + index2 = t; + } + if (index2-index1 === 1) return; + + let steps = index2-index1-1; + let c1 = currentPalette[index1]; + let c2 = currentPalette[index2]; + let delta = [c2[0]-c1[0],c2[1]-c1[1],c2[2]-c1[2]]; + for (let i=1;i<=steps;i++){ + let color = [c1[0]+Math.floor(delta[0]*i/(steps+1)),c1[1]+Math.floor(delta[1]*i/(steps+1)),c1[2]+Math.floor(delta[2]*i/(steps+1))]; + currentPalette[index1+i] = color; + } + me.set(currentPalette); + EventBus.trigger(EVENT.paletteChanged); + } + + me.removeColor = function(index){ + currentPalette.splice(index,1); + me.set(currentPalette); + EventBus.trigger(EVENT.paletteChanged); + } + + me.addColor = function(color){ + currentPalette.push(color); + me.set(currentPalette); + EventBus.trigger(EVENT.paletteChanged); + } + + + me.sortByHue = function(){ + currentPalette.sort((a,b)=>{ + let h1 = Color.hue(a); + let h2 = Color.hue(b); + if (h1h2) return -1; + let l1 = Color.lightness(a); + let l2 = Color.lightness(b); + return l1l2?-1:0; + }); + me.set(currentPalette); + EventBus.trigger(EVENT.paletteChanged); + } + + me.sortByLightness = function(){ + currentPalette.sort((a,b)=>{ + let l1 = Color.lightness(a); + let l2 = Color.lightness(b); + return l1l2?-1:0; + }); + me.set(currentPalette); + EventBus.trigger(EVENT.paletteChanged); + } + + me.sortBySaturation = function(){ + currentPalette.sort((a,b)=>{ + let s1 = Color.saturation(a); + let s2 = Color.saturation(b); + return s1s2?-1:0; + }); + me.set(currentPalette); + EventBus.trigger(EVENT.paletteChanged); + } + + me.sortByUseCount = function(andTest){ + ImageFile.generateIndexedPixels(); + let image = ImageFile.getCurrentFile(); + let pixels = image.indexedPixels || []; + let counts = {}; + pixels.forEach(line=>{ + line.forEach(pixel=>{ + counts[pixel] = counts[pixel] || 0; + counts[pixel]++; + }) + }); + let sorted = Object.keys(counts).sort((a,b)=>counts[b]-counts[a]); + let palette = []; + sorted.forEach(pixel=>{ + palette.push(currentPalette[pixel]); + }); + me.set(palette); + } + + me.reverse = function(){ + currentPalette.reverse(); + me.set(currentPalette); + EventBus.trigger(EVENT.paletteChanged); + } + + me.generateControlPanel = function(parent){ + + let palettePanel = $div("subpanel","",parent); + $div("label","Palette",palettePanel); + let pselect = document.createElement("select"); + Object.keys(paletteMap).forEach((key,index)=>{ + let option = document.createElement("option"); + option.value=key; + option.innerHTML=paletteMap[key].label; + pselect.appendChild(option); + }); + pselect.onchange = function(){ + targetPalette = paletteMap[pselect.value].palette; + if (pselect.value === "current") targetPalette = currentPalette; + if (typeof targetPalette === "string"){ + me.loadPreset(paletteMap[pselect.value]).then(palette=>{ + paletteMap[pselect.value].palette = palette; + targetPalette = palette; + me.reduce(); + }) + }else{ + applyReduce(); + } + + } + palettePanel.appendChild(pselect); + + let fixed = $div("subpanel","",parent); + $div("label","Optimize colors",fixed); + let value = $div("value","32",fixed); + let range = document.createElement("input"); + range.type = "range"; + range.value = 5; + range.min = 1; + range.max = 9; + range.oninput = function(){ + targetColorCount = Math.pow(2,range.value); + value.innerHTML = range.value<9?targetColorCount:"Full"; + } + range.onchange = function(){ + targetColorCount = Math.pow(2,range.value); + pselect.value = "optimized"; + targetPalette = null; + applyReduce(); + } + fixed.appendChild(range); + + let dithering = $div("subpanel","",parent); + $div("label","Dithering",dithering); + + let select = document.createElement("select"); + select.value=0; + let options = ImageProcessing.getDithering(); + options.forEach((o,index)=>{ + let option = document.createElement("option"); + option.value=index; + option.innerHTML=o.label; + select.appendChild(option); + }); + select.onchange = function(){ + ditherIndex = select.value; + applyReduce(); + } + $div("button square prev","<",dithering,()=>{ + let v = parseInt(select.value)-1; + if (v<0)v=options.length-1; + select.value = v; + select.onchange(); + }); + $div("button square next",">",dithering,()=>{ + let v = parseInt(select.value)+1; + if (v>=options.length)v=0; + select.value = v; + select.onchange(); + }); + + dithering.appendChild(select); + + let alpha = $div("subpanel","",parent); + $checkbox("Alpha Threshold",alpha,"label small",(value)=>{ + useAlphaThreshold = value; + arange.disabled = !useAlphaThreshold; + applyReduce(); + },useAlphaThreshold); + //$div("label","Alpha Threshold",alpha ); + let avalue = $div("value",alphaThreshold + "%",alpha ); + let arange = document.createElement("input"); + arange.type = "range"; + arange.value = alphaThreshold; + arange.min = 0; + arange.max = 100; + arange.oninput = function(){ + avalue.innerHTML = arange.value + "%"; + } + arange.onchange = function(){ + alphaThreshold = arange.value; + applyReduce(); + } + alpha.appendChild(arange); + + + let button = $div("button full","Apply",parent,applyReduce); + + function applyReduce(){ + if (pselect.value === "current") targetPalette = currentPalette; + me.reduce(); + } + } + + me.openLocal = function(){ + var input = document.createElement('input'); + input.type = 'file'; + input.onchange = function(e){ + let files = e.target.files; + if (files.length){ + var file = files[0]; + var ext = file.name.split(".").pop().toLowerCase(); + if (ext === "json"){ + var reader = new FileReader(); + reader.onload = function(){ + let data = {}; + try{ + data = JSON.parse(reader.result); + }catch(error){ + console.error("Error parsing Palette"); + }; + if (data && data.type === "palette" && data.palette){ + me.set(data.palette); + EventBus.trigger(EVENT.paletteChanged); + } + + } + reader.readAsText(file); + } + } + }; + input.click(); + } + + me.getPaletteMap = function(){ + return paletteMap; + } + + me.hasDuplicates = function(){ + return hasDuplicates; + } + + me.loadPreset = function(preset){ + return new Promise((resolve)=>{ + if (preset && preset.palette){ + if (typeof preset.palette === "object"){ + resolve(preset.palette); + return; + } + if (typeof preset.palette === "string"){ + console.log("loading palette",preset.palette); + fetch("_data/palettes/"+preset.palette).then(r=>r.json()).then(data=>{ + if (data && data.palette){ + preset.palette = data.palette; + resolve(data.palette); + }else{ + console.error("Error loading palette",preset.palette); + resolve([]); + } + }); + return; + } + } + resolve([]) + }); + } + + me.addPalette = function(){ + paletteList.push(currentPalette.slice()); + me.setPaletteListIndex(paletteList.length-1); + } + + me.setPaletteListIndex = function(index,offset){ + + let fromPalette = currentPalette.slice() + + paletteListIndex = index + (offset || 0); + if (paletteListIndex<0) paletteListIndex = paletteList.length-1; + if (paletteListIndex>=paletteList.length) paletteListIndex = 0; + if (paletteListIndexElm) paletteListIndexElm.innerHTML = paletteListIndex+1; + me.set(paletteList[paletteListIndex]); + let toPalette = currentPalette.slice(); + let map = {}; + let hasChanges = false; + fromPalette.forEach((color,index)=>{ + let t = Color.toString(toPalette[index]); + let c = Color.toString(color); + if (t!==c) hasChanges = true; + map[c] = t; + }); + if (hasChanges){ + me.reMap(map); + me.setColor(currentPalette[drawColorIndex],false,false); + me.setColor(currentPalette[backColorIndex],true,false); + } + } + + me.reMap = function(map){ + let image = ImageFile.getCurrentFile(); + let layers = ImageFile.getActiveFrame().layers; + layers.forEach(layer=>{ + console.log("remap",layer.name); + let ctx = layer.getContext(); + let data = ctx.getImageData(0,0,image.width,image.height); + for (let i=0;irange.active); + if (!active){ + Modal.softAlert("There are no active color ranges.","Can't Start Color Cycle"); + return; + } + + let imageData = ImageFile.getContext().getImageData(0,0,image.width,image.height); + let data = imageData.data; + + let hasLayers = ImageFile.getActiveFrame().layers && ImageFile.getActiveFrame().layers.length>1; + // probably a good idea to move the color cycling to a separate layer anyway + hasLayers = true; + + if (Animator.isRunning(ANIMATION.CYCLE)){ + Animator.stop(ANIMATION.CYCLE); + if (hasLayers){ + let cycleLayer = ImageFile.getActiveFrame().layers.findIndex(l=>l.name === "Colour Cycling" && l.locked); + if (cycleLayer>=0){ + ImageFile.removeLayer(cycleLayer); + } + } + image.colorRange.forEach((range,index)=>{ + if (range.active){ + range.index = 0; + updateRangeColors(range,data); + EventBus.trigger(EVENT.colorCycleChanged,index); + if (!hasLayers){ + ImageFile.getContext().putImageData(imageData,0,0); + } + EventBus.trigger (EVENT.imageContentChanged); + } + }); + drawPalette(); + }else{ + + if (hasLayers){ + // add temporary layer to combine all layers and use for color cycling + ImageFile.addLayer(undefined,"Colour Cycling",{locked:true,internal:true}); + } + let renderContext = ImageFile.getLayer(ImageFile.getActiveFrame().layers.length-1).getContext(); + + generateColorLayers(); + image.colorRange.forEach((range,index)=>{ + let fps = Math.abs(range.fps || 10); + if (range.active) Animator.start(ANIMATION.CYCLE,()=>{ + if (range.reverse){ + range.index--; + if (range.index<0) range.index=range.max-1; + }else{ + range.index++; + if (range.index>=range.max) range.index=0; + } + + updateRangeColors(range,data); + EventBus.trigger(EVENT.colorCycleChanged,index); + renderContext.putImageData(imageData,0,0); + EventBus.trigger (EVENT.imageContentChanged); + + },fps); + }); + + + } + EventBus.trigger (EVENT.colorCycleToggled); + }else{ + Modal.softAlert("There are no color ranges defined.","Can't Start Color Cycle"); + } + } + + me.isCycling = function(){ + return Animator.isRunning(ANIMATION.CYCLE); + } + + me.getColorDepth = function(){ + return colorDepth; + } + + function generateColorLayers(){ + colorLayers = {}; + let image = ImageFile.getCurrentFile(); + + let regeneratePixels = true; + if (regeneratePixels) ImageFile.generateIndexedPixels(); + let pixels = image.indexedPixels || []; + + image.colorRange.forEach(range=>{ + range.index = 0; + range.max = range.high-range.low+1; + }) + + function isInRange(pixel){ + let inRange = false; + image.colorRange.forEach(range=>{ + if (range.active && pixel>=range.low && pixel<=range.high){ + inRange = true; + } + }); + return inRange; + } + + + // put every active pixel in a color layer + pixels.forEach((line,y)=>{ + line.forEach((pixel,x)=>{ + if (isInRange(pixel)){ + colorLayers[pixel] = colorLayers[pixel] || []; + colorLayers[pixel].push(y*image.width+x); + } + }) + }) + } + + function updateRangeColors(range,data){ + for (let i=range.low;i<=range.high;i++){ + let colorIndex = i - range.index; + if (colorIndex{ + let offset = index*4; + data[offset] = color[0]; + data[offset+1] = color[1]; + data[offset+2] = color[2]; + data[offset+3] = 255; + }); + } + } + + function scanDuplicates(){ + hasDuplicates = false; + for (let i=0;i{ + return index!==i && c[0]===color[0] && c[1]===color[1] && c[2]===color[2]; + }); + if (match>=0){ + hasDuplicates = true; + break; + } + } + if (hasDuplicates){ + console.warn("Palette has duplicates"); + } + } + + function reduceBits(bits){ + bits = bits || 24; + colorDepth = bits; + bits = Math.floor(bits/3); + currentPalette.forEach((color,index)=>{ + currentPalette[index] = Color.setBitDepth(color,bits); + }); + me.set(currentPalette); + EventBus.trigger(EVENT.paletteChanged); + me.apply(); + EventBus.trigger(EVENT.colorDepthChanged); + } + + + EventBus.on(COMMAND.PALETTEFROMIMAGE,me.fromImage); + EventBus.on(COMMAND.PALETTEREDUCE,me.reduce); + EventBus.on(COMMAND.SWAPCOLORS,()=>{ + let c = drawColor; + me.setColor(backgroundColor,false,false); + me.setColor(c,true,false); + c = drawColorIndex; + drawColorIndex = backColorIndex; + backColorIndex = c; + }) + EventBus.on(EVENT.UIresize,()=>{ + let box = paletteCanvas.getBoundingClientRect(); + let parentBox = paletteCanvas.parentElement.getBoundingClientRect(); + let availableHeight = parentBox.height + parentBox.top - box.top - 2; + if (availableHeight !== currentHeight){ + me.set(currentPalette); + } + }); + + EventBus.on(EVENT.colorCycleChanged,(index)=>{ + let image = ImageFile.getCurrentFile(); + let range = image.colorRange[index]; + for (let i=range.low;i<=range.high;i++){ + let index = i-(range.index || 0); + if (index=start && i{ + if (cycleButton){ + cycleButton.classList.toggle("active",Animator.isRunning(ANIMATION.CYCLE)); + } + }) + + EventBus.on(COMMAND.LOCKPALETTE,()=>{ + isLocked = !isLocked; + lockButton.classList.toggle("active",isLocked); + }); + + EventBus.on(COMMAND.CYCLEPALETTE,me.cycle); + + EventBus.on(EVENT.layerContentChanged,(options)=>{ + options = options || {}; + if (me.isCycling() && options.commit){ + // toggle cycle to regenerate the color cycle layer after draw + me.cycle(); + me.cycle(); + } + }); + + EventBus.on(COMMAND.COLORDEPTH24,()=>{ + reduceBits(24); + }); + + EventBus.on(COMMAND.COLORDEPTH12,()=>{ + reduceBits(12); + }); + + EventBus.on(COMMAND.COLORDEPTH9,()=>{ + reduceBits(9); + }); + + EventBus.on(COMMAND.ADDPALETTE,()=>{ + me.addPalette(); + }); + + EventBus.on(COMMAND.NEXTPALETTE,()=>{ + me.setPaletteListIndex(paletteListIndex,1); + }); + + EventBus.on(COMMAND.PREVPALETTE,()=>{ + me.setPaletteListIndex(paletteListIndex,1); + }); + + + return me; +}(); + +export default Palette; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/selection.js b/app/web-tools/dpaint/_script/ui/selection.js new file mode 100644 index 00000000..a6f0b160 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/selection.js @@ -0,0 +1,169 @@ +import Brush from "./brush.js"; +import EventBus from "../util/eventbus.js"; +import {COMMAND, EVENT} from "../enum.js"; +import ImageFile from "../image.js"; +import {duplicateCanvas} from "../util/canvasUtils.js"; + +/* +Selection holds the data of the current selected pixels. +it always has a left,top,width, height of the bounding box. + +if it has nothing more, selection is a rectangle +if it has a "points" array it's a polygon +if it has a canvas object, it is based on (the alpha layer of) the canvas. + */ + +let Selection = function(){ + let me = {}; + let currentSelection; + + me.set = function(selection){ + currentSelection = selection || {}; + EventBus.trigger(EVENT.selectionChanged); + } + + me.move = function(x,y,w,h){ + currentSelection = { + left: x, + top: y, + width: w, + height: h + } + EventBus.trigger(EVENT.selectionChanged); + } + + me.get = function(){ + return currentSelection; + } + + me.clear = function(){ + let hasSelection = !!currentSelection; + currentSelection = undefined; + if (hasSelection) EventBus.trigger(EVENT.selectionChanged); + } + + me.selectAll = function(){ + let w = ImageFile.getCurrentFile().width; + let h = ImageFile.getCurrentFile().height; + EventBus.trigger(COMMAND.CLEARSELECTION); + EventBus.trigger(COMMAND.SELECT); + me.set({left: 0, top: 0, width: w, height: h}); + } + + me.getCanvas = function(){ + // renders the current selection to canvas + if (currentSelection){ + if (currentSelection.canvas){ + return duplicateCanvas(currentSelection.canvas,true); + }else if (currentSelection.points && currentSelection.points.length){ + let result = document.createElement("canvas"); + result.width = ImageFile.getCurrentFile().width; + result.height = ImageFile.getCurrentFile().height; + let ctx=result.getContext("2d"); + ctx.fillStyle = "black"; + ctx.beginPath(); + currentSelection.points.forEach((point,index)=>{ + if (index){ + ctx.lineTo(point.x,point.y); + }else{ + ctx.moveTo(point.x,point.y); + } + }); + ctx.closePath(); + ctx.fill(); + return result; + } + } + console.error("No selection to convert to canvas"); + } + + me.toCanvas = function(){ + // TODO: non rectangular selections + if (currentSelection){ + let canvas = document.createElement("canvas"); + canvas.width = currentSelection.width; + canvas.height = currentSelection.height; + let ctx = canvas.getContext("2d"); + ctx.imageSmoothingEnabled = false; + ctx.drawImage(ImageFile.getActiveLayer().getCanvas(),currentSelection.left,currentSelection.top,canvas.width,canvas.height,0,0, canvas.width, canvas.height); + return canvas; + } + } + + me.toStamp = function(){ + let canvas = me.toCanvas(); + if (canvas){ + Brush.set("canvas",canvas); + EventBus.trigger(COMMAND.DRAW); + EventBus.trigger(COMMAND.CLEARSELECTION); + } + } + + me.toLayer = function(andCut){ + if (currentSelection){ + let canvas = ImageFile.getActiveLayer().getCanvas(); + let sourceLayerIndex = ImageFile.getActiveLayerIndex(); + ImageFile.duplicateLayer(); + let layer = ImageFile.getActiveLayer(); + + if (currentSelection.points || currentSelection.canvas){ + // draw on Mask + layer.addMask(); + layer.toggleMask(); + let ctx = layer.getContext(); + ctx.fillStyle = "black"; + ctx.fillRect(0,0,canvas.width,canvas.height); + + if (currentSelection.points){ + // draw Polygon on Mask + ctx.fillStyle = "white"; + ctx.beginPath(); + currentSelection.points.forEach((point,index)=>{ + if (index){ + ctx.lineTo(point.x,point.y); + }else{ + ctx.moveTo(point.x,point.y); + } + }); + ctx.closePath(); + ctx.fill(); + } + + if (currentSelection.canvas){ + ctx.drawImage(currentSelection.canvas,0,0); + } + + layer.update(); + layer.removeMask(true); + }else{ + // rectangle + layer.clear(); + layer.getContext().drawImage(canvas,currentSelection.left,currentSelection.top,currentSelection.width,currentSelection.height,currentSelection.left,currentSelection.top, currentSelection.width, currentSelection.height); + } + + if (andCut){ + ImageFile.activateLayer(sourceLayerIndex); + EventBus.trigger(COMMAND.CLEAR); + ImageFile.activateLayer(sourceLayerIndex+1); + } + + + EventBus.trigger(EVENT.layerContentChanged); + EventBus.trigger(COMMAND.CLEARSELECTION); + } + } + + EventBus.on(COMMAND.SELECTALL,me.selectAll); + EventBus.on(COMMAND.CLEARSELECTION,me.clear) + EventBus.on(COMMAND.STAMP,me.toStamp) + EventBus.on(COMMAND.TOLAYER,me.toLayer) + + EventBus.on(COMMAND.CUTTOLAYER,()=>{ + me.toLayer(true); + }) + + + return me; +}() + +export default Selection \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/sidepanel.js b/app/web-tools/dpaint/_script/ui/sidepanel.js new file mode 100644 index 00000000..51a001a7 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/sidepanel.js @@ -0,0 +1,171 @@ +import {COMMAND, EVENT} from "../enum.js"; +import {$div} from "../util/dom.js"; +import ImageProcessing from "../util/imageProcessing.js"; +import ImageFile from "../image.js"; +import EventBus from "../util/eventbus.js"; +import Palette from "./palette.js"; +import LayerPanel from "./toolPanels/layerPanel.js"; +import FramesPanel from "./toolPanels/framesPanel.js"; +import BrushPanel from "./toolPanels/brushPanel.js"; +import ColorPicker from "./components/colorPicker.js"; +import GridPanel from "./toolPanels/gridPanel.js"; + +var SidePanel = function(){ + let me = {} + let container; + let collapsedHeight = 21; + + let panels = { + info:{ + label: "Info", + height: 84 + }, + frames:{ + label: "Frames", + height: 116, + content: parent=>{ + FramesPanel.generate(parent); + } + }, + layers:{ + label: "Layers", + height: 180, + content: parent=>{ + LayerPanel.generate(parent); + } + }, + brush:{ + label: "Brush", + height: 118, + content: parent=>{ + BrushPanel.generate(parent); + } + }, + color:{ + label: "Color", + height: 142, + content: parent=>{ + ColorPicker.generate(parent); + } + }, + grid:{ + label: "Grid", + height: 120, + content: parent=>{ + GridPanel.generate(parent); + } + }, + reduce:{ + label: "Reduce Colors", + collapsed: true, + height: 270, + content: parent=>{ + Palette.generateControlPanel(parent); + } + } + } + + me.init = parent=>{ + container = $div("sidepanel"); + parent.appendChild(container); + generate(); + } + + me.show = (section)=>{ + document.body.classList.add("withsidepanel"); + if (section){ + Object.keys(panels).forEach(key=>{ + if (key === section){ + panels[key].collapsed = false; + } + }) + setPanelsState(); + } + } + + me.hide = ()=>{ + document.body.classList.remove("withsidepanel"); + } + + me.toggle = ()=>{ + document.body.classList.toggle("withsidepanel"); + EventBus.trigger(EVENT.UIresize); + } + + me.isVisible = ()=>{ + return document.body.classList.contains("withsidepanel"); + } + + me.showInfo = (file)=>{ + let contentPanel = panels.info.container.querySelector(".inner"); + if (contentPanel){ + if (file && file.width){ + contentPanel.innerHTML = ""; + generateInfoLine("Width",file.width + "px",contentPanel); + generateInfoLine("Height",file.height + "px",contentPanel); + generateInfoLine("Colors",ImageProcessing.getColors(ImageFile.getCanvas()).length,contentPanel); + }else{ + contentPanel.innerHTML = "No file present"; + } + } + FramesPanel.list(); + me.show(); + } + + function generate(){ + let y = 0; + Object.keys(panels).forEach(key=>{ + let panel = panels[key]; + let height = panel.collapsed?collapsedHeight:(panel.height || 100); + panel.container = generatePanel(panel,container); + panel.container.style.height = height + "px"; + panel.container.style.top = y + "px"; + y+= height; + }) + } + + function generatePanel(panelInfo,parent){ + let panel = $div("panel " + panelInfo.label.toLowerCase() + (panelInfo.collapsed?' collapsed':''),"",parent); + let caption = $div("caption"," " + panelInfo.label,panel,()=>{ + panelInfo.collapsed = !panelInfo.collapsed; + setPanelsState(); + }); + let close = $div("close info","x",caption,()=>EventBus.trigger(COMMAND.TOGGLESIDEPANEL)); + close.info = "Close side panels"; + let inner = $div("inner","",panel); + if (panelInfo.content){ + panelInfo.content(inner); + } + return panel; + } + + function setPanelsState(){ + let y = 0; + Object.keys(panels).forEach(key=>{ + let panel = panels[key]; + let height = (panel.height || 100); + if (panel.collapsed) height=collapsedHeight; + panel.container.style.height = height + "px"; + panel.container.style.top = y + "px"; + panel.container.classList.toggle("collapsed",!!panel.collapsed); + y+= height; + }) + } + + function generateInfoLine(label,value,parent){ + let line = document.createElement("dl"); + let dt = document.createElement("dt"); + dt.innerHTML = label; + let dd = document.createElement("dd"); + dd.innerHTML = value; + line.appendChild(dt); + line.appendChild(dd); + parent.appendChild(line); + } + + EventBus.on(COMMAND.TOGGLESIDEPANEL,me.toggle); + + return me; +}() + +export default SidePanel; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/statusbar.js b/app/web-tools/dpaint/_script/ui/statusbar.js new file mode 100644 index 00000000..e1d03def --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/statusbar.js @@ -0,0 +1,27 @@ +import {$div} from "../util/dom.js"; + +let StatusBar = (()=>{ + let me = {}; + let container; + let toolTip; + let overide = false; + + me.init = (parent)=>{ + container = $div("statusbar","",parent); + toolTip = $div("tooltip","",container); + } + + me.setToolTip = (text)=>{ + if (overide) return; + toolTip.innerHTML = text; + } + + me.overideToolTip = (text)=>{ + overide = true; + toolTip.innerHTML = text; + } + + return me; +})(); + +export default StatusBar; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/toolPanels/brushPanel.js b/app/web-tools/dpaint/_script/ui/toolPanels/brushPanel.js new file mode 100644 index 00000000..9d74e10f --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/toolPanels/brushPanel.js @@ -0,0 +1,132 @@ +import {$div, $input, $elm} from "../../util/dom.js"; +import Brush from "../brush.js"; +import EventBus from "../../util/eventbus.js"; +import {EVENT} from "../../enum.js"; +import DitherPanel from "./ditherPanel.js"; +import Modal, {DIALOG} from "../modal.js"; + +let BrushPanel = function(){ + let me = {}; + let sizeRange; + let sizeInput; + let softRange; + let opacityRange; + let opacityInput; + let ditherPatterns = []; + + me.generate = (parent)=> { + + let sizeSelect = $div("rangeselect", "", parent); + $elm("label","Size",sizeSelect); + sizeRange = $input("range",0,sizeSelect,()=>{ + update(); + sizeInput.value = sizeRange.value; + }); + sizeInput = $input("text",1,sizeSelect); + sizeRange.min = 1; + sanitize(sizeInput,sizeRange); + + let softSelect = $div("rangeselect", "", parent); + $elm("label","Softness",softSelect); + softRange = $input("range",0,softSelect,()=>{ + update(); + softInput.value = softRange.value; + }); + let softInput = $input("text",0,softSelect); + softRange.max = 10; + sanitize(softInput,softRange); + + let opacitySelect = $div("rangeselect", "", parent); + $elm("label","Opacity",opacitySelect); + opacityRange = $input("range",100,opacitySelect,()=>{ + update(); + opacityInput.value = opacityRange.value; + }); + opacityInput = $input("text",100,opacitySelect); + opacityRange.min = 1; + sanitize(opacityInput,opacityRange); + + let ditherPanel = $div("dither",'',parent); + $elm("label","Dither",ditherPanel); + let patterns = $div("patterns","",ditherPanel); + + ditherPatterns = []; + for (let i = 0; i<5; i++){ + let hasPattern = (i<4 && i>0)? " hasPattern" : ""; + ditherPatterns.push($div("pattern p"+i+hasPattern,"",patterns,()=>{ + if (i === 4){ + Modal.show(DIALOG.DITHER); + }else{ + DitherPanel.setDitherPattern(i); + } + })) + } + ditherPatterns[0].classList.add("active"); + + + + + }; + + function update(){ + let size = parseInt(sizeRange.value); + let soft = parseInt(softRange.value); + let opacity = parseInt(opacityRange.value); + Brush.set("dynamic",{width: size, height: size, softness: soft, opacity: opacity}); + } + + me.set = function(settings){ + let doUpdate = false; + if (settings.size){ + sizeRange.value = settings.size; + doUpdate=true; + } + if (settings.opacity){ + opacityRange.value = settings.opacity; + doUpdate=true; + } + if (doUpdate) update(); + } + + function sanitize(input,range){ + range.min = range.min || 0; + range.max = range.max || 100; + + input.onkeydown = (e)=>{ + e.stopPropagation(); + } + + input.oninput = (e)=>{ + let val = parseInt(input.value); + if (isNaN(val)) val = range.min; + if (valrange.max) val = range.max; + range.value = val; + update(); + } + } + + EventBus.on(EVENT.brushOptionsChanged,()=>{ + let settings = Brush.getSettings(); + sizeRange.value = settings.width; + sizeInput.value = settings.width; + opacityRange.value = settings.opacity; + opacityInput.value = settings.opacity; + + let invert = DitherPanel.getDitherInvertState(); + ditherPatterns.forEach(elm=>{ + elm.classList.remove("active"); + elm.classList.toggle("invert",invert); + }) + let ditherIndex = DitherPanel.getDitherIndex(); + let elm = ditherPatterns[ditherIndex]; + if (elm){ + elm.classList.add("active"); + } + + }) + + return me; +}(); + +export default BrushPanel; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/toolPanels/ditherPanel.js b/app/web-tools/dpaint/_script/ui/toolPanels/ditherPanel.js new file mode 100644 index 00000000..178e77f4 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/toolPanels/ditherPanel.js @@ -0,0 +1,154 @@ +import ImageFile from "../../image.js"; +import EventBus from "../../util/eventbus.js"; +import {COMMAND, EVENT} from "../../enum.js"; + +let DitherPanel = function(){ + let me = {}; + let ditherPattern; + let ditherCtx; + let dither; + let invert = false; + let ditherIndex = 3; + let patternCanvas = document.createElement("canvas"); + patternCanvas.width = 4; + patternCanvas.height = 4; + + let patterns=[ + [0,1,0,1, + 0,0,0,0, + 0,1,0,1, + 0,0,0,0], + [0,1,0,1, + 1,0,0,0, + 0,1,0,1, + 0,0,1,0], + [0,1,0,1, + 1,0,1,0, + 0,1,0,1, + 1,0,1,0], + [0,1,0,1, + 1,1,1,0, + 0,1,0,1, + 1,0,1,1], + [1,1,1,1, + 0,0,0,0, + 1,1,1,1, + 0,0,0,0], + [1,0,1,0, + 1,0,1,0, + 1,0,1,0, + 1,0,1,0], + [1,0,0, + 0,1,0, + 0,0,1], + [1,1,0,0, + 0,0,1,1, + 1,1,0,0, + 0,0,1,1] + ] + + me.getDitherPattern = ()=>{ + if (!ditherPattern) generateDitherPattern(); + return ditherPattern; + } + + me.setDitherPattern = (pattern)=>{ + dither = !!pattern; + if (dither){ + if (typeof pattern === "number"){ + ditherIndex = pattern; + generateDitherPattern(); + } + if (typeof pattern === "object"){ + generateDitherPattern(pattern); + } + } + EventBus.trigger(EVENT.brushOptionsChanged); + } + + me.setDitherState = (state)=>{ + dither = !!state; + EventBus.trigger(EVENT.brushOptionsChanged); + } + + me.getDitherState = ()=>{ + return dither; + } + + me.setDitherInvertState = (state)=>{ + invert = !!state; + EventBus.trigger(EVENT.brushOptionsChanged); + ditherPattern = undefined; + } + + me.getDitherInvertState = ()=>{ + return invert; + } + + me.getDitherIndex = ()=>{ + return dither ? ditherIndex : 0; + } + + function generateDitherPattern(fromCanvas){ + if (!ditherPattern){ + ditherPattern = document.createElement("canvas"); + ditherPattern.width = ImageFile.getCurrentFile().width; + ditherPattern.height = ImageFile.getCurrentFile().height; + ditherCtx = ditherPattern.getContext("2d"); + + } + + ditherCtx.clearRect(0,0,ditherPattern.width,ditherPattern.height); + + let pCtx = patternCanvas.getContext("2d"); + if (fromCanvas){ + let s = fromCanvas.width; + patternCanvas.width = s; + patternCanvas.height = s; + pCtx.clearRect(0,0,s,s); + pCtx.drawImage(fromCanvas,0,0); + }else{ + let grid = patterns[ditherIndex-1]; + let s = grid.length === 9?3:4; + patternCanvas.width = s; + patternCanvas.height = s; + pCtx.clearRect(0,0,s,s); + pCtx.fillStyle = "black"; + for (let y = 0;y{ + ditherPattern = undefined; + }) + + EventBus.on(COMMAND.TOGGLEDITHER,()=>{ + dither = !dither; + EventBus.trigger(EVENT.brushOptionsChanged); + }) + + EventBus.on(COMMAND.TOGGLEINVERT,()=>{ + invert = !invert; + ditherPattern = undefined; + EventBus.trigger(EVENT.brushOptionsChanged); + }) + + return me; +}() + +export default DitherPanel; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/toolPanels/framesPanel.js b/app/web-tools/dpaint/_script/ui/toolPanels/framesPanel.js new file mode 100644 index 00000000..e38b1c90 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/toolPanels/framesPanel.js @@ -0,0 +1,137 @@ +import ImageFile from "../../image.js"; +import $,{$div} from "../../util/dom.js"; +import EventBus from "../../util/eventbus.js"; +import {COMMAND, EVENT} from "../../enum.js"; +import ContextMenu from "../components/contextMenu.js"; +import Input from "../input.js"; +import Cursor from "../cursor.js"; + +let FramesPanel = function(){ + let me = {}; + let contentPanel; + let frames=[]; + + me.generate = (parent)=>{ + $(".paneltools",{parent:parent}, + $(".button.delete",{ + onClick:()=>{EventBus.trigger(COMMAND.DELETEFRAME)}, + info: "Delete active frame"}), + $(".button.add",{ + onClick:()=>{EventBus.trigger(COMMAND.ADDFRAME)}, + info: "Add new frame"}), + ); + + contentPanel = $div("panelcontent","",parent); + + } + + me.list = ()=>{ + contentPanel.innerHTML = ""; + frames=[]; + let activeIndex = ImageFile.getActiveFrameIndex() || 0; + let imageFile = ImageFile.getCurrentFile(); + if (imageFile && imageFile.frames){ + let hasMultipleItems = imageFile.frames.length>1; + let max = imageFile.frames.length-1; + imageFile.frames.forEach((frame,index)=>{ + let elm = $div("frame info" + ((activeIndex === index && hasMultipleItems) ? " active":""),"",contentPanel,()=>{ + ImageFile.activateFrame(index); + }); + elm.currentIndex = elm.targetIndex = index; + elm.id = "frame" + index; + elm.style.left = (index*52) + "px"; + elm.info = "Click to activate, drag to reorder, right click for more options"; + + let canvas = document.createElement("canvas"); + canvas.width = 48; + canvas.height = 48; + canvas.getContext("2d").drawImage(ImageFile.getCanvas(index),0,0,48,48); + elm.appendChild(canvas); + + elm.onDragStart = (e)=>{ + let dupe = $div("dragelement box frame","Frame " + index); + Input.setDragElement(dupe); + let currentTarget = contentPanel.querySelector("#frame" + elm.currentIndex); + if (currentTarget){ + currentTarget.startDragX = (currentTarget.currentIndex*52) + e.clientX - currentTarget.getBoundingClientRect().left; + } + } + + elm.onDrag = (x,y)=>{ + let distance = Math.abs(x); + // TODO: avoid comple list refresh on pointer down ? + if (distance>5){ + Cursor.set("drag"); + contentPanel.classList.add("inactive"); + let currentTarget = contentPanel.querySelector("#frame" + elm.currentIndex); + if (currentTarget){ + currentTarget.classList.add("ghost"); + let newIndex = Math.floor((currentTarget.startDragX+x)/52); + elm.targetIndex = newIndex; + if (newIndex<0) newIndex=0; + if (newIndex>=max) newIndex = max; + + for (let i = 0;i<=max;i++){ + let el = contentPanel.querySelector("#frame" + i); + if (el){ + if (elm.currentIndex === i){ + el.style.left = (newIndex*52) + "px"; + }else{ + let ci = 0; + if (newIndex= newIndex && i<=elm.currentIndex) ci=-1; + if (newIndex>elm.currentIndex && i <= newIndex && i>=elm.currentIndex) ci=1; + el.style.left = ((i-ci)*52) + "px"; + } + } + } + } + } + } + elm.onDragEnd = (e)=>{ + Input.removeDragElement(); + if (elm.currentIndex !== elm.targetIndex){ + ImageFile.moveFrame(elm.currentIndex,elm.targetIndex); + } + let currentTarget = contentPanel.querySelector("#frame" + elm.currentIndex); + if (currentTarget) currentTarget.classList.remove("ghost"); + Cursor.reset(); + contentPanel.classList.remove("inactive"); + } + + elm.onContextMenu = ()=>{ + let items = []; + if (hasMultipleItems) items.push ({label: "Remove Frame", command: COMMAND.DELETEFRAME}); + items.push ({label: "Duplicate Frame", command: COMMAND.DUPLICATEFRAME}); + + ContextMenu.show(items); + } + + $div("label","" + index,elm); + + frames.push(elm); + + }); + } + } + + me.update = ()=>{ + let index = ImageFile.getActiveFrameIndex(); + let frame = frames[index]; + if (frame){ + let canvas = frame.querySelector("canvas"); + if (canvas){ + let ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0,canvas.width,canvas.height); + ctx.drawImage(ImageFile.getCanvas(index),0,0,48,48); + } + } + } + + EventBus.on(EVENT.imageSizeChanged,me.list); + EventBus.on(EVENT.framesChanged,me.list); + EventBus.on(EVENT.layerContentChanged,me.update); + + return me; +}(); + +export default FramesPanel; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/toolPanels/gridPanel.js b/app/web-tools/dpaint/_script/ui/toolPanels/gridPanel.js new file mode 100644 index 00000000..21f9194c --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/toolPanels/gridPanel.js @@ -0,0 +1,123 @@ +import ImageFile from "../../image.js"; +import EventBus from "../../util/eventbus.js"; +import {COMMAND, EVENT} from "../../enum.js"; +import $, {$div, $elm, $input} from "../../util/dom.js"; +import Brush from "../brush.js"; + +let GridPanel = function(){ + let me = {}; + let sizeRange; + let sizeInput; + let opacityRange; + let opacityInput; + let brightnessRange; + let brightnessInput; + let visibleToggle; + let contentPanel; + let options={ + size: 32, + brightness: 0, + opacity: 100, + visible: false + } + + me.generate = (parent)=>{ + + let visibleSelect = $div("rangeselect", "", parent); + $elm("label","Visible",visibleSelect); + visibleToggle = $(".yesno",{parent:visibleSelect,onClick:(e)=>{ + EventBus.trigger(COMMAND.TOGGLEGRID); + },info:"Show or hide the grid"},$(".option","Yes"),$(".option","No")); + + + + + let sizeSelect = $div("rangeselect", "", parent); + $elm("label","Size",sizeSelect); + sizeRange = $input("range",options.size,sizeSelect,()=>{ + update(); + }); + sizeInput = $input("text",options.size,sizeSelect); + sizeRange.min = 16; + sizeRange.max = 100; + sanitize(sizeInput,sizeRange); + + + let opacitySelect = $div("rangeselect", "", parent); + $elm("label","Opacity",opacitySelect); + opacityRange = $input("range",options.opacity,opacitySelect,()=>{ + update(); + }); + opacityInput = $input("text",options.opacity,opacitySelect); + opacityRange.min = 1; + opacityRange.max = 100; + sanitize(opacityInput,opacityRange); + + let brightnessSelect = $div("rangeselect", "", parent); + $elm("label","Brightness",brightnessSelect); + brightnessRange = $input("range",options.brightness,brightnessSelect,()=>{ + update(); + }); + brightnessInput = $input("text",options.brightness,brightnessSelect); + brightnessRange.min = 0; + brightnessRange.max = 100; + sanitize(brightnessInput,brightnessRange); + + contentPanel = $div("panelcontent","",parent); + + } + + me.getOptions = ()=>{ + return options; + } + + function update(){ + options.size = parseInt(sizeRange.value); + options.opacity = parseInt(opacityRange.value); + options.brightness = parseInt(brightnessRange.value); + + EventBus.trigger(EVENT.gridOptionsChanged,options); + } + + function sanitize(input,range){ + range.min = range.min || 0; + range.max = range.max || 100; + let min = parseInt(range.min); + let max = parseInt(range.max); + + input.onkeydown = (e)=>{ + e.stopPropagation(); + } + + input.onchange = (e)=>{ + let val = parseInt(input.value,10); + if (isNaN(val)) val = min; + if (valmax) val = max; + range.value = val; + update(); + } + } + + EventBus.on(EVENT.gridOptionsChanged,(_options)=>{ + options = _options; + sizeRange.value = options.size; + sizeInput.value = options.size; + opacityRange.value = options.opacity; + opacityInput.value = options.opacity; + brightnessRange.value = options.brightness; + brightnessInput.value = options.brightness; + }); + + EventBus.on(COMMAND.TOGGLEGRID,()=>{ + options.visible = !options.visible; + visibleToggle.classList.toggle("selected",options.visible); + }); + + + + + return me; +}() + +export default GridPanel; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/toolPanels/layerPanel.js b/app/web-tools/dpaint/_script/ui/toolPanels/layerPanel.js new file mode 100644 index 00000000..8a79c71d --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/toolPanels/layerPanel.js @@ -0,0 +1,290 @@ +import ImageFile from "../../image.js"; +import $,{$div, $elm, $input} from "../../util/dom.js"; +import EventBus from "../../util/eventbus.js"; +import {COMMAND, EVENT} from "../../enum.js"; +import input from "../input.js"; +import Input from "../input.js"; +import ContextMenu from "../components/contextMenu.js"; +import Historyservice from "../../services/historyservice.js"; +import HistoryService from "../../services/historyservice.js"; + +let LayerPanel = function(){ + let me = {}; + let contentPanel; + let opacityRange; + let blendSelect; + let editIndex; + + let blendModes=[ + "normal", + "lighter", + "multiply", + "screen", + "overlay", + "darken", + "lighten", + "color-dodge", + "color-burn", + "hard-light", + "soft-light", + "hue", + "saturation", + "color", + "luminosity", + + /*"source-in", + "source-out", + "source-atop", + "destination-over", + "destination-in", + "destination-out", + "destination-atop", + "lighter", + "copy", + "xor", + "difference", + "exclusion"*/ + + ] + + me.generate = (parent)=>{ + $(".paneltools.multirow",{parent:parent}, + $(".rangeselect", + {info: "Set transparency of active layer"}, + $(".label","Opacity"), + opacityRange = $("input",{type:"range",max:100,min:0,value:100,oninput:()=>{ + ImageFile.setLayerOpacity(opacityRange.value); + }}) + ), + $(".blendselect", + $(".label","Blend"), + blendSelect = $("select",{oninput:()=>{ + ImageFile.setLayerBlendMode(blendSelect.value); + }}) + ), + $(".button.delete",{ + onclick:()=>{EventBus.trigger(COMMAND.DELETELAYER);}, + info:"Delete active layer" + }), + $(".button.add",{ + onclick:()=>{EventBus.trigger(COMMAND.NEWLAYER);}, + info:"Add new layer" + }) + ); + + contentPanel = $(".panelcontent",{parent:parent}); + blendModes.forEach(mode=>{ + $elm("option",mode,blendSelect); + }); + } + + me.list = ()=>{ + contentPanel.innerHTML = ""; + let activeIndex = ImageFile.getActiveLayerIndex() || 0; + let imageFile = ImageFile.getCurrentFile(); + let frame = imageFile.frames[ImageFile.getActiveFrameIndex()]; + let max = frame.layers.length-1; + let systemLayers = 0; + let startY = 0; + if (window.override){ + for (let i = 0;i<=max;i++){ + if (frame.layers[i].name.indexOf("_")===0){ + systemLayers++; + } + } + startY = -(systemLayers*23); + startY = -23; + } + + let offset = 0; + for (let i = 0;i<=max;i++){ + let layer = frame.layers[i]; + let elm = $div("layer info" + (activeIndex === i ? " active":"") + (layer.visible?"":" hidden"),layer.name,contentPanel,()=>{ + if (elm.classList.contains('hasinput')){ + let input = elm.querySelector("input"); + if (input) input.focus(); + return; + }; + if (activeIndex !== i) ImageFile.activateLayer(i); + }); + elm.style.top = startY + ((max-i)*23) - offset + "px"; + elm.currentIndex = elm.targetIndex = i; + elm.id = "layer" + i; + elm.info = "Drag to reorder, double click to rename, right click for more options"; + if (layer.name.indexOf("_")===0){ + elm.classList.add("system"); + if (window.override) offset -= 23; + } + + elm.onDragStart = (e)=>{ + if (elm.classList.contains('hasinput')) return; + // TODO probably more performant if we postpone this to when we actually drag + let dupe = $div("dragelement box",elm.innerText); + Input.setDragElement(dupe); + } + + elm.onDrag = (x,y)=>{ + if (elm.classList.contains('hasinput')) return; + let distance = Math.abs(y) + + if (distance>5){ + // Meh... did we just did a rugpull regenerating the layer list? FIXME! + let currentTarget = contentPanel.querySelector("#layer" + elm.currentIndex); + currentTarget.classList.add("ghost"); + + let indexChange = Math.round(y/23); + let newIndex = elm.currentIndex - indexChange; + elm.targetIndex = newIndex; + if (newIndex<0) newIndex=0; + if (newIndex>=max) newIndex = max; + + + for (let i = 0;i<=max;i++){ + let el = contentPanel.querySelector("#layer" + i); + if (el){ + if (elm.currentIndex === i){ + el.style.top = ((max-newIndex)*23) + "px"; + }else{ + let ci = 0; + if (newIndex= newIndex && i<=elm.currentIndex){ + ci=1; + } + if (newIndex>elm.currentIndex && i <= newIndex && i>=elm.currentIndex){ + ci=-1; + } + el.style.top = ((max-i-ci)*23) + "px"; + } + } + } + } + } + + elm.onDragEnd = (e)=>{ + console.log("drop"); + Input.removeDragElement(); + let currentTarget = contentPanel.querySelector("#layer" + elm.currentIndex); + currentTarget.classList.remove("ghost"); + if (elm.currentIndex !== elm.targetIndex){ + ImageFile.moveLayer(elm.currentIndex,elm.targetIndex); + } + } + + elm.onDoubleClick = ()=>{ + renameLayer(i); + } + + elm.onContextMenu = ()=>{ + let items = []; + if (max>1) items.push ({label: "Remove Layer", command: COMMAND.DELETELAYER}); + items.push ({label: "Duplicate Layer", command: COMMAND.DUPLICATELAYER}); + items.push ({label: "Rename Layer", action: ()=>{ + renameLayer(i); + }}); + + if (layer.hasMask){ + items.push({label: "Remove Layer Mask", command: COMMAND.DELETELAYERMASK}); + if (layer.isMaskEnabled()){ + items.push({label: "Disable Layer Mask", command: COMMAND.DISABLELAYERMASK}); + }else{ + items.push({label: "Enable Layer Mask", command: COMMAND.ENABLELAYERMASK}); + } + items.push({label: "Apply Layer Mask", command: COMMAND.APPLYLAYERMASK}); + }else{ + items.push({label: "Add Layer Mask: Show", command: COMMAND.LAYERMASK}); + items.push({label: "Add Layer Mask: Hide", command: COMMAND.LAYERMASKHIDE}); + } + + if (i0){ + items.push ({label: "Move Down", command: COMMAND.LAYERDOWN}); + items.push ({label: "Merge Down", command: COMMAND.MERGEDOWN}); + } + + + ContextMenu.show(items); + } + + if (elm.currentIndex === editIndex){ + let input = $input("text",layer.name); + elm.appendChild(input); + } + + $(".eye",{ + parent:elm, + onClick:()=>{ + Historyservice.start(EVENT.layerPropertyHistory,i); + ImageFile.toggleLayer(i); + Historyservice.end(); + }, + info:"Toggle layer visibility" + }) + + if (layer.hasMask){ + $(".mask" + (layer.isMaskActive()?".active":"") + (layer.isMaskEnabled()?"":".disabled"),{ + parent:elm, + onClick:()=>{ + if (!layer.isMaskEnabled()) return; + Historyservice.start(EVENT.layerPropertyHistory,i); + layer.toggleMask(); + Historyservice.end(); + EventBus.trigger(EVENT.toolChanged); + EventBus.trigger(EVENT.layersChanged); + }, + info : "Toggle layer mask" + }) + } + + if (layer.locked){ + elm.classList.add("locked"); + $(".lock",{ + parent:elm, + info:"Layer is locked" + }) + } + + if (activeIndex === i){ + opacityRange.value = layer.opacity; + blendSelect.value = layer.blendMode; + } + } + } + + + function renameLayer(index){ + let elm=contentPanel.querySelector("#layer" + index); + let layer = ImageFile.getLayer(index); + if (elm){ + if (elm.classList.contains('hasinput')) return; + let input = $input("text",layer.name); + input.onkeydown = function(e){ + e.stopPropagation(); + if (e.code === "Enter"){ + HistoryService.start(EVENT.layerPropertyHistory,index); + layer.name = input.value; + HistoryService.end(); + me.list(); + } + if (e.code === "Escape"){ + me.list(); + } + } + elm.appendChild(input); + elm.classList.add('hasinput'); + elm.classList.remove('handle'); + input.focus(); + + // needed for rename from context menu + setTimeout(()=>{ + input.focus(); + input.select(); + },50); + } + + } + + EventBus.on(EVENT.layersChanged,me.list); + + return me; +}(); + +export default LayerPanel; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/toolbar.js b/app/web-tools/dpaint/_script/ui/toolbar.js new file mode 100644 index 00000000..90843e6f --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/toolbar.js @@ -0,0 +1,126 @@ +import $,{$div, $link} from "../util/dom.js"; +import {COMMAND, EVENT} from "../enum.js"; +import EventBus from "../util/eventbus.js"; +import Palette from "./palette.js"; +import Brush from "./brush.js"; +import Editor from "./editor.js"; +import ToolOptions from "./components/toolOptions.js"; +import SidePanel from "./sidepanel.js"; + +let Toolbar = function(){ + let me = {} + let container; + let undo,redo; + let isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; + let meta = isMac?"Cmd":"Ctrl"; + + let items=[ + {name: "pencil",command: COMMAND.DRAW, isTool: true, info: "B Left click: draw with foreground color, Right click: draw with background color."}, + {name: "select",command: COMMAND.SELECT, isTool: true, handleDeActivate: true, info: "S Make rectangular selection."}, + {name: "polygonselect",command: COMMAND.POLYGONSELECT, isTool: true, handleDeActivate: true, info: "P Make polygon selection."}, + {name: "floodselect", isTool: true, command: COMMAND.FLOODSELECT, info: "W Make selection of on area of the same color."}, + {name: "circle",label: "", isTool: true, canFill: true, command: COMMAND.CIRCLE, info: "C Draw ellipsis. Shift to lock to circle, select again to toggle fill."}, + {name: "square",label: "", isTool: true, canFill: true, command: COMMAND.SQUARE, info: "R Draw rectangle. Shift to lock to square, select again to toggle fill."}, + {name: "line",label: "", isTool: true, command: COMMAND.LINE, info: "L Draw straight line."}, + {name: "gradient", isTool: true, command: COMMAND.GRADIENT, info: "G Gradient fill, draw line to set start- and endpoint."}, + {name: "flood", isTool: true, command: COMMAND.FLOOD, info: "F Fill an area."}, + {name: "spray", isTool: true, command: COMMAND.SPRAY, info: "P Spray brush."}, + {name: "text", command: COMMAND.TEXT,isTool: true, info: "Write text."}, + {name: "smudge", isTool: true, command: COMMAND.SMUDGE, info: "M Smudge/Smear colors."}, + {name: "erase", isTool: true, command: COMMAND.ERASE, info: "E Erase."}, + {name: "split", command: COMMAND.SPLITSCREEN, toggleProperty: "splitPanel", info: "Z Toggle split view."}, + {name: "pan", isTool: true, command: COMMAND.PAN, info: "H or Space Hand: Pan the image."}, + {name: "picker", isTool: true, command: COMMAND.COLORPICKER, info: "K or Shift+Draw Pick color from image."}, + {name: "zoom",label: "", command: COMMAND.ZOOMIN, info: "+ Zoom in."}, + {name: "zoomout",label: "",command: COMMAND.ZOOMOUT, info: "- Zoom out."}, + {name: "undo", command: COMMAND.UNDO, info: ""+meta+"-Z Undo."}, + {name: "redo",command: COMMAND.REDO, info: ""+meta+"-Y Redo."} + ] + + me.init = function(parent){ + container = $div("toolbar","",parent); + generate(); + + EventBus.on(EVENT.toolOptionsChanged,()=>{ + if (container) container.classList.toggle("fill",ToolOptions.isFill()); + }) + + } + + me.activateButton = function(index){ + let item = items[index]; + if (item){ + if (item.element){ + if (item.canFill && item.element.classList.contains("active")){ + ToolOptions.setFill(!ToolOptions.isFill()); + } + } + if (item.command) EventBus.trigger(item.command); + } + } + + function generate(){ + let tools = $(".tools",{parent: container}, + $(".togglepanel.sidebar",{ + onClick: ()=>EventBus.trigger(COMMAND.TOGGLESIDEPANEL), + info:"Toggle side panels" + }) + ); + + Brush.init(tools); + + items.forEach((item,index)=>{ + item.element = $div("button handle info icon " + item.name,item.label,tools,(e) =>{ + me.activateButton(index); + }); + + item.element.info = item.info; + if (item.isTool && item.command){ + EventBus.on(item.command,()=>{ + items.forEach((itm,i)=>{ + if (itm.isTool){ + if (itm.handleDeActivate && itm.command && itm.element.classList.contains("active") && index !== i){ + EventBus.trigger(EVENT.toolDeActivated,itm.command); + } + itm.element.classList.toggle("active",index === i); + } + }); + EventBus.trigger(EVENT.toolChanged,item.command); + }); + } + + if (item.toggleProperty && item.command){ + EventBus.on(item.command,()=>{ + setTimeout(() => { + items.forEach((itm,i)=>{ + if (index === i){ + itm.element.classList.toggle("active",Editor.isStateActive(item.toggleProperty)); + } + }); + },50) + }) + } + + if (item.command === COMMAND.UNDO){ + undo = item.element; + undo.classList.toggle("disabled",true); + } + if (item.command === COMMAND.REDO){ + redo = item.element; + redo.classList.toggle("disabled",true); + } + }); + Palette.init(tools,container); + } + + EventBus.on(EVENT.historyChanged,([undoCount,redoCount])=>{ + if (undo) undo.classList.toggle("disabled",undoCount === 0); + if (redo) redo.classList.toggle("disabled",redoCount === 0); + }); + + return me; + + +}(); + +export default Toolbar; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/ui/ui.js b/app/web-tools/dpaint/_script/ui/ui.js new file mode 100644 index 00000000..fc10d612 --- /dev/null +++ b/app/web-tools/dpaint/_script/ui/ui.js @@ -0,0 +1,54 @@ +import Input from "./input.js"; +import {$div} from "../util/dom.js"; +import Menu from "./menu.js"; +import Toolbar from "./toolbar.js"; +import Editor from "./editor.js"; +import Cursor from "./cursor.js"; +import Sidepanel from "./sidepanel.js"; +import StatusBar from "./statusbar.js"; +import PaletteList from "./components/paletteList.js"; +import EventBus from "../util/eventbus.js"; +import {COMMAND, EVENT} from "../enum.js"; + +let UI = function(){ + let me = {} + let container; + + me.init = function(){ + container = $div("container"); + document.body.appendChild(container); + Cursor.init(); + Input.init(); + Menu.init(container); + Toolbar.init(container); + StatusBar.init(container); + Sidepanel.init(container); + PaletteList.init(container); + Editor.init(container); + + window.addEventListener("resize",()=>{ + EventBus.trigger(EVENT.UIresize); + },{passive:true}); + } + + me.fuzzy = function(value){ + if (container) container.classList.toggle("fuzzy",value) + } + + + me.getContainer = function(){ + return container; + } + + me.inPresentation = function(){ + return document.body.classList.contains("presentation"); + } + + EventBus.on(COMMAND.PRESENTATION,()=>{ + document.body.classList.toggle("presentation"); + }) + + return me; +}(); + +export default UI; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/userSettings.js b/app/web-tools/dpaint/_script/userSettings.js new file mode 100644 index 00000000..d73976c2 --- /dev/null +++ b/app/web-tools/dpaint/_script/userSettings.js @@ -0,0 +1,29 @@ +let UserSettings = (()=>{ + let me = {}; + + let settings = {}; + let stored = localStorage.getItem("dp_settings"); + if (stored){ + try { + settings = JSON.parse(stored); + }catch (e) { + console.error("Could not parse settings", e); + settings = {}; + } + } + + + me.get = (key)=>{ + return settings[key]; + } + + me.set = (key,value)=>{ + settings[key] = value; + localStorage.setItem("dp_settings",JSON.stringify(settings)); + } + + return me; + +})(); + +export default UserSettings; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/animator.js b/app/web-tools/dpaint/_script/util/animator.js new file mode 100644 index 00000000..9b7c0000 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/animator.js @@ -0,0 +1,66 @@ +let Animator = function(){ + let me = {}; + let running = {}; + let tickFunctions = {}; + let needsTicking = false; + + + me.start = function(type,tickFunction,fps){ + tickFunctions[type] = tickFunctions[type] || []; + let tick = { + tickFunction: tickFunction, + fps: fps, + fpsInterval: 1000 / fps, + then: window.performance.now(), + startTime: window.performance.now(), + } + tickFunctions[type].push(tick); + if (!running[type]){ + let wasTicking = isTicking(); + running[type] = true; + needsTicking = true; + if (!wasTicking) requestAnimationFrame(animate); + } + } + + me.stop = function(type){ + running[type] = false; + tickFunctions[type] = []; + needsTicking = isTicking(); + } + + me.isRunning = function(type){ + return running[type]; + } + + function animate(newtime) { + if (!needsTicking) return; + requestAnimationFrame(animate); + + for (let key in running){ + if (running[key]){ + let functions = tickFunctions[key]; + functions.forEach(tick=>{ + tick.now = newtime; + tick.elapsed = tick.now - tick.then; + + if (tick.elapsed > tick.fpsInterval) { + tick.then = tick.now - (tick.elapsed % tick.fpsInterval); + tick.tickFunction(); + } + }) + } + } + } + + function isTicking(){ + for (let key in running){ + if (running[key]) return true; + } + return false; + } + + return me; +}(); + +export default Animator; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/binarystream.js b/app/web-tools/dpaint/_script/util/binarystream.js new file mode 100644 index 00000000..9b74ba66 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/binarystream.js @@ -0,0 +1,293 @@ +/* + + MIT License + + Copyright (c) 2019 Steffest - dev@stef.be + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +function BinaryStream(arrayBuffer, bigEndian){ + var obj = { + index: 0, + litteEndian : !bigEndian + }; + + obj.goto = function(value){ + setIndex(value); + }; + + obj.jump = function(value){ + this.goto(this.index + value); + }; + + obj.readByte = function(position){ + setIndex(position); + var b = this.dataView.getInt8(this.index); + this.index++; + return b; + }; + + obj.writeByte = function(value,position){ + setIndex(position); + this.dataView.setInt8(this.index,value); + this.index++; + }; + + obj.readUbyte = function(position){ + setIndex(position); + var b = this.dataView.getUint8(this.index); + this.index++; + return b; + }; + + obj.writeUbyte = function(value,position){ + setIndex(position); + this.dataView.setUint8(this.index,value); + this.index++; + }; + + obj.readUint = function(position){ + setIndex(position); + var i = this.dataView.getUint32(this.index,this.litteEndian); + this.index+=4; + return i; + }; + + obj.readUWord = function(position) { + setIndex(position); + var i = this.dataView.getUint16(this.index, this.littleEndian); + this.index += 2; + return i; + } + + obj.writeUint = function(value,position){ + setIndex(position); + this.dataView.setUint32(this.index,value,this.litteEndian); + this.index+=4; + }; + + obj.readUBytes = function(len,position,buffer) { + setIndex(position); + if (!buffer) buffer = new Uint8Array(len); + var i = this.index; + var offset = 0; + for (; offset < len; offset++) buffer[offset] = this.dataView.getUint8(i+offset); + + this.index += len; + return buffer; + }; + + obj.readBytes = function(len,position,buffer) { + setIndex(position); + if (!buffer) buffer = new Int8Array(len); + var i = this.index; + var offset = 0; + for (; offset < len; offset++) buffer[offset] = this.dataView.getInt8(i+offset); + + this.index += len; + return buffer; + }; + + obj.readString = function(len,position){ + setIndex(position); + var i = this.index; + var src = this.dataView; + var text = ""; + + if ((len += i) > this.length) len = this.length; + + for (; i < len; ++i){ + var c = src.getUint8(i); + if (c == 0) break; + text += String.fromCharCode(c); + } + + this.index = len; + return text; + }; + + obj.writeString = function(value,position){ + setIndex(position); + var src = this.dataView; + var len = value.length; + for (var i = 0; i < len; i++) src.setUint8(this.index + i,value.charCodeAt(i)); + this.index += len; + }; + + obj.writeStringSection = function(value,max,paddValue,position){ + setIndex(position); + max = max || 1; + value = value || ""; + paddValue = paddValue || 0; + var len = value.length; + if (len>max) value = value.substr(0,max); + obj.writeString(value); + obj.fill(paddValue,max-len); + }; + + // same as readUshort + obj.readWord = function(position){ + setIndex(position); + var w = this.dataView.getUint16(this.index, this.litteEndian); + this.index += 2; + return w; + }; + + obj.writeWord = function(value,position){ + setIndex(position); + this.dataView.setUint16(this.index,value,this.litteEndian); + this.index += 2; + }; + + obj.readLong = obj.readDWord = obj.readUint; + obj.writeLong = obj.writeDWord = obj.writeUint; + + obj.readShort = function(value,position){ + setIndex(position); + var w = this.dataView.getInt16(this.index, this.litteEndian); + this.index += 2; + return w; + }; + + obj.readBits = function(count,bitPosition,position){ + + // this reads $count bits, starting from byte $position and bit position $bitPosition + // bitPosition can be > 8 , count should be <= 8; + + position = position === 0 ? position : position || obj.index; + var bytePosition = position + (bitPosition >> 3); + setIndex(bytePosition); + + bitPosition = bitPosition - ((bitPosition >> 3) << 3); + + var bits = byte2Bits(this.dataView.getUint8(this.index)); + + if ((bitPosition + count)>8 && this.index8){ + var bts = []; + var bindex = 0; + var _this = this; + + function write() { + if (bts.length){ + var b = bits2Int(bts); + _this.dataView.setUint8(_this.index,b); + _this.index++; + bts = []; + } + } + + while (bts.length<8 && bindex=8) write(); + } + write(); + + }else{ + var b = bits2Int(bits); + this.dataView.setUint8(this.index,b); + this.index++; + } + }; + + obj.writeByteArray = function(array,position){ + setIndex(position); + for (let i = 0; i= (this.length-margin); + }; + + function setIndex(value){ + value = value === 0 ? value : value || obj.index; + if (value<0) value = 0; + if (value >= obj.length) value = obj.length-1; + + obj.index = value; + } + + obj.buffer = arrayBuffer; + obj.dataView = new DataView(arrayBuffer); + obj.length = arrayBuffer.byteLength; + + return obj; + + // todo + /* + + check if copying typed arraybuffers is faster then reading dataview + + var dstU8 = new Uint8Array(dst, dstOffset, byteLength); + var srcU8 = new Uint8Array(src, srcOffset, byteLength); + dstU8.set(srcU8); + + */ + + function byte2Bits(b){ + return [ + b>>7 & 1, + b>>6 & 1, + b>>5 & 1, + b>>4 & 1, + b>>3 & 1, + b>>2 & 1, + b>>1 & 1, + b & 1 + ] + } + + function bits2Int(bits){ + var v = 0; + var len = bits.length-1; + for (var i = 0; i<=len ; i++){ + v += bits[i] << (len-i); + } + return v; + } +} + +//if (typeof module !== 'undefined' && module.exports) module.exports = BinaryStream; +export default BinaryStream; + diff --git a/app/web-tools/dpaint/_script/util/canvasUtils.js b/app/web-tools/dpaint/_script/util/canvasUtils.js new file mode 100644 index 00000000..95a9c0a1 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/canvasUtils.js @@ -0,0 +1,215 @@ +import Palette from "../ui/palette.js"; + +export function duplicateCanvas(canvas,includingContent){ + let result = document.createElement("canvas"); + result.width = canvas.width; + result.height = canvas.height; + if (includingContent) result.getContext("2d").drawImage(canvas,0,0); + return result; +} + +export function releaseCanvas(canvas) { + // mostly needed for safari as it tends to hold on the canvas elements; + canvas.width = 1; + canvas.height = 1; + canvas.getContext('2d').clearRect(0, 0, 1, 1); + canvas = undefined; +} + +// create an SVG outline path from the non-transparent pixels of a canvas context, +// next to the SVG, the function also returns the bounding box of the pixels +// TODO: move to webworker? + +export function outLineCanvas(ctx,generateSVG){ + + let lines = []; + let img = ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height); + let topLine = {x:0, y:0, w:0} + let bottomLine = {x:0, y:0, w:0} + let leftLine = {x:0, y:0, h:0} + let rightLine = {x:0, y:0, h:0} + let w = img.width; + let h = img.height; + + let boundingCoords = {x1:w,y1:h,x2:0,y2:0}; + + function isPixel(x,y){ + if (x<0 || x>=w || y<0 || y>=h) return false; + let index = (y * w + x) * 4; + let alpha = img.data[index + 3]; + return alpha>1; + } + + function addLine(line,horizontal){ + if (line.w){ + if (horizontal){ + lines.push([line.x,line.y,line.x+line.w,line.y]); + }else{ + lines.push([line.x,line.y,line.x,line.y+line.w]); + } + line.w = 0; + } + } + + // find horizontal lines; + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + if (isPixel(x,y)){ + + // update bounding box + if (xboundingCoords.x2) boundingCoords.x2 = x; + if (yboundingCoords.y2) boundingCoords.y2 = y; + + if (!isPixel(x,y-1)){ + // top edge found + if (topLine.w){ + topLine.w++; + }else{ + topLine = {x:x,y:y,w:1}; + } + }else{ + addLine(topLine,true); + } + + if (!isPixel(x,y+1)){ + // bottom edge found + if (bottomLine.w){ + bottomLine.w++; + }else{ + bottomLine = {x:x,y:y+1,w:1}; + } + }else{ + addLine(bottomLine,true); + } + }else{ + addLine(topLine,true); + addLine(bottomLine,true); + } + } + addLine(topLine,true); + addLine(bottomLine,true); + } + let boundingBox = { + x:boundingCoords.x1, + y:boundingCoords.y1, + w:boundingCoords.x2-boundingCoords.x1, + h:boundingCoords.y2-boundingCoords.y1 + } + + // find vertical lines; + for (let x = 0; x < w; x++) { + for (let y = 0; y < h; y++) { + if (isPixel(x,y)){ + if (!isPixel(x-1,y)){ + // left edge found + if (leftLine.w){ + leftLine.w++; + }else{ + leftLine = {x:x,y:y,w:1}; + } + }else{ + addLine(leftLine); + } + + if (!isPixel(x+1,y)){ + // right edge found + if (rightLine.w){ + rightLine.w++; + }else{ + rightLine = {x:x+1,y:y,w:1}; + } + }else{ + addLine(rightLine); + } + }else{ + addLine(leftLine); + addLine(rightLine); + } + } + + addLine(leftLine); + addLine(rightLine); + } + + // TODO: should we do the extra pass to construct polylines? + + let totalLines = lines.length; + + let svg; + if (generateSVG){ + svg = ""; + if (totalLines>6000){ + console.warn("too many lines, displaying bounding box instead"); + + svg += ''; + svg += ''; + + }else{ + // draw lines + lines.forEach(h=>{ + let x = h[0]; + let y = h[1]; + let x2 = h[2]; + let y2 = h[3]; + svg += ''; + svg += ''; + }); + + } + + svg += "" + } + + return { + svg:svg, + box:boundingBox, + lines:lines, + lineCount: totalLines + } + + +} + +export function indexPixelsToPalette(ctx,palette,oneDimensional){ + let width = ctx.canvas.width; + let height = ctx.canvas.height; + let pixels = []; + let data = ctx.getImageData(0,0,width,height).data; + let notFoundCount = 0; + let transparentIndex = 0; + + function getIndex(color,x,y){ + let index = palette.findIndex((c)=>{return c[0] === color[0] && c[1] === color[1] && c[2] === color[2]}); + if (index<0){ + index = 0; + notFoundCount++; + } + return index; + } + + for (let i=0;i{ + color[i] = parseInt(c); + }); + return color; + } + if (color.indexOf("rgb") === 0){ + let parts = color.split("(")[1].split(")")[0].split(","); + if (parts.length===4) return [parseInt(parts[0]),parseInt(parts[1]),parseInt(parts[2]),parseInt(parts[3])]; + return [parseInt(parts[0]),parseInt(parts[1]),parseInt(parts[2])]; + } + if (color.indexOf("#") === 0){ + let r = parseInt(color.substr(1,2),16); + let g = parseInt(color.substr(3,2),16); + let b = parseInt(color.substr(5,2),16); + if (color.length===9) return [r,g,b,parseInt(color.substr(7,2),16)]; + return [r,g,b]; + } + return color; + } + + me.toHex=function(color){ + if (typeof color==="string"){ + color = me.fromString(color); + } + if (typeof color === "object" && color.length){ + let result = "#" + hexByte(color[0]) + hexByte(color[1]) + hexByte(color[2]); + if (color.length===4) result += hexByte(color[3]); + return result; + } + } + + me.toHSV = function(color,maxRange){ + if (typeof color==="string"){ + color = me.fromString(color); + } + if (typeof color === "object" && color.length){ + let r = color[0]/255; + let g = color[1]/255; + let b = color[2]/255; + let max = Math.max(r,g,b); + let min = Math.min(r,g,b); + let h,s,v = max; + let d = max-min; + s = max===0 ? 0 : d/max; + if (max===min){ + h = 0; // shade of gray + }else{ + switch (max){ + case r: h = (g-b)/d + (g0.04045) ? Math.pow((r+0.055)/1.055,2.4) : r/12.92; + g = (g>0.04045) ? Math.pow((g+0.055)/1.055,2.4) : g/12.92; + b = (b>0.04045) ? Math.pow((b+0.055)/1.055,2.4) : b/12.92; + x = (r*0.4124 + g*0.3576 + b*0.1805)/0.95047; + y = (r*0.2126 + g*0.7152 + b*0.0722); + z = (r*0.0193 + g*0.1192 + b*0.9505)/1.08883; + x = (x>0.008856) ? Math.pow(x,1/3) : (7.787*x) + 16/116; + y = (y>0.008856) ? Math.pow(y,1/3) : (7.787*y) + 16/116; + z = (z>0.008856) ? Math.pow(z,1/3) : (7.787*z) + 16/116; + return [(116*y)-16,500*(x-y),200*(y-z)]; + } + } + + me.distance = function(color1,color2){ + // note: this is rather simplistic, but it's fast to calculate + // for better results, use a color space like LAB or HSV + color1 = me.fromString(color1); + color2 = me.fromString(color2); + let r = color1[0] - color2[0]; + let g = color1[1] - color2[1]; + let b = color1[2] - color2[2]; + if (color1.length===4 && color2.length===4){ + let a = color1[3] - color2[3]; + return Math.sqrt(r*r + g*g + b*b + a*a); + } + return Math.sqrt(r*r + g*g + b*b); + } + + me.distanceHSV = function(color1,color2){ + let c1 = me.toHSV(color1); + let c2 = me.toHSV(color2); + let h = c1[0] - c2[0]; + let s = c1[1] - c2[1]; + let v = c1[2] - c2[2]; + return Math.sqrt(h*h + s*s + v*v); + } + + me.distanceLAB = function(color1,color2){ + let c1 = me.toLAB(color1); + let c2 = me.toLAB(color2); + let l = c1[0] - c2[0]; + let a = c1[1] - c2[1]; + let b = c1[2] - c2[2]; + return Math.sqrt(l*l + a*a + b*b); + } + + me.hue = function (color){ + let c = me.toHSV(color); + return c[0]; + } + + me.lightness = function(color){ + let c = me.toHSV(color); + return c[2]; + } + + me.saturation = function(color){ + let c = me.toHSV(color); + return c[1]; + } + + me.blend = (color1,color2,amount)=>{ + let remaining = 1-amount; + let c1 = me.fromString(color1); + let c2 = me.fromString(color2); + let r = Math.round(c1[0]*remaining + c2[0]*amount); + let g = Math.round(c1[1]*remaining + c2[1]*amount); + let b = Math.round(c1[2]*remaining + c2[2]*amount); + return [r,g,b]; + } + + me.equals = (color1,color2)=>{ + color1 = me.fromString(color1); + color2 = me.fromString(color2); + return color1[0]===color2[0] && color1[1]===color2[1] && color1[2]===color2[2]; + } + + me.setBitDepth = (color,depth)=>{ + color = me.fromString(color); + if (depth===8) return color; + if (depth===4) return me.to24bit(me.to12bit(color),depth); + if (depth===3) return me.to24bit(me.to9bit(color),depth); + } + + me.to24bit = (color,depth)=>{ + depth = depth || 8; + color = me.fromString(color); + let r = color[0]; + let g = color[1]; + let b = color[2]; + + if (depth===4){ + r = Math.min((r << 4) + r,255); + g = Math.min((g << 4) + g,255); + b = Math.min((b << 4) + b,255); + } + if (depth===3){ + r = Math.min((r << 5) + (r<<1),255); + g = Math.min((g << 5) + (g<<1),255); + b = Math.min((b << 5) + (b<<1),255); + } + return [r,g,b]; + } + + me.to12bit = (color)=>{ + let r = color[0] >> 4; + let g = color[1] >> 4; + let b = color[2] >> 4; + return [r,g,b]; + } + + me.to9bit = (color)=>{ + let r = color[0] >> 5; + let g = color[1] >> 5; + let b = color[2] >> 5; + return [r,g,b]; + } + + function hexByte(nr){ + if (typeof nr === "string") nr=parseInt(nr); + let result = nr.toString(16); + if (result.length===1) result = "0" + result; + return result; + } + + return me; +}(); + +export default Color; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/crc32.js b/app/web-tools/dpaint/_script/util/crc32.js new file mode 100644 index 00000000..ccd75393 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/crc32.js @@ -0,0 +1,30 @@ +let CRC32 = function(){ + + let me = {}; + + let crcTable = new Uint32Array(256); + for (let n = 0; n < 256; n++){ + let c = n; + for (let k = 0; k < 8; k++){ + if (c & 1){ + c = 0xedb88320 ^ (c >>> 1); + }else{ + c = c >>> 1; + } + } + crcTable[n] = c; + } + + me.get= function(data){ + let crc = 0xffffffff; + for (let i = 0; i < data.length; i++){ + crc = crcTable[(crc ^ data[i]) & 0xff] ^ (crc >>> 8); + } + return crc ^ 0xffffffff; + } + + return me; + +}() + +export default CRC32; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/dom.js b/app/web-tools/dpaint/_script/util/dom.js new file mode 100644 index 00000000..4c681191 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/dom.js @@ -0,0 +1,153 @@ +export default function dom(tagName,options){ + let elm; + let opt = {}; + let index = 1; + let hasParent = false; + if (typeof options === "object" && !Array.isArray(options) && !(options instanceof Element)){ + opt = options; + index++; + } + + if (tagName instanceof Element){ + elm = tagName; + }else{ + // allow tag.class and tag#id constructors + if (tagName.indexOf(".")>=0){ + let classNames = tagName.split("."); + tagName = classNames.shift(); + opt.className = ((opt.className || "") + " " + classNames.join(" ")).trim(); + } + if (tagName.indexOf("#")>=0){ + let p = tagName.split("#"); + tagName = p.shift(); + opt.id = p[0]; + } + tagName = tagName||"div"; + elm = document.createElement(tagName); + } + + + for (let key in opt) { + if (key === 'parent'){ + opt[key].appendChild(elm); + hasParent = true; + continue; + } + + // custom dpaint.js stuff + if (key === 'onClick') addClass(elm,opt,"handle"); + if (key === 'info') addClass(elm,opt,"info"); + // end custom dpaint.js stuff + + elm[key] = opt[key]; + } + + for (; index < arguments.length; index++) { + append(elm, arguments[index]); + } + + if (defaultParent && !hasParent) defaultParent.appendChild(elm); + return elm; +} + +export function $div(classname,innerHTML,parent,onClick){ + let result = document.createElement("div"); + if (classname) result.className = classname; + if (innerHTML) result.innerHTML = innerHTML; + if (onClick){ + result.onClick = onClick; + result.classList.add("handle"); + } + if (parent) parent.appendChild(result); + return result; +} + +export function $link(classname,innerHTML,parent,onClick){ + let result = document.createElement("a"); + if (classname) result.className = classname; + if (innerHTML) result.innerHTML = innerHTML; + if (onClick) result.onClick = onClick; + if (parent) parent.appendChild(result); + return result; +} + +export function $title(level,innerHTML,parent){ + let result = document.createElement("h" + level); + if (innerHTML) result.innerHTML = innerHTML; + if (parent) parent.appendChild(result); + return result; +} + +export function $elm(type,innerHTML,parent,classname){ + let result = document.createElement(type); + if (innerHTML) result.innerHTML = innerHTML; + if (classname) result.className = classname; + if (parent) parent.appendChild(result); + return result; +} +export function $checkbox(label,parent,classname,onToggle,checked){ + + let result = document.createElement("span"); + result.className = "checkbox"; + + let labelElm = document.createElement("label"); + let checkbox = document.createElement("input"); + checkbox.type="checkbox"; + checkbox.checked = !!checked; + let textElm = document.createElement("span"); + labelElm.appendChild(checkbox); + labelElm.appendChild(textElm); + if (label) textElm.innerText = label; + result.appendChild(labelElm); + + if (onToggle){ + checkbox.oninput = ()=>{ + onToggle(checkbox.checked) + }; + } + if (classname) result.className += " " + classname; + if (parent) parent.appendChild(result); + result.setState = (state)=>{ + checkbox.checked = !!state; + } + return result; +} + +export function $input(type,value,parent,onInput){ + let result = document.createElement("input"); + result.type = type || "text"; + if (typeof value !== "undefined") result.value = value; + if (onInput){ + result.oninput = onInput; + } + if (parent) parent.appendChild(result); + return result +} + + + + +let append = (parent, child) => { + if (child) { + if (Array.isArray(child)) { + child.map(sub => append(parent, sub)); + } else { + if (typeof child === "string") child = document.createTextNode(child); + parent.appendChild(child); + } + } +}; + +let addClass=(elm,opt,className)=>{ + elm.classList.add(className); + if (opt.className) opt.className += " " + className; +} + +let defaultParent; + +// TODO move to new Dom constructor + + +export function $setTarget(parent){ + defaultParent = parent +} diff --git a/app/web-tools/dpaint/_script/util/eventbus.js b/app/web-tools/dpaint/_script/util/eventbus.js new file mode 100644 index 00000000..c4d2ab52 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/eventbus.js @@ -0,0 +1,42 @@ +let EventBus = function(){ + let me = {}; + let handlers = {}; + let active = true; + let buffer = {}; + + me.hold = function(){ + active = false; + } + me.release = function(){ + let keys = Object.keys(buffer); + console.log("releasing " + keys.length + " event(s)") + keys.forEach(key=>{ + me.trigger(key,buffer[key]); + }); + buffer = {}; + active = true; + } + + me.trigger = function(action,context){ + if (!active){ + buffer[action] = context; + return; + } + let actionHandler = handlers[action]; + if (actionHandler){ + actionHandler.forEach(handler=>{ + handler(context); + }) + } + } + + me.on = function(action,handler){ + handlers[action] = handlers[action] || []; + let actionHandler = handlers[action]; + actionHandler.push(handler); + } + + return me; +}(); + +export default EventBus; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/filesaver.js b/app/web-tools/dpaint/_script/util/filesaver.js new file mode 100644 index 00000000..e6246872 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/filesaver.js @@ -0,0 +1,122 @@ +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ +var saveAs = saveAs || function (e) { + "use strict"; + if (typeof e === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { + return + } + var t = e.document, n = function () { + return e.URL || e.webkitURL || e + }, r = t.createElementNS("http://www.w3.org/1999/xhtml", "a"), o = "download" in r, a = function (e) { + var t = new MouseEvent("click"); + e.dispatchEvent(t) + }, i = /constructor/i.test(e.HTMLElement) || e.safari, f = /CriOS\/[\d]+/.test(navigator.userAgent), + u = function (t) { + (e.setImmediate || e.setTimeout)(function () { + throw t + }, 0) + }, s = "application/octet-stream", d = 1e3 * 40, c = function (e) { + var t = function () { + if (typeof e === "string") { + n().revokeObjectURL(e) + } else { + e.remove() + } + }; + setTimeout(t, d) + }, l = function (e, t, n) { + t = [].concat(t); + var r = t.length; + while (r--) { + var o = e["on" + t[r]]; + if (typeof o === "function") { + try { + o.call(e, n || e) + } catch (a) { + u(a) + } + } + } + }, p = function (e) { + if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)) { + return new Blob([String.fromCharCode(65279), e], {type: e.type}) + } + return e + }, v = function (t, u, d) { + if (!d) { + t = p(t) + } + var v = this, w = t.type, m = w === s, y, h = function () { + l(v, "writestart progress write writeend".split(" ")) + }, S = function () { + if ((f || m && i) && e.FileReader) { + var r = new FileReader; + r.onloadend = function () { + var t = f ? r.result : r.result.replace(/^data:[^;]*;/, "data:attachment/file;"); + var n = e.open(t, "_blank"); + if (!n) e.location.href = t; + t = undefined; + v.readyState = v.DONE; + h() + }; + r.readAsDataURL(t); + v.readyState = v.INIT; + return + } + if (!y) { + y = n().createObjectURL(t) + } + if (m) { + e.location.href = y + } else { + var o = e.open(y, "_blank"); + if (!o) { + e.location.href = y + } + } + v.readyState = v.DONE; + h(); + c(y) + }; + v.readyState = v.INIT; + if (o) { + y = n().createObjectURL(t); + setTimeout(function () { + r.href = y; + r.download = u; + a(r); + h(); + c(y); + v.readyState = v.DONE + }); + return + } + S() + }, w = v.prototype, m = function (e, t, n) { + return new v(e, t || e.name || "download", n) + }; + if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { + return function (e, t, n) { + t = t || e.name || "download"; + if (!n) { + e = p(e) + } + return navigator.msSaveOrOpenBlob(e, t) + } + } + w.abort = function () { + }; + w.readyState = w.INIT = 0; + w.WRITING = 1; + w.DONE = 2; + w.error = w.onwritestart = w.onprogress = w.onwrite = w.onabort = w.onerror = w.onwriteend = null; + return m +}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content); +if (typeof module !== "undefined" && module.exports) { + module.exports.saveAs = saveAs +} else if (typeof define !== "undefined" && define !== null && define.amd !== null) { + define("FileSaver.js", function () { + return saveAs + }) +} + +export default saveAs; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/imageProcessing.js b/app/web-tools/dpaint/_script/util/imageProcessing.js new file mode 100644 index 00000000..3f9e40e7 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/imageProcessing.js @@ -0,0 +1,890 @@ +import EventBus from "./eventbus.js"; +import {COMMAND, EVENT} from "../enum.js"; +import Palette from "../ui/palette.js"; +import ImageFile from "../image.js"; +import Color from "./color.js"; + +var ImageProcessing = function(){ + var me = {}; + + var imageInfos = {}; + + // good explanation on dithering: https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html + // also: implement https://twitter.com/lorenschmidt/status/1468671174821486594?s=20 ? + + // I kind of forgot where the original code came from. + // maybe http://tool.anides.de/ ? + // if so: credits to the original author. Sorry, I forgot your name. + + var dithering = [ + { Name: "none", label: "None", pattern: null}, + { Name: "checks1", label: "Checks (very low)", pattern : [ 1 * 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "checks2", label: "Checks (low)", pattern : [ 2 * 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "checks3", label: "Checks (medium)", pattern: [ 4 * 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "checks4", label: "Checks (high)", pattern : [ 8 * 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "checks5", label: "Checks (very high)", pattern: [ 16 * 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "checks6", label: "Checks (very high 2)", pattern: [ 32 * 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "fs", label: "Floyd-Steinberg", pattern: [ 0, 0, 0, 7.0 / 16.0, 0, 0, 3.0 / 16.0, 5.0 / 16.0, 1.0 / 16.0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "fs85", label: "Floyd-Steinberg (85%)", pattern: [ 0, 0, 0, 7.0 * 0.85 / 16.0, 0, 0, 3.0 * 0.85 / 16.0, 5.0 * 0.85 / 16.0, 1.0 * 0.85 / 16.0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "fs75", label: "Floyd-Steinberg (75%)", pattern: [ 0, 0, 0, 7.0 * 0.75 / 16.0, 0, 0, 3.0 * 0.75 / 16.0, 5.0 * 0.75 / 16.0, 1.0 * 0.75 / 16.0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "fs50", label: "Floyd-Steinberg (50%)", pattern: [ 0, 0, 0, 7.0 * 0.5 / 16.0, 0, 0, 3.0 * 0.5 / 16.0, 5.0 * 0.5 / 16.0, 1.0 * 0.5 / 16.0, 0, 0, 0, 0, 0, 0 ] }, + { Name: "jjn", label: "Jarvis, Judice, and Ninke", pattern: [ 0, 0, 0, 7.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0, 5.0 / 48.0, 7.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0, 1.0 / 48.0, 3.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0, 1.0 / 48.0 ] }, + { Name: "s", label: "Stucki", pattern: [ 0, 0, 0, 8.0 / 42.0, 4.0 / 42.0, 2.0 / 42.0, 4.0 / 42.0, 8.0 / 42.0, 4.0 / 42.0, 2.0 / 42.0, 1.0 / 42.0, 2.0 / 42.0, 4.0 / 42.0, 2.0 / 42.0, 1.0 / 42.0 ] }, + { Name: "a", label: "Atkinson", pattern: [ 0, 0, 0, 1.0 / 8.0, 1.0 / 8.0, 0, 1.0 / 8.0, 1.0 / 8.0, 1.0 / 8.0, 0, 0, 0, 1.0 / 8.0, 0, 0 ] }, + { Name: "b", label: "Burkes", pattern: [ 0, 0, 0, 8.0 / 32.0, 4.0 / 32.0, 2.0 / 32.0, 4.0 / 32.0, 8.0 / 32.0, 4.0 / 32.0, 2.0 / 32.0, 0, 0, 0, 0, 0 ] }, + { Name: "s", label: "Sierra", pattern: [ 0, 0, 0, 5.0 / 32.0, 3.0 / 32.0, 2.0 / 32.0, 4.0 / 32.0, 5.0 / 32.0, 4.0 / 32.0, 2.0 / 32.0, 0, 2.0 / 32.0, 3.0 / 32.0, 2.0 / 32.0, 0 ] }, + { Name: "trs", label: "Two-Row Sierra", pattern: [ 0, 0, 0, 4.0 / 16.0, 3.0 / 16.0, 1.0 / 16.0, 2.0 / 16.0, 3.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0, 0, 0, 0, 0, 0 ] }, + { Name: "sl", label: "Sierra Lite", pattern: [ 0, 0, 0, 2.0 / 4.0, 0, 0, 1.0 / 4.0, 1.0 / 4.0, 0, 0, 0, 0, 0, 0, 0 ] } , + //{ Name: "bayer", label: "Bayer", pattern: [0,15/255, 135/255, 45/255, 165/255, 195/255, 75/255, 225/255, 105/255, 60/255, 180/255, 30/255, 150/255 , 240/255, 120/255, 210/255, 90/255] } + ]; + var ditherPattern = null; + var alphaThreshold = 44; + var mattingColor = "rgb(149,149,149)"; + mattingColor = "rgb(192,192,192)"; + + me.getDithering = function(){ + return dithering; + }; + + + me.matting = function(){ + if (imageInfos.canvas){ + var opaqueCanvas = document.createElement("canvas"); + opaqueCanvas.width = imageInfos.canvas.width; + opaqueCanvas.height = imageInfos.canvas.height; + var opaqueCtx = opaqueCanvas.getContext("2d"); + opaqueCtx.fillStyle = mattingColor; + opaqueCtx.fillRect(0,0,opaqueCanvas.width,opaqueCanvas.height); + opaqueCtx.drawImage(imageInfos.canvas,0,0); + + var ctx = imageInfos.canvas.getContext("2d"); + + var data = ctx.getImageData(0, 0, imageInfos.canvas.width, imageInfos.canvas.height); + var opaqueData = opaqueCtx.getImageData(0, 0, imageInfos.canvas.width, imageInfos.canvas.height); + + for(var y = 0; y < imageInfos.canvas.height; y++){ + for(var x = 0; x < imageInfos.canvas.width; x++){ + var index = (x + y * imageInfos.canvas.width) * 4; + var alpha = data.data[index + 3]; + + if(alpha < 255){ + if (alpha>=alphaThreshold){ + data.data[index] = opaqueData.data[index]; + data.data[index + 1] = opaqueData.data[index + 1]; + data.data[index + 2] = opaqueData.data[index + 2]; + data.data[index + 3] = 255; + }else{ + data.data[index + 3] = 0; + } + } + } + } + ctx.putImageData(data, 0, 0); + + EventBus.trigger(EVENT.imageContentChanged) + + } + }; + + me.getColors = function(canvas,stopAtMax) { + imageInfos.canvas = canvas; + + let ctx = canvas.getContext("2d"); + let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + let colorCube = new Uint32Array(256 * 256 * 256); + let colors = []; + + for(var Y = 0; Y < canvas.height; Y++) { + for(var X = 0; X < canvas.width; X++) { + var pixelIndex = (X + Y * canvas.width) * 4; + + var red = data[pixelIndex]; + var green = data[pixelIndex + 1]; + var blue = data[pixelIndex + 2]; + var alpha = data[pixelIndex + 3]; + + if(alpha >= alphaThreshold) { + if(colorCube[red * 256 * 256 + green * 256 + blue] == 0) + colors.push([red,green,blue]); + + colorCube[red * 256 * 256 + green * 256 + blue]++; + if (stopAtMax && colors.length>stopAtMax) return colors; + } + } + } + + colors.sort(function (c1, c2) { return (SrgbToRgb(c1[0]) * 0.21 + SrgbToRgb(c1[1]) * 0.72 + SrgbToRgb(c1[2]) * 0.07) - (SrgbToRgb(c2[0]) * 0.21 + SrgbToRgb(c2[1]) * 0.72 + SrgbToRgb(c2[2]) * 0.07) }); + return colors; + + }; + + me.reduce = function(canvas,colors,_alphaThreshold,ditherIndex,useAlphaThreshold){ + + alphaThreshold = _alphaThreshold || 0; + ditherPattern = dithering[ditherIndex || 0].pattern; + var bitsPerColor = 8; + + var mode = "Palette"; + if (!isNaN(colors)){ + mode = colors; + }else{ + imageInfos.palette = colors; + } + + + imageInfos.canvas = canvas; + imageInfos.colorCount = colors; + + if (useAlphaThreshold) me.matting(); + + processImage(mode, bitsPerColor, ditherPattern, "id"); + }; + + function RgbToSrgb(ColorChannel) { + return Math.pow(ColorChannel / 255, 1 / 2.2) * 255; + } + + function SrgbToRgb(ColorChannel) { + return Math.pow(ColorChannel / 255, 2.2) * 255; + } + + function colorDistance(RedDelta, GreenDelta, BlueDelta, LuminanceDelta) { + return RedDelta * RedDelta + GreenDelta * GreenDelta + BlueDelta * BlueDelta + LuminanceDelta * LuminanceDelta * 6; + } + + function remapImage(canvas, Colors, ditherPattern) { + var Context = canvas.getContext("2d"); + var Data = Context.getImageData(0, 0, canvas.width, canvas.height); + + var MixedColors = []; + + for(var Index = 0; Index < Colors.length; Index++) + MixedColors.push({ Red: Colors[Index].Red, Green: Colors[Index].Green, Blue: Colors[Index].Blue, TrueRed: SrgbToRgb(Colors[Index].Red), TrueGreen: SrgbToRgb(Colors[Index].Green), TrueBlue: SrgbToRgb(Colors[Index].Blue) }); + + if(ditherPattern && ditherPattern[0] > 0 && Colors.length <= 64) { + for(var Index1 = 0; Index1 < Colors.length; Index1++){ + for(var Index2 = Index1 + 1; Index2 < Colors.length; Index2++) { + var Luminance1 = SrgbToRgb(Colors[Index1].Red) * 0.21 + SrgbToRgb(Colors[Index1].Green) * 0.72 + SrgbToRgb(Colors[Index1].Blue) * 0.07; + var Luminance2 = SrgbToRgb(Colors[Index2].Red) * 0.21 + SrgbToRgb(Colors[Index2].Green) * 0.72 + SrgbToRgb(Colors[Index2].Blue) * 0.07; + var LuminanceDeltaSquare = (Luminance1 - Luminance2) * (Luminance1 - Luminance2); + + if(LuminanceDeltaSquare < ditherPattern[0] * ditherPattern[0]) + { + var Red = RgbToSrgb((SrgbToRgb(Colors[Index1].Red) + SrgbToRgb(Colors[Index2].Red)) / 2.0); + var Green = RgbToSrgb((SrgbToRgb(Colors[Index1].Green) + SrgbToRgb(Colors[Index2].Green)) / 2.0); + var Blue = RgbToSrgb((SrgbToRgb(Colors[Index1].Blue) + SrgbToRgb(Colors[Index2].Blue)) / 2.0); + + MixedColors.push({ Index1: Index1, Index2: Index2, Red: Red, Green: Green, Blue: Blue, TrueRed: SrgbToRgb(Red), TrueGreen: SrgbToRgb(Green), TrueBlue: SrgbToRgb(Blue) }); + } + } + } + } + + Colors = MixedColors; + + for(var Y = 0; Y < canvas.height; Y++) { + for(var X = 0; X < canvas.width; X++) + { + var pixelIndex = (X + Y * canvas.width) * 4; + var SrgbIndex = X + Y * canvas.width; + + var Red = Data.data[pixelIndex]; + var Green = Data.data[pixelIndex + 1]; + var Blue = Data.data[pixelIndex + 2]; + var alpha = Data.data[pixelIndex + 3]; + + var TrueRed = SrgbToRgb(Red); + var TrueGreen = SrgbToRgb(Green); + var TrueBlue = SrgbToRgb(Blue); + + var Luminance = TrueRed * 0.21 + TrueGreen * 0.72 + TrueBlue * 0.07; + + if(alpha >= alphaThreshold) { + // Find the matching color index. + + var LastDistance = Number.MAX_VALUE; + var RemappedColorIndex = 0; + + for(var ColorIndex = 0; ColorIndex < Colors.length; ColorIndex++){ + var RedDelta = Colors[ColorIndex].TrueRed - TrueRed; + var GreenDelta = Colors[ColorIndex].TrueGreen - TrueGreen; + var BlueDelta = Colors[ColorIndex].TrueBlue - TrueBlue; + var LuminanceDelta = Colors[ColorIndex].TrueRed * 0.21 + Colors[ColorIndex].TrueGreen * 0.72 + Colors[ColorIndex].TrueBlue * 0.07 - Luminance; + + var Distance = 0; + + if(Colors[ColorIndex].Index1 !== undefined) + { + var RedDelta1 = Colors[Colors[ColorIndex].Index1].TrueRed - TrueRed; + var GreenDelta1 = Colors[Colors[ColorIndex].Index1].TrueGreen - TrueGreen; + var BlueDelta1 = Colors[Colors[ColorIndex].Index1].TrueBlue - TrueBlue; + + var LuminanceDelta1 = Colors[Colors[ColorIndex].Index1].TrueRed * 0.21 + Colors[Colors[ColorIndex].Index1].TrueGreen * 0.72 + Colors[Colors[ColorIndex].Index1].TrueBlue * 0.07 - Luminance; + + var RedDelta2 = Colors[Colors[ColorIndex].Index2].TrueRed - TrueRed; + var GreenDelta2 = Colors[Colors[ColorIndex].Index2].TrueGreen - TrueGreen; + var BlueDelta2 = Colors[Colors[ColorIndex].Index2].TrueBlue - TrueBlue; + + var LuminanceDelta2 = Colors[Colors[ColorIndex].Index2].TrueRed * 0.21 + Colors[Colors[ColorIndex].Index2].TrueGreen * 0.72 + Colors[Colors[ColorIndex].Index2].TrueBlue * 0.07 - Luminance; + + Distance = colorDistance(RedDelta, GreenDelta, BlueDelta, LuminanceDelta) * 4; + Distance += colorDistance(RedDelta1, GreenDelta1, BlueDelta1, LuminanceDelta1); + Distance += colorDistance(RedDelta2, GreenDelta2, BlueDelta2, LuminanceDelta2); + + Distance /= 4 + 1 + 1; + } + else + { + Distance = colorDistance(RedDelta, GreenDelta, BlueDelta, LuminanceDelta); + } + + + if(Distance < LastDistance) + { + RemappedColorIndex = ColorIndex; + LastDistance = Distance; + } + + + } + + if(ditherPattern) { + if(ditherPattern[0] > 0) { // Checker pattern. + if(Colors[RemappedColorIndex].Index1 !== undefined) + { + if((X ^ Y) & 1) + RemappedColorIndex = Colors[RemappedColorIndex].Index1; + else + RemappedColorIndex = Colors[RemappedColorIndex].Index2; + } + + Data.data[pixelIndex] = Colors[RemappedColorIndex].Red; + Data.data[pixelIndex + 1] = Colors[RemappedColorIndex].Green; + Data.data[pixelIndex + 2] = Colors[RemappedColorIndex].Blue; + Data.data[pixelIndex + 3] = alpha; + } + else { // Error diffusion. + var RedDelta = Colors[RemappedColorIndex].Red - Red; + var GreenDelta = Colors[RemappedColorIndex].Green - Green; + var BlueDelta = Colors[RemappedColorIndex].Blue - Blue; + + if(X < canvas.width - 2) + { + if(ditherPattern[4]) + { + Data.data[pixelIndex + 8] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + 8] - RedDelta * ditherPattern[4]))); + Data.data[pixelIndex + 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + 8 + 1] - GreenDelta * ditherPattern[4]))); + Data.data[pixelIndex + 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + 8 + 2] - BlueDelta * ditherPattern[4]))); + } + + if(Y < canvas.height - 1 && ditherPattern[9]) + { + Data.data[pixelIndex + canvas.width * 4 + 8] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 + 8] - RedDelta * ditherPattern[9]))); + Data.data[pixelIndex + canvas.width * 4 + 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 + 8 + 1] - GreenDelta * ditherPattern[9]))); + Data.data[pixelIndex + canvas.width * 4 + 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 + 8 + 2] - BlueDelta * ditherPattern[9]))); + } + + if(Y < canvas.height - 2 && ditherPattern[14]) + { + Data.data[pixelIndex + canvas.width * 2 * 4 + 8] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 + 8] - RedDelta * ditherPattern[14]))); + Data.data[pixelIndex + canvas.width * 2 * 4 + 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 + 8 + 1] - GreenDelta * ditherPattern[14]))); + Data.data[pixelIndex + canvas.width * 2 * 4 + 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 + 8 + 2] - BlueDelta * ditherPattern[14]))); + } + } + + if(X < canvas.width - 1) + { + if(ditherPattern[3]) + { + Data.data[pixelIndex + 4] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + 4] - RedDelta * ditherPattern[3]))); + Data.data[pixelIndex + 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + 4 + 1] - GreenDelta * ditherPattern[3]))); + Data.data[pixelIndex + 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + 4 + 2] - BlueDelta * ditherPattern[3]))); + } + + if(Y < canvas.height - 1 && ditherPattern[8]) + { + Data.data[pixelIndex + canvas.width * 4 + 4] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 + 4] - RedDelta * ditherPattern[8]))); + Data.data[pixelIndex + canvas.width * 4 + 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 + 4 + 1] - GreenDelta * ditherPattern[8]))); + Data.data[pixelIndex + canvas.width * 4 + 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 + 4 + 2] - BlueDelta * ditherPattern[8]))); + } + + if(Y < canvas.height - 2 && ditherPattern[13]) + { + Data.data[pixelIndex + canvas.width * 2 * 4 + 4] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 + 4] - RedDelta * ditherPattern[13]))); + Data.data[pixelIndex + canvas.width * 2 * 4 + 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 + 4 + 1] - GreenDelta * ditherPattern[13]))); + Data.data[pixelIndex + canvas.width * 2 * 4 + 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 + 4 + 2] - BlueDelta * ditherPattern[13]))); + } + } + + if(Y < canvas.height - 1 && ditherPattern[7]) + { + Data.data[pixelIndex + canvas.width * 4] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4] - RedDelta * ditherPattern[7]))); + Data.data[pixelIndex + canvas.width * 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 + 1] - GreenDelta * ditherPattern[7]))); + Data.data[pixelIndex + canvas.width * 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 + 2] - BlueDelta * ditherPattern[7]))); + } + + if(Y < canvas.height - 2 && ditherPattern[12]) + { + Data.data[pixelIndex + canvas.width * 2 * 4] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4] - RedDelta * ditherPattern[12]))); + Data.data[pixelIndex + canvas.width * 2 * 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 + 1] - GreenDelta * ditherPattern[12]))); + Data.data[pixelIndex + canvas.width * 2 * 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 + 2] - BlueDelta * ditherPattern[12]))); + } + + if(X > 0) + { + if(Y < canvas.height - 1 && ditherPattern[6]) + { + Data.data[pixelIndex + canvas.width * 4 - 4] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 - 4] - RedDelta * ditherPattern[6]))); + Data.data[pixelIndex + canvas.width * 4 - 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 - 4 + 1] - GreenDelta * ditherPattern[6]))); + Data.data[pixelIndex + canvas.width * 4 - 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 - 4 + 2] - BlueDelta * ditherPattern[6]))); + } + + if(Y < canvas.height - 2 && ditherPattern[11]) + { + Data.data[pixelIndex + canvas.width * 2 * 4 - 4] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 - 4] - RedDelta * ditherPattern[11]))); + Data.data[pixelIndex + canvas.width * 2 * 4 - 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 - 4 + 1] - GreenDelta * ditherPattern[11]))); + Data.data[pixelIndex + canvas.width * 2 * 4 - 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 - 4 + 2] - BlueDelta * ditherPattern[11]))); + } + } + + if(X > 1) + { + if(Y < canvas.height - 1 && ditherPattern[5]) + { + Data.data[pixelIndex + canvas.width * 4 - 8] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 - 8] - RedDelta * ditherPattern[5]))); + Data.data[pixelIndex + canvas.width * 4 - 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 - 8 + 1] - GreenDelta * ditherPattern[5]))); + Data.data[pixelIndex + canvas.width * 4 - 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 4 - 8 + 2] - BlueDelta * ditherPattern[5]))); + } + + if(Y < canvas.height - 2 && ditherPattern[10]) + { + Data.data[pixelIndex + canvas.width * 2 * 4 - 8] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 - 8] - RedDelta * ditherPattern[10]))); + Data.data[pixelIndex + canvas.width * 2 * 4 - 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 - 8 + 1] - GreenDelta * ditherPattern[10]))); + Data.data[pixelIndex + canvas.width * 2 * 4 - 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[pixelIndex + canvas.width * 2 * 4 - 8 + 2] - BlueDelta * ditherPattern[10]))); + } + } + + Data.data[pixelIndex] = Colors[RemappedColorIndex].Red; + Data.data[pixelIndex + 1] = Colors[RemappedColorIndex].Green; + Data.data[pixelIndex + 2] = Colors[RemappedColorIndex].Blue; + Data.data[pixelIndex + 3] = alpha; + } + } + else { + var c = Colors[RemappedColorIndex]; + Data.data[pixelIndex] = c.Red; + Data.data[pixelIndex + 1] = c.Green; + Data.data[pixelIndex + 2] =c.Blue; + Data.data[pixelIndex + 3] = alpha; + } + } + } + } + + Context.putImageData(Data, 0, 0); + } + + function remapFullPaletteImage(Canvas, BitsPerColor, DitherPattern) { + var Context = Canvas.getContext("2d"); + var Data = Context.getImageData(0, 0, Canvas.width, Canvas.height); + var ShadesPerColor = 1 << BitsPerColor; + + for(var Y = 0; Y < Canvas.height; Y++) { + for(var X = 0; X < Canvas.width; X++) { + var PixelIndex = (X + Y * Canvas.width) * 4; + + var Red = Data.data[PixelIndex]; + var Green = Data.data[PixelIndex + 1]; + var Blue = Data.data[PixelIndex + 2]; + var Alpha = Data.data[PixelIndex + 3]; + var Luminance = Red * 0.21 + Green * 0.72 + Blue * 0.07; + + var MatchingRed = Red; + var MatchingGreen = Green; + var MatchingBlue = Blue; + + if(Alpha >= alphaThreshold) { + if(DitherPattern) { + if(DitherPattern[0] == 1) { + // Checker pattern. + } + else { + // Error diffusion. + var ShadesScale = (ShadesPerColor - 1) / 255; + var InverseShadesScale = 1 / ShadesScale; + + MatchingRed = Math.round(Math.round(Red * ShadesScale) * InverseShadesScale); + MatchingGreen = Math.round(Math.round(Green * ShadesScale) * InverseShadesScale); + MatchingBlue = Math.round(Math.round(Blue * ShadesScale) * InverseShadesScale); + + var RedDelta = MatchingRed - Red; + var GreenDelta = MatchingGreen - Green; + var BlueDelta = MatchingBlue - Blue; + + if(X < Canvas.width - 2) { + if(DitherPattern[4]) + { + Data.data[PixelIndex + 8] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + 8] - RedDelta * DitherPattern[4]))); + Data.data[PixelIndex + 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + 8 + 1] - GreenDelta * DitherPattern[4]))); + Data.data[PixelIndex + 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + 8 + 2] - BlueDelta * DitherPattern[4]))); + } + + if(Y < Canvas.height - 1 && DitherPattern[9]) + { + Data.data[PixelIndex + Canvas.width * 4 + 8] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 + 8] - RedDelta * DitherPattern[9]))); + Data.data[PixelIndex + Canvas.width * 4 + 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 + 8 + 1] - GreenDelta * DitherPattern[9]))); + Data.data[PixelIndex + Canvas.width * 4 + 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 + 8 + 2] - BlueDelta * DitherPattern[9]))); + } + + if(Y < Canvas.height - 2 && DitherPattern[14]) + { + Data.data[PixelIndex + Canvas.width * 2 * 4 + 8] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 + 8] - RedDelta * DitherPattern[14]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 + 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 + 8 + 1] - GreenDelta * DitherPattern[14]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 + 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 + 8 + 2] - BlueDelta * DitherPattern[14]))); + } + } + + if(X < Canvas.width - 1) + { + if(DitherPattern[3]) + { + Data.data[PixelIndex + 4] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + 4] - RedDelta * DitherPattern[3]))); + Data.data[PixelIndex + 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + 4 + 1] - GreenDelta * DitherPattern[3]))); + Data.data[PixelIndex + 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + 4 + 2] - BlueDelta * DitherPattern[3]))); + } + + if(Y < Canvas.height - 1 && DitherPattern[8]) + { + Data.data[PixelIndex + Canvas.width * 4 + 4] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 + 4] - RedDelta * DitherPattern[8]))); + Data.data[PixelIndex + Canvas.width * 4 + 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 + 4 + 1] - GreenDelta * DitherPattern[8]))); + Data.data[PixelIndex + Canvas.width * 4 + 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 + 4 + 2] - BlueDelta * DitherPattern[8]))); + } + + if(Y < Canvas.height - 2 && DitherPattern[13]) + { + Data.data[PixelIndex + Canvas.width * 2 * 4 + 4] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 + 4] - RedDelta * DitherPattern[13]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 + 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 + 4 + 1] - GreenDelta * DitherPattern[13]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 + 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 + 4 + 2] - BlueDelta * DitherPattern[13]))); + } + } + + if(Y < Canvas.height - 1 && DitherPattern[7]) + { + Data.data[PixelIndex + Canvas.width * 4] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4] - RedDelta * DitherPattern[7]))); + Data.data[PixelIndex + Canvas.width * 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 + 1] - GreenDelta * DitherPattern[7]))); + Data.data[PixelIndex + Canvas.width * 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 + 2] - BlueDelta * DitherPattern[7]))); + } + + if(Y < Canvas.height - 2 && DitherPattern[12]) + { + Data.data[PixelIndex + Canvas.width * 2 * 4] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4] - RedDelta * DitherPattern[12]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 + 1] - GreenDelta * DitherPattern[12]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 + 2] - BlueDelta * DitherPattern[12]))); + } + + if(X > 0) + { + if(Y < Canvas.height - 1 && DitherPattern[6]) + { + Data.data[PixelIndex + Canvas.width * 4 - 4] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 - 4] - RedDelta * DitherPattern[6]))); + Data.data[PixelIndex + Canvas.width * 4 - 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 - 4 + 1] - GreenDelta * DitherPattern[6]))); + Data.data[PixelIndex + Canvas.width * 4 - 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 - 4 + 2] - BlueDelta * DitherPattern[6]))); + } + + if(Y < Canvas.height - 2 && DitherPattern[11]) + { + Data.data[PixelIndex + Canvas.width * 2 * 4 - 4] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 - 4] - RedDelta * DitherPattern[11]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 - 4 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 - 4 + 1] - GreenDelta * DitherPattern[11]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 - 4 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 - 4 + 2] - BlueDelta * DitherPattern[11]))); + } + } + + if(X > 1) + { + if(Y < Canvas.height - 1 && DitherPattern[5]) + { + Data.data[PixelIndex + Canvas.width * 4 - 8] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 - 8] - RedDelta * DitherPattern[5]))); + Data.data[PixelIndex + Canvas.width * 4 - 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 - 8 + 1] - GreenDelta * DitherPattern[5]))); + Data.data[PixelIndex + Canvas.width * 4 - 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 4 - 8 + 2] - BlueDelta * DitherPattern[5]))); + } + + if(Y < Canvas.height - 2 && DitherPattern[10]) + { + Data.data[PixelIndex + Canvas.width * 2 * 4 - 8] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 - 8] - RedDelta * DitherPattern[10]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 - 8 + 1] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 - 8 + 1] - GreenDelta * DitherPattern[10]))); + Data.data[PixelIndex + Canvas.width * 2 * 4 - 8 + 2] = Math.round(Math.min(255, Math.max(0, Data.data[PixelIndex + Canvas.width * 2 * 4 - 8 + 2] - BlueDelta * DitherPattern[10]))); + } + } + } + } + + //console.error(MatchingRed); + Data.data[PixelIndex] = MatchingRed; + Data.data[PixelIndex + 1] = MatchingGreen; + Data.data[PixelIndex + 2] = MatchingBlue; + Data.data[PixelIndex + 3] = Alpha; + }else{ + // transparency? + } + } + } + + Context.putImageData(Data, 0, 0); + } + + + + function processImage(colorCount, bitsPerColor, ditherPattern, Id) { + var colors = []; + var useTransparentColor = true; + + var transparentColor = useTransparentColor?Palette.getBackgroundColor():undefined; + + if(colorCount === "Palette") { + for(var i = 0; i < imageInfos.palette.length; i++){ + var color = Color.fromString(imageInfos.palette[i]); + colors.push({ Red: color[0], Green: color[1], Blue: color[2], Alpha: 255 }); + } + imageInfos.QuantizedColors = colors; + remapImage(imageInfos.canvas, colors, ditherPattern); + }else{ + if(!imageInfos.Colors || imageInfos.Colors.length > colorCount) { + if(colorCount === 2){ + if (useTransparentColor){ + colors.push({ Red: transparentColor[0], Green: transparentColor[1], Blue: transparentColor[2] }); + }else{ + colors.push({ Red: 255, Green: 255, Blue: 255 }); + } + colors.push({ Red: 0, Green: 0, Blue: 0 }); + + imageInfos.QuantizedColors = colors; + + remapImage(imageInfos.canvas, colors, ditherPattern); + + updateImageWindow(Id); + } + else + { + if (useTransparentColor) colorCount--; + var MaxRecursionDepth = 1; + + while(Math.pow(2, MaxRecursionDepth) < colorCount) MaxRecursionDepth++; + + + var QuantizeWorker = new Worker( + new URL('../workers/quantize.js', import.meta.url), + {type: 'module'} + ); + + var QuantizeData = { LineIndex: 0, CanvasData: imageInfos.canvas.getContext("2d").getImageData(0, 0, imageInfos.canvas.width, imageInfos.canvas.height), MaxRecursionDepth: MaxRecursionDepth, BitsPerColor: bitsPerColor, ColorCount: colorCount, transparentColor: transparentColor}; + + QuantizeWorker.addEventListener( + "message", + function(e) + { + imageInfos.QuantizedColors = e.data.Colors; + + + remapImage(imageInfos.canvas, imageInfos.QuantizedColors, ditherPattern); + + if (useTransparentColor && colorCount>4){ + imageInfos.QuantizedColors.unshift({Red: transparentColor[0], Green: transparentColor[1], Blue: transparentColor[2]}) + } + + updateImageWindow(Id); + }, + false); + + QuantizeWorker.postMessage(QuantizeData); + + } + + return; + }else{ + for (var Index = 0; Index < imageInfos.Colors.length; Index++) + colors.push({ Red: imageInfos.Colors[Index].Red, Green: imageInfos.Colors[Index].Green, Blue: imageInfos.Colors[Index].Blue }); + } + + var ShadesPerColor = 1 << bitsPerColor; + + for(var Index = 0; Index < colors.length; Index++) + { + var ShadesScale = (ShadesPerColor - 1) / 255; + var InverseShadesScale = 1 / ShadesScale; + + colors[Index].Red = Math.round(Math.round(colors[Index].Red * ShadesScale) * InverseShadesScale); + colors[Index].Green = Math.round(Math.round(colors[Index].Green * ShadesScale) * InverseShadesScale); + colors[Index].Blue = Math.round(Math.round(colors[Index].Blue * ShadesScale) * InverseShadesScale); + } + } + + // Remap image. + + if(colorCount === "Palette") { + remapFullPaletteImage(imageInfos.canvas, bitsPerColor, ditherPattern); + } + + imageInfos.QuantizedColors = colors; + + updateImageWindow(Id); + } + + + function updateImageWindow(){ + //console.error(imageInfos); + + if (imageInfos.QuantizedColors){ + var palette = []; + imageInfos.QuantizedColors.forEach(function(c){ + palette.push([c.Red,c.Green,c.Blue]); + }); + //console.error(palette); + let f = ImageFile.getCurrentFile(); + let ctx = ImageFile.getActiveContext(); + ctx.clearRect(0,0,f.width,f.height); + ctx.drawImage(imageInfos.canvas,0,0); + EventBus.trigger(EVENT.layerContentChanged,{keepImageCache:true}); + Palette.set(palette); + EventBus.trigger(COMMAND.INFO); + //IconEditor.setPalette(palette); + //IconEditor.updateIcon(); + } + } + + me.rotate = function(canvas,left){ + let ctx = canvas.getContext("2d"); + let newCanvas = document.createElement("canvas"); + let w = canvas.height; + let h = canvas.width; + newCanvas.width = w; + newCanvas.height = h; + let newCtx = newCanvas.getContext("2d"); + newCtx.save(); + newCtx.translate(w/2,h/2); + newCtx.rotate(90*Math.PI/180 * (left?-1:1)); + newCtx.drawImage(canvas, -h/2,-w/2); + newCtx.restore(); + + canvas.height = h; + canvas.width = w; + ctx.clearRect(0,0,w,h); + ctx.drawImage(newCanvas,0,0); + newCanvas = null; + } + + me.bayer = function (ctx, threshold, whiteTransparent) { + let image = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height); + const thresholdMap = [ + [15, 135, 45, 165], + [195, 75, 225, 105], + [60, 180, 30, 150], + [240, 120, 210, 90], + ]; + + for (let i = 0; i < image.data.length; i += 4) { + const luminance = (image.data[i] * 0.299) + (image.data[i + 1] * 0.587) + (image.data[i + 2] * 0.114); + const x = i / 4 % image.width; + const y = Math.floor(i / 4 / image.width); + const map = Math.floor((luminance + thresholdMap[x % 4][y % 4]) / 2); + //console.error(map); + let value = map < threshold ? 0 : 255; + image.data.fill(value, i, i + 3); + if (whiteTransparent && value===255){ + image.data[i+3] = 0; + } + } + + ctx.putImageData(image,0,0); + + } + + + // https://github.com/ytiurin/downscale/blob/master/src/downsample.js + // MIT License - Copyright (c) 2017 Eugene Tiurin + // seems "good enough" for our purpose and reasonable fast + // TODO: implement in webworker + // good article: https://stackoverflow.com/questions/18922880/html5-canvas-resize-downscale-image-high-quality + // also checkout https://github.com/nodeca/pica + // TODO; checkout createImageBitmap + me.downScale = function(sourceImageData,destWidth, destHeight){ + function round(val) + { + return (val + 0.49) << 0 + } + + function downsample(sourceImageData, destWidth, destHeight, sourceX, sourceY, + sourceWidth, sourceHeight) + { + var dest = new ImageData(destWidth, destHeight) + + var SOURCE_DATA = new Int32Array(sourceImageData.data.buffer) + var SOURCE_WIDTH = sourceImageData.width + + var DEST_DATA = new Int32Array(dest.data.buffer) + var DEST_WIDTH = dest.width + + var SCALE_FACTOR_X = destWidth / sourceWidth + var SCALE_FACTOR_Y = destHeight / sourceHeight + var SCALE_RANGE_X = round(1 / SCALE_FACTOR_X) + var SCALE_RANGE_Y = round(1 / SCALE_FACTOR_Y) + var SCALE_RANGE_SQR = SCALE_RANGE_X * SCALE_RANGE_Y + + for (var destRow = 0; destRow < dest.height; destRow++) { + for (var destCol = 0; destCol < DEST_WIDTH; destCol++) { + + var sourceInd = sourceX + round(destCol / SCALE_FACTOR_X) + + (sourceY + round(destRow / SCALE_FACTOR_Y)) * SOURCE_WIDTH + + var destRed = 0 + var destGreen = 0 + var destBlue = 0 + var destAlpha = 0 + + for (var sourceRow = 0; sourceRow < SCALE_RANGE_Y; sourceRow++) + for (var sourceCol = 0; sourceCol < SCALE_RANGE_X; sourceCol++) { + var sourcePx = SOURCE_DATA[sourceInd + sourceCol + sourceRow * SOURCE_WIDTH] + destRed += sourcePx << 24 >>> 24 + destGreen += sourcePx << 16 >>> 24 + destBlue += sourcePx << 8 >>> 24 + destAlpha += sourcePx >>> 24 + } + + destRed = round(destRed / SCALE_RANGE_SQR) + destGreen = round(destGreen / SCALE_RANGE_SQR) + destBlue = round(destBlue / SCALE_RANGE_SQR) + destAlpha = round(destAlpha / SCALE_RANGE_SQR) + + DEST_DATA[destCol + destRow * DEST_WIDTH] = + (destAlpha << 24) | + (destBlue << 16) | + (destGreen << 8) | + (destRed) + } + } + + return dest + } + + return downsample(sourceImageData, destWidth, destHeight, 0, 0, sourceImageData.width, sourceImageData.height); + } + + + + // http://jsfiddle.net/HZewg/1/ + me.biCubic = function(sourceImageData,destWidth, destHeight){ + + function TERP(t, a, b, c, d){ + return 0.5 * (c - a + (2.0*a - 5.0*b + 4.0*c - d + (3.0*(b - c) + d - a)*t)*t)*t + b; + } + + function ivect(ix, iy, w) { + // byte array, r,g,b,a + return((ix + w * iy) * 4); + } + + function BicubicInterpolation(x, y, values){ + var i0, i1, i2, i3; + + i0 = TERP(x, values[0][0], values[1][0], values[2][0], values[3][0]); + i1 = TERP(x, values[0][1], values[1][1], values[2][1], values[3][1]); + i2 = TERP(x, values[0][2], values[1][2], values[2][2], values[3][2]); + i3 = TERP(x, values[0][3], values[1][3], values[2][3], values[3][3]); + return TERP(y, i0, i1, i2, i3); + } + + + var dest = new ImageData(destWidth, destHeight); + + bicubic(sourceImageData, dest); + return dest; + function bicubic(srcImg, destImg) { + + let scaleX = destWidth/sourceImageData.width; + let scaleY = destHeight/sourceImageData.height; + + var i, j; + var dx, dy; + var repeatX, repeatY; + var offset_row0, offset_row1, offset_row2, offset_row3; + var offset_col0, offset_col1, offset_col2, offset_col3; + var red_pixels, green_pixels, blue_pixels, alpha_pixels; + for (i = 0; i < destImg.height; ++i) { + let iyv = i / scaleY; + let iy0 = Math.floor(iyv); + + // We have to special-case the pixels along the border and repeat their values if necessary + repeatY = 0; + if(iy0 < 1) repeatY = -1; + else if(iy0 > srcImg.height - 3) repeatY = iy0 - (srcImg.height - 3); + + for (j = 0; j < destImg.width; ++j) { + let ixv = j / scaleX; + let ix0 = Math.floor(ixv); + + // We have to special-case the pixels along the border and repeat their values if necessary + repeatX = 0; + if(ix0 < 1) repeatX = -1; + else if(ix0 > srcImg.width - 3) repeatX = ix0 - (srcImg.width - 3); + + offset_row1 = ((iy0) * srcImg.width + ix0) * 4; + offset_row0 = repeatY < 0 ? offset_row1 : ((iy0-1) * srcImg.width + ix0) * 4; + offset_row2 = repeatY > 1 ? offset_row1 : ((iy0+1) * srcImg.width + ix0) * 4; + offset_row3 = repeatY > 0 ? offset_row2 : ((iy0+2) * srcImg.width + ix0) * 4; + + offset_col1 = 0; + offset_col0 = repeatX < 0 ? offset_col1 : -4; + offset_col2 = repeatX > 1 ? offset_col1 : 4; + offset_col3 = repeatX > 0 ? offset_col2 : 8; + + //Each offset is for the start of a row's red pixels + red_pixels = [[srcImg.data[offset_row0+offset_col0], srcImg.data[offset_row1+offset_col0], srcImg.data[offset_row2+offset_col0], srcImg.data[offset_row3+offset_col0]], + [srcImg.data[offset_row0+offset_col1], srcImg.data[offset_row1+offset_col1], srcImg.data[offset_row2+offset_col1], srcImg.data[offset_row3+offset_col1]], + [srcImg.data[offset_row0+offset_col2], srcImg.data[offset_row1+offset_col2], srcImg.data[offset_row2+offset_col2], srcImg.data[offset_row3+offset_col2]], + [srcImg.data[offset_row0+offset_col3], srcImg.data[offset_row1+offset_col3], srcImg.data[offset_row2+offset_col3], srcImg.data[offset_row3+offset_col3]]]; + offset_row0++; + offset_row1++; + offset_row2++; + offset_row3++; + //Each offset is for the start of a row's green pixels + green_pixels = [[srcImg.data[offset_row0+offset_col0], srcImg.data[offset_row1+offset_col0], srcImg.data[offset_row2+offset_col0], srcImg.data[offset_row3+offset_col0]], + [srcImg.data[offset_row0+offset_col1], srcImg.data[offset_row1+offset_col1], srcImg.data[offset_row2+offset_col1], srcImg.data[offset_row3+offset_col1]], + [srcImg.data[offset_row0+offset_col2], srcImg.data[offset_row1+offset_col2], srcImg.data[offset_row2+offset_col2], srcImg.data[offset_row3+offset_col2]], + [srcImg.data[offset_row0+offset_col3], srcImg.data[offset_row1+offset_col3], srcImg.data[offset_row2+offset_col3], srcImg.data[offset_row3+offset_col3]]]; + offset_row0++; + offset_row1++; + offset_row2++; + offset_row3++; + //Each offset is for the start of a row's blue pixels + blue_pixels = [[srcImg.data[offset_row0+offset_col0], srcImg.data[offset_row1+offset_col0], srcImg.data[offset_row2+offset_col0], srcImg.data[offset_row3+offset_col0]], + [srcImg.data[offset_row0+offset_col1], srcImg.data[offset_row1+offset_col1], srcImg.data[offset_row2+offset_col1], srcImg.data[offset_row3+offset_col1]], + [srcImg.data[offset_row0+offset_col2], srcImg.data[offset_row1+offset_col2], srcImg.data[offset_row2+offset_col2], srcImg.data[offset_row3+offset_col2]], + [srcImg.data[offset_row0+offset_col3], srcImg.data[offset_row1+offset_col3], srcImg.data[offset_row2+offset_col3], srcImg.data[offset_row3+offset_col3]]]; + offset_row0++; + offset_row1++; + offset_row2++; + offset_row3++; + //Each offset is for the start of a row's alpha pixels + alpha_pixels =[[srcImg.data[offset_row0+offset_col0], srcImg.data[offset_row1+offset_col0], srcImg.data[offset_row2+offset_col0], srcImg.data[offset_row3+offset_col0]], + [srcImg.data[offset_row0+offset_col1], srcImg.data[offset_row1+offset_col1], srcImg.data[offset_row2+offset_col1], srcImg.data[offset_row3+offset_col1]], + [srcImg.data[offset_row0+offset_col2], srcImg.data[offset_row1+offset_col2], srcImg.data[offset_row2+offset_col2], srcImg.data[offset_row3+offset_col2]], + [srcImg.data[offset_row0+offset_col3], srcImg.data[offset_row1+offset_col3], srcImg.data[offset_row2+offset_col3], srcImg.data[offset_row3+offset_col3]]]; + + // overall coordinates to unit square + dx = ixv - ix0; dy = iyv - iy0; + + let idxD = ivect(j, i, destImg.width); + + destImg.data[idxD] = BicubicInterpolation(dx, dy, red_pixels); + destImg.data[idxD+1] = BicubicInterpolation(dx, dy, green_pixels); + destImg.data[idxD+2] = BicubicInterpolation(dx, dy, blue_pixels); + destImg.data[idxD+3] = BicubicInterpolation(dx, dy, alpha_pixels); + } + } + } + + } + + return me; +}(); + +export default ImageProcessing; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/lzw.js b/app/web-tools/dpaint/_script/util/lzw.js new file mode 100644 index 00000000..f3b862e2 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/lzw.js @@ -0,0 +1,338 @@ +let LZW = function() { + + var me = {}; + + me.encode= function(pixels, imgWidth, imgHeight, color_depth) { + // adapted from https://github.com/antimatter15/jsgif/blob/master/LZWEncoder.js + // MIT License + // Copyright (c) 2010-2014 Kevin Kwok + + var EOF = -1; + var initCodeSize; + var remaining; + var curPixel; + var BITS = 12; + var HSIZE = 5003; // 80% occupancy + var n_bits; // number of bits/code + var maxbits = BITS; // user settable max # bits/code + var maxcode; // maximum code, given n_bits + var maxmaxcode = 1 << BITS; // should NEVER generate this code + var htab = []; + var codetab = []; + var hsize = HSIZE; // for dynamic table sizing + var free_ent = 0; // first unused entry + var clear_flg = false; + var g_init_bits; + var ClearCode; + var EOFCode; + var cur_accum = 0; + var cur_bits = 0; + var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF]; + var a_count; + + + var accum = []; + + + initCodeSize = Math.max(2, color_depth); + + let out = []; + out.push(initCodeSize); // write "initial code size" byte + + remaining = imgWidth * imgHeight; // reset navigation variables + curPixel = 0; + compress(initCodeSize + 1, out); // compress and write the pixel data + //os.writeByte(0); // write block terminator + out.push(0); // write block terminator + + return out; + + + // Add a character to the end of the current packet, and if it is 254 + // characters, flush the packet to disk. + function char_out(c, outs) { + accum[a_count++] = c; + if (a_count >= 254) flush_char(outs); + }; + + // Clear out the hash table + // table clear for block compress + + function cl_block(outs) { + cl_hash(hsize); + free_ent = ClearCode + 2; + clear_flg = true; + output(ClearCode, outs); + }; + + // reset code table + function cl_hash(hsize) { + for (var i = 0; i < hsize; ++i) htab[i] = -1; + }; + + function compress(init_bits, outs) { + + var fcode; + var i; /* = 0 */ + var c; + var ent; + var disp; + var hsize_reg; + var hshift; + + // Set up the globals: g_init_bits - initial number of bits + g_init_bits = init_bits; + + // Set up the necessary values + clear_flg = false; + n_bits = g_init_bits; + maxcode = MAXCODE(n_bits); + + ClearCode = 1 << (init_bits - 1); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + a_count = 0; // clear packet + + ent = nextPixel(); + + hshift = 0; + for (fcode = hsize; fcode < 65536; fcode *= 2) + ++hshift; + hshift = 8 - hshift; // set hash code range bound + + hsize_reg = hsize; + cl_hash(hsize_reg); // clear hash table + + output(ClearCode, outs); + + outer_loop: while ((c = nextPixel()) != EOF) { + fcode = (c << maxbits) + ent; + i = (c << hshift) ^ ent; // xor hashing + + if (htab[i] == fcode) { + ent = codetab[i]; + continue; + } + + else if (htab[i] >= 0) { // non-empty slot + + disp = hsize_reg - i; // secondary hash (after G. Knott) + if (i === 0) disp = 1; + + do { + if ((i -= disp) < 0) + i += hsize_reg; + + if (htab[i] == fcode) { + ent = codetab[i]; + continue outer_loop; + } + } while (htab[i] >= 0); + } + + output(ent, outs); + ent = c; + if (free_ent < maxmaxcode) { + codetab[i] = free_ent++; // code -> hashtable + htab[i] = fcode; + } + else cl_block(outs); + } + + // Put out the final code. + output(ent, outs); + output(EOFCode, outs); + }; + + // ---------------------------------------------------------------------------- + + + + // Flush the packet to disk, and reset the accumulator + function flush_char(outs) { + if (a_count > 0) { + if (outs.writeByte){ + outs.writeByte(a_count); + for (var i = 0; i < a_count; i++){ + outs.writeByte(accum[i]); + } + }else{ + outs.push(a_count); + for (var i = 0; i < a_count; i++){ + outs.push(accum[i]); + } + } + a_count = 0; + } + }; + + function MAXCODE(n_bits) { + return (1 << n_bits) - 1; + }; + + // ---------------------------------------------------------------------------- + // Return the next pixel from the image + // ---------------------------------------------------------------------------- + + function nextPixel() { + if (remaining === 0) return EOF; + --remaining; + var pix = pixels[curPixel++]; + return pix & 0xff; + }; + + function output(code, outs) { + + cur_accum &= masks[cur_bits]; + + if (cur_bits > 0) cur_accum |= (code << cur_bits); + else cur_accum = code; + + cur_bits += n_bits; + + while (cur_bits >= 8) { + char_out((cur_accum & 0xff), outs); + cur_accum >>= 8; + cur_bits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + + if (free_ent > maxcode || clear_flg) { + + if (clear_flg) { + + maxcode = MAXCODE(n_bits = g_init_bits); + clear_flg = false; + + } else { + + ++n_bits; + if (n_bits == maxbits) maxcode = maxmaxcode; + else maxcode = MAXCODE(n_bits); + } + } + + if (code == EOFCode) { + + // At EOF, write the rest of the buffer. + while (cur_bits > 0) { + char_out((cur_accum & 0xff), outs); + cur_accum >>= 8; + cur_bits -= 8; + } + + flush_char(outs); + } + } + } + + + + me.decode = function(data, minCodeSize, pixelCount){ + // adapted from https://github.com/matt-way/gifuct-js + // MIT license + // Copyright (c) 2015 Matt Way + + const MAX_STACK_SIZE = 4096; + const nullCode = -1; + const npix = pixelCount; + let available, clear, code_mask, code_size, end_of_information, in_code, old_code, code, i, data_size; + + const dstPixels = new Array(pixelCount); + const prefix = new Array(MAX_STACK_SIZE); + const suffix = new Array(MAX_STACK_SIZE); + const pixelStack = new Array(MAX_STACK_SIZE + 1); + + // Initialize GIF data stream decoder. + data_size = minCodeSize; + clear = 1 << data_size; + end_of_information = clear + 1; + available = clear + 2; + old_code = nullCode; + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + for (code = 0; code < clear; code++) { + prefix[code] = 0; + suffix[code] = code; + } + + // Decode GIF pixel stream. + let datum, bits, count, first, top, pi, bi + datum = bits = count = first = top = pi = bi = 0; + for (i = 0; i < npix; ) { + if (top === 0) { + if (bits < code_size) { + // get the next byte + datum += data[bi] << bits; + bits += 8; + bi++; + continue; + } + // Get the next code. + code = datum & code_mask; + datum >>= code_size; + bits -= code_size; + // Interpret the code + if (code > available || code == end_of_information) break; + if (code == clear) { + // Reset decoder. + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + available = clear + 2; + old_code = nullCode; + continue; + } + if (old_code == nullCode) { + pixelStack[top++] = suffix[code]; + old_code = code; + first = code; + continue; + } + in_code = code; + if (code == available) { + pixelStack[top++] = first; + code = old_code; + } + while (code > clear) { + pixelStack[top++] = suffix[code]; + code = prefix[code]; + } + + first = suffix[code] & 0xff; + pixelStack[top++] = first; + + // add a new string to the table, but only if space is available + // if not, just continue with current table until a clear code is found + // (deferred clear code implementation as per GIF spec) + if (available < MAX_STACK_SIZE) { + prefix[available] = old_code; + suffix[available] = first; + available++; + if ((available & code_mask) === 0 && available < MAX_STACK_SIZE) { + code_size++; + code_mask += available; + } + } + old_code = in_code; + } + // Pop a pixel off the pixel stack. + top--; + dstPixels[pi++] = pixelStack[top]; + i++; + } + + for (i = pi; i < npix; i++) { + dstPixels[i] = 0; // clear missing pixels + } + + return dstPixels; + } + + + return me; +}; + +export default LZW(); \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/md5.js b/app/web-tools/dpaint/_script/util/md5.js new file mode 100644 index 00000000..7890a2c1 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/md5.js @@ -0,0 +1,752 @@ +(function (factory) { + if (typeof exports === 'object') { + // Node/CommonJS + module.exports = factory(); + } else if (typeof define === 'function' && define.amd) { + // AMD + define(factory); + } else { + // Browser globals (with support for web workers) + var glob; + + try { + glob = window; + } catch (e) { + glob = self; + } + + glob.SparkMD5 = factory(); + } +}(function (undefined) { + + 'use strict'; + + /* + * Fastest md5 implementation around (JKM md5). + * Credits: Joseph Myers + * + * @see http://www.myersdaily.org/joseph/javascript/md5-text.html + * @see http://jsperf.com/md5-shootout/7 + */ + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + }, + hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + + + function cmn(q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + } + + function md5cycle(x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a += (b & c | ~b & d) + k[0] - 680876936 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[1] - 389564586 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[2] + 606105819 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[3] - 1044525330 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[4] - 176418897 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[5] + 1200080426 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[6] - 1473231341 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[7] - 45705983 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[8] + 1770035416 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[9] - 1958414417 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[10] - 42063 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[11] - 1990404162 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[12] + 1804603682 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[13] - 40341101 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[14] - 1502002290 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[15] + 1236535329 | 0; + b = (b << 22 | b >>> 10) + c | 0; + + a += (b & d | c & ~d) + k[1] - 165796510 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[6] - 1069501632 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[11] + 643717713 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[0] - 373897302 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[5] - 701558691 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[10] + 38016083 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[15] - 660478335 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[4] - 405537848 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[9] + 568446438 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[14] - 1019803690 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[3] - 187363961 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[8] + 1163531501 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[13] - 1444681467 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[2] - 51403784 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[7] + 1735328473 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[12] - 1926607734 | 0; + b = (b << 20 | b >>> 12) + c | 0; + + a += (b ^ c ^ d) + k[5] - 378558 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[8] - 2022574463 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[11] + 1839030562 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[14] - 35309556 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[1] - 1530992060 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[4] + 1272893353 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[7] - 155497632 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[10] - 1094730640 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[13] + 681279174 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[0] - 358537222 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[3] - 722521979 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[6] + 76029189 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[9] - 640364487 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[12] - 421815835 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[15] + 530742520 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[2] - 995338651 | 0; + b = (b << 23 | b >>> 9) + c | 0; + + a += (c ^ (b | ~d)) + k[0] - 198630844 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[5] - 57434055 | 0; + b = (b << 21 |b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[10] - 1051523 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0; + b = (b << 21 |b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[15] - 30611744 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0; + b = (b << 21 |b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[4] - 145523070 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[2] + 718787259 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[9] - 343485551 | 0; + b = (b << 21 | b >>> 11) + c | 0; + + x[0] = a + x[0] | 0; + x[1] = b + x[1] | 0; + x[2] = c + x[2] | 0; + x[3] = d + x[3] | 0; + } + + function md5blk(s) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + } + + function md5blk_array(a) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + } + + function md51(s) { + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + } + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + return state; + } + + function md51_array(a) { + var n = a.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + + // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 + // containing the last element of the parent array if the sub array specified starts + // beyond the length of the parent array - weird. + // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue + a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); + + length = a.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << ((i % 4) << 3); + } + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + + return state; + } + + function rhex(n) { + var s = '', + j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + } + return s; + } + + function hex(x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(''); + } + + // In some cases the fast add32 function cannot be used.. + if (hex(md51('hello')) !== '5d41402abc4b2a76b9719d911017c592') { + add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + } + + // --------------------------------------------------- + + /** + * ArrayBuffer slice polyfill. + * + * @see https://github.com/ttaubert/node-arraybuffer-slice + */ + + if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) { + (function () { + function clamp(val, length) { + val = (val | 0) || 0; + + if (val < 0) { + return Math.max(val + length, 0); + } + + return Math.min(val, length); + } + + ArrayBuffer.prototype.slice = function (from, to) { + var length = this.byteLength, + begin = clamp(from, length), + end = length, + num, + target, + targetArray, + sourceArray; + + if (to !== undefined) { + end = clamp(to, length); + } + + if (begin > end) { + return new ArrayBuffer(0); + } + + num = end - begin; + target = new ArrayBuffer(num); + targetArray = new Uint8Array(target); + + sourceArray = new Uint8Array(this, begin, num); + targetArray.set(sourceArray); + + return target; + }; + })(); + } + + // --------------------------------------------------- + + /** + * Helpers. + */ + + function toUtf8(str) { + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + return str; + } + + function utf8Str2ArrayBuffer(str, returnUInt8Array) { + var length = str.length, + buff = new ArrayBuffer(length), + arr = new Uint8Array(buff), + i; + + for (i = 0; i < length; i += 1) { + arr[i] = str.charCodeAt(i); + } + + return returnUInt8Array ? arr : buff; + } + + function arrayBuffer2Utf8Str(buff) { + return String.fromCharCode.apply(null, new Uint8Array(buff)); + } + + function concatenateArrayBuffers(first, second, returnUInt8Array) { + var result = new Uint8Array(first.byteLength + second.byteLength); + + result.set(new Uint8Array(first)); + result.set(new Uint8Array(second), first.byteLength); + + return returnUInt8Array ? result : result.buffer; + } + + function hexToBinaryString(hex) { + var bytes = [], + length = hex.length, + x; + + for (x = 0; x < length - 1; x += 2) { + bytes.push(parseInt(hex.substr(x, 2), 16)); + } + + return String.fromCharCode.apply(String, bytes); + } + + // --------------------------------------------------- + + /** + * SparkMD5 OOP implementation. + * + * Use this class to perform an incremental md5, otherwise use the + * static methods instead. + */ + + function SparkMD5() { + // call reset to init the instance + this.reset(); + } + + /** + * Appends a string. + * A conversion will be applied if an utf8 string is detected. + * + * @param {String} str The string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.append = function (str) { + // Converts the string to utf8 bytes if necessary + // Then append as binary + this.appendBinary(toUtf8(str)); + + return this; + }; + + /** + * Appends a binary string. + * + * @param {String} contents The binary string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.appendBinary = function (contents) { + this._buff += contents; + this._length += contents.length; + + var length = this._buff.length, + i; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i))); + } + + this._buff = this._buff.substring(i - 64); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * + * @param {Boolean} raw True to get the raw string, false to get the hex string + * + * @return {String} The result + */ + SparkMD5.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + i, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = hex(this._hash); + + if (raw) { + ret = hexToBinaryString(ret); + } + + this.reset(); + + return ret; + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.reset = function () { + this._buff = ''; + this._length = 0; + this._hash = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Gets the internal state of the computation. + * + * @return {Object} The state + */ + SparkMD5.prototype.getState = function () { + return { + buff: this._buff, + length: this._length, + hash: this._hash.slice() + }; + }; + + /** + * Gets the internal state of the computation. + * + * @param {Object} state The state + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.setState = function (state) { + this._buff = state.buff; + this._length = state.length; + this._hash = state.hash; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other additional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.prototype.destroy = function () { + delete this._hash; + delete this._buff; + delete this._length; + }; + + /** + * Finish the final calculation based on the tail. + * + * @param {Array} tail The tail (will be modified) + * @param {Number} length The length of the remaining buffer + */ + SparkMD5.prototype._finish = function (tail, length) { + var i = length, + tmp, + lo, + hi; + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(this._hash, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Do the final computation based on the tail and length + // Beware that the final length may not fit in 32 bits so we take care of that + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + md5cycle(this._hash, tail); + }; + + /** + * Performs the md5 hash on a string. + * A conversion will be applied if utf8 string is detected. + * + * @param {String} str The string + * @param {Boolean} [raw] True to get the raw string, false to get the hex string + * + * @return {String} The result + */ + SparkMD5.hash = function (str, raw) { + // Converts the string to utf8 bytes if necessary + // Then compute it using the binary function + return SparkMD5.hashBinary(toUtf8(str), raw); + }; + + /** + * Performs the md5 hash on a binary string. + * + * @param {String} content The binary string + * @param {Boolean} [raw] True to get the raw string, false to get the hex string + * + * @return {String} The result + */ + SparkMD5.hashBinary = function (content, raw) { + var hash = md51(content), + ret = hex(hash); + + return raw ? hexToBinaryString(ret) : ret; + }; + + // --------------------------------------------------- + + /** + * SparkMD5 OOP implementation for array buffers. + * + * Use this class to perform an incremental md5 ONLY for array buffers. + */ + SparkMD5.ArrayBuffer = function () { + // call reset to init the instance + this.reset(); + }; + + /** + * Appends an array buffer. + * + * @param {ArrayBuffer} arr The array to be appended + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.append = function (arr) { + var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), + length = buff.length, + i; + + this._length += arr.byteLength; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i))); + } + + this._buff = (i - 64) < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * + * @param {Boolean} raw True to get the raw string, false to get the hex string + * + * @return {String} The result + */ + SparkMD5.ArrayBuffer.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + i, + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = hex(this._hash); + + if (raw) { + ret = hexToBinaryString(ret); + } + + this.reset(); + + return ret; + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.reset = function () { + this._buff = new Uint8Array(0); + this._length = 0; + this._hash = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Gets the internal state of the computation. + * + * @return {Object} The state + */ + SparkMD5.ArrayBuffer.prototype.getState = function () { + var state = SparkMD5.prototype.getState.call(this); + + // Convert buffer to a string + state.buff = arrayBuffer2Utf8Str(state.buff); + + return state; + }; + + /** + * Gets the internal state of the computation. + * + * @param {Object} state The state + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.setState = function (state) { + // Convert string to buffer + state.buff = utf8Str2ArrayBuffer(state.buff, true); + + return SparkMD5.prototype.setState.call(this, state); + }; + + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + + /** + * Performs the md5 hash on an array buffer. + * + * @param {ArrayBuffer} arr The array buffer + * @param {Boolean} [raw] True to get the raw string, false to get the hex one + * + * @return {String} The result + */ + SparkMD5.ArrayBuffer.hash = function (arr, raw) { + var hash = md51_array(new Uint8Array(arr)), + ret = hex(hash); + + return raw ? hexToBinaryString(ret) : ret; + }; + + return SparkMD5; +})); + diff --git a/app/web-tools/dpaint/_script/util/stackBlur.js b/app/web-tools/dpaint/_script/util/stackBlur.js new file mode 100644 index 00000000..9daf8195 --- /dev/null +++ b/app/web-tools/dpaint/_script/util/stackBlur.js @@ -0,0 +1,2 @@ +function t(r){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(r)}var r=[512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,289,287,285,282,280,278,275,273,271,269,267,265,263,261,259],a=[9,11,12,13,13,14,14,15,15,15,15,16,16,16,16,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24];function n(t,r,a,n,e,i){if("string"==typeof t&&(t=document.getElementById(t)),t&&"naturalWidth"in t){var g=e?"offset":"natural",v=t[g+"Width"],s=t[g+"Height"];if("string"==typeof r&&(r=document.getElementById(r)),r&&"getContext"in r){i||(r.style.width=v+"px",r.style.height=s+"px"),r.width=v,r.height=s;var u=r.getContext("2d");u.clearRect(0,0,v,s),u.drawImage(t,0,0,t.naturalWidth,t.naturalHeight,0,0,v,s),isNaN(a)||a<1||(n?o(r,0,0,v,s,a):f(r,0,0,v,s,a))}}}function e(r,a,n,e,o){if("string"==typeof r&&(r=document.getElementById(r)),!r||"object"!==t(r)||!("getContext"in r))throw new TypeError("Expecting canvas with `getContext` method in processCanvasRGB(A) calls!");var i=r.getContext("2d");try{return i.getImageData(a,n,e,o)}catch(t){throw new Error("unable to access image data: "+t)}}function o(t,r,a,n,o,f){if(!(isNaN(f)||f<1)){f|=0;var g=e(t,r,a,n,o);g=i(g,r,a,n,o,f),t.getContext("2d").putImageData(g,r,a)}}function i(t,n,e,o,i,f){for(var g,s=t.data,u=2*f+1,c=o-1,l=i-1,x=f+1,b=x*(x+1)/2,y=new v,h=y,m=1;m>E;if(s[C+3]=Z,0!==Z){var $=255/Z;s[C]=(F*B>>E)*$,s[C+1]=(J*B>>E)*$,s[C+2]=(K*B>>E)*$}else s[C]=s[C+1]=s[C+2]=0;F-=j,J-=k,K-=q,L-=z,j-=d.r,k-=d.g,q-=d.b,z-=d.a;var _=Y+f+1;_=w+(_>E,vt>0?(vt=255/vt,s[Rt]=(xt*B>>E)*vt,s[Rt+1]=(bt*B>>E)*vt,s[Rt+2]=(yt*B>>E)*vt):s[Rt]=s[Rt+1]=s[Rt+2]=0,xt-=st,bt-=ut,yt-=ct,ht-=lt,st-=d.r,ut-=d.g,ct-=d.b,lt-=d.a,Rt=ot+((Rt=Nt+x)>E,s[N+1]=j*B>>E,s[N+2]=k*B>>E,T-=A,j-=W,k-=H,A-=w.r,W-=w.g,H-=w.b,d=I+((d=L+f+1)>E,s[d+1]=Z*B>>E,s[d+2]=$*B>>E,Y-=U,Z-=V,$-=X,U-=w.r,V-=w.g,X-=w.b,d=M+((d=ot+x){ + typeData[type] = typeData[type] || []; + if (typeof index === "number"){ + typeData[type][index] = content; + }else{ + typeData[type].push(content); + } + localStorage.setItem("dp_"+type,JSON.stringify(typeData[type])); + } + + me.get=(type,index)=>{ + if (typeData[type]) return typeData[type]; + + let s = localStorage.getItem("dp_" + type); + if (s){ + try{s=JSON.parse(s)}catch (e){s=[]} + } + typeData[type] = s; + return s; + } + + me.remove=(type,index)=>{ + typeData[type] = typeData[type] || []; + typeData[type].splice(index,1); + localStorage.setItem("dp_"+type,JSON.stringify(typeData[type])); + } + + me.putFile = function(path,content){ + return new Promise(async (next)=>{ + let db = await openDb(); + let transaction = db.transaction(["files"], "readwrite"); + transaction.onerror = (event) => { + console.error("Transaction error",event.target.error); + }; + const objectStore = transaction.objectStore("files"); + const request = objectStore.put({ path: path, content: content }); + request.onsuccess = (event) => { + next(); + } + request.onerror = (event) => { + console.error("Storage put failed",event.target.error); + next(); + } + }); + } + + me.getFile = function(path){ + return new Promise(async (next)=>{ + let db = await openDb(); + let transaction = db.transaction(["files"]); + transaction.onerror = (event) => { + console.error("Transaction error",event.target.error); + } + const objectStore = transaction.objectStore("files"); + const getRequest = objectStore.get(path); + getRequest.onsuccess = (event) => { + let result = event.target.result; + if (result && result.content) result = result.content; + next(result); + } + getRequest.onerror = (event) => { + console.error("Storage get failed",event.target.error); + next(); + } + }); + } + + function openDb(){ + return new Promise(function(next){ + let request = window.indexedDB.open(dbName, 1); + request.onerror = function(event) { + console.error("Error opening db",event.target.error.name); + if (event.target.error.name === "VersionError"){ + console.error("VersionError",event.target.error.name); + window.indexedDB.deleteDatabase(dbName); + request = window.indexedDB.open(dbName, 1); + }else{ + next(undefined); + } + } + request.onsuccess = function(event) { + next(event.target.result); + } + + request.onupgradeneeded = (event) => { + let db = event.target.result; + let settings = db.createObjectStore("settings", { keyPath: "key" }); + let files = db.createObjectStore("files", { keyPath: "path" }); + settings.createIndex("key", "key", { unique: true }); + files.createIndex("path", "path", { unique: true }); + } + }); + } + + return me; +}(); + +export default Storage; \ No newline at end of file diff --git a/app/web-tools/dpaint/_script/util/zlib.js b/app/web-tools/dpaint/_script/util/zlib.js new file mode 100644 index 00000000..7e421a7c --- /dev/null +++ b/app/web-tools/dpaint/_script/util/zlib.js @@ -0,0 +1,42 @@ +/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */ + + +export default function() {'use strict';function m(d){throw d;}var w=void 0,z=!0,aa=this;function A(d,a){var c=d.split("."),e=aa;!(c[0]in e)&&e.execScript&&e.execScript("var "+c[0]);for(var b;c.length&&(b=c.shift());)!c.length&&a!==w?e[b]=a:e=e[b]?e[b]:e[b]={}};var G="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function I(d,a){this.index="number"===typeof a?a:0;this.i=0;this.buffer=d instanceof(G?Uint8Array:Array)?d:new (G?Uint8Array:Array)(32768);2*this.buffer.length<=this.index&&m(Error("invalid index"));this.buffer.length<=this.index&&this.f()}I.prototype.f=function(){var d=this.buffer,a,c=d.length,e=new (G?Uint8Array:Array)(c<<1);if(G)e.set(d);else for(a=0;a>>8&255]<<16|Q[d>>>16&255]<<8|Q[d>>>24&255])>>32-a:Q[d]>>8-a);if(8>a+f)g=g<>a-h-1&1,8===++f&&(f=0,e[b++]=Q[g],g=0,b===e.length&&(e=this.f()));e[b]=g;this.buffer=e;this.i=f;this.index=b};I.prototype.finish=function(){var d=this.buffer,a=this.index,c;0ca;++ca){for(var R=ca,ha=R,ia=7,R=R>>>1;R;R>>>=1)ha<<=1,ha|=R&1,--ia;ba[ca]=(ha<>>0}var Q=ba;function ja(d){this.buffer=new (G?Uint16Array:Array)(2*d);this.length=0}ja.prototype.getParent=function(d){return 2*((d-2)/4|0)};ja.prototype.push=function(d,a){var c,e,b=this.buffer,f;c=this.length;b[this.length++]=a;for(b[this.length++]=d;0b[e])f=b[c],b[c]=b[e],b[e]=f,f=b[c+1],b[c+1]=b[e+1],b[e+1]=f,c=e;else break;return this.length}; + ja.prototype.pop=function(){var d,a,c=this.buffer,e,b,f;a=c[0];d=c[1];this.length-=2;c[0]=c[this.length];c[1]=c[this.length+1];for(f=0;;){b=2*f+2;if(b>=this.length)break;b+2c[b]&&(b+=2);if(c[b]>c[f])e=c[f],c[f]=c[b],c[b]=e,e=c[f+1],c[f+1]=c[b+1],c[b+1]=e;else break;f=b}return{index:d,value:a,length:this.length}};function S(d){var a=d.length,c=0,e=Number.POSITIVE_INFINITY,b,f,g,h,k,p,q,r,n,l;for(r=0;rc&&(c=d[r]),d[r]>=1;l=g<<16|r;for(n=p;nT;T++)switch(z){case 143>=T:pa.push([T+48,8]);break;case 255>=T:pa.push([T-144+400,9]);break;case 279>=T:pa.push([T-256+0,7]);break;case 287>=T:pa.push([T-280+192,8]);break;default:m("invalid literal: "+T)} + ka.prototype.j=function(){var d,a,c,e,b=this.input;switch(this.h){case 0:c=0;for(e=b.length;c>>8&255;n[l++]=p&255;n[l++]=p>>>8&255;if(G)n.set(f,l),l+=f.length,n=n.subarray(0,l);else{q=0;for(r=f.length;qy)for(;0y?y:138,F>y-3&&F=F?(J[H++]=17,J[H++]=F-3,O[17]++):(J[H++]=18,J[H++]=F-11,O[18]++),y-=F;else if(J[H++]=K[u],O[K[u]]++,y--,3>y)for(;0y?y:6,F>y-3&&FD;D++)sa[D]=la[gb[D]];for(Z=19;4=b:return[265,b-11,1];case 14>=b:return[266,b-13,1];case 16>=b:return[267,b-15,1];case 18>=b:return[268,b-17,1];case 22>=b:return[269,b-19,2];case 26>=b:return[270,b-23,2];case 30>=b:return[271,b-27,2];case 34>=b:return[272, + b-31,2];case 42>=b:return[273,b-35,3];case 50>=b:return[274,b-43,3];case 58>=b:return[275,b-51,3];case 66>=b:return[276,b-59,3];case 82>=b:return[277,b-67,4];case 98>=b:return[278,b-83,4];case 114>=b:return[279,b-99,4];case 130>=b:return[280,b-115,4];case 162>=b:return[281,b-131,5];case 194>=b:return[282,b-163,5];case 226>=b:return[283,b-195,5];case 257>=b:return[284,b-227,5];case 258===b:return[285,b-258,0];default:m("invalid length: "+b)}}var a=[],c,e;for(c=3;258>=c;c++)e=d(c),a[c]=e[2]<<24|e[1]<< + 16|e[0];return a}(),xa=G?new Uint32Array(wa):wa; + function qa(d,a){function c(b,c){var a=b.G,d=[],e=0,f;f=xa[b.length];d[e++]=f&65535;d[e++]=f>>16&255;d[e++]=f>>24;var g;switch(z){case 1===a:g=[0,a-1,0];break;case 2===a:g=[1,a-2,0];break;case 3===a:g=[2,a-3,0];break;case 4===a:g=[3,a-4,0];break;case 6>=a:g=[4,a-5,1];break;case 8>=a:g=[5,a-7,1];break;case 12>=a:g=[6,a-9,2];break;case 16>=a:g=[7,a-13,2];break;case 24>=a:g=[8,a-17,3];break;case 32>=a:g=[9,a-25,3];break;case 48>=a:g=[10,a-33,4];break;case 64>=a:g=[11,a-49,4];break;case 96>=a:g=[12,a- + 65,5];break;case 128>=a:g=[13,a-97,5];break;case 192>=a:g=[14,a-129,6];break;case 256>=a:g=[15,a-193,6];break;case 384>=a:g=[16,a-257,7];break;case 512>=a:g=[17,a-385,7];break;case 768>=a:g=[18,a-513,8];break;case 1024>=a:g=[19,a-769,8];break;case 1536>=a:g=[20,a-1025,9];break;case 2048>=a:g=[21,a-1537,9];break;case 3072>=a:g=[22,a-2049,10];break;case 4096>=a:g=[23,a-3073,10];break;case 6144>=a:g=[24,a-4097,11];break;case 8192>=a:g=[25,a-6145,11];break;case 12288>=a:g=[26,a-8193,12];break;case 16384>= + a:g=[27,a-12289,12];break;case 24576>=a:g=[28,a-16385,13];break;case 32768>=a:g=[29,a-24577,13];break;default:m("invalid distance")}f=g;d[e++]=f[0];d[e++]=f[1];d[e++]=f[2];var h,k;h=0;for(k=d.length;h=f;)t[f++]=0;for(f=0;29>=f;)x[f++]=0}t[256]=1;e=0;for(b=a.length;e=b){r&&c(r,-1);f=0;for(g=b-e;fg&&a+gf&&(b=e,f=g);if(258===g)break}return new ua(f,a-b)} + function ra(d,a){var c=d.length,e=new ja(572),b=new (G?Uint8Array:Array)(c),f,g,h,k,p;if(!G)for(k=0;k2*b[l-1]+f[l]&&(b[l]=2*b[l-1]+f[l]),h[l]=Array(b[l]),k[l]=Array(b[l]);for(n=0;nd[n]?(h[l][s]=t,k[l][s]=a,x+=2):(h[l][s]=d[n],k[l][s]=n,++n);p[l]=0;1===f[l]&&e(l)}return g} + function ta(d){var a=new (G?Uint16Array:Array)(d.length),c=[],e=[],b=0,f,g,h,k;f=0;for(g=d.length;f>>=1}return a};function U(d,a){this.l=[];this.m=32768;this.e=this.g=this.c=this.q=0;this.input=G?new Uint8Array(d):d;this.s=!1;this.n=Aa;this.B=!1;if(a||!(a={}))a.index&&(this.c=a.index),a.bufferSize&&(this.m=a.bufferSize),a.bufferType&&(this.n=a.bufferType),a.resize&&(this.B=a.resize);switch(this.n){case Ba:this.b=32768;this.a=new (G?Uint8Array:Array)(32768+this.m+258);break;case Aa:this.b=0;this.a=new (G?Uint8Array:Array)(this.m);this.f=this.J;this.t=this.H;this.o=this.I;break;default:m(Error("invalid inflate mode"))}} + var Ba=0,Aa=1,Ca={D:Ba,C:Aa}; + U.prototype.p=function(){for(;!this.s;){var d=V(this,3);d&1&&(this.s=z);d>>>=1;switch(d){case 0:var a=this.input,c=this.c,e=this.a,b=this.b,f=a.length,g=w,h=w,k=e.length,p=w;this.e=this.g=0;c+1>=f&&m(Error("invalid uncompressed block header: LEN"));g=a[c++]|a[c++]<<8;c+1>=f&&m(Error("invalid uncompressed block header: NLEN"));h=a[c++]|a[c++]<<8;g===~h&&m(Error("invalid uncompressed block header: length verify"));c+g>a.length&&m(Error("input buffer is broken"));switch(this.n){case Ba:for(;b+g>e.length;){p= + k-b;g-=p;if(G)e.set(a.subarray(c,c+p),b),b+=p,c+=p;else for(;p--;)e[b++]=a[c++];this.b=b;e=this.f();b=this.b}break;case Aa:for(;b+g>e.length;)e=this.f({v:2});break;default:m(Error("invalid inflate mode"))}if(G)e.set(a.subarray(c,c+g),b),b+=g,c+=g;else for(;g--;)e[b++]=a[c++];this.c=c;this.b=b;this.a=e;break;case 1:this.o(Da,Ea);break;case 2:for(var q=V(this,5)+257,r=V(this,5)+1,n=V(this,4)+4,l=new (G?Uint8Array:Array)(Sa.length),s=w,t=w,x=w,E=w,B=w,C=w,L=w,v=w,M=w,v=0;v=W?8:255>=W?9:279>=W?7:8;var Da=S(cb),eb=new (G?Uint8Array:Array)(30),fb,hb;fb=0;for(hb=eb.length;fb=g&&m(Error("input buffer is broken")),c|=b[f++]<>>a;d.e=e-a;d.c=f;return h} + function Ta(d,a){for(var c=d.g,e=d.e,b=d.input,f=d.c,g=b.length,h=a[0],k=a[1],p,q;e=g);)c|=b[f++]<>>16;q>e&&m(Error("invalid code length: "+q));d.g=c>>q;d.e=e-q;d.c=f;return p&65535} + U.prototype.o=function(d,a){var c=this.a,e=this.b;this.u=d;for(var b=c.length-258,f,g,h,k;256!==(f=Ta(this,d));)if(256>f)e>=b&&(this.b=e,c=this.f(),e=this.b),c[e++]=f;else{g=f-257;k=Wa[g];0=b&&(this.b=e,c=this.f(),e=this.b);for(;k--;)c[e]=c[e++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=e}; + U.prototype.I=function(d,a){var c=this.a,e=this.b;this.u=d;for(var b=c.length,f,g,h,k;256!==(f=Ta(this,d));)if(256>f)e>=b&&(c=this.f(),b=c.length),c[e++]=f;else{g=f-257;k=Wa[g];0b&&(c=this.f(),b=c.length);for(;k--;)c[e]=c[e++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=e}; + U.prototype.f=function(){var d=new (G?Uint8Array:Array)(this.b-32768),a=this.b-32768,c,e,b=this.a;if(G)d.set(b.subarray(32768,d.length));else{c=0;for(e=d.length;cc;++c)b[c]=b[a+c];this.b=32768;return b}; + U.prototype.J=function(d){var a,c=this.input.length/this.c+1|0,e,b,f,g=this.input,h=this.a;d&&("number"===typeof d.v&&(c=d.v),"number"===typeof d.F&&(c+=d.F));2>c?(e=(g.length-this.c)/this.u[2],f=258*(e/2)|0,b=fa&&(this.a.length=a),d=this.a);return this.buffer=d};function ib(d){if("string"===typeof d){var a=d.split(""),c,e;c=0;for(e=a.length;c>>0;d=a}for(var b=1,f=0,g=d.length,h,k=0;0>>0};function jb(d,a){var c,e;this.input=d;this.c=0;if(a||!(a={}))a.index&&(this.c=a.index),a.verify&&(this.M=a.verify);c=d[this.c++];e=d[this.c++];switch(c&15){case kb:this.method=kb;break;default:m(Error("unsupported compression method"))}0!==((c<<8)+e)%31&&m(Error("invalid fcheck flag:"+((c<<8)+e)%31));e&32&&m(Error("fdict flag is not supported"));this.A=new U(d,{index:this.c,bufferSize:a.bufferSize,bufferType:a.bufferType,resize:a.resize})} + jb.prototype.p=function(){var d=this.input,a,c;a=this.A.p();this.c=this.A.c;this.M&&(c=(d[this.c++]<<24|d[this.c++]<<16|d[this.c++]<<8|d[this.c++])>>>0,c!==ib(a)&&m(Error("invalid adler-32 checksum")));return a};var kb=8;function lb(d,a){this.input=d;this.a=new (G?Uint8Array:Array)(32768);this.h=X.k;var c={},e;if((a||!(a={}))&&"number"===typeof a.compressionType)this.h=a.compressionType;for(e in a)c[e]=a[e];c.outputBuffer=this.a;this.z=new ka(this.input,c)}var X=oa; + lb.prototype.j=function(){var d,a,c,e,b,f,g,h=0;g=this.a;d=kb;switch(d){case kb:a=Math.LOG2E*Math.log(32768)-8;break;default:m(Error("invalid compression method"))}c=a<<4|d;g[h++]=c;switch(d){case kb:switch(this.h){case X.NONE:b=0;break;case X.r:b=1;break;case X.k:b=2;break;default:m(Error("unsupported compression type"))}break;default:m(Error("invalid compression method"))}e=b<<6|0;g[h++]=e|31-(256*c+e)%31;f=ib(this.input);this.z.b=h;g=this.z.j();h=g.length;G&&(g=new Uint8Array(g.buffer),g.length<= + h+4&&(this.a=new Uint8Array(g.length+4),this.a.set(g),g=this.a),g=g.subarray(0,h+4));g[h++]=f>>24&255;g[h++]=f>>16&255;g[h++]=f>>8&255;g[h++]=f&255;return g};function mb(d,a){var c,e,b,f;if(Object.keys)c=Object.keys(a);else for(e in c=[],b=0,a)c[b++]=e;b=0;for(f=c.length;b=0){ + colors.splice(i,1); + colors.unshift({Red: transparentColor[0], Green: transparentColor[1], Blue: transparentColor[2]}) + } + } + + self.postMessage({ LineIndex: lineIndex, Colors: colors }); + + self.close(); +}; + +function createColorCube(CanvasData, transparentColor) +{ + var TotalColorCount = 0; + var ColorCube = {}; // Note: an associative array is actually an object. + + for(var Y = 0; Y < CanvasData.height; Y++) + { + for(var X = 0; X < CanvasData.width; X++) + { + var PixelIndex = (X + Y * CanvasData.width) * 4; + + var Red = CanvasData.data[PixelIndex]; + var Green = CanvasData.data[PixelIndex + 1]; + var Blue = CanvasData.data[PixelIndex + 2]; + var Alpha = CanvasData.data[PixelIndex + 3]; + + //console.error(Alpha); + if (transparentColor && Alpha<100){ + Red = transparentColor[0]; + Green = transparentColor[1]; + Blue = transparentColor[2]; + Alpha = 255; + }; + //var BitsPerColor = 4; + //var ShadesPerColor = 1 << BitsPerColor; + + //Red = Math.round(Math.round(Red * (ShadesPerColor - 1) / 255) * 255 / (ShadesPerColor - 1)); + //Green = Math.round(Math.round(Green * (ShadesPerColor - 1) / 255) * 255 / (ShadesPerColor - 1)); + //Blue = Math.round(Math.round(Blue * (ShadesPerColor - 1) / 255) * 255 / (ShadesPerColor - 1)); + + if(Alpha == 255) + { + if(ColorCube[Red * 256 * 256 + Green * 256 + Blue]){ + ColorCube[Red * 256 * 256 + Green * 256 + Blue]++; + }else{ + ColorCube[Red * 256 * 256 + Green * 256 + Blue] = 1; + TotalColorCount++; + } + } + } + } + + return ColorCube; +} + +function TrimColorCube(ColorCube, ColorCubeInfo) +{ + var RedMin = 255; + var RedMax = 0; + + var GreenMin = 255; + var GreenMax = 0; + + var BlueMin = 255; + var BlueMax = 0; + + var RedCounts = new Uint32Array(256); + var GreenCounts = new Uint32Array(256); + var BlueCounts = new Uint32Array(256); + + var TotalColorCount = 0; + + var AverageRed = 0; + var AverageGreen = 0; + var AverageBlue = 0; + + for(var Color in ColorCube) + { + var Red = Color >> 16; + var Green = (Color >> 8) & 0xff; + var Blue = Color & 0xff; + + if(Red >= ColorCubeInfo.RedMin && Red <= ColorCubeInfo.RedMax && + Green >= ColorCubeInfo.GreenMin && Green <= ColorCubeInfo.GreenMax && + Blue >= ColorCubeInfo.BlueMin && Blue <= ColorCubeInfo.BlueMax) + { + var ColorCount = ColorCube[Color]; + + // throw JSON.stringify({ data: { Color: Color, Red: Red, Green: Green, Blue: Blue, ColorCount: ColorCount } }); + + RedCounts[Red] += ColorCount; + GreenCounts[Green] += ColorCount; + BlueCounts[Blue] += ColorCount; + + if(Red < RedMin) + RedMin = Red; + + if(Red > RedMax) + RedMax = Red; + + if(Green < GreenMin) + GreenMin = Green; + + if(Green > GreenMax) + GreenMax = Green; + + if(Blue < BlueMin) + BlueMin = Blue; + + if(Blue > BlueMax) + BlueMax = Blue; + + AverageRed += Red * ColorCount; + AverageGreen += Green * ColorCount; + AverageBlue += Blue * ColorCount; + + TotalColorCount += ColorCount; + } + } + + AverageRed = Math.round(AverageRed / TotalColorCount); + AverageGreen = Math.round(AverageGreen / TotalColorCount); + AverageBlue = Math.round(AverageBlue / TotalColorCount); + + return { RedMin: RedMin, RedMax: RedMax, GreenMin: GreenMin, GreenMax: GreenMax, BlueMin: BlueMin, BlueMax: BlueMax, RedCounts: RedCounts, GreenCounts: GreenCounts, BlueCounts: BlueCounts, Red: AverageRed, Green: AverageGreen, Blue: AverageBlue, ColorCount: TotalColorCount }; +} + +function QuantizationCountWeight(Count) +{ + return Math.pow(Count, 0.2); // Standard. + //return Math.pow(Count, 0.1); + //return Count; +} + +function quantizeRecursive(ColorCube, ColorCubeInfo, Palette, RecursionDepth, MaxRecursionDepth) +{ + var RedLength = ColorCubeInfo.RedMax - ColorCubeInfo.RedMin; + var GreenLength = ColorCubeInfo.GreenMax - ColorCubeInfo.GreenMin; + var BlueLength = ColorCubeInfo.BlueMax - ColorCubeInfo.BlueMin; + + if(Math.max(RedLength, GreenLength, BlueLength) == 1) + return; + + if(RecursionDepth == MaxRecursionDepth) + { + Palette.push({ Red: ColorCubeInfo.Red, Green: ColorCubeInfo.Green, Blue: ColorCubeInfo.Blue }); + + return; + } + + var NewColorCubeInfo = new Array(); + + NewColorCubeInfo.RedMin = ColorCubeInfo.RedMin; + NewColorCubeInfo.RedMax = ColorCubeInfo.RedMax; + NewColorCubeInfo.GreenMin = ColorCubeInfo.GreenMin; + NewColorCubeInfo.GreenMax = ColorCubeInfo.GreenMax; + NewColorCubeInfo.BlueMin = ColorCubeInfo.BlueMin; + NewColorCubeInfo.BlueMax = ColorCubeInfo.BlueMax; + + if(RedLength >= GreenLength && RedLength >= BlueLength) + { + var LowIndex = ColorCubeInfo.RedMin; + var HighIndex = ColorCubeInfo.RedMax; + var LowCount = QuantizationCountWeight(ColorCubeInfo.RedCounts[LowIndex]); + var HighCount = QuantizationCountWeight(ColorCubeInfo.RedCounts[HighIndex]); + + while(LowIndex < HighIndex - 1) + { + if(LowCount < HighCount) + { + LowCount += QuantizationCountWeight(ColorCubeInfo.RedCounts[++LowIndex]); + } + else + { + HighCount += QuantizationCountWeight(ColorCubeInfo.RedCounts[--HighIndex]); + } + } + + ColorCubeInfo.RedMax = LowIndex; + NewColorCubeInfo.RedMin = HighIndex; + } + else if(GreenLength >= RedLength && GreenLength >= BlueLength) + { + var LowIndex = ColorCubeInfo.GreenMin; + var HighIndex = ColorCubeInfo.GreenMax; + var LowCount = QuantizationCountWeight(ColorCubeInfo.GreenCounts[LowIndex]); + var HighCount = QuantizationCountWeight(ColorCubeInfo.GreenCounts[HighIndex]); + + while(LowIndex < HighIndex - 1) + { + if(LowCount < HighCount) + { + LowCount += QuantizationCountWeight(ColorCubeInfo.GreenCounts[++LowIndex]); + } + else + { + HighCount += QuantizationCountWeight(ColorCubeInfo.GreenCounts[--HighIndex]); + } + } + + ColorCubeInfo.GreenMax = LowIndex; + NewColorCubeInfo.GreenMin = HighIndex; + } + else + { + var LowIndex = ColorCubeInfo.BlueMin; + var HighIndex = ColorCubeInfo.BlueMax; + var LowCount = QuantizationCountWeight(ColorCubeInfo.BlueCounts[LowIndex]); + var HighCount = QuantizationCountWeight(ColorCubeInfo.BlueCounts[HighIndex]); + + while(LowIndex < HighIndex - 1) + { + if(LowCount < HighCount) + { + LowCount += QuantizationCountWeight(ColorCubeInfo.BlueCounts[++LowIndex]); + } + else + { + HighCount += QuantizationCountWeight(ColorCubeInfo.BlueCounts[--HighIndex]); + } + } + + ColorCubeInfo.BlueMax = LowIndex; + NewColorCubeInfo.BlueMin = HighIndex; + } + + quantizeRecursive(ColorCube, TrimColorCube(ColorCube, ColorCubeInfo), Palette, RecursionDepth + 1, MaxRecursionDepth); + quantizeRecursive(ColorCube, TrimColorCube(ColorCube, NewColorCubeInfo), Palette, RecursionDepth + 1, MaxRecursionDepth); +} + +function QuantizeColors(Canvas, ColorCount) +{ + var ColorCube = createColorCube(Canvas); + var ColorCubeInfos = new Array(TrimColorCube(ColorCube, { RedMin: 0, RedMax: 255, GreenMin: 0, GreenMax: 255, BlueMin: 0, BlueMax: 255 })); + + while(ColorCubeInfos.length < ColorCount) + { + var LongestCubeLength = 0; + var LongestCubeIndex = 0; + + var HeaviestCubeCount = 0; + var HeaviestCubeIndex = 0; + + var RedLength; + var GreenLength; + var BlueLength; + + for(var Index = 0; Index < ColorCubeInfos.length; Index++) + { + RedLength = ColorCubeInfos[Index].RedMax - ColorCubeInfos[Index].RedMin; + GreenLength = ColorCubeInfos[Index].GreenMax - ColorCubeInfos[Index].GreenMin; + BlueLength = ColorCubeInfos[Index].BlueMax - ColorCubeInfos[Index].BlueMin; + + if(Math.max(RedLength, GreenLength, BlueLength) > LongestCubeLength) + { + LongestCubeLength = Math.max(RedLength, GreenLength, BlueLength); + LongestCubeIndex = Index; + } + + if(Math.max(RedLength, GreenLength, BlueLength) > 1 && ColorCubeInfos[Index].ColorCount > HeaviestCubeCount) + { + HeaviestCubeCount = ColorCubeInfos[Index].ColorCount; + HeaviestCubeIndex = Index; + } + } + + var OldColorCubeInfo = ColorCubeInfos[LongestCubeIndex]; + //var OldColorCubeInfo = ColorCubeInfos[HeaviestCubeIndex]; + var NewColorCubeInfo = new Array(); + + NewColorCubeInfo.RedMin = OldColorCubeInfo.RedMin; + NewColorCubeInfo.RedMax = OldColorCubeInfo.RedMax; + NewColorCubeInfo.GreenMin = OldColorCubeInfo.GreenMin; + NewColorCubeInfo.GreenMax = OldColorCubeInfo.GreenMax; + NewColorCubeInfo.BlueMin = OldColorCubeInfo.BlueMin; + NewColorCubeInfo.BlueMax = OldColorCubeInfo.BlueMax; + + RedLength = OldColorCubeInfo.RedMax - OldColorCubeInfo.RedMin; + GreenLength = OldColorCubeInfo.GreenMax - OldColorCubeInfo.GreenMin; + BlueLength = OldColorCubeInfo.BlueMax - OldColorCubeInfo.BlueMin; + + if(RedLength >= GreenLength && RedLength >= BlueLength) + { + if(RedLength > 1) + { + var LowIndex = OldColorCubeInfo.RedMin; + var HighIndex = OldColorCubeInfo.RedMax; + var LowCount = Math.pow(OldColorCubeInfo.RedCounts[LowIndex], 0.2); + var HighCount = Math.pow(OldColorCubeInfo.RedCounts[HighIndex], 0.2); + + while(LowIndex < HighIndex - 1) + { + if(LowCount < HighCount) + { + LowCount += Math.pow(OldColorCubeInfo.RedCounts[++LowIndex], 0.2); + } + else + { + HighCount += Math.pow(OldColorCubeInfo.RedCounts[--HighIndex], 0.2); + } + } + + //OldColorCubeInfo.RedMax = LowIndex; + //NewColorCubeInfo.RedMin = HighIndex; + + NewColorCubeInfo.RedMax = OldColorCubeInfo.RedMax; + OldColorCubeInfo.RedMax = OldColorCubeInfo.RedMin + Math.floor(RedLength / 2.0); + NewColorCubeInfo.RedMin = OldColorCubeInfo.RedMax + 1; + } + else + { + break; + } + } + else if(GreenLength >= RedLength && GreenLength >= BlueLength) + { + if(GreenLength > 1) + { + var LowIndex = OldColorCubeInfo.GreenMin; + var HighIndex = OldColorCubeInfo.GreenMax; + var LowCount = Math.pow(OldColorCubeInfo.GreenCounts[LowIndex], 0.2); + var HighCount = Math.pow(OldColorCubeInfo.GreenCounts[HighIndex], 0.2); + + while(LowIndex < HighIndex - 1) + { + if(LowCount < HighCount) + { + LowCount += OldColorCubeInfo.GreenCounts[++LowIndex]; + } + else + { + HighCount += OldColorCubeInfo.GreenCounts[--HighIndex]; + } + } + + //OldColorCubeInfo.GreenMax = LowIndex; + //NewColorCubeInfo.GreenMin = HighIndex; + + NewColorCubeInfo.GreenMax = OldColorCubeInfo.GreenMax; + OldColorCubeInfo.GreenMax = OldColorCubeInfo.GreenMin + Math.floor(GreenLength / 2.0); + NewColorCubeInfo.GreenMin = OldColorCubeInfo.GreenMax + 1; + } + else + { + break; + } + } + else + { + if(BlueLength > 1) + { + var LowIndex = OldColorCubeInfo.BlueMin; + var HighIndex = OldColorCubeInfo.BlueMax; + var LowCount = Math.pow(OldColorCubeInfo.BlueCounts[LowIndex], 0.2); + var HighCount = Math.pow(OldColorCubeInfo.BlueCounts[HighIndex], 0.2); + + while(LowIndex < HighIndex - 1) + { + if(LowCount < HighCount) + { + LowCount += Math.pow(OldColorCubeInfo.BlueCounts[++LowIndex], 0.2); + } + else + { + HighCount += Math.pow(OldColorCubeInfo.BlueCounts[--HighIndex], 0.2); + } + } + + //OldColorCubeInfo.BlueMax = LowIndex; + //NewColorCubeInfo.BlueMin = HighIndex; + + NewColorCubeInfo.BlueMax = OldColorCubeInfo.BlueMax; + OldColorCubeInfo.BlueMax = OldColorCubeInfo.BlueMin + Math.floor(BlueLength / 2.0); + NewColorCubeInfo.BlueMin = OldColorCubeInfo.BlueMax + 1; + } + else + { + break; + } + } + + ColorCubeInfos[LongestCubeIndex] = TrimColorCube(ColorCube, OldColorCubeInfo); + //ColorCubeInfos[HeaviestCubeIndex] = TrimColorCube(ColorCube, OldColorCubeInfo); + ColorCubeInfos.push(TrimColorCube(ColorCube, NewColorCubeInfo)); + } + + return ColorCubeInfos; +} + diff --git a/app/web-tools/dpaint/_style/_contextMenu.scss b/app/web-tools/dpaint/_style/_contextMenu.scss new file mode 100644 index 00000000..5a5df732 --- /dev/null +++ b/app/web-tools/dpaint/_style/_contextMenu.scss @@ -0,0 +1,31 @@ +.contextmenu{ + position: absolute; + z-index: 999; + border: 1px solid black; + background-color: $panel-background-color; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5); + opacity: 0; + display: none; + pointer-events: none; + color: $menu-text-color; + + &.active{ + opacity: 1; + display: block; + pointer-events: all; + } + + .contextmenuitem{ + display: block; + padding: 0 24px 0 10px; + font-size: 13px; + line-height: 23px; + white-space: nowrap; + position: relative; + + &:hover{ + background-color: $panel-background-active; + cursor: pointer; + } + } +} diff --git a/app/web-tools/dpaint/_style/_cursor.scss b/app/web-tools/dpaint/_style/_cursor.scss new file mode 100644 index 00000000..9ee3a49d --- /dev/null +++ b/app/web-tools/dpaint/_style/_cursor.scss @@ -0,0 +1,150 @@ +.cursor{ + position: absolute; + width: 20px; + height: 20px; + left: 0; + top: 0; + margin-top: -18px; + margin-left: -2px; + pointer-events: none; + background-size: contain; + z-index: 1002; + display: none; + + &.rotate{ + background-image: url("../_img/rotate1.svg"); + margin-top: -10px; + margin-left: -10px; + } + +} + +body.customcursor.hoverviewport{ + cursor: none; + + .sizebox{ + cursor: none; + } + + .cursor{ + display: block; + } + + +} + +body.hoverviewport{ + &.cursor-draw{ + cursor: url("../_img/cursors/cross.png") 12 12, auto; + } + + &.cursor-colorpicker{ + cursor: url("../_img/cursors/pipette.png") 0 24, auto; + + canvas.overlaycanvas{ + display: none; + } + } + + &.cursor-rotate{ + cursor: url("../_img/cursors/rotatene.png") 0 0, auto; + } + + &.cursor-select{ + cursor: crosshair; + } + + &.cursor-text{ + cursor: text; + } + + &.cursor-pan{ + cursor: grab; + + canvas.overlaycanvas{ + display: none; + } + } +} + +/* cursors that also apply to main UI */ +body, +body.hoverviewport{ + + &.cursor-drag{ + cursor: grabbing !important; + .cursor{ + display: none; + } + } +} + +body.hovercanvas.pointerdown.cursor-colorpicker{ + .cursor{ + display: block; + + .mark{ + position: absolute; + pointer-events: none; + display: none; + width: 60px; + height: 60px; + margin: -14px 0 0 -26px; + border: 8px solid green; + border-radius: 50%; + box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.5), inset 0 0 3px 1px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(255, 255, 255, 0.5); + } + } +} + +#dragelement{ + position: absolute; + z-index: 1002; + pointer-events: none; + opacity: 0; + top: 0; + left: 0; + transition: opacity 0.3s ease-in-out; + + &.active{ + opacity: 1; + } + + .dragelement{ + &.box{ + width: 120px; + padding: 4px 8px; + color: $menu-text-color; + background-color: #313335; + border: 1px solid #000000; + box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, 0.63); + + &.frame{ + width: 50px; + height: 50px; + font-size: 12px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + } + } + + &.tooltip{ + background-color: #d0c5b1; + padding: 4px 8px; + border: 1px solid black; + font-size: 12px; + white-space: nowrap; + pointer-events: none; + margin: 12px 0 0 12px; + } + } + + +} + + + + + diff --git a/app/web-tools/dpaint/_style/_ditherEditor.scss b/app/web-tools/dpaint/_style/_ditherEditor.scss new file mode 100644 index 00000000..a38fc2d7 --- /dev/null +++ b/app/web-tools/dpaint/_style/_ditherEditor.scss @@ -0,0 +1,120 @@ +.ditheredit{ + .preset{ + display: inline-block; + width: 50px; + height: 50px; + border: 1px solid black; + background-size: 50% 50%; + background-color: #6b6b6b; + @include pixelated; + + &:hover{ + border: 1px solid $active-color; + background-color: #bbbbbb; + cursor: pointer; + } + + &.p0{ + background-image: url("../_img/patterns/dots.png"); + } + &.p1{ + background-image: url("../_img/patterns/cross.png"); + } + &.p2{ + background-image: url("../_img/patterns/grid.png"); + } + &.p3{ + background-image: url("../_img/patterns/cross2.png"); + } + &.p4{ + background-image: url("../_img/patterns/lines_hor.png"); + } + &.p5{ + background-image: url("../_img/patterns/lines_ver.png"); + } + &.p6{ + background-image: url("../_img/patterns/lines_diag.png"); + } + &.p7{ + background-image: url("../_img/patterns/longgrid.png"); + } + &.user{ + canvas{ + width: 100%; + height: 100%; + } + } + + + + } + + .editpreset, + .previewpreset, + .presets{ + position: absolute; + left: 5px; + top: 5px; + bottom: 5px; + width: 202px; + border: 1px solid black; + } + + .editpreset{ + left: 210px; + width: 242px; + } + + .previewpreset{ + left: 455px; + canvas{ + background-image: url("../_img/patterns/gradient.png"); + background-size: 100% 100%; + } + } + + h2{ + font-size: 12px; + padding: 4px; + border-bottom: 1px solid black; + font-weight: normal; + margin: 0; + } + + .subtoolbar{ + border-top: 1px solid black; + text-align: right; + font-size: 12px; + position: relative; + padding: 0; + + .button{ + display: inline-block; + padding: 4px 9px; + text-align: center; + white-space: nowrap; + + &.small{ + width: 24px; + padding: 4px; + } + + &.active{ + border-color: $active-color-dim; + color: $active-color-dim; + background-color: #413e28; + } + + &.hidden{ + display: none; + } + + &.left{ + position: absolute; + left: 0; + max-width: 49%; + } + } + } + +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_editor.scss b/app/web-tools/dpaint/_style/_editor.scss new file mode 100644 index 00000000..eeff2d5d --- /dev/null +++ b/app/web-tools/dpaint/_style/_editor.scss @@ -0,0 +1,289 @@ +.splitpanel{ + position: absolute; + top: 27px; + left: 70px; + right: 0; + bottom: 22px; + + .panel{ + position: absolute; + top: 0; + bottom: 0; + width: calc(50% - 4px); + border: 1px solid black; + + &:first-of-type{ + left: 0; + width: 100%; + } + &:last-of-type{ + right: 0; + display: none; + } + + .toolbar{ + top: 0; + right: 0; + width: unset; + height: 21px; + border: none; + border-bottom: 1px solid black; + justify-content: flex-start; + display: flex; + white-space: nowrap; + + + .button{ + height: 20px; + width: 20px; + color: $menu-text-color; + font-size: 12px; + line-height: 20px; + border: none; + border-right: 1px solid black; + background-color: $button-background-dark; + + &.auto{ + width: auto; + padding: 0 5px; + } + + &.expand{ + background-image: url("../_img/fullscreen.svg"); + background-size: 60% 60%; + background-position: center center; + background-repeat: no-repeat; + } + + &.closepresentation{ + display: none; + } + + &.right{ + position: absolute; + right: 0; + border-left: 1px solid black; + } + } + + .toolpanel{ + .options{ + display: flex; + padding: 3px 2px 0 2px; + color: $menu-text-color; + font-size: 12px; + + span.tool{ + padding: 0px 4px 0 10px; + color: $menu-text-color-dim; + } + + .optionsgroup{ + display: flex; + } + + .flex{ + display: flex; + } + + .checkbox{ + font-size: 12px; + padding-right: 10px; + label span{ + padding-left: 14px; + &:before{ + width: 10px; + height: 10px; + } + } + + &.mask{ + margin-left: 10px; + } + + &.inline{ + margin-left: 10px; + } + } + + input[type=range] { + margin: 0 4px 0 4px; + width: 40px; + height: 6px; + } + + input[type=range]::-webkit-slider-thumb { + margin-top: -7px; + width: 8px; + height: 14px; + } + + input[type=range]::-webkit-slider-runnable-track { + background: #010101; + height: 3px; + margin-top: -2px; + cursor: pointer; + } + + label{ + font-size: 11px; + &.inline{ + margin-left: 6px; + } + } + + select{ + background-color: #181818; + color: $menu-text-color; + border: 1px solid #626262; + font-size: 11px; + margin-right: 4px; + &:focus{ + outline: none; + } + } + } + } + } + + .viewport, + .windowContainer, + .tileContainer{ + position: absolute; + top: 21px; + left: 0; + right: 0; + bottom: 0; + overflow: auto; + display: flex; + touch-action: none; + + &.hidden{ + display: none; + } + } + + .windowContainer{ + .window{ + width: 300px; + height: 170px; + background-image: url("../_img/frame.png"); + position: absolute; + top: 50px; + left: 10px; + + &:nth-child(2){ + top: 250px; + } + + &:nth-child(3){ + top: 450px; + } + + canvas{ + position: absolute; + top: 18px; + left: 4px; + } + } + } + } + + .splitter{ + position: absolute; + left: calc(50% - 4px); + margin-left: 1px; + top: 0; + bottom: 0; + width: 6px; + cursor: col-resize; + z-index: 10; + display: none; + + &:hover{ + background-color: rgba(0, 0, 0, 0.2); + } + } + + .canvaswrapper{ + display: block; + margin: auto; + position: relative; + } + + .canvascontainer{ + display: block; + position: relative; + border: 1px solid black; + background-image: url("../_img/checkers.png"); + } + + canvas{ + image-rendering: optimizeSpeed; + image-rendering: optimize-contrast; + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; + -webkit-touch-callout: none; + display: block; + position: relative; + z-index: 1; + + &.overlaycanvas{ + position: absolute; + left: 0; + top: 0; + z-index: 2; + pointer-events: none; + } + + } + +} + + +body.shift.mousedown{ + .splitpanel{ + .overlaycanvas{ + display: none; + } + } +} + + +body.withsidepanel{ + .splitpanel{ + left: 250px; + } +} + +body.withfilebrowser{ + .splitpanel{ + right: 154px; + } +} + +body.presentation{ + .splitpanel{ + left: 0; + top: 0; + bottom: 0; + + .toolbar{ + .button{ + display: none; + + &.closepresentation{ + display: block; + } + } + + .toolpanel{ + display: none; + } + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_effectEditor.scss b/app/web-tools/dpaint/_style/_effectEditor.scss new file mode 100644 index 00000000..24974aa7 --- /dev/null +++ b/app/web-tools/dpaint/_style/_effectEditor.scss @@ -0,0 +1,298 @@ +.effects{ + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + + .tabs{ + position: absolute; + left: 5px; + width: 220px; + border-bottom: 1px solid black; + height: 36px; + overflow: hidden; + + .tab{ + position: absolute; + width: 90px; + text-align: center; + line-height: 24px; + border: 1px solid black; + height: 24px; + bottom: -3px; + left: 5px; + opacity: 0.5; + transition: all 0.3s ease-in-out; + user-select: none; + + &:nth-of-type(2){ + left: 100px; + } + + &:hover, + &.active{ + cursor: pointer; + background-color: rgba(255, 255, 255, 0.2); + bottom: 0; + opacity: 1; + } + } + } + .sliders{ + position: absolute; + left: 10px; + top: 40px; + display: none; + + &.active{ + display: block; + } + } + + .slider{ + position: relative; + margin: 4px 0; + label{ + display: block; + font-size: 12px; + text-transform: capitalize; + } + + input[type="range"]{ + width: 200px; + position: relative; + z-index: 10; + margin-top: -2px; + + &::-webkit-slider-runnable-track { + background: linear-gradient(90deg, rgb(0,0,0) 0%, rgb(200,200,200) 100%); + border: 1px solid #010101; + width: 100%; + height: 4px; + cursor: pointer; + } + + &::-moz-range-track { + background: linear-gradient(90deg, rgb(0,0,0) 0%, rgb(200,200,200) 100%); + border: 1px solid #010101; + width: 100%; + height: 2px; + cursor: pointer; + } + + &.saturation::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #7F7F7F 0%, #F50A0A 100%); + } + &.saturation::-moz-range-track{ + background: linear-gradient(90deg, #7F7F7F 0%, #F50A0A 100%); + } + &.contrast::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #7F7F7F 0%, #000000 100%); + } + &.contrast::-moz-range-track{ + background: linear-gradient(90deg, #7F7F7F 0%, #000000 100%); + } + &.hue::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #FF3A00 0%, #F0FF00 20%, #00FF1D 40%, #0007FF 60%, #FF00F1 80%, #FA0505 100%); + } + &.hue::-moz-range-track{ + background: linear-gradient(90deg, #FF3A00 0%, #F0FF00 20%, #00FF1D 40%, #0007FF 60%, #FF00F1 80%, #FA0505 100%); + } + &.sepia::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #313335 0%, #8A4F38 100%); + } + &.sepia::-moz-range-track{ + background: linear-gradient(90deg, #313335 0%, #8A4F38 100%); + } + &.cyan::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #0AF5F5 0%, #F50A0A 100%); + } + &.cyan::-moz-range-track{ + background: linear-gradient(90deg, #0AF5F5 0%, #F50A0A 100%); + } + &.magenta::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #F50AF5 0%, #0AF50A 100%); + } + &.magenta::-moz-range-track{ + background: linear-gradient(90deg, #F50AF5 0%, #0AF50A 100%); + } + &.yellow::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #F5F50A 0%, #0A0AF5 100%); + } + &.yellow::-moz-range-track{ + background: linear-gradient(90deg, #F5F50A 0%, #0A0AF5 100%); + } + } + + input[type="text"]{ + position: absolute; + width: 40px; + right: 0; + top: 0; + font-size: 12px; + padding: 2px 4px; + } + + } + + .alchemy{ + position: absolute; + left: 10px; + top: 40px; + width: 200px; + + display: none; + + &.active{ + display: block; + } + + .recipe{ + padding: 6px 6px 6px 24px; + border-bottom: 1px solid black; + background-image: url("../_img/flask.svg"); + background-repeat: no-repeat; + background-position: 2px center; + background-size: 16px 16px; + font-size: 12px; + + &:hover{ + cursor: pointer; + background-color: $panel-background-active; + } + } + } + + .code{ + position: absolute; + left: 10px; + top: 250px; + right: 10px; + + code{ + position: absolute; + left: 0; + right: 0; + top: 0; + height: 170px; + overflow: auto; + white-space: pre; + font-family: monospace; + display: block; + border: 1px solid black; + background-color: #1c1c1c; + color: $menu-text-color-dim; + + &:focus{ + outline: none; + } + + .string{color:#A1E46D;} + .number{color:#4176BA;} + .html {color:#E4D95F;} + .reserved {color:#C87531;} + .js {color:#6DE4D1;} + .globals {color:#ab66b7;} + .comment{color:#aaa;} + .method {color:#F9C26B;} + .source {color: #64b969;} + .target {color: #64b9a9;} + } + + .button{ + position: absolute; + left: 0; + top: 176px; + user-select: none; + } + + .params{ + position: absolute; + background-color: $panel-background-color; + z-index: 1; + right: 0; + top: -242px; + width: 256px; + height: 206px; + + h3{ + padding-left: 0; + } + + .slider{ + margin-left: 20px; + margin-right: 36px; + + label, + input[type="text"]{ + font-size: 11px; + } + + input[type="text"]{ + padding: 1px 2px; + } + + input[type=range]::-webkit-slider-thumb{ + height: 12px; + margin-top: -5px; + } + input[type=range]::-ms-thumb { + height: 12px; + } + input[type=range]::-moz-range-thumb { + height: 10px; + } + } + + &.columns{ + .slider{ + margin-left: 10px; + margin-right: 0; + width: 110px; + display: inline-block; + + &.main{ + width: 230px; + + input[type="range"]{ + width: 230px; + } + } + + input[type="range"]{ + width: 110px; + } + + input[type="text"]{ + width: 30px; + } + } + } + } + } + + .previewpanel{ + position: absolute; + left: 240px; + top: 12px; + + .preview{ + width: 200px; + height: 200px; + border: 1px solid black; + margin-bottom: 10px; + } + } + + .buttons{ + left: 0; + right: 0; + justify-content: flex-end; + + .button.left{ + position: absolute; + left: 10px; + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_fileBrowser.scss b/app/web-tools/dpaint/_style/_fileBrowser.scss new file mode 100644 index 00000000..892a7474 --- /dev/null +++ b/app/web-tools/dpaint/_style/_fileBrowser.scss @@ -0,0 +1,108 @@ +.filebrowser{ + position: absolute; + right: 0; + top: 27px; + width: 150px; + bottom: 22px; + background-color: $panel-background-color; + border: 1px solid black; + display: none; + overflow-y: auto; + overflow-x: hidden; + + &.active{ + display: block; + } + + .caption{ + color: $menu-text-color; + padding: 4px 5px 2px 20px; + font-size: 12px; + height: 21px; + border-bottom: 1px solid black; + + + .close{ + position: absolute; + height: 20px; + width: 20px; + line-height: 20px; + left: 0; + top: 0; + text-align: center; + cursor: pointer; + } + } + + .disk{ + padding: 2px 2px 2px 21px; + border-bottom: 1px solid black; + color: $menu-text-color; + font-size: 11px; + line-height: 20px; + background-image: url("../_img/disk.svg"); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: 2px center; + position: relative; + + .download{ + position: absolute; + height: 16px; + width: 16px; + right: 2px; + top: 2px; + cursor: pointer; + opacity: 0.7; + background-image: url("../_img/download.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; + + &:hover{ + opacity: 1; + } + } + } + + .listitem{ + color: $menu-text-color; + padding: 2px 2px 2px 21px; + border-bottom: 1px solid black; + position: relative; + + &.folder{ + background-image: url("../_img/folder.svg"); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: 2px center; + } + + &.file{ + opacity: 0.5; + &:before{ + content: ""; + position: absolute; + left: 5px; + top: 5px; + width: 10px; + height: 10px; + border-radius: 5px; + background-color: grey; + } + + &.image{ + opacity: 1; + &:before{ + background-color: #71b471; + } + } + } + + &:hover{ + cursor: pointer; + background-color: $panel-background-active; + color: white; + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_forms.scss b/app/web-tools/dpaint/_style/_forms.scss new file mode 100644 index 00000000..ab9a7b21 --- /dev/null +++ b/app/web-tools/dpaint/_style/_forms.scss @@ -0,0 +1,151 @@ +.checkbox{ + display: block; + + label{ + position: relative; + + span{ + padding: 2px 0 2px 18px; + line-height: 14px; + white-space: nowrap; + color: $menu-text-color; + + &:before{ + content: ""; + position: absolute; + left: 0; + top: 1px; + width: 14px; + height: 14px; + background-color: #2b2b2b; + border: 1px solid #8a8a8a; + } + } + + &:hover{ + cursor: pointer; + + span{ + color: #d7d7d7; + + &:before{ + border: 1px solid #e8e8e8; + } + } + } + } + + input{ + opacity: 0; + position: absolute; + + &:checked + span{ + &:before{ + background-color: #c0c0c0; + box-shadow: inset 0 0 0px 2px black; + } + } + } + + &.small{ + margin-top: 1px; + label{ + span{ + padding: 2px 0 2px 14px; + + &:before{ + width: 10px; + height: 10px; + top: 2px; + } + } + } + + input{ + &:checked + span{ + &:before{ + box-shadow: inset 0 0 0 1px black; + } + } + } + } +} + +input[type="number"], +input[type="text"]{ + display: inline-block; + background-color: #2b2b2b; + border: 1px solid #6A6A6A; + color: #BBBBBB; + padding: 4px; + + &:focus{ + outline: none; + } +} + +.yesno{ + display: flex; + width: 70px; + height: 18px; + border: 1px solid #6A6A6A; + margin: 2px 2px 0 0; + padding-top: 1px; + font-size: 12px; + position: relative; + overflow: hidden; + + .option{ + width: 50px; + text-align: center; + opacity: 0.5; + position: relative; + z-index: 2; + + &:nth-child(2){ + opacity: 1; + color: black; + } + } + + &:before{ + content: ""; + position: absolute; + left: 50%; + top: 0; + bottom: 0; + width: 30px; + background-color: $menu-text-color; + transition: left 0.3s ease-in-out; + } + + &:hover{ + cursor: pointer; + + .option{ + opacity: 1; + + &:nth-child(2){ + opacity: 1 !important; + } + } + } + + &.selected{ + .option{ + &:nth-child(1){ + opacity: 1; + color: black; + } + + &:nth-child(2){ + opacity: 0.5; + color: inherit; + } + } + + &:before{ + left: 0; + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_gallery.scss b/app/web-tools/dpaint/_style/_gallery.scss new file mode 100644 index 00000000..c31798ff --- /dev/null +++ b/app/web-tools/dpaint/_style/_gallery.scss @@ -0,0 +1,69 @@ +.gallery{ + + .item{ + margin: auto; + width: 120px; + font-size: 11px; + color: $menu-text-color; + margin-top: 10px; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.7); + transition: transform 0.2s, color 0.2s; + + &:hover{ + cursor: pointer; + transform: scale(1.05); + + .fileinfo{ + background-color: #505050; + } + color: $menu-text-color-high; + } + } + + .thumb{ + width: 120px; + height: 70px; + background-size: cover; + background-position: center; + } + + .fileinfo{ + position: relative; + padding: 4px; + background-color: #404244; + } + + .year{ + position: absolute; + right: 4px; + top: 4px; + color: $menu-text-color-dim; + } + + .artist{ + font-style: italic; + } + + a.artist{ + color: $menu-text-color-high; + &:hover{ + color: white; + } + } + + + .section{ + padding: 4px 8px; + .title{ + font-size: 13px; + color: $menu-text-color; + border-bottom: 1px solid $menu-text-color; + margin: 8px 0; + } + + .description{ + font-size: 12px; + color: $menu-text-color-dim; + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_menu.scss b/app/web-tools/dpaint/_style/_menu.scss new file mode 100644 index 00000000..02d05e3d --- /dev/null +++ b/app/web-tools/dpaint/_style/_menu.scss @@ -0,0 +1,152 @@ +@import "var"; + +.menu{ + position: absolute; + left: 0; + right: 0; + border: 1px solid black; + height: 25px; + background-color: $panel-background-color; + color: $menu-text-color; + z-index: 1000; + user-select: none; + white-space: nowrap; + + .hamburger{ + display: none; + } + + a.main{ + position: relative; + display: inline-block; + padding: 0 10px; + line-height: 23px; + font-size: 13px; + + .sub{ + left: 0; + margin-top: 0; + position: absolute; + background-color: $panel-background-color; + color: $menu-text-color; + border: 1px solid black; + display: none; + + a{ + display: block; + padding: 0 24px 0 10px; + font-size: 13px; + line-height: 23px; + white-space: nowrap; + position: relative; + + &.wide{ + padding: 0 70px 0 10px; + + &.ultra{ + padding: 0 90px 0 10px; + } + } + + &.caret{ + &:after{ + content: ""; + position: absolute; + right: 2px; + width: 16px; + height: 23px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + opacity: 0.5; + } + } + + &.checked{ + &:before{ + content: ""; + position: absolute; + left: 4px; + top: 4px; + bottom: 0; + width: 14px; + background-image: url("../_img/check.svg"); + background-size: contain; + background-repeat: no-repeat; + } + } + + &:hover{ + background-color: $panel-background-active; + cursor: pointer; + + .subsub{ + display: block; + } + } + + .shortkey{ + position: absolute; + right: 6px; + top: 1px; + color: $menu-text-color-dim; + font-size: 11px; + } + } + + &.checkable{ + a{ + padding: 0 24px 0 20px; + } + } + + .info{ + position: absolute; + right: 6px; + top: 1px; + color: $menu-text-color-dim; + font-size: 11px; + } + + + .subsub{ + position: absolute; + background-color: $panel-background-color; + border: 1px solid black; + z-index: 100; + top: 0; + left: 50%; + display: none; + + &.checkable{ + a{ + padding: 0 24px 0 20px; + + &.hasinfo{ + padding-right: 120px; + } + } + } + } + } + + &:hover, + &.active{ + background-color: $panel-background-active; + cursor: pointer; + } + + &.active{ + .sub{ + display: block; + } + } + } +} + +body.presentation{ + .menu { + display: none; + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_mobile.scss b/app/web-tools/dpaint/_style/_mobile.scss new file mode 100644 index 00000000..0b6c9250 --- /dev/null +++ b/app/web-tools/dpaint/_style/_mobile.scss @@ -0,0 +1,63 @@ +@media only screen and (max-width: 600px) { + .menu{ + width: 100px; + height: auto; + top: 27px; + border: none; + + .hamburger{ + display: block; + background-image: url("../_img/hamburger.svg"); + width: 200px; + height: 27px; + background-size: 24px 24px; + background-repeat: no-repeat; + background-position: 6px center; + position: absolute; + top: -27px; + padding: 6px 4px 2px 40px; + } + + a.main{ + display: none; + font-size: 13px; + border: 1px solid black; + border-bottom: none; + padding: 2px 10px; + + &:last-child{ + border-bottom: 1px solid black; + } + + .sub{ + left: 100px; + top: 0; + + a{ + font-size: 13px; + border-bottom: 1px solid rgba(0, 0, 0, 0.4); + padding: 2px 20px 2px 10px; + + &:last-child{ + border-bottom: none; + } + + &.wide{ + padding: 2px 80px 2px 10px; + } + + &.ultrawide{ + padding: 2px 100px 2px 10px + } + } + } + } + + + &.active{ + a.main{ + display: block; + } + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_modal.scss b/app/web-tools/dpaint/_style/_modal.scss new file mode 100644 index 00000000..22dca7a8 --- /dev/null +++ b/app/web-tools/dpaint/_style/_modal.scss @@ -0,0 +1,471 @@ +.blanket{ + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + z-index: 1000; + display: none; + + &.active{ + display: block; + } +} + +.modalwindow{ + position: absolute; + z-index: 1001; + background-color: $panel-background-color; + width: 440px; + height: 260px; + border: 1px solid black; + top: calc(50vh - 130px); + left: 50%; + margin-left: -100px; + box-shadow: 0 0 8px 0px black; + display: none; + + .caption{ + color: $menu-text-color; + padding: 2px 4px; + border-bottom: 1px solid black; + position: relative; + height: 20px; + background-color: #00000038; + cursor: move; + user-select: none; + + .button{ + position: absolute; + width: 16px; + height: 16px; + line-height: 14px; + text-align: center; + border: 1px solid black; + top: 2px; + right: 2px; + font-size: 12px; + user-select: none; + + &:hover{ + background-color: $panel-background-active; + cursor: pointer; + } + } + } + + &.active{ + display: block; + } + + .inner{ + color: $menu-text-color; + + + .button{ + border: 1px solid #6a6a6a; + margin: 2px; + padding: 6px; + user-select: none; + + .title{ + color: $menu-text-color; + } + + .subtitle{ + color: #797979; + } + + &:hover{ + cursor: pointer; + border: 1px solid #8c8c8c; + background-color: #232323; + } + + &.large{ + padding: 10px; + margin: 10px; + text-align: center; + } + + &.highlight{ + border: 1px solid #ded293; + color: #ded293; + } + } + + h3{ + font-size: 13px; + padding: 0 10px; + margin: 10px 0; + } + + .panel.form{ + padding: 0 10px; + + span.label{ + display: inline-block; + width: 80px; + text-align: right; + padding-right: 8px; + } + + input[type="number"]{ + width: 100px; + margin-right: 8px; + margin-bottom: 4px; + } + } + + select{ + color: $menu-text-color; + width: 100%; + border: 1px solid black; + background-color: transparent; + margin: 4px 0; + + &:focus{ + outline: none; + } + + option{ + color: $menu-text-color; + background-color: #2B2B2B; + border-radius: 0; + } + + &.resize{ + width: 100px; + padding: 2px; + position: absolute; + left: 198px; + top: 120px; + } + } + + .buttons{ + position: absolute; + display: flex; + bottom: 0; + right: 0; + padding: 10px 8px; + + &.relative{ + position: relative; + } + + .button{ + width: 150px; + text-align: center; + font-size: 12px; + line-height: 20px; + margin: 0 2px; + + &.ghost{ + opacity: 0.7; + } + } + } + + .about{ + img{ + width: 100%; + height: auto; + } + + .text{ + position: absolute; + z-index: 1; + font-size: 12px; + line-height: 14px; + left: 56px; + color: #a5bac2; + opacity: 0.8; + pointer-events: none; + + &.info{ + width: 380px; + bottom: 110px; + } + + &.copyright{ + bottom: 82px; + } + + &.github{ + bottom: 55px; + } + + &.nobullshit{ + bottom: 10px; + opacity: 0.5; + } + + + &.link{ + pointer-events: all; + + &:hover{ + color: white; + cursor: pointer; + text-decoration: underline; + } + } + + &.version{ + left: unset; + right: 66px; + bottom: 55px; + } + + &.contrib{ + + i{ + opacity: 0.8; + display: block; + font-style: normal; + } + bottom: 10px; + text-align: right; + left: unset; + right: 66px; + opacity: 0.5; + } + } + + } + + .optiondialog{ + p{ + margin: 0; + padding: 10px; + } + } + + .lock{ + position: absolute; + height: 33px; + width: 13px; + border: 1px solid transparent; + border-left: none; + top: 46px; + left: 246px; + opacity: 0.7; + + .link{ + position: absolute; + width: 16px; + height: 16px; + top: 50%; + right: -8px; + margin-top: -8px; + background-color: $panel-background-color; + background-image: url("../_img/link.svg"); + background-size: contain; + background-repeat: no-repeat; + opacity: 0.7; + + &:hover{ + opacity: 1; + cursor: pointer; + } + } + + &.active{ + border: 1px solid $menu-text-color-dim; + border-left: none; + opacity: 1; + } + } + + .anchor{ + position: absolute; + border:1px solid black; + left: 290px; + top: 56px; + width: 92px; + height: 92px; + background-color: #533030; + display: flex; + flex-wrap: wrap; + + + h3{ + position: absolute; + left: -11px; + top: -37px; + } + + .page{ + position: absolute; + width: 60px; + height: 60px; + border: 1px solid black; + background-color: #47494d; + left: 15px; + top: 15px; + z-index: 1; + transition: left 0.2s ease-in-out, top 0.2s ease-in-out; + } + + .hotspot{ + width: 30px; + height: 30px; + position: relative; + z-index: 2; + &:hover{ + background-color: rgba(255, 255, 255, 0.13); + cursor: pointer; + } + } + + .arrow{ + position: absolute; + width: 16px; + height: 16px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; + margin: -8px 0 0 -8px; + opacity: 0.6; + pointer-events: none; + transition: all 0.2s ease-in-out; + + &.top{ + left: 50%; + top: 8px; + transform: rotate(270deg); + } + + &.right{ + right: 0; + top: 50%; + } + + &.bottom{ + left: 50%; + bottom: 0; + transform: rotate(90deg); + } + + &.left{ + left: 8px; + top: 50%; + transform: rotate(180deg); + } + } + + &.top{ + .page{ + top: -1px; + } + .arrow.bottom{ + bottom: 10px; + } + } + + &.left{ + .page{ + left: -1px; + } + .arrow.right{ + right: 10px; + } + } + + &.right{ + .page{ + left: 31px; + } + .arrow.left{ + left: 18px; + } + } + + &.bottom{ + .page{ + top: 31px; + } + .arrow.top{ + top: 18px; + } + } + + } + + .quick{ + position: absolute; + top: 123px; + left: 88px; + font-size: 12px; + + .button.calc{ + display: inline-block; + width: 22px; + padding: 2px; + text-align: center; + } + } + + .textlink{ + text-decoration: underline; + color: $menu-text-color; + + &:hover{ + color: white; + cursor: pointer; + } + } + + &.full{ + position: absolute; + left: 0; + top: 20px; + right: 0; + bottom: 0; + } + + + + @import "paletteEditor"; + @import "effectEditor"; + @import "ditherEditor"; + @import "saveDialog"; + } +} + +.notificationbox{ + position: fixed; + left: 20px; + bottom: 40px; + z-index: 1000; + width: 250px; + height: 80px; + background-color: #1a1a1a; + color: $menu-text-color; + border: 1px solid #3b3b3b; + font-size: 13px; + margin-left: -200px; + opacity: 0; + transition: all 0.2s ease-in-out; + pointer-events: none; + + .title{ + background-color: #101010; + border-bottom: 1px solid #3b3b3b; + padding: 4px 10px; + } + + .text{ + padding: 4px 10px; + } + + &.active{ + display: block; + opacity: 1; + margin-left: 0; + pointer-events: all; + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_paletteEditor.scss b/app/web-tools/dpaint/_style/_paletteEditor.scss new file mode 100644 index 00000000..f5f0a195 --- /dev/null +++ b/app/web-tools/dpaint/_style/_paletteEditor.scss @@ -0,0 +1,609 @@ +.palette{ + position: relative; + height: 281px; + margin-top: 1px; + + &.withactions{ + .palettepanel{ + .caption{ + &:after{ + transform: rotate(180deg); + } + } + } + .mainpanel{ + left: 201px; + + .sliders{ + padding-left: 10px; + padding-right: 10px; + + input.hex{ + right: 10px; + width: 80px; + } + + .pixelcount{ + left: 75px; + } + + .slider{ + input.slider{ + width: 110px; + margin: 4px 0 0 8px + } + + span.label{ + right: 43px; + font-size: 12px; + width: 40px; + } + } + } + + .buttons{ + right: 0; + } + + .options{ + padding-left: 10px; + } + } + + .actions{ + display: block; + } + } + + .caption.sub{ + cursor: default; + font-size: 12px; + line-height: 17px; + } + + .palettepanel{ + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 121px; + border-right: 1px solid rgba(0, 0, 0, 0.58); + background-color: #292b2c; + + .caption{ + &:after{ + content: ""; + position: absolute; + width: 16px; + height: 18px; + right: 2px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + opacity: 0.5; + transition: transform 0.3s ease-in-out; + } + + &:hover{ + cursor: pointer; + &:after{ + opacity: 1; + } + + } + } + + canvas{ + display: block; + margin: 0; + }; + + .highlight{ + position: absolute; + width: 30px; + height: 30px; + margin: 21px 0 0 1px; + border: 3px solid black; + pointer-events: none; + + &:before{ + content: ""; + position: absolute; + right: -1px; + bottom: -1px; + left: -4px; + top: -4px; + border: 2px solid white; + } + } + + .nav{ + border-top: 1px solid black; + width: 120px; + height: 20px; + background-color: #313335; + display: flex; + color: #BBBBBB; + + + .prev,.next{ + width: 40px; + background-image: url(../_img/caret.svg); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + opacity: 0.1; + pointer-events: none; + + &.active{ + opacity: 0.5; + pointer-events: all; + } + + &:hover{ + opacity: 1; + cursor: pointer; + } + } + + .prev{ + transform: rotate(180deg); + } + + .page{ + width: 40px; + text-align: center; + } + + } + } + + .actions{ + position: absolute; + left: 121px; + width: 80px; + right: 0; + top: 0; + bottom: 0; + border-right: 1px solid rgba(0, 0, 0, 0.58); + display: none; + + .caption{ + &:after{ + content: "x"; + text-align: center; + position: absolute; + width: 16px; + height: 18px; + right: 2px; + opacity: 0.5; + transition: transform 0.3s ease-in-out; + } + + &:hover{ + cursor: pointer; + &:after{ + opacity: 1; + } + } + } + + .spacer{ + height: 20px; + } + + .nav{ + position: absolute; + bottom: 0; + left: 0; + right: 0; + display: flex; + border-top: 1px solid black; + + div{ + width: 100%; + height: 20px; + background-image: url("../_img/zoom.svg"); + background-size: 14px 14px; + background-repeat: no-repeat; + background-position: center center; + opacity: 0.5; + + &:hover{ + cursor: pointer; + opacity: 1; + } + + &.zoomout{ + background-image: url("../_img/zoomout.svg"); + border-left: 1px solid black; + } + } + } + } + + .mainpanel{ + position: absolute; + left: 121px; + right: 0; + top: 0; + bottom: 0; + + .tabs{ + display: flex; + width: 100%; + + .caption.sub{ + width: 100%; + padding-left: 10px; + pointer-events: none; + + &.inactive{ + background-color: $panel-background-color; + opacity: 0.5; + pointer-events: all; + + &:hover{ + cursor: pointer; + opacity: 1; + } + } + } + } + + .colorpanel{ + position: absolute; + left: 0; + right: 0; + top: 20px; + bottom: 0; + } + + .rangepanel{ + position: absolute; + left: 0; + right: 0; + top: 20px; + bottom: 0; + display: none; + + .norange{ + padding: 20px; + opacity: 0.5; + } + + .bottom{ + position: absolute; + bottom: 0; + padding: 4px; + display: flex; + + .button.small{ + position: relative; + width: auto; + + &.active{ + border: 1px solid $active-color; + color: $active-color; + box-shadow: 0 0 2px yellow; + } + } + } + + .captions{ + display: flex; + font-size: 11px; + border-bottom: 1px solid black; + background-color: #262829; + color: #808080; + + div{ + padding: 4px; + border-left: 1px solid $panel-background-color; + + &.a{ + padding: 4px 2px; + border-left: none; + } + + &.b{ + width: 100px; + } + } + } + + .ranges{ + position: absolute; + left: 0; + right: 0; + top: 20px; + bottom: 30px; + overflow: auto; + } + + .range{ + padding: 4px 4px 4px 8px; + overflow: auto; + overflow-y: hidden; + border-bottom: 1px solid black; + border-top: 1px solid #464545; + display: flex; + align-items: center; + + &.active{ + background-color: #3e4144; + } + + + .checkbox{ + display: block; + } + + .slider{ + display: block; + width: 60px; + margin: 0 4px; + } + + .speed{ + width: 40px; + margin: 0 4px 0 0; + } + + canvas{ + display: block; + cursor: pointer; + + &.active{ + box-shadow: 0 0 5px 2px $active-color; + } + } + } + } + + .sliders{ + position: absolute; + left: 0; + top: 10px; + right: 0; + padding: 0 20px; + + .tabs{ + display: block; + margin-top: 10px; + position: relative; + .tab{ + position: absolute; + left: 0; + font-size: 11px; + height: 50px; + width: 18px; + text-align: center; + top: 53px; + background-color: $panel-background-color; + pointer-events: none; + + &:before, + &:after{ + content: ""; + position: absolute; + left: 0; + right: 1px; + border-top: 1px solid #6A6A6A; + transform-origin: top right; + } + + &:before{ + top: 0; + transform: rotate(-15deg); + } + + &:after{ + bottom: -1px; + transform: rotate(15deg); + } + + span{ + display: block; + border-left: 1px solid #6A6A6A; + margin: 5px 0; + width: 14px; + padding: 2px 0 2px 4px; + height: 41px; + } + + + &:first-child{ + top: 0; + &:before{ + right: 0; + } + } + + + &.inactive{ + opacity: 0.4; + margin-left: 1px; + transition: all 0.1s ease-in-out; + pointer-events: all; + background-color: transparent; + + &:hover{ + cursor: pointer; + opacity: 1; + margin-left: -1px; + } + } + } + + .panel{ + border: 1px solid #6A6A6A; + margin-left: 16px; + } + } + + .slider{ + position: relative; + margin: 8px 0; + + span.label{ + position: absolute; + right: 49px; + text-align: left; + text-transform: capitalize; + font-size: 13px; + margin-top: 5px; + width: 45px; + } + + input.slider{ + width: 148px; + margin: 4px 0 0 12px; + + &.red::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #000000 0%, #c40202 100%); + border: none; + } + &.red.hsv::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #FF3A00 0%, #F0FF00 20%, #00FF1D 40%, #0007FF 60%, #FF00F1 80%, #FA0505 100%); + } + &.green::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #000000 0%, #00a402 100%); + border: none; + } + &.green.hsv::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #000000 0%, #c40202 100%); + } + &.blue::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #000000 0%, #085db9 100%); + border: none; + } + &.blue.hsv::-webkit-slider-runnable-track{ + background: linear-gradient(90deg, #000000 0%, #FFFFFF 100%); + } + } + + input.rangevalue{ + position: absolute; + right: 4px; + width: 40px; + } + } + + input.hex{ + width: 100px; + position: absolute; + right: 20px; + } + + input.masked{ + position: absolute; + opacity: 0; + pointer-events: none; + left: 0; + top: 0; + height: 30px; + } + + .pixelcount{ + position: absolute; + left: 86px; + top: 1px; + font-size: 12px; + } + } + + .button.small{ + position: absolute; + width: 50px; + + &.revert{ + right: 60px; + } + + &.apply{ + right: 8px; + } + } + + + } + + + + .buttons{ + position: absolute; + left: 0; + bottom: 72px; + right: 10px; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease-in-out; + + &.active{ + opacity: 1; + pointer-events: all; + } + } + + .depthinfo{ + position: absolute; + bottom: 62px; + left: 40px; + color: #c99721; + padding: 3px 3px 3px 18px; + font-size: 11px; + display: none; + background-image: url("../_img/warning.svg"); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: 0 0; + + &.active{ + display: block; + } + } + + .button.small{ + line-height: 14px; + font-size: 12px; + padding: 2px 4px; + text-align: center; + + .contextmenu{ + position: absolute; + + .item{ + padding: 2px 4px; + background-color: $panel-background-color; + border: 1px solid black; + border-radius: 2px; + font-size: 12px; + margin: 2px 0; + width: 100px; + text-align: left; + + &:hover{ + cursor: pointer; + background-color: $panel-background-active; + } + } + } + } + + .options{ + position: absolute; + left: 0; + bottom: 0; + right: 0; + padding: 5px 10px 5px 20px; + border-top: 1px solid black; + + .checkbox{ + padding: 3px 0; + position: relative; + font-size: 12px; + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_paletteList.scss b/app/web-tools/dpaint/_style/_paletteList.scss new file mode 100644 index 00000000..db6b369b --- /dev/null +++ b/app/web-tools/dpaint/_style/_paletteList.scss @@ -0,0 +1,137 @@ +.palettelist{ + position: absolute; + left: 70px; + top: 27px; + bottom: 20px; + border: 1px solid black; + width: 100px; + background-color: #313335; + z-index: 101; + display: none; + + &.active{ + display: block; + box-shadow: 1px 0 2px rgba(0, 0, 0, 0.2); + } + + .caption{ + background-color: #282A2C; + color: #BBBBBB; + padding: 3px 5px 3px 5px; + font-size: 12px; + height: 21px; + border-bottom: 1px solid rgba(0, 0, 0, 0.4); + user-select: none; + + .close{ + position: absolute; + height: 20px; + width: 20px; + line-height: 20px; + right: 0; + top: 0; + text-align: center; + cursor: pointer; + } + } + + .inner{ + position: absolute; + left:0; + right: 0; + top: 20px; + bottom: 0; + overflow: auto; + overflow-x: hidden; + } + + .group{ + color: #BBBBBB; + padding: 3px 5px 3px 16px; + position: relative ; + font-size: 12px; + border-bottom: 1px solid black; + + &:before{ + content: ""; + position: absolute; + width: 16px; + height: 16px; + background-image: url("../_img/caret.svg"); + background-repeat: no-repeat; + background-size: contain; + left: 1px; + top: 2px; + opacity: 0.7; + } + + &.active{ + &:before{ + transform: rotate(90deg); + } + } + + &:hover{ + cursor: pointer; + background-color: #434548; + + &:before{ + opacity: 1; + } + } + } + + .palette{ + padding-bottom: 10px; + clear: both; + background-color:#313335; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + + .caption{ + background-color:transparent; + border-top: 1px solid rgb(67, 69, 72); + width: 100%; + height: auto; + font-size: 11px; + white-space: nowrap; + } + + canvas{ + display: block; + margin: 2px; + } + + &:hover{ + background-color: #434548; + cursor: pointer; + + .caption{ + color: white; + } + } + + &:nth-child(2){ + .caption{ + border-top:none; + } + } + } + + .button{ + line-height: 14px; + text-align: center; + border: 1px solid rgba(141, 142, 143, 0.5); + font-size: 12px; + user-select: none; + color: $menu-text-color; + padding: 3px 0; + margin: 4px 2px; + + &:hover{ + background-color: $panel-background-active; + cursor: pointer; + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_range.scss b/app/web-tools/dpaint/_style/_range.scss new file mode 100644 index 00000000..53a891d2 --- /dev/null +++ b/app/web-tools/dpaint/_style/_range.scss @@ -0,0 +1,81 @@ +input[type=range] { + margin: 0; + background-color: transparent; + -webkit-appearance: none; + box-sizing: border-box; + height: 21px; +} +input[type=range]:focus { + outline: none; +} +input[type=range]:disabled { + opacity: 0.3; +} +input[type=range]::-webkit-slider-runnable-track { + background: #c8c8c8; + border: 1px solid #010101; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range]::-webkit-slider-thumb { + margin-top: -8.6px; + width: 8px; + height: 16px; + background: #c8c8c8; + border: 1px solid rgba(50, 50, 50, 0.7); + cursor: pointer; + -webkit-appearance: none; +} +input[type=range]:focus::-webkit-slider-runnable-track { + background: #d5d5d5; +} +input[type=range]::-moz-range-track { + background: #c8c8c8; + border: 1px solid #010101; + width: 100%; + height: 0; + margin-top: -2px; + cursor: pointer; +} +input[type=range]::-moz-range-thumb { + width: 6px; + height: 14px; + background: #c8c8c8; + border: 1px solid rgba(50, 50, 50, 0.7); + border-radius: 0; + cursor: pointer; + margin-top: -2px; +} +input[type=range]::-ms-track { + background: transparent; + border-color: transparent; + border-width: 8px 0; + color: transparent; + width: 100%; + height: 2px; + cursor: pointer; +} +input[type=range]::-ms-fill-lower { + background: #bbbbbb; + border: 1px solid #010101; +} +input[type=range]::-ms-fill-upper { + background: #c8c8c8; + border: 1px solid #010101; +} +input[type=range]::-ms-thumb { + width: 8px; + height: 16px; + background: #c8c8c8; + border: 1px solid rgba(50, 50, 50, 0.7); + cursor: pointer; + margin-top: 0; +} +input[type=range]:focus::-ms-fill-lower { + background: #c8c8c8; +} +input[type=range]:focus::-ms-fill-upper { + background: #d5d5d5; +} + diff --git a/app/web-tools/dpaint/_style/_saveDialog.scss b/app/web-tools/dpaint/_style/_saveDialog.scss new file mode 100644 index 00000000..ee08b47c --- /dev/null +++ b/app/web-tools/dpaint/_style/_saveDialog.scss @@ -0,0 +1,309 @@ +.saveform{ + padding: 10px; + h4{ + padding: 0; + margin: 0 0 4px 0; + font-weight: normal; + + &.amiga, + &.general{ + background-image: url("../_img/amigatick.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: left center; + padding-left: 20px; + } + + &.general{ + background-image: url("../_img/image.svg"); + padding-left: 22px; + } + } + + input{ + width: 100%; + margin-bottom: 10px; + } + + .platform{ + display: inline-block; + width: 50%; + vertical-align: top; + + .button{ + border: none; + position: relative; + padding: 5px 5px 5px 40px; + + .icon{ + width: 32px; + height:32px; + left: 0; + position: absolute; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + + &.png{background-image: url("../_img/png.svg");} + &.json{background-image: url("../_img/json.svg");} + &.psd{background-image: url("../_img/psd.svg");} + &.mui{background-image: url("../_img/mui.png");} + &.os3{background-image: url("../_img/os3.png");} + &.os4{background-image: url("../_img/os4.png");} + &.iff{background-image: url("../_img/iff.png");} + &.adf{background-image: url("../_img/floppy.png");} + } + + .info{ + background-color: #4d5052; + position: absolute; + padding: 4px 4px 4px 8px; + border: 1px solid black; + top: 0; + left: 140px; + z-index: 10; + width: 200px; + opacity: 0; + pointer-events: none; + min-height: 40px; + transition: opacity 0.2s ease-in-out, left 0.2s ease-in-out; + + &:before{ + content: ""; + position: absolute; + top: 10px; + left: -21px; + border: 10px solid transparent; + border-right: 10px solid black; + } + + &:after{ + content: ""; + position: absolute; + top: 10px; + left: -20px; + border: 10px solid transparent; + border-right: 10px solid #4d5052; + } + + } + + &:hover{ + .info{ + opacity: 1; + left: 160px; + } + } + + &.more{ + position: absolute; + bottom: 10px; + min-width: 60px; + padding: 5px 8px; + + &:after{ + content: ""; + position: absolute; + right: 2px; + width: 16px; + height: 17px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + opacity: 0.5; + } + } + } + + &.amiga{ + .button{ + .info{ + left: unset; + right: 140px; + transition: opacity 0.2s ease-in-out, right 0.2s ease-in-out; + + &:before{ + left: unset; + right: -21px; + transform: rotate(180deg); + } + + &:after{ + left: unset; + right: -20px; + transform: rotate(180deg); + } + } + &:hover{ + .info{ + right: 160px; + } + } + } + } + } + + + .moremenu{ + z-index: 10; + position: absolute; + top: 20px; + bottom: 0; + right: 0; + width: 220px; + background-color: #2B2B2B; + border-left: 1px solid black; + display: none; + + .item{ + padding:5px 5px 5px 44px; + border-bottom: 1px solid black; + position: relative; + + .subtitle{ + font-size: 13px; + padding: 4px 0; + color: #797979; + } + + &:hover{ + background-color: #232323; + cursor: pointer; + + .info{ + opacity: 1; + right: 220px; + } + } + + &:after{ + content: ""; + position: absolute; + left: 2px; + top: 2px; + width: 34px; + height: 34px; + background-image: url("../_img/layers.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + } + + &.mask:after{ + background-image: url("../_img/layers_mask.svg"); + } + &.index:after{ + background-image: url("../_img/pixelgrid.svg"); + } + + &.loading{ + &:after{ + display: none; + } + } + + .spinner{ + top: 8px; + left: 30px; + width: 32px; + height: 32px; + } + + .info{ + background-color: #232323; + position: absolute; + padding: 4px 4px 4px 8px; + border: 1px solid black; + top: 0px; + z-index: 10; + width: 170px; + opacity: 0; + pointer-events: none; + min-height: 40px; + transition: opacity 0.2s ease-in-out, right 0.2s ease-in-out; + right: 120px; + box-shadow: 0 0 20px 0px #00000082; + + + &:before{ + content: ""; + position: absolute; + top: 10px; + border: 10px solid transparent; + border-right: 10px solid black; + right: -21px; + transform: rotate(180deg); + } + + &:after{ + content: ""; + position: absolute; + top: 10px; + right: -20px; + transform: rotate(180deg); + border: 10px solid transparent; + border-right: 10px solid #232323; + } + + } + + } + } + + &.hasmore{ + .moremenu{ + display: block; + } + + .button.more:after{ + transform: rotate(180deg); + } + + input{ + width: calc(100% - 220px); + } + } + + +} + +.saveoverlay{ + position: absolute; + top: 20px; + left: 1px; + right: 1px; + bottom: 1px; + background-color:#3D3F41; + z-index: 10; + + .info{ + text-align: center; + padding: 50px 10px 10px 10px; + + b{ + font-size: 20px; + display: block; + padding: 20px 0; + } + } + + .spinner{ + display: none; + } + + &.loading{ + .info{ + display: none; + } + + .buttons{ + pointer-events: none; + opacity: 0.5; + } + + .spinner{ + display: block; + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_scrollbar.scss b/app/web-tools/dpaint/_style/_scrollbar.scss new file mode 100644 index 00000000..8c55f437 --- /dev/null +++ b/app/web-tools/dpaint/_style/_scrollbar.scss @@ -0,0 +1,29 @@ +/* Firefox */ +* { + scrollbar-width: auto; + scrollbar-color: #8c8c8c #454545; +} + +/* Chrome, Edge, and Safari */ +*::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +*::-webkit-scrollbar-track { + background: #000000; +} + +*::-webkit-scrollbar-thumb { + background-color: #4d4d4d; + border-radius: 10px; + border: 2px solid #2b2b2b; +} + +*::-webkit-scrollbar-thumb:hover{ + background-color: #626262; +} + +::-webkit-scrollbar-corner { + background: #000000; +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_selection.scss b/app/web-tools/dpaint/_style/_selection.scss new file mode 100644 index 00000000..3faa56e3 --- /dev/null +++ b/app/web-tools/dpaint/_style/_selection.scss @@ -0,0 +1,210 @@ +.sizebox{ + position: absolute; + width: 10px; + height: 10px; + /*background-color: rgba(255, 0, 0, 0.24);*/ + z-index: 100; + display: none; + cursor: move; + + &.hot{ + pointer-events: none; + } + + &.active{ + display: block; + } + + .sizedot:nth-of-type(1){cursor: nw-resize;} + .sizedot:nth-of-type(2){cursor: n-resize;} + .sizedot:nth-of-type(3){cursor: ne-resize;} + .sizedot:nth-of-type(4){cursor: e-resize;} + .sizedot:nth-of-type(5){cursor: se-resize;} + .sizedot:nth-of-type(6){cursor: s-resize;} + .sizedot:nth-of-type(7){cursor: sw-resize;} + .sizedot:nth-of-type(8){cursor: w-resize;} + + canvas{ + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + z-index: 0; + } +} + +.selectbox{ + position: absolute; + display: none; + z-index: 4; + pointer-events: none; + left: 0; + top: 0; + right: 0; + bottom: 0; + /*background-color: rgba(0, 0, 255, 0.25);*/ + + .border{ + position: absolute; + left: -1px; + top: -1px; + right: -1px; + bottom: -1px; + display: none; + + svg{ + width: 100%; + height: 100%; + display: none; + + .white, + .ants{ + stroke-width: 2px; + } + } + + &.active{ + display: block; + + svg{ + display: block; + } + } + + &.filled{ + display: block; + background-color: rgba(255, 0, 0, 0.25); + } + } + + svg{ + .white { + fill: none; + stroke: #FFF; + stroke-width: 1px; + vector-effect: non-scaling-stroke; + } + + .filled{ + fill: rgba(255, 0, 0, 0.25); + } + + .ants { + fill: none; + stroke: #000; + stroke-width: 1px; + vector-effect: non-scaling-stroke; + stroke-dasharray: 4px; + animation: stroke 0.2s linear infinite; + /*shape-rendering: geometricPrecision;*/ + stroke-dashoffset: 8px; + } + + @keyframes stroke { + to { + stroke-dashoffset: 0; + } + } + + } + + .content{ + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + + .dots{ + position: absolute; + left: 0; + top: 0; + z-index: 11; + } + + .shape{ + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + pointer-events: none; + z-index: 10; + + svg{ + width: 100%; + } + } + + canvas{ + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + display: block; + z-index: 9; + } + + } + + &.active{ + display: block; + } + + &.capture{ + /*background-color: rgba(0, 255, 0, 0.15);*/ + pointer-events: all; + } + +} + +.sizedot{ + position: absolute; + z-index: 2; + width: 10px; + height: 10px; + margin: -5px 0 0 -5px; + border: 1px solid white; + background-color: black; + + &:hover{ + background-color: white; + } +} + +.rotatedot{ + position: absolute; + z-index: 1; + width: 20px; + height: 20px; + margin: -20px 0 0 -20px; + cursor: url("../_img/cursors/rotatenw.png") 0 0, auto; + display: none; + + &.d2{ + margin: -20px -20px 0 0; + cursor: url("../_img/cursors/rotatene.png") 0 0, auto; + } + &.d3{ + margin: 0 -20px -20px 0; + cursor: url("../_img/cursors/rotatese.png") 0 0, auto; + } + &.d4{ + margin: 0 0 -20px -20px; + cursor: url("../_img/cursors/rotatesw.png") 0 0, auto; + } + + &:hover{ + /*background-color: green;*/ + } +} + +.sizebox.canrotate{ + .rotatedot{ + display: block; + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_sidepanel.scss b/app/web-tools/dpaint/_style/_sidepanel.scss new file mode 100644 index 00000000..7c4d2390 --- /dev/null +++ b/app/web-tools/dpaint/_style/_sidepanel.scss @@ -0,0 +1,678 @@ +.sidepanel{ + position: absolute; + left: 70px; + top: 27px; + bottom: 20px; + border: 1px solid black; + width: 175px; + background-color: $panel-background-color; + display: none; + overflow: auto; + + .panel{ + position: absolute; + left: 0; + right: 0; + top: 0; + + .inner{ + position: absolute; + top: 22px; + bottom: 0; + overflow: auto; + left: 0; + right: 0; + + small{ + font-size: 11px; + color: $dimmed-text-color; + text-align: center; + margin-top: 20px; + display: block; + } + + dl{ + margin: 0; + padding: 0; + border-bottom: 1px solid #4d4d4d; + display: flex; + dt, + dd{ + display: inline-block; + color: $dimmed-text-color; + font-size: 11px; + padding: 3px 5px; + margin: 0; + background-color: rgba(255, 255, 255, 0.05); + width: 60px; + } + + dd{ + opacity: 0.7; + width: 100px; + } + } + + .layer{ + padding: 5px; + color: $menu-text-color; + border-bottom: 1px solid #626262; + font-size: 12px; + user-select: none; + position: absolute; + left: 0; + right: 0; + height: 23px; + + &.active{ + background-color: rgba(255, 255, 255, 0.1); + color: white; + + .mask{ + &:after{ + background-color: #46484A; + } + } + } + + &:hover{ + cursor: pointer; + } + + &.ghost, + &.hidden{ + opacity: 0.4; + .eye{ + opacity: 0.4; + } + } + + &.locked{ + pointer-events: none; + } + + .eye{ + position: absolute; + right: 4px; + top: 7px; + width: 16px; + height: 16px; + background-image: url("../_img/eye.svg"); + background-size: contain; + background-repeat: no-repeat; + opacity: 0.7; + } + + .lock{ + position: absolute; + right: 24px; + top: 3px; + width: 16px; + height: 16px; + background-image: url("../_img/lock_closed.svg"); + background-size: contain; + background-repeat: no-repeat; + opacity: 0.7; + } + + .mask{ + position: absolute; + right: 27px; + top: 4px; + width: 16px; + height: 16px; + border: 1px solid #C7C8C8; + background-color: #C7C8C8; + text-align: center; + + &:after{ + content: ""; + position: absolute; + left: 50%; + top: 50%; + width: 14px; + height: 14px; + border-radius: 7px; + margin: -7px 0 0 -7px; + background-color: #313335; + } + + &.active{ + border: 1px solid $active-color; + background-color: $active-color; + box-shadow: 0 0 2px yellow; + } + + &.disabled{ + pointer-events: none; + + &:before{ + content: ""; + position: absolute; + z-index: 10; + left: -4px; + transform: rotate(-45deg); + top: 6px; + width: 22px; + height: 2; + background-color: orange; + } + } + } + + input[type="text"]{ + position: absolute; + z-index: 10; + left: 0; + top: 0; + font-size: 12px; + right: 0; + } + } + + .frame{ + width: 50px; + height: 50px; + border: 1px solid #626262; + margin: 1px; + position: absolute; + opacity: 0.8; + + &:hover{ + border: 1px solid white; + cursor: pointer; + opacity: 1; + } + + .label{ + position: absolute; + left: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + color: $menu-text-color; + font-size: 12px; + padding: 4px; + } + + &.active{ + box-shadow: 0 0 3px 1px rgba(255, 255, 255, 0.8); + opacity: 1; + + .label{ + color: #CCCCCC; + background-color: rgba(0, 0, 0, 0.7); + } + } + + &.ghost{ + opacity: 0.2; + } + } + + } + + h2{ + font-size: 14px; + color: $menu-text-color; + font-weight: 100; + padding: 2px; + margin: 0; + } + + &.color{ + + } + + &.frames{ + .panelcontent{ + position: absolute; + left: 0; + right: 0; + top: 22px; + bottom: 0; + overflow: auto; + overflow-y: hidden; + + &.inactive{ + pointer-events: none; + } + } + } + + &.collapsed{ + .caption{ + i{ + transform: rotate(0deg); + } + } + + .inner{ + display: none; + } + } + + .paneltools{ + border-bottom: 1px solid rgba(0, 0, 0, 0.7); + background-color: $button-background-medium; + display: flex; + justify-content: flex-end; + font-size: 12px; + color: $menu-text-color; + position: relative; + + &.multirow{ + height: 47px; + } + + .button{ + height: 20px; + width: 20px; + margin-left: 2px; + font-size: 12px; + line-height: 20px; + border: none; + border-right: 1px solid black; + background-color: #282A2C; + background-image: url("../_img/add.svg"); + background-position: center center; + background-repeat: no-repeat; + background-size: contain; + opacity: 0.5; + + &.delete{ + background-image: url("../_img/trashcan.svg"); + } + + &:hover{ + cursor: pointer; + opacity: 1; + } + } + + .rangeselect{ + position: absolute; + left: 4px; + top: 4px; + right: 44px; + font-size: 11px; + color: #9d9c9c; + + input{ + position: absolute; + margin: 0 0 0 4px; + height: 17px; + left: 40px; + top: 0; + right: 0; + width: auto; + + &::-webkit-slider-thumb, + &::-moz-range-thumb{ + margin-top: -6px; + width: 8px; + height: 12px; + background: #9d9d9d; + border: 1px solid rgba(50, 50, 50, 0.7); + } + &::-moz-range-thumb{ + width: 6px; + height: 10px; + } + } + } + + .blendselect{ + position: absolute; + left: 4px; + top: 25px; + right: 0; + color: #9d9c9c; + + .label{ + line-height: 19px; + } + + select{ + position: absolute; + right: 4px; + top: 0; + height: 19px; + left: 40px; + background-color: #181818; + color: #888888; + border: 1px solid #7b7b7b; + + &:focus{ + outline: none; + } + } + } + } + + &.brush, + &.grid{ + color: $menu-text-color; + font-size: 12px; + + label{ + opacity: 0.7; + line-height: 19px; + padding: 0 4px; + width: 110px; + } + + .rangeselect{ + display: flex; + margin-bottom: 3px; + + + input[type="text"]{ + padding: 0 4px; + width: 30px; + height: 19px; + margin-left: 4px; + margin-right: 2px; + font-size: 11px; + } + + } + + .dither{ + display: flex; + + } + + .patterns{ + display: flex; + width: 140%;; + + .pattern{ + width: 100%; + height: 20px; + margin-right: 2px; + border: 1px solid black; + background-color: #808080; + background-size: contain; + + &.invert.hasPattern{ + filter: invert(1); + border: 1px solid white; + background-color: #A0A0A0; + opacity: 0.5; + + &.active{ + border-color: #600fe0; + box-shadow: 0 0 2px blue; + opacity: 0.9; + } + + } + + &.p1{ + background-image: url("../_img/patterns/dots.png"); + } + + &.p2{ + background-image: url("../_img/patterns/cross.png"); + } + + &.p3{ + background-image: url("../_img/patterns/grid.png"); + } + + &:last-of-type{ + margin-right: 0; + background-color: transparent; + background-image: url("../_img/caret.svg"); + opacity: 0.5; + &:hover{ + background-color: transparent; + opacity: 1; + } + } + + &:hover{ + cursor: pointer; + background-color: white; + + &.invert.hasPattern{ + background-color: white; + opacity: 1; + } + } + + + &.active{ + border-color: $active-color; + box-shadow: 0 0 2px yellow; + } + + } + } + + + } + + &.color{ + .inner{ + overflow: hidden; + } + .colorpicker{ + height: 120px; + width: 100%; + overflow: hidden; + + canvas{ + border-right: 1px solid black; + } + canvas:last-of-type{ + border-left: 1px solid black; + margin-left: 4px; + } + + .dot{ + position: absolute; + pointer-events: none; + width: 10px; + height: 10px; + border: 2px solid white; + border-radius: 50%; + margin: -5px 0 0 -5px; + left: 119px; + top: 0; + box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.7), inset 0 0 0 1px rgba(0, 0, 0, 0.5); + } + + .line{ + position: absolute; + pointer-events: none; + width: 0; + height: 0; + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + border-right: 5px solid white; + left: 144px; + filter: drop-shadow(0px 0px 2px black); + top: 0; + margin-top: -5px; + + &:before{ + content: ""; + position: absolute; + left: -20px; + top: -5px; + width: 0; + height: 0; + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + border-left: 5px solid white; + } + } + } + } + + &.layers{ + .panelcontent{ + position: absolute; + left: 0; + right: 0; + top: 47px; + bottom: 0; + overflow: auto; + } + } + } + + .caption{ + background-color: $button-background-dark; + color: $menu-text-color; + padding: 3px 5px 3px 16px; + font-size: 12px; + position: absolute; + top: 0; + right: 0; + left: 0; + width: unset; + height: 21px; + border: none; + border-bottom: 1px solid black; + justify-content: flex-start; + user-select: none; + + i{ + position: absolute; + left: 0; + top: 2px; + width: 16px; + height: 16px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-repeat: no-repeat; + transform: rotate(90deg); + transition: transform 0.2s ease-in-out; + opacity: 0.7; + } + + .close{ + position: absolute; + height: 20px; + width: 20px; + line-height: 20px; + right: 0; + top: 0; + text-align: center; + cursor: pointer; + } + } + + .subpanel{ + color: $menu-text-color; + padding: 4px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + border-bottom: 1px solid rgba(0, 0, 0, 0.5); + position: relative; + + .label{ + opacity: 0.7; + font-size: 12px; + margin-bottom: 4px; + } + + .value{ + position: absolute; + right: 4px; + top: 4px; + } + + .button.square{ + position: absolute; + right: 4px; + top: 4px; + width: 16px; + height: 16px; + border: 1px solid black; + text-align: center; + line-height: 16px; + font-size: 12px; + &:hover{ + cursor: pointer; + } + + &.prev{ + right: 21px; + } + } + + select{ + color: $menu-text-color; + width: 100%; + border: 1px solid black; + background-color: transparent; + margin: 4px 0; + + &:focus{ + outline: none; + } + } + } + + + input[type=range] { + width: 100%; + } + + + .button.full{ + color: $menu-text-color-dim; + text-align: center; + border: 1px solid black; + line-height: 16px; + font-size: 12px; + margin: 4px; + background-color: $button-background-medium; + &:hover{ + cursor: pointer; + color: $menu-text-color; + background-color: $button-background-dark; + + } + } + +} + + +body.withsidepanel{ + .sidepanel{ + display: block; + } +} + +body.override{ + .sidepanel{ + .panel{ + .layer.system{ + display: none; + + + .layer{ + background-color: rgba(255, 255, 255, 0.1); + color: white; + } + } + } + } + + .splitpanel .panel .toolbar .toolpanel .options{ + .checkbox.mask{ + display: none; + } + + .checkbox.pressure span{ + &:before{ + background-color: #c0c0c0; + box-shadow: inset 0 0 0px 2px black; + } + + } + } + +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_statusbar.scss b/app/web-tools/dpaint/_style/_statusbar.scss new file mode 100644 index 00000000..41511f3f --- /dev/null +++ b/app/web-tools/dpaint/_style/_statusbar.scss @@ -0,0 +1,32 @@ +.statusbar{ + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 20px; + background-color: $panel-background-color; + color: $menu-text-color; + font-size: 12px; + padding: 2px 0 0 10px; + border-top: 1px solid black; + overflow: hidden; + + .tooltip{ + b{ + font-weight: normal; + display: inline-block; + border: 1px solid #9a9a9a; + font-size: 10px; + height: 14px;; + padding: 0 3px; + text-align: center; + } + + } +} + +body.presentation{ + .statusbar{ + display: none; + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_toolbar.scss b/app/web-tools/dpaint/_style/_toolbar.scss new file mode 100644 index 00000000..230f639e --- /dev/null +++ b/app/web-tools/dpaint/_style/_toolbar.scss @@ -0,0 +1,516 @@ +@import "var"; + +.toolbar{ + position: absolute; + left: 0; + top: 27px; + width: 64px; + bottom: 20px; + user-select: none; + + .togglepanel{ + height: 21px; + width: 100%; + border-bottom: 1px solid black; + position: relative; + color: $menu-text-color; + font-size: 12px; + font-weight: 100; + + &:after{ + content: ""; + position: absolute; + right: 2px; + top: 2px; + background-position: center center; + width: 16px; + height: 16px; + background-image: url("../_img/sidebar.svg"); + background-repeat: no-repeat; + background-size: contain; + opacity: 0.5; + transition: transform 0.3s ease-in-out; + } + + &:hover{ + cursor: pointer; + &:after{ + opacity: 1; + } + } + + &.showpalettetools{ + border-top: 1px solid black; + border-bottom: none; + line-height: 20px; + text-align: left; + padding-left: 16px; + + &:after{ + right: unset; + left: 0; + background-image: url("../_img/caret.svg"); + } + + &.active{ + &:after{ + transform: rotate(90deg); + } + + } + } + } + + .tools{ + display: flex; + flex-wrap: wrap; + align-content: flex-start; + justify-content: space-around; + background-color: $panel-background-color; + border: 1px solid black; + } + + .button{ + display: block; + width: 30px; + height: 30px; + border: 1px solid rgba(0, 0, 0, 0); + line-height: 30px; + text-align: center; + + + &:hover, + &.active{ + background-color: $panel-background-active; + cursor: pointer; + } + + &.icon{ + background-repeat: no-repeat; + background-position: center center; + opacity: 0.5; + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-crisp-edges; + image-rendering: pixelated; + image-rendering: crisp-edges; + background-size: 20px 20px; + color: white; + + &:hover{ + opacity: 0.7; + } + + &.active{ + opacity: 1; + background-color: $button-background-dark; + } + } + + &.pencil{ + background-image: url("../_img/pencil.svg"); + background-size: 70% 70%; + } + + &.select{ + background-image: url("../_img/select.svg"); + } + + &.zoomout{ + background-image: url("../_img/zoomout.svg"); + background-size: 70% 70%; + } + + &.zoom{ + background-image: url("../_img/zoom.svg"); + background-size: 70% 70%; + } + + &.split{ + background-image: url("../_img/split.svg"); + } + + &.circle{ + background-image: url("../_img/circle.svg"); + } + + &.square{ + background-image: url("../_img/square.svg"); + } + + &.line{ + background-image: url("../_img/line.svg"); + background-size: 80% 80%; + } + + &.erase{ + background-image: url("../_img/eraser.svg"); + background-size: 80% 80%; + } + + &.smudge{ + background-image: url("../_img/smudge.svg"); + background-size: 80% 80%; + } + + &.stamp{ + background-image: url("../_img/stamp.svg"); + background-size: 80% 80%; + } + + &.spray{ + background-image: url("../_img/spray.svg"); + background-size: 80% 80%; + } + + &.gradient{ + background-image: url("../_img/gradient.svg"); + } + + &.polygonselect{ + background-image: url("../_img/poly.svg"); + background-size: 80% 80%; + } + + &.pan{ + background-image: url("../_img/hand.svg"); + background-size: 80% 80%; + } + + &.picker{ + background-image: url("../_img/pipette_white.svg"); + background-size: 80% 80%; + } + + &.floodselect{ + background-image: url("../_img/magicwand.svg"); + background-size: 80% 80%; + } + + &.text{ + background-image: url("../_img/text.svg"); + background-size: 80% 80%; + } + + &.flood{ + background-image: url("../_img/fill.svg"); + background-size: 90% 90%; + } + + &.undo{ + background-image: url("../_img/undo.svg"); + background-size: 60% 60%; + &.disabled{ + opacity: 0.2; + } + } + + &.redo{ + background-image: url("../_img/redo.svg"); + background-size: 60% 60%; + &.disabled{ + opacity: 0.2; + } + } + } + + + .brushes{ + width: 55px; + height: 22px; + margin: 5px auto; + display: flex; + flex-wrap: wrap; + + .brush{ + width: 11px; + height: 11px; + background-image: url("../_img/brushes.png"); + opacity: 0.3; + display: block; + image-rendering: optimizeSpeed; + image-rendering: optimize-contrast; + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; + + &.active{ + opacity: 1; + background-color: black; + } + + &:hover{ + opacity: 1; + background-color: black; + box-shadow: 0 0 2px black; + } + } + } + + .palette{ + width: 60px; + text-align: center; + + .display{ + height: 72px; + position: relative; + + div{ + position: absolute; + width: 24px; + height: 24px; + border: 1px solid black; + left: 24px; + top: 16px; + background-color: black; + + &.nofill{ + background-image: url("../_img/nofill.svg"); + background-size: 100% 100%; + } + + &.front{ + z-index: 2; + left: 12px; + top: 6px; + } + + &.button{ + width: 30px; + height: 30px; + left: 30px; + top: 42px; + background-color:transparent; + border: 0; + background-image: url("../_img/swap.svg"); + background-repeat: no-repeat; + background-size: 60% 60%; + background-position: center center; + opacity: 0.7; + + &:hover, + &.active{ + background-color: $panel-background-active; + cursor: pointer; + opacity: 1; + } + + &.transparentcolors{ + left: 0; + background-image: url("../_img/nofill-white.svg"); + background-size: 50% 50%; + } + } + } + + input[type="color"]{ + position: absolute; + opacity: 0; + bottom: 30px; + } + } + + .color{ + width: 14px; + height: 14px; + display: inline-block; + } + } + + .palettebuttons{ + background-color: $panel-background-color; + border: 1px solid black; + margin: 2px auto 0 auto; + + &.hidden{ + display: none; + } + + .button{ + color: white; + opacity: 0.4; + font-size: 12px; + display: inline-block; + width: 30px; + height: 30px; + + &:hover{ + background-color: $panel-background-active; + cursor: pointer; + opacity: 1; + } + + &.active{ + + } + + .icon{ + width: 24px; + height: 24px; + display: inline-block; + vertical-align: middle; + background-color: transparent; + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + margin: 4px; + } + + + &.edit{ + .icon{ + background-image: url("../_img/palette.svg"); + } + } + + &.plus{ + .icon{ + background-image: url("../_img/add.svg"); + } + } + + &.lock{ + .icon{ + background-image: url("../_img/lock_open.svg"); + } + + &.active{ + opacity: 1; + .icon{ + background-image: url("../_img/lock_closed.svg"); + filter: brightness(0) saturate(100%) invert(91%) sepia(54%) saturate(6141%) hue-rotate(14deg) brightness(92%) contrast(90%); + } + } + } + + &.cycle{ + .icon{ + background-image: url("../_img/cycle.svg"); + } + + &.active{ + opacity: 1; + .icon{ + animation: spin 1s infinite linear; + filter: brightness(0) saturate(100%) invert(91%) sepia(54%) saturate(6141%) hue-rotate(14deg) brightness(92%) contrast(90%); + } + } + + } + } + } + + .palettenav{ + position: absolute; + bottom: 2px; + left: 0; + right: 0; + } + + .palettenav,.paletteListNav{ + .nav{ + height: 20px; + background-color: $panel-background-color; + border: 1px solid black; + margin: 2px auto 0 auto; + display: flex; + color: $menu-text-color; + + .prev,.next{ + width: 20px; + background-image: url("../_img/caret.svg"); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + opacity: 0.1; + pointer-events: none; + + &.active{ + opacity: 0.5; + pointer-events: all; + } + + &:hover{ + opacity: 1; + cursor: pointer; + } + } + + .prev{ + transform: rotate(180deg); + } + + .page{ + width: 20px; + text-align: center; + } + } + } + + .paletteListNav{ + position: relative; + } + + .viewstyle{ + display: flex; + margin-left: 20px; + border-left: 1px solid black; + + .button{ + opacity: 0.5; + + &:hover{ + opacity: 1; + } + + &.active{ + opacity: 1; + } + } + + } + + &.fill{ + .button{ + &.circle{ + background-image: url("../_img/circle_fill.svg"); + } + + &.square{ + background-image: url("../_img/square_fill.svg"); + } + } + } + + .palettecanvas{ + margin: 8px 0 0 4px; + } +} + +body.withsidepanel{ + .toolbar { + .togglepanel.sidebar { + &:after { + transform: rotate(180deg); + background-image: url("../_img/caret.svg"); + } + } + } +} + +body.presentation{ + .toolbar { + display: none; + } +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_uae.scss b/app/web-tools/dpaint/_style/_uae.scss new file mode 100644 index 00000000..87e36e31 --- /dev/null +++ b/app/web-tools/dpaint/_style/_uae.scss @@ -0,0 +1,61 @@ +.uae{ + position: absolute; + left:0; + top: 0; + z-index: 1000; + background-color: $button-background-dark; + box-shadow: 0 0 20px 0px black; + + .caption{ + height: 20px; + color: $menu-text-color; + border-bottom: 1px solid black; + font-size: 12px; + padding: 2px 2px 2px 24px; + background-image: url("../_img/amigatick.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: 2px center; + cursor: move; + + .close{ + position: absolute; + height: 20px; + width: 20px; + line-height: 20px; + right: 0; + top: 0; + text-align: center; + cursor: pointer; + opacity: 0.7; + + &:hover{ + opacity: 1; + } + } + } + + .resizer{ + position: absolute; + width: 20px; + height: 20px; + right: 0; + bottom: 0; + border:10px solid transparent; + border-bottom-color: #313335; + border-right-color: #313335; + cursor: nwse-resize; + } + + iframe{ + width: 100%; + height: calc(100% - 21px); + border: none; + } + + &.dragging{ + iframe{ + pointer-events: none; + } + } +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_var.scss b/app/web-tools/dpaint/_style/_var.scss new file mode 100644 index 00000000..10a7e7a7 --- /dev/null +++ b/app/web-tools/dpaint/_style/_var.scss @@ -0,0 +1,25 @@ +$background-color: #2B2B2B; +$panel-background-color: #313335; +$panel-background-active: #3C3F41; + +$button-background-dark: #282A2C; +$button-background-medium: #2D2E30; + +$menu-text-color: #BBBBBB; +$menu-text-color-high: #DDDDDD; +$menu-text-color-dim: #8f8f8f; +$dimmed-text-color: #adadad; + +$active-color: #b1e00f; +$active-color-dim: #b2970e; + +@mixin pixelated{ + image-rendering: optimizeSpeed; + image-rendering: optimize-contrast; + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/_visualAids.scss b/app/web-tools/dpaint/_style/_visualAids.scss new file mode 100644 index 00000000..4554331f --- /dev/null +++ b/app/web-tools/dpaint/_style/_visualAids.scss @@ -0,0 +1,40 @@ +.visualaids{ + position: absolute; + top: 0; + left: 0; + z-index: 1000; + pointer-events: none; + right: 0; + bottom: 0; + + .grid{ + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + + .line{ + position: absolute; + pointer-events: none; + + + &.vertical{ + top: 0; + bottom: 0; + border-left: 1px solid white; + width: 1px; + } + + &.horizontal{ + left: 0; + right: 0; + border-top: 1px solid white; + height: 1px; + } + } + } + + +} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/main.css b/app/web-tools/dpaint/_style/main.css new file mode 100644 index 00000000..4f8de3ca --- /dev/null +++ b/app/web-tools/dpaint/_style/main.css @@ -0,0 +1,3935 @@ +html { + box-sizing: border-box; + overflow: hidden; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +body { + background-color: #2B2B2B; + font-family: sans-serif; + font-size: 14px; + height: 100%; + margin: 0; + padding: 1px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; +} + +.container { + position: relative; + height: 100%; + background-color: #2B2B2B; +} +.container.fuzzy { + pointer-events: none; + filter: blur(2px); +} + +h1.error { + text-align: center; + color: white; + font-size: 24px; + font-weight: 100; + margin: 50px auto; + width: 50%; +} +h1.error a { + color: white; +} + +.spinner { + position: absolute; + top: 40%; + left: 50%; + margin-left: -25px; + width: 50px; + height: 50px; + border: 5px solid rgba(255, 255, 255, 0.3); + border-top: 5px solid white; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +/* Firefox */ +* { + scrollbar-width: auto; + scrollbar-color: #8c8c8c #454545; +} + +/* Chrome, Edge, and Safari */ +*::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +*::-webkit-scrollbar-track { + background: #000000; +} + +*::-webkit-scrollbar-thumb { + background-color: #4d4d4d; + border-radius: 10px; + border: 2px solid #2b2b2b; +} + +*::-webkit-scrollbar-thumb:hover { + background-color: #626262; +} + +::-webkit-scrollbar-corner { + background: #000000; +} + +.checkbox { + display: block; +} +.checkbox label { + position: relative; +} +.checkbox label span { + padding: 2px 0 2px 18px; + line-height: 14px; + white-space: nowrap; + color: #BBBBBB; +} +.checkbox label span:before { + content: ""; + position: absolute; + left: 0; + top: 1px; + width: 14px; + height: 14px; + background-color: #2b2b2b; + border: 1px solid #8a8a8a; +} +.checkbox label:hover { + cursor: pointer; +} +.checkbox label:hover span { + color: #d7d7d7; +} +.checkbox label:hover span:before { + border: 1px solid #e8e8e8; +} +.checkbox input { + opacity: 0; + position: absolute; +} +.checkbox input:checked + span:before { + background-color: #c0c0c0; + box-shadow: inset 0 0 0px 2px black; +} +.checkbox.small { + margin-top: 1px; +} +.checkbox.small label span { + padding: 2px 0 2px 14px; +} +.checkbox.small label span:before { + width: 10px; + height: 10px; + top: 2px; +} +.checkbox.small input:checked + span:before { + box-shadow: inset 0 0 0 1px black; +} + +input[type=number], +input[type=text] { + display: inline-block; + background-color: #2b2b2b; + border: 1px solid #6A6A6A; + color: #BBBBBB; + padding: 4px; +} +input[type=number]:focus, +input[type=text]:focus { + outline: none; +} + +.yesno { + display: flex; + width: 70px; + height: 18px; + border: 1px solid #6A6A6A; + margin: 2px 2px 0 0; + padding-top: 1px; + font-size: 12px; + position: relative; + overflow: hidden; +} +.yesno .option { + width: 50px; + text-align: center; + opacity: 0.5; + position: relative; + z-index: 2; +} +.yesno .option:nth-child(2) { + opacity: 1; + color: black; +} +.yesno:before { + content: ""; + position: absolute; + left: 50%; + top: 0; + bottom: 0; + width: 30px; + background-color: #BBBBBB; + transition: left 0.3s ease-in-out; +} +.yesno:hover { + cursor: pointer; +} +.yesno:hover .option { + opacity: 1; +} +.yesno:hover .option:nth-child(2) { + opacity: 1 !important; +} +.yesno.selected .option:nth-child(1) { + opacity: 1; + color: black; +} +.yesno.selected .option:nth-child(2) { + opacity: 0.5; + color: inherit; +} +.yesno.selected:before { + left: 0; +} + +input[type=range] { + margin: 0; + background-color: transparent; + -webkit-appearance: none; + box-sizing: border-box; + height: 21px; +} + +input[type=range]:focus { + outline: none; +} + +input[type=range]:disabled { + opacity: 0.3; +} + +input[type=range]::-webkit-slider-runnable-track { + background: #c8c8c8; + border: 1px solid #010101; + width: 100%; + height: 2px; + cursor: pointer; +} + +input[type=range]::-webkit-slider-thumb { + margin-top: -8.6px; + width: 8px; + height: 16px; + background: #c8c8c8; + border: 1px solid rgba(50, 50, 50, 0.7); + cursor: pointer; + -webkit-appearance: none; +} + +input[type=range]:focus::-webkit-slider-runnable-track { + background: #d5d5d5; +} + +input[type=range]::-moz-range-track { + background: #c8c8c8; + border: 1px solid #010101; + width: 100%; + height: 0; + margin-top: -2px; + cursor: pointer; +} + +input[type=range]::-moz-range-thumb { + width: 6px; + height: 14px; + background: #c8c8c8; + border: 1px solid rgba(50, 50, 50, 0.7); + border-radius: 0; + cursor: pointer; + margin-top: -2px; +} + +input[type=range]::-ms-track { + background: transparent; + border-color: transparent; + border-width: 8px 0; + color: transparent; + width: 100%; + height: 2px; + cursor: pointer; +} + +input[type=range]::-ms-fill-lower { + background: #bbbbbb; + border: 1px solid #010101; +} + +input[type=range]::-ms-fill-upper { + background: #c8c8c8; + border: 1px solid #010101; +} + +input[type=range]::-ms-thumb { + width: 8px; + height: 16px; + background: #c8c8c8; + border: 1px solid rgba(50, 50, 50, 0.7); + cursor: pointer; + margin-top: 0; +} + +input[type=range]:focus::-ms-fill-lower { + background: #c8c8c8; +} + +input[type=range]:focus::-ms-fill-upper { + background: #d5d5d5; +} + +.menu { + position: absolute; + left: 0; + right: 0; + border: 1px solid black; + height: 25px; + background-color: #313335; + color: #BBBBBB; + z-index: 1000; + user-select: none; + white-space: nowrap; +} +.menu .hamburger { + display: none; +} +.menu a.main { + position: relative; + display: inline-block; + padding: 0 10px; + line-height: 23px; + font-size: 13px; +} +.menu a.main .sub { + left: 0; + margin-top: 0; + position: absolute; + background-color: #313335; + color: #BBBBBB; + border: 1px solid black; + display: none; +} +.menu a.main .sub a { + display: block; + padding: 0 24px 0 10px; + font-size: 13px; + line-height: 23px; + white-space: nowrap; + position: relative; +} +.menu a.main .sub a.wide { + padding: 0 70px 0 10px; +} +.menu a.main .sub a.wide.ultra { + padding: 0 90px 0 10px; +} +.menu a.main .sub a.caret:after { + content: ""; + position: absolute; + right: 2px; + width: 16px; + height: 23px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + opacity: 0.5; +} +.menu a.main .sub a.checked:before { + content: ""; + position: absolute; + left: 4px; + top: 4px; + bottom: 0; + width: 14px; + background-image: url("../_img/check.svg"); + background-size: contain; + background-repeat: no-repeat; +} +.menu a.main .sub a:hover { + background-color: #3C3F41; + cursor: pointer; +} +.menu a.main .sub a:hover .subsub { + display: block; +} +.menu a.main .sub a .shortkey { + position: absolute; + right: 6px; + top: 1px; + color: #8f8f8f; + font-size: 11px; +} +.menu a.main .sub.checkable a { + padding: 0 24px 0 20px; +} +.menu a.main .sub .info { + position: absolute; + right: 6px; + top: 1px; + color: #8f8f8f; + font-size: 11px; +} +.menu a.main .sub .subsub { + position: absolute; + background-color: #313335; + border: 1px solid black; + z-index: 100; + top: 0; + left: 50%; + display: none; +} +.menu a.main .sub .subsub.checkable a { + padding: 0 24px 0 20px; +} +.menu a.main .sub .subsub.checkable a.hasinfo { + padding-right: 120px; +} +.menu a.main:hover, .menu a.main.active { + background-color: #3C3F41; + cursor: pointer; +} +.menu a.main.active .sub { + display: block; +} + +body.presentation .menu { + display: none; +} + +.contextmenu { + position: absolute; + z-index: 999; + border: 1px solid black; + background-color: #313335; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5); + opacity: 0; + display: none; + pointer-events: none; + color: #BBBBBB; +} +.contextmenu.active { + opacity: 1; + display: block; + pointer-events: all; +} +.contextmenu .contextmenuitem { + display: block; + padding: 0 24px 0 10px; + font-size: 13px; + line-height: 23px; + white-space: nowrap; + position: relative; +} +.contextmenu .contextmenuitem:hover { + background-color: #3C3F41; + cursor: pointer; +} + +.toolbar { + position: absolute; + left: 0; + top: 27px; + width: 64px; + bottom: 20px; + user-select: none; +} +.toolbar .togglepanel { + height: 21px; + width: 100%; + border-bottom: 1px solid black; + position: relative; + color: #BBBBBB; + font-size: 12px; + font-weight: 100; +} +.toolbar .togglepanel:after { + content: ""; + position: absolute; + right: 2px; + top: 2px; + background-position: center center; + width: 16px; + height: 16px; + background-image: url("../_img/sidebar.svg"); + background-repeat: no-repeat; + background-size: contain; + opacity: 0.5; + transition: transform 0.3s ease-in-out; +} +.toolbar .togglepanel:hover { + cursor: pointer; +} +.toolbar .togglepanel:hover:after { + opacity: 1; +} +.toolbar .togglepanel.showpalettetools { + border-top: 1px solid black; + border-bottom: none; + line-height: 20px; + text-align: left; + padding-left: 16px; +} +.toolbar .togglepanel.showpalettetools:after { + right: unset; + left: 0; + background-image: url("../_img/caret.svg"); +} +.toolbar .togglepanel.showpalettetools.active:after { + transform: rotate(90deg); +} +.toolbar .tools { + display: flex; + flex-wrap: wrap; + align-content: flex-start; + justify-content: space-around; + background-color: #313335; + border: 1px solid black; +} +.toolbar .button { + display: block; + width: 30px; + height: 30px; + border: 1px solid rgba(0, 0, 0, 0); + line-height: 30px; + text-align: center; +} +.toolbar .button:hover, .toolbar .button.active { + background-color: #3C3F41; + cursor: pointer; +} +.toolbar .button.icon { + background-repeat: no-repeat; + background-position: center center; + opacity: 0.5; + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-crisp-edges; + image-rendering: pixelated; + image-rendering: crisp-edges; + background-size: 20px 20px; + color: white; +} +.toolbar .button.icon:hover { + opacity: 0.7; +} +.toolbar .button.icon.active { + opacity: 1; + background-color: #282A2C; +} +.toolbar .button.pencil { + background-image: url("../_img/pencil.svg"); + background-size: 70% 70%; +} +.toolbar .button.select { + background-image: url("../_img/select.svg"); +} +.toolbar .button.zoomout { + background-image: url("../_img/zoomout.svg"); + background-size: 70% 70%; +} +.toolbar .button.zoom { + background-image: url("../_img/zoom.svg"); + background-size: 70% 70%; +} +.toolbar .button.split { + background-image: url("../_img/split.svg"); +} +.toolbar .button.circle { + background-image: url("../_img/circle.svg"); +} +.toolbar .button.square { + background-image: url("../_img/square.svg"); +} +.toolbar .button.line { + background-image: url("../_img/line.svg"); + background-size: 80% 80%; +} +.toolbar .button.erase { + background-image: url("../_img/eraser.svg"); + background-size: 80% 80%; +} +.toolbar .button.smudge { + background-image: url("../_img/smudge.svg"); + background-size: 80% 80%; +} +.toolbar .button.stamp { + background-image: url("../_img/stamp.svg"); + background-size: 80% 80%; +} +.toolbar .button.spray { + background-image: url("../_img/spray.svg"); + background-size: 80% 80%; +} +.toolbar .button.gradient { + background-image: url("../_img/gradient.svg"); +} +.toolbar .button.polygonselect { + background-image: url("../_img/poly.svg"); + background-size: 80% 80%; +} +.toolbar .button.pan { + background-image: url("../_img/hand.svg"); + background-size: 80% 80%; +} +.toolbar .button.picker { + background-image: url("../_img/pipette_white.svg"); + background-size: 80% 80%; +} +.toolbar .button.floodselect { + background-image: url("../_img/magicwand.svg"); + background-size: 80% 80%; +} +.toolbar .button.text { + background-image: url("../_img/text.svg"); + background-size: 80% 80%; +} +.toolbar .button.flood { + background-image: url("../_img/fill.svg"); + background-size: 90% 90%; +} +.toolbar .button.undo { + background-image: url("../_img/undo.svg"); + background-size: 60% 60%; +} +.toolbar .button.undo.disabled { + opacity: 0.2; +} +.toolbar .button.redo { + background-image: url("../_img/redo.svg"); + background-size: 60% 60%; +} +.toolbar .button.redo.disabled { + opacity: 0.2; +} +.toolbar .brushes { + width: 55px; + height: 22px; + margin: 5px auto; + display: flex; + flex-wrap: wrap; +} +.toolbar .brushes .brush { + width: 11px; + height: 11px; + background-image: url("../_img/brushes.png"); + opacity: 0.3; + display: block; + image-rendering: optimizeSpeed; + image-rendering: optimize-contrast; + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; +} +.toolbar .brushes .brush.active { + opacity: 1; + background-color: black; +} +.toolbar .brushes .brush:hover { + opacity: 1; + background-color: black; + box-shadow: 0 0 2px black; +} +.toolbar .palette { + width: 60px; + text-align: center; +} +.toolbar .palette .display { + height: 72px; + position: relative; +} +.toolbar .palette .display div { + position: absolute; + width: 24px; + height: 24px; + border: 1px solid black; + left: 24px; + top: 16px; + background-color: black; +} +.toolbar .palette .display div.nofill { + background-image: url("../_img/nofill.svg"); + background-size: 100% 100%; +} +.toolbar .palette .display div.front { + z-index: 2; + left: 12px; + top: 6px; +} +.toolbar .palette .display div.button { + width: 30px; + height: 30px; + left: 30px; + top: 42px; + background-color: transparent; + border: 0; + background-image: url("../_img/swap.svg"); + background-repeat: no-repeat; + background-size: 60% 60%; + background-position: center center; + opacity: 0.7; +} +.toolbar .palette .display div.button:hover, .toolbar .palette .display div.button.active { + background-color: #3C3F41; + cursor: pointer; + opacity: 1; +} +.toolbar .palette .display div.button.transparentcolors { + left: 0; + background-image: url("../_img/nofill-white.svg"); + background-size: 50% 50%; +} +.toolbar .palette .display input[type=color] { + position: absolute; + opacity: 0; + bottom: 30px; +} +.toolbar .palette .color { + width: 14px; + height: 14px; + display: inline-block; +} +.toolbar .palettebuttons { + background-color: #313335; + border: 1px solid black; + margin: 2px auto 0 auto; +} +.toolbar .palettebuttons.hidden { + display: none; +} +.toolbar .palettebuttons .button { + color: white; + opacity: 0.4; + font-size: 12px; + display: inline-block; + width: 30px; + height: 30px; +} +.toolbar .palettebuttons .button:hover { + background-color: #3C3F41; + cursor: pointer; + opacity: 1; +} +.toolbar .palettebuttons .button .icon { + width: 24px; + height: 24px; + display: inline-block; + vertical-align: middle; + background-color: transparent; + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + margin: 4px; +} +.toolbar .palettebuttons .button.edit .icon { + background-image: url("../_img/palette.svg"); +} +.toolbar .palettebuttons .button.plus .icon { + background-image: url("../_img/add.svg"); +} +.toolbar .palettebuttons .button.lock .icon { + background-image: url("../_img/lock_open.svg"); +} +.toolbar .palettebuttons .button.lock.active { + opacity: 1; +} +.toolbar .palettebuttons .button.lock.active .icon { + background-image: url("../_img/lock_closed.svg"); + filter: brightness(0) saturate(100%) invert(91%) sepia(54%) saturate(6141%) hue-rotate(14deg) brightness(92%) contrast(90%); +} +.toolbar .palettebuttons .button.cycle .icon { + background-image: url("../_img/cycle.svg"); +} +.toolbar .palettebuttons .button.cycle.active { + opacity: 1; +} +.toolbar .palettebuttons .button.cycle.active .icon { + animation: spin 1s infinite linear; + filter: brightness(0) saturate(100%) invert(91%) sepia(54%) saturate(6141%) hue-rotate(14deg) brightness(92%) contrast(90%); +} +.toolbar .palettenav { + position: absolute; + bottom: 2px; + left: 0; + right: 0; +} +.toolbar .palettenav .nav, .toolbar .paletteListNav .nav { + height: 20px; + background-color: #313335; + border: 1px solid black; + margin: 2px auto 0 auto; + display: flex; + color: #BBBBBB; +} +.toolbar .palettenav .nav .prev, .toolbar .palettenav .nav .next, .toolbar .paletteListNav .nav .prev, .toolbar .paletteListNav .nav .next { + width: 20px; + background-image: url("../_img/caret.svg"); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + opacity: 0.1; + pointer-events: none; +} +.toolbar .palettenav .nav .prev.active, .toolbar .palettenav .nav .next.active, .toolbar .paletteListNav .nav .prev.active, .toolbar .paletteListNav .nav .next.active { + opacity: 0.5; + pointer-events: all; +} +.toolbar .palettenav .nav .prev:hover, .toolbar .palettenav .nav .next:hover, .toolbar .paletteListNav .nav .prev:hover, .toolbar .paletteListNav .nav .next:hover { + opacity: 1; + cursor: pointer; +} +.toolbar .palettenav .nav .prev, .toolbar .paletteListNav .nav .prev { + transform: rotate(180deg); +} +.toolbar .palettenav .nav .page, .toolbar .paletteListNav .nav .page { + width: 20px; + text-align: center; +} +.toolbar .paletteListNav { + position: relative; +} +.toolbar .viewstyle { + display: flex; + margin-left: 20px; + border-left: 1px solid black; +} +.toolbar .viewstyle .button { + opacity: 0.5; +} +.toolbar .viewstyle .button:hover { + opacity: 1; +} +.toolbar .viewstyle .button.active { + opacity: 1; +} +.toolbar.fill .button.circle { + background-image: url("../_img/circle_fill.svg"); +} +.toolbar.fill .button.square { + background-image: url("../_img/square_fill.svg"); +} +.toolbar .palettecanvas { + margin: 8px 0 0 4px; +} + +body.withsidepanel .toolbar .togglepanel.sidebar:after { + transform: rotate(180deg); + background-image: url("../_img/caret.svg"); +} + +body.presentation .toolbar { + display: none; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +} +.statusbar { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 20px; + background-color: #313335; + color: #BBBBBB; + font-size: 12px; + padding: 2px 0 0 10px; + border-top: 1px solid black; + overflow: hidden; +} +.statusbar .tooltip b { + font-weight: normal; + display: inline-block; + border: 1px solid #9a9a9a; + font-size: 10px; + height: 14px; + padding: 0 3px; + text-align: center; +} + +body.presentation .statusbar { + display: none; +} + +.sidepanel { + position: absolute; + left: 70px; + top: 27px; + bottom: 20px; + border: 1px solid black; + width: 175px; + background-color: #313335; + display: none; + overflow: auto; +} +.sidepanel .panel { + position: absolute; + left: 0; + right: 0; + top: 0; +} +.sidepanel .panel .inner { + position: absolute; + top: 22px; + bottom: 0; + overflow: auto; + left: 0; + right: 0; +} +.sidepanel .panel .inner small { + font-size: 11px; + color: #adadad; + text-align: center; + margin-top: 20px; + display: block; +} +.sidepanel .panel .inner dl { + margin: 0; + padding: 0; + border-bottom: 1px solid #4d4d4d; + display: flex; +} +.sidepanel .panel .inner dl dt, +.sidepanel .panel .inner dl dd { + display: inline-block; + color: #adadad; + font-size: 11px; + padding: 3px 5px; + margin: 0; + background-color: rgba(255, 255, 255, 0.05); + width: 60px; +} +.sidepanel .panel .inner dl dd { + opacity: 0.7; + width: 100px; +} +.sidepanel .panel .inner .layer { + padding: 5px; + color: #BBBBBB; + border-bottom: 1px solid #626262; + font-size: 12px; + user-select: none; + position: absolute; + left: 0; + right: 0; + height: 23px; +} +.sidepanel .panel .inner .layer.active { + background-color: rgba(255, 255, 255, 0.1); + color: white; +} +.sidepanel .panel .inner .layer.active .mask:after { + background-color: #46484A; +} +.sidepanel .panel .inner .layer:hover { + cursor: pointer; +} +.sidepanel .panel .inner .layer.ghost, .sidepanel .panel .inner .layer.hidden { + opacity: 0.4; +} +.sidepanel .panel .inner .layer.ghost .eye, .sidepanel .panel .inner .layer.hidden .eye { + opacity: 0.4; +} +.sidepanel .panel .inner .layer.locked { + pointer-events: none; +} +.sidepanel .panel .inner .layer .eye { + position: absolute; + right: 4px; + top: 7px; + width: 16px; + height: 16px; + background-image: url("../_img/eye.svg"); + background-size: contain; + background-repeat: no-repeat; + opacity: 0.7; +} +.sidepanel .panel .inner .layer .lock { + position: absolute; + right: 24px; + top: 3px; + width: 16px; + height: 16px; + background-image: url("../_img/lock_closed.svg"); + background-size: contain; + background-repeat: no-repeat; + opacity: 0.7; +} +.sidepanel .panel .inner .layer .mask { + position: absolute; + right: 27px; + top: 4px; + width: 16px; + height: 16px; + border: 1px solid #C7C8C8; + background-color: #C7C8C8; + text-align: center; +} +.sidepanel .panel .inner .layer .mask:after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + width: 14px; + height: 14px; + border-radius: 7px; + margin: -7px 0 0 -7px; + background-color: #313335; +} +.sidepanel .panel .inner .layer .mask.active { + border: 1px solid #b1e00f; + background-color: #b1e00f; + box-shadow: 0 0 2px yellow; +} +.sidepanel .panel .inner .layer .mask.disabled { + pointer-events: none; +} +.sidepanel .panel .inner .layer .mask.disabled:before { + content: ""; + position: absolute; + z-index: 10; + left: -4px; + transform: rotate(-45deg); + top: 6px; + width: 22px; + height: 2; + background-color: orange; +} +.sidepanel .panel .inner .layer input[type=text] { + position: absolute; + z-index: 10; + left: 0; + top: 0; + font-size: 12px; + right: 0; +} +.sidepanel .panel .inner .frame { + width: 50px; + height: 50px; + border: 1px solid #626262; + margin: 1px; + position: absolute; + opacity: 0.8; +} +.sidepanel .panel .inner .frame:hover { + border: 1px solid white; + cursor: pointer; + opacity: 1; +} +.sidepanel .panel .inner .frame .label { + position: absolute; + left: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + color: #BBBBBB; + font-size: 12px; + padding: 4px; +} +.sidepanel .panel .inner .frame.active { + box-shadow: 0 0 3px 1px rgba(255, 255, 255, 0.8); + opacity: 1; +} +.sidepanel .panel .inner .frame.active .label { + color: #CCCCCC; + background-color: rgba(0, 0, 0, 0.7); +} +.sidepanel .panel .inner .frame.ghost { + opacity: 0.2; +} +.sidepanel .panel h2 { + font-size: 14px; + color: #BBBBBB; + font-weight: 100; + padding: 2px; + margin: 0; +} +.sidepanel .panel.frames .panelcontent { + position: absolute; + left: 0; + right: 0; + top: 22px; + bottom: 0; + overflow: auto; + overflow-y: hidden; +} +.sidepanel .panel.frames .panelcontent.inactive { + pointer-events: none; +} +.sidepanel .panel.collapsed .caption i { + transform: rotate(0deg); +} +.sidepanel .panel.collapsed .inner { + display: none; +} +.sidepanel .panel .paneltools { + border-bottom: 1px solid rgba(0, 0, 0, 0.7); + background-color: #2D2E30; + display: flex; + justify-content: flex-end; + font-size: 12px; + color: #BBBBBB; + position: relative; +} +.sidepanel .panel .paneltools.multirow { + height: 47px; +} +.sidepanel .panel .paneltools .button { + height: 20px; + width: 20px; + margin-left: 2px; + font-size: 12px; + line-height: 20px; + border: none; + border-right: 1px solid black; + background-color: #282A2C; + background-image: url("../_img/add.svg"); + background-position: center center; + background-repeat: no-repeat; + background-size: contain; + opacity: 0.5; +} +.sidepanel .panel .paneltools .button.delete { + background-image: url("../_img/trashcan.svg"); +} +.sidepanel .panel .paneltools .button:hover { + cursor: pointer; + opacity: 1; +} +.sidepanel .panel .paneltools .rangeselect { + position: absolute; + left: 4px; + top: 4px; + right: 44px; + font-size: 11px; + color: #9d9c9c; +} +.sidepanel .panel .paneltools .rangeselect input { + position: absolute; + margin: 0 0 0 4px; + height: 17px; + left: 40px; + top: 0; + right: 0; + width: auto; +} +.sidepanel .panel .paneltools .rangeselect input::-webkit-slider-thumb, .sidepanel .panel .paneltools .rangeselect input::-moz-range-thumb { + margin-top: -6px; + width: 8px; + height: 12px; + background: #9d9d9d; + border: 1px solid rgba(50, 50, 50, 0.7); +} +.sidepanel .panel .paneltools .rangeselect input::-moz-range-thumb { + width: 6px; + height: 10px; +} +.sidepanel .panel .paneltools .blendselect { + position: absolute; + left: 4px; + top: 25px; + right: 0; + color: #9d9c9c; +} +.sidepanel .panel .paneltools .blendselect .label { + line-height: 19px; +} +.sidepanel .panel .paneltools .blendselect select { + position: absolute; + right: 4px; + top: 0; + height: 19px; + left: 40px; + background-color: #181818; + color: #888888; + border: 1px solid #7b7b7b; +} +.sidepanel .panel .paneltools .blendselect select:focus { + outline: none; +} +.sidepanel .panel.brush, .sidepanel .panel.grid { + color: #BBBBBB; + font-size: 12px; +} +.sidepanel .panel.brush label, .sidepanel .panel.grid label { + opacity: 0.7; + line-height: 19px; + padding: 0 4px; + width: 110px; +} +.sidepanel .panel.brush .rangeselect, .sidepanel .panel.grid .rangeselect { + display: flex; + margin-bottom: 3px; +} +.sidepanel .panel.brush .rangeselect input[type=text], .sidepanel .panel.grid .rangeselect input[type=text] { + padding: 0 4px; + width: 30px; + height: 19px; + margin-left: 4px; + margin-right: 2px; + font-size: 11px; +} +.sidepanel .panel.brush .dither, .sidepanel .panel.grid .dither { + display: flex; +} +.sidepanel .panel.brush .patterns, .sidepanel .panel.grid .patterns { + display: flex; + width: 140%; +} +.sidepanel .panel.brush .patterns .pattern, .sidepanel .panel.grid .patterns .pattern { + width: 100%; + height: 20px; + margin-right: 2px; + border: 1px solid black; + background-color: #808080; + background-size: contain; +} +.sidepanel .panel.brush .patterns .pattern.invert.hasPattern, .sidepanel .panel.grid .patterns .pattern.invert.hasPattern { + filter: invert(1); + border: 1px solid white; + background-color: #A0A0A0; + opacity: 0.5; +} +.sidepanel .panel.brush .patterns .pattern.invert.hasPattern.active, .sidepanel .panel.grid .patterns .pattern.invert.hasPattern.active { + border-color: #600fe0; + box-shadow: 0 0 2px blue; + opacity: 0.9; +} +.sidepanel .panel.brush .patterns .pattern.p1, .sidepanel .panel.grid .patterns .pattern.p1 { + background-image: url("../_img/patterns/dots.png"); +} +.sidepanel .panel.brush .patterns .pattern.p2, .sidepanel .panel.grid .patterns .pattern.p2 { + background-image: url("../_img/patterns/cross.png"); +} +.sidepanel .panel.brush .patterns .pattern.p3, .sidepanel .panel.grid .patterns .pattern.p3 { + background-image: url("../_img/patterns/grid.png"); +} +.sidepanel .panel.brush .patterns .pattern:last-of-type, .sidepanel .panel.grid .patterns .pattern:last-of-type { + margin-right: 0; + background-color: transparent; + background-image: url("../_img/caret.svg"); + opacity: 0.5; +} +.sidepanel .panel.brush .patterns .pattern:last-of-type:hover, .sidepanel .panel.grid .patterns .pattern:last-of-type:hover { + background-color: transparent; + opacity: 1; +} +.sidepanel .panel.brush .patterns .pattern:hover, .sidepanel .panel.grid .patterns .pattern:hover { + cursor: pointer; + background-color: white; +} +.sidepanel .panel.brush .patterns .pattern:hover.invert.hasPattern, .sidepanel .panel.grid .patterns .pattern:hover.invert.hasPattern { + background-color: white; + opacity: 1; +} +.sidepanel .panel.brush .patterns .pattern.active, .sidepanel .panel.grid .patterns .pattern.active { + border-color: #b1e00f; + box-shadow: 0 0 2px yellow; +} +.sidepanel .panel.color .inner { + overflow: hidden; +} +.sidepanel .panel.color .colorpicker { + height: 120px; + width: 100%; + overflow: hidden; +} +.sidepanel .panel.color .colorpicker canvas { + border-right: 1px solid black; +} +.sidepanel .panel.color .colorpicker canvas:last-of-type { + border-left: 1px solid black; + margin-left: 4px; +} +.sidepanel .panel.color .colorpicker .dot { + position: absolute; + pointer-events: none; + width: 10px; + height: 10px; + border: 2px solid white; + border-radius: 50%; + margin: -5px 0 0 -5px; + left: 119px; + top: 0; + box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.7), inset 0 0 0 1px rgba(0, 0, 0, 0.5); +} +.sidepanel .panel.color .colorpicker .line { + position: absolute; + pointer-events: none; + width: 0; + height: 0; + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + border-right: 5px solid white; + left: 144px; + filter: drop-shadow(0px 0px 2px black); + top: 0; + margin-top: -5px; +} +.sidepanel .panel.color .colorpicker .line:before { + content: ""; + position: absolute; + left: -20px; + top: -5px; + width: 0; + height: 0; + border-bottom: 5px solid transparent; + border-top: 5px solid transparent; + border-left: 5px solid white; +} +.sidepanel .panel.layers .panelcontent { + position: absolute; + left: 0; + right: 0; + top: 47px; + bottom: 0; + overflow: auto; +} +.sidepanel .caption { + background-color: #282A2C; + color: #BBBBBB; + padding: 3px 5px 3px 16px; + font-size: 12px; + position: absolute; + top: 0; + right: 0; + left: 0; + width: unset; + height: 21px; + border: none; + border-bottom: 1px solid black; + justify-content: flex-start; + user-select: none; +} +.sidepanel .caption i { + position: absolute; + left: 0; + top: 2px; + width: 16px; + height: 16px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-repeat: no-repeat; + transform: rotate(90deg); + transition: transform 0.2s ease-in-out; + opacity: 0.7; +} +.sidepanel .caption .close { + position: absolute; + height: 20px; + width: 20px; + line-height: 20px; + right: 0; + top: 0; + text-align: center; + cursor: pointer; +} +.sidepanel .subpanel { + color: #BBBBBB; + padding: 4px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + border-bottom: 1px solid rgba(0, 0, 0, 0.5); + position: relative; +} +.sidepanel .subpanel .label { + opacity: 0.7; + font-size: 12px; + margin-bottom: 4px; +} +.sidepanel .subpanel .value { + position: absolute; + right: 4px; + top: 4px; +} +.sidepanel .subpanel .button.square { + position: absolute; + right: 4px; + top: 4px; + width: 16px; + height: 16px; + border: 1px solid black; + text-align: center; + line-height: 16px; + font-size: 12px; +} +.sidepanel .subpanel .button.square:hover { + cursor: pointer; +} +.sidepanel .subpanel .button.square.prev { + right: 21px; +} +.sidepanel .subpanel select { + color: #BBBBBB; + width: 100%; + border: 1px solid black; + background-color: transparent; + margin: 4px 0; +} +.sidepanel .subpanel select:focus { + outline: none; +} +.sidepanel input[type=range] { + width: 100%; +} +.sidepanel .button.full { + color: #8f8f8f; + text-align: center; + border: 1px solid black; + line-height: 16px; + font-size: 12px; + margin: 4px; + background-color: #2D2E30; +} +.sidepanel .button.full:hover { + cursor: pointer; + color: #BBBBBB; + background-color: #282A2C; +} + +body.withsidepanel .sidepanel { + display: block; +} + +body.override .sidepanel .panel .layer.system { + display: none; +} +body.override .sidepanel .panel .layer.system + .layer { + background-color: rgba(255, 255, 255, 0.1); + color: white; +} +body.override .splitpanel .panel .toolbar .toolpanel .options .checkbox.mask { + display: none; +} +body.override .splitpanel .panel .toolbar .toolpanel .options .checkbox.pressure span:before { + background-color: #c0c0c0; + box-shadow: inset 0 0 0px 2px black; +} + +.palettelist { + position: absolute; + left: 70px; + top: 27px; + bottom: 20px; + border: 1px solid black; + width: 100px; + background-color: #313335; + z-index: 101; + display: none; +} +.palettelist.active { + display: block; + box-shadow: 1px 0 2px rgba(0, 0, 0, 0.2); +} +.palettelist .caption { + background-color: #282A2C; + color: #BBBBBB; + padding: 3px 5px 3px 5px; + font-size: 12px; + height: 21px; + border-bottom: 1px solid rgba(0, 0, 0, 0.4); + user-select: none; +} +.palettelist .caption .close { + position: absolute; + height: 20px; + width: 20px; + line-height: 20px; + right: 0; + top: 0; + text-align: center; + cursor: pointer; +} +.palettelist .inner { + position: absolute; + left: 0; + right: 0; + top: 20px; + bottom: 0; + overflow: auto; + overflow-x: hidden; +} +.palettelist .group { + color: #BBBBBB; + padding: 3px 5px 3px 16px; + position: relative; + font-size: 12px; + border-bottom: 1px solid black; +} +.palettelist .group:before { + content: ""; + position: absolute; + width: 16px; + height: 16px; + background-image: url("../_img/caret.svg"); + background-repeat: no-repeat; + background-size: contain; + left: 1px; + top: 2px; + opacity: 0.7; +} +.palettelist .group.active:before { + transform: rotate(90deg); +} +.palettelist .group:hover { + cursor: pointer; + background-color: #434548; +} +.palettelist .group:hover:before { + opacity: 1; +} +.palettelist .palette { + padding-bottom: 10px; + clear: both; + background-color: #313335; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; +} +.palettelist .palette .caption { + background-color: transparent; + border-top: 1px solid rgb(67, 69, 72); + width: 100%; + height: auto; + font-size: 11px; + white-space: nowrap; +} +.palettelist .palette canvas { + display: block; + margin: 2px; +} +.palettelist .palette:hover { + background-color: #434548; + cursor: pointer; +} +.palettelist .palette:hover .caption { + color: white; +} +.palettelist .palette:nth-child(2) .caption { + border-top: none; +} +.palettelist .button { + line-height: 14px; + text-align: center; + border: 1px solid rgba(141, 142, 143, 0.5); + font-size: 12px; + user-select: none; + color: #BBBBBB; + padding: 3px 0; + margin: 4px 2px; +} +.palettelist .button:hover { + background-color: #3C3F41; + cursor: pointer; +} + +.splitpanel { + position: absolute; + top: 27px; + left: 70px; + right: 0; + bottom: 22px; +} +.splitpanel .panel { + position: absolute; + top: 0; + bottom: 0; + width: calc(50% - 4px); + border: 1px solid black; +} +.splitpanel .panel:first-of-type { + left: 0; + width: 100%; +} +.splitpanel .panel:last-of-type { + right: 0; + display: none; +} +.splitpanel .panel .toolbar { + top: 0; + right: 0; + width: unset; + height: 21px; + border: none; + border-bottom: 1px solid black; + justify-content: flex-start; + display: flex; + white-space: nowrap; +} +.splitpanel .panel .toolbar .button { + height: 20px; + width: 20px; + color: #BBBBBB; + font-size: 12px; + line-height: 20px; + border: none; + border-right: 1px solid black; + background-color: #282A2C; +} +.splitpanel .panel .toolbar .button.auto { + width: auto; + padding: 0 5px; +} +.splitpanel .panel .toolbar .button.expand { + background-image: url("../_img/fullscreen.svg"); + background-size: 60% 60%; + background-position: center center; + background-repeat: no-repeat; +} +.splitpanel .panel .toolbar .button.closepresentation { + display: none; +} +.splitpanel .panel .toolbar .button.right { + position: absolute; + right: 0; + border-left: 1px solid black; +} +.splitpanel .panel .toolbar .toolpanel .options { + display: flex; + padding: 3px 2px 0 2px; + color: #BBBBBB; + font-size: 12px; +} +.splitpanel .panel .toolbar .toolpanel .options span.tool { + padding: 0px 4px 0 10px; + color: #8f8f8f; +} +.splitpanel .panel .toolbar .toolpanel .options .optionsgroup { + display: flex; +} +.splitpanel .panel .toolbar .toolpanel .options .flex { + display: flex; +} +.splitpanel .panel .toolbar .toolpanel .options .checkbox { + font-size: 12px; + padding-right: 10px; +} +.splitpanel .panel .toolbar .toolpanel .options .checkbox label span { + padding-left: 14px; +} +.splitpanel .panel .toolbar .toolpanel .options .checkbox label span:before { + width: 10px; + height: 10px; +} +.splitpanel .panel .toolbar .toolpanel .options .checkbox.mask { + margin-left: 10px; +} +.splitpanel .panel .toolbar .toolpanel .options .checkbox.inline { + margin-left: 10px; +} +.splitpanel .panel .toolbar .toolpanel .options input[type=range] { + margin: 0 4px 0 4px; + width: 40px; + height: 6px; +} +.splitpanel .panel .toolbar .toolpanel .options input[type=range]::-webkit-slider-thumb { + margin-top: -7px; + width: 8px; + height: 14px; +} +.splitpanel .panel .toolbar .toolpanel .options input[type=range]::-webkit-slider-runnable-track { + background: #010101; + height: 3px; + margin-top: -2px; + cursor: pointer; +} +.splitpanel .panel .toolbar .toolpanel .options label { + font-size: 11px; +} +.splitpanel .panel .toolbar .toolpanel .options label.inline { + margin-left: 6px; +} +.splitpanel .panel .toolbar .toolpanel .options select { + background-color: #181818; + color: #BBBBBB; + border: 1px solid #626262; + font-size: 11px; + margin-right: 4px; +} +.splitpanel .panel .toolbar .toolpanel .options select:focus { + outline: none; +} +.splitpanel .panel .viewport, +.splitpanel .panel .windowContainer, +.splitpanel .panel .tileContainer { + position: absolute; + top: 21px; + left: 0; + right: 0; + bottom: 0; + overflow: auto; + display: flex; + touch-action: none; +} +.splitpanel .panel .viewport.hidden, +.splitpanel .panel .windowContainer.hidden, +.splitpanel .panel .tileContainer.hidden { + display: none; +} +.splitpanel .panel .windowContainer .window { + width: 300px; + height: 170px; + background-image: url("../_img/frame.png"); + position: absolute; + top: 50px; + left: 10px; +} +.splitpanel .panel .windowContainer .window:nth-child(2) { + top: 250px; +} +.splitpanel .panel .windowContainer .window:nth-child(3) { + top: 450px; +} +.splitpanel .panel .windowContainer .window canvas { + position: absolute; + top: 18px; + left: 4px; +} +.splitpanel .splitter { + position: absolute; + left: calc(50% - 4px); + margin-left: 1px; + top: 0; + bottom: 0; + width: 6px; + cursor: col-resize; + z-index: 10; + display: none; +} +.splitpanel .splitter:hover { + background-color: rgba(0, 0, 0, 0.2); +} +.splitpanel .canvaswrapper { + display: block; + margin: auto; + position: relative; +} +.splitpanel .canvascontainer { + display: block; + position: relative; + border: 1px solid black; + background-image: url("../_img/checkers.png"); +} +.splitpanel canvas { + image-rendering: optimizeSpeed; + image-rendering: optimize-contrast; + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; + -webkit-touch-callout: none; + display: block; + position: relative; + z-index: 1; +} +.splitpanel canvas.overlaycanvas { + position: absolute; + left: 0; + top: 0; + z-index: 2; + pointer-events: none; +} + +body.shift.mousedown .splitpanel .overlaycanvas { + display: none; +} + +body.withsidepanel .splitpanel { + left: 250px; +} + +body.withfilebrowser .splitpanel { + right: 154px; +} + +body.presentation .splitpanel { + left: 0; + top: 0; + bottom: 0; +} +body.presentation .splitpanel .toolbar .button { + display: none; +} +body.presentation .splitpanel .toolbar .button.closepresentation { + display: block; +} +body.presentation .splitpanel .toolbar .toolpanel { + display: none; +} + +.visualaids { + position: absolute; + top: 0; + left: 0; + z-index: 1000; + pointer-events: none; + right: 0; + bottom: 0; +} +.visualaids .grid { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; +} +.visualaids .grid .line { + position: absolute; + pointer-events: none; +} +.visualaids .grid .line.vertical { + top: 0; + bottom: 0; + border-left: 1px solid white; + width: 1px; +} +.visualaids .grid .line.horizontal { + left: 0; + right: 0; + border-top: 1px solid white; + height: 1px; +} + +.sizebox { + position: absolute; + width: 10px; + height: 10px; + /*background-color: rgba(255, 0, 0, 0.24);*/ + z-index: 100; + display: none; + cursor: move; +} +.sizebox.hot { + pointer-events: none; +} +.sizebox.active { + display: block; +} +.sizebox .sizedot:nth-of-type(1) { + cursor: nw-resize; +} +.sizebox .sizedot:nth-of-type(2) { + cursor: n-resize; +} +.sizebox .sizedot:nth-of-type(3) { + cursor: ne-resize; +} +.sizebox .sizedot:nth-of-type(4) { + cursor: e-resize; +} +.sizebox .sizedot:nth-of-type(5) { + cursor: se-resize; +} +.sizebox .sizedot:nth-of-type(6) { + cursor: s-resize; +} +.sizebox .sizedot:nth-of-type(7) { + cursor: sw-resize; +} +.sizebox .sizedot:nth-of-type(8) { + cursor: w-resize; +} +.sizebox canvas { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + z-index: 0; +} + +.selectbox { + position: absolute; + display: none; + z-index: 4; + pointer-events: none; + left: 0; + top: 0; + right: 0; + bottom: 0; + /*background-color: rgba(0, 0, 255, 0.25);*/ +} +.selectbox .border { + position: absolute; + left: -1px; + top: -1px; + right: -1px; + bottom: -1px; + display: none; +} +.selectbox .border svg { + width: 100%; + height: 100%; + display: none; +} +.selectbox .border svg .white, +.selectbox .border svg .ants { + stroke-width: 2px; +} +.selectbox .border.active { + display: block; +} +.selectbox .border.active svg { + display: block; +} +.selectbox .border.filled { + display: block; + background-color: rgba(255, 0, 0, 0.25); +} +.selectbox svg .white { + fill: none; + stroke: #FFF; + stroke-width: 1px; + vector-effect: non-scaling-stroke; +} +.selectbox svg .filled { + fill: rgba(255, 0, 0, 0.25); +} +.selectbox svg .ants { + fill: none; + stroke: #000; + stroke-width: 1px; + vector-effect: non-scaling-stroke; + stroke-dasharray: 4px; + animation: stroke 0.2s linear infinite; + /*shape-rendering: geometricPrecision;*/ + stroke-dashoffset: 8px; +} +@keyframes stroke { + to { + stroke-dashoffset: 0; + } +} +.selectbox .content { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} +.selectbox .content .dots { + position: absolute; + left: 0; + top: 0; + z-index: 11; +} +.selectbox .content .shape { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + pointer-events: none; + z-index: 10; +} +.selectbox .content .shape svg { + width: 100%; +} +.selectbox .content canvas { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + display: block; + z-index: 9; +} +.selectbox.active { + display: block; +} +.selectbox.capture { + /*background-color: rgba(0, 255, 0, 0.15);*/ + pointer-events: all; +} + +.sizedot { + position: absolute; + z-index: 2; + width: 10px; + height: 10px; + margin: -5px 0 0 -5px; + border: 1px solid white; + background-color: black; +} +.sizedot:hover { + background-color: white; +} + +.rotatedot { + position: absolute; + z-index: 1; + width: 20px; + height: 20px; + margin: -20px 0 0 -20px; + cursor: url("../_img/cursors/rotatenw.png") 0 0, auto; + display: none; +} +.rotatedot.d2 { + margin: -20px -20px 0 0; + cursor: url("../_img/cursors/rotatene.png") 0 0, auto; +} +.rotatedot.d3 { + margin: 0 -20px -20px 0; + cursor: url("../_img/cursors/rotatese.png") 0 0, auto; +} +.rotatedot.d4 { + margin: 0 0 -20px -20px; + cursor: url("../_img/cursors/rotatesw.png") 0 0, auto; +} +.rotatedot:hover { + /*background-color: green;*/ +} + +.sizebox.canrotate .rotatedot { + display: block; +} + +.cursor { + position: absolute; + width: 20px; + height: 20px; + left: 0; + top: 0; + margin-top: -18px; + margin-left: -2px; + pointer-events: none; + background-size: contain; + z-index: 1002; + display: none; +} +.cursor.rotate { + background-image: url("../_img/rotate1.svg"); + margin-top: -10px; + margin-left: -10px; +} + +body.customcursor.hoverviewport { + cursor: none; +} +body.customcursor.hoverviewport .sizebox { + cursor: none; +} +body.customcursor.hoverviewport .cursor { + display: block; +} + +body.hoverviewport.cursor-draw { + cursor: url("../_img/cursors/cross.png") 12 12, auto; +} +body.hoverviewport.cursor-colorpicker { + cursor: url("../_img/cursors/pipette.png") 0 24, auto; +} +body.hoverviewport.cursor-colorpicker canvas.overlaycanvas { + display: none; +} +body.hoverviewport.cursor-rotate { + cursor: url("../_img/cursors/rotatene.png") 0 0, auto; +} +body.hoverviewport.cursor-select { + cursor: crosshair; +} +body.hoverviewport.cursor-text { + cursor: text; +} +body.hoverviewport.cursor-pan { + cursor: grab; +} +body.hoverviewport.cursor-pan canvas.overlaycanvas { + display: none; +} + +/* cursors that also apply to main UI */ +body.cursor-drag, +body.hoverviewport.cursor-drag { + cursor: grabbing !important; +} +body.cursor-drag .cursor, +body.hoverviewport.cursor-drag .cursor { + display: none; +} + +body.hovercanvas.pointerdown.cursor-colorpicker .cursor { + display: block; +} +body.hovercanvas.pointerdown.cursor-colorpicker .cursor .mark { + position: absolute; + pointer-events: none; + display: none; + width: 60px; + height: 60px; + margin: -14px 0 0 -26px; + border: 8px solid green; + border-radius: 50%; + box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.5), inset 0 0 3px 1px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(255, 255, 255, 0.5); +} + +#dragelement { + position: absolute; + z-index: 1002; + pointer-events: none; + opacity: 0; + top: 0; + left: 0; + transition: opacity 0.3s ease-in-out; +} +#dragelement.active { + opacity: 1; +} +#dragelement .dragelement.box { + width: 120px; + padding: 4px 8px; + color: #BBBBBB; + background-color: #313335; + border: 1px solid #000000; + box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, 0.63); +} +#dragelement .dragelement.box.frame { + width: 50px; + height: 50px; + font-size: 12px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} +#dragelement .dragelement.tooltip { + background-color: #d0c5b1; + padding: 4px 8px; + border: 1px solid black; + font-size: 12px; + white-space: nowrap; + pointer-events: none; + margin: 12px 0 0 12px; +} + +.blanket { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + z-index: 1000; + display: none; +} +.blanket.active { + display: block; +} + +.modalwindow { + position: absolute; + z-index: 1001; + background-color: #313335; + width: 440px; + height: 260px; + border: 1px solid black; + top: calc(50vh - 130px); + left: 50%; + margin-left: -100px; + box-shadow: 0 0 8px 0px black; + display: none; +} +.modalwindow .caption { + color: #BBBBBB; + padding: 2px 4px; + border-bottom: 1px solid black; + position: relative; + height: 20px; + background-color: rgba(0, 0, 0, 0.2196078431); + cursor: move; + user-select: none; +} +.modalwindow .caption .button { + position: absolute; + width: 16px; + height: 16px; + line-height: 14px; + text-align: center; + border: 1px solid black; + top: 2px; + right: 2px; + font-size: 12px; + user-select: none; +} +.modalwindow .caption .button:hover { + background-color: #3C3F41; + cursor: pointer; +} +.modalwindow.active { + display: block; +} +.modalwindow .inner { + color: #BBBBBB; +} +.modalwindow .inner .button { + border: 1px solid #6a6a6a; + margin: 2px; + padding: 6px; + user-select: none; +} +.modalwindow .inner .button .title { + color: #BBBBBB; +} +.modalwindow .inner .button .subtitle { + color: #797979; +} +.modalwindow .inner .button:hover { + cursor: pointer; + border: 1px solid #8c8c8c; + background-color: #232323; +} +.modalwindow .inner .button.large { + padding: 10px; + margin: 10px; + text-align: center; +} +.modalwindow .inner .button.highlight { + border: 1px solid #ded293; + color: #ded293; +} +.modalwindow .inner h3 { + font-size: 13px; + padding: 0 10px; + margin: 10px 0; +} +.modalwindow .inner .panel.form { + padding: 0 10px; +} +.modalwindow .inner .panel.form span.label { + display: inline-block; + width: 80px; + text-align: right; + padding-right: 8px; +} +.modalwindow .inner .panel.form input[type=number] { + width: 100px; + margin-right: 8px; + margin-bottom: 4px; +} +.modalwindow .inner select { + color: #BBBBBB; + width: 100%; + border: 1px solid black; + background-color: transparent; + margin: 4px 0; +} +.modalwindow .inner select:focus { + outline: none; +} +.modalwindow .inner select option { + color: #BBBBBB; + background-color: #2B2B2B; + border-radius: 0; +} +.modalwindow .inner select.resize { + width: 100px; + padding: 2px; + position: absolute; + left: 198px; + top: 120px; +} +.modalwindow .inner .buttons { + position: absolute; + display: flex; + bottom: 0; + right: 0; + padding: 10px 8px; +} +.modalwindow .inner .buttons.relative { + position: relative; +} +.modalwindow .inner .buttons .button { + width: 150px; + text-align: center; + font-size: 12px; + line-height: 20px; + margin: 0 2px; +} +.modalwindow .inner .buttons .button.ghost { + opacity: 0.7; +} +.modalwindow .inner .about img { + width: 100%; + height: auto; +} +.modalwindow .inner .about .text { + position: absolute; + z-index: 1; + font-size: 12px; + line-height: 14px; + left: 56px; + color: #a5bac2; + opacity: 0.8; + pointer-events: none; +} +.modalwindow .inner .about .text.info { + width: 380px; + bottom: 110px; +} +.modalwindow .inner .about .text.copyright { + bottom: 82px; +} +.modalwindow .inner .about .text.github { + bottom: 55px; +} +.modalwindow .inner .about .text.nobullshit { + bottom: 10px; + opacity: 0.5; +} +.modalwindow .inner .about .text.link { + pointer-events: all; +} +.modalwindow .inner .about .text.link:hover { + color: white; + cursor: pointer; + text-decoration: underline; +} +.modalwindow .inner .about .text.version { + left: unset; + right: 66px; + bottom: 55px; +} +.modalwindow .inner .about .text.contrib { + bottom: 10px; + text-align: right; + left: unset; + right: 66px; + opacity: 0.5; +} +.modalwindow .inner .about .text.contrib i { + opacity: 0.8; + display: block; + font-style: normal; +} +.modalwindow .inner .optiondialog p { + margin: 0; + padding: 10px; +} +.modalwindow .inner .lock { + position: absolute; + height: 33px; + width: 13px; + border: 1px solid transparent; + border-left: none; + top: 46px; + left: 246px; + opacity: 0.7; +} +.modalwindow .inner .lock .link { + position: absolute; + width: 16px; + height: 16px; + top: 50%; + right: -8px; + margin-top: -8px; + background-color: #313335; + background-image: url("../_img/link.svg"); + background-size: contain; + background-repeat: no-repeat; + opacity: 0.7; +} +.modalwindow .inner .lock .link:hover { + opacity: 1; + cursor: pointer; +} +.modalwindow .inner .lock.active { + border: 1px solid #8f8f8f; + border-left: none; + opacity: 1; +} +.modalwindow .inner .anchor { + position: absolute; + border: 1px solid black; + left: 290px; + top: 56px; + width: 92px; + height: 92px; + background-color: #533030; + display: flex; + flex-wrap: wrap; +} +.modalwindow .inner .anchor h3 { + position: absolute; + left: -11px; + top: -37px; +} +.modalwindow .inner .anchor .page { + position: absolute; + width: 60px; + height: 60px; + border: 1px solid black; + background-color: #47494d; + left: 15px; + top: 15px; + z-index: 1; + transition: left 0.2s ease-in-out, top 0.2s ease-in-out; +} +.modalwindow .inner .anchor .hotspot { + width: 30px; + height: 30px; + position: relative; + z-index: 2; +} +.modalwindow .inner .anchor .hotspot:hover { + background-color: rgba(255, 255, 255, 0.13); + cursor: pointer; +} +.modalwindow .inner .anchor .arrow { + position: absolute; + width: 16px; + height: 16px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; + margin: -8px 0 0 -8px; + opacity: 0.6; + pointer-events: none; + transition: all 0.2s ease-in-out; +} +.modalwindow .inner .anchor .arrow.top { + left: 50%; + top: 8px; + transform: rotate(270deg); +} +.modalwindow .inner .anchor .arrow.right { + right: 0; + top: 50%; +} +.modalwindow .inner .anchor .arrow.bottom { + left: 50%; + bottom: 0; + transform: rotate(90deg); +} +.modalwindow .inner .anchor .arrow.left { + left: 8px; + top: 50%; + transform: rotate(180deg); +} +.modalwindow .inner .anchor.top .page { + top: -1px; +} +.modalwindow .inner .anchor.top .arrow.bottom { + bottom: 10px; +} +.modalwindow .inner .anchor.left .page { + left: -1px; +} +.modalwindow .inner .anchor.left .arrow.right { + right: 10px; +} +.modalwindow .inner .anchor.right .page { + left: 31px; +} +.modalwindow .inner .anchor.right .arrow.left { + left: 18px; +} +.modalwindow .inner .anchor.bottom .page { + top: 31px; +} +.modalwindow .inner .anchor.bottom .arrow.top { + top: 18px; +} +.modalwindow .inner .quick { + position: absolute; + top: 123px; + left: 88px; + font-size: 12px; +} +.modalwindow .inner .quick .button.calc { + display: inline-block; + width: 22px; + padding: 2px; + text-align: center; +} +.modalwindow .inner .textlink { + text-decoration: underline; + color: #BBBBBB; +} +.modalwindow .inner .textlink:hover { + color: white; + cursor: pointer; +} +.modalwindow .inner.full { + position: absolute; + left: 0; + top: 20px; + right: 0; + bottom: 0; +} +.modalwindow .inner .palette { + position: relative; + height: 281px; + margin-top: 1px; +} +.modalwindow .inner .palette.withactions .palettepanel .caption:after { + transform: rotate(180deg); +} +.modalwindow .inner .palette.withactions .mainpanel { + left: 201px; +} +.modalwindow .inner .palette.withactions .mainpanel .sliders { + padding-left: 10px; + padding-right: 10px; +} +.modalwindow .inner .palette.withactions .mainpanel .sliders input.hex { + right: 10px; + width: 80px; +} +.modalwindow .inner .palette.withactions .mainpanel .sliders .pixelcount { + left: 75px; +} +.modalwindow .inner .palette.withactions .mainpanel .sliders .slider input.slider { + width: 110px; + margin: 4px 0 0 8px; +} +.modalwindow .inner .palette.withactions .mainpanel .sliders .slider span.label { + right: 43px; + font-size: 12px; + width: 40px; +} +.modalwindow .inner .palette.withactions .mainpanel .buttons { + right: 0; +} +.modalwindow .inner .palette.withactions .mainpanel .options { + padding-left: 10px; +} +.modalwindow .inner .palette.withactions .actions { + display: block; +} +.modalwindow .inner .palette .caption.sub { + cursor: default; + font-size: 12px; + line-height: 17px; +} +.modalwindow .inner .palette .palettepanel { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 121px; + border-right: 1px solid rgba(0, 0, 0, 0.58); + background-color: #292b2c; +} +.modalwindow .inner .palette .palettepanel .caption:after { + content: ""; + position: absolute; + width: 16px; + height: 18px; + right: 2px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + opacity: 0.5; + transition: transform 0.3s ease-in-out; +} +.modalwindow .inner .palette .palettepanel .caption:hover { + cursor: pointer; +} +.modalwindow .inner .palette .palettepanel .caption:hover:after { + opacity: 1; +} +.modalwindow .inner .palette .palettepanel canvas { + display: block; + margin: 0; +} +.modalwindow .inner .palette .palettepanel .highlight { + position: absolute; + width: 30px; + height: 30px; + margin: 21px 0 0 1px; + border: 3px solid black; + pointer-events: none; +} +.modalwindow .inner .palette .palettepanel .highlight:before { + content: ""; + position: absolute; + right: -1px; + bottom: -1px; + left: -4px; + top: -4px; + border: 2px solid white; +} +.modalwindow .inner .palette .palettepanel .nav { + border-top: 1px solid black; + width: 120px; + height: 20px; + background-color: #313335; + display: flex; + color: #BBBBBB; +} +.modalwindow .inner .palette .palettepanel .nav .prev, .modalwindow .inner .palette .palettepanel .nav .next { + width: 40px; + background-image: url(../_img/caret.svg); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + opacity: 0.1; + pointer-events: none; +} +.modalwindow .inner .palette .palettepanel .nav .prev.active, .modalwindow .inner .palette .palettepanel .nav .next.active { + opacity: 0.5; + pointer-events: all; +} +.modalwindow .inner .palette .palettepanel .nav .prev:hover, .modalwindow .inner .palette .palettepanel .nav .next:hover { + opacity: 1; + cursor: pointer; +} +.modalwindow .inner .palette .palettepanel .nav .prev { + transform: rotate(180deg); +} +.modalwindow .inner .palette .palettepanel .nav .page { + width: 40px; + text-align: center; +} +.modalwindow .inner .palette .actions { + position: absolute; + left: 121px; + width: 80px; + right: 0; + top: 0; + bottom: 0; + border-right: 1px solid rgba(0, 0, 0, 0.58); + display: none; +} +.modalwindow .inner .palette .actions .caption:after { + content: "x"; + text-align: center; + position: absolute; + width: 16px; + height: 18px; + right: 2px; + opacity: 0.5; + transition: transform 0.3s ease-in-out; +} +.modalwindow .inner .palette .actions .caption:hover { + cursor: pointer; +} +.modalwindow .inner .palette .actions .caption:hover:after { + opacity: 1; +} +.modalwindow .inner .palette .actions .spacer { + height: 20px; +} +.modalwindow .inner .palette .actions .nav { + position: absolute; + bottom: 0; + left: 0; + right: 0; + display: flex; + border-top: 1px solid black; +} +.modalwindow .inner .palette .actions .nav div { + width: 100%; + height: 20px; + background-image: url("../_img/zoom.svg"); + background-size: 14px 14px; + background-repeat: no-repeat; + background-position: center center; + opacity: 0.5; +} +.modalwindow .inner .palette .actions .nav div:hover { + cursor: pointer; + opacity: 1; +} +.modalwindow .inner .palette .actions .nav div.zoomout { + background-image: url("../_img/zoomout.svg"); + border-left: 1px solid black; +} +.modalwindow .inner .palette .mainpanel { + position: absolute; + left: 121px; + right: 0; + top: 0; + bottom: 0; +} +.modalwindow .inner .palette .mainpanel .tabs { + display: flex; + width: 100%; +} +.modalwindow .inner .palette .mainpanel .tabs .caption.sub { + width: 100%; + padding-left: 10px; + pointer-events: none; +} +.modalwindow .inner .palette .mainpanel .tabs .caption.sub.inactive { + background-color: #313335; + opacity: 0.5; + pointer-events: all; +} +.modalwindow .inner .palette .mainpanel .tabs .caption.sub.inactive:hover { + cursor: pointer; + opacity: 1; +} +.modalwindow .inner .palette .mainpanel .colorpanel { + position: absolute; + left: 0; + right: 0; + top: 20px; + bottom: 0; +} +.modalwindow .inner .palette .mainpanel .rangepanel { + position: absolute; + left: 0; + right: 0; + top: 20px; + bottom: 0; + display: none; +} +.modalwindow .inner .palette .mainpanel .rangepanel .norange { + padding: 20px; + opacity: 0.5; +} +.modalwindow .inner .palette .mainpanel .rangepanel .bottom { + position: absolute; + bottom: 0; + padding: 4px; + display: flex; +} +.modalwindow .inner .palette .mainpanel .rangepanel .bottom .button.small { + position: relative; + width: auto; +} +.modalwindow .inner .palette .mainpanel .rangepanel .bottom .button.small.active { + border: 1px solid #b1e00f; + color: #b1e00f; + box-shadow: 0 0 2px yellow; +} +.modalwindow .inner .palette .mainpanel .rangepanel .captions { + display: flex; + font-size: 11px; + border-bottom: 1px solid black; + background-color: #262829; + color: #808080; +} +.modalwindow .inner .palette .mainpanel .rangepanel .captions div { + padding: 4px; + border-left: 1px solid #313335; +} +.modalwindow .inner .palette .mainpanel .rangepanel .captions div.a { + padding: 4px 2px; + border-left: none; +} +.modalwindow .inner .palette .mainpanel .rangepanel .captions div.b { + width: 100px; +} +.modalwindow .inner .palette .mainpanel .rangepanel .ranges { + position: absolute; + left: 0; + right: 0; + top: 20px; + bottom: 30px; + overflow: auto; +} +.modalwindow .inner .palette .mainpanel .rangepanel .range { + padding: 4px 4px 4px 8px; + overflow: auto; + overflow-y: hidden; + border-bottom: 1px solid black; + border-top: 1px solid #464545; + display: flex; + align-items: center; +} +.modalwindow .inner .palette .mainpanel .rangepanel .range.active { + background-color: #3e4144; +} +.modalwindow .inner .palette .mainpanel .rangepanel .range .checkbox { + display: block; +} +.modalwindow .inner .palette .mainpanel .rangepanel .range .slider { + display: block; + width: 60px; + margin: 0 4px; +} +.modalwindow .inner .palette .mainpanel .rangepanel .range .speed { + width: 40px; + margin: 0 4px 0 0; +} +.modalwindow .inner .palette .mainpanel .rangepanel .range canvas { + display: block; + cursor: pointer; +} +.modalwindow .inner .palette .mainpanel .rangepanel .range canvas.active { + box-shadow: 0 0 5px 2px #b1e00f; +} +.modalwindow .inner .palette .mainpanel .sliders { + position: absolute; + left: 0; + top: 10px; + right: 0; + padding: 0 20px; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs { + display: block; + margin-top: 10px; + position: relative; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab { + position: absolute; + left: 0; + font-size: 11px; + height: 50px; + width: 18px; + text-align: center; + top: 53px; + background-color: #313335; + pointer-events: none; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab:before, .modalwindow .inner .palette .mainpanel .sliders .tabs .tab:after { + content: ""; + position: absolute; + left: 0; + right: 1px; + border-top: 1px solid #6A6A6A; + transform-origin: top right; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab:before { + top: 0; + transform: rotate(-15deg); +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab:after { + bottom: -1px; + transform: rotate(15deg); +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab span { + display: block; + border-left: 1px solid #6A6A6A; + margin: 5px 0; + width: 14px; + padding: 2px 0 2px 4px; + height: 41px; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab:first-child { + top: 0; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab:first-child:before { + right: 0; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab.inactive { + opacity: 0.4; + margin-left: 1px; + transition: all 0.1s ease-in-out; + pointer-events: all; + background-color: transparent; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .tab.inactive:hover { + cursor: pointer; + opacity: 1; + margin-left: -1px; +} +.modalwindow .inner .palette .mainpanel .sliders .tabs .panel { + border: 1px solid #6A6A6A; + margin-left: 16px; +} +.modalwindow .inner .palette .mainpanel .sliders .slider { + position: relative; + margin: 8px 0; +} +.modalwindow .inner .palette .mainpanel .sliders .slider span.label { + position: absolute; + right: 49px; + text-align: left; + text-transform: capitalize; + font-size: 13px; + margin-top: 5px; + width: 45px; +} +.modalwindow .inner .palette .mainpanel .sliders .slider input.slider { + width: 148px; + margin: 4px 0 0 12px; +} +.modalwindow .inner .palette .mainpanel .sliders .slider input.slider.red::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #000000 0%, #c40202 100%); + border: none; +} +.modalwindow .inner .palette .mainpanel .sliders .slider input.slider.red.hsv::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #FF3A00 0%, #F0FF00 20%, #00FF1D 40%, #0007FF 60%, #FF00F1 80%, #FA0505 100%); +} +.modalwindow .inner .palette .mainpanel .sliders .slider input.slider.green::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #000000 0%, #00a402 100%); + border: none; +} +.modalwindow .inner .palette .mainpanel .sliders .slider input.slider.green.hsv::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #000000 0%, #c40202 100%); +} +.modalwindow .inner .palette .mainpanel .sliders .slider input.slider.blue::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #000000 0%, #085db9 100%); + border: none; +} +.modalwindow .inner .palette .mainpanel .sliders .slider input.slider.blue.hsv::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #000000 0%, #FFFFFF 100%); +} +.modalwindow .inner .palette .mainpanel .sliders .slider input.rangevalue { + position: absolute; + right: 4px; + width: 40px; +} +.modalwindow .inner .palette .mainpanel .sliders input.hex { + width: 100px; + position: absolute; + right: 20px; +} +.modalwindow .inner .palette .mainpanel .sliders input.masked { + position: absolute; + opacity: 0; + pointer-events: none; + left: 0; + top: 0; + height: 30px; +} +.modalwindow .inner .palette .mainpanel .sliders .pixelcount { + position: absolute; + left: 86px; + top: 1px; + font-size: 12px; +} +.modalwindow .inner .palette .mainpanel .button.small { + position: absolute; + width: 50px; +} +.modalwindow .inner .palette .mainpanel .button.small.revert { + right: 60px; +} +.modalwindow .inner .palette .mainpanel .button.small.apply { + right: 8px; +} +.modalwindow .inner .palette .buttons { + position: absolute; + left: 0; + bottom: 72px; + right: 10px; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease-in-out; +} +.modalwindow .inner .palette .buttons.active { + opacity: 1; + pointer-events: all; +} +.modalwindow .inner .palette .depthinfo { + position: absolute; + bottom: 62px; + left: 40px; + color: #c99721; + padding: 3px 3px 3px 18px; + font-size: 11px; + display: none; + background-image: url("../_img/warning.svg"); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: 0 0; +} +.modalwindow .inner .palette .depthinfo.active { + display: block; +} +.modalwindow .inner .palette .button.small { + line-height: 14px; + font-size: 12px; + padding: 2px 4px; + text-align: center; +} +.modalwindow .inner .palette .button.small .contextmenu { + position: absolute; +} +.modalwindow .inner .palette .button.small .contextmenu .item { + padding: 2px 4px; + background-color: #313335; + border: 1px solid black; + border-radius: 2px; + font-size: 12px; + margin: 2px 0; + width: 100px; + text-align: left; +} +.modalwindow .inner .palette .button.small .contextmenu .item:hover { + cursor: pointer; + background-color: #3C3F41; +} +.modalwindow .inner .palette .options { + position: absolute; + left: 0; + bottom: 0; + right: 0; + padding: 5px 10px 5px 20px; + border-top: 1px solid black; +} +.modalwindow .inner .palette .options .checkbox { + padding: 3px 0; + position: relative; + font-size: 12px; +} +.modalwindow .inner .effects { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} +.modalwindow .inner .effects .tabs { + position: absolute; + left: 5px; + width: 220px; + border-bottom: 1px solid black; + height: 36px; + overflow: hidden; +} +.modalwindow .inner .effects .tabs .tab { + position: absolute; + width: 90px; + text-align: center; + line-height: 24px; + border: 1px solid black; + height: 24px; + bottom: -3px; + left: 5px; + opacity: 0.5; + transition: all 0.3s ease-in-out; + user-select: none; +} +.modalwindow .inner .effects .tabs .tab:nth-of-type(2) { + left: 100px; +} +.modalwindow .inner .effects .tabs .tab:hover, .modalwindow .inner .effects .tabs .tab.active { + cursor: pointer; + background-color: rgba(255, 255, 255, 0.2); + bottom: 0; + opacity: 1; +} +.modalwindow .inner .effects .sliders { + position: absolute; + left: 10px; + top: 40px; + display: none; +} +.modalwindow .inner .effects .sliders.active { + display: block; +} +.modalwindow .inner .effects .slider { + position: relative; + margin: 4px 0; +} +.modalwindow .inner .effects .slider label { + display: block; + font-size: 12px; + text-transform: capitalize; +} +.modalwindow .inner .effects .slider input[type=range] { + width: 200px; + position: relative; + z-index: 10; + margin-top: -2px; +} +.modalwindow .inner .effects .slider input[type=range]::-webkit-slider-runnable-track { + background: linear-gradient(90deg, rgb(0, 0, 0) 0%, rgb(200, 200, 200) 100%); + border: 1px solid #010101; + width: 100%; + height: 4px; + cursor: pointer; +} +.modalwindow .inner .effects .slider input[type=range]::-moz-range-track { + background: linear-gradient(90deg, rgb(0, 0, 0) 0%, rgb(200, 200, 200) 100%); + border: 1px solid #010101; + width: 100%; + height: 2px; + cursor: pointer; +} +.modalwindow .inner .effects .slider input[type=range].saturation::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #7F7F7F 0%, #F50A0A 100%); +} +.modalwindow .inner .effects .slider input[type=range].saturation::-moz-range-track { + background: linear-gradient(90deg, #7F7F7F 0%, #F50A0A 100%); +} +.modalwindow .inner .effects .slider input[type=range].contrast::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #7F7F7F 0%, #000000 100%); +} +.modalwindow .inner .effects .slider input[type=range].contrast::-moz-range-track { + background: linear-gradient(90deg, #7F7F7F 0%, #000000 100%); +} +.modalwindow .inner .effects .slider input[type=range].hue::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #FF3A00 0%, #F0FF00 20%, #00FF1D 40%, #0007FF 60%, #FF00F1 80%, #FA0505 100%); +} +.modalwindow .inner .effects .slider input[type=range].hue::-moz-range-track { + background: linear-gradient(90deg, #FF3A00 0%, #F0FF00 20%, #00FF1D 40%, #0007FF 60%, #FF00F1 80%, #FA0505 100%); +} +.modalwindow .inner .effects .slider input[type=range].sepia::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #313335 0%, #8A4F38 100%); +} +.modalwindow .inner .effects .slider input[type=range].sepia::-moz-range-track { + background: linear-gradient(90deg, #313335 0%, #8A4F38 100%); +} +.modalwindow .inner .effects .slider input[type=range].cyan::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #0AF5F5 0%, #F50A0A 100%); +} +.modalwindow .inner .effects .slider input[type=range].cyan::-moz-range-track { + background: linear-gradient(90deg, #0AF5F5 0%, #F50A0A 100%); +} +.modalwindow .inner .effects .slider input[type=range].magenta::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #F50AF5 0%, #0AF50A 100%); +} +.modalwindow .inner .effects .slider input[type=range].magenta::-moz-range-track { + background: linear-gradient(90deg, #F50AF5 0%, #0AF50A 100%); +} +.modalwindow .inner .effects .slider input[type=range].yellow::-webkit-slider-runnable-track { + background: linear-gradient(90deg, #F5F50A 0%, #0A0AF5 100%); +} +.modalwindow .inner .effects .slider input[type=range].yellow::-moz-range-track { + background: linear-gradient(90deg, #F5F50A 0%, #0A0AF5 100%); +} +.modalwindow .inner .effects .slider input[type=text] { + position: absolute; + width: 40px; + right: 0; + top: 0; + font-size: 12px; + padding: 2px 4px; +} +.modalwindow .inner .effects .alchemy { + position: absolute; + left: 10px; + top: 40px; + width: 200px; + display: none; +} +.modalwindow .inner .effects .alchemy.active { + display: block; +} +.modalwindow .inner .effects .alchemy .recipe { + padding: 6px 6px 6px 24px; + border-bottom: 1px solid black; + background-image: url("../_img/flask.svg"); + background-repeat: no-repeat; + background-position: 2px center; + background-size: 16px 16px; + font-size: 12px; +} +.modalwindow .inner .effects .alchemy .recipe:hover { + cursor: pointer; + background-color: #3C3F41; +} +.modalwindow .inner .effects .code { + position: absolute; + left: 10px; + top: 250px; + right: 10px; +} +.modalwindow .inner .effects .code code { + position: absolute; + left: 0; + right: 0; + top: 0; + height: 170px; + overflow: auto; + white-space: pre; + font-family: monospace; + display: block; + border: 1px solid black; + background-color: #1c1c1c; + color: #8f8f8f; +} +.modalwindow .inner .effects .code code:focus { + outline: none; +} +.modalwindow .inner .effects .code code .string { + color: #A1E46D; +} +.modalwindow .inner .effects .code code .number { + color: #4176BA; +} +.modalwindow .inner .effects .code code .html { + color: #E4D95F; +} +.modalwindow .inner .effects .code code .reserved { + color: #C87531; +} +.modalwindow .inner .effects .code code .js { + color: #6DE4D1; +} +.modalwindow .inner .effects .code code .globals { + color: #ab66b7; +} +.modalwindow .inner .effects .code code .comment { + color: #aaa; +} +.modalwindow .inner .effects .code code .method { + color: #F9C26B; +} +.modalwindow .inner .effects .code code .source { + color: #64b969; +} +.modalwindow .inner .effects .code code .target { + color: #64b9a9; +} +.modalwindow .inner .effects .code .button { + position: absolute; + left: 0; + top: 176px; + user-select: none; +} +.modalwindow .inner .effects .code .params { + position: absolute; + background-color: #313335; + z-index: 1; + right: 0; + top: -242px; + width: 256px; + height: 206px; +} +.modalwindow .inner .effects .code .params h3 { + padding-left: 0; +} +.modalwindow .inner .effects .code .params .slider { + margin-left: 20px; + margin-right: 36px; +} +.modalwindow .inner .effects .code .params .slider label, +.modalwindow .inner .effects .code .params .slider input[type=text] { + font-size: 11px; +} +.modalwindow .inner .effects .code .params .slider input[type=text] { + padding: 1px 2px; +} +.modalwindow .inner .effects .code .params .slider input[type=range]::-webkit-slider-thumb { + height: 12px; + margin-top: -5px; +} +.modalwindow .inner .effects .code .params .slider input[type=range]::-ms-thumb { + height: 12px; +} +.modalwindow .inner .effects .code .params .slider input[type=range]::-moz-range-thumb { + height: 10px; +} +.modalwindow .inner .effects .code .params.columns .slider { + margin-left: 10px; + margin-right: 0; + width: 110px; + display: inline-block; +} +.modalwindow .inner .effects .code .params.columns .slider.main { + width: 230px; +} +.modalwindow .inner .effects .code .params.columns .slider.main input[type=range] { + width: 230px; +} +.modalwindow .inner .effects .code .params.columns .slider input[type=range] { + width: 110px; +} +.modalwindow .inner .effects .code .params.columns .slider input[type=text] { + width: 30px; +} +.modalwindow .inner .effects .previewpanel { + position: absolute; + left: 240px; + top: 12px; +} +.modalwindow .inner .effects .previewpanel .preview { + width: 200px; + height: 200px; + border: 1px solid black; + margin-bottom: 10px; +} +.modalwindow .inner .effects .buttons { + left: 0; + right: 0; + justify-content: flex-end; +} +.modalwindow .inner .effects .buttons .button.left { + position: absolute; + left: 10px; +} +.modalwindow .inner .ditheredit .preset { + display: inline-block; + width: 50px; + height: 50px; + border: 1px solid black; + background-size: 50% 50%; + background-color: #6b6b6b; + image-rendering: optimizeSpeed; + image-rendering: optimize-contrast; + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; +} +.modalwindow .inner .ditheredit .preset:hover { + border: 1px solid #b1e00f; + background-color: #bbbbbb; + cursor: pointer; +} +.modalwindow .inner .ditheredit .preset.p0 { + background-image: url("../_img/patterns/dots.png"); +} +.modalwindow .inner .ditheredit .preset.p1 { + background-image: url("../_img/patterns/cross.png"); +} +.modalwindow .inner .ditheredit .preset.p2 { + background-image: url("../_img/patterns/grid.png"); +} +.modalwindow .inner .ditheredit .preset.p3 { + background-image: url("../_img/patterns/cross2.png"); +} +.modalwindow .inner .ditheredit .preset.p4 { + background-image: url("../_img/patterns/lines_hor.png"); +} +.modalwindow .inner .ditheredit .preset.p5 { + background-image: url("../_img/patterns/lines_ver.png"); +} +.modalwindow .inner .ditheredit .preset.p6 { + background-image: url("../_img/patterns/lines_diag.png"); +} +.modalwindow .inner .ditheredit .preset.p7 { + background-image: url("../_img/patterns/longgrid.png"); +} +.modalwindow .inner .ditheredit .preset.user canvas { + width: 100%; + height: 100%; +} +.modalwindow .inner .ditheredit .editpreset, +.modalwindow .inner .ditheredit .previewpreset, +.modalwindow .inner .ditheredit .presets { + position: absolute; + left: 5px; + top: 5px; + bottom: 5px; + width: 202px; + border: 1px solid black; +} +.modalwindow .inner .ditheredit .editpreset { + left: 210px; + width: 242px; +} +.modalwindow .inner .ditheredit .previewpreset { + left: 455px; +} +.modalwindow .inner .ditheredit .previewpreset canvas { + background-image: url("../_img/patterns/gradient.png"); + background-size: 100% 100%; +} +.modalwindow .inner .ditheredit h2 { + font-size: 12px; + padding: 4px; + border-bottom: 1px solid black; + font-weight: normal; + margin: 0; +} +.modalwindow .inner .ditheredit .subtoolbar { + border-top: 1px solid black; + text-align: right; + font-size: 12px; + position: relative; + padding: 0; +} +.modalwindow .inner .ditheredit .subtoolbar .button { + display: inline-block; + padding: 4px 9px; + text-align: center; + white-space: nowrap; +} +.modalwindow .inner .ditheredit .subtoolbar .button.small { + width: 24px; + padding: 4px; +} +.modalwindow .inner .ditheredit .subtoolbar .button.active { + border-color: #b2970e; + color: #b2970e; + background-color: #413e28; +} +.modalwindow .inner .ditheredit .subtoolbar .button.hidden { + display: none; +} +.modalwindow .inner .ditheredit .subtoolbar .button.left { + position: absolute; + left: 0; + max-width: 49%; +} +.modalwindow .inner .saveform { + padding: 10px; +} +.modalwindow .inner .saveform h4 { + padding: 0; + margin: 0 0 4px 0; + font-weight: normal; +} +.modalwindow .inner .saveform h4.amiga, .modalwindow .inner .saveform h4.general { + background-image: url("../_img/amigatick.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: left center; + padding-left: 20px; +} +.modalwindow .inner .saveform h4.general { + background-image: url("../_img/image.svg"); + padding-left: 22px; +} +.modalwindow .inner .saveform input { + width: 100%; + margin-bottom: 10px; +} +.modalwindow .inner .saveform .platform { + display: inline-block; + width: 50%; + vertical-align: top; +} +.modalwindow .inner .saveform .platform .button { + border: none; + position: relative; + padding: 5px 5px 5px 40px; +} +.modalwindow .inner .saveform .platform .button .icon { + width: 32px; + height: 32px; + left: 0; + position: absolute; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} +.modalwindow .inner .saveform .platform .button .icon.png { + background-image: url("../_img/png.svg"); +} +.modalwindow .inner .saveform .platform .button .icon.json { + background-image: url("../_img/json.svg"); +} +.modalwindow .inner .saveform .platform .button .icon.psd { + background-image: url("../_img/psd.svg"); +} +.modalwindow .inner .saveform .platform .button .icon.mui { + background-image: url("../_img/mui.png"); +} +.modalwindow .inner .saveform .platform .button .icon.os3 { + background-image: url("../_img/os3.png"); +} +.modalwindow .inner .saveform .platform .button .icon.os4 { + background-image: url("../_img/os4.png"); +} +.modalwindow .inner .saveform .platform .button .icon.iff { + background-image: url("../_img/iff.png"); +} +.modalwindow .inner .saveform .platform .button .icon.adf { + background-image: url("../_img/floppy.png"); +} +.modalwindow .inner .saveform .platform .button .info { + background-color: #4d5052; + position: absolute; + padding: 4px 4px 4px 8px; + border: 1px solid black; + top: 0; + left: 140px; + z-index: 10; + width: 200px; + opacity: 0; + pointer-events: none; + min-height: 40px; + transition: opacity 0.2s ease-in-out, left 0.2s ease-in-out; +} +.modalwindow .inner .saveform .platform .button .info:before { + content: ""; + position: absolute; + top: 10px; + left: -21px; + border: 10px solid transparent; + border-right: 10px solid black; +} +.modalwindow .inner .saveform .platform .button .info:after { + content: ""; + position: absolute; + top: 10px; + left: -20px; + border: 10px solid transparent; + border-right: 10px solid #4d5052; +} +.modalwindow .inner .saveform .platform .button:hover .info { + opacity: 1; + left: 160px; +} +.modalwindow .inner .saveform .platform .button.more { + position: absolute; + bottom: 10px; + min-width: 60px; + padding: 5px 8px; +} +.modalwindow .inner .saveform .platform .button.more:after { + content: ""; + position: absolute; + right: 2px; + width: 16px; + height: 17px; + background-image: url("../_img/caret.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + opacity: 0.5; +} +.modalwindow .inner .saveform .platform.amiga .button .info { + left: unset; + right: 140px; + transition: opacity 0.2s ease-in-out, right 0.2s ease-in-out; +} +.modalwindow .inner .saveform .platform.amiga .button .info:before { + left: unset; + right: -21px; + transform: rotate(180deg); +} +.modalwindow .inner .saveform .platform.amiga .button .info:after { + left: unset; + right: -20px; + transform: rotate(180deg); +} +.modalwindow .inner .saveform .platform.amiga .button:hover .info { + right: 160px; +} +.modalwindow .inner .saveform .moremenu { + z-index: 10; + position: absolute; + top: 20px; + bottom: 0; + right: 0; + width: 220px; + background-color: #2B2B2B; + border-left: 1px solid black; + display: none; +} +.modalwindow .inner .saveform .moremenu .item { + padding: 5px 5px 5px 44px; + border-bottom: 1px solid black; + position: relative; +} +.modalwindow .inner .saveform .moremenu .item .subtitle { + font-size: 13px; + padding: 4px 0; + color: #797979; +} +.modalwindow .inner .saveform .moremenu .item:hover { + background-color: #232323; + cursor: pointer; +} +.modalwindow .inner .saveform .moremenu .item:hover .info { + opacity: 1; + right: 220px; +} +.modalwindow .inner .saveform .moremenu .item:after { + content: ""; + position: absolute; + left: 2px; + top: 2px; + width: 34px; + height: 34px; + background-image: url("../_img/layers.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} +.modalwindow .inner .saveform .moremenu .item.mask:after { + background-image: url("../_img/layers_mask.svg"); +} +.modalwindow .inner .saveform .moremenu .item.index:after { + background-image: url("../_img/pixelgrid.svg"); +} +.modalwindow .inner .saveform .moremenu .item.loading:after { + display: none; +} +.modalwindow .inner .saveform .moremenu .item .spinner { + top: 8px; + left: 30px; + width: 32px; + height: 32px; +} +.modalwindow .inner .saveform .moremenu .item .info { + background-color: #232323; + position: absolute; + padding: 4px 4px 4px 8px; + border: 1px solid black; + top: 0px; + z-index: 10; + width: 170px; + opacity: 0; + pointer-events: none; + min-height: 40px; + transition: opacity 0.2s ease-in-out, right 0.2s ease-in-out; + right: 120px; + box-shadow: 0 0 20px 0px rgba(0, 0, 0, 0.5098039216); +} +.modalwindow .inner .saveform .moremenu .item .info:before { + content: ""; + position: absolute; + top: 10px; + border: 10px solid transparent; + border-right: 10px solid black; + right: -21px; + transform: rotate(180deg); +} +.modalwindow .inner .saveform .moremenu .item .info:after { + content: ""; + position: absolute; + top: 10px; + right: -20px; + transform: rotate(180deg); + border: 10px solid transparent; + border-right: 10px solid #232323; +} +.modalwindow .inner .saveform.hasmore .moremenu { + display: block; +} +.modalwindow .inner .saveform.hasmore .button.more:after { + transform: rotate(180deg); +} +.modalwindow .inner .saveform.hasmore input { + width: calc(100% - 220px); +} +.modalwindow .inner .saveoverlay { + position: absolute; + top: 20px; + left: 1px; + right: 1px; + bottom: 1px; + background-color: #3D3F41; + z-index: 10; +} +.modalwindow .inner .saveoverlay .info { + text-align: center; + padding: 50px 10px 10px 10px; +} +.modalwindow .inner .saveoverlay .info b { + font-size: 20px; + display: block; + padding: 20px 0; +} +.modalwindow .inner .saveoverlay .spinner { + display: none; +} +.modalwindow .inner .saveoverlay.loading .info { + display: none; +} +.modalwindow .inner .saveoverlay.loading .buttons { + pointer-events: none; + opacity: 0.5; +} +.modalwindow .inner .saveoverlay.loading .spinner { + display: block; +} + +.notificationbox { + position: fixed; + left: 20px; + bottom: 40px; + z-index: 1000; + width: 250px; + height: 80px; + background-color: #1a1a1a; + color: #BBBBBB; + border: 1px solid #3b3b3b; + font-size: 13px; + margin-left: -200px; + opacity: 0; + transition: all 0.2s ease-in-out; + pointer-events: none; +} +.notificationbox .title { + background-color: #101010; + border-bottom: 1px solid #3b3b3b; + padding: 4px 10px; +} +.notificationbox .text { + padding: 4px 10px; +} +.notificationbox.active { + display: block; + opacity: 1; + margin-left: 0; + pointer-events: all; +} + +.filebrowser { + position: absolute; + right: 0; + top: 27px; + width: 150px; + bottom: 22px; + background-color: #313335; + border: 1px solid black; + display: none; + overflow-y: auto; + overflow-x: hidden; +} +.filebrowser.active { + display: block; +} +.filebrowser .caption { + color: #BBBBBB; + padding: 4px 5px 2px 20px; + font-size: 12px; + height: 21px; + border-bottom: 1px solid black; +} +.filebrowser .caption .close { + position: absolute; + height: 20px; + width: 20px; + line-height: 20px; + left: 0; + top: 0; + text-align: center; + cursor: pointer; +} +.filebrowser .disk { + padding: 2px 2px 2px 21px; + border-bottom: 1px solid black; + color: #BBBBBB; + font-size: 11px; + line-height: 20px; + background-image: url("../_img/disk.svg"); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: 2px center; + position: relative; +} +.filebrowser .disk .download { + position: absolute; + height: 16px; + width: 16px; + right: 2px; + top: 2px; + cursor: pointer; + opacity: 0.7; + background-image: url("../_img/download.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} +.filebrowser .disk .download:hover { + opacity: 1; +} +.filebrowser .listitem { + color: #BBBBBB; + padding: 2px 2px 2px 21px; + border-bottom: 1px solid black; + position: relative; +} +.filebrowser .listitem.folder { + background-image: url("../_img/folder.svg"); + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: 2px center; +} +.filebrowser .listitem.file { + opacity: 0.5; +} +.filebrowser .listitem.file:before { + content: ""; + position: absolute; + left: 5px; + top: 5px; + width: 10px; + height: 10px; + border-radius: 5px; + background-color: grey; +} +.filebrowser .listitem.file.image { + opacity: 1; +} +.filebrowser .listitem.file.image:before { + background-color: #71b471; +} +.filebrowser .listitem:hover { + cursor: pointer; + background-color: #3C3F41; + color: white; +} + +.gallery .item { + margin: auto; + width: 120px; + font-size: 11px; + color: #BBBBBB; + margin-top: 10px; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.7); + transition: transform 0.2s, color 0.2s; +} +.gallery .item:hover { + cursor: pointer; + transform: scale(1.05); + color: #DDDDDD; +} +.gallery .item:hover .fileinfo { + background-color: #505050; +} +.gallery .thumb { + width: 120px; + height: 70px; + background-size: cover; + background-position: center; +} +.gallery .fileinfo { + position: relative; + padding: 4px; + background-color: #404244; +} +.gallery .year { + position: absolute; + right: 4px; + top: 4px; + color: #8f8f8f; +} +.gallery .artist { + font-style: italic; +} +.gallery a.artist { + color: #DDDDDD; +} +.gallery a.artist:hover { + color: white; +} +.gallery .section { + padding: 4px 8px; +} +.gallery .section .title { + font-size: 13px; + color: #BBBBBB; + border-bottom: 1px solid #BBBBBB; + margin: 8px 0; +} +.gallery .section .description { + font-size: 12px; + color: #8f8f8f; +} + +.uae { + position: absolute; + left: 0; + top: 0; + z-index: 1000; + background-color: #282A2C; + box-shadow: 0 0 20px 0px black; +} +.uae .caption { + height: 20px; + color: #BBBBBB; + border-bottom: 1px solid black; + font-size: 12px; + padding: 2px 2px 2px 24px; + background-image: url("../_img/amigatick.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: 2px center; + cursor: move; +} +.uae .caption .close { + position: absolute; + height: 20px; + width: 20px; + line-height: 20px; + right: 0; + top: 0; + text-align: center; + cursor: pointer; + opacity: 0.7; +} +.uae .caption .close:hover { + opacity: 1; +} +.uae .resizer { + position: absolute; + width: 20px; + height: 20px; + right: 0; + bottom: 0; + border: 10px solid transparent; + border-bottom-color: #313335; + border-right-color: #313335; + cursor: nwse-resize; +} +.uae iframe { + width: 100%; + height: calc(100% - 21px); + border: none; +} +.uae.dragging iframe { + pointer-events: none; +} + +@media only screen and (max-width: 600px) { + .menu { + width: 100px; + height: auto; + top: 27px; + border: none; + } + .menu .hamburger { + display: block; + background-image: url("../_img/hamburger.svg"); + width: 200px; + height: 27px; + background-size: 24px 24px; + background-repeat: no-repeat; + background-position: 6px center; + position: absolute; + top: -27px; + padding: 6px 4px 2px 40px; + } + .menu a.main { + display: none; + font-size: 13px; + border: 1px solid black; + border-bottom: none; + padding: 2px 10px; + } + .menu a.main:last-child { + border-bottom: 1px solid black; + } + .menu a.main .sub { + left: 100px; + top: 0; + } + .menu a.main .sub a { + font-size: 13px; + border-bottom: 1px solid rgba(0, 0, 0, 0.4); + padding: 2px 20px 2px 10px; + } + .menu a.main .sub a:last-child { + border-bottom: none; + } + .menu a.main .sub a.wide { + padding: 2px 80px 2px 10px; + } + .menu a.main .sub a.ultrawide { + padding: 2px 100px 2px 10px; + } + .menu.active a.main { + display: block; + } +} + +/*# sourceMappingURL=main.css.map */ diff --git a/app/web-tools/dpaint/_style/main.css.map b/app/web-tools/dpaint/_style/main.css.map new file mode 100644 index 00000000..4551e223 --- /dev/null +++ b/app/web-tools/dpaint/_style/main.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["main.scss","_var.scss","_scrollbar.scss","_forms.scss","_range.scss","_menu.scss","_contextMenu.scss","_toolbar.scss","_statusbar.scss","_sidepanel.scss","_paletteList.scss","_editor.scss","_visualAids.scss","_selection.scss","_cursor.scss","_modal.scss","_paletteEditor.scss","_effectEditor.scss","_ditherEditor.scss","_saveDialog.scss","_fileBrowser.scss","_gallery.scss","_uae.scss","_mobile.scss"],"names":[],"mappings":"AAEA;EACE;EACA;;;AAEF;EACE;;;AAGF;EACE,kBCXiB;EDYjB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA,kBC1BiB;;AD4BjB;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AEzDJ;AACA;EACI;EACA;;;AAGJ;AACA;EACI;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;;AC3BJ;EACE;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA,OFHY;;AEKZ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;;AAEA;EACE;;AAMR;EACE;EACA;;AAGE;EACE;EACA;;AAKN;EACE;;AAEE;EACE;;AAEA;EACE;EACA;EACA;;AAOF;EACE;;;AAOV;AAAA;EAEE;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBF9Gc;EE+Gd;;AAGF;EACE;;AAEA;EACE;;AAEA;EACE;;AAOF;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;;ACnJN;EACE;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;;;AC5EF;EACE;EACA;EACA;EACA;EACA;EACA,kBJPuB;EIQvB,OJFgB;EIGhB;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,kBJ5BmB;EI6BnB,OJvBY;EIwBZ;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE,kBJ9EgB;EI+EhB;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA,OJnFY;EIoFZ;;AAKF;EACE;;AAIJ;EACE;EACA;EACA;EACA,OJlGc;EImGd;;AAIF;EACE;EACA,kBJjHiB;EIkHjB;EACA;EACA;EACA;EACA;;AAGE;EACE;;AAEA;EACE;;AAOV;EAEE,kBJrIoB;EIsIpB;;AAIA;EACE;;;AAON;EACE;;;ACrJJ;EACE;EACA;EACA;EACA,kBLHuB;EKIvB;EACA;EACA;EACA;EACA,OLFgB;;AKIhB;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,kBLxBoB;EKyBpB;;;ACzBN;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA,ONRc;EMSd;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AACA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIA;EACE;;AAOR;EACE;EACA;EACA;EACA;EACA,kBNnEqB;EMoErB;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGA;EAEE,kBNjFoB;EMkFpB;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA,kBNpGiB;;AMwGrB;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;AACA;EACE;;AAMN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAKN;EACE;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE,kBNpSc;EMqSd;EACA;;AAGF;EACE;EACA;EACA;;AAKN;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAIJ;EACE,kBNjUqB;EMkUrB;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,kBNjVkB;EMkVlB;EACA;;AAOF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;;AAKF;EACE;;AAKF;EACE;;AAGF;EACE;;AACA;EACE;EACA;;AAMJ;EACE;;AAGF;EACE;;AACA;EACE;EACA;;AAQV;EACE;EACA;EACA;EACA;;AAIA;EACE;EACA,kBN7ZmB;EM8ZnB;EACA;EACA;EACA,ON3ZY;;AM6ZZ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAGF;EACE;EACA;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAGF;EACE;;AAQF;EACE;;AAGF;EACE;;AAKN;EACE;;;AAOE;EACE;EACA;;;AAON;EACE;;;AAIJ;EACE;IACE;;EAEF;IACE;;;ACjgBJ;EACE;EACA;EACA;EACA;EACA;EACA,kBPLuB;EOMvB;EACA;EACA;EACA;EACA;;AAGE;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAOJ;EACC;;;AC7BH;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBRNuB;EQOvB;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,ORjBY;EQkBZ;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AACA;AAAA;EAEE;EACA,OR/BU;EQgCV;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA,ORlDU;EQmDV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGE;EACE;;AAKN;EACE;;AAGF;EAEE;;AACA;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA,kBRjIG;EQkIH;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA,ORxLQ;EQyLR;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;;AAMN;EACE;EACA,ORhNY;EQiNZ;EACA;EACA;;AAQA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAOF;EACE;;AAIJ;EACE;;AAIJ;EACE;EACA,kBR1PqB;EQ2PrB;EACA;EACA;EACA,OR5PY;EQ6PZ;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMR;EAEE,OR1VY;EQ2VZ;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACE;;AAIF;EACE;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;;AAKN;EACE,cRjbG;EQkbH;;AAUN;EACE;;AAEF;EACE;EACA;EACA;;AAEA;EACE;;AAEF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAON;EACE;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE,kBR1gBqB;EQ2gBrB,ORxgBc;EQygBd;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE,ORjjBc;EQkjBd;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAGF;EACE;;AAIJ;EACE,ORvlBY;EQwlBZ;EACA;EACA;EACA;;AAEA;EACE;;AAMN;EACE;;AAIF;EACE,ORxmBkB;EQymBlB;EACA;EACA;EACA;EACA;EACA,kBRlnBuB;;AQmnBvB;EACE;EACA,ORnnBY;EQonBZ,kBRvnBmB;;;AQgoBvB;EACE;;;AAOE;EACE;;AAEA;EACE;EACA;;AAON;EACE;;AAIA;EACE;EACA;;;AC/pBR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;;AAIJ;EACI;EACA;;AAEF;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAKF;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA,OTxHc;ESyHd;EACA;;AAEA;EACE,kBTlIoB;ESmIpB;;;ACrIN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA,OV/BU;EUgCV;EACA;EACA;EACA;EACA,kBVvCiB;;AUyCjB;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAKF;EACE;EACA;EACA,OVjEQ;EUkER;;AAEA;EACE;EACA,OVpEU;;AUuEZ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AACA;EACE;;AACA;EACE;EACA;;AAIJ;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AACA;EACE;;AAIJ;EACE;EACA,OVjIM;EUkIN;EACA;EACA;;AACA;EACE;;AAOV;AAAA;AAAA;EAGE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAMR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;;AAUF;EACE;;;AAOJ;EACE;;;AAKA;EACI;;;AAKN;EACE;EACA;EACA;;AAGE;EACE;;AAEA;EACE;;AAIJ;EACE;;;AC5RR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;ACjCR;EACE;EACA;EACA;AACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EAAwB;;AACxB;EAAwB;;AACxB;EAAwB;;AACxB;EAAwB;;AACxB;EAAwB;;AACxB;EAAwB;;AACxB;EAAwB;;AACxB;EAAwB;;AAExB;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;AAAA;EAEE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACI;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAGF;EACI;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;AACA;EACA;;AAGF;EACE;IACE;;;AAMN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACE;;AAGF;AACE;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEF;EACE;EACA;;AAEF;EACE;EACA;;AAGF;AACE;;;AAKF;EACE;;;AC/MJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAKJ;EACE;;AAEA;EACE;;AAGF;EACE;;;AAOF;EACE;;AAGF;EACE;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;;;AAKN;AAIE;AAAA;EACE;;AACA;AAAA;EACE;;;AAMJ;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIA;EACE;EACA;EACA,Ob7GY;Ea8GZ;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AC3IN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA,kBdjBuB;EckBvB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,OdtBc;EcuBd;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,kBdjDkB;EckDlB;;AAKN;EACE;;AAGF;EACE,OdvDc;;Ac0Dd;EACE;EACA;EACA;EACA;;AAEA;EACE,OdjEU;;AcoEZ;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;;AAEA;EACI;EACA;EACA;EACA;;AAGJ;EACE;EACA;EACA;;AAIJ;EACE,OdlHY;EcmHZ;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE,Od7HU;Ec8HV;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAIF;EACE;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EAOE;EACA;EACA;EACA;EACA;;AATA;EACE;EACA;EACA;;AAaN;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBdrQiB;EcsQjB;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAKF;EACE;;AAEF;EACE;;AAKF;EACE;;AAEF;EACE;;AAKF;EACE;;AAEF;EACE;;AAKF;EACE;;AAEF;EACE;;AAMN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA,OdtZY;;AcwZZ;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AC1aN;EACE;EACA;EACA;;AAKM;EACC;;AAIL;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAIA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAKN;EACE;;AAGF;EACE;;AAIJ;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGE;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AACA;EACE;;AAMN;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAGF;EACE;EACA;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGE;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AACA;EACE;;AAKN;EACI;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;EACE;EACA;;AAMR;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE,kBfjPe;EekPf;EACA;;AAEA;EACE;EACA;;AAMR;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA,OfnRG;EeoRH;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAMR;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,kBf9Xe;Ee+Xf;;AAEA;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;;AACA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAEF;EACE;;AAEF;EACE;EACA;;AAEF;EACE;;AAEF;EACE;EACA;;AAEF;EACE;;AAIJ;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AASN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA,kBfjkBiB;EekkBjB;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,kBf1kBgB;;AeglBxB;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AC7lBN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EAEE;EACA;EACA;EACA;;AAIN;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AACA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EAEA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,kBhB/JkB;;AgBoKxB;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OhB/KgB;;AgBiLhB;EACE;;AAGF;EAAQ;;AACR;EAAQ;;AACR;EAAO;;AACP;EAAW;;AACX;EAAK;;AACL;EAAU;;AACV;EAAS;;AACT;EAAS;;AACT;EAAS;;AACT;EAAS;;AAGX;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA,kBhBlNmB;EgBmNnB;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAEA;AAAA;EAEE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEF;EACE;;AAEF;EACE;;AAKF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;;AAOV;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;ACrSJ;EACE;EACA;EACA;EACA;EACA;EACA;EjBSF;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AiBbE;EACE;EACA;EACA;;AAGF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAGA;EACE;EACA;;AAQN;AAAA;AAAA;EAGE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AACA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE,cjBzFW;EiB0FX,OjB1FW;EiB2FX;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AClHR;EACE;;AACA;EACE;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAM;;AACN;EAAO;;AACP;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AAGR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAMF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAOF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIA;EACE;;AAQZ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAEF;EACE;;AAIA;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AASN;EACE;;AAGF;EACI;;AAGJ;EACE;;AAON;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;;AAIA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;;AJqIN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OdvbgB;EcwbhB;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;;AKpdJ;EACE;EACA;EACA;EACA;EACA;EACA,kBnBLuB;EmBMvB;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE,OnBVc;EmBWd;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA,OnBhCc;EmBiCd;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE,OnB7Dc;EmB8Dd;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AACA;EACE;;AAKN;EACE;EACA,kBnBrGoB;EmBsGpB;;;ACtGJ;EACE;EACA;EACA;EACA,OpBCc;EoBAd;EACA;EACA;;AAEA;EACE;EACA;EAKA,OpBViB;;AoBOjB;EACE;;AAMN;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OpB9BkB;;AoBiCpB;EACE;;AAGF;EACE,OpBvCmB;;AoBwCnB;EACE;;AAKJ;EACE;;AACA;EACE;EACA,OpBnDY;EoBoDZ;EACA;;AAGF;EACE;EACA,OpBxDgB;;;AqBTtB;EACE;EACA;EACA;EACA;EACA,kBrBDuB;EqBEvB;;AAEA;EACE;EACA,OrBHc;EqBId;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIA;EACE;;;ACzDN;EACC;IACE;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA;;EAEA;IACE;IACA;IACA;;EAEA;IACE;;EAGF;IACE;;EAGF;IACE;;EAQN;IACE","file":"main.css"} \ No newline at end of file diff --git a/app/web-tools/dpaint/_style/main.scss b/app/web-tools/dpaint/_style/main.scss new file mode 100644 index 00000000..6e7af0e0 --- /dev/null +++ b/app/web-tools/dpaint/_style/main.scss @@ -0,0 +1,81 @@ +@import "var"; + +html { + box-sizing: border-box; + overflow: hidden; +} +*, *:before, *:after { + box-sizing: inherit; +} + +body{ + background-color: $background-color; + font-family: sans-serif; + font-size: 14px; + height: 100%; + margin: 0; + padding: 1px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; +} + +.container{ + position: relative; + height: 100%; + background-color: $background-color; + + &.fuzzy{ + pointer-events: none; + filter: blur(2px); + } +} + +h1.error{ + text-align: center; + color: white; + font-size: 24px; + font-weight: 100; + margin: 50px auto; + width: 50%; + + a{ + color: white; + } +} + +.spinner{ + position: absolute; + top: 40%; + left: 50%; + margin-left: -25px; + width: 50px; + height: 50px; + border: 5px solid rgba(255, 255, 255, 0.3); + border-top: 5px solid white; + border-radius: 50%; + animation: spin 1s linear infinite; + +} + + + +@import "scrollbar"; +@import "forms"; +@import "range"; +@import "menu"; +@import "contextMenu"; +@import "toolbar"; +@import "statusbar"; +@import "sidepanel"; +@import "paletteList"; +@import "editor"; +@import "visualAids"; +@import "selection"; +@import "cursor"; +@import "modal"; +@import "fileBrowser"; +@import "gallery"; +@import "uae"; +@import "mobile"; \ No newline at end of file diff --git a/app/web-tools/dpaint/dev.html b/app/web-tools/dpaint/dev.html new file mode 100644 index 00000000..731cf17e --- /dev/null +++ b/app/web-tools/dpaint/dev.html @@ -0,0 +1,54 @@ + \ No newline at end of file diff --git a/app/web-tools/dpaint/docs/_img/adf.gif b/app/web-tools/dpaint/docs/_img/adf.gif new file mode 100644 index 00000000..08a09ee5 Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/adf.gif differ diff --git a/app/web-tools/dpaint/docs/_img/dither.gif b/app/web-tools/dpaint/docs/_img/dither.gif new file mode 100644 index 00000000..bf7f2c6f Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/dither.gif differ diff --git a/app/web-tools/dpaint/docs/_img/eagle1.gif b/app/web-tools/dpaint/docs/_img/eagle1.gif new file mode 100644 index 00000000..2ad8cab1 Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/eagle1.gif differ diff --git a/app/web-tools/dpaint/docs/_img/gradient.gif b/app/web-tools/dpaint/docs/_img/gradient.gif new file mode 100644 index 00000000..741ece3e Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/gradient.gif differ diff --git a/app/web-tools/dpaint/docs/_img/icon-preview.gif b/app/web-tools/dpaint/docs/_img/icon-preview.gif new file mode 100644 index 00000000..f78772c6 Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/icon-preview.gif differ diff --git a/app/web-tools/dpaint/docs/_img/lasso.gif b/app/web-tools/dpaint/docs/_img/lasso.gif new file mode 100644 index 00000000..99fe5ebf Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/lasso.gif differ diff --git a/app/web-tools/dpaint/docs/_img/reduce.gif b/app/web-tools/dpaint/docs/_img/reduce.gif new file mode 100644 index 00000000..f0bb2f1a Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/reduce.gif differ diff --git a/app/web-tools/dpaint/docs/_img/scripted_fx.png b/app/web-tools/dpaint/docs/_img/scripted_fx.png new file mode 100644 index 00000000..7cb78bf7 Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/scripted_fx.png differ diff --git a/app/web-tools/dpaint/docs/_img/steffest.png b/app/web-tools/dpaint/docs/_img/steffest.png new file mode 100644 index 00000000..b4fac2c7 Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/steffest.png differ diff --git a/app/web-tools/dpaint/docs/_img/stencil.gif b/app/web-tools/dpaint/docs/_img/stencil.gif new file mode 100644 index 00000000..0d097de5 Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/stencil.gif differ diff --git a/app/web-tools/dpaint/docs/_img/uae.gif b/app/web-tools/dpaint/docs/_img/uae.gif new file mode 100644 index 00000000..01956b79 Binary files /dev/null and b/app/web-tools/dpaint/docs/_img/uae.gif differ diff --git a/app/web-tools/dpaint/docs/_img/warning-icon.svg b/app/web-tools/dpaint/docs/_img/warning-icon.svg new file mode 100644 index 00000000..c9d259fc --- /dev/null +++ b/app/web-tools/dpaint/docs/_img/warning-icon.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/web-tools/dpaint/docs/about.html b/app/web-tools/dpaint/docs/about.html new file mode 100644 index 00000000..8bcabc77 --- /dev/null +++ b/app/web-tools/dpaint/docs/about.html @@ -0,0 +1,37 @@ +

About

+ +DPaint.js Logo +

DPaint.js is a web based image editor modeled after the legendary Deluxe Paint.
+ It's main purpose is to be used as a tool for creating pixel art, but it can also be used for general image editing.

+ +

It has a sweet spot for Amiga file formats: it can read and write all Amiga Icon formats and read/write Amiga IFF Images.
+You can even read/write files directly from ADF disk files and preview your work instantly in the "real" Deluxe Paint! (on an embedded emulated Amiga)

+ +

+It initially was made as a modern icon-editor for Amiga icons, but it grew into a fully featured image editor with + + +

    +
  • Layers
  • +
  • Selections
  • +
  • Masks
  • +
  • Effects and filters
  • +
  • Multiple undo/redo
  • +
  • Copy/Paste from any other image program or image source
  • +
  • Super fine color reduction tools
  • +
  • Customizable dither tools
  • +
  • And much more ...
  • +
+

+ +

It runs in your browser, works on any system and works fine on touch-screen devices like iPads.
+It is written in 100% plain JavaScript and is completely open source.
+ It's 100% free, no ads, no tracking, no accounts, no nothing.
+ All processing is done in your browser, no data is sent to any server.

+

+ +
+

Small Demo Video of Amiga Spefific Features

+ + + diff --git a/app/web-tools/dpaint/docs/archive/amigaicons.html b/app/web-tools/dpaint/docs/archive/amigaicons.html new file mode 100644 index 00000000..807c3244 --- /dev/null +++ b/app/web-tools/dpaint/docs/archive/amigaicons.html @@ -0,0 +1,490 @@ + + + + + + + Amiga icon file format + + + + + +
+

+ This file is a copy of the original article, which was published on http://krashan.ppa.pl/articles/amigaicons/
+ It's included here because the original site is no longer available. +

+
+ +
+ + + +
+ Amiga MagicWB icons +
+ +
Amiga Icon File Format
+
An unofficial guide to decoding classic Amiga icon images
+ + + + +

1. Introduction

+ +

Icons in AmigaOS and its derivatives (MorphOS, AROS), are basically the + same thing as on other operating systems – a small images identifying + files, directories, disks and so on. Except of image data, Amiga icons + contain some additional informations. File icons can contain a path to + an application used to open the project file when it is doubleclicked (the + default tool). They can also contain so-called tooltypes, which are just + strings used as program parameters. Dawer (folder) and disk icons contain + position, size and viewmode of a window opened on the desktop after + clicking the icon. All icons can store their position in parent drawer (or + desktop) window.

+ +

I've decided to write this unofficial specification, after I've found + none on the Internet. While Amiga icons are not very popular, as AmigaOS + itself is rather a niche, hobby OS, someone may find this article useful. + Note that there are a few kinds of Amiga icons. This article describes an + old, bitplane based format. Later NewIcons and PNG icons (as used in AmigaOS + 4, MorphOS and AROS) are not described here.

+ +

2. Real and default icons

+ +

Icons in AmigaOS are separate files, with the same name as the 'main' + file, and '.info' extension at the end. Note that if a file has an extension + by itself (for example 'archive.lha'), the '.info' extension does not + replace file extension, but is appended at the end, so an icon for + 'archive.lha' has the name 'archive.lha.info'. It may look strange for a + Windows user, but comes from the fact, that Amiga system does not use + extensions for file type recognition at all. An executable file without an + '.exe' is perfectly OK for example. To say more, executable files on + Amiga have no extension at all usually.

+ +

When a file has no icon associated, it is invisible in Workbench window + until "Show all files" viewmode is selected. Then a default icon for the + file is used. Older AmigaOS versions have stored default icons in + "ENVARC:sys/def_xxxx.info", where xxxx is 'drawer', 'project', 'tool' or + 'disk'. Some extensions to this system have been developed, which allowed + different icons for different file types. What is important here, default + icons have the same file format as real ones. One can easily create a real + icon from a default one, using system tools, or just by copying the icon and + changing its name.

+ +

3. Amiga icon structure

+ +

The icon file is composed directly of some system structures. It has an + advantage of fast icon loading and displaying, the file is just loaded to + memory, structures addresses are extracted and may be passed directly to + Intuition, the main Amiga UI library. On the other hand many fields of these + system structures are unused or redundant, making the icon file bigger. + Some data in these structures are referenced via pointers in memory, in an + icon file, the data are stored after structures in a defined order.

+ +

The selected state of an Amiga icon is not generated programatically, but + is specified in the icon itself. Often the selected state is a second, separate + image, sometimes it is specified as palette entries swap.

+ +

3.1. Format overview.

+ + The icon file starts from a DiskObject structure, which embeds Gadget + structure. Then there may be a DrawerData structure, followed by one or two + Image structures. After them raster image data for Image(s) follows. + Then there is a default tool string and tooltypes strings. DrawerData + contains a NewWindow structure. Here is a block diagram of an Amiga icon: +

+ + + + + + + + + +
DiskObjectDrawerDataImage 1raster data
for Image 1
Image 2raster data
for Image 2
default tooltooltypes
+
Fig. 1. A block diagram of Amiga icon file structure (gray elements are optional). +
+

+ +

NOTE: All multi-byte fields of an Amiga icon are big-endian. x86 + programmers have to swap bytes.

+ +

3.2. DiskObject structure

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0000uint16do_MagicA file identifier. All + icons have 0xE310 here.
0002uint16do_VersionIcon version. The + current version is 1.
0004uint32do_Gadget.NextGadgetUnused. + Contains 0 usually.
0008int16do_Gadget.LeftEdgeHorizontal + position of the icon left edge relative to its parent window left + edge. This field is only used when an icon is loaded into memory.
0010int16do_Gadget.TopEdgeVertical + position of the icon top edge relative to its parent window top + edge. This field is only used when an icon is loaded into memory.
0012uint16do_Gadget.WidthIcon width + in pixels.
0014uint16do_Gadget.HeightIcon height + in pixels.
0016uint16do_Gadget.FlagsGadget flags. Used only by + Intuition, when the icon is loaded into memory. Usually set to 5.
0018uint16do_Gadget.ActivationGadget activation flags. + The usual value is 3 here (both 'immediate' and 'relverify' activation methods set).
0020uint16do_Gadget.GadgetTypeGadget type. The usual value + is 1 here (means boolean gadget).
0022uint32do_Gadget.GadgetRenderIn memory a pointer + to the first Image, used for not selected state. In file it should be any non-zero + value. Zero here should not happen.
0026uint32do_Gadget.SelectRenderIn memory a pointer + to the second Image, used for selected state. In file non-zero value means that the + icon has the second Image and raster data.
0030uint32do_Gadget.GadgetTextUnused. Usually 0.
0034uint32do_Gadget.MutualExcludeUnused. Usually 0.
0038uint32do_Gadget.SpecialInfoUnused. Usually 0.
0042uint16do_Gadget.GadgetIDUnused. Usually 0.
0044uint32do_Gadget.UserDataUsed for icon revision. 0 for + OS 1.x icons. 1 for OS 2.x/3.x icons.
0048uint8do_TypeA type of icon: +
  • 1 – disk or volume.
  • +
  • 2 – drawer (folder).
  • +
  • 3 – tool (executable).
  • +
  • 4 – project (data file).
  • +
  • 5 – trashcan.
  • +
  • 6 – device.
  • +
  • 7 – Kickstart ROM image.
  • +
  • 8 – an appicon (placed on the desktop by application).
0049uint8paddingJust padding byte.
0050uint32do_DefaultToolIn memory a pointer to a + default tool path string. In file should be interpreted as boolean field indicating + default tool presence.
0054uint32do_ToolTypesIn memory a pointer to a table + containing pointers to tooltype strings. In file should be interptered as boolean + field indicating tooltypes table presence.
0058int32do_CurrentXVirtual horizontal position of + the icon in the drawer window.
0062int32do_CurrentYVirtual vertical position of the + icon in the drawer window.
0066uint32do_DrawerDataIn memory a pointer to + DrawerData structure. In file should be interpreted as a boolean field indicating + DrawerData presence.
0070uint32do_ToolWindowUnused.
0074uint32do_StackSizeTask stack size for an application. + (in case of project file, this size is for default tool application).
Total size: 78 bytes.
+ +

3.3. DrawerData structure

+ +

The structure starts from a NewWindow structure. Note that DrawerData may be just skipped when + only icon image is to be decoded. I've put the information here just for completness.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0000int16dd_NewWindow.LeftEdgeDrawer + window left edge relative to the Workbench screen.
0002int16dd_NewWindow.TopEdgeDrawer window top edge relative to the + Workbench screen.
0004int16dd_NewWindow.WidthDrawer window width.
0006int16dd_NewWindow.HeightDrawer window height.
0008uint8dd_NewWindow.DetailPenNumber of graphics pen used to render + window details.
0009uint8dd_NewWindow.BlockPenNumber of graphics pen used to + render window frame background.
0010uint32dd_NewWindow.IDCMPFlagsKinds of IDCMP (GUI -> application) + events requested.
0014uint32dd_NewWindow.FlagsVarious window flags (borders, system + gadgets etc.).
0018uint32dd_NewWindow.FirstGadgetIn memory a pointer to the first + window gadget in a linked list. Unused in an icon file.
0022uint32dd_NewWindow.CheckMarkIn memory a pointer to checkmark + imagery for the window. Unused in an icon file.
0026uint32dd_NewWindow.TitleIn memory a pointer to the window title + string. Unused in an icon file.
0030uint32dd_NewWindow.ScreenIn memory a pointer to system Screen + a window is to be opened on. Unused in an icon file.
0034uint32dd_NewWindow.BitMapIn memory points to a system BitMap + for the window. Unused in an icon file.
0038int16dd_NewWindow.MinWidthMinimum width for the window.
0040int16dd_NewWindow.MinHeightMinimum height for the window.
0042uint16dd_NewWindow.MaxWidthMaximum width for the window.
0044uint16dd_NewWindow.MaxHeightMaximum height for the window.
0046uint16dd_NewWindow.TypeWindow type (public/custom screen).
0048int32dd_CurrentXHorizontal position of originating icon.
0052int32dd_CurrentYVertical position of originating icon.
Total size: 56 bytes.
+ +

3.4. Image structure

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0000int16LeftEdgeImage + left edge position relative to the icon left edge. Image clipping should be done for negative + values.
0002int16TopEdgeImage top edge position relative to + the icon top edge. Image clipping should be done for negative values.
0004uint16WidthImage width in pixels. May be less + than icon width (stored in DiskObject.Gadget), missing columns use color 0. If it is + bigger than icon width I recommend to clip the image.
0006uint16HeightImage height in pixels. May be less + than icon height (stored in DiskObject.Gadget), missing rows use color 0. If it is + bigger than icon height I recommend to clip the image.
0008uint16DepthNumber of image bitplanes (see chapter + 3.5.).
0010uint32ImageDataIn memory it is a pointer to + bitplanes, in file it should be treated as a boolean value (if not zero, bitplane + data are stored as shown on fig. 1.).
0014uint8PlanePickA bitfield controlling which + image bitplane is copied to which screen bitplane. Used only by classic Amiga + graphics chipset. Meaningless in a file, as it is interpreted in Amiga chipset + context displaying particular screen.
0015uint8PlaneOnOffA bitfield controlling + screen bitplanes not fed with icon data. They may be either filled by zeros or + by ones. Used only by classic Amiga graphics chipset. Meaningless in a file, + as it is interpreted in Amiga chipset context displaying particular screen.
0016uint32NextImageUnused. Usually 0.
Total size: 20 bytes.
+ + +

3.5. Image data and palette

+ +

Icon image data are stored as bitplanes. While a bit cumbersome for nowadays display devices, + this format is a native one for Amiga graphics chipsets. The image data, once loaded into + graphics ("chip") memory, can be directly blitted to a screen. On bitplanes, every pixel + occupies one bit, regardless of number of palette colors. Palette size is determined by a + number of bitplanes, for N bitplanes there are 2N colors available. + To determine a pixel color, one has to gather this pixel bits from all bitplanes (bitplane 0, + which comes first in the data, is the most significant one) as shown on fig. 2, form a number + and use it as an index to the palette table.

+ +
Bitplaned image data
+
Fig. 2. Bitplaned image data. +
+ +

Amiga icon bitplanes are not interleaved, complete planes are stored one after one. Pixel + scanning order is usual left-to-right, top-to-bottom. Plane rows are padded to 16-bit words, + there is no vertical padding.

+ +

In spite of image data are palette-based, there is no palette stored in + the icon, just some standard palette is assumed. Unfortunately there are a few + "standard" palettes used: +

  1. Standard AmigaOS 1.x palette, 4 colors.
    +       + color 0, R=0, G=85, B=170 (0x0055AA)
    +       + color 1, R=255, G=255, B=255 (0xFFFFFF)
    +       + color 2, R=0, G=0, B=0 (0x000000)
    +       + color 3, R=255, G=136, B=0 (0xFF8800) +
  2. +
  3. Standard AmigaOS 2.x palette, 4 colors.
    +       + color 0, R=149, G=149, B=149 (0x959595)
    +       + color 1, R=0, G=0, B=0 (0x000000)
    +       + color 2, R=255, G=255, B=255 (0xFFFFFF)
    +       + color 3, R=59, G=103, B=162 (0x3B67A2) +
  4. +
  5. MagicWB palette, 8 colors. It extends AmigaOS 2.x palette with 4 additional colors.
    +       + color 4, R=123, G=123, B=123 (0x7B7B7B)
    +       + color 5, R=175, G=175, B=175 (0xAFAFAF)
    +       + color 6, R=170, G=144, B=124 (0xAA907C)
    +       + color 7, R=255, G=169, B=151 (0xFFA997) +
+

There were some other palettes proposed, usually extending MagicWB with more colors, but + they have not gained popularity. NewIcons format solved the problem finally, storing a palette + inside an icon. Which palette one should use converting the icon image to RGB color space? + If we limit possibilities to palettes shown above, icon revision allows to choose between OS 1.x and OS 2.x + palette. Then if revision is 1 and number of bitplanes is 3, the icon is MagicWB one.

+ +

4. Decoding icon images

+ +

Usually you will be only interested in extracting images from an icon, and convert them to + the RGB color space (unless you are writing an Amiga Workbench replacement...). Here is a short guide + how to do it: + +

    + +
  1. + Take into account, that most of information in the icon is unused. All fields in DiskObject and Image + structures really needed to decode imagery are marked gray + in the above tables. White entries may be ignored. If you are on x86 or other little-endian platform, + do not forget about byte-swapping. +
  2. + +
  3. + Start from loading a fixed size DiskObject structure. Verify do_Magic and do_Version. Then + check for presence of DrawerData. Non zero do_DrawerData is the primary indicator, do_Type of + 1, 2 or 5 is the secondary one. Check for presence of the first image (do_Gadget.GadgetRender). The + second image primary indicator is do_Gadget.SelectRender, the secondary one is that gadget highlight + bits are set to 2 (do_Gadget.Flags & 0x0003 == 0x0002). +
  4. + +
  5. + If DrawerData is present, skip it. +
  6. + +
  7. + Read the first Image structure. Extract offsets, width, height and number of bitplanes. Calculate bitplane width + in bytes taking horizontal padding into account. It may be calculated as + ((Width + 15) >> 4) << 1. +
  8. + +
  9. + Check for ImageData. If it is 0, it may mean that the image is empty, or the file is damaged. If not, + load bitplanes to memory. +
  10. + +
  11. + If you've detected the second Image, repeat 4. and 5. Be prepared for different offsets and dimensions (may + happen often) or different bitplanes number (very unlikely, but who knows). +
  12. + +
  13. + Select the palette for every image, based on icon revision and number of bitplanes. Convert bitplanes to + a RGB pixelmap, using selected palette. Note that padding bits should be ignored even if non-zero. +
  14. + +
  15. + Create an empty rectangle of icon size (do_Gadget.Width × do_Gadget_Height). Fill it + with palette color 0. Then impose the normal or selected image on it using Image LeftEdge and + TopEdge as offsets. Perform clipping if neccesary. +
  16. + +

+
+ + + + + + diff --git a/app/web-tools/dpaint/docs/archive/artstyles.css b/app/web-tools/dpaint/docs/archive/artstyles.css new file mode 100644 index 00000000..6bb027a7 --- /dev/null +++ b/app/web-tools/dpaint/docs/archive/artstyles.css @@ -0,0 +1,277 @@ +html { + -webkit-text-size-adjust: none; +} + +body { + font-family: "Arial","Helvetica",sans-serif; + font-size: 16px; + background-color: white; + color: black; + padding: 0px; + margin: 0px; +} + +div.main { + max-width: 1200px; + width: 80%; + margin: 2em auto; + border: 1px solid #B2B2B2; + background-color: #F4F4F4; + background-image: url('https://web.archive.org/web/20220120040355im_/http://teleinfo.pb.edu.pl/krashan/images/bg453.png'); + padding: 3.5em 4em; + border-radius: 12px; + box-shadow: 5px 5px 10px #B9B9B9; +} + +div.author { + margin-top: 0.5em; + margin-bottom: 1.2em; + font-size: 114%; +} + +div.author a { + color: #500000; + text-decoration: none; + transition: color 0.5s; +} + +div.author a:hover { + color: #C00000; +} + +div.footer { + margin-top: 1em; + padding-top: 0.5em; + border-top: 1px solid #C0C0C0; + text-align: justify; + font-size: 87%; +} + +a.text { + color: #500000; + text-decoration: none; + background-color: rgba(200,80,0,0.2); + padding: 0em 0.2em; + transition: color 0.5s; + border-radius: 2px; +} + +a.text:hover { + color: #C00000; +} + +div.footer a { + color: #500000; + text-decoration: none; + transition: color 0.5s; + background: none; +} + +div.footer a:hover { + color: #C00000; +} + +p { + line-height: 130%; + text-align: justify; +} + +p.date { + font-style: italic; + text-align: right; +} + +pre { + font-family: "Menlo",monospace; + text-align: left; + font-weight: normal; + border: 1px dotted #9C9C9C; + background-color: #F5F5F5; + padding: 0.3em 0.4em; + font-size: 80%; +} + +p.leadin { + font-size: 114%; +} + + +h1 { + font-size: 160%; + font-weight: bold; + margin-top: 1.5em; + text-shadow: 0.08em 0.08em 0.1em #C0C0C0; +} + +h2 { + font-size: 130%; + font-weight: bold; + margin-top: 1.5em; + text-shadow: 0.08em 0.08em 0.1em #C0C0C0; +} + +h3 { + font-size: 110%; + font-weight: bold; + margin-top: 1.5em; + text-shadow: 0.08em 0.08em 0.1em #C0C0C0; +} + +div.math { text-align: center; } + +div.hd1 { + clear: both; + margin-bottom: 2em; +} + +div.hd2 { + float: right; + margin: 0em 0em 0.7em 1.5em; +} + +img.hd2 { + background-color: white; + padding: 0.4em; + border: 1px solid #C0C0C0; +} + +div.hd3 { + font-size: 200%; + text-shadow: 0.08em 0.08em 0.12em #C0C0C0; + margin-bottom: 0.6em; +} + +div.hd4 { + font-style: italic; + padding-bottom: 0.5em; + line-height: 130%; + text-align: justify; + color: #606060; +} + +div.hd5 { + font-size: 200%; + text-shadow: 0.08em 0.08em 0.12em #C0C0C0; + margin-top: 0.75em; +} + +table { + border: 1px solid #808080; + border-spacing: 0px; + border-collapse: collapse; +} + +td { + border: 1px solid #808080; + font-size: 75%; + padding: 1px 2px; +} + +th { + border: 1px solid #808080; + font-size: 75%; + padding: 1px 2px; + font-weight: bold; + text-align: center; +} + +td.number { + text-align: right; +} + +sub { + vertical-align: baseline; + position: relative; + font-size: 70%; + bottom: -0.33em; + margin-left: 0.04em; + line-height: 100%; +} + +sup { + vertical-align: baseline; + position: relative; + font-size: 70%; + bottom: 0.5em; + margin-left: 1px; + line-height: 100%; +} + +li { text-align: justify; line-height: 135%; } +ul { margin: 0.2em 0em; } + +div.last_updated { + font-size: 90%; + border-top: 1px solid rgba(0,0,0,0.15); + padding-top: 0.3em; + margin-top: 1.0em; + font-style: italic; + color: #606060; +} + +/* Division for "+1" and "Like it" buttons */ + +div.sharebuttons { } +div.gplusadjust { display: inline-block; } +div.fbookadjust { display: inline-block; position: relative; top: 2px; } +div.profilebutton { text-align: right; } + +/* mobile tweaks */ + + +@media all and (max-width: 1023px) +{ + body { + font-size: 14px; + } + + div.main { + margin: 0em; + border: none; + box-shadow: none; + border-radius: 0px; + width: auto; + } +} + +@media all and (max-width: 567px) +{ + body { + font-size: 13px; + } + + div.main { + padding: 1.7em 2em; + } + + div.hd2 { + display: none; + } + + ul { + padding-left: 1.5em; + } + + ol { + padding-left: 1.5em; + } + + pre { + font-size: 70%; + } + + + @media all and (max-width: 399px) + { + pre { + font-size: 62%; + } + } + + @media all and (max-width: 359px) + { + pre { + font-size: 57%; + } + } +} diff --git a/app/web-tools/dpaint/docs/changelog.txt b/app/web-tools/dpaint/docs/changelog.txt new file mode 100644 index 00000000..be61c920 --- /dev/null +++ b/app/web-tools/dpaint/docs/changelog.txt @@ -0,0 +1,41 @@ +V 0.1.0 + - Initial release + +V 0.1.1 + - Bugfixes + - Additional palettes + - Duplicate Frame + - ReOrder Frame + - pixelated circle tool + +V 0.1.2 + - Resize image: option for smooth rescale or pixelated rescale + - UAE window is now draggable and resizable + - fill in gaps in draw tool on fast mouse moves + - Additional palettes and pagination for long palettes + - UI to change Alchemy script parameters + - smudge tool + - bugfixes for Amiga Color Icons + +V 0.1.3 + - color cycling support + - HSV mode and other palette editing improvements + - PBM support (Thanks Michael) + - navigate palette and frames with arrow keys + - load files from url and other url parameter commands + - presentation mode + - support for saving indexed color PNGs +V 0.1.4 + - palette locking + - GIF export + - spray tool + - text tool + - rotsprite and mapped-smooth rotation + - Better touch screen support on Android + - pressure sensitivity support for stylus + - smear/blur tools +V 0.1.5 + - Load/Save Brushes + - support for 12 and 9 bit color depths (Amiga OCS / Atari ST) + - Grid tool + - Autosave current image in local storage \ No newline at end of file diff --git a/app/web-tools/dpaint/docs/contact.html b/app/web-tools/dpaint/docs/contact.html new file mode 100644 index 00000000..89659aff --- /dev/null +++ b/app/web-tools/dpaint/docs/contact.html @@ -0,0 +1,17 @@ +

Contact

+ +

+ +
+ + If you have any questions, comments, or suggestions,
+ please feel free to open an issue in GitHub or contact me at + +

+ dev@stef.be  •  + @steffest  •  + Steffest#4438 + +

+ +

diff --git a/app/web-tools/dpaint/docs/docs.txt b/app/web-tools/dpaint/docs/docs.txt new file mode 100644 index 00000000..089f1dbe --- /dev/null +++ b/app/web-tools/dpaint/docs/docs.txt @@ -0,0 +1,21 @@ +Some observations: + +Copy/Paste + +With Chrome you can copy an image as file from the filesystem and paste it in DPaint. +With Firefox this doesn't work. + +On OSX - Copy/Paste from Photoshop keeps transparency (if the image is in RGB color mode) - on Window it doesn't + + +Good read on ClipBoard Access: +https://web.dev/async-clipboard/ + +for Amibase: https://web.dev/async-clipboard/#permissions-policy-integration + + + + diff --git a/app/web-tools/dpaint/docs/faq.html b/app/web-tools/dpaint/docs/faq.html new file mode 100644 index 00000000..84978528 --- /dev/null +++ b/app/web-tools/dpaint/docs/faq.html @@ -0,0 +1,56 @@ +

F.A.Q.

+ +

Dude, your effects/filters are not working

+ +
+ You are using safari, right? (or another IOS browser)
+ Safari doesn't support Canvas filters.
+ I might one day implement them in plain JavaScript, but currently I'm not spending too much time on all the weird and crippling quirks of Safari.
+ Please use a modern browser like Chrome, Firefox or Edge, or at least one that doesn't actively tries to hold back the web. +
+ +

Dude, When I copy and paste an image, I loose transparency

+
+ Yes very often you do...
+ This is mainly an issue with Photoshop on Windows, which doesn't save the transparency information in the clipboard in a standard way.
+ Weirdly enough, Photoshop on Mac does preserve transparency in copy/paste.
+ As a workaround, you can first save your file as PNG and import that instead. +
+ +

Dude, what's up with this Deluxe Paint bullsh*t, it's nothing like it.

+
+ I mean: the toolbox is even on the wrong side!
+ Yep. It's certainly not a remake of Deluxe Paint, but it's heavily inspired by it, regarding Amiga spirit and pixel handling.
+ I like to think of as "What if Deluxe Paint was made today?"
+ Besides: Deluxe Paint on Atari has the tools at the bottom, blasphemy! +
If you're looking for a more faithful recreation of Deluxe Paint, check out PyDPainter. +
+ +

Can I import palettes from Lospec?

+
+ Yes.
+ Download the Lospec Palette as PNG, open that image in DPaint.js.
+ Next to that, you can extract the palette from any image by selecting the menu Palette -> From image. +
+ +

How do I save Amiga Icons in the "New Icon" format?

+
+ Don't.
+ The "New Icon" format was a hacky and messy way to solve a problem 30 years ago, but nowadays there are far better solutions.
+ Save your icons as "Color Icon" (or "Glow Icon") and use a proper icon library on your Amiga.
+ Please stop using the "New Icon" format, the sooner it fades out, the better.
+ DPaint.js can read New Icons, but write support is not planned. +
+ +

How do I save images as JPEG?

+
+ Apologies for being opinionated and pedantic.
+ DPaint.js is targeted at pixel art and lo-spec images.
+ While the JPEG format is fine for high resolution photos, + it's not a good match if you want to preserve each pixel of an image because it's a lossy compression that blurs and alters the image.
+ Therefore, DPaint.js doesn't provide an option to save as JPEG.
+
+ + + + diff --git a/app/web-tools/dpaint/docs/features.html b/app/web-tools/dpaint/docs/features.html new file mode 100644 index 00000000..12e76a5b --- /dev/null +++ b/app/web-tools/dpaint/docs/features.html @@ -0,0 +1,119 @@ +

Unique features

+ +
+
+

Live Pixel preview

+

+ Preview your pixel before you actually put it. Very useful when hunting for the perfect pixel spot. +

+ Live Pixel Preview +
+ +
+

Direct preview as icon or tile

+

+ Instantly see how your image looks like as an icon or tile, so you can preview how it will look in the Amiga Workbench. +

+ Live Icon Preview +
+
+ + + +
+
+

Lasso tool with editable points

+

+ Edit the points of your lasso selection, so you can finetune your selection. +

+ Lasso tool +
+ +
+

Customizable dither tools

+

+ Create your own dither patterns, or use one of the predefined ones. +

+ Dither Tools +
+
+ + +
+
+

Scriptable Effects

+

+ Create your own effects in JavaScript (examples included) +

+ Scriptable Effects +
+ +
+

Dither Gradients

+

+ Mix 2 colors with a dither pattern. +

+ Dither Gradients +
+
+ +
+
+

Stencil tool

+

+ (Soon to be animated) +

+ Stencil tool +
+ +
+

Fine Grained Color reduction tools

+

+ Reduce your image colours to a specific palette, with various dithering options. +

+ Color reduction tools +
+
+ +
+
+

ADF disk support

+

+ Direct editing of files on ADF disks (Amiga Disk format) +

+ ADF support +
+ +
+

Deluxe Paint Amiga Preview

+

+ for that nostalgic itch, you can even directly open your image in the orignal Deluxe Paint, right in your browser. +

+ Deluxe Paint Preview +
+
+ +

+ +

And of course no modern image editor is complete without these features:

+ +
    +
  • Layers
  • +
  • Masks
  • +
  • Blend modes
  • +
  • Multiple undo/redo
  • +
  • Copy/paste from external programs
  • +
+ +

Features still missing

+planned for future releases if there's a need for it. + +
    +
  • Support for non-square pixel modes such as hiRes and Interlaced
  • +
  • Animation support (Working on it, it won't be frame based, though, but using a timeline, like e.g. Adobe Animate)
  • +
  • Shading/transparency tools that stay within the palette.
  • +
  • Color Cycling
  • +
  • PSD import and export
  • +
  • Animated GIF import/export
  • +
  • SpriteSheet support
  • +
diff --git a/app/web-tools/dpaint/docs/index.html b/app/web-tools/dpaint/docs/index.html new file mode 100644 index 00000000..05041077 --- /dev/null +++ b/app/web-tools/dpaint/docs/index.html @@ -0,0 +1,312 @@ + + + + + + +
+

DPaint.js Documentation

+ +
+ +
+ + +
+ + + + + + diff --git a/app/web-tools/dpaint/docs/keyboard_commands.html b/app/web-tools/dpaint/docs/keyboard_commands.html new file mode 100644 index 00000000..dbf76dd3 --- /dev/null +++ b/app/web-tools/dpaint/docs/keyboard_commands.html @@ -0,0 +1,65 @@ +

Keyboard commands

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Tools
ASelect All
BDraw
CCircle
DToggle Dither
EErase
FFlood Fill
GGradient
HHand/Pan
IInvert Dither Pattern
KColor Picker
LLine
MSmudge
PPolygon Select
RRectangle
SSelect
TTransform Layer
VTransform Layer
WFlood Select (Magic Wand)
XSwap Background/Foreground color
ZToggle Splitscreen
-Zoom Out
+Zoom In

Actions
AToggle Mask
DelClear Selection/Layer
EnterCommit Transform
TabToggle Color Cycle
Ctrl / ⌘ + ASelect All
Ctrl / ⌘ + BCopy Selection to Brush
Ctrl / ⌘ + DDuplicate Layer
Ctrl / ⌘ + EEffects
Ctrl / ⌘ + GToggle Grid
Ctrl / ⌘ + IImport
Ctrl / ⌘ + JCopy Selection to new Layer
Ctrl / ⌘ + KCut Selection to new Layer
Ctrl / ⌘ + NNew File
Ctrl / ⌘ + OOpen File
Ctrl / ⌘ + PResize Canvas
Ctrl / ⌘ + RResize Image
Ctrl / ⌘ + SSave Image
Ctrl / ⌘ + YRedo
Ctrl / ⌘ + ZUndo
Ctrl / ⌘ + Shift + AAdd Layer Mask - Show All
Ctrl / ⌘ + Shift + HAdd Layer Mask - Hide All
+
+ + Instead of the Ctrl-key on Windows and the Command-Key on Mac,
+ you can also use Shift or Alt. +
+

+

While Drawing
Hold ShiftPick Color / Limit Angle
Hold SpacePan
+ diff --git a/app/web-tools/dpaint/docs/licenses.html b/app/web-tools/dpaint/docs/licenses.html new file mode 100644 index 00000000..b5678694 --- /dev/null +++ b/app/web-tools/dpaint/docs/licenses.html @@ -0,0 +1,49 @@ +

Licenses

+ +DPaint.js uses (parts of) the following packages: + + +

StackBlur.js

+MIT License - Copyright (c) 2010 Mario Klingemann
+https://github.com/flozz/StackBlur/
+ +

Dithering techniques based on PhotoDemon

+Simplified BSD license - Copyright 2002-2023 by Tanner Helland
+https://github.com/tannerhelland/PhotoDemon/blob/main/Forms/Adjustments_BlackAndWhite.frm
+https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html
+ +
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +
+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE.

+ +

Sharpen Algorithm

+mikecao and jaredatdannyronsrescue
+https://gist.github.com/mikecao/65d9fc92dc7197cb8a7c?permalink_comment_id=4314200#gistcomment-4314200
+ +

Image Scale Algorithm

+MIT License - Copyright (c) 2017 Eugene Tiurin
+https://github.com/ytiurin/downscale/blob/master/src/downsample.js
+ +

RotSprite Algorithm for WebGL

+MIT License - Copyright (c) 2022 Abdelrahman Adnan Lahrech
+https://github.com/adnanlah/rotsprite-webgl/tree/master
+ +

LZW Decoder Used for decoding GIF files

+MIT License - Copyright (c) 2015 Matt Way
+https://github.com/matt-way/gifuct-js/blob/master/src/lzw.js
+ +

FileSaver.js (Used as fallback for older browsers)

+MIT License - Copyright © 2016 Eli Grey
+https://github.com/eligrey/FileSaver.js
+
+ + +Additional Retro Platform palettes by Zeb Elwood diff --git a/app/web-tools/dpaint/docs/licenses.txt b/app/web-tools/dpaint/docs/licenses.txt new file mode 100644 index 00000000..d9028396 --- /dev/null +++ b/app/web-tools/dpaint/docs/licenses.txt @@ -0,0 +1,40 @@ +Uses (parts of) the following packages: + +StackBlur.js + MIT License - Copyright (c) 2010 Mario Klingemann + https://github.com/flozz/StackBlur/ + +Dithering techniques based on PhotoDemon + Simplified BSD license - Copyright 2002-2023 by Tanner Helland + https://github.com/tannerhelland/PhotoDemon/blob/main/Forms/Adjustments_BlackAndWhite.frm + https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + +Sharpen Algorithm + mikecao and jaredatdannyronsrescue + https://gist.github.com/mikecao/65d9fc92dc7197cb8a7c?permalink_comment_id=4314200#gistcomment-4314200 + +Image Scale Algorithm + MIT License - Copyright (c) 2017 Eugene Tiurin + https://github.com/ytiurin/downscale/blob/master/src/downsample.js + +FileSaver.js - used as fallback for older browsers + MIT License - Copyright © 2016 Eli Grey + https://github.com/eligrey/FileSaver.js + +LZWDecoder - used for GIF decoding + MIT License - Copyright (c) 2015 Matt Way + https://github.com/matt-way/gifuct-js/blob/master/src/lzw.js + +Additional Retro Platform palettes by Zeb Elwood diff --git a/app/web-tools/dpaint/docs/url_parameters.html b/app/web-tools/dpaint/docs/url_parameters.html new file mode 100644 index 00000000..0a0d3501 --- /dev/null +++ b/app/web-tools/dpaint/docs/url_parameters.html @@ -0,0 +1,74 @@ +

URL parameters

+ +DPaint.js can be configured using URL parameters.
+This is useful for sharing a link to a specific image or for integrating DPaint.js in your webapp.
+
+The following parameters are supported:
+ +

file=...

+
+ You can directly preload an image into DPaint.js by adding the file parameter to the URL.
+
Warning: the server should support CORS to allow loading of assets from other domains.
+
+ If the server doesn't support CORS, or if the url is using HTTP instead of HTTPS, DPaint.js will proxy the image through it's own server.
+ Inevitably, this is slow and a bit wasteful towards my servers' bandwidth, so please try to setup proper CORS headers if you are using this feature.
+
+ Example:
+ +
+ +

presentation=true

+
+ You can start DPaint.js in presentation mode by adding the presentation=true parameter to the URL.
+ This is useful for displaying image formats that are not supported directly by the browser or by ensuring a sharp pixel zoom for your pixel-art images
+ Users can zoom and pan the image.
+ They can exit the presentation mode at any time to return to the editor.
+
+ Example:
+ +
+ +

play=true

+
+ Use in combination with the file parameter.
+ If the image is "playable" (e.g. a gif or an animation, or an image that has Color Cycle ranges), you can start playback immediately by adding the play=true parameter to the URL.
+
+ Example:
+ +
+ + +

zoom=true

+
+ Use in combination with the file parameter.
+ Set the zoom to fill the available screen space after the image is loaded.
+
+ Example:
+ +
+ +

gallery=true

+
+ Opens the gallery after startup and loads the first image from the gallery.
+
+ Example:
+ +
+ + +

postback=...

+

putback=...

+
+ You can use the postback and putback parameters to define a URL where the image data will be sent to when the user clicks the "Save" button.
+ The image will be posted as raw binary data.
+ If your endpoint expects a PUT request, use the putback parameter.
+ If your endpoint expects a POST request, use the postback parameter.
+
Warning: the server should support CORS to allow calling your endpoints from other domains.
+ +
+ Together with the file parameter, this allows a nicely integrated roundtrip to integrate DPaint.js in your own system, providing an easy image-editing flow that loads from and saves to your own API.
+
+ + + + diff --git a/app/web-tools/dpaint/docs/why.html b/app/web-tools/dpaint/docs/why.html new file mode 100644 index 00000000..b029d04b --- /dev/null +++ b/app/web-tools/dpaint/docs/why.html @@ -0,0 +1,28 @@ +

Why ?

+ +There are many many excellent paint programs out there.

+Next to the unavoidable PhotoShop there are many excellent pixel art tools like +Asesprite, +ProMotion NG, +GrafX2,... +
+And even brilliant browser based image editors like PhotoPea, +Piskel or +Pixelorama, ... + +

+ +So why make another one?

+The main reason is that I wanted a modern tool for creating Amiga Icons, a long gone ancient file format that no modern editor supports. +(And that even the icon editors on the Amiga platform struggle with)
+Next to that I was missing some pixel-specific features that I knew from the good old Deluxe Paint days.

+ +DPaint.js was born: a tool that combines 3 things: +
    +
  • the pixel approach of Deluxe Paint
  • +
  • the modern features of today's image editors
  • +
  • the platform independent nature of browser based applications.
  • +
+

+

+ diff --git a/app/web-tools/dpaint/index.html b/app/web-tools/dpaint/index.html new file mode 100644 index 00000000..33bf2eeb --- /dev/null +++ b/app/web-tools/dpaint/index.html @@ -0,0 +1,21 @@ + + + DPaint JS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/web-tools/dpaint/license.txt b/app/web-tools/dpaint/license.txt new file mode 100644 index 00000000..0fbb93e6 --- /dev/null +++ b/app/web-tools/dpaint/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-2024 Steffest + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/app/web-tools/dpaint/manifest.json b/app/web-tools/dpaint/manifest.json new file mode 100644 index 00000000..ed1ad2bd --- /dev/null +++ b/app/web-tools/dpaint/manifest.json @@ -0,0 +1,29 @@ +{ + "name": "Dpaint.js", + "icons": [ + { + "src": "_img/icon48.png", + "sizes": "48x48", + "type": "image/png", + "density": 1.0 + }, + { + "src": "_img/icon192.png", + "sizes": "192x192", + "type": "image/png", + "density": 1.0 + }, + { + "src": "_img/icon512.png", + "sizes": "512x512", + "type": "image/png", + "density": 1.0, + "purpose": "any maskable" + } + ], + "start_url": "index.html", + "display": "standalone", + "orientation": "any", + "background_color": "#282A2C", + "theme_color": "#282A2C" +} diff --git a/app/web-tools/dpaint/package-lock.json b/app/web-tools/dpaint/package-lock.json new file mode 100644 index 00000000..3a12192b --- /dev/null +++ b/app/web-tools/dpaint/package-lock.json @@ -0,0 +1,5030 @@ +{ + "name": "DPaintJS", + "version": "0.1.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "DPaintJS", + "version": "0.1.4", + "license": "MIT", + "dependencies": { + "eslint-plugin-prettier": "^4.2.1" + }, + "devDependencies": { + "@parcel/packager-raw-url": "^2.12.0", + "@parcel/transformer-webmanifest": "^2.12.0", + "assert": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.5.0", + "events": "^3.1.0", + "parcel": "^2.12.0", + "process": "^0.11.10", + "stream-browserify": "^3.0.0", + "util": "^0.12.3" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "peer": true + }, + "node_modules/@lezer/common": { + "version": "0.15.12", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz", + "integrity": "sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig==", + "dev": true + }, + "node_modules/@lezer/lr": { + "version": "0.15.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.15.8.tgz", + "integrity": "sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg==", + "dev": true, + "dependencies": { + "@lezer/common": "^0.15.0" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.8.5.tgz", + "integrity": "sha512-KPDeVScZgA1oq0CiPBcOa3kHIqU+pTOwRFDIhxvmf8CTNvqdZQYp5cCKW0bUk69VygB2PuTiINFWbY78aR2pQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.8.5.tgz", + "integrity": "sha512-w/sLhN4T7MW1nB3R/U8WK5BgQLz904wh+/SmA2jD8NnF7BLLoUgflCNxOeSPOWp8geP6nP/+VjWzZVip7rZ1ug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.8.5.tgz", + "integrity": "sha512-c0TGMbm2M55pwTDIfkDLB6BpIsgxV4PjYck2HiOX+cy/JWiBXz32lYbarPqejKs9Flm7YVAKSILUducU9g2RVg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.8.5.tgz", + "integrity": "sha512-vtbZRHH5UDlL01TT5jB576Zox3+hdyogvpcbvVJlmU5PdL3c5V7cj1EODdh1CHPksRl+cws/58ugEHi8bcj4Ww==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.8.5.tgz", + "integrity": "sha512-Xkc8IUx9aEhP0zvgeKy7IQ3ReX2N8N1L0WPcQwnZweWmOuKfwpS3GRIYqLtK5za/w3E60zhFfNdS+3pBZPytqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.8.5.tgz", + "integrity": "sha512-4wvrf5BgnR8RpogHhtpCPJMKBmvyZPhhUtEwMJbXh0ni2BucpfF07jlmyM11zRqQ2XIq6PbC2j7W7UCCcm1rRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@mischnic/json-sourcemap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz", + "integrity": "sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA==", + "dev": true, + "dependencies": { + "@lezer/common": "^0.15.7", + "@lezer/lr": "^0.15.4", + "json5": "^2.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", + "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz", + "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz", + "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz", + "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz", + "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz", + "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "peer": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/bundler-default": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.12.0.tgz", + "integrity": "sha512-3ybN74oYNMKyjD6V20c9Gerdbh7teeNvVMwIoHIQMzuIFT6IGX53PyOLlOKRLbjxMc0TMimQQxIt2eQqxR5LsA==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/graph": "3.2.0", + "@parcel/plugin": "2.12.0", + "@parcel/rust": "2.12.0", + "@parcel/utils": "2.12.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/cache": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.12.0.tgz", + "integrity": "sha512-FX5ZpTEkxvq/yvWklRHDESVRz+c7sLTXgFuzz6uEnBcXV38j6dMSikflNpHA6q/L4GKkCqRywm9R6XQwhwIMyw==", + "dev": true, + "dependencies": { + "@parcel/fs": "2.12.0", + "@parcel/logger": "2.12.0", + "@parcel/utils": "2.12.0", + "lmdb": "2.8.5" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.12.0" + } + }, + "node_modules/@parcel/codeframe": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.12.0.tgz", + "integrity": "sha512-v2VmneILFiHZJTxPiR7GEF1wey1/IXPdZMcUlNXBiPZyWDfcuNgGGVQkx/xW561rULLIvDPharOMdxz5oHOKQg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/compressor-raw": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.12.0.tgz", + "integrity": "sha512-h41Q3X7ZAQ9wbQ2csP8QGrwepasLZdXiuEdpUryDce6rF9ZiHoJ97MRpdLxOhOPyASTw/xDgE1xyaPQr0Q3f5A==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/config-default": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.12.0.tgz", + "integrity": "sha512-dPNe2n9eEsKRc1soWIY0yToMUPirPIa2QhxcCB3Z5RjpDGIXm0pds+BaiqY6uGLEEzsjhRO0ujd4v2Rmm0vuFg==", + "dev": true, + "dependencies": { + "@parcel/bundler-default": "2.12.0", + "@parcel/compressor-raw": "2.12.0", + "@parcel/namer-default": "2.12.0", + "@parcel/optimizer-css": "2.12.0", + "@parcel/optimizer-htmlnano": "2.12.0", + "@parcel/optimizer-image": "2.12.0", + "@parcel/optimizer-svgo": "2.12.0", + "@parcel/optimizer-swc": "2.12.0", + "@parcel/packager-css": "2.12.0", + "@parcel/packager-html": "2.12.0", + "@parcel/packager-js": "2.12.0", + "@parcel/packager-raw": "2.12.0", + "@parcel/packager-svg": "2.12.0", + "@parcel/packager-wasm": "2.12.0", + "@parcel/reporter-dev-server": "2.12.0", + "@parcel/resolver-default": "2.12.0", + "@parcel/runtime-browser-hmr": "2.12.0", + "@parcel/runtime-js": "2.12.0", + "@parcel/runtime-react-refresh": "2.12.0", + "@parcel/runtime-service-worker": "2.12.0", + "@parcel/transformer-babel": "2.12.0", + "@parcel/transformer-css": "2.12.0", + "@parcel/transformer-html": "2.12.0", + "@parcel/transformer-image": "2.12.0", + "@parcel/transformer-js": "2.12.0", + "@parcel/transformer-json": "2.12.0", + "@parcel/transformer-postcss": "2.12.0", + "@parcel/transformer-posthtml": "2.12.0", + "@parcel/transformer-raw": "2.12.0", + "@parcel/transformer-react-refresh-wrap": "2.12.0", + "@parcel/transformer-svg": "2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.12.0" + } + }, + "node_modules/@parcel/core": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.12.0.tgz", + "integrity": "sha512-s+6pwEj+GfKf7vqGUzN9iSEPueUssCCQrCBUlcAfKrJe0a22hTUCjewpB0I7lNrCIULt8dkndD+sMdOrXsRl6Q==", + "dev": true, + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", + "@parcel/cache": "2.12.0", + "@parcel/diagnostic": "2.12.0", + "@parcel/events": "2.12.0", + "@parcel/fs": "2.12.0", + "@parcel/graph": "3.2.0", + "@parcel/logger": "2.12.0", + "@parcel/package-manager": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/profiler": "2.12.0", + "@parcel/rust": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/types": "2.12.0", + "@parcel/utils": "2.12.0", + "@parcel/workers": "2.12.0", + "abortcontroller-polyfill": "^1.1.9", + "base-x": "^3.0.8", + "browserslist": "^4.6.6", + "clone": "^2.1.1", + "dotenv": "^7.0.0", + "dotenv-expand": "^5.1.0", + "json5": "^2.2.0", + "msgpackr": "^1.9.9", + "nullthrows": "^1.1.1", + "semver": "^7.5.2" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/diagnostic": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.12.0.tgz", + "integrity": "sha512-8f1NOsSFK+F4AwFCKynyIu9Kr/uWHC+SywAv4oS6Bv3Acig0gtwUjugk0C9UaB8ztBZiW5TQZhw+uPZn9T/lJA==", + "dev": true, + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/events": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.12.0.tgz", + "integrity": "sha512-nmAAEIKLjW1kB2cUbCYSmZOGbnGj8wCzhqnK727zCCWaA25ogzAtt657GPOeFyqW77KyosU728Tl63Fc8hphIA==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/fs": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.12.0.tgz", + "integrity": "sha512-NnFkuvou1YBtPOhTdZr44WN7I60cGyly2wpHzqRl62yhObyi1KvW0SjwOMa0QGNcBOIzp4G0CapoZ93hD0RG5Q==", + "dev": true, + "dependencies": { + "@parcel/rust": "2.12.0", + "@parcel/types": "2.12.0", + "@parcel/utils": "2.12.0", + "@parcel/watcher": "^2.0.7", + "@parcel/workers": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.12.0" + } + }, + "node_modules/@parcel/graph": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.2.0.tgz", + "integrity": "sha512-xlrmCPqy58D4Fg5umV7bpwDx5Vyt7MlnQPxW68vae5+BA4GSWetfZt+Cs5dtotMG2oCHzZxhIPt7YZ7NRyQzLA==", + "dev": true, + "dependencies": { + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/logger": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.12.0.tgz", + "integrity": "sha512-cJ7Paqa7/9VJ7C+KwgJlwMqTQBOjjn71FbKk0G07hydUEBISU2aDfmc/52o60ErL9l+vXB26zTrIBanbxS8rVg==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/events": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/markdown-ansi": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.12.0.tgz", + "integrity": "sha512-WZz3rzL8k0H3WR4qTHX6Ic8DlEs17keO9gtD4MNGyMNQbqQEvQ61lWJaIH0nAtgEetu0SOITiVqdZrb8zx/M7w==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/namer-default": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.12.0.tgz", + "integrity": "sha512-9DNKPDHWgMnMtqqZIMiEj/R9PNWW16lpnlHjwK3ciRlMPgjPJ8+UNc255teZODhX0T17GOzPdGbU/O/xbxVPzA==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/node-resolver-core": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.3.0.tgz", + "integrity": "sha512-rhPW9DYPEIqQBSlYzz3S0AjXxjN6Ub2yS6tzzsW/4S3Gpsgk/uEq4ZfxPvoPf/6TgZndVxmKwpmxaKtGMmf3cA==", + "dev": true, + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", + "@parcel/diagnostic": "2.12.0", + "@parcel/fs": "2.12.0", + "@parcel/rust": "2.12.0", + "@parcel/utils": "2.12.0", + "nullthrows": "^1.1.1", + "semver": "^7.5.2" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-css": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.12.0.tgz", + "integrity": "sha512-ifbcC97fRzpruTjaa8axIFeX4MjjSIlQfem3EJug3L2AVqQUXnM1XO8L0NaXGNLTW2qnh1ZjIJ7vXT/QhsphsA==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.12.0", + "browserslist": "^4.6.6", + "lightningcss": "^1.22.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-htmlnano": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.12.0.tgz", + "integrity": "sha512-MfPMeCrT8FYiOrpFHVR+NcZQlXAptK2r4nGJjfT+ndPBhEEZp4yyL7n1y7HfX9geg5altc4WTb4Gug7rCoW8VQ==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "htmlnano": "^2.0.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "svgo": "^2.4.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@parcel/optimizer-image": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.12.0.tgz", + "integrity": "sha512-bo1O7raeAIbRU5nmNVtx8divLW9Xqn0c57GVNGeAK4mygnQoqHqRZ0mR9uboh64pxv6ijXZHPhKvU9HEpjPjBQ==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/rust": "2.12.0", + "@parcel/utils": "2.12.0", + "@parcel/workers": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.12.0" + } + }, + "node_modules/@parcel/optimizer-svgo": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.12.0.tgz", + "integrity": "sha512-Kyli+ZZXnoonnbeRQdoWwee9Bk2jm/49xvnfb+2OO8NN0d41lblBoRhOyFiScRnJrw7eVl1Xrz7NTkXCIO7XFQ==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0", + "svgo": "^2.4.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-svgo/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/@parcel/optimizer-svgo/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@parcel/optimizer-svgo/node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@parcel/optimizer-svgo/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "node_modules/@parcel/optimizer-svgo/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@parcel/optimizer-swc": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.12.0.tgz", + "integrity": "sha512-iBi6LZB3lm6WmbXfzi8J3DCVPmn4FN2lw7DGXxUXu7MouDPVWfTsM6U/5TkSHJRNRogZ2gqy5q9g34NPxHbJcw==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.12.0", + "@swc/core": "^1.3.36", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/package-manager": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.12.0.tgz", + "integrity": "sha512-0nvAezcjPx9FT+hIL+LS1jb0aohwLZXct7jAh7i0MLMtehOi0z1Sau+QpgMlA9rfEZZ1LIeFdnZZwqSy7Ccspw==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/fs": "2.12.0", + "@parcel/logger": "2.12.0", + "@parcel/node-resolver-core": "3.3.0", + "@parcel/types": "2.12.0", + "@parcel/utils": "2.12.0", + "@parcel/workers": "2.12.0", + "@swc/core": "^1.3.36", + "semver": "^7.5.2" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.12.0" + } + }, + "node_modules/@parcel/packager-css": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.12.0.tgz", + "integrity": "sha512-j3a/ODciaNKD19IYdWJT+TP+tnhhn5koBGBWWtrKSu0UxWpnezIGZetit3eE+Y9+NTePalMkvpIlit2eDhvfJA==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.12.0", + "lightningcss": "^1.22.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-html": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.12.0.tgz", + "integrity": "sha512-PpvGB9hFFe+19NXGz2ApvPrkA9GwEqaDAninT+3pJD57OVBaxB8U+HN4a5LICKxjUppPPqmrLb6YPbD65IX4RA==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/types": "2.12.0", + "@parcel/utils": "2.12.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-js": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.12.0.tgz", + "integrity": "sha512-viMF+FszITRRr8+2iJyk+4ruGiL27Y6AF7hQ3xbJfzqnmbOhGFtLTQwuwhOLqN/mWR2VKdgbLpZSarWaO3yAMg==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/rust": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/types": "2.12.0", + "@parcel/utils": "2.12.0", + "globals": "^13.2.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-raw": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.12.0.tgz", + "integrity": "sha512-tJZqFbHqP24aq1F+OojFbQIc09P/u8HAW5xfndCrFnXpW4wTgM3p03P0xfw3gnNq+TtxHJ8c3UFE5LnXNNKhYA==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-raw-url": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-raw-url/-/packager-raw-url-2.12.0.tgz", + "integrity": "sha512-sH7cvLbotS+qknhQUCGfd9mslQr4KcanlZE5CgzM0uGn3SnyZoKznqHrbouzgnIP/LHgXKOKmMaNjPLtVe4rcA==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-svg": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.12.0.tgz", + "integrity": "sha512-ldaGiacGb2lLqcXas97k8JiZRbAnNREmcvoY2W2dvW4loVuDT9B9fU777mbV6zODpcgcHWsLL3lYbJ5Lt3y9cg==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/types": "2.12.0", + "@parcel/utils": "2.12.0", + "posthtml": "^0.16.4" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-wasm": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-wasm/-/packager-wasm-2.12.0.tgz", + "integrity": "sha512-fYqZzIqO9fGYveeImzF8ll6KRo2LrOXfD+2Y5U3BiX/wp9wv17dz50QLDQm9hmTcKGWxK4yWqKQh+Evp/fae7A==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0" + }, + "engines": { + "node": ">=12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/plugin": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.12.0.tgz", + "integrity": "sha512-nc/uRA8DiMoe4neBbzV6kDndh/58a4wQuGKw5oEoIwBCHUvE2W8ZFSu7ollSXUGRzfacTt4NdY8TwS73ScWZ+g==", + "dev": true, + "dependencies": { + "@parcel/types": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/profiler": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.12.0.tgz", + "integrity": "sha512-q53fvl5LDcFYzMUtSusUBZSjQrKjMlLEBgKeQHFwkimwR1mgoseaDBDuNz0XvmzDzF1UelJ02TUKCGacU8W2qA==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/events": "2.12.0", + "chrome-trace-event": "^1.0.2" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-cli": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.12.0.tgz", + "integrity": "sha512-TqKsH4GVOLPSCanZ6tcTPj+rdVHERnt5y4bwTM82cajM21bCX1Ruwp8xOKU+03091oV2pv5ieB18pJyRF7IpIw==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/types": "2.12.0", + "@parcel/utils": "2.12.0", + "chalk": "^4.1.0", + "term-size": "^2.2.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-dev-server": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.12.0.tgz", + "integrity": "sha512-tIcDqRvAPAttRlTV28dHcbWT5K2r/MBFks7nM4nrEDHWtnrCwimkDmZTc1kD8QOCCjGVwRHcQybpHvxfwol6GA==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-tracer": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.12.0.tgz", + "integrity": "sha512-g8rlu9GxB8Ut/F8WGx4zidIPQ4pcYFjU9bZO+fyRIPrSUFH2bKijCnbZcr4ntqzDGx74hwD6cCG4DBoleq2UlQ==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0", + "chrome-trace-event": "^1.0.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/resolver-default": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.12.0.tgz", + "integrity": "sha512-uuhbajTax37TwCxu7V98JtRLiT6hzE4VYSu5B7Qkauy14/WFt2dz6GOUXPgVsED569/hkxebPx3KCMtZW6cHHA==", + "dev": true, + "dependencies": { + "@parcel/node-resolver-core": "3.3.0", + "@parcel/plugin": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-browser-hmr": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.12.0.tgz", + "integrity": "sha512-4ZLp2FWyD32r0GlTulO3+jxgsA3oO1P1b5oO2IWuWilfhcJH5LTiazpL5YdusUjtNn9PGN6QLAWfxmzRIfM+Ow==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-js": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.12.0.tgz", + "integrity": "sha512-sBerP32Z1crX5PfLNGDSXSdqzlllM++GVnVQVeM7DgMKS8JIFG3VLi28YkX+dYYGtPypm01JoIHCkvwiZEcQJg==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-react-refresh": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.12.0.tgz", + "integrity": "sha512-SCHkcczJIDFTFdLTzrHTkQ0aTrX3xH6jrA4UsCBL6ji61+w+ohy4jEEe9qCgJVXhnJfGLE43HNXek+0MStX+Mw==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0", + "react-error-overlay": "6.0.9", + "react-refresh": "^0.9.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-service-worker": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.12.0.tgz", + "integrity": "sha512-BXuMBsfiwpIEnssn+jqfC3jkgbS8oxeo3C7xhSQsuSv+AF2FwY3O3AO1c1RBskEW3XrBLNINOJujroNw80VTKA==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/rust": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/rust/-/rust-2.12.0.tgz", + "integrity": "sha512-005cldMdFZFDPOjbDVEXcINQ3wT4vrxvSavRWI3Az0e3E18exO/x/mW9f648KtXugOXMAqCEqhFHcXECL9nmMw==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/source-map": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", + "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": "^12.18.3 || >=14" + } + }, + "node_modules/@parcel/transformer-babel": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.12.0.tgz", + "integrity": "sha512-zQaBfOnf/l8rPxYGnsk/ufh/0EuqvmnxafjBIpKZ//j6rGylw5JCqXSb1QvvAqRYruKeccxGv7+HrxpqKU6V4A==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.12.0", + "browserslist": "^4.6.6", + "json5": "^2.2.0", + "nullthrows": "^1.1.1", + "semver": "^7.5.2" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-css": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.12.0.tgz", + "integrity": "sha512-vXhOqoAlQGATYyQ433Z1DXKmiKmzOAUmKysbYH3FD+LKEKLMEl/pA14goqp00TW+A/EjtSKKyeMyHlMIIUqj4Q==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.12.0", + "browserslist": "^4.6.6", + "lightningcss": "^1.22.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-html": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.12.0.tgz", + "integrity": "sha512-5jW4dFFBlYBvIQk4nrH62rfA/G/KzVzEDa6S+Nne0xXhglLjkm64Ci9b/d4tKZfuGWUbpm2ASAq8skti/nfpXw==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/rust": "2.12.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^7.5.2", + "srcset": "4" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-html/node_modules/srcset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", + "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@parcel/transformer-image": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.12.0.tgz", + "integrity": "sha512-8hXrGm2IRII49R7lZ0RpmNk27EhcsH+uNKsvxuMpXPuEnWgC/ha/IrjaI29xCng1uGur74bJF43NUSQhR4aTdw==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0", + "@parcel/workers": "2.12.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "peerDependencies": { + "@parcel/core": "^2.12.0" + } + }, + "node_modules/@parcel/transformer-js": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.12.0.tgz", + "integrity": "sha512-OSZpOu+FGDbC/xivu24v092D9w6EGytB3vidwbdiJ2FaPgfV7rxS0WIUjH4I0OcvHAcitArRXL0a3+HrNTdQQw==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/rust": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/utils": "2.12.0", + "@parcel/workers": "2.12.0", + "@swc/helpers": "^0.5.0", + "browserslist": "^4.6.6", + "nullthrows": "^1.1.1", + "regenerator-runtime": "^0.13.7", + "semver": "^7.5.2" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.12.0" + } + }, + "node_modules/@parcel/transformer-json": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.12.0.tgz", + "integrity": "sha512-Utv64GLRCQILK5r0KFs4o7I41ixMPllwOLOhkdjJKvf1hZmN6WqfOmB1YLbWS/y5Zb/iB52DU2pWZm96vLFQZQ==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "json5": "^2.2.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-postcss": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.12.0.tgz", + "integrity": "sha512-FZqn+oUtiLfPOn67EZxPpBkfdFiTnF4iwiXPqvst3XI8H+iC+yNgzmtJkunOOuylpYY6NOU5jT8d7saqWSDv2Q==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/rust": "2.12.0", + "@parcel/utils": "2.12.0", + "clone": "^2.1.1", + "nullthrows": "^1.1.1", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-posthtml": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.12.0.tgz", + "integrity": "sha512-z6Z7rav/pcaWdeD+2sDUcd0mmNZRUvtHaUGa50Y2mr+poxrKilpsnFMSiWBT+oOqPt7j71jzDvrdnAF4XkCljg==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-raw": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.12.0.tgz", + "integrity": "sha512-Ht1fQvXxix0NncdnmnXZsa6hra20RXYh1VqhBYZLsDfkvGGFnXIgO03Jqn4Z8MkKoa0tiNbDhpKIeTjyclbBxQ==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-react-refresh-wrap": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.12.0.tgz", + "integrity": "sha512-GE8gmP2AZtkpBIV5vSCVhewgOFRhqwdM5Q9jNPOY5PKcM3/Ff0qCqDiTzzGLhk0/VMBrdjssrfZkVx6S/lHdJw==", + "dev": true, + "dependencies": { + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0", + "react-refresh": "^0.9.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-svg": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.12.0.tgz", + "integrity": "sha512-cZJqGRJ4JNdYcb+vj94J7PdOuTnwyy45dM9xqbIMH+HSiiIkfrMsdEwYft0GTyFTdsnf+hdHn3tau7Qa5hhX+A==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/rust": "2.12.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-webmanifest": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-webmanifest/-/transformer-webmanifest-2.12.0.tgz", + "integrity": "sha512-suiUv9DDEpyryHtzahbIeJSZTIeE/t4cdrU0Ikb/O46wsy5RLo59nE4E6TGWM84R7fQO8m/MhzeQM5Y3NV6jKg==", + "dev": true, + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", + "@parcel/diagnostic": "2.12.0", + "@parcel/plugin": "2.12.0", + "@parcel/utils": "2.12.0" + }, + "engines": { + "parcel": "^2.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.12.0.tgz", + "integrity": "sha512-8zAFiYNCwNTQcglIObyNwKfRYQK5ELlL13GuBOrSMxueUiI5ylgsGbTS1N7J3dAGZixHO8KhHGv5a71FILn9rQ==", + "dev": true, + "dependencies": { + "@parcel/cache": "2.12.0", + "@parcel/diagnostic": "2.12.0", + "@parcel/fs": "2.12.0", + "@parcel/package-manager": "2.12.0", + "@parcel/source-map": "^2.1.1", + "@parcel/workers": "2.12.0", + "utility-types": "^3.10.0" + } + }, + "node_modules/@parcel/utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.12.0.tgz", + "integrity": "sha512-z1JhLuZ8QmDaYoEIuUCVZlhcFrS7LMfHrb2OCRui5SQFntRWBH2fNM6H/fXXUkT9SkxcuFP2DUA6/m4+Gkz72g==", + "dev": true, + "dependencies": { + "@parcel/codeframe": "2.12.0", + "@parcel/diagnostic": "2.12.0", + "@parcel/logger": "2.12.0", + "@parcel/markdown-ansi": "2.12.0", + "@parcel/rust": "2.12.0", + "@parcel/source-map": "^2.1.1", + "chalk": "^4.1.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.0.tgz", + "integrity": "sha512-XJLGVL0DEclX5pcWa2N9SX1jCGTDd8l972biNooLFtjneuGqodupPQh6XseXIBBeVIMaaJ7bTcs3qGvXwsp4vg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.4.0", + "@parcel/watcher-darwin-arm64": "2.4.0", + "@parcel/watcher-darwin-x64": "2.4.0", + "@parcel/watcher-freebsd-x64": "2.4.0", + "@parcel/watcher-linux-arm-glibc": "2.4.0", + "@parcel/watcher-linux-arm64-glibc": "2.4.0", + "@parcel/watcher-linux-arm64-musl": "2.4.0", + "@parcel/watcher-linux-x64-glibc": "2.4.0", + "@parcel/watcher-linux-x64-musl": "2.4.0", + "@parcel/watcher-win32-arm64": "2.4.0", + "@parcel/watcher-win32-ia32": "2.4.0", + "@parcel/watcher-win32-x64": "2.4.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.0.tgz", + "integrity": "sha512-+fPtO/GsbYX1LJnCYCaDVT3EOBjvSFdQN9Mrzh9zWAOOfvidPWyScTrHIZHHfJBvlHzNA0Gy0U3NXFA/M7PHUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.0.tgz", + "integrity": "sha512-T/At5pansFuQ8VJLRx0C6C87cgfqIYhW2N/kBfLCUvDhCah0EnLLwaD/6MW3ux+rpgkpQAnMELOCTKlbwncwiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.0.tgz", + "integrity": "sha512-vZMv9jl+szz5YLsSqEGCMSllBl1gU1snfbRL5ysJU03MEa6gkVy9OMcvXV1j4g0++jHEcvzhs3Z3LpeEbVmY6Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.0.tgz", + "integrity": "sha512-dHTRMIplPDT1M0+BkXjtMN+qLtqq24sLDUhmU+UxxLP2TEY2k8GIoqIJiVrGWGomdWsy5IO27aDV1vWyQ6gfHA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.0.tgz", + "integrity": "sha512-9NQXD+qk46RwATNC3/UB7HWurscY18CnAPMTFcI9Y8CTbtm63/eex1SNt+BHFinEQuLBjaZwR2Lp+n7pmEJPpQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.0.tgz", + "integrity": "sha512-QuJTAQdsd7PFW9jNGaV9Pw+ZMWV9wKThEzzlY3Lhnnwy7iW23qtQFPql8iEaSFMCVI5StNNmONUopk+MFKpiKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.0.tgz", + "integrity": "sha512-oyN+uA9xcTDo/45bwsd6TFHa7Lc7hKujyMlvwrCLvSckvWogndCEoVYFNfZ6JJ2KNL/6fFiGPcbjp8jJmEh5Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.0.tgz", + "integrity": "sha512-KphV8awJmxU3q52JQvJot0QMu07CIyEjV+2Tb2ZtbucEgqyRcxOBDMsqp1JNq5nuDXtcCC0uHQICeiEz38dPBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.0.tgz", + "integrity": "sha512-7jzcOonpXNWcSijPpKD5IbC6xC7yTibjJw9jviVzZostYLGxbz8LDJLUnLzLzhASPlPGgpeKLtFUMjAAzM+gSA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.0.tgz", + "integrity": "sha512-NOej2lqlq8bQNYhUMnOD0nwvNql8ToQF+1Zhi9ULZoG+XTtJ9hNnCFfyICxoZLXor4bBPTOnzs/aVVoefYnjIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.0.tgz", + "integrity": "sha512-IO/nM+K2YD/iwjWAfHFMBPz4Zqn6qBDqZxY4j2n9s+4+OuTSRM/y/irksnuqcspom5DjkSeF9d0YbO+qpys+JA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.0.tgz", + "integrity": "sha512-pAUyUVjfFjWaf/pShmJpJmNxZhbMvJASUpdes9jL6bTEJ+gDxPRSpXTIemNyNsb9AtbiGXs9XduP1reThmd+dA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/workers": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.12.0.tgz", + "integrity": "sha512-zv5We5Jmb+ZWXlU6A+AufyjY4oZckkxsZ8J4dvyWL0W8IQvGO1JB4FGeryyttzQv3RM3OxcN/BpTGPiDG6keBw==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.12.0", + "@parcel/logger": "2.12.0", + "@parcel/profiler": "2.12.0", + "@parcel/types": "2.12.0", + "@parcel/utils": "2.12.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.12.0" + } + }, + "node_modules/@swc/core": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.6.1.tgz", + "integrity": "sha512-Yz5uj5hNZpS5brLtBvKY0L4s2tBAbQ4TjmW8xF1EC3YLFxQRrUjMP49Zm1kp/KYyYvTkSaG48Ffj2YWLu9nChw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.8" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.6.1", + "@swc/core-darwin-x64": "1.6.1", + "@swc/core-linux-arm-gnueabihf": "1.6.1", + "@swc/core-linux-arm64-gnu": "1.6.1", + "@swc/core-linux-arm64-musl": "1.6.1", + "@swc/core-linux-x64-gnu": "1.6.1", + "@swc/core-linux-x64-musl": "1.6.1", + "@swc/core-win32-arm64-msvc": "1.6.1", + "@swc/core-win32-ia32-msvc": "1.6.1", + "@swc/core-win32-x64-msvc": "1.6.1" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.1.tgz", + "integrity": "sha512-u6GdwOXsOEdNAdSI6nWq6G2BQw5HiSNIZVcBaH1iSvBnxZvWbnIKyDiZKaYnDwTLHLzig2GuUjjE2NaCJPy4jg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.6.1.tgz", + "integrity": "sha512-/tXwQibkDNLVbAtr7PUQI0iQjoB708fjhDDDfJ6WILSBVZ3+qs/LHjJ7jHwumEYxVq1XA7Fv2Q7SE/ZSQoWHcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.1.tgz", + "integrity": "sha512-aDgipxhJTms8iH78emHVutFR2c16LNhO+NTRCdYi+X4PyIn58/DyYTH6VDZ0AeEcS5f132ZFldU5AEgExwihXA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.1.tgz", + "integrity": "sha512-XkJ+eO4zUKG5g458RyhmKPyBGxI0FwfWFgpfIj5eDybxYJ6s4HBT5MoxyBLorB5kMlZ0XoY/usUMobPVY3nL0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.1.tgz", + "integrity": "sha512-dr6YbLBg/SsNxs1hDqJhxdcrS8dGMlOXJwXIrUvACiA8jAd6S5BxYCaqsCefLYXtaOmu0bbx1FB/evfodqB70Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.1.tgz", + "integrity": "sha512-A0b/3V+yFy4LXh3O9umIE7LXPC7NBWdjl6AQYqymSMcMu0EOb1/iygA6s6uWhz9y3e172Hpb9b/CGsuD8Px/bg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.1.tgz", + "integrity": "sha512-5dJjlzZXhC87nZZZWbpiDP8kBIO0ibis893F/rtPIQBI5poH+iJuA32EU3wN4/WFHeK4et8z6SGSVghPtWyk4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.1.tgz", + "integrity": "sha512-HBi1ZlwvfcUibLtT3g/lP57FaDPC799AD6InolB2KSgkqyBbZJ9wAXM8/CcH67GLIP0tZ7FqblrJTzGXxetTJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.1.tgz", + "integrity": "sha512-AKqHohlWERclexar5y6ux4sQ8yaMejEXNxeKXm7xPhXrp13/1p4/I3E5bPVX/jMnvpm4HpcKSP0ee2WsqmhhPw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.1.tgz", + "integrity": "sha512-0dLdTLd+ONve8kgC5T6VQ2Y5G+OZ7y0ujjapnK66wpvCBM6BKYGdT/OKhZKZydrC5gUKaxFN6Y5oOt9JOFUrOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz", + "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@swc/types": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.8.tgz", + "integrity": "sha512-RNFA3+7OJFNYY78x0FYwi1Ow+iF1eF5WvmfY1nXPOEH4R2p/D4Cr1vzje7dNAI2aLFqpv8Wyz4oKSWqIZArpQA==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "peer": true + }, + "node_modules/abortcontroller-polyfill": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", + "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dev": true, + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "peer": true + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001588", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", + "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "peer": true + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/css-select/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "peer": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", + "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.675", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.675.tgz", + "integrity": "sha512-+1u3F/XPNIdUwv8i1lDxHAxCvNNU0QIqgb1Ycn+Jnng8ITzWSvUqixRSM7NOazJuwhf65IV17f/VbKj8DmL26A==", + "dev": true + }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "peer": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "peer": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "peer": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.0.tgz", + "integrity": "sha512-noqGuLw158+DuD9UPRKHpJ2hGxpFyDlYYrfM0mWt4XhT4n0lwzTLh70Tkdyy4kyTmyTT9Bv7bWAJqw7cgkEXDg==", + "peer": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "peer": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "peer": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlnano": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.1.tgz", + "integrity": "sha512-kAERyg/LuNZYmdqgCdYvugyLWNFAm8MWXpQMz1pLpetmCbFwoMxvkSoaAMlFrOC4OKTWI4KlZGT/RsNxg4ghOw==", + "dev": true, + "dependencies": { + "cosmiconfig": "^9.0.0", + "posthtml": "^0.16.5", + "timsort": "^0.3.0" + }, + "peerDependencies": { + "cssnano": "^7.0.0", + "postcss": "^8.3.11", + "purgecss": "^6.0.0", + "relateurl": "^0.2.7", + "srcset": "5.0.1", + "svgo": "^3.0.2", + "terser": "^5.10.0", + "uncss": "^0.17.3" + }, + "peerDependenciesMeta": { + "cssnano": { + "optional": true + }, + "postcss": { + "optional": true + }, + "purgecss": { + "optional": true + }, + "relateurl": { + "optional": true + }, + "srcset": { + "optional": true + }, + "svgo": { + "optional": true + }, + "terser": { + "optional": true + }, + "uncss": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "peer": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.25.1.tgz", + "integrity": "sha512-V0RMVZzK1+rCHpymRv4URK2lNhIRyO8g7U7zOFwVAhJuat74HtkjIQpQRKNCwFEYkRGpafOpmXXLoaoBcyVtBg==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.25.1", + "lightningcss-darwin-x64": "1.25.1", + "lightningcss-freebsd-x64": "1.25.1", + "lightningcss-linux-arm-gnueabihf": "1.25.1", + "lightningcss-linux-arm64-gnu": "1.25.1", + "lightningcss-linux-arm64-musl": "1.25.1", + "lightningcss-linux-x64-gnu": "1.25.1", + "lightningcss-linux-x64-musl": "1.25.1", + "lightningcss-win32-x64-msvc": "1.25.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.25.1.tgz", + "integrity": "sha512-G4Dcvv85bs5NLENcu/s1f7ehzE3D5ThnlWSDwE190tWXRQCQaqwcuHe+MGSVI/slm0XrxnaayXY+cNl3cSricw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.25.1.tgz", + "integrity": "sha512-dYWuCzzfqRueDSmto6YU5SoGHvZTMU1Em9xvhcdROpmtOQLorurUZz8+xFxZ51lCO2LnYbfdjZ/gCqWEkwixNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.25.1.tgz", + "integrity": "sha512-hXoy2s9A3KVNAIoKz+Fp6bNeY+h9c3tkcx1J3+pS48CqAt+5bI/R/YY4hxGL57fWAIquRjGKW50arltD6iRt/w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.25.1.tgz", + "integrity": "sha512-tWyMgHFlHlp1e5iW3EpqvH5MvsgoN7ZkylBbG2R2LWxnvH3FuWCJOhtGcYx9Ks0Kv0eZOBud789odkYLhyf1ng==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.25.1.tgz", + "integrity": "sha512-Xjxsx286OT9/XSnVLIsFEDyDipqe4BcLeB4pXQ/FEA5+2uWCCuAEarUNQumRucnj7k6ftkAHUEph5r821KBccQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.25.1.tgz", + "integrity": "sha512-IhxVFJoTW8wq6yLvxdPvyHv4NjzcpN1B7gjxrY3uaykQNXPHNIpChLB52+wfH+yS58zm1PL4LemUp8u9Cfp6Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.25.1.tgz", + "integrity": "sha512-RXIaru79KrREPEd6WLXfKfIp4QzoppZvD3x7vuTKkDA64PwTzKJ2jaC43RZHRt8BmyIkRRlmywNhTRMbmkPYpA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.25.1.tgz", + "integrity": "sha512-TdcNqFsAENEEFr8fJWg0Y4fZ/nwuqTRsIr7W7t2wmDUlA8eSXVepeeONYcb+gtTj1RaXn/WgNLB45SFkz+XBZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.25.1.tgz", + "integrity": "sha512-9KZZkmmy9oGDSrnyHuxP6iMhbsgChUiu/NSgOx+U1I/wTngBStDf2i2aGRCHvFqj19HqqBEI4WuGVQBa2V6e0A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lmdb": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.8.5.tgz", + "integrity": "sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "msgpackr": "^1.9.5", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.1.1", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "2.8.5", + "@lmdb/lmdb-darwin-x64": "2.8.5", + "@lmdb/lmdb-linux-arm": "2.8.5", + "@lmdb/lmdb-linux-arm64": "2.8.5", + "@lmdb/lmdb-linux-x64": "2.8.5", + "@lmdb/lmdb-win32-x64": "2.8.5" + } + }, + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "peer": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "peer": true + }, + "node_modules/msgpackr": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz", + "integrity": "sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz", + "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.0.7" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" + } + }, + "node_modules/msgpackr-extract/node_modules/node-gyp-build-optional-packages": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", + "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "peer": true + }, + "node_modules/node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "dev": true, + "engines": { + "node": "^16 || ^18 || >= 20" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "peer": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ordered-binary": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz", + "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==", + "dev": true + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parcel": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.12.0.tgz", + "integrity": "sha512-W+gxAq7aQ9dJIg/XLKGcRT0cvnStFAQHPaI0pvD0U2l6IVLueUAm3nwN7lkY62zZNmlvNx6jNtE4wlbS+CyqSg==", + "dev": true, + "dependencies": { + "@parcel/config-default": "2.12.0", + "@parcel/core": "2.12.0", + "@parcel/diagnostic": "2.12.0", + "@parcel/events": "2.12.0", + "@parcel/fs": "2.12.0", + "@parcel/logger": "2.12.0", + "@parcel/package-manager": "2.12.0", + "@parcel/reporter-cli": "2.12.0", + "@parcel/reporter-dev-server": "2.12.0", + "@parcel/reporter-tracer": "2.12.0", + "@parcel/utils": "2.12.0", + "chalk": "^4.1.0", + "commander": "^7.0.0", + "get-port": "^4.2.0" + }, + "bin": { + "parcel": "lib/bin.js" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/posthtml": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", + "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", + "dev": true, + "dependencies": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/posthtml-parser": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz", + "integrity": "sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==", + "dev": true, + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, + "dependencies": { + "is-json": "^2.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml/node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/react-error-overlay": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", + "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", + "dev": true + }, + "node_modules/react-refresh": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", + "integrity": "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "peer": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/srcset": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-5.0.1.tgz", + "integrity": "sha512-/P1UYbGfJVlxZag7aABNRrulEXAwCSDo7fklafOQrantuPTDmYgijJMks2zusPCVzgW9+4P69mq7w6pYuZpgxw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "peer": true + }, + "node_modules/timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "peer": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/app/web-tools/dpaint/package.json b/app/web-tools/dpaint/package.json new file mode 100644 index 00000000..14a21ea5 --- /dev/null +++ b/app/web-tools/dpaint/package.json @@ -0,0 +1,30 @@ +{ + "name": "DPaintJS", + "version": "0.1.4", + "description": "Webbased image editor with a focus on retro Amiga file formats in plain JavaScript", + "repository": "https://github.com/steffest/dpaint-js", + "author": "Steffest", + "license": "MIT", + "devDependencies": { + "@parcel/packager-raw-url": "^2.12.0", + "@parcel/transformer-webmanifest": "^2.12.0", + "assert": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.5.0", + "events": "^3.1.0", + "parcel": "^2.12.0", + "process": "^0.11.10", + "stream-browserify": "^3.0.0", + "util": "^0.12.3" + }, + "scripts": { + "clean": "rm -rf dist && rm -rf .parcel-cache", + "cleanwin": "del /q .\\.parcel-cache\\* && del /q .\\dist\\*", + "build": "parcel build --public-url ./", + "start": "parcel index.html" + }, + "source": "index.html", + "dependencies": { + "eslint-plugin-prettier": "^4.2.1" + } +} diff --git a/app/web-tools/dpaint/scrap/dev.js b/app/web-tools/dpaint/scrap/dev.js new file mode 100644 index 00000000..600ead2a --- /dev/null +++ b/app/web-tools/dpaint/scrap/dev.js @@ -0,0 +1,46 @@ +import Modal, {DIALOG} from "../_script/ui/modal.js"; +import UI from "../_script/ui/ui.js"; +import Input from "../_script/ui/input.js"; + +let App = function(){ + let me = {} + let canvas; + let canvasOut; + + me.init = function(){ + Input.init(); + canvas = document.createElement("canvas"); + canvasOut = document.createElement("canvas"); + canvasOut.width = canvas.width = 256; + canvasOut.height = canvas.height = 256; + + document.body.appendChild(canvas); + document.body.appendChild(canvasOut); + let image = new Image(); + image.onload = ()=>{ + canvas.getContext("2d").drawImage(image,0,0); + } + image.src = "scrap/face1.png"; + + Modal.show(DIALOG.EFFECTS); + } + + window.addEventListener('DOMContentLoaded', (event) => { + me.init(); + }); + + me.getTarget = ()=>{ + return canvasOut.getContext("2d"); + } + + me.getSource = ()=>{ + return canvas; + } + + + + + return me; +}(); + +export default App \ No newline at end of file diff --git a/app/web-tools/dpaint/scrap/touch.html b/app/web-tools/dpaint/scrap/touch.html new file mode 100644 index 00000000..92fda9c3 --- /dev/null +++ b/app/web-tools/dpaint/scrap/touch.html @@ -0,0 +1,222 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/web-tools/dpaint/scrap/waterpaint.html b/app/web-tools/dpaint/scrap/waterpaint.html new file mode 100644 index 00000000..145c09b4 --- /dev/null +++ b/app/web-tools/dpaint/scrap/waterpaint.html @@ -0,0 +1,169 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/template/foot.html b/src/template/foot.html index 1222160e..6771bd97 100644 --- a/src/template/foot.html +++ b/src/template/foot.html @@ -34,7 +34,7 @@