├── docs ├── images │ ├── 1.1.png │ ├── 1.10.gif │ ├── 1.11.gif │ ├── 1.2.png │ ├── 1.3.png │ ├── 1.4.png │ ├── 1.5.gif │ ├── 1.6.gif │ ├── 1.7.gif │ ├── 1.8.gif │ ├── 1.9.gif │ ├── 2.1.gif │ ├── 2.10.gif │ ├── 2.11.gif │ ├── 2.12.gif │ ├── 2.13.gif │ ├── 2.14.gif │ ├── 2.15.gif │ ├── 2.16.gif │ ├── 2.17.gif │ ├── 2.18.gif │ ├── 2.19.gif │ ├── 2.2.gif │ ├── 2.20.gif │ ├── 2.21.gif │ ├── 2.22.gif │ ├── 2.3.gif │ ├── 2.4.gif │ ├── 2.5.gif │ ├── 2.6.gif │ ├── 2.7.gif │ ├── 2.8.gif │ ├── 2.9.gif │ ├── 3.1.gif │ ├── 3.2.png │ ├── 3.3.png │ ├── 3.4.gif │ ├── 4.1.gif │ ├── 4.2.gif │ ├── 4.3.gif │ ├── 4.4.gif │ ├── 4.5.gif │ ├── 4.6.gif │ ├── 4.7.png │ ├── 4.8.gif │ ├── 5.1.gif │ ├── 5.2.gif │ ├── 5.3.gif │ ├── 5.4.gif │ ├── 5.5.gif │ └── 5.6.gif ├── main.css ├── index.html ├── properties.html ├── tips.html └── styles.html ├── glc ├── app │ ├── interpolation.js │ ├── shapes │ │ ├── line.js │ │ ├── ray.js │ │ ├── curve.js │ │ ├── beziercurve.js │ │ ├── rect.js │ │ ├── grid.js │ │ ├── poly.js │ │ ├── circle.js │ │ ├── raysegment.js │ │ ├── star.js │ │ ├── path.js │ │ ├── heart.js │ │ ├── segment.js │ │ ├── oval.js │ │ ├── arcSegment.js │ │ ├── arrow.js │ │ ├── spiral.js │ │ ├── curvesegment.js │ │ ├── text.js │ │ ├── beziersegment.js │ │ ├── gear.js │ │ ├── cube.js │ │ └── shape.js │ ├── styles.js │ ├── ui │ │ ├── infopanel.js │ │ ├── creditspanel.js │ │ ├── canvaspanel.js │ │ ├── outputpanel.js │ │ └── controlpanel.js │ ├── scheduler.js │ ├── valueparser.js │ ├── glc.js │ ├── renderlist.js │ └── colorparser.js └── libs │ ├── quicksettings_minimal.css │ ├── color.js │ ├── LZWEncoder.js │ ├── NeuQuant.js │ ├── require.js │ └── GIFEncoder.js ├── README.md ├── sketches ├── code │ └── template.js ├── index.html ├── examples │ ├── grid.js │ ├── spiral.js │ ├── spiral2.js │ ├── rays.js │ ├── rays2.js │ ├── phase.js │ ├── arcSegments.js │ ├── segments.js │ ├── poly.js │ ├── bezierSegments.js │ ├── gridlayout.js │ ├── hearts.js │ ├── gears.js │ ├── cube.js │ ├── single.js │ ├── funcs.js │ ├── single2.js │ ├── rects.js │ ├── circles.js │ └── allshapes.js └── glcloader.js └── LICENSE /docs/images/1.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.1.png -------------------------------------------------------------------------------- /docs/images/1.10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.10.gif -------------------------------------------------------------------------------- /docs/images/1.11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.11.gif -------------------------------------------------------------------------------- /docs/images/1.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.2.png -------------------------------------------------------------------------------- /docs/images/1.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.3.png -------------------------------------------------------------------------------- /docs/images/1.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.4.png -------------------------------------------------------------------------------- /docs/images/1.5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.5.gif -------------------------------------------------------------------------------- /docs/images/1.6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.6.gif -------------------------------------------------------------------------------- /docs/images/1.7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.7.gif -------------------------------------------------------------------------------- /docs/images/1.8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.8.gif -------------------------------------------------------------------------------- /docs/images/1.9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/1.9.gif -------------------------------------------------------------------------------- /docs/images/2.1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.1.gif -------------------------------------------------------------------------------- /docs/images/2.10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.10.gif -------------------------------------------------------------------------------- /docs/images/2.11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.11.gif -------------------------------------------------------------------------------- /docs/images/2.12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.12.gif -------------------------------------------------------------------------------- /docs/images/2.13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.13.gif -------------------------------------------------------------------------------- /docs/images/2.14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.14.gif -------------------------------------------------------------------------------- /docs/images/2.15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.15.gif -------------------------------------------------------------------------------- /docs/images/2.16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.16.gif -------------------------------------------------------------------------------- /docs/images/2.17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.17.gif -------------------------------------------------------------------------------- /docs/images/2.18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.18.gif -------------------------------------------------------------------------------- /docs/images/2.19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.19.gif -------------------------------------------------------------------------------- /docs/images/2.2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.2.gif -------------------------------------------------------------------------------- /docs/images/2.20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.20.gif -------------------------------------------------------------------------------- /docs/images/2.21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.21.gif -------------------------------------------------------------------------------- /docs/images/2.22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.22.gif -------------------------------------------------------------------------------- /docs/images/2.3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.3.gif -------------------------------------------------------------------------------- /docs/images/2.4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.4.gif -------------------------------------------------------------------------------- /docs/images/2.5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.5.gif -------------------------------------------------------------------------------- /docs/images/2.6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.6.gif -------------------------------------------------------------------------------- /docs/images/2.7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.7.gif -------------------------------------------------------------------------------- /docs/images/2.8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.8.gif -------------------------------------------------------------------------------- /docs/images/2.9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/2.9.gif -------------------------------------------------------------------------------- /docs/images/3.1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/3.1.gif -------------------------------------------------------------------------------- /docs/images/3.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/3.2.png -------------------------------------------------------------------------------- /docs/images/3.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/3.3.png -------------------------------------------------------------------------------- /docs/images/3.4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/3.4.gif -------------------------------------------------------------------------------- /docs/images/4.1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/4.1.gif -------------------------------------------------------------------------------- /docs/images/4.2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/4.2.gif -------------------------------------------------------------------------------- /docs/images/4.3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/4.3.gif -------------------------------------------------------------------------------- /docs/images/4.4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/4.4.gif -------------------------------------------------------------------------------- /docs/images/4.5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/4.5.gif -------------------------------------------------------------------------------- /docs/images/4.6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/4.6.gif -------------------------------------------------------------------------------- /docs/images/4.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/4.7.png -------------------------------------------------------------------------------- /docs/images/4.8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/4.8.gif -------------------------------------------------------------------------------- /docs/images/5.1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/5.1.gif -------------------------------------------------------------------------------- /docs/images/5.2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/5.2.gif -------------------------------------------------------------------------------- /docs/images/5.3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/5.3.gif -------------------------------------------------------------------------------- /docs/images/5.4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/5.4.gif -------------------------------------------------------------------------------- /docs/images/5.5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/5.5.gif -------------------------------------------------------------------------------- /docs/images/5.6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msurguy/gifloopcoder-original/master/docs/images/5.6.gif -------------------------------------------------------------------------------- /glc/app/interpolation.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | return { 3 | mode: "bounce", 4 | easing: true 5 | } 6 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gifloopcoder 2 | HTML/JS Library/App for coding looping gif animations. 3 | 4 | See the [documentation](http://gifloopcoder.com/docs) 5 | -------------------------------------------------------------------------------- /glc/app/shapes/line.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x0 = this.getNumber("x0", t, 0), 6 | y0 = this.getNumber("y0", t, 0), 7 | x1 = this.getNumber("x1", t, 100), 8 | y1 = this.getNumber("y1", t, 100); 9 | 10 | context.moveTo(x0, y0); 11 | context.lineTo(x1, y1); 12 | 13 | this.drawFillAndStroke(context, t, false, true); 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /sketches/code/template.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h, 12 | color = glc.color; 13 | 14 | // your code goes here: 15 | 16 | 17 | 18 | } -------------------------------------------------------------------------------- /glc/app/styles.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | return { 3 | backgroundColor: "#ffffff", 4 | lineWidth: 5, 5 | strokeStyle: "#000000", 6 | fillStyle: "#000000", 7 | lineCap: "round", 8 | lineJoin: "miter", 9 | lineDash: [], 10 | miterLimit: 10, 11 | shadowColor: null, 12 | shadowOffsetX: 0, 13 | shadowOffsetY: 0, 14 | shadowBlur: 0, 15 | globalAlpha: 1, 16 | translationX: 0, 17 | translationY: 0, 18 | shake: 0, 19 | blendMode: "source-over" 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /glc/app/shapes/ray.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | angle = this.getNumber("angle", t, 0) * Math.PI / 180, 8 | length = this.getNumber("length", t, 100); 9 | 10 | context.translate(x, y); 11 | context.rotate(angle); 12 | context.moveTo(0, 0); 13 | context.lineTo(length, 0); 14 | 15 | this.drawFillAndStroke(context, t, false, true); 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /glc/app/shapes/curve.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x0 = this.getNumber("x0", t, 20), 6 | y0 = this.getNumber("y0", t, 10), 7 | x1 = this.getNumber("x1", t, 100), 8 | y1 = this.getNumber("y1", t, 200), 9 | x2 = this.getNumber("x2", t, 180), 10 | y2 = this.getNumber("y2", t, 10); 11 | 12 | context.moveTo(x0, y0); 13 | context.quadraticCurveTo(x1, y1, x2, y2); 14 | 15 | this.drawFillAndStroke(context, t, false, true); 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /sketches/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GIF Loop Coder 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sketches/examples/grid.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | // your code goes here: 14 | 15 | list.addGrid({ 16 | x: 0, 17 | y: 0, 18 | w: width, 19 | h: height, 20 | lineWidth: 1, 21 | gridSize: [20, 30] 22 | }) 23 | 24 | 25 | } -------------------------------------------------------------------------------- /sketches/examples/spiral.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | // your code goes here: 14 | 15 | list.addSpiral({ 16 | x: width / 2, 17 | y: height / 2, 18 | innerRadius: 20, 19 | outerRadius: 200, 20 | turns: [4, 8] 21 | }) 22 | 23 | } -------------------------------------------------------------------------------- /sketches/examples/spiral2.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | var list = glc.renderList; 3 | 4 | glc.loop(); 5 | glc.size(540, 540); 6 | glc.setDuration(3); 7 | glc.setFPS(30); 8 | 9 | list.addSpiral({ 10 | x: glc.w/2, 11 | y: glc.h/2, 12 | innerRadius: [5,100], 13 | outerRadius: 150, 14 | turns: [5,12], 15 | res: 1, 16 | rotation: [0,360], 17 | stroke: true, 18 | fill: true, 19 | strokeWidth:[2,10], 20 | strokeStyle: "white", 21 | fillStyle: "black" 22 | }); 23 | 24 | 25 | 26 | } -------------------------------------------------------------------------------- /glc/app/shapes/beziercurve.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x0 = this.getNumber("x0", t, 50), 6 | y0 = this.getNumber("y0", t, 10), 7 | x1 = this.getNumber("x1", t, 200), 8 | y1 = this.getNumber("y1", t, 100), 9 | x2 = this.getNumber("x2", t, 0), 10 | y2 = this.getNumber("y2", t, 100), 11 | x3 = this.getNumber("x3", t, 150), 12 | y3 = this.getNumber("y3", t, 10); 13 | 14 | context.moveTo(x0, y0); 15 | context.bezierCurveTo(x1, y1, x2, y2, x3, y3); 16 | 17 | this.drawFillAndStroke(context, t, false, true); 18 | } 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /glc/app/shapes/rect.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | w = this.getNumber("w", t, 100), 8 | h = this.getNumber("h", t, 100); 9 | 10 | context.translate(x, y); 11 | context.rotate(this.getNumber("rotation", t, 0) * Math.PI / 180); 12 | if(this.getBool("drawFromCenter", t, true)) { 13 | context.rect(-w * 0.5, -h * 0.5, w, h); 14 | } 15 | else { 16 | context.rect(0, 0, w, h); 17 | } 18 | 19 | this.drawFillAndStroke(context, t, true, false); 20 | } 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /sketches/glcloader.js: -------------------------------------------------------------------------------- 1 | // if you move your sketches folder somewhere other than the default, 2 | // update this baseUrl property so it continues to point to the glc directory. 3 | require.config({ 4 | baseUrl: "../glc" 5 | }); 6 | 7 | 8 | if(document.location.hash) { 9 | var script = document.createElement("script"); 10 | script.src = document.location.hash.substring(1); 11 | document.head.appendChild(script); 12 | } 13 | 14 | window.onhashchange = function() { 15 | document.location.reload(); 16 | } 17 | 18 | require(["app/glc"], function(glc) { 19 | if(window.onGLC) { 20 | window.onGLC(glc); 21 | } 22 | }); -------------------------------------------------------------------------------- /glc/app/shapes/grid.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 0), 6 | y = this.getNumber("y", t, 0), 7 | w = this.getNumber("w", t, 100), 8 | h = this.getNumber("h", t, 100), 9 | gridSize = this.getNumber("gridSize", t, 20); 10 | 11 | for(var i = y; i <= y + h; i += gridSize) { 12 | context.moveTo(x, i); 13 | context.lineTo(x + w, i); 14 | } 15 | for(i = x; i <= x + w; i += gridSize) { 16 | context.moveTo(i, y); 17 | context.lineTo(i, y + h); 18 | } 19 | 20 | this.drawFillAndStroke(context, t, false, true); 21 | } 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /sketches/examples/rays.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | // your code goes here: 14 | 15 | for(var a = 0; a < 360; a += 30) { 16 | list.addRay({ 17 | x: width / 2, 18 | y: height / 2, 19 | angle: a, 20 | length: [0, glc.w / 2], 21 | phase: a / 360 22 | }) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /sketches/examples/rays2.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | var list = glc.renderList; 3 | 4 | glc.loop(); 5 | glc.size(540, 540); 6 | glc.setDuration(3); 7 | glc.setFPS(30); 8 | glc.styles.backgroundColor = "#000000"; 9 | 10 | 11 | // your code goes here: 12 | 13 | var num=100; 14 | 15 | for (var i=0; iBuy me a beer (or two)"); 8 | infoPanel.addButton("Credits", controller.showCredits); 9 | } 10 | 11 | function setPosition(x, y) { 12 | infoPanel.setPosition(x, y); 13 | } 14 | 15 | return { 16 | init: init, 17 | setPosition: setPosition 18 | }; 19 | }); -------------------------------------------------------------------------------- /sketches/examples/segments.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | 14 | glc.size(400, 250); 15 | var list = glc.renderList; 16 | 17 | for(var i = 0; i < 10; i++) { 18 | list.addBezierSegment({ 19 | x0: 20, 20 | y0: 30, 21 | x1: 300, 22 | y1: 400, 23 | x2: 200, 24 | y2: 40, 25 | x3: 380, 26 | y3: 30, 27 | lineWidth: 40 - i * 7.5, 28 | percent: i * 0.2 29 | }); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /glc/app/shapes/circle.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | radius = this.getNumber("radius", t, 50), 8 | startAngle = this.getNumber("startAngle", t, 0), 9 | endAngle = this.getNumber("endAngle", t, 360), 10 | drawFromCenter = this.getBool("drawFromCenter", t, false); 11 | 12 | context.translate(x, y); 13 | context.rotate(this.getNumber("rotation", t, 0) * Math.PI / 180); 14 | if(drawFromCenter) { 15 | context.moveTo(0, 0); 16 | } 17 | context.arc(0, 0, radius, startAngle * Math.PI / 180, endAngle * Math.PI / 180); 18 | if(drawFromCenter) { 19 | context.closePath(); 20 | } 21 | 22 | this.drawFillAndStroke(context, t, true, false); 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /docs/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana, Geneva, sans-serif; 3 | margin: 0 2em; 4 | color: #eeeeee; 5 | background-color: #1c1c1c; 6 | } 7 | 8 | .code { 9 | margin: 0 2em; 10 | padding: 0 1em; 11 | background-color: #111111; 12 | } 13 | 14 | .section { 15 | margin: 0 2em; 16 | background-color: #222222; 17 | padding: 1em; 18 | } 19 | 20 | .subsection { 21 | margin: 0 2em; 22 | } 23 | 24 | a:visited { 25 | color: #999999; 26 | } 27 | a { 28 | color: #999999; 29 | } 30 | 31 | .bold { 32 | font-weight: bold; 33 | color: #338833; 34 | } 35 | 36 | p code, li code { 37 | color: #449944; 38 | font-size: 1.2em; 39 | } 40 | 41 | h3 { 42 | margin-top: 3em; 43 | } 44 | 45 | img { 46 | margin: 0 2em; 47 | } 48 | 49 | .current_section { 50 | color: #ffff00; 51 | } -------------------------------------------------------------------------------- /glc/app/shapes/raysegment.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | angle = this.getNumber("angle", t, 0) * Math.PI / 180, 8 | length = this.getNumber("length", t, 100), 9 | segmentLength = this.getNumber("segmentLength", t, 50), 10 | start = -0.01, 11 | end = (length + segmentLength) * t; 12 | 13 | if(end > segmentLength) { 14 | start = end - segmentLength; 15 | } 16 | if(end > length) { 17 | end = length + 0.01; 18 | } 19 | 20 | context.translate(x, y); 21 | context.rotate(angle); 22 | context.moveTo(start, 0); 23 | context.lineTo(end, 0); 24 | 25 | this.drawFillAndStroke(context, t, false, true); 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /sketches/examples/poly.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | 3 | glc.loop(); 4 | glc.size(540,540); 5 | glc.setFPS(33); 6 | glc.setDuration(3); 7 | glc.styles.backgroundColor = "white" 8 | 9 | 10 | var list = glc.renderList, 11 | width = glc.w, 12 | height = glc.h; 13 | 14 | // your code goes here: 15 | 16 | var n = 6; 17 | var rad = (width/n)/2; 18 | 19 | for (var j=0; j endIndex) { 14 | var temp = startIndex; 15 | startIndex = endIndex; 16 | endIndex = temp; 17 | } 18 | 19 | context.moveTo(path[startIndex], path[startIndex + 1]); 20 | 21 | for(var i = startIndex + 2; i < endIndex - 1; i += 2) { 22 | context.lineTo(path[i], path[i + 1]); 23 | } 24 | 25 | this.drawFillAndStroke(context, t, false, true); } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /glc/app/shapes/heart.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | w = this.getNumber("w", t, 50), 8 | h = this.getNumber("h", t, 50); 9 | 10 | var x0 = 0, 11 | y0 = -.25, 12 | x1 = .2, 13 | y1 = -.8, 14 | x2 = 1.1, 15 | y2 = -.2, 16 | x3 = 0, 17 | y3 = .5; 18 | 19 | context.save(); 20 | context.translate(x, y); 21 | context.rotate(this.getNumber("rotation", t, 0) * Math.PI / 180); 22 | context.save(); 23 | context.scale(w, h); 24 | context.moveTo(x0, y0); 25 | context.bezierCurveTo(x1, y1, x2, y2, x3, y3); 26 | context.bezierCurveTo(-x2, y2, -x1, y1, -x0, y0); 27 | context.restore(); 28 | this.drawFillAndStroke(context, t, true, false); 29 | context.restore(); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /sketches/examples/bezierSegments.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | 3 | glc.size(540,540); 4 | glc.setFPS(45); 5 | glc.setDuration(2.5); 6 | glc.styles.backgroundColor = "#74677A" // "black"; 7 | glc.loop(); 8 | 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | // your code goes here: 14 | 15 | 16 | 17 | var list = glc.renderList; 18 | var num = 50; 19 | 20 | for (var i=0; i segmentLength) { 18 | start = end - segmentLength; 19 | } 20 | if(end > dist) { 21 | end = dist + 0.01; 22 | } 23 | 24 | context.translate(x0, y0); 25 | context.rotate(angle); 26 | context.moveTo(start, 0); 27 | context.lineTo(end, 0); 28 | 29 | this.drawFillAndStroke(context, t, false, true); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /glc/app/shapes/oval.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | rx = this.getNumber("rx", t, 50), 8 | ry = this.getNumber("ry", t, 50), 9 | startAngle = this.getNumber("startAngle", t, 0), 10 | endAngle = this.getNumber("endAngle", t, 360), 11 | drawFromCenter = this.getBool("drawFromCenter", t, false); 12 | 13 | context.translate(x, y); 14 | context.rotate(this.getNumber("rotation", t, 0) * Math.PI / 180); 15 | context.save(); 16 | context.scale(rx / 100, ry / 100); 17 | if(drawFromCenter) { 18 | context.moveTo(0, 0); 19 | } 20 | context.arc(0, 0, 100, startAngle * Math.PI / 180, endAngle * Math.PI / 180); 21 | if(drawFromCenter) { 22 | context.closePath(); 23 | } 24 | context.restore(); 25 | 26 | this.drawFillAndStroke(context, t, true, false); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /sketches/examples/hearts.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | function randomPink() { 14 | var red = (Math.floor(Math.random() * 56) + 200).toString(16), 15 | greenBlue = (Math.floor(Math.random() * 56) + 130).toString(16); 16 | return "#" + red + greenBlue + greenBlue; 17 | } 18 | 19 | 20 | for(var x = 0; x <= width; x += 40) { 21 | list.addHeart({ 22 | x: x, 23 | w: Math.random() * 50 + 20, 24 | h: Math.random() * 50 + 20, 25 | y: [height / 2 + 50, height / 2 - 50], 26 | fillStyle: randomPink(), 27 | phase: x / width 28 | }) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /glc/app/ui/creditspanel.js: -------------------------------------------------------------------------------- 1 | define(["libs/quicksettings"], function(QuickSettings) { 2 | var creditsPanel = null; 3 | 4 | function init() { 5 | creditsPanel = QuickSettings.create(100, 100, "Credits"); 6 | creditsPanel.addInfo("creator", "Architect, coding, design, etc.: Keith Peters, kp@bit-101.com"); 7 | creditsPanel.addInfo("testers", "Testers: Jerome Herr, Justin Gitlin, André Michelle"); 8 | creditsPanel.addInfo("encoder", "GIF Encoder: Kevin Weiner, Thibault Imbert, Kevin Kwok, Johan Nordberg"); 9 | creditsPanel.addInfo("QS", "User interface created with QuickSettings.js."); 10 | creditsPanel.addButton("Close", function() { 11 | creditsPanel.hide(); 12 | }); 13 | creditsPanel.hide(); 14 | } 15 | 16 | function show() { 17 | creditsPanel.show(); 18 | } 19 | 20 | return { 21 | init: init, 22 | show: show 23 | }; 24 | 25 | }); -------------------------------------------------------------------------------- /glc/app/shapes/arcSegment.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | radius = this.getNumber("radius", t, 50), 8 | startAngle = this.getNumber("startAngle", t, 0), 9 | endAngle = this.getNumber("endAngle", t, 360); 10 | 11 | if(startAngle > endAngle) { 12 | var temp = startAngle; 13 | startAngle = endAngle; 14 | endAngle = temp; 15 | } 16 | var arc = this.getNumber("arc", t, 20), 17 | start = startAngle - 1, 18 | end = startAngle + t * (endAngle - startAngle + arc); 19 | 20 | if(end > startAngle + arc) { 21 | start = end - arc; 22 | } 23 | if(end > endAngle) { 24 | end = endAngle + 1; 25 | } 26 | 27 | context.translate(x, y); 28 | context.rotate(this.getNumber("rotation", t, 0) * Math.PI / 180); 29 | context.arc(0, 0, radius, start * Math.PI / 180, end * Math.PI / 180); 30 | 31 | this.drawFillAndStroke(context, t, false, true); 32 | } 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /glc/app/shapes/arrow.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | w = this.getNumber("w", t, 100), 8 | h = this.getNumber("h", t, 100), 9 | pointPercent = this.getNumber("pointPercent", t, 0.5), 10 | shaftPercent = this.getNumber("shaftPercent", t, 0.5); 11 | 12 | context.translate(x, y); 13 | context.rotate(this.getNumber("rotation", t, 0) * Math.PI / 180); 14 | 15 | // context.translate(-w / 2, 0); 16 | 17 | context.moveTo(-w / 2, -h * shaftPercent * 0.5); 18 | context.lineTo(w / 2 - w * pointPercent, -h * shaftPercent * 0.5); 19 | context.lineTo(w / 2 - w * pointPercent, -h * 0.5); 20 | context.lineTo(w / 2, 0); 21 | context.lineTo(w / 2 - w * pointPercent, h * 0.5); 22 | context.lineTo(w / 2 - w * pointPercent, h * shaftPercent * 0.5); 23 | context.lineTo(-w / 2, h * shaftPercent * 0.5); 24 | context.lineTo(-w / 2, -h * shaftPercent * 0.5); 25 | 26 | this.drawFillAndStroke(context, t, true, false); 27 | } 28 | } 29 | }); -------------------------------------------------------------------------------- /sketches/examples/gears.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | glc.setDuration(5); 5 | // glc.setFPS(20); 6 | glc.setMode("single"); 7 | glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | glc.styles.shadowColor = "rgba(0,0,0,0.4)"; 10 | glc.styles.shadowOffsetX = 10; 11 | glc.styles.shadowOffsetY = 10; 12 | glc.styles.shadowBlur = 10; 13 | var list = glc.renderList, 14 | width = glc.w, 15 | height = glc.h; 16 | 17 | function randomGray() { 18 | var shade = Math.floor(Math.random() * 256).toString(16); 19 | return "#" + shade + shade + shade; 20 | } 21 | 22 | 23 | for(var a = 0; a < 50; a += 1) { 24 | list.addGear({ 25 | 26 | x: Math.random() * width, 27 | y: Math.random() * height, 28 | radius: 20 + Math.random() * 30, 29 | teeth: 5 + Math.round(Math.random() * 5), 30 | rotation: [0, 360], 31 | fillStyle: randomGray(), 32 | phase: Math.random() 33 | }) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /glc/app/shapes/spiral.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, "100"), 6 | y = this.getNumber("y", t, "100"), 7 | innerRadius = this.getNumber("innerRadius", t, 10), 8 | outerRadius = this.getNumber("outerRadius", t, 90), 9 | turns = this.getNumber("turns", t, 6), 10 | res = this.getNumber("res", t, 1) * Math.PI / 180, 11 | fullAngle = Math.PI * 2 * turns; 12 | 13 | context.translate(x, y); 14 | context.rotate(this.getNumber("rotation", t, 0) * Math.PI / 180); 15 | 16 | 17 | if(fullAngle > 0) { 18 | for(var a = 0; a < fullAngle; a += res) { 19 | var r = innerRadius + (outerRadius - innerRadius) * a / fullAngle; 20 | context.lineTo(Math.cos(a) * r, Math.sin(a) * r); 21 | } 22 | } 23 | else { 24 | for(var a = 0; a > fullAngle; a -= res) { 25 | var r = innerRadius + (outerRadius - innerRadius) * a / fullAngle; 26 | context.lineTo(Math.cos(a) * r, Math.sin(a) * r); 27 | } 28 | } 29 | this.drawFillAndStroke(context, t, false, true); 30 | } 31 | }; 32 | 33 | }); -------------------------------------------------------------------------------- /glc/app/ui/canvaspanel.js: -------------------------------------------------------------------------------- 1 | define(["libs/quicksettings"], function(QuickSettings) { 2 | var canvasPanel = null, 3 | model = null, 4 | controller = null; 5 | 6 | 7 | function init(pModel, pController, canvas) { 8 | model = pModel; 9 | controller = pController; 10 | canvasPanel = QuickSettings.create(20, 20, "Canvas Panel"); 11 | canvasPanel.setWidth(model.w + 12); 12 | canvasPanel.addElement("Canvas", canvas); 13 | canvasPanel.addRange("Scrub", 0, 1, 0, 0.01, onScrub); 14 | } 15 | 16 | function onScrub(value) { 17 | if(!model.getIsRunning()) { 18 | controller.renderFrame(value); 19 | } 20 | } 21 | 22 | function setWidth(width) { 23 | canvasPanel.setWidth(width); 24 | } 25 | 26 | function setTime(t) { 27 | canvasPanel.setRangeValue("Scrub", t); 28 | } 29 | 30 | function disableControls() { 31 | canvasPanel.disableControl("Scrub"); 32 | } 33 | 34 | function enableControls() { 35 | canvasPanel.enableControl("Scrub"); 36 | } 37 | 38 | return { 39 | init: init, 40 | setWidth: setWidth, 41 | setTime: setTime, 42 | disableControls: disableControls, 43 | enableControls: enableControls 44 | } 45 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Keith Peters 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /glc/app/shapes/curvesegment.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | function quadratic(t, v0, v1, v2) { 4 | return (1 - t) * (1 - t) * v0 + 2 * (1 - t) * t * v1 + t * t * v2; 5 | } 6 | 7 | return { 8 | draw: function(context, t) { 9 | var x0 = this.getNumber("x0", t, 20), 10 | y0 = this.getNumber("y0", t, 20), 11 | x1 = this.getNumber("x1", t, 100), 12 | y1 = this.getNumber("y1", t, 200), 13 | x2 = this.getNumber("x2", t, 180), 14 | y2 = this.getNumber("y2", t, 20), 15 | percent = this.getNumber("percent", t, 0.1), 16 | t1 = t * (1 + percent), 17 | t0 = t1 - percent, 18 | res = 0.01, 19 | x, 20 | y; 21 | 22 | t1 = Math.min(t1, 1); 23 | t0 = Math.max(t0, 0); 24 | 25 | for(var i = t0; i < t1; i += res) { 26 | x = quadratic(i, x0, x1, x2); 27 | y = quadratic(i, y0, y1, y2); 28 | if(i === t0) { 29 | context.moveTo(x, y); 30 | } 31 | else { 32 | context.lineTo(x, y); 33 | } 34 | } 35 | x = quadratic(t1, x0, x1, x2); 36 | y = quadratic(t1, y0, y1, y2); 37 | context.lineTo(x, y); 38 | 39 | this.drawFillAndStroke(context, t, false, true); } 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /sketches/examples/cube.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | glc.size(600, 150); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | glc.setMode("single"); 7 | glc.setEasing(false); 8 | var list = glc.renderList, 9 | width = glc.w, 10 | height = glc.h; 11 | 12 | var size = 150; 13 | 14 | list.addCube({ 15 | translationX: 0, 16 | x: size / 2, 17 | y: size / 2, 18 | size: size / 2, 19 | rotationX: [0, 90], 20 | }); 21 | 22 | list.addCube({ 23 | translationX: size, 24 | x: size / 2, 25 | y: size / 2, 26 | size: size / 2, 27 | rotationY: [0, -90], 28 | }); 29 | 30 | list.addCube({ 31 | translationX: size * 2, 32 | x: size / 2, 33 | y: size / 2, 34 | size: size / 2, 35 | rotationZ: [90, 0] 36 | }); 37 | 38 | list.addCube({ 39 | translationX: size * 3, 40 | x: size / 2, 41 | y: size / 2, 42 | size: size / 2, 43 | rotationX: [0, 90], 44 | rotationY: [0, -90], 45 | rotationZ: [90, 0] 46 | }); 47 | 48 | 49 | 50 | } -------------------------------------------------------------------------------- /glc/app/shapes/text.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | text = this.getString("text", t, "hello"), 8 | fontSize = this.getNumber("fontSize", t, 20), 9 | fontWeight = this.getString("fontWeight", t, "normal"); 10 | fontFamily = this.getString("fontFamily", t, "sans-serif"); 11 | fontStyle = this.getString("fontStyle", t, "normal"); 12 | 13 | context.font = fontWeight + " " + fontStyle + " " + fontSize + "px " + fontFamily; 14 | var width = context.measureText(text).width; 15 | context.translate(x, y); 16 | context.rotate(this.getNumber("rotation", t, 0) * Math.PI / 180); 17 | var shadowsSet = false; 18 | context.save(); 19 | if(this.getBool("fill", t, true)) { 20 | this.setShadowParams(context, t); 21 | shadowsSet = true; 22 | context.fillText(text, -width / 2, fontSize * 0.4); 23 | } 24 | context.restore(); 25 | if(this.getBool("stroke", t, false)) { 26 | if(!shadowsSet) { 27 | this.setShadowParams(context, t); 28 | } 29 | context.strokeText(text, -width / 2, fontSize * 0.4); 30 | } 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /glc/app/shapes/beziersegment.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | function bezier(t, v0, v1, v2, v3) { 4 | return (1 - t) * (1 - t) * (1 - t) * v0 + 3 * (1 - t) * (1 - t) * t * v1 + 3 * (1 - t) * t * t * v2 + t * t * t * v3; 5 | } 6 | 7 | return { 8 | draw: function(context, t) { 9 | var x0 = this.getNumber("x0", t, 50), 10 | y0 = this.getNumber("y0", t, 10), 11 | x1 = this.getNumber("x1", t, 200), 12 | y1 = this.getNumber("y1", t, 100), 13 | x2 = this.getNumber("x2", t, 0), 14 | y2 = this.getNumber("y2", t, 100), 15 | x3 = this.getNumber("x3", t, 150), 16 | y3 = this.getNumber("y3", t, 10), 17 | percent = this.getNumber("percent", t, 0.1), 18 | t1 = t * (1 + percent), 19 | t0 = t1 - percent, 20 | res = 0.01, 21 | x, 22 | y; 23 | 24 | t1 = Math.min(t1, 1.001); 25 | t0 = Math.max(t0, -0.001); 26 | 27 | for(var i = t0; i < t1; i += res) { 28 | x = bezier(i, x0, x1, x2, x3); 29 | y = bezier(i, y0, y1, y2, y3); 30 | if(i === t0) { 31 | context.moveTo(x, y); 32 | } 33 | else { 34 | context.lineTo(x, y); 35 | } 36 | } 37 | x = bezier(t1, x0, x1, x2, x3); 38 | y = bezier(t1, y0, y1, y2, y3); 39 | context.lineTo(x, y); 40 | 41 | this.drawFillAndStroke(context, t, false, true); 42 | } 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /sketches/examples/single.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | glc.size(400, 250); 4 | glc.setDuration(1); 5 | // glc.setFPS(20); 6 | glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | 14 | 15 | list.addLine({ 16 | x0: -20, 17 | y0: -20, 18 | x1: [0, 80], 19 | y1: [40, 230] 20 | }); 21 | 22 | list.addLine({ 23 | x0: [-20, 200], 24 | y0: [-20, 20], 25 | x1: 80, 26 | y1: 230 27 | }); 28 | 29 | list.addLine({ 30 | x0: 200, 31 | y0: 20, 32 | x1: [80, 250], 33 | y1: [230, 200] 34 | }); 35 | 36 | list.addLine({ 37 | x0: [200, 300], 38 | y0: [20, 230], 39 | x1: 250, 40 | y1: 200 41 | }); 42 | 43 | list.addLine({ 44 | x0: 300, 45 | y0: 230, 46 | x1: [250, 350], 47 | y1: 200 48 | }); 49 | 50 | list.addLine({ 51 | x0: [300, 410], 52 | y0: [230, 20], 53 | x1: 350, 54 | y1: 200 55 | }); 56 | 57 | list.addLine({ 58 | x0: 410, 59 | y0: 20, 60 | x1: [350, 410], 61 | y1: 200 62 | }) 63 | 64 | 65 | 66 | } -------------------------------------------------------------------------------- /glc/app/shapes/gear.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | radius = this.getNumber("radius", t, 50), 8 | toothHeight = this.getNumber("toothHeight", t, 10), 9 | hub = this.getNumber("hub", t, 10), 10 | rotation = this.getNumber("rotation", t, 0) * Math.PI / 180, 11 | teeth = this.getNumber("teeth", t, 10), 12 | toothAngle = this.getNumber("toothAngle", t, 0.3), 13 | face = 0.5 - toothAngle / 2, 14 | side = 0.5 - face, 15 | innerRadius = radius - toothHeight; 16 | 17 | context.translate(x, y); 18 | context.rotate(rotation); 19 | context.save(); 20 | context.moveTo(radius, 0); 21 | var angle = Math.PI * 2 / teeth; 22 | 23 | for(var i = 0; i < teeth; i++) { 24 | context.rotate(angle * face); 25 | context.lineTo(radius, 0); 26 | context.rotate(angle * side); 27 | context.lineTo(innerRadius, 0); 28 | context.rotate(angle * face); 29 | context.lineTo(innerRadius, 0); 30 | context.rotate(angle * side); 31 | context.lineTo(radius, 0); 32 | } 33 | context.lineTo(radius, 0); 34 | context.restore(); 35 | 36 | context.moveTo(hub, 0); 37 | context.arc(0, 0, hub, 0, Math.PI * 2, true); 38 | 39 | this.drawFillAndStroke(context, t, true, false); 40 | } 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GIF Loop Coder Documentation 5 | 6 | 7 | 8 | Home 9 |

GIF Loop Coder (GLC) Documentation

10 | 11 |

This is the documentation for glc. I suggest you start by reading the intro section. Follow along with that and actually type and run the examples there. Then scan through the Objects, Styles and Property Types sections and try out different objects and styles. Once you get the hang of the program, check the Tips and Advance Use section to take it to the next level.

12 | 13 |

You can also check out the GLC Blog, where new features, sample code and small tutorials are posted from time to time.

14 | 15 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /sketches/examples/funcs.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMaxColors(256); 7 | glc.setMode("single"); 8 | glc.setEasing(false); 9 | glc.styles.backgroundColor = "black"; 10 | var list = glc.renderList, 11 | width = glc.w, 12 | height = glc.h; 13 | 14 | 15 | for(var i = 0; i < 100; i++) { 16 | list.addCircle({ 17 | x: function(t) { 18 | return 200 + Math.sin(t * Math.PI * 2) * 50; 19 | }, 20 | y: Math.random() * 400, 21 | radius: function(t) { 22 | return 3 + Math.sin(t * Math.PI * 2 + Math.PI / 2) * 2; 23 | }, 24 | globalAlpha: function(t) { 25 | return 0.6 + Math.sin(t * Math.PI * 2 + Math.PI / 2) + 0.4; 26 | }, 27 | phase: Math.random(), 28 | fillStyle: "red" 29 | }) 30 | } 31 | for(var i = 0; i < 100; i++) { 32 | list.addCircle({ 33 | x: function(t) { 34 | return 200 + Math.sin(t * Math.PI * 2) * 100; 35 | }, 36 | y: Math.random() * 400, 37 | radius: function(t) { 38 | return 6 + Math.sin(t * Math.PI * 2 + Math.PI / 2) * 4; 39 | }, 40 | globalAlpha: function(t) { 41 | return 0.6 + Math.sin(t * Math.PI * 2 + Math.PI / 2) + 0.4; 42 | }, 43 | phase: Math.random(), 44 | fillStyle: "green" 45 | }) 46 | } 47 | 48 | 49 | 50 | 51 | } -------------------------------------------------------------------------------- /sketches/examples/single2.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | glc.size(400, 200); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | 14 | // the key to single mode animations is the start and end positions. 15 | // start position of one object should match the end position of another object, 16 | // or the start and end states should be somehow hidden (offscreen, behind something else, or transparent) 17 | for(var x = -40; x < glc.w + 40; x += 80) { 18 | // each rect starts behind larger circle and ends fully transparent 19 | list.addRect({ 20 | x: [x, x + 80], 21 | y: [100, 175], 22 | globalAlpha: [1, 0], 23 | w: 20, 24 | h: 20, 25 | rotation: [360, 0], 26 | fillStyle: "red" 27 | }); 28 | 29 | // each star starts off screen and ends behind the larger circle. 30 | list.addStar({ 31 | x: [x, x + 80], 32 | y: [-50, 100], 33 | innerRadius: 10, 34 | outerRadius: 18, 35 | rotation: [0, 360], 36 | fillStyle: "blue" 37 | }); 38 | 39 | 40 | // each circle begins where the last one ends. 41 | // except first one which starts off screen, and last one which ends off screen 42 | list.addCircle({ 43 | x: [x, x + 80], 44 | y: 100, 45 | radius: 20 46 | }); 47 | 48 | 49 | } 50 | 51 | 52 | } -------------------------------------------------------------------------------- /sketches/examples/rects.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | glc.size(300, 300); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | // your code goes here: 14 | 15 | list.addRect({ 16 | x: 75, 17 | y: 75, 18 | w: [50, 100], 19 | h: 50 20 | }); 21 | 22 | list.addRect({ 23 | x: 75, 24 | y: 225, 25 | w: [50, 100], 26 | h: 50, 27 | drawFromCenter: false 28 | }); 29 | list.addRect({ 30 | x: 225, 31 | y: 75, 32 | w: 50, 33 | h: 50, 34 | rotation: [-45, 45] 35 | }); 36 | 37 | list.addRect({ 38 | x: 225, 39 | y: 225, 40 | w: 50, 41 | h: 50, 42 | rotation: [-45, 45], 43 | drawFromCenter: false 44 | }); 45 | 46 | list.addLine({ 47 | x0: 75, 48 | y0: 0, 49 | x1: 75, 50 | y1: height, 51 | lineWidth: 1, 52 | strokeStyle: "red" 53 | }); 54 | list.addLine({ 55 | x0: 225, 56 | y0: 0, 57 | x1: 225, 58 | y1: height, 59 | lineWidth: 1, 60 | strokeStyle: "red" 61 | }); 62 | list.addLine({ 63 | x0: 0, 64 | y0: 75, 65 | x1: width, 66 | y1: 75, 67 | lineWidth: 1, 68 | strokeStyle: "red" 69 | }); 70 | list.addLine({ 71 | x0: 0, 72 | y0: 225, 73 | x1: width, 74 | y1: 225, 75 | lineWidth: 1, 76 | strokeStyle: "red" 77 | }); 78 | 79 | list.addText({ 80 | text: "drawFromCenter: true", 81 | x: 150, 82 | y: 120 83 | }); 84 | list.addText({ 85 | text: "drawFromCenter: false", 86 | x: 150, 87 | y: 180 88 | }); 89 | 90 | 91 | } -------------------------------------------------------------------------------- /glc/app/scheduler.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | var t = 0, 4 | duration = 2, 5 | fps = 30, 6 | running = false, 7 | looping = false, 8 | renderCallback = null, 9 | completeCallback = null, 10 | renderList = null 11 | 12 | function init(onRender, onCompete) { 13 | renderCallback = onRender; 14 | completeCallback = onCompete; 15 | } 16 | 17 | function render() { 18 | if(running) { 19 | if(renderCallback) { 20 | renderCallback(t); 21 | } 22 | advance(); 23 | setTimeout(onTimeout, 1000 / fps); 24 | } 25 | else if(completeCallback) { 26 | completeCallback(); 27 | } 28 | } 29 | 30 | function onTimeout() { 31 | requestAnimationFrame(render); 32 | } 33 | 34 | function advance() { 35 | var numFrames = duration * fps, 36 | speed = 1 / numFrames; 37 | t += speed; 38 | if(Math.round(t * 10000) / 10000 >= 1) { 39 | if(looping) { 40 | t -= 1; 41 | } 42 | else { 43 | t = 0; 44 | stop(); 45 | } 46 | } 47 | } 48 | 49 | function loop() { 50 | if(!running) { 51 | t = 0; 52 | looping = true; 53 | running = true; 54 | render(); 55 | } 56 | } 57 | 58 | function stop() { 59 | running = false; 60 | looping = false; 61 | t = 0; 62 | } 63 | 64 | function playOnce() { 65 | if(!running) { 66 | t = 0; 67 | looping = false; 68 | running = true; 69 | render(); 70 | } 71 | } 72 | 73 | function isRunning() { 74 | return running; 75 | } 76 | 77 | function setDuration(value) { 78 | duration = value; 79 | } 80 | 81 | function getDuration() { 82 | return duration; 83 | } 84 | 85 | function setFPS(value) { 86 | fps = value; 87 | } 88 | 89 | function getFPS() { 90 | return fps; 91 | } 92 | 93 | 94 | return { 95 | init: init, 96 | loop: loop, 97 | playOnce: playOnce, 98 | stop: stop, 99 | isRunning: isRunning, 100 | setDuration: setDuration, 101 | getDuration: getDuration, 102 | setFPS: setFPS, 103 | getFPS: getFPS 104 | }; 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /sketches/examples/circles.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | 14 | list.addCircle({ 15 | x: 80, 16 | y: 80, 17 | stroke: true, 18 | fill: false, 19 | radius: 40, 20 | startAngle: [0, 90], 21 | endAngle: [360, 270], 22 | lineWidth: 30, 23 | lineCap: "butt" 24 | }); 25 | 26 | list.addCircle({ 27 | x: 200, 28 | y: 80, 29 | stroke: true, 30 | fill: false, 31 | radius: 40, 32 | startAngle: 45, 33 | endAngle: 315, 34 | lineWidth: 30, 35 | rotation: [0, 360], 36 | lineCap: "butt" 37 | }); 38 | 39 | list.addCircle({ 40 | x: 320, 41 | y: 80, 42 | stroke: true, 43 | fill: false, 44 | radius: 40, 45 | lineWidth: [1, 30] 46 | }); 47 | 48 | list.addCircle({ 49 | x: 80, 50 | y: 200, 51 | stroke: true, 52 | fill: false, 53 | radius: [10, 40], 54 | }); 55 | 56 | list.addCircle({ 57 | x: 200, 58 | y: 200, 59 | stroke: true, 60 | fill: false, 61 | radius: 40, 62 | lineDash: [[10, 5], [40, 10]] 63 | }); 64 | 65 | list.addCircle({ 66 | x: 320, 67 | y: 200, 68 | radius: 40, 69 | fillStyle: ["#ff0000", "#ffff00"] 70 | }); 71 | 72 | list.addCircle({ 73 | x: 80, 74 | y: 320, 75 | radius: 40, 76 | shadowColor: "#50000000", 77 | shadowOffsetX: [-20, 20], 78 | shadowOffsetY: 20, 79 | shadowBlur: 20 80 | }); 81 | 82 | list.addCircle({ 83 | x: 200, 84 | y: 320, 85 | stroke: true, 86 | fill: [true, false, true, false, true, false], 87 | radius: 40, 88 | }); 89 | 90 | list.addCircle({ 91 | x: 320, 92 | y: 320, 93 | radius: 40, 94 | fillStyle: ["#ff0000ff", "#000000ff"] 95 | }); 96 | 97 | 98 | } -------------------------------------------------------------------------------- /glc/app/valueparser.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | 5 | getNumber: function(prop, t, def) { 6 | if(typeof(prop) === "number") { 7 | return prop; 8 | } 9 | else if(typeof(prop) === "function") { 10 | return prop(t); 11 | } 12 | else if(prop && prop.length === 2) { 13 | var start = prop[0], 14 | end = prop[1]; 15 | return start + (end - start) * t; 16 | } 17 | else if(prop && prop.length) { 18 | return prop[Math.round(t * (prop.length - 1))]; 19 | } 20 | return def; 21 | }, 22 | 23 | 24 | getString: function(prop, t, def) { 25 | if(prop === undefined) { 26 | return def; 27 | } 28 | else if(typeof(prop) === "string") { 29 | return prop; 30 | } 31 | else if(typeof(prop) === "function") { 32 | return prop(t); 33 | } 34 | else if(prop && prop.length) { 35 | return prop[Math.round(t * (prop.length - 1))]; 36 | } 37 | return prop; 38 | }, 39 | 40 | getBool: function(prop, t, def) { 41 | if(prop === undefined) { 42 | return def; 43 | } 44 | else if(typeof(prop) === "function") { 45 | return prop(t); 46 | } 47 | else if(prop && prop.length) { 48 | return prop[Math.round(t * (prop.length - 1))]; 49 | } 50 | return prop; 51 | }, 52 | 53 | getArray: function(prop, t, def) { 54 | // string will have length, but is useless 55 | if(typeof(prop) === "string") { 56 | return def; 57 | } 58 | else if(typeof(prop) === "function") { 59 | return prop(t); 60 | } 61 | else if(prop && (prop.length == 2) && prop[0].length && prop[1].length) { 62 | // we seem to have an array of arrays 63 | var arr0 = prop[0], 64 | arr1 = prop[1], 65 | len = Math.min(arr0.length, arr1.length), 66 | result = []; 67 | 68 | for(var i = 0; i < len; i++) { 69 | var v0 = arr0[i], 70 | v1 = arr1[i]; 71 | result.push(v0 + (v1 - v0) * t); 72 | } 73 | return result; 74 | 75 | } 76 | else if(prop && prop.length > 1) { 77 | return prop; 78 | } 79 | return def; 80 | }, 81 | 82 | getObject: function(prop, t, def) { 83 | if(prop === undefined) { 84 | return def; 85 | } 86 | else if(typeof(prop) === "function") { 87 | return prop(t); 88 | } 89 | else if(prop && prop.length) { 90 | return prop[Math.round(t * (prop.length - 1))]; 91 | } 92 | return prop; 93 | } 94 | 95 | 96 | } 97 | 98 | 99 | 100 | }); 101 | -------------------------------------------------------------------------------- /glc/app/ui/outputpanel.js: -------------------------------------------------------------------------------- 1 | define(["libs/quicksettings"], function(QuickSettings) { 2 | var outputPanel = null, 3 | model = null, 4 | controller = null; 5 | 6 | 7 | // controls 8 | var captureImage = "Capture", 9 | sizeInfo = "size", 10 | clearImageButton = "Clear Image"; 11 | 12 | function init(pModel, pController) { 13 | model = pModel; 14 | controller = pController; 15 | outputPanel = QuickSettings.create(model.w + 220, 20, "Output"); 16 | outputPanel.setWidth(model.w + 12); 17 | outputPanel.addImage(captureImage, ""); 18 | outputPanel.addInfo(sizeInfo, ""); 19 | outputPanel.addButton(clearImageButton, controller.clearOutput); 20 | } 21 | 22 | 23 | function setGIF(binaryGIF) { 24 | var dataURL = "data:image/gif;base64," + encode64(binaryGIF); 25 | outputPanel.setImageURL(captureImage, dataURL); 26 | var header = 'data:image/gif;base64,', 27 | imgFileSize = Math.round((dataURL.length - header.length) * 3 / 4); 28 | outputPanel.setInfo(sizeInfo, "Approx size: " + Math.round(imgFileSize / 1024) + "kb"); 29 | } 30 | 31 | function setPNG(dataURL) { 32 | outputPanel.setImageURL(captureImage, dataURL); 33 | var header = 'data:image/png;base64,', 34 | imgFileSize = Math.round((dataURL.length - header.length) * 3 / 4); 35 | outputPanel.setInfo(sizeInfo, "Approx size: " + Math.round(imgFileSize / 1024) + "kb"); 36 | } 37 | 38 | function setWidth(width) { 39 | outputPanel.setWidth(width); 40 | } 41 | 42 | function setPosition(x, y) { 43 | outputPanel.setPosition(x, y); 44 | } 45 | 46 | function clearOutput() { 47 | outputPanel.setImageURL(captureImage, ""); 48 | outputPanel.setInfo(sizeInfo, ""); 49 | outputPanel.setWidth(model.w + 12); 50 | } 51 | 52 | function encode64(input) { 53 | var output = "", i = 0, l = input.length, 54 | key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 55 | chr1, chr2, chr3, enc1, enc2, enc3, enc4; 56 | while (i < l) { 57 | chr1 = input.charCodeAt(i++); 58 | chr2 = input.charCodeAt(i++); 59 | chr3 = input.charCodeAt(i++); 60 | enc1 = chr1 >> 2; 61 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 62 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 63 | enc4 = chr3 & 63; 64 | if (isNaN(chr2)) { 65 | enc3 = enc4 = 64; 66 | } 67 | else if (isNaN(chr3)) { 68 | enc4 = 64; 69 | } 70 | output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4); 71 | } 72 | return output; 73 | } 74 | 75 | 76 | return { 77 | init: init, 78 | setGIF: setGIF, 79 | setPNG: setPNG, 80 | setWidth: setWidth, 81 | setPosition: setPosition, 82 | clearOutput: clearOutput 83 | } 84 | }); -------------------------------------------------------------------------------- /sketches/examples/allshapes.js: -------------------------------------------------------------------------------- 1 | function onGLC(glc) { 2 | glc.loop(); 3 | // glc.size(400, 400); 4 | // glc.setDuration(5); 5 | // glc.setFPS(20); 6 | // glc.setMode("single"); 7 | // glc.setEasing(false); 8 | // glc.setMaxColors(256); 9 | var list = glc.renderList, 10 | width = glc.w, 11 | height = glc.h; 12 | 13 | // doesn't really contain ALL shapes anymore... 14 | 15 | list.addLine({ 16 | translationX: width * 0.16, 17 | translationY: height * 0.16, 18 | x0: -50, 19 | y0: -50, 20 | x1: 50, 21 | y1: [50, -50] 22 | }); 23 | 24 | list.addSegment({ 25 | translationX: width * 0.16, 26 | translationY: height * 0.16 + 10, 27 | x0: -50, 28 | y0: -50, 29 | x1: 50, 30 | y1: 50 31 | }); 32 | 33 | list.addBezierCurve({ 34 | translationX: width * 0.5, 35 | translationY: height * 0.16, 36 | x0: -30, 37 | y0: -50, 38 | x1: [75, -75], 39 | y1: 30, 40 | x2: [-75, 75], 41 | y2: 30, 42 | x3: 30, 43 | y3: -50 44 | }); 45 | 46 | list.addBezierSegment({ 47 | translationX: width * 0.5, 48 | translationY: height * 0.16 + 30, 49 | x0: -50, 50 | y0: -50, 51 | x1: 75, 52 | y1: 50, 53 | x2: -75, 54 | y2: 50, 55 | x3: 50, 56 | y3: -50 57 | }); 58 | 59 | list.addCurve({ 60 | translationX: width * 0.83, 61 | translationY: height * 0.16, 62 | x0: -30, 63 | y0: -50, 64 | x1: [-50, 50], 65 | y1: 70, 66 | x2: 30, 67 | y2: -50 68 | }); 69 | 70 | list.addCurveSegment({ 71 | translationX: width * 0.83, 72 | translationY: height * 0.16 + 30, 73 | x0: -50, 74 | y0: -50, 75 | x1: 0, 76 | y1: 100, 77 | x2: 50, 78 | y2: -50 79 | }); 80 | 81 | list.addArcSegment({ 82 | translationX: width * 0.16, 83 | translationY: height * 0.5, 84 | x: 0, 85 | y: 0, 86 | radius: 50, 87 | arc: 45, 88 | startAngle: 90, 89 | endAngle: 270 90 | }); 91 | 92 | list.addRect({ 93 | translationX: width * 0.5, 94 | translationY: height * 0.5, 95 | x: 0, 96 | y: 0, 97 | w: [50, 100], 98 | h: [100, 50] 99 | }); 100 | 101 | list.addText({ 102 | translationX: width * 0.83, 103 | translationY: height * 0.5, 104 | x: 0, 105 | y: 0, 106 | rotation: [-45, 45] 107 | }) 108 | 109 | 110 | list.addCircle({ 111 | translationX: width * 0.16, 112 | translationY: height * 0.83, 113 | x: 0, 114 | y: 0, 115 | radius: [20, 50] 116 | }); 117 | 118 | list.addPoly({ 119 | translationX: width * 0.5, 120 | translationY: height * 0.83, 121 | x: 0, 122 | y: 0, 123 | rotation: [0, 90] 124 | }); 125 | 126 | list.addStar({ 127 | translationX: width * 0.83, 128 | translationY: height * 0.83, 129 | x: 0, 130 | y: 0, 131 | rotation: [0, 90] 132 | }); 133 | 134 | 135 | } -------------------------------------------------------------------------------- /glc/libs/quicksettings_minimal.css: -------------------------------------------------------------------------------- 1 | .msettings_main { 2 | background-color: #dddddd; 3 | position: absolute; 4 | width: 150px; 5 | font: 9px sans-serif; 6 | box-shadow: 5px 5px 8px rgba(0, 0, 0, 0.35); 7 | user-select: none; 8 | -webkit-user-select: none; 9 | } 10 | 11 | .msettings_content { 12 | background-color: #dddddd; 13 | overflow-y: auto; 14 | } 15 | 16 | .msettings_title_bar { 17 | background-color: #ffffff; 18 | user-select: none; 19 | -webkit-user-select: none; 20 | cursor: pointer; 21 | padding: 3px; 22 | border: 1px #eeeeee solid; 23 | } 24 | 25 | .msettings_container { 26 | margin: 3px; 27 | padding: 3px; 28 | background-color: #eeeeee; 29 | } 30 | 31 | .msettings_range { 32 | -webkit-appearance: none; 33 | width: 100%; 34 | padding: 0px; 35 | margin: 0px; 36 | } 37 | 38 | .msettings_range:focus { 39 | outline: none; 40 | } 41 | 42 | .msettings_range::-ms-track { 43 | width: 100%; 44 | cursor: pointer; 45 | background: transparent; 46 | border-color: transparent; 47 | color: transparent; 48 | } 49 | 50 | .msettings_range::-webkit-slider-thumb { 51 | -webkit-appearance: none; 52 | height: 10px; 53 | width: 10px; 54 | border-radius: 0px; 55 | background: #999999; 56 | cursor: pointer; 57 | margin-top: 0px; 58 | } 59 | 60 | .msettings_range::-moz-range-thumb { 61 | height: 10px; 62 | width: 10px; 63 | border: none; 64 | border-radius: 0px; 65 | background: #999999; 66 | cursor: pointer; 67 | } 68 | 69 | .msettings_range::-ms-thumb { 70 | height: 10px; 71 | width: 10px; 72 | border-radius: 0px; 73 | background: #999999; 74 | cursor: pointer; 75 | } 76 | 77 | .msettings_range::-webkit-slider-runnable-track { 78 | width: 100%; 79 | height: 10px; 80 | cursor: pointer; 81 | background: #cccccc; 82 | border-radius: 0px; 83 | } 84 | 85 | .msettings_range:focus::-webkit-slider-runnable-track { 86 | background: #cccccc; 87 | } 88 | 89 | .msettings_range::-moz-range-track { 90 | width: 100%; 91 | height: 12px; 92 | cursor: pointer; 93 | background: #cccccc; 94 | border-radius: 0px; 95 | } 96 | 97 | .msettings_range::-ms-track { 98 | width: 100%; 99 | height: 10px; 100 | cursor: pointer; 101 | background: transparent; 102 | color: transparent; 103 | } 104 | .msettings_range::-ms-fill-lower { 105 | background: #cccccc; 106 | border-radius: 0px; 107 | } 108 | .msettings_range:focus::-ms-fill-lower { 109 | background: #cccccc; 110 | } 111 | .msettings_range::-ms-fill-upper { 112 | background: #cccccc; 113 | border-radius: 0px; 114 | } 115 | .msettings_range:focus::-ms-fill-upper { 116 | background: #cccccc; 117 | } 118 | 119 | .msettings_checkbox { 120 | margin-left: 2px; 121 | } 122 | 123 | .msettings_checkbox_label { 124 | user-select: none; 125 | -webkit-user-select: none; 126 | cursor: default; 127 | } 128 | 129 | .msettings_label { 130 | margin-bottom: 2px; 131 | user-select: none; 132 | -webkit-user-select: none; 133 | cursor: default; 134 | } 135 | 136 | .msettings_color { 137 | width: 40px; 138 | font: 9px sans-serif; 139 | margin: 0px; 140 | } 141 | 142 | .msettings_button { 143 | font-size: 9px; 144 | } 145 | 146 | .msettings_text_input { 147 | font-size: 9px; 148 | } 149 | 150 | .msettings_select { 151 | font-size: 9px; 152 | } 153 | 154 | .msettings_image { 155 | width: 100%; 156 | } 157 | 158 | .msettings_progress { 159 | width: 100%; 160 | } 161 | 162 | .msettings_textarea { 163 | font-size: 12px; 164 | resize: vertical; 165 | width: 100%; 166 | padding: 0; 167 | margin: 0; 168 | height: 100px; 169 | } -------------------------------------------------------------------------------- /glc/app/ui/controlpanel.js: -------------------------------------------------------------------------------- 1 | define(["libs/quicksettings"], function(QuickSettings) { 2 | var panel = null, 3 | model, 4 | controller; 5 | 6 | // conrols: 7 | var modeDropDown = "mode", 8 | durationSlider = "duration", 9 | fpsSlider = "fps", 10 | maxColorsSlider = "Max Colors", 11 | easingCheckbox = "easing", 12 | playOnceButton = "Play Once", 13 | loopButton = "Loop", 14 | stopButton = "Stop", 15 | makeAGifButton = "Make a gif", 16 | captureStillButton = "Capture still", 17 | statusInfo = "status"; 18 | 19 | 20 | function init(pModel, pController) { 21 | model = pModel; 22 | controller = pController; 23 | panel = QuickSettings.create(model.w + 50, 20, "Control Panel"); 24 | panel.addRange(durationSlider, 0.5, 10, model.getDuration(), 0.5, model.setDuration); 25 | panel.addRange(fpsSlider, 1, 60, model.getFPS(), 1, model.setFPS); 26 | panel.addRange(maxColorsSlider, 2, 256, model.maxColors, 1, function(value) { 27 | model.maxColors = value; 28 | }); 29 | panel.bindDropDown(modeDropDown, ["bounce", "single"], model.interpolation); 30 | panel.bindBoolean(easingCheckbox, model.interpolation.easing, model.interpolation); 31 | panel.addButton(playOnceButton, playOnce); 32 | panel.addButton(loopButton, loop); 33 | panel.addButton(stopButton, stop); 34 | panel.addButton(makeAGifButton, makeGif); 35 | panel.addButton(captureStillButton, captureStill); 36 | panel.addInfo(statusInfo, "stopped"); 37 | } 38 | 39 | function playOnce() { 40 | panel.setInfo(statusInfo, "playing"); 41 | controller.playOnce(); 42 | controller.disableControls(); 43 | } 44 | 45 | function loop() { 46 | panel.setInfo(statusInfo, "playing"); 47 | controller.loop(); 48 | controller.disableControls(); 49 | } 50 | 51 | function makeGif() { 52 | if(!model.getIsRunning()) { 53 | controller.clearOutput(); 54 | model.capture = true; 55 | controller.startEncoder(); 56 | playOnce(); 57 | } 58 | else { 59 | panel.setInfo(statusInfo, "Animation already running"); 60 | } 61 | } 62 | 63 | function captureStill() { 64 | controller.captureStill(); 65 | } 66 | 67 | function stop() { 68 | setStatus("stopped"); 69 | controller.stop(); 70 | controller.enableControls(); 71 | } 72 | 73 | function setPosition(x, y) { 74 | panel.setPosition(x, y); 75 | } 76 | 77 | function setStatus(status) { 78 | panel.setInfo(statusInfo, status); 79 | } 80 | 81 | function enableControls() { 82 | panel.enableControl(playOnceButton); 83 | panel.enableControl(loopButton); 84 | panel.enableControl(makeAGifButton); 85 | panel.enableControl(captureStillButton); 86 | } 87 | 88 | function disableControls() { 89 | panel.disableControl(playOnceButton); 90 | panel.disableControl(loopButton); 91 | panel.disableControl(makeAGifButton); 92 | panel.disableControl(captureStillButton); 93 | } 94 | 95 | function setFPS(value) { 96 | panel.setRangeValue(fpsSlider, value); 97 | } 98 | 99 | function setDuration(value) { 100 | panel.setRangeValue(durationSlider, value); 101 | } 102 | 103 | function setMode(mode) { 104 | panel.setDropDownIndex(modeDropDown, mode === "bounce" ? 0 : 1); 105 | } 106 | 107 | function setEasing(value) { 108 | panel.setBoolean(easingCheckbox, value); 109 | } 110 | 111 | function setMaxColors(value) { 112 | value = Math.max(2, value); 113 | value = Math.min(256, value); 114 | panel.setRangeValue(maxColorsSlider, value); 115 | } 116 | 117 | return { 118 | init: init, 119 | setPosition: setPosition, 120 | setStatus: setStatus, 121 | enableControls: enableControls, 122 | disableControls: disableControls, 123 | loop: loop, 124 | playOnce: playOnce, 125 | setFPS: setFPS, 126 | setDuration: setDuration, 127 | setMode: setMode, 128 | setEasing: setEasing, 129 | setMaxColors: setMaxColors 130 | }; 131 | 132 | }); -------------------------------------------------------------------------------- /glc/app/shapes/cube.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return { 4 | draw: function(context, t) { 5 | var x = this.getNumber("x", t, 100), 6 | y = this.getNumber("y", t, 100), 7 | z = this.getNumber("z", t, 0), 8 | size = this.getNumber("size", t, 100), 9 | rotationX = this.getNumber("rotationX", t, 0) * Math.PI / 180, 10 | rotationY = this.getNumber("rotationY", t, 0) * Math.PI / 180, 11 | rotationZ = this.getNumber("rotationZ", t, 0) * Math.PI / 180; 12 | 13 | var points = makePoints(); 14 | scale(points, size / 2); 15 | rotateX(points, rotationX); 16 | rotateY(points, rotationY); 17 | rotateZ(points, rotationZ); 18 | project(points, z); 19 | 20 | context.lineJoin = this.getString("lineJoin", t, "round"); 21 | context.lineWidth = this.getNumber("lineWidth", t, 1); 22 | 23 | context.translate(x, y); 24 | 25 | context.moveTo(points[0].sx, points[0].sy); 26 | context.lineTo(points[1].sx, points[1].sy); 27 | context.lineTo(points[2].sx, points[2].sy); 28 | context.lineTo(points[3].sx, points[3].sy); 29 | context.lineTo(points[0].sx, points[0].sy); 30 | 31 | context.moveTo(points[4].sx, points[4].sy); 32 | context.lineTo(points[5].sx, points[5].sy); 33 | context.lineTo(points[6].sx, points[6].sy); 34 | context.lineTo(points[7].sx, points[7].sy); 35 | context.lineTo(points[4].sx, points[4].sy); 36 | 37 | context.moveTo(points[0].sx, points[0].sy); 38 | context.lineTo(points[4].sx, points[4].sy); 39 | 40 | context.moveTo(points[1].sx, points[1].sy); 41 | context.lineTo(points[5].sx, points[5].sy); 42 | 43 | context.moveTo(points[2].sx, points[2].sy); 44 | context.lineTo(points[6].sx, points[6].sy); 45 | 46 | context.moveTo(points[3].sx, points[3].sy); 47 | context.lineTo(points[7].sx, points[7].sy); 48 | 49 | this.setShadowParams(context, t); 50 | context.stroke(); 51 | } 52 | } 53 | 54 | function scale(points, size) { 55 | for(var i = 0; i < points.length; i++) { 56 | var p = points[i]; 57 | p.x *= size; 58 | p.y *= size; 59 | p.z *= size; 60 | } 61 | } 62 | 63 | function rotateX(points, angle) { 64 | var cos = Math.cos(angle), 65 | sin = Math.sin(angle); 66 | for(var i = 0; i < points.length; i++) { 67 | var p = points[i], 68 | y = p.y * cos - p.z * sin, 69 | z = p.z * cos + p.y * sin; 70 | p.y = y; 71 | p.z = z; 72 | } 73 | } 74 | 75 | function rotateY(points, angle) { 76 | var cos = Math.cos(angle), 77 | sin = Math.sin(angle); 78 | for(var i = 0; i < points.length; i++) { 79 | var p = points[i], 80 | x = p.x * cos - p.z * sin, 81 | z = p.z * cos + p.x * sin; 82 | p.x = x; 83 | p.z = z; 84 | } 85 | } 86 | 87 | function rotateZ(points, angle) { 88 | var cos = Math.cos(angle), 89 | sin = Math.sin(angle); 90 | for(var i = 0; i < points.length; i++) { 91 | var p = points[i], 92 | x = p.x * cos - p.y * sin, 93 | y = p.y * cos + p.x * sin; 94 | p.x = x; 95 | p.y = y; 96 | } 97 | } 98 | 99 | function project(points, z) { 100 | var fl = 300; 101 | for(var i = 0; i < points.length; i++) { 102 | var p = points[i], 103 | scale = fl / (fl + p.z + z); 104 | p.sx = p.x * scale; 105 | p.sy = p.y * scale; 106 | } 107 | } 108 | 109 | function makePoints() { 110 | return [ 111 | { 112 | x: -1, 113 | y: -1, 114 | z: -1 115 | }, 116 | { 117 | x: 1, 118 | y: -1, 119 | z: -1 120 | }, 121 | { 122 | x: 1, 123 | y: 1, 124 | z: -1 125 | }, 126 | { 127 | x: -1, 128 | y: 1, 129 | z: -1 130 | }, 131 | { 132 | x: -1, 133 | y: -1, 134 | z: 1 135 | }, 136 | { 137 | x: 1, 138 | y: -1, 139 | z: 1 140 | }, 141 | { 142 | x: 1, 143 | y: 1, 144 | z: 1 145 | }, 146 | { 147 | x: -1, 148 | y: 1, 149 | z: 1 150 | } 151 | ]; 152 | } 153 | }); 154 | -------------------------------------------------------------------------------- /glc/libs/color.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | function rgba(r, g, b, a) { 4 | var clr = Object.create(color); 5 | clr.setRGBA(r, g, b, a); 6 | return clr.toString(); 7 | } 8 | 9 | function rgb(r, g, b) { 10 | return rgba(r, g, b, 1); 11 | } 12 | 13 | function randomRGB(min, max) { 14 | min = min || 0; 15 | max = max || 256; 16 | return rgb( 17 | Math.floor(min + Math.random() * (max - min)), 18 | Math.floor(min + Math.random() * (max - min)), 19 | Math.floor(min + Math.random() * (max - min)) 20 | ); 21 | } 22 | 23 | function randomGray(min, max) { 24 | min = min || 0; 25 | max = max || 256; 26 | return gray(Math.floor(min + Math.random() * (max - min))); 27 | } 28 | 29 | function gray(shade) { 30 | return rgb(shade, shade, shade); 31 | } 32 | 33 | function num(num) { 34 | var red = num >> 16, 35 | green = num >> 8 & 0xff, 36 | blue = num & 0xff; 37 | return rgb(red, green, blue); 38 | } 39 | 40 | function randomHSV(minH, maxH, minS, maxS, minV, maxV) { 41 | var h = minH + Math.random() * (maxH - minH), 42 | s = minS + Math.random() * (maxS - minS), 43 | v = minV + Math.random() * (maxV - minV); 44 | return hsv(h, s, v); 45 | } 46 | 47 | function hsva(h, s, v, a) { 48 | var r, g, b, 49 | i = Math.floor(h / 60), 50 | f = h / 60 - i, 51 | p = v * (1 - s), 52 | q = v * (1 - f * s), 53 | t = v * (1 - (1 - f) * s); 54 | switch (i % 6) { 55 | case 0: r = v, g = t, b = p; break; 56 | case 1: r = q, g = v, b = p; break; 57 | case 2: r = p, g = v, b = t; break; 58 | case 3: r = p, g = q, b = v; break; 59 | case 4: r = t, g = p, b = v; break; 60 | case 5: r = v, g = p, b = q; break; 61 | } 62 | return rgba( 63 | Math.floor(r * 255), 64 | Math.floor(g * 255), 65 | Math.floor(b * 255), 66 | a 67 | ); 68 | } 69 | 70 | function hsv(h, s, v) { 71 | return hsva(h, s, v, 1); 72 | } 73 | 74 | function animHSVA(startH, endH, startS, endS, startV, endV, startA, endA) { 75 | return function(t) { 76 | var h = startH + t * (endH - startH), 77 | s = startS + t * (endS - startS), 78 | v = startV + t * (endV - startV), 79 | a = startA + t * (endA - startA); 80 | return hsva(h, s, v, a); 81 | } 82 | } 83 | 84 | function animHSV(startH, endH, startS, endS, startV, endV) { 85 | return animHSVA(startH, endH, startS, endS, startV, endV, 1, 1); 86 | } 87 | 88 | 89 | 90 | 91 | 92 | ///////////////////// 93 | // gradients 94 | ///////////////////// 95 | function createLinearGradient(x0, y0, x1, y1) { 96 | var g = { 97 | type: "linearGradient", 98 | x0: x0, 99 | y0: y0, 100 | x1: x1, 101 | y1: y1, 102 | colorStops: [], 103 | addColorStop: function(position, color) { 104 | this.colorStops.push({ 105 | position: position, 106 | color: color 107 | }); 108 | } 109 | } 110 | return g; 111 | } 112 | 113 | function createRadialGradient(x0, y0, r0, x1, y1, r1) { 114 | var g = { 115 | type: "radialGradient", 116 | x0: x0, 117 | y0: y0, 118 | r0: r0, 119 | x1: x1, 120 | y1: y1, 121 | r1: r1, 122 | colorStops: [], 123 | addColorStop: function(position, color) { 124 | this.colorStops.push({ 125 | position: position, 126 | color: color 127 | }); 128 | } 129 | } 130 | return g; 131 | } 132 | 133 | color = { 134 | r: 255, 135 | g: 255, 136 | b: 255, 137 | a: 1, 138 | 139 | setRGBA: function(r, g, b, a) { 140 | this.r = r; 141 | this.g = g; 142 | this.b = b; 143 | this.a = a; 144 | return this; 145 | }, 146 | 147 | toString: function() { 148 | return "rgba(" + Math.floor(this.r) + "," + Math.floor(this.g) + "," + Math.floor(this.b) + "," + this.a + ")"; 149 | } 150 | }; 151 | 152 | return { 153 | rgb: rgb, 154 | rgba: rgba, 155 | randomRGB: randomRGB, 156 | randomGray: randomGray, 157 | gray: gray, 158 | num: num, 159 | hsv: hsv, 160 | hsva: hsva, 161 | animHSV: animHSV, 162 | animHSVA: animHSVA, 163 | randomHSV: randomHSV, 164 | createLinearGradient: createLinearGradient, 165 | createRadialGradient: createRadialGradient 166 | }; 167 | }); -------------------------------------------------------------------------------- /glc/app/shapes/shape.js: -------------------------------------------------------------------------------- 1 | define(["app/valueparser", "app/colorparser"], function(valueParser, colorParser) { 2 | 3 | return { 4 | styles: null, 5 | interpolation: null, 6 | 7 | create: function(type, props) { 8 | var obj = Object.create(this); 9 | obj.init(type, props || {}); 10 | return obj; 11 | }, 12 | 13 | init: function(type, props) { 14 | this.props = props; 15 | this.draw = type.draw; 16 | }, 17 | 18 | render: function(context, t) { 19 | var t = this.interpolate(t); 20 | 21 | this.startDraw(context, t); 22 | this.draw(context, t); 23 | this.endDraw(context, t); 24 | }, 25 | 26 | interpolate: function(t) { 27 | t *= this.props.speedMult || 1; 28 | t += this.props.phase || 0; 29 | 30 | switch(this.interpolation.mode) { 31 | case "bounce": 32 | if(this.interpolation.easing) { 33 | var a = t * Math.PI * 2; 34 | return 0.5 - Math.cos(a) * 0.5; 35 | } 36 | else { 37 | t = t % 1; 38 | return t < 0.5 ? t * 2 : t = (1 - t) * 2; 39 | } 40 | break; 41 | 42 | case "single": 43 | if(t > 1) { 44 | t %= 1; 45 | } 46 | if(this.interpolation.easing) { 47 | var a = t * Math.PI; 48 | return 0.5 - Math.cos(a) * 0.5; 49 | } 50 | else{ 51 | return t; 52 | } 53 | } 54 | 55 | }, 56 | 57 | 58 | startDraw: function(context, t) { 59 | context.save(); 60 | context.lineWidth = this.getNumber("lineWidth", t, this.styles.lineWidth); 61 | context.strokeStyle = this.getColor("strokeStyle", t, this.styles.strokeStyle); 62 | context.fillStyle = this.getColor("fillStyle", t, this.styles.fillStyle); 63 | context.lineCap = this.getString("lineCap", t, this.styles.lineCap); 64 | context.lineJoin = this.getString("lineJoin", t, this.styles.lineJoin); 65 | context.miterLimit = this.getString("miterLimit", t, this.styles.miterLimit); 66 | context.globalAlpha = this.getNumber("globalAlpha", t, this.styles.globalAlpha); 67 | context.translate(this.getNumber("translationX", t, this.styles.translationX), this.getNumber("translationY", t, this.styles.translationY)); 68 | context.globalCompositeOperation = this.getString("blendMode", t, this.styles.blendMode); 69 | var shake = this.getNumber("shake", t, this.styles.shake); 70 | context.translate(Math.random() * shake - shake / 2, Math.random() * shake - shake / 2); 71 | 72 | var lineDash = this.getArray("lineDash", t, this.styles.lineDash); 73 | if(lineDash) { 74 | context.setLineDash(lineDash); 75 | } 76 | context.beginPath(); 77 | }, 78 | 79 | drawFillAndStroke: function(context, t, doFill, doStroke) { 80 | var fill = this.getBool("fill", t, doFill), 81 | stroke = this.getBool("stroke", t, doStroke); 82 | 83 | context.save(); 84 | if(fill) { 85 | this.setShadowParams(context, t); 86 | context.fill(); 87 | } 88 | context.restore(); 89 | if(stroke) { 90 | if(!fill) { 91 | this.setShadowParams(context, t); 92 | } 93 | context.stroke(); 94 | } 95 | }, 96 | 97 | setShadowParams: function(context, t) { 98 | context.shadowColor = this.getColor("shadowColor", t, this.styles.shadowColor); 99 | context.shadowOffsetX = this.getNumber("shadowOffsetX", t, this.styles.shadowOffsetX); 100 | context.shadowOffsetY = this.getNumber("shadowOffsetY", t, this.styles.shadowOffsetY); 101 | context.shadowBlur = this.getNumber("shadowBlur", t, this.styles.shadowBlur); 102 | }, 103 | 104 | endDraw: function(context) { 105 | context.restore(); 106 | }, 107 | 108 | getNumber: function(prop, t, def) { 109 | return valueParser.getNumber(this.props[prop], t, def); 110 | }, 111 | 112 | getColor: function(prop, t, def) { 113 | return colorParser.getColor(this.props[prop], t, def); 114 | }, 115 | 116 | getString: function(prop, t, def) { 117 | return valueParser.getString(this.props[prop], t, def); 118 | }, 119 | 120 | getBool: function(prop, t, def) { 121 | return valueParser.getBool(this.props[prop], t, def); 122 | }, 123 | 124 | getArray: function(prop, t, def) { 125 | return valueParser.getArray(this.props[prop], t, def); 126 | }, 127 | 128 | getObject: function(prop, t, def) { 129 | return valueParser.getObject(this.props[prop], t, def); 130 | }, 131 | 132 | getPosition: function(prop, t, def) { 133 | return valueParser.getPosition(this.props[prop], t, def); 134 | } 135 | } 136 | }); 137 | -------------------------------------------------------------------------------- /glc/app/glc.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "app/renderlist", 3 | "app/scheduler", 4 | "app/styles", 5 | "app/interpolation", 6 | "libs/quicksettings", 7 | "libs/GIFEncoder", 8 | "libs/color", 9 | "app/ui/controlpanel", 10 | "app/ui/creditspanel", 11 | "app/ui/infopanel", 12 | "app/ui/canvaspanel", 13 | "app/ui/outputpanel"], 14 | 15 | function( 16 | renderList, 17 | scheduler, 18 | styles, 19 | interpolation, 20 | QuickSettings, 21 | GIFEncoder, 22 | color, 23 | controlPanel, 24 | creditsPanel, 25 | infoPanel, 26 | canvasPanel, 27 | outputPanel) { 28 | 29 | // this could be a module too. 30 | // ideally it wouldn't know about scheduler directly 31 | var model = { 32 | // interpolation could just be absorbed into model 33 | interpolation: interpolation, 34 | maxColors: 256, 35 | w: 400, 36 | h: 400, 37 | capture: false, 38 | getDuration: function() { 39 | return scheduler.getDuration(); 40 | }, 41 | setDuration: function(value) { 42 | scheduler.setDuration(value); 43 | }, 44 | getFPS: function() { 45 | return scheduler.getFPS(); 46 | }, 47 | setFPS: function(value) { 48 | scheduler.setFPS(value); 49 | }, 50 | getIsRunning: function() { 51 | return scheduler.isRunning(); 52 | } 53 | }; 54 | 55 | // this could be a module that knows about all panels + schedule 56 | var controller = { 57 | playOnce: scheduler.playOnce, 58 | loop: scheduler.loop, 59 | stop: scheduler.stop, 60 | enableControls: enableControls, 61 | disableControls: disableControls, 62 | clearOutput: outputPanel.clearOutput, 63 | captureStill: captureStill, 64 | showCredits: creditsPanel.show, 65 | renderFrame: renderList.render, 66 | startEncoder: startEncoder 67 | }; 68 | 69 | function init() { 70 | loadCSS(); 71 | renderList.init(model.w, model.h, styles, interpolation); 72 | scheduler.init(onRender, onComplete); 73 | canvasPanel.init(model, controller, renderList.getCanvas()); 74 | outputPanel.init(model, controller); 75 | infoPanel.init(model, controller); 76 | creditsPanel.init(); 77 | controlPanel.init(model, controller); 78 | setCallbacks(); 79 | } 80 | 81 | function size(width, height) { 82 | this.w = model.w = width; 83 | this.h = model.h = height; 84 | renderList.size(model.w, model.h); 85 | canvasPanel.setWidth(model.w + 12); 86 | outputPanel.setWidth(model.w + 12); 87 | controlPanel.setPosition(model.w + 50, 20); 88 | infoPanel.setPosition(model.w + 50, 350); 89 | outputPanel.setPosition(model.w + 220, 20); 90 | } 91 | 92 | function loadCSS() { 93 | var head = document.getElementsByTagName('head')[0], 94 | link = document.createElement('link'); 95 | link.type = 'text/css'; 96 | link.rel = 'stylesheet'; 97 | link.href = require.toUrl("libs/quicksettings_minimal.css"); 98 | head.appendChild(link); 99 | } 100 | 101 | ///////////////////// 102 | // callback methods 103 | ///////////////////// 104 | 105 | function setCallbacks() { 106 | scheduler.renderCallback = onRender; 107 | scheduler.completeCallback = onComplete; 108 | } 109 | 110 | 111 | function onRender(t) { 112 | canvasPanel.setTime(t); 113 | renderList.render(t); 114 | if(model.capture) { 115 | controlPanel.setStatus("capturing..."); 116 | GIFEncoder.addFrame(renderList.getContext()); 117 | } 118 | } 119 | 120 | function onComplete() { 121 | if(model.capture) { 122 | model.capture = false; 123 | GIFEncoder.finish(); 124 | outputPanel.setGIF(GIFEncoder.stream().getData()); 125 | } 126 | controlPanel.setStatus("stopped"); 127 | controller.enableControls(); 128 | } 129 | 130 | ///////////////////// 131 | // controller methods 132 | ///////////////////// 133 | 134 | function enableControls() { 135 | controlPanel.enableControls(); 136 | canvasPanel.enableControls(); 137 | } 138 | 139 | function disableControls() { 140 | controlPanel.disableControls(); 141 | canvasPanel.disableControls(); 142 | } 143 | 144 | function captureStill() { 145 | var canvas = renderList.getCanvas(), 146 | dataURL = canvas.toDataURL(); 147 | outputPanel.setWidth(model.w + 12); 148 | outputPanel.setPNG(dataURL); 149 | } 150 | 151 | function startEncoder() { 152 | GIFEncoder.setMaxColors(model.maxColors); 153 | GIFEncoder.setRepeat(0); 154 | GIFEncoder.setDelay(1000 / scheduler.getFPS()); 155 | GIFEncoder.start(); 156 | } 157 | 158 | 159 | var glc = { 160 | w: model.w, 161 | h: model.h, 162 | renderList: renderList, 163 | styles: styles, 164 | size: size, 165 | playOnce: controlPanel.playOnce, 166 | loop: controlPanel.loop, 167 | setFPS: controlPanel.setFPS, 168 | setDuration: controlPanel.setDuration, 169 | setMode: controlPanel.setMode, 170 | setEasing: controlPanel.setEasing, 171 | setMaxColors: controlPanel.setMaxColors, 172 | color: color 173 | }; 174 | 175 | init(); 176 | 177 | return glc; 178 | }); 179 | -------------------------------------------------------------------------------- /glc/app/renderlist.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "app/shapes/shape", 3 | "app/shapes/arrow", 4 | "app/shapes/arcSegment", 5 | "app/shapes/beziercurve", 6 | "app/shapes/beziersegment", 7 | "app/shapes/circle", 8 | "app/shapes/cube", 9 | "app/shapes/curve", 10 | "app/shapes/curvesegment", 11 | "app/shapes/gear", 12 | "app/shapes/grid", 13 | "app/shapes/heart", 14 | "app/shapes/line", 15 | "app/shapes/oval", 16 | "app/shapes/path", 17 | "app/shapes/poly", 18 | "app/shapes/ray", 19 | "app/shapes/raysegment", 20 | "app/shapes/rect", 21 | "app/shapes/segment", 22 | "app/shapes/spiral", 23 | "app/shapes/star", 24 | "app/shapes/text", 25 | ], 26 | function( 27 | Shape, 28 | Arrow, 29 | ArcSegment, 30 | BezierCurve, 31 | BezierSegment, 32 | Circle, 33 | Cube, 34 | Curve, 35 | CurveSegment, 36 | Gear, 37 | Grid, 38 | Heart, 39 | Line, 40 | Oval, 41 | Path, 42 | Poly, 43 | Ray, 44 | RaySegment, 45 | Rect, 46 | Segment, 47 | Spiral, 48 | Star, 49 | Text 50 | ) { 51 | 52 | 53 | var canvas = null, 54 | context = null, 55 | width = 0, 56 | height = 0, 57 | list = [], 58 | styles = null; 59 | 60 | function init(w, h, stylesValue, interpolation) { 61 | canvas = document.createElement("canvas"); 62 | width = canvas.width = w; 63 | height = canvas.height = h; 64 | context = canvas.getContext("2d"); 65 | styles = stylesValue 66 | Shape.styles = styles; 67 | Shape.interpolation = interpolation; 68 | } 69 | 70 | function size(w, h) { 71 | width = canvas.width = w; 72 | height = canvas.height = h; 73 | } 74 | 75 | function add(item) { 76 | list.push(item); 77 | render(0); 78 | } 79 | 80 | function clear() { 81 | list.length = 0; 82 | } 83 | 84 | function addArrow(props) { 85 | add(Shape.create(Arrow, props)); 86 | } 87 | 88 | function addArcSegment(props) { 89 | add(Shape.create(ArcSegment, props)); 90 | } 91 | 92 | function addBezierCurve(props) { 93 | add(Shape.create(BezierCurve, props)); 94 | } 95 | 96 | function addBezierSegment(props) { 97 | add(Shape.create(BezierSegment, props)); 98 | } 99 | 100 | function addCircle(props) { 101 | add(Shape.create(Circle, props)); 102 | } 103 | 104 | function addCube(props) { 105 | add(Shape.create(Cube, props)); 106 | } 107 | 108 | function addCurve(props) { 109 | add(Shape.create(Curve, props)); 110 | } 111 | 112 | function addCurveSegment(props) { 113 | add(Shape.create(CurveSegment, props)); 114 | } 115 | 116 | function addGear(props) { 117 | add(Shape.create(Gear, props)); 118 | } 119 | 120 | function addGrid(props) { 121 | add(Shape.create(Grid, props)); 122 | } 123 | 124 | function addHeart(props) { 125 | add(Shape.create(Heart, props)); 126 | } 127 | 128 | function addLine(props) { 129 | add(Shape.create(Line, props)); 130 | } 131 | 132 | function addOval(props) { 133 | add(Shape.create(Oval, props)); 134 | } 135 | 136 | function addPath(props) { 137 | add(Shape.create(Path, props)); 138 | } 139 | 140 | function addPoly(props) { 141 | add(Shape.create(Poly, props)); 142 | } 143 | 144 | function addRay(props) { 145 | add(Shape.create(Ray, props)); 146 | } 147 | 148 | function addRaySegment(props) { 149 | add(Shape.create(RaySegment, props)); 150 | } 151 | 152 | function addRect(props) { 153 | add(Shape.create(Rect, props)); 154 | } 155 | 156 | function addSegment(props) { 157 | add(Shape.create(Segment, props)); 158 | } 159 | 160 | function addSpiral(props) { 161 | add(Shape.create(Spiral, props)); 162 | } 163 | 164 | function addStar(props) { 165 | add(Shape.create(Star, props)); 166 | } 167 | 168 | function addText(props) { 169 | add(Shape.create(Text, props)); 170 | } 171 | 172 | function render(t) { 173 | if(styles.backgroundColor === "transparent") { 174 | context.clearRect(0, 0, width, height); 175 | } 176 | else { 177 | context.fillStyle = styles.backgroundColor; 178 | context.fillRect(0, 0, width, height); 179 | } 180 | for(var i = 0; i < list.length; i++) { 181 | list[i].render(context, t); 182 | } 183 | } 184 | 185 | function getCanvas() { 186 | return canvas; 187 | } 188 | 189 | function getContext() { 190 | return context; 191 | } 192 | 193 | return { 194 | init: init, 195 | size: size, 196 | getCanvas: getCanvas, 197 | getContext: getContext, 198 | add: add, 199 | clear: clear, 200 | addArrow: addArrow, 201 | addArcSegment: addArcSegment, 202 | addBezierCurve: addBezierCurve, 203 | addBezierSegment: addBezierSegment, 204 | addCircle: addCircle, 205 | addCube: addCube, 206 | addCurve: addCurve, 207 | addCurveSegment: addCurveSegment, 208 | addGear: addGear, 209 | addGrid: addGrid, 210 | addHeart: addHeart, 211 | addLine: addLine, 212 | addOval: addOval, 213 | addPath: addPath, 214 | addPoly: addPoly, 215 | addRay: addRay, 216 | addRaySegment: addRaySegment, 217 | addRect: addRect, 218 | addSegment: addSegment, 219 | addSpiral: addSpiral, 220 | addStar: addStar, 221 | addText: addText, 222 | render: render 223 | }; 224 | 225 | }); 226 | -------------------------------------------------------------------------------- /glc/libs/LZWEncoder.js: -------------------------------------------------------------------------------- 1 | /* 2 | LZWEncoder.js 3 | 4 | Authors 5 | Kevin Weiner (original Java version - kweiner@fmsware.com) 6 | Thibault Imbert (AS3 version - bytearray.org) 7 | Johan Nordberg (JS version - code@johan-nordberg.com) 8 | 9 | Acknowledgements 10 | GIFCOMPR.C - GIF Image compression routines 11 | Lempel-Ziv compression based on 'compress'. GIF modifications by 12 | David Rowley (mgardi@watdcsu.waterloo.edu) 13 | GIF Image compression - modified 'compress' 14 | Based on: compress.c - File compression ala IEEE Computer, June 1984. 15 | By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) 16 | Jim McKie (decvax!mcvax!jim) 17 | Steve Davies (decvax!vax135!petsd!peora!srd) 18 | Ken Turkowski (decvax!decwrl!turtlevax!ken) 19 | James A. Woods (decvax!ihnp4!ames!jaw) 20 | Joe Orost (decvax!vax135!petsd!joe) 21 | Keith Peters (slight mods to make it work with require.js - bit-101.com) 22 | */ 23 | define(function() { 24 | var EOF = -1; 25 | var BITS = 12; 26 | var HSIZE = 5003; // 80% occupancy 27 | var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 28 | 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 29 | 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF]; 30 | 31 | function LZWEncoder(width, height, pixels, colorDepth) { 32 | var initCodeSize = Math.max(2, colorDepth); 33 | 34 | var accum = new Uint8Array(256); 35 | var htab = new Int32Array(HSIZE); 36 | var codetab = new Int32Array(HSIZE); 37 | 38 | var cur_accum, cur_bits = 0; 39 | var a_count; 40 | var free_ent = 0; // first unused entry 41 | var maxcode; 42 | 43 | // block compression parameters -- after all codes are used up, 44 | // and compression rate changes, start over. 45 | var clear_flg = false; 46 | 47 | // Algorithm: use open addressing double hashing (no chaining) on the 48 | // prefix code / next character combination. We do a variant of Knuth's 49 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime 50 | // secondary probe. Here, the modular division first probe is gives way 51 | // to a faster exclusive-or manipulation. Also do block compression with 52 | // an adaptive reset, whereby the code table is cleared when the compression 53 | // ratio decreases, but after the table fills. The variable-length output 54 | // codes are re-sized at this point, and a special CLEAR code is generated 55 | // for the decompressor. Late addition: construct the table according to 56 | // file size for noticeable speed improvement on small files. Please direct 57 | // questions about this implementation to ames!jaw. 58 | var g_init_bits, ClearCode, EOFCode; 59 | 60 | // Add a character to the end of the current packet, and if it is 254 61 | // characters, flush the packet to disk. 62 | function char_out(c, outs) { 63 | accum[a_count++] = c; 64 | if (a_count >= 254) flush_char(outs); 65 | } 66 | 67 | // Clear out the hash table 68 | // table clear for block compress 69 | function cl_block(outs) { 70 | cl_hash(HSIZE); 71 | free_ent = ClearCode + 2; 72 | clear_flg = true; 73 | output(ClearCode, outs); 74 | } 75 | 76 | // Reset code table 77 | function cl_hash(hsize) { 78 | for (var i = 0; i < hsize; ++i) htab[i] = -1; 79 | } 80 | 81 | function compress(init_bits, outs) { 82 | var fcode, c, i, ent, disp, hsize_reg, hshift; 83 | 84 | // Set up the globals: g_init_bits - initial number of bits 85 | g_init_bits = init_bits; 86 | 87 | // Set up the necessary values 88 | clear_flg = false; 89 | n_bits = g_init_bits; 90 | maxcode = MAXCODE(n_bits); 91 | 92 | ClearCode = 1 << (init_bits - 1); 93 | EOFCode = ClearCode + 1; 94 | free_ent = ClearCode + 2; 95 | 96 | a_count = 0; // clear packet 97 | 98 | ent = nextPixel(); 99 | 100 | hshift = 0; 101 | for (fcode = HSIZE; fcode < 65536; fcode *= 2) ++hshift; 102 | hshift = 8 - hshift; // set hash code range bound 103 | hsize_reg = HSIZE; 104 | cl_hash(hsize_reg); // clear hash table 105 | 106 | output(ClearCode, outs); 107 | 108 | outer_loop: while ((c = nextPixel()) != EOF) { 109 | fcode = (c << BITS) + ent; 110 | i = (c << hshift) ^ ent; // xor hashing 111 | if (htab[i] === fcode) { 112 | ent = codetab[i]; 113 | continue; 114 | } else if (htab[i] >= 0) { // non-empty slot 115 | disp = hsize_reg - i; // secondary hash (after G. Knott) 116 | if (i === 0) disp = 1; 117 | do { 118 | if ((i -= disp) < 0) i += hsize_reg; 119 | if (htab[i] === fcode) { 120 | ent = codetab[i]; 121 | continue outer_loop; 122 | } 123 | } while (htab[i] >= 0); 124 | } 125 | output(ent, outs); 126 | ent = c; 127 | if (free_ent < 1 << BITS) { 128 | codetab[i] = free_ent++; // code -> hashtable 129 | htab[i] = fcode; 130 | } else { 131 | cl_block(outs); 132 | } 133 | } 134 | 135 | // Put out the final code. 136 | output(ent, outs); 137 | output(EOFCode, outs); 138 | } 139 | 140 | function encode(outs) { 141 | outs.writeByte(initCodeSize); // write "initial code size" byte 142 | remaining = width * height; // reset navigation variables 143 | curPixel = 0; 144 | compress(initCodeSize + 1, outs); // compress and write the pixel data 145 | outs.writeByte(0); // write block terminator 146 | } 147 | 148 | // Flush the packet to disk, and reset the accumulator 149 | function flush_char(outs) { 150 | if (a_count > 0) { 151 | outs.writeByte(a_count); 152 | outs.writeBytes(accum, 0, a_count); 153 | a_count = 0; 154 | } 155 | } 156 | 157 | function MAXCODE(n_bits) { 158 | return (1 << n_bits) - 1; 159 | } 160 | 161 | // Return the next pixel from the image 162 | function nextPixel() { 163 | if (remaining === 0) return EOF; 164 | --remaining; 165 | var pix = pixels[curPixel++]; 166 | return pix & 0xff; 167 | } 168 | 169 | function output(code, outs) { 170 | cur_accum &= masks[cur_bits]; 171 | 172 | if (cur_bits > 0) cur_accum |= (code << cur_bits); 173 | else cur_accum = code; 174 | 175 | cur_bits += n_bits; 176 | 177 | while (cur_bits >= 8) { 178 | char_out((cur_accum & 0xff), outs); 179 | cur_accum >>= 8; 180 | cur_bits -= 8; 181 | } 182 | 183 | // If the next entry is going to be too big for the code size, 184 | // then increase it, if possible. 185 | if (free_ent > maxcode || clear_flg) { 186 | if (clear_flg) { 187 | maxcode = MAXCODE(n_bits = g_init_bits); 188 | clear_flg = false; 189 | } else { 190 | ++n_bits; 191 | if (n_bits == BITS) maxcode = 1 << BITS; 192 | else maxcode = MAXCODE(n_bits); 193 | } 194 | } 195 | 196 | if (code == EOFCode) { 197 | // At EOF, write the rest of the buffer. 198 | while (cur_bits > 0) { 199 | char_out((cur_accum & 0xff), outs); 200 | cur_accum >>= 8; 201 | cur_bits -= 8; 202 | } 203 | flush_char(outs); 204 | } 205 | } 206 | 207 | this.encode = encode; 208 | } 209 | return LZWEncoder; 210 | }); 211 | -------------------------------------------------------------------------------- /glc/app/colorparser.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | var context = document.createElement("canvas").getContext("2d"); 3 | 4 | function getColor(prop, t, def) { 5 | if(prop === undefined) { 6 | return def; 7 | } 8 | if(typeof(prop) === "string") { 9 | if(prop.charAt(0) === "#" && prop.length > 7) { 10 | var obj = getColorObj(prop); 11 | return getColorString(obj); 12 | } 13 | return prop; 14 | } 15 | else if(typeof(prop) === "function") { 16 | return prop(t); 17 | } 18 | else if(prop && prop.length === 2) { 19 | if(isLinearGradient(prop)) { 20 | return parseLinearGradient(prop, t); 21 | } 22 | if(isRadialGradient(prop)) { 23 | return parseRadialGradient(prop, t); 24 | } 25 | var c0 = getColorObj(prop[0]), 26 | c1 = getColorObj(prop[1]); 27 | return interpolateColor([c0, c1], t); 28 | } 29 | else if(prop && prop.length) { 30 | return prop[Math.round(t * (prop.length - 1))]; 31 | } 32 | if(prop.type === "linearGradient") { 33 | var g = context.createLinearGradient(prop.x0, prop.y0, prop.x1, prop.y1); 34 | for(var i = 0; i < prop.colorStops.length; i++) { 35 | var stop = prop.colorStops[i]; 36 | g.addColorStop(stop.position, stop.color); 37 | } 38 | return g; 39 | } 40 | if(prop.type === "radialGradient") { 41 | var g = context.createRadialGradient(prop.x0, prop.y0, prop.r0, prop.x1, prop.y1, prop.r1); 42 | for(var i = 0; i < prop.colorStops.length; i++) { 43 | var stop = prop.colorStops[i]; 44 | g.addColorStop(stop.position, stop.color); 45 | } 46 | return g; 47 | } 48 | return def; 49 | } 50 | 51 | function isLinearGradient(prop) { 52 | return prop[0].type === "linearGradient" && prop[1].type === "linearGradient"; 53 | } 54 | 55 | function parseLinearGradient(prop, t) { 56 | var g0 = prop[0], 57 | g1 = prop[1], 58 | x0 = g0.x0 + (g1.x0 - g0.x0) * t, 59 | y0 = g0.y0 + (g1.y0 - g0.y0) * t, 60 | x1 = g0.x1 + (g1.x1 - g0.x1) * t, 61 | y1 = g0.y1 + (g1.y1 - g0.y1) * t; 62 | 63 | var g = context.createLinearGradient(x0, y0, x1, y1); 64 | for(var i = 0; i < g0.colorStops.length; i++) { 65 | var stopA = g0.colorStops[i], 66 | stopB = g1.colorStops[i], 67 | position = stopA.position + (stopB.position - stopA.position) * t, 68 | colorA = getColorObj(stopA.color), 69 | colorB = getColorObj(stopB.color), 70 | color = interpolateColor([colorA, colorB], t); 71 | g.addColorStop(position, color); 72 | } 73 | return g; 74 | } 75 | 76 | function isRadialGradient(prop) { 77 | return prop[0].type === "radialGradient" && prop[1].type === "radialGradient"; 78 | } 79 | 80 | function parseRadialGradient(prop, t) { 81 | var g0 = prop[0], 82 | g1 = prop[1], 83 | x0 = g0.x0 + (g1.x0 - g0.x0) * t, 84 | y0 = g0.y0 + (g1.y0 - g0.y0) * t, 85 | r0 = g0.r0 + (g1.r0 - g0.r0) * t, 86 | x1 = g0.x1 + (g1.x1 - g0.x1) * t, 87 | y1 = g0.y1 + (g1.y1 - g0.y1) * t, 88 | r1 = g0.r1 + (g1.r1 - g0.r1) * t; 89 | 90 | var g = context.createRadialGradient(x0, y0, r0, x1, y1, r1); 91 | for(var i = 0; i < g0.colorStops.length; i++) { 92 | var stopA = g0.colorStops[i], 93 | stopB = g1.colorStops[i], 94 | position = stopA.position + (stopB.position - stopA.position) * t, 95 | colorA = getColorObj(stopA.color), 96 | colorB = getColorObj(stopB.color), 97 | color = interpolateColor([colorA, colorB], t); 98 | g.addColorStop(position, color); 99 | } 100 | return g; 101 | } 102 | 103 | function getColorString(obj) { 104 | return "rgba(" + obj.r + "," + obj.g + "," + obj.b + "," + (obj.a / 255) + ")"; 105 | } 106 | 107 | 108 | 109 | function getColorObj(color) { 110 | if(color.charAt(0) === "#") { 111 | if(color.length === 7) { // #rrggbb 112 | return { 113 | a: 255, 114 | r: parseInt(color.substring(1, 3), 16), 115 | g: parseInt(color.substring(3, 5), 16), 116 | b: parseInt(color.substring(5, 7), 16) 117 | } 118 | } 119 | else if(color.length === 9) { // #aarrggbb 120 | return { 121 | a: parseInt(color.substring(1, 3), 16), 122 | r: parseInt(color.substring(3, 5), 16), 123 | g: parseInt(color.substring(5, 7), 16), 124 | b: parseInt(color.substring(7, 9), 16) 125 | } 126 | } 127 | else { // #rgb 128 | var r = color.charAt(1), 129 | g = color.charAt(2), 130 | b = color.charAt(3); 131 | 132 | return { 133 | a: 255, 134 | r: parseInt(r + r, 16), 135 | g: parseInt(g + g, 16), 136 | b: parseInt(b + b, 16), 137 | } 138 | } 139 | } 140 | else if(color.substring(0, 4) === "rgb(") { 141 | var s = color.indexOf("(") + 1, 142 | e = color.indexOf(")"), 143 | channels = color.substring(s, e).split(","); 144 | return { 145 | a: 255, 146 | r: parseInt(channels[0], 10), 147 | g: parseInt(channels[1], 10), 148 | b: parseInt(channels[2], 10), 149 | } 150 | } 151 | else if(color.substring(0, 4) === "rgba") { 152 | var s = color.indexOf("(") + 1, 153 | e = color.indexOf(")"), 154 | channels = color.substring(s, e).split(","); 155 | return { 156 | a: parseFloat(channels[3]) * 255, 157 | r: parseInt(channels[0], 10), 158 | g: parseInt(channels[1], 10), 159 | b: parseInt(channels[2], 10), 160 | } 161 | } 162 | else { 163 | color = color.toLowerCase(); 164 | if(namedColors[color] != null) { 165 | return getColorObj(namedColors[color]); 166 | } 167 | } 168 | return 0; 169 | } 170 | 171 | function interpolateColor(arr, t) { 172 | var c0 = arr[0], 173 | c1 = arr[1]; 174 | 175 | var alpha = c0.a + (c1.a - c0.a) * t, 176 | red = Math.round(c0.r + (c1.r - c0.r) * t), 177 | green = Math.round(c0.g + (c1.g - c0.g) * t), 178 | blue = Math.round(c0.b + (c1.b - c0.b) * t); 179 | return "rgba(" + red + "," + green + "," + blue + "," + (alpha / 255) + ")"; 180 | } 181 | 182 | var namedColors = { 183 | aliceblue: "#f0f8ff", 184 | antiquewhite: "#faebd7", 185 | aqua: "#00ffff", 186 | aquamarine: "#7fffd4", 187 | azure: "#f0ffff", 188 | beige: "#f5f5dc", 189 | bisque: "#ffe4c4", 190 | black: "#000000", 191 | blanchedalmond: "#ffebcd", 192 | blue: "#0000ff", 193 | blueviolet: "#8a2be2", 194 | brown: "#a52a2a", 195 | burlywood: "#deb887", 196 | cadetblue: "#5f9ea0", 197 | chartreuse: "#7fff00", 198 | chocolate: "#d2691e", 199 | coral: "#ff7f50", 200 | cornflowerblue: "#6495ed", 201 | cornsilk: "#fff8dc", 202 | crimson: "#dc143c", 203 | cyan: "#00ffff", 204 | darkblue: "#00008b", 205 | darkcyan: "#008b8b", 206 | darkgoldenrod: "#b8860b", 207 | darkgray: "#a9a9a9", 208 | darkgrey: "#a9a9a9", 209 | darkgreen: "#006400", 210 | darkkhaki: "#bdb76b", 211 | darkmagenta: "#8b008b", 212 | darkolivegreen: "#556b2f", 213 | darkorange: "#ff8c00", 214 | darkorchid: "#9932cc", 215 | darkred: "#8b0000", 216 | darksalmon: "#e9967a", 217 | darkseagreen: "#8fbc8f", 218 | darkslateblue: "#483d8b", 219 | darkslategray: "#2f4f4f", 220 | darkslategrey: "#2f4f4f", 221 | darkturquoise: "#00ced1", 222 | darkviolet: "#9400d3", 223 | deeppink: "#ff1493", 224 | deepskyblue: "#00bfff", 225 | dimgray: "#696969", 226 | dimgrey: "#696969", 227 | dodgerblue: "#1e90ff", 228 | firebrick: "#b22222", 229 | floralwhite: "#fffaf0", 230 | forestgreen: "#228b22", 231 | fuchsia: "#ff00ff", 232 | gainsboro: "#dcdcdc", 233 | ghostwhite: "#f8f8ff", 234 | gold: "#ffd700", 235 | goldenrod: "#daa520", 236 | gray: "#808080", 237 | grey: "#808080", 238 | green: "#008000", 239 | greenyellow: "#adff2f", 240 | honeydew: "#f0fff0", 241 | hotpink: "#ff69b4", 242 | indianred: "#cd5c5c", 243 | indigo: "#4b0082", 244 | ivory: "#fffff0", 245 | khaki: "#f0e68c", 246 | lavender: "#e6e6fa", 247 | lavenderblush: "#fff0f5", 248 | lawngreen: "#7cfc00", 249 | lemonchiffon: "#fffacd", 250 | lightblue: "#add8e6", 251 | lightcoral: "#f08080", 252 | lightcyan: "#e0ffff", 253 | lightgoldenrodyellow: "#fafad2", 254 | lightgray: "#d3d3d3", 255 | lightgrey: "#d3d3d3", 256 | lightgreen: "#90ee90", 257 | lightpink: "#ffb6c1", 258 | lightsalmon: "#ffa07a", 259 | lightseagreen: "#20b2aa", 260 | lightskyblue: "#87cefa", 261 | lightslategray: "#778899", 262 | lightslategrey: "#778899", 263 | lightsteelblue: "#b0c4de", 264 | lightyellow: "#ffffe0", 265 | lime: "#00ff00", 266 | limegreen: "#32cd32", 267 | linen: "#faf0e6", 268 | magenta: "#ff00ff", 269 | maroon: "#800000", 270 | mediumaquamarine: "#66cdaa", 271 | mediumblue: "#0000cd", 272 | mediumorchid: "#ba55d3", 273 | mediumpurple: "#9370d8", 274 | mediumseagreen: "#3cb371", 275 | mediumslateblue: "#7b68ee", 276 | mediumspringgreen: "#00fa9a", 277 | mediumturquoise: "#48d1cc", 278 | mediumvioletred: "#c71585", 279 | midnightblue: "#191970", 280 | mintcream: "#f5fffa", 281 | mistyrose: "#ffe4e1", 282 | moccasin: "#ffe4b5", 283 | navajowhite: "#ffdead", 284 | navy: "#000080", 285 | oldlace: "#fdf5e6", 286 | olive: "#808000", 287 | olivedrab: "#6b8e23", 288 | orange: "#ffa500", 289 | orangered: "#ff4500", 290 | orchid: "#da70d6", 291 | palegoldenrod: "#eee8aa", 292 | palegreen: "#98fb98", 293 | paleturquoise: "#afeeee", 294 | palevioletred: "#d87093", 295 | papayawhip: "#ffefd5", 296 | peachpuff: "#ffdab9", 297 | peru: "#cd853f", 298 | pink: "#ffc0cb", 299 | plum: "#dda0dd", 300 | powderblue: "#b0e0e6", 301 | purple: "#800080", 302 | red: "#ff0000", 303 | rosybrown: "#bc8f8f", 304 | royalblue: "#4169e1", 305 | saddlebrown: "#8b4513", 306 | salmon: "#fa8072", 307 | sandybrown: "#f4a460", 308 | seagreen: "#2e8b57", 309 | seashell: "#fff5ee", 310 | sienna: "#a0522d", 311 | silver: "#c0c0c0", 312 | skyblue: "#87ceeb", 313 | slateblue: "#6a5acd", 314 | slategray: "#708090", 315 | slategrey: "#708090", 316 | snow: "#fffafa", 317 | springgreen: "#00ff7f", 318 | steelblue: "#4682b4", 319 | tan: "#d2b48c", 320 | teal: "#008080", 321 | thistle: "#d8bfd8", 322 | tomato: "#ff6347", 323 | turquoise: "#40e0d0", 324 | violet: "#ee82ee", 325 | wheat: "#f5deb3", 326 | white: "#ffffff", 327 | whitesmoke: "#f5f5f5", 328 | yellow: "#ffff00", 329 | yellowgreen: "#9acd32", 330 | } 331 | 332 | return { 333 | getColor: getColor 334 | }; 335 | }); -------------------------------------------------------------------------------- /docs/properties.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GIF Loop Coder Documentation 5 | 6 | 7 | 8 | Home 9 | 10 |

