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