      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