GIF Loop Coder (GLC) Documentation

11 | 27 | 28 | 29 |

4. Properties

30 | 31 |
32 | 33 |

This section discusses the different types of properties, and the kind of data that you can assign to each one of them.

34 | 35 | 36 |

Number

37 | 38 |
39 | 40 |

A number property is pretty simple to understand. You can assign a single number of course:

41 | 42 | 43 |

 44 | radius: 100
 45 |     
46 | 47 |

This will result in that property having that value at all times.

48 | 49 |

Or you can assign a 2-element array:

50 | 51 |

 52 | radius: [100, 200]
 53 |     
54 | 55 |

This smoothly interpolates the value from the first element to the second one over the course of the animation.

56 | 57 |

An additional method is to assign a larger array of values to the property:

58 | 59 |

 60 | radius: [10, 40, 70, 100]
 61 |     
62 | 63 |

In this case, the property will cycle through the values in the array as the animation progresses. Note, that it will NOT smoothly interpolate between the values. In the example above where there are four values, the first value will be used while t is between 0 and 0.25. When t is between 0.25 and 0.5, the second value will be assigned to the property, and so on, with the third value for t between 0.5 and 0.75 and value four for 0.75 - 1.0

64 | 65 | 66 | 67 |

Finally, if none of these work for you, you can assign a function to the property, in the format:

