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