Sunday, October 17, 2010

Animating the Canvas part 8: Splitting Image

Right now the library is good at drawing solid blocks. To be useful, however, better image support is needed. The one class that would make this library sufficient for the creation of games, which is the goal of this after all, would be an image layer that had support for image strips and image grids. Better yet, an image atlas since image strips and image grids are a subset of an image atlas. This is actually very simple to do.

An image atlas, for those who are not familiar with the term, is simply just a large image that contains smaller images. The idea here is that instead of dealing with a large number of image files, you instead only need to load one image and show portions of that image. This speeds up loading as you only need to request a single file instead of having to request multiple files from the server. It is also commonly used with 3D rendering but that is beyond the scope of this series.

What makes this class so easy to create is simply the fact that it inherits most of its functionality from the Layer class. JavaScript does not currently have support for subclasses so I make use of the inheritProperties function that was created earlier. As it is likely that the image layer will be used to display an entire image, the constructor simply contains the image to use. The portion of the image used is set to the entire image, though this can be changed any time allowing this class to be used as an image atlas.

BGLayers.ImageLayer = function(id, image)
    this.supr = new BGLayers.Layer(id, image.width, image.height);
    BGLayers.inheritProperties(this, this.supr);
    this.clip = new BGLayers.Rectangle(0,0, image.width, image.height);
    this.image = image;

To set the particular portion of the image that should be displayed, the setClip function sets the new portion of the image to be shown. This can be used for animation by simply having a strip or grid of frames and altering the portion being shown every frame.

BGLayers.ImageLayer.prototype.setClip = function(rect)
    this.clip.x = rect.x;
    this.clip.y = rect.y;
    this.clip.width = rect.width;
    this.clip.height = rect.height;

The hard part comes with the drawing as the portion of the image within the clipping bounds. This has to be calculated. This is done in a similar way to the calculation of the real layer position but using the clipping portion of the image. In theory it is possible to just set the canvas clipping region and draw the whole image, but I have had some issues with clipping so figure it is safest to avoid clipping whenever possible. Besides, relying on clipping makes the assumption that the canvas implementation is actually efficiently written where as manually drawing just the clip guarantees that the minimum amount of drawing is being done.

BGLayers.ImageLayer.prototype.drawSelf = function(ctx, bounds)
    if (this.findRealPosition().intersects(bounds) == false)
    var rect = this.realPosition.getIntersection(bounds);
    var scaleX = this.clip.width / this.realPosition.width;
    var scaleY = this.clip.height / this.realPosition.height;
    var boundClip = new BGLayers.Rectangle(
                this.clip.x + (rect.x - this.realPosition.x) * scaleX,
                this.clip.y + (rect.y - this.realPosition.y) * scaleY,
                rect.width * scaleX,
                rect.height * scaleY);
    ctx.drawImage(this.image, boundClip.x, boundClip.y, boundClip.width, boundClip.height,
                rect.x, rect.y, rect.width, rect.height);

And that is all that is needed. Creating a new layer is that simple. The atlas functionality of this class should be adequate for most uses so already this library is useful. Still, while doing some debugging on my Tarot project, stepping through the drawing of things did help me notice a lot of inefficiencies in how the library works so I am planning on having an optimization pass. I also want to do a bit of fine tuning to the library to make it clearer what variables are private, even though JavaScript does not support private variables. I may be renaming some things and adding some functionality so there is no need to access private variables. If something is clearly private and somebody modifies it anyway breaking their program it is their problem.

No comments: