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类包含一组创建矢量图形的方法。 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 创建对象的属性参数。可包含此类所有可写属性。 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 笔画的线条宽度。默认为1。只读属性。 23 * @property {Number} lineAlpha 笔画的线条透明度。默认为1。只读属性。 24 * @property {String} lineCap 笔画的线条端部样式。可选值有:butt、round、square等,默认为null。只读属性。 25 * @property {String} lineJoin 笔画的线条连接样式。可选值有:miter、round、bevel等,默认为null。只读属性。 26 * @property {Number} miterLimit 斜连线长度和线条宽度的最大比率。此属性仅当lineJoin为miter时有效。默认值为10。只读属性。 27 * @property {String} strokeStyle 笔画边框的样式。默认值为'0',即黑色。只读属性。 28 * @property {String} fillStyle 内容填充的样式。默认值为'0',即黑色。只读属性。 29 * @property {Number} fillAlpha 内容填充的透明度。默认值为0。只读属性。 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 * 指定绘制图形的线条样式。 60 * @param {Number} thickness 线条的粗细值。默认为1。 61 * @param {String} lineColor 线条的CSS颜色值。默认为黑色,即'0'。 62 * @param {Number} lineAlpha 线条的透明度值。默认为不透明,即1。 63 * @param {String} lineCap 线条的端部样式。可选值有:butt、round、square等,默认值为null。 64 * @param {String} lineJoin 线条的连接样式。可选值有:miter、round、bevel等,默认值为null。 65 * @param {Number} miterLimit 斜连线长度和线条宽度的最大比率。此属性仅当lineJoin为miter时有效。默认值为10。 66 * @returns {Graphics} Graphics对象本身。 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 * 指定绘制图形的填充样式和透明度。 92 * @param {String} fill 填充样式。可以是color、gradient或pattern。 93 * @param {Number} alpha 透明度。 94 * @returns {Graphics} Graphics对象本身。 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 * 应用并结束笔画的绘制和图形样式的填充。 107 * @returns {Graphics} Graphics对象本身。 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 * 指定绘制图形的线性渐变填充样式。 120 * @param {Number} x0 渐变的起始点的x轴坐标。 121 * @param {Number} y0 渐变的起始点的y轴坐标。 122 * @param {Number} x1 渐变的结束点的x轴坐标。 123 * @param {Number} y1 渐变的结束点的y轴坐标。 124 * @param {Array} colors 渐变中使用的CSS颜色值数组。 125 * @param {Array} ratios 渐变中开始与结束之间的位置数组。需与colors数组里的颜色值一一对应,介于0.0与1.0之间的值。 126 * @returns {Graphics} Graphics对象本身。 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 * 指定绘制图形的放射性渐变填充样式。 140 * @param {Number} x0 渐变的起始圆的x轴坐标。 141 * @param {Number} y0 渐变的起始圆的y轴坐标。 142 * @param {Number} r0 渐变的起始圆的半径。 143 * @param {Number} x1 渐变的结束圆的x轴坐标。 144 * @param {Number} y1 渐变的结束圆的y轴坐标。 145 * @param {Number} r1 渐变的结束圆的半径。 146 * @param {Array} colors 渐变中使用的CSS颜色值数组。 147 * @param {Array} ratios 渐变中开始与结束之间的位置数组。需与colors数组里的颜色值一一对应,介于0.0与1.0之间的值。 148 * @returns {Graphics} Graphics对象本身。 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 * 开始一个位图填充样式。 162 * @param {HTMLImageElement} image 指定填充的Image对象。 163 * @param {String} repetition 指定填充的重复设置参数。它可以是以下任意一个值:repeat, repeat-x, repeat-y, no-repeat。默认为''。 164 * @returns {Graphics} Graphics对象本身。 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 * 开始一个新的路径。 174 * @returns {Graphics} Graphics对象本身。 175 */ 176 beginPath: function(){ 177 return this._addAction(['beginPath']); 178 }, 179 180 /** 181 * 关闭当前的路径。 182 * @returns {Graphics} Graphics对象本身。 183 */ 184 closePath: function(){ 185 return this._addAction(['closePath']); 186 }, 187 188 /** 189 * 将当前绘制位置移动到点(x, y)。 190 * @param {Number} x x轴坐标。 191 * @param {Number} y y轴坐标。 192 * @returns {Graphics} Graphics对象本身。 193 */ 194 moveTo: function(x, y){ 195 return this._addAction(['moveTo', x, y]); 196 }, 197 198 /** 199 * 绘制从当前位置开始到点(x, y)结束的直线。 200 * @param {Number} x x轴坐标。 201 * @param {Number} y y轴坐标。 202 * @returns {Graphics} Graphics对象本身。 203 */ 204 lineTo: function(x, y){ 205 return this._addAction(['lineTo', x, y]); 206 }, 207 208 /** 209 * 绘制从当前位置开始到点(x, y)结束的二次曲线。 210 * @param {Number} cpx 控制点cp的x轴坐标。 211 * @param {Number} cpy 控制点cp的y轴坐标。 212 * @param {Number} x x轴坐标。 213 * @param {Number} y y轴坐标。 214 * @returns {Graphics} Graphics对象本身。 215 */ 216 quadraticCurveTo: function(cpx, cpy, x, y){ 217 return this._addAction(['quadraticCurveTo', cpx, cpy, x, y]); 218 }, 219 220 /** 221 * 绘制从当前位置开始到点(x, y)结束的贝塞尔曲线。 222 * @param {Number} cp1x 控制点cp1的x轴坐标。 223 * @param {Number} cp1y 控制点cp1的y轴坐标。 224 * @param {Number} cp2x 控制点cp2的x轴坐标。 225 * @param {Number} cp2y 控制点cp2的y轴坐标。 226 * @param {Number} x x轴坐标。 227 * @param {Number} y y轴坐标。 228 * @returns {Graphics} Graphics对象本身。 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 * 绘制一个矩形。 236 * @param {Number} x x轴坐标。 237 * @param {Number} y y轴坐标。 238 * @param {Number} width 矩形的宽度。 239 * @param {Number} height 矩形的高度。 240 * @returns {Graphics} Graphics对象本身。 241 */ 242 drawRect: function(x, y, width, height){ 243 return this._addAction(['rect', x, y, width, height]); 244 }, 245 246 /** 247 * 绘制一个复杂的圆角矩形。 248 * @param {Number} x x轴坐标。 249 * @param {Number} y y轴坐标。 250 * @param {Number} width 圆角矩形的宽度。 251 * @param {Number} height 圆角矩形的高度。 252 * @param {Number} cornerTL 圆角矩形的左上圆角大小。 253 * @param {Number} cornerTR 圆角矩形的右上圆角大小。 254 * @param {Number} cornerBR 圆角矩形的右下圆角大小。 255 * @param {Number} cornerBL 圆角矩形的左下圆角大小。 256 * @returns {Graphics} Graphics对象本身。 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 * 绘制一个圆角矩形。 274 * @param {Number} x x轴坐标。 275 * @param {Number} y y轴坐标。 276 * @param {Number} width 圆角矩形的宽度。 277 * @param {Number} height 圆角矩形的高度。 278 * @param {Number} cornerSize 圆角矩形的圆角大小。 279 * @returns {Graphics} Graphics对象本身。 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 * 绘制一个圆。 287 * @param {Number} x x轴坐标。 288 * @param {Number} y y轴坐标。 289 * @param {Number} radius 圆的半径。 290 * @returns {Graphics} Graphics对象本身。 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 * 绘制一个椭圆。 298 * @param {Number} x x轴坐标。 299 * @param {Number} y y轴坐标。 300 * @param {Number} width 椭圆的宽度。 301 * @param {Number} height 椭圆的高度。 302 * @returns {Graphics} Graphics对象本身。 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 * 根据参数指定的SVG数据绘制一条路径。不支持Arcs。 323 * 代码示例: 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 要绘制的SVG路径数据。 328 * @returns {Graphics} Graphics对象本身。 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 * 执行全部绘制动作。内部私有方法。 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 * 重写渲染实现。 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 * 清除所有绘制动作并复原所有初始状态。 498 * @returns {Graphics} Graphics对象本身。 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 * 添加一个绘制动作。内部私有方法。 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