diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index bdb0cab..0000000 --- a/.gitattributes +++ /dev/null @@ -1,17 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto - -# Custom for Visual Studio -*.cs diff=csharp - -# Standard to msysgit -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 416130f..65dbdf4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -node_modules -package-lock.json - # Windows image file caches Thumbs.db ehthumbs.db @@ -53,8 +50,10 @@ Temporary Items *.zip itch* -# node -node_modules - # IDE stuff -.idea \ No newline at end of file +.idea + +/Cargo.lock +/dist/ +/index.html +/includes/style.css diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3738210 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pixsy" +version = "0.72.7" +description = "convert images to Bitsy rooms" +authors = ["Max Bradbury "] +edition = "2018" +license = "MIT" +repository = "https://tinybird.dev/max/image-to-bitsy" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +base64 = "^0.12.3" +bitsy-parser = "^0.72.5" +image = "^0.23.7" +json = "^0.12.4" +lazy_static = "^1.4.0" +wasm-bindgen = "=0.2.64" # newer versions are bugged... diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ab3b50e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Max Bradbury + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6a3647 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# pixsy + +convert images to rooms for use in Bitsy diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..7e5744d --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +# todo + +* tile reuse +* noise reduction (remove lonely pixels) +* implement Atkinson and Bayer dithering options +* fix weird problem with pixels flipping (see test::example) diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 9da9a02..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-dinky \ No newline at end of file diff --git a/build.sh b/build.sh index a9ef88a..ac2bd52 100644 --- a/build.sh +++ b/build.sh @@ -1,4 +1,5 @@ -#!/usr/bin/env bash +#! /usr/bin/env bash -pug index.pug index.html +pug index.pug lessc includes/style.less includes/style.css +wasm-pack build --target web diff --git a/deploy.sh b/deploy.sh index 71b88e7..4ee7b5d 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,4 +1,6 @@ -#!/usr/bin/env bash +#! /usr/bin/env bash -zip -r image-to-bitsy.zip readme.md index.html includes/ -butler push image-to-bitsy.zip ruin/image-to-bitsy:html +rm -rf dist +mkdir dist +cp -r README.md LICENSE index.html script.js pkg includes old dist +butler push dist ruin/pixsy:html diff --git a/includes/Rubik-Regular.ttf b/includes/Rubik-Regular.ttf deleted file mode 100644 index 20100d2..0000000 Binary files a/includes/Rubik-Regular.ttf and /dev/null differ diff --git a/includes/croppie.js b/includes/croppie.js deleted file mode 100644 index f6f4671..0000000 --- a/includes/croppie.js +++ /dev/null @@ -1,1596 +0,0 @@ -/************************* - * Croppie - * Copyright 2017 - * Foliotek - * Version: 2.5.1 - *************************/ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['exports'], factory); - } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { - // CommonJS - factory(exports); - } else { - // Browser globals - factory((root.commonJsStrict = {})); - } -}(this, function (exports) { - - /* Polyfills */ - if (typeof Promise !== 'function') { - /*! promise-polyfill 3.1.0 */ - !function(a){function b(a,b){return function(){a.apply(b,arguments)}}function c(a){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof a)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],i(a,b(e,this),b(f,this))}function d(a){var b=this;return null===this._state?void this._deferreds.push(a):void k(function(){var c=b._state?a.onFulfilled:a.onRejected;if(null===c)return void(b._state?a.resolve:a.reject)(b._value);var d;try{d=c(b._value)}catch(e){return void a.reject(e)}a.resolve(d)})}function e(a){try{if(a===this)throw new TypeError("A promise cannot be resolved with itself.");if(a&&("object"==typeof a||"function"==typeof a)){var c=a.then;if("function"==typeof c)return void i(b(c,a),b(e,this),b(f,this))}this._state=!0,this._value=a,g.call(this)}catch(d){f.call(this,d)}}function f(a){this._state=!1,this._value=a,g.call(this)}function g(){for(var a=0,b=this._deferreds.length;b>a;a++)d.call(this,this._deferreds[a]);this._deferreds=null}function h(a,b,c,d){this.onFulfilled="function"==typeof a?a:null,this.onRejected="function"==typeof b?b:null,this.resolve=c,this.reject=d}function i(a,b,c){var d=!1;try{a(function(a){d||(d=!0,b(a))},function(a){d||(d=!0,c(a))})}catch(e){if(d)return;d=!0,c(e)}}var j=setTimeout,k="function"==typeof setImmediate&&setImmediate||function(a){j(a,1)},l=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)};c.prototype["catch"]=function(a){return this.then(null,a)},c.prototype.then=function(a,b){var e=this;return new c(function(c,f){d.call(e,new h(a,b,c,f))})},c.all=function(){var a=Array.prototype.slice.call(1===arguments.length&&l(arguments[0])?arguments[0]:arguments);return new c(function(b,c){function d(f,g){try{if(g&&("object"==typeof g||"function"==typeof g)){var h=g.then;if("function"==typeof h)return void h.call(g,function(a){d(f,a)},c)}a[f]=g,0===--e&&b(a)}catch(i){c(i)}}if(0===a.length)return b([]);for(var e=a.length,f=0;fd;d++)a[d].then(b,c)})},c._setImmediateFn=function(a){k=a},"undefined"!=typeof module&&module.exports?module.exports=c:a.Promise||(a.Promise=c)}(this); - } - - if ( typeof window.CustomEvent !== "function" ) { - (function(){ - function CustomEvent ( event, params ) { - params = params || { bubbles: false, cancelable: false, detail: undefined }; - var evt = document.createEvent( 'CustomEvent' ); - evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); - return evt; - } - CustomEvent.prototype = window.Event.prototype; - window.CustomEvent = CustomEvent; - }()); - } - - if (!HTMLCanvasElement.prototype.toBlob) { - Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { - value: function (callback, type, quality) { - var binStr = atob( this.toDataURL(type, quality).split(',')[1] ), - len = binStr.length, - arr = new Uint8Array(len); - - for (var i=0; i= 5) { - var x= w; - w = h; - h = x; - } - return { width: w, height: h }; - } - - /* CSS Transform Prototype */ - var TRANSLATE_OPTS = { - 'translate3d': { - suffix: ', 0px' - }, - 'translate': { - suffix: '' - } - }; - var Transform = function (x, y, scale) { - this.x = parseFloat(x); - this.y = parseFloat(y); - this.scale = parseFloat(scale); - }; - - Transform.parse = function (v) { - if (v.style) { - return Transform.parse(v.style[CSS_TRANSFORM]); - } - else if (v.indexOf('matrix') > -1 || v.indexOf('none') > -1) { - return Transform.fromMatrix(v); - } - else { - return Transform.fromString(v); - } - }; - - Transform.fromMatrix = function (v) { - var vals = v.substring(7).split(','); - if (!vals.length || v === 'none') { - vals = [1, 0, 0, 1, 0, 0]; - } - - return new Transform(num(vals[4]), num(vals[5]), parseFloat(vals[0])); - }; - - Transform.fromString = function (v) { - var values = v.split(') '), - translate = values[0].substring(Croppie.globals.translate.length + 1).split(','), - scale = values.length > 1 ? values[1].substring(6) : 1, - x = translate.length > 1 ? translate[0] : 0, - y = translate.length > 1 ? translate[1] : 0; - - return new Transform(x, y, scale); - }; - - Transform.prototype.toString = function () { - var suffix = TRANSLATE_OPTS[Croppie.globals.translate].suffix || ''; - return Croppie.globals.translate + '(' + this.x + 'px, ' + this.y + 'px' + suffix + ') scale(' + this.scale + ')'; - }; - - var TransformOrigin = function (el) { - if (!el || !el.style[CSS_TRANS_ORG]) { - this.x = 0; - this.y = 0; - return; - } - var css = el.style[CSS_TRANS_ORG].split(' '); - this.x = parseFloat(css[0]); - this.y = parseFloat(css[1]); - }; - - TransformOrigin.prototype.toString = function () { - return this.x + 'px ' + this.y + 'px'; - }; - - function getExifOrientation (img) { - return img.exifdata.Orientation; - } - - function drawCanvas(canvas, img, orientation) { - var width = img.width, - height = img.height, - ctx = canvas.getContext('2d'); - - canvas.width = img.width; - canvas.height = img.height; - - ctx.save(); - switch (orientation) { - case 2: - ctx.translate(width, 0); - ctx.scale(-1, 1); - break; - - case 3: - ctx.translate(width, height); - ctx.rotate(180*Math.PI/180); - break; - - case 4: - ctx.translate(0, height); - ctx.scale(1, -1); - break; - - case 5: - canvas.width = height; - canvas.height = width; - ctx.rotate(90*Math.PI/180); - ctx.scale(1, -1); - break; - - case 6: - canvas.width = height; - canvas.height = width; - ctx.rotate(90*Math.PI/180); - ctx.translate(0, -height); - break; - - case 7: - canvas.width = height; - canvas.height = width; - ctx.rotate(-90*Math.PI/180); - ctx.translate(-width, height); - ctx.scale(1, -1); - break; - - case 8: - canvas.width = height; - canvas.height = width; - ctx.translate(0, width); - ctx.rotate(-90*Math.PI/180); - break; - } - ctx.drawImage(img, 0,0, width, height); - ctx.restore(); - } - - /* Private Methods */ - function _create() { - var self = this, - contClass = 'croppie-container', - customViewportClass = self.options.viewport.type ? 'cr-vp-' + self.options.viewport.type : null, - boundary, img, viewport, overlay, bw, bh; - - self.options.useCanvas = self.options.enableOrientation || _hasExif.call(self); - // Properties on class - self.data = {}; - self.elements = {}; - - boundary = self.elements.boundary = document.createElement('div'); - viewport = self.elements.viewport = document.createElement('div'); - img = self.elements.img = document.createElement('img'); - overlay = self.elements.overlay = document.createElement('div'); - - if (self.options.useCanvas) { - self.elements.canvas = document.createElement('canvas'); - self.elements.preview = self.elements.canvas; - } - else { - self.elements.preview = self.elements.img; - } - - addClass(boundary, 'cr-boundary'); - boundary.setAttribute('aria-dropeffect', 'none'); - bw = self.options.boundary.width; - bh = self.options.boundary.height; - css(boundary, { - width: (bw + (isNaN(bw) ? '' : 'px')), - height: (bh + (isNaN(bh) ? '' : 'px')) - }); - - addClass(viewport, 'cr-viewport'); - if (customViewportClass) { - addClass(viewport, customViewportClass); - } - css(viewport, { - width: self.options.viewport.width + 'px', - height: self.options.viewport.height + 'px' - }); - viewport.setAttribute('tabindex', 0); - - addClass(self.elements.preview, 'cr-image'); - setAttributes(self.elements.preview, { 'alt': 'preview', 'aria-grabbed': 'false' }); - addClass(overlay, 'cr-overlay'); - - self.element.appendChild(boundary); - boundary.appendChild(self.elements.preview); - boundary.appendChild(viewport); - boundary.appendChild(overlay); - - addClass(self.element, contClass); - if (self.options.customClass) { - addClass(self.element, self.options.customClass); - } - - _initDraggable.call(this); - - if (self.options.enableZoom) { - _initializeZoom.call(self); - } - - // if (self.options.enableOrientation) { - // _initRotationControls.call(self); - // } - - if (self.options.enableResize) { - _initializeResize.call(self); - } - } - - // function _initRotationControls () { - // var self = this, - // wrap, btnLeft, btnRight, iLeft, iRight; - - // wrap = document.createElement('div'); - // self.elements.orientationBtnLeft = btnLeft = document.createElement('button'); - // self.elements.orientationBtnRight = btnRight = document.createElement('button'); - - // wrap.appendChild(btnLeft); - // wrap.appendChild(btnRight); - - // iLeft = document.createElement('i'); - // iRight = document.createElement('i'); - // btnLeft.appendChild(iLeft); - // btnRight.appendChild(iRight); - - // addClass(wrap, 'cr-rotate-controls'); - // addClass(btnLeft, 'cr-rotate-l'); - // addClass(btnRight, 'cr-rotate-r'); - - // self.elements.boundary.appendChild(wrap); - - // btnLeft.addEventListener('click', function () { - // self.rotate(-90); - // }); - // btnRight.addEventListener('click', function () { - // self.rotate(90); - // }); - // } - - function _hasExif() { - return this.options.enableExif && window.EXIF; - } - - function _initializeResize () { - var self = this; - var wrap = document.createElement('div'); - var isDragging = false; - var direction; - var originalX; - var originalY; - var minSize = 50; - var maxWidth; - var maxHeight; - var vr; - var hr; - - addClass(wrap, 'cr-resizer'); - css(wrap, { - width: this.options.viewport.width + 'px', - height: this.options.viewport.height + 'px' - }); - - if (this.options.resizeControls.height) { - vr = document.createElement('div'); - addClass(vr, 'cr-resizer-vertical'); - wrap.appendChild(vr); - } - - if (this.options.resizeControls.width) { - hr = document.createElement('div'); - addClass(hr, 'cr-resizer-horisontal'); - wrap.appendChild(hr); - } - - function mouseDown(ev) { - if (ev.button !== undefined && ev.button !== 0) return; - - ev.preventDefault(); - if (isDragging) { - return; - } - - var overlayRect = self.elements.overlay.getBoundingClientRect(); - - isDragging = true; - originalX = ev.pageX; - originalY = ev.pageY; - direction = ev.currentTarget.className.indexOf('vertical') !== -1 ? 'v' : 'h'; - maxWidth = overlayRect.width; - maxHeight = overlayRect.height; - - if (ev.touches) { - var touches = ev.touches[0]; - originalX = touches.pageX; - originalY = touches.pageY; - } - - window.addEventListener('mousemove', mouseMove); - window.addEventListener('touchmove', mouseMove); - window.addEventListener('mouseup', mouseUp); - window.addEventListener('touchend', mouseUp); - document.body.style[CSS_USERSELECT] = 'none'; - } - - function mouseMove(ev) { - var pageX = ev.pageX; - var pageY = ev.pageY; - - ev.preventDefault(); - - if (ev.touches) { - var touches = ev.touches[0]; - pageX = touches.pageX; - pageY = touches.pageY; - } - - var deltaX = pageX - originalX; - var deltaY = pageY - originalY; - var newHeight = self.options.viewport.height + deltaY; - var newWidth = self.options.viewport.width + deltaX; - - if (direction === 'v' && newHeight >= minSize && newHeight <= maxHeight) { - css(wrap, { - height: newHeight + 'px' - }); - - self.options.boundary.height += deltaY; - css(self.elements.boundary, { - height: self.options.boundary.height + 'px' - }); - - self.options.viewport.height += deltaY; - css(self.elements.viewport, { - height: self.options.viewport.height + 'px' - }); - } - else if (direction === 'h' && newWidth >= minSize && newWidth <= maxWidth) { - css(wrap, { - width: newWidth + 'px' - }); - - self.options.boundary.width += deltaX; - css(self.elements.boundary, { - width: self.options.boundary.width + 'px' - }); - - self.options.viewport.width += deltaX; - css(self.elements.viewport, { - width: self.options.viewport.width + 'px' - }); - } - - _updateOverlay.call(self); - _updateZoomLimits.call(self); - _updateCenterPoint.call(self); - _triggerUpdate.call(self); - originalY = pageY; - originalX = pageX; - } - - function mouseUp() { - isDragging = false; - window.removeEventListener('mousemove', mouseMove); - window.removeEventListener('touchmove', mouseMove); - window.removeEventListener('mouseup', mouseUp); - window.removeEventListener('touchend', mouseUp); - document.body.style[CSS_USERSELECT] = ''; - } - - if (vr) { - vr.addEventListener('mousedown', mouseDown); - } - - if (hr) { - hr.addEventListener('mousedown', mouseDown); - } - - this.elements.boundary.appendChild(wrap); - } - - function _setZoomerVal(v) { - if (this.options.enableZoom) { - var z = this.elements.zoomer, - val = fix(v, 4); - - z.value = Math.max(z.min, Math.min(z.max, val)); - } - } - - function _initializeZoom() { - var self = this, - wrap = self.elements.zoomerWrap = document.createElement('div'), - zoomer = self.elements.zoomer = document.createElement('input'); - - addClass(wrap, 'cr-slider-wrap'); - addClass(zoomer, 'cr-slider'); - zoomer.type = 'range'; - zoomer.step = '0.0001'; - zoomer.value = 1; - zoomer.style.display = self.options.showZoomer ? '' : 'none'; - zoomer.setAttribute('aria-label', 'zoom'); - - self.element.appendChild(wrap); - wrap.appendChild(zoomer); - - self._currentZoom = 1; - - function change() { - _onZoom.call(self, { - value: parseFloat(zoomer.value), - origin: new TransformOrigin(self.elements.preview), - viewportRect: self.elements.viewport.getBoundingClientRect(), - transform: Transform.parse(self.elements.preview) - }); - } - - function scroll(ev) { - var delta, targetZoom; - - if (ev.wheelDelta) { - delta = ev.wheelDelta / 1200; //wheelDelta min: -120 max: 120 // max x 10 x 2 - } else if (ev.deltaY) { - delta = ev.deltaY / 1060; //deltaY min: -53 max: 53 // max x 10 x 2 - } else if (ev.detail) { - delta = ev.detail / -60; //delta min: -3 max: 3 // max x 10 x 2 - } else { - delta = 0; - } - - targetZoom = self._currentZoom + (delta * self._currentZoom); - - ev.preventDefault(); - _setZoomerVal.call(self, targetZoom); - change.call(self); - } - - self.elements.zoomer.addEventListener('input', change);// this is being fired twice on keypress - self.elements.zoomer.addEventListener('change', change); - - if (self.options.mouseWheelZoom) { - self.elements.boundary.addEventListener('mousewheel', scroll); - self.elements.boundary.addEventListener('DOMMouseScroll', scroll); - } - } - - function _onZoom(ui) { - var self = this, - transform = ui ? ui.transform : Transform.parse(self.elements.preview), - vpRect = ui ? ui.viewportRect : self.elements.viewport.getBoundingClientRect(), - origin = ui ? ui.origin : new TransformOrigin(self.elements.preview); - - function applyCss() { - var transCss = {}; - transCss[CSS_TRANSFORM] = transform.toString(); - transCss[CSS_TRANS_ORG] = origin.toString(); - css(self.elements.preview, transCss); - } - - self._currentZoom = ui ? ui.value : self._currentZoom; - transform.scale = self._currentZoom; - self.elements.zoomer.setAttribute('aria-valuenow', self._currentZoom); - applyCss(); - - if (self.options.enforceBoundary) { - var boundaries = _getVirtualBoundaries.call(self, vpRect), - transBoundaries = boundaries.translate, - oBoundaries = boundaries.origin; - - if (transform.x >= transBoundaries.maxX) { - origin.x = oBoundaries.minX; - transform.x = transBoundaries.maxX; - } - - if (transform.x <= transBoundaries.minX) { - origin.x = oBoundaries.maxX; - transform.x = transBoundaries.minX; - } - - if (transform.y >= transBoundaries.maxY) { - origin.y = oBoundaries.minY; - transform.y = transBoundaries.maxY; - } - - if (transform.y <= transBoundaries.minY) { - origin.y = oBoundaries.maxY; - transform.y = transBoundaries.minY; - } - } - applyCss(); - _debouncedOverlay.call(self); - _triggerUpdate.call(self); - } - - function _getVirtualBoundaries(viewport) { - var self = this, - scale = self._currentZoom, - vpWidth = viewport.width, - vpHeight = viewport.height, - centerFromBoundaryX = self.elements.boundary.clientWidth / 2, - centerFromBoundaryY = self.elements.boundary.clientHeight / 2, - imgRect = self.elements.preview.getBoundingClientRect(), - curImgWidth = imgRect.width, - curImgHeight = imgRect.height, - halfWidth = vpWidth / 2, - halfHeight = vpHeight / 2; - - var maxX = ((halfWidth / scale) - centerFromBoundaryX) * -1; - var minX = maxX - ((curImgWidth * (1 / scale)) - (vpWidth * (1 / scale))); - - var maxY = ((halfHeight / scale) - centerFromBoundaryY) * -1; - var minY = maxY - ((curImgHeight * (1 / scale)) - (vpHeight * (1 / scale))); - - var originMinX = (1 / scale) * halfWidth; - var originMaxX = (curImgWidth * (1 / scale)) - originMinX; - - var originMinY = (1 / scale) * halfHeight; - var originMaxY = (curImgHeight * (1 / scale)) - originMinY; - - return { - translate: { - maxX: maxX, - minX: minX, - maxY: maxY, - minY: minY - }, - origin: { - maxX: originMaxX, - minX: originMinX, - maxY: originMaxY, - minY: originMinY - } - }; - } - - function _updateCenterPoint() { - var self = this, - scale = self._currentZoom, - data = self.elements.preview.getBoundingClientRect(), - vpData = self.elements.viewport.getBoundingClientRect(), - transform = Transform.parse(self.elements.preview.style[CSS_TRANSFORM]), - pc = new TransformOrigin(self.elements.preview), - top = (vpData.top - data.top) + (vpData.height / 2), - left = (vpData.left - data.left) + (vpData.width / 2), - center = {}, - adj = {}; - - center.y = top / scale; - center.x = left / scale; - - adj.y = (center.y - pc.y) * (1 - scale); - adj.x = (center.x - pc.x) * (1 - scale); - - transform.x -= adj.x; - transform.y -= adj.y; - - var newCss = {}; - newCss[CSS_TRANS_ORG] = center.x + 'px ' + center.y + 'px'; - newCss[CSS_TRANSFORM] = transform.toString(); - css(self.elements.preview, newCss); - } - - function _initDraggable() { - var self = this, - isDragging = false, - originalX, - originalY, - originalDistance, - vpRect, - transform; - - function assignTransformCoordinates(deltaX, deltaY) { - var imgRect = self.elements.preview.getBoundingClientRect(), - top = transform.y + deltaY, - left = transform.x + deltaX; - - if (self.options.enforceBoundary) { - if (vpRect.top > imgRect.top + deltaY && vpRect.bottom < imgRect.bottom + deltaY) { - transform.y = top; - } - - if (vpRect.left > imgRect.left + deltaX && vpRect.right < imgRect.right + deltaX) { - transform.x = left; - } - } - else { - transform.y = top; - transform.x = left; - } - } - - function toggleGrabState(isDragging) { - self.elements.preview.setAttribute('aria-grabbed', isDragging); - self.elements.boundary.setAttribute('aria-dropeffect', isDragging? 'move': 'none'); - } - - function keyDown(ev) { - var LEFT_ARROW = 37, - UP_ARROW = 38, - RIGHT_ARROW = 39, - DOWN_ARROW = 40; - - if (ev.shiftKey && (ev.keyCode == UP_ARROW || ev.keyCode == DOWN_ARROW)) { - var zoom = 0.0; - if (ev.keyCode == UP_ARROW) { - zoom = parseFloat(self.elements.zoomer.value, 10) + parseFloat(self.elements.zoomer.step, 10) - } - else { - zoom = parseFloat(self.elements.zoomer.value, 10) - parseFloat(self.elements.zoomer.step, 10) - } - self.setZoom(zoom); - } - else if (self.options.enableKeyMovement && (ev.keyCode >= 37 && ev.keyCode <= 40)) { - ev.preventDefault(); - var movement = parseKeyDown(ev.keyCode); - - transform = Transform.parse(self.elements.preview); - document.body.style[CSS_USERSELECT] = 'none'; - vpRect = self.elements.viewport.getBoundingClientRect(); - keyMove(movement); - }; - - function parseKeyDown(key) { - switch (key) { - case LEFT_ARROW: - return [1, 0]; - case UP_ARROW: - return [0, 1]; - case RIGHT_ARROW: - return [-1, 0]; - case DOWN_ARROW: - return [0, -1]; - }; - }; - } - - function keyMove(movement) { - var deltaX = movement[0], - deltaY = movement[1], - newCss = {}; - - assignTransformCoordinates(deltaX, deltaY); - - newCss[CSS_TRANSFORM] = transform.toString(); - css(self.elements.preview, newCss); - _updateOverlay.call(self); - document.body.style[CSS_USERSELECT] = ''; - _updateCenterPoint.call(self); - _triggerUpdate.call(self); - originalDistance = 0; - } - - function mouseDown(ev) { - if (ev.button !== undefined && ev.button !== 0) return; - - ev.preventDefault(); - if (isDragging) return; - isDragging = true; - originalX = ev.pageX; - originalY = ev.pageY; - - if (ev.touches) { - var touches = ev.touches[0]; - originalX = touches.pageX; - originalY = touches.pageY; - } - toggleGrabState(isDragging); - transform = Transform.parse(self.elements.preview); - window.addEventListener('mousemove', mouseMove); - window.addEventListener('touchmove', mouseMove); - window.addEventListener('mouseup', mouseUp); - window.addEventListener('touchend', mouseUp); - document.body.style[CSS_USERSELECT] = 'none'; - vpRect = self.elements.viewport.getBoundingClientRect(); - } - - function mouseMove(ev) { - ev.preventDefault(); - var pageX = ev.pageX, - pageY = ev.pageY; - - if (ev.touches) { - var touches = ev.touches[0]; - pageX = touches.pageX; - pageY = touches.pageY; - } - - var deltaX = pageX - originalX, - deltaY = pageY - originalY, - newCss = {}; - - if (ev.type == 'touchmove') { - if (ev.touches.length > 1) { - var touch1 = ev.touches[0]; - var touch2 = ev.touches[1]; - var dist = Math.sqrt((touch1.pageX - touch2.pageX) * (touch1.pageX - touch2.pageX) + (touch1.pageY - touch2.pageY) * (touch1.pageY - touch2.pageY)); - - if (!originalDistance) { - originalDistance = dist / self._currentZoom; - } - - var scale = dist / originalDistance; - - _setZoomerVal.call(self, scale); - dispatchChange(self.elements.zoomer); - return; - } - } - - assignTransformCoordinates(deltaX, deltaY); - - newCss[CSS_TRANSFORM] = transform.toString(); - css(self.elements.preview, newCss); - _updateOverlay.call(self); - originalY = pageY; - originalX = pageX; - } - - function mouseUp() { - isDragging = false; - toggleGrabState(isDragging); - window.removeEventListener('mousemove', mouseMove); - window.removeEventListener('touchmove', mouseMove); - window.removeEventListener('mouseup', mouseUp); - window.removeEventListener('touchend', mouseUp); - document.body.style[CSS_USERSELECT] = ''; - _updateCenterPoint.call(self); - _triggerUpdate.call(self); - originalDistance = 0; - } - - self.elements.overlay.addEventListener('mousedown', mouseDown); - self.elements.viewport.addEventListener('keydown', keyDown); - self.elements.overlay.addEventListener('touchstart', mouseDown); - } - - function _updateOverlay() { - var self = this, - boundRect = self.elements.boundary.getBoundingClientRect(), - imgData = self.elements.preview.getBoundingClientRect(); - - css(self.elements.overlay, { - width: imgData.width + 'px', - height: imgData.height + 'px', - top: (imgData.top - boundRect.top) + 'px', - left: (imgData.left - boundRect.left) + 'px' - }); - } - var _debouncedOverlay = debounce(_updateOverlay, 500); - - function _triggerUpdate() { - var self = this, - data = self.get(), - ev; - - if (!_isVisible.call(self)) { - return; - } - - self.options.update.call(self, data); - if (self.$ && typeof Prototype == 'undefined') { - self.$(self.element).trigger('update', data); - } - else { - var ev; - if (window.CustomEvent) { - ev = new CustomEvent('update', { detail: data }); - } else { - ev = document.createEvent('CustomEvent'); - ev.initCustomEvent('update', true, true, data); - } - - self.element.dispatchEvent(ev); - } - } - - function _isVisible() { - return this.elements.preview.offsetHeight > 0 && this.elements.preview.offsetWidth > 0; - } - - function _updatePropertiesFromImage() { - var self = this, - initialZoom = 1, - cssReset = {}, - img = self.elements.preview, - imgData = null, - transformReset = new Transform(0, 0, initialZoom), - originReset = new TransformOrigin(), - isVisible = _isVisible.call(self); - - if (!isVisible || self.data.bound) { - // if the croppie isn't visible or it doesn't need binding - return; - } - - self.data.bound = true; - cssReset[CSS_TRANSFORM] = transformReset.toString(); - cssReset[CSS_TRANS_ORG] = originReset.toString(); - cssReset['opacity'] = 1; - css(img, cssReset); - - imgData = self.elements.preview.getBoundingClientRect(); - - self._originalImageWidth = imgData.width; - self._originalImageHeight = imgData.height; - - if (self.options.enableZoom) { - _updateZoomLimits.call(self, true); - } - else { - self._currentZoom = initialZoom; - } - - transformReset.scale = self._currentZoom; - cssReset[CSS_TRANSFORM] = transformReset.toString(); - css(img, cssReset); - - if (self.data.points.length) { - _bindPoints.call(self, self.data.points); - } - else { - _centerImage.call(self); - } - - _updateCenterPoint.call(self); - _updateOverlay.call(self); - } - - function _updateZoomLimits (initial) { - var self = this, - minZoom = 0, - maxZoom = 1.5, - initialZoom, - defaultInitialZoom, - zoomer = self.elements.zoomer, - scale = parseFloat(zoomer.value), - boundaryData = self.elements.boundary.getBoundingClientRect(), - imgData = self.elements.preview.getBoundingClientRect(), - vpData = self.elements.viewport.getBoundingClientRect(), - minW, - minH; - - if (self.options.enforceBoundary) { - minW = vpData.width / (initial ? imgData.width : imgData.width / scale); - minH = vpData.height / (initial ? imgData.height : imgData.height / scale); - minZoom = Math.max(minW, minH); - } - - if (minZoom >= maxZoom) { - maxZoom = minZoom + 1; - } - - zoomer.min = fix(minZoom, 4); - zoomer.max = fix(maxZoom, 4); - - if (initial) { - defaultInitialZoom = Math.max((boundaryData.width / imgData.width), (boundaryData.height / imgData.height)); - initialZoom = self.data.boundZoom !== null ? self.data.boundZoom : defaultInitialZoom; - _setZoomerVal.call(self, initialZoom); - } - - dispatchChange(zoomer); - } - - function _bindPoints(points) { - if (points.length != 4) { - throw "Croppie - Invalid number of points supplied: " + points; - } - var self = this, - pointsWidth = points[2] - points[0], - // pointsHeight = points[3] - points[1], - vpData = self.elements.viewport.getBoundingClientRect(), - boundRect = self.elements.boundary.getBoundingClientRect(), - vpOffset = { - left: vpData.left - boundRect.left, - top: vpData.top - boundRect.top - }, - scale = vpData.width / pointsWidth, - originTop = points[1], - originLeft = points[0], - transformTop = (-1 * points[1]) + vpOffset.top, - transformLeft = (-1 * points[0]) + vpOffset.left, - newCss = {}; - - newCss[CSS_TRANS_ORG] = originLeft + 'px ' + originTop + 'px'; - newCss[CSS_TRANSFORM] = new Transform(transformLeft, transformTop, scale).toString(); - css(self.elements.preview, newCss); - - _setZoomerVal.call(self, scale); - self._currentZoom = scale; - } - - function _centerImage() { - var self = this, - imgDim = self.elements.preview.getBoundingClientRect(), - vpDim = self.elements.viewport.getBoundingClientRect(), - boundDim = self.elements.boundary.getBoundingClientRect(), - vpLeft = vpDim.left - boundDim.left, - vpTop = vpDim.top - boundDim.top, - w = vpLeft - ((imgDim.width - vpDim.width) / 2), - h = vpTop - ((imgDim.height - vpDim.height) / 2), - transform = new Transform(w, h, self._currentZoom); - - css(self.elements.preview, CSS_TRANSFORM, transform.toString()); - } - - function _transferImageToCanvas(customOrientation) { - var self = this, - canvas = self.elements.canvas, - img = self.elements.img, - ctx = canvas.getContext('2d'), - exif = _hasExif.call(self), - customOrientation = self.options.enableOrientation && customOrientation; - - ctx.clearRect(0, 0, canvas.width, canvas.height); - canvas.width = img.width; - canvas.height = img.height; - - if (exif && !customOrientation) { - var orientation = getExifOrientation(img); - drawCanvas(canvas, img, num(orientation || 0, 10)); - } - else if (customOrientation) { - drawCanvas(canvas, img, customOrientation); - } - } - - function _getCanvas(data) { - var self = this, - points = data.points, - left = num(points[0]), - top = num(points[1]), - right = num(points[2]), - bottom = num(points[3]), - width = right-left, - height = bottom-top, - circle = data.circle, - canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - outWidth = width, - outHeight = height, - startX = 0, - startY = 0, - canvasWidth = outWidth, - canvasHeight = outHeight, - customDimensions = (data.outputWidth && data.outputHeight), - outputWidthRatio = 1; - outputHeightRatio = 1; - - if (customDimensions) { - canvasWidth = data.outputWidth; - canvasHeight = data.outputHeight; - outputWidthRatio = canvasWidth / outWidth; - outputHeightRatio = canvasHeight / outHeight; - } - - canvas.width = canvasWidth; - canvas.height = canvasHeight; - - if (data.backgroundColor) { - ctx.fillStyle = data.backgroundColor; - ctx.fillRect(0, 0, canvasWidth, canvasHeight); - } - - - // start fixing data to send to draw image for enforceBoundary: false - if (!self.options.enforceBoundary) { - if (left < 0) { - startX = Math.abs(left); - left = 0; - } - if (top < 0) { - startY = Math.abs(top); - top = 0; - } - if (right > self._originalImageWidth) { - width = self._originalImageWidth - left; - outWidth = width; - } - if (bottom > self._originalImageHeight) { - height = self._originalImageHeight - top; - outHeight = height; - } - } - else{ - width=Math.min(width, self._originalImageWidth); - height=Math.min(height, self._originalImageHeight) - } - - if (outputWidthRatio !== 1 || outputHeightRatio !== 1) { - startX *= outputWidthRatio; - startY *= outputHeightRatio; - outWidth *= outputWidthRatio; - outHeight *= outputHeightRatio; - } - ctx.drawImage(this.elements.preview, left, top, width, height, startX, startY, outWidth, outHeight); - if (circle) { - ctx.fillStyle = '#fff'; - ctx.globalCompositeOperation = 'destination-in'; - ctx.beginPath(); - ctx.arc(outWidth / 2, outHeight / 2, outWidth / 2, 0, Math.PI * 2, true); - ctx.closePath(); - ctx.fill(); - } - return canvas; - } - - function _getHtmlResult(data) { - var points = data.points, - div = document.createElement('div'), - img = document.createElement('img'), - width = points[2] - points[0], - height = points[3] - points[1]; - - addClass(div, 'croppie-result'); - div.appendChild(img); - css(img, { - left: (-1 * points[0]) + 'px', - top: (-1 * points[1]) + 'px' - }); - img.src = data.url; - css(div, { - width: width + 'px', - height: height + 'px' - }); - - return div; - } - - function _getBase64Result(data) { - return _getCanvas.call(this, data).toDataURL(data.format, data.quality); - } - - function _getBlobResult(data) { - var self = this; - return new Promise(function (resolve, reject) { - _getCanvas.call(self, data).toBlob(function (blob) { - resolve(blob); - }, data.format, data.quality); - }); - } - - function _bind(options, cb) { - var self = this, - url, - points = [], - zoom = null, - hasExif = _hasExif.call(self);; - - if (typeof (options) === 'string') { - url = options; - options = {}; - } - else if (Array.isArray(options)) { - points = options.slice(); - } - else if (typeof (options) == 'undefined' && self.data.url) { //refreshing - _updatePropertiesFromImage.call(self); - _triggerUpdate.call(self); - return null; - } - else { - url = options.url; - points = options.points || []; - zoom = typeof(options.zoom) === 'undefined' ? null : options.zoom; - } - - self.data.bound = false; - self.data.url = url || self.data.url; - self.data.boundZoom = zoom; - - return loadImage(url, self.elements.img, hasExif).then(function (img) { - if (!points.length) { - var natDim = naturalImageDimensions(img); - var rect = self.elements.viewport.getBoundingClientRect(); - var aspectRatio = rect.width / rect.height; - var imgAspectRatio = natDim.width / natDim.height; - var width, height; - - if (imgAspectRatio > aspectRatio) { - height = natDim.height; - width = height * aspectRatio; - } - else { - width = natDim.width; - height = width / aspectRatio; - } - - var x0 = (natDim.width - width) / 2; - var y0 = (natDim.height - height) / 2; - var x1 = x0 + width; - var y1 = y0 + height; - - self.data.points = [x0, y0, x1, y1]; - } - else if (self.options.relative) { - points = [ - points[0] * img.naturalWidth / 100, - points[1] * img.naturalHeight / 100, - points[2] * img.naturalWidth / 100, - points[3] * img.naturalHeight / 100 - ]; - } - - self.data.points = points.map(function (p) { - return parseFloat(p); - }); - if (self.options.useCanvas) { - _transferImageToCanvas.call(self, options.orientation || 1); - } - _updatePropertiesFromImage.call(self); - _triggerUpdate.call(self); - cb && cb(); - }); - } - - function fix(v, decimalPoints) { - return parseFloat(v).toFixed(decimalPoints || 0); - } - - function _get() { - var self = this, - imgData = self.elements.preview.getBoundingClientRect(), - vpData = self.elements.viewport.getBoundingClientRect(), - x1 = vpData.left - imgData.left, - y1 = vpData.top - imgData.top, - widthDiff = (vpData.width - self.elements.viewport.offsetWidth) / 2, //border - heightDiff = (vpData.height - self.elements.viewport.offsetHeight) / 2, - x2 = x1 + self.elements.viewport.offsetWidth + widthDiff, - y2 = y1 + self.elements.viewport.offsetHeight + heightDiff, - scale = self._currentZoom; - - if (scale === Infinity || isNaN(scale)) { - scale = 1; - } - - var max = self.options.enforceBoundary ? 0 : Number.NEGATIVE_INFINITY; - x1 = Math.max(max, x1 / scale); - y1 = Math.max(max, y1 / scale); - x2 = Math.max(max, x2 / scale); - y2 = Math.max(max, y2 / scale); - - return { - points: [fix(x1), fix(y1), fix(x2), fix(y2)], - zoom: scale - }; - } - - var RESULT_DEFAULTS = { - type: 'canvas', - format: 'png', - quality: 1 - }, - RESULT_FORMATS = ['jpeg', 'webp', 'png']; - - function _result(options) { - var self = this, - data = _get.call(self), - opts = deepExtend(RESULT_DEFAULTS, deepExtend({}, options)), - resultType = (typeof (options) === 'string' ? options : (opts.type || 'base64')), - size = opts.size || 'viewport', - format = opts.format, - quality = opts.quality, - backgroundColor = opts.backgroundColor, - circle = typeof opts.circle === 'boolean' ? opts.circle : (self.options.viewport.type === 'circle'), - vpRect = self.elements.viewport.getBoundingClientRect(), - ratio = vpRect.width / vpRect.height, - prom; - - if (size === 'viewport') { - data.outputWidth = vpRect.width; - data.outputHeight = vpRect.height; - } else if (typeof size === 'object') { - if (size.width && size.height) { - data.outputWidth = size.width; - data.outputHeight = size.height; - } else if (size.width) { - data.outputWidth = size.width; - data.outputHeight = size.width / ratio; - } else if (size.height) { - data.outputWidth = size.height * ratio; - data.outputHeight = size.height; - } - } - - if (RESULT_FORMATS.indexOf(format) > -1) { - data.format = 'image/' + format; - data.quality = quality; - } - - data.circle = circle; - data.url = self.data.url; - data.backgroundColor = backgroundColor; - - prom = new Promise(function (resolve, reject) { - switch(resultType.toLowerCase()) - { - case 'rawcanvas': - resolve(_getCanvas.call(self, data)); - break; - case 'canvas': - case 'base64': - resolve(_getBase64Result.call(self, data)); - break; - case 'blob': - _getBlobResult.call(self, data).then(resolve); - break; - default: - resolve(_getHtmlResult.call(self, data)); - break; - } - }); - return prom; - } - - function _refresh() { - _updatePropertiesFromImage.call(this); - } - - function _rotate(deg) { - if (!this.options.useCanvas) { - throw 'Croppie: Cannot rotate without enableOrientation'; - } - - var self = this, - canvas = self.elements.canvas, - copy = document.createElement('canvas'), - ornt = 1; - - copy.width = canvas.width; - copy.height = canvas.height; - var ctx = copy.getContext('2d'); - ctx.drawImage(canvas, 0, 0); - - if (deg === 90 || deg === -270) ornt = 6; - if (deg === -90 || deg === 270) ornt = 8; - if (deg === 180 || deg === -180) ornt = 3; - - drawCanvas(canvas, copy, ornt); - _onZoom.call(self); - copy = null; - } - - function _destroy() { - var self = this; - self.element.removeChild(self.elements.boundary); - removeClass(self.element, 'croppie-container'); - if (self.options.enableZoom) { - self.element.removeChild(self.elements.zoomerWrap); - } - delete self.elements; - } - - if (window.jQuery) { - var $ = window.jQuery; - $.fn.croppie = function (opts) { - var ot = typeof opts; - - if (ot === 'string') { - var args = Array.prototype.slice.call(arguments, 1); - var singleInst = $(this).data('croppie'); - - if (opts === 'get') { - return singleInst.get(); - } - else if (opts === 'result') { - return singleInst.result.apply(singleInst, args); - } - else if (opts === 'bind') { - return singleInst.bind.apply(singleInst, args); - } - - return this.each(function () { - var i = $(this).data('croppie'); - if (!i) return; - - var method = i[opts]; - if ($.isFunction(method)) { - method.apply(i, args); - if (opts === 'destroy') { - $(this).removeData('croppie'); - } - } - else { - throw 'Croppie ' + opts + ' method not found'; - } - }); - } - else { - return this.each(function () { - var i = new Croppie(this, opts); - i.$ = $; - $(this).data('croppie', i); - }); - } - }; - } - - function Croppie(element, opts) { - this.element = element; - this.options = deepExtend(deepExtend({}, Croppie.defaults), opts); - - if (this.element.tagName.toLowerCase() === 'img') { - var origImage = this.element; - addClass(origImage, 'cr-original-image'); - setAttributes(origImage, {'aria-hidden' : 'true', 'alt' : '' }); - var replacementDiv = document.createElement('div'); - this.element.parentNode.appendChild(replacementDiv); - replacementDiv.appendChild(origImage); - this.element = replacementDiv; - this.options.url = this.options.url || origImage.src; - } - - _create.call(this); - if (this.options.url) { - var bindOpts = { - url: this.options.url, - points: this.options.points - }; - delete this.options['url']; - delete this.options['points']; - _bind.call(this, bindOpts); - } - } - - Croppie.defaults = { - viewport: { - width: 100, - height: 100, - type: 'square' - }, - boundary: { }, - orientationControls: { - enabled: true, - leftClass: '', - rightClass: '' - }, - resizeControls: { - width: true, - height: true - }, - customClass: '', - showZoomer: true, - enableZoom: true, - enableResize: false, - mouseWheelZoom: true, - enableExif: false, - enforceBoundary: true, - enableOrientation: false, - enableKeyMovement: true, - update: function () { } - }; - - Croppie.globals = { - translate: 'translate3d' - }; - - deepExtend(Croppie.prototype, { - bind: function (options, cb) { - return _bind.call(this, options, cb); - }, - get: function () { - var data = _get.call(this); - var points = data.points; - if (this.options.relative) { - points[0] /= this.elements.img.naturalWidth / 100; - points[1] /= this.elements.img.naturalHeight / 100; - points[2] /= this.elements.img.naturalWidth / 100; - points[3] /= this.elements.img.naturalHeight / 100; - } - return data; - }, - result: function (type) { - return _result.call(this, type); - }, - refresh: function () { - return _refresh.call(this); - }, - setZoom: function (v) { - _setZoomerVal.call(this, v); - dispatchChange(this.elements.zoomer); - }, - rotate: function (deg) { - _rotate.call(this, deg); - }, - destroy: function () { - return _destroy.call(this); - } - }); - - exports.Croppie = window.Croppie = Croppie; - - if (typeof module === 'object' && !!module.exports) { - module.exports = Croppie; - } -})); diff --git a/includes/croppie.min.js b/includes/croppie.min.js new file mode 100644 index 0000000..1223fd2 --- /dev/null +++ b/includes/croppie.min.js @@ -0,0 +1 @@ +!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports&&"string"!=typeof exports.nodeName?module.exports=t():e.Croppie=t()}("undefined"!=typeof self?self:this,function(){"function"!=typeof Promise&&function(e){function n(e,t){return function(){e.apply(t,arguments)}}function r(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],u(e,n(i,this),n(o,this))}function a(n){var i=this;return null===this._state?void this._deferreds.push(n):void c(function(){var e=i._state?n.onFulfilled:n.onRejected;if(null!==e){var t;try{t=e(i._value)}catch(e){return void n.reject(e)}n.resolve(t)}else(i._state?n.resolve:n.reject)(i._value)})}function i(e){try{if(e===this)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var t=e.then;if("function"==typeof t)return void u(n(t,e),n(i,this),n(o,this))}this._state=!0,this._value=e,s.call(this)}catch(e){o.call(this,e)}}function o(e){this._state=!1,this._value=e,s.call(this)}function s(){for(var e=0,t=this._deferreds.length;en.top+t&&l.bottomn.left+e&&l.right=s.maxX&&(o.x=l.minX,n.x=s.maxX),n.x<=s.minX&&(o.x=l.maxX,n.x=s.minX),n.y>=s.maxY&&(o.y=l.minY,n.y=s.maxY),n.y<=s.minY&&(o.y=l.maxY,n.y=s.minY)}r(),I.call(t),F.call(t)}).call(i,{value:parseFloat(t.value),origin:new L(i.elements.preview),viewportRect:i.elements.viewport.getBoundingClientRect(),transform:E.parse(i.elements.preview)})}function n(e){var t,n;if("ctrl"===i.options.mouseWheelZoom&&!0!==e.ctrlKey)return 0;t=e.wheelDelta?e.wheelDelta/1200:e.deltaY?e.deltaY/1060:e.detail?e.detail/-60:0,n=i._currentZoom+t*i._currentZoom,e.preventDefault(),B.call(i,n),o.call(i)}x(e,"cr-slider-wrap"),x(t,"cr-slider"),t.type="range",t.step="0.0001",t.value="1",t.style.display=i.options.showZoomer?"":"none",t.setAttribute("aria-label","zoom"),i.element.appendChild(e),e.appendChild(t),i._currentZoom=1,i.elements.zoomer.addEventListener("input",o),i.elements.zoomer.addEventListener("change",o),i.options.mouseWheelZoom&&(i.elements.boundary.addEventListener("mousewheel",n),i.elements.boundary.addEventListener("DOMMouseScroll",n))}.call(a),a.options.enableResize&&function(){var l,u,c,h,p,e,t,d=this,m=document.createElement("div"),i=!1,f=50;x(m,"cr-resizer"),b(m,{width:this.options.viewport.width+"px",height:this.options.viewport.height+"px"}),this.options.resizeControls.height&&(x(e=document.createElement("div"),"cr-resizer-vertical"),m.appendChild(e));this.options.resizeControls.width&&(x(t=document.createElement("div"),"cr-resizer-horisontal"),m.appendChild(t));function n(e){if((void 0===e.button||0===e.button)&&(e.preventDefault(),!i)){var t=d.elements.overlay.getBoundingClientRect();if(i=!0,u=e.pageX,c=e.pageY,l=-1!==e.currentTarget.className.indexOf("vertical")?"v":"h",h=t.width,p=t.height,e.touches){var n=e.touches[0];u=n.pageX,c=n.pageY}window.addEventListener("mousemove",o),window.addEventListener("touchmove",o),window.addEventListener("mouseup",r),window.addEventListener("touchend",r),document.body.style[w]="none"}}function o(e){var t=e.pageX,n=e.pageY;if(e.preventDefault(),e.touches){var i=e.touches[0];t=i.pageX,n=i.pageY}var o=t-u,r=n-c,a=d.options.viewport.height+r,s=d.options.viewport.width+o;"v"===l&&f<=a&&a<=p?(b(m,{height:a+"px"}),d.options.boundary.height+=r,b(d.elements.boundary,{height:d.options.boundary.height+"px"}),d.options.viewport.height+=r,b(d.elements.viewport,{height:d.options.viewport.height+"px"})):"h"===l&&f<=s&&s<=h&&(b(m,{width:s+"px"}),d.options.boundary.width+=o,b(d.elements.boundary,{width:d.options.boundary.width+"px"}),d.options.viewport.width+=o,b(d.elements.viewport,{width:d.options.viewport.width+"px"})),z.call(d),W.call(d),Z.call(d),F.call(d),c=n,u=t}function r(){i=!1,window.removeEventListener("mousemove",o),window.removeEventListener("touchmove",o),window.removeEventListener("mouseup",r),window.removeEventListener("touchend",r),document.body.style[w]=""}e&&(e.addEventListener("mousedown",n),e.addEventListener("touchstart",n));t&&(t.addEventListener("mousedown",n),t.addEventListener("touchstart",n));this.elements.boundary.appendChild(m)}.call(a)}function R(){return this.options.enableExif&&window.EXIF}function B(e){if(this.options.enableZoom){var t=this.elements.zoomer,n=A(e,4);t.value=Math.max(parseFloat(t.min),Math.min(parseFloat(t.max),n)).toString()}}function Z(e){var t=this,n=t._currentZoom,i=t.elements.preview.getBoundingClientRect(),o=t.elements.viewport.getBoundingClientRect(),r=E.parse(t.elements.preview.style[g]),a=new L(t.elements.preview),s=o.top-i.top+o.height/2,l=o.left-i.left+o.width/2,u={},c={};if(e){var h=a.x,p=a.y,d=r.x,m=r.y;u.y=h,u.x=p,r.y=d,r.x=m}else u.y=s/n,u.x=l/n,c.y=(u.y-a.y)*(1-n),c.x=(u.x-a.x)*(1-n),r.x-=c.x,r.y-=c.y;var f={};f[v]=u.x+"px "+u.y+"px",f[g]=r.toString(),b(t.elements.preview,f)}function z(){if(this.elements){var e=this.elements.boundary.getBoundingClientRect(),t=this.elements.preview.getBoundingClientRect();b(this.elements.overlay,{width:t.width+"px",height:t.height+"px",top:t.top-e.top+"px",left:t.left-e.left+"px"})}}L.prototype.toString=function(){return this.x+"px "+this.y+"px"};var a,s,h,M,I=(a=z,s=500,function(){var e=this,t=arguments,n=h&&!M;clearTimeout(M),M=setTimeout(function(){M=null,h||a.apply(e,t)},s),n&&a.apply(e,t)});function F(){var e,t=this,n=t.get();X.call(t)&&(t.options.update.call(t,n),t.$&&"undefined"==typeof Prototype?t.$(t.element).trigger("update.croppie",n):(window.CustomEvent?e=new CustomEvent("update",{detail:n}):(e=document.createEvent("CustomEvent")).initCustomEvent("update",!0,!0,n),t.element.dispatchEvent(e)))}function X(){return 0l.max)?B.call(r,uthis._originalImageWidth&&(g=(d=this._originalImageWidth-h)/o*u),i<0&&(p=0,v=Math.abs(i)/r*c),m+p>this._originalImageHeight&&(w=(m=this._originalImageHeight-p)/r*c),l.drawImage(this.elements.preview,h,p,d,m,f,v,g,w),a&&(l.fillStyle="#fff",l.globalCompositeOperation="destination-in",l.beginPath(),l.arc(s.width/2,s.height/2,s.width/2,0,2*Math.PI,!0),l.closePath(),l.fill()),s}function k(c,h){var e,i,o,r,p=this,d=[],t=null,n=R.call(p);if("string"==typeof c)e=c,c={};else if(Array.isArray(c))d=c.slice();else{if(void 0===c&&p.data.url)return Y.call(p),F.call(p),null;e=c.url,d=c.points||[],t=void 0===c.zoom?null:c.zoom}return p.data.bound=!1,p.data.url=e||p.data.url,p.data.boundZoom=t,(i=e,o=n,r=new Image,r.style.opacity="0",new Promise(function(e,t){function n(){r.style.opacity="1",setTimeout(function(){e(r)},1)}r.removeAttribute("crossOrigin"),i.match(/^https?:\/\/|^\/\//)&&r.setAttribute("crossOrigin","anonymous"),r.onload=function(){o?EXIF.getData(r,function(){n()}):n()},r.onerror=function(e){r.style.opacity=1,setTimeout(function(){t(e)},1)},r.src=i})).then(function(e){if(function(t){this.elements.img.parentNode&&(Array.prototype.forEach.call(this.elements.img.classList,function(e){t.classList.add(e)}),this.elements.img.parentNode.replaceChild(t,this.elements.img),this.elements.preview=t),this.elements.img=t}.call(p,e),d.length)p.options.relative&&(d=[d[0]*e.naturalWidth/100,d[1]*e.naturalHeight/100,d[2]*e.naturalWidth/100,d[3]*e.naturalHeight/100]);else{var t,n,i=m(e),o=p.elements.viewport.getBoundingClientRect(),r=o.width/o.height;r+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"']/g,J=RegExp(G.source),Y=RegExp(H.source),Q=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,nn=/<%=([\s\S]+?)%>/g,tn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,rn=/^\w*$/,en=/^\./,un=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,on=/[\\^$.*+?()[\]{}|]/g,fn=RegExp(on.source),cn=/^\s+|\s+$/g,an=/^\s+/,ln=/\s+$/,sn=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,hn=/\{\n\/\* \[wrapped with (.+)\] \*/,pn=/,? & /,_n=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,vn=/\\(\\)?/g,gn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,dn=/\w*$/,yn=/^[-+]0x[0-9a-f]+$/i,bn=/^0b[01]+$/i,xn=/^\[object .+?Constructor\]$/,jn=/^0o[0-7]+$/i,wn=/^(?:0|[1-9]\d*)$/,mn=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,An=/($^)/,kn=/['\n\r\u2028\u2029\\]/g,En="[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?)*",On="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+En,Sn="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]?|[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",In=RegExp("['\u2019]","g"),Rn=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g"),zn=RegExp("\\ud83c[\\udffb-\\udfff](?=\\ud83c[\\udffb-\\udfff])|"+Sn+En,"g"),Wn=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:d|ll|m|re|s|t|ve))?|[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?|\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)|\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)|\\d+",On].join("|"),"g"),Bn=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]"),Ln=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Un="Array Buffer DataView Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Promise RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Cn={}; -Cn["[object Float32Array]"]=Cn["[object Float64Array]"]=Cn["[object Int8Array]"]=Cn["[object Int16Array]"]=Cn["[object Int32Array]"]=Cn["[object Uint8Array]"]=Cn["[object Uint8ClampedArray]"]=Cn["[object Uint16Array]"]=Cn["[object Uint32Array]"]=true,Cn["[object Arguments]"]=Cn["[object Array]"]=Cn["[object ArrayBuffer]"]=Cn["[object Boolean]"]=Cn["[object DataView]"]=Cn["[object Date]"]=Cn["[object Error]"]=Cn["[object Function]"]=Cn["[object Map]"]=Cn["[object Number]"]=Cn["[object Object]"]=Cn["[object RegExp]"]=Cn["[object Set]"]=Cn["[object String]"]=Cn["[object WeakMap]"]=false; -var Dn={};Dn["[object Arguments]"]=Dn["[object Array]"]=Dn["[object ArrayBuffer]"]=Dn["[object DataView]"]=Dn["[object Boolean]"]=Dn["[object Date]"]=Dn["[object Float32Array]"]=Dn["[object Float64Array]"]=Dn["[object Int8Array]"]=Dn["[object Int16Array]"]=Dn["[object Int32Array]"]=Dn["[object Map]"]=Dn["[object Number]"]=Dn["[object Object]"]=Dn["[object RegExp]"]=Dn["[object Set]"]=Dn["[object String]"]=Dn["[object Symbol]"]=Dn["[object Uint8Array]"]=Dn["[object Uint8ClampedArray]"]=Dn["[object Uint16Array]"]=Dn["[object Uint32Array]"]=true, -Dn["[object Error]"]=Dn["[object Function]"]=Dn["[object WeakMap]"]=false;var Mn,Tn={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},$n=parseFloat,Fn=parseInt,Nn=typeof global=="object"&&global&&global.Object===Object&&global,Pn=typeof self=="object"&&self&&self.Object===Object&&self,Zn=Nn||Pn||Function("return this")(),qn=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Vn=qn&&typeof module=="object"&&module&&!module.nodeType&&module,Kn=Vn&&Vn.exports===qn,Gn=Kn&&Nn.process; -n:{try{Mn=Gn&&Gn.binding&&Gn.binding("util");break n}catch(n){}Mn=void 0}var Hn=Mn&&Mn.isArrayBuffer,Jn=Mn&&Mn.isDate,Yn=Mn&&Mn.isMap,Qn=Mn&&Mn.isRegExp,Xn=Mn&&Mn.isSet,nt=Mn&&Mn.isTypedArray,tt=j("length"),rt=w({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I", -"\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C", -"\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i", -"\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S", -"\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe", -"\u0149":"'n","\u017f":"s"}),et=w({"&":"&","<":"<",">":">",'"':""","'":"'"}),ut=w({"&":"&","<":"<",">":">",""":'"',"'":"'"}),it=function w(En){function On(n){if(xu(n)&&!af(n)&&!(n instanceof Mn)){if(n instanceof zn)return n;if(ci.call(n,"__wrapped__"))return Pe(n)}return new zn(n)}function Sn(){}function zn(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=F}function Mn(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1, -this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Tn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t=t?n:t)),n}function dt(n,t,r,e,i,o){var f,c=1&t,a=2&t,l=4&t;if(r&&(f=i?r(n,e,i,o):r(n)),f!==F)return f;if(!bu(n))return n;if(e=af(n)){if(f=Ee(n),!c)return Mr(n,f)}else{var s=yo(n),h="[object Function]"==s||"[object GeneratorFunction]"==s;if(sf(n))return Wr(n,c);if("[object Object]"==s||"[object Arguments]"==s||h&&!i){if(f=a||h?{}:Oe(n),!c)return a?Fr(n,pt(f,n)):$r(n,ht(f,n))}else{if(!Dn[s])return i?n:{};f=Se(n,s,dt,c)}}if(o||(o=new Vn), -i=o.get(n))return i;o.set(n,f);var a=l?a?ye:de:a?Uu:Lu,p=e?F:a(n);return u(p||n,function(e,u){p&&(u=e,e=n[u]),at(f,u,dt(e,t,r,u,n,o))}),f}function yt(n){var t=Lu(n);return function(r){return bt(r,n,t)}}function bt(n,t,r){var e=r.length;if(null==n)return!e;for(n=ni(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===F&&!(u in n)||!i(o))return false}return true}function xt(n,t,r){if(typeof n!="function")throw new ei("Expected a function");return jo(function(){n.apply(F,r)},t)}function jt(n,t,r,e){var u=-1,i=c,o=true,f=n.length,s=[],h=t.length; -if(!f)return s;r&&(t=l(t,S(r))),e?(i=a,o=false):200<=t.length&&(i=R,o=false,t=new qn(t));n:for(;++ut}function Bt(n,t){return null!=n&&ci.call(n,t)}function Lt(n,t){return null!=n&&t in ni(n)}function Ut(n,t,r){for(var e=r?a:c,u=n[0].length,i=n.length,o=i,f=Hu(i),s=1/0,h=[];o--;){var p=n[o];o&&t&&(p=l(p,S(t))),s=Mi(p.length,s),f[o]=!r&&(t||120<=u&&120<=p.length)?new qn(o&&p):F}var p=n[0],_=-1,v=f[0];n:for(;++_t.length?n:It(n,vr(t,0,-1)),t=null==n?n:n[$e(Ge(t))],null==t?F:r(t,n,e)}function Mt(n){return xu(n)&&"[object Arguments]"==zt(n)}function Tt(n){return xu(n)&&"[object ArrayBuffer]"==zt(n)}function $t(n){return xu(n)&&"[object Date]"==zt(n)}function Ft(n,t,r,e,u){if(n===t)t=true;else if(null==n||null==t||!xu(n)&&!xu(t))t=n!==n&&t!==t;else n:{ -var i=af(n),o=af(t),f=i?"[object Array]":yo(n),c=o?"[object Array]":yo(t),f="[object Arguments]"==f?"[object Object]":f,c="[object Arguments]"==c?"[object Object]":c,a="[object Object]"==f,o="[object Object]"==c;if((c=f==c)&&sf(n)){if(!sf(t)){t=false;break n}i=true,a=false}if(c&&!a)u||(u=new Vn),t=i||gf(n)?_e(n,t,r,e,Ft,u):ve(n,t,f,r,e,Ft,u);else{if(!(1&r)&&(i=a&&ci.call(n,"__wrapped__"),f=o&&ci.call(t,"__wrapped__"),i||f)){n=i?n.value():n,t=f?t.value():t,u||(u=new Vn),t=Ft(n,t,r,e,u);break n}if(c)t:if(u||(u=new Vn), -i=1&r,f=de(n),o=f.length,c=de(t).length,o==c||i){for(a=o;a--;){var l=f[a];if(!(i?l in t:ci.call(t,l))){t=false;break t}}if((c=u.get(n))&&u.get(t))t=c==t;else{c=true,u.set(n,t),u.set(t,n);for(var s=i;++at?r:0,Re(t,r)?n[t]:F}function rr(n,t,r){var e=-1;return t=l(t.length?t:[Nu],S(je())),n=Yt(n,function(n){return{a:l(t,function(t){return t(n)}),b:++e,c:n}}),A(n,function(n,t){var e;n:{e=-1;for(var u=n.a,i=t.a,o=u.length,f=r.length;++e=f?c:c*("desc"==r[e]?-1:1); -break n}}e=n.b-t.b}return e})}function er(n,t){return ur(n,t,function(t,r){return Bu(n,r)})}function ur(n,t,r){for(var e=-1,u=t.length,i={};++et||9007199254740991t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Hu(u);++e=u){for(;e>>1,o=n[i];null!==o&&!Au(o)&&(r?o<=t:ot.length?n:It(n,vr(t,0,-1)), -null==n||delete n[$e(Ge(t))]}function Ar(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++ie)return e?wr(n[0]):[];for(var u=-1,i=Hu(e);++u=e?n:vr(n,t,r)}function Wr(n,t){if(t)return n.slice();var r=n.length,r=yi?yi(r):new n.constructor(r);return n.copy(r),r}function Br(n){var t=new n.constructor(n.byteLength);return new di(t).set(new di(n)),t}function Lr(n,t){return new n.constructor(t?Br(n.buffer):n.buffer,n.byteOffset,n.length)}function Ur(n,t){ -if(n!==t){var r=n!==F,e=null===n,u=n===n,i=Au(n),o=t!==F,f=null===t,c=t===t,a=Au(t);if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&nu?F:i,u=1),t=ni(t);++eo&&f[0]!==a&&f[o-1]!==a?[]:C(f,a),o-=c.length,or?r?ar(t,n):t:(r=ar(t,Ri(n/T(t))),Bn.test(t)?zr($(r),0,n).join(""):r.slice(0,n))}function ue(n,t,e,u){function i(){for(var t=-1,c=arguments.length,a=-1,l=u.length,s=Hu(l+c),h=this&&this!==Zn&&this instanceof i?f:n;++at||e)&&(1&n&&(i[2]=h[2],t|=1&r?0:4),(r=h[3])&&(e=i[3],i[3]=e?Cr(e,r,h[4]):r,i[4]=e?C(i[3],"__lodash_placeholder__"):h[4]),(r=h[5])&&(e=i[5],i[5]=e?Dr(e,r,h[6]):r,i[6]=e?C(i[5],"__lodash_placeholder__"):h[6]),(r=h[7])&&(i[7]=r),128&n&&(i[8]=null==i[8]?h[8]:Mi(i[8],h[8])),null==i[9]&&(i[9]=h[9]),i[0]=h[0],i[1]=t),n=i[0],t=i[1], -r=i[2],e=i[3],u=i[4],f=i[9]=i[9]===F?c?0:n.length:Di(i[9]-a,0),!f&&24&t&&(t&=-25),De((h?lo:xo)(t&&1!=t?8==t||16==t?Jr(n,t,f):32!=t&&33!=t||u.length?Xr.apply(F,i):ue(n,t,r,e):Vr(n,t,r),i),n,t)}function se(n,t,r,e){return n===F||hu(n,ii[r])&&!ci.call(e,r)?t:n}function he(n,t,r,e,u,i){return bu(n)&&bu(t)&&(i.set(t,n),nr(n,t,F,he,i),i.delete(t)),n}function pe(n){return wu(n)?F:n}function _e(n,t,r,e,u,i){var o=1&r,f=n.length,c=t.length;if(f!=c&&!(o&&c>f))return false;if((c=i.get(n))&&i.get(t))return c==t;var c=-1,a=true,l=2&r?new qn:F; -for(i.set(n,t),i.set(t,n);++cr&&(r=Di(e+r,0)),g(n,je(t,3),r)):-1}function qe(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;return r!==F&&(u=Ou(r),u=0>r?Di(e+u,0):Mi(u,e-1)), -g(n,je(t,3),u,true)}function Ve(n){return(null==n?0:n.length)?kt(n,1):[]}function Ke(n){return n&&n.length?n[0]:F}function Ge(n){var t=null==n?0:n.length;return t?n[t-1]:F}function He(n,t){return n&&n.length&&t&&t.length?or(n,t):n}function Je(n){return null==n?n:Ni.call(n)}function Ye(n){if(!n||!n.length)return[];var t=0;return n=f(n,function(n){if(_u(n))return t=Di(n.length,t),true}),E(t,function(t){return l(n,j(t))})}function Qe(n,t){if(!n||!n.length)return[];var e=Ye(n);return null==t?e:l(e,function(n){ -return r(t,F,n)})}function Xe(n){return n=On(n),n.__chain__=true,n}function nu(n,t){return t(n)}function tu(){return this}function ru(n,t){return(af(n)?u:oo)(n,je(t,3))}function eu(n,t){return(af(n)?i:fo)(n,je(t,3))}function uu(n,t){return(af(n)?l:Yt)(n,je(t,3))}function iu(n,t,r){return t=r?F:t,t=n&&null==t?n.length:t,le(n,128,F,F,F,F,t)}function ou(n,t){var r;if(typeof t!="function")throw new ei("Expected a function");return n=Ou(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=F), -r}}function fu(n,t,r){return t=r?F:t,n=le(n,8,F,F,F,F,F,t),n.placeholder=fu.placeholder,n}function cu(n,t,r){return t=r?F:t,n=le(n,16,F,F,F,F,F,t),n.placeholder=cu.placeholder,n}function au(n,t,r){function e(t){var r=c,e=a;return c=a=F,_=t,s=n.apply(e,r)}function u(n){var r=n-p;return n-=_,p===F||r>=t||0>r||g&&n>=l}function i(){var n=Jo();if(u(n))return o(n);var r,e=jo;r=n-_,n=t-(n-p),r=g?Mi(n,l-r):n,h=e(i,r)}function o(n){return h=F,d&&c?e(n):(c=a=F,s)}function f(){var n=Jo(),r=u(n);if(c=arguments, -a=this,p=n,r){if(h===F)return _=n=p,h=jo(i,t),v?e(n):s;if(g)return h=jo(i,t),e(p)}return h===F&&(h=jo(i,t)),s}var c,a,l,s,h,p,_=0,v=false,g=false,d=true;if(typeof n!="function")throw new ei("Expected a function");return t=Iu(t)||0,bu(r)&&(v=!!r.leading,l=(g="maxWait"in r)?Di(Iu(r.maxWait)||0,t):l,d="trailing"in r?!!r.trailing:d),f.cancel=function(){h!==F&&ho(h),_=0,c=p=a=h=F},f.flush=function(){return h===F?s:o(Jo())},f}function lu(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;return i.has(u)?i.get(u):(e=n.apply(this,e), -r.cache=i.set(u,e)||i,e)}if(typeof n!="function"||null!=t&&typeof t!="function")throw new ei("Expected a function");return r.cache=new(lu.Cache||Pn),r}function su(n){if(typeof n!="function")throw new ei("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function hu(n,t){return n===t||n!==n&&t!==t}function pu(n){return null!=n&&yu(n.length)&&!gu(n); -}function _u(n){return xu(n)&&pu(n)}function vu(n){if(!xu(n))return false;var t=zt(n);return"[object Error]"==t||"[object DOMException]"==t||typeof n.message=="string"&&typeof n.name=="string"&&!wu(n)}function gu(n){return!!bu(n)&&(n=zt(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n)}function du(n){return typeof n=="number"&&n==Ou(n)}function yu(n){return typeof n=="number"&&-1=n}function bu(n){var t=typeof n;return null!=n&&("object"==t||"function"==t); -}function xu(n){return null!=n&&typeof n=="object"}function ju(n){return typeof n=="number"||xu(n)&&"[object Number]"==zt(n)}function wu(n){return!(!xu(n)||"[object Object]"!=zt(n))&&(n=bi(n),null===n||(n=ci.call(n,"constructor")&&n.constructor,typeof n=="function"&&n instanceof n&&fi.call(n)==hi))}function mu(n){return typeof n=="string"||!af(n)&&xu(n)&&"[object String]"==zt(n)}function Au(n){return typeof n=="symbol"||xu(n)&&"[object Symbol]"==zt(n)}function ku(n){if(!n)return[];if(pu(n))return mu(n)?$(n):Mr(n); -if(Ai&&n[Ai]){n=n[Ai]();for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}return t=yo(n),("[object Map]"==t?L:"[object Set]"==t?D:Du)(n)}function Eu(n){return n?(n=Iu(n),n===N||n===-N?1.7976931348623157e308*(0>n?-1:1):n===n?n:0):0===n?n:0}function Ou(n){n=Eu(n);var t=n%1;return n===n?t?n-t:n:0}function Su(n){return n?gt(Ou(n),0,4294967295):0}function Iu(n){if(typeof n=="number")return n;if(Au(n))return P;if(bu(n)&&(n=typeof n.valueOf=="function"?n.valueOf():n,n=bu(n)?n+"":n),typeof n!="string")return 0===n?n:+n; -n=n.replace(cn,"");var t=bn.test(n);return t||jn.test(n)?Fn(n.slice(2),t?2:8):yn.test(n)?P:+n}function Ru(n){return Tr(n,Uu(n))}function zu(n){return null==n?"":jr(n)}function Wu(n,t,r){return n=null==n?F:It(n,t),n===F?r:n}function Bu(n,t){return null!=n&&ke(n,t,Lt)}function Lu(n){return pu(n)?Gn(n):Ht(n)}function Uu(n){if(pu(n))n=Gn(n,true);else if(bu(n)){var t,r=Le(n),e=[];for(t in n)("constructor"!=t||!r&&ci.call(n,t))&&e.push(t);n=e}else{if(t=[],null!=n)for(r in ni(n))t.push(r);n=t}return n}function Cu(n,t){ -if(null==n)return{};var r=l(ye(n),function(n){return[n]});return t=je(t),ur(n,r,function(n,r){return t(n,r[0])})}function Du(n){return null==n?[]:I(n,Lu(n))}function Mu(n){return Nf(zu(n).toLowerCase())}function Tu(n){return(n=zu(n))&&n.replace(mn,rt).replace(Rn,"")}function $u(n,t,r){return n=zu(n),t=r?F:t,t===F?Ln.test(n)?n.match(Wn)||[]:n.match(_n)||[]:n.match(t)||[]}function Fu(n){return function(){return n}}function Nu(n){return n}function Pu(n){return Gt(typeof n=="function"?n:dt(n,1))}function Zu(n,t,r){ -var e=Lu(t),i=St(t,e);null!=r||bu(t)&&(i.length||!e.length)||(r=t,t=n,n=this,i=St(t,Lu(t)));var o=!(bu(r)&&"chain"in r&&!r.chain),f=gu(n);return u(i,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;if(o||t){var r=n(this.__wrapped__);return(r.__actions__=Mr(this.__actions__)).push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,s([this.value()],arguments))})}),n}function qu(){}function Vu(n){return We(n)?j($e(n)):ir(n)}function Ku(){return[]}function Gu(){ -return false}En=null==En?Zn:it.defaults(Zn.Object(),En,it.pick(Zn,Un));var Hu=En.Array,Ju=En.Date,Yu=En.Error,Qu=En.Function,Xu=En.Math,ni=En.Object,ti=En.RegExp,ri=En.String,ei=En.TypeError,ui=Hu.prototype,ii=ni.prototype,oi=En["__core-js_shared__"],fi=Qu.prototype.toString,ci=ii.hasOwnProperty,ai=0,li=function(){var n=/[^.]+$/.exec(oi&&oi.keys&&oi.keys.IE_PROTO||"");return n?"Symbol(src)_1."+n:""}(),si=ii.toString,hi=fi.call(ni),pi=Zn._,_i=ti("^"+fi.call(ci).replace(on,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),vi=Kn?En.Buffer:F,gi=En.Symbol,di=En.Uint8Array,yi=vi?vi.f:F,bi=U(ni.getPrototypeOf,ni),xi=ni.create,ji=ii.propertyIsEnumerable,wi=ui.splice,mi=gi?gi.isConcatSpreadable:F,Ai=gi?gi.iterator:F,ki=gi?gi.toStringTag:F,Ei=function(){ -try{var n=Ae(ni,"defineProperty");return n({},"",{}),n}catch(n){}}(),Oi=En.clearTimeout!==Zn.clearTimeout&&En.clearTimeout,Si=Ju&&Ju.now!==Zn.Date.now&&Ju.now,Ii=En.setTimeout!==Zn.setTimeout&&En.setTimeout,Ri=Xu.ceil,zi=Xu.floor,Wi=ni.getOwnPropertySymbols,Bi=vi?vi.isBuffer:F,Li=En.isFinite,Ui=ui.join,Ci=U(ni.keys,ni),Di=Xu.max,Mi=Xu.min,Ti=Ju.now,$i=En.parseInt,Fi=Xu.random,Ni=ui.reverse,Pi=Ae(En,"DataView"),Zi=Ae(En,"Map"),qi=Ae(En,"Promise"),Vi=Ae(En,"Set"),Ki=Ae(En,"WeakMap"),Gi=Ae(ni,"create"),Hi=Ki&&new Ki,Ji={},Yi=Fe(Pi),Qi=Fe(Zi),Xi=Fe(qi),no=Fe(Vi),to=Fe(Ki),ro=gi?gi.prototype:F,eo=ro?ro.valueOf:F,uo=ro?ro.toString:F,io=function(){ -function n(){}return function(t){return bu(t)?xi?xi(t):(n.prototype=t,t=new n,n.prototype=F,t):{}}}();On.templateSettings={escape:Q,evaluate:X,interpolate:nn,variable:"",imports:{_:On}},On.prototype=Sn.prototype,On.prototype.constructor=On,zn.prototype=io(Sn.prototype),zn.prototype.constructor=zn,Mn.prototype=io(Sn.prototype),Mn.prototype.constructor=Mn,Tn.prototype.clear=function(){this.__data__=Gi?Gi(null):{},this.size=0},Tn.prototype.delete=function(n){return n=this.has(n)&&delete this.__data__[n], -this.size-=n?1:0,n},Tn.prototype.get=function(n){var t=this.__data__;return Gi?(n=t[n],"__lodash_hash_undefined__"===n?F:n):ci.call(t,n)?t[n]:F},Tn.prototype.has=function(n){var t=this.__data__;return Gi?t[n]!==F:ci.call(t,n)},Tn.prototype.set=function(n,t){var r=this.__data__;return this.size+=this.has(n)?0:1,r[n]=Gi&&t===F?"__lodash_hash_undefined__":t,this},Nn.prototype.clear=function(){this.__data__=[],this.size=0},Nn.prototype.delete=function(n){var t=this.__data__;return n=lt(t,n),!(0>n)&&(n==t.length-1?t.pop():wi.call(t,n,1), ---this.size,true)},Nn.prototype.get=function(n){var t=this.__data__;return n=lt(t,n),0>n?F:t[n][1]},Nn.prototype.has=function(n){return-1e?(++this.size,r.push([n,t])):r[e][1]=t,this},Pn.prototype.clear=function(){this.size=0,this.__data__={hash:new Tn,map:new(Zi||Nn),string:new Tn}},Pn.prototype.delete=function(n){return n=we(this,n).delete(n),this.size-=n?1:0,n},Pn.prototype.get=function(n){return we(this,n).get(n); -},Pn.prototype.has=function(n){return we(this,n).has(n)},Pn.prototype.set=function(n,t){var r=we(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this},qn.prototype.add=qn.prototype.push=function(n){return this.__data__.set(n,"__lodash_hash_undefined__"),this},qn.prototype.has=function(n){return this.__data__.has(n)},Vn.prototype.clear=function(){this.__data__=new Nn,this.size=0},Vn.prototype.delete=function(n){var t=this.__data__;return n=t.delete(n),this.size=t.size,n},Vn.prototype.get=function(n){ -return this.__data__.get(n)},Vn.prototype.has=function(n){return this.__data__.has(n)},Vn.prototype.set=function(n,t){var r=this.__data__;if(r instanceof Nn){var e=r.__data__;if(!Zi||199>e.length)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new Pn(e)}return r.set(n,t),this.size=r.size,this};var oo=Zr(Et),fo=Zr(Ot,true),co=qr(),ao=qr(true),lo=Hi?function(n,t){return Hi.set(n,t),n}:Nu,so=Ei?function(n,t){return Ei(n,"toString",{configurable:true,enumerable:false,value:Fu(t),writable:true})}:Nu,ho=Oi||function(n){ -return Zn.clearTimeout(n)},po=Vi&&1/D(new Vi([,-0]))[1]==N?function(n){return new Vi(n)}:qu,_o=Hi?function(n){return Hi.get(n)}:qu,vo=Wi?function(n){return null==n?[]:(n=ni(n),f(Wi(n),function(t){return ji.call(n,t)}))}:Ku,go=Wi?function(n){for(var t=[];n;)s(t,vo(n)),n=bi(n);return t}:Ku,yo=zt;(Pi&&"[object DataView]"!=yo(new Pi(new ArrayBuffer(1)))||Zi&&"[object Map]"!=yo(new Zi)||qi&&"[object Promise]"!=yo(qi.resolve())||Vi&&"[object Set]"!=yo(new Vi)||Ki&&"[object WeakMap]"!=yo(new Ki))&&(yo=function(n){ -var t=zt(n);if(n=(n="[object Object]"==t?n.constructor:F)?Fe(n):"")switch(n){case Yi:return"[object DataView]";case Qi:return"[object Map]";case Xi:return"[object Promise]";case no:return"[object Set]";case to:return"[object WeakMap]"}return t});var bo=oi?gu:Gu,xo=Me(lo),jo=Ii||function(n,t){return Zn.setTimeout(n,t)},wo=Me(so),mo=function(n){n=lu(n,function(n){return 500===t.size&&t.clear(),n});var t=n.cache;return n}(function(n){var t=[];return en.test(n)&&t.push(""),n.replace(un,function(n,r,e,u){ -t.push(e?u.replace(vn,"$1"):r||n)}),t}),Ao=lr(function(n,t){return _u(n)?jt(n,kt(t,1,_u,true)):[]}),ko=lr(function(n,t){var r=Ge(t);return _u(r)&&(r=F),_u(n)?jt(n,kt(t,1,_u,true),je(r,2)):[]}),Eo=lr(function(n,t){var r=Ge(t);return _u(r)&&(r=F),_u(n)?jt(n,kt(t,1,_u,true),F,r):[]}),Oo=lr(function(n){var t=l(n,Sr);return t.length&&t[0]===n[0]?Ut(t):[]}),So=lr(function(n){var t=Ge(n),r=l(n,Sr);return t===Ge(r)?t=F:r.pop(),r.length&&r[0]===n[0]?Ut(r,je(t,2)):[]}),Io=lr(function(n){var t=Ge(n),r=l(n,Sr);return(t=typeof t=="function"?t:F)&&r.pop(), -r.length&&r[0]===n[0]?Ut(r,F,t):[]}),Ro=lr(He),zo=ge(function(n,t){var r=null==n?0:n.length,e=vt(n,t);return fr(n,l(t,function(n){return Re(n,r)?+n:n}).sort(Ur)),e}),Wo=lr(function(n){return wr(kt(n,1,_u,true))}),Bo=lr(function(n){var t=Ge(n);return _u(t)&&(t=F),wr(kt(n,1,_u,true),je(t,2))}),Lo=lr(function(n){var t=Ge(n),t=typeof t=="function"?t:F;return wr(kt(n,1,_u,true),F,t)}),Uo=lr(function(n,t){return _u(n)?jt(n,t):[]}),Co=lr(function(n){return Er(f(n,_u))}),Do=lr(function(n){var t=Ge(n);return _u(t)&&(t=F), -Er(f(n,_u),je(t,2))}),Mo=lr(function(n){var t=Ge(n),t=typeof t=="function"?t:F;return Er(f(n,_u),F,t)}),To=lr(Ye),$o=lr(function(n){var t=n.length,t=1=t}),cf=Mt(function(){return arguments}())?Mt:function(n){return xu(n)&&ci.call(n,"callee")&&!ji.call(n,"callee")},af=Hu.isArray,lf=Hn?S(Hn):Tt,sf=Bi||Gu,hf=Jn?S(Jn):$t,pf=Yn?S(Yn):Nt,_f=Qn?S(Qn):qt,vf=Xn?S(Xn):Vt,gf=nt?S(nt):Kt,df=oe(Jt),yf=oe(function(n,t){return n<=t}),bf=Pr(function(n,t){ -if(Le(t)||pu(t))Tr(t,Lu(t),n);else for(var r in t)ci.call(t,r)&&at(n,r,t[r])}),xf=Pr(function(n,t){Tr(t,Uu(t),n)}),jf=Pr(function(n,t,r,e){Tr(t,Uu(t),n,e)}),wf=Pr(function(n,t,r,e){Tr(t,Lu(t),n,e)}),mf=ge(vt),Af=lr(function(n){return n.push(F,se),r(jf,F,n)}),kf=lr(function(n){return n.push(F,he),r(Rf,F,n)}),Ef=ne(function(n,t,r){n[t]=r},Fu(Nu)),Of=ne(function(n,t,r){ci.call(n,t)?n[t].push(r):n[t]=[r]},je),Sf=lr(Dt),If=Pr(function(n,t,r){nr(n,t,r)}),Rf=Pr(function(n,t,r,e){nr(n,t,r,e)}),zf=ge(function(n,t){ -var r={};if(null==n)return r;var e=false;t=l(t,function(t){return t=Rr(t,n),e||(e=1--n)return t.apply(this,arguments)}},On.ary=iu,On.assign=bf,On.assignIn=xf,On.assignInWith=jf,On.assignWith=wf,On.at=mf,On.before=ou,On.bind=Yo,On.bindAll=Zf,On.bindKey=Qo,On.castArray=function(){if(!arguments.length)return[];var n=arguments[0];return af(n)?n:[n]}, -On.chain=Xe,On.chunk=function(n,t,r){if(t=(r?ze(n,t,r):t===F)?1:Di(Ou(t),0),r=null==n?0:n.length,!r||1>t)return[];for(var e=0,u=0,i=Hu(Ri(r/t));et?0:t,e)):[]},On.dropRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===F?1:Ou(t),t=e-t,vr(n,0,0>t?0:t)):[]},On.dropRightWhile=function(n,t){return n&&n.length?Ar(n,je(t,3),true,true):[]},On.dropWhile=function(n,t){return n&&n.length?Ar(n,je(t,3),true):[]},On.fill=function(n,t,r,e){var u=null==n?0:n.length;if(!u)return[];for(r&&typeof r!="number"&&ze(n,t,r)&&(r=0,e=u),u=n.length,r=Ou(r),0>r&&(r=-r>u?0:u+r),e=e===F||e>u?u:Ou(e),0>e&&(e+=u),e=r>e?0:Su(e);r>>0,r?(n=zu(n))&&(typeof t=="string"||null!=t&&!_f(t))&&(t=jr(t), -!t&&Bn.test(n))?zr($(n),0,r):n.split(t,r):[]},On.spread=function(n,t){if(typeof n!="function")throw new ei("Expected a function");return t=null==t?0:Di(Ou(t),0),lr(function(e){var u=e[t];return e=zr(e,0,t),u&&s(e,u),r(n,this,e)})},On.tail=function(n){var t=null==n?0:n.length;return t?vr(n,1,t):[]},On.take=function(n,t,r){return n&&n.length?(t=r||t===F?1:Ou(t),vr(n,0,0>t?0:t)):[]},On.takeRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===F?1:Ou(t),t=e-t,vr(n,0>t?0:t,e)):[]},On.takeRightWhile=function(n,t){ -return n&&n.length?Ar(n,je(t,3),false,true):[]},On.takeWhile=function(n,t){return n&&n.length?Ar(n,je(t,3)):[]},On.tap=function(n,t){return t(n),n},On.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new ei("Expected a function");return bu(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),au(n,t,{leading:e,maxWait:t,trailing:u})},On.thru=nu,On.toArray=ku,On.toPairs=Bf,On.toPairsIn=Lf,On.toPath=function(n){return af(n)?l(n,$e):Au(n)?[n]:Mr(mo(zu(n)))},On.toPlainObject=Ru, -On.transform=function(n,t,r){var e=af(n),i=e||sf(n)||gf(n);if(t=je(t,4),null==r){var o=n&&n.constructor;r=i?e?new o:[]:bu(n)&&gu(o)?io(bi(n)):{}}return(i?u:Et)(n,function(n,e,u){return t(r,n,e,u)}),r},On.unary=function(n){return iu(n,1)},On.union=Wo,On.unionBy=Bo,On.unionWith=Lo,On.uniq=function(n){return n&&n.length?wr(n):[]},On.uniqBy=function(n,t){return n&&n.length?wr(n,je(t,2)):[]},On.uniqWith=function(n,t){return t=typeof t=="function"?t:F,n&&n.length?wr(n,F,t):[]},On.unset=function(n,t){return null==n||mr(n,t); -},On.unzip=Ye,On.unzipWith=Qe,On.update=function(n,t,r){return null==n?n:pr(n,t,Ir(r)(It(n,t)),void 0)},On.updateWith=function(n,t,r,e){return e=typeof e=="function"?e:F,null!=n&&(n=pr(n,t,Ir(r)(It(n,t)),e)),n},On.values=Du,On.valuesIn=function(n){return null==n?[]:I(n,Uu(n))},On.without=Uo,On.words=$u,On.wrap=function(n,t){return rf(Ir(t),n)},On.xor=Co,On.xorBy=Do,On.xorWith=Mo,On.zip=To,On.zipObject=function(n,t){return Or(n||[],t||[],at)},On.zipObjectDeep=function(n,t){return Or(n||[],t||[],pr); -},On.zipWith=$o,On.entries=Bf,On.entriesIn=Lf,On.extend=xf,On.extendWith=jf,Zu(On,On),On.add=nc,On.attempt=Pf,On.camelCase=Uf,On.capitalize=Mu,On.ceil=tc,On.clamp=function(n,t,r){return r===F&&(r=t,t=F),r!==F&&(r=Iu(r),r=r===r?r:0),t!==F&&(t=Iu(t),t=t===t?t:0),gt(Iu(n),t,r)},On.clone=function(n){return dt(n,4)},On.cloneDeep=function(n){return dt(n,5)},On.cloneDeepWith=function(n,t){return t=typeof t=="function"?t:F,dt(n,5,t)},On.cloneWith=function(n,t){return t=typeof t=="function"?t:F,dt(n,4,t)}, -On.conformsTo=function(n,t){return null==t||bt(n,t,Lu(t))},On.deburr=Tu,On.defaultTo=function(n,t){return null==n||n!==n?t:n},On.divide=rc,On.endsWith=function(n,t,r){n=zu(n),t=jr(t);var e=n.length,e=r=r===F?e:gt(Ou(r),0,e);return r-=t.length,0<=r&&n.slice(r,e)==t},On.eq=hu,On.escape=function(n){return(n=zu(n))&&Y.test(n)?n.replace(H,et):n},On.escapeRegExp=function(n){return(n=zu(n))&&fn.test(n)?n.replace(on,"\\$&"):n},On.every=function(n,t,r){var e=af(n)?o:wt;return r&&ze(n,t,r)&&(t=F),e(n,je(t,3)); -},On.find=Po,On.findIndex=Ze,On.findKey=function(n,t){return v(n,je(t,3),Et)},On.findLast=Zo,On.findLastIndex=qe,On.findLastKey=function(n,t){return v(n,je(t,3),Ot)},On.floor=ec,On.forEach=ru,On.forEachRight=eu,On.forIn=function(n,t){return null==n?n:co(n,je(t,3),Uu)},On.forInRight=function(n,t){return null==n?n:ao(n,je(t,3),Uu)},On.forOwn=function(n,t){return n&&Et(n,je(t,3))},On.forOwnRight=function(n,t){return n&&Ot(n,je(t,3))},On.get=Wu,On.gt=of,On.gte=ff,On.has=function(n,t){return null!=n&&ke(n,t,Bt); -},On.hasIn=Bu,On.head=Ke,On.identity=Nu,On.includes=function(n,t,r,e){return n=pu(n)?n:Du(n),r=r&&!e?Ou(r):0,e=n.length,0>r&&(r=Di(e+r,0)),mu(n)?r<=e&&-1r&&(r=Di(e+r,0)),d(n,t,r)):-1},On.inRange=function(n,t,r){return t=Eu(t),r===F?(r=t,t=0):r=Eu(r),n=Iu(n),n>=Mi(t,r)&&n=n},On.isSet=vf,On.isString=mu,On.isSymbol=Au,On.isTypedArray=gf,On.isUndefined=function(n){return n===F},On.isWeakMap=function(n){return xu(n)&&"[object WeakMap]"==yo(n)},On.isWeakSet=function(n){return xu(n)&&"[object WeakSet]"==zt(n)},On.join=function(n,t){ -return null==n?"":Ui.call(n,t)},On.kebabCase=Cf,On.last=Ge,On.lastIndexOf=function(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e;if(r!==F&&(u=Ou(r),u=0>u?Di(e+u,0):Mi(u,e-1)),t===t){for(r=u+1;r--&&n[r]!==t;);n=r}else n=g(n,b,u,true);return n},On.lowerCase=Df,On.lowerFirst=Mf,On.lt=df,On.lte=yf,On.max=function(n){return n&&n.length?mt(n,Nu,Wt):F},On.maxBy=function(n,t){return n&&n.length?mt(n,je(t,2),Wt):F},On.mean=function(n){return x(n,Nu)},On.meanBy=function(n,t){return x(n,je(t,2))},On.min=function(n){ -return n&&n.length?mt(n,Nu,Jt):F},On.minBy=function(n,t){return n&&n.length?mt(n,je(t,2),Jt):F},On.stubArray=Ku,On.stubFalse=Gu,On.stubObject=function(){return{}},On.stubString=function(){return""},On.stubTrue=function(){return true},On.multiply=uc,On.nth=function(n,t){return n&&n.length?tr(n,Ou(t)):F},On.noConflict=function(){return Zn._===this&&(Zn._=pi),this},On.noop=qu,On.now=Jo,On.pad=function(n,t,r){n=zu(n);var e=(t=Ou(t))?T(n):0;return!t||e>=t?n:(t=(t-e)/2,ee(zi(t),r)+n+ee(Ri(t),r))},On.padEnd=function(n,t,r){ -n=zu(n);var e=(t=Ou(t))?T(n):0;return t&&et){var e=n;n=t,t=e}return r||n%1||t%1?(r=Fi(),Mi(n+r*(t-n+$n("1e-"+((r+"").length-1))),t)):cr(n,t); -},On.reduce=function(n,t,r){var e=af(n)?h:m,u=3>arguments.length;return e(n,je(t,4),r,u,oo)},On.reduceRight=function(n,t,r){var e=af(n)?p:m,u=3>arguments.length;return e(n,je(t,4),r,u,fo)},On.repeat=function(n,t,r){return t=(r?ze(n,t,r):t===F)?1:Ou(t),ar(zu(n),t)},On.replace=function(){var n=arguments,t=zu(n[0]);return 3>n.length?t:t.replace(n[1],n[2])},On.result=function(n,t,r){t=Rr(t,n);var e=-1,u=t.length;for(u||(u=1,n=F);++en||9007199254740991=i)return n;if(i=r-T(e),1>i)return e; -if(r=o?zr(o,0,i).join(""):n.slice(0,i),u===F)return r+e;if(o&&(i+=r.length-i),_f(u)){if(n.slice(i).search(u)){var f=r;for(u.global||(u=ti(u.source,zu(dn.exec(u))+"g")),u.lastIndex=0;o=u.exec(f);)var c=o.index;r=r.slice(0,c===F?i:c)}}else n.indexOf(jr(u),i)!=i&&(u=r.lastIndexOf(u),-1e.__dir__?"Right":"")}),e},Mn.prototype[n+"Right"]=function(t){ -return this.reverse()[n](t).reverse()}}),u(["filter","map","takeWhile"],function(n,t){var r=t+1,e=1==r||3==r;Mn.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:je(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),u(["head","last"],function(n,t){var r="take"+(t?"Right":"");Mn.prototype[n]=function(){return this[r](1).value()[0]}}),u(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Mn.prototype[n]=function(){return this.__filtered__?new Mn(this):this[r](1); -}}),Mn.prototype.compact=function(){return this.filter(Nu)},Mn.prototype.find=function(n){return this.filter(n).head()},Mn.prototype.findLast=function(n){return this.reverse().find(n)},Mn.prototype.invokeMap=lr(function(n,t){return typeof n=="function"?new Mn(this):this.map(function(r){return Dt(r,n,t)})}),Mn.prototype.reject=function(n){return this.filter(su(je(n)))},Mn.prototype.slice=function(n,t){n=Ou(n);var r=this;return r.__filtered__&&(0t)?new Mn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)), -t!==F&&(t=Ou(t),r=0>t?r.dropRight(-t):r.take(t-n)),r)},Mn.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Mn.prototype.toArray=function(){return this.take(4294967295)},Et(Mn.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=On[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t);u&&(On.prototype[t]=function(){function t(n){return n=u.apply(On,s([n],f)),e&&h?n[0]:n}var o=this.__wrapped__,f=e?[1]:arguments,c=o instanceof Mn,a=f[0],l=c||af(o); -l&&r&&typeof a=="function"&&1!=a.length&&(c=l=false);var h=this.__chain__,p=!!this.__actions__.length,a=i&&!h,c=c&&!p;return!i&&l?(o=c?o:new Mn(this),o=n.apply(o,f),o.__actions__.push({func:nu,args:[t],thisArg:F}),new zn(o,h)):a&&c?n.apply(this,f):(o=this.thru(t),a?e?o.value()[0]:o.value():o)})}),u("pop push shift sort splice unshift".split(" "),function(n){var t=ui[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);On.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){ -var u=this.value();return t.apply(af(u)?u:[],n)}return this[r](function(r){return t.apply(af(r)?r:[],n)})}}),Et(Mn.prototype,function(n,t){var r=On[t];if(r){var e=r.name+"";(Ji[e]||(Ji[e]=[])).push({name:t,func:r})}}),Ji[Xr(F,2).name]=[{name:"wrapper",func:F}],Mn.prototype.clone=function(){var n=new Mn(this.__wrapped__);return n.__actions__=Mr(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Mr(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Mr(this.__views__), -n},Mn.prototype.reverse=function(){if(this.__filtered__){var n=new Mn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},Mn.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=af(t),u=0>r,i=e?t.length:0;n=i;for(var o=this.__views__,f=0,c=-1,a=o.length;++c=this.__values__.length;return{done:n,value:n?F:this.__values__[this.__index__++]}},On.prototype.plant=function(n){for(var t,r=this;r instanceof Sn;){var e=Pe(r);e.__index__=0,e.__values__=F,t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},On.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Mn?(this.__actions__.length&&(n=new Mn(this)),n=n.reverse(),n.__actions__.push({func:nu,args:[Je],thisArg:F}),new zn(n,this.__chain__)):this.thru(Je); -},On.prototype.toJSON=On.prototype.valueOf=On.prototype.value=function(){return kr(this.__wrapped__,this.__actions__)},On.prototype.first=On.prototype.head,Ai&&(On.prototype[Ai]=tu),On}();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Zn._=it, define(function(){return it})):Vn?((Vn.exports=it)._=it,qn._=it):Zn._=it}).call(this); \ No newline at end of file diff --git a/includes/rubik-regular-webfont.woff b/includes/rubik-regular-webfont.woff deleted file mode 100644 index e12d964..0000000 Binary files a/includes/rubik-regular-webfont.woff and /dev/null differ diff --git a/includes/rubik-regular-webfont.woff2 b/includes/rubik-regular-webfont.woff2 deleted file mode 100644 index 3a1b47a..0000000 Binary files a/includes/rubik-regular-webfont.woff2 and /dev/null differ diff --git a/includes/script.js b/includes/script.js deleted file mode 100644 index 5d6c0f0..0000000 --- a/includes/script.js +++ /dev/null @@ -1,551 +0,0 @@ -$(document).ready(function() { - // todo define things like 16x16, 128x128 etc. as constants? - // also script debounce/throttle times - let animationTime = 400; // defined in bitsy.js - - let bitsyData = {}; - - let palette = { - id: 0, - background: { - red: 62, - green: 43, - blue: 32 - }, - tile: { - red: 208, - green: 112, - blue: 56 - }, - sprite: { - red: 229, - green: 92, - blue: 68 - } - }; - - let room = []; - - let tileMatchThreshold = 64; - - let croptions = { - url: 'https://i.imgur.com/ThQZ94v.jpg', - viewport: {width: 128, height: 128, type: 'square'}, - boundary: {width: 256, height: 256}, - zoom: 0 - }; - - let $croppie = $('#croppie'); - - $croppie.croppie(croptions); - - function colourDifference(colour1, colour2) { - let difference = {}; - - _.each(['red', 'green', 'blue'], function(key) { - difference[key] = Math.abs(colour1[key] - colour2[key]); - }); - - return _.toInteger(_.sum(_.toArray(difference))); - } - - function zeroPad(input, desiredLength) { - while (input.length < desiredLength) { - input = "0" + input; - } - - return input; - } - - function colourToHex(colour) { - return '#' + zeroPad(Number(colour.red ).toString(16), 2) - + zeroPad(Number(colour.green).toString(16), 2) - + zeroPad(Number(colour.blue ).toString(16), 2); - } - - function hexToColour(hex) { - let rgb = hex.match(/[\da-f]{2}/gi); - - return { - red: parseInt(rgb[0], 16), - green: parseInt(rgb[1], 16), - blue: parseInt(rgb[2], 16) - }; - } - - function getClosestColour(initialColour, colourOptions) { - // ditch sprite colour as we're not using it atm - delete colourOptions.sprite; - - _.each(palette, function(colour, name) { - colourOptions[name].name = name; - colourOptions[name].difference = colourDifference(initialColour, colour); - }); - - // lowest difference (closest) wins - return _.first(_.sortBy(colourOptions, 'difference')); - } - - function newTileName() { - let tileNames = _.map(bitsyData.tiles, 'name'); - - let i = 1; // start with 1 as 0 is an implicit tile - - while (tileNames.indexOf(i.toString(36)) > -1) { - i++; - } - - // base 36 = 0-9a-z - return i.toString(36); - } - - function handleBitsyGameData() { - let input = $bitsyData.val(); - - if ( ! input) { - return; - } - - bitsyData = {}; - - // get palettes - let palettes = input.match(/PAL ([^\n]*)\n(NAME ([^\n]*)\n)?(([0-9,]+){3}\n){3,}/g); - - bitsyData.palettes = {}; - - // do palettes always go 0..n? - // will this cause problems if not? - _.each(palettes, function(palette, n) { - let name = ""; - - if (palette.match(/NAME (.+)\n/)) { - name = palette.match(/NAME (.+)\n/)[0].replace('NAME ', ''); - } else if (palette.match(/PAL (\d+)\n/)) { - name = palette.match(/PAL (\d+)\n/)[0].replace("PAL", "palette"); - } - - let colours = palette.match(/\d+,\d+,\d+/g); - - colours = _.map(colours, function(colour) { - let rgb = colour.split(','); - - return {red: rgb[0], green: rgb[1], blue: rgb[2]}; - }); - - bitsyData.palettes[name] = { - id: n, - background: colours[0], - tile: colours[1], - sprite: colours[2] - } - }); - - // get tiles - - bitsyData.tiles = []; - - // tile 0 (background colour only) is implicit in bitsy rather than being stored in the game data - // so, make our own version - bitsyData.tiles.push({ - name: "0", - bitmap: _.chunk(_.times(64, _.constant(0)), 8), - new: false // this could also be used to stop it from being added to the game data, wooo - }); - - // everything after > is an optional second animation frame - // todo: handle multiple animation frames! more than 2 are allowed (but not via the standard editor) - let tiles = input.match(/TIL (.*)\n([01]{8}\n){8}(>\n([01]{8}\n){8})?/g); - - _.each(tiles, function(tile) { - let name = tile.match(/TIL .*/)[0].replace('TIL ', ''); - - tile = tile.replace(/TIL .*\n/, ''); - - let bitmap = _.map(tile.match(/[01]/g), _.toInteger); - - let newTile = { - name: name, - new: false - }; - - // todo make this agnostic? i.e. tile.frames = _.chunk(bitmap, 64) - - if (bitmap.length === 64) { // normal tile - newTile.bitmap = _.chunk(bitmap, 8); - } else if (bitmap.length === 128) { // animated tile - newTile.bitmap = _.chunk(_.take( bitmap, 64), 8); - newTile.secondAnimationFrame = _.chunk(_.takeRight(bitmap, 64), 8); - } - - bitsyData.tiles.push(newTile); - }); - - if (_.find(bitsyData.palettes, {'id': palette.id})) { - // user has already selected a palette, leave it be - - // in case this is the first run: - palette = _.find(bitsyData.palettes, {'id': palette.id}) - - // if we just set the palette to the newly imported palette with the same ID, - // we will lose any changes the user has made to the palettes - // is this a big issue considering that the palettes cannot be currently saved anyway? - } else { - // set palette to first imported palette and redraw - palette = _.first(_.sortBy(bitsyData.palettes, 'id')); - } - - renderDebounced(); - - // update palette picker - $('tr.palette').remove(); - - _.each(bitsyData.palettes, function(palette, name) { - $('#palette tbody').append( - '' - + '' - + '' - + '' - + '' - + '' - + '' - + '' - + '' - + '' - ); - }); - - $('input[name="id"][value="' + palette.id + '"]').siblings(':radio').trigger('click'); - } - - function readFile(input, callback) { - if (input.files && input.files[0]) { - let reader = new FileReader(); - - reader.onload = callback; - - reader.readAsDataURL(input.files[0]); - } - } - - function readTextFile(input, callback) { - if (input.files && input.files[0]) { - let reader = new FileReader(); - - reader.onload = callback; - - reader.readAsText(input.files[0]); - } - } - - function render() { - $croppie.croppie('result', { - type: 'rawcanvas', - size: 'viewport' - }).then(function (result) { - let imageData = result.getContext('2d').getImageData(0, 0, 128, 128); - let rawData = imageData.data; - let monochrome = []; - - let brightnessAdjustment = parseFloat($('#brightness').val()); - - // for each pixel - for (let i = 0; i < rawData.length; i += 4) { - // this brightness adjustment is pretty crude but whatever - let pixel = { - red: _.clamp(rawData[i ] + brightnessAdjustment, 0, 255), - green: _.clamp(rawData[i + 1] + brightnessAdjustment, 0, 255), - blue: _.clamp(rawData[i + 2] + brightnessAdjustment, 0, 255) - }; - - let targetColour = getClosestColour(pixel, palette); - - if (targetColour.name === "background") { - monochrome.push(0); - } else { // tile - monochrome.push(1) - } - - rawData[i ] = targetColour.red; - rawData[i + 1] = targetColour.green; - rawData[i + 2] = targetColour.blue; - rawData[i + 3] = 255; // alpha - } - - // split monochrome bitmap into equal chunks for easier x:y access - monochrome = _.chunk(monochrome, 128); - - document.getElementById('preview').getContext('2d').putImageData(imageData, 0, 0); - - // tiled output - - room = []; - - _.times(16, function(tileY) { - _.times(16, function(tileX) { - // make pseudo-tile from monochrome bitmap - let pseudoTile = []; - - _.times(8, function(y) { - pseudoTile.push( - _.slice(monochrome[(tileY * 8) + y], (tileX * 8), (tileX * 8) + 8) - ); - }); - - let bestMatch; - - // if we want to always create new tiles, don't bother trying to check matches - if (tileMatchThreshold === 64) { - // even if we want to "always create new tiles" we still don't want to create duplicates - bestMatch = _.find(bitsyData.tiles, function(tile) { - return _.isEqual(tile.bitmap, pseudoTile); - }); - - if (bestMatch) { - bestMatch.match = 64; - } - } else { - _.each(bitsyData.tiles, function(tile) { - tile.match = 0; - - _.each(tile.bitmap, function(row, y) { - _.each(row, function(pixel, x) { - if (parseInt(pixel) === parseInt(pseudoTile[y][x])) { - tile.match++; - } - }); - }); - - if (tile.secondAnimationFrame) { - _.each(tile.secondAnimationFrame, function(row, y) { - _.each(row, function(pixel, x) { - if (parseInt(pixel) === parseInt(pseudoTile[y][x])) { - tile.match++; - } - }); - }); - - tile.match /= 2; - } - }); - - // what if there are several equally good matches? - // find highest match amount and find all of them - let bestMatchAmount = _.last(_.sortBy(bitsyData.tiles, ['match'])).match; - let bestMatches = _.filter(bitsyData.tiles, {'match': bestMatchAmount}); - - // sort by name in ascending order - // earlier names are preferable - bestMatch = _.first(_.sortBy(bestMatches, 'name')); - } - - if ( ! bestMatch || bestMatch.match < tileMatchThreshold) { - // turn pseudo-tile into a real tile and add it to the tile data - - let name = newTileName(); - - bitsyData.tiles.push({ - name: name, - bitmap: pseudoTile, - new: true - }); - - room.push(name); - - // issue with this approach: - // what if a tile we add late in the loop is a better match for an earlier "good enough" match? - // this would also cause different results if the user were to add the same room several times - // we could keep iterating until the room no longer changes - } else { - room.push(bestMatch.name); - } - }); - }); - - room = _.chunk(room, 16); - - // write room to output - - imageData = document.getElementById("room-output").getContext('2d').getImageData(0, 0, 128, 128); - rawData = imageData.data; - - _.each(room, function(row, tileY) { - _.each(row, function(tileName, tileX) { - let tile = _.find(bitsyData.tiles, {'name' : tileName}); - - _.each(tile.bitmap, function(row, y) { - _.each(row, function(pixel, x) { - let position = (((tileY * 8) + y) * 128) + ((tileX * 8) + x); - - position *= 4; // 4 values (rgba) per pixel - - let pixelColour = {}; - - switch(parseInt(pixel)) { - case 0: pixelColour = palette.background; break; - case 1: pixelColour = palette.tile; break; - default: console.log("error"); - } - - rawData[position ] = pixelColour.red; - rawData[position + 1] = pixelColour.green; - rawData[position + 2] = pixelColour.blue; - rawData[position + 3] = 255; - }); - }); - }); - }); - - document.getElementById('room-output').getContext('2d').putImageData(imageData, 0, 0); - }); - } - - let renderDebounced = _.debounce(render, 30); - let renderThrottled = _.throttle(render, 30); - - $croppie.on('update', renderDebounced); - - let $brightness = $('#brightness'); - - $brightness.on('change', renderThrottled); - - $brightness.on('dblclick', function() { - $(this).val(0); - - renderDebounced(); - }); - - $('label[for="brightness"]').on('click touchdown', function() { - $('#brightness').trigger('dblclick'); - }); - - let $bitsyData = $('#bitsy-data'); - - $bitsyData.on('change blur keyup', handleBitsyGameData); - - $bitsyData.on('focus', function() { - $(this).select(); - }); - - handleBitsyGameData(); - - $('#imageUpload').on('change', function () { - readFile(this, function (e) { - $croppie.croppie('bind', { - url: e.target.result, - zoom: 0 - }); - }); - }); - - $('input.game-data').on('change', function() { - readTextFile(this, function (e) { - $bitsyData.val(e.target.result); - handleBitsyGameData(); - }); - }); - - // these inputs get added and removed from the DOM so the event handler needs to be on the document - $(document).on('change', '#palette input', function() { - let id = parseInt($(this).closest('.palette').find('input[name="id"]').val()); - - // if this is a colour input, update the palette - if ($(this).attr('type') === 'color') { - if (id === palette.id) { - palette[$(this).attr('name')] = hexToColour($(this).val()); - } - } - - // if this is a radio button, pick this palette - if ($(this).attr('type') === 'radio') { - palette.id = id; - palette.background = hexToColour($(this).closest('.palette').find('input[name="background"]').val()); - palette.tile = hexToColour($(this).closest('.palette').find('input[name="tile"]' ).val()); - // sprite colour is not currently used - } - - renderDebounced(); - }); - - $(document).on('change', '#threshold', function() { - let newValue = parseInt($(this).val()); - - if (newValue < tileMatchThreshold) { - // set tiles back to default - bitsyData.tiles = _.filter(bitsyData.tiles, ['new', false]); - } - - tileMatchThreshold = newValue; - - renderThrottled(); - }); - - $('#never').on('click touchend', function() { - $('#threshold').val(0).change(); - }); - - $('#always').on('click touchend', function() { - $('#threshold').val(64).change(); - }); - - $('#save').on('click touchend', function() { - let $textArea = $('textarea'); - - let newGameData = $textArea.val(); - - // handle rooms - - // need to import IDs so we don't give the new room a conflicting ID - let roomIds = newGameData.match(/ROOM \d+\n/g); - - roomIds = _.map(roomIds, function(roomId) { - return parseInt(roomId.replace(/[^\d]+/g, "")); - }); - - let newRoomId = _.max(roomIds) + 1; - - let newRoomName = $('#roomName').val(); - // remove invalid chars? what's invalid? newlines? are those possible? - - let newRoom = "ROOM " + newRoomId + "\n"; - - _.each(room, function(row) { - newRoom += _.toString(row) + "\n"; - }); - - if (newRoomName) { - newRoom += "NAME " + newRoomName + "\n"; - } - - newRoom += "PAL " + palette.id + "\n"; - - newGameData = newGameData.replace(/(ROOM .*\n(.*\n)*PAL .*)/g, '$1\n\n' + newRoom); - - // handle tiles - - let newTiles = _.filter(bitsyData.tiles, 'new'); - let tileText = ""; - - _.each(newTiles, function(tile, n) { - tileText += "TIL " + tile.name + "\n"; //again, rename tile name to id... - - _.each(tile.bitmap, function(row) { - tileText += row.join('') + "\n"; - }); - - tileText += "NAME " + newRoomName + " " + (n + 1) + "\n"; - - // don't need to worry about animation right now - - tileText += "\n"; - }); - - newGameData = newGameData.replace(/(TIL.*(.*\n)*)SPR/g, '$1\n\n' + tileText + 'SPR'); - - // write - $textArea.val(newGameData); - - handleBitsyGameData(); - - // todo: give the user some nice "yay! it worked!" kinda feedback? - }); -}); diff --git a/includes/style.css b/includes/style.css deleted file mode 100644 index 8a6ebcb..0000000 --- a/includes/style.css +++ /dev/null @@ -1,112 +0,0 @@ -@font-face { - font-family: 'rubikregular'; - src: url('rubik-regular-webfont.woff2') format('woff2'), url('rubik-regular-webfont.woff') format('woff'); - font-weight: normal; - font-style: normal; -} -* { - box-sizing: border-box; - font-family: "rubikregular", sans-serif; - text-align: center; -} -html, -body { - background-color: #594a54; - color: #d3cbd0; -} -a { - color: #f69f8f; -} -canvas { - image-rendering: -moz-crisp-edges; - image-rendering: pixelated; -} -input[type="color"] { - width: 2em; - height: 2em; - margin: 0; - padding: 0; - border: none; - background: none; -} -input[type="color"][disabled] { - opacity: 0.5; -} -input[type="file"] { - padding: 1em 0; - width: 256px; -} -input[type="text"], -button { - width: 14em; - margin: 1em; - padding: 0.25em; - font-size: 1em; -} -table { - margin: 0 auto; -} -table td, -table th { - width: 50%; -} -table td { - text-align: left; -} -table th { - text-align: right; -} -textarea { - height: 256px; - width: 256px; - font-family: monospace; -} -textarea, -input { - text-align: left; -} -.box256 { - height: 256px; - width: 256px; -} -.centre { - margin: 0 auto; -} -.croppie-container { - height: auto; -} -.flex-container { - background-color: #d3cbd0; - display: flex; - flex-flow: row wrap; -} -.section { - margin: 0 auto; - background-color: #d3cbd0; - color: #594a54; - width: 256px; - padding-bottom: 1em; -} -#brightness { - width: 256px; -} -#threshold { - width: 150px; -} -#brightness + label, -#threshold + label { - margin: 0 auto; -} -#palettes { - height: 256px; - width: 256px; - overflow-y: scroll; -} -#preview, -#room-output { - margin: 0 auto; - width: 256px; -} -#save { - margin-top: 0; -} diff --git a/includes/style.less b/includes/style.less index 98f8b33..e811bca 100644 --- a/includes/style.less +++ b/includes/style.less @@ -1,144 +1,133 @@ -@font-face { - font-family: 'rubikregular'; - src: url('rubik-regular-webfont.woff2') format('woff2'), - url('rubik-regular-webfont.woff') format('woff'); - font-weight: normal; - font-style: normal; -} - -@light: #d3cbd0; -@dark: #594a54; -@accent: #f69f8f; // pink +@background: #57506a; +@page-background: #968eb5; +@accent: #ec6d7d; +@text: #464256; * { box-sizing: border-box; - font-family: "rubikregular", sans-serif; + color: @text; + margin: 0 auto 0.5em auto; text-align: center; } html, body { - background-color: @dark; - color: @light; + background-color: @background; + font-size: 3vmin; + margin: 0; + padding: 0; } -a { +button { + padding: 1em; + white-space: nowrap; + width: 100%; + + &.pagination:not(.normal) { + position: absolute; + bottom: 5vmin; + width: auto; + + &.prev { + left: 5vmin; + } + + &.next, &.start { + right: 5vmin; + } + } +} + +header * { color: @accent; } -canvas { - image-rendering: -moz-crisp-edges; - image-rendering: pixelated; -} - -input[type="color"] { - &[disabled] { - opacity: 0.5; - } - - width: 2em; - height: 2em; +h1 { margin: 0; - padding: 0; - border: none; - background: none; } -input[type="file"] { - padding: 1em 0; - width: 256px; +h3 { + font-size: 0.9em; } -input[type="text"], button { - width: 14em; - margin: 1em; - padding: 0.25em; - font-size: 1em; +input { + width: 100%; + text-align: left; + + &[type="checkbox"] { + width: auto; + margin-right: 1em; + position: relative; + top: 0.25em; + left: 0.25em; + } } -table { - margin: 0 auto; +img { + max-width: 100%; + margin: 0; +} - td, th { - width: 50%; - } +p { + font-size: 0.8em; + margin: 0 auto 1em auto; +} - td { - text-align: left; - } +label { + font-size: 0.75em; + font-weight: bold; +} - th { - text-align: right; - } +select { + width: 100%; } textarea { - .box256; - - font-family: monospace; + height: 15em; + padding: 0.5em; + text-align: left; + width: 100%; } -textarea, input { +.background { + background-color: @background; + padding: 0.5em; + border-radius: 0.5em; +} + +.checkboxes label { + margin-right: 1em; +} + +.cropper-tools { + margin-top: 0; +} + +.half { + display: inline-block; + text-align: left; + width: 50%; +} + +.image-container { + height: 46vh; text-align: left; } -.box256 { - height: 256px; - width: 256px; +.page { + height: 80vmin; + width: 80vmin; + + background-color: @page-background; + color: @text; + border-radius: 5vmin; + box-shadow: @accent 1vmin 1vmin; + padding: 5vmin; + position: relative; } -.centre { - margin: 0 auto; -} - -.croppie-container { - height: auto; -} - -.flex-container { - background-color: @light; - display: flex; - flex-flow: row wrap; -} - -// make this just for desktop view? -// put sections in a single column on mobile/etc? -.section { - .centre; - - background-color: @light; - color: @dark; +#preview { width: 256px; - padding-bottom: 1em; -} - -#brightness { - width: 256px; -} - -#threshold { - width: 150px; -} - -#brightness, #threshold { - + label{ - .centre; - } - - // todo make this match the croppie slider or vice versa -} - -#palettes { - .box256; - - overflow-y: scroll; -} - -#preview, #room-output { - .centre; - - width: 256px; -} - -#save { - margin-top: 0; + height: 256px; + image-rendering: pixelated; + image-rendering: crisp-edges; } diff --git a/index.html b/index.html deleted file mode 100644 index 4bc52bc..0000000 --- a/index.html +++ /dev/null @@ -1,90 +0,0 @@ -image to bitsy

