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  * Singleton that handles the loading of the sprite frames. It saves in a cache the sprite frames.
 29  * @class
 30  * @extends cc.Class
 31  * @example
 32  * // add SpriteFrames to SpriteFrameCache With File
 33  * cc.SpriteFrameCache.getInstance().addSpriteFrames(s_grossiniPlist);
 34  */
 35 cc.SpriteFrameCache = cc.Class.extend(/** @lends cc.SpriteFrameCache# */{
 36     _spriteFrames:null,
 37     _spriteFramesAliases:null,
 38     _loadedFileNames:null,
 39 
 40     /**
 41      * Constructor
 42      */
 43     ctor:function () {
 44         this._spriteFrames = {};
 45         this._spriteFramesAliases = {};
 46         this._loadedFileNames = [];
 47     },
 48 
 49     /**
 50      * Adds multiple Sprite Frames with a dictionary. The texture will be associated with the created sprite frames.
 51      * @param {object} dictionary
 52      * @param {HTMLImageElement|cc.Texture2D} texture
 53      */
 54     _addSpriteFramesWithDictionary:function (dictionary, texture) {
 55         var metadataDict = dictionary["metadata"];
 56         var framesDict = dictionary["frames"];
 57         var format = 0;
 58         // get the format
 59         if (metadataDict != null) {
 60             format = parseInt(this._valueForKey("format", metadataDict));
 61         }
 62 
 63         // check the format
 64         cc.Assert(format >= 0 && format <= 3, "format is not supported for cc.SpriteFrameCache addSpriteFramesWithDictionary:textureFilename:");
 65 
 66         for (var key in framesDict) {
 67             var frameDict = framesDict[key];
 68             if (frameDict) {
 69                 var spriteFrame = this._spriteFrames[key];
 70                 if (spriteFrame) {
 71                     continue;
 72                 }
 73 
 74                 if (format == 0) {
 75                     var x = parseFloat(this._valueForKey("x", frameDict));
 76                     var y = parseFloat(this._valueForKey("y", frameDict));
 77                     var w = parseFloat(this._valueForKey("width", frameDict));
 78                     var h = parseFloat(this._valueForKey("height", frameDict));
 79                     var ox = parseFloat(this._valueForKey("offsetX", frameDict));
 80                     var oy = parseFloat(this._valueForKey("offsetY", frameDict));
 81                     var ow = parseInt(this._valueForKey("originalWidth", frameDict));
 82                     var oh = parseInt(this._valueForKey("originalHeight", frameDict));
 83                     // check ow/oh
 84                     if (!ow || !oh) {
 85                         cc.log("cocos2d: WARNING: originalWidth/Height not found on the cc.SpriteFrame. AnchorPoint won't work as expected. Regenrate the .plist");
 86                     }
 87                     // Math.abs ow/oh
 88                     ow = Math.abs(ow);
 89                     oh = Math.abs(oh);
 90                     // create frame
 91                     spriteFrame = new cc.SpriteFrame();
 92                     spriteFrame.initWithTexture(texture, cc.rect(x, y, w, h), false, cc.p(ox, oy), cc.size(ow, oh));
 93                 } else if (format == 1 || format == 2) {
 94                     var frame = cc.RectFromString(this._valueForKey("frame", frameDict));
 95                     var rotated = false;
 96 
 97                     // rotation
 98                     if (format == 2) {
 99                         rotated = this._valueForKey("rotated", frameDict) == "true";
100                     }
101                     var offset = cc.PointFromString(this._valueForKey("offset", frameDict));
102                     var sourceSize = cc.SizeFromString(this._valueForKey("sourceSize", frameDict));
103                     // create frame
104                     spriteFrame = new cc.SpriteFrame();
105                     spriteFrame.initWithTexture(texture, frame, rotated, offset, sourceSize);
106                 } else if (format == 3) {
107                     // get values
108                     var spriteSize, spriteOffset, spriteSourceSize, textureRect, textureRotated;
109                     spriteSize = cc.SizeFromString(this._valueForKey("spriteSize", frameDict));
110                     spriteOffset = cc.PointFromString(this._valueForKey("spriteOffset", frameDict));
111                     spriteSourceSize = cc.SizeFromString(this._valueForKey("spriteSourceSize", frameDict));
112                     textureRect = cc.RectFromString(this._valueForKey("textureRect", frameDict));
113                     textureRotated = this._valueForKey("textureRotated", frameDict) == "true";
114 
115                     // get aliases
116                     var aliases = frameDict["aliases"];
117                     var frameKey = key.toString();
118 
119                     for (var aliasKey in aliases) {
120                         if (this._spriteFramesAliases.hasOwnProperty(aliases[aliasKey])) {
121                             cc.log("cocos2d: WARNING: an alias with name " + aliasKey + " already exists");
122                         }
123                         this._spriteFramesAliases[aliases[aliasKey]] = frameKey;
124                     }
125 
126                     // create frame
127                     spriteFrame = new cc.SpriteFrame();
128                     if (frameDict.hasOwnProperty("spriteSize")) {
129                         spriteFrame.initWithTexture(texture,
130                             cc.rect(textureRect.origin.x, textureRect.origin.y, spriteSize.width, spriteSize.height),
131                             textureRotated,
132                             spriteOffset,
133                             spriteSourceSize);
134                     } else {
135                         spriteFrame.initWithTexture(texture, spriteSize, textureRotated, spriteOffset, spriteSourceSize);
136                     }
137                 }
138 
139                 if (spriteFrame.isRotated()) {
140                     //clip to canvas
141                     var tempTexture = cc.cutRotateImageToCanvas(spriteFrame.getTexture(), spriteFrame.getRect());
142                     var rect = spriteFrame.getRect();
143                     spriteFrame.setRect(cc.rect(0, 0, rect.size.width, rect.size.height));
144                     spriteFrame.setTexture(tempTexture);
145                 }
146 
147                 // add sprite frame
148                 this._spriteFrames[key] = spriteFrame;
149             }
150         }
151     },
152 
153     /**
154      * Adds multiple Sprite Frames from a json file. A texture will be loaded automatically.
155      * @param {object} jsonData
156      */
157     addSpriteFramesWithJson:function (jsonData) {
158         var dict = jsonData;
159         var texturePath = "";
160 
161         var metadataDict = dict["metadata"];
162         if (metadataDict) {
163             // try to read  texture file name from meta data
164             texturePath = this._valueForKey("textureFileName", metadataDict);
165             texturePath = texturePath.toString();
166         }
167 
168         var texture = cc.TextureCache.getInstance().addImage(texturePath);
169         if (texture) {
170             this._addSpriteFramesWithDictionary(dict, texture);
171         }
172         else {
173             cc.log("cocos2d: cc.SpriteFrameCache: Couldn't load texture");
174         }
175     },
176 
177     /**
178      * <p>
179      *   Adds multiple Sprite Frames from a plist file.<br/>
180      *   A texture will be loaded automatically. The texture name will composed by replacing the .plist suffix with .png<br/>
181      *   If you want to use another texture, you should use the addSpriteFrames:texture method.<br/>
182      * </p>
183      * @param {String} plist plist filename
184      * @param {HTMLImageElement|cc.Texture2D} texture
185      * @example
186      * // add SpriteFrames to SpriteFrameCache With File
187      * cc.SpriteFrameCache.getInstance().addSpriteFrames(s_grossiniPlist);
188      */
189     addSpriteFrames:function (plist, texture) {
190         var dict = cc.FileUtils.getInstance().dictionaryWithContentsOfFileThreadSafe(plist);
191 
192         switch (arguments.length) {
193             case 1:
194                 cc.Assert(plist, "plist filename should not be NULL");
195                 if (!cc.ArrayContainsObject(this._loadedFileNames, plist)) {
196                     var texturePath = "";
197                     var metadataDict = dict["metadata"];
198                     if (metadataDict) {
199                         // try to read  texture file name from meta data
200                         texturePath = this._valueForKey("textureFileName", metadataDict).toString();
201                     }
202                     if (texturePath != "") {
203                         // build texture path relative to plist file
204                         var getIndex = plist.lastIndexOf('/'), pszPath;
205                         pszPath = getIndex ? plist.substring(0, getIndex + 1) : "";
206                         texturePath = pszPath + texturePath;
207                     } else {
208                         // build texture path by replacing file extension
209                         texturePath = plist;
210 
211                         // remove .xxx
212                         var startPos = texturePath.lastIndexOf(".", texturePath.length);
213                         texturePath = texturePath.substr(0, startPos);
214 
215                         // append .png
216                         texturePath = texturePath + ".png";
217                     }
218 
219                     var getTexture = cc.TextureCache.getInstance().addImage(texturePath);
220                     if (getTexture) {
221                         this._addSpriteFramesWithDictionary(dict, getTexture);
222                     } else {
223                         cc.log("cocos2d: cc.SpriteFrameCache: Couldn't load texture");
224                     }
225                 }
226                 break;
227             case 2:
228                 if ((texture instanceof cc.Texture2D) || (texture instanceof HTMLImageElement) || (texture instanceof HTMLCanvasElement)) {
229                     /** Adds multiple Sprite Frames from a plist file. The texture will be associated with the created sprite frames. */
230                     this._addSpriteFramesWithDictionary(dict, texture);
231                 } else {
232                     /** Adds multiple Sprite Frames from a plist file. The texture will be associated with the created sprite frames.
233                      @since v0.99.5
234                      */
235                     var textureFileName = texture;
236                     cc.Assert(textureFileName, "texture name should not be null");
237                     var gTexture = cc.TextureCache.getInstance().addImage(textureFileName);
238 
239                     if (gTexture) {
240                         this._addSpriteFramesWithDictionary(dict, gTexture);
241                     } else {
242                         cc.log("cocos2d: cc.SpriteFrameCache: couldn't load texture file. File not found " + textureFileName);
243                     }
244                 }
245                 break;
246             default:
247                 throw "Argument must be non-nil ";
248         }
249     },
250 
251     /**
252      * <p>
253      *  Adds an sprite frame with a given name.<br/>
254      *  If the name already exists, then the contents of the old name will be replaced with the new one.
255      * </p>
256      * @param {cc.SpriteFrame} frame
257      * @param {String} frameName
258      */
259     addSpriteFrame:function (frame, frameName) {
260         this._spriteFrames[frameName] = frame;
261     },
262 
263     /**
264      * <p>
265      *   Purges the dictionary of loaded sprite frames.<br/>
266      *   Call this method if you receive the "Memory Warning".<br/>
267      *   In the short term: it will free some resources preventing your app from being killed.<br/>
268      *   In the medium term: it will allocate more resources.<br/>
269      *   In the long term: it will be the same.<br/>
270      * </p>
271      */
272     removeSpriteFrames:function () {
273         this._spriteFrames = [];
274         this._spriteFramesAliases = [];
275         this._loadedFileNames = {};
276     },
277 
278     /**
279      * Deletes an sprite frame from the sprite frame cache.
280      * @param {String} name
281      */
282     removeSpriteFrameByName:function (name) {
283         // explicit nil handling
284         if (!name) {
285             return;
286         }
287 
288         // Is this an alias ?
289         if (this._spriteFramesAliases.hasOwnProperty(name)) {
290             delete(this._spriteFramesAliases[name]);
291         }
292         if (this._spriteFrames.hasOwnProperty(name)) {
293             delete(this._spriteFrames[name]);
294         }
295         // XXX. Since we don't know the .plist file that originated the frame, we must remove all .plist from the cache
296         this._loadedFileNames = {};
297     },
298 
299     /**
300      * <p>
301      *     Removes multiple Sprite Frames from a plist file.<br/>
302      *     Sprite Frames stored in this file will be removed.<br/>
303      *     It is convinient to call this method when a specific texture needs to be removed.<br/>
304      * </p>
305      * @param {String} plist plist filename
306      */
307     removeSpriteFramesFromFile:function (plist) {
308         var path = cc.FileUtils.getInstance().fullPathFromRelativePath(plist);
309         var dict = cc.FileUtils.getInstance().dictionaryWithContentsOfFileThreadSafe(path);
310 
311         this._removeSpriteFramesFromDictionary(dict);
312 
313         //remove it from the cache
314         if (cc.ArrayContainsObject(this._loadedFileNames, plist)) {
315             cc.ArrayRemoveObject(plist);
316         }
317     },
318 
319     /**
320      * Removes multiple Sprite Frames from Dictionary.
321      * @param {object} dictionary SpriteFrame of Dictionary
322      */
323     _removeSpriteFramesFromDictionary:function (dictionary) {
324         var framesDict = dictionary["frames"];
325 
326         for (var key in framesDict) {
327             if (this._spriteFrames.hasOwnProperty(key)) {
328                 delete(this._spriteFrames[key]);
329             }
330         }
331     },
332 
333     /**
334      * <p>
335      *    Removes all Sprite Frames associated with the specified textures.<br/>
336      *    It is convinient to call this method when a specific texture needs to be removed.
337      * </p>
338      * @param {HTMLImageElement|HTMLCanvasElement|cc.Texture2D} texture
339      */
340     removeSpriteFramesFromTexture:function (texture) {
341         for (var key in this._spriteFrames) {
342             var frame = this._spriteFrames[key];
343             if (frame && (frame.getTexture() == texture)) {
344                 delete(this._spriteFrames[key]);
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.getInstance().getSpriteFrame("grossini_dance_01.png");
360      */
361     getSpriteFrame:function (name) {
362         var frame;
363         if (this._spriteFrames.hasOwnProperty(name)) {
364             frame = this._spriteFrames[name];
365         }
366 
367         if (!frame) {
368             // try alias dictionary
369             var key;
370             if (this._spriteFramesAliases.hasOwnProperty(name)) {
371                 key = this._spriteFramesAliases[name];
372             }
373             if (key) {
374                 if (this._spriteFrames.hasOwnProperty(key.toString())) {
375                     frame = this._spriteFrames[key.toString()];
376                 }
377                 if (!frame) {
378                     cc.log("cocos2d: cc.SpriteFrameCahce: Frame " + name + " not found");
379                 }
380             }
381         }
382         return frame;
383     },
384 
385     _valueForKey:function (key, dict) {
386         if (dict) {
387             if (dict.hasOwnProperty(key)) {
388                 return dict[key].toString();
389             }
390         }
391         return "";
392     }
393 });
394 
395 cc.s_sharedSpriteFrameCache = null;
396 
397 /**
398  * Returns the shared instance of the Sprite Frame cache
399  * @return {cc.SpriteFrameCache}
400  */
401 cc.SpriteFrameCache.getInstance = function () {
402     if (!cc.s_sharedSpriteFrameCache) {
403         cc.s_sharedSpriteFrameCache = new cc.SpriteFrameCache();
404     }
405     return cc.s_sharedSpriteFrameCache;
406 };
407 
408 /**
409  * Purges the cache. It releases all the Sprite Frames and the retained instance.
410  */
411 cc.SpriteFrameCache.purgeSharedSpriteFrameCache = function () {
412     cc.s_sharedSpriteFrameCache = null;
413 };
414