68 | 69 |

 70 | radius: function(t) {
 71 |     return someValue;
 72 | }
 73 |     
74 | 75 |

This is the most powerful method, allowing you to do whatever calculations you want based on the t value, and return any value you want. For example, the following code uses a sine wave to oscillate and object back and forth across the canvas, based on t:

76 | 77 |

 78 | list.addCircle({
 79 |     x: function(t) {
 80 |         return 100 + Math.sin(t * Math.PI * 2) * 100;
 81 |     },
 82 |     y: [0, 200],
 83 |     radius: 20
 84 | });
 85 |     
86 | 87 | 88 | 89 | 90 |

Remember that t will vary from 0 to 1, so here we multiply it by Math.PI * 2 to get an angle that will complete a full cycle of the sine wave.

91 | 92 |
93 | 94 | 95 |

String

96 | 97 |
98 | 99 |

A string property is also quite simple. Again, you can assign a simple string:

100 | 101 |

102 | text: "hello"
103 |     
104 | 105 |

You can also assign an array of values to the property:

106 | 107 |

108 | text: ["hello", "goodbye"]
109 |     
110 | 111 |

or...

112 | 113 |

114 | text: ["small", "medium", "large"]
115 |     
116 | 117 |

This will function in the same way as a number array, with the difference that even a 2-element array will work this way.

