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