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 /**
 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         cc.assert(url, cc._LogInfos.spriteFrameCache_addSpriteFrames_2);
195 
196         //Is it a SpriteFrame plist?
197         var dict = this._frameConfigCache[url] || cc.loader.getRes(url);
198         if(!dict || !dict["frames"])
199             return;
200 
201         var self = this;
202         var frameConfig = self._frameConfigCache[url] || self._getFrameConfig(url);
203         //self._checkConflict(frameConfig);                             //TODO
204         var frames = frameConfig.frames, meta = frameConfig.meta;
205         if(!texture){
206             var texturePath = cc.path.changeBasename(url, meta.image || ".png");
207             texture = cc.textureCache.addImage(texturePath);
208         }else if(texture instanceof cc.Texture2D){
209             //do nothing
210         }else if(typeof texture == "string"){//string
211             texture = cc.textureCache.addImage(texture);
212         }else{
213             cc.assert(0, cc._LogInfos.spriteFrameCache_addSpriteFrames_3);
214         }
215 
216         //create sprite frames
217         var spAliases = self._spriteFramesAliases, spriteFrames = self._spriteFrames;
218         for (var key in frames) {
219             var frame = frames[key];
220             var spriteFrame = spriteFrames[key];
221             if (!spriteFrame) {
222                 spriteFrame = cc.SpriteFrame.create(texture, frame.rect, frame.rotated, frame.offset, frame.size);
223                 var aliases = frame.aliases;
224                 if(aliases){//set aliases
225                     for(var i = 0, li = aliases.length; i < li; i++){
226                         var alias = aliases[i];
227                         if (spAliases[alias]) {
228                             cc.log(cc._LogInfos.spriteFrameCache_addSpriteFrames, alias);
229                         }
230                         spAliases[alias] = key;
231                     }
232                 }
233 
234                 if (cc._renderType === cc._RENDER_TYPE_CANVAS && spriteFrame.isRotated()) {
235                     //clip to canvas
236                     var locTexture = spriteFrame.getTexture();
237                     if (locTexture.isLoaded()) {
238                         var tempElement = spriteFrame.getTexture().getHtmlElementObj();
239                         tempElement = cc.cutRotateImageToCanvas(tempElement, spriteFrame.getRectInPixels());
240                         var tempTexture = new cc.Texture2D();
241                         tempTexture.initWithElement(tempElement);
242                         tempTexture.handleLoadedTexture();
243                         spriteFrame.setTexture(tempTexture);
244 
245                         var rect = spriteFrame._rect;
246                         spriteFrame.setRect(cc.rect(0, 0, rect.width, rect.height));
247                     }
248                 }
249 
250                 spriteFrames[key] = spriteFrame;
251             }
252         }
253     },
254 
255     // Function to check if frames to add exists already, if so there may be name conflit that must be solved
256     _checkConflict: function (dictionary) {
257         var framesDict = dictionary["frames"];
258 
259         for (var key in framesDict) {
260             if (this._spriteFrames[key]) {
261                 cc.log(cc._LogInfos.spriteFrameCache__checkConflict, key);
262             }
263         }
264     },
265 
266     /**
267      * <p>
268      *  Adds an sprite frame with a given name.<br/>
269      *  If the name already exists, then the contents of the old name will be replaced with the new one.
270      * </p>
271      * @param {cc.SpriteFrame} frame
272      * @param {String} frameName
273      */
274     addSpriteFrame: function (frame, frameName) {
275         this._spriteFrames[frameName] = frame;
276     },
277 
278     /**
279      * <p>
280      *   Purges the dictionary of loaded sprite frames.<br/>
281      *   Call this method if you receive the "Memory Warning".<br/>
282      *   In the short term: it will free some resources preventing your app from being killed.<br/>
283      *   In the medium term: it will allocate more resources.<br/>
284      *   In the long term: it will be the same.<br/>
285      * </p>
286      */
287     removeSpriteFrames: function () {
288         this._spriteFrames = {};
289         this._spriteFramesAliases = {};
290     },
291 
292     /**
293      * Deletes an sprite frame from the sprite frame cache.
294      * @param {String} name
295      */
296     removeSpriteFrameByName: function (name) {
297         // explicit nil handling
298         if (!name) {
299             return;
300         }
301 
302         // Is this an alias ?
303         if (this._spriteFramesAliases[name]) {
304             delete(this._spriteFramesAliases[name]);
305         }
306         if (this._spriteFrames[name]) {
307             delete(this._spriteFrames[name]);
308         }
309         // XXX. Since we don't know the .plist file that originated the frame, we must remove all .plist from the cache
310     },
311 
312     /**
313      * <p>
314      *     Removes multiple Sprite Frames from a plist file.<br/>
315      *     Sprite Frames stored in this file will be removed.<br/>
316      *     It is convinient to call this method when a specific texture needs to be removed.<br/>
317      * </p>
318      * @param {String} url plist filename
319      */
320     removeSpriteFramesFromFile: function (url) {
321         var self = this, spriteFrames = self._spriteFrames,
322             aliases = self._spriteFramesAliases, cfg = self._frameConfigCache[url];
323         if(!cfg) return;
324         var frames = cfg.frames;
325         for (var key in frames) {
326             if (spriteFrames[key]) {
327                 delete(spriteFrames[key]);
328                 for (var alias in aliases) {//remove alias
329                     if(aliases[alias] == key) delete aliases[alias];
330                 }
331             }
332         }
333     },
334 
335     /**
336      * <p>
337      *    Removes all Sprite Frames associated with the specified textures.<br/>
338      *    It is convinient to call this method when a specific texture needs to be removed.
339      * </p>
340      * @param {HTMLImageElement|HTMLCanvasElement|cc.Texture2D} texture
341      */
342     removeSpriteFramesFromTexture: function (texture) {
343         var self = this, spriteFrames = self._spriteFrames, aliases = self._spriteFramesAliases;
344         for (var key in spriteFrames) {
345             var frame = spriteFrames[key];
346             if (frame && (frame.getTexture() == texture)) {
347                 delete(spriteFrames[key]);
348                 for (var alias in aliases) {//remove alias
349                     if(aliases[alias] == key) delete aliases[alias];
350                 }
351             }
352         }
353     },
354 
355     /**
356      * <p>
357      *   Returns an Sprite Frame that was previously added.<br/>
358      *   If the name is not found it will return nil.<br/>
359      *   You should retain the returned copy if you are going to use it.<br/>
360      * </p>
361      * @param {String} name name of SpriteFrame
362      * @return {cc.SpriteFrame}
363      * @example
364      * //get a SpriteFrame by name
365      * var frame = cc.spriteFrameCache.getSpriteFrame("grossini_dance_01.png");
366      */
367     getSpriteFrame: function (name) {
368         var self = this, frame = self._spriteFrames[name];
369         if (!frame) {
370             // try alias dictionary
371             var key = self._spriteFramesAliases[name];
372             if (key) {
373                 frame = self._spriteFrames[key.toString()];
374                 if(!frame) delete self._spriteFramesAliases[name];
375             }
376         }
377         if (!frame) cc.log(cc._LogInfos.spriteFrameCache_getSpriteFrame, name);
378         return frame;
379     },
380 
381 	_clear: function () {
382 		this._spriteFrames = {};
383 		this._spriteFramesAliases = {};
384 		this._frameConfigCache = {};
385 	}
386 };
387