1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga 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 /**
 28  * @namespace <p>
 29  * Singleton that handles the loading of the sprite frames. It saves in a cache the sprite frames.<br/>
 30  * <br/>
 31  * example<br/>
 32  * // add SpriteFrames to spriteFrameCache With File<br/>
 33  * cc.spriteFrameCache.addSpriteFrames(s_grossiniPlist);<br/>
 34  * </p>
 35  */
 36 cc.spriteFrameCache = /** @lends cc.spriteFrameCache# */{
 37     _CCNS_REG1 : /^\s*\{\s*([\-]?\d+[.]?\d*)\s*,\s*([\-]?\d+[.]?\d*)\s*\}\s*$/,
 38     _CCNS_REG2 : /^\s*\{\s*\{\s*([\-]?\d+[.]?\d*)\s*,\s*([\-]?\d+[.]?\d*)\s*\}\s*,\s*\{\s*([\-]?\d+[.]?\d*)\s*,\s*([\-]?\d+[.]?\d*)\s*\}\s*\}\s*$/,
 39 
 40     _spriteFrames: {},
 41     _spriteFramesAliases: {},
 42     _frameConfigCache : {},
 43 
 44     /**
 45      * Returns a Core Graphics rectangle structure corresponding to the data in a given string. <br/>
 46      * The string is not localized, so items are always separated with a comma. <br/>
 47      * If the string is not well-formed, the function returns cc.rect(0, 0, 0, 0).
 48      * @function
 49      * @param {String} content content A string object whose contents are of the form "{{x,y},{w, h}}",<br/>
 50      * where x is the x coordinate, y is the y coordinate, w is the width, and h is the height. <br/>
 51      * These components can represent integer or float values.
 52      * @return {cc.Rect} A Core Graphics structure that represents a rectangle.
 53      * Constructor
 54      * @example
 55      * // example
 56      * var rect = this._rectFromString("{{3,2},{4,5}}");
 57      */
 58     _rectFromString :  function (content) {
 59         var result = this._CCNS_REG2.exec(content);
 60         if(!result) return cc.rect(0, 0, 0, 0);
 61         return cc.rect(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3]), parseFloat(result[4]));
 62     },
 63 
 64     /**
 65      * Returns a Core Graphics point structure corresponding to the data in a given string.
 66      * @function
 67      * @param {String} content   A string object whose contents are of the form "{x,y}",
 68      * where x is the x coordinate and y is the y coordinate.<br/>
 69      * The x and y values can represent integer or float values. <br/>
 70      * The string is not localized, so items are always separated with a comma.<br/>
 71      * @return {cc.Point} A Core Graphics structure that represents a point.<br/>
 72      * If the string is not well-formed, the function returns cc.p(0,0).
 73      * Constructor
 74      * @example
 75      * //example
 76      * var point = this._pointFromString("{3.0,2.5}");
 77      */
 78     _pointFromString : function (content) {
 79         var result = this._CCNS_REG1.exec(content);
 80         if(!result) return cc.p(0,0);
 81         return cc.p(parseFloat(result[1]), parseFloat(result[2]));
 82     },
 83     /**
 84      * Returns a Core Graphics size structure corresponding to the data in a given string.
 85      * @function
 86      * @param {String} content   A string object whose contents are of the form "{w, h}",<br/>
 87      * where w is the width and h is the height.<br/>
 88      * The w and h values can be integer or float values. <br/>
 89      * The string is not localized, so items are always separated with a comma.<br/>
 90      * @return {cc.Size} A Core Graphics structure that represents a size.<br/>
 91      * If the string is not well-formed, the function returns cc.size(0,0).
 92      * @example
 93      * // example
 94      * var size = this._sizeFromString("{3.0,2.5}");
 95      */
 96     _sizeFromString : function (content) {
 97         var result = this._CCNS_REG1.exec(content);
 98         if(!result) return cc.size(0, 0);
 99         return cc.size(parseFloat(result[1]), parseFloat(result[2]));
100     },
101 
102     /**
103      * Get the real data structure of frame used by engine.
104      * @param url
105      * @returns {*}
106      * @private
107      */
108     _getFrameConfig : function(url){
109         var dict = cc.loader.getRes(url);
110 
111         cc.assert(dict, cc._LogInfos.spriteFrameCache__getFrameConfig_2, url);
112 
113         cc.loader.release(url);//release it in loader
114         if(dict._inited){
115             this._frameConfigCache[url] = dict;
116             return dict;
117         }
118         var tempFrames = dict["frames"], tempMeta = dict["metadata"] || dict["meta"];
119         var frames = {}, meta = {};
120         var format = 0;
121         if(tempMeta){//init meta
122             var tmpFormat = tempMeta["format"];
123             format = (tmpFormat.length <= 1) ? parseInt(tmpFormat) : tmpFormat;
124             meta.image = tempMeta["textureFileName"] || tempMeta["textureFileName"] || tempMeta["image"];
125         }
126         for (var key in tempFrames) {
127             var frameDict = tempFrames[key];
128             if(!frameDict) continue;
129             var tempFrame = {};
130 
131             if (format == 0) {
132                 tempFrame.rect = cc.rect(frameDict["x"], frameDict["y"], frameDict["width"], frameDict["height"]);
133                 tempFrame.rotated = false;
134                 tempFrame.offset = cc.p(frameDict["offsetX"], frameDict["offsetY"]);
135                 var ow = frameDict["originalWidth"];
136                 var oh = frameDict["originalHeight"];
137                 // check ow/oh
138                 if (!ow || !oh) {
139                     cc.log(cc._LogInfos.spriteFrameCache__getFrameConfig);
140                 }
141                 // Math.abs ow/oh
142                 ow = Math.abs(ow);
143                 oh = Math.abs(oh);
144                 tempFrame.size = cc.size(ow, oh);
145             } else if (format == 1 || format == 2) {
146                 tempFrame.rect = this._rectFromString(frameDict["frame"]);
147                 tempFrame.rotated = frameDict["rotated"] || false;
148                 tempFrame.offset = this._pointFromString(frameDict["offset"]);
149                 tempFrame.size = this._sizeFromString(frameDict["sourceSize"]);
150             } else if (format == 3) {
151                 // get values
152                 var spriteSize = this._sizeFromString(frameDict["spriteSize"]);
153                 var textureRect = this._rectFromString(frameDict["textureRect"]);
154                 if (spriteSize) {
155                     textureRect = cc.rect(textureRect.x, textureRect.y, spriteSize.width, spriteSize.height);
156                 }
157                 tempFrame.rect = textureRect;
158                 tempFrame.rotated = frameDict["textureRotated"] || false; // == "true";
159                 tempFrame.offset = this._pointFromString(frameDict["spriteOffset"]);
160                 tempFrame.size = this._sizeFromString(frameDict["spriteSourceSize"]);
161                 tempFrame.aliases = frameDict["aliases"];
162             } else {
163                 var tmpFrame = frameDict["frame"], tmpSourceSize = frameDict["sourceSize"];
164                 key = frameDict["filename"] || key;
165                 tempFrame.rect = cc.rect(tmpFrame["x"], tmpFrame["y"], tmpFrame["w"], tmpFrame["h"]);
166                 tempFrame.rotated = frameDict["rotated"] || false;
167                 tempFrame.offset = cc.p(0, 0);
168                 tempFrame.size = cc.size(tmpSourceSize["w"], tmpSourceSize["h"]);
169             }
170             frames[key] = tempFrame;
171         }
172         var cfg = this._frameConfigCache[url] = {
173             _inited : true,
174             frames : frames,
175             meta : meta
176         };
177         return cfg;
178     },
179 
180     /**
181      * <p>
182      *   Adds multiple Sprite Frames from a plist or json file.<br/>
183      *   A texture will be loaded automatically. The texture name will composed by replacing the .plist or .json suffix with .png<br/>
184      *   If you want to use another texture, you should use the addSpriteFrames:texture method.<br/>
185      * </p>
186      * @param {String} url file path
187      * @param {HTMLImageElement|cc.Texture2D|string} texture
188      * @example
189      * // add SpriteFrames to SpriteFrameCache With File
190      * cc.spriteFrameCache.addSpriteFrames(s_grossiniPlist);
191      * cc.spriteFrameCache.addSpriteFrames(s_grossiniJson);
192      */
193     addSpriteFrames: function (url, texture) {
194 
195         cc.assert(url, cc._LogInfos.spriteFrameCache_addSpriteFrames_2);
196 
197         var self = this;
198         var frameConfig = self._frameConfigCache[url] || self._getFrameConfig(url);
199         //self._checkConflict(frameConfig);                             //TODO
200         var frames = frameConfig.frames, meta = frameConfig.meta;
201         if(!texture){
202             var texturePath = cc.path.changeBasename(url, meta.image || ".png");
203             texture = cc.textureCache.addImage(texturePath);
204         }else if(texture instanceof cc.Texture2D){
205             //do nothing
206         }else if(typeof texture == "string"){//string
207             texture = cc.textureCache.addImage(texture);
208         }else{
209             cc.assert(0, cc._LogInfos.spriteFrameCache_addSpriteFrames_3);
210         }
211 
212         //create sprite frames
213         var spAliases = self._spriteFramesAliases, spriteFrames = self._spriteFrames;
214         for (var key in frames) {
215             var frame = frames[key];
216             var spriteFrame = spriteFrames[key];
217             if (!spriteFrame) {
218                 spriteFrame = cc.SpriteFrame.create(texture, frame.rect, frame.rotated, frame.offset, frame.size);
219                 var aliases = frame.aliases;
220                 if(aliases){//set aliases
221                     for(var i = 0, li = aliases.length; i < li; i++){
222                         var alias = aliases[i];
223                         if (spAliases[alias]) {
224                             cc.log(cc._LogInfos.spriteFrameCache_addSpriteFrames, alias);
225                         }
226                         spAliases[alias] = key;
227                     }
228                 }
229                 if (cc._renderType === cc._RENDER_TYPE_CANVAS && spriteFrame.isRotated()) {
230                     //clip to canvas
231                     var locTexture = spriteFrame.getTexture();
232                     if (locTexture.isLoaded()) {
233                         var tempElement = spriteFrame.getTexture().getHtmlElementObj();
234                         tempElement = cc.cutRotateImageToCanvas(tempElement, spriteFrame.getRectInPixels());
235                         var tempTexture = new cc.Texture2D();
236                         tempTexture.initWithElement(tempElement);
237                         tempTexture.handleLoadedTexture();
238                         spriteFrame.setTexture(tempTexture);
239 
240                         var rect = spriteFrame._rect;
241                         spriteFrame.setRect(cc.rect(0, 0, rect.width, rect.height));
242                     }
243                 }
244                 spriteFrames[key] = spriteFrame;
245             }
246         }
247     },
248 
249     // Function to check if frames to add exists already, if so there may be name conflit that must be solved
250     _checkConflict: function (dictionary) {
251         var framesDict = dictionary["frames"];
252 
253         for (var key in framesDict) {
254             if (this._spriteFrames[key]) {
255                 cc.log(cc._LogInfos.spriteFrameCache__checkConflict, key);
256             }
257         }
258     },
259 
260     /**
261      * <p>
262      *  Adds an sprite frame with a given name.<br/>
263      *  If the name already exists, then the contents of the old name will be replaced with the new one.
264      * </p>
265      * @param {cc.SpriteFrame} frame
266      * @param {String} frameName
267      */
268     addSpriteFrame: function (frame, frameName) {
269         this._spriteFrames[frameName] = frame;
270     },
271 
272     /**
273      * <p>
274      *   Purges the dictionary of loaded sprite frames.<br/>
275      *   Call this method if you receive the "Memory Warning".<br/>
276      *   In the short term: it will free some resources preventing your app from being killed.<br/>
277      *   In the medium term: it will allocate more resources.<br/>
278      *   In the long term: it will be the same.<br/>
279      * </p>
280      */
281     removeSpriteFrames: function () {
282         this._spriteFrames = {};
283         this._spriteFramesAliases = {};
284     },
285 
286     /**
287      * Deletes an sprite frame from the sprite frame cache.
288      * @param {String} name
289      */
290     removeSpriteFrameByName: function (name) {
291         // explicit nil handling
292         if (!name) {
293             return;
294         }
295 
296         // Is this an alias ?
297         if (this._spriteFramesAliases[name]) {
298             delete(this._spriteFramesAliases[name]);
299         }
300         if (this._spriteFrames[name]) {
301             delete(this._spriteFrames[name]);
302         }
303         // XXX. Since we don't know the .plist file that originated the frame, we must remove all .plist from the cache
304     },
305 
306     /**
307      * <p>
308      *     Removes multiple Sprite Frames from a plist file.<br/>
309      *     Sprite Frames stored in this file will be removed.<br/>
310      *     It is convinient to call this method when a specific texture needs to be removed.<br/>
311      * </p>
312      * @param {String} url plist filename
313      */
314     removeSpriteFramesFromFile: function (url) {
315         var self = this, spriteFrames = self._spriteFrames,
316             aliases = self._spriteFramesAliases, cfg = self._frameConfigCache[url];
317         if(!cfg) return;
318         var frames = cfg.frames;
319         for (var key in frames) {
320             if (spriteFrames[key]) {
321                 delete(spriteFrames[key]);
322                 for (var alias in aliases) {//remove alias
323                     if(aliases[alias] == key) delete aliases[alias];
324                 }
325             }
326         }
327     },
328 
329     /**
330      * <p>
331      *    Removes all Sprite Frames associated with the specified textures.<br/>
332      *    It is convinient to call this method when a specific texture needs to be removed.
333      * </p>
334      * @param {HTMLImageElement|HTMLCanvasElement|cc.Texture2D} texture
335      */
336     removeSpriteFramesFromTexture: function (texture) {
337         var self = this, spriteFrames = self._spriteFrames, aliases = self._spriteFramesAliases;
338         for (var key in spriteFrames) {
339             var frame = spriteFrames[key];
340             if (frame && (frame.getTexture() == texture)) {
341                 delete(spriteFrames[key]);
342                 for (var alias in aliases) {//remove alias
343                     if(aliases[alias] == key) delete aliases[alias];
344                 }
345             }
346         }
347     },
348 
349     /**
350      * <p>
351      *   Returns an Sprite Frame that was previously added.<br/>
352      *   If the name is not found it will return nil.<br/>
353      *   You should retain the returned copy if you are going to use it.<br/>
354      * </p>
355      * @param {String} name name of SpriteFrame
356      * @return {cc.SpriteFrame}
357      * @example
358      * //get a SpriteFrame by name
359      * var frame = cc.spriteFrameCache.getSpriteFrame("grossini_dance_01.png");
360      */
361     getSpriteFrame: function (name) {
362         var self = this, frame = self._spriteFrames[name];
363         if (!frame) {
364             // try alias dictionary
365             var key = self._spriteFramesAliases[name];
366             if (key) {
367                 frame = self._spriteFrames[key.toString()];
368                 if(!frame) delete self._spriteFramesAliases[name];
369             }
370         }
371         if (!frame) cc.log(cc._LogInfos.spriteFrameCache_getSpriteFrame, name);
372         return frame;
373     },
374 
375 	_clear: function () {
376 		this._spriteFrames = {};
377 		this._spriteFramesAliases = {};
378 		this._frameConfigCache = {};
379 	}
380 };
381