1 /** 2 * Hilo 3 * Copyright 2015 alibaba.com 4 * Licensed under the MIT License 5 */ 6 7 /** 8 * <iframe src='../../../examples/Graphics.html?noHeader' width = '430' height = '400' scrolling='no'></iframe> 9 * <br/> 10 * @class Graphics class contains a group of functions for creating vector graphics. 11 * @augments View 12 * @mixes CacheMixin 13 * @borrows CacheMixin#cache as #cache 14 * @borrows CacheMixin#updateCache as #updateCache 15 * @borrows CacheMixin#setCacheDirty as #setCacheDirty 16 * @param {Object} properties Properties parameters of the object to create. Contains all writable properties of this class. 17 * @module hilo/view/Graphics 18 * @requires hilo/core/Hilo 19 * @requires hilo/core/Class 20 * @requires hilo/view/View 21 * @requires hilo/view/CacheMixin 22 * @property {Number} lineWidth The thickness of lines in space units, default value is 1, readonly! 23 * @property {Number} lineAlpha The alpha value (transparency) of line, default value is 1, readonly! 24 * @property {String} lineCap The style of how every end point of line are drawn, value options: butt, round, square. default value is null, readonly! 25 * @property {String} lineJoin The joint style of two lines. value options: miter, round, bevel. default value is null, readonly! 26 * @property {Number} miterLimit The miter limit ratio in space units, works only when lineJoin value is miter. default value is 10, readonly! 27 * @property {String} strokeStyle The color or style to use for lines around shapes, default value is 0 (the black color), readonly! 28 * @property {String} fillStyle The color or style to use inside shapes, default value is 0 (the black color), readonly! 29 * @property {Number} fillAlpha The transparency of color or style inside shapes, default value is 0, readonly! 30 */ 31 var Graphics = (function(){ 32 33 var canvas = document.createElement('canvas'); 34 var helpContext = canvas.getContext && canvas.getContext('2d'); 35 36 return Class.create(/** @lends Graphics.prototype */{ 37 Extends: View, 38 Mixes:CacheMixin, 39 constructor: function(properties){ 40 properties = properties || {}; 41 this.id = this.id || properties.id || Hilo.getUid('Graphics'); 42 Graphics.superclass.constructor.call(this, properties); 43 44 this._actions = []; 45 }, 46 47 lineWidth: 1, 48 lineAlpha: 1, 49 lineCap: null, //'butt', 'round', 'square' 50 lineJoin: null, //'miter', 'round', 'bevel' 51 miterLimit: 10, 52 hasStroke: false, 53 strokeStyle: '0', 54 hasFill: false, 55 fillStyle: '0', 56 fillAlpha: 0, 57 58 /** 59 * Set the lines style for drawing shapes. 60 * @param {Number} thickness The thickness of lines, default value is 1. 61 * @param {String} lineColor The CSS color value of lines, default value is 0 (the black color). 62 * @param {Number} lineAlpha The transparency of lines, default value is 1 (fully opaque). 63 * @param {String} lineCap The style of how every end point of line are drawn, value options: butt, round, square. default value is null. 64 * @param {String} lineJoin The joint style of two lines. value options: miter, round, bevel. default value is null. 65 * @param {Number} miterLimit The miter limit ratio in space units, works only when lineJoin value is miter. default value is 10. 66 * @returns {Graphics} The Graphics Object. 67 */ 68 lineStyle: function(thickness, lineColor, lineAlpha, lineCap, lineJoin, miterLimit){ 69 var me = this, addAction = me._addAction; 70 71 addAction.call(me, ['lineWidth', (me.lineWidth = thickness || 1)]); 72 addAction.call(me, ['strokeStyle', (me.strokeStyle = lineColor || '0')]); 73 addAction.call(me, ['lineAlpha', (me.lineAlpha = lineAlpha || 1)]); 74 if(lineCap != undefined) addAction.call(me, ['lineCap', (me.lineCap = lineCap)]); 75 if(lineJoin != undefined) addAction.call(me, ['lineJoin', (me.lineJoin = lineJoin)]); 76 if(miterLimit != undefined) addAction.call(me, ['miterLimit', (me.miterLimit = miterLimit)]); 77 me.hasStroke = true; 78 return me; 79 }, 80 81 /** 82 * 设置虚线样式 83 * @param {Number[]} segments 一组描述交替绘制线段和间距(坐标空间单位)长度的数字 84 * @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/setLineDash 85 */ 86 setLineDash: function(segments){ 87 return this._addAction(['setLineDash', segments]); 88 }, 89 90 /** 91 * Set how to fill shapes and the transparency. 92 * @param {String} fill Filling style. this can be color, gradient or pattern. 93 * @param {Number} alpha Transparency. 94 * @returns {Graphics} The Graphics Object. 95 */ 96 beginFill: function(fill, alpha){ 97 var me = this, addAction = me._addAction; 98 99 addAction.call(me, ['fillStyle', (me.fillStyle = fill)]); 100 addAction.call(me, ['fillAlpha', (me.fillAlpha = alpha || 1)]); 101 me.hasFill = true; 102 return me; 103 }, 104 105 /** 106 * Apply and end lines-drawing and shapes-filling. 107 * @returns {Graphics} The Graphics Object. 108 */ 109 endFill: function(){ 110 var me = this, addAction = me._addAction; 111 112 if(me.hasStroke) addAction.call(me, ['stroke']); 113 if(me.hasFill) addAction.call(me, ['fill']); 114 me.setCacheDirty(true); 115 return me; 116 }, 117 118 /** 119 * Set linear gradient filling style to draw shapes. 120 * @param {Number} x0 The x-coordinate value of the linear gradient start point. 121 * @param {Number} y0 The y-coordinate value of the linear gradient start point. 122 * @param {Number} x1 The x-coordinate value of the linear gradient end point. 123 * @param {Number} y1 The y-coordinate value of the linear gradient end point. 124 * @param {Array} colors An array of CSS colors used in the linear gradient. 125 * @param {Array} ratios An array of position between start point and end point, should be one-to-one to colors in the colors array. each value range between 0.0 to 1.0. 126 * @returns {Graphics} The Graphics Object. 127 */ 128 beginLinearGradientFill: function(x0, y0, x1, y1, colors, ratios){ 129 var me = this, gradient = helpContext.createLinearGradient(x0, y0, x1, y1); 130 131 for (var i = 0, len = colors.length; i < len; i++){ 132 gradient.addColorStop(ratios[i], colors[i]); 133 } 134 me.hasFill = true; 135 return me._addAction(['fillStyle', (me.fillStyle = gradient)]); 136 }, 137 138 /** 139 * Set radial gradient filling style to draw shapes. 140 * @param {Number} x0 The x-coordinate value of the radial gradient start circle. 141 * @param {Number} y0 The y-coordinate value of the radial gradient start circle. 142 * @param {Number} r0 The diameter of the radial gradient start circle. 143 * @param {Number} x1 The x-coordinate value of the radial gradient end circle. 144 * @param {Number} y1 The y-coordinate value of the radial gradient end circle. 145 * @param {Number} r1 The radius of the radial gradient end circle. 146 * @param {Array} colors An array of CSS colors used in the radial gradient. 147 * @param {Array} ratios An array of position between start circle and end circle, should be one-to-one to colors in the colors array. each value range between 0.0 to 1.0. 148 * @returns {Graphics} The Graphics Object. 149 */ 150 beginRadialGradientFill: function(x0, y0, r0, x1, y1, r1, colors, ratios){ 151 var me = this, gradient = helpContext.createRadialGradient(x0, y0, r0, x1, y1, r1); 152 for (var i = 0, len = colors.length; i < len; i++) 153 { 154 gradient.addColorStop(ratios[i], colors[i]); 155 } 156 me.hasFill = true; 157 return me._addAction(['fillStyle', (me.fillStyle = gradient)]); 158 }, 159 160 /** 161 * Begin an image filling pattern. 162 * @param {HTMLImageElement} image The Image to fill. 163 * @param {String} repetition The fill repetition style, can be one of valus:repeat, repeat-x, repeat-y, no-repeat. default valus is ''. 164 * @returns {Graphics} The Graphics Object. 165 */ 166 beginBitmapFill: function(image, repetition){ 167 var me = this, pattern = helpContext.createPattern(image, repetition || ''); 168 me.hasFill = true; 169 return me._addAction(['fillStyle', (me.fillStyle = pattern)]); 170 }, 171 172 /** 173 * Begin a new path. 174 * @returns {Graphics} The Graphics Object. 175 */ 176 beginPath: function(){ 177 return this._addAction(['beginPath']); 178 }, 179 180 /** 181 * Close current path. 182 * @returns {Graphics} The Graphics Object. 183 */ 184 closePath: function(){ 185 return this._addAction(['closePath']); 186 }, 187 188 /** 189 * Move current drawing point to a new point on coordinate values (x, y). 190 * @param {Number} x The x-coordinate value. 191 * @param {Number} y The y-coordinate value. 192 * @returns {Graphics} The Graphics Object. 193 */ 194 moveTo: function(x, y){ 195 return this._addAction(['moveTo', x, y]); 196 }, 197 198 /** 199 * Draw a line from current point to the point on the coordinate value (x, y). 200 * @param {Number} x The x-coordinate value. 201 * @param {Number} y The y-coordinate value. 202 * @returns {Graphics} The Graphics Object. 203 */ 204 lineTo: function(x, y){ 205 return this._addAction(['lineTo', x, y]); 206 }, 207 208 /** 209 * Draw a quadratic Bézier curve from current point to the point on coordinate (x, y). 210 * @param {Number} cpx The x-coordinate value of the Bézier curve control point cp. 211 * @param {Number} cpy The y-coordinate value of the Bézier curve control point cp. 212 * @param {Number} x The x-coordinate value. 213 * @param {Number} y The y-coordinate value. 214 * @returns {Graphics} The Graphics Object. 215 */ 216 quadraticCurveTo: function(cpx, cpy, x, y){ 217 return this._addAction(['quadraticCurveTo', cpx, cpy, x, y]); 218 }, 219 220 /** 221 * Draw a Bézier curve from current point to the point on coordinate (x, y). 222 * @param {Number} cp1x The x-coordinate value of the Bézier curve control point cp1. 223 * @param {Number} cp1y The y-coordinate value of the Bézier curve control point cp1. 224 * @param {Number} cp2x The x-coordinate value of the Bézier curve control point cp2. 225 * @param {Number} cp2y The y-coordinate value of the Bézier curve control point cp2. 226 * @param {Number} x The x-coordinate value. 227 * @param {Number} y The y-coordinate value. 228 * @returns {Graphics} The Graphics Object. 229 */ 230 bezierCurveTo: function(cp1x, cp1y, cp2x, cp2y, x, y){ 231 return this._addAction(['bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y]); 232 }, 233 234 /** 235 * Draw a rectangle. 236 * @param {Number} x The x-coordinate value. 237 * @param {Number} y The y-coordinate value. 238 * @param {Number} width The width of the rectangle. 239 * @param {Number} height The height of the rectangle. 240 * @returns {Graphics} The Graphics Object. 241 */ 242 drawRect: function(x, y, width, height){ 243 return this._addAction(['rect', x, y, width, height]); 244 }, 245 246 /** 247 * Draw a complex rounded rectangle. 248 * @param {Number} x The x-coordinate value. 249 * @param {Number} y The y-coordinate value. 250 * @param {Number} width The width of rounded rectangle. 251 * @param {Number} height The height of rounded rectangle. 252 * @param {Number} cornerTL The size of the rounded corner on the top-left of the rounded rectangle. 253 * @param {Number} cornerTR The size of the rounded corner on the top-right of the rounded rectangle. 254 * @param {Number} cornerBR The size of the rounded corner on the bottom-left of the rounded rectangle. 255 * @param {Number} cornerBL The size of the rounded corner on the bottom-right of the rounded rectangle. 256 * @returns {Graphics} The Graphics Object. 257 */ 258 drawRoundRectComplex: function(x, y, width, height, cornerTL, cornerTR, cornerBR, cornerBL){ 259 var me = this, addAction = me._addAction; 260 addAction.call(me, ['moveTo', x + cornerTL, y]); 261 addAction.call(me, ['lineTo', x + width - cornerTR, y]); 262 addAction.call(me, ['arc', x + width - cornerTR, y + cornerTR, cornerTR, -Math.PI/2, 0, false]); 263 addAction.call(me, ['lineTo', x + width, y + height - cornerBR]); 264 addAction.call(me, ['arc', x + width - cornerBR, y + height - cornerBR, cornerBR, 0, Math.PI/2, false]); 265 addAction.call(me, ['lineTo', x + cornerBL, y + height]); 266 addAction.call(me, ['arc', x + cornerBL, y + height - cornerBL, cornerBL, Math.PI/2, Math.PI, false]); 267 addAction.call(me, ['lineTo', x, y + cornerTL]); 268 addAction.call(me, ['arc', x + cornerTL, y + cornerTL, cornerTL, Math.PI, Math.PI*3/2, false]); 269 return me; 270 }, 271 272 /** 273 * Draw a rounded rectangle. 274 * @param {Number} x The x-coordinate value. 275 * @param {Number} y The y-coordinate value. 276 * @param {Number} width The width of rounded rectangle. 277 * @param {Number} height The height of rounded rectangle. 278 * @param {Number} cornerSize The size of all rounded corners. 279 * @returns {Graphics} The Graphics Object. 280 */ 281 drawRoundRect: function(x, y, width, height, cornerSize){ 282 return this.drawRoundRectComplex(x, y, width, height, cornerSize, cornerSize, cornerSize, cornerSize); 283 }, 284 285 /** 286 * Draw a circle. 287 * @param {Number} x The x-coordinate value. 288 * @param {Number} y The y-coordinate value. 289 * @param {Number} radius The radius of the circle. 290 * @returns {Graphics} The Graphics Object. 291 */ 292 drawCircle: function(x, y, radius){ 293 return this._addAction(['arc', x + radius, y + radius, radius, 0, Math.PI * 2, 0]); 294 }, 295 296 /** 297 * Draw an ellipse. 298 * @param {Number} x The x-coordinate value. 299 * @param {Number} y The y-coordinate value. 300 * @param {Number} width The width of the ellipse. 301 * @param {Number} height The height of the ellipse. 302 * @returns {Graphics} The Graphics Object. 303 */ 304 drawEllipse: function(x, y, width, height){ 305 var me = this; 306 if(width == height) return me.drawCircle(x, y, width); 307 308 var addAction = me._addAction; 309 var w = width / 2, h = height / 2, C = 0.5522847498307933, cx = C * w, cy = C * h; 310 x = x + w; 311 y = y + h; 312 313 addAction.call(me, ['moveTo', x + w, y]); 314 addAction.call(me, ['bezierCurveTo', x + w, y - cy, x + cx, y - h, x, y - h]); 315 addAction.call(me, ['bezierCurveTo', x - cx, y - h, x - w, y - cy, x - w, y]); 316 addAction.call(me, ['bezierCurveTo', x - w, y + cy, x - cx, y + h, x, y + h]); 317 addAction.call(me, ['bezierCurveTo', x + cx, y + h, x + w, y + cy, x + w, y]); 318 return me; 319 }, 320 321 /** 322 * Draw a path from the SVG data given by parameters. Not support Arcs. 323 * Demo: 324 * <p>var path = 'M250 150 L150 350 L350 350 Z';</p> 325 * <p>var shape = new Hilo.Graphics({width:500, height:500});</p> 326 * <p>shape.drawSVGPath(path).beginFill('#0ff').endFill();</p> 327 * @param {String} pathData The SVG path data to draw. 328 * @returns {Graphics} The Graphics Object. 329 */ 330 drawSVGPath: function(pathData){ 331 var me = this, addAction = me._addAction, 332 path = pathData.replace(/,/g, ' ').replace(/-/g, ' -').split(/(?=[a-zA-Z])/); 333 addAction.call(me, ['beginPath']); 334 var currentPoint = {x:0, y:0}; 335 var lastControlPoint = {x:0, y:0}; 336 var lastCmd; 337 for(var i = 0, len = path.length; i < len; i++){ 338 var str = path[i]; 339 if(!str.length){ 340 continue; 341 } 342 var realCmd = str[0]; 343 var cmd = realCmd.toUpperCase(); 344 var p = this._getSVGParams(str); 345 var useRelative = cmd !== realCmd; 346 347 switch(cmd){ 348 case 'M': 349 if(useRelative){ 350 this._convertToAbsolute(currentPoint, p); 351 } 352 addAction.call(me, ['moveTo', p[0], p[1]]); 353 this._setCurrentPoint(currentPoint, p[0], p[1]); 354 break; 355 case 'L': 356 if(useRelative){ 357 this._convertToAbsolute(currentPoint, p); 358 } 359 addAction.call(me, ['lineTo', p[0], p[1]]); 360 this._setCurrentPoint(currentPoint, p[0], p[1]); 361 break; 362 case 'H': 363 if(useRelative){ 364 p[0] += currentPoint.x; 365 } 366 addAction.call(me, ['lineTo', p[0], currentPoint.y]); 367 currentPoint.x = p[0]; 368 break; 369 case 'V': 370 if(useRelative){ 371 p[0] += currentPoint.y; 372 } 373 addAction.call(me, ['lineTo', currentPoint.x, p[0]]); 374 currentPoint.y = p[0]; 375 break; 376 case 'Z': 377 addAction.call(me, ['closePath']); 378 break; 379 case 'C': 380 if(useRelative){ 381 this._convertToAbsolute(currentPoint, p); 382 } 383 addAction.call(me, ['bezierCurveTo', p[0], p[1], p[2], p[3], p[4], p[5]]); 384 lastControlPoint.x = p[2]; 385 lastControlPoint.y = p[3]; 386 this._setCurrentPoint(currentPoint, p[4], p[5]); 387 break; 388 case 'S': 389 if(useRelative){ 390 this._convertToAbsolute(currentPoint, p); 391 } 392 if(lastCmd === 'C' || lastCmd === 'S'){ 393 controlPoint = this._getReflectionPoint(currentPoint, lastControlPoint); 394 } 395 else{ 396 controlPoint = currentPoint; 397 } 398 addAction.call(me, ['bezierCurveTo', controlPoint.x, controlPoint.y, p[0], p[1], p[2], p[3]]); 399 lastControlPoint.x = p[0]; 400 lastControlPoint.y = p[1]; 401 this._setCurrentPoint(currentPoint, p[2], p[3]); 402 break; 403 case 'Q': 404 if(useRelative){ 405 this._convertToAbsolute(currentPoint, p); 406 } 407 addAction.call(me, ['quadraticCurveTo', p[0], p[1], p[2], p[3]]); 408 lastControlPoint.x = p[0]; 409 lastControlPoint.y = p[1]; 410 this._setCurrentPoint(currentPoint, p[2], p[3]); 411 break; 412 case 'T': 413 if(useRelative){ 414 this._convertToAbsolute(currentPoint, p); 415 } 416 var controlPoint; 417 if(lastCmd === 'Q' || lastCmd === 'T'){ 418 controlPoint = this._getReflectionPoint(currentPoint, lastControlPoint); 419 } 420 else{ 421 controlPoint = currentPoint; 422 } 423 addAction.call(me, ['quadraticCurveTo', controlPoint.x, controlPoint.y, p[0], p[1]]); 424 lastControlPoint = controlPoint; 425 this._setCurrentPoint(currentPoint, p[0], p[1]); 426 break; 427 } 428 lastCmd = cmd; 429 430 } 431 return me; 432 }, 433 _getSVGParams:function(str){ 434 var p = str.substring(1).replace(/[\s]+$|^[\s]+/g, '').split(/[\s]+/); 435 if(p[0].length == 0) { 436 p.shift(); 437 } 438 for(var i = 0, l = p.length;i < l;i ++){ 439 p[i] = parseFloat(p[i]); 440 } 441 return p; 442 }, 443 _convertToAbsolute:function(currentPoint, data){ 444 for(var i = 0, l = data.length;i < l;i ++){ 445 if(i%2 === 0){ 446 data[i] += currentPoint.x; 447 } 448 else{ 449 data[i] += currentPoint.y; 450 } 451 } 452 }, 453 _setCurrentPoint:function(currentPoint, x, y){ 454 currentPoint.x = x; 455 currentPoint.y = y; 456 }, 457 _getReflectionPoint:function(centerPoint, point){ 458 return { 459 x:centerPoint.x * 2 - point.x, 460 y:centerPoint.y * 2 - point.y 461 }; 462 }, 463 464 /** 465 * Apply all draw actions. private function. 466 * @private 467 */ 468 _draw: function(context){ 469 var me = this, actions = me._actions, len = actions.length, i; 470 471 context.beginPath(); 472 for(i = 0; i < len; i++){ 473 var action = actions[i], 474 f = action[0], 475 args = action.length > 1 ? action.slice(1) : null; 476 477 if(typeof(context[f]) == 'function') context[f].apply(context, args); 478 else context[f] = action[1]; 479 } 480 }, 481 482 /** 483 * Overwrite render function. 484 * @private 485 */ 486 render: function(renderer, delta){ 487 var me = this; 488 if(renderer.renderType === 'canvas'){ 489 me._draw(renderer.context); 490 }else{ 491 me.cache(); 492 renderer.draw(me); 493 } 494 }, 495 496 /** 497 * Clear all draw actions and reset to the initial state. 498 * @returns {Graphics} The Graphics Object. 499 */ 500 clear: function(){ 501 var me = this; 502 503 me._actions.length = 0; 504 me.lineWidth = 1; 505 me.lineAlpha = 1; 506 me.lineCap = null; 507 me.lineJoin = null; 508 me.miterLimit = 10; 509 me.hasStroke = false; 510 me.strokeStyle = '0'; 511 me.hasFill = false; 512 me.fillStyle = '0'; 513 me.fillAlpha = 1; 514 515 me.setCacheDirty(true); 516 return me; 517 }, 518 519 /** 520 * Add a draw action, this is a private function. 521 * @private 522 */ 523 _addAction: function(action){ 524 var me = this; 525 me._actions.push(action); 526 return me; 527 } 528 529 }); 530 531 })(); 532