118 | 119 |

In the first example, text will be assigned the string "hello" for the first half of the animation, and "goodbye" for the last half.

120 | 121 |

In the second example, text will be assigned "small" for the first third of the animation, "medium" for the middle third, and "large" for the last third

122 | 123 | 124 | 125 | 126 |

And again you can assign a function to the property, in the format:

127 | 128 |

129 | text: function(t) {
130 |     return someString;
131 | }
132 |     
133 | 134 |

For example:

135 | 136 |

137 | text: function(t) {
138 |     return "t = " + t;
139 | }
140 |     
141 | 142 | 143 | 144 | 145 |

This will return a string telling you the current value of t, for whatever that's worth. The sky is the limit here.

146 |
147 | 148 | 149 | 150 |

Color

151 | 152 |
153 | 154 |

You can simply assign any valid color string, as you would in addressing Canvas directly. Examples:

155 | 156 |

Hex strings:

157 | 158 |

159 | fillStyle: "#ff0000"
160 |     
161 | 162 |

or...

163 | 164 |

165 | fillStyle: "#f00"
166 |     
167 | 168 |

rgb strings:

169 | 170 |

171 | fillStyle: "rgb(255, 0, 0)"
172 |     
173 | 174 |

rgba strings:

175 | 176 |

177 | fillStyle: "rgba(255, 0, 0, 0.5)"
178 |     
179 | 180 |

