diff --git a/css/main.css b/css/main.css index 845e4f8..602b559 100644 --- a/css/main.css +++ b/css/main.css @@ -9,7 +9,6 @@ html { - background-color: var(--bg-color); color: var(--text-color); font-size: 1em; line-height: 1.4; @@ -62,11 +61,23 @@ html { } #bg { - z-index: -1; + z-index: -2; overflow: hidden; position: fixed; + min-width: 100%; + min-height: 100%; + background: rgb(34,34,51); + background: linear-gradient(180deg, rgba(34,34,51,1) 0%, rgba(34,34,51,1) 70%, rgba(171, 92, 102,1) 120%); +} + +#bg img { + z-index: -1; + position: fixed; + /* max-width: 100%; + max-height: 100%; */ } + #desk { /* display: none; */ display: flex; @@ -285,6 +296,15 @@ input[type=range]#crossfader::-ms-thumb { /*Needed to keep the Edge thumb centred*/ } + +.footer a, .footer a:visited { + color: var(--text-color); + text-decoration: none; +} + +.footer span { + cursor: pointer; +} /* ========================================================================== Helper classes ========================================================================== */ diff --git a/img/stars_1920x1080.svg b/img/stars_1920x1080.svg new file mode 100644 index 0000000..659cd24 --- /dev/null +++ b/img/stars_1920x1080.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/stars_2560x1440.svg b/img/stars_2560x1440.svg new file mode 100644 index 0000000..50c075c --- /dev/null +++ b/img/stars_2560x1440.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html index 5d2f141..458af08 100644 --- a/index.html +++ b/index.html @@ -24,7 +24,9 @@ -
+
+ A night sky with plenty of stars +
@@ -68,16 +70,21 @@
--> -

YTDJ!

+

Mix like a pro youtube DJ!

+ - + - + diff --git a/js/main.js b/js/main.js index ba25657..e3c1d11 100644 --- a/js/main.js +++ b/js/main.js @@ -201,3 +201,20 @@ function componentToHex(c) { } // Thumbnails: https://i1.ytimg.com/vi/{trackID}/mqdefault.jpg +if (typeof variable !== 'undefined') { + let separatorSpan = document.createElement('span'); + separatorSpan.innerHTML = ' - '; + document.getElementById('shooting-stars-enable').after(separatorSpan); + document.getElementById('shooting-stars-enable').innerHTML = 'Disable shooting stars⭐️'; +} +document.getElementById('shooting-stars-enable').addEventListener('click', () => { + if (processingIsOn) { + processingSketch.frameRate(0); + document.getElementById('shooting-stars-enable').innerHTML = 'Enable shooting stars⭐️'; + processingIsOn = false; + } else { + processingSketch.frameRate(24); + document.getElementById('shooting-stars-enable').innerHTML = 'Disable shooting stars⭐️'; + processingIsOn = true; + } +}); diff --git a/js/sketch.js b/js/sketch.js index e80df6e..70a26dd 100644 --- a/js/sketch.js +++ b/js/sketch.js @@ -1,3 +1,5 @@ +// Another possible inspiration: https://www.khanacademy.org/computer-programming/twinkle-twinkle/6280832014565376 + let sketch = function(p) { let bgCol = hexToRgb('#222233'); let starCol = [hexToRgb('#ffcdff'), hexToRgb('#804442'), hexToRgb('#ffff7d')] @@ -16,22 +18,25 @@ let sketch = function(p) { let time = 0; + let sketchLoop = 0; + p.setup = function(){ p.frameRate(24); currentWidth = p.windowWidth; currentHeight = p.windowHeight; - p.createCanvas(currentWidth, currentHeight); + // p.createCanvas(currentWidth, currentHeight); + p.createCanvas(1920, 1080, p.SVG) // To export the result to SVG + p.noLoop(); // To export the result to SVG bgInit(); mover = new Mover(); - - // p.noLoop(); } p.draw = function(){ - p.background(bgCol); + // p.background(bgCol); + p.clear(); p.image(bg, 0, 0); @@ -52,6 +57,8 @@ let sketch = function(p) { time += 1; + p.save(); // To export the result to SVG + } //End of draw() class Stars { @@ -118,15 +125,10 @@ let sketch = function(p) { this.acc.set(0, 0); this.vel.set(0, 0); this.pos.x = p.random(p.round(0.05 * p.width), p.round(0.95 * p.width)); - this.pos.y = p.random(0, p.round(p.height*0.3)); + this.pos.y = p.random(0, p.round(p.height*0.45)); let gravity = p.createVector(0, 0.02); this.applyForce(gravity); - - // this.pos = p.createVector(200, 200); - - // this.acc.x = p.random(7, 15) * (1 + p.random(0, 1) * -2); - // this.acc.y = p.random(0, 3) - 6; } @@ -167,11 +169,6 @@ let sketch = function(p) { } show() { - // p.stroke(255); - // p.strokeWeight(2); - // p.fill(255, 100); - // p.ellipse(this.pos.x, this.pos.y, this.r * 2); - p.strokeWeight(this.size); // p.stroke(this.color); p.stroke(255, p.constrain(305-this.lifespan, 0, 255), 0, 255-0.7*this.lifespan); @@ -181,10 +178,11 @@ let sketch = function(p) { } p.windowResized = function() { - //Redraw only if new size if bigger than previously let newWidth = p.windowWidth; let newHeight = p.windowHeight; - if (newWidth > currentWidth || newHeight > currentHeight) { + //Redraw only if new size if bigger than previously + // if (newWidth > currentWidth || newHeight > currentHeight) { + if (true) { currentWidth = newWidth; currentHeight = newHeight; @@ -197,8 +195,9 @@ let sketch = function(p) { function bgInit() { nbStars = calcNumberOfStars(); bg = p.createGraphics(p.width, p.height); - bg.background(bgCol); - setGradient(0, 0, p.width, p.height, bgCol, starCol[1]); + // bg.background(bgCol); + // setGradient(0, 0, p.width, p.height, bgCol, starCol[1]); + // bg.background(0, 0, 0, 0); stars = new Stars(nbStars); stars.make(); @@ -240,4 +239,6 @@ let sketch = function(p) { } }; -new p5(sketch, 'bg'); +let processingSketch = new p5(sketch, 'bg'); + +let processingIsOn = true; diff --git a/js/vendor/p5.svg.cjs.min.js b/js/vendor/p5.svg.cjs.min.js new file mode 100644 index 0000000..f872516 --- /dev/null +++ b/js/vendor/p5.svg.cjs.min.js @@ -0,0 +1,22 @@ +/** + * Minified by jsDelivr using Terser v5.10.0. + * Original file: /npm/p5.js-svg@1.3.3/dist/p5.svg.cjs.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +"use strict";function toString(t){return t?"string"==typeof t?t:t+"":t}class ImageUtils{async svg2canvas(t,e,r){const i=await new Promise((e=>{var r=new Image;r.onload=function(){e(r)},r.src=t}));var n=document.createElement("canvas");n.width=e,n.height=r;return n.getContext("2d").drawImage(i,0,0),n}toDataURL(t,e,r,i,n,a){var s=(new XMLSerializer).serializeToString(t);if(document.documentMode){/xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi.test(s)&&(s=s.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink'))}a||(a={});var o="data:image/svg+xml;charset=utf-8,"+encodeURIComponent(s);if("image/svg+xml"===i||!i)return a.async?Promise.resolve(o):o;if("image/jpeg"===i||"image/png"===i){if(!a.async)throw new Error("svgcanvas: options.async must be set to true if type is image/jpeg | image/png");return(async()=>{const t=await this.svg2canvas(o,e,r),a=t.toDataURL(i,n);return t.remove(),a})()}throw new Error("svgcanvas: Unknown type for toDataURL, please use image/jpeg | image/png | image/svg+xml.")}getImageData(t,e,r,i,n,a,s,o){if(o||(o={}),!o.async)throw new Error("svgcanvas: options.async must be set to true for getImageData");const l=this.toDataURL(t,e,r,"image/svg+xml");return(async()=>{const t=await this.svg2canvas(l,e,r),o=t.getContext("2d").getImageData(i,n,a,s);return t.remove(),o})()}}const utils=new ImageUtils; +/*!! + * SVGCanvas v2.0.3 + * Draw on SVG using Canvas's 2D Context API. + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Author: + * Kerry Liu + * Zeno Zeng + * + * Copyright (c) 2014 Gliffy Inc. + * Copyright (c) 2021 Zeno Zeng + */var Context=function(){var t,e,r,i,n;function a(t,e){var r,i=Object.keys(e);for(r=0;r1?((r=i).width=arguments[0],r.height=arguments[1]):r=t||i,!(this instanceof e))return new e(r);this.width=r.width||i.width,this.height=r.height||i.height,this.enableMirroring=void 0!==r.enableMirroring?r.enableMirroring:i.enableMirroring,this.canvas=this,this.__document=r.document||document,r.ctx?this.__ctx=r.ctx:(this.__canvas=this.__document.createElement("canvas"),this.__ctx=this.__canvas.getContext("2d")),this.__setDefaultStyles(),this.__styleStack=[this.__getStyleState()],this.__groupStack=[],this.__root=this.__document.createElementNS("http://www.w3.org/2000/svg","svg"),this.__root.setAttribute("version",1.1),this.__root.setAttribute("xmlns","http://www.w3.org/2000/svg"),this.__root.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),this.__root.setAttribute("width",this.width),this.__root.setAttribute("height",this.height),this.__ids={},this.__defs=this.__document.createElementNS("http://www.w3.org/2000/svg","defs"),this.__root.appendChild(this.__defs),this.__currentElement=this.__document.createElementNS("http://www.w3.org/2000/svg","g"),this.__root.appendChild(this.__currentElement),this.resetTransform(),this.__options=r,this.__id=Math.random().toString(16).substring(2,8),this.__debug("new",t)},e.prototype.__debug=function(...t){this.__options.debug&&console.debug(`svgcanvas#${this.__id}:`,...t)},e.prototype.__createElement=function(t,e,r){void 0===e&&(e={});var i,n,a=this.__document.createElementNS("http://www.w3.org/2000/svg",t),s=Object.keys(e);for(r&&(a.setAttribute("fill","none"),a.setAttribute("stroke","none")),i=0;i0&&this.setTransform(this.__transformMatrixStack.pop())},e.prototype.beginPath=function(){var t;this.__currentDefaultPath="",this.__currentPosition={},t=this.__createElement("path",{},!0),this.__closestGroupOrSvg().appendChild(t),this.__currentElement=t},e.prototype.__applyCurrentDefaultPath=function(){var t=this.__currentElement;"path"===t.nodeName?t.setAttribute("d",this.__currentDefaultPath):console.error("Attempted to apply path command to node",t.nodeName)},e.prototype.__addPathCommand=function(t){this.__currentDefaultPath+=" ",this.__currentDefaultPath+=t},e.prototype.moveTo=function(t,e){"path"!==this.__currentElement.nodeName&&this.beginPath(),this.__currentPosition={x:t,y:e},this.__addPathCommand(a("M {x} {y}",{x:this.__matrixTransform(t,e).x,y:this.__matrixTransform(t,e).y}))},e.prototype.closePath=function(){this.__currentDefaultPath&&this.__addPathCommand("Z")},e.prototype.lineTo=function(t,e){this.__currentPosition={x:t,y:e},this.__currentDefaultPath.indexOf("M")>-1?this.__addPathCommand(a("L {x} {y}",{x:this.__matrixTransform(t,e).x,y:this.__matrixTransform(t,e).y})):this.__addPathCommand(a("M {x} {y}",{x:this.__matrixTransform(t,e).x,y:this.__matrixTransform(t,e).y}))},e.prototype.bezierCurveTo=function(t,e,r,i,n,s){this.__currentPosition={x:n,y:s},this.__addPathCommand(a("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}",{cp1x:this.__matrixTransform(t,e).x,cp1y:this.__matrixTransform(t,e).y,cp2x:this.__matrixTransform(r,i).x,cp2y:this.__matrixTransform(r,i).y,x:this.__matrixTransform(n,s).x,y:this.__matrixTransform(n,s).y}))},e.prototype.quadraticCurveTo=function(t,e,r,i){this.__currentPosition={x:r,y:i},this.__addPathCommand(a("Q {cpx} {cpy} {x} {y}",{cpx:this.__matrixTransform(t,e).x,cpy:this.__matrixTransform(t,e).y,x:this.__matrixTransform(r,i).x,y:this.__matrixTransform(r,i).y}))};var l=function(t){var e=Math.sqrt(t[0]*t[0]+t[1]*t[1]);return[t[0]/e,t[1]/e]};return e.prototype.arcTo=function(t,e,r,i,n){var a=this.__currentPosition&&this.__currentPosition.x,s=this.__currentPosition&&this.__currentPosition.y;if(void 0!==a&&void 0!==s){if(n<0)throw new Error("IndexSizeError: The radius provided ("+n+") is negative.");if(a===t&&s===e||t===r&&e===i||0===n)this.lineTo(t,e);else{var o=l([a-t,s-e]),h=l([r-t,i-e]);if(o[0]*h[1]!=o[1]*h[0]){var p=o[0]*h[0]+o[1]*h[1],c=Math.acos(Math.abs(p)),u=l([o[0]+h[0],o[1]+h[1]]),d=n/Math.sin(c/2),_=t+d*u[0],f=e+d*u[1],g=[-o[1],o[0]],m=[h[1],-h[0]],v=function(t){var e=t[0];return t[1]>=0?Math.acos(e):-Math.acos(e)},y=v(g),x=v(m);this.lineTo(_+g[0]*n,f+g[1]*n),this.arc(_,f,n,y,x)}else this.lineTo(t,e)}}},e.prototype.stroke=function(){"path"===this.__currentElement.nodeName&&this.__currentElement.setAttribute("paint-order","fill stroke markers"),this.__applyCurrentDefaultPath(),this.__applyStyleToCurrentElement("stroke")},e.prototype.fill=function(){"path"===this.__currentElement.nodeName&&this.__currentElement.setAttribute("paint-order","stroke fill markers"),this.__applyCurrentDefaultPath(),this.__applyStyleToCurrentElement("fill")},e.prototype.rect=function(t,e,r,i){"path"!==this.__currentElement.nodeName&&this.beginPath(),this.moveTo(t,e),this.lineTo(t+r,e),this.lineTo(t+r,e+i),this.lineTo(t,e+i),this.lineTo(t,e),this.closePath()},e.prototype.fillRect=function(t,e,r,i){let{a:n,b:a,c:s,d:o,e:l,f:h}=this.getTransform();var p;JSON.stringify([n,a,s,o,l,h])===JSON.stringify([1,0,0,1,0,0])&&0===t&&0===e&&r===this.width&&i===this.height&&this.__clearCanvas(),p=this.__createElement("rect",{x:t,y:e,width:r,height:i},!0),this.__closestGroupOrSvg().appendChild(p),this.__currentElement=p,this.__applyTransformation(p),this.__applyStyleToCurrentElement("fill")},e.prototype.strokeRect=function(t,e,r,i){var n;n=this.__createElement("rect",{x:t,y:e,width:r,height:i},!0),this.__closestGroupOrSvg().appendChild(n),this.__currentElement=n,this.__applyTransformation(n),this.__applyStyleToCurrentElement("stroke")},e.prototype.__clearCanvas=function(){var t=this.__root.childNodes[1];this.__root.removeChild(t),this.__currentElement=this.__document.createElementNS("http://www.w3.org/2000/svg","g"),this.__root.appendChild(this.__currentElement),this.__groupStack=[]},e.prototype.clearRect=function(t,e,r,i){let{a:n,b:a,c:s,d:o,e:l,f:h}=this.getTransform();if(JSON.stringify([n,a,s,o,l,h])!==JSON.stringify([1,0,0,1,0,0])||0!==t||0!==e||r!==this.width||i!==this.height){var p,c=this.__closestGroupOrSvg();p=this.__createElement("rect",{x:t,y:e,width:r,height:i,fill:"#FFFFFF"},!0),this.__applyTransformation(p),c.appendChild(p)}else this.__clearCanvas()},e.prototype.createLinearGradient=function(t,e,i,n){var a=this.__createElement("linearGradient",{id:s(this.__ids),x1:t+"px",x2:i+"px",y1:e+"px",y2:n+"px",gradientUnits:"userSpaceOnUse"},!1);return this.__defs.appendChild(a),new r(a,this)},e.prototype.createRadialGradient=function(t,e,i,n,a,o){var l=this.__createElement("radialGradient",{id:s(this.__ids),cx:n+"px",cy:a+"px",r:o+"px",fx:t+"px",fy:e+"px",gradientUnits:"userSpaceOnUse"},!1);return this.__defs.appendChild(l),new r(l,this)},e.prototype.__parseFont=function(){var t=/^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\'\"\sa-z0-9]+?)\s*$/i.exec(this.font),e={style:t[1]||"normal",size:t[4]||"10px",family:t[6]||"sans-serif",weight:t[3]||"normal",decoration:t[2]||"normal",href:null};return"underline"===this.__fontUnderline&&(e.decoration="underline"),this.__fontHref&&(e.href=this.__fontHref),e},e.prototype.__wrapTextLink=function(t,e){if(t.href){var r=this.__createElement("a");return r.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",t.href),r.appendChild(e),r}return e},e.prototype.__applyText=function(t,e,r,i){var n,a,s=this.__parseFont(),l=this.__closestGroupOrSvg(),h=this.__createElement("text",{"font-family":s.family,"font-size":s.size,"font-style":s.style,"font-weight":s.weight,"text-decoration":s.decoration,x:e,y:r,"text-anchor":(n=this.textAlign,a={left:"start",right:"end",center:"middle",start:"start",end:"end"},a[n]||a.start),"dominant-baseline":o(this.textBaseline)},!0);h.appendChild(this.__document.createTextNode(t)),this.__currentElement=h,this.__applyTransformation(h),this.__applyStyleToCurrentElement(i),l.appendChild(this.__wrapTextLink(s,h))},e.prototype.fillText=function(t,e,r){this.__applyText(t,e,r,"fill")},e.prototype.strokeText=function(t,e,r){this.__applyText(t,e,r,"stroke")},e.prototype.measureText=function(t){return this.__ctx.font=this.font,this.__ctx.measureText(t)},e.prototype.arc=function(t,e,r,i,n,s){if(i!==n){(i%=2*Math.PI)===(n%=2*Math.PI)&&(n=(n+2*Math.PI-.001*(s?-1:1))%(2*Math.PI));var o=t+r*Math.cos(n),l=e+r*Math.sin(n),h=t+r*Math.cos(i),p=e+r*Math.sin(i),c=s?0:1,u=0,d=n-i;d<0&&(d+=2*Math.PI),u=s?d>Math.PI?0:1:d>Math.PI?1:0,this.lineTo(h,p),this.__addPathCommand(a("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}",{rx:r,ry:r,xAxisRotation:0,largeArcFlag:u,sweepFlag:c,endX:this.__matrixTransform(o,l).x,endY:this.__matrixTransform(o,l).y})),this.__currentPosition={x:o,y:l}}},e.prototype.clip=function(){var t=this.__closestGroupOrSvg(),e=this.__createElement("clipPath"),r=s(this.__ids),i=this.__createElement("g");this.__applyCurrentDefaultPath(),t.removeChild(this.__currentElement),e.setAttribute("id",r),e.appendChild(this.__currentElement),this.__defs.appendChild(e),t.setAttribute("clip-path",a("url(#{id})",{id:r})),t.appendChild(i),this.__currentElement=i},e.prototype.drawImage=function(){var t,r,i,n,a,s,o,l,h,p,c,u,d,_=Array.prototype.slice.call(arguments),f=_[0],g=0,m=0;if(3===_.length)t=_[1],r=_[2],i=a=f.width,n=s=f.height;else if(5===_.length)t=_[1],r=_[2],i=_[3],n=_[4],a=f.width,s=f.height;else{if(9!==_.length)throw new Error("Invalid number of arguments passed to drawImage: "+arguments.length);g=_[1],m=_[2],a=_[3],s=_[4],t=_[5],r=_[6],i=_[7],n=_[8]}o=this.__closestGroupOrSvg();const v=this.getTransform().translate(t,r);if(f instanceof e){if((l=f.getSvg().cloneNode(!0)).childNodes&&l.childNodes.length>1){for(h=l.childNodes[0];h.childNodes.length;)d=h.childNodes[0].getAttribute("id"),this.__ids[d]=d,this.__defs.appendChild(h.childNodes[0]);(p=l.childNodes[1])&&(this.__applyTransformation(p,v),o.appendChild(p))}}else"CANVAS"!==f.nodeName&&"IMG"!==f.nodeName||((c=this.__createElement("image")).setAttribute("width",i),c.setAttribute("height",n),c.setAttribute("preserveAspectRatio","none"),(g||m||a!==f.width||s!==f.height)&&((u=this.__document.createElement("canvas")).width=i,u.height=n,u.getContext("2d").drawImage(f,g,m,a,s,0,0,i,n),f=u),this.__applyTransformation(c,v),c.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href","CANVAS"===f.nodeName?f.toDataURL():f.getAttribute("src")),o.appendChild(c))},e.prototype.createPattern=function(t,r){var n,a=this.__document.createElementNS("http://www.w3.org/2000/svg","pattern"),o=s(this.__ids);return a.setAttribute("id",o),a.setAttribute("width",t.width),a.setAttribute("height",t.height),a.setAttribute("patternUnits","userSpaceOnUse"),"CANVAS"===t.nodeName||"IMG"===t.nodeName?((n=this.__document.createElementNS("http://www.w3.org/2000/svg","image")).setAttribute("width",t.width),n.setAttribute("height",t.height),n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href","CANVAS"===t.nodeName?t.toDataURL():t.getAttribute("src")),a.appendChild(n),this.__defs.appendChild(a)):t instanceof e&&(a.appendChild(t.__root.childNodes[1]),this.__defs.appendChild(a)),new i(a,this)},e.prototype.setLineDash=function(t){t&&t.length>0?this.lineDash=t.join(","):this.lineDash=null},e.prototype.setTransform=function(t,e,r,i,n,a){t instanceof DOMMatrix?this.__transformMatrix=new DOMMatrix([t.a,t.b,t.c,t.d,t.e,t.f]):this.__transformMatrix=new DOMMatrix([t,e,r,i,n,a])},e.prototype.getTransform=function(){let{a:t,b:e,c:r,d:i,e:n,f:a}=this.__transformMatrix;return new DOMMatrix([t,e,r,i,n,a])},e.prototype.resetTransform=function(){this.setTransform(1,0,0,1,0,0)},e.prototype.scale=function(t,e){if(void 0===e&&(e=t),isNaN(t)||isNaN(e)||!isFinite(t)||!isFinite(e))return;let r=this.getTransform().scale(t,e);this.setTransform(r)},e.prototype.rotate=function(t){let e=this.getTransform().multiply(new DOMMatrix([Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0]));this.setTransform(e)},e.prototype.translate=function(t,e){const r=this.getTransform().translate(t,e);this.setTransform(r)},e.prototype.transform=function(t,e,r,i,n,a){const s=this.getTransform().multiply(new DOMMatrix([t,e,r,i,n,a]));this.setTransform(s)},e.prototype.__matrixTransform=function(t,e){return new DOMPoint(t,e).matrixTransform(this.__transformMatrix)},e.prototype.getImageData=function(t,e,r,i,n){return utils.getImageData(this.getSvg(),this.width,this.height,t,e,r,i,n)},e.prototype.drawFocusRing=function(){},e.prototype.createImageData=function(){},e.prototype.putImageData=function(){},e.prototype.globalCompositeOperation=function(){},e}();function SVGCanvasElement(t){this.ctx=new Context(t),this.svg=this.ctx.__root;var e=this.svg,r=this,i=document.createElement("div");i.style.display="inline-block",i.appendChild(e),this.wrapper=i,Object.defineProperty(this,"className",{get:function(){return i.getAttribute("class")||""},set:function(t){return i.setAttribute("class",t)}}),["width","height"].forEach((function(t){Object.defineProperty(r,t,{get:function(){return 0|e.getAttribute(t)},set:function(n){if(!isNaN(n)&&void 0!==n)return r.ctx[t]=n,e.setAttribute(t,n),i[t]=n}})})),["style","id"].forEach((function(t){Object.defineProperty(r,t,{get:function(){return i[t]},set:function(e){if(void 0!==e)return i[t]=e}})})),["getBoundingClientRect"].forEach((function(t){r[t]=function(){return e[t]()}}))}SVGCanvasElement.prototype.getContext=function(t){if("2d"!==t)throw new Error("Unsupported type of context for SVGCanvas");return this.ctx},SVGCanvasElement.prototype.toObjectURL=function(){var t=(new XMLSerializer).serializeToString(this.svg),e=new Blob([t],{type:"image/svg+xml;charset=utf-8"});return URL.createObjectURL(e)},SVGCanvasElement.prototype.toDataURL=function(t,e,r){return utils.toDataURL(this.svg,this.width,this.height,t,e,r)},SVGCanvasElement.prototype.addEventListener=function(){return this.svg.addEventListener.apply(this.svg,arguments)},SVGCanvasElement.prototype.getElement=function(){return this.wrapper};var Element$1=SVGCanvasElement;const DEBUG=!1;function RendererSVG(t){function e(e,r,i){var n=new Element$1({debug:false}),a=n.svg,s=e.parentNode,o=e.id,l=e.className;s.replaceChild(n.getElement(),e),n.id=o,n.className=l,(e=n).parentNode={removeChild:function(t){if(t===e){var r=n.getElement();r.parentNode.removeChild(r)}}};const h=new Proxy(r,{get:function(t,e){return"_pixelDensity"===e?1:t[e]}});return t.Renderer2D.call(this,e,h,i),this.isSVG=!0,this.svg=a,this}e.prototype=Object.create(t.Renderer2D.prototype),e.prototype._applyDefaults=function(){t.Renderer2D.prototype._applyDefaults.call(this),this.drawingContext.lineWidth=1},e.prototype.resize=function(e,r){e&&r&&(this.width===e&&this.height===r||this.drawingContext.__clearCanvas(),t.Renderer2D.prototype.resize.call(this,e,r),this.svg.setAttribute("viewBox",[0,0,e,r].join(" ")))},e.prototype.appendChild=function(t){t&&t.elt&&(t=t.elt),this.drawingContext.__closestGroupOrSvg().appendChild(t)},e.prototype.image=function(e,r,i,n,a,s,o,l,h){if(!e)throw new Error("Invalid image: "+e);var p=e._renderer&&e._renderer.svg;(p=(p=p||e.elt&&e.elt.nodeName&&"svg"===e.elt.nodeName.toLowerCase()&&e.elt)||e.nodeName&&"svg"==e.nodeName.toLowerCase()&&e)?((p=p.cloneNode(!0)).setAttribute("width",l),p.setAttribute("height",h),p.setAttribute("x",s),p.setAttribute("y",o),(r||i||n||a)&&(n/=this._pInst._pixelDensity,a/=this._pInst._pixelDensity,p.setAttribute("viewBox",[r,i,n,a].join(", "))),this.appendChild(p)):t.Renderer2D.prototype.image.apply(this,arguments)},e.prototype.parent=function(){const e={elt:this.elt.getElement()};return t.Element.prototype.parent.apply(e,arguments)},e.prototype.loadPixels=async function(){const t=this._pixelsState,e=t._pixelDensity,r=this.width*e,i=this.height*e,n=await this.drawingContext.getImageData(0,0,r,i,{async:!0});t._setProperty("imageData",n),t._setProperty("pixels",n.data)},t.RendererSVG=e}var constants={SVG:"svg"};function Rendering(t){var e=t.Graphics;t.Graphics=function(r,i,n,a){const s=n===constants.SVG;if(e.apply(this,[r,i,s?t.P2D:n,a]),s){var o=this._renderer.elt;this._renderer=new t.RendererSVG(o,this,!1),o=this._renderer.elt,this.elt=o,this._renderer.resize(r,i),this._renderer._applyDefaults()}return this},t.Graphics.prototype=e.prototype;var r=t.prototype.createCanvas;t.prototype.createCanvas=function(e,i,n){var a=r.apply(this,arguments);if(n===constants.SVG){var s=a.canvas;this._setProperty("_renderer",new t.RendererSVG(s,this,!0)),this._isdefaultGraphics=!0,this._renderer.resize(e,i),this._renderer._applyDefaults()}return this._renderer},t.prototype.createGraphics=function(e,r,i){return new t.Graphics(e,r,i,this)}}function IO(t){t.prototype._makeSVGFrame=function(t){var e=t.filename||"untitled",r=t.extension;r=r||this._checkFileExtension(e,r)[1];var i=new RegExp("\\."+r+"$");e=e.replace(i,""),""===r&&(r="svg");var n={png:"image/png",jpeg:"image/jpeg",jpg:"image/jpeg",svg:"image/svg+xml"}[r];if(!n)throw new Error("Fail to getFrame, invalid extension: "+r+", please use png | jpeg | jpg | svg.");!function(t,e,r){if(t=(new XMLSerializer).serializeToString(t),t="data:image/svg+xml;charset=utf-8,"+encodeURIComponent(t),"image/svg+xml"!=e){var i=new Image,n=document.createElement("canvas"),a=n.getContext("2d");i.onload=function(){n.width=i.width,n.height=i.height,a.drawImage(i,0,0);var t=n.toDataURL(e);r(null,t)},i.src=t}else r(null,t)}(t.svg||this._renderer.svg,n,(function(i,a){a=a.replace(n,"image/octet-stream"),t.callback(i,{imageData:a,filename:e,ext:r})}))},t.prototype.saveSVG=function(){var e,r=arguments;(r=[r[0],r[1],r[2]])[0]instanceof t.Graphics&&(e=r[0]._renderer.svg,r.shift()),r[0]&&r[0].elt&&(e=r[0].elt,r.shift()),"object"==typeof r[0]&&(e=r[0],r.shift());var i=r[0],n=r[1],a=this;this._makeSVGFrame({svg:e,filename:i,extension:n,callback:function(t,e){a.downloadFile(e.imageData,e.filename,e.ext)}})};var e=t.prototype.saveFrames;t.prototype.saveFrames=function(r,i,n,a,s){var o=arguments;if(this._renderer.svg){n=n||3,n=t.prototype.constrain(n,0,15),n*=1e3,a=a||15,a=t.prototype.constrain(a,0,22);var l=0,h=[],p=0,c=this,u=setInterval((function(){!function(t){p++,c._makeSVGFrame({filename:r+t,extension:i,callback:function(e,r){h[t]=r,p--}})}(l),l++}),1e3/a),d=function(){p>0?setTimeout((function(){d()}),10):s?s(h):h.forEach((function(t){c.downloadFile(t.imageData,t.filename,t.ext)}))};setTimeout((function(){clearInterval(u),d()}),n+.01)}else e.apply(this,o)};var r=t.prototype.save;t.prototype.save=function(){var e,i=arguments;if((i=[i[0],i[1]])[0]instanceof t.Graphics){var n=i[0].elt;e=n.svg,i.shift()}i[0]&&i[0].elt&&(e=i[0].elt,i.shift()),"object"==typeof i[0]&&(e=i[0],i.shift()),e=e||this._renderer&&this._renderer.svg;var a=i[0],s=["jpeg","png","jpg","svg",""],o=this._checkFileExtension(a,"")[1],l=e&&e.nodeName&&"svg"===e.nodeName.toLowerCase()&&s.indexOf(o)>-1;if(!l)return r.apply(this,arguments);this.saveSVG(e,a)},t.prototype._svg_get=function(t,e,r){if(0===t.indexOf("data:")){if(-1===t.indexOf(","))return void r(new Error("Fail to parse dataurl: "+t));var i=t.split(",").pop();return setTimeout((function(){i=t.indexOf(";base64,")>-1?atob(i):decodeURIComponent(i),e(i)}),1),i}return this.httpGet(t,e),null},t.prototype.loadSVG=function(e,r,i){var n=document.createElement("div"),a=new t.SVGElement(n);return this._incrementPreload(),new Promise(((t,r)=>{this._svg_get(e,(function(e){n.innerHTML=e,(e=n.querySelector("svg"))?(a.elt=e,t(a)):r("Fail to create .")}),r)})).then((t=>{r&&r(t)})).catch((t=>{i&&i(t)})).finally((()=>{this._decrementPreload()})),a},t.prototype.getDataURL=function(){return this._renderer.elt.toDataURL("image/svg+xml")}}function Element(t){function e(e){return e?t.Element.apply(this,arguments):null}t.prototype.querySVG=function(e){var r=this._renderer&&this._renderer.svg;return r?t.SVGElement.prototype.query.call({elt:r},e):null},e.prototype=Object.create(t.Element.prototype),e.prototype.query=function(t){for(var r=this.elt.querySelectorAll(t),i=[],n=0;n255)throw new Error("Level must be greater than 2 and less than 255 for posterize");var a=e._discreteTableValues((function(t){return 255*(t*n>>8)/(n-1)})),s=r.create("feComponentTransfer",{in:t,result:i,"color-interpolation-filters":"sRGB"});return["R","G","B"].forEach((function(t){var e=r.create("feFunc"+t,{type:"discrete",tableValues:a.join(" ")});s.append(e)})),s},e._blendOffset=function(t,e,i){var n=[];return[["left",-1,0],["right",1,0],["up",0,-1],["down",0,1]].forEach((function(i){n.push(r.create("feOffset",{in:t,result:e+"-"+i[0],dx:i[1],dy:i[2]}))})),[[null,t],[e+"-left",e+"-tmp-0"],[e+"-right",e+"-tmp-1"],[e+"-up",e+"-tmp-2"],[e+"-down",e+"-tmp-3"]].forEach((function(t,e,a){0!==e&&n.push(r.create("feBlend",{in:a[e-1][1],in2:t[0],result:t[1],mode:i}))})),n},e.erode=function(t,r){return e._blendOffset(t,r,"darken")},e.dilate=function(t,r){return e._blendOffset(t,r,"lighten")},t.SVGFilters=e,e}function Filters(t){var e=t.prototype.filter,r=P5SVGFilters(t);t.prototype.registerSVGFilter=function(t,e){r[t]=e},t.prototype.filter=function(r,i){var n=this._renderer.svg;if(n){const e=this._renderer.drawingContext,n=e.__root.querySelectorAll("defs")[0],a=e.__root.childNodes[1];let s=t.SVGElement.create("g");for(;a.childNodes.length>0;)s.elt.appendChild(a.childNodes[0]);a.appendChild(s.elt),s._filter(r,i,n),s=t.SVGElement.create("g"),a.appendChild(s.elt),e.__currentElement=s.elt}else e.apply(this,arguments)}}function Image$1(t){t.prototype.loadPixels=function(...e){return t._validateParameters("loadPixels",e),this._renderer.loadPixels()}}function init(t){RendererSVG(t),Rendering(t),IO(t),Element(t),Filters(t),Image$1(t),Object.keys(constants).forEach((function(e){t.prototype[e]=constants[e]}))}void 0!==window.p5&&init(window.p5),module.exports=init; +//# sourceMappingURL=/sm/cb1c75a18a0f8f09eec5854a495246671ba16e7adac58a5d25edacc9f5ce3111.map \ No newline at end of file diff --git a/js/vendor/p5.svg.js b/js/vendor/p5.svg.js new file mode 100644 index 0000000..cc3d679 --- /dev/null +++ b/js/vendor/p5.svg.js @@ -0,0 +1,2509 @@ +(function () { + 'use strict'; + + function toString(obj) { + if (!obj) { + return obj + } + if (typeof obj === 'string') { + return obj + } + return obj + ''; + } + + class ImageUtils { + + /** + * Convert svg dataurl to canvas element + * + * @private + */ + async svg2canvas(svgDataURL, width, height) { + const svgImage = await new Promise((resolve) => { + var svgImage = new Image(); + svgImage.onload = function() { + resolve(svgImage); + }; + svgImage.src = svgDataURL; + }); + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(svgImage, 0, 0); + return canvas; + } + + toDataURL(svgNode, width, height, type, encoderOptions, options) { + var xml = new XMLSerializer().serializeToString(svgNode); + + // documentMode is an IE-only property + // http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx + // http://stackoverflow.com/questions/10964966/detect-ie-version-prior-to-v9-in-javascript + var isIE = document.documentMode; + + if (isIE) { + // This is patch from canvas2svg + // IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly + var xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; + if(xmlns.test(xml)) { + xml = xml.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink'); + } + } + + if (!options) { + options = {}; + } + + var SVGDataURL = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(xml); + if (type === "image/svg+xml" || !type) { + if (options.async) { + return Promise.resolve(SVGDataURL) + } + return SVGDataURL; + } + if (type === "image/jpeg" || type === "image/png") { + if (!options.async) { + throw new Error('svgcanvas: options.async must be set to true if type is image/jpeg | image/png') + } + return (async () => { + const canvas = await this.svg2canvas(SVGDataURL, width, height); + const dataUrl = canvas.toDataURL(type, encoderOptions); + canvas.remove(); + return dataUrl; + })() + } + throw new Error('svgcanvas: Unknown type for toDataURL, please use image/jpeg | image/png | image/svg+xml.'); + } + + getImageData(svgNode, width, height, sx, sy, sw, sh, options) { + if (!options) { + options = {}; + } + if (!options.async) { + throw new Error('svgcanvas: options.async must be set to true for getImageData') + } + const svgDataURL = this.toDataURL(svgNode, width, height, 'image/svg+xml'); + return (async () => { + const canvas = await this.svg2canvas(svgDataURL, width, height); + const ctx = canvas.getContext('2d'); + const imageData = ctx.getImageData(sx, sy, sw, sh); + canvas.remove(); + return imageData; + })() + } + } + + const utils = new ImageUtils(); + + /*!! + * SVGCanvas v2.0.3 + * Draw on SVG using Canvas's 2D Context API. + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Author: + * Kerry Liu + * Zeno Zeng + * + * Copyright (c) 2014 Gliffy Inc. + * Copyright (c) 2021 Zeno Zeng + */ + + var Context = (function () { + + var STYLES, Context, CanvasGradient, CanvasPattern, namedEntities; + + //helper function to format a string + function format(str, args) { + var keys = Object.keys(args), i; + for (i=0; i 1) { + options = defaultOptions; + options.width = arguments[0]; + options.height = arguments[1]; + } else if ( !o ) { + options = defaultOptions; + } else { + options = o; + } + + if (!(this instanceof Context)) { + //did someone call this without new? + return new Context(options); + } + + //setup options + this.width = options.width || defaultOptions.width; + this.height = options.height || defaultOptions.height; + this.enableMirroring = options.enableMirroring !== undefined ? options.enableMirroring : defaultOptions.enableMirroring; + + this.canvas = this; ///point back to this instance! + this.__document = options.document || document; + + // allow passing in an existing context to wrap around + // if a context is passed in, we know a canvas already exist + if (options.ctx) { + this.__ctx = options.ctx; + } else { + this.__canvas = this.__document.createElement("canvas"); + this.__ctx = this.__canvas.getContext("2d"); + } + + this.__setDefaultStyles(); + this.__styleStack = [this.__getStyleState()]; + this.__groupStack = []; + + //the root svg element + this.__root = this.__document.createElementNS("http://www.w3.org/2000/svg", "svg"); + this.__root.setAttribute("version", 1.1); + this.__root.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + this.__root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"); + this.__root.setAttribute("width", this.width); + this.__root.setAttribute("height", this.height); + + //make sure we don't generate the same ids in defs + this.__ids = {}; + + //defs tag + this.__defs = this.__document.createElementNS("http://www.w3.org/2000/svg", "defs"); + this.__root.appendChild(this.__defs); + + //also add a group child. the svg element can't use the transform attribute + this.__currentElement = this.__document.createElementNS("http://www.w3.org/2000/svg", "g"); + this.__root.appendChild(this.__currentElement); + + // init transformation matrix + this.resetTransform(); + + this.__options = options; + this.__id = Math.random().toString(16).substring(2, 8); + this.__debug(`new`, o); + }; + + /** + * Log + * + * @private + */ + Context.prototype.__debug = function(...data) { + if (!this.__options.debug) { + return + } + console.debug(`svgcanvas#${this.__id}:`, ...data); + }; + + /** + * Creates the specified svg element + * @private + */ + Context.prototype.__createElement = function (elementName, properties, resetFill) { + if (typeof properties === "undefined") { + properties = {}; + } + + var element = this.__document.createElementNS("http://www.w3.org/2000/svg", elementName), + keys = Object.keys(properties), i, key; + if (resetFill) { + //if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black. + element.setAttribute("fill", "none"); + element.setAttribute("stroke", "none"); + } + for (i=0; i 0) { + this.setTransform(this.__transformMatrixStack.pop()); + } + + }; + + /** + * Create a new Path Element + */ + Context.prototype.beginPath = function () { + var path, parent; + + // Note that there is only one current default path, it is not part of the drawing state. + // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path + this.__currentDefaultPath = ""; + this.__currentPosition = {}; + + path = this.__createElement("path", {}, true); + parent = this.__closestGroupOrSvg(); + parent.appendChild(path); + this.__currentElement = path; + }; + + /** + * Helper function to apply currentDefaultPath to current path element + * @private + */ + Context.prototype.__applyCurrentDefaultPath = function () { + var currentElement = this.__currentElement; + if (currentElement.nodeName === "path") { + currentElement.setAttribute("d", this.__currentDefaultPath); + } else { + console.error("Attempted to apply path command to node", currentElement.nodeName); + } + }; + + /** + * Helper function to add path command + * @private + */ + Context.prototype.__addPathCommand = function (command) { + this.__currentDefaultPath += " "; + this.__currentDefaultPath += command; + }; + + /** + * Adds the move command to the current path element, + * if the currentPathElement is not empty create a new path element + */ + Context.prototype.moveTo = function (x,y) { + if (this.__currentElement.nodeName !== "path") { + this.beginPath(); + } + + // creates a new subpath with the given point + this.__currentPosition = {x: x, y: y}; + this.__addPathCommand(format("M {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + }; + + /** + * Closes the current path + */ + Context.prototype.closePath = function () { + if (this.__currentDefaultPath) { + this.__addPathCommand("Z"); + } + }; + + /** + * Adds a line to command + */ + Context.prototype.lineTo = function (x, y) { + this.__currentPosition = {x: x, y: y}; + if (this.__currentDefaultPath.indexOf('M') > -1) { + this.__addPathCommand(format("L {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + } else { + this.__addPathCommand(format("M {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + } + }; + + /** + * Add a bezier command + */ + Context.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { + this.__currentPosition = {x: x, y: y}; + this.__addPathCommand(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", + { + cp1x: this.__matrixTransform(cp1x, cp1y).x, + cp1y: this.__matrixTransform(cp1x, cp1y).y, + cp2x: this.__matrixTransform(cp2x, cp2y).x, + cp2y: this.__matrixTransform(cp2x, cp2y).y, + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + }; + + /** + * Adds a quadratic curve to command + */ + Context.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { + this.__currentPosition = {x: x, y: y}; + this.__addPathCommand(format("Q {cpx} {cpy} {x} {y}", { + cpx: this.__matrixTransform(cpx, cpy).x, + cpy: this.__matrixTransform(cpx, cpy).y, + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + }; + + + /** + * Return a new normalized vector of given vector + */ + var normalize = function (vector) { + var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); + return [vector[0] / len, vector[1] / len]; + }; + + /** + * Adds the arcTo to the current path + * + * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto + */ + Context.prototype.arcTo = function (x1, y1, x2, y2, radius) { + // Let the point (x0, y0) be the last point in the subpath. + var x0 = this.__currentPosition && this.__currentPosition.x; + var y0 = this.__currentPosition && this.__currentPosition.y; + + // First ensure there is a subpath for (x1, y1). + if (typeof x0 == "undefined" || typeof y0 == "undefined") { + return; + } + + // Negative values for radius must cause the implementation to throw an IndexSizeError exception. + if (radius < 0) { + throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative."); + } + + // If the point (x0, y0) is equal to the point (x1, y1), + // or if the point (x1, y1) is equal to the point (x2, y2), + // or if the radius radius is zero, + // then the method must add the point (x1, y1) to the subpath, + // and connect that point to the previous point (x0, y0) by a straight line. + if (((x0 === x1) && (y0 === y1)) + || ((x1 === x2) && (y1 === y2)) + || (radius === 0)) { + this.lineTo(x1, y1); + return; + } + + // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, + // then the method must add the point (x1, y1) to the subpath, + // and connect that point to the previous point (x0, y0) by a straight line. + var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); + var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); + if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) { + this.lineTo(x1, y1); + return; + } + + // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius, + // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1), + // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2). + // The points at which this circle touches these two lines are called the start and end tangent points respectively. + + // note that both vectors are unit vectors, so the length is 1 + var cos = (unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]); + var theta = Math.acos(Math.abs(cos)); + + // Calculate origin + var unit_vec_p1_origin = normalize([ + unit_vec_p1_p0[0] + unit_vec_p1_p2[0], + unit_vec_p1_p0[1] + unit_vec_p1_p2[1] + ]); + var len_p1_origin = radius / Math.sin(theta / 2); + var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; + var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; + + // Calculate start angle and end angle + // rotate 90deg clockwise (note that y axis points to its down) + var unit_vec_origin_start_tangent = [ + -unit_vec_p1_p0[1], + unit_vec_p1_p0[0] + ]; + // rotate 90deg counter clockwise (note that y axis points to its down) + var unit_vec_origin_end_tangent = [ + unit_vec_p1_p2[1], + -unit_vec_p1_p2[0] + ]; + var getAngle = function (vector) { + // get angle (clockwise) between vector and (1, 0) + var x = vector[0]; + var y = vector[1]; + if (y >= 0) { // note that y axis points to its down + return Math.acos(x); + } else { + return -Math.acos(x); + } + }; + var startAngle = getAngle(unit_vec_origin_start_tangent); + var endAngle = getAngle(unit_vec_origin_end_tangent); + + // Connect the point (x0, y0) to the start tangent point by a straight line + this.lineTo(x + unit_vec_origin_start_tangent[0] * radius, + y + unit_vec_origin_start_tangent[1] * radius); + + // Connect the start tangent point to the end tangent point by arc + // and adding the end tangent point to the subpath. + this.arc(x, y, radius, startAngle, endAngle); + }; + + /** + * Sets the stroke property on the current element + */ + Context.prototype.stroke = function () { + if (this.__currentElement.nodeName === "path") { + this.__currentElement.setAttribute("paint-order", "fill stroke markers"); + } + this.__applyCurrentDefaultPath(); + this.__applyStyleToCurrentElement("stroke"); + }; + + /** + * Sets fill properties on the current element + */ + Context.prototype.fill = function () { + if (this.__currentElement.nodeName === "path") { + this.__currentElement.setAttribute("paint-order", "stroke fill markers"); + } + this.__applyCurrentDefaultPath(); + this.__applyStyleToCurrentElement("fill"); + }; + + /** + * Adds a rectangle to the path. + */ + Context.prototype.rect = function (x, y, width, height) { + if (this.__currentElement.nodeName !== "path") { + this.beginPath(); + } + this.moveTo(x, y); + this.lineTo(x+width, y); + this.lineTo(x+width, y+height); + this.lineTo(x, y+height); + this.lineTo(x, y); + this.closePath(); + }; + + + /** + * adds a rectangle element + */ + Context.prototype.fillRect = function (x, y, width, height) { + let {a, b, c, d, e, f} = this.getTransform(); + if (JSON.stringify([a, b, c, d, e, f]) === JSON.stringify([1, 0, 0, 1, 0, 0])) { + //clear entire canvas + if (x === 0 && y === 0 && width === this.width && height === this.height) { + this.__clearCanvas(); + } + } + var rect, parent; + rect = this.__createElement("rect", { + x : x, + y : y, + width : width, + height : height + }, true); + parent = this.__closestGroupOrSvg(); + parent.appendChild(rect); + this.__currentElement = rect; + this.__applyTransformation(rect); + this.__applyStyleToCurrentElement("fill"); + }; + + /** + * Draws a rectangle with no fill + * @param x + * @param y + * @param width + * @param height + */ + Context.prototype.strokeRect = function (x, y, width, height) { + var rect, parent; + rect = this.__createElement("rect", { + x : x, + y : y, + width : width, + height : height + }, true); + parent = this.__closestGroupOrSvg(); + parent.appendChild(rect); + this.__currentElement = rect; + this.__applyTransformation(rect); + this.__applyStyleToCurrentElement("stroke"); + }; + + + /** + * Clear entire canvas: + * 1. save current transforms + * 2. remove all the childNodes of the root g element + */ + Context.prototype.__clearCanvas = function () { + var rootGroup = this.__root.childNodes[1]; + this.__root.removeChild(rootGroup); + this.__currentElement = this.__document.createElementNS("http://www.w3.org/2000/svg", "g"); + this.__root.appendChild(this.__currentElement); + //reset __groupStack as all the child group nodes are all removed. + this.__groupStack = []; + }; + + /** + * "Clears" a canvas by just drawing a white rectangle in the current group. + */ + Context.prototype.clearRect = function (x, y, width, height) { + let {a, b, c, d, e, f} = this.getTransform(); + if (JSON.stringify([a, b, c, d, e, f]) === JSON.stringify([1, 0, 0, 1, 0, 0])) { + //clear entire canvas + if (x === 0 && y === 0 && width === this.width && height === this.height) { + this.__clearCanvas(); + return; + } + } + var rect, parent = this.__closestGroupOrSvg(); + rect = this.__createElement("rect", { + x : x, + y : y, + width : width, + height : height, + fill : "#FFFFFF" + }, true); + this.__applyTransformation(rect); + parent.appendChild(rect); + }; + + /** + * Adds a linear gradient to a defs tag. + * Returns a canvas gradient object that has a reference to it's parent def + */ + Context.prototype.createLinearGradient = function (x1, y1, x2, y2) { + var grad = this.__createElement("linearGradient", { + id : randomString(this.__ids), + x1 : x1+"px", + x2 : x2+"px", + y1 : y1+"px", + y2 : y2+"px", + "gradientUnits" : "userSpaceOnUse" + }, false); + this.__defs.appendChild(grad); + return new CanvasGradient(grad, this); + }; + + /** + * Adds a radial gradient to a defs tag. + * Returns a canvas gradient object that has a reference to it's parent def + */ + Context.prototype.createRadialGradient = function (x0, y0, r0, x1, y1, r1) { + var grad = this.__createElement("radialGradient", { + id : randomString(this.__ids), + cx : x1+"px", + cy : y1+"px", + r : r1+"px", + fx : x0+"px", + fy : y0+"px", + "gradientUnits" : "userSpaceOnUse" + }, false); + this.__defs.appendChild(grad); + return new CanvasGradient(grad, this); + + }; + + /** + * Parses the font string and returns svg mapping + * @private + */ + Context.prototype.__parseFont = function () { + var regex = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\'\"\sa-z0-9]+?)\s*$/i; + var fontPart = regex.exec( this.font ); + var data = { + style : fontPart[1] || 'normal', + size : fontPart[4] || '10px', + family : fontPart[6] || 'sans-serif', + weight: fontPart[3] || 'normal', + decoration : fontPart[2] || 'normal', + href : null + }; + + //canvas doesn't support underline natively, but we can pass this attribute + if (this.__fontUnderline === "underline") { + data.decoration = "underline"; + } + + //canvas also doesn't support linking, but we can pass this as well + if (this.__fontHref) { + data.href = this.__fontHref; + } + + return data; + }; + + /** + * Helper to link text fragments + * @param font + * @param element + * @return {*} + * @private + */ + Context.prototype.__wrapTextLink = function (font, element) { + if (font.href) { + var a = this.__createElement("a"); + a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", font.href); + a.appendChild(element); + return a; + } + return element; + }; + + /** + * Fills or strokes text + * @param text + * @param x + * @param y + * @param action - stroke or fill + * @private + */ + Context.prototype.__applyText = function (text, x, y, action) { + var font = this.__parseFont(), + parent = this.__closestGroupOrSvg(), + textElement = this.__createElement("text", { + "font-family" : font.family, + "font-size" : font.size, + "font-style" : font.style, + "font-weight" : font.weight, + "text-decoration" : font.decoration, + "x" : x, + "y" : y, + "text-anchor": getTextAnchor(this.textAlign), + "dominant-baseline": getDominantBaseline(this.textBaseline) + }, true); + + textElement.appendChild(this.__document.createTextNode(text)); + this.__currentElement = textElement; + this.__applyTransformation(textElement); + this.__applyStyleToCurrentElement(action); + parent.appendChild(this.__wrapTextLink(font,textElement)); + }; + + /** + * Creates a text element + * @param text + * @param x + * @param y + */ + Context.prototype.fillText = function (text, x, y) { + this.__applyText(text, x, y, "fill"); + }; + + /** + * Strokes text + * @param text + * @param x + * @param y + */ + Context.prototype.strokeText = function (text, x, y) { + this.__applyText(text, x, y, "stroke"); + }; + + /** + * No need to implement this for svg. + * @param text + * @return {TextMetrics} + */ + Context.prototype.measureText = function (text) { + this.__ctx.font = this.font; + return this.__ctx.measureText(text); + }; + + /** + * Arc command! + */ + Context.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) { + // in canvas no circle is drawn if no angle is provided. + if (startAngle === endAngle) { + return; + } + startAngle = startAngle % (2*Math.PI); + endAngle = endAngle % (2*Math.PI); + if (startAngle === endAngle) { + //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) + endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); + } + var endX = x+radius*Math.cos(endAngle), + endY = y+radius*Math.sin(endAngle), + startX = x+radius*Math.cos(startAngle), + startY = y+radius*Math.sin(startAngle), + sweepFlag = counterClockwise ? 0 : 1, + largeArcFlag = 0, + diff = endAngle - startAngle; + + // https://github.com/gliffy/canvas2svg/issues/4 + if (diff < 0) { + diff += 2*Math.PI; + } + + if (counterClockwise) { + largeArcFlag = diff > Math.PI ? 0 : 1; + } else { + largeArcFlag = diff > Math.PI ? 1 : 0; + } + + this.lineTo(startX, startY); + this.__addPathCommand(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", + { + rx:radius, + ry:radius, + xAxisRotation:0, + largeArcFlag:largeArcFlag, + sweepFlag:sweepFlag, + endX: this.__matrixTransform(endX, endY).x, + endY: this.__matrixTransform(endX, endY).y + })); + + this.__currentPosition = {x: endX, y: endY}; + }; + + /** + * Generates a ClipPath from the clip command. + */ + Context.prototype.clip = function () { + var group = this.__closestGroupOrSvg(), + clipPath = this.__createElement("clipPath"), + id = randomString(this.__ids), + newGroup = this.__createElement("g"); + + this.__applyCurrentDefaultPath(); + group.removeChild(this.__currentElement); + clipPath.setAttribute("id", id); + clipPath.appendChild(this.__currentElement); + + this.__defs.appendChild(clipPath); + + //set the clip path to this group + group.setAttribute("clip-path", format("url(#{id})", {id:id})); + + //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations + // to this path + group.appendChild(newGroup); + + this.__currentElement = newGroup; + + }; + + /** + * Draws a canvas, image or mock context to this canvas. + * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support. + * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage + */ + Context.prototype.drawImage = function () { + //convert arguments to a real array + var args = Array.prototype.slice.call(arguments), + image=args[0], + dx, dy, dw, dh, sx=0, sy=0, sw, sh, parent, svg, defs, group, + svgImage, canvas, context, id; + + if (args.length === 3) { + dx = args[1]; + dy = args[2]; + sw = image.width; + sh = image.height; + dw = sw; + dh = sh; + } else if (args.length === 5) { + dx = args[1]; + dy = args[2]; + dw = args[3]; + dh = args[4]; + sw = image.width; + sh = image.height; + } else if (args.length === 9) { + sx = args[1]; + sy = args[2]; + sw = args[3]; + sh = args[4]; + dx = args[5]; + dy = args[6]; + dw = args[7]; + dh = args[8]; + } else { + throw new Error("Invalid number of arguments passed to drawImage: " + arguments.length); + } + + parent = this.__closestGroupOrSvg(); + const matrix = this.getTransform().translate(dx, dy); + if (image instanceof Context) { + //canvas2svg mock canvas context. In the future we may want to clone nodes instead. + //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context. + svg = image.getSvg().cloneNode(true); + if (svg.childNodes && svg.childNodes.length > 1) { + defs = svg.childNodes[0]; + while(defs.childNodes.length) { + id = defs.childNodes[0].getAttribute("id"); + this.__ids[id] = id; + this.__defs.appendChild(defs.childNodes[0]); + } + group = svg.childNodes[1]; + if (group) { + this.__applyTransformation(group, matrix); + parent.appendChild(group); + } + } + } else if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { + //canvas or image + svgImage = this.__createElement("image"); + svgImage.setAttribute("width", dw); + svgImage.setAttribute("height", dh); + svgImage.setAttribute("preserveAspectRatio", "none"); + + if (sx || sy || sw !== image.width || sh !== image.height) { + //crop the image using a temporary canvas + canvas = this.__document.createElement("canvas"); + canvas.width = dw; + canvas.height = dh; + context = canvas.getContext("2d"); + context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh); + image = canvas; + } + this.__applyTransformation(svgImage, matrix); + svgImage.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", + image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src")); + parent.appendChild(svgImage); + } + }; + + /** + * Generates a pattern tag + */ + Context.prototype.createPattern = function (image, repetition) { + var pattern = this.__document.createElementNS("http://www.w3.org/2000/svg", "pattern"), id = randomString(this.__ids), + img; + pattern.setAttribute("id", id); + pattern.setAttribute("width", image.width); + pattern.setAttribute("height", image.height); + // We want the pattern sizing to be absolute, and not relative + // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Patterns + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternUnits + pattern.setAttribute("patternUnits", "userSpaceOnUse"); + + if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { + img = this.__document.createElementNS("http://www.w3.org/2000/svg", "image"); + img.setAttribute("width", image.width); + img.setAttribute("height", image.height); + img.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", + image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src")); + pattern.appendChild(img); + this.__defs.appendChild(pattern); + } else if (image instanceof Context) { + pattern.appendChild(image.__root.childNodes[1]); + this.__defs.appendChild(pattern); + } + return new CanvasPattern(pattern, this); + }; + + Context.prototype.setLineDash = function (dashArray) { + if (dashArray && dashArray.length > 0) { + this.lineDash = dashArray.join(","); + } else { + this.lineDash = null; + } + }; + + /** + * SetTransform changes the current transformation matrix to + * the matrix given by the arguments as described below. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform + */ + Context.prototype.setTransform = function (a, b, c, d, e, f) { + if (a instanceof DOMMatrix) { + this.__transformMatrix = new DOMMatrix([a.a, a.b, a.c, a.d, a.e, a.f]); + } else { + this.__transformMatrix = new DOMMatrix([a, b, c, d, e, f]); + } + }; + + /** + * GetTransform Returns a copy of the current transformation matrix, + * as a newly created DOMMAtrix Object + * + * @returns A DOMMatrix Object + */ + Context.prototype.getTransform = function () { + let {a, b, c, d, e, f} = this.__transformMatrix; + return new DOMMatrix([a, b, c, d, e, f]); + }; + + /** + * ResetTransform resets the current transformation matrix to the identity matrix + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/resetTransform + */ + Context.prototype.resetTransform = function () { + this.setTransform(1, 0, 0, 1, 0, 0); + }; + + /** + * Add the scaling transformation described by the arguments to the current transformation matrix. + * + * @param x The x argument represents the scale factor in the horizontal direction + * @param y The y argument represents the scale factor in the vertical direction. + * @see https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-scale + */ + Context.prototype.scale = function (x, y) { + if (y === undefined) { + y = x; + } + // If either of the arguments are infinite or NaN, then return. + if (isNaN(x) || isNaN(y) || !isFinite(x) || !isFinite(y)) { + return + } + let matrix = this.getTransform().scale(x, y); + this.setTransform(matrix); + }; + + /** + * Rotate adds a rotation to the transformation matrix. + * + * @param angle The rotation angle, clockwise in radians. You can use degree * Math.PI / 180 to calculate a radian from a degree. + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate + * @see https://www.w3.org/TR/css-transforms-1 + */ + Context.prototype.rotate = function (angle) { + let matrix = this.getTransform().multiply(new DOMMatrix([ + Math.cos(angle), + Math.sin(angle), + -Math.sin(angle), + Math.cos(angle), + 0, + 0 + ])); + this.setTransform(matrix); + }; + + /** + * Translate adds a translation transformation to the current matrix. + * + * @param x Distance to move in the horizontal direction. Positive values are to the right, and negative to the left. + * @param y Distance to move in the vertical direction. Positive values are down, and negative are up. + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate + */ + Context.prototype.translate = function (x, y) { + const matrix = this.getTransform().translate(x, y); + this.setTransform(matrix); + }; + + /** + * Transform multiplies the current transformation with the matrix described by the arguments of this method. + * This lets you scale, rotate, translate (move), and skew the context. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform + */ + Context.prototype.transform = function (a, b, c, d, e, f) { + const matrix = this.getTransform().multiply(new DOMMatrix([a, b, c, d, e, f])); + this.setTransform(matrix); + }; + + Context.prototype.__matrixTransform = function(x, y) { + return new DOMPoint(x, y).matrixTransform(this.__transformMatrix) + }; + + /** + * + * @param {*} sx The x-axis coordinate of the top-left corner of the rectangle from which the ImageData will be extracted. + * @param {*} sy The y-axis coordinate of the top-left corner of the rectangle from which the ImageData will be extracted. + * @param {*} sw The width of the rectangle from which the ImageData will be extracted. Positive values are to the right, and negative to the left. + * @param {*} sh The height of the rectangle from which the ImageData will be extracted. Positive values are down, and negative are up. + * @param {Boolean} options.async Will return a Promise if true, must be set to true + * @returns An ImageData object containing the image data for the rectangle of the canvas specified. The coordinates of the rectangle's top-left corner are (sx, sy), while the coordinates of the bottom corner are (sx + sw, sy + sh). + */ + Context.prototype.getImageData = function(sx, sy, sw, sh, options) { + return utils.getImageData(this.getSvg(), this.width, this.height, sx, sy, sw, sh, options); + }; + + /** + * Not yet implemented + */ + Context.prototype.drawFocusRing = function () {}; + Context.prototype.createImageData = function () {}; + Context.prototype.putImageData = function () {}; + Context.prototype.globalCompositeOperation = function () {}; + + return Context; + }()); + + function SVGCanvasElement(options) { + + this.ctx = new Context(options); + this.svg = this.ctx.__root; + + // sync attributes to svg + var svg = this.svg; + var _this = this; + + var wrapper = document.createElement('div'); + wrapper.style.display = 'inline-block'; + wrapper.appendChild(svg); + this.wrapper = wrapper; + + Object.defineProperty(this, 'className', { + get: function() { + return wrapper.getAttribute('class') || ''; + }, + set: function(val) { + return wrapper.setAttribute('class', val); + } + }); + + ["width", "height"].forEach(function(prop) { + Object.defineProperty(_this, prop, { + get: function() { + return svg.getAttribute(prop) | 0; + }, + set: function(val) { + if (isNaN(val) || (typeof val === "undefined")) { + return; + } + _this.ctx[prop] = val; + svg.setAttribute(prop, val); + return wrapper[prop] = val; + } + }); + }); + + ["style", "id"].forEach(function(prop) { + Object.defineProperty(_this, prop, { + get: function() { + return wrapper[prop]; + }, + set: function(val) { + if (typeof val !== "undefined") { + return wrapper[prop] = val; + } + } + }); + }); + + ["getBoundingClientRect"].forEach(function(fn) { + _this[fn] = function() { + return svg[fn](); + }; + }); + } + + SVGCanvasElement.prototype.getContext = function(type) { + if (type !== '2d') { + throw new Error('Unsupported type of context for SVGCanvas'); + } + + return this.ctx; + }; + + // you should always use URL.revokeObjectURL after your work done + SVGCanvasElement.prototype.toObjectURL = function() { + var data = new XMLSerializer().serializeToString(this.svg); + var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'}); + return URL.createObjectURL(svg); + }; + + /** + * toDataURL returns a data URI containing a representation of the image in the format specified by the type parameter. + * + * @param {String} type A DOMString indicating the image format. The default type is image/svg+xml; this image format will be also used if the specified type is not supported. + * @param {Number} encoderOptions A Number between 0 and 1 indicating the image quality to be used when creating images using file formats that support lossy compression (such as image/jpeg or image/webp). A user agent will use its default quality value if this option is not specified, or if the number is outside the allowed range. + * @param {Boolean} options.async Will return a Promise if true, must be set to true if type is not image/svg+xml + */ + SVGCanvasElement.prototype.toDataURL = function(type, encoderOptions, options) { + return utils.toDataURL(this.svg, this.width, this.height, type, encoderOptions, options) + }; + + SVGCanvasElement.prototype.addEventListener = function() { + return this.svg.addEventListener.apply(this.svg, arguments); + }; + + // will return wrapper element:
+ SVGCanvasElement.prototype.getElement = function() { + return this.wrapper; + }; + var Element$1 = SVGCanvasElement; + + const DEBUG = false; + + function RendererSVG(p5) { + /** + * @namespace RendererSVG + * @constructor + * @param {Element} elt canvas element to be replaced + * @param {p5} pInst p5 Instance + * @param {Bool} isMainCanvas + */ + function RendererSVG(elt, pInst, isMainCanvas) { + var svgCanvas = new Element$1({debug: DEBUG}); + var svg = svgCanvas.svg; + + // replace with and copy id, className + var parent = elt.parentNode; + var id = elt.id; + var className = elt.className; + parent.replaceChild(svgCanvas.getElement(), elt); + svgCanvas.id = id; + svgCanvas.className = className; + elt = svgCanvas; // our fake + + elt.parentNode = { + // fake parentNode.removeChild so that noCanvas will work + removeChild: function(element) { + if (element === elt) { + var wrapper = svgCanvas.getElement(); + wrapper.parentNode.removeChild(wrapper); + } + } + }; + + const pInstProxy = new Proxy(pInst, { + get: function(target, prop) { + if (prop === '_pixelDensity') { + // 1 is OK for SVG + return 1; + } + return target[prop]; + } + }); + + p5.Renderer2D.call(this, elt, pInstProxy, isMainCanvas); + + this.isSVG = true; + this.svg = svg; + + return this; + } + + RendererSVG.prototype = Object.create(p5.Renderer2D.prototype); + + RendererSVG.prototype._applyDefaults = function() { + p5.Renderer2D.prototype._applyDefaults.call(this); + this.drawingContext.lineWidth = 1; + }; + + RendererSVG.prototype.resize = function(w, h) { + if (!w || !h) { + return; + } + if (this.width !== w || this.height !== h) { + // canvas will be cleared if its size changed + // so, we do same thing for SVG + // note that at first this.width and this.height is undefined + this.drawingContext.__clearCanvas(); + } + + p5.Renderer2D.prototype.resize.call(this, w, h); + + // For scale, crop + // see also: http://sarasoueidan.com/blog/svg-coordinate-systems/ + this.svg.setAttribute('viewBox', [0, 0, w, h].join(' ')); + }; + + /** + * Append a element to current SVG Graphics + * + * @function appendChild + * @memberof RendererSVG.prototype + * @param {SVGElement|Element} element + */ + RendererSVG.prototype.appendChild = function(element) { + if (element && element.elt) { + element = element.elt; + } + var g = this.drawingContext.__closestGroupOrSvg(); + g.appendChild(element); + }; + + /** + * Draw an image or SVG to current SVG Graphics + * + * FIXME: sx, sy, sWidth, sHeight + * + * @function image + * @memberof RendererSVG.prototype + * @param {p5.Graphics|SVGGraphics|SVGElement|Element} image + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + RendererSVG.prototype.image = function(img, sx, sy, sWidth, sHeight, x, y, w, h) { + if (!img) { + throw new Error('Invalid image: ' + img); + } + var elt = img._renderer && img._renderer.svg; // handle SVG Graphics + elt = elt || (img.elt && img.elt.nodeName && (img.elt.nodeName.toLowerCase() === 'svg') && img.elt); // SVGElement + elt = elt || (img.nodeName && (img.nodeName.toLowerCase() == 'svg') && img); // + if (elt) { + // it's element, let's handle it + elt = elt.cloneNode(true); + elt.setAttribute('width', w); + elt.setAttribute('height', h); + elt.setAttribute('x', x); + elt.setAttribute('y', y); + if (sx || sy || sWidth || sHeight) { + sWidth /= this._pInst._pixelDensity; + sHeight /= this._pInst._pixelDensity; + elt.setAttribute('viewBox', [sx, sy, sWidth, sHeight].join(', ')); + } + this.appendChild(elt); + } else { + p5.Renderer2D.prototype.image.apply(this, arguments); + } + }; + + /** + * @method parent + * @return {p5.Element} + * + * @see https://github.com/zenozeng/p5.js-svg/issues/187 + */ + RendererSVG.prototype.parent = function() { + const $this = { + elt: this.elt.getElement() + }; + return p5.Element.prototype.parent.apply($this, arguments); + }; + + + RendererSVG.prototype.loadPixels = async function() { + const pixelsState = this._pixelsState; // if called by p5.Image + const pd = pixelsState._pixelDensity; + const w = this.width * pd; + const h = this.height * pd; + const imageData = await this.drawingContext.getImageData(0, 0, w, h, {async: true}); + pixelsState._setProperty('imageData', imageData); + pixelsState._setProperty('pixels', imageData.data); + }; + + p5.RendererSVG = RendererSVG; + } + + var constants = { + SVG: 'svg' + }; + + function Rendering(p5) { + // patch p5.Graphics for SVG + var _graphics = p5.Graphics; + p5.Graphics = function(w, h, renderer, pInst) { + const isSVG = renderer === constants.SVG; + _graphics.apply(this, [w, h, isSVG ? p5.P2D : renderer, pInst]); + if (isSVG) { + // replace with + var c = this._renderer.elt; + this._renderer = new p5.RendererSVG(c, this, false); // replace renderer + c = this._renderer.elt; + this.elt = c; // replace this.elt + // do default again + this._renderer.resize(w, h); + this._renderer._applyDefaults(); + } + return this; + }; + p5.Graphics.prototype = _graphics.prototype; + + /** + * Patched version of createCanvas + * + * use createCanvas(100, 100, SVG) to create SVG canvas. + * + * Creates a SVG element in the document, and sets its width and + * height in pixels. This method should be called only once at + * the start of setup. + * @function createCanvas + * @memberof p5.prototype + * @param {Number} width - Width (in px) for SVG Element + * @param {Number} height - Height (in px) for SVG Element + * @return {Graphics} + */ + var _createCanvas = p5.prototype.createCanvas; + p5.prototype.createCanvas = function(w, h, renderer) { + var graphics = _createCanvas.apply(this, arguments); + if (renderer === constants.SVG) { + var c = graphics.canvas; + this._setProperty('_renderer', new p5.RendererSVG(c, this, true)); + this._isdefaultGraphics = true; + this._renderer.resize(w, h); + this._renderer._applyDefaults(); + } + return this._renderer; + }; + + p5.prototype.createGraphics = function(w, h, renderer) { + return new p5.Graphics(w, h, renderer, this); + }; + + } + + function IO(p5) { + /** + * Convert SVG Element to jpeg / png data url + * + * @private + * @param {SVGElement} svg SVG Element + * @param {String} mine Mine + * @param {Function} callback + */ + var svg2img = function(svg, mine, callback) { + svg = (new XMLSerializer()).serializeToString(svg); + svg = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg); + if (mine == 'image/svg+xml') { + callback(null, svg); + return; + } + var img = new Image(); + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + img.onload = function() { + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + var dataURL = canvas.toDataURL(mine); + callback(null, dataURL); + }; + img.src = svg; + }; + + /** + * Get SVG frame, and convert to target type + * + * @private + * @param {Object} options + * @param {SVGElement} options.svg SVG Element, defaults to current svg element + * @param {String} options.filename + * @param {String} options.ext Extension: 'svg' or 'jpg' or 'jpeg' or 'png' + * @param {Function} options.callback + */ + p5.prototype._makeSVGFrame = function(options) { + var filename = options.filename || 'untitled'; + var ext = options.extension; + ext = ext || this._checkFileExtension(filename, ext)[1]; + var regexp = new RegExp('\\.' + ext + '$'); + filename = filename.replace(regexp, ''); + if (ext === '') { + ext = 'svg'; + } + var mine = { + png: 'image/png', + jpeg: 'image/jpeg', + jpg: 'image/jpeg', + svg: 'image/svg+xml' + }[ext]; + if (!mine) { + throw new Error('Fail to getFrame, invalid extension: ' + ext + ', please use png | jpeg | jpg | svg.'); + } + + var svg = options.svg || this._renderer.svg; + svg2img(svg, mine, function(err, dataURL) { + var downloadMime = 'image/octet-stream'; + dataURL = dataURL.replace(mine, downloadMime); + options.callback(err, { + imageData: dataURL, + filename: filename, + ext: ext + }); + }); + }; + + /** + * Save the current SVG as an image. In Safari, will open the + * image in the window and the user must provide their own + * filename on save-as. Other browsers will either save the + * file immediately, or prompt the user with a dialogue window. + * + * @function saveSVG + * @memberof p5.prototype + * @param {Graphics|Element|SVGElement} [svg] Source to save + * @param {String} [filename] + * @param {String} [extension] Extension: 'svg' or 'jpg' or 'jpeg' or 'png' + */ + p5.prototype.saveSVG = function() { + // don't use slice on arguments because it prevents optimizations + var args = arguments; + args = [args[0], args[1], args[2]]; + + var svg; + + if (args[0] instanceof p5.Graphics) { + svg = args[0]._renderer.svg; + args.shift(); + } + + if (args[0] && args[0].elt) { + svg = args[0].elt; + args.shift(); + } + + if (typeof args[0] == 'object') { + svg = args[0]; + args.shift(); + } + + var filename = args[0]; + var ext = args[1]; + + var p = this; + this._makeSVGFrame({ + svg: svg, + filename: filename, + extension: ext, + callback: function(err, frame) { + p.downloadFile(frame.imageData, frame.filename, frame.ext); + } + }); + }; + + /** + * Extends p5's saveFrames with SVG support + * + * @function saveFrames + * @memberof p5.prototype + * @param {String} filename filename + * @param {String} extension Extension: 'svg' or 'jpg' or 'jpeg' or 'png' + * @param {Number} duration duration + * @param {Number} fps fps + * @param {Function} callback callback + */ + var _saveFrames = p5.prototype.saveFrames; + p5.prototype.saveFrames = function(filename, extension, duration, fps, callback) { + var args = arguments; + + if (!this._renderer.svg) { + _saveFrames.apply(this, args); + return; + } + + duration = duration || 3; + duration = p5.prototype.constrain(duration, 0, 15); + duration = duration * 1000; + fps = fps || 15; + fps = p5.prototype.constrain(fps, 0, 22); + var count = 0; + + var frames = []; + var pending = 0; + + var p = this; + var frameFactory = setInterval(function () { + (function(count) { + pending++; + p._makeSVGFrame({ + filename: filename + count, + extension: extension, + callback: function(err, frame) { + frames[count] = frame; + pending--; + } + }); + })(count); + count++; + }, 1000 / fps); + + var done = function() { + if (pending > 0) { + setTimeout(function() { + done(); + }, 10); + return; + } + if (callback) { + callback(frames); + } else { + frames.forEach(function(f) { + p.downloadFile(f.imageData, f.filename, f.ext); + }); + } + }; + + setTimeout(function () { + clearInterval(frameFactory); + done(); + }, duration + 0.01); + }; + + /** + * Extends p5's save method with SVG support + * + * @function save + * @memberof p5.prototype + * @param {Graphics|Element|SVGElement} [source] Source to save + * @param {String} [filename] filename + */ + var _save = p5.prototype.save; + p5.prototype.save = function() { + var args = arguments; + args = [args[0], args[1]]; + + var svg; + + if (args[0] instanceof p5.Graphics) { + var svgcanvas = args[0].elt; + svg = svgcanvas.svg; + args.shift(); + } + + if (args[0] && args[0].elt) { + svg = args[0].elt; + args.shift(); + } + + if (typeof args[0] == 'object') { + svg = args[0]; + args.shift(); + } + + svg = svg || (this._renderer && this._renderer.svg); + + var filename = args[0]; + var supportedExtensions = ['jpeg', 'png', 'jpg', 'svg', '']; + var ext = this._checkFileExtension(filename, '')[1]; + + var useSVG = svg && svg.nodeName && svg.nodeName.toLowerCase() === 'svg' && supportedExtensions.indexOf(ext) > -1; + + if (useSVG) { + this.saveSVG(svg, filename); + } else { + return _save.apply(this, arguments); + } + }; + + /** + * Custom get in p5.svg (handles http and dataurl) + * @private + */ + p5.prototype._svg_get = function(path, successCallback, failureCallback) { + if (path.indexOf('data:') === 0) { + if (path.indexOf(',') === -1) { + failureCallback(new Error('Fail to parse dataurl: ' + path)); + return; + } + var svg = path.split(',').pop(); + // force request to dataurl to be async + // so that it won't make preload mess + setTimeout(function() { + if (path.indexOf(';base64,') > -1) { + svg = atob(svg); + } else { + svg = decodeURIComponent(svg); + } + successCallback(svg); + }, 1); + return svg; + } else { + this.httpGet(path, successCallback); + return null; + } + }; + + /** + * loadSVG (like loadImage, but will return SVGElement) + * + * @function loadSVG + * @memberof p5.prototype + * @returns {p5.SVGElement} + */ + p5.prototype.loadSVG = function(path, successCallback, failureCallback) { + var div = document.createElement('div'); + var element = new p5.SVGElement(div); + this._incrementPreload(); + new Promise((resolve, reject) => { + this._svg_get(path, function(svg) { + div.innerHTML = svg; + svg = div.querySelector('svg'); + if (!svg) { + reject('Fail to create .'); + return; + } + element.elt = svg; + resolve(element); + }, reject); + }).then((v) => { + successCallback && successCallback(v); + }).catch((e) => { + failureCallback && failureCallback(e); + }).finally(() => { + this._decrementPreload(); + }); + return element; + }; + + p5.prototype.getDataURL = function() { + return this._renderer.elt.toDataURL('image/svg+xml'); + }; + } + + function Element(p5) { + /** + * Returns an Array of SVGElements of current SVG Graphics matching given selector + * + * @function querySVG + * @memberof p5.prototype + * @param {String} selector CSS selector for query + * @returns {SVGElement[]} + */ + p5.prototype.querySVG = function(selector) { + var svg = this._renderer && this._renderer.svg; + if (!svg) { + return null; + } + return p5.SVGElement.prototype.query.call({elt: svg}, selector); + }; + + /** + * @namespace SVGElement + * @constructor + * @param {Element} element + */ + function SVGElement(element) { + if (!element) { + return null; + } + return p5.Element.apply(this, arguments); + } + + SVGElement.prototype = Object.create(p5.Element.prototype); + + /** + * Returns an Array of children of current SVG Element matching given selector + * + * @function query + * @memberof SVGElement.prototype + * @param {String} selector CSS selector for query + * @returns {SVGElement[]} + */ + SVGElement.prototype.query = function(selector) { + var elements = this.elt.querySelectorAll(selector); + var objects = []; + for (var i = 0; i < elements.length; i++) { + objects[i] = new SVGElement(elements[i]); + } + return objects; + }; + + /** + * Append a new child to current element. + * + * @function append + * @memberof SVGElement.prototype + * @param {SVGElement|Element} element + */ + SVGElement.prototype.append = function(element) { + var elt = element.elt || element; + this.elt.appendChild(elt); + return this; + }; + + /** + * Apply different attribute operation based on arguments.length + *
    + *
  • setAttribute(name, value)
  • + *
  • setAttributeNS(namespace, name, value)
  • + *
  • getAttribute(name)
  • + *
+ * + * @function attribute + * @memberof SVGElement.prototype + */ + SVGElement.prototype.attribute = function() { + var args = arguments; + if (args.length === 3) { + this.elt.setAttributeNS.apply(this.elt, args); + } + if (args.length === 2) { + this.elt.setAttribute.apply(this.elt, args); + } + if (args.length === 1) { + return this.elt.getAttribute.apply(this.elt, args); + } + return this; + }; + + /** + * Apply filter on current element. + * If called multiple times, + * these filters will be chained together and combine to a bigger SVG filter. + * + * @function filter + * @memberof SVGElement.prototype + * @param {String} filter BLUR, GRAY, INVERT, THRESHOLD, OPAQUE, ERODE, DILATE (defined in p5's constants) + * @param {Any} argument Argument for that filter + */ + SVGElement.prototype._filter = function(filter, arg, defs) { + p5.SVGFilters.apply(this, filter, arg, defs); + return this; + }; + + /** + * Create SVGElement + * + * @function create + * @memberof SVGElement + * @param {String} nodeName + * @param {Object} [attributes] Attributes for the element + * @return {SVGElement} + */ + SVGElement.create = function(nodeName, attributes) { + attributes = attributes || {}; + var elt = document.createElementNS('http://www.w3.org/2000/svg', nodeName); + Object.keys(attributes).forEach(function(k) { + elt.setAttribute(k, attributes[k]); + }); + return new SVGElement(elt); + }; + + + /** + * Get parentNode. + * If selector not given, returns parentNode. + * Otherwise, will look up all ancestors, + * and return closest element matching given selector, + * or return null if not found. + * + * @function parentNode + * @memberof SVGElement.prototype + * @param {String} [selector] CSS Selector + * @return {SVGElement} + */ + SVGElement.prototype.parentNode = function(selector) { + if (!selector) { + return new SVGElement(this.elt.parentNode); + } + var elt = this; + while (elt) { + elt = this.parentNode(); + if (elt && elt.matches(selector)) { + return elt; + } + } + return null; + }; + + p5.SVGElement = SVGElement; + } + + function P5SVGFilters(p5) { + var SVGFilters = function() {}; + + var SVGElement = p5.SVGElement; + + var generateID = function() { + return Date.now().toString() + Math.random().toString().replace(/0\./, ''); + }; + + // @private + // We have to build a filter for each element + // the `filter: f1 f2` and svg param is not supported by many browsers + // so we can just modify the filter def to do so + SVGFilters.apply = function(svgElement, func, arg, defs) { + // get filters + var filters = svgElement.attribute('data-p5-svg-filters') || '[]'; + filters = JSON.parse(filters); + if (func) { + filters.push([func, arg]); + } + svgElement.attribute('data-p5-svg-filters', JSON.stringify(filters)); + + if (filters.length === 0) { + svgElement.attribute('filter', null); + return; + } + + // generate filters chain + filters = filters.map(function(filter, index) { + var inGraphics = index === 0 ? 'SourceGraphic' : ('result-' + (index - 1)); + var resultGraphics = 'result-' + index; + return SVGFilters[filter[0]].call(null, inGraphics, resultGraphics, filter[1]); + }); + + // get filter id for this element or create one + var filterid = svgElement.attribute('data-p5-svg-filter-id'); + if (!filterid) { + filterid = 'p5-svg-' + generateID(); + svgElement.attribute('data-p5-svg-filter-id', filterid); + } + // Note that when filters is [], we will remove filter attr + // So, here, we write this attr every time + svgElement.attribute('filter', 'url(#' + filterid + ')'); + + // create + var filter = SVGElement.create('filter', {id: filterid}); + filters.forEach(function(elt) { + if (!Array.isArray(elt)) { + elt = [elt]; + } + elt.forEach(function(elt) { + filter.append(elt); + }); + }); + + // get defs + var oldfilter = defs.querySelectorAll('#' + filterid)[0]; + if (!oldfilter) { + defs.appendChild(filter.elt); + } else { + oldfilter.elt.parentNode.replaceChild(filter.elt, oldfilter); + } + }; + + SVGFilters.blur = function(inGraphics, resultGraphics, val) { + return SVGElement.create('feGaussianBlur', { + stdDeviation: val, + in: inGraphics, + result: resultGraphics, + 'color-interpolation-filters': 'sRGB' + }); + }; + + // See also: http://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement + // See also: http://stackoverflow.com/questions/21977929/match-colors-in-fecolormatrix-filter + SVGFilters.colorMatrix = function(inGraphics, resultGraphics, matrix) { + return SVGElement.create('feColorMatrix', { + type: 'matrix', + values: matrix.join(' '), + 'color-interpolation-filters': 'sRGB', + in: inGraphics, + result: resultGraphics + }); + }; + + // Here we use CIE luminance for RGB + SVGFilters.gray = function(inGraphics, resultGraphics) { + var matrix = [ + 0.2126, 0.7152, 0.0722, 0, 0, // R' + 0.2126, 0.7152, 0.0722, 0, 0, // G' + 0.2126, 0.7152, 0.0722, 0, 0, // B' + 0, 0, 0, 1, 0 // A' + ]; + return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); + }; + + SVGFilters.threshold = function(inGraphics, resultGraphics, val) { + var elements = []; + elements.push(SVGFilters.gray(inGraphics, resultGraphics + '-tmp')); + var componentTransfer = SVGElement.create('feComponentTransfer', { + 'in': resultGraphics + '-tmp', + result: resultGraphics + }); + var thresh = Math.floor(val * 255); + ['R', 'G', 'B'].forEach(function(channel) { + // Note that original value is from 0 to 1 + var func = SVGElement.create('feFunc' + channel, { + type: 'linear', + slope: 255, // all non-zero * 255 + intercept: (thresh - 1) * -1 + }); + componentTransfer.append(func); + }); + elements.push(componentTransfer); + return elements; + }; + + SVGFilters.invert = function(inGraphics, resultGraphics) { + var matrix = [ + -1, 0, 0, 0, 1, + 0, -1, 0, 0, 1, + 0, 0, -1, 0, 1, + 0, 0, 0, 1, 0 + ]; + return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); + }; + + SVGFilters.opaque = function(inGraphics, resultGraphics) { + var matrix = [ + 1, 0, 0, 0, 0, // original R + 0, 1, 0, 0, 0, // original G + 0, 0, 1, 0, 0, // original B + 0, 0, 0, 0, 1 // set A to 1 + ]; + return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); + }; + + /** + * Generate discrete table values based on the given color map function + * + * @private + * @param {Function} fn - Function to map channel values (val ∈ [0, 255]) + * @see http://www.w3.org/TR/SVG/filters.html#feComponentTransferElement + */ + SVGFilters._discreteTableValues = function(fn) { + var table = []; + for (var val = 0; val < 256; val++) { + var newval = fn(val); + table.push(newval / 255); // map to ∈ [0, 1] + } + return table; + }; + + /** + * Limits each channel of the image to the number of colors specified as + * the parameter. The parameter can be set to values between 2 and 255, but + * results are most noticeable in the lower ranges. + * + * Adapted from p5's Filters.posterize + */ + SVGFilters.posterize = function(inGraphics, resultGraphics, level) { + level = parseInt(level, 10); + if ((level < 2) || (level > 255)) { + throw new Error( + 'Level must be greater than 2 and less than 255 for posterize' + ); + } + + var tableValues = SVGFilters._discreteTableValues(function(val) { + return (((val * level) >> 8) * 255) / (level - 1); + }); + + var componentTransfer = SVGElement.create('feComponentTransfer', { + 'in': inGraphics, + result: resultGraphics, + 'color-interpolation-filters': 'sRGB' + }); + ['R', 'G', 'B'].forEach(function(channel) { + var func = SVGElement.create('feFunc' + channel, { + type: 'discrete', + tableValues: tableValues.join(' ') + }); + componentTransfer.append(func); + }); + + return componentTransfer; + }; + + SVGFilters._blendOffset = function(inGraphics, resultGraphics, mode) { + var elements = []; + [ + ['left', -1, 0], + ['right', 1, 0], + ['up', 0, -1], + ['down', 0, 1] + ].forEach(function(neighbor) { + elements.push(SVGElement.create('feOffset', { + 'in': inGraphics, + result: resultGraphics + '-' + neighbor[0], + dx: neighbor[1], + dy: neighbor[2] + })); + }); + [ + [null, inGraphics], + [resultGraphics + '-left', resultGraphics + '-tmp-0'], + [resultGraphics + '-right', resultGraphics + '-tmp-1'], + [resultGraphics + '-up', resultGraphics + '-tmp-2'], + [resultGraphics + '-down', resultGraphics + '-tmp-3'] + ].forEach(function(layer, i, layers) { + if (i === 0) { + return; + } + elements.push(SVGElement.create('feBlend', { + 'in': layers[i - 1][1], + in2: layer[0], + result: layer[1], + mode: mode + })); + }); + return elements; + }; + + /** + * Increases the bright areas in an image + * + * Will create 4 offset layer and blend them using darken mode + */ + SVGFilters.erode = function(inGraphics, resultGraphics) { + return SVGFilters._blendOffset(inGraphics, resultGraphics, 'darken'); + }; + + SVGFilters.dilate = function(inGraphics, resultGraphics) { + return SVGFilters._blendOffset(inGraphics, resultGraphics, 'lighten'); + }; + + p5.SVGFilters = SVGFilters; + + return SVGFilters; + } + + // SVG Filter + + function Filters(p5) { + var _filter = p5.prototype.filter; + + var SVGFilters = P5SVGFilters(p5); + + /** + * Register a custom SVG Filter + * + * @function registerSVGFilter + * @memberof p5.prototype + * @param {String} name Name for Custom SVG filter + * @param {Function} filterFunction filterFunction(inGraphicsName, resultGraphicsName, value) + * should return SVGElement or Array of SVGElement. + * @example + * registerSVGFilter('myblur', function(inGraphicsName, resultGraphicsName, value) { + * return SVGElement.create('feGaussianBlur', { + * stdDeviation: val, + * in: inGraphics, + * result: resultGraphics, + * 'color-interpolation-filters': 'sRGB' + * }); + * }); + * filter('myblur', 5); + */ + p5.prototype.registerSVGFilter = function(name, fn) { + SVGFilters[name] = fn; + }; + + p5.prototype.filter = function(operation, value) { + var svg = this._renderer.svg; + if (svg) { + const ctx = this._renderer.drawingContext; + const defs = ctx.__root.querySelectorAll('defs')[0]; + const rootGroup = ctx.__root.childNodes[1]; + // move nodes to a new + let g = p5.SVGElement.create('g'); + while (rootGroup.childNodes.length > 0) { + g.elt.appendChild(rootGroup.childNodes[0]); + } + rootGroup.appendChild(g.elt); + + // apply filter + g._filter(operation, value, defs); + + // create new so that new element won't be influenced by the filter + g = p5.SVGElement.create('g'); + rootGroup.appendChild(g.elt); + ctx.__currentElement = g.elt; + } else { + _filter.apply(this, arguments); + } + }; + } + + function Image$1(p5) { + p5.prototype.loadPixels = function(...args) { + p5._validateParameters('loadPixels', args); + return this._renderer.loadPixels(); + }; + } + + function init(p5) { + /** + * @namespace p5 + */ + RendererSVG(p5); + Rendering(p5); + IO(p5); + Element(p5); + Filters(p5); + Image$1(p5); + + // attach constants to p5 instance + Object.keys(constants).forEach(function(k) { + p5.prototype[k] = constants[k]; + }); + } + + if (typeof window.p5 !== 'undefined') { + init(window.p5); + } + + return init; + +}()); +//# sourceMappingURL=p5.svg.js.map