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.onLoad = function (e) {
 47         cc.TextureCache.getInstance().cacheImage(imageUrl, image);
 48     };
 49 };
 50 
 51 /**
 52  *  Support image format type
 53  * @param {String} filename
 54  * @return {Number}
 55  */
 56 cc.computeImageFormatType = function (filename) {
 57     if (filename.toLowerCase().indexOf('.jpg') > 0 || filename.toLowerCase().indexOf('.jpeg') > 0) {
 58         return cc.FMT_JPG;
 59     } else if (filename.indexOf('.png') > 0 || filename.indexOf('.PNG') > 0) {
 60         return cc.FMT_PNG;
 61     }
 62     return cc.FMT_UNKNOWN;
 63 };
 64 
 65 /**
 66  *  Implementation TextureCache
 67  * @class
 68  * @extends cc.Class
 69  */
 70 cc.TextureCache = cc.Class.extend(/** @lends cc.TextureCache# */{
 71     textures:{},
 72     _textureColorsCache:{},
 73     _textureKeySeq:1000,
 74 
 75     /**
 76      * Constructor
 77      */
 78     ctor:function () {
 79         cc.Assert(cc.g_sharedTextureCache == null, "Attempted to allocate a second instance of a singleton.");
 80         this._textureKeySeq += (0|Math.random() * 1000);
 81     },
 82 
 83     /**
 84      *  Loading the images asynchronously
 85      * @param {String} path
 86      * @param {cc.Node} target
 87      * @param {Function} selector
 88      * @return {Image}
 89      * @example
 90      * //example
 91      * cc.TextureCache.getInstance().addImageAsync("hello.png", this, this.loadingCallBack);
 92      */
 93     addImageAsync:function (path, target, selector) {
 94         cc.Assert(path != null, "TextureCache: path MUST not be null");
 95 
 96         path = cc.FileUtils.getInstance().fullPathFromRelativePath(path);
 97 
 98         var texture = this.textures[path.toString()];
 99 
100         if (texture) {
101             this._addImageAsyncCallBack(target, selector);
102         }
103         else {
104             texture = new Image();
105             var that = this;
106             texture.addEventListener("load", function () {
107                 that._addImageAsyncCallBack(target, selector);
108             });
109             texture.src = path;
110             this.textures[path.toString()] = texture;
111         }
112 
113         if (cc.renderContextType == cc.CANVAS) {
114             return this.textures[path.toString()];
115         } else {
116             //todo texure for gl
117         }
118     },
119     _addImageAsyncCallBack:function (target, selector) {
120         if (target && (typeof(selector) == "string")) {
121             target[selector]();
122         } else if (target && (typeof(selector) == "function")) {
123             selector.call(target);
124         }
125     },
126 
127     /**
128      * AddPVRTCImage does not support
129      */
130     addPVRTCImage:function () {
131         cc.Assert(0, "TextureCache:addPVRTCImage does not support");
132     },
133 
134     /**
135      * Description
136      * @return {String}
137      */
138     description:function () {
139         return "<TextureCache | Number of textures = " + this.textures.length + ">";
140     },
141 
142     /**
143      * <p>Returns a Texture2D object given an file image <br />
144      * If the file image was not previously loaded, it will create a new Texture2D <br />
145      *  object and it will return it. It will use the filename as a key.<br />
146      * Otherwise it will return a reference of a previously loaded image. <br />
147      * Supported image extensions: .png, .jpg, .gif</p>
148      * @param {String} path
149      * @return {Image}
150      * @example
151      * //example
152      * cc.TextureCache.getInstance().addImage("hello.png");
153      */
154     addImage:function (path) {
155         cc.Assert(path != null, "TextureCache: path MUST not be null");
156 
157         path = cc.FileUtils.getInstance().fullPathFromRelativePath(path);
158 
159         var texture = this.textures[path.toString()];
160         if (texture) {
161             cc.Loader.getInstance().onResLoaded();
162         }
163         else {
164             texture = new Image();
165             var that = this;
166             texture.addEventListener("load", function () {
167 
168                 cc.Loader.getInstance().onResLoaded();
169             });
170             texture.addEventListener("error", function () {
171                 cc.Loader.getInstance().onResLoadingErr(path);
172             });
173             texture.src = path;
174             this.textures[path.toString()] = texture;
175         }
176 
177         if (cc.renderContextType == cc.CANVAS) {
178             return this.textures[path.toString()];
179         } else {
180             //todo texture for gl
181         }
182     },
183 
184     /**
185      *  Cache the image data
186      * @param {String} path
187      * @param {Image} texture
188      */
189     cacheImage:function (path, texture) {
190         if (!this.textures[path.toString()]) {
191             this.textures[path.toString()] = texture;
192         }
193     },
194 
195     /**
196      * <p>Returns a Texture2D object given an UIImage image<br />
197      * If the image was not previously loaded, it will create a new Texture2D object and it will return it.<br />
198      * Otherwise it will return a reference of a previously loaded image<br />
199      * The "key" parameter will be used as the "key" for the cache.<br />
200      * If "key" is null, then a new texture will be created each time.</p>
201      * @param {Image} image
202      * @param {String} key
203      * @return {cc.Texture2D}
204      */
205     addUIImage:function (image, key) {
206         cc.Assert(image != null, "TextureCache: image MUST not be nulll");
207 
208         var texture = null;
209 
210         if (key) {
211             if (this.textures.hasOwnProperty(key)) {
212                 texture = this.textures[key];
213                 if (texture) {
214                     return texture;
215                 }
216             }
217         }
218 
219         // prevents overloading the autorelease pool
220         texture = new cc.Texture2D();
221         texture.initWithImage(image);
222 
223         if ((key != null) && (texture != null)) {
224             this.textures[key] = texture;
225         } else {
226             cc.log("cocos2d: Couldn't add UIImage in TextureCache");
227         }
228 
229         return texture;
230     },
231 
232     /**
233      * Returns an already created texture. Returns null if the texture doesn't exist.
234      * @param {String} key
235      * @return {Image|Null}
236      * @example
237      * //example
238      * var key = cc.TextureCache.getInstance().textureForKey("hello.png");
239      */
240     textureForKey:function (key) {
241         if (this.textures.hasOwnProperty(key)) {
242             return this.textures[key];
243         } else {
244             return null;
245         }
246     },
247 
248     /**
249      * @param {Image} texture
250      * @return {String|Null}
251      * @example
252      * //example
253      * var key = cc.TextureCache.getInstance().getKeyByTexture(texture);
254      */
255     getKeyByTexture:function (texture) {
256         for (var key in this.textures) {
257             if (this.textures[key] == texture) {
258                 return key;
259             }
260         }
261         return null;
262     },
263 
264     _generalTextureKey:function(){
265         this._textureKeySeq++;
266         return "_textureKey_" + this._textureKeySeq;
267     },
268 
269     /**
270      * @param {Image} texture
271      * @return {Array}
272      * @example
273      * //example
274      * var cacheTextureForColor = cc.TextureCache.getInstance().getTextureColors(texture);
275      */
276     getTextureColors:function (texture) {
277         var key = this.getKeyByTexture(texture);
278         if (!key) {
279             if (texture instanceof HTMLImageElement) {
280                 key = texture.src;
281             } else {
282                 key = this._generalTextureKey();
283             }
284         }
285 
286         if (!this._textureColorsCache.hasOwnProperty(key)) {
287             this._textureColorsCache[key] = cc.generateTextureCacheForColor(texture);
288         }
289         return this._textureColorsCache[key];
290     },
291 
292     /**
293      * <p>Purges the dictionary of loaded textures. <br />
294      * Call this method if you receive the "Memory Warning"  <br />
295      * In the short term: it will free some resources preventing your app from being killed  <br />
296      * In the medium term: it will allocate more resources <br />
297      * In the long term: it will be the same</p>
298      * @example
299      * //example
300      * cc.TextureCache.getInstance().removeAllTextures();
301      */
302     removeAllTextures:function () {
303         this.textures = {};
304     },
305 
306     /**
307      * Deletes a texture from the cache given a texture
308      * @param {Image} texture
309      * @example
310      * //example
311      * cc.TextureCache.getInstance().removeTexture(texture);
312      */
313     removeTexture:function (texture) {
314         if (!texture)
315             return;
316 
317         for (var key in this.textures) {
318             if (this.textures[key] == texture) {
319                 delete(this.textures[key]);
320                 return;
321             }
322         }
323     },
324 
325     /**
326      * Deletes a texture from the cache given a its key name
327      * @param {String} textureKeyName
328      * @example
329      * //example
330      * cc.TextureCache.getInstance().removeTexture("hello.png");
331      */
332     removeTextureForKey:function (textureKeyName) {
333         if (textureKeyName == null) {
334             return;
335         }
336         if (this.textures[textureKeyName]) {
337             delete(this.textures[textureKeyName]);
338         }
339     },
340 
341     /**
342      * <p>Output to cc.log the current contents of this TextureCache <br />
343      * This will attempt to calculate the size of each texture, and the total texture memory in use. </p>
344      */
345     dumpCachedTextureInfo:function () {
346         var count = 0;
347         var totalBytes = 0;
348         for (var key in this.textures) {
349             var tex = this.textures[key];
350             var bpp = tex.bitsPerPixelForFormat();
351             // Each texture takes up width * height * bytesPerPixel bytes.
352             var bytes = tex.getPixelsWide() * tex.getPixelsHigh() * bpp / 8;
353             totalBytes += bytes;
354             count++;
355             cc.log("cocos2d: '" + tex.toString() + "' id=" + tex.getName() + " " + tex.getPixelsWide() + " x " + tex.getPixelsHigh() + " @ " + bpp + " bpp => " + bytes / 1024 + " KB");
356         }
357 
358         cc.log("cocos2d: TextureCache dumpDebugInfo: " + count + " textures, for " + (totalBytes / 1024) + " KB (" + (totalBytes / (1024.0 * 1024.0)).toFixed(2) + " MB)");
359     },
360 
361     /**
362      * <p>Returns a Texture2D object given an PVR filename<br />
363      * If the file image was not previously loaded, it will create a new Texture2D<br />
364      *  object and it will return it. Otherwise it will return a reference of a previously loaded image </p>
365      * @param {String} path
366      * @return {cc.Texture2D}
367      */
368     addPVRImage:function (path) {
369         cc.Assert(path != null, "TextureCache: file image MUST not be null");
370 
371         path = cc.FileUtils.getInstance().fullPathFromRelativePath(path);
372 
373         var key = path;
374 
375         if (this.textures[key] != null) {
376             return this.textures[key];
377         }
378 
379         // Split up directory and filename
380         var tex = new cc.Texture2D();
381         if (tex.initWithPVRFile(key)) {
382             this.textures[key] = tex;
383         } else {
384             cc.log("cocos2d: Couldn't add PVRImage:" + key + " in TextureCache");
385         }
386 
387         return tex;
388     }
389 });
390 
391 /**
392  * Return ths shared instance of the cache
393  * @return {cc.TextureCache}
394  */
395 cc.TextureCache.getInstance = function () {
396     if (!cc.g_sharedTextureCache)
397         cc.g_sharedTextureCache = new cc.TextureCache();
398     return cc.g_sharedTextureCache;
399 };
400 
401 /**
402  * Purges the cache. It releases the retained instance.
403  */
404 cc.TextureCache.purgeSharedTextureCache = function () {
405     cc.g_sharedTextureCache = null;
406 };
407