Video Engine Images
Before we begin discussing the image API, it is helpful to provide a summary of how images are managed by the video engine in the background. Many of the images used in Allacrost are small in size (32x32 pixels for map tiles) and there are a large number (> 100) of these in use at any particular time. To reduce the performance overhead that would be required from continuous OpenGL texture switching, the video engine stores multiple images in texture sheets. Texture sheets are assigned a single OpenGL texture and may contain multiple amounts of image data. The default texture sheet size is 512x512 pixels, although it will create up to 1024x1024 size texture sheets if it is requested to load an image larger than 512x512. All of the texture sheet management is done by the backend of the video engine, so the user never has to worry about memory leaks in the image or texture code. Additionally, the image back end and texture management system work together to keep track of which images have already been loaded and where they are contained within texture memory. This prevents the code from loading in the same image data more than once, which keeps our memory utilization as low as possible and also helps increase performance for loading calls.
The image manipulation API consists of the classes listed below. The GameVideo class does not contain any members nor methods that implement a part of this particular API, although some GameVideo methods such as Move(...) and SetDrawFlags(...) can have an effect on the way in which images are displayed on the screen.
- ProceduralImage (not yet available)
One concept of the image API is that of a "multi image". A multi image is nothing more than a single image file which contains multiple adjacent sub-images within it. For example, a tileset file contains numerous tile images is a multi image. All sub-images are required to have the same dimensions in a multi image file. Multi images are never explicitly contained in any way by the video engine -- rather, these multi images are first loaded into memory and then the sub-images are immediately split up into separate image objects. The two most common uses for multi images are map tilesets and sprite animations.
ImageDescriptor is an abstract class that serves as the parent for all inherited image classes. It also contains the implementation of a number of static methods that perform some more general image operations. Below are a list of the non-static public functions available to this class. Note that there are no methods for loading the image available on this base class because the inherited classes all require a different approach to loading image data.
- void Clear()
Resets all members to their initial state and releases any image data that was referenced.
- void Draw() const
- void Draw(const Color& draw_color) const
These two methods draw the image to the render target. The one which accepts a reference to a color will draw that image modulated by that single color. This can be used, for instance, to fade the level of transparency of the image. It is vital to note that where the image is drawn, and in what orientation it is drawn, is affected by the draw cursor and the draw flags that are set in the VideoManager singleton of the GameVideo class. Thus, you will often see calls to VideoManager->Move(...), VideoManager->MoveRelative(...), or VideoManager->SetDrawFlags(...) preceding the Draw() call for images.
- float GetWidth() const
- float GetHeight() const
- void SetWidth(float width)
- void SetHeight(float height)
- void SetDimensions(float width, float height)
These are accessor methods for the image's dimensions (width and height). The dimensions are stored in floating point format, and the image draw operations are affected by the current coordinate system. Be aware that an image with dimensions of 50.0f, 50.0f will be drawn very differently on a coordinate system of (0.0f, 1024.0f, 0.0f, 768.0f) versus a coordinate system of (0.0f, 64.0f, 0.0f, 40.0f). Note that if the dimensions are not set prior to loading an image (i.e. width and height members are both zero), then upon successful loading of the image, the dimensions will be set to represent the pixel dimensions of the image (e.g., a 32x32 pixel image will have width and height both set to 32.0f). Some of the inherited classes provide the option to set the width and height of the image in a Load method.
- bool IsGrayScale() const
Returns true if the image is in grayscale format. The inherited image classes may or may not have methods to enable/disable the grayscale property.
- void SetStatic(bool is_static)
This enables or disables the "static" property for images, which is disabled by default. An image with a static property indicates to the video engine that: "it is likely that this image will remain loaded and referenced in texture memory for a very long time". A good example of an image that we would want to load as static would be a GUI menu skin. The video engine attempts to group these static images into the same texture sheet for better performance. The static property must be set before loading the image, otherwise it will have no effect.
- void SetColor(const Color& color)
- void SetVertexColors(const Color& tl, const Color& tr, const Color& bl, const Color& br)
Each image has four color members corresponding to its four vertices (top left, top right, bottom left, bottom right). The default colors are white, which will not modify the image's appearance. For non-white colors, this will multiply the pixel colors by the RGBA color specified. You probably won't need to use this function very often.
- static void GetImageInfo(const std::string& filename, uint32& rows, uint32& cols, uint32& bpp)
This method allows one to retrieve various properties about an image without loading it into memory. It will only succeed for PNG or JPG images. rows and cols return the number of rows and columns of pixels in the image (i.e. the image's height and width), while bpp returns the number of bits-per-pixel of the image. If there is a problem and this function fails to retrieve the correct information, it will throw an Exception.
- static bool LoadMultiImageFromElementSize(std::vector<StillImage>& images, const std::string& filename, const uint32 elem_width, const uint32 elem_height)
- static bool LoadMultiImageFromElementGrid(std::vector<StillImage>& images, const std::string& filename, const uint32 grid_rows, const uint32 grid_cols)
These two methods load a multi image and place each image element into a vector of StillImage objects, which is the images reference parameter. If this vector is not the size of the number of image elements that will be extracted by the multi image, it will be resized to the appropriate amount (so be careful about passing a vector which is too large -- some StillImage objects will be removed). If any entries in the images vector have a width or height that is zero, the zero dimension will be set to the number of pixels in the image element of that dimension (which of course can be reset to a different dimension after the load call completes if needed). In practice, usually you would either pass this method an empty StillImage vector and let the call set up all the images and their properties, or to pass it a vector of the same size as the number of image elements that will be loaded, and to initialize the properties (dimensions, static/non-static, vertex colors) of each StillImage in that vector prior to the LoadMultiImage... call.
In addition to the filename of the image to load, the user must specify either the grid layout (rows and columns) of image elements within the multi image, or the size (width and height in pixels) of each element contained in the multi image. The element grid/size must evenly divide into the multi image, otherwise the call will fail. For example, telling a multi image of size 20x40 that there are 3 rows and 4 columns of elements will fail (40 is not evenly divisible by 3). This is because image elements within a multi image must all have equal dimensions.
This function will return true if the multi image was loaded and the StillImage vector was constructed successfully. The size of the images vector upon return true will be exactly equal to the number of image elements that were contained within the multi image.
- static bool SaveMultiImage(const std::vector<StillImage*>& images, const std::string& filename, const uint32 grid_rows, const uint32 grid_cols)
This function allows one to save multiple images to a file, creating a multi image file. Each entry in the images vector argument must have the same dimensions, otherwise the function will refuse to save the image and abort. The size of the images vector must be at least grid_rows * grid_columns. It may be larger, but any entries past the position grid_rows * grid_columns - 1 will not be saved. grid_rows, grid_columns specify the number of rows and columns of the multi image to save. The function returns true if the images were successfully saved to a multi image file.
StillImage is the primary class of the image API. Quite simply, it represents a single, non-animated image that was constructed from loading an image file.
- bool Load(const std::string& filename);
- bool Load(const std::string& filename, float width, float height)
These functions load an image file (which should either be a PNG or JPG image) and construct the StillImage from that file data. The version which accepts a width and height argument additionally calls the SetDimensions(...) method prior to initiating the load call, and is meant as a convenience to the user (so they can avoid making a separate SetDimensions(...) call). The function returns true only if the image was successfully loaded. Note that this function will remove all image data regardless of whether or not the function succeeds, so use care not to accidentally remove image data that you wish to keep around.
- const std::string& GetFilename() const
This method will only return a valid (non-empty) string for single StillImage objects that have been loaded. Unloaded images or composite images do not have this member set, as they do not reference a single, valid image file's data.
- bool Save(const std::string& filename) const
Quite simply, this function allows the loaded image to be saved to a PNG or JPG file on hard disk. The filename argument should have either a ".png" or ".jpg" extension to indicate this, otherwise the function will not succeed. Saving of composite images is not yet supported, so you can expect this function to always fail (returns false) for StillImage objects with more than one element. Obviously, the StillImage needs to have at least one image element, otherwise it has no image data to save and will fail.
- void EnableGrayScale()
- void DisableGrayScale()
These methods convert a normal image to grayscale mode and back again. When an image is made grayscale, a new grayscale copy of the image's data is created in texture memory (unless a grayscale copy already exists in texture memory, in which case it will simply increment the reference count to the grayscale copy). The original image data is maintained in texture memory, so you are always guaranteed that while you have a copy of a grayscale image, the original is still contained in memory. Therefore, the EnableGrayScale() method is usually a fairly costly operation since it has to copy image data and convert each pixel to grayscale mode, but the DisableGrayScale() method is an extremely cheap operation, as it is really nothing more than changing the address of a pointer.
- void GetVertexColor(Color& c, uint8 index)
This will retrieve the color of a given vertex of the image. The index argument should be from 0-3, where the vertex indeces are: 0 = top left, 1 = top right, 2 = bottom left, 3 = bottom right.
- uint32 GetNumElements() const
This returns the number of elements in the image. An unloaded/uninitialized StillImage will return 0. A single loaded StillImage will return 1. A composite image will return a value greater than 1.
- const private_video::BaseImageElement* GetElement(uint32 index) const
This method allows one to retrieve an element of the image. BaseImageElement is a class used in the back end of the video engine to manage image objects. It is strongly advised that you do not attempt to make use of this function.
AnimatedImage objects are created through multiple frames, where each animation frame is a pair of a StillImage object and a time value to indicate how long the frame image should display for. All frame images must be of the same size. Animations may consist of any number of frames (including 1, although that is not truly an animation).
- bool LoadFromFrameSize(const std::string& filename, const std::vector<uint32>& timings, const uint32 frame_width, const uint32 frame_height, const uint32 trim = 0)
- bool LoadFromFrameGrid(const std::string& filename, const std::vector<uint32>& timings, const uint32 frame_rows, const uint32 frame_cols, const uint32 trim = 0)
These two methods are nearly the exact same as the LoadMultiImage calls on the ImageDescriptor, and for good reason. Because an animated image consists of multiple frames of the same size, it is best to store animations in a multi image file. The format and declaration is exactly the same as the LoadMultiImage calls, with a few difference. First, what we referred to as an "image element" in a multi image we now call a "frame image". Second, the timings vector is a new addition, and this vectors contains all the timing values (in milliseconds) for each frame that will be loaded. The additional trim argument tells us which, if any, frames in the multi image we do not wish to load. For example, if we have a multi image file that is 80x80 pixels and we only wish to load 6 frames that are 20x40 in size, the call will not add the final two frame images (in the last row, and the last and second to last columns) to the animation.
The timings vector should be at least the size of the number of frames in the multi image minus the trim so that each frame will have valid timing information saved with it. These functions return true only if the multi image was successfully loaded and the animation properly constructed.
- bool Save(const std::string& filename, const uint32 grid_rows = 0, const uint32 grid_cols = 0) const
This method saves the animation as a multi file image (as long as there are multiple frames). The filename argument should end in either ".png" or ".jpg" to specify the type of image file to save the frames in. The grid_rows and grid_cols arguments allow the user to specify the layout of frames inside the multi image if requested. If left at their default 0 values, the multi image will have one row and as many columns as there are frame images to save. The function returns true if the image was succesfully saved to a file on the hard disk.
- bool AddFrame(const std::string& frame, uint32 frame_time)
- bool AddFrame(const StillImage& frame, uint32 frame_time)
These methods append a single frame to the end of the animation. These methods can be used as alternatives to the Load functions to construct an animation, or to append to an existing animation. The first argument is either a loaded StillImage reference, or a filename to use to load a new StillImage. The second argument is the number of milliseconds that the frame should last for. The functions return true only when a new frame was successfully appended to the animation.
- void EnableGrayScale()
- void DisableGrayScale()
These methods invoke the StillImage method of the same name for each StillImage frame.
- void ResetAnimation()
This method does exactly what it sounds: it resets the animation so that the first frame is to be displayed, the frame timer is set back to zero, and the loop counter is set back to zero.
- uint32 GetNumFrames() const
This method returns the total number of frames that compose the animation.
- void Update()
This method will update the animation by the number of milliseconds that have transpired since the previous frame was drawn by the GameVideo class. It will also update the loop counter if the animation was specified to have a finite number of loops. When the number of loops requested is reached, further calls to this function will result in no operation until the ResetAnimation() method is called, or the number of loops to animate is changed.
- uint32 GetCurrentFrameIndex() const
- void SetFrameIndex(const uint32 index)
The GetCurrentFrameIndex() method returns the integer index of the frame in the animation. It will return zero if there are no frames in the animation. The SetFrameIndex will automatically set the animation to display the particular frame requested (as long as the index argument is valid) and set the frame timer counter to zero.
- StillImage* GetCurrentFrame() const
- StillImage* GetFrame(uint32 index) const
These methods retrieve a poitner to the image that represents either the current frame, or the frame index requested. If there are no frames or if the index argument is invalid, NULL will be returned.
- uint32 GetTimeProgress() const
- float GetPercentProgress() const
- void SetTimeProgress(uint32 time)
These methods allow one to determine at what stage the animation is in. GetTimeProgress() returns the value of the frame timer. GetPercentProgress() returns a floating point value from 0.0f to 1.0f, representing what percentage of the animation has completed for the current loop. SetTimeProgress sets the frame timer to the value specified.
- bool IsLoopsFinished() const
- void SetLoopsFinished(bool loops)
- void SetNumberLoops(int32 loops)
- void SetLoopCounter(int32 loops)
These methods deal with the looping mechanism. When IsLoopsFinished() returns true, this indicates that the animation has finished all of the loops it has requested to run, and that the animation will no longer update unless the user indicates for it to do otherwise. SetLoopsFinished() changes the value of this member and if the loops argument is false, will also set the loop counter back to zero. SetNumberLoops() indicates how many loops this animation should run for, where a negative value indicates that the animation should loop infinitely (this is the default). Finally, SetLoopCounter() sets the loop count member and may additionally set the loops finished member if the loop counter exceeds the number of loops to animate.
A composite image is an image that is created by piecing together multiple StillImage objects to form a larger, cohesive image. Each StillImage that is a part of the CompositeImage is given x and y offset coordinates that indicate where that particular image "element" should be drawn in relation to the others in the composite. Note that this concept is different from a multi image because multi images only exist as image files, where as composite images only exist in run-time execution. One use of composite images is for creating GUI windows, where we create an image of the window by placing the individual border images of the GUI skin around, and then fill the inner area with a background image and/or color. The image below shows an example of a composite image.
- void AddImage(const StillImage& img, float x_offset, float y_offset, float u1 = 0.0f, float v1 = 0.0f, float u2 = 1.0f, float v2 = 1.0f)
This method is one way to construct a composite StillImage. This uses a loaded StillImage argument (which itself may be a composite image) and adds each element of the argument image to the current StillImage at the x and y offsets specified by the 2nd and 3rd arguments (the offsets are a function of the coordinate system in which the image is intended to be used). The u1, v1, u2, v2 arguments indicate which portion of the image to use, and are almost always left at their default values.
- void ConstructCompositeImage(const std::vector<StillImage>& tiles, const std::vector<std::vector<uint32> >& indeces)
This method is a second and more specialized way to construct a composite StillImage. All StillImage objects in the tiles vector should have the same dimensions and, of course, reference valid image data that is loaded. The tiles vector must have at least one entry. The indeces argument is a 2D vector (where all rows are the same size) which contain several integers that index the tiles argument. What this allows us to do is to easily construct a composite image using multiple like-sized image elements without performing several AddImage(...) calls. For example, if the tiles vector contains an image that contains tiles for a wall's bottom, middle, and top sections, we can construct a wall of any size by setting the indeces argument to index all bottom tiles in the last row, all middle tiles in the middle rows, and all top tiles in the first row. The function prints out a warning message if it fails to construct the composite image.
This class is documented in the text API section. It is mentioned here because this class is strictly an image (it derives from the ImageDescriptor class) that contains rendered text.
This class is not yet available. It will provide for images that are created procedurally via OpenGL draw calls. This is currently achieved by a somewhat messy hack in the StillImage code.