There are many image editing programs that can be used to create and modify bmp assets for use with CircuitPython. If you have access to and are familiar with Adobe Photoshop you can use that. If you are into making games and/or pixel art specifically, there is Aespire which offers many great features that make it very easy to work with pixel art indexed bmp files. It's not free, but it is a nice utility for making and editing graphics.

For this guide, we'll use a free alternative: The GNU Image Manipulation Program or GIMP. This works on Linux, Mac, and Windows PCs, and it's free to use and open sourced. We will use this to edit the image so that we can make the white background appear as transparent in the game. If you don't already have GIMP installed, use the link above and follow the installation instructions for your OS.

We'll starting from this bmp file:

Open this file in GIMP by using File -> Open or Ctrl+O then navigate to the directory where you've download the bmp file or project zip.

At real size 100% zoom the image will likely appear very small in GIMP. It's very helpful to zoom in with Ctrl+MouseWheel or the plus and minus buttons.

Transparency within Indexed BMPs

The bmp file format does not directly support transparency. Instead we are able to add code in CircuitPython that declares certain colors to appear as transparent instead of showing normally. You can think of it sort of like a green screen effect.

In the existing image, white is the color in the background, but we don't want to set white as transparent because Blinka and the Robot's eye's contain white so they would look wrong. Sometimes bright pink is used as the background color in sprite sheets for this purpose. But we have the heart graphic already using pink though, so we don't want to use pink either. We'll use green instead to follow with the green screen analogy. But really it could be any color that is not already used in the graphic.

Converting to RGB Mode

Before we can modify the image we need to change it back to RGB color mode. Right now the image is in indexed color mode which means it only has access to a small set of colors. 

Here are the currently available colors for this image:

 

Since the green we want to use is not one of these colors it won't work, by default GIMP would choose the closest available color. But instead we want to add green to the available colors and use it. So we need to switch back to RGB color mode for now. 

To do that click Image -> Mode -> RGB

Filling in the Background

Now you can set your foreground color and use the paint bucket tool to fill in the background parts of the sprite image with pure green #00FF00.

In the image above, I've added small paint bucket icons in the two spots I clicked with the paint bucket tool to fill in the background green. 

Converting back to Indexed Color Mode

Once the background is filled in we need to switch the image back to indexed color mode so that CircuitPython can use it. 

Click: Image -> Mode -> Indexed

Leave the default settings in the Convert Image to Index Colors configuration dialog. It should look like this.

 

Click the Convert button.

Re-arrange the Color Index

In the CircuitPython code we will need to specify a color index that is going to be shown as transparent. We can see the colors and their indexes inside of gimp in the Rearrange Colormap window. To access it:

Click: Color -> Map -> Rearrange Colormap

In my case, the newly added pure green transparency placeholder is at index 14. It may be different for you, if so that's no problem.  

We could make note of this index and use it in the CircuitPython code. But then if we ever change the image, we would need to make sure to go change the code if the index changed again due to us adding or removing colors. 

Instead let's move the transparency placeholder to index 0. Then we can always make index 0 transparent in the code and as long as we always follow this rule when preparing bmp files, we can use the same code everywhere and it will work. 

So click and drag your placeholder color over to the very first position in the list. The index still shows the wrong number but that is okay for now. 

Once your transparency placeholder is in the first position, click the Ok button.

At this point you are ready to export your image as a bmp file.

But I know it is weird the indexes were wrong on the previous screen... If you like to double check on things you can click Color -> Map -> Rearrange Colormap again to open up a new window and see that the indexes are updated to the proper numbers. It would look like this now:

Saving the Image as a BMP File

Now the image is ready to be exported out of gimp as a bitmap file that is ready to show with CircuitPython displayio.

Click: File -> Export As..

or Press: Ctrl+Shift+E

You can choose any name for your file, but it must end with .bmp because this will be the format that gimp saves the file with. I am going to use the name castle_sprite_sheet_edited.bmp. You can choose a different name if you like, if you do make sure to edit the rest of the sample code to use your own bmp filename. 