CSS color named color strings:

181 | 182 |

183 | fillStyle: "fuchsia"
184 |     
185 | 186 | 187 |

To smoothly animate between two colors, supply a 2-element array:

188 | 189 |

190 | fillStyle: ["#ff0000", "#00ff00"]
191 |     
192 | 193 |

This works with ALL kinds of strings described above. You can even mix and match them. All of the following are exactly equivalent and will create a smooth interpolation:

194 | 195 |

196 | fillStyle: ["#ff0000", "green"]
197 | fillStyle: ["#f00", "rgb(0, 255, 0)"]
198 | fillStyle: ["red", "rgba(0, 255, 0, 1)"]
199 | fillStyle: ["rgb(255, 0, 0)", "#00ff00"]
200 | fillStyle: ["rgba(255, 0, 0, 1)", "#0f0"]
201 |     
202 | 203 |

Even something as crazy as this works:

204 | 205 |

206 | fillStyle: ["burlywood", "mediumorchid"]
207 |     
208 | 209 |

Yes, that will animated between those two colors just fine. CSS color names are also case insensitive, so you can type "burlywood", "BurlyWood", "BURLYWOOD" or even "bUrLyWoOd" if you are so inclined.

210 | 211 |

A word on alpha channels. You can specify the alpha channel of a color two ways:

212 | 213 |

1. With an rgba string: "rgba(255, 0, 0, 0.5)"

214 | 215 |

2. With a hex string: "#80ff0000". 216 | 217 |

Both of the above examples will give you a red color with 50% transparency. Specifying colors any other way will give you full alpha.

