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