image-to-bitsy

convert any image to a bitsy room

about -| -please contact me if you have any issues: -twitter, -email -

game data

paste or upload your game data (or html) here

(maybe make a backup first)

image

palette

preview

output



\ No newline at end of file diff --git a/index.pug b/index.pug index a7c450a..7d0164f 100644 --- a/index.pug +++ b/index.pug @@ -1,96 +1,96 @@ doctype html -html +html(lang="en-gb") head meta(charset="utf-8") - title image to bitsy - - // lodash - script(src="includes/lodash.min.js") - - // jquery - script(src="includes/jquery.min.js") - - // croppie + title pixsy + link(rel="stylesheet" href="includes/style.css") link(rel="stylesheet" href="includes/croppie.css") - script(src="includes/croppie.js") - - // main stuff - link(rel="stylesheet" type="text/css" href="includes/style.css") - script(src="includes/script.js") + script(src="includes/croppie.min.js") body header - h1 image-to-bitsy - p convert any image to a #[a(href="https://ledoux.itch.io/bitsy") bitsy] room + h1 + | pixsy + //img(alt="pixsy" src="includes/pixsy.png") p. - #[a(href="https://github.com/synth-ruiner/bitsy-image-to-room") about] + convert images to Bitsy rooms + | + #[a(href="./old/") old version] | - please contact me if you have any issues: - #[a(href="https://twitter.com/synth_ruiner") twitter], #[a(href="mailto:max@tinybird.info") email] - - .flex-container - #game-data.section + | + #[a(href="https://twitter.com/synth_ruiner") twitter] + .pages + .page#start + button.normal.pagination.next#new create a new bitsy game + button.normal.pagination.next#load load an existing bitsy game + .page.game-data h2 game data - textarea#bitsy-data(placeholder="Bitsy data or html") - include includes/default.bitsy + input#game(type="file" autocomplete="off") + br - p - input.game-data(type="file") - p paste or upload your game data (or html) here - p (maybe make a backup first) + textarea#game-data( + placeholder="Paste your game data here or use the file chooser button above" + autocomplete="off" + ) - #image.section + button.pagination.prev previous + button.pagination.next#game-data-next(disabled=true) next + .page.image#page-image h2 image - #croppie + .image-container + input#image(type="file" accept="image/*") + #crop(style="display: none;") - input#imageUpload(type="file" accepts="image/*") + button.pagination.prev previous + button.pagination.next#image-next(disabled=true) next + .page.room#page-room + h2 room - #palette.section - h2 palette + table + tbody + tr + td(style="width: 60%") + img#preview(alt="preview") + br - form#palettes - table - tbody + label + | brightness + input#brightness(type="range" min=-96 max=96 value=0) + td(style="vertical-align: top;") + label + | name (optional) + input#room-name(type="text" placeholder="e.g. 'bedroom'" autocomplete="off") - #crop.section - h2 preview + label + | palette + select#palette - canvas#preview(width=128, height=128) + #new-palette(style="display: none;") + .half + input#colour-background(type="color" value="#000000") + .half + input#colour-foreground(type="color" value="#ffffff") - input#brightness(type="range" min=-255 max=255 value=0) + label + input#dither(type="checkbox" checked=true) + | dither + br - label(for="brightness") brightness + button.pagination.prev#back-to-image previous + button.pagination.next#room-next add room + .page.download + p#added - //- to do - input#dithering(type="checkbox") - label(for="dithering") dithering + h2 download - //- to do - input#smoothing(type="checkbox") - label(for="smoothing") smoothing - - #output.section - h2 output - - canvas#room-output(width=128, height=128) - - label#never never - input#threshold(type="range" min=0 max=64 value=64) - label#always always - br - label(for="threshold") create new tiles + textarea#output(autocomplete="off") br - input#roomName(type="text", placeholder="room name") + button#download download - button#save write to game data - - //- to do - - //-label favour broad strokes | favour details - - br - - //-button Download \ No newline at end of file + button.pagination.prev#add add another image + button.pagination.start#reset start again + script(type="module") + include script.js diff --git a/package.json b/package.json deleted file mode 100644 index 28778ee..0000000 --- a/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "bitsy-image-to-room", - "description": "Tool to convert images to Bitsy rooms. Can work as an offline web application", - "version": "0.0.1", - "devDependencies": { - "pug": "latest", - "pug-cli": "latest", - "less": "latest" - }, - "dependencies": { - "croppie": "^2.5.1", - "jquery": "^3.5.0" - } -} diff --git a/readme.md b/readme.md deleted file mode 100644 index a50634d..0000000 --- a/readme.md +++ /dev/null @@ -1,87 +0,0 @@ -# image to bitsy - -## about - -a tool for Bitsy. upload any image and convert it into a room. -the room will be added to the game data automatically and you can paste it back into Bitsy. - -## thanks - -to **Adam Le Doux** for creating the wonderful and inspiring Bitsy - -to **J.P. LeBreton** for creating Playscii which was a huge inspiration for this tool - -to **Mark Wonnacott** for being relentlessly encouraging and making me want to work even an eighth as hard as them - -to **Foliotek** for the **Croppie** image plugin - -## contributing - -Forks and pull requests welcome! - -The stylesheet and html are auto-generated; if you want to alter them please edit the pug template or less stylesheet, then either run the included `build.sh` script or build from the command line as follows: - -`pug index.pug index.html` - -`lessc style.less style.css` - -`pug` and `less` can be installed via `npm` as follows: - -`npm install -g pug-cli` - -`npm install -g less` - -I had to hack the included Croppie plugin to allow the user to upload images from their own computer without falling foul of CORS restrictions. So the version bundled here is non-standard. It's a one-line change: - -``` -// croppie.js:182 (original) -if (src.match(/^(https)?:\/\/|^\/\//)) { -// croppie.js:182 (mine) -if (src.match(/^(file|https)?:\/\/|^\/\//)) { -``` - -## bugs - -* does not work on ipad (can't scroll to the right?) - * possibly fixed; needs testing -* create tiles slider breaks onto 2 lines in some browsers? -* sometimes overwrites existing rooms? - -## to do - -* test new room ID handling!!! -* don't reuse wall tiles -* add 'clear'/'upload' buttons for game data -* select all on clicking game data entry field -* allow user to save output as image, or tweet it :) - * *user can currently right-click -> Save As but the 128x128 size is not great* -* animate animated tiles -* profile script performance and optimise where most needed -* make brightness slider trigger redraw every so often while being dragged, instead of waiting until drag stop -* add up/down arrows to the brightness slider for incremental tweaks -* add 'all-white' tile by default in case the game data doesn't have it? -* handle arbitrary animation frames (editor only supports 2 frames, but game data has no upper limit) -* list how many new tiles are being created -* combine preview and output -* give warning on duplicate room names? -* loading spinner on rendering? -* make brightness slider exponential -* rotation options for image - -## could do - -* add some alternate default tiles - something more useful e.g. dithered tiles, gradients -* add dithering options -* add camera support so users can take a pic instead of uploading an image -* add a 'smoothing' stage to remove errant pixels -* allow user to add palettes to game data -* allow user to draw to canvas -* do a 'branching tree' approach to finding the closest tile? i.e. create a 1x1, 2x2, 4x4 version of each tile, so all the broadly darker tiles will sit under '0' and lighter tiles under '1', then tiles that are lighter at the top will sit under '1100', etc... I'm not sure how much more effective this will be or whatever it will give better/faster results but it's worth a try -* give heavier weighting to edge pixels when finding a matching tile? (thanks Mark) -* apply grid lines to preview -* optionally add inverted versions of existing tiles if they are a better match -* make new tiles out of fragments of existing tiles instead of directly copying from bitmap -* allow user to zoom out so the image is letterboxed/windowboxed/etc. -* add 'invert' function -* allow for image colour hack -* make a tool for batch importing images? diff --git a/script.js b/script.js new file mode 100644 index 0000000..ed11d6e --- /dev/null +++ b/script.js @@ -0,0 +1,273 @@ +import init, { + add_room, + get_palettes, + get_preview, + load_image, + load_game, + load_default_game, + output, + set_brightness, + set_dither, + set_palette, + set_room_name, +} from './pkg/pixsy.js'; + +if (typeof WebAssembly !== "object") { + window.location = "./old/" +} + +// stolen from https://ourcodeworld.com/articles/read/189/how-to-create-a-file-and-generate-a-download-with-javascript-in-the-browser-without-a-server +function download(filename, text) { + let element = document.createElement('a'); + + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); +} + +function el(id) { + return document.getElementById(id); +} + +function pagination(e) { + const parent = e.target.parentNode; + + parent.style.display = "none"; + + if (e.target.classList.contains("next")) { + parent.nextSibling.style.display = "block"; + } else if (e.target.classList.contains("prev")) { + parent.previousSibling.style.display = "block"; + } else if (e.target.classList.contains("start")) { + document.getElementById("start").style.display = "block"; + } +} + +function readFile(input, callback, type = "text") { + if (input.files && input.files[0]) { + let reader = new FileReader(); + reader.onload = callback; + + if (type === "text") { + reader.readAsText(input.files[0]); + } else { + reader.readAsDataURL(input.files[0]); + } + } +} + +async function run() { + await init(); + + const buttonAddImage = el("add"); + const buttonBackToImage = el("back-to-image"); + const buttonDownload = el("download"); + const buttonGameDataProceed = el("game-data-next"); + const buttonImageProceed = el("image-next"); + const buttonRoomProceed = el("room-next"); + const buttonLoadGame = el("load"); + const buttonNewGame = el("new"); + const buttonReset = el("reset"); + const checkboxDither = el("dither"); + const divCroppie = el("crop"); + const divNewPalette = el("new-palette"); + const inputBrightness = el("brightness"); + const inputColourBackground = el("colour-background"); + const inputColourForeground = el("colour-foreground"); + const inputRoomName = el("room-name"); + const selectPalette = el("palette"); + const textareaGameDataInput = el("game-data"); + const textareaGameDataOutput = el("output"); + + const croppie = new Croppie(divCroppie, { + viewport: {width: 128, height: 128, type: 'square'}, + boundary: {width: 256, height: 256}, + enableZoom: true, + }); + + // hide all pages except start page + for (let page of document.getElementsByClassName('page')) { + page.style.display = "none"; + } + el("start").style.display = "block"; + + for (let pageButton of document.getElementsByClassName("pagination")) { + pageButton.addEventListener('click', pagination); + pageButton.addEventListener('touchend', pagination); + } + + // croppie needs to be on screen to work; + // halt pagination until we're finished gathering the results + buttonImageProceed.removeEventListener("click", pagination); + + function new_game() { + load_default_game(); + textareaGameDataInput.value = output(); + checkGameData(); + // we don't need to look at the default game data, so skip ahead to the image page + buttonGameDataProceed.click(); + } + + function clear_game() { + textareaGameDataInput.value = ""; + checkGameData(); + } + + buttonNewGame.addEventListener("click", new_game); + buttonNewGame.addEventListener("touchend", new_game); + buttonLoadGame.addEventListener("click", clear_game); + buttonLoadGame.addEventListener("touchend", clear_game); + + // handle game data and image + + el("game").addEventListener("change", function() { + readFile(this, function (e) { + textareaGameDataInput.value = e.target.result; + console.log(load_game(e.target.result)); + checkGameData(); + }, "text"); + }); + + function setPaletteDropdown() { + const palettes = JSON.parse(get_palettes()); + console.debug(palettes); + + selectPalette.innerHTML = ""; + + palettes.push({ + id: "NEW_PALETTE", + name: "new palette" + }); + + for (let palette of palettes) { + let option = document.createElement("option"); + + option.value = palette.id; + option.innerText = palette.name; + + selectPalette.appendChild(option); + } + } + + function checkGameData() { + if (textareaGameDataInput.value.length > 0) { + buttonGameDataProceed.removeAttribute("disabled"); + setPaletteDropdown(); + } else { + buttonGameDataProceed.setAttribute("disabled", "disabled"); + } + } + + textareaGameDataInput.addEventListener("change", checkGameData); + textareaGameDataInput.addEventListener("keyup", checkGameData); + checkGameData(); + + el('image').addEventListener('change', function () { + readFile(this, function (e) { + croppie.bind({url: e.target.result, zoom: 0}); + divCroppie.style.display = "block"; + buttonImageProceed.removeAttribute("disabled"); + }, "image"); + }); + + function loadPreview() { + el("preview").setAttribute("src", get_preview()); + } + + function handleImage() { + croppie.result({ + type: "base64", + size: "viewport", + format: "png", + }).then((result) => { + console.log("Loading image: " + load_image(result)); + + el("page-image").style.display = "none"; + el("page-room" ).style.display = "block"; + + loadPreview(); + }); + } + + buttonImageProceed.addEventListener("click", handleImage); + buttonImageProceed.addEventListener("touchend", handleImage); + + selectPalette.addEventListener("change", () => { + set_palette(selectPalette.value, inputColourBackground.value, inputColourForeground.value); + + if (selectPalette.value === "NEW_PALETTE") { + divNewPalette.style.display = "block"; + } else { + divNewPalette.style.display = "none"; + } + + loadPreview(); + }); + + function updateCustomPalette() { + set_palette(selectPalette.value, inputColourBackground.value, inputColourForeground.value); + loadPreview(); + } + + inputColourForeground.addEventListener("change", updateCustomPalette); + inputColourBackground.addEventListener("change", updateCustomPalette); + + checkboxDither.addEventListener("change", () => { + set_dither(checkboxDither.checked); + loadPreview(); + }); + + inputRoomName.addEventListener("change", () => { + set_room_name(inputRoomName.value); + }); + + inputBrightness.addEventListener("input", () => { + set_brightness(inputBrightness.value); + loadPreview(); + }); + + function addRoom() { + el("added").innerText = add_room(); + textareaGameDataOutput.value = output(); + } + + buttonRoomProceed.addEventListener("click", addRoom); + buttonRoomProceed.addEventListener("touchend", addRoom); + + function handleDownload() { + download("output.bitsy", textareaGameDataOutput.value); + } + + buttonDownload.addEventListener("click", handleDownload); + buttonDownload.addEventListener("touchend", handleDownload); + + function addImage() { + textareaGameDataInput.value = textareaGameDataOutput.value; + textareaGameDataOutput.value = ""; + buttonBackToImage.click(); + } + + buttonAddImage.addEventListener("click", addImage); + buttonAddImage.addEventListener("touchend", addImage); + + // would it be easier just to reload the page? lol + function reset() { + clear_game(); + // todo clear file inputs + inputBrightness.value = 0; + inputRoomName.value = ""; + selectPalette.innerHTML = ""; + divNewPalette.style.display = "none"; + inputColourBackground.value = "#000000"; + inputColourForeground.value = "#ffffff"; + checkboxDither.checked = true; + } + + buttonReset.addEventListener("click", reset); + buttonReset.addEventListener("touchend", reset); +} + +run(); diff --git a/src/colour_map.rs b/src/colour_map.rs new file mode 100644 index 0000000..0293982 --- /dev/null +++ b/src/colour_map.rs @@ -0,0 +1,74 @@ +use image::Rgba; + +#[derive(Clone, Copy)] +pub struct ColourMap { + background: Rgba, + foreground: Rgba, +} + +impl ColourMap { + pub(crate) fn from(palette: &bitsy_parser::Palette) -> ColourMap { + let background = Rgba::from([ + palette.colours[0].red, + palette.colours[0].green, + palette.colours[0].blue, + 255, + ]); + + let foreground = Rgba::from([ + palette.colours[1].red, + palette.colours[1].green, + palette.colours[1].blue, + 255, + ]); + + ColourMap { background, foreground } + } +} + +fn diff(a: &Rgba, b:&Rgba) -> u32 { + let diff_red = (a[0] as i16 - b[0] as i16).abs(); + let diff_green= (a[1] as i16 - b[1] as i16).abs(); + let diff_blue = (a[2] as i16 - b[2] as i16).abs(); + + (diff_red + diff_green + diff_blue) as u32 +} + +impl image::imageops::colorops::ColorMap for ColourMap { + type Color = Rgba; + + #[inline(always)] + fn index_of(&self, color: &Self::Color) -> usize { + let diff_background = diff(color, &self.background); + let diff_foreground = diff(color, &self.foreground); + + if diff_foreground <= diff_background { 1 } else { 0 } + } + + #[inline(always)] + fn lookup(&self, idx: usize) -> Option { + match idx { + 0 => Some(self.background.into()), + 1 => Some(self.foreground.into()), + _ => None, + } + } + + /// Indicate NeuQuant implements `lookup`. + fn has_lookup(&self) -> bool { + true + } + + #[inline(always)] + fn map_color(&self, color: &mut Self::Color) { + let closest = match self.index_of(color) { + 1 => self.foreground, + _ => self.background, + }; + + color[0] = closest[0]; + color[1] = closest[1]; + color[2] = closest[2]; + color[3] = closest[3]; + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b6178a1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,395 @@ +#![feature(clamp)] + +use bitsy_parser::game::Game; +use bitsy_parser::image::Image; +use bitsy_parser::tile::Tile; +use image::{GenericImageView, Pixel, DynamicImage}; +use image::imageops::ColorMap; +use image::imageops::FilterType::CatmullRom; +use lazy_static::lazy_static; +use std::sync::Mutex; +use wasm_bindgen::prelude::*; + +mod colour_map; + +use colour_map::ColourMap; + +const SD: u32 = 8; + +enum SelectedPalette { + None, + Existing { + id: String, + }, + New { + background: bitsy_parser::Colour, + foreground: bitsy_parser::Colour, + } +} + +struct State { + game: Option, + image: Option, + room_name: Option, + palette: SelectedPalette, + dither: bool, + brightness: i32, +} + +lazy_static! { + static ref STATE: Mutex = Mutex::new( + State { + game: None, + image: None, + room_name: None, + palette: SelectedPalette::None, + dither: true, + brightness: 0, + } + ); +} + +fn tile_name(prefix: &Option, x: &u32, y: &u32) -> Option { + if let Some(prefix) = prefix { + Some(format!("{} ({},{})", prefix, x, y)) + } else { + None + } +} + +#[wasm_bindgen] +pub fn load_default_game() { + let mut state = STATE.lock().unwrap(); + + state.game = Some(bitsy_parser::mock::game_default()); + + // yes, this will probably always just be "0", but to be safe… + state.palette = SelectedPalette::Existing { + id: bitsy_parser::mock::game_default().palette_ids()[0].clone() + } +} + +#[wasm_bindgen] +pub fn load_game(game_data: String) -> String { + let mut state = STATE.lock().unwrap(); + + let result = Game::from(game_data); + + match result { + Ok((game, _errs)) => { + let palette_id = game.palette_ids()[0].clone(); + state.game = Some(game); + state.palette = SelectedPalette::Existing { id: palette_id }; + format!("Loaded game") + }, + _ => { + state.game = None; + state.palette = SelectedPalette::None; + format!("{}", result.err().unwrap()) + } + } +} + +#[wasm_bindgen] +pub fn load_image(image_base64: String) -> String { + let mut state = STATE.lock().expect("Couldn't lock application state"); + + let image_base64: Vec<&str> = image_base64.split("base64,").collect(); + + if image_base64.len() < 2 { + return format!("Error: Badly-formatted base64: {}", image_base64.join("")); + } + + let image_base64 = image_base64[1]; + + match base64::decode(image_base64) { + Ok(image) => { + match image::load_from_memory(image.as_ref()) { + Ok(image) => { + let size = format!("{}×{}", image.width(), image.height()); + // todo get rid of magic numbers! what about Bitsy HD? + let image = image.resize(128, 128, CatmullRom); + state.image = Some(image); + size + }, + _ => { + state.image = None; + "Error: Couldn't load image".to_string() + } + } + }, + _ => { + state.image = None; + "Error: Couldn't decode image".to_string() + } + } +} + +#[wasm_bindgen] +pub fn set_dither(dither: bool) { + let mut state = STATE.lock().unwrap(); + state.dither = dither; +} + +#[wasm_bindgen] +pub fn set_palette(palette_id: &str, background: String, foreground: String) { + let mut state = STATE.lock().unwrap(); + + state.palette = match palette_id { + "NEW_PALETTE" => SelectedPalette::New { + background: bitsy_parser::Colour::from_hex(&background).unwrap(), + foreground: bitsy_parser::Colour::from_hex(&foreground).unwrap(), + }, + "" => SelectedPalette::None, + _ => SelectedPalette::Existing { id: palette_id.to_string() }, + } +} + +#[wasm_bindgen] +pub fn set_room_name(room_name: String) { + let mut state = STATE.lock().unwrap(); + + match room_name.is_empty() { + true => { state.room_name = None }, + false => { state.room_name = Some(room_name) }, + } +} + +#[wasm_bindgen] +pub fn set_brightness(brightness: i32) { + let mut state = STATE.lock().unwrap(); + state.brightness = brightness; +} + +#[wasm_bindgen] +pub fn get_palettes() -> String { + let state = STATE.lock().unwrap(); + + let mut palette_objects = json::JsonValue::new_array(); + + for palette in &state.game.as_ref().unwrap().palettes { + let mut object = json::JsonValue::new_object(); + + object.insert("id", palette.id.clone()).unwrap(); + + object.insert( + "name", + palette.name.clone().unwrap_or( + format!("Palette {}", palette.id)) + ).unwrap(); + + palette_objects.push(object).unwrap(); + } + + json::stringify(palette_objects) +} + +fn image_to_base64(image: &DynamicImage) -> String { + let mut bytes: Vec = Vec::new(); + image.write_to(&mut bytes, image::ImageOutputFormat::Png).unwrap(); + format!("data:image/png;base64,{}", base64::encode(&bytes)) +} + +fn palette_from(bg: &bitsy_parser::Colour, fg: &bitsy_parser::Colour) -> bitsy_parser::Palette { + bitsy_parser::Palette { + id: "0".to_string(), + name: None, + colours: vec![ + bg.clone(), fg.clone(), bitsy_parser::Colour { red: 0, green: 0, blue: 0 } + ], + } +} + +fn render_preview(state: &State) -> DynamicImage { + let mut buffer = state.image.as_ref().unwrap().clone().into_rgba(); + + let palette = match &state.palette { + SelectedPalette::None => bitsy_parser::mock::game_default().palettes[0].clone(), + SelectedPalette::Existing { id } => state.game.as_ref().unwrap().get_palette(id).unwrap().clone(), + SelectedPalette::New { background, foreground } => palette_from(background, foreground), + }; + + let colour_map = crate::ColourMap::from(&palette); + + // adjust brightness + let mut buffer = image::imageops::brighten(&mut buffer, state.brightness); + + if state.dither { + image::imageops::dither(&mut buffer, &colour_map); + } else { + // just do colour indexing + let indices = image::imageops::colorops::index_colors(&mut buffer, &colour_map); + + // todo get rid of magic numbers! what about Bitsy HD? + buffer = image::ImageBuffer::from_fn(128, 128, |x, y| -> image::Rgba { + let p = indices.get_pixel(x, y); + + colour_map + .lookup(p.0[0] as usize) + .expect("indexed colour out-of-range") + .into() + }); + } + + image::DynamicImage::ImageRgba8(buffer) +} + +#[wasm_bindgen] +pub fn get_preview() -> String { + let state = STATE.lock().unwrap(); + + match &state.image.is_some() { + true => image_to_base64(&render_preview(&state)), + false => "".to_string(), + } +} + +#[wasm_bindgen] +pub fn add_room() -> String { + let mut state = STATE.lock().expect("Couldn't lock application state"); + + if state.game.is_none() { + return "No game data loaded".to_string(); + } + + match &state.palette { + SelectedPalette::None => { return "No palette selected".to_string(); }, + _ => {} + }; + + let mut game = state.game.clone().unwrap(); + + if state.image.is_none() { + return "No image loaded".to_string(); + } + + let palette_id = Some(match &state.palette { + SelectedPalette::None => bitsy_parser::mock::game_default().palettes[0].id.clone(), + SelectedPalette::Existing { id } => id.clone(), + SelectedPalette::New { background, foreground } => { + game.add_palette(palette_from(background, foreground)) + }, + }); + + let foreground = &game.palettes + .iter() + .find(|&palette| &palette.id == palette_id.as_ref().unwrap()) + .unwrap().colours[1].clone(); + + let preview = render_preview(&state); + + let width = 128; + let height = 128; + let columns = (width as f64 / SD as f64).floor() as u32; + let rows = (height as f64 / SD as f64).floor() as u32; + + let mut tile_ids = Vec::new(); + let initial_tile_count = game.tiles.len(); + + for row in 0..rows { + for column in 0..columns { + let mut pixels = Vec::with_capacity(64); + + fn colour_match(a: &image::Rgb, b: &bitsy_parser::Colour) -> u8 { + if a[0] == b.red && a[1] == b.green && a[2] == b.blue { 1 } else { 0 } + }; + + for y in (row * SD)..((row + 1) * SD) { + for x in (column * SD)..((column + 1) * SD) { + let pixel = preview.get_pixel(x, y).to_rgb(); + pixels.push(colour_match(&pixel, foreground)); + } + } + + let tile = Tile { + // "0" will get overwritten to a new, safe tile ID + id: "0".to_string(), + name: tile_name(&state.room_name, &column, &row), + wall: None, + animation_frames: vec![Image { pixels }], + colour_id: None + }; + + let tile_id = if game.tiles.contains(&tile) { + game.tiles.iter().find(|&t| t == &tile).unwrap().id.clone() + } else { + game.add_tile(tile) + }; + + tile_ids.push(tile_id); + } + } + + game.add_room(bitsy_parser::Room { + id: "0".to_string(), + palette_id, + name: state.room_name.clone(), + tiles: tile_ids, + items: vec![], + exits: vec![], + endings: vec![], + walls: None + }); + + game.dedupe_tiles(); + + let new_tile_count = game.tiles.len(); + + state.game = Some(game.to_owned()); + + format!( + "Added room \"{}\" with {} new tiles", + &state.room_name.as_ref().unwrap_or(&"untitled".to_string()), + new_tile_count - initial_tile_count + ) +} + +#[wasm_bindgen] +pub fn output() -> String { + let state = STATE.lock().unwrap(); + + match &state.game { + Some(game) => game.to_string(), + None => "No game loaded".to_string(), + } +} + +#[cfg(test)] +mod test { + use crate::{add_room, load_image, load_default_game, output, get_preview, set_room_name}; + + #[test] + fn image_to_base64() { + let image = image::load_from_memory(include_bytes!("test-resources/test.png")).unwrap(); + let output = crate::image_to_base64(&image); + let expected = include_str!("test-resources/test.png.base64").trim(); + assert_eq!(output, expected); + } + + #[test] + fn get_palettes() { + load_default_game(); + let output = crate::get_palettes(); + let expected = "[{\"id\":\"0\",\"name\":\"blueprint\"}]"; + assert_eq!(output, expected); + } + + #[test] + fn render_preview() { + load_default_game(); + load_image(include_str!("test-resources/colour_input.png.base64").trim().to_string()); + let output = get_preview(); + let expected = include_str!("test-resources/colour_input.png.base64.greyscale").trim(); + assert_eq!(output, expected); + } + + #[test] + fn example() { + load_default_game(); + load_image(include_str!("test-resources/test.png.base64").trim().to_string()); + set_room_name("test".to_string()); + println!("add_room(): {}", add_room()); + // todo what? why are extraneous pixels appearing in the output tiles? + assert_eq!(output(), include_str!("test-resources/expected.bitsy")); + } +} diff --git a/src/test-resources/colour_input.png b/src/test-resources/colour_input.png new file mode 100644 index 0000000..227c1a3 Binary files /dev/null and b/src/test-resources/colour_input.png differ diff --git a/src/test-resources/colour_input.png.base64 b/src/test-resources/colour_input.png.base64 new file mode 100644 index 0000000..7321a57 --- /dev/null +++ b/src/test-resources/colour_input.png.base64 @@ -0,0 +1 @@ +data:image/png;base64,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 diff --git a/src/test-resources/colour_input.png.base64.greyscale b/src/test-resources/colour_input.png.base64.greyscale new file mode 100644 index 0000000..e6383ce --- /dev/null +++ b/src/test-resources/colour_input.png.base64.greyscale @@ -0,0 +1 @@ +data:image/png;base64,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 diff --git a/src/test-resources/expected.bitsy b/src/test-resources/expected.bitsy new file mode 100644 index 0000000..579845b --- /dev/null +++ b/src/test-resources/expected.bitsy @@ -0,0 +1,159 @@ +Write your game's title here + +# BITSY VERSION 7.2 + +! ROOM_FORMAT 1 + +PAL 0 +NAME blueprint +0,82,204 +128,159,255 +255,255,255 + +ROOM 0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,a,a,a,a,a,a,a,a,a,a,a,a,a,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,a,a,a,a,a,a,a,a,a,a,a,a,a,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +NAME example room +PAL 0 + +ROOM 1 +a,a,1,1,2,2,3,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +NAME test +PAL 0 + +TIL a +11111111 +10000001 +10000001 +10011001 +10011001 +10000001 +10000001 +11111111 +NAME block + +TIL 1 +00011000 +00111000 +00011000 +00011000 +00011000 +00011000 +00011000 +00111100 +NAME test (2,0) + +TIL 2 +00111100 +01100110 +01100110 +00001100 +00011000 +00110000 +01100000 +01111110 +NAME test (4,0) + +TIL 3 +00111100 +01100110 +01100110 +00001100 +00001100 +01100110 +01100110 +00111100 +NAME test (6,0) + +SPR A +00011000 +00011000 +00011000 +00111100 +01111110 +10111101 +00100100 +00100100 +POS 0 4,4 + +SPR a +00000000 +00000000 +01010001 +01110001 +01110010 +01111100 +00111100 +00100100 +NAME cat +DLG 0 +POS 0 8,12 + +ITM 0 +00000000 +00000000 +00000000 +00111100 +01100100 +00100100 +00011000 +00000000 +NAME tea +DLG 1 + +ITM 1 +00000000 +00111100 +00100100 +00111100 +00010000 +00011000 +00010000 +00011000 +NAME key +DLG 2 + +DLG 0 +I'm a cat +NAME cat dialog + +DLG 1 +You found a nice warm cup of tea +NAME tea dialog + +DLG 2 +A key! {wvy}What does it open?{wvy} +NAME key dialog + +VAR a +42 + diff --git a/src/test-resources/test.png b/src/test-resources/test.png new file mode 100644 index 0000000..0455f0c Binary files /dev/null and b/src/test-resources/test.png differ diff --git a/src/test-resources/test.png.base64 b/src/test-resources/test.png.base64 new file mode 100644 index 0000000..acc6769 --- /dev/null +++ b/src/test-resources/test.png.base64 @@ -0,0 +1 @@ +data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAcJJREFUeJzt19FqgzAAQFEz+v+/nD1t2NLWsQgW7jlP2ogVvUYdc865LRhj/C7/7Gr/29678cfDeNxmdZznbtv2vwu2H39cPtr26H/mnNuc8259ZZzXvs7a0dHJdjE+022/sjKFrzh6jKyO89ppM8AZrgqw7C6AMcbbk3s0vmL1BfLZewDHPmIGcOdfZ5z1GbjymfbqEI7u5nf7EMvfjG3bzJVhH/EI4DoCiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHEfQM+SHkFb+/YEwAAAABJRU5ErkJggg==