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