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.x, textureRect.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(cc.renderContextType === cc.CANVAS && spriteFrame.isRotated()){
140                     //clip to canvas
141                     var locTexture = spriteFrame.getTexture();
142                     if(locTexture.isLoaded()){
143                         var tempElement = spriteFrame.getTexture().getHtmlElementObj();
144                         tempElement = cc.cutRotateImageToCanvas(tempElement, spriteFrame.getRect());
145                         var tempTexture = new cc.Texture2D();
146                         tempTexture.initWithElement(tempElement);
147                         tempTexture.handleLoadedTexture();
148                         spriteFrame.setTexture(tempTexture);
149 
150                         var rect = spriteFrame.getRect();
151                         spriteFrame.setRect(cc.rect(0, 0, rect.width, rect.height));
152                     }
153                 }
154 
155                 // add sprite frame
156                 this._spriteFrames[key] = spriteFrame;
157             }
158         }
159     },
160 
161     /**
162      * Adds multiple Sprite Frames from a json file. A texture will be loaded automatically.
163      * @param {object} jsonData
164      * @deprecated
165      */
166     addSpriteFramesWithJson:function (jsonData) {
167         cc.log("addSpriteFramesWithJson is deprecated, because Json format doesn't support on JSB. Use XML format instead");
168         var dict = jsonData;
169         var texturePath = "";
170 
171         var metadataDict = dict["metadata"];
172         if (metadataDict) {
173             // try to read  texture file name from meta data
174             texturePath = this._valueForKey("textureFileName", metadataDict);
175             texturePath = texturePath.toString();
176         }
177 
178         var texture = cc.TextureCache.getInstance().addImage(texturePath);
179         if (texture) {
180             this._addSpriteFramesWithDictionary(dict, texture);
181         }
182         else {
183             cc.log("cocos2d: cc.SpriteFrameCache: Couldn't load texture");
184         }
185     },
186 
187     /**
188      * <p>
189      *   Adds multiple Sprite Frames from a plist file.<br/>
190      *   A texture will be loaded automatically. The texture name will composed by replacing the .plist suffix with .png<br/>
191      *   If you want to use another texture, you should use the addSpriteFrames:texture method.<br/>
192      * </p>
193      * @param {String} plist plist filename
194      * @param {HTMLImageElement|cc.Texture2D} texture
195      * @example
196      * // add SpriteFrames to SpriteFrameCache With File
197      * cc.SpriteFrameCache.getInstance().addSpriteFrames(s_grossiniPlist);
198      */
199     addSpriteFrames:function (plist, texture) {
200         var fileUtils = cc.FileUtils.getInstance();
201         var fullPath = fileUtils.fullPathForFilename(plist);
202         var dict = fileUtils.dictionaryWithContentsOfFileThreadSafe(fullPath);
203 
204         switch (arguments.length) {
205             case 1:
206                 cc.Assert(plist, "plist filename should not be NULL");
207                 if (!cc.ArrayContainsObject(this._loadedFileNames, plist)) {
208                     var texturePath = "";
209                     var metadataDict = dict["metadata"];
210                     if (metadataDict) {
211                         // try to read  texture file name from meta data
212                         texturePath = this._valueForKey("textureFileName", metadataDict).toString();
213                     }
214                     if (texturePath != "") {
215                         // build texture path relative to plist file
216                         texturePath = fileUtils.fullPathFromRelativeFile(texturePath, plist);
217                     } else {
218                         // build texture path by replacing file extension
219                         texturePath = plist;
220 
221                         // remove .xxx
222                         var startPos = texturePath.lastIndexOf(".", texturePath.length);
223                         texturePath = texturePath.substr(0, startPos);
224 
225                         // append .png
226                         texturePath = texturePath + ".png";
227                     }
228 
229                     var getTexture = cc.TextureCache.getInstance().addImage(texturePath);
230                     if (getTexture) {
231                         this._addSpriteFramesWithDictionary(dict, getTexture);
232                     } else {
233                         cc.log("cocos2d: cc.SpriteFrameCache: Couldn't load texture");
234                     }
235                 }
236                 break;
237             case 2:
238                 //if ((texture instanceof cc.Texture2D) || (texture instanceof HTMLImageElement) || (texture instanceof HTMLCanvasElement)) {
239                 if (texture instanceof cc.Texture2D) {
240                     /** Adds multiple Sprite Frames from a plist file. The texture will be associated with the created sprite frames. */
241                     this._addSpriteFramesWithDictionary(dict, texture);
242                 } else {
243                     /** Adds multiple Sprite Frames from a plist file. The texture will be associated with the created sprite frames.
244                      @since v0.99.5
245                      */
246                     var textureFileName = texture;
247                     cc.Assert(textureFileName, "texture name should not be null");
248                     var gTexture = cc.TextureCache.getInstance().addImage(textureFileName);
249 
250                     if (gTexture) {
251                         this._addSpriteFramesWithDictionary(dict, gTexture);
252                     } else {
253                         cc.log("cocos2d: cc.SpriteFrameCache: couldn't load texture file. File not found " + textureFileName);
254                     }
255                 }
256                 break;
257             default:
258                 throw "Argument must be non-nil ";
259         }
260     },
261 
262     /**
263      * <p>
264      *  Adds an sprite frame with a given name.<br/>
265      *  If the name already exists, then the contents of the old name will be replaced with the new one.
266      * </p>
267      * @param {cc.SpriteFrame} frame
268      * @param {String} frameName
269      */
270     addSpriteFrame:function (frame, frameName) {
271         this._spriteFrames[frameName] = frame;
272     },
273 
274     /**
275      * <p>
276      *   Purges the dictionary of loaded sprite frames.<br/>
277      *   Call this method if you receive the "Memory Warning".<br/>
278      *   In the short term: it will free some resources preventing your app from being killed.<br/>
279      *   In the medium term: it will allocate more resources.<br/>
280      *   In the long term: it will be the same.<br/>
281      * </p>
282      */
283     removeSpriteFrames:function () {
284         this._spriteFrames = [];
285         this._spriteFramesAliases = [];
286         this._loadedFileNames = {};
287     },
288 
289     /**
290      * Deletes an sprite frame from the sprite frame cache.
291      * @param {String} name
292      */
293     removeSpriteFrameByName:function (name) {
294         // explicit nil handling
295         if (!name) {
296             return;
297         }
298 
299         // Is this an alias ?
300         if (this._spriteFramesAliases.hasOwnProperty(name)) {
301             delete(this._spriteFramesAliases[name]);
302         }
303         if (this._spriteFrames.hasOwnProperty(name)) {
304             delete(this._spriteFrames[name]);
305         }
306         // XXX. Since we don't know the .plist file that originated the frame, we must remove all .plist from the cache
307         this._loadedFileNames = {};
308     },
309 
310     /**
311      * <p>
312      *     Removes multiple Sprite Frames from a plist file.<br/>
313      *     Sprite Frames stored in this file will be removed.<br/>
314      *     It is convinient to call this method when a specific texture needs to be removed.<br/>
315      * </p>
316      * @param {String} plist plist filename
317      */
318     removeSpriteFramesFromFile:function (plist) {
319         var fileUtils = cc.FileUtils.getInstance();
320         var path = fileUtils.fullPathForFilename(plist);
321         var dict = fileUtils.dictionaryWithContentsOfFileThreadSafe(path);
322 
323         this._removeSpriteFramesFromDictionary(dict);
324 
325         //remove it from the cache
326         if (cc.ArrayContainsObject(this._loadedFileNames, plist)) {
327             cc.ArrayRemoveObject(plist);
328         }
329     },
330 
331     /**
332      * Removes multiple Sprite Frames from Dictionary.
333      * @param {object} dictionary SpriteFrame of Dictionary
334      */
335     _removeSpriteFramesFromDictionary:function (dictionary) {
336         var framesDict = dictionary["frames"];
337 
338         for (var key in framesDict) {
339             if (this._spriteFrames.hasOwnProperty(key)) {
340                 delete(this._spriteFrames[key]);
341             }
342         }
343     },
344 
345     /**
346      * <p>
347      *    Removes all Sprite Frames associated with the specified textures.<br/>
348      *    It is convinient to call this method when a specific texture needs to be removed.
349      * </p>
350      * @param {HTMLImageElement|HTMLCanvasElement|cc.Texture2D} texture
351      */
352     removeSpriteFramesFromTexture:function (texture) {
353         for (var key in this._spriteFrames) {
354             var frame = this._spriteFrames[key];
355             if (frame && (frame.getTexture() == texture)) {
356                 delete(this._spriteFrames[key]);
357             }
358         }
359     },
360 
361     /**
362      * <p>
363      *   Returns an Sprite Frame that was previously added.<br/>
364      *   If the name is not found it will return nil.<br/>
365      *   You should retain the returned copy if you are going to use it.<br/>
366      * </p>
367      * @param {String} name name of SpriteFrame
368      * @return {cc.SpriteFrame}
369      * @example
370      * //get a SpriteFrame by name
371      * var frame = cc.SpriteFrameCache.getInstance().getSpriteFrame("grossini_dance_01.png");
372      */
373     getSpriteFrame:function (name) {
374         var frame;
375         if (this._spriteFrames.hasOwnProperty(name)) {
376             frame = this._spriteFrames[name];
377         }
378 
379         if (!frame) {
380             // try alias dictionary
381             var key;
382             if (this._spriteFramesAliases.hasOwnProperty(name)) {
383                 key = this._spriteFramesAliases[name];
384             }
385             if (key) {
386                 if (this._spriteFrames.hasOwnProperty(key.toString())) {
387                     frame = this._spriteFrames[key.toString()];
388                 }
389                 if (!frame) {
390                     cc.log("cocos2d: cc.SpriteFrameCahce: Frame " + name + " not found");
391                 }
392             }
393         }
394         return frame;
395     },
396 
397     _valueForKey:function (key, dict) {
398         if (dict) {
399             if (dict.hasOwnProperty(key)) {
400                 return dict[key].toString();
401             }
402         }
403         return "";
404     }
405 });
406 
407 cc.s_sharedSpriteFrameCache = null;
408 
409 /**
410  * Returns the shared instance of the Sprite Frame cache
411  * @return {cc.SpriteFrameCache}
412  */
413 cc.SpriteFrameCache.getInstance = function () {
414     if (!cc.s_sharedSpriteFrameCache) {
415         cc.s_sharedSpriteFrameCache = new cc.SpriteFrameCache();
416     }
417     return cc.s_sharedSpriteFrameCache;
418 };
419 
420 /**
421  * Purges the cache. It releases all the Sprite Frames and the retained instance.
422  */
423 cc.SpriteFrameCache.purgeSharedSpriteFrameCache = function () {
424     cc.s_sharedSpriteFrameCache = null;
425 };
426