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