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