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 * TextureCache - Alloc, Init & Dealloc 29 * @type object 30 */ 31 cc.g_sharedTextureCache = null; 32 33 /** 34 * Load the images to the cache 35 * @param {String} imageUrl 36 */ 37 cc.loadImage = function (imageUrl) { 38 // compute image type 39 var imageType = cc.computeImageFormatType(imageUrl); 40 if (imageType == cc.FMT_UNKNOWN) { 41 cc.log("unsupported format:" + imageUrl); 42 return; 43 } 44 var image = new Image(); 45 image.src = imageUrl; 46 image.addEventListener('load',function(){ 47 cc.TextureCache.getInstance().cacheImage(imageUrl, image); 48 this.removeEventListener('load', arguments.callee, false); 49 },false); 50 }; 51 52 /** 53 * Support image format type 54 * @param {String} filename 55 * @return {Number} 56 */ 57 cc.computeImageFormatType = function (filename) { 58 if (filename.toLowerCase().indexOf('.jpg') > 0 || filename.toLowerCase().indexOf('.jpeg') > 0) { 59 return cc.FMT_JPG; 60 } else if (filename.toLowerCase().indexOf('.png') > 0) { 61 return cc.FMT_PNG; 62 } else if (filename.toLowerCase().indexOf('.webp') > 0) { 63 return cc.FMT_WEBP; 64 } 65 66 return cc.FMT_UNKNOWN; 67 }; 68 69 /** 70 * Implementation TextureCache 71 * @class 72 * @extends cc.Class 73 */ 74 cc.TextureCache = cc.Class.extend(/** @lends cc.TextureCache# */{ 75 _textures:null, 76 _textureColorsCache:null, 77 _textureKeySeq:null, 78 79 _rendererInitialized:false, 80 _loadedTexturesBefore:null, 81 _loadingTexturesBefore:null, 82 83 /** 84 * Constructor 85 */ 86 ctor: function () { 87 cc.Assert(cc.g_sharedTextureCache == null, "Attempted to allocate a second instance of a singleton."); 88 this._textureKeySeq += (0 | Math.random() * 1000); 89 this._textures = {}; 90 this._textureColorsCache = {}; 91 if(cc.renderContextType === cc.WEBGL){ 92 this._loadedTexturesBefore = {}; 93 this._loadingTexturesBefore = {}; 94 } 95 }, 96 97 _addImageAsyncCallBack:function (target, selector) { 98 if (target && (typeof(selector) === "string")) { 99 target[selector](); 100 } else if (target && (typeof(selector) === "function")) { 101 selector.call(target); 102 } 103 }, 104 105 _initializingRenderer : function(){ 106 this._rendererInitialized = true; 107 108 var selPath; 109 //init texture from _loadedTexturesBefore 110 var locLoadedTexturesBefore = this._loadedTexturesBefore, locTextures = this._textures; 111 for(selPath in locLoadedTexturesBefore){ 112 var htmlImage = locLoadedTexturesBefore[selPath]; 113 114 var texture2d = new cc.Texture2D(); 115 texture2d.initWithElement(htmlImage); 116 texture2d.handleLoadedTexture(); 117 locTextures[selPath] = texture2d; 118 } 119 this._loadedTexturesBefore = {}; 120 }, 121 122 /** 123 * <p> 124 * Returns a Texture2D object given an PVR filename <br/> 125 * If the file image was not previously loaded, it will create a new CCTexture2D <br/> 126 * object and it will return it. Otherwise it will return a reference of a previously loaded image <br/> 127 * note: AddPVRTCImage does not support on HTML5 128 * </p> 129 * @param {String} filename 130 * @return {cc.Texture2D} 131 */ 132 addPVRTCImage:function (filename) { 133 cc.Assert(0, "TextureCache:addPVRTCImage does not support on HTML5"); 134 }, 135 136 137 /** 138 * <p> 139 * Returns a Texture2D object given an ETC filename <br/> 140 * If the file image was not previously loaded, it will create a new CCTexture2D <br/> 141 * object and it will return it. Otherwise it will return a reference of a previously loaded image <br/> 142 * note:addETCImage does not support on HTML5 143 * </p> 144 * @param {String} filename 145 * @return {cc.Texture2D} 146 */ 147 addETCImage:function (filename) { 148 cc.Assert(0, "TextureCache:addPVRTCImage does not support on HTML5"); 149 }, 150 151 /** 152 * Description 153 * @return {String} 154 */ 155 description:function () { 156 return "<TextureCache | Number of textures = " + this._textures.length + ">"; 157 }, 158 159 /** 160 * Returns an already created texture. Returns null if the texture doesn't exist. 161 * @param {String} textureKeyName 162 * @return {cc.Texture2D|Null} 163 * @example 164 * //example 165 * var key = cc.TextureCache.getInstance().textureForKey("hello.png"); 166 */ 167 textureForKey:function (textureKeyName) { 168 var fullPath = cc.FileUtils.getInstance().fullPathForFilename(textureKeyName); 169 if (this._textures.hasOwnProperty(fullPath)) 170 return this._textures[fullPath]; 171 return null; 172 }, 173 174 /** 175 * @param {Image} texture 176 * @return {String|Null} 177 * @example 178 * //example 179 * var key = cc.TextureCache.getInstance().getKeyByTexture(texture); 180 */ 181 getKeyByTexture:function (texture) { 182 for (var key in this._textures) { 183 if (this._textures[key] == texture) { 184 return key; 185 } 186 } 187 return null; 188 }, 189 190 _generalTextureKey:function () { 191 this._textureKeySeq++; 192 return "_textureKey_" + this._textureKeySeq; 193 }, 194 195 /** 196 * @param {Image} texture 197 * @return {Array} 198 * @example 199 * //example 200 * var cacheTextureForColor = cc.TextureCache.getInstance().getTextureColors(texture); 201 */ 202 getTextureColors:function (texture) { 203 var key = this.getKeyByTexture(texture); 204 if (!key) { 205 if (texture instanceof HTMLImageElement) 206 key = texture.src; 207 else 208 key = this._generalTextureKey(); 209 } 210 211 if (!this._textureColorsCache.hasOwnProperty(key)) 212 this._textureColorsCache[key] = cc.generateTextureCacheForColor(texture); 213 return this._textureColorsCache[key]; 214 }, 215 216 /** 217 * <p>Returns a Texture2D object given an PVR filename<br /> 218 * If the file image was not previously loaded, it will create a new Texture2D<br /> 219 * object and it will return it. Otherwise it will return a reference of a previously loaded image </p> 220 * @param {String} path 221 * @return {cc.Texture2D} 222 */ 223 addPVRImage:function (path) { 224 cc.Assert(path != null, "TextureCache: file image MUST not be null"); 225 226 path = cc.FileUtils.getInstance().fullPathForFilename(path); 227 228 var key = path; 229 230 if (this._textures[key] != null) { 231 return this._textures[key]; 232 } 233 234 // Split up directory and filename 235 var tex = new cc.Texture2D(); 236 if (tex.initWithPVRFile(key)) { 237 this._textures[key] = tex; 238 } else { 239 cc.log("cocos2d: Couldn't add PVRImage:" + key + " in TextureCache"); 240 } 241 return tex; 242 }, 243 244 /** 245 * <p>Purges the dictionary of loaded textures. <br /> 246 * Call this method if you receive the "Memory Warning" <br /> 247 * In the short term: it will free some resources preventing your app from being killed <br /> 248 * In the medium term: it will allocate more resources <br /> 249 * In the long term: it will be the same</p> 250 * @example 251 * //example 252 * cc.TextureCache.getInstance().removeAllTextures(); 253 */ 254 removeAllTextures:function () { 255 var locTextures = this._textures; 256 for (var selKey in locTextures) { 257 if(locTextures[selKey]) 258 locTextures[selKey].releaseTexture(); 259 } 260 this._textures = {}; 261 }, 262 263 /** 264 * Deletes a texture from the cache given a texture 265 * @param {Image} texture 266 * @example 267 * //example 268 * cc.TextureCache.getInstance().removeTexture(texture); 269 */ 270 removeTexture:function (texture) { 271 if (!texture) 272 return; 273 274 var locTextures = this._textures; 275 for (var selKey in locTextures) { 276 if (locTextures[selKey] == texture) { 277 locTextures[selKey].releaseTexture(); 278 delete(locTextures[selKey]); 279 } 280 } 281 }, 282 283 /** 284 * Deletes a texture from the cache given a its key name 285 * @param {String} textureKeyName 286 * @example 287 * //example 288 * cc.TextureCache.getInstance().removeTexture("hello.png"); 289 */ 290 removeTextureForKey:function (textureKeyName) { 291 if (textureKeyName == null) 292 return; 293 var fullPath = cc.FileUtils.getInstance().fullPathForFilename(textureKeyName); 294 if (this._textures[fullPath]) 295 delete(this._textures[fullPath]); 296 }, 297 298 /** 299 * Loading the images asynchronously 300 * @param {String} path 301 * @param {cc.Node} target 302 * @param {Function} selector 303 * @return {cc.Texture2D} 304 * @example 305 * //example 306 * cc.TextureCache.getInstance().addImageAsync("hello.png", this, this.loadingCallBack); 307 */ 308 addImageAsync:function (path, target, selector) { 309 cc.Assert(path != null, "TextureCache: path MUST not be null"); 310 path = cc.FileUtils.getInstance().fullPathForFilename(path); 311 var texture = this._textures[path]; 312 var image,that; 313 if (texture) { 314 if(texture.isLoaded()){ 315 this._addImageAsyncCallBack(target, selector); 316 }else{ 317 that = this; 318 image = texture.getHtmlElementObj(); 319 image.addEventListener("load", function () { 320 texture.handleLoadedTexture(); 321 that._addImageAsyncCallBack(target, selector); 322 this.removeEventListener('load', arguments.callee, false); 323 }); 324 } 325 } else { 326 image = new Image(); 327 image.crossOrigin = "Anonymous"; 328 329 that = this; 330 image.addEventListener("load", function () { 331 if (that._textures.hasOwnProperty(path)) 332 that._textures[path].handleLoadedTexture(); 333 that._addImageAsyncCallBack(target, selector); 334 this.removeEventListener('load', arguments.callee, false); 335 this.removeEventListener('error', arguments.callee, false); 336 }); 337 image.addEventListener("error", function () { 338 cc.Loader.getInstance().onResLoadingErr(path); 339 //remove from cache 340 if (that._textures.hasOwnProperty(path)) 341 delete that._textures[path]; 342 343 this.removeEventListener('error', arguments.callee, false); 344 }); 345 image.src = path; 346 var texture2d = new cc.Texture2D(); 347 texture2d.initWithElement(image); 348 this._textures[path] = texture2d; 349 } 350 return this._textures[path]; 351 }, 352 353 _addImageBeforeRenderer:function(path){ 354 var texture = new Image(); 355 texture.crossOrigin = "Anonymous"; 356 var that = this; 357 texture.addEventListener("load", function () { 358 cc.Loader.getInstance().onResLoaded(); 359 that._loadedTexturesBefore[path] = texture; 360 delete that._loadingTexturesBefore[path]; 361 362 this.removeEventListener('load', arguments.callee, false); 363 this.removeEventListener('error', arguments.callee, false); 364 }); 365 texture.addEventListener("error", function () { 366 cc.Loader.getInstance().onResLoadingErr(path); 367 delete that._loadingTexturesBefore[path]; 368 369 this.removeEventListener('error', arguments.callee, false); 370 }); 371 texture.src = path; 372 this._loadingTexturesBefore[path] = texture; 373 }, 374 375 /** 376 * <p>Returns a Texture2D object given an file image <br /> 377 * If the file image was not previously loaded, it will create a new Texture2D <br /> 378 * object and it will return it. It will use the filename as a key.<br /> 379 * Otherwise it will return a reference of a previously loaded image. <br /> 380 * Supported image extensions: .png, .jpg, .gif</p> 381 * @param {String} path 382 * @return {cc.Texture2D} 383 * @example 384 * //example 385 * cc.TextureCache.getInstance().addImage("hello.png"); 386 */ 387 addImage:function (path) { 388 cc.Assert(path != null, "TextureCache: path MUST not be null"); 389 if(cc.renderContextType === cc.WEBGL){ 390 if (!this._rendererInitialized) 391 return this._addImageBeforeRenderer(path); 392 } 393 394 path = cc.FileUtils.getInstance().fullPathForFilename(path); 395 396 var texture = this._textures[path]; 397 var image; 398 if (texture) { 399 if (texture.isLoaded()) { 400 cc.Loader.getInstance().onResLoaded(); 401 } else { 402 image = texture.getHtmlElementObj(); 403 image.addEventListener("load", function () { 404 texture.handleLoadedTexture(); 405 cc.Loader.getInstance().onResLoaded(); 406 this.removeEventListener('load', arguments.callee, false); 407 }); 408 } 409 } else { 410 image = new Image(); 411 image.crossOrigin = "Anonymous"; 412 413 var that = this; 414 image.addEventListener("load", function () { 415 cc.Loader.getInstance().onResLoaded(); 416 if (that._textures.hasOwnProperty(path)) 417 that._textures[path].handleLoadedTexture(); 418 this.removeEventListener('load', arguments.callee, false); 419 this.removeEventListener('error', arguments.callee, false); 420 }); 421 image.addEventListener("error", function () { 422 cc.Loader.getInstance().onResLoadingErr(path); 423 //remove from cache 424 if (that._textures.hasOwnProperty(path)) 425 delete that._textures[path]; 426 427 this.removeEventListener('error', arguments.callee, false); 428 }); 429 image.src = path; 430 var texture2d = new cc.Texture2D(); 431 texture2d.initWithElement(image); 432 this._textures[path] = texture2d; 433 } 434 435 return this._textures[path]; 436 }, 437 438 /** 439 * Cache the image data 440 * @param {String} path 441 * @param {Image|HTMLImageElement|HTMLCanvasElement} texture 442 */ 443 cacheImage:function (path, texture) { 444 if(texture instanceof cc.Texture2D){ 445 this._textures[path] = texture; 446 return ; 447 } 448 var texture2d = new cc.Texture2D(); 449 texture2d.initWithElement(texture); 450 texture2d.handleLoadedTexture(); 451 this._textures[path] = texture2d; 452 }, 453 454 /** 455 * <p>Returns a Texture2D object given an UIImage image<br /> 456 * If the image was not previously loaded, it will create a new Texture2D object and it will return it.<br /> 457 * Otherwise it will return a reference of a previously loaded image<br /> 458 * The "key" parameter will be used as the "key" for the cache.<br /> 459 * If "key" is null, then a new texture will be created each time.</p> 460 * @param {HTMLImageElement|HTMLCanvasElement} image 461 * @param {String} key 462 * @return {cc.Texture2D} 463 */ 464 addUIImage:function (image, key) { 465 cc.Assert(image != null, "TextureCache: image MUST not be nulll"); 466 467 if (key) { 468 if (this._textures.hasOwnProperty(key) && this._textures[key]) 469 return this._textures[key]; 470 } 471 472 // prevents overloading the autorelease pool 473 var texture = new cc.Texture2D(); 474 texture.initWithImage(image); 475 if ((key != null) && (texture != null)) 476 this._textures[key] = texture; 477 else 478 cc.log("cocos2d: Couldn't add UIImage in TextureCache"); 479 return texture; 480 }, 481 482 /** 483 * <p>Output to cc.log the current contents of this TextureCache <br /> 484 * This will attempt to calculate the size of each texture, and the total texture memory in use. </p> 485 */ 486 dumpCachedTextureInfo:function () { 487 var count = 0; 488 var totalBytes = 0, locTextures = this._textures; 489 490 for (var key in locTextures) { 491 var selTexture = locTextures[key]; 492 count++; 493 if (selTexture.getHtmlElementObj() instanceof HTMLImageElement) 494 cc.log("cocos2d: '" + key + "' id=" + selTexture.getHtmlElementObj().src + " " + selTexture.getPixelsWide() + " x " + selTexture.getPixelsHigh()); 495 else { 496 cc.log("cocos2d: '" + key + "' id= HTMLCanvasElement " + selTexture.getPixelsWide() + " x " + selTexture.getPixelsHigh()); 497 } 498 totalBytes += selTexture.getPixelsWide() * selTexture.getPixelsHigh() * 4; 499 } 500 501 var locTextureColorsCache = this._textureColorsCache; 502 for (key in locTextureColorsCache) { 503 var selCanvasColorsArr = locTextureColorsCache[key]; 504 for (var selCanvasKey in selCanvasColorsArr){ 505 var selCanvas = selCanvasColorsArr[selCanvasKey]; 506 count++; 507 cc.log("cocos2d: '" + key + "' id= HTMLCanvasElement " + selCanvas.width + " x " + selCanvas.height); 508 totalBytes += selCanvas.width * selCanvas.height * 4; 509 } 510 511 } 512 cc.log("cocos2d: TextureCache dumpDebugInfo: " + count + " textures, HTMLCanvasElement for " 513 + (totalBytes / 1024) + " KB (" + (totalBytes / (1024.0 * 1024.0)).toFixed(2) + " MB)"); 514 } 515 }); 516 517 /** 518 * Return ths shared instance of the cache 519 * @return {cc.TextureCache} 520 */ 521 cc.TextureCache.getInstance = function () { 522 if (!cc.g_sharedTextureCache) 523 cc.g_sharedTextureCache = new cc.TextureCache(); 524 return cc.g_sharedTextureCache; 525 }; 526 527 /** 528 * Purges the cache. It releases the retained instance. 529 */ 530 cc.TextureCache.purgeSharedTextureCache = function () { 531 cc.g_sharedTextureCache = null; 532 }; 533