├── 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 |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 |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 |A number property is pretty simple to understand. You can assign a single number of course:
41 | 42 | 43 |
44 | radius: 100
45 | 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 | 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 | 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
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 | 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:
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 |
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.
A string property is also quite simple. Again, you can assign a simple string:
100 | 101 |
102 | text: "hello"
103 | You can also assign an array of values to the property:
106 | 107 |
108 | text: ["hello", "goodbye"]
109 | or...
112 | 113 |
114 | text: ["small", "medium", "large"]
115 | 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 | For example:
135 | 136 |
137 | text: function(t) {
138 | return "t = " + t;
139 | }
140 |
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 |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 | or...
163 | 164 |
165 | fillStyle: "#f00"
166 | rgb strings:
169 | 170 |
171 | fillStyle: "rgb(255, 0, 0)"
172 | rgba strings:
175 | 176 |
177 | fillStyle: "rgba(255, 0, 0, 0.5)"
178 | CSS color named color strings:
181 | 182 |
183 | fillStyle: "fuchsia"
184 | To smoothly animate between two colors, supply a 2-element array:
188 | 189 |
190 | fillStyle: ["#ff0000", "#00ff00"]
191 | 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 | Even something as crazy as this works:
204 | 205 |
206 | fillStyle: ["burlywood", "mediumorchid"]
207 | 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)"
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)".
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 | 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 |
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 values are pretty simple. They're either true or false. Single values are easy:
251 | 252 |
253 | stroke: true
254 | Like strings, you can assign an array of any length:
257 | 258 |
259 | stroke: [true, false, true, true]
260 | 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 | 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.
So, in the case of lineDash, assigning an array does NOT create an animation. It just creates a regular line dash:
285 | lineDash: [50, 10, 20, 10]
286 |
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 | 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 | 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 | 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 |
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 |
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 |
143 |
144 |
145 | Once you get the idea of this, it opens up the doors to all kinds of different animations.
146 |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.
There are actually two more advanced properties that I've withheld from you until now: phase and speedMult.
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.
First, phase. Judicious use of the phase property can provide you with an easy way of creating much more intricate animations, very simply.
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 |
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.
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.
So, say I gave each one of these circles a different phase, and spaced them out using the for loop i variable...
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 |
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.
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.
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.
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 | 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:
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.
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.
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.
Here again is the index.html file, with the important bit highlighted.
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 | 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.
Next, here is glcloader.js:
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 | 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.
Once those two items point to the right places, your newly located sketches folder should work just fine.
359 | 360 |The "g" in "gif" is pronounced exactly the same way it is in "git" and "gin".
369 | 370 |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 | All of these defaults are defined on the glc.styles property. So you can change any of these globally by writing, for example:
56 | glc.styles.lineWidth = 20;
57 |
58 | list.addCircle({
59 | stroke: true,
60 | fill: false,
61 | radius: [50, 80]
62 | });
63 |
66 |
67 | Now, all objects will be drawn with a line width of 20 pixels, unless they explicitly set their own lineWidth property.
Note that you can not set default animations on glc.styles, only single values. Animations are only parsed within an object's property definition.
There is one additional property on glc.styles that does not apply to individual objects:
75 | backgroundColor "#ffffff"
76 | 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.
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.
All of the other styles do pretty much what they do in the HTML5 Canvas drawing API. The exceptions are:
83 | 84 |lineDash property, which is a setLineDash method on the 2d rendering context.translationX and translationY, which are translate(x, y) on the context."#80ff0000"shake property adds an adjustable jitter to any object it is applied to, randomly changing its position by a small amount on each frame.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.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.
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 | 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 | or...
125 | 126 |
127 | fillStyle: color.rgba(255, 128, 0, 0.5)
128 | 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 | 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.
You can also easily define a grayscale color with the color.gray method. Just pass it a value from 0 to 255.
141 | fillStyle: color.gray(128)
142 | Then there are methods for generating random colors or grays. color.randomRGB() will return a random color with full alpha.
147 | fillStyle: color.randomRGB()
148 | 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 | 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 | 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.
165 | fillStyle: color.randomGray()
166 | 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 | 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.
177 | fillStyle: color.hsv(30, 1, 1)
178 | There's also a version with alpha:
181 | 182 |
183 | fillStyle: color.hsva(30, 1, 1, 0.5)
184 | 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:
189 | fillStyle: color.randomHSV(30, 60, 1, 1, 1, 1)
190 | 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 | And you can mix and match them with any other color strings:
199 | 200 |
201 | fillStyle: [color.randomRGB(), "blue"]
202 | 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:
207 | fillStyle: [color.hsv(0, 1, 1), color.hsv(360, 1, 1)]
208 | 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.
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.
216 | fillStyle: color.animHSV(startH, endH, startS, endS, startV, endV)
217 | 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 | For a bit more in depth discussion of the color module, check out this post.
230 | 231 |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.
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 | 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:
254 | var gradient = color.createLinearGradient(x0, y0, x1, y1);
255 | gradient.addColorStop(0, "red");
256 | gradient.addColorStop(1, "blue");
257 | 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 | 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:
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 | 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:
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 | 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!
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 | 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 |