At a minimum, you need to export one copy to your CIRCUITPY drive, but you can also save a copy to your local file system if you like. For this example we are going to save it into the tilegame_assets directory.

Once you've got the filename and location selected click on the Export button at the bottom.

Leave the default values in he export dialog. 

Click the Export button.

 

Now we can change the sprite image loading code to use the new image and set the color at index 0 to be transparent.

Download: file
sprite_sheet, palette = adafruit_imageload.load("tilegame_assets/castle_sprite_sheet_edited.bmp",
                                                bitmap=displayio.Bitmap,
                                                palette=displayio.Palette)

# Make the color at index 0 show as transparent
palette.make_transparent(0)
import board
import displayio
import adafruit_imageload
import ugame

display = board.DISPLAY
player_loc = {"x": 4, "y": 3}

# Load the sprite sheet (bitmap)
sprite_sheet, palette = adafruit_imageload.load(
    "tilegame_assets/castle_sprite_sheet_edited.bmp",
    bitmap=displayio.Bitmap,
    palette=displayio.Palette,
)
# make the color at 0 index transparent.
palette.make_transparent(0)

# Create the sprite TileGrid
sprite = displayio.TileGrid(
    sprite_sheet,
    pixel_shader=palette,
    width=1,
    height=1,
    tile_width=16,
    tile_height=16,
    default_tile=0,
)

# Create the castle TileGrid
castle = displayio.TileGrid(
    sprite_sheet,
    pixel_shader=palette,
    width=10,
    height=8,
    tile_width=16,
    tile_height=16,
)

# Create a Group to hold the sprite and add it
sprite_group = displayio.Group()
sprite_group.append(sprite)

# Create a Group to hold the castle and add it
castle_group = displayio.Group(scale=1)
castle_group.append(castle)

# Create a Group to hold the sprite and castle
group = displayio.Group()

# Add the sprite and castle to the group
group.append(castle_group)
group.append(sprite_group)

# Castle tile assignments
# corners
castle[0, 0] = 3  # upper left
castle[9, 0] = 5  # upper right
castle[0, 7] = 9  # lower left
castle[9, 7] = 11  # lower right
# top / bottom walls
for x in range(1, 9):
    castle[x, 0] = 4  # top
    castle[x, 7] = 10  # bottom
# left/ right walls
for y in range(1, 7):
    castle[0, y] = 6  # left
    castle[9, y] = 8  # right
# floor
for x in range(1, 9):
    for y in range(1, 7):
        castle[x, y] = 7  # floor

# put the sprite somewhere in the castle
sprite.x = 16 * player_loc["x"]
sprite.y = 16 * player_loc["y"]

# Add the Group to the Display
display.show(group)

prev_btn_vals = ugame.buttons.get_pressed()

while True:
    cur_btn_vals = ugame.buttons.get_pressed()
    if not prev_btn_vals & ugame.K_UP and cur_btn_vals & ugame.K_UP:
        player_loc["y"] = max(1, player_loc["y"] - 1)
    if not prev_btn_vals & ugame.K_DOWN and cur_btn_vals & ugame.K_DOWN:
        player_loc["y"] = min(6, player_loc["y"] + 1)

    if not prev_btn_vals & ugame.K_RIGHT and cur_btn_vals & ugame.K_RIGHT:
        player_loc["x"] = min(8, player_loc["x"] + 1)
    if not prev_btn_vals & ugame.K_LEFT and cur_btn_vals & ugame.K_LEFT:
        player_loc["x"] = max(1, player_loc["x"] - 1)

    # update the the player sprite position
    sprite.x = 16 * player_loc["x"]
    sprite.y = 16 * player_loc["y"]

    # update the previous values
    prev_btn_vals = cur_btn_vals

This guide was first published on Jun 10, 2020. It was last updated on Jun 10, 2020.

This page (Indexed BMP Graphics) was last updated on Nov 06, 2020.