1 /**
  2  * xanvas 0.1 - JavaScript Canvas Library
  3  *
  4  * Copyright (c) 2010 Michael Gerhaeuser
  5  * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
  6  */
  7 
  8 xanvas = (function(document, undefined) {
  9     return (function (canvas) {
 10         var xanvas, i,
 11             fromx = 0, fromy = 0,
 12             _is_array = function(a) {
 13                 return a && typeof a === 'object' && typeof a.length === 'number' && !(a.propertyIsEnumerable('length'));
 14             },
 15             _make_cascade = function(towrap, that) {
 16                 return (function() {
 17                     towrap.apply(that, arguments);
 18                     return that;
 19                 });
 20             },
 21             toCascade = {
 22                 'beginPath': 'b',
 23                 'closePath': 'c',
 24                 'stroke': 's',
 25                 'fill': 'f'
 26             },
 27             saveRestore = [
 28                 'lineDashArray'
 29             ];
 30     
 31         if(canvas && canvas.getContext) {
 32             xanvas = canvas.getContext('2d');
 33         } else if(canvas && canvas.beginPath) {
 34             xanvas = canvas;
 35         } else if(canvas && typeof canvas === 'string') {
 36             xanvas = document.getElementById(canvas).getContext('2d');
 37         } else {
 38             return canvas;
 39         }
 40 
 41         // generate shortcuts out of the methods in toCascade
 42         for(i in toCascade) {
 43             xanvas[toCascade[i]] = _make_cascade(xanvas[i], xanvas);
 44         }
 45         
 46         /**
 47          * Shortcut for stroke(); fill(); closePath();
 48          */
 49         xanvas.sfc = function() {
 50             this.s();
 51             this.f();
 52             this.c();
 53             return this;
 54         };
 55         
 56         /**
 57          * Shortcut for stroke(); closePath();
 58          */
 59         xanvas.sc = function() {
 60             this.s();
 61             this.c();
 62             return this;
 63         };
 64         
 65         /**
 66          * Shortcut for fill(); closePath();
 67          */
 68         xanvas.fc = function() {
 69             this.f();
 70             this.c();
 71             return this;
 72         };
 73         
 74         /**
 75          * Backup of the canvas.save method
 76          * @private
 77          */
 78         xanvas._save = xanvas.save;
 79         
 80         /**
 81          * Backup of the canvas.restore method
 82          * @private
 83          */
 84         xanvas._restore = xanvas.restore;
 85         
 86         /**
 87          * Canvas state stack for extended xanvas styles.
 88          * @type Array
 89          * @private
 90          */
 91         xanvas._xstack = [];
 92         
 93         /**
 94          * This is the xanvas implementation of the save method to also save the canvas states
 95          * in the canvas state stack.
 96          * @returns {Object} Reference to the xanvas object.
 97          */
 98         xanvas.save = function() {
 99             var i, o = {};
100             
101             for(i in saveRestore) {
102                 o[i] = this[i];
103             }
104             this._xstack.push(o);
105             this._save();
106             return this;
107         };
108         
109         /**
110          * This is the xanvas implementation of the save method to also save the canvas states
111          * in the canvas state stack.
112          * @returns {Object} Reference to the xanvas object.
113          */
114         xanvas.restore = function() {
115             var xs = this._xstack.pop(), i;
116             this._restore();
117             for(i in saveRestore) {
118                 this[i] = xs[i];
119             }
120             return this;
121         };
122 
123         /**
124          * Determines the line dash style using an array of numbers. If the length of the array is odd,
125          * the array is duplicated to determine the dash style.
126          * @example [9, 3, 5] // equals "nine-pixel dash, three-pixel gap,  five-pixel dash, 
127          * nine-pixel gap,  three-pixel dash, five-pixel gap"
128          * [9, 5] // equals "nine-pixel dash, five-pixel gap"
129          * @type Array
130          */
131         xanvas.lineDashArray = [];
132         
133         /**
134          * Backup of the canvas moveTo
135          */
136         xanvas._moveTo = xanvas.moveTo;
137         
138         /**
139          * We need to override moveTo to keep track of the current cursor position for lineTo
140          */
141         xanvas.moveTo = function(tox, toy) {
142             fromx = tox;
143             fromy = toy;
144             this._moveTo(tox, toy);
145             
146             return this;
147         };
148         
149         /**
150          * Backup of the original canvas lineTo
151          */
152         xanvas._lineTo = xanvas.lineTo;
153         
154         /**
155          * We need to override lineTo, because of the new styles like lineDashArray.
156          */
157         xanvas.lineTo = function(tox, toy) {
158             var dA = this.lineDashArray,
159                 dTotal, dX = [], dY = [], d,
160                 i, x, y,
161                 _move_on = function(x, y, i) {
162                     if(i%2 === 0) {
163                         this._lineTo(x, y);
164                     } else {
165                         this._moveTo(x, y);
166                     }
167                 };
168 
169             if(_is_array(this.lineDashArray) && this.lineDashArray.length > 0) {
170                 // we need to dash
171                 // if the length is odd, we need to concatenate dA with itself
172                 dA = dA.length % 2 === 1 ? dA.concat(dA) : dA;
173                 dTotal = Math.sqrt((tox-fromx)*(tox-fromx)+(toy-fromy)*(toy-fromy));
174                 for(i=0; i<dA.length; i++) {
175                     dX[i] = dA[i]/dTotal*(tox-fromx);
176                     dY[i] = dA[i]/dTotal*(toy-fromy);
177                 }
178                 
179                 i = 0;
180                 d = 0;
181                 x = fromx;
182                 y = fromy;
183                 while(d+dA[i] <= dTotal) {
184                     x += dX[i];
185                     y += dY[i];
186                     d += dA[i];
187 
188                     _move_on.apply(this, [x, y, i]);
189                     
190                     i++;
191                     if(i === dA.length) i = 0;
192                 }
193                 
194                 x = tox;
195                 y = toy;
196                 _move_on.apply(this, [x, y, i]);
197             } else {
198                 // no dash array given, just line to (tox, toy)
199                 this._lineTo(tox, toy);
200             }
201             
202             return this;
203         };
204         
205         /**
206          * Draws a line from (fromx, fromy) to (tox, toy).
207          * @param {Number} fromx
208          * @param {Number} fromy
209          * @param {Number} tox
210          * @param {Number} toy
211          * @returns {Object} Reference to xanvas object.
212          */
213         xanvas.line = function(fromx, fromy, tox, toy) {
214             this.moveTo(fromx, fromy);
215             this.lineTo(tox, toy);
216 
217             return this;
218         };
219         
220         /**
221          * Backup of canvas arc method
222          */
223         xanvas._arc = xanvas.arc;
224         
225         /**
226          * Draws an arc.
227          * @param {Number} cx Center x
228          * @param {Number} cy Center y
229          * @param {Number} r Radius of arc
230          * @param {Number} start Start angle
231          * @param {Number} end End angle
232          * @param {Boolean} anticlockwise If true, the arc is drawn counterclockwise
233          * @returns {Object} Reference to xanvas object
234          */
235         xanvas.arc = function(cx, cy, r, start, end, anticlockwise) {
236             var dA = this.lineDashArray,
237                 dTotal, dAngle = [], d,
238                 i, angle, buf;
239 
240             if(_is_array(this.lineDashArray) && this.lineDashArray.length > 0) {
241                 // we need to dash
242 
243                 if(anticlockwise) {
244                     buf = start;
245                     start = end;
246                     end = buf;
247                 }
248                 
249                 // if the length is odd, we need to concatenate dA with itself
250                 dA = dA.length % 2 === 1 ? dA.concat(dA) : dA;
251                 dTotal = Math.abs(r*(end - start));
252                 for(i=0; i<dA.length; i++) {
253                     dAngle[i] = dA[i]/r;
254                 }
255                 
256                 i = 0;
257                 d = 0;
258                 angle = start;
259                 while(Math.abs(d+dA[i]) <= dTotal) {
260                     d += dA[i];
261                     angle += dAngle[i];
262 
263                     if(i%2 === 0) {
264                         this._arc(cx, cy, r, angle-dAngle[i], angle, false);
265                     } else {
266                         this._moveTo(cx + r*Math.cos(angle), cy + r*Math.sin(angle));
267                     }
268                     
269                     i++;
270                     if(i === dA.length) i = 0;
271                 }
272             } else {
273                 // no dash array given, just draw the whole arc
274                 this._arc(cx, cy, r, start, end, anticlockwise);
275             }
276             
277             return this;
278         };
279         
280         /**
281          * Draws a circle around (cx, cy) with radius r.
282          * @param {Number} cx Center x
283          * @param {Number} cy Center y
284          * @param {Number} [r=1] Radius
285          * @returns {Object} Reference to xanvas object.
286          */
287         xanvas.circle = function(cx, cy, r) {
288             r = r || 1;
289             
290             this.arc(cx, cy, r, 0, 2*Math.PI, true);
291             return this;
292         };
293 
294         /**
295          * Get the rgb color values at (x, y)
296          * @param {Number} x
297          * @param {Number} y
298          * @returns {Array} An array containing the red, green, and blue color values and
299          * the alpha value at (x, y).
300          */
301         xanvas.getPixel = function(x, y) {
302             var imageData, index;
303             imageData = this.getImageData(x, y, 1, 1);
304             
305             return imageData.splice(0, 4);
306         };
307         
308         /**
309          * Set the rgb color values at (x, y)
310          * @param {Number} x Coordinate
311          * @param {Number} y Coordinate
312          * @param {Array,Number} rgba Either an array containing the red, green, blue, and alpha value
313          * of the color to set, or just the red value as a number from 0 to 255. In the latter case,
314          * the optional parameters have to be set, too.
315          * @param {Number} [g] Green (from 0 to 255).
316          * @param {Number} [b] Blue (from 0 to 255).
317          * @param {Number} [a] Alpha; 0 equals transparent, 255 equals opaque.
318          * @example x.setPixel(250, 250, 0, 255, 0, 255); // puts a 100% opaque green pixel at (250, 250)
319          * x.setPixel(250,250, 0, 0, 255, 128); // puts a 50% transparent blue pixel at (250, 250)
320          * @returns {Object} Reference to the xanvas object.
321          */
322         xanvas.setPixel = function(x, y, rgba, g, b, a) {
323             var imageData, index, r;
324             imageData = this.getImageData(x, y, 1, 1);
325             
326             if(arguments.length === 3 && _is_array(rgba)) {
327                 r = rgba[0];
328                 g = rgba[1];
329                 b = rgba[2];
330                 a = rgba[3];
331             } else {
332                 r = rgba;
333             }
334             
335             imageData.data[0] = r;
336             imageData.data[1] = g;
337             imageData.data[2] = b;
338             imageData.data[3] = a;
339             
340             this.putImageData(imageData, x, y);
341             return this;
342         };
343         
344         return xanvas;
345     });
346 })(document);
347