-
Notifications
You must be signed in to change notification settings - Fork 1.6k
ImageAsset Guide
All assets are known engine types that allow instances to be created at runtime. The "ImageAsset", like all assets, is derived from a base type of "AssetBase". That means it includes all the fields from that type as well as adding its own fields specific to itself.
The ImageAsset provides a way to refer to a single image file and produce logical frames that represent areas within the image itself. It can represent a single frame or an unlimited number of frames. Each frame is referenced in one of two ways: either using a zero-based index (implicit mode) or by giving each frame a unique name (explicit mode).
For an ImageAsset to be valid, it must contain at least a single frame i.e. frame #0.
The ImageAsset type exposes the following fields in addition to those it inherits. Shown are the equivalent methods available that perform the same action in setting and getting the respective field:
-
ImageFile
- setImageFile(fileName)
- getImageFile()
-
CellCountX
- setCellCountX(integer)
- getCellCountX():
-
CellCountY
- setCellCountY(integer)
- getCellCountY():
-
CellWidth
- setCellWidth(integer)
- getCellWidth():
-
CellHeight
- setCellHeight(integer)
- getCellHeight():
-
CellStrideX
- setCellStrideX(integer)
- getCellStrideX():
-
CellStrideY
- setCellStrideY(integer)
- getCellStrideY():
-
CellOffsetX
- setCellOffsetX(integer)
- getCellOffsetX():
-
CellOffsetY
- setCellOffsetY(integer)
- getCellOffsetY():
-
CellRowOrder
- setCellRowOrder(true/false)
- getCellRowOrder():
-
FilterMode
- setFilterMode(filterMode)
- getFilterMode():
-
Force16bit
- setForce16Bit(true/false)
- getForce16Bit():
-
ExplicitMode
- setExplicitMode(true/false)
- getExplicitMode():
In the list above, only "ImageFile" is mandatory, all others are completely optional.
Here's an example of the most basic form of an ImageAsset where only the mandatory "ImageFile" field is specified:
<ImageAsset
AssetName="Gems"
ImageFile="Gems.png"
/>
This would produce an asset known as "Gems" and produce a single texture from the image "Gems.png" which it expects to be located alongside where the XML ImageAsset file is. There is no need to make the asset name match the image name but it can make keeping the relevant files together on disk easier with an alphabetic file sort.
The following is a complete description of each of these fields.
This is the only mandatory field for the type. It should refer to an image file that the engine can load. It is not essential to add the file extension here as the engine will attempt different extensions for you that it knows it can load (it'll try what you originally specified, then add a ".png" then a ".jpg").
If the image file could not be found or loaded then the asset is still generated however internally it is flagged as invalid. Additionally, you will see this as a warning in the console.
You can refer to sub-folders here but using a format like:
<ImageAsset
AssetName="Gems"
ImageFile="artwork/Gems.png"
/>
Do not use the "." to indicate the current folder as this is already implicitly assumed. You can however use ".." to indicate a parent folder but it is highly discouraged to store artwork in parent folders.
When a valid image file is found, this is used when the asset is eventually required. The image file is loaded and uploaded as a single texture. To do this, the image size must be supported by your graphics card as each graphics cards has a size limit for textures.
For the image to be uploaded as a texture however it must be a power-of-two in size but Torque 2D will internally re-size it if this is not the case so that it can be uploaded. Torque 2D will not modifie the image on the disk, it does this in-memory prior to uploading as a texture so the change is completely volatile. It also has to do this whenever the asset is loaded from disk and so whilst the process is very fast, it does waste a little time but more importantly is wasted memory on the graphics card. It also only adds extra space to the right and bottom of the image, it does not rescale or change the image in any way so this change is completely transparent but is done purely for the benefit of the graphics card.
If you turn-on the "metrics" on your SceneWindow i.e. something like 'SandboxScene.setDebugOn("metrics")' and look at the category "Textures", you'll see several entries that show you metrics like how many textures are resident on the graphics card, their total estimated size in bytes, how much main memory is used with images loaded into (ImageAsset releases its bitmaps so no need to worry about that) and finally how much waste their is on textures resident on the graphics card. Texture waste is the "space" that was created to resize the original image into a texture that was is a power-of-two in size. If your image was already a power-of-two in size then it will produce no waste so that's the optimal case.
Torque 2D does not perform any packing of textures like legacy Torque 2D. This was a major cause of performance and memory issues in that product, especially on lower-end devices. All those problems have been removed. Texture packing and ensuring that the images loaded are now in the hands of artists and other content pipeline products.
If you don't specify any "Cell" fields or don't use the "Explicit" mode described below then the ImageAsset will assume that the whole image is a single frame. This makes defining "full frame" images used for things like backgrounds extremely easy.
However, it's a very common practice to divide the image into a regular grid of "cells". All the "Cell" fields allow you to configure this grid of cells.
The "CellCountX" and "CellCountY" control how many of these cells there are in the horizontal and vertical axis respectively. These two fields multiplied together state how many frames are expected e.g a "CellCountX" of "5" and "CellCountY" of "10" would together produce fifty frames.
As with all the "Cell" fields described here, you must ensure that the frame areas existing within the dimensions of the image you specify. If any of the configuration results in cells positioned beyond the dimensions of the image you specify then the whole ImageAsset is created but flagged as invalid.
Controlling how many frames in the horizontal and vertical directions is the start however it's also vital that you configure the width and height of each cell. This is what "CellWidth" and "CellHeight" do.
So for example, if you have the following:
<ImageAsset
AssetName="Gems"
ImageFile="Gems.png"
CellCountX="8"
CellCountY="8"
CellWidth="64"
CellHeight="64" />
.. then it would be defining an asset named "Gems" with 8 x 8 frames (64) where each frame is a square 64x64 pixels.
You can see this asset in the codebase:
It's important to remember that whilst the overall image size needs to be a power-of-two for upload to your graphics card as a texture, cell width or cell height can be anything from a single pixel to as wide or tall as the image. Also, this example shows a 64x64 pixel cell size but the width and height don't have to be the same, you could have 1x50, 3x927, 321x33 etc.
The fields "CellCountX", "CellCountY", "CellWidth" and "CellHeight" are used in combination and none should be omitted if any of the others are used i.e. they must all be used together. If you only want a single frame then don't specify any of them and a single full-image frame is assumed in that case.
Typical cells are assumed to be next to each other i.e. the cell grid has no "spaces" in-between the cells. If this isn't the case then you can control how the ImageAsset steps or "strides" from cell to cell in both the horizontal and vertical.
By default, if you don't specify "CellStrideX" or "CellStrideY", the ImageAsset automatically sets them to be the "CellWidth" and "CellHeight" respectively.
This means that you can set them to be whatever you like. For instance, if each next cell in the horizontal has a single pixel space (gutter) in-between them then you want to set the "CellStrideX" to be the "CellWidth" plus one. The same would go for any configuration in the vertical using "CellStrideY".
Here's an example of that:
<ImageAsset
AssetName="Gems"
ImageFile="Gems.png"
CellCountX="8"
CellCountY="8"
CellWidth="64"
CellHeight="64"
CellStrideX="70"
CellStrideY="80"/>
In this example, each cell is 64x64 but the next cell in the horizontal as indicated by "CellStrideX" is "70" pixels along i.e. there's a 16 pixel gap in-between. In the vertical as indicated by the "CellStrideY", the next cell is "80" pixels down i.e. there's a "26" pixel gap in-between.
You are free to only specify the horizontal or vertical strides if you so choose.
As you have seen, when calculating the area for each cell, a cell width, height and horizontal/vertical strides are used. This however all assumes that the cells on the left of the image start at pixel 0 and likewise the cells on the top of the image start at pixel 0. These are denoted by "CellOffsetX" and "CellOffsetY" respectively.
If the cells start offset from the left of the image, you can specify this offset using "CellOffsetX". It must always be a positive integer and defaults to zero if you don't specify it. Likewise, if the cells start offset from the top of the image, you can specify this offset using "CellOffsetY".
This field controls how frame numbers are allocated to cells.
When using cells, the ImageAsset will generate frames from frame #0 onwards. The default for this generation is to look for cells horizontally i.e. cells order into rows. This means that the first frame will be at the top-left cell, the next frame will be the next cell horizontally (assuming there is a next cell horizontally) and will continue to find cells as specified using "CellCountX". When all the cells in that "row" have produced frames then the next row of cells will be processed according to "CellCountY".
In other words, frame numbers are allocated moving horizontally from left to right in "rows" moving down to the next "row" and continuing until all the cells have been processed.
This may not be how you wish to order your frames however. An alternate strategy is to not allocate frames in a "row" order but do so in a "column" order. By default "row order" is true but if you change this to "false" then "column order" is used where frame numbers are allocated vertically from top to bottom moving to the next "column" and continuing until all cells have been processed.
When the texture is generated on the graphics card, the texture is associated with a texture filtering mode. The two modes available are:
These are specified like this:
<ImageAsset
AssetName="Logo"
ImageFile="NiceLogo.png"
FilterMode="Nearest"/>
... or like this ...
<ImageAsset
AssetName="Logo"
ImageFile="NiceLogo.png"
FilterMode="Bilinear"/>
This allows you to control how the graphics card uses the texture on a per-ImageAsset basis. This is known as the "local filter" for that asset and you are free to configure each asset however you like.
You can however configure this using an engine-wide setting so you don't need to modify your assets. This may be useful if you are reusing assets and don't wish the "style" of filtering to be specified in the assets themselves.
You can specify the engine-wide filtering like this.
$pref::T2D::imageAssetGlobalFilterMode = "Nearest";
...
$pref::T2D::imageAssetGlobalFilterMode = "Bilinear";
To stop using an engine-wide filtering then set the above variable to be an empty string.
When the bitmap is uploaded to the graphics card, it is typically uploaded as a 24 or 32-bit image where each color channel (RGB) and an optional alpha channel is 8-bits wide however you can save graphics card memory by getting Torque 2D to upload the image as a 16-bit image. Depending on whether the bitmap contains an alpha channel or not, this typically means a red and blue with 5-bits and green with 6-bits or with an alpha channel, 4-bits for all color and alpha channels.
The obvious downside is greatly reduced texture quality.
Torque 2D needs to improve in this area and support more modern texture compression formats!
As you have seen, there are two ways to generate frames from an ImageAsset. The first is to not specify any frame dimensions, in which case the whole image is assumed to be a single frame. The second is to specify the layout and dimensions of "Cells" spanning the image.
There is however a third way known as "explicit" mode. It is explicit because it involves explicitly defining the positions of each and every cell within the image.
This is by far the most powerful and flexible method as it lends itself well to using images where frames have been packed tightly together into a complex arrangement. In this case, each frame needs to be specified.
By default, explicit mode is "false". Setting it to "true" however is only an indication of explicit mode, you still need to specify the frames themselves.
There are two ways to discuss how this is one and both are covered now.
The following methods are available to configure the explicit cells:
- clearExplicitCells()
- addExplicitCell()
- insertExplicitCell()
- removeExplicitCell()
- setExplicitCell()
- getExplicitCellCount()
- getExplicitCellWidth()
- getExplicitCellHeight()
Without going into detail on each of these calls as the relevant information can be found in the script-binding referenced at the top of this document (also in the automatically produced reference documentation), an overview of this process can be shown.
Essentially, setting explicit mode on is the first thing to be done. When this is done, all the other "Cell" fields are ignored.
The ImageAsset has an internal list of frames. It's this internal list that you can modify. You can do this by simply adding, inserting, removing or clearing cell explicit cells like so:
// Create a bare asset.
%asset = new ImageAsset();
// Configure the basic asset details.
%asset.AssetName = "Stuff";
%asset.ImageFile = "things.png"
%asset.ExplicitMode = true;
// Configure the explicit cells i.e. frames.
%asset.addExplicitCell( 0, 0, 10, 20, "square" );
%asset.addExplicitCell( 10, 0, 30, 15, "circle" );
%asset.addExplicitCell( 40, 60, 23, 10, "triangle" );
// Save it as an asset definition to be used by the asset system.
TamlWrite( %asset, "stuff.asset.taml" );
The "addExplicitCell" call (as can be seen in the documentation) takes the following arguments:
- cellOffsetX
- cellOffsetY
- cellWidth
- cellHeight
- regionName
These are the vital parameters of any cell. The "cellOffsetX" and "cellOffsetY" specify the position of the cell top-left relative to the image top-left i.e. the X/Y offset from the top-left of the image. The "cellWidth" and "cellHeight" obviously specify the width and height of that cell. The "regionName" defines the name of the frame, which can then be used when assigning an image frame to an object like a Sprite.
If no "regionName" is specified, it will automatically be assigned a frame number as its name. If all cells are unnamed, the order of these cells in the list is the order in which frame numbers will be allocated i.e the first unnamed cell will be frame#0, the second frame#1 etc. If you have a mix of named and unnamed cells, the unnamed cells are given a "frame number" name which correspondes to its position in the index list. It is highly recommended if you start naming cells, that all cells in the asset are given a name.
You can have as many cells as you wish, they can overlap, the same cell regions can be specified more than once without issue if it helps. In other words, you are free to do what you like.
You can use the "insertExplicitCell()" to insert a cell into an existing list and "removeExplicitCell()" to remove a cell from the list.
You must remember to set explicit mode to "true" prior to saving the asset otherwise the explicit cell details won't be saved.
As you saw in the previous section, you can set explicit cells using the API provided by the ImageAsset type itself. When you save the ImageAsset, it will save those explicit cells in its own custom format and that format is what is detailed here.
Obviously you are free to either programmatically or manually produce the appropriate TAML file to generate these explicit cells. Indeed, doing so programmatically would be a common way to produce an "export" of packed frames from other products.
This can be seen in practice using TexturePacker in the following video here.
You can also find the TexturePacker exported in the tool folder of the repository here.
Here's an example of the ImageAsset TAML XML format using the same example above generated using the ImageAsset API:
<ImageAsset
AssetName="Stuff"
ImageFile="things.png">
<ImageAsset.Cells>
<Cell Offset="0 0" Width="10" Height="20" RegionName="square"/>
<Cell Offset="10 0" Width="30" Height="15" RegionName="circle"/>
<Cell Offset="40 60" Width="23" Height="10" RegionName="triangle"/>
</ImageAsset.Cells>
/>
As you can see, the ImageAsset uses a custom property of "Cells" (this is TAML custom formatting) which contains an unlimited number of "Cell" XML elements where each relates to an individual cell (frame).
The order that the "Cell" XML elements appear here are the order which the frames will be allocated i.e. the first one will be frame#0, the second frame#1 etc.
If these were generated via writing out an ImageAsset then the order here is the order of cells in the ImageAsset as discussed previously.
When reading in a TAML file, the "Offset" field can be specified as shown in the example above (a 2 value string). Alternatively, you can split the offset field into its X and Y component.
<Cell OffsetX="0" OffsetY="0" Width="10" Height="20" RegionName="square"/>
The ImageAsset exposes a few additional methods that can be useful under certain circumstances.
This method gets the count of frames that the ImageAsset defines. If the frame count is zero then the asset is invalid and something has gone wrong. There is no upper-limit to the number of frames allowed.
This method returns the dimensions of each frame specified by the frame number (index). Frame numbers are zero-based therefore they range from 0 to "getFrameCount()-1". You can use this range to iterate all the frames in an ImageAsset.
The frame size is in pixels and is returns as the frame width and height separated with a space like so:
for( %i = 0; %i < %asset.getFrameCount(); %i++ )
{
echo( %asset.getFrameSize( %i ) );
}
Assuming the asset has only one frame with a width of 34 pixels and height of 78 pixels then it should output:
"34 78"
This method returns the width (in pixels) of the source bitmap. Note that this is the width of the image on disk and has nothing to do with any requirements for it to be a power-of-two in size.
This method returns the height (in pixels) of the source bitmap. Again, note that this is the height of the image on disk and has nothing to do with any requirements for it to be a power-of-two in size.
This method returns both the width and height (in pixels) of the source bitmap. In other words, it's the source bitmap dimensions as returned individually by "getImageWidth()" & "getImageHeight()".
This method returns whether the original source bitmap was a valid power-of-two in size or not. You can calculate this yourself but this method performs it for you. Whilst this method has limited value, it would allow an editor to highlight to an end-user that the image being used in the ImageAsset is not optimal in size in relation to not being a power-of-two in dimension.