Sometimes, the design of an app dictates the need for “image swapping” in which the developer displays an image at a given location and then, after some time or some action, swaps that image for another. Here’s a theoretical example:
local myImage = display.newImageRect( "image1.png", 64, 64 ) -- After some time or upon some event myImage:swapImage( "image2.png" )
However, this is not how Solar2D works because :swapImage() is not a
local myImage = display.newImageRect( "image1.png", 64, 64 ) -- After some time or upon some event display.remove( myImage ) myImage = display.newImageRect( "image2.png", 64, 64 )
In this theoretical scenario, by destroying image1.png with the display.remove() call, you’re probably not planning on using it again soon. The advantage to this method is that it minimizes texture memory since only one of the two images is loaded at once. However, if you want to get the original image back, you’re caught in this cycle of image loading and unloading over and over. In itself, this is an inefficient process that can impact performance. Thus, if you need to swap images more frequently, you should explore other techniques. Let’s look at a few options:
If you have two images, you can simply load them both, add them to a display group, and reference the images as parameters of the group. In this example, we load two images, redBall and blueBall. We then position them at the same location, making one visible and the other not.
-- Create a basic display group local imageGroup = display.newGroup() -- Create a red ball inside the group local redBall = display.newImageRect( imageGroup, "red-ball.png", 128, 128 ) redBall.x = display.contentCenterX redBall.y = display.contentCenterY -- Create a blue ball inside the same group local blueBall = display.newImageRect( imageGroup, "blue-ball.png", 128, 128 ) blueBall.x = display.contentCenterX blueBall.y = display.contentCenterY -- Make the blue ball invisible blueBall.isVisible = false
Now let’s focus on the swapping code:
for i = 1,imageGroup.numChildren do
if ( imageGroup[i].isVisible == false ) then
imageGroup[i].isVisible = true
else
imageGroup[i].isVisible = false
end
end
Simply enough, this code loops through the children of imageGroup which are obviously redBall and blueBall. If the child is invisible, it’s toggled back to visible and
You can also take advantage of the graphics “fill” methods to swap images:
local image1 = { type="image", filename="red-ball.png" }
local image2 = { type="image", filename="blue-ball.png" }
local ball = display.newRect( display.contentCenterX, display.contentCenterY, 128, 128 )
ball.fill = image1
ball.isShowing = "image1"
This method eliminates the need for the display group — we just create a base display object (in this case a rectangle the size of the images) and fill it with the image1 paint of red-ball.pngisShowing, to be used in the swapping code as follows:
if ( ball.isShowing == "image1" ) then
ball.fill = image2
ball.isShowing = "image2"
else
ball.fill = image1
ball.isShowing = "image1"
end
Sometimes you’ll want to swap more than two images. In this case, we can go back to the display group model, load all of the images into an array, and access them via their index number. Here, we iterate through the imageCache table to create the display objects, storing them in the balls table as we go along, and make each one invisible. Then, we make the first one visible again.
local imageGroup = display.newGroup()
local imageCache = {
"red-ball.png",
"blue-ball.png",
"green-ball.png",
"yellow-ball.png",
"purple-ball.png"
}
local balls = {}
for i = 1,#imageCache do
balls[i] = display.newImageRect( imageGroup, imageCache[i], 128, 128 )
balls[i].x = display.contentCenterX
balls[i].y = display.contentCenterY
balls[i].isVisible = false
end
imageGroup.isShowing = 1
balls[imageGroup.isShowing].isVisible = true
Now, to manage the swapping, we can execute the following code, setting the local variable showingImage to the index of the image we want to show. In this case, the value of 4 will show the yellow-ball.pngimageCache array.
local showingImage = 4
for i = 1,imageGroup.numChildren do
if ( i == showingImage ) then
imageGroup[i].isVisible = true
else
imageGroup[i].isVisible = false
end
end
While the above methods are all perfectly valid, using individual image files is not the best use of memory and loading time. Also, creating/storing a large amount of individual files in your project directory can get chaotic. This is why we encourage the use of image sheets in which you load a single image sheet/atlas containing all of the individual images and then display a specific frame from it. Image sheets take a little more initial setup but the the benefits are well worth it.

Converting the above code to an image sheet may look like this:
local imageGroup = display.newGroup()
local sheetOptions = {
width = 128,
height = 128,
numFrames = 5,
sheetContentWidth = 640,
sheetContentHeight = 128
}
local ballSheet = graphics.newImageSheet( "ballSheet.png", sheetOptions )
local balls = {}
for i = 1,sheetOptions.numFrames do
balls[i] = display.newImageRect( imageGroup, ballSheet, i, 128, 128 )
balls[i].x = display.contentCenterX
balls[i].y = display.contentCenterY
balls[i].isVisible = false
end
imageGroup.isShowing = 2
balls[imageGroup.isShowing].isVisible = true
From a code perspective, this is not much different from the array method above, other than the efficiencies gained from using image sheets — but image sheets are a gateway to sprites, an excellent way swap images!
Solar2D includes a comprehensive sprite engine. While the term “sprite” may seem to indicate only an animated object in a game, you should consider it as simply an ordered sequence of images which can be used for multiple purposes, including swapping images.
Let’s look at a
local sheetOptions = {
width = 128,
height = 128,
numFrames = 5,
sheetContentWidth = 640,
sheetContentHeight = 128
}
local ballSheet = graphics.newImageSheet( "ballSheet.png", sheetOptions )
local ball = display.newSprite( ballSheet, { name="balls", start=1, count=sheetOptions.numFrames } )
ball:setFrame( 2 )
ball.x = display.contentCenterX
ball.y = display.contentCenterY
Like the version using graphics fills, we no longer need the display group (imageGroup) since the sprite is, by definition, a
As you can see, there are various approaches and methods to the “swap images” concept and it depends on your needs and design specifics as to which method is most suitable. From individual images to fills to sprites, Solar2D has you covered!