Bitmap module
Bitmap loads BMP images to draw them with Draw or to make sprites with them, and it writes BMP files from your program. Each loaded image is a bitmap: an object with its size and its transparent color.

use Bitmap
Formats
- It reads uncompressed BMP files with 16, 24 or 32 bits per pixel (also those with color masks), stored top-down or bottom-up.
- It does not read BMP files with 1, 4 or 8 bits (palette-based ones), compressed ones, or other formats such as PNG or JPG:
Bitmap.loadthrowsERR_FILE_NOT_FOUND, with the message “Cannot load bitmap file”, even though the file exists. - When it loads it, the VM turns the image into 16 bits per pixel (RGB565), on Windows and on the board. Each color becomes the closest 16-bit one, and the image takes about
width × height × 2bytes. - It does not read the alpha channel. Transparency is a color: the one you give to
Bitmap.loador totcolor, black by default.
The IDE's image editor exports the project's images, when you build, to BMP files the VM reads.
Platforms
| Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 | |
|---|---|---|---|---|
| Where it loads from | The program's folder (Build/flash); with sd:, the sd folder next to it | The flash memory; with sd:, the SD card | The same | The same |
size() | Rounds each row up to a multiple of 4 bytes | width × height × 2 | The same | The same |
Functions
| Function | What it does | Returns |
|---|---|---|
load(file [, transparent]) | Loads a BMP image | A bitmap |
writeHeader(file, width, height, bpp) | Writes the header of a BMP to an open file | nil |
writeLine(file, colors) | Writes a row of pixels | nil |
new(a, b, c) | Does nothing in this version | nil |
If something fails, the Bitmap functions throw an exception, which you catch with try (see Errors and exceptions).
load
Bitmap.load(file [, transparent]) loads a BMP image and returns a bitmap to draw it or to make a sprite with it.
| Parameter | Type | Description |
|---|---|---|
file | string | The path of the image in the project, such as Images/logo.bmp |
transparent | integer | (Optional) The color that is not drawn, 0xRRGGBB. Default is 0x000000 (black) |
| Error | When |
|---|---|
ERR_FILE_NOT_FOUND | The file is not there, or it is not a BMP the VM can read (see Formats). |
ERR_OUT_OF_MEMORY | There is no memory for the image. |
ERR_TYPE_MISMATCH | file is not a string, or transparent is not an integer. |
ERR_INVALID_ARG_NUMBER | You do not pass it one or two arguments. |
use Bitmap
use Display
// Open the display
Display.open(320, 240)
// Load a bitmap
var image = Bitmap.load("Images/logo.bmp")
// Show bitmap information
println("Width: ", image.width())
println("Height: ", image.height())
println("Takes: ", image.size(), " bytes")
With another transparent color, for example magenta, the image's black is drawn:
use Bitmap
use Display
Display.open(320, 240)
// Load bitmap with magenta (0xFF00FF) as transparent color
var sprite = Bitmap.load("Images/character.bmp", 0xFF00FF)
// The magenta color won't be drawn, allowing the background to show through
Once loaded, you draw it with the Draw functions:
use Bitmap
use Display
Display.open(320, 240)
var draw = Display.draw
var background = Bitmap.load("Images/background.bmp")
var character = Bitmap.load("Images/hero.bmp", 0xFF00FF)
// The background at (0, 0), without a transparent color
draw.bitmapOpaque(0, 0, background)
// The character, centered
var x = (Display.width - character.width()) div 2
var y = (Display.height - character.height()) div 2
draw.bitmap(x, y, character)
Display.update()
The path is the image's path in the project. When you build, the IDE copies to the program's folder (Build/flash) the images that have Include in Build turned on, and Run writes them with the program to the board's flash memory (Flash the firmware and run). With sd: in front ("sd:photos/beach.bmp"), the image is read from the SD card; on Windows, from the sd folder next to Build/flash. More about paths in File.
The transparent color is compared once the image is already in 16 bits. With black, almost-black colors such as 0x050205, which are the same color in 16 bits, are transparent too.
writeHeader
Bitmap.writeHeader(file, width, height, bpp) writes the header of a 24-bit BMP to a file opened for binary writing. Then, write its rows with writeLine.
| Parameter | Type | Description |
|---|---|---|
file | file | A File file, opened with "wb" |
width, height | integer | The size of the image, greater than 0 |
bpp | integer | Bits per pixel: only 24 |
use Bitmap, File
var f = File.open("two.bmp", "wb")
Bitmap.writeHeader(f, 2, 1, 24) // 2 x 1 pixels
Bitmap.writeLine(f, [0xFF0000, 0x0000FF]) // red and blue
f.close()
- It writes the 54 bytes of the header of an uncompressed BMP, stored top-down: the first row you write is the top one.
- After it you have to write
heightrows ofwidthcolors each.
| Error | When |
|---|---|
ERR_INVALID_VALUE | bpp is not 24, or width or height are not greater than 0. |
ERR_TYPE_MISMATCH | file is not a file, or the sizes are not integers. |
ERR_FILE_WRITE_FAIL | The file is not open or cannot be written. |
ERR_INVALID_ARG_NUMBER | You do not pass it four arguments. |
writeLine
Bitmap.writeLine(file, colors) writes a row of pixels: a list of colors 0xRRGGBB, from left to right.
use Bitmap, File
var f = File.open("stripes.bmp", "wb")
Bitmap.writeHeader(f, 3, 2, 24)
Bitmap.writeLine(f, [0xFF0000, 0x00FF00, 0x0000FF]) // the top row
Bitmap.writeLine(f, [0xFFFFFF, 0x000000, 0x808080]) // the bottom one
f.close()
- It adds the padding the BMP format needs: you do not have to do anything.
- It does not check the width of the header: it writes as many pixels as there are colors in the list. Always pass it
widthcolors. - With an empty list it writes nothing.
| Error | When |
|---|---|
ERR_TYPE_MISMATCH | file is not a file, colors is not a list, or an element is not an integer. |
ERR_FILE_WRITE_FAIL | The file is not open or cannot be written. |
ERR_OUT_OF_MEMORY | There is no memory to prepare the row. |
ERR_INVALID_ARG_NUMBER | You do not pass it two arguments. |
new
Bitmap.new(a, b, c) is in the module, but in this version it creates nothing: with three strings it returns nil, and with other arguments it throws ERR_INVALID_ARG_NUMBER or ERR_TYPE_MISMATCH. Do not use it. To make an image from the program, write it with writeHeader and writeLine and load it with load.
Objects it returns
Bitmap.load returns a bitmap. Its methods are called with parentheses:
| Method | What it does | Returns |
|---|---|---|
width() | The width in pixels | An integer |
height() | The height in pixels | An integer |
bpp() | The bits per pixel: always 16, because the VM converts the image to RGB565 when loading it | An integer |
size() | The bytes the image takes in memory | An integer |
tcolor(color) | Changes the transparent color | nil |
With one argument too many, the first four throw ERR_INVALID_ARG_NUMBER.
size
The bytes the image takes: width × height × 2, because each pixel is stored in 16 bits. On Windows, each row is rounded up to a multiple of 4 bytes, so an image with an odd width takes a little more (a 15 × 10 one takes 320 bytes).
Bitmaps take up significant memory, especially on ESP32 devices with limited resources. Keep in mind:
- A 100x100 pixel bitmap at 16 bits takes approximately 20 KB
- A 320x240 pixel bitmap at 16 bits takes approximately 150 KB
Use memFree() to monitor available memory before loading large images. How much memory each board has is in Memory and limits.
use Bitmap
// Check memory before loading
println("Free memory: ", memFree(), " bytes")
var image = Bitmap.load("Images/large.bmp")
println("Memory after loading: ", memFree(), " bytes")
println("Bitmap size: ", image.size(), " bytes")
tcolor
bitmap.tcolor(color) changes the bitmap's transparent color: from then on, that color is not drawn, and the previous one is.
use Bitmap
var ship = Bitmap.load("Images/ship.bmp") // transparent: black
ship.tcolor(0xFF00FF) // now, magenta
- It counts in everything that draws that image, also in the sprites made with it with
Sprite.new. - There is no way to read it:
tcolorneeds the color. - Without an argument it throws
ERR_INVALID_ARG_NUMBER, and with something that is not an integer,ERR_TYPE_MISMATCH.
Complete example
The program writes a 64 × 32 BMP (a gradient with a black frame), loads it and draws it in two ways: rotating, without the frame, because black is transparent, and with bitmapOpaque, with the frame.
use Display, Bitmap, File
// 1. Write a 64 x 32 BMP: a gradient with a black frame
var f = File.open("gradient.bmp", "wb")
Bitmap.writeHeader(f, 64, 32, 24)
for (var y = 0; y < 32; y++)
var row = []
for (var x = 0; x < 64; x++)
if (x == 0 or y == 0 or x == 63 or y == 31) then
row.append(0x000000) // the frame, black
else
row.append(rgb(x * 4, 0, 255 - y * 8))
end
end
Bitmap.writeLine(f, row)
end
f.close()
// 2. Load it and draw it
Display.open(320, 240)
Display.fps = 30
var draw = Display.draw
var img = Bitmap.load("gradient.bmp") // black is transparent
println(img.width(), " x ", img.height(), ": ", img.size(), " bytes") // 64 x 32: 4096 bytes
var x0 = (Display.width - img.width()) div 2
for (var i = 0; i < 90; i++) // 3 seconds
draw.bitmap(x0, 40, img, i * 4) // rotating, without the frame
draw.bitmapOpaque(x0, 150, img) // with the black frame
Display.update()
end
Display.close()
Pitfalls and known issues
- Black is transparent unless you say otherwise. An image with pure black that has to show comes out with holes. Load it with another transparent color (
Bitmap.load(file, 0xFF00FF)) or draw it withdraw.bitmapOpaque. The same happens with the sprites of the sprite editor without Remove background. - An 8-bit BMP gives “Cannot load bitmap file”, even though the file is there. Save it in 24 bits, or let the image editor export it.
Bitmap.newdoes nothing in this version (see new).
All the known issues of JARU, together, are in Known issues.