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