1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @language=en
  9  * <iframe src='../../../examples/Graphics.html?noHeader' width = '320' height = '400' scrolling='no'></iframe>
 10  * <br/>
 11  * @class Graphics class contains a group of functions for creating vector graphics.
 12  * @augments View
 13  * @mixes CacheMixin
 14  * @param {Object} properties Properties parameters of the object to create. Contains all writable properties of this class.
 15  * @module hilo/view/Graphics
 16  * @requires hilo/core/Hilo
 17  * @requires hilo/core/Class
 18  * @requires hilo/view/View
 19  * @requires hilo/view/CacheMixin
 20  * @property {Number} lineWidth The thickness of lines in space units, default value is 1, readonly!
 21  * @property {Number} lineAlpha The alpha value (transparency) of line, default value is 1, readonly!
 22  * @property {String} lineCap The style of how every end point of line are drawn, value options: butt, round, square. default value is null, readonly!
 23  * @property {String} lineJoin The joint style of two lines. value options: miter, round, bevel. default value is null, readonly!
 24  * @property {Number} miterLimit The miter limit ratio in space units, works only when lineJoin value is miter. default value is 10, readonly!
 25  * @property {String} strokeStyle The color or style to use for lines around shapes, default value is 0 (the black color), readonly!
 26  * @property {String} fillStyle The color or style to use inside shapes, default value is 0 (the black color), readonly!
 27  * @property {Number} fillAlpha The transparency of color or style inside shapes, default value is 0, readonly!
 28  */
 29 var Graphics = (function(){
 30 
 31 var canvas = document.createElement('canvas');
 32 var helpContext = canvas.getContext && canvas.getContext('2d');
 33 
 34 return Class.create(/** @lends Graphics.prototype */{
 35     Extends: View,
 36     Mixes:CacheMixin,
 37     constructor: function(properties){
 38         properties = properties || {};
 39         this.id = this.id || properties.id || Hilo.getUid('Graphics');
 40         Graphics.superclass.constructor.call(this, properties);
 41 
 42         this._actions = [];
 43     },
 44 
 45     lineWidth: 1,
 46     lineAlpha: 1,
 47     lineCap: null, //'butt', 'round', 'square'
 48     lineJoin: null, //'miter', 'round', 'bevel'
 49     miterLimit: 10,
 50     hasStroke: false,
 51     strokeStyle: '0',
 52     hasFill: false,
 53     fillStyle: '0',
 54     fillAlpha: 0,
 55 
 56     /**
 57      * @language=en
 58      * Set the lines style for drawing shapes.
 59      * @param {Number} thickness The thickness of lines, default value is 1.
 60      * @param {String} lineColor The CSS color value of lines, default value is 0 (the black color).
 61      * @param {Number} lineAlpha The transparency of lines, default value is 1 (fully opaque).
 62      * @param {String} lineCap The style of how every end point of line are drawn, value options: butt, round, square. default value is null.
 63      * @param {String} lineJoin The joint style of two lines. value options: miter, round, bevel. default value is null.
 64      * @param {Number} miterLimit The miter limit ratio in space units, works only when lineJoin value is miter. default value is 10.
 65      * @returns {Graphics} The Graphics Object.
 66      */
 67     lineStyle: function(thickness, lineColor, lineAlpha, lineCap, lineJoin, miterLimit){
 68         var me = this, addAction = me._addAction;
 69 
 70         addAction.call(me, ['lineWidth', (me.lineWidth = thickness || 1)]);
 71         addAction.call(me, ['strokeStyle', (me.strokeStyle = lineColor || '0')]);
 72         addAction.call(me, ['lineAlpha', (me.lineAlpha = lineAlpha || 1)]);
 73         if(lineCap != undefined) addAction.call(me, ['lineCap', (me.lineCap = lineCap)]);
 74         if(lineJoin != undefined) addAction.call(me, ['lineJoin', (me.lineJoin = lineJoin)]);
 75         if(miterLimit != undefined) addAction.call(me, ['miterLimit', (me.miterLimit = miterLimit)]);
 76         me.hasStroke = true;
 77         return me;
 78     },
 79 
 80     /**
 81      * @language=en
 82      * Set how to fill shapes and the transparency.
 83      * @param {String} fill Filling style. this can be color, gradient or pattern.
 84      * @param {Number} alpha Transparency.
 85      * @returns {Graphics} The Graphics Object.
 86      */
 87     beginFill: function(fill, alpha){
 88         var me = this, addAction = me._addAction;
 89 
 90         addAction.call(me, ['fillStyle', (me.fillStyle = fill)]);
 91         addAction.call(me, ['fillAlpha', (me.fillAlpha = alpha || 1)]);
 92         me.hasFill = true;
 93         return me;
 94     },
 95 
 96     /**
 97      * @language=en
 98      * Apply and end lines-drawing and shapes-filling.
 99      * @returns {Graphics} The Graphics Object.
100      */
101     endFill: function(){
102         var me = this, addAction = me._addAction;
103 
104         if(me.hasStroke) addAction.call(me, ['stroke']);
105         if(me.hasFill) addAction.call(me, ['fill']);
106         me.setCacheDirty(true);
107         return me;
108     },
109 
110     /**
111      * @language=en
112      * Set linear gradient filling style to draw shapes.
113      * @param {Number} x0 The x-coordinate value of the linear gradient start point.
114      * @param {Number} y0 The y-coordinate value of the linear gradient start point.
115      * @param {Number} x1 The x-coordinate value of the linear gradient end point.
116      * @param {Number} y1 The y-coordinate value of the linear gradient end point.
117      * @param {Array} colors An array of CSS colors used in the linear gradient.
118      * @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.
119      * @returns {Graphics} The Graphics Object.
120      */
121     beginLinearGradientFill: function(x0, y0, x1, y1, colors, ratios){
122         var me = this, gradient = helpContext.createLinearGradient(x0, y0, x1, y1);
123 
124         for (var i = 0, len = colors.length; i < len; i++){
125             gradient.addColorStop(ratios[i], colors[i]);
126         }
127         me.hasFill = true;
128         return me._addAction(['fillStyle', (me.fillStyle = gradient)]);
129     },
130 
131     /**
132      * @language=en
133      * Set radial gradient filling style to draw shapes.
134      * @param {Number} x0 The x-coordinate value of the radial gradient start circle.
135      * @param {Number} y0 The y-coordinate value of the radial gradient start circle.
136      * @param {Number} r0 The diameter of the radial gradient start circle.
137      * @param {Number} x1 The x-coordinate value of the radial gradient end circle.
138      * @param {Number} y1 The y-coordinate value of the radial gradient end circle.
139      * @param {Number} r1 The radius of the radial gradient end circle.
140      * @param {Array} colors An array of CSS colors used in the radial gradient.
141      * @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.
142      * @returns {Graphics} The Graphics Object.
143      */
144     beginRadialGradientFill: function(x0, y0, r0, x1, y1, r1, colors, ratios){
145         var me = this, gradient = helpContext.createRadialGradient(x0, y0, r0, x1, y1, r1);
146         for (var i = 0, len = colors.length; i < len; i++)
147         {
148             gradient.addColorStop(ratios[i], colors[i]);
149         }
150         me.hasFill = true;
151         return me._addAction(['fillStyle', (me.fillStyle = gradient)]);
152     },
153 
154     /**
155      * @language=en
156      * Begin an image filling pattern.
157      * @param {HTMLImageElement} image The Image to fill.
158      * @param {String} repetition The fill repetition style, can be one of valus:repeat, repeat-x, repeat-y, no-repeat. default valus is ''.
159      * @returns {Graphics} The Graphics Object.
160      */
161     beginBitmapFill: function(image, repetition){
162         var me = this, pattern = helpContext.createPattern(image, repetition || '');
163         me.hasFill = true;
164         return me._addAction(['fillStyle', (me.fillStyle = pattern)]);
165     },
166 
167     /**
168      * @language=en
169      * Begin a new path.
170      * @returns {Graphics} The Graphics Object.
171      */
172     beginPath: function(){
173         return this._addAction(['beginPath']);
174     },
175 
176     /**
177      * @language=en
178      * Close current path.
179      * @returns {Graphics} The Graphics Object.
180      */
181     closePath: function(){
182         return this._addAction(['closePath']);
183     },
184 
185     /**
186      * @language=en
187      * Move current drawing point to a new point on coordinate values (x, y).
188      * @param {Number} x The x-coordinate value.
189      * @param {Number} y The y-coordinate value.
190      * @returns {Graphics} The Graphics Object.
191      */
192     moveTo: function(x, y){
193         return this._addAction(['moveTo', x, y]);
194     },
195 
196     /**
197      * @language=en
198      * Draw a line from current point to the point on the coordinate value (x, y).
199      * @param {Number} x The x-coordinate value.
200      * @param {Number} y The y-coordinate value.
201      * @returns {Graphics} The Graphics Object.
202      */
203     lineTo: function(x, y){
204         return this._addAction(['lineTo', x, y]);
205     },
206 
207     /**
208      * @language=en
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      * @language=en
222      * Draw a Bézier curve from current point to the point on coordinate (x, y).
223      * @param {Number} cp1x The x-coordinate value of the Bézier curve control point cp1.
224      * @param {Number} cp1y The y-coordinate value of the Bézier curve control point cp1.
225      * @param {Number} cp2x The x-coordinate value of the Bézier curve control point cp2.
226      * @param {Number} cp2y The y-coordinate value of the Bézier curve control point cp2.
227      * @param {Number} x The x-coordinate value.
228      * @param {Number} y The y-coordinate value.
229      * @returns {Graphics} The Graphics Object.
230      */
231     bezierCurveTo: function(cp1x, cp1y, cp2x, cp2y, x, y){
232         return this._addAction(['bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y]);
233     },
234 
235     /**
236      * @language=en
237      * Draw a rectangle.
238      * @param {Number} x The x-coordinate value.
239      * @param {Number} y The y-coordinate value.
240      * @param {Number} width The width of the rectangle.
241      * @param {Number} height The height of the rectangle.
242      * @returns {Graphics} The Graphics Object.
243      */
244     drawRect: function(x, y, width, height){
245         return this._addAction(['rect', x, y, width, height]);
246     },
247 
248     /**
249      * @language=en
250      * Draw a complex rounded rectangle.
251      * @param {Number} x The x-coordinate value.
252      * @param {Number} y The y-coordinate value.
253      * @param {Number} width The width of rounded rectangle.
254      * @param {Number} height The height of rounded rectangle.
255      * @param {Number} cornerTL The size of the rounded corner on the top-left of the rounded rectangle.
256      * @param {Number} cornerTR The size of the rounded corner on the top-right of the rounded rectangle.
257      * @param {Number} cornerBR The size of the rounded corner on the bottom-left of the rounded rectangle.
258      * @param {Number} cornerBL The size of the rounded corner on the bottom-right of the rounded rectangle.
259      * @returns {Graphics} The Graphics Object.
260      */
261     drawRoundRectComplex: function(x, y, width, height, cornerTL, cornerTR, cornerBR, cornerBL){
262         var me = this, addAction = me._addAction;
263         addAction.call(me, ['moveTo', x + cornerTL, y]);
264         addAction.call(me, ['lineTo', x + width - cornerTR, y]);
265         addAction.call(me, ['arc', x + width - cornerTR, y + cornerTR, cornerTR, -Math.PI/2, 0, false]);
266         addAction.call(me, ['lineTo', x + width, y + height - cornerBR]);
267         addAction.call(me, ['arc', x + width - cornerBR, y + height - cornerBR, cornerBR, 0, Math.PI/2, false]);
268         addAction.call(me, ['lineTo', x + cornerBL, y + height]);
269         addAction.call(me, ['arc', x + cornerBL, y + height - cornerBL, cornerBL, Math.PI/2, Math.PI, false]);
270         addAction.call(me, ['lineTo', x, y + cornerTL]);
271         addAction.call(me, ['arc', x + cornerTL, y + cornerTL, cornerTL, Math.PI, Math.PI*3/2, false]);
272         return me;
273     },
274 
275     /**
276      * @language=en
277      * Draw a rounded rectangle.
278      * @param {Number} x The x-coordinate value.
279      * @param {Number} y The y-coordinate value.
280      * @param {Number} width The width of rounded rectangle.
281      * @param {Number} height The height of rounded rectangle.
282      * @param {Number} cornerSize The size of all rounded corners.
283      * @returns {Graphics} The Graphics Object.
284      */
285     drawRoundRect: function(x, y, width, height, cornerSize){
286         return this.drawRoundRectComplex(x, y, width, height, cornerSize, cornerSize, cornerSize, cornerSize);
287     },
288 
289     /**
290      * @language=en
291      * Draw a circle.
292      * @param {Number} x The x-coordinate value.
293      * @param {Number} y The y-coordinate value.
294      * @param {Number} radius The radius of the circle.
295      * @returns {Graphics} The Graphics Object.
296      */
297     drawCircle: function(x, y, radius){
298         return this._addAction(['arc', x + radius, y + radius, radius, 0, Math.PI * 2, 0]);
299     },
300 
301     /**
302      * @language=en
303      * Draw an ellipse.
304      * @param {Number} x The x-coordinate value.
305      * @param {Number} y The y-coordinate value.
306      * @param {Number} width The width of the ellipse.
307      * @param {Number} height The height of the ellipse.
308      * @returns {Graphics} The Graphics Object.
309      */
310     drawEllipse: function(x, y, width, height){
311         var me = this;
312         if(width == height) return me.drawCircle(x, y, width);
313 
314         var addAction = me._addAction;
315         var w = width / 2, h = height / 2, C = 0.5522847498307933, cx = C * w, cy = C * h;
316         x = x + w;
317         y = y + h;
318 
319         addAction.call(me, ['moveTo', x + w, y]);
320         addAction.call(me, ['bezierCurveTo', x + w, y - cy, x + cx, y - h, x, y - h]);
321         addAction.call(me, ['bezierCurveTo', x - cx, y - h, x - w, y - cy, x - w, y]);
322         addAction.call(me, ['bezierCurveTo', x - w, y + cy, x - cx, y + h, x, y + h]);
323         addAction.call(me, ['bezierCurveTo', x + cx, y + h, x + w, y + cy, x + w, y]);
324         return me;
325     },
326 
327     /**
328      * @language=en
329      * Draw a path from the SVG data given by parameters.
330      * Demo:
331      * <p>var path = 'M250 150 L150 350 L350 350 Z';</p>
332      * <p>var shape = new Hilo.Graphics({width:500, height:500});</p>
333      * <p>shape.drawSVGPath(path).beginFill('#0ff').endFill();</p>
334      * @param {String} pathData The SVG path data to draw.
335      * @returns {Graphics} The Graphics Object.
336      */
337     drawSVGPath: function(pathData){
338         var me = this, addAction = me._addAction,
339             path = pathData.split(/,| (?=[a-zA-Z])/);
340 
341         addAction.call(me, ['beginPath']);
342         for(var i = 0, len = path.length; i < len; i++){
343             var str = path[i], cmd = str[0].toUpperCase(), p = str.substring(1).split(/,| /);
344             if(p[0].length == 0) p.shift();
345 
346             switch(cmd){
347                 case 'M':
348                     addAction.call(me, ['moveTo', p[0], p[1]]);
349                     break;
350                 case 'L':
351                     addAction.call(me, ['lineTo', p[0], p[1]]);
352                     break;
353                 case 'C':
354                     addAction.call(me, ['bezierCurveTo', p[0], p[1], p[2], p[3], p[4], p[5]]);
355                     break;
356                 case 'Z':
357                     addAction.call(me, ['closePath']);
358                     break;
359             }
360         }
361         return me;
362     },
363 
364     /**
365      * @language=en
366      * Apply all draw actions. private function.
367      * @private
368      */
369     _draw: function(context){
370         var me = this, actions = me._actions, len = actions.length, i;
371 
372         context.beginPath();
373         for(i = 0; i < len; i++){
374             var action = actions[i],
375                 f = action[0],
376                 args = action.length > 1 ? action.slice(1) : null;
377 
378             if(typeof(context[f]) == 'function') context[f].apply(context, args);
379             else context[f] = action[1];
380         }
381     },
382 
383     /**
384      * @language=en
385      * Overwrite render function.
386      * @private
387      */
388     render: function(renderer, delta){
389         var me = this, canvas = renderer.canvas;
390         if(renderer.renderType === 'canvas'){
391             me._draw(renderer.context);
392         }else{
393             me.cache();
394             renderer.draw(me);
395         }
396     },
397 
398     /**
399      * @language=en
400      * Clear all draw actions and reset to the initial state.
401      * @returns {Graphics} The Graphics Object.
402      */
403     clear: function(){
404         var me = this;
405 
406         me._actions.length = 0;
407         me.lineWidth = 1;
408         me.lineAlpha = 1;
409         me.lineCap = null;
410         me.lineJoin = null;
411         me.miterLimit = 10;
412         me.hasStroke = false;
413         me.strokeStyle = '0';
414         me.hasFill = false;
415         me.fillStyle = '0';
416         me.fillAlpha = 1;
417 
418         me.setCacheDirty(true);
419         return me;
420     },
421 
422     /**
423      * @language=en
424      * Add a draw action, this is a private function.
425      * @private
426      */
427     _addAction: function(action){
428         var me = this;
429         me._actions.push(action);
430         return me;
431     }
432 
433 });
434 
435 })();
436