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 = '320' height = '400' scrolling='no'></iframe>
  9  * <br/>
 10  * @class Graphics类包含一组创建矢量图形的方法。
 11  * @augments View
 12  * @mixes CacheMixin
 13  * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
 14  * @module hilo/view/Graphics
 15  * @requires hilo/core/Hilo
 16  * @requires hilo/core/Class
 17  * @requires hilo/view/View
 18  * @requires hilo/view/CacheMixin
 19  * @property {Number} lineWidth 笔画的线条宽度。默认为1。只读属性。
 20  * @property {Number} lineAlpha 笔画的线条透明度。默认为1。只读属性。
 21  * @property {String} lineCap 笔画的线条端部样式。可选值有:butt、round、square等,默认为null。只读属性。
 22  * @property {String} lineJoin 笔画的线条连接样式。可选值有:miter、round、bevel等,默认为null。只读属性。
 23  * @property {Number} miterLimit 斜连线长度和线条宽度的最大比率。此属性仅当lineJoin为miter时有效。默认值为10。只读属性。
 24  * @property {String} strokeStyle 笔画边框的样式。默认值为'0',即黑色。只读属性。
 25  * @property {String} fillStyle 内容填充的样式。默认值为'0',即黑色。只读属性。
 26  * @property {Number} fillAlpha 内容填充的透明度。默认值为0。只读属性。
 27  */
 28 var Graphics = (function(){
 29 
 30 var canvas = document.createElement('canvas');
 31 var helpContext = canvas.getContext && canvas.getContext('2d');
 32 
 33 return Class.create(/** @lends Graphics.prototype */{
 34     Extends: View,
 35     Mixes:CacheMixin,
 36     constructor: function(properties){
 37         properties = properties || {};
 38         this.id = this.id || properties.id || Hilo.getUid('Graphics');
 39         Graphics.superclass.constructor.call(this, properties);
 40 
 41         this._actions = [];
 42     },
 43 
 44     lineWidth: 1,
 45     lineAlpha: 1,
 46     lineCap: null, //'butt', 'round', 'square'
 47     lineJoin: null, //'miter', 'round', 'bevel'
 48     miterLimit: 10,
 49     hasStroke: false,
 50     strokeStyle: '0',
 51     hasFill: false,
 52     fillStyle: '0',
 53     fillAlpha: 0,
 54 
 55     /**
 56      * 指定绘制图形的线条样式。
 57      * @param {Number} thickness 线条的粗细值。默认为1。
 58      * @param {String} lineColor 线条的CSS颜色值。默认为黑色,即'0'。
 59      * @param {Number} lineAlpha 线条的透明度值。默认为不透明,即1。
 60      * @param {String} lineCap 线条的端部样式。可选值有:butt、round、square等,默认值为null。
 61      * @param {String} lineJoin 线条的连接样式。可选值有:miter、round、bevel等,默认值为null。
 62      * @param {Number} miterLimit 斜连线长度和线条宽度的最大比率。此属性仅当lineJoin为miter时有效。默认值为10。
 63      * @returns {Graphics} Graphics对象本身。
 64      */
 65     lineStyle: function(thickness, lineColor, lineAlpha, lineCap, lineJoin, miterLimit){
 66         var me = this, addAction = me._addAction;
 67 
 68         addAction.call(me, ['lineWidth', (me.lineWidth = thickness || 1)]);
 69         addAction.call(me, ['strokeStyle', (me.strokeStyle = lineColor || '0')]);
 70         addAction.call(me, ['lineAlpha', (me.lineAlpha = lineAlpha || 1)]);
 71         if(lineCap != undefined) addAction.call(me, ['lineCap', (me.lineCap = lineCap)]);
 72         if(lineJoin != undefined) addAction.call(me, ['lineJoin', (me.lineJoin = lineJoin)]);
 73         if(miterLimit != undefined) addAction.call(me, ['miterLimit', (me.miterLimit = miterLimit)]);
 74         me.hasStroke = true;
 75         return me;
 76     },
 77 
 78     /**
 79      * 指定绘制图形的填充样式和透明度。
 80      * @param {String} fill 填充样式。可以是color、gradient或pattern。
 81      * @param {Number} alpha 透明度。
 82      * @returns {Graphics} Graphics对象本身。
 83      */
 84     beginFill: function(fill, alpha){
 85         var me = this, addAction = me._addAction;
 86 
 87         addAction.call(me, ['fillStyle', (me.fillStyle = fill)]);
 88         addAction.call(me, ['fillAlpha', (me.fillAlpha = alpha || 1)]);
 89         me.hasFill = true;
 90         return me;
 91     },
 92 
 93     /**
 94      * 应用并结束笔画的绘制和图形样式的填充。
 95      * @returns {Graphics} Graphics对象本身。
 96      */
 97     endFill: function(){
 98         var me = this, addAction = me._addAction;
 99 
100         if(me.hasStroke) addAction.call(me, ['stroke']);
101         if(me.hasFill) addAction.call(me, ['fill']);
102         me.setCacheDirty(true);
103         return me;
104     },
105 
106     /**
107      * 指定绘制图形的线性渐变填充样式。
108      * @param {Number} x0 渐变的起始点的x轴坐标。
109      * @param {Number} y0 渐变的起始点的y轴坐标。
110      * @param {Number} x1 渐变的结束点的x轴坐标。
111      * @param {Number} y1 渐变的结束点的y轴坐标。
112      * @param {Array} colors 渐变中使用的CSS颜色值数组。
113      * @param {Array} ratois 渐变中开始与结束之间的位置数组。需与colors数组里的颜色值一一对应,介于0.0与1.0之间的值。
114      * @returns {Graphics} Graphics对象本身。
115      */
116     beginLinearGradientFill: function(x0, y0, x1, y1, colors, ratios){
117         var me = this, gradient = helpContext.createLinearGradient(x0, y0, x1, y1);
118 
119         for (var i = 0, len = colors.length; i < len; i++){
120             gradient.addColorStop(ratios[i], colors[i]);
121         }
122         me.hasFill = true;
123         return me._addAction(['fillStyle', (me.fillStyle = gradient)]);
124     },
125 
126     /**
127      * 指定绘制图形的放射性渐变填充样式。
128      * @param {Number} x0 渐变的起始圆的x轴坐标。
129      * @param {Number} y0 渐变的起始圆的y轴坐标。
130      * @param {Number} r0 渐变的起始圆的半径。
131      * @param {Number} x1 渐变的结束圆的x轴坐标。
132      * @param {Number} y1 渐变的结束圆的y轴坐标。
133      * @param {Number} r1 渐变的结束圆的半径。
134      * @param {Array} colors 渐变中使用的CSS颜色值数组。
135      * @param {Array} ratois 渐变中开始与结束之间的位置数组。需与colors数组里的颜色值一一对应,介于0.0与1.0之间的值。
136      * @returns {Graphics} Graphics对象本身。
137      */
138     beginRadialGradientFill: function(x0, y0, r0, x1, y1, r1, colors, ratios){
139         var me = this, gradient = helpContext.createRadialGradient(x0, y0, r0, x1, y1, r1);
140         for (var i = 0, len = colors.length; i < len; i++)
141         {
142             gradient.addColorStop(ratios[i], colors[i]);
143         }
144         me.hasFill = true;
145         return me._addAction(['fillStyle', (me.fillStyle = gradient)]);
146     },
147 
148     /**
149      * 开始一个位图填充样式。
150      * @param {HTMLImageElement} image 指定填充的Image对象。
151      * @param {String} repetition 指定填充的重复设置参数。它可以是以下任意一个值:repeat, repeat-x, repeat-y, no-repeat。默认为''。
152      * @returns {Graphics} Graphics对象本身。
153      */
154     beginBitmapFill: function(image, repetition){
155         var me = this, pattern = helpContext.createPattern(image, repetition || '');
156         me.hasFill = true;
157         return me._addAction(['fillStyle', (me.fillStyle = pattern)]);
158     },
159 
160     /**
161      * 开始一个新的路径。
162      * @returns {Graphics} Graphics对象本身。
163      */
164     beginPath: function(){
165         return this._addAction(['beginPath']);
166     },
167 
168     /**
169      * 关闭当前的路径。
170      * @returns {Graphics} Graphics对象本身。
171      */
172     closePath: function(){
173         return this._addAction(['closePath']);
174     },
175 
176     /**
177      * 将当前绘制位置移动到点(x, y)。
178      * @param {Number} x x轴坐标。
179      * @param {Number} y y轴坐标。
180      * @returns {Graphics} Graphics对象本身。
181      */
182     moveTo: function(x, y){
183         return this._addAction(['moveTo', x, y]);
184     },
185 
186     /**
187      * 绘制从当前位置开始到点(x, y)结束的直线。
188      * @param {Number} x x轴坐标。
189      * @param {Number} y y轴坐标。
190      * @returns {Graphics} Graphics对象本身。
191      */
192     lineTo: function(x, y){
193         return this._addAction(['lineTo', x, y]);
194     },
195 
196     /**
197      * 绘制从当前位置开始到点(x, y)结束的二次曲线。
198      * @param {Number} cpx 控制点cp的x轴坐标。
199      * @param {Number} cpy 控制点cp的y轴坐标。
200      * @param {Number} x x轴坐标。
201      * @param {Number} y y轴坐标。
202      * @returns {Graphics} Graphics对象本身。
203      */
204     quadraticCurveTo: function(cpx, cpy, x, y){
205         return this._addAction(['quadraticCurveTo', cpx, cpy, x, y]);
206     },
207 
208     /**
209      * 绘制从当前位置开始到点(x, y)结束的贝塞尔曲线。
210      * @param {Number} cp1x 控制点cp1的x轴坐标。
211      * @param {Number} cp1y 控制点cp1的y轴坐标。
212      * @param {Number} cp2x 控制点cp2的x轴坐标。
213      * @param {Number} cp2y 控制点cp2的y轴坐标。
214      * @param {Number} x x轴坐标。
215      * @param {Number} y y轴坐标。
216      * @returns {Graphics} Graphics对象本身。
217      */
218     bezierCurveTo: function(cp1x, cp1y, cp2x, cp2y, x, y){
219         return this._addAction(['bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y]);
220     },
221 
222     /**
223      * 绘制一个矩形。
224      * @param {Number} x x轴坐标。
225      * @param {Number} y y轴坐标。
226      * @param {Number} width 矩形的宽度。
227      * @param {Number} height 矩形的高度。
228      * @returns {Graphics} Graphics对象本身。
229      */
230     drawRect: function(x, y, width, height){
231         return this._addAction(['rect', x, y, width, height]);
232     },
233 
234     /**
235      * 绘制一个复杂的圆角矩形。
236      * @param {Number} x x轴坐标。
237      * @param {Number} y y轴坐标。
238      * @param {Number} width 圆角矩形的宽度。
239      * @param {Number} height 圆角矩形的高度。
240      * @param {Number} cornerTL 圆角矩形的左上圆角大小。
241      * @param {Number} cornerTR 圆角矩形的右上圆角大小。
242      * @param {Number} cornerBR 圆角矩形的右下圆角大小。
243      * @param {Number} cornerBL 圆角矩形的左下圆角大小。
244      * @returns {Graphics} Graphics对象本身。
245      */
246     drawRoundRectComplex: function(x, y, width, height, cornerTL, cornerTR, cornerBR, cornerBL){
247         var me = this, addAction = me._addAction;
248         addAction.call(me, ['moveTo', x + cornerTL, y]);
249         addAction.call(me, ['lineTo', x + width - cornerTR, y]);
250         addAction.call(me, ['arc', x + width - cornerTR, y + cornerTR, cornerTR, -Math.PI/2, 0, false]);
251         addAction.call(me, ['lineTo', x + width, y + height - cornerBR]);
252         addAction.call(me, ['arc', x + width - cornerBR, y + height - cornerBR, cornerBR, 0, Math.PI/2, false]);
253         addAction.call(me, ['lineTo', x + cornerBL, y + height]);
254         addAction.call(me, ['arc', x + cornerBL, y + height - cornerBL, cornerBL, Math.PI/2, Math.PI, false]);
255         addAction.call(me, ['lineTo', x, y + cornerTL]);
256         addAction.call(me, ['arc', x + cornerTL, y + cornerTL, cornerTL, Math.PI, Math.PI*3/2, false]);
257         return me;
258     },
259 
260     /**
261      * 绘制一个圆角矩形。
262      * @param {Number} x x轴坐标。
263      * @param {Number} y y轴坐标。
264      * @param {Number} width 圆角矩形的宽度。
265      * @param {Number} height 圆角矩形的高度。
266      * @param {Number} cornerSize 圆角矩形的圆角大小。
267      * @returns {Graphics} Graphics对象本身。
268      */
269     drawRoundRect: function(x, y, width, height, cornerSize){
270         return this.drawRoundRectComplex(x, y, width, height, cornerSize, cornerSize, cornerSize, cornerSize);
271     },
272 
273     /**
274      * 绘制一个圆。
275      * @param {Number} x x轴坐标。
276      * @param {Number} y y轴坐标。
277      * @param {Number} radius 圆的半径。
278      * @returns {Graphics} Graphics对象本身。
279      */
280     drawCircle: function(x, y, radius){
281         return this._addAction(['arc', x + radius, y + radius, radius, 0, Math.PI * 2, 0]);
282     },
283 
284     /**
285      * 绘制一个椭圆。
286      * @param {Number} x x轴坐标。
287      * @param {Number} y y轴坐标。
288      * @param {Number} width 椭圆的宽度。
289      * @param {Number} height 椭圆的高度。
290      * @returns {Graphics} Graphics对象本身。
291      */
292     drawEllipse: function(x, y, width, height){
293         var me = this;
294         if(width == height) return me.drawCircle(x, y, width);
295 
296         var addAction = me._addAction;
297         var w = width / 2, h = height / 2, C = 0.5522847498307933, cx = C * w, cy = C * h;
298         x = x + w;
299         y = y + h;
300 
301         addAction.call(me, ['moveTo', x + w, y]);
302         addAction.call(me, ['bezierCurveTo', x + w, y - cy, x + cx, y - h, x, y - h]);
303         addAction.call(me, ['bezierCurveTo', x - cx, y - h, x - w, y - cy, x - w, y]);
304         addAction.call(me, ['bezierCurveTo', x - w, y + cy, x - cx, y + h, x, y + h]);
305         addAction.call(me, ['bezierCurveTo', x + cx, y + h, x + w, y + cy, x + w, y]);
306         return me;
307     },
308 
309     /**
310      * 根据参数指定的SVG数据绘制一条路径。
311      * 代码示例:
312      * <p>var path = 'M250 150 L150 350 L350 350 Z';</p>
313      * <p>var shape = new Hilo.Graphics({width:500, height:500});</p>
314      * <p>shape.drawSVGPath(path).beginFill('#0ff').endFill();</p>
315      * @param {String} pathData 要绘制的SVG路径数据。
316      * @returns {Graphics} Graphics对象本身。
317      */
318     drawSVGPath: function(pathData){
319         var me = this, addAction = me._addAction,
320             path = pathData.split(/,| (?=[a-zA-Z])/);
321 
322         addAction.call(me, ['beginPath']);
323         for(var i = 0, len = path.length; i < len; i++){
324             var str = path[i], cmd = str[0].toUpperCase(), p = str.substring(1).split(/,| /);
325             if(p[0].length == 0) p.shift();
326 
327             switch(cmd){
328                 case 'M':
329                     addAction.call(me, ['moveTo', p[0], p[1]]);
330                     break;
331                 case 'L':
332                     addAction.call(me, ['lineTo', p[0], p[1]]);
333                     break;
334                 case 'C':
335                     addAction.call(me, ['bezierCurveTo', p[0], p[1], p[2], p[3], p[4], p[5]]);
336                     break;
337                 case 'Z':
338                     addAction.call(me, ['closePath']);
339                     break;
340             }
341         }
342         return me;
343     },
344 
345     /**
346      * 执行全部绘制动作。内部私有方法。
347      * @private
348      */
349     _draw: function(context){
350         var me = this, actions = me._actions, len = actions.length, i;
351 
352         context.beginPath();
353         for(i = 0; i < len; i++){
354             var action = actions[i],
355                 f = action[0],
356                 args = action.length > 1 ? action.slice(1) : null;
357 
358             if(typeof(context[f]) == 'function') context[f].apply(context, args);
359             else context[f] = action[1];
360         }
361     },
362 
363     /**
364      * 重写渲染实现。
365      * @private
366      */
367     render: function(renderer, delta){
368         var me = this, canvas = renderer.canvas;
369         if(renderer.renderType === 'canvas'){
370             me._draw(renderer.context);
371         }else{
372             me.cache();
373             renderer.draw(me);
374         }
375     },
376 
377     /**
378      * 清除所有绘制动作并复原所有初始状态。
379      * @returns {Graphics} Graphics对象本身。
380      */
381     clear: function(){
382         var me = this;
383 
384         me._actions.length = 0;
385         me.lineWidth = 1;
386         me.lineAlpha = 1;
387         me.lineCap = null;
388         me.lineJoin = null;
389         me.miterLimit = 10;
390         me.hasStroke = false;
391         me.strokeStyle = '0';
392         me.hasFill = false;
393         me.fillStyle = '0';
394         me.fillAlpha = 1;
395 
396         me.setCacheDirty(true);
397         return me;
398     },
399 
400     /**
401      * 添加一个绘制动作。内部私有方法。
402      * @private
403      */
404     _addAction: function(action){
405         var me = this;
406         me._actions.push(action);
407         return me;
408     }
409 
410 });
411 
412 })();