├── 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 | 
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 |
"
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