1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /** <p> cc.AtlasNode is a subclass of cc.Node that implements the cc.RGBAProtocol and<br/>
 28  * cc.TextureProtocol protocol</p>
 29  *
 30  * <p> It knows how to render a TextureAtlas object.  <br/>
 31  * If you are going to render a TextureAtlas consider subclassing cc.AtlasNode (or a subclass of cc.AtlasNode)</p>
 32  *
 33  * <p> All features from cc.Node are valid, plus the following features:  <br/>
 34  * - opacity and RGB colors </p>
 35  * @class
 36  * @extends cc.Node
 37  *
 38  * @property {cc.Texture2D}     texture         - Current used texture
 39  * @property {cc.TextureAtlas}  textureAtlas    - Texture atlas for cc.AtlasNode
 40  * @property {Number}           quadsToDraw     - Number of quads to draw
 41  *
 42  */
 43 cc.AtlasNode = cc.Node.extend(/** @lends cc.AtlasNode# */{
 44     textureAtlas: null,
 45     quadsToDraw: 0,
 46 
 47     //! chars per row
 48     _itemsPerRow: 0,
 49     //! chars per column
 50     _itemsPerColumn: 0,
 51     //! width of each char
 52     _itemWidth: 0,
 53     //! height of each char
 54     _itemHeight: 0,
 55 
 56     _colorUnmodified: null,
 57 
 58     // protocol variables
 59     _opacityModifyRGB: false,
 60     _blendFunc: null,
 61 
 62     _ignoreContentScaleFactor: false,                               // This variable is only used for CCLabelAtlas FPS display. So plz don't modify its value.
 63     _className: "AtlasNode",
 64 
 65     /**
 66      * Creates a cc.AtlasNode with an Atlas file the width and height of each item and the quantity of items to render
 67      * Constructor of cc.AtlasNode
 68      * @param {String} tile
 69      * @param {Number} tileWidth
 70      * @param {Number} tileHeight
 71      * @param {Number} itemsToRender
 72      * @example
 73      * var node = new cc.AtlasNode("pathOfTile", 16, 16, 1);
 74      */
 75     ctor: function (tile, tileWidth, tileHeight, itemsToRender) {
 76         cc.Node.prototype.ctor.call(this);
 77         this._colorUnmodified = cc.color.WHITE;
 78         this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST};
 79         this._ignoreContentScaleFactor = false;
 80 
 81         itemsToRender !== undefined && this.initWithTileFile(tile, tileWidth, tileHeight, itemsToRender);
 82     },
 83 
 84     /** updates the Atlas (indexed vertex array).
 85      * Shall be overridden in subclasses
 86      */
 87     updateAtlasValues: function () {
 88         cc.log(cc._LogInfos.AtlasNode_updateAtlasValues);
 89     },
 90 
 91     /** cc.AtlasNode - RGBA protocol
 92      * @return {cc.Color}
 93      */
 94     getColor: function () {
 95         if (this._opacityModifyRGB)
 96             return this._colorUnmodified;
 97         return cc.Node.prototype.getColor.call(this);
 98     },
 99 
100     /**
101      * @param {Boolean} value
102      */
103     setOpacityModifyRGB: function (value) {
104         var oldColor = this.color;
105         this._opacityModifyRGB = value;
106         this.color = oldColor;
107     },
108 
109     /**
110      * @return {Boolean}
111      */
112     isOpacityModifyRGB: function () {
113         return this._opacityModifyRGB;
114     },
115 
116     /** cc.AtlasNode - CocosNodeTexture protocol
117      * @return {cc.BlendFunc}
118      */
119     getBlendFunc: function () {
120         return this._blendFunc;
121     },
122 
123     /**
124      * BlendFunc setter
125      * @param {Number | cc.BlendFunc} src
126      * @param {Number} dst
127      */
128     setBlendFunc: function (src, dst) {
129         if (dst === undefined)
130             this._blendFunc = src;
131         else
132             this._blendFunc = {src: src, dst: dst};
133     },
134 
135     /**
136      * @param {cc.TextureAtlas} value
137      */
138     setTextureAtlas: function (value) {
139         this.textureAtlas = value;
140     },
141 
142     /**
143      * @return {cc.TextureAtlas}
144      */
145     getTextureAtlas: function () {
146         return this.textureAtlas;
147     },
148 
149     /**
150      * @return {Number}
151      */
152     getQuadsToDraw: function () {
153         return this.quadsToDraw;
154     },
155 
156     /**
157      * @param {Number} quadsToDraw
158      */
159     setQuadsToDraw: function (quadsToDraw) {
160         this.quadsToDraw = quadsToDraw;
161     },
162 
163     _textureForCanvas: null,
164     _originalTexture: null,
165 
166     _uniformColor: null,
167     _colorF32Array: null,
168 
169     /** initializes an cc.AtlasNode  with an Atlas file the width and height of each item and the quantity of items to render
170      * @param {String} tile
171      * @param {Number} tileWidth
172      * @param {Number} tileHeight
173      * @param {Number} itemsToRender
174      * @return {Boolean}
175      */
176     initWithTileFile: function (tile, tileWidth, tileHeight, itemsToRender) {
177         if (!tile)
178             throw "cc.AtlasNode.initWithTileFile(): title should not be null";
179         var texture = cc.textureCache.addImage(tile);
180         return this.initWithTexture(texture, tileWidth, tileHeight, itemsToRender);
181     },
182 
183     /**
184      * initializes an CCAtlasNode  with a texture the width and height of each item measured in points and the quantity of items to render
185      * @param {cc.Texture2D} texture
186      * @param {Number} tileWidth
187      * @param {Number} tileHeight
188      * @param {Number} itemsToRender
189      * @return {Boolean}
190      */
191     initWithTexture: null,
192 
193     _initWithTextureForCanvas: function (texture, tileWidth, tileHeight, itemsToRender) {
194         this._itemWidth = tileWidth;
195         this._itemHeight = tileHeight;
196 
197         this._opacityModifyRGB = true;
198         this._originalTexture = texture;
199         if (!this._originalTexture) {
200             cc.log(cc._LogInfos.AtlasNode__initWithTexture);
201             return false;
202         }
203         this._textureForCanvas = this._originalTexture;
204         this._calculateMaxItems();
205 
206         this.quadsToDraw = itemsToRender;
207         return true;
208     },
209 
210     _initWithTextureForWebGL: function (texture, tileWidth, tileHeight, itemsToRender) {
211         this._itemWidth = tileWidth;
212         this._itemHeight = tileHeight;
213         this._colorUnmodified = cc.color.WHITE;
214         this._opacityModifyRGB = true;
215 
216         this._blendFunc.src = cc.BLEND_SRC;
217         this._blendFunc.dst = cc.BLEND_DST;
218 
219         var locRealColor = this._realColor;
220         this._colorF32Array = new Float32Array([locRealColor.r / 255.0, locRealColor.g / 255.0, locRealColor.b / 255.0, this._realOpacity / 255.0]);
221         this.textureAtlas = new cc.TextureAtlas();
222         this.textureAtlas.initWithTexture(texture, itemsToRender);
223 
224         if (!this.textureAtlas) {
225             cc.log(cc._LogInfos.AtlasNode__initWithTexture);
226             return false;
227         }
228 
229         this._updateBlendFunc();
230         this._updateOpacityModifyRGB();
231         this._calculateMaxItems();
232         this.quadsToDraw = itemsToRender;
233 
234         //shader stuff
235         this.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURE_UCOLOR);
236         this._uniformColor = cc._renderContext.getUniformLocation(this.shaderProgram.getProgram(), "u_color");
237         return true;
238     },
239 
240     draw: null,
241 
242     /**
243      * @param {WebGLRenderingContext} ctx renderContext
244      */
245     _drawForWebGL: function (ctx) {
246         var context = ctx || cc._renderContext;
247         cc.nodeDrawSetup(this);
248         cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst);
249         if(this._uniformColor && this._colorF32Array){
250             context.uniform4fv(this._uniformColor, this._colorF32Array);
251             this.textureAtlas.drawNumberOfQuads(this.quadsToDraw, 0);
252         }
253     },
254 
255     /**
256      * @function
257      * @param {cc.Color} color3
258      */
259     setColor: null,
260 
261     _setColorForCanvas: function (color3) {
262         var locRealColor = this._realColor;
263         if ((locRealColor.r == color3.r) && (locRealColor.g == color3.g) && (locRealColor.b == color3.b))
264             return;
265         var temp = cc.color(color3.r, color3.g, color3.b);
266         this._colorUnmodified = color3;
267 
268         if (this._opacityModifyRGB) {
269             var locDisplayedOpacity = this._displayedOpacity;
270             temp.r = temp.r * locDisplayedOpacity / 255;
271             temp.g = temp.g * locDisplayedOpacity / 255;
272             temp.b = temp.b * locDisplayedOpacity / 255;
273         }
274         cc.Node.prototype.setColor.call(this, color3);
275         this._changeTextureColor();
276     },
277 
278     _changeTextureColor: function(){
279         var locTexture = this.getTexture();
280         if (locTexture && this._originalTexture) {
281             var element = this._originalTexture.getHtmlElementObj();
282             if(!element)
283                 return;
284             var locElement = locTexture.getHtmlElementObj();
285             var textureRect = cc.rect(0, 0, element.width, element.height);
286             if (locElement instanceof HTMLCanvasElement)
287                 cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect, locElement);
288             else {
289                 locElement = cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect);
290                 locTexture = new cc.Texture2D();
291                 locTexture.initWithElement(locElement);
292                 locTexture.handleLoadedTexture();
293                 this.setTexture(locTexture);
294             }
295         }
296     },
297 
298     _setColorForWebGL: function (color3) {
299         var temp = cc.color(color3.r, color3.g, color3.b);
300         this._colorUnmodified = color3;
301         var locDisplayedOpacity = this._displayedOpacity;
302         if (this._opacityModifyRGB) {
303             temp.r = temp.r * locDisplayedOpacity / 255;
304             temp.g = temp.g * locDisplayedOpacity / 255;
305             temp.b = temp.b * locDisplayedOpacity / 255;
306         }
307         cc.Node.prototype.setColor.call(this, color3);
308         var locDisplayedColor = this._displayedColor;
309         this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
310             locDisplayedColor.b / 255.0, locDisplayedOpacity / 255.0]);
311     },
312 
313     /**
314      * @function
315      * @param {Number} opacity
316      */
317     setOpacity: function (opacity) {
318     },
319 
320     _setOpacityForCanvas: function (opacity) {
321         cc.Node.prototype.setOpacity.call(this, opacity);
322         // special opacity for premultiplied textures
323         if (this._opacityModifyRGB) {
324             this.color = this._colorUnmodified;
325         }
326     },
327 
328     _setOpacityForWebGL: function (opacity) {
329         cc.Node.prototype.setOpacity.call(this, opacity);
330         // special opacity for premultiplied textures
331         if (this._opacityModifyRGB) {
332             this.color = this._colorUnmodified;
333         } else {
334             var locDisplayedColor = this._displayedColor;
335             this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
336                 locDisplayedColor.b / 255.0, this._displayedOpacity / 255.0]);
337         }
338     },
339 
340     // cc.Texture protocol
341     /**
342      * returns the used texture
343      * @function
344      * @return {cc.Texture2D}
345      */
346     getTexture: null,
347 
348     _getTextureForCanvas: function () {
349         return  this._textureForCanvas;
350     },
351 
352     _getTextureForWebGL: function () {
353         return  this.textureAtlas.texture;
354     },
355 
356     /**
357      * sets a new texture. it will be retained
358      * @function
359      * @param {cc.Texture2D} texture
360      */
361     setTexture: null,
362 
363     _setTextureForCanvas: function (texture) {
364         this._textureForCanvas = texture;
365     },
366 
367     _setTextureForWebGL: function (texture) {
368         this.textureAtlas.texture = texture;
369         this._updateBlendFunc();
370         this._updateOpacityModifyRGB();
371     },
372 
373     _calculateMaxItems: null,
374 
375     _calculateMaxItemsForCanvas: function () {
376         var selTexture = this.texture;
377         var size = selTexture.getContentSize();
378 
379         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
380         this._itemsPerRow = 0 | (size.width / this._itemWidth);
381     },
382 
383     _calculateMaxItemsForWebGL: function () {
384         var selTexture = this.texture;
385         var size = selTexture.getContentSize();
386         if (this._ignoreContentScaleFactor)
387             size = selTexture.getContentSizeInPixels();
388 
389         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
390         this._itemsPerRow = 0 | (size.width / this._itemWidth);
391     },
392 
393     _updateBlendFunc: function () {
394         if (!this.textureAtlas.texture.hasPremultipliedAlpha()) {
395             this._blendFunc.src = cc.SRC_ALPHA;
396             this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
397         }
398     },
399 
400     _updateOpacityModifyRGB: function () {
401         this._opacityModifyRGB = this.textureAtlas.texture.hasPremultipliedAlpha();
402     },
403 
404     _setIgnoreContentScaleFactor: function (ignoreContentScaleFactor) {
405         this._ignoreContentScaleFactor = ignoreContentScaleFactor;
406     }
407 });
408 
409 var _p = cc.AtlasNode.prototype;
410 if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
411     _p.initWithTexture = _p._initWithTextureForWebGL;
412     _p.draw = _p._drawForWebGL;
413     _p.setColor = _p._setColorForWebGL;
414     _p.setOpacity = _p._setOpacityForWebGL;
415     _p.getTexture = _p._getTextureForWebGL;
416     _p.setTexture = _p._setTextureForWebGL;
417     _p._calculateMaxItems = _p._calculateMaxItemsForWebGL;
418 } else {
419     _p.initWithTexture = _p._initWithTextureForCanvas;
420     _p.draw = cc.Node.prototype.draw;
421     _p.setColor = _p._setColorForCanvas;
422     _p.setOpacity = _p._setOpacityForCanvas;
423     _p.getTexture = _p._getTextureForCanvas;
424     _p.setTexture = _p._setTextureForCanvas;
425     _p._calculateMaxItems = _p._calculateMaxItemsForCanvas;
426     if(!cc.sys._supportCanvasNewBlendModes)
427         _p._changeTextureColor = function(){
428             var locElement, locTexture = this.getTexture();
429             if (locTexture && this._originalTexture) {
430                 locElement = locTexture.getHtmlElementObj();
431                 if (!locElement)
432                     return;
433                 var element = this._originalTexture.getHtmlElementObj();
434                 var cacheTextureForColor = cc.textureCache.getTextureColors(element);
435                 if (cacheTextureForColor) {
436                     var textureRect = cc.rect(0, 0, element.width, element.height);
437                     if (locElement instanceof HTMLCanvasElement)
438                         cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, textureRect, locElement);
439                     else {
440                         locElement = cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, textureRect);
441                         locTexture = new cc.Texture2D();
442                         locTexture.initWithElement(locElement);
443                         locTexture.handleLoadedTexture();
444                         this.setTexture(locTexture);
445                     }
446                 }
447             }
448         };
449 }
450 
451 // Override properties
452 cc.defineGetterSetter(_p, "opacity", _p.getOpacity, _p.setOpacity);
453 cc.defineGetterSetter(_p, "color", _p.getColor, _p.setColor);
454 
455 // Extended properties
456 /** @expose */
457 _p.texture;
458 cc.defineGetterSetter(_p, "texture", _p.getTexture, _p.setTexture);
459 /** @expose */
460 _p.textureAtlas;
461 /** @expose */
462 _p.quadsToDraw;
463 
464 
465 /** creates a cc.AtlasNode with an Atlas file the width and height of each item and the quantity of items to render
466  * @deprecated
467  * @param {String} tile
468  * @param {Number} tileWidth
469  * @param {Number} tileHeight
470  * @param {Number} itemsToRender
471  * @return {cc.AtlasNode}
472  * @example
473  * // example
474  * var node = cc.AtlasNode.create("pathOfTile", 16, 16, 1);
475  */
476 cc.AtlasNode.create = function (tile, tileWidth, tileHeight, itemsToRender) {
477     return new cc.AtlasNode(tile, tileWidth, tileHeight, itemsToRender);
478 };
479 
480