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