218 | 219 | 220 |

"red", "#ff0000", "#f00" and "rgb(255, 0, 0)" will all give you a fully opaque red, as will "#ffff0000" and "rgba(255, 0, 0, 1)".

221 | 222 |

You can animate between alpha values with any of these combinations as well.

223 | 224 |

225 | fillStyle: ["rgba(255, 0, 0, 1)", "#00ff0000"]
226 | fillStyle: ["#f00", "rgba(255, 0, 0, 0)"]
227 | fillStyle: ["#ff0000", "#00ff0000"]
228 | fillStyle: ["#ffff0000", "rgba(255, 0, 0, 0)"]
229 |     
230 | 231 |

All of the above are equivalent and will create an animation from fully opaque red to fully transparent red.

232 | 233 |

You can also create an array with more than two elements:

234 | 235 |

236 | fillStyle: ["#ff0000", "#00ff00", "#0000ff"]
237 |     
238 | 239 | 240 | 241 |

In this case, the colors would not be interpolated, but would jump from one to the other.

242 | 243 |

And of course, you can assign a function. This function must return a valid color string! If you're using numerical values to calculate a color, you need to convert that back into a color string before returning it. You can use the color module described in the Styles section to do this easily with the color.num method. 244 | 245 | 246 |

Boolean

247 | 248 |
249 | 250 |

Boolean values are pretty simple. They're either true or false. Single values are easy:

251 | 252 |

253 | stroke: true
254 |     
255 | 256 |

Like strings, you can assign an array of any length:

257 | 258 |

259 | stroke: [true, false, true, true]
260 |     
261 | 262 |

Here, stroke would be true for the first 25% of the animation, false for the next 25% and true again for the remainder.

263 | 264 | 265 | 266 |

And like other values, you use a function:

267 | 268 |

269 | stroke: function(t) {
270 |     return trueOrFalse;
271 | }
272 |     
273 |
274 | 275 | 276 |

Array

277 | 278 |
279 | 280 |

There are two cases for the array type - the lineDash style and the path property of the Path object. Both of these require an array of values to work properly. So a single array cannot be used to create an animation. We need two arrays - one containing the starting values, and one with the end values.

281 | 282 |

So, in the case of lineDash, assigning an array does NOT create an animation. It just creates a regular line dash:

283 | 284 |

285 | lineDash: [50, 10, 20, 10]
286 |     
287 | 288 | 289 | 290 |

This will create a static line dash. To animate the dash, you need to assign an array of arrays!

291 | 292 |

293 | lineDash: [[50, 10, 20, 10], [20, 10, 50, 10]]
294 |     
295 | 296 |

This will actually animate the dash to reverse the 50-pixel and 20-pixel segments.

297 | 298 | 299 | 300 |

The same applies to the Path object. It needs an array of points. So if you want to animate a path, you need to supply a starting array, and an ending array.

301 | 302 |

And, like all the others, you can assign a function that returns an array:

303 | 304 |

