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         if(!dict) throw "Please load the resource first : " + url;
111         cc.loader.release(url);//release it in loader
112         if(dict._inited){
113             this._frameConfigCache[url] = dict;
114             return dict;
115         }
116         var tempFrames = dict["frames"], tempMeta = dict["metadata"] || dict["meta"];
117         var frames = {}, meta = {};
118         var format = 0;
119         if(tempMeta){//init meta
120             var tmpFormat = tempMeta["format"];
121             format = (tmpFormat.length <= 1) ? parseInt(tmpFormat) : tmpFormat;
122             meta.image = tempMeta["textureFileName"] || tempMeta["textureFileName"] || tempMeta["image"];
123         }
124         for (var key in tempFrames) {
125             var frameDict = tempFrames[key];
126             if(!frameDict) continue;
127             var tempFrame = {};
128 
129             if (format == 0) {
130                 tempFrame.rect = cc.rect(frameDict["x"], frameDict["y"], frameDict["width"], frameDict["height"]);
131                 tempFrame.rotated = false;
132                 tempFrame.offset = cc.p(frameDict["offsetX"], frameDict["offsetY"]);
133                 var ow = frameDict["originalWidth"];
134                 var oh = frameDict["originalHeight"];
135                 // check ow/oh
136                 if (!ow || !oh) {
137                     cc.log("cocos2d: WARNING: originalWidth/Height not found on the cc.SpriteFrame. AnchorPoint won't work as expected. Regenrate the .plist");
138                 }
139                 // Math.abs ow/oh
140                 ow = Math.abs(ow);
141                 oh = Math.abs(oh);
142                 tempFrame.size = cc.size(ow, oh);
143             } else if (format == 1 || format == 2) {
144                 tempFrame.rect = this._rectFromString(frameDict["frame"]);
145                 tempFrame.rotated = frameDict["rotated"] || false;
146                 tempFrame.offset = this._pointFromString(frameDict["offset"]);
147                 tempFrame.size = this._sizeFromString(frameDict["sourceSize"]);
148             } else if (format == 3) {
149                 // get values
150                 var spriteSize = this._sizeFromString(frameDict["spriteSize"]);
151                 var textureRect = this._rectFromString(frameDict["textureRect"]);
152                 if (spriteSize) {
153                     textureRect = cc.rect(textureRect.x, textureRect.y, spriteSize.width, spriteSize.height);
154                 }
155                 tempFrame.rect = textureRect;
156                 tempFrame.rotated = frameDict["textureRotated"] || false; // == "true";
157                 tempFrame.offset = this._pointFromString(frameDict["spriteOffset"]);
158                 tempFrame.size = this._sizeFromString(frameDict["spriteSourceSize"]);
159                 tempFrame.aliases = frameDict["aliases"];
160             } else {
161                 var tmpFrame = frameDict["frame"], tmpSourceSize = frameDict["sourceSize"];
162                 key = frameDict["filename"] || key;
163                 tempFrame.rect = cc.rect(tmpFrame["x"], tmpFrame["y"], tmpFrame["w"], tmpFrame["h"]);
164                 tempFrame.rotated = frameDict["rotated"] || false;
165                 tempFrame.offset = cc.p(0, 0);
166                 tempFrame.size = cc.size(tmpSourceSize["w"], tmpSourceSize["h"]);
167             }
168             frames[key] = tempFrame;
169         }
170         var cfg = this._frameConfigCache[url] = {
171             _inited : true,
172             frames : frames,
173             meta : meta
174         };
175         return cfg;
176     },
177 
178     /**
179      * <p>
180      *   Adds multiple Sprite Frames from a plist or json file.<br/>
181      *   A texture will be loaded automatically. The texture name will composed by replacing the .plist or .json suffix with .png<br/>
182      *   If you want to use another texture, you should use the addSpriteFrames:texture method.<br/>
183      * </p>
184      * @param {String} url file path
185      * @param {HTMLImageElement|cc.Texture2D|string} texture
186      * @example
187      * // add SpriteFrames to SpriteFrameCache With File
188      * cc.spriteFrameCache.addSpriteFrames(s_grossiniPlist);
189      * cc.spriteFrameCache.addSpriteFrames(s_grossiniJson);
190      */
191     addSpriteFrames: function (url, texture) {
192         if (!url)
193             throw "cc.SpriteFrameCache.addSpriteFrames(): plist should be non-null";
194 
195         var self = this;
196         var frameConfig = self._frameConfigCache[url] || self._getFrameConfig(url);
197         //self._checkConflict(frameConfig);                             //TODO
198         var frames = frameConfig.frames, meta = frameConfig.meta;
199         if(!texture){
200             var texturePath = cc.path.changeBasename(url, meta.image || ".png");
201             texture = cc.textureCache.addImage(texturePath);
202         }else if(texture instanceof cc.Texture2D){
203             //do nothing
204         }else if(typeof texture == "string"){//string
205             texture = cc.textureCache.addImage(texture);
206         }else throw "Argument must be non-nil"
207 
208         //create sprite frames
209         var spAliases = self._spriteFramesAliases, spriteFrames = self._spriteFrames;
210         for (var key in frames) {
211             var frame = frames[key];
212             var spriteFrame = spriteFrames[key];
213             if (!spriteFrame) {
214                 spriteFrame = cc.SpriteFrame.create(texture, frame.rect, frame.rotated, frame.offset, frame.size);
215                 var aliases = frame.aliases;
216                 if(aliases){//set aliases
217                     for(var i = 0, li = aliases.length; i < li; i++){
218                         var alias = aliases[i];
219                         if (spAliases[alias]) {
220                             cc.log("cocos2d: WARNING: an alias with name " + alias + " already exists");
221                         }
222                         spAliases[alias] = key;
223                     }
224                 }
225                 if (cc._renderType === cc._RENDER_TYPE_CANVAS && spriteFrame.isRotated()) {
226                     //clip to canvas
227                     var locTexture = spriteFrame.getTexture();
228                     if (locTexture.isLoaded()) {
229                         var tempElement = spriteFrame.getTexture().getHtmlElementObj();
230                         tempElement = cc.cutRotateImageToCanvas(tempElement, spriteFrame.getRectInPixels());
231                         var tempTexture = new cc.Texture2D();
232                         tempTexture.initWithElement(tempElement);
233                         tempTexture.handleLoadedTexture();
234                         spriteFrame.setTexture(tempTexture);
235 
236                         var rect = spriteFrame._rect;
237                         spriteFrame.setRect(cc.rect(0, 0, rect.width, rect.height));
238                     }
239                 }
240                 spriteFrames[key] = spriteFrame;
241             }
242         }
243     },
244 
245     // Function to check if frames to add exists already, if so there may be name conflit that must be solved
246     _checkConflict: function (dictionary) {
247         var framesDict = dictionary["frames"];
248 
249         for (var key in framesDict) {
250             if (this._spriteFrames[key]) {
251                 cc.log("cocos2d: WARNING: Sprite frame: "+key+" has already been added by another source, please fix name conflit");
252             }
253         }
254     },
255 
256     /**
257      * <p>
258      *  Adds an sprite frame with a given name.<br/>
259      *  If the name already exists, then the contents of the old name will be replaced with the new one.
260      * </p>
261      * @param {cc.SpriteFrame} frame
262      * @param {String} frameName
263      */
264     addSpriteFrame: function (frame, frameName) {
265         this._spriteFrames[frameName] = frame;
266     },
267 
268     /**
269      * <p>
270      *   Purges the dictionary of loaded sprite frames.<br/>
271      *   Call this method if you receive the "Memory Warning".<br/>
272      *   In the short term: it will free some resources preventing your app from being killed.<br/>
273      *   In the medium term: it will allocate more resources.<br/>
274      *   In the long term: it will be the same.<br/>
275      * </p>
276      */
277     removeSpriteFrames: function () {
278         this._spriteFrames = {};
279         this._spriteFramesAliases = {};
280     },
281 
282     /**
283      * Deletes an sprite frame from the sprite frame cache.
284      * @param {String} name
285      */
286     removeSpriteFrameByName: function (name) {
287         // explicit nil handling
288         if (!name) {
289             return;
290         }
291 
292         // Is this an alias ?
293         if (this._spriteFramesAliases[name]) {
294             delete(this._spriteFramesAliases[name]);
295         }
296         if (this._spriteFrames[name]) {
297             delete(this._spriteFrames[name]);
298         }
299         // XXX. Since we don't know the .plist file that originated the frame, we must remove all .plist from the cache
300     },
301 
302     /**
303      * <p>
304      *     Removes multiple Sprite Frames from a plist file.<br/>
305      *     Sprite Frames stored in this file will be removed.<br/>
306      *     It is convinient to call this method when a specific texture needs to be removed.<br/>
307      * </p>
308      * @param {String} url plist filename
309      */
310     removeSpriteFramesFromFile: function (url) {
311         var self = this, spriteFrames = self._spriteFrames,
312             aliases = self._spriteFramesAliases, cfg = self._frameConfigCache[url];
313         if(!cfg) return;
314         var frames = cfg.frames;
315         for (var key in frames) {
316             if (spriteFrames[key]) {
317                 delete(spriteFrames[key]);
318                 for (var alias in aliases) {//remove alias
319                     if(aliases[alias] == key) delete aliases[alias];
320                 }
321             }
322         }
323     },
324 
325     /**
326      * <p>
327      *    Removes all Sprite Frames associated with the specified textures.<br/>
328      *    It is convinient to call this method when a specific texture needs to be removed.
329      * </p>
330      * @param {HTMLImageElement|HTMLCanvasElement|cc.Texture2D} texture
331      */
332     removeSpriteFramesFromTexture: function (texture) {
333         var self = this, spriteFrames = self._spriteFrames, aliases = self._spriteFramesAliases;
334         for (var key in spriteFrames) {
335             var frame = spriteFrames[key];
336             if (frame && (frame.getTexture() == texture)) {
337                 delete(spriteFrames[key]);
338                 for (var alias in aliases) {//remove alias
339                     if(aliases[alias] == key) delete aliases[alias];
340                 }
341             }
342         }
343     },
344 
345     /**
346      * <p>
347      *   Returns an Sprite Frame that was previously added.<br/>
348      *   If the name is not found it will return nil.<br/>
349      *   You should retain the returned copy if you are going to use it.<br/>
350      * </p>
351      * @param {String} name name of SpriteFrame
352      * @return {cc.SpriteFrame}
353      * @example
354      * //get a SpriteFrame by name
355      * var frame = cc.spriteFrameCache.getSpriteFrame("grossini_dance_01.png");
356      */
357     getSpriteFrame: function (name) {
358         var self = this, frame = self._spriteFrames[name];
359         if (!frame) {
360             // try alias dictionary
361             var key = self._spriteFramesAliases[name];
362             if (key) {
363                 frame = self._spriteFrames[key.toString()];
364                 if(!frame) delete self._spriteFramesAliases[name];
365             }
366         }
367         if (!frame) cc.log("cocos2d: cc.SpriteFrameCahce: Frame " + name + " not found");
368         return frame;
369     },
370 
371 	_clear: function () {
372 		this._spriteFrames = {};
373 		this._spriteFramesAliases = {};
374 		this._frameConfigCache = {};
375 	}
376 };
377