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