305 | lineDash: function(t) {
306 |     return someArray;   
307 | }
308 |     
309 |
310 | 311 | 312 | top 313 |
314 | 315 | 316 | 317 | -------------------------------------------------------------------------------- /glc/libs/NeuQuant.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NeuQuant Neural-Net Quantization Algorithm 3 | * ------------------------------------------ 4 | * 5 | * Copyright (c) 1994 Anthony Dekker 6 | * 7 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See 8 | * "Kohonen neural networks for optimal colour quantization" in "Network: 9 | * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of 10 | * the algorithm. 11 | * 12 | * Any party obtaining a copy of these files from the author, directly or 13 | * indirectly, is granted, free of charge, a full and unrestricted irrevocable, 14 | * world-wide, paid up, royalty-free, nonexclusive right and license to deal in 15 | * this software and documentation files (the "Software"), including without 16 | * limitation the rights to use, copy, modify, merge, publish, distribute, 17 | * sublicense, and/or sell copies of the Software, and to permit persons who 18 | * receive copies from any such party to do so, with the only requirement being 19 | * that this copyright notice remain intact. 20 | */ 21 | 22 | /* 23 | * This class handles Neural-Net quantization algorithm 24 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com) 25 | * @author Thibault Imbert (AS3 version - bytearray.org) 26 | * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) 27 | * @author Keith Peters (slight mods to make it work with require.js - bit-101.com) 28 | * @version 0.1 AS3 implementation 29 | */ 30 | 31 | define(function() { 32 | 33 | var netsize = 256; /* number of colours used */ 34 | 35 | /* four primes near 500 - assume no image has a length so large */ 36 | /* that it is divisible by all four primes */ 37 | 38 | var prime1 = 499; 39 | var prime2 = 491; 40 | var prime3 = 487; 41 | var prime4 = 503; 42 | var minpicturebytes = (3 * prime4); /* minimum size for input image */ 43 | 44 | /* 45 | * Program Skeleton ---------------- [select samplefac in range 1..30] [read 46 | * image from input file] pic = (unsigned char*) malloc(3*width*height); 47 | * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output 48 | * image header, using writecolourmap(f)] inxbuild(); write output image using 49 | * inxsearch(b,g,r) 50 | */ 51 | 52 | /* 53 | * Network Definitions ------------------- 54 | */ 55 | 56 | var maxnetpos = (netsize - 1); 57 | var netbiasshift = 4; /* bias for colour values */ 58 | var ncycles = 100; /* no. of learning cycles */ 59 | 60 | /* defs for freq and bias */ 61 | var intbiasshift = 16; /* bias for fractions */ 62 | var intbias = (1 << intbiasshift); 63 | var gammashift = 10; /* gamma = 1024 */ 64 | var gamma = (1 << gammashift); 65 | var betashift = 10; 66 | var beta = (intbias >> betashift); /* beta = 1/1024 */ 67 | var betagamma = (intbias << (gammashift - betashift)); 68 | 69 | /* defs for decreasing radius factor */ 70 | var initrad = (netsize >> 3); /* for 256 cols, radius starts */ 71 | var radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ 72 | var radiusbias = (1 << radiusbiasshift); 73 | var initradius = (initrad * radiusbias); /* and decreases by a */ 74 | var radiusdec = 30; /* factor of 1/30 each cycle */ 75 | 76 | /* defs for decreasing alpha factor */ 77 | var alphabiasshift = 10; /* alpha starts at 1.0 */ 78 | var initalpha = (1 << alphabiasshift); 79 | var alphadec; /* biased by 10 bits */ 80 | 81 | /* radbias and alpharadbias used for radpower calculation */ 82 | var radbiasshift = 8; 83 | var radbias = (1 << radbiasshift); 84 | var alpharadbshift = (alphabiasshift + radbiasshift); 85 | var alpharadbias = (1 << alpharadbshift); 86 | 87 | /* 88 | * Types and Global Variables -------------------------- 89 | */ 90 | 91 | var thepicture; /* the input image itself */ 92 | var lengthcount; /* lengthcount = H*W*3 */ 93 | var samplefac; /* sampling factor 1..30 */ 94 | 95 | // typedef int pixel[4]; /* BGRc */ 96 | var network; /* the network itself - [netsize][4] */ 97 | var netindex = []; 98 | 99 | /* for network lookup - really 256 */ 100 | var bias = []; 101 | 102 | /* bias and freq arrays for learning */ 103 | var freq = []; 104 | var radpower = []; 105 | 106 | function NeuQuant(thepic, len, sample) { 107 | 108 | var i; 109 | var p; 110 | 111 | thepicture = thepic; 112 | lengthcount = len; 113 | samplefac = sample; 114 | 115 | network = new Array(netsize); 116 | 117 | for (i = 0; i < netsize; i++) { 118 | 119 | network[i] = new Array(4); 120 | p = network[i]; 121 | p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; 122 | freq[i] = intbias / netsize; /* 1/netsize */ 123 | bias[i] = 0; 124 | } 125 | this.process = process; 126 | this.map = map; 127 | this.setMaxColors = setMaxColors; 128 | 129 | } 130 | 131 | function setMaxColors(maxColors) { 132 | netsize = maxColors; 133 | maxnetpos = (netsize - 1); 134 | initrad = (netsize >> 3); /* for 256 cols, radius starts */ 135 | initradius = (initrad * radiusbias); /* and decreases by a */ 136 | } 137 | 138 | 139 | function process() { 140 | learn(); 141 | unbiasnet(); 142 | inxbuild(); 143 | return colorMap(); 144 | }; 145 | 146 | function colorMap() { 147 | 148 | var map = []; 149 | var index = new Array(netsize); 150 | 151 | for (var i = 0; i < netsize; i++) 152 | index[network[i][3]] = i; 153 | 154 | var k = 0; 155 | for (var l = 0; l < netsize; l++) { 156 | var j = index[l]; 157 | map[k++] = (network[j][0]); 158 | map[k++] = (network[j][1]); 159 | map[k++] = (network[j][2]); 160 | } 161 | 162 | return map; 163 | }; 164 | 165 | /* 166 | * Insertion sort of network and building of netindex[0..255] (to do after 167 | * unbias) 168 | * ------------------------------------------------------------------------------- 169 | */ 170 | 171 | function inxbuild() { 172 | 173 | var i; 174 | var j; 175 | var smallpos; 176 | var smallval; 177 | var p; 178 | var q; 179 | var previouscol; 180 | var startpos; 181 | 182 | previouscol = 0; 183 | startpos = 0; 184 | for (i = 0; i < netsize; i++) { 185 | 186 | p = network[i]; 187 | smallpos = i; 188 | smallval = p[1]; /* index on g */ 189 | 190 | /* find smallest in i..netsize-1 */ 191 | for (j = i + 1; j < netsize; j++) { 192 | 193 | q = network[j]; 194 | if (q[1] < smallval) { /* index on g */ 195 | smallpos = j; 196 | smallval = q[1]; /* index on g */ 197 | } 198 | } 199 | q = network[smallpos]; 200 | 201 | /* swap p (i) and q (smallpos) entries */ 202 | if (i != smallpos) { 203 | j = q[0]; 204 | q[0] = p[0]; 205 | p[0] = j; 206 | j = q[1]; 207 | q[1] = p[1]; 208 | p[1] = j; 209 | j = q[2]; 210 | q[2] = p[2]; 211 | p[2] = j; 212 | j = q[3]; 213 | q[3] = p[3]; 214 | p[3] = j; 215 | } 216 | 217 | /* smallval entry is now in position i */ 218 | 219 | if (smallval != previouscol) { 220 | 221 | netindex[previouscol] = (startpos + i) >> 1; 222 | 223 | for (j = previouscol + 1; j < smallval; j++) netindex[j] = i; 224 | 225 | previouscol = smallval; 226 | startpos = i; 227 | } 228 | } 229 | 230 | netindex[previouscol] = (startpos + maxnetpos) >> 1; 231 | for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */ 232 | }; 233 | 234 | /* 235 | * Main Learning Loop ------------------ 236 | */ 237 | 238 | function learn() { 239 | 240 | var i; 241 | var j; 242 | var b; 243 | var g; 244 | var r; 245 | var radius; 246 | var rad; 247 | var alpha; 248 | var step; 249 | var delta; 250 | var samplepixels; 251 | var p; 252 | var pix; 253 | var lim; 254 | 255 | if (lengthcount < minpicturebytes) samplefac = 1; 256 | 257 | alphadec = 30 + ((samplefac - 1) / 3); 258 | p = thepicture; 259 | pix = 0; 260 | lim = lengthcount; 261 | samplepixels = lengthcount / (3 * samplefac); 262 | delta = (samplepixels / ncycles) | 0; 263 | alpha = initalpha; 264 | radius = initradius; 265 | 266 | rad = radius >> radiusbiasshift; 267 | if (rad <= 1) rad = 0; 268 | 269 | for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); 270 | 271 | if (lengthcount < minpicturebytes) step = 3; 272 | 273 | else if ((lengthcount % prime1) !== 0) step = 3 * prime1; 274 | 275 | else { 276 | 277 | if ((lengthcount % prime2) !== 0) step = 3 * prime2; 278 | else { 279 | if ((lengthcount % prime3) !== 0) step = 3 * prime3; 280 | else step = 3 * prime4; 281 | } 282 | } 283 | 284 | i = 0; 285 | while (i < samplepixels) { 286 | 287 | b = (p[pix + 0] & 0xff) << netbiasshift; 288 | g = (p[pix + 1] & 0xff) << netbiasshift; 289 | r = (p[pix + 2] & 0xff) << netbiasshift; 290 | j = contest(b, g, r); 291 | 292 | altersingle(alpha, j, b, g, r); 293 | if (rad !== 0) alterneigh(rad, j, b, g, r); /* alter neighbours */ 294 | 295 | pix += step; 296 | if (pix >= lim) pix -= lengthcount; 297 | 298 | i++; 299 | 300 | if (delta === 0) delta = 1; 301 | 302 | if (i % delta === 0) { 303 | alpha -= alpha / alphadec; 304 | radius -= radius / radiusdec; 305 | rad = radius >> radiusbiasshift; 306 | 307 | if (rad <= 1) rad = 0; 308 | 309 | for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); 310 | } 311 | } 312 | }; 313 | 314 | /* 315 | ** Search for BGR values 0..255 (after net is unbiased) and return colour 316 | * index 317 | * ---------------------------------------------------------------------------- 318 | */ 319 | 320 | function map(b, g, r) { 321 | 322 | var i; 323 | var j; 324 | var dist; 325 | var a; 326 | var bestd; 327 | var p; 328 | var best; 329 | 330 | bestd = 1000; /* biggest possible dist is 256*3 */ 331 | best = -1; 332 | i = netindex[g]; /* index on g */ 333 | j = i - 1; /* start at netindex[g] and work outwards */ 334 | 335 | while ((i < netsize) || (j >= 0)) { 336 | 337 | if (i < netsize) { 338 | p = network[i]; 339 | dist = p[1] - g; /* inx key */ 340 | 341 | if (dist >= bestd) i = netsize; /* stop iter */ 342 | 343 | else { 344 | 345 | i++; 346 | if (dist < 0) dist = -dist; 347 | a = p[0] - b; 348 | if (a < 0) a = -a; 349 | dist += a; 350 | 351 | if (dist < bestd) { 352 | a = p[2] - r; 353 | if (a < 0) a = -a; 354 | dist += a; 355 | 356 | if (dist < bestd) { 357 | bestd = dist; 358 | best = p[3]; 359 | } 360 | } 361 | } 362 | } 363 | 364 | if (j >= 0) { 365 | 366 | p = network[j]; 367 | dist = g - p[1]; /* inx key - reverse dif */ 368 | 369 | if (dist >= bestd) j = -1; /* stop iter */ 370 | 371 | else { 372 | 373 | j--; 374 | if (dist < 0) dist = -dist; 375 | a = p[0] - b; 376 | if (a < 0) a = -a; 377 | dist += a; 378 | 379 | if (dist < bestd) { 380 | a = p[2] - r; 381 | if (a < 0) a = -a; 382 | dist += a; 383 | if (dist < bestd) { 384 | bestd = dist; 385 | best = p[3]; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | return (best); 393 | }; 394 | 395 | 396 | /* 397 | * Unbias network to give byte values 0..255 and record position i to prepare 398 | * for sort 399 | * ----------------------------------------------------------------------------------- 400 | */ 401 | 402 | function unbiasnet() { 403 | 404 | var i; 405 | var j; 406 | 407 | for (i = 0; i < netsize; i++) { 408 | network[i][0] >>= netbiasshift; 409 | network[i][1] >>= netbiasshift; 410 | network[i][2] >>= netbiasshift; 411 | network[i][3] = i; /* record colour no */ 412 | } 413 | }; 414 | 415 | /* 416 | * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in 417 | * radpower[|i-j|] 418 | * --------------------------------------------------------------------------------- 419 | */ 420 | 421 | function alterneigh(rad, i, b, g, r) { 422 | 423 | var j; 424 | var k; 425 | var lo; 426 | var hi; 427 | var a; 428 | var m; 429 | var p; 430 | 431 | lo = i - rad; 432 | if (lo < -1) lo = -1; 433 | 434 | hi = i + rad; 435 | if (hi > netsize) hi = netsize; 436 | 437 | j = i + 1; 438 | k = i - 1; 439 | m = 1; 440 | 441 | while ((j < hi) || (k > lo)) { 442 | a = radpower[m++]; 443 | 444 | if (j < hi) { 445 | p = network[j++]; 446 | 447 | try { 448 | p[0] -= (a * (p[0] - b)) / alpharadbias; 449 | p[1] -= (a * (p[1] - g)) / alpharadbias; 450 | p[2] -= (a * (p[2] - r)) / alpharadbias; 451 | } catch (e) {} // prevents 1.3 miscompilation 452 | } 453 | 454 | if (k > lo) { 455 | p = network[k--]; 456 | 457 | try { 458 | p[0] -= (a * (p[0] - b)) / alpharadbias; 459 | p[1] -= (a * (p[1] - g)) / alpharadbias; 460 | p[2] -= (a * (p[2] - r)) / alpharadbias; 461 | } catch (e) {} 462 | } 463 | } 464 | }; 465 | 466 | /* 467 | * Move neuron i towards biased (b,g,r) by factor alpha 468 | * ---------------------------------------------------- 469 | */ 470 | 471 | function altersingle(alpha, i, b, g, r) { 472 | 473 | /* alter hit neuron */ 474 | var n = network[i]; 475 | n[0] -= (alpha * (n[0] - b)) / initalpha; 476 | n[1] -= (alpha * (n[1] - g)) / initalpha; 477 | n[2] -= (alpha * (n[2] - r)) / initalpha; 478 | }; 479 | 480 | /* 481 | * Search for biased BGR values ---------------------------- 482 | */ 483 | 484 | var contest = function contest(b, g, r) { 485 | 486 | /* finds closest neuron (min dist) and updates freq */ 487 | /* finds best neuron (min dist-bias) and returns position */ 488 | /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ 489 | /* bias[i] = gamma*((1/netsize)-freq[i]) */ 490 | 491 | var i; 492 | var dist; 493 | var a; 494 | var biasdist; 495 | var betafreq; 496 | var bestpos; 497 | var bestbiaspos; 498 | var bestd; 499 | var bestbiasd; 500 | var n; 501 | 502 | bestd = ~ (1 << 31); 503 | bestbiasd = bestd; 504 | bestpos = -1; 505 | bestbiaspos = bestpos; 506 | 507 | for (i = 0; i < netsize; i++) { 508 | n = network[i]; 509 | dist = n[0] - b; 510 | if (dist < 0) dist = -dist; 511 | a = n[1] - g; 512 | if (a < 0) a = -a; 513 | dist += a; 514 | a = n[2] - r; 515 | if (a < 0) a = -a; 516 | dist += a; 517 | 518 | if (dist < bestd) { 519 | bestd = dist; 520 | bestpos = i; 521 | } 522 | 523 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); 524 | 525 | if (biasdist < bestbiasd) { 526 | bestbiasd = biasdist; 527 | bestbiaspos = i; 528 | } 529 | 530 | betafreq = (freq[i] >> betashift); 531 | freq[i] -= betafreq; 532 | bias[i] += (betafreq << gammashift); 533 | } 534 | 535 | freq[bestpos] += beta; 536 | bias[bestpos] -= betagamma; 537 | return (bestbiaspos); 538 | }; 539 | 540 | 541 | return NeuQuant; 542 | }); 543 | -------------------------------------------------------------------------------- /glc/libs/require.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.1.20 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(ba){function G(b){return"[object Function]"===K.call(b)}function H(b){return"[object Array]"===K.call(b)}function v(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||e.onError!==ca)try{f=h.execCb(c,l,b,f)}catch(d){a=d}else f=h.execCb(c,l,b,f);this.map.isDefine&& 19 | void 0===f&&((b=this.module)?f=b.exports:this.usingExports&&(f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(q[c]=f,e.onResourceLoad))e.onResourceLoad(h,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete= 20 | !0)}}else t(h.defQueueMap,c)||this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=i(a.prefix);this.depMaps.push(d);s(d,"defined",u(this,function(f){var l,d;d=n(aa,this.map.id);var g=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,p=h.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(g=f.normalize(g,function(a){return c(a,P,!0)})||""),f=i(a.prefix+"!"+g,this.map.parentMap),s(f,"defined",u(this,function(a){this.init([],function(){return a}, 21 | null,{enabled:!0,ignore:!0})})),d=n(m,f.id)){this.depMaps.push(f);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=h.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];A(m,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,g=i(d),P=M;c&&(f=c);P&& 22 | (M=!1);r(g);t(k.config,b)&&(k.config[d]=k.config[b]);try{e.exec(f)}catch(m){return w(B("fromtexteval","fromText eval for "+b+" failed: "+m,m,[b]))}P&&(M=!0);this.depMaps.push(g);h.completeLoad(d);p([d],l)}),f.load(a.name,p,l,k))}));h.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=i(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c= 23 | n(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",u(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?s(a,"error",u(this,this.errback)):this.events.error&&s(a,"error",u(this,function(a){this.emit("error",a)}))}c=a.id;f=m[c];!t(L,c)&&(f&&!f.enabled)&&h.enable(a,this)}));A(this.pluginMaps,u(this,function(a){var b=n(m,a.id);b&&!b.enabled&&h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]= 24 | []);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};h={config:k,contextName:b,registry:m,defined:q,urlFetched:S,defQueue:C,defQueueMap:{},Module:Z,makeModuleMap:i,nextTick:e.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.shim,c={paths:!0,bundles:!0,config:!0,map:!0};A(a,function(a,b){c[b]?(k[b]||(k[b]={}),U(k[b],a,!0,!0)):k[b]=a});a.bundles&&A(a.bundles,function(a,b){v(a, 25 | function(a){a!==b&&(aa[a]=b)})});a.shim&&(A(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=h.makeShimExports(a);b[c]=a}),k.shim=b);a.packages&&v(a.packages,function(a){var b,a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(k.paths[b]=a.location);k.pkgs[b]=a.name+"/"+(a.main||"main").replace(ha,"").replace(Q,"")});A(m,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=i(b,null,!0))});if(a.deps||a.callback)h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b; 26 | a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,j){function g(c,d,p){var k,n;j.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild=!0);if("string"===typeof c){if(G(d))return w(B("requireargs","Invalid require call"),p);if(a&&t(L,c))return L[c](m[a.id]);if(e.get)return e.get(h,c,a,g);k=i(c,a,!1,!0);k=k.id;return!t(q,k)?w(B("notloaded",'Module name "'+k+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):q[k]}J();h.nextTick(function(){J(); 27 | n=r(i(null,a));n.skipMap=j.skipMap;n.init(c,d,p,{enabled:!0});D()});return g}j=j||{};U(g,{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),j=b.split("/")[0];if(-1!==e&&(!("."===j||".."===j)||1g.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1));g.src=d;J=g;D?y.insertBefore(g,D):y.appendChild(g);J=null;return g}if(ea)try{importScripts(d),b.completeLoad(c)}catch(i){b.onError(B("importscripts", 35 | "importScripts failed for "+c+" at "+d,i,[c]))}};z&&!s.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return r=I,s.baseUrl||(E=r.split("/"),r=E.pop(),O=E.length?E.join("/")+"/":"./",s.baseUrl=O),r=r.replace(Q,""),e.jsExtRegExp.test(r)&&(r=I),s.deps=s.deps?s.deps.concat(r):[r],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ja,"").replace(ka, 36 | function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b||(b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}g?(g.defQueue.push([b,c,d]),g.defQueueMap[b]=!0):R.push([b,c,d])};define.amd={jQuery:!0};e.exec=function(b){return eval(b)};e(s)}})(this); 37 | -------------------------------------------------------------------------------- /glc/libs/GIFEncoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class lets you encode animated GIF files 3 | * Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm 4 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com) 5 | * @author Thibault Imbert (AS3 version - bytearray.org) 6 | * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) 7 | * @version 0.1 AS3 implementation 8 | * @author Keith Peters (slight mods to make it work with require.js - bit-101.com) 9 | */ 10 | 11 | define(["libs/LZWEncoder", "libs/NeuQuant"], function(LZWEncoder, NeuQuant) { 12 | 13 | for (var i = 0, chr = {}; i < 256; i++) 14 | chr[i] = String.fromCharCode(i); 15 | 16 | function ByteArray() { 17 | this.bin = []; 18 | } 19 | 20 | ByteArray.prototype.getData = function() { 21 | for (var v = '', l = this.bin.length, i = 0; i < l; i++) 22 | v += chr[this.bin[i]]; 23 | return v; 24 | }; 25 | 26 | ByteArray.prototype.writeByte = function(val) { 27 | this.bin.push(val); 28 | }; 29 | 30 | ByteArray.prototype.writeUTFBytes = function(string) { 31 | for (var l = string.length, i = 0; i < l; i++) 32 | this.writeByte(string.charCodeAt(i)); 33 | }; 34 | 35 | ByteArray.prototype.writeBytes = function(array, offset, length) { 36 | for (var l = length || array.length, i = offset || 0; i < l; i++) 37 | this.writeByte(array[i]); 38 | }; 39 | 40 | var exports = {}; 41 | var width; // image size 42 | var height; 43 | var transparent = null; // transparent color if given 44 | var transIndex; // transparent index in color table 45 | var repeat = -1; // no repeat 46 | var delay = 0; // frame delay (hundredths) 47 | var started = false; // ready to output frames 48 | var out; 49 | var image; // current frame 50 | var pixels; // BGR byte array from frame 51 | var indexedPixels; // converted frame indexed to palette 52 | var colorDepth; // number of bit planes 53 | var colorTab; // RGB palette 54 | var usedEntry = []; // active palette entries 55 | var palSize = 7; // color table size (bits-1) 56 | var dispose = -1; // disposal code (-1 = use default) 57 | var closeStream = false; // close stream when finished 58 | var firstFrame = true; 59 | var sizeSet = false; // if false, get size from first frame 60 | var sample = 10; // default sample interval for quantizer 61 | var comment = "Generated by jsgif (https://github.com/antimatter15/jsgif/)"; // default comment for generated gif 62 | var maxColors = 256; 63 | 64 | /** 65 | * Sets the delay time between each frame, or changes it for subsequent frames 66 | * (applies to last frame added) 67 | * int delay time in milliseconds 68 | * @param ms 69 | */ 70 | 71 | var setDelay = exports.setDelay = function setDelay(ms) { 72 | delay = Math.round(ms / 10); 73 | }; 74 | 75 | /** 76 | * Sets the GIF frame disposal code for the last added frame and any 77 | * 78 | * subsequent frames. Default is 0 if no transparent color has been set, 79 | * otherwise 2. 80 | * @param code 81 | * int disposal code. 82 | */ 83 | 84 | var setDispose = exports.setDispose = function setDispose(code) { 85 | if (code >= 0) dispose = code; 86 | }; 87 | 88 | /** 89 | * Sets the number of times the set of GIF frames should be played. Default is 90 | * 1; 0 means play indefinitely. Must be invoked before the first image is 91 | * added. 92 | * 93 | * @param iter 94 | * int number of iterations. 95 | * @return 96 | */ 97 | 98 | var setRepeat = exports.setRepeat = function setRepeat(iter) { 99 | if (iter >= 0) repeat = iter; 100 | }; 101 | 102 | /** 103 | * Sets the transparent color for the last added frame and any subsequent 104 | * frames. Since all colors are subject to modification in the quantization 105 | * process, the color in the final palette for each frame closest to the given 106 | * color becomes the transparent color for that frame. May be set to null to 107 | * indicate no transparent color. 108 | * @param 109 | * Color to be treated as transparent on display. 110 | */ 111 | 112 | var setTransparent = exports.setTransparent = function setTransparent(c) { 113 | transparent = c; 114 | }; 115 | 116 | 117 | /** 118 | * Sets the comment for the block comment 119 | * @param 120 | * string to be insterted as comment 121 | */ 122 | 123 | var setComment = exports.setComment = function setComment(c) { 124 | comment = c; 125 | }; 126 | 127 | 128 | 129 | /** 130 | * The addFrame method takes an incoming BitmapData object to create each frames 131 | * @param 132 | * BitmapData object to be treated as a GIF's frame 133 | */ 134 | 135 | var addFrame = exports.addFrame = function addFrame(im, is_imageData) { 136 | 137 | if ((im === null) || !started || out === null) { 138 | throw new Error("Please call start method before calling addFrame"); 139 | } 140 | 141 | var ok = true; 142 | 143 | try { 144 | if (!is_imageData) { 145 | image = im.getImageData(0, 0, im.canvas.width, im.canvas.height).data; 146 | if (!sizeSet) setSize(im.canvas.width, im.canvas.height); 147 | } else { 148 | image = im; 149 | } 150 | getImagePixels(); // convert to correct format if necessary 151 | analyzePixels(); // build color table & map pixels 152 | 153 | if (firstFrame) { 154 | writeLSD(); // logical screen descriptior 155 | writePalette(); // global color table 156 | if (repeat >= 0) { 157 | // use NS app extension to indicate reps 158 | writeNetscapeExt(); 159 | } 160 | } 161 | 162 | writeGraphicCtrlExt(); // write graphic control extension 163 | if (comment !== '') { 164 | writeCommentExt(); // write comment extension 165 | } 166 | writeImageDesc(); // image descriptor 167 | if (!firstFrame) writePalette(); // local color table 168 | writePixels(); // encode and write pixel data 169 | firstFrame = false; 170 | } catch (e) { 171 | ok = false; 172 | } 173 | 174 | return ok; 175 | }; 176 | 177 | /** 178 | * Adds final trailer to the GIF stream, if you don't call the finish method 179 | * the GIF stream will not be valid. 180 | */ 181 | 182 | var finish = exports.finish = function finish() { 183 | 184 | if (!started) return false; 185 | 186 | var ok = true; 187 | started = false; 188 | 189 | try { 190 | out.writeByte(0x3b); // gif trailer 191 | } catch (e) { 192 | ok = false; 193 | } 194 | 195 | return ok; 196 | }; 197 | 198 | /** 199 | * Resets some members so that a new stream can be started. 200 | * This method is actually called by the start method 201 | */ 202 | 203 | var reset = function reset() { 204 | 205 | // reset for subsequent use 206 | transIndex = 0; 207 | image = null; 208 | pixels = null; 209 | indexedPixels = null; 210 | colorTab = null; 211 | closeStream = false; 212 | firstFrame = true; 213 | }; 214 | 215 | /** 216 | * * Sets frame rate in frames per second. Equivalent to 217 | * setDelay(1000/fps). 218 | * @param fps 219 | * float frame rate (frames per second) 220 | */ 221 | 222 | var setFrameRate = exports.setFrameRate = function setFrameRate(fps) { 223 | if (fps != 0xf) delay = Math.round(100 / fps); 224 | }; 225 | 226 | /** 227 | * Sets quality of color quantization (conversion of images to the maximum 256 228 | * colors allowed by the GIF specification). Lower values (minimum = 1) 229 | * produce better colors, but slow processing significantly. 10 is the 230 | * default, and produces good color mapping at reasonable speeds. Values 231 | * greater than 20 do not yield significant improvements in speed. 232 | * @param quality 233 | * int greater than 0. 234 | * @return 235 | */ 236 | 237 | var setQuality = exports.setQuality = function setQuality(quality) { 238 | if (quality < 1) quality = 1; 239 | sample = quality; 240 | }; 241 | 242 | /** 243 | * Sets the GIF frame size. The default size is the size of the first frame 244 | * added if this method is not invoked. 245 | * @param w 246 | * int frame width. 247 | * @param h 248 | * int frame width. 249 | */ 250 | 251 | var setSize = exports.setSize = function setSize(w, h) { 252 | 253 | if (started && !firstFrame) return; 254 | width = w; 255 | height = h; 256 | if (width < 1) width = 320; 257 | if (height < 1) height = 240; 258 | sizeSet = true; 259 | }; 260 | 261 | /** 262 | * Initiates GIF file creation on the given stream. 263 | * @param os 264 | * OutputStream on which GIF images are written. 265 | * @return false if initial write failed. 266 | */ 267 | 268 | var start = exports.start = function start() { 269 | 270 | reset(); 271 | var ok = true; 272 | closeStream = false; 273 | out = new ByteArray(); 274 | try { 275 | out.writeUTFBytes("GIF89a"); // header 276 | } catch (e) { 277 | ok = false; 278 | } 279 | 280 | return started = ok; 281 | }; 282 | 283 | var cont = exports.cont = function cont() { 284 | 285 | reset(); 286 | var ok = true; 287 | closeStream = false; 288 | out = new ByteArray(); 289 | 290 | return started = ok; 291 | }; 292 | 293 | var setMaxColors = exports.setMaxColors = function setMaxColors(colors) { 294 | maxColors = colors; 295 | } 296 | 297 | /** 298 | * Analyzes image colors and creates color map. 299 | */ 300 | 301 | var analyzePixels = function analyzePixels() { 302 | 303 | var len = pixels.length; 304 | var nPix = len / 3; 305 | indexedPixels = []; 306 | var nq = new NeuQuant(pixels, len, sample); 307 | nq.setMaxColors(maxColors); 308 | 309 | // initialize quantizer 310 | colorTab = nq.process(); // create reduced palette 311 | 312 | // map image pixels to new palette 313 | var k = 0; 314 | for (var j = 0; j < nPix; j++) { 315 | var index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff); 316 | usedEntry[index] = true; 317 | indexedPixels[j] = index; 318 | } 319 | 320 | pixels = null; 321 | colorDepth = 8; 322 | palSize = 7; 323 | 324 | // get closest match to transparent color if specified 325 | if (transparent !== null) { 326 | transIndex = findClosest(transparent); 327 | } 328 | }; 329 | 330 | /** 331 | * Returns index of palette color closest to c 332 | */ 333 | 334 | var findClosest = function findClosest(c) { 335 | 336 | if (colorTab === null) return -1; 337 | var r = (c & 0xFF0000) >> 16; 338 | var g = (c & 0x00FF00) >> 8; 339 | var b = (c & 0x0000FF); 340 | var minpos = 0; 341 | var dmin = 256 * 256 * 256; 342 | var len = colorTab.length; 343 | 344 | for (var i = 0; i < len;) { 345 | var dr = r - (colorTab[i++] & 0xff); 346 | var dg = g - (colorTab[i++] & 0xff); 347 | var db = b - (colorTab[i] & 0xff); 348 | var d = dr * dr + dg * dg + db * db; 349 | var index = i / 3; 350 | if (usedEntry[index] && (d < dmin)) { 351 | dmin = d; 352 | minpos = index; 353 | } 354 | i++; 355 | } 356 | return minpos; 357 | }; 358 | 359 | /** 360 | * Extracts image pixels into byte array "pixels 361 | */ 362 | 363 | var getImagePixels = function getImagePixels() { 364 | var w = width; 365 | var h = height; 366 | pixels = []; 367 | var data = image; 368 | var count = 0; 369 | 370 | for (var i = 0; i < h; i++) { 371 | 372 | for (var j = 0; j < w; j++) { 373 | 374 | var b = (i * w * 4) + j * 4; 375 | pixels[count++] = data[b]; 376 | pixels[count++] = data[b + 1]; 377 | pixels[count++] = data[b + 2]; 378 | 379 | } 380 | 381 | } 382 | }; 383 | 384 | /** 385 | * Writes Graphic Control Extension 386 | */ 387 | 388 | var writeGraphicCtrlExt = function writeGraphicCtrlExt() { 389 | out.writeByte(0x21); // extension introducer 390 | out.writeByte(0xf9); // GCE label 391 | out.writeByte(4); // data block size 392 | var transp; 393 | var disp; 394 | if (transparent === null) { 395 | transp = 0; 396 | disp = 0; // dispose = no action 397 | } else { 398 | transp = 1; 399 | disp = 2; // force clear if using transparent color 400 | } 401 | if (dispose >= 0) { 402 | disp = dispose & 7; // user override 403 | } 404 | disp <<= 2; 405 | // packed fields 406 | out.writeByte(0 | // 1:3 reserved 407 | disp | // 4:6 disposal 408 | 0 | // 7 user input - 0 = none 409 | transp); // 8 transparency flag 410 | 411 | WriteShort(delay); // delay x 1/100 sec 412 | out.writeByte(transIndex); // transparent color index 413 | out.writeByte(0); // block terminator 414 | }; 415 | 416 | /** 417 | * Writes Comment Extention 418 | */ 419 | 420 | var writeCommentExt = function writeCommentExt() { 421 | out.writeByte(0x21); // extension introducer 422 | out.writeByte(0xfe); // comment label 423 | out.writeByte(comment.length); // Block Size (s) 424 | out.writeUTFBytes(comment); 425 | out.writeByte(0); // block terminator 426 | }; 427 | 428 | 429 | /** 430 | * Writes Image Descriptor 431 | */ 432 | 433 | var writeImageDesc = function writeImageDesc() { 434 | 435 | out.writeByte(0x2c); // image separator 436 | WriteShort(0); // image position x,y = 0,0 437 | WriteShort(0); 438 | WriteShort(width); // image size 439 | WriteShort(height); 440 | 441 | // packed fields 442 | if (firstFrame) { 443 | // no LCT - GCT is used for first (or only) frame 444 | out.writeByte(0); 445 | } else { 446 | // specify normal LCT 447 | out.writeByte(0x80 | // 1 local color table 1=yes 448 | 0 | // 2 interlace - 0=no 449 | 0 | // 3 sorted - 0=no 450 | 0 | // 4-5 reserved 451 | palSize); // 6-8 size of color table 452 | } 453 | }; 454 | 455 | /** 456 | * Writes Logical Screen Descriptor 457 | */ 458 | 459 | var writeLSD = function writeLSD() { 460 | 461 | // logical screen size 462 | WriteShort(width); 463 | WriteShort(height); 464 | // packed fields 465 | out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used) 466 | 0x70 | // 2-4 : color resolution = 7 467 | 0x00 | // 5 : gct sort flag = 0 468 | palSize)); // 6-8 : gct size 469 | 470 | out.writeByte(0); // background color index 471 | out.writeByte(0); // pixel aspect ratio - assume 1:1 472 | }; 473 | 474 | /** 475 | * Writes Netscape application extension to define repeat count. 476 | */ 477 | 478 | var writeNetscapeExt = function writeNetscapeExt() { 479 | out.writeByte(0x21); // extension introducer 480 | out.writeByte(0xff); // app extension label 481 | out.writeByte(11); // block size 482 | out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code 483 | out.writeByte(3); // sub-block size 484 | out.writeByte(1); // loop sub-block id 485 | WriteShort(repeat); // loop count (extra iterations, 0=repeat forever) 486 | out.writeByte(0); // block terminator 487 | }; 488 | 489 | /** 490 | * Writes color table 491 | */ 492 | 493 | var writePalette = function writePalette() { 494 | out.writeBytes(colorTab); 495 | var n = (3 * 256) - colorTab.length; 496 | for (var i = 0; i < n; i++) out.writeByte(0); 497 | }; 498 | 499 | var WriteShort = function WriteShort(pValue) { 500 | out.writeByte(pValue & 0xFF); 501 | out.writeByte((pValue >> 8) & 0xFF); 502 | }; 503 | 504 | /** 505 | * Encodes and writes pixel data 506 | */ 507 | 508 | var writePixels = function writePixels() { 509 | var myencoder = new LZWEncoder(width, height, indexedPixels, colorDepth); 510 | myencoder.encode(out); 511 | }; 512 | 513 | /** 514 | * Retrieves the GIF stream 515 | */ 516 | 517 | var stream = exports.stream = function stream() { 518 | return out; 519 | }; 520 | 521 | var setProperties = exports.setProperties = function setProperties(has_start, is_first) { 522 | started = has_start; 523 | firstFrame = is_first; 524 | }; 525 | 526 | return exports; 527 | 528 | }); -------------------------------------------------------------------------------- /docs/tips.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GIF Loop Coder Documentation 5 | 6 | 7 | 8 | Home 9 | 10 |

GIF Loop Coder (GLC) Documentation

11 | 28 | 29 | 30 | 31 |

5. Tips and Advanced Use

32 | 33 |
34 | 35 | 36 |

Single Mode Animations

37 | 38 |
39 | 40 |

When you first make a simple animation and see it playing smoothly back and forth in bounce mode, and then switch over to single mode, you'll likely not be very happy with the results. If you are moving an object from, say, 0 to 100 pixels on the x-axis. You're going to see it smoothly glide over to 100, and then instantly jump back to 0. Over and over. Yuck.

41 | 42 |

So initially, you might think that single mode is useless. But, once you know how to work around this, it's very powerful and can create a totally different type of animation. The trick is that you want the end state to look like the the start state. This often involves using multiple objects. Let's look at an example

43 | 44 |

First I'll set up a circle. This will be centered vertically, and move from -25 on the x-axis to the center of the canvas horizontally. Because the radius is 25-pixels, that means it's going to start out off screen, and move to center screen, and then disappear.

45 | 46 |

 47 | function onGLC(glc) {
 48 |     glc.loop();
 49 |     glc.size(400, 200);
 50 |     // glc.setDuration(5);
 51 |     // glc.setFPS(20);
 52 |     // glc.setMode("single");
 53 |     // glc.setEasing(false);
 54 |     // glc.setMaxColors(256);
 55 |     var list = glc.renderList,
 56 |         width = glc.w,
 57 |         height = glc.h;
 58 | 
 59 |     list.addCircle({
 60 |         x: [-25, 200],
 61 |         y: 200,
 62 |         radius: 25
 63 |     });
 64 | 
 65 |     glc.loop();
 66 | }
 67 |     
68 | 69 | 70 | 71 |

So, at the end of the animation we have a circle sitting at the center of the screen. But when it starts the next cycle, there's nothing there. To handle that, we put the same type of object in that position at the start of the animation. And we have that move OFF the screen.

72 | 73 |

 74 | function onGLC(glc) {
 75 |     glc.loop();
 76 |     glc.size(400, 200);
 77 |     // glc.setDuration(5);
 78 |     // glc.setFPS(20);
 79 |     // glc.setMode("single");
 80 |     // glc.setEasing(false);
 81 |     // glc.setMaxColors(256);
 82 |     var list = glc.renderList,
 83 |         width = glc.w,
 84 |         height = glc.h;
 85 | 
 86 |     list.addCircle({
 87 |         x: [-25, 200],
 88 |         y: 100,
 89 |         radius: 25
 90 |     });
 91 |     list.addCircle({
 92 |         x: [200, 425],
 93 |         y: 100,
 94 |         radius: 25
 95 |     });
 96 | 
 97 |     glc.loop();
 98 | }
 99 |     
100 | 101 | 102 | 103 |

That's much better. You can even chain several objects together like this, and animate other properties as well. Just remember that the end state of one object has to match up with the start state of another, or the start or end needs to be off screen or invisible.

104 | 105 | 106 |

107 | function onGLC(glc) {
108 |     glc.loop();
109 |     glc.size(400, 200);
110 |     // glc.setDuration(5);
111 |     // glc.setFPS(20);
112 |     // glc.setMode("single");
113 |     // glc.setEasing(false);
114 |     // glc.setMaxColors(256);
115 |     var list = glc.renderList,
116 |         width = glc.w,
117 |         height = glc.h;
118 | 
119 |     list.addCircle({
120 |         x: [-25, 150],
121 |         y: [0, 175],
122 |         radius: 25,
123 |         fillStyle: [0xff0000, 0x00ff00]
124 |     });
125 |     list.addCircle({
126 |         x: [150, 300],
127 |         y: [175, 25],
128 |         radius: 25,
129 |         fillStyle: [0x00ff00, 0x0000ff]
130 |     });
131 |     list.addCircle({
132 |         x: [300, 425],
133 |         y: [25, 200],
134 |         radius: 25,
135 |         fillStyle: [0x0000ff, 0xff0000]
136 |     });
137 | 
138 |     glc.loop();
139 | }
140 |     
141 | 142 | 143 | 144 | 145 |

Once you get the idea of this, it opens up the doors to all kinds of different animations.

146 |
147 | 148 | 149 |

GIF Size and Optimization

150 | 151 |
152 | 153 |

When you are making an animated gif, you are basically making a bunch of images and assembling them into a single file that can be played back later. The animated gif format takes care of a lot of optimizing to try to get the size as small as possible, but the encoder that is built into glc is probably not the best at this. So, there are some things you might want to consider when making gifs.

154 | 155 |

In the current version of GLC, the output panel displays the approximate file size of the animated gif before you even save it. This makes it much easier to get an idea of how big the output will be, and whether or not you need to do some optimization, before you even save it.

156 | 157 |

First of all, consider the frame rate, length and physical size of your animation. While glc will make animations up to 60 fps, there's probably no reason to do so. This will inflate the file size dramatically, and not really look any better. Play with the fps slider and see how low you can set it while still looking good.

158 | 159 |

The duration is what it is. If you want a 10 second animation, just realize that it's going to have twice as many frames as a 5 second animation. And it's going to be larger.

160 | 161 |

The maximum colors slider can have a big effect on the size of your animation. By default, GLC creates GIFs with 256 colors. This is often way more than you need. Try changing this to a lower number and recompiling your gif. You may find that the lower setting looks just as good as the higher one while giving you a much smaller file size.

162 | 163 |

And for size, try to make the animation as small as you can. Not meaning that to make everything tiny, but if your animation only covers the center 100 pixels of the canvas, there's no reason to make it 400 pixels tall. Crop it to the size of the moving objects.

164 | 165 |

If you've done all you can and you still feel the animation is too heavy, there are a number of animated gif optimization tools. Some are web-based - you upload the gif, it optimizes it and you download the optimized version. Others are executable programs you download. Some of these can dramatically reduce file size with very little noticeable change in quality. Do a search and try some of them out.

166 | 167 |

When you have your gif all rendered and playing back, right click it and save it to your computer. Or, right click and open it in a new tab and save it from there. Do NOT right click and copy to the clipboard. This is not the same as copying and pasting from the file system on your computer. Copying the image from the web page will copy a binary string of the data that makes up the image. It's huge and will will take forever to copy onto the clipboard and may even hang or crash your browser.

168 | 169 |

As mentioned in the intro section, sometimes on larger animations, the right click to save method will fail. If this happens try dragging and dropping the animation to a file system window. I've saved gifs that were over 9.5 megs that way, so this is a pretty reliable fallback.

170 | 171 |

If you ever get brave enough to dive into the glc.js source code, you might notice that there is some disabled code to add a "Save gif" link to the Output panel. I removed this as it was very unreliable and would fail way more often than the right click and save method. However, if you want to try it out, look for the glc.js file and you'll see the lines that create this link, commented out. Uncomment them to get the link back.

172 |
173 | 174 | 175 |

Phase and SpeedMult

176 | 177 |
178 | 179 |

There are actually two more advanced properties that I've withheld from you until now: phase and speedMult.

180 | 181 |

As described earlier, glc works by increasing an internal t variable from 0 to 1 and basing all its object animations from that. You've seen this t variable in the Canvas Panel scrubber and in the custom functions used for setting properties. Because everything is based on t, every object is perfectly in sync. This is good, as it results in perfectly loopable animations. However, it can make some more advanced effects more difficult. Both of these properties affect how t can be altered before being apaplied to your animation.

182 | 183 |

First, phase. Judicious use of the phase property can provide you with an easy way of creating much more intricate animations, very simply.

184 | 185 |

Here, I've set up 5 circles with a simple x-axis animation. They are spaced out on the y-axis via a for loop.

186 | 187 |

188 | function onGLC(glc) {
189 |     glc.loop();
190 |     // glc.size(400, 400);
191 |     // glc.setDuration(5);
192 |     // glc.setFPS(20);
193 |     // glc.setMode("single");
194 |     // glc.setEasing(false);
195 |     // glc.setMaxColors(256);
196 |     var list = glc.renderList,
197 |         width = glc.w,
198 |         height = glc.h;
199 | 
200 |     for(var i = 0; i < 5; i++) {
201 |         list.addCircle({
202 |             x: [50, 350],
203 |             y: 50 + i * 75,
204 |             radius: 25,
205 |         });
206 |     }
207 | 
208 |     glc.loop();
209 | }
210 |     
211 | 212 | 213 | 214 | 215 |

Now this is fine, but they're all moving exactly in sync, which is rather boring. Say you wanted them to all move back and forth exactly as they are, but you wanted them all to start at different times, so they were out of sync. That would be pretty tough, though you could get it working with a custom function property. However, with the phase property, it's a piece of cake.

216 | 217 |

Setting the phase property for an object changes the t value that it uses for its animations. If you set phase to 0.25 for an object, then, while all other objects were at a t of 0, it would get a t of 0.25. When all other objects got to t = 0.75, it would be at 1, and when the others got to 1, that object's t would already be moving backwards and would be back to 0.75. It just shifts the whole timeline.

218 | 219 |

So, say I gave each one of these circles a different phase, and spaced them out using the for loop i variable...

220 | 221 |

222 | function onGLC(glc) {
223 |     glc.loop();
224 |     // glc.size(400, 400);
225 |     // glc.setDuration(5);
226 |     // glc.setFPS(20);
227 |     // glc.setMode("single");
228 |     // glc.setEasing(false);
229 |     // glc.setMaxColors(256);
230 |     var list = glc.renderList,
231 |         width = glc.w,
232 |         height = glc.h;
233 | 
234 |     for(var i = 0; i < 5; i++) {
235 |         list.addCircle({
236 |             x: [50, 350],
237 |             y: 50 + i * 75,
238 |             radius: 25,
239 |             phase: i * 0.2
240 |         });
241 |     }
242 | 
243 |     glc.loop();
244 | }
245 |     
246 | 247 | 248 | 249 |

Now, this is WAY more interesting! And, it only took one line of code! And, it doesn't break the looping of the animation at all. This is just the tip of the iceberg as far as what you can do with the phase property. Explore it.

250 | 251 |

Before you ask, no, it's NOT possible to animate the phase value. It can only be a single value. Animating the thing that's animating the things... I don't know. I think the universe would implode or something.

252 | 253 |

Then, there's the speedMult property. This essentially multiplies the t value by some amount, but just for that one object that it's being applied to. It lets you speed up the animation for a single object. In the following example, four circles are created. They all animate from the left side of the canvas to the right and back.

254 | 255 |

256 | function onGLC(glc) {
257 |     glc.loop();
258 |     glc.size(200, 200);
259 |     var list = glc.renderList,
260 |         width = glc.w,
261 |         height = glc.h,
262 |         color = glc.color;
263 | 
264 |     list.addCircle({
265 |         x: [25, 175],
266 |         y: 25,
267 |         radius: 25
268 |     });
269 |     list.addCircle({
270 |         x: [25, 175],
271 |         y: 75,
272 |         radius: 25,
273 |         speedMult: 2
274 |     });
275 |     list.addCircle({
276 |         x: [25, 175],
277 |         y: 125,
278 |         radius: 25,
279 |         speedMult: 3
280 |     });
281 |     list.addCircle({
282 |         x: [25, 175],
283 |         y: 175,
284 |         radius: 25,
285 |         speedMult: 4
286 |     });
287 | }
288 |     
289 | 290 |

Other than the y position of each of these circles, the main difference is the speedMult property. On the first one, it is not assigned at all, which defaults to one. So that circle moves back and forth at a normal speed. The second circle has a speedMult of 2, so it will move back and forth twice for every single trip the first circle makes. The next two circles have a speedMult of 3 and 4, so those move even faster. Here's what you get:

291 | 292 | 293 | 294 |

Be careful with this one. Assigning speedMult samll integer values, such as 2, 3, 4, like we did here, will maintain the smooth looping aspect that comes automatically in GLC. But you can assign any number you want - floating point numbers, super high numbers, negative values. Some of these values will break your animation. But, in the right combinations, may create new, interesting effects. Experiment away.

295 | 296 |
297 | 298 | 299 |

Moving the Sketches Folder

300 | 301 |
302 | 303 |

The sketches folder is set up to be the place where you keep the HTML and JavaScript files for your various animations. If you just keep all your JavaScript files in the code folder and reference them from the index.html file, things will work fine. However, for whatever reason, you may want to rearrange things. Here's how to do that.

304 | 305 |

First of all, you can put your JavaScript sketch files wherever you want, as long as you add the correct relative or absolute path to them in the HTML file. The JavaScript files can be in any folder on the same drive, a different drive, or even somewhere else on the network. As long as the HTML has the correct path to the file, it should load and run.

306 | 307 |

But say you still want to move that sketch folder, or set up multiple sketch folders. That can be done too, but takes a few more steps. The important thing to know is that in addition to the HTML file needing to know where your JavaScript file is, it needs to know where the require.js file is. In addition to that, the glcloader.js file needs to know the location of the glc folder.

308 | 309 |

Here again is the index.html file, with the important bit highlighted.

310 | 311 |

312 | <!DOCTYPE html>
313 | <html>
314 | <head>
315 |     <title>GIF Loop Coder</title>
316 | </head>
317 | <body>
318 |     <!-- This should point to the sketch you want to run -->
319 |     <script type="text/javascript" src="examples/allshapes.js"></script>
320 | 
321 |     <!-- Don't touch this unless you move your sketches folder. If so, update the path to require.js -->
322 |     <script type="text/javascript" src="../glc/libs/require.js" data-main="glcloader"></script>
323 | </body>
324 | </html>
325 |     
326 | 327 |

Currently, it's saying that to find require.js from the sketches folder, go up one directory, and then down the given path. So, if that no longer points to require.js, adjust that path so it does.

328 | 329 |

Next, here is glcloader.js:

330 | 331 |

332 | // if you move your sketches folder somewhere other than the default,
333 | // update this baseUrl property so it continues to point to the glc directory.
334 | require.config({
335 |     baseUrl: "../glc"
336 | });
337 | 
338 | 
339 | if(document.location.hash) {
340 |     var script = document.createElement("script");
341 |     script.src = document.location.hash.substring(1);
342 |     document.head.appendChild(script);
343 | }
344 | 
345 | window.onhashchange = function() {
346 |     document.location.reload();
347 | }
348 | 
349 | require(["app/glc"], function(glc) {
350 |     if(window.onGLC) {
351 |         window.onGLC(glc);
352 |     }
353 | });
354 |     
355 | 356 |

Here, you see a require.config block that contains a baseUrl property. This needs to point to the glc folder. Again, from the sketches folder, that's up one level. So if you move things around, this is the other path you'll have to adjust.

357 | 358 |

Once those two items point to the right places, your newly located sketches folder should work just fine.

359 | 360 |
361 | 362 | 363 | 364 |

GIF or JIF?

365 | 366 |
367 | 368 |

The "g" in "gif" is pronounced exactly the same way it is in "git" and "gin".

369 | 370 |
371 |
372 | 373 | 374 | -------------------------------------------------------------------------------- /docs/styles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GIF Loop Coder Documentation 5 | 6 | 7 | 8 | Home 9 | 10 |

GIF Loop Coder (GLC) Documentation

11 | 25 | 26 | 27 | 28 |

3. Styles

29 | 30 |
31 | 32 |

The following properties can be set on any object when creating it. This also lists their default values if they are not set.

33 | 34 |

 35 | lineWidth       5
 36 | strokeStyle     "#000000"
 37 | fillStyle       "#000000"
 38 | lineCap         "round"
 39 | lineJoin        "miter"
 40 | lineDash        []
 41 | miterLimit      10
 42 | shadowColor     null
 43 | shadowOffsetX   0
 44 | shadowOffsetY   0
 45 | shadowBlur      0
 46 | globalAlpha     1
 47 | translationX    0
 48 | translationY    0
 49 | shake           0
 50 | blendMode       "source-over"
 51 |     
52 | 53 |

All of these defaults are defined on the glc.styles property. So you can change any of these globally by writing, for example:

54 | 55 |

 56 | glc.styles.lineWidth = 20;
 57 | 
 58 | list.addCircle({
 59 |     stroke: true,
 60 |     fill: false,
 61 |     radius: [50, 80]
 62 | });
 63 |     
64 | 65 | 66 | 67 |

Now, all objects will be drawn with a line width of 20 pixels, unless they explicitly set their own lineWidth property.

68 | 69 |

Note that you can not set default animations on glc.styles, only single values. Animations are only parsed within an object's property definition.

70 | 71 | 72 |

There is one additional property on glc.styles that does not apply to individual objects:

73 | 74 |

 75 | backgroundColor "#ffffff"
 76 |     
77 | 78 |

This is a global style only. The backgroundColor is only used to draw the main background of the animation and is not used by objects. It is not animatable. If you want to animate the background color, just create a rect behind all the other objects and animate its color.

79 | 80 |

You can create a background color by doing something like "rgba(0, 0, 0, 0.1)". This has the result of partially covering the previous frame with black. After multiple frames, the background becomes mostly black, but you will sometimes see "trails" of moving objects. This can be used to create some great effects. Note, that the non-standard 8-digit hex string like "#80000000" cannot be used here because this value is not parsed by the GLC color parsing system.

81 | 82 |

All of the other styles do pretty much what they do in the HTML5 Canvas drawing API. The exceptions are:

83 | 84 |
    85 |
  • The lineDash property, which is a setLineDash method on the 2d rendering context.
  • 86 |
  • translationX and translationY, which are translate(x, y) on the context.
  • 87 |
  • The fact that you can specify hex colors with an alpha channel here, which you can't do directly on the 2d renering context. e.g. "#80ff0000"
  • 88 |
  • The shake property adds an adjustable jitter to any object it is applied to, randomly changing its position by a small amount on each frame.
  • 89 |
  • The blendMode property maps to the Canvas globalCompositeOperation property. It's exactly the same. I just didn't feel the need to make you type "globalCompositeOperation" every time you need to use it.
  • 90 |
91 | 92 | 93 |

The Color Module

94 | 95 |
96 | 97 |

Although colors are already pretty powerful in GLC, I added a color module that makes defining colors even easier. You can access this as glc.color and it is now aliased in the template to simply be color.

98 | 99 |

The color module contains several methods that return color strings that can be used for fill styles, stroke styles, shadow colors or background colors. Here are all the methods:

100 | 101 |

102 | - color.rgb(r, g, b)
103 | - color.rgba(r, g, b, a)
104 | - color.gray(shade)
105 | - color.randomRGB()
106 | - color.randomRGB(min, max)
107 | - color.randomGray()
108 | - color.randomGray(min, max)
109 | - color.num(number)
110 | - color.hsv(h, s, v)
111 | - color.hsva(h, s, v, a)
112 | - color.animHSV(h, s, v)
113 | - color.animHSVA(h, s, v, a)
114 | - color.randomHSV(minH, maxH, minS, maxS, minV, maxV)
115 |     
116 | 117 | 118 |

Some of these simply make it easier to define colors using numbers for the component channels.

119 | 120 |

121 | fillStyle: color.rgb(255, 128, 0)
122 |     
123 | 124 |

or...

125 | 126 |

127 | fillStyle: color.rgba(255, 128, 0, 0.5)
128 |     
129 | 130 |

The color.num method lets you pass in a single integer that will be converted to a color string. This is usually done with a hexadecimal based number. 131 | 132 |


133 | fillStyle: color.num(0xff8000)
134 |     
135 | 136 |

Note that the num method only supports 24-bit numbers, so no alpha channel. Which is to say that the color will always be fully opaque.

137 | 138 |

You can also easily define a grayscale color with the color.gray method. Just pass it a value from 0 to 255.

139 | 140 |

141 | fillStyle: color.gray(128)
142 |     
143 | 144 |

Then there are methods for generating random colors or grays. color.randomRGB() will return a random color with full alpha.

145 | 146 |

147 | fillStyle: color.randomRGB()
148 |     
149 | 150 |

You can also pass minimum and maximum parameters to this method. For example, the following will generate a random color where all of the components have a value between 0 and 128. This will be a darker color, whatever it winds up being.

151 | 152 |

153 | fillStyle: color.randomRGB(0, 128)
154 |     
155 | 156 |

Whereas this example will generate colors where all the components have higher values - between 128 and 255:

157 | 158 |

159 | fillStyle: color.randomRGB(128, 255)
160 |     
161 | 162 |

Then there's the color.randomGray() method. This works the same way. With no parameters, you'll get a random gray from the full spectrum of grays.

163 | 164 |

165 | fillStyle: color.randomGray()
166 |     
167 | 168 |

Or you can pass in min and max values to set a range of grays where your random one will be pulled from.

169 | 170 |

171 | fillStyle: color.randomGray(0, 128)
172 |     
173 | 174 |

Finally, there are two methods for getting HSV (hue, saturation, value) based colors. First there is color.hsv(h, s, v). Here, you pass a value from 0 to 360 for hue. This determines the base color. Then a value from 0 to 1 for saturation and 0 to 1 for value. Saturation controls how much of the base hue will be in the final color, where 1 is a fully saturated value and 0 will give you white. And value controls how dark the color is, where 1 is as bright as it can be and 0 will be black.

175 | 176 |

177 | fillStyle: color.hsv(30, 1, 1)
178 |     
179 | 180 |

There's also a version with alpha:

181 | 182 |

183 | fillStyle: color.hsva(30, 1, 1, 0.5)
184 |     
185 | 186 |

And, of course, there's a random method for hsv: color.randomHue(min, max, s, v). With this one, the min and max parameters, as well as s and v, are all required. The following will generate colors with a orange to yellow hue:

187 | 188 |

189 | fillStyle: color.randomHSV(30, 60, 1, 1, 1, 1)
190 |     
191 | 192 |

A note on animating with the color module. All of the methods discussed so far return valid color strings, so they can all be animated just as if you'd assigned any other basic string. For example:

193 | 194 |

195 | fillStyle: [color.rgb(255, 0, 0), color.gray(128)]
196 |     
197 | 198 |

And you can mix and match them with any other color strings:

199 | 200 |

201 | fillStyle: [color.randomRGB(), "blue"]
202 |     
203 | 204 |

The only caveat is on using the color.hsv method. The values you assign here will be converted to a color string - which is rgb based - BEFORE animating. So you may not get the result you're looking for when animating between two hsv-defined colors. Take the following example:

205 | 206 |

207 | fillStyle: [color.hsv(0, 1, 1), color.hsv(360, 1, 1)]
208 |     
209 | 210 |

You might expect that to animate through the full spectrum of colors, giving you a rainbow effect. That's not what you'll get though. Actually hsv(0, 1, 1) will be converted to "#ff0000". And hsv(1, 1, 1) will convert to the exact same thing. So you'll be animating from red to red - no visible animation at all.

211 | 212 | 213 |

That's what the color.animHSV method is for. This takes six values - start and end parameters for each of h, s and v, and will animate between them as you would expect.

214 | 215 |

216 | fillStyle: color.animHSV(startH, endH, startS, endS, startV, endV)
217 |     
218 | 219 |

This will recalculate a new hsv value on each frame, so it is actually animating through various hues. If you try this one, I suggest you try it in single mode, no easing, with a duration of at least 5. Otherwise, the entire spectrum will be moving by so quickly, it will just look like random flashing colors. Animating hues often works better for smaller ranges, such as a min and max of 30 and 60, which will move between an orange and a yellow color.

220 | 221 |

And, for the sake of completeness, the alpha version:

222 | 223 |

224 | fillStyle: color.animHSVA(startH, endH, startS, endS, startV, endV, startA, endA)
225 |     
226 | 227 | 228 | 229 |

For a bit more in depth discussion of the color module, check out this post.

230 | 231 |
232 | 233 | 234 | 235 |

Gradients

236 | 237 |
238 | 239 |

Gradients, even in native canvas, are complex. Creating a single gradient is complicated enough, so it was a challenge to allow for a method of animating gradients without making it any more complicated. One of the main design decisions, good or bad, in GLC was to match the native canvas drawing API as much as possible. In canvas, you create gradients by calling context.createLinearGradient() or context.createRadialGradient() and then adding colors at specific spots in the gradient by calling gradient.addColorStop(). This is well documented elsewhere on the web, so I'm not going to go into that much here.

240 | 241 |

But basically, you'll be using the same API to make gradients in GLC. The big difference is that the gradients you create in GLC will be animatable, just like everything else.

242 | 243 |

The methods for creating gradients have been added to the color module (which again is at glc.color, and aliased to simply color in the template.js file). 244 | 245 |

So, to create a linear gradient, you would say:

246 | 247 |

248 | var gradient = color.createLinearGradient(x0, y0, x1, y1);
249 |     
250 | 251 |

This creates a linear gradient that starts at point x0, y0 and goes to x1, y1. Now we need to add color stops. Say we want the color to be red at point x0, y0 and gradiently change to blue when it reaches x1, y1. We say:

252 | 253 |

254 | var gradient = color.createLinearGradient(x0, y0, x1, y1);
255 | gradient.addColorStop(0, "red");
256 | gradient.addColorStop(1, "blue");
257 |     
258 | 259 |

You can add as many stops as you want. Just ensure that you keep the positions in the range of 0 - 1.

260 | 261 |

262 | var gradient = color.createLinearGradient(x0, y0, x1, y1);
263 | gradient.addColorStop(0, "red");
264 | gradient.addColorStop(0.5, "green");
265 | gradient.addColorStop(1, "blue");
266 |     
267 | 268 |

Now, the gradient will start at red, move through green at the midway point, and end at blue. We can then simply assign this to the fillStyle or strokeStyle property of any object we are drawing. So here's what we have:

269 | 270 | 271 |

272 | function onGLC(glc) {
273 |     glc.loop();
274 |     glc.size(200, 200);
275 |     var list = glc.renderList,
276 |         width = glc.w,
277 |         height = glc.h,
278 |         color = glc.color;
279 | 
280 |     var gradient = color.createLinearGradient(0, -100, 0, 100);
281 |     gradient.addColorStop(0, "red");
282 |     gradient.addColorStop(0.5, "green");
283 |     gradient.addColorStop(1, "blue");
284 | 
285 |     list.addCircle({
286 |         x: 100,
287 |         y: 100,
288 |         radius: 100,
289 |         fillStyle: gradient
290 |     });
291 | }
292 |     
293 | 294 |

Here, the gradient goes from point 0, -100 to point 0, 100. This is relative to the center of the circle we are drawing. Since the circle has a radius of 100, the gradient will go from the top of the circle to the bottom. And here's what this gives us:

295 | 296 | 297 | 298 | 299 |

The center of the object distinction is unique to GLC. In native canvas, the gradient is always centered at the top left of the canvas itself. This makes calculating your gradient sizes and positions to match what you're drawing a bit tricky.

300 | 301 |

But for many of the objects in GLC the gradient is centered on the object, so things generally become a whole lot simpler.

302 | 303 |

The gradient is centered on the object for the following shapes: circle, gear, heart, oval, poly, rect, star and text. These objects all have a size and position that is known beforehand. Also, these are the objects most likely to be filled, and gradients are generally more commonly used on fills than on strokes. So this makes filling these objects with a gradient very easy.

304 | 305 |

When using gradients on other objects, such as curves, lines and paths, the gradient will most likely start at the default top left of the canvas and you'll have to work out where you want it from there, just like in native canvas. In some objects, such as the ray, the gradient will be centered on the first point of the ray. So you might need to experiment a bit to see where the gradient is actually starting and adjust it from there.

306 | 307 |

For radial gradients, the process is the same, but you'd call color.createRadialGradient(x0, y0, r0, x1, y1, r1). Again, you're specifying two points, but also two radii. Basically the gradient goes between two circles. Usually these are somewhat concentric circles, with one very small, and the other the size of your object. So we have this:

308 | 309 | 310 |

311 | function onGLC(glc) {
312 |     glc.loop();
313 |     glc.size(200, 200);
314 |     var list = glc.renderList,
315 |         width = glc.w,
316 |         height = glc.h,
317 |         color = glc.color;
318 | 
319 |     var gradient = color.createRadialGradient(0, 0, 0, 0, 0, 100);
320 |     gradient.addColorStop(0, "red");
321 |     gradient.addColorStop(0.5, "green");
322 |     gradient.addColorStop(1, "blue");
323 | 
324 |     list.addCircle({
325 |         x: 100,
326 |         y: 100,
327 |         radius: 100,
328 |         fillStyle: gradient
329 |     });
330 | }
331 |     
332 | 333 |

The only difference here is that we created a radial gradient. The first point is 0, 0 with a radius of 0. The second point is also 0, 0, with a radius of 100. Color stops are the same. So we get:

334 | 335 | 336 | 337 |

Now, on to animating! A lot of work went into making this as easy as possible while still following the canvas API. In the end, all we have to do is create two gradients, and assign them as an array to fillStyle. GCL will animate between them. It will animate the points and radii of the gradients, and the positions and colors of all the color stops!

338 | 339 |

Here you go:

340 | 341 |

342 | function onGLC(glc) {
343 |     glc.loop();
344 |     glc.size(200, 200);
345 |     var list = glc.renderList,
346 |         width = glc.w,
347 |         height = glc.h,
348 |         color = glc.color;
349 | 
350 |     var gradient1 = color.createRadialGradient(0, 0, 0, 0, 0, 100);
351 |     gradient1.addColorStop(0, "red");
352 |     gradient1.addColorStop(0.5, "green");
353 |     gradient1.addColorStop(1, "blue");
354 | 
355 |     var gradient2 = color.createRadialGradient(0, 0, 0, 0, 0, 100);
356 |     gradient2.addColorStop(0, "green");
357 |     gradient2.addColorStop(0.5, "blue");
358 |     gradient2.addColorStop(1, "red");
359 | 
360 |     list.addCircle({
361 |         x: 100,
362 |         y: 100,
363 |         radius: 100,
364 |         fillStyle: [gradient1, gradient2]
365 |     });
366 | }
367 |     
368 | 369 |

Here, all I did was change the colors, but you could change just about anything. The result:

370 | 371 | 372 | 373 |

Now before you ask, NO, it's not possible to animate between different kinds of gradients. For example, you can't animate between a solid fill and a gradient, or between a linear and radial gradient. You could, of course, create two gradients, and have the colors all the same in one. This would, in effect, create a solid fill or stroke. But since it's really a gradient, it would animate to a different gradient.

374 | 375 |

Also, the number of color stops in the two gradients should be equal. If they aren't, some color stops might be dropped, or your computer might blow up.

376 | 377 |
378 | 379 |
380 | 381 | 382 | 383 | --------------------------------------------------------------------------------