Detect when an image has loaded

Hi there,

I have an app/game where I am loading some images from a remote site and want to scale them to a fixed size.

The challenge I have is the calls to create a sprite or add an image to the texture cache return immediately (as they should) with undefined heights and widths. A subsequent call to setScale cannot use the content size to help make decisions on scaling the original image to the fixed size.

i.e.
@
img = cc.TextureCache.getInstance().addImage(“some web address of an image”);
image = cc.Sprite.createWithTexture(img);
image.setScaleX(size/image.getContentSize().width);
image.setScaleY(size/image.getContentSize().height);@

My current solution for now (so I can work on other things) is simply to crop when I don’t know the image size but it is of course not ideal. Once it is in the texture cache all subsequent usage can be scaled properly.

img = cc.TextureCache.getInstance().addImage("some web address of an image"); if (img.height == 0) { image = cc.Sprite.createWithTexture(img, cc.rect(0,0,size,size)); } else { image = cc.Sprite.createWithTexture(img); image.setScaleX(size/image.getContentSize().width); image.setScaleY(size/image.getContentSize().height); }

Does cocos2d have a scale to size type function? This would alleviate the need to calculate the scale and will resolve itself later when the image draws.

The alternative is to detect when the image has loaded and run an action at that time. This would mean overriding the “load” listener on the canvas Image() I believe. Is there some built in functionality for this in cocos2d-html5 or will I need to subclass and possibly build a class that basically does the same thing as the standard image loaders but calls an action (if defined) after loading. This sounds like something that others might eventually need as well.

Alan

I had a similar requirement and took this approach to solving it: http://www.gamefromscratch.com/post/2012/07/25/JavaScript-Toddler-Game-Part-4-The-Game-itself.aspx

Basically I had arbitrarily sized images, but I wanted them to be resized ( up or down ) to a fixed size. However, I had control of the server side, since my backend was NodeJS based. One of the major problems I ran into ( and you will run into ), is the size of the image isn’t set until the image is loaded into the DOM.

My approach to solving this was to resize the image on the server ( behind the scenes using Imagemagick ), then implemnting a pair of REST services, one for getting the image size, the other for the image itself. It sounds much more complex then it really is. Of course, if you have no control over the server, this option isn’t available to you.

Thanks Mike,

I do have access to our servers which I can pull the image into and process prior to delivering to the cocos client. I was hoping to avoid this, we have enough traffic to and from our servers.

I don’t show the image until the Dom has loaded it (standard cocos functionality in its “load” listener) so it is just a matter of intercepting the event when it arrives. I don’t come from a web development background so I will need to dig into the Image loading in the Canvas and Dom to understand how I can insert myself into the load notification chain (if there even is one … otherwise I will end up having to write over some code in the library … don’t really like to do that).

Alan

So I took the plunge and subclassed and this works pretty well. I created a subclass of Sprite and created a method that takes an action

cc.WebSprite = cc.Sprite.extend({

    initWithFileAction:function (filename, action, rect) {
        cc.Assert(filename != null, "Sprite#initWithFile():Invalid filename for sprite");
        var selfPointer = this;
        var texture = cc.TextureCache.getInstance().textureForKey(filename);
        if (!texture) {
            //texture = cc.TextureCache.getInstance().addImage(filename);
            this._isVisible = false;
            var loadImg = new Image();
            loadImg.addEventListener("load", function () {
                if (!rect) {
                    rect = cc.rect(0, 0, loadImg.width, loadImg.height);
                }

                selfPointer.initWithTexture(loadImg, rect);
                cc.TextureCache.getInstance().cacheImage(filename, loadImg);
                selfPointer._isVisible = true;
                if (action) {
                    selfPointer.runAction(action);
                }
            });
            loadImg.addEventListener("error", function () {
                cc.log("load failure:" + filename);
            });
            loadImg.src = filename;
            return true;
        } else {
            if (texture) {
                if (!rect) {
                    rect = cc.rect(0, 0, 0, 0);
                    if (texture instanceof cc.Texture2D)
                        rect.size = texture.getContentSize();
                    else if ((texture instanceof HTMLImageElement) || (texture instanceof HTMLCanvasElement))
                        rect.size = cc.size(texture.width, texture.height);
                }
                var sprite = this.initWithTexture(texture, rect);
                if (action) {
                    this.runAction(action);
                }
                return sprite;
            }
        }
        return false;
    }
});

cc.WebSprite.create = function (fileName, action, rect) {
    var argnum = arguments.length;
    var sprite = new cc.WebSprite();
    if (argnum < 2) {
        /** Creates an sprite with an image filename.
         The rect used will be the size of the image.
         The offset will be (0,0).
         */
        if (sprite && sprite.initWithFileAction(fileName, action)) {
            return sprite;
        }
        return null;
    } else {
        /** Creates an sprite with an CCBatchNode and a rect
         */
        if (sprite && sprite.initWithFileAction(fileName, action, rect)) {
            return sprite;
        }
        return null;
    }
};

This is the same function as initWithFile but it calls the function at the end of the load (both in the case where the file is being downloaded or not).

It can take any action on the load … in my case I call it with this code (I scale the image to a 40x40 box):

    var action = cc.CallFunc.create(this, this.webLoadScaleCallback, 40);
    image = cc.WebSprite.create("location of file on web", action);

my callback action looks like:

    webLoadScaleCallback:function (pSender, data) {
        // pSender is the sprite
        // data is the size we want the sprite
        pSender.setScaleX(data/pSender.getContentSize().width);
        pSender.setScaleY(data/pSender.getContentSize().height);
    },

Since there is bound to be much more asynchronous loading of items it would be great to see some kind of mechanism like this in the SDK. Might be already there somewhere :slight_smile:

Alan