├── img ├── clear.png └── info.png ├── README.md ├── index.html ├── index.js ├── lib ├── sketch_rnn.js └── p5.svg.js └── LICENSE.txt /img/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymatuschak/scrying-pen/HEAD/img/clear.png -------------------------------------------------------------------------------- /img/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andymatuschak/scrying-pen/HEAD/img/info.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scrying Pen 2 | 3 | This pen's ink stretches backwards into the past and forwards into possible futures. The two sides make a strange loop: the future ink influences how you draw, which in turn becomes the new "past" ink influencing further future ink. 4 | 5 | Put another way: this is a realtime implementation of [SketchRNN](https://arxiv.org/abs/1704.03477) which predicts future strokes while you draw. 6 | 7 | [Play with it here!](http://andymatuschak.org/scrying-pen) (You'll need to use Chrome for now.) 8 | 9 | ![video of toy in action](https://andymatuschak.org/scrying-pen/images/hand.gif) 10 | 11 | Let it guide your hand, or not, while you draw. Enjoy the gentle interplay between your volition and the machine's. 12 | 13 | ## Background 14 | 15 | I'm excited about applications of machine learning to human augmentation—as opposed to automating tedious work or solving number-crunching problems. I believe effective media for human augmentation require feedback loops tight enough to work at the speed of thought. All that is to say: I'm interested in what happens when we take complex operations and make them interactive in real-time. 16 | 17 | ## Thanks 18 | 19 | I'm grateful to David Ha, Douglas Eck, and their team for their great work with SketchRNN. The paper's wonderful to read; the models from Quick, Draw! are a great resource to the community; the demo implementations were a very helpful reference. 20 | 21 | I'm also grateful to Michael Nielsen, Robert Ochshorn, and M Eifler for useful conversations about this project. 22 | 23 | Thanks also to the [OpenAI Hackathon](https://blog.openai.com/hackathon/) for providing a nice venue for polishing the last bits of this project up. 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
127 |

Draw here!

128 |

Loading…

129 |
130 | 134 | 138 |

Scrying Pen

139 |
140 |
141 | 143 |
144 | 147 |
148 |
149 | 150 | 151 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Based on an original demo at https://github.com/tensorflow/magenta-demos/tree/master/sketch-rnn-js. 2 | // See LICENSE for full attribution and license details 3 | 4 | dl = deeplearn; 5 | math = dl.ENV.math; 6 | 7 | const sketch = function (p) { 8 | "use strict"; 9 | 10 | const classList = [ 11 | 'ant', 12 | 'ambulance', 13 | 'angel', 14 | 'alarm_clock', 15 | 'antyoga', 16 | 'backpack', 17 | 'barn', 18 | 'basket', 19 | 'bear', 20 | 'bee', 21 | 'beeflower', 22 | 'bicycle', 23 | 'bird', 24 | 'book', 25 | 'brain', 26 | 'bridge', 27 | 'bulldozer', 28 | 'bus', 29 | 'butterfly', 30 | 'cactus', 31 | 'calendar', 32 | 'castle', 33 | 'cat', 34 | 'catbus', 35 | 'catpig', 36 | 'chair', 37 | 'couch', 38 | 'crab', 39 | 'crabchair', 40 | 'crabrabbitfacepig', 41 | 'cruise_ship', 42 | 'diving_board', 43 | 'dog', 44 | 'dogbunny', 45 | 'dolphin', 46 | 'duck', 47 | 'elephant', 48 | 'elephantpig', 49 | 'everything', 50 | 'eye', 51 | 'face', 52 | 'fan', 53 | 'fire_hydrant', 54 | 'firetruck', 55 | 'flamingo', 56 | 'flower', 57 | 'floweryoga', 58 | 'frog', 59 | 'frogsofa', 60 | 'garden', 61 | 'hand', 62 | 'hedgeberry', 63 | 'hedgehog', 64 | 'helicopter', 65 | 'kangaroo', 66 | 'key', 67 | 'lantern', 68 | 'lighthouse', 69 | 'lion', 70 | 'lionsheep', 71 | 'lobster', 72 | 'map', 73 | 'mermaid', 74 | 'monapassport', 75 | 'monkey', 76 | 'mosquito', 77 | 'octopus', 78 | 'owl', 79 | 'paintbrush', 80 | 'palm_tree', 81 | 'parrot', 82 | 'passport', 83 | 'peas', 84 | 'penguin', 85 | 'pig', 86 | 'pigsheep', 87 | 'pineapple', 88 | 'pool', 89 | 'postcard', 90 | 'power_outlet', 91 | 'rabbit', 92 | 'rabbitturtle', 93 | 'radio', 94 | 'radioface', 95 | 'rain', 96 | 'rhinoceros', 97 | 'rifle', 98 | 'roller_coaster', 99 | 'sandwich', 100 | 'scorpion', 101 | 'sea_turtle', 102 | 'sheep', 103 | 'skull', 104 | 'snail', 105 | 'snowflake', 106 | 'speedboat', 107 | 'spider', 108 | 'squirrel', 109 | 'steak', 110 | 'stove', 111 | 'strawberry', 112 | 'swan', 113 | 'swing_set', 114 | 'the_mona_lisa', 115 | 'tiger', 116 | 'toothbrush', 117 | 'toothpaste', 118 | 'tractor', 119 | 'trombone', 120 | 'truck', 121 | 'whale', 122 | 'windmill', 123 | 'yoga', 124 | 'yogabicycle' 125 | ]; 126 | 127 | let isWaitingForHallucination = false; 128 | 129 | // sketch_rnn model 130 | let model; 131 | let temperature = 0.25; 132 | const minimumSequenceLength = 5; // We don't bother with predictions when we have fewer than this many samples. 133 | const defaultModel = "cat"; 134 | 135 | let lastCommittedModelState; // RNN state as of the last time the pen lifted 136 | let currentModelState; // the more ephemeral RNN state, built on lastCommittedModelState, adding in the current stroke as it evolves 137 | 138 | let lastMouseState = null; 139 | let startingMouseState = null; 140 | const epsilon = 2.0; // we ignore mouse movement under this threshold 141 | 142 | let simplifiedRawLines; 143 | let pendingRawLine; 144 | let pendingStrokeIndex; 145 | let strokes; 146 | 147 | p.colorMode(p.HSB, 100); 148 | const lineColor = p.color(0, 0, 0); 149 | const hallucinationLineColor = p.color(30, 40, 90, 100); 150 | const hallucinatedSampleCount = 25; // How many steps forward do we project? 151 | let drawingGraphics = null; 152 | let hallucinationGraphics = null; 153 | 154 | let timeSamples = []; 155 | 156 | // UI 157 | let screenWidth, 158 | screenHeight; 159 | const lineWidth = 2.0; 160 | const screenScaleFactor = 3.0; 161 | 162 | const init = function () { 163 | ModelImporter.set_init_model(model_raw_data); 164 | model = new SketchRNN(ModelImporter.get_model_data()); 165 | 166 | screenWidth = p.windowWidth; 167 | screenHeight = p.windowHeight; 168 | 169 | // Wire up the UI controls. 170 | document 171 | .getElementById("clearButton") 172 | .onclick = onClear; 173 | const selectElement = document.getElementById("model"); 174 | for (let i = 0; i < classList.length; i++) { 175 | const optionElement = document.createElement("option"); 176 | const formattedLabel = classList[i].replace("_", " "); 177 | optionElement.innerHTML = formattedLabel 178 | .charAt(0) 179 | .toUpperCase() + formattedLabel.slice(1); 180 | optionElement.setAttribute("value", classList[i]); 181 | if (classList[i] === defaultModel) { 182 | optionElement.setAttribute("selected", true); 183 | } 184 | selectElement.appendChild(optionElement); 185 | } 186 | selectElement.onchange = onModelSelection; 187 | }; 188 | 189 | const disposeModelState = function (modelState) { 190 | if (modelState) { 191 | for (let component of modelState) { 192 | component.dispose(); 193 | } 194 | } 195 | } 196 | 197 | const currentScaleFactor = function () { 198 | return model 199 | .get_info() 200 | .scale_factor / screenScaleFactor; 201 | } 202 | 203 | const scaleScreenSpaceStrokeSample = function (screenSpaceStrokeSample) { 204 | const scaleFactor = currentScaleFactor(); 205 | return [ 206 | screenSpaceStrokeSample[0] / scaleFactor, 207 | screenSpaceStrokeSample[1] / scaleFactor, 208 | screenSpaceStrokeSample[2] 209 | ]; 210 | } 211 | 212 | const setIsLoading = function (isLoadingState) { 213 | const loadingElement = document.getElementById("loading"); 214 | if (/Safari/.test(window.navigator.userAgent) && !/Chrome/.test(window.navigator.userAgent)) { 215 | loadingElement.innerHTML = "

Alas, Safari is unsupported; try Chrome?

" 216 | } else { 217 | document.getElementById("loading").style.opacity = isLoadingState ? 1 : 0; 218 | } 219 | } 220 | 221 | let lastHallucinationStartPoint = null; 222 | 223 | const restart = function () { 224 | // make sure we enforce some minimum size of our demo 225 | screenWidth = Math.max(window.innerWidth, 480); 226 | screenHeight = Math.max(window.innerHeight, 320); 227 | 228 | // variables for the sketch input interface. 229 | simplifiedRawLines = []; 230 | pendingRawLine = []; 231 | strokes = []; 232 | pendingStrokeIndex = null; 233 | lastMouseState = null; 234 | startingMouseState = null; 235 | 236 | lastHallucinationStartPoint = null; 237 | 238 | disposeModelState(lastCommittedModelState); 239 | lastCommittedModelState = null; 240 | disposeModelState(currentModelState); 241 | currentModelState = null; 242 | }; 243 | 244 | const clearScreen = function () { 245 | p.background(0, 0, 100, 100); 246 | hallucinationGraphics.background(0, 0, 100, 100); 247 | drawingGraphics.clear(); 248 | }; 249 | 250 | p.setup = function () { 251 | init(); 252 | 253 | const mainCanvasPixelDensity = p.pixelDensity(); 254 | hallucinationGraphics = p.createGraphics(screenWidth * mainCanvasPixelDensity, screenHeight * mainCanvasPixelDensity); 255 | drawingGraphics = p.createGraphics(screenWidth * mainCanvasPixelDensity, screenHeight * mainCanvasPixelDensity); 256 | const prepareRenderer = (renderer) => { 257 | renderer.pixelDensity(1); 258 | renderer.scale(mainCanvasPixelDensity, mainCanvasPixelDensity); 259 | renderer.colorMode(p.HSB, 100); 260 | } 261 | prepareRenderer(hallucinationGraphics) 262 | prepareRenderer(drawingGraphics) 263 | 264 | restart(); 265 | p.createCanvas(screenWidth, screenHeight); 266 | p.frameRate(60); 267 | clearScreen(); 268 | 269 | // Preheat the shader stages used in the hallucination pipeline: it takes a 270 | // second or two the first time. 271 | hallucinate(model.update(model.zero_input(), model.zero_state()), [model.zero_input()]); 272 | 273 | console.log('ready.'); 274 | setIsLoading(false); 275 | }; 276 | 277 | const updateModelStateUsingCurrentStrokes = (isFinished) => { 278 | // We smooth the user input to reduce the number of input samples to feed 279 | // forward through the model. 280 | const simplifiedPendingRawLine = DataTool.simplify_line(pendingRawLine); 281 | if (simplifiedPendingRawLine.length <= 1) { 282 | return; 283 | } 284 | 285 | // Have we recorded any simplified lines for this stroke yet? Update if so, append if not. 286 | if (pendingStrokeIndex !== null) { 287 | simplifiedRawLines[simplifiedRawLines.length - 1] = simplifiedPendingRawLine; 288 | } else { 289 | simplifiedRawLines.push(simplifiedPendingRawLine); 290 | } 291 | 292 | // Where did the previous stroke end? For the first stroke: where did it all 293 | // begin? We need this because the network is trained on all relative motion. 294 | let previousStrokeFinalX = startingMouseState.x, 295 | previousStrokeFinalY = startingMouseState.y; 296 | if (strokes.length > 0) { 297 | const lastCommittedRawLineIndex = simplifiedRawLines.length - (lastMouseState.down 298 | ? 2 299 | : 1); 300 | if (lastCommittedRawLineIndex >= 0) { 301 | const lastCommittedPoint = simplifiedRawLines[lastCommittedRawLineIndex][simplifiedRawLines[lastCommittedRawLineIndex].length - 1]; 302 | previousStrokeFinalX = lastCommittedPoint[0]; 303 | previousStrokeFinalY = lastCommittedPoint[1]; 304 | } 305 | } 306 | 307 | // Convert that smoothed stroke to the format the model expects and update our 308 | // internal state. 309 | const stroke = DataTool.line_to_stroke(simplifiedPendingRawLine, [ 310 | previousStrokeFinalX, previousStrokeFinalY 311 | ], isFinished); 312 | if (pendingStrokeIndex !== null) { 313 | strokes = strokes 314 | .slice(0, pendingStrokeIndex) 315 | .concat(stroke); 316 | } else { 317 | pendingStrokeIndex = strokes.length; 318 | strokes = strokes.concat(stroke); 319 | } 320 | 321 | // Update our RNN state with the new strokes. 322 | if (strokes.length > minimumSequenceLength) { 323 | disposeModelState(currentModelState); 324 | if (lastCommittedModelState) { 325 | currentModelState = model.copy_state(lastCommittedModelState); 326 | } else { 327 | currentModelState = model.zero_state(); 328 | } 329 | 330 | if (pendingStrokeIndex === 0) { 331 | currentModelState = model.update(model.zero_input(), currentModelState); 332 | } 333 | // Encode each sample in the latest stroke. 334 | for (let i = pendingStrokeIndex; i < strokes.length - 1; i++) { 335 | currentModelState = model.update(scaleScreenSpaceStrokeSample(strokes[i]), currentModelState); 336 | } 337 | 338 | // If the pen was just lifted, copy the pending model state onto the base state. 339 | if (strokes[strokes.length - 1][2] === 1) { 340 | disposeModelState(lastCommittedModelState); 341 | lastCommittedModelState = model.copy_state(currentModelState); 342 | } 343 | } 344 | } 345 | 346 | const hallucinate = function (modelState, strokes) { 347 | if (isWaitingForHallucination) { 348 | return; 349 | } 350 | isWaitingForHallucination = true; 351 | 352 | const lastStroke = strokes[strokes.length - 1]; 353 | let lastSample = scaleScreenSpaceStrokeSample(lastStroke); 354 | let concatenatedSamples = null; 355 | let sampleCount = 0; 356 | 357 | let hallucinatedState = model.copy_state(modelState); 358 | while (sampleCount < hallucinatedSampleCount) { 359 | math.scope((keep, track) => { 360 | const oldModelState = hallucinatedState; 361 | hallucinatedState = model.update(lastSample, hallucinatedState); 362 | disposeModelState(oldModelState); 363 | hallucinatedState.forEach(keep); 364 | 365 | const modelPDF = model.get_pdf(hallucinatedState); 366 | const output = model.sample(modelPDF, temperature); 367 | 368 | if (concatenatedSamples) { 369 | const oldConcatenatedSample = concatenatedSamples; 370 | concatenatedSamples = keep(math.concat2D(concatenatedSamples, output.as2D(1, output.size), 0)); 371 | oldConcatenatedSample.dispose(); 372 | } else { 373 | concatenatedSamples = keep(output.as2D(1, output.size)); 374 | } 375 | if (lastSample instanceof dl.Array1D) { 376 | lastSample.dispose(); 377 | } 378 | lastSample = keep(output); 379 | sampleCount += 1; 380 | }) 381 | } 382 | disposeModelState(hallucinatedState); 383 | lastSample.dispose(); 384 | 385 | const startTime = Date.now(); 386 | concatenatedSamples 387 | .data() 388 | .then((data) => { 389 | concatenatedSamples.dispose(); 390 | 391 | // Process performance logs. 392 | const dt = Date.now() - startTime; 393 | timeSamples.push(dt); 394 | if (timeSamples.length > 100) { 395 | timeSamples.sort((a, b) => a - b); 396 | console.log(`Median: ${timeSamples[Math.ceil(timeSamples.length / 2)]}; Min: ${timeSamples[0]}; Max: ${timeSamples[timeSamples.length - 1]}`); 397 | timeSamples = []; 398 | } 399 | isWaitingForHallucination = false; 400 | 401 | if (!currentModelState) { 402 | // If we've reset since the GPU request was made, bail. This is only a weak 403 | // heuristic, rather than a rigorous queue/sequencing, but it's fine for this 404 | // demo. 405 | return; 406 | } 407 | 408 | // Find the point where the hallucination should start. 409 | const lastRawLineIndex = simplifiedRawLines.length - 1; 410 | const lastPoint = simplifiedRawLines[lastRawLineIndex][simplifiedRawLines[lastRawLineIndex].length - 1]; 411 | let hallucinationX = lastPoint[0]; 412 | let hallucinationY = lastPoint[1]; 413 | 414 | // Fade out the previous hallucinations according to how far the user's moved. 415 | if (lastHallucinationStartPoint) { 416 | const dx = lastMouseState.x - lastHallucinationStartPoint[0]; 417 | const dy = lastMouseState.y - lastHallucinationStartPoint[1]; 418 | const drawingLengthSinceLastHallucination = lastMouseState.down 419 | ? Math.sqrt(dx * dx + dy * dy) 420 | : 10; 421 | hallucinationGraphics.background(0, 0, 100, p.lerp(10, 90, drawingLengthSinceLastHallucination / 30)); 422 | } 423 | lastHallucinationStartPoint = [hallucinationX, hallucinationY]; 424 | 425 | const effectiveScaleFactor = currentScaleFactor(); 426 | hallucinationGraphics.stroke(hallucinationLineColor); 427 | for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) { 428 | const baseIndex = sampleIndex * 5; 429 | const hallucinationDX = data[baseIndex + 0] * effectiveScaleFactor; 430 | const hallucinationDY = data[baseIndex + 1] * effectiveScaleFactor; 431 | 432 | if (data[baseIndex + 4]) { // Corresponds to the logit for "end of drawing" 433 | break; 434 | } 435 | 436 | // We'll fade the line out over the last few samples. 437 | const alpha = p.lerp(100, 0, (sampleIndex - (hallucinatedSampleCount - 15)) / 15) 438 | const currentColor = p.color(hallucinationLineColor); 439 | currentColor.setAlpha(alpha); 440 | hallucinationGraphics.strokeWeight(lineWidth); 441 | hallucinationGraphics.stroke(currentColor); 442 | 443 | const isContinuingStroke = strokes[strokes.length - 1][2] === 0; // Look at the last pen index. 444 | if (sampleIndex > 0 && data[baseIndex - 5 + 2] || (sampleIndex === 0 && isContinuingStroke)) { 445 | hallucinationGraphics.line(hallucinationX, hallucinationY, hallucinationX + hallucinationDX, hallucinationY + hallucinationDY); 446 | if (data[baseIndex + 3]) { 447 | hallucinationGraphics.fill(0, 0, 255, 255); 448 | hallucinationGraphics.strokeWeight(1); 449 | hallucinationGraphics.ellipse(hallucinationX + hallucinationDX, hallucinationY + hallucinationDY, 5, 5); 450 | } 451 | } else if ((sampleIndex > 0 && data[baseIndex - 5 + 3] && data[baseIndex + 2]) || (sampleIndex === 0 && !isContinuingStroke)) { 452 | hallucinationGraphics.fill(currentColor); 453 | hallucinationGraphics.ellipse(hallucinationX + hallucinationDX, hallucinationY + hallucinationDY, 3, 3); 454 | } 455 | 456 | hallucinationX += hallucinationDX; 457 | hallucinationY += hallucinationDY; 458 | } 459 | }); 460 | } 461 | 462 | p.draw = function () { 463 | const mouseState = { 464 | x: p.mouseX, 465 | y: p.mouseY, 466 | down: p.mouseIsPressed 467 | }; 468 | 469 | // record pen drawing from user: 470 | if (mouseState.down && (mouseState.x > 0) && mouseState.y < (screenHeight - 90)) { // pen is touching the paper 471 | if (lastMouseState === null) { // first time anything is written 472 | startingMouseState = mouseState; 473 | lastMouseState = mouseState; 474 | document.getElementById("hint").style.opacity = 0; 475 | } 476 | 477 | // Have we moved far enough to bother drawing anything? 478 | const dx = mouseState.x - lastMouseState.x; 479 | const dy = mouseState.y - lastMouseState.y; 480 | if (dx * dx + dy * dy > epsilon * epsilon) { 481 | if (lastMouseState.down) { 482 | drawingGraphics.stroke(lineColor); 483 | drawingGraphics.strokeWeight(lineWidth); 484 | drawingGraphics.line(lastMouseState.x, lastMouseState.y, lastMouseState.x + dx, lastMouseState.y + dy); // draw line connecting prev point to current point. 485 | } 486 | 487 | pendingRawLine.push([mouseState.x, mouseState.y]); 488 | updateModelStateUsingCurrentStrokes(false); 489 | lastMouseState = mouseState; 490 | } 491 | } else if (lastMouseState !== null) { // pen is above the paper 492 | updateModelStateUsingCurrentStrokes(true); 493 | pendingRawLine = []; 494 | pendingStrokeIndex = null; 495 | lastMouseState = mouseState; 496 | } 497 | 498 | if (currentModelState) { 499 | hallucinate(currentModelState, strokes); 500 | } 501 | 502 | const canvasScaleFactor = p.pixelDensity(); 503 | p.image(hallucinationGraphics, 0, 0, screenWidth, screenHeight, 0, 0, screenWidth * canvasScaleFactor, screenHeight * canvasScaleFactor); 504 | p.image(drawingGraphics, 0, 0, screenWidth, screenHeight, 0, 0, screenWidth * canvasScaleFactor, screenHeight * canvasScaleFactor); 505 | }; 506 | 507 | const onModelSelection = function (event) { 508 | const c = event.target.value; 509 | const modelMode = "gen"; 510 | console.log("user wants to change to model " + c); 511 | setIsLoading(true); 512 | const callback = function (newModel) { 513 | setIsLoading(false); 514 | model = newModel; 515 | restart(); 516 | clearScreen(); 517 | } 518 | ModelImporter.change_model(model, c, modelMode, callback); 519 | }; 520 | 521 | const onClear = function () { 522 | restart(); 523 | clearScreen(); 524 | }; 525 | }; 526 | const p5Instance = new p5(sketch, 'sketch'); 527 | -------------------------------------------------------------------------------- /lib/sketch_rnn.js: -------------------------------------------------------------------------------- 1 | // Based on an original demo at https://github.com/tensorflow/magenta-demos/tree/master/sketch-rnn-js. 2 | // See LICENSE for full attribution and license details. 3 | 4 | dl = deeplearn; 5 | math = dl.ENV.math; 6 | 7 | /** 8 | * Location of JSON models used for sketch-rnn-js 9 | */ 10 | var SketchRNNConfig = { 11 | BaseURL: "https://storage.googleapis.com/scrying-pen-models/" 12 | }; 13 | 14 | /** 15 | * Tool to load simplify lines of a sketch using RDP Algorithm 16 | */ 17 | var DataTool = {}; 18 | 19 | (function (global) { 20 | "use strict"; 21 | 22 | function simplify_line(V, tolerance) { 23 | // from https://gist.github.com/adammiller/826148 24 | // V ... [[x1,y1],[x2,y2],...] polyline 25 | // tol ... approximation tolerance 26 | // ============================================== 27 | // Copyright 2002, softSurfer (www.softsurfer.com) 28 | // This code may be freely used and modified for any purpose 29 | // providing that this copyright notice is included with it. 30 | // SoftSurfer makes no warranty for this code, and cannot be held 31 | // liable for any real or imagined damage resulting from its use. 32 | // Users of this code must verify correctness for their application. 33 | // http://softsurfer.com/Archive/algorithm_0205/algorithm_0205.htm 34 | 35 | var tol=2.0; 36 | if (typeof(tolerance) === "number") { 37 | tol = tolerance; 38 | } 39 | 40 | var sum = function(u,v) {return [u[0]+v[0], u[1]+v[1]];} 41 | var diff = function(u,v) {return [u[0]-v[0], u[1]-v[1]];} 42 | var prod = function(u,v) {return [u[0]*v[0], u[1]*v[1]];} 43 | var dot = function(u,v) {return u[0]*v[0] + u[1]*v[1];} 44 | var norm2 = function(v) {return v[0]*v[0] + v[1]*v[1];} 45 | var norm = function(v) {return Math.sqrt(norm2(v));} 46 | var d2 = function(u,v) {return norm2(diff(u,v));} 47 | var d = function(u,v) {return norm(diff(u,v));} 48 | 49 | var simplifyDP = function( tol, v, j, k, mk ) { 50 | // This is the Douglas-Peucker recursive simplification routine 51 | // It just marks vertices that are part of the simplified polyline 52 | // for approximating the polyline subchain v[j] to v[k]. 53 | // mk[] ... array of markers matching vertex array v[] 54 | if (k <= j+1) { // there is nothing to simplify 55 | return; 56 | } 57 | // check for adequate approximation by segment S from v[j] to v[k] 58 | var maxi = j; // index of vertex farthest from S 59 | var maxd2 = 0; // distance squared of farthest vertex 60 | var tol2 = tol * tol; // tolerance squared 61 | var S = [v[j], v[k]]; // segment from v[j] to v[k] 62 | var u = diff(S[1], S[0]); // segment direction vector 63 | var cu = norm2(u,u); // segment length squared 64 | // test each vertex v[i] for max distance from S 65 | // compute using the Feb 2001 Algorithm's dist_Point_to_Segment() 66 | // Note: this works in any dimension (2D, 3D, ...) 67 | var w; // vector 68 | var Pb; // point, base of perpendicular from v[i] to S 69 | var b, cw, dv2; // dv2 = distance v[i] to S squared 70 | for (var i=j+1; i tol2) { // error is worse than the tolerance 92 | // split the polyline at the farthest vertex from S 93 | mk[maxi] = 1; // mark v[maxi] for the simplified polyline 94 | // recursively simplify the two subpolylines at v[maxi] 95 | simplifyDP( tol, v, j, maxi, mk ); // polyline v[j] to v[maxi] 96 | simplifyDP( tol, v, maxi, k, mk ); // polyline v[maxi] to v[k] 97 | } 98 | // else the approximation is OK, so ignore intermediate vertices 99 | return; 100 | } 101 | 102 | var n = V.length; 103 | var sV = []; 104 | var i, k, m, pv; // misc counters 105 | var tol2 = tol * tol; // tolerance squared 106 | var vt = []; // vertex buffer, points 107 | var mk = []; // marker buffer, ints 108 | 109 | // STAGE 1. Vertex Reduction within tolerance of prior vertex cluster 110 | vt[0] = V[0]; // start at the beginning 111 | for (i=k=1, pv=0; i 1) { 163 | for (j=0;j { 337 | 338 | var concat = math.concat1D(x, h); 339 | var hidden = math.add(math.vectorTimesMatrix(concat, this.Wfull), this.bias); 340 | var num_units = this.num_units; 341 | var forget_bias = this.forget_bias; 342 | 343 | var i=math.sigmoid(math.slice1D(hidden, 0*num_units, num_units)); 344 | var g=math.tanh(math.slice1D(hidden, 1*num_units, num_units)); 345 | var f=math.sigmoid(math.add(math.slice1D(hidden, 2*num_units, num_units), track(dl.Scalar.new(forget_bias)))); 346 | var o=math.sigmoid(math.slice1D(hidden, 3*num_units, num_units)); 347 | 348 | var new_c = math.add(math.multiply(c, f), math.multiply(g, i)); 349 | var new_h = math.multiply(math.tanh(new_c), o); 350 | 351 | return [new_h, new_c]; 352 | }); 353 | }; 354 | LSTMCell.prototype.encode = function(sequence) { 355 | var x; 356 | var state = this.zero_state(); 357 | var h = state[0]; 358 | var c = state[1]; 359 | var N = sequence.length; 360 | for (var i=0;i 1) return gaussRandom(); 430 | var c = Math.sqrt(-2*Math.log(r)/r); 431 | v_val = v*c; // cache this 432 | return_v = true; 433 | return u*c; 434 | } 435 | function randf(a, b) { return Math.random()*(b-a)+a; }; 436 | // from http://www.math.grin.edu/~mooret/courses/math336/bivariate-normal.html 437 | function birandn(weights, z1, z2, mu1, mu2, std1, std2, rho) { 438 | var ones = dl.NDArray.zerosLike(rho); 439 | ones.fill(1); 440 | 441 | var x = math.add( 442 | math.add( 443 | math.elementWiseMul( 444 | math.sqrt(math.sub(ones, math.elementWiseMul(rho, rho))), 445 | math.elementWiseMul(std1, z1) 446 | ), 447 | math.elementWiseMul(rho, math.elementWiseMul(std1, z2)) 448 | ), 449 | mu1 450 | ); 451 | var y = math.add( 452 | math.elementWiseMul(std2, z2), 453 | mu2 454 | ); 455 | return [math.dotProduct(weights, x), math.dotProduct(weights, y)]; 456 | }; 457 | 458 | /** 459 | * loads a JSON-parsed model in its specified format. 460 | */ 461 | function load_model(model_raw_data) { 462 | "use strict"; 463 | var i, j; 464 | 465 | info = model_raw_data[0]; 466 | dimensions = model_raw_data[1]; 467 | num_blobs = dimensions.length; 468 | var weightsIn = model_raw_data[2]; 469 | weights = Array(weightsIn.length) 470 | max_weight = 10.0; 471 | N_mixture=20; 472 | 473 | max_seq_len = info.max_seq_len; 474 | 475 | for (i=0;i { 562 | // y is an optional vector parameter, used for conditional mode only. 563 | var x_ = null; 564 | if (x instanceof dl.Array1D) { 565 | x_ = x; 566 | } else { 567 | // HACK: beware, the fact that we divide by scale_factor here but not in the other branch is pretty tricksy 568 | x_ = track(dl.Array1D.new([x[0], x[1], x[2] === 0 ? 1 : 0, x[2] === 1 ? 1 : 0, x[2] === 2 ? 1 : 0])); 569 | } 570 | 571 | var lstm_input, rnn_state; 572 | 573 | if (y) { 574 | var z = nj.array(y); 575 | lstm_input = nj.concatenate([x_, z]); 576 | } else { 577 | lstm_input = x_; 578 | } 579 | 580 | rnn_state = dec_lstm.forward(lstm_input, s[0], s[1]); 581 | 582 | return rnn_state; 583 | }); 584 | }; 585 | 586 | /** 587 | * Gets the parameters of the mixture density distribution for the next point 588 | */ 589 | function get_pdf(s) { 590 | var h = s[0]; 591 | var NOUT = N_mixture; 592 | var z=math.add(math.vectorTimesMatrix(h, dec_output_w), dec_output_b); 593 | var z_pen_logits = math.slice1D(z, 0, 3); 594 | var z_pi = math.slice1D(z, 3+NOUT*0, NOUT); 595 | var z_mu1 = math.slice1D(z, 3+NOUT*1, NOUT); 596 | var z_mu2 = math.slice1D(z, 3+NOUT*2, NOUT); 597 | var z_sigma1 = math.exp(math.slice1D(z, 3+NOUT*3, NOUT)); 598 | var z_sigma2 = math.exp(math.slice1D(z, 3+NOUT*4, NOUT)); 599 | var z_corr = math.tanh(math.slice1D(z, 3+NOUT*5, NOUT)); 600 | z_pen_logits = math.sub(z_pen_logits, math.max(z_pen_logits)); 601 | var z_pen = math.softmax(z_pen_logits); 602 | z_pi = math.sub(z_pi, math.max(z_pi)); 603 | z_pi = math.softmax(z_pi); 604 | 605 | return [z_pi, z_mu1, z_mu2, z_sigma1, z_sigma2, z_corr, z_pen]; 606 | }; 607 | 608 | /** 609 | * sample from a categorial distribution 610 | */ 611 | function sample_softmax(z_sample) { 612 | return math.multinomial(z_sample, 1).asScalar(); 613 | }; 614 | 615 | /** 616 | * adjust the temperature of a categorial distribution 617 | */ 618 | function adjust_temp(z_old, temp) { 619 | return math.scope((keep, track) => { 620 | var z = math.clone(z_old); 621 | var i; 622 | var x; 623 | z = math.divide(math.log(z), track(dl.Scalar.new(temp))) 624 | x = math.max(z); 625 | z = math.sub(z, x); 626 | z = math.exp(z); 627 | x = math.sum(z); 628 | z = math.divide(z, x); 629 | return z; 630 | }); 631 | }; 632 | 633 | /** 634 | * samples the next point of the sketch given pdf parameters and optional temperature params 635 | */ 636 | function sample(z, temperature, softmax_temperature) { 637 | return math.scope((keep, track) => { 638 | // z is [z_pi, z_mu1, z_mu2, z_sigma1, z_sigma2, z_corr, z_pen] 639 | // returns [x, y, eos] 640 | // optional softmax_temperature 641 | var temp=0.65; 642 | if (typeof(temperature) === "number") { 643 | temp = temperature; 644 | } 645 | var softmax_temp = 0.5+temp*0.5; 646 | if (typeof(softmax_temperature) === "number") { 647 | softmax_temp = softmax_temperature; 648 | } 649 | var z_0 = adjust_temp(z[0], softmax_temp); 650 | var z_6 = adjust_temp(z[6], softmax_temp); 651 | 652 | var pen_idx = sample_softmax(z_6); 653 | 654 | var tempScalar = track(dl.Scalar.new(Math.sqrt(temp))); 655 | var delta = birandn( 656 | z[0], 657 | dl.Array1D.randNormal(z[0].shape), 658 | dl.Array1D.randNormal(z[0].shape), 659 | z[1], 660 | z[2], 661 | math.multiply(tempScalar, z[3]), 662 | math.multiply(tempScalar, z[4]), 663 | z[5] 664 | ); 665 | 666 | return math.concat1D( 667 | math.concat1D(delta[0].as1D(), delta[1].as1D()), 668 | math.oneHot(pen_idx.as1D(), 3).as1D() 669 | ) 670 | }) 671 | } 672 | 673 | load_model(model_raw_data); 674 | 675 | function get_info() { 676 | return this.info; 677 | }; 678 | 679 | this.zero_state = zero_state; 680 | this.zero_input = zero_input; 681 | this.copy_state = copy_state; 682 | this.update = update; 683 | this.get_pdf = get_pdf; 684 | this.sample = sample; 685 | this.info = info; 686 | this.name = info.name; 687 | this.mode = info.mode; 688 | this.get_info = get_info; 689 | } 690 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This project the magenta-demos project on which it's based[1], and 2 | deeplearn.js are licensed under the Apache 2.0 license, reproduced here: 3 | 4 | Apache License 5 | Version 2.0, January 2004 6 | http://www.apache.org/licenses/ 7 | 8 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 9 | 10 | 1. Definitions. 11 | 12 | "License" shall mean the terms and conditions for use, reproduction, 13 | and distribution as defined by Sections 1 through 9 of this document. 14 | 15 | "Licensor" shall mean the copyright owner or entity authorized by 16 | the copyright owner that is granting the License. 17 | 18 | "Legal Entity" shall mean the union of the acting entity and all 19 | other entities that control, are controlled by, or are under common 20 | control with that entity. For the purposes of this definition, 21 | "control" means (i) the power, direct or indirect, to cause the 22 | direction or management of such entity, whether by contract or 23 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 24 | outstanding shares, or (iii) beneficial ownership of such entity. 25 | 26 | "You" (or "Your") shall mean an individual or Legal Entity 27 | exercising permissions granted by this License. 28 | 29 | "Source" form shall mean the preferred form for making modifications, 30 | including but not limited to software source code, documentation 31 | source, and configuration files. 32 | 33 | "Object" form shall mean any form resulting from mechanical 34 | transformation or translation of a Source form, including but 35 | not limited to compiled object code, generated documentation, 36 | and conversions to other media types. 37 | 38 | "Work" shall mean the work of authorship, whether in Source or 39 | Object form, made available under the License, as indicated by a 40 | copyright notice that is included in or attached to the work 41 | (an example is provided in the Appendix below). 42 | 43 | "Derivative Works" shall mean any work, whether in Source or Object 44 | form, that is based on (or derived from) the Work and for which the 45 | editorial revisions, annotations, elaborations, or other modifications 46 | represent, as a whole, an original work of authorship. For the purposes 47 | of this License, Derivative Works shall not include works that remain 48 | separable from, or merely link (or bind by name) to the interfaces of, 49 | the Work and Derivative Works thereof. 50 | 51 | "Contribution" shall mean any work of authorship, including 52 | the original version of the Work and any modifications or additions 53 | to that Work or Derivative Works thereof, that is intentionally 54 | submitted to Licensor for inclusion in the Work by the copyright owner 55 | or by an individual or Legal Entity authorized to submit on behalf of 56 | the copyright owner. For the purposes of this definition, "submitted" 57 | means any form of electronic, verbal, or written communication sent 58 | to the Licensor or its representatives, including but not limited to 59 | communication on electronic mailing lists, source code control systems, 60 | and issue tracking systems that are managed by, or on behalf of, the 61 | Licensor for the purpose of discussing and improving the Work, but 62 | excluding communication that is conspicuously marked or otherwise 63 | designated in writing by the copyright owner as "Not a Contribution." 64 | 65 | "Contributor" shall mean Licensor and any individual or Legal Entity 66 | on behalf of whom a Contribution has been received by Licensor and 67 | subsequently incorporated within the Work. 68 | 69 | 2. Grant of Copyright License. Subject to the terms and conditions of 70 | this License, each Contributor hereby grants to You a perpetual, 71 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 72 | copyright license to reproduce, prepare Derivative Works of, 73 | publicly display, publicly perform, sublicense, and distribute the 74 | Work and such Derivative Works in Source or Object form. 75 | 76 | 3. Grant of Patent License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | (except as stated in this section) patent license to make, have made, 80 | use, offer to sell, sell, import, and otherwise transfer the Work, 81 | where such license applies only to those patent claims licensable 82 | by such Contributor that are necessarily infringed by their 83 | Contribution(s) alone or by combination of their Contribution(s) 84 | with the Work to which such Contribution(s) was submitted. If You 85 | institute patent litigation against any entity (including a 86 | cross-claim or counterclaim in a lawsuit) alleging that the Work 87 | or a Contribution incorporated within the Work constitutes direct 88 | or contributory patent infringement, then any patent licenses 89 | granted to You under this License for that Work shall terminate 90 | as of the date such litigation is filed. 91 | 92 | 4. Redistribution. You may reproduce and distribute copies of the 93 | Work or Derivative Works thereof in any medium, with or without 94 | modifications, and in Source or Object form, provided that You 95 | meet the following conditions: 96 | 97 | (a) You must give any other recipients of the Work or 98 | Derivative Works a copy of this License; and 99 | 100 | (b) You must cause any modified files to carry prominent notices 101 | stating that You changed the files; and 102 | 103 | (c) You must retain, in the Source form of any Derivative Works 104 | that You distribute, all copyright, patent, trademark, and 105 | attribution notices from the Source form of the Work, 106 | excluding those notices that do not pertain to any part of 107 | the Derivative Works; and 108 | 109 | (d) If the Work includes a "NOTICE" text file as part of its 110 | distribution, then any Derivative Works that You distribute must 111 | include a readable copy of the attribution notices contained 112 | within such NOTICE file, excluding those notices that do not 113 | pertain to any part of the Derivative Works, in at least one 114 | of the following places: within a NOTICE text file distributed 115 | as part of the Derivative Works; within the Source form or 116 | documentation, if provided along with the Derivative Works; or, 117 | within a display generated by the Derivative Works, if and 118 | wherever such third-party notices normally appear. The contents 119 | of the NOTICE file are for informational purposes only and 120 | do not modify the License. You may add Your own attribution 121 | notices within Derivative Works that You distribute, alongside 122 | or as an addendum to the NOTICE text from the Work, provided 123 | that such additional attribution notices cannot be construed 124 | as modifying the License. 125 | 126 | You may add Your own copyright statement to Your modifications and 127 | may provide additional or different license terms and conditions 128 | for use, reproduction, or distribution of Your modifications, or 129 | for any such Derivative Works as a whole, provided Your use, 130 | reproduction, and distribution of the Work otherwise complies with 131 | the conditions stated in this License. 132 | 133 | 5. Submission of Contributions. Unless You explicitly state otherwise, 134 | any Contribution intentionally submitted for inclusion in the Work 135 | by You to the Licensor shall be under the terms and conditions of 136 | this License, without any additional terms or conditions. 137 | Notwithstanding the above, nothing herein shall supersede or modify 138 | the terms of any separate license agreement you may have executed 139 | with Licensor regarding such Contributions. 140 | 141 | 6. Trademarks. This License does not grant permission to use the trade 142 | names, trademarks, service marks, or product names of the Licensor, 143 | except as required for reasonable and customary use in describing the 144 | origin of the Work and reproducing the content of the NOTICE file. 145 | 146 | 7. Disclaimer of Warranty. Unless required by applicable law or 147 | agreed to in writing, Licensor provides the Work (and each 148 | Contributor provides its Contributions) on an "AS IS" BASIS, 149 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 150 | implied, including, without limitation, any warranties or conditions 151 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 152 | PARTICULAR PURPOSE. You are solely responsible for determining the 153 | appropriateness of using or redistributing the Work and assume any 154 | risks associated with Your exercise of permissions under this License. 155 | 156 | 8. Limitation of Liability. In no event and under no legal theory, 157 | whether in tort (including negligence), contract, or otherwise, 158 | unless required by applicable law (such as deliberate and grossly 159 | negligent acts) or agreed to in writing, shall any Contributor be 160 | liable to You for damages, including any direct, indirect, special, 161 | incidental, or consequential damages of any character arising as a 162 | result of this License or out of the use or inability to use the 163 | Work (including but not limited to damages for loss of goodwill, 164 | work stoppage, computer failure or malfunction, or any and all 165 | other commercial damages or losses), even if such Contributor 166 | has been advised of the possibility of such damages. 167 | 168 | 9. Accepting Warranty or Additional Liability. While redistributing 169 | the Work or Derivative Works thereof, You may choose to offer, 170 | and charge a fee for, acceptance of support, warranty, indemnity, 171 | or other liability obligations and/or rights consistent with this 172 | License. However, in accepting such obligations, You may act only 173 | on Your own behalf and on Your sole responsibility, not on behalf 174 | of any other Contributor, and only if You agree to indemnify, 175 | defend, and hold each Contributor harmless for any liability 176 | incurred by, or claims asserted against, such Contributor by reason 177 | of your accepting any such warranty or additional liability. 178 | 179 | END OF TERMS AND CONDITIONS 180 | 181 | APPENDIX: How to apply the Apache License to your work. 182 | 183 | To apply the Apache License to your work, attach the following 184 | boilerplate notice, with the fields enclosed by brackets "{}" 185 | replaced with your own identifying information. (Don't include 186 | the brackets!) The text should be enclosed in the appropriate 187 | comment syntax for the file format. We also recommend that a 188 | file or class name and description of purpose be included on the 189 | same "printed page" as the copyright notice for easier 190 | identification within third-party archives. 191 | 192 | Copyright 2018 Andy Matuschak 193 | 194 | Licensed under the Apache License, Version 2.0 (the "License"); 195 | you may not use this file except in compliance with the License. 196 | You may obtain a copy of the License at 197 | 198 | http://www.apache.org/licenses/LICENSE-2.0 199 | 200 | Unless required by applicable law or agreed to in writing, software 201 | distributed under the License is distributed on an "AS IS" BASIS, 202 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 203 | See the License for the specific language governing permissions and 204 | limitations under the License. 205 | 206 | p5.js is licensed under LGPL, as followed: 207 | 208 | GNU LESSER GENERAL PUBLIC LICENSE 209 | Version 2.1, February 1999 210 | 211 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 212 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 213 | Everyone is permitted to copy and distribute verbatim copies 214 | of this license document, but changing it is not allowed. 215 | 216 | (This is the first released version of the Lesser GPL. It also counts 217 | as the successor of the GNU Library Public License, version 2, hence 218 | the version number 2.1.) 219 | 220 | Preamble 221 | 222 | The licenses for most software are designed to take away your 223 | freedom to share and change it. By contrast, the GNU General Public 224 | Licenses are intended to guarantee your freedom to share and change 225 | free software--to make sure the software is free for all its users. 226 | 227 | This license, the Lesser General Public License, applies to some 228 | specially designated software packages--typically libraries--of the 229 | Free Software Foundation and other authors who decide to use it. You 230 | can use it too, but we suggest you first think carefully about whether 231 | this license or the ordinary General Public License is the better 232 | strategy to use in any particular case, based on the explanations below. 233 | 234 | When we speak of free software, we are referring to freedom of use, 235 | not price. Our General Public Licenses are designed to make sure that 236 | you have the freedom to distribute copies of free software (and charge 237 | for this service if you wish); that you receive source code or can get 238 | it if you want it; that you can change the software and use pieces of 239 | it in new free programs; and that you are informed that you can do 240 | these things. 241 | 242 | To protect your rights, we need to make restrictions that forbid 243 | distributors to deny you these rights or to ask you to surrender these 244 | rights. These restrictions translate to certain responsibilities for 245 | you if you distribute copies of the library or if you modify it. 246 | 247 | For example, if you distribute copies of the library, whether gratis 248 | or for a fee, you must give the recipients all the rights that we gave 249 | you. You must make sure that they, too, receive or can get the source 250 | code. If you link other code with the library, you must provide 251 | complete object files to the recipients, so that they can relink them 252 | with the library after making changes to the library and recompiling 253 | it. And you must show them these terms so they know their rights. 254 | 255 | We protect your rights with a two-step method: (1) we copyright the 256 | library, and (2) we offer you this license, which gives you legal 257 | permission to copy, distribute and/or modify the library. 258 | 259 | To protect each distributor, we want to make it very clear that 260 | there is no warranty for the free library. Also, if the library is 261 | modified by someone else and passed on, the recipients should know 262 | that what they have is not the original version, so that the original 263 | author's reputation will not be affected by problems that might be 264 | introduced by others. 265 | 266 | Finally, software patents pose a constant threat to the existence of 267 | any free program. We wish to make sure that a company cannot 268 | effectively restrict the users of a free program by obtaining a 269 | restrictive license from a patent holder. Therefore, we insist that 270 | any patent license obtained for a version of the library must be 271 | consistent with the full freedom of use specified in this license. 272 | 273 | Most GNU software, including some libraries, is covered by the 274 | ordinary GNU General Public License. This license, the GNU Lesser 275 | General Public License, applies to certain designated libraries, and 276 | is quite different from the ordinary General Public License. We use 277 | this license for certain libraries in order to permit linking those 278 | libraries into non-free programs. 279 | 280 | When a program is linked with a library, whether statically or using 281 | a shared library, the combination of the two is legally speaking a 282 | combined work, a derivative of the original library. The ordinary 283 | General Public License therefore permits such linking only if the 284 | entire combination fits its criteria of freedom. The Lesser General 285 | Public License permits more lax criteria for linking other code with 286 | the library. 287 | 288 | We call this license the "Lesser" General Public License because it 289 | does Less to protect the user's freedom than the ordinary General 290 | Public License. It also provides other free software developers Less 291 | of an advantage over competing non-free programs. These disadvantages 292 | are the reason we use the ordinary General Public License for many 293 | libraries. However, the Lesser license provides advantages in certain 294 | special circumstances. 295 | 296 | For example, on rare occasions, there may be a special need to 297 | encourage the widest possible use of a certain library, so that it becomes 298 | a de-facto standard. To achieve this, non-free programs must be 299 | allowed to use the library. A more frequent case is that a free 300 | library does the same job as widely used non-free libraries. In this 301 | case, there is little to gain by limiting the free library to free 302 | software only, so we use the Lesser General Public License. 303 | 304 | In other cases, permission to use a particular library in non-free 305 | programs enables a greater number of people to use a large body of 306 | free software. For example, permission to use the GNU C Library in 307 | non-free programs enables many more people to use the whole GNU 308 | operating system, as well as its variant, the GNU/Linux operating 309 | system. 310 | 311 | Although the Lesser General Public License is Less protective of the 312 | users' freedom, it does ensure that the user of a program that is 313 | linked with the Library has the freedom and the wherewithal to run 314 | that program using a modified version of the Library. 315 | 316 | The precise terms and conditions for copying, distribution and 317 | modification follow. Pay close attention to the difference between a 318 | "work based on the library" and a "work that uses the library". The 319 | former contains code derived from the library, whereas the latter must 320 | be combined with the library in order to run. 321 | 322 | GNU LESSER GENERAL PUBLIC LICENSE 323 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 324 | 325 | 0. This License Agreement applies to any software library or other 326 | program which contains a notice placed by the copyright holder or 327 | other authorized party saying it may be distributed under the terms of 328 | this Lesser General Public License (also called "this License"). 329 | Each licensee is addressed as "you". 330 | 331 | A "library" means a collection of software functions and/or data 332 | prepared so as to be conveniently linked with application programs 333 | (which use some of those functions and data) to form executables. 334 | 335 | The "Library", below, refers to any such software library or work 336 | which has been distributed under these terms. A "work based on the 337 | Library" means either the Library or any derivative work under 338 | copyright law: that is to say, a work containing the Library or a 339 | portion of it, either verbatim or with modifications and/or translated 340 | straightforwardly into another language. (Hereinafter, translation is 341 | included without limitation in the term "modification".) 342 | 343 | "Source code" for a work means the preferred form of the work for 344 | making modifications to it. For a library, complete source code means 345 | all the source code for all modules it contains, plus any associated 346 | interface definition files, plus the scripts used to control compilation 347 | and installation of the library. 348 | 349 | Activities other than copying, distribution and modification are not 350 | covered by this License; they are outside its scope. The act of 351 | running a program using the Library is not restricted, and output from 352 | such a program is covered only if its contents constitute a work based 353 | on the Library (independent of the use of the Library in a tool for 354 | writing it). Whether that is true depends on what the Library does 355 | and what the program that uses the Library does. 356 | 357 | 1. You may copy and distribute verbatim copies of the Library's 358 | complete source code as you receive it, in any medium, provided that 359 | you conspicuously and appropriately publish on each copy an 360 | appropriate copyright notice and disclaimer of warranty; keep intact 361 | all the notices that refer to this License and to the absence of any 362 | warranty; and distribute a copy of this License along with the 363 | Library. 364 | 365 | You may charge a fee for the physical act of transferring a copy, 366 | and you may at your option offer warranty protection in exchange for a 367 | fee. 368 | 369 | 2. You may modify your copy or copies of the Library or any portion 370 | of it, thus forming a work based on the Library, and copy and 371 | distribute such modifications or work under the terms of Section 1 372 | above, provided that you also meet all of these conditions: 373 | 374 | a) The modified work must itself be a software library. 375 | 376 | b) You must cause the files modified to carry prominent notices 377 | stating that you changed the files and the date of any change. 378 | 379 | c) You must cause the whole of the work to be licensed at no 380 | charge to all third parties under the terms of this License. 381 | 382 | d) If a facility in the modified Library refers to a function or a 383 | table of data to be supplied by an application program that uses 384 | the facility, other than as an argument passed when the facility 385 | is invoked, then you must make a good faith effort to ensure that, 386 | in the event an application does not supply such function or 387 | table, the facility still operates, and performs whatever part of 388 | its purpose remains meaningful. 389 | 390 | (For example, a function in a library to compute square roots has 391 | a purpose that is entirely well-defined independent of the 392 | application. Therefore, Subsection 2d requires that any 393 | application-supplied function or table used by this function must 394 | be optional: if the application does not supply it, the square 395 | root function must still compute square roots.) 396 | 397 | These requirements apply to the modified work as a whole. If 398 | identifiable sections of that work are not derived from the Library, 399 | and can be reasonably considered independent and separate works in 400 | themselves, then this License, and its terms, do not apply to those 401 | sections when you distribute them as separate works. But when you 402 | distribute the same sections as part of a whole which is a work based 403 | on the Library, the distribution of the whole must be on the terms of 404 | this License, whose permissions for other licensees extend to the 405 | entire whole, and thus to each and every part regardless of who wrote 406 | it. 407 | 408 | Thus, it is not the intent of this section to claim rights or contest 409 | your rights to work written entirely by you; rather, the intent is to 410 | exercise the right to control the distribution of derivative or 411 | collective works based on the Library. 412 | 413 | In addition, mere aggregation of another work not based on the Library 414 | with the Library (or with a work based on the Library) on a volume of 415 | a storage or distribution medium does not bring the other work under 416 | the scope of this License. 417 | 418 | 3. You may opt to apply the terms of the ordinary GNU General Public 419 | License instead of this License to a given copy of the Library. To do 420 | this, you must alter all the notices that refer to this License, so 421 | that they refer to the ordinary GNU General Public License, version 2, 422 | instead of to this License. (If a newer version than version 2 of the 423 | ordinary GNU General Public License has appeared, then you can specify 424 | that version instead if you wish.) Do not make any other change in 425 | these notices. 426 | 427 | Once this change is made in a given copy, it is irreversible for 428 | that copy, so the ordinary GNU General Public License applies to all 429 | subsequent copies and derivative works made from that copy. 430 | 431 | This option is useful when you wish to copy part of the code of 432 | the Library into a program that is not a library. 433 | 434 | 4. You may copy and distribute the Library (or a portion or 435 | derivative of it, under Section 2) in object code or executable form 436 | under the terms of Sections 1 and 2 above provided that you accompany 437 | it with the complete corresponding machine-readable source code, which 438 | must be distributed under the terms of Sections 1 and 2 above on a 439 | medium customarily used for software interchange. 440 | 441 | If distribution of object code is made by offering access to copy 442 | from a designated place, then offering equivalent access to copy the 443 | source code from the same place satisfies the requirement to 444 | distribute the source code, even though third parties are not 445 | compelled to copy the source along with the object code. 446 | 447 | 5. A program that contains no derivative of any portion of the 448 | Library, but is designed to work with the Library by being compiled or 449 | linked with it, is called a "work that uses the Library". Such a 450 | work, in isolation, is not a derivative work of the Library, and 451 | therefore falls outside the scope of this License. 452 | 453 | However, linking a "work that uses the Library" with the Library 454 | creates an executable that is a derivative of the Library (because it 455 | contains portions of the Library), rather than a "work that uses the 456 | library". The executable is therefore covered by this License. 457 | Section 6 states terms for distribution of such executables. 458 | 459 | When a "work that uses the Library" uses material from a header file 460 | that is part of the Library, the object code for the work may be a 461 | derivative work of the Library even though the source code is not. 462 | Whether this is true is especially significant if the work can be 463 | linked without the Library, or if the work is itself a library. The 464 | threshold for this to be true is not precisely defined by law. 465 | 466 | If such an object file uses only numerical parameters, data 467 | structure layouts and accessors, and small macros and small inline 468 | functions (ten lines or less in length), then the use of the object 469 | file is unrestricted, regardless of whether it is legally a derivative 470 | work. (Executables containing this object code plus portions of the 471 | Library will still fall under Section 6.) 472 | 473 | Otherwise, if the work is a derivative of the Library, you may 474 | distribute the object code for the work under the terms of Section 6. 475 | Any executables containing that work also fall under Section 6, 476 | whether or not they are linked directly with the Library itself. 477 | 478 | 6. As an exception to the Sections above, you may also combine or 479 | link a "work that uses the Library" with the Library to produce a 480 | work containing portions of the Library, and distribute that work 481 | under terms of your choice, provided that the terms permit 482 | modification of the work for the customer's own use and reverse 483 | engineering for debugging such modifications. 484 | 485 | You must give prominent notice with each copy of the work that the 486 | Library is used in it and that the Library and its use are covered by 487 | this License. You must supply a copy of this License. If the work 488 | during execution displays copyright notices, you must include the 489 | copyright notice for the Library among them, as well as a reference 490 | directing the user to the copy of this License. Also, you must do one 491 | of these things: 492 | 493 | a) Accompany the work with the complete corresponding 494 | machine-readable source code for the Library including whatever 495 | changes were used in the work (which must be distributed under 496 | Sections 1 and 2 above); and, if the work is an executable linked 497 | with the Library, with the complete machine-readable "work that 498 | uses the Library", as object code and/or source code, so that the 499 | user can modify the Library and then relink to produce a modified 500 | executable containing the modified Library. (It is understood 501 | that the user who changes the contents of definitions files in the 502 | Library will not necessarily be able to recompile the application 503 | to use the modified definitions.) 504 | 505 | b) Use a suitable shared library mechanism for linking with the 506 | Library. A suitable mechanism is one that (1) uses at run time a 507 | copy of the library already present on the user's computer system, 508 | rather than copying library functions into the executable, and (2) 509 | will operate properly with a modified version of the library, if 510 | the user installs one, as long as the modified version is 511 | interface-compatible with the version that the work was made with. 512 | 513 | c) Accompany the work with a written offer, valid for at 514 | least three years, to give the same user the materials 515 | specified in Subsection 6a, above, for a charge no more 516 | than the cost of performing this distribution. 517 | 518 | d) If distribution of the work is made by offering access to copy 519 | from a designated place, offer equivalent access to copy the above 520 | specified materials from the same place. 521 | 522 | e) Verify that the user has already received a copy of these 523 | materials or that you have already sent this user a copy. 524 | 525 | For an executable, the required form of the "work that uses the 526 | Library" must include any data and utility programs needed for 527 | reproducing the executable from it. However, as a special exception, 528 | the materials to be distributed need not include anything that is 529 | normally distributed (in either source or binary form) with the major 530 | components (compiler, kernel, and so on) of the operating system on 531 | which the executable runs, unless that component itself accompanies 532 | the executable. 533 | 534 | It may happen that this requirement contradicts the license 535 | restrictions of other proprietary libraries that do not normally 536 | accompany the operating system. Such a contradiction means you cannot 537 | use both them and the Library together in an executable that you 538 | distribute. 539 | 540 | 7. You may place library facilities that are a work based on the 541 | Library side-by-side in a single library together with other library 542 | facilities not covered by this License, and distribute such a combined 543 | library, provided that the separate distribution of the work based on 544 | the Library and of the other library facilities is otherwise 545 | permitted, and provided that you do these two things: 546 | 547 | a) Accompany the combined library with a copy of the same work 548 | based on the Library, uncombined with any other library 549 | facilities. This must be distributed under the terms of the 550 | Sections above. 551 | 552 | b) Give prominent notice with the combined library of the fact 553 | that part of it is a work based on the Library, and explaining 554 | where to find the accompanying uncombined form of the same work. 555 | 556 | 8. You may not copy, modify, sublicense, link with, or distribute 557 | the Library except as expressly provided under this License. Any 558 | attempt otherwise to copy, modify, sublicense, link with, or 559 | distribute the Library is void, and will automatically terminate your 560 | rights under this License. However, parties who have received copies, 561 | or rights, from you under this License will not have their licenses 562 | terminated so long as such parties remain in full compliance. 563 | 564 | 9. You are not required to accept this License, since you have not 565 | signed it. However, nothing else grants you permission to modify or 566 | distribute the Library or its derivative works. These actions are 567 | prohibited by law if you do not accept this License. Therefore, by 568 | modifying or distributing the Library (or any work based on the 569 | Library), you indicate your acceptance of this License to do so, and 570 | all its terms and conditions for copying, distributing or modifying 571 | the Library or works based on it. 572 | 573 | 10. Each time you redistribute the Library (or any work based on the 574 | Library), the recipient automatically receives a license from the 575 | original licensor to copy, distribute, link with or modify the Library 576 | subject to these terms and conditions. You may not impose any further 577 | restrictions on the recipients' exercise of the rights granted herein. 578 | You are not responsible for enforcing compliance by third parties with 579 | this License. 580 | 581 | 11. If, as a consequence of a court judgment or allegation of patent 582 | infringement or for any other reason (not limited to patent issues), 583 | conditions are imposed on you (whether by court order, agreement or 584 | otherwise) that contradict the conditions of this License, they do not 585 | excuse you from the conditions of this License. If you cannot 586 | distribute so as to satisfy simultaneously your obligations under this 587 | License and any other pertinent obligations, then as a consequence you 588 | may not distribute the Library at all. For example, if a patent 589 | license would not permit royalty-free redistribution of the Library by 590 | all those who receive copies directly or indirectly through you, then 591 | the only way you could satisfy both it and this License would be to 592 | refrain entirely from distribution of the Library. 593 | 594 | If any portion of this section is held invalid or unenforceable under any 595 | particular circumstance, the balance of the section is intended to apply, 596 | and the section as a whole is intended to apply in other circumstances. 597 | 598 | It is not the purpose of this section to induce you to infringe any 599 | patents or other property right claims or to contest validity of any 600 | such claims; this section has the sole purpose of protecting the 601 | integrity of the free software distribution system which is 602 | implemented by public license practices. Many people have made 603 | generous contributions to the wide range of software distributed 604 | through that system in reliance on consistent application of that 605 | system; it is up to the author/donor to decide if he or she is willing 606 | to distribute software through any other system and a licensee cannot 607 | impose that choice. 608 | 609 | This section is intended to make thoroughly clear what is believed to 610 | be a consequence of the rest of this License. 611 | 612 | 12. If the distribution and/or use of the Library is restricted in 613 | certain countries either by patents or by copyrighted interfaces, the 614 | original copyright holder who places the Library under this License may add 615 | an explicit geographical distribution limitation excluding those countries, 616 | so that distribution is permitted only in or among countries not thus 617 | excluded. In such case, this License incorporates the limitation as if 618 | written in the body of this License. 619 | 620 | 13. The Free Software Foundation may publish revised and/or new 621 | versions of the Lesser General Public License from time to time. 622 | Such new versions will be similar in spirit to the present version, 623 | but may differ in detail to address new problems or concerns. 624 | 625 | Each version is given a distinguishing version number. If the Library 626 | specifies a version number of this License which applies to it and 627 | "any later version", you have the option of following the terms and 628 | conditions either of that version or of any later version published by 629 | the Free Software Foundation. If the Library does not specify a 630 | license version number, you may choose any version ever published by 631 | the Free Software Foundation. 632 | 633 | 14. If you wish to incorporate parts of the Library into other free 634 | programs whose distribution conditions are incompatible with these, 635 | write to the author to ask for permission. For software which is 636 | copyrighted by the Free Software Foundation, write to the Free 637 | Software Foundation; we sometimes make exceptions for this. Our 638 | decision will be guided by the two goals of preserving the free status 639 | of all derivatives of our free software and of promoting the sharing 640 | and reuse of software generally. 641 | 642 | NO WARRANTY 643 | 644 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 645 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 646 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 647 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 648 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 649 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 650 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 651 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 652 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 653 | 654 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 655 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 656 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 657 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 658 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 659 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 660 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 661 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 662 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 663 | DAMAGES. 664 | 665 | END OF TERMS AND CONDITIONS 666 | 667 | 668 | The Quick, Draw! dataset is made available by Google, Inc. under the 669 | Creative Commons Attribution 4.0 International license. 670 | https://creativecommons.org/licenses/by/4.0/ 671 | 672 | 673 | The trash can icon was created by Adrien Coquet from the Noun Project. 674 | The info icon was created by Aini from the Noun Project. 675 | 676 | [1]: https://github.com/tensorflow/magenta-demos -------------------------------------------------------------------------------- /lib/p5.svg.js: -------------------------------------------------------------------------------- 1 | /*!! 2 | * p5.svg v0.6.0.0 3 | * SVG Runtime for p5.js. 4 | * 5 | * Copyright (C) 2015-2016 Zeno Zeng 6 | * Licensed under the LGPL license. 7 | */ 8 | (function (root, factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | define('p5.svg', ['p5'], function (p5) { 11 | factory(p5); 12 | }); 13 | } 14 | else if (typeof exports === 'object') { 15 | module.exports = factory; 16 | } 17 | else { 18 | factory(root['p5']); 19 | } 20 | })(this, function (p5) { 21 | 22 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) { 240 | options = defaultOptions; 241 | options.width = arguments[0]; 242 | options.height = arguments[1]; 243 | } else if( !o ) { 244 | options = defaultOptions; 245 | } else { 246 | options = o; 247 | } 248 | 249 | if(!(this instanceof ctx)) { 250 | //did someone call this without new? 251 | return new ctx(options); 252 | } 253 | 254 | //setup options 255 | this.width = options.width || defaultOptions.width; 256 | this.height = options.height || defaultOptions.height; 257 | this.enableMirroring = options.enableMirroring !== undefined ? options.enableMirroring : defaultOptions.enableMirroring; 258 | 259 | this.canvas = this; ///point back to this instance! 260 | this.__canvas = document.createElement("canvas"); 261 | this.__ctx = this.__canvas.getContext("2d"); 262 | 263 | this.__setDefaultStyles(); 264 | this.__stack = [this.__getStyleState()]; 265 | this.__groupStack = []; 266 | 267 | //the root svg element 268 | this.__root = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 269 | this.__root.setAttribute("version", 1.1); 270 | this.__root.setAttribute("xmlns", "http://www.w3.org/2000/svg"); 271 | this.__root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"); 272 | this.__root.setAttribute("width", this.width); 273 | this.__root.setAttribute("height", this.height); 274 | 275 | //make sure we don't generate the same ids in defs 276 | this.__ids = {}; 277 | 278 | //defs tag 279 | this.__defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); 280 | this.__root.appendChild(this.__defs); 281 | 282 | //also add a group child. the svg element can't use the transform attribute 283 | this.__currentElement = document.createElementNS("http://www.w3.org/2000/svg", "g"); 284 | this.__root.appendChild(this.__currentElement); 285 | }; 286 | 287 | /** 288 | * Creates the specified svg element 289 | * @private 290 | */ 291 | ctx.prototype.__createElement = function(elementName, properties, resetFill) { 292 | if (typeof properties === "undefined") { 293 | properties = {}; 294 | } 295 | 296 | var element = document.createElementNS("http://www.w3.org/2000/svg", elementName), 297 | keys = Object.keys(properties), i, key; 298 | if(resetFill) { 299 | //if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black. 300 | element.setAttribute("fill", "none"); 301 | element.setAttribute("stroke", "none"); 302 | } 303 | for(i=0; i 0) { 476 | var group = this.__createElement("g"); 477 | parent.appendChild(group); 478 | this.__currentElement = group; 479 | } 480 | 481 | var transform = this.__currentElement.getAttribute("transform"); 482 | if(transform) { 483 | transform += " "; 484 | } else { 485 | transform = ""; 486 | } 487 | transform += t; 488 | this.__currentElement.setAttribute("transform", transform); 489 | }; 490 | 491 | /** 492 | * scales the current element 493 | */ 494 | ctx.prototype.scale = function(x, y) { 495 | if(y === undefined) { 496 | y = x; 497 | } 498 | this.__addTransform(format("scale({x},{y})", {x:x, y:y})); 499 | }; 500 | 501 | /** 502 | * rotates the current element 503 | */ 504 | ctx.prototype.rotate = function(angle){ 505 | var degrees = (angle * 180 / Math.PI); 506 | this.__addTransform(format("rotate({angle},{cx},{cy})", {angle:degrees, cx:0, cy:0})); 507 | }; 508 | 509 | /** 510 | * translates the current element 511 | */ 512 | ctx.prototype.translate = function(x, y){ 513 | this.__addTransform(format("translate({x},{y})", {x:x,y:y})); 514 | }; 515 | 516 | /** 517 | * applies a transform to the current element 518 | */ 519 | ctx.prototype.transform = function(a, b, c, d, e, f){ 520 | this.__addTransform(format("matrix({a},{b},{c},{d},{e},{f})", {a:a, b:b, c:c, d:d, e:e, f:f})); 521 | }; 522 | 523 | /** 524 | * Create a new Path Element 525 | */ 526 | ctx.prototype.beginPath = function(){ 527 | var path, parent; 528 | 529 | // Note that there is only one current default path, it is not part of the drawing state. 530 | // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path 531 | this.__currentDefaultPath = ""; 532 | this.__currentPosition = {}; 533 | 534 | path = this.__createElement("path", {}, true); 535 | parent = this.__closestGroupOrSvg(); 536 | parent.appendChild(path); 537 | this.__currentElement = path; 538 | }; 539 | 540 | /** 541 | * Helper function to apply currentDefaultPath to current path element 542 | * @private 543 | */ 544 | ctx.prototype.__applyCurrentDefaultPath = function() { 545 | if(this.__currentElement.nodeName === "path") { 546 | var d = this.__currentDefaultPath; 547 | this.__currentElement.setAttribute("d", d); 548 | } else { 549 | throw new Error("Attempted to apply path command to node " + this.__currentElement.nodeName); 550 | } 551 | }; 552 | 553 | /** 554 | * Helper function to add path command 555 | * @private 556 | */ 557 | ctx.prototype.__addPathCommand = function(command){ 558 | this.__currentDefaultPath += " "; 559 | this.__currentDefaultPath += command; 560 | }; 561 | 562 | /** 563 | * Adds the move command to the current path element, 564 | * if the currentPathElement is not empty create a new path element 565 | */ 566 | ctx.prototype.moveTo = function(x,y){ 567 | if(this.__currentElement.nodeName !== "path") { 568 | this.beginPath(); 569 | } 570 | 571 | // creates a new subpath with the given point 572 | this.__currentPosition = {x: x, y: y}; 573 | this.__addPathCommand(format("M {x} {y}", {x:x, y:y})); 574 | }; 575 | 576 | /** 577 | * Closes the current path 578 | */ 579 | ctx.prototype.closePath = function(){ 580 | this.__addPathCommand("Z"); 581 | }; 582 | 583 | /** 584 | * Adds a line to command 585 | */ 586 | ctx.prototype.lineTo = function(x, y){ 587 | this.__currentPosition = {x: x, y: y}; 588 | if (this.__currentDefaultPath.indexOf('M') > -1) { 589 | this.__addPathCommand(format("L {x} {y}", {x:x, y:y})); 590 | } else { 591 | this.__addPathCommand(format("M {x} {y}", {x:x, y:y})); 592 | } 593 | }; 594 | 595 | /** 596 | * Add a bezier command 597 | */ 598 | ctx.prototype.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { 599 | this.__currentPosition = {x: x, y: y}; 600 | this.__addPathCommand(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", 601 | {cp1x:cp1x, cp1y:cp1y, cp2x:cp2x, cp2y:cp2y, x:x, y:y})); 602 | }; 603 | 604 | /** 605 | * Adds a quadratic curve to command 606 | */ 607 | ctx.prototype.quadraticCurveTo = function(cpx, cpy, x, y){ 608 | this.__currentPosition = {x: x, y: y}; 609 | this.__addPathCommand(format("Q {cpx} {cpy} {x} {y}", {cpx:cpx, cpy:cpy, x:x, y:y})); 610 | }; 611 | 612 | 613 | /** 614 | * Return a new normalized vector of given vector 615 | */ 616 | var normalize = function(vector) { 617 | var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); 618 | return [vector[0] / len, vector[1] / len]; 619 | }; 620 | 621 | /** 622 | * Adds the arcTo to the current path 623 | * 624 | * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto 625 | */ 626 | ctx.prototype.arcTo = function(x1, y1, x2, y2, radius) { 627 | // Let the point (x0, y0) be the last point in the subpath. 628 | var x0 = this.__currentPosition && this.__currentPosition.x; 629 | var y0 = this.__currentPosition && this.__currentPosition.y; 630 | 631 | // First ensure there is a subpath for (x1, y1). 632 | if (typeof x0 == "undefined" || typeof y0 == "undefined") { 633 | return; 634 | } 635 | 636 | // Negative values for radius must cause the implementation to throw an IndexSizeError exception. 637 | if (radius < 0) { 638 | throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative."); 639 | } 640 | 641 | // If the point (x0, y0) is equal to the point (x1, y1), 642 | // or if the point (x1, y1) is equal to the point (x2, y2), 643 | // or if the radius radius is zero, 644 | // then the method must add the point (x1, y1) to the subpath, 645 | // and connect that point to the previous point (x0, y0) by a straight line. 646 | if (((x0 === x1) && (y0 === y1)) 647 | || ((x1 === x2) && (y1 === y2)) 648 | || (radius === 0)) { 649 | this.lineTo(x1, y1); 650 | return; 651 | } 652 | 653 | // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, 654 | // then the method must add the point (x1, y1) to the subpath, 655 | // and connect that point to the previous point (x0, y0) by a straight line. 656 | var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); 657 | var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); 658 | if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) { 659 | this.lineTo(x1, y1); 660 | return; 661 | } 662 | 663 | // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius, 664 | // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1), 665 | // 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). 666 | // The points at which this circle touches these two lines are called the start and end tangent points respectively. 667 | 668 | // note that both vectors are unit vectors, so the length is 1 669 | var cos = (unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]); 670 | var theta = Math.acos(Math.abs(cos)); 671 | 672 | // Calculate origin 673 | var unit_vec_p1_origin = normalize([ 674 | unit_vec_p1_p0[0] + unit_vec_p1_p2[0], 675 | unit_vec_p1_p0[1] + unit_vec_p1_p2[1] 676 | ]); 677 | var len_p1_origin = radius / Math.sin(theta / 2); 678 | var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; 679 | var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; 680 | 681 | // Calculate start angle and end angle 682 | // rotate 90deg clockwise (note that y axis points to its down) 683 | var unit_vec_origin_start_tangent = [ 684 | -unit_vec_p1_p0[1], 685 | unit_vec_p1_p0[0] 686 | ]; 687 | // rotate 90deg counter clockwise (note that y axis points to its down) 688 | var unit_vec_origin_end_tangent = [ 689 | unit_vec_p1_p2[1], 690 | -unit_vec_p1_p2[0] 691 | ]; 692 | var getAngle = function(vector) { 693 | // get angle (clockwise) between vector and (1, 0) 694 | var x = vector[0]; 695 | var y = vector[1]; 696 | if (y >= 0) { // note that y axis points to its down 697 | return Math.acos(x); 698 | } else { 699 | return -Math.acos(x); 700 | } 701 | }; 702 | var startAngle = getAngle(unit_vec_origin_start_tangent); 703 | var endAngle = getAngle(unit_vec_origin_end_tangent); 704 | 705 | // Connect the point (x0, y0) to the start tangent point by a straight line 706 | this.lineTo(x + unit_vec_origin_start_tangent[0] * radius, 707 | y + unit_vec_origin_start_tangent[1] * radius); 708 | 709 | // Connect the start tangent point to the end tangent point by arc 710 | // and adding the end tangent point to the subpath. 711 | this.arc(x, y, radius, startAngle, endAngle); 712 | }; 713 | 714 | /** 715 | * Sets the stroke property on the current element 716 | */ 717 | ctx.prototype.stroke = function(){ 718 | if(this.__currentElement.nodeName === "path") { 719 | this.__currentElement.setAttribute("paint-order", "fill stroke markers"); 720 | } 721 | this.__applyCurrentDefaultPath(); 722 | this.__applyStyleToCurrentElement("stroke"); 723 | }; 724 | 725 | /** 726 | * Sets fill properties on the current element 727 | */ 728 | ctx.prototype.fill = function(){ 729 | if(this.__currentElement.nodeName === "path") { 730 | this.__currentElement.setAttribute("paint-order", "stroke fill markers"); 731 | } 732 | this.__applyCurrentDefaultPath(); 733 | this.__applyStyleToCurrentElement("fill"); 734 | }; 735 | 736 | /** 737 | * Adds a rectangle to the path. 738 | */ 739 | ctx.prototype.rect = function(x, y, width, height){ 740 | if(this.__currentElement.nodeName !== "path") { 741 | this.beginPath(); 742 | } 743 | this.moveTo(x, y); 744 | this.lineTo(x+width, y); 745 | this.lineTo(x+width, y+height); 746 | this.lineTo(x, y+height); 747 | this.lineTo(x, y); 748 | this.closePath(); 749 | }; 750 | 751 | 752 | /** 753 | * adds a rectangle element 754 | */ 755 | ctx.prototype.fillRect = function(x, y, width, height){ 756 | var rect, parent; 757 | rect = this.__createElement("rect", { 758 | x : x, 759 | y : y, 760 | width : width, 761 | height : height 762 | }, true); 763 | parent = this.__closestGroupOrSvg(); 764 | parent.appendChild(rect); 765 | this.__currentElement = rect; 766 | this.__applyStyleToCurrentElement("fill"); 767 | }; 768 | 769 | /** 770 | * Draws a rectangle with no fill 771 | * @param x 772 | * @param y 773 | * @param width 774 | * @param height 775 | */ 776 | ctx.prototype.strokeRect = function(x, y, width, height){ 777 | var rect, parent; 778 | rect = this.__createElement("rect", { 779 | x : x, 780 | y : y, 781 | width : width, 782 | height : height 783 | }, true); 784 | parent = this.__closestGroupOrSvg(); 785 | parent.appendChild(rect); 786 | this.__currentElement = rect; 787 | this.__applyStyleToCurrentElement("stroke"); 788 | }; 789 | 790 | 791 | /** 792 | * "Clears" a canvas by just drawing a white rectangle in the current group. 793 | */ 794 | ctx.prototype.clearRect = function(x, y, width, height) { 795 | var rect, parent = this.__closestGroupOrSvg(); 796 | rect = this.__createElement("rect", { 797 | x : x, 798 | y : y, 799 | width : width, 800 | height : height, 801 | fill : "#FFFFFF" 802 | }, true); 803 | parent.appendChild(rect); 804 | }; 805 | 806 | /** 807 | * Adds a linear gradient to a defs tag. 808 | * Returns a canvas gradient object that has a reference to it's parent def 809 | */ 810 | ctx.prototype.createLinearGradient = function(x1, y1, x2, y2){ 811 | var grad = this.__createElement("linearGradient", { 812 | id : randomString(this.__ids), 813 | x1 : x1+"px", 814 | x2 : x2+"px", 815 | y1 : y1+"px", 816 | y2 : y2+"px", 817 | "gradientUnits" : "userSpaceOnUse" 818 | }, false); 819 | this.__defs.appendChild(grad); 820 | return new CanvasGradient(grad); 821 | }; 822 | 823 | /** 824 | * Adds a radial gradient to a defs tag. 825 | * Returns a canvas gradient object that has a reference to it's parent def 826 | */ 827 | ctx.prototype.createRadialGradient = function(x0, y0, r0, x1, y1, r1){ 828 | var grad = this.__createElement("radialGradient", { 829 | id : randomString(this.__ids), 830 | cx : x1+"px", 831 | cy : y1+"px", 832 | r : r1+"px", 833 | fx : x0+"px", 834 | fy : y0+"px", 835 | "gradientUnits" : "userSpaceOnUse" 836 | }, false); 837 | this.__defs.appendChild(grad); 838 | return new CanvasGradient(grad); 839 | 840 | }; 841 | 842 | /** 843 | * Parses the font string and returns svg mapping 844 | * @private 845 | */ 846 | ctx.prototype.__parseFont = function() { 847 | 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-z]+?)\s*$/i; 848 | var fontPart = regex.exec( this.font ); 849 | var data = { 850 | style : fontPart[1] || 'normal', 851 | size : fontPart[4] || '10px', 852 | family : fontPart[6] || 'sans-serif', 853 | weight: fontPart[3] || 'normal', 854 | decoration : fontPart[2] || 'normal', 855 | href : null 856 | }; 857 | 858 | //canvas doesn't support underline natively, but we can pass this attribute 859 | if(this.__fontUnderline === "underline") { 860 | data.decoration = "underline"; 861 | } 862 | 863 | //canvas also doesn't support linking, but we can pass this as well 864 | if(this.__fontHref) { 865 | data.href = this.__fontHref; 866 | } 867 | 868 | return data; 869 | }; 870 | 871 | /** 872 | * Helper to link text fragments 873 | * @param font 874 | * @param element 875 | * @return {*} 876 | * @private 877 | */ 878 | ctx.prototype.__wrapTextLink = function(font, element) { 879 | if(font.href) { 880 | var a = this.__createElement("a"); 881 | a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", font.href); 882 | a.appendChild(element); 883 | return a; 884 | } 885 | return element; 886 | }; 887 | 888 | /** 889 | * Fills or strokes text 890 | * @param text 891 | * @param x 892 | * @param y 893 | * @param action - stroke or fill 894 | * @private 895 | */ 896 | ctx.prototype.__applyText = function(text, x, y, action) { 897 | var font = this.__parseFont(), 898 | parent = this.__closestGroupOrSvg(), 899 | textElement = this.__createElement("text", { 900 | "font-family" : font.family, 901 | "font-size" : font.size, 902 | "font-style" : font.style, 903 | "font-weight" : font.weight, 904 | "text-decoration" : font.decoration, 905 | "x" : x, 906 | "y" : y, 907 | "text-anchor": getTextAnchor(this.textAlign), 908 | "dominant-baseline": getDominantBaseline(this.textBaseline) 909 | }, true); 910 | 911 | textElement.appendChild(document.createTextNode(text)); 912 | this.__currentElement = textElement; 913 | this.__applyStyleToCurrentElement(action); 914 | parent.appendChild(this.__wrapTextLink(font,textElement)); 915 | }; 916 | 917 | /** 918 | * Creates a text element 919 | * @param text 920 | * @param x 921 | * @param y 922 | */ 923 | ctx.prototype.fillText = function(text, x, y){ 924 | this.__applyText(text, x, y, "fill"); 925 | }; 926 | 927 | /** 928 | * Strokes text 929 | * @param text 930 | * @param x 931 | * @param y 932 | */ 933 | ctx.prototype.strokeText = function(text, x, y){ 934 | this.__applyText(text, x, y, "stroke"); 935 | }; 936 | 937 | /** 938 | * No need to implement this for svg. 939 | * @param text 940 | * @return {TextMetrics} 941 | */ 942 | ctx.prototype.measureText = function(text){ 943 | this.__ctx.font = this.font; 944 | return this.__ctx.measureText(text); 945 | }; 946 | 947 | /** 948 | * Arc command! 949 | */ 950 | ctx.prototype.arc = function(x, y, radius, startAngle, endAngle, counterClockwise) { 951 | startAngle = startAngle % (2*Math.PI); 952 | endAngle = endAngle % (2*Math.PI); 953 | if(startAngle === endAngle) { 954 | //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) 955 | endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); 956 | } 957 | var endX = x+radius*Math.cos(endAngle), 958 | endY = y+radius*Math.sin(endAngle), 959 | startX = x+radius*Math.cos(startAngle), 960 | startY = y+radius*Math.sin(startAngle), 961 | sweepFlag = counterClockwise ? 0 : 1, 962 | largeArcFlag = 0, 963 | diff = endAngle - startAngle; 964 | 965 | // https://github.com/gliffy/canvas2svg/issues/4 966 | if(diff < 0) { 967 | diff += 2*Math.PI; 968 | } 969 | 970 | if(counterClockwise) { 971 | largeArcFlag = diff > Math.PI ? 0 : 1; 972 | } else { 973 | largeArcFlag = diff > Math.PI ? 1 : 0; 974 | } 975 | 976 | this.lineTo(startX, startY); 977 | this.__addPathCommand(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", 978 | {rx:radius, ry:radius, xAxisRotation:0, largeArcFlag:largeArcFlag, sweepFlag:sweepFlag, endX:endX, endY:endY})); 979 | 980 | this.__currentPosition = {x: endX, y: endY}; 981 | }; 982 | 983 | /** 984 | * Generates a ClipPath from the clip command. 985 | */ 986 | ctx.prototype.clip = function(){ 987 | var group = this.__closestGroupOrSvg(), 988 | clipPath = this.__createElement("clipPath"), 989 | id = randomString(this.__ids), 990 | newGroup = this.__createElement("g"); 991 | 992 | group.removeChild(this.__currentElement); 993 | clipPath.setAttribute("id", id); 994 | clipPath.appendChild(this.__currentElement); 995 | 996 | this.__defs.appendChild(clipPath); 997 | 998 | //set the clip path to this group 999 | group.setAttribute("clip-path", format("url(#{id})", {id:id})); 1000 | 1001 | //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations 1002 | // to this path 1003 | group.appendChild(newGroup); 1004 | 1005 | this.__currentElement = newGroup; 1006 | 1007 | }; 1008 | 1009 | /** 1010 | * Draws a canvas, image or mock context to this canvas. 1011 | * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support. 1012 | * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage 1013 | */ 1014 | ctx.prototype.drawImage = function(){ 1015 | //convert arguments to a real array 1016 | var args = Array.prototype.slice.call(arguments), 1017 | image=args[0], 1018 | dx, dy, dw, dh, sx=0, sy=0, sw, sh, parent, svg, defs, group, 1019 | currentElement, svgImage, canvas, context, id; 1020 | 1021 | if(args.length === 3) { 1022 | dx = args[1]; 1023 | dy = args[2]; 1024 | sw = image.width; 1025 | sh = image.height; 1026 | dw = sw; 1027 | dh = sh; 1028 | } else if(args.length === 5) { 1029 | dx = args[1]; 1030 | dy = args[2]; 1031 | dw = args[3]; 1032 | dh = args[4]; 1033 | sw = image.width; 1034 | sh = image.height; 1035 | } else if(args.length === 9) { 1036 | sx = args[1]; 1037 | sy = args[2]; 1038 | sw = args[3]; 1039 | sh = args[4]; 1040 | dx = args[5]; 1041 | dy = args[6]; 1042 | dw = args[7]; 1043 | dh = args[8]; 1044 | } else { 1045 | throw new Error("Inavlid number of arguments passed to drawImage: " + arguments.length); 1046 | } 1047 | 1048 | parent = this.__closestGroupOrSvg(); 1049 | currentElement = this.__currentElement; 1050 | 1051 | if(image instanceof ctx) { 1052 | //canvas2svg mock canvas context. In the future we may want to clone nodes instead. 1053 | //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context. 1054 | svg = image.getSvg(); 1055 | defs = svg.childNodes[0]; 1056 | while(defs.childNodes.length) { 1057 | id = defs.childNodes[0].getAttribute("id"); 1058 | this.__ids[id] = id; 1059 | this.__defs.appendChild(defs.childNodes[0]); 1060 | } 1061 | group = svg.childNodes[1]; 1062 | parent.appendChild(group); 1063 | this.__currentElement = group; 1064 | this.translate(dx, dy); 1065 | this.__currentElement = currentElement; 1066 | } else if(image.nodeName === "CANVAS" || image.nodeName === "IMG") { 1067 | //canvas or image 1068 | svgImage = this.__createElement("image"); 1069 | svgImage.setAttribute("width", dw); 1070 | svgImage.setAttribute("height", dh); 1071 | svgImage.setAttribute("preserveAspectRatio", "none"); 1072 | 1073 | if(sx || sy || sw !== image.width || sh !== image.height) { 1074 | //crop the image using a temporary canvas 1075 | canvas = document.createElement("canvas"); 1076 | canvas.width = dw; 1077 | canvas.height = dh; 1078 | context = canvas.getContext("2d"); 1079 | context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh); 1080 | image = canvas; 1081 | } 1082 | 1083 | svgImage.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", 1084 | image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src")); 1085 | parent.appendChild(svgImage); 1086 | this.__currentElement = svgImage; 1087 | this.translate(dx, dy); 1088 | this.__currentElement = currentElement; 1089 | } 1090 | }; 1091 | 1092 | /** 1093 | * Generates a pattern tag 1094 | */ 1095 | ctx.prototype.createPattern = function(image, repetition){ 1096 | var pattern = document.createElementNS("http://www.w3.org/2000/svg", "pattern"), id = randomString(this.__ids), 1097 | img; 1098 | pattern.setAttribute("id", id); 1099 | pattern.setAttribute("width", image.width); 1100 | pattern.setAttribute("height", image.height); 1101 | if(image.nodeName === "CANVAS" || image.nodeName === "IMG") { 1102 | img = document.createElementNS("http://www.w3.org/2000/svg", "image"); 1103 | img.setAttribute("width", image.width); 1104 | img.setAttribute("height", image.height); 1105 | img.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", 1106 | image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src")); 1107 | pattern.appendChild(img); 1108 | this.__defs.appendChild(pattern); 1109 | } else if(image instanceof ctx) { 1110 | pattern.appendChild(image.__root.childNodes[1]); 1111 | this.__defs.appendChild(pattern); 1112 | } 1113 | return new CanvasPattern(pattern, this); 1114 | }; 1115 | 1116 | /** 1117 | * Not yet implemented 1118 | */ 1119 | ctx.prototype.drawFocusRing = function(){}; 1120 | ctx.prototype.createImageData = function(){}; 1121 | ctx.prototype.getImageData = function(){}; 1122 | ctx.prototype.putImageData = function(){}; 1123 | ctx.prototype.globalCompositeOperation = function(){}; 1124 | ctx.prototype.setTransform = function(){}; 1125 | 1126 | //add options for alternative namespace 1127 | module.exports = ctx; 1128 | 1129 | }()); 1130 | 1131 | },{}],2:[function(require,module,exports){ 1132 | var C2S = require('./canvas2svg'); 1133 | 1134 | var Context = function(width, height, options) { 1135 | C2S.call(this); 1136 | this.__width = width; 1137 | this.__height = height; 1138 | this.generations = [[]]; // used to collect element references for different generations 1139 | 1140 | var _this = this; 1141 | this.__imageSmoothingEnabled = true; 1142 | 1143 | ["mozImageSmoothingEnabled", 1144 | "webkitImageSmoothingEnabled", 1145 | "msImageSmoothingEnabled", 1146 | "imageSmoothingEnabled"].forEach(function(k) { 1147 | Object.defineProperty(_this, k, { 1148 | get: function() { 1149 | return _this.__imageSmoothingEnabled; 1150 | }, 1151 | set: function(val) { 1152 | _this.__imageSmoothingEnabled = val; 1153 | } 1154 | }); 1155 | }); 1156 | 1157 | options = options || {}; 1158 | 1159 | ["fillStyle", "strokeStyle"].forEach(function(prop) { 1160 | var key = "__" + prop; 1161 | Object.defineProperty(_this, prop, { 1162 | get: function() { 1163 | return _this[key]; 1164 | }, 1165 | set: function(val) { 1166 | if (val.indexOf('NaN') > -1) { 1167 | console.warn("svgcanvas: invalid value for " + prop + ", fail to set it to " + val); 1168 | return; 1169 | } 1170 | _this[key] = val; 1171 | } 1172 | }); 1173 | }); 1174 | 1175 | 1176 | if (options.debug) { 1177 | this.__history = []; // method history 1178 | 1179 | var methods = []; 1180 | for(var key in this) { 1181 | if (typeof this[key] === "function") { 1182 | if (key.indexOf('__') !== 0) { 1183 | if (key !== 'getSerializedSvg') { 1184 | methods.push(key); 1185 | } 1186 | } 1187 | } 1188 | } 1189 | ["__fillStyle", "__strokeStyle"].forEach(function(prop) { 1190 | var key = "__debug__" + prop; 1191 | Object.defineProperty(_this, prop, { 1192 | get: function() { 1193 | return _this[key]; 1194 | }, 1195 | set: function(val) { 1196 | var call = prop.replace(/__/g, '') + " = " + val; 1197 | _this.__history.push(call); 1198 | _this[key] = val; 1199 | } 1200 | }); 1201 | }); 1202 | methods.forEach(function(method) { 1203 | var fn = _this[method]; 1204 | _this[method] = function() { 1205 | var call = method + '(' + Array.prototype.slice.call(arguments).join(', ') + ');'; 1206 | 1207 | // keep call history 1208 | _this.__history.push(call); 1209 | if (_this.__history.length > 100) { 1210 | _this.__history.shift(); 1211 | } 1212 | 1213 | return fn.apply(_this, arguments); 1214 | }; 1215 | }); 1216 | } 1217 | }; 1218 | 1219 | Context.prototype = Object.create(C2S.prototype); 1220 | 1221 | Context.prototype.scale = function(x, y) { 1222 | if (x === undefined || y === undefined) { 1223 | return; 1224 | } else { 1225 | C2S.prototype.scale.apply(this, arguments); 1226 | } 1227 | }; 1228 | 1229 | Context.prototype.__createElement = function(elementName, properties, resetFill) { 1230 | if (!this.__imageSmoothingEnabled) { 1231 | // only shape elements can use the shape-rendering attribute 1232 | if (["circle", "ellipse", "line", "path", "polygon", "polyline", "rect"].indexOf(elementName) > -1) { 1233 | properties = properties || {}; 1234 | properties["shape-rendering"] = "crispEdges"; // disable anti-aliasing 1235 | } 1236 | } 1237 | 1238 | var element = C2S.prototype.__createElement.call(this, elementName, properties, resetFill); 1239 | var currentGeneration = this.generations[this.generations.length - 1]; 1240 | currentGeneration.push(element); 1241 | return element; 1242 | }; 1243 | 1244 | Context.prototype.__gc = function() { 1245 | this.generations.push([]); 1246 | var ctx = this; 1247 | // make sure it happens after current job done 1248 | // for example: in p5.js's redraw use setTimeout will make gc called after both save() and restore() called 1249 | setTimeout(function() { 1250 | if (ctx.__groupStack.length > 0) { 1251 | // we are between ctx.save() and ctx.restore(), skip gc 1252 | return; 1253 | } 1254 | if (ctx.__currentElement.nodeName === 'path') { 1255 | // we are still in path, skip gc 1256 | return; 1257 | } 1258 | // keep only latest generation 1259 | while (ctx.generations.length > 1) { 1260 | var elements = ctx.generations.shift(); 1261 | var lastCount = 0; 1262 | var count = elements.length; 1263 | while (count > 0) { 1264 | lastCount = count; 1265 | elements = elements.filter(function(elem) { 1266 | // in case children may from live generation, gc from bottom to top 1267 | var children = elem.children || elem.childNodes; // childNodes for IE 1268 | if (children.length === 0) { 1269 | elem.parentNode.removeChild(elem); 1270 | return false; 1271 | } else { 1272 | return true; 1273 | } 1274 | }); 1275 | count = elements.length; 1276 | if (count === lastCount) { 1277 | // could not gc more, exit now 1278 | // save this elements to live generation 1279 | var liveGeneration = ctx.generations[ctx.generations.length - 1]; 1280 | elements.forEach(function(elem) { 1281 | liveGeneration.push(elem); 1282 | }); 1283 | // exit 1284 | break; 1285 | } 1286 | } 1287 | } 1288 | }, 0); 1289 | }; 1290 | 1291 | /** 1292 | * Clear full canvas and do gc 1293 | * @private 1294 | */ 1295 | Context.prototype.__clearCanvas = function() { 1296 | // remove all 1297 | this.generations.forEach(function(elems) { 1298 | elems.forEach(function(elem) { 1299 | if (elem) { 1300 | elem.parentNode.removeChild(elem); 1301 | } 1302 | }); 1303 | }); 1304 | this.generations = [[]]; 1305 | var g = this.__createElement('g'); 1306 | this.__root.appendChild(g); 1307 | this.__currentElement = g; 1308 | }; 1309 | 1310 | Context.prototype.clearRect = function(x, y, w, h) { 1311 | if (x === 0 && y === 0 && w === this.__width && h === this.__height) { 1312 | this.__clearCanvas(); 1313 | } else { 1314 | C2S.prototype.clearRect.call(this, x, y, w, h); 1315 | } 1316 | }; 1317 | 1318 | Context.prototype.fillRect = function(x, y, w, h) { 1319 | if (x === 0 && y === 0 && w === this.__width && h === this.__height) { 1320 | this.__gc(); 1321 | } 1322 | C2S.prototype.fillRect.call(this, x, y, w, h); 1323 | }; 1324 | 1325 | // Simple version of drawImage 1326 | // Note that this version does not handle drawing mock context 1327 | Context.prototype.drawImage = function() { 1328 | var canvas = document.createElement('canvas'); 1329 | canvas.width = this.__width; 1330 | canvas.height = this.__height; 1331 | var args = arguments; 1332 | var ctx = canvas.getContext('2d'); 1333 | ctx.drawImage.apply(ctx, args); 1334 | // Note: don't use foreign object, 1335 | // otherwise the saved SVG may be unusable for other application 1336 | var url = canvas.toDataURL('image/png'); 1337 | var image = this.__createElement('image', { 1338 | x: 0, 1339 | y: 0, 1340 | width: canvas.width, 1341 | height: canvas.height, 1342 | preserveAspectRatio: 'none' 1343 | }); 1344 | var parent = this.__closestGroupOrSvg(); 1345 | image.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", url); 1346 | parent.appendChild(image); 1347 | }; 1348 | 1349 | Context.prototype.getSerializedSvg = null; 1350 | 1351 | module.exports = Context; 1352 | 1353 | },{"./canvas2svg":1}],3:[function(require,module,exports){ 1354 | var Context = require('./context'); 1355 | 1356 | function SVGCanvas(options) { 1357 | 1358 | var debug = options && options.debug; 1359 | 1360 | this.ctx = new Context(100, 100, {debug: debug}); 1361 | this.svg = this.ctx.__root; 1362 | 1363 | // sync attributes to svg 1364 | var svg = this.svg; 1365 | var _this = this; 1366 | 1367 | var wrapper = document.createElement('div'); 1368 | wrapper.style.display = 'inline-block'; 1369 | wrapper.appendChild(svg); 1370 | this.wrapper = wrapper; 1371 | 1372 | Object.defineProperty(this, 'className', { 1373 | get: function() { 1374 | return wrapper.getAttribute('class') || ''; 1375 | }, 1376 | set: function(val) { 1377 | return wrapper.setAttribute('class', val); 1378 | } 1379 | }); 1380 | 1381 | ["width", "height"].forEach(function(prop) { 1382 | Object.defineProperty(_this, prop, { 1383 | get: function() { 1384 | return svg.getAttribute(prop) | 0; 1385 | }, 1386 | set: function(val) { 1387 | if (isNaN(val) || (typeof val === "undefined")) { 1388 | return; 1389 | } 1390 | _this.ctx['__'+prop] = val; 1391 | svg.setAttribute(prop, val); 1392 | return wrapper[prop] = val; 1393 | } 1394 | }); 1395 | }); 1396 | 1397 | ["style", "id"].forEach(function(prop) { 1398 | Object.defineProperty(_this, prop, { 1399 | get: function() { 1400 | return wrapper[prop]; 1401 | }, 1402 | set: function(val) { 1403 | if (typeof val !== "undefined") { 1404 | return wrapper[prop] = val; 1405 | } 1406 | } 1407 | }); 1408 | }); 1409 | 1410 | ["getBoundingClientRect"].forEach(function(fn) { 1411 | _this[fn] = function() { 1412 | return svg[fn](); 1413 | }; 1414 | }); 1415 | } 1416 | 1417 | SVGCanvas.prototype.getContext = function(type) { 1418 | if (type !== '2d') { 1419 | throw new Error('Unsupported type of context for SVGCanvas'); 1420 | } 1421 | 1422 | return this.ctx; 1423 | }; 1424 | 1425 | // you should always use URL.revokeObjectURL after your work done 1426 | SVGCanvas.prototype.toObjectURL = function() { 1427 | var data = new XMLSerializer().serializeToString(this.svg); 1428 | var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'}); 1429 | return URL.createObjectURL(svg); 1430 | }; 1431 | 1432 | SVGCanvas.prototype.toDataURL = function(type, options) { 1433 | var xml = new XMLSerializer().serializeToString(this.svg); 1434 | 1435 | // documentMode is an IE-only property 1436 | // http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 1437 | // http://stackoverflow.com/questions/10964966/detect-ie-version-prior-to-v9-in-javascript 1438 | var isIE = document.documentMode; 1439 | 1440 | if (isIE) { 1441 | // This is patch from canvas2svg 1442 | // IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly 1443 | var xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; 1444 | if(xmlns.test(xml)) { 1445 | xml = xml.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink'); 1446 | } 1447 | } 1448 | 1449 | var SVGDataURL = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(xml); 1450 | if (type === "image/svg+xml" || !type) { 1451 | return SVGDataURL; 1452 | } 1453 | if (type === "image/jpeg" || type === "image/png") { 1454 | var canvas = document.createElement('canvas'); 1455 | canvas.width = this.width; 1456 | canvas.height = this.height; 1457 | var ctx = canvas.getContext('2d'); 1458 | var img = new Image(); 1459 | img.src = SVGDataURL; 1460 | if (img.complete && img.width > 0 && img.height > 0) { 1461 | // for chrome, it's ready immediately 1462 | ctx.drawImage(img, 0, 0); 1463 | return canvas.toDataURL(type, options); 1464 | } else { 1465 | // for firefox, it's not possible to provide sync api in current thread 1466 | // and web worker doesn't provide canvas API, so 1467 | throw new Error('svgcanvas.toDataURL() for jpeg/png is only available in Chrome.'); 1468 | } 1469 | } 1470 | throw new Error('Unknown type for SVGCanvas.prototype.toDataURL, please use image/jpeg | image/png | image/svg+xml.'); 1471 | }; 1472 | 1473 | SVGCanvas.prototype.addEventListener = function() { 1474 | return this.svg.addEventListener.apply(this, arguments); 1475 | }; 1476 | 1477 | // will return wrapper element:
1478 | SVGCanvas.prototype.getElement = function() { 1479 | return this.wrapper; 1480 | }; 1481 | 1482 | module.exports = SVGCanvas; 1483 | 1484 | },{"./context":2}],4:[function(require,module,exports){ 1485 | var constants = { 1486 | SVG: 'svg' 1487 | }; 1488 | 1489 | module.exports = constants; 1490 | 1491 | },{}],5:[function(require,module,exports){ 1492 | module.exports = function(p5) { 1493 | /** 1494 | * Returns an Array of SVGElements of current SVG Graphics matching given selector 1495 | * 1496 | * @function querySVG 1497 | * @memberof p5.prototype 1498 | * @param {String} selector CSS selector for query 1499 | * @returns {SVGElement[]} 1500 | */ 1501 | p5.prototype.querySVG = function(selector) { 1502 | var svg = this._renderer && this._renderer.svg; 1503 | if (!svg) { 1504 | return null; 1505 | } 1506 | return p5.SVGElement.prototype.query.call({elt: svg}, selector); 1507 | }; 1508 | 1509 | /** 1510 | * @namespace SVGElement 1511 | * @constructor 1512 | * @param {Element} element 1513 | */ 1514 | function SVGElement(element) { 1515 | if (!element) { 1516 | return null; 1517 | } 1518 | return p5.Element.apply(this, arguments); 1519 | } 1520 | 1521 | SVGElement.prototype = Object.create(p5.Element.prototype); 1522 | 1523 | /** 1524 | * Returns an Array of children of current SVG Element matching given selector 1525 | * 1526 | * @function query 1527 | * @memberof SVGElement.prototype 1528 | * @param {String} selector CSS selector for query 1529 | * @returns {SVGElement[]} 1530 | */ 1531 | SVGElement.prototype.query = function(selector) { 1532 | var elements = this.elt.querySelectorAll(selector); 1533 | var objects = []; 1534 | for (var i = 0; i < elements.length; i++) { 1535 | objects[i] = new SVGElement(elements[i]); 1536 | } 1537 | return objects; 1538 | }; 1539 | 1540 | /** 1541 | * Append a new child to current element. 1542 | * 1543 | * @function append 1544 | * @memberof SVGElement.prototype 1545 | * @param {SVGElement|Element} element 1546 | */ 1547 | SVGElement.prototype.append = function(element) { 1548 | var elt = element.elt || element; 1549 | this.elt.appendChild(elt); 1550 | return this; 1551 | }; 1552 | 1553 | /** 1554 | * Apply different attribute operation based on arguments.length 1555 | *
    1556 | *
  • setAttribute(name, value)
  • 1557 | *
  • setAttributeNS(namespace, name, value)
  • 1558 | *
  • getAttribute(name)
  • 1559 | *
1560 | * 1561 | * @function attribute 1562 | * @memberof SVGElement.prototype 1563 | */ 1564 | SVGElement.prototype.attribute = function() { 1565 | var args = arguments; 1566 | if (args.length === 3) { 1567 | this.elt.setAttributeNS.apply(this.elt, args); 1568 | } 1569 | if (args.length === 2) { 1570 | this.elt.setAttribute.apply(this.elt, args); 1571 | } 1572 | if (args.length === 1) { 1573 | return this.elt.getAttribute.apply(this.elt, args); 1574 | } 1575 | return this; 1576 | }; 1577 | 1578 | /** 1579 | * Apply filter on current element. 1580 | * If called multiple times, 1581 | * these filters will be chained together and combine to a bigger SVG filter. 1582 | * 1583 | * @function filter 1584 | * @memberof SVGElement.prototype 1585 | * @param {String} filter BLUR, GRAY, INVERT, THRESHOLD, OPAQUE, ERODE, DILATE (defined in p5's constants) 1586 | * @param {Any} argument Argument for that filter 1587 | */ 1588 | SVGElement.prototype.filter = function(filter, arg) { 1589 | p5.SVGFilters.apply(this, filter, arg); 1590 | return this; 1591 | }; 1592 | 1593 | /** 1594 | * Remove applied filter on current element 1595 | * After called, rest filters will be chained together 1596 | * and combine to a new SVG filter. 1597 | * 1598 | * @function unfilter 1599 | * @memberof SVGElement.prototype 1600 | * @param {String} filter BLUR, GRAY, INVERT, THRESHOLD, OPAQUE, ERODE, DILATE (defined in p5's constants) 1601 | * @param {Any} argument Argument for that filter 1602 | */ 1603 | SVGElement.prototype.unfilter = function(filterName, arg) { 1604 | var filters = this.attribute('data-p5-svg-filters') || '[]'; 1605 | filters = JSON.parse(filters); 1606 | if (arg === undefined) { 1607 | arg = null; 1608 | } 1609 | var found = false; 1610 | filters = filters.reverse().filter(function(filter) { 1611 | if ((filter[0] === filterName) && (filter[1] === arg) && !found) { 1612 | found = true; 1613 | return false; 1614 | } 1615 | return true; 1616 | }).reverse(); 1617 | this.attribute('data-p5-svg-filters', JSON.stringify(filters)); 1618 | p5.SVGFilters.apply(this, null); 1619 | return this; 1620 | }; 1621 | 1622 | /** 1623 | * Create SVGElement 1624 | * 1625 | * @function create 1626 | * @memberof SVGElement 1627 | * @param {String} nodeName 1628 | * @param {Object} [attributes] Attributes for the element 1629 | * @return {SVGElement} 1630 | */ 1631 | SVGElement.create = function(nodeName, attributes) { 1632 | attributes = attributes || {}; 1633 | var elt = document.createElementNS('http://www.w3.org/2000/svg', nodeName); 1634 | Object.keys(attributes).forEach(function(k) { 1635 | elt.setAttribute(k, attributes[k]); 1636 | }); 1637 | return new SVGElement(elt); 1638 | }; 1639 | 1640 | /** 1641 | * Tell if current element matching given selector. 1642 | * This is polyfill from MDN. 1643 | * 1644 | * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches 1645 | * 1646 | * @function matches 1647 | * @memberof SVGElement.prototype 1648 | * @param {String} selector CSS Selector 1649 | * @return {Bool} 1650 | */ 1651 | SVGElement.prototype.matches = function(selector) { 1652 | var element = this.elt; 1653 | var matches = (element.document || element.ownerDocument).querySelectorAll(selector); 1654 | var i = 0; 1655 | while (matches[i] && matches[i] !== element) { 1656 | i++; 1657 | } 1658 | return matches[i] ? true : false; 1659 | }; 1660 | 1661 | /** 1662 | * Get defs element, or create one if not exists 1663 | * 1664 | * @private 1665 | */ 1666 | SVGElement.prototype._getDefs = function() { 1667 | var svg = this.parentNode('svg'); 1668 | var defs = svg.query('defs'); 1669 | if (defs[0]) { 1670 | defs = defs[0]; 1671 | } else { 1672 | defs = SVGElement.create('defs'); 1673 | svg.append(defs); 1674 | } 1675 | return defs; 1676 | }; 1677 | 1678 | /** 1679 | * Get parentNode. 1680 | * If selector not given, returns parentNode. 1681 | * Otherwise, will look up all ancestors, 1682 | * and return closest element matching given selector, 1683 | * or return null if not found. 1684 | * 1685 | * @function parentNode 1686 | * @memberof SVGElement.prototype 1687 | * @param {String} [selector] CSS Selector 1688 | * @return {SVGElement} 1689 | */ 1690 | SVGElement.prototype.parentNode = function(selector) { 1691 | if (!selector) { 1692 | return new SVGElement(this.elt.parentNode); 1693 | } 1694 | var elt = this; 1695 | while (elt) { 1696 | elt = this.parentNode(); 1697 | if (elt && elt.matches(selector)) { 1698 | return elt; 1699 | } 1700 | } 1701 | return null; 1702 | }; 1703 | 1704 | p5.SVGElement = SVGElement; 1705 | }; 1706 | 1707 | },{}],6:[function(require,module,exports){ 1708 | // SVG Filter 1709 | 1710 | module.exports = function(p5) { 1711 | var _filter = p5.prototype.filter; 1712 | 1713 | var SVGFilters = require('./p5.SVGFilters')(p5); 1714 | 1715 | /** 1716 | * Register a custom SVG Filter 1717 | * 1718 | * @function registerSVGFilter 1719 | * @memberof p5.prototype 1720 | * @param {String} name Name for Custom SVG filter 1721 | * @param {Function} filterFunction filterFunction(inGraphicsName, resultGraphicsName, value) 1722 | * should return SVGElement or Array of SVGElement. 1723 | * @example 1724 | * registerSVGFilter('myblur', function(inGraphicsName, resultGraphicsName, value) { 1725 | * return SVGElement.create('feGaussianBlur', { 1726 | * stdDeviation: val, 1727 | * in: inGraphics, 1728 | * result: resultGraphics, 1729 | * 'color-interpolation-filters': 'sRGB' 1730 | * }); 1731 | * }); 1732 | * filter('myblur', 5); 1733 | */ 1734 | p5.prototype.registerSVGFilter = function(name, fn) { 1735 | SVGFilters[name] = fn; 1736 | }; 1737 | 1738 | p5.prototype.filter = function(operation, value) { 1739 | var svg = this._renderer.svg; 1740 | if (svg) { 1741 | // move nodes to a new 1742 | var nodes = svg.children || svg.childNodes; // childNodes is for IE 1743 | var g = p5.SVGElement.create('g'); 1744 | this._renderer._setGCFlag(g.elt); 1745 | svg.appendChild(g.elt); 1746 | // convert nodeList to array and use forEach 1747 | // instead of using for loop, 1748 | // which is buggy due to the length changed during append 1749 | nodes = Array.prototype.slice.call(nodes); 1750 | nodes.forEach(function(node) { 1751 | if (node !== g.elt && (node.nodeName.toLowerCase() !== 'defs')) { 1752 | g.elt.appendChild(node); 1753 | } 1754 | }); 1755 | 1756 | // apply filter 1757 | g.filter(operation, value); 1758 | 1759 | // create new so that new element won't be influenced by the filter 1760 | g = p5.SVGElement.create('g'); 1761 | this._renderer._setGCFlag(g.elt); 1762 | this._renderer.svg.appendChild(g.elt); 1763 | this._renderer.drawingContext.__currentElement = g.elt; 1764 | } else { 1765 | _filter.apply(this, arguments); 1766 | } 1767 | }; 1768 | }; 1769 | 1770 | },{"./p5.SVGFilters":10}],7:[function(require,module,exports){ 1771 | module.exports = function(p5) { 1772 | /** 1773 | * @namespace p5 1774 | */ 1775 | require('./p5.RendererSVG')(p5); 1776 | require('./rendering')(p5); 1777 | require('./io')(p5); 1778 | require('./element')(p5); 1779 | require('./filters')(p5); 1780 | 1781 | // attach constants to p5 instance 1782 | var constants = require('./constants'); 1783 | Object.keys(constants).forEach(function(k) { 1784 | p5.prototype[k] = constants[k]; 1785 | }); 1786 | }; 1787 | 1788 | },{"./constants":4,"./element":5,"./filters":6,"./io":8,"./p5.RendererSVG":9,"./rendering":11}],8:[function(require,module,exports){ 1789 | module.exports = function(p5) { 1790 | /** 1791 | * Convert SVG Element to jpeg / png data url 1792 | * 1793 | * @private 1794 | * @param {SVGElement} svg SVG Element 1795 | * @param {String} mine Mine 1796 | * @param {Function} callback 1797 | */ 1798 | var svg2img = function(svg, mine, callback) { 1799 | svg = (new XMLSerializer()).serializeToString(svg); 1800 | svg = 'data:image/svg+xml;charset=utf-8,' + encodeURI(svg); 1801 | if (mine == 'image/svg+xml') { 1802 | callback(null, svg); 1803 | return; 1804 | } 1805 | var img = new Image(); 1806 | var canvas = document.createElement('canvas'); 1807 | var ctx = canvas.getContext('2d'); 1808 | img.onload = function() { 1809 | canvas.width = img.width; 1810 | canvas.height = img.height; 1811 | ctx.drawImage(img, 0, 0); 1812 | var dataURL = canvas.toDataURL(mine); 1813 | callback(null, dataURL); 1814 | }; 1815 | img.src = svg; 1816 | }; 1817 | 1818 | /** 1819 | * Get SVG frame, and convert to target type 1820 | * 1821 | * @private 1822 | * @param {Object} options 1823 | * @param {SVGElement} options.svg SVG Element, defaults to current svg element 1824 | * @param {String} options.filename 1825 | * @param {String} options.ext Extension: 'svg' or 'jpg' or 'jpeg' or 'png' 1826 | * @param {Function} options.callback 1827 | */ 1828 | p5.prototype._makeSVGFrame = function(options) { 1829 | var filename = options.filename || 'untitled'; 1830 | var ext = options.extension; 1831 | ext = ext || this._checkFileExtension(filename, ext)[1]; 1832 | var regexp = new RegExp('\\.' + ext + '$'); 1833 | filename = filename.replace(regexp, ''); 1834 | if (ext === '') { 1835 | ext = 'svg'; 1836 | } 1837 | var mine = { 1838 | png: 'image/png', 1839 | jpeg: 'image/jpeg', 1840 | jpg: 'image/jpeg', 1841 | svg: 'image/svg+xml' 1842 | }[ext]; 1843 | if (!mine) { 1844 | throw new Error('Fail to getFrame, invalid extension: ' + ext + ', please use png | jpeg | jpg | svg.'); 1845 | } 1846 | 1847 | var svg = options.svg || this._renderer.svg; 1848 | svg2img(svg, mine, function(err, dataURL) { 1849 | var downloadMime = 'image/octet-stream'; 1850 | dataURL = dataURL.replace(mine, downloadMime); 1851 | options.callback(err, { 1852 | imageData: dataURL, 1853 | filename: filename, 1854 | ext: ext 1855 | }); 1856 | }); 1857 | }; 1858 | 1859 | /** 1860 | * Save the current SVG as an image. In Safari, will open the 1861 | * image in the window and the user must provide their own 1862 | * filename on save-as. Other browsers will either save the 1863 | * file immediately, or prompt the user with a dialogue window. 1864 | * 1865 | * @function saveSVG 1866 | * @memberof p5.prototype 1867 | * @param {Graphics|Element|SVGElement} [svg] Source to save 1868 | * @param {String} [filename] 1869 | * @param {String} [extension] Extension: 'svg' or 'jpg' or 'jpeg' or 'png' 1870 | */ 1871 | p5.prototype.saveSVG = function() { 1872 | // don't use slice on arguments because it prevents optimizations 1873 | var args = arguments; 1874 | args = [args[0], args[1], args[2]]; 1875 | 1876 | var svg; 1877 | 1878 | if (args[0] instanceof p5.Graphics) { 1879 | svg = args[0]._renderer.svg; 1880 | args.shift(); 1881 | } 1882 | 1883 | if (args[0] && args[0].elt) { 1884 | svg = args[0].elt; 1885 | args.shift(); 1886 | } 1887 | 1888 | if (typeof args[0] == 'object') { 1889 | svg = args[0]; 1890 | args.shift(); 1891 | } 1892 | 1893 | var filename = args[0]; 1894 | var ext = args[1]; 1895 | 1896 | var p = this; 1897 | this._makeSVGFrame({ 1898 | svg: svg, 1899 | filename: filename, 1900 | extension: ext, 1901 | callback: function(err, frame) { 1902 | p.downloadFile(frame.imageData, frame.filename, frame.ext); 1903 | } 1904 | }); 1905 | }; 1906 | 1907 | /** 1908 | * Extends p5's saveFrames with SVG support 1909 | * 1910 | * @function saveFrames 1911 | * @memberof p5.prototype 1912 | * @param {String} filename filename 1913 | * @param {String} extension Extension: 'svg' or 'jpg' or 'jpeg' or 'png' 1914 | * @param {Number} duration duration 1915 | * @param {Number} fps fps 1916 | * @param {Function} callback callback 1917 | */ 1918 | var _saveFrames = p5.prototype.saveFrames; 1919 | p5.prototype.saveFrames = function(filename, extension, duration, fps, callback) { 1920 | var args = arguments; 1921 | 1922 | if (!this._renderer.svg) { 1923 | _saveFrames.apply(this, args); 1924 | return; 1925 | } 1926 | 1927 | duration = duration || 3; 1928 | duration = p5.prototype.constrain(duration, 0, 15); 1929 | duration = duration * 1000; 1930 | fps = fps || 15; 1931 | fps = p5.prototype.constrain(fps, 0, 22); 1932 | var count = 0; 1933 | 1934 | var frames = []; 1935 | var pending = 0; 1936 | 1937 | var p = this; 1938 | var frameFactory = setInterval(function () { 1939 | (function(count) { 1940 | pending++; 1941 | p._makeSVGFrame({ 1942 | filename: filename + count, 1943 | extension: extension, 1944 | callback: function(err, frame) { 1945 | frames[count] = frame; 1946 | pending--; 1947 | } 1948 | }); 1949 | })(count); 1950 | count++; 1951 | }, 1000 / fps); 1952 | 1953 | var done = function() { 1954 | if (pending > 0) { 1955 | setTimeout(function() { 1956 | done(); 1957 | }, 10); 1958 | return; 1959 | } 1960 | if (callback) { 1961 | callback(frames); 1962 | } else { 1963 | frames.forEach(function(f) { 1964 | p.downloadFile(f.imageData, f.filename, f.ext); 1965 | }); 1966 | } 1967 | }; 1968 | 1969 | setTimeout(function () { 1970 | clearInterval(frameFactory); 1971 | done(); 1972 | }, duration + 0.01); 1973 | }; 1974 | 1975 | /** 1976 | * Extends p5's save method with SVG support 1977 | * 1978 | * @function save 1979 | * @memberof p5.prototype 1980 | * @param {Graphics|Element|SVGElement} [source] Source to save 1981 | * @param {String} [filename] filename 1982 | */ 1983 | var _save = p5.prototype.save; 1984 | p5.prototype.save = function() { 1985 | var args = arguments; 1986 | args = [args[0], args[1]]; 1987 | 1988 | var svg; 1989 | 1990 | if (args[0] instanceof p5.Graphics) { 1991 | var svgcanvas = args[0].elt; 1992 | svg = svgcanvas.svg; 1993 | args.shift(); 1994 | } 1995 | 1996 | if (args[0] && args[0].elt) { 1997 | svg = args[0].elt; 1998 | args.shift(); 1999 | } 2000 | 2001 | if (typeof args[0] == 'object') { 2002 | svg = args[0]; 2003 | args.shift(); 2004 | } 2005 | 2006 | svg = svg || (this._renderer && this._renderer.svg); 2007 | 2008 | var filename = args[0]; 2009 | var supportedExtensions = ['jpeg', 'png', 'jpg', 'svg', '']; 2010 | var ext = this._checkFileExtension(filename, '')[1]; 2011 | 2012 | var useSVG = svg && svg.nodeName && svg.nodeName.toLowerCase() === 'svg' && supportedExtensions.indexOf(ext) > -1; 2013 | 2014 | if (useSVG) { 2015 | this.saveSVG(svg, filename); 2016 | } else { 2017 | return _save.apply(this, arguments); 2018 | } 2019 | }; 2020 | 2021 | /** 2022 | * Custom get in p5.svg (handles http and dataurl) 2023 | * @private 2024 | */ 2025 | p5.prototype._svg_get = function(path, successCallback, failureCallback) { 2026 | if (path.indexOf('data:') === 0) { 2027 | if (path.indexOf(',') === -1) { 2028 | failureCallback(new Error('Fail to parse dataurl: ' + path)); 2029 | return; 2030 | } 2031 | var svg = path.split(',').pop(); 2032 | // force request to dataurl to be async 2033 | // so that it won't make preload mess 2034 | setTimeout(function() { 2035 | if (path.indexOf(';base64,') > -1) { 2036 | svg = atob(svg); 2037 | } else { 2038 | svg = decodeURIComponent(svg); 2039 | } 2040 | successCallback(svg); 2041 | }, 1); 2042 | return svg; 2043 | } else { 2044 | this.httpGet(path, successCallback); 2045 | return null; 2046 | } 2047 | }; 2048 | 2049 | /** 2050 | * loadSVG (like loadImage, but will return SVGElement) 2051 | * 2052 | * @function loadSVG 2053 | * @memberof p5.prototype 2054 | * @returns {p5.SVGElement} 2055 | */ 2056 | p5.prototype.loadSVG = function(path, successCallback, failureCallback) { 2057 | var div = document.createElement('div'); 2058 | var element = new p5.SVGElement(div); 2059 | this._svg_get(path, function(svg) { 2060 | div.innerHTML = svg; 2061 | svg = div.querySelector('svg'); 2062 | if (!svg) { 2063 | if (failureCallback) { 2064 | failureCallback(new Error('Fail to create .')); 2065 | } 2066 | return; 2067 | } 2068 | element.elt = svg; 2069 | if (successCallback) { 2070 | successCallback(element); 2071 | } 2072 | }, failureCallback); 2073 | return element; 2074 | }; 2075 | // cause preload to wait 2076 | p5.prototype._preloadMethods.loadSVG = p5.prototype; 2077 | 2078 | p5.prototype.getDataURL = function() { 2079 | return this._renderer.elt.toDataURL('image/svg+xml'); 2080 | }; 2081 | }; 2082 | 2083 | },{}],9:[function(require,module,exports){ 2084 | var SVGCanvas = require('svgcanvas'); 2085 | 2086 | module.exports = function(p5) { 2087 | /** 2088 | * @namespace RendererSVG 2089 | * @constructor 2090 | * @param {Element} elt canvas element to be replaced 2091 | * @param {p5} pInst p5 Instance 2092 | * @param {Bool} isMainCanvas 2093 | */ 2094 | function RendererSVG(elt, pInst, isMainCanvas) { 2095 | var svgCanvas = new SVGCanvas(); 2096 | var svg = svgCanvas.svg; 2097 | 2098 | // replace with and copy id, className 2099 | var parent = elt.parentNode; 2100 | var id = elt.id; 2101 | var className = elt.className; 2102 | parent.replaceChild(svgCanvas.getElement(), elt); 2103 | svgCanvas.id = id; 2104 | svgCanvas.className = className; 2105 | elt = svgCanvas; // our fake 2106 | 2107 | elt.parentNode = { 2108 | // fake parentNode.removeChild so that noCanvas will work 2109 | removeChild: function(element) { 2110 | if (element === elt) { 2111 | var wrapper = svgCanvas.getElement(); 2112 | wrapper.parentNode.removeChild(wrapper); 2113 | } 2114 | } 2115 | }; 2116 | 2117 | p5.Renderer2D.call(this, elt, pInst, isMainCanvas); 2118 | 2119 | this.isSVG = true; 2120 | this.svg = svg; 2121 | 2122 | return this; 2123 | } 2124 | 2125 | RendererSVG.prototype = Object.create(p5.Renderer2D.prototype); 2126 | 2127 | RendererSVG.prototype._applyDefaults = function() { 2128 | p5.Renderer2D.prototype._applyDefaults.call(this); 2129 | this.drawingContext.lineWidth = 1; 2130 | }; 2131 | 2132 | RendererSVG.prototype.line = function(x1, y1, x2, y2) { 2133 | var styleEmpty = 'rgba(0,0,0,0)'; 2134 | var ctx = this.drawingContext; 2135 | if (!this._doStroke) { 2136 | return this; 2137 | } else if(ctx.strokeStyle === styleEmpty){ 2138 | return this; 2139 | } 2140 | ctx.beginPath(); 2141 | ctx.moveTo(x1, y1); 2142 | ctx.lineTo(x2, y2); 2143 | ctx.stroke(); 2144 | return this; 2145 | }; 2146 | 2147 | RendererSVG.prototype.resize = function(w, h) { 2148 | 2149 | // console.log({w: w, h: h, tw: this.width, th: this.height}); 2150 | 2151 | if (!w || !h) { 2152 | // ignore invalid values for width and height 2153 | return; 2154 | } 2155 | if (this.width !== w || this.height !== h) { 2156 | // canvas will be cleared if its size changed 2157 | // so, we do same thing for SVG 2158 | // note that at first this.width and this.height is undefined 2159 | this.drawingContext.__clearCanvas(); 2160 | } 2161 | this._withPixelDensity(function() { 2162 | p5.Renderer2D.prototype.resize.call(this, w, h); 2163 | }); 2164 | // For scale, crop 2165 | // see also: http://sarasoueidan.com/blog/svg-coordinate-systems/ 2166 | this.svg.setAttribute('viewBox', [0, 0, w, h].join(' ')); 2167 | }; 2168 | 2169 | /** 2170 | * @private 2171 | */ 2172 | RendererSVG.prototype._withPixelDensity = function(fn) { 2173 | var pixelDensity = this._pInst._pixelDensity; 2174 | this._pInst._pixelDensity = 1; // 1 is OK for SVG 2175 | fn.apply(this); 2176 | this._pInst._pixelDensity = pixelDensity; 2177 | }; 2178 | 2179 | RendererSVG.prototype.background = function() { 2180 | var args = arguments; 2181 | this._withPixelDensity(function() { 2182 | p5.Renderer2D.prototype.background.apply(this, args); 2183 | }); 2184 | }; 2185 | 2186 | RendererSVG.prototype.resetMatrix = function() { 2187 | this._withPixelDensity(function() { 2188 | p5.Renderer2D.prototype.resetMatrix.apply(this); 2189 | }); 2190 | }; 2191 | 2192 | /** 2193 | * set gc flag for svgcanvas 2194 | * 2195 | * @private 2196 | */ 2197 | RendererSVG.prototype._setGCFlag = function(element) { 2198 | var that = this.drawingContext; 2199 | var currentGeneration = that.generations[that.generations.length - 1]; 2200 | currentGeneration.push(element); 2201 | }; 2202 | 2203 | /** 2204 | * Append a element to current SVG Graphics 2205 | * 2206 | * @function appendChild 2207 | * @memberof RendererSVG.prototype 2208 | * @param {SVGElement|Element} element 2209 | */ 2210 | RendererSVG.prototype.appendChild = function(element) { 2211 | if (element && element.elt) { 2212 | element = element.elt; 2213 | } 2214 | this._setGCFlag(element); 2215 | var g = this.drawingContext.__closestGroupOrSvg(); 2216 | g.appendChild(element); 2217 | }; 2218 | 2219 | /** 2220 | * Draw an image or SVG to current SVG Graphics 2221 | * 2222 | * FIXME: sx, sy, sWidth, sHeight 2223 | * 2224 | * @function image 2225 | * @memberof RendererSVG.prototype 2226 | * @param {p5.Graphics|SVGGraphics|SVGElement|Element} image 2227 | * @param {Number} x 2228 | * @param {Number} y 2229 | * @param {Number} width 2230 | * @param {Number} height 2231 | */ 2232 | RendererSVG.prototype.image = function(img, sx, sy, sWidth, sHeight, x, y, w, h) { 2233 | if (!img) { 2234 | throw new Error('Invalid image: ' + img); 2235 | } 2236 | var elt = img._renderer && img._renderer.svg; // handle SVG Graphics 2237 | elt = elt || (img.elt && img.elt.nodeName && (img.elt.nodeName.toLowerCase() === 'svg') && img.elt); // SVGElement 2238 | elt = elt || (img.nodeName && (img.nodeName.toLowerCase() == 'svg') && img); // 2239 | if (elt) { 2240 | // it's element, let's handle it 2241 | elt = elt.cloneNode(true); 2242 | elt.setAttribute('width', w); 2243 | elt.setAttribute('height', h); 2244 | elt.setAttribute('x', x); 2245 | elt.setAttribute('y', y); 2246 | this.appendChild(elt); 2247 | } else { 2248 | p5.Renderer2D.prototype.image.apply(this, arguments); 2249 | } 2250 | }; 2251 | 2252 | p5.RendererSVG = RendererSVG; 2253 | }; 2254 | 2255 | },{"svgcanvas":3}],10:[function(require,module,exports){ 2256 | module.exports = function(p5) { 2257 | var SVGFilters = function() {}; 2258 | 2259 | var SVGElement = p5.SVGElement; 2260 | 2261 | var generateID = function() { 2262 | return Date.now().toString() + Math.random().toString().replace(/0\./, ''); 2263 | }; 2264 | 2265 | // @private 2266 | // We have to build a filter for each element 2267 | // the `filter: f1 f2` and svg param is not supported by many browsers 2268 | // so we can just modify the filter def to do so 2269 | SVGFilters.apply = function(svgElement, func, arg) { 2270 | // get filters 2271 | var filters = svgElement.attribute('data-p5-svg-filters') || '[]'; 2272 | filters = JSON.parse(filters); 2273 | if (func) { 2274 | filters.push([func, arg]); 2275 | } 2276 | svgElement.attribute('data-p5-svg-filters', JSON.stringify(filters)); 2277 | 2278 | if (filters.length === 0) { 2279 | svgElement.attribute('filter', null); 2280 | return; 2281 | } 2282 | 2283 | // generate filters chain 2284 | filters = filters.map(function(filter, index) { 2285 | var inGraphics = index === 0 ? 'SourceGraphic' : ('result-' + (index - 1)); 2286 | var resultGraphics = 'result-' + index; 2287 | return SVGFilters[filter[0]].call(null, inGraphics, resultGraphics, filter[1]); 2288 | }); 2289 | 2290 | // get filter id for this element or create one 2291 | var filterid = svgElement.attribute('data-p5-svg-filter-id'); 2292 | if (!filterid) { 2293 | filterid = 'p5-svg-' + generateID(); 2294 | svgElement.attribute('data-p5-svg-filter-id', filterid); 2295 | } 2296 | // Note that when filters is [], we will remove filter attr 2297 | // So, here, we write this attr every time 2298 | svgElement.attribute('filter', 'url(#' + filterid + ')'); 2299 | 2300 | // create 2301 | var filter = SVGElement.create('filter', {id: filterid}); 2302 | filters.forEach(function(elt) { 2303 | if (!Array.isArray(elt)) { 2304 | elt = [elt]; 2305 | } 2306 | elt.forEach(function(elt) { 2307 | filter.append(elt); 2308 | }); 2309 | }); 2310 | 2311 | // get defs 2312 | var defs = svgElement._getDefs(); 2313 | var oldfilter = defs.query('#' + filterid)[0]; 2314 | if (!oldfilter) { 2315 | defs.append(filter); 2316 | } else { 2317 | oldfilter.elt.parentNode.replaceChild(filter.elt, oldfilter.elt); 2318 | } 2319 | }; 2320 | 2321 | SVGFilters.blur = function(inGraphics, resultGraphics, val) { 2322 | return SVGElement.create('feGaussianBlur', { 2323 | stdDeviation: val, 2324 | in: inGraphics, 2325 | result: resultGraphics, 2326 | 'color-interpolation-filters': 'sRGB' 2327 | }); 2328 | }; 2329 | 2330 | // See also: http://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement 2331 | // See also: http://stackoverflow.com/questions/21977929/match-colors-in-fecolormatrix-filter 2332 | SVGFilters.colorMatrix = function(inGraphics, resultGraphics, matrix) { 2333 | return SVGElement.create('feColorMatrix', { 2334 | type: 'matrix', 2335 | values: matrix.join(' '), 2336 | 'color-interpolation-filters': 'sRGB', 2337 | in: inGraphics, 2338 | result: resultGraphics 2339 | }); 2340 | }; 2341 | 2342 | // Here we use CIE luminance for RGB 2343 | SVGFilters.gray = function(inGraphics, resultGraphics) { 2344 | var matrix = [ 2345 | 0.2126, 0.7152, 0.0722, 0, 0, // R' 2346 | 0.2126, 0.7152, 0.0722, 0, 0, // G' 2347 | 0.2126, 0.7152, 0.0722, 0, 0, // B' 2348 | 0, 0, 0, 1, 0 // A' 2349 | ]; 2350 | return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); 2351 | }; 2352 | 2353 | SVGFilters.threshold = function(inGraphics, resultGraphics, val) { 2354 | var elements = []; 2355 | elements.push(SVGFilters.gray(inGraphics, resultGraphics + '-tmp')); 2356 | var componentTransfer = SVGElement.create('feComponentTransfer', { 2357 | 'in': resultGraphics + '-tmp', 2358 | result: resultGraphics 2359 | }); 2360 | var thresh = Math.floor(val * 255); 2361 | ['R', 'G', 'B'].forEach(function(channel) { 2362 | // Note that original value is from 0 to 1 2363 | var func = SVGElement.create('feFunc' + channel, { 2364 | type: 'linear', 2365 | slope: 255, // all non-zero * 255 2366 | intercept: (thresh - 1) * -1 2367 | }); 2368 | componentTransfer.append(func); 2369 | }); 2370 | elements.push(componentTransfer); 2371 | return elements; 2372 | }; 2373 | 2374 | SVGFilters.invert = function(inGraphics, resultGraphics) { 2375 | var matrix = [ 2376 | -1, 0, 0, 0, 1, 2377 | 0, -1, 0, 0, 1, 2378 | 0, 0, -1, 0, 1, 2379 | 0, 0, 0, 1, 0 2380 | ]; 2381 | return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); 2382 | }; 2383 | 2384 | SVGFilters.opaque = function(inGraphics, resultGraphics) { 2385 | var matrix = [ 2386 | 1, 0, 0, 0, 0, // original R 2387 | 0, 1, 0, 0, 0, // original G 2388 | 0, 0, 1, 0, 0, // original B 2389 | 0, 0, 0, 0, 1 // set A to 1 2390 | ]; 2391 | return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); 2392 | }; 2393 | 2394 | /** 2395 | * Generate discrete table values based on the given color map function 2396 | * 2397 | * @private 2398 | * @param {Function} fn - Function to map channel values (val ∈ [0, 255]) 2399 | * @see http://www.w3.org/TR/SVG/filters.html#feComponentTransferElement 2400 | */ 2401 | SVGFilters._discreteTableValues = function(fn) { 2402 | var table = []; 2403 | for (var val = 0; val < 256; val++) { 2404 | var newval = fn(val); 2405 | table.push(newval / 255); // map to ∈ [0, 1] 2406 | } 2407 | return table; 2408 | }; 2409 | 2410 | /** 2411 | * Limits each channel of the image to the number of colors specified as 2412 | * the parameter. The parameter can be set to values between 2 and 255, but 2413 | * results are most noticeable in the lower ranges. 2414 | * 2415 | * Adapted from p5's Filters.posterize 2416 | */ 2417 | SVGFilters.posterize = function(inGraphics, resultGraphics, level) { 2418 | level = parseInt(level, 10); 2419 | if ((level < 2) || (level > 255)) { 2420 | throw new Error( 2421 | 'Level must be greater than 2 and less than 255 for posterize' 2422 | ); 2423 | } 2424 | 2425 | var tableValues = SVGFilters._discreteTableValues(function(val) { 2426 | return (((val * level) >> 8) * 255) / (level - 1); 2427 | }); 2428 | 2429 | var componentTransfer = SVGElement.create('feComponentTransfer', { 2430 | 'in': inGraphics, 2431 | result: resultGraphics, 2432 | 'color-interpolation-filters': 'sRGB' 2433 | }); 2434 | ['R', 'G', 'B'].forEach(function(channel) { 2435 | var func = SVGElement.create('feFunc' + channel, { 2436 | type: 'discrete', 2437 | tableValues: tableValues.join(' ') 2438 | }); 2439 | componentTransfer.append(func); 2440 | }); 2441 | 2442 | return componentTransfer; 2443 | }; 2444 | 2445 | SVGFilters._blendOffset = function(inGraphics, resultGraphics, mode) { 2446 | var elements = []; 2447 | [ 2448 | ['left', -1, 0], 2449 | ['right', 1, 0], 2450 | ['up', 0, -1], 2451 | ['down', 0, 1] 2452 | ].forEach(function(neighbor) { 2453 | elements.push(SVGElement.create('feOffset', { 2454 | 'in': inGraphics, 2455 | result: resultGraphics + '-' + neighbor[0], 2456 | dx: neighbor[1], 2457 | dy: neighbor[2] 2458 | })); 2459 | }); 2460 | [ 2461 | [null, inGraphics], 2462 | [resultGraphics + '-left', resultGraphics + '-tmp-0'], 2463 | [resultGraphics + '-right', resultGraphics + '-tmp-1'], 2464 | [resultGraphics + '-up', resultGraphics + '-tmp-2'], 2465 | [resultGraphics + '-down', resultGraphics + '-tmp-3'] 2466 | ].forEach(function(layer, i, layers) { 2467 | if (i === 0) { 2468 | return; 2469 | } 2470 | elements.push(SVGElement.create('feBlend', { 2471 | 'in': layers[i - 1][1], 2472 | in2: layer[0], 2473 | result: layer[1], 2474 | mode: mode 2475 | })); 2476 | }); 2477 | return elements; 2478 | }; 2479 | 2480 | /** 2481 | * Increases the bright areas in an image 2482 | * 2483 | * Will create 4 offset layer and blend them using darken mode 2484 | */ 2485 | SVGFilters.erode = function(inGraphics, resultGraphics) { 2486 | return SVGFilters._blendOffset(inGraphics, resultGraphics, 'darken'); 2487 | }; 2488 | 2489 | SVGFilters.dilate = function(inGraphics, resultGraphics) { 2490 | return SVGFilters._blendOffset(inGraphics, resultGraphics, 'lighten'); 2491 | }; 2492 | 2493 | p5.SVGFilters = SVGFilters; 2494 | 2495 | return SVGFilters; 2496 | }; 2497 | 2498 | },{}],11:[function(require,module,exports){ 2499 | var constants = require('./constants'); 2500 | var SVGCanvas = require('svgcanvas'); 2501 | 2502 | module.exports = function(p5) { 2503 | // patch p5.Graphics for SVG 2504 | var _graphics = p5.Graphics; 2505 | p5.Graphics = function(w, h, renderer, pInst) { 2506 | var args = arguments; 2507 | _graphics.apply(this, args); 2508 | if (renderer === constants.SVG) { 2509 | // replace with 2510 | var c = this._renderer.elt; 2511 | this._renderer = new p5.RendererSVG(c, pInst, false); // replace renderer 2512 | c = this._renderer.elt; 2513 | this.elt = c; // replace this.elt 2514 | // do default again 2515 | this._renderer.resize(w, h); 2516 | this._renderer._applyDefaults(); 2517 | } 2518 | return this; 2519 | }; 2520 | p5.Graphics.prototype = _graphics.prototype; 2521 | 2522 | /** 2523 | * Due to a known issue (image is not surely ready before onload fires), 2524 | * we have no way to draw SVG element synchronously. 2525 | * So, this method will load a SVG Graphics 2526 | * and then convert it to Canvas Graphics asynchronously 2527 | * 2528 | * @see https://github.com/zenozeng/p5.js-svg/issues/78 2529 | * 2530 | * @function loadGraphics 2531 | * @memberof p5.prototype 2532 | * @param {p5.Graphics} graphics the p5.Grphaics object 2533 | * @param {Function(p5.Graphics)} [successCallback] Function to be called once 2534 | * the SVG Graphics is loaded. Will be passed the 2535 | * p5.Graphics. 2536 | * @param {Function(Event)} [failureCallback] called with event error. 2537 | * 2538 | * @example 2539 | * pg = createGraphics(100, 100, SVG); 2540 | * background(200); 2541 | * pg.background(100); 2542 | * pg.ellipse(pg.width/2, pg.height/2, 50, 50); 2543 | * loadGraphics(pg, function(pgCanvas) { 2544 | * image(pgCanvas, 50, 50); 2545 | * image(pgCanvas, 0, 0, 50, 50); 2546 | * }); 2547 | * 2548 | */ 2549 | p5.prototype.loadGraphics = function(graphics, successCallback, failureCallback) { 2550 | if (graphics._renderer.svg) { 2551 | var svg = graphics._renderer.svg; 2552 | var url = SVGCanvas.prototype.toDataURL.call(graphics._renderer.elt, 'image/svg+xml'); 2553 | var pg = this.createGraphics(graphics.width, graphics.height, constants.SVG); 2554 | // also copy SVG, so we can keep vector SVG when image(pg) in SVG runtime 2555 | pg._renderer.svg = svg.cloneNode(true); 2556 | pg.loadImage(url, function(img) { 2557 | pg.image(img); 2558 | successCallback(pg); 2559 | }, failureCallback); 2560 | } else { 2561 | successCallback(graphics); 2562 | } 2563 | }; 2564 | 2565 | /** 2566 | * Patched version of createCanvas 2567 | * 2568 | * use createCanvas(100, 100, SVG) to create SVG canvas. 2569 | * 2570 | * Creates a SVG element in the document, and sets its width and 2571 | * height in pixels. This method should be called only once at 2572 | * the start of setup. 2573 | * @function createCanvas 2574 | * @memberof p5.prototype 2575 | * @param {Number} width - Width (in px) for SVG Element 2576 | * @param {Number} height - Height (in px) for SVG Element 2577 | * @return {Graphics} 2578 | */ 2579 | var _createCanvas = p5.prototype.createCanvas; 2580 | p5.prototype.createCanvas = function(w, h, renderer) { 2581 | var graphics = _createCanvas.apply(this, arguments); 2582 | if (renderer === constants.SVG) { 2583 | var c = graphics.elt; 2584 | this._setProperty('_renderer', new p5.RendererSVG(c, this, true)); 2585 | this._isdefaultGraphics = true; 2586 | this._renderer.resize(w, h); 2587 | this._renderer._applyDefaults(); 2588 | } 2589 | return this._renderer; 2590 | }; 2591 | }; 2592 | 2593 | },{"./constants":4,"svgcanvas":3}],12:[function(require,module,exports){ 2594 | require('../src/index.js')(p5); 2595 | 2596 | },{"../src/index.js":7}]},{},[12]); 2597 | }); --------------------------------------------------------------------------------