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