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