Skip to main content

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.

JARU Bitmap module screenshot for loading images and drawing them in graphical projects

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.load throws ERR_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 × 2 bytes.
  • It does not read the alpha channel. Transparency is a color: the one you give to Bitmap.load or to tcolor, black by default.

The IDE's image editor exports the project's images, when you build, to BMP files the VM reads.

Platforms​

WindowsESP32 / ESP32-S3ESP32-P4ESP32-S31
Where it loads fromThe program's folder (Build/flash); with sd:, the sd folder next to itThe flash memory; with sd:, the SD cardThe sameThe same
size()Rounds each row up to a multiple of 4 byteswidth × height × 2The sameThe same

Functions​

FunctionWhat it doesReturns
load(file [, transparent])Loads a BMP imageA bitmap
writeHeader(file, width, height, bpp)Writes the header of a BMP to an open filenil
writeLine(file, colors)Writes a row of pixelsnil
new(a, b, c)Does nothing in this versionnil

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.

ParameterTypeDescription
filestringThe path of the image in the project, such as Images/logo.bmp
transparentinteger(Optional) The color that is not drawn, 0xRRGGBB. Default is 0x000000 (black)
ErrorWhen
ERR_FILE_NOT_FOUNDThe file is not there, or it is not a BMP the VM can read (see Formats).
ERR_OUT_OF_MEMORYThere is no memory for the image.
ERR_TYPE_MISMATCHfile is not a string, or transparent is not an integer.
ERR_INVALID_ARG_NUMBERYou 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()
Where the files are

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.

Transparency is compared in 16 bits

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.

ParameterTypeDescription
filefileA File file, opened with "wb"
width, heightintegerThe size of the image, greater than 0
bppintegerBits 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 height rows of width colors each.
ErrorWhen
ERR_INVALID_VALUEbpp is not 24, or width or height are not greater than 0.
ERR_TYPE_MISMATCHfile is not a file, or the sizes are not integers.
ERR_FILE_WRITE_FAILThe file is not open or cannot be written.
ERR_INVALID_ARG_NUMBERYou 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 width colors.
  • With an empty list it writes nothing.
ErrorWhen
ERR_TYPE_MISMATCHfile is not a file, colors is not a list, or an element is not an integer.
ERR_FILE_WRITE_FAILThe file is not open or cannot be written.
ERR_OUT_OF_MEMORYThere is no memory to prepare the row.
ERR_INVALID_ARG_NUMBERYou 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:

MethodWhat it doesReturns
width()The width in pixelsAn integer
height()The height in pixelsAn integer
bpp()The bits per pixel: always 16, because the VM converts the image to RGB565 when loading itAn integer
size()The bytes the image takes in memoryAn integer
tcolor(color)Changes the transparent colornil

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).

Memory usage

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: tcolor needs 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 with draw.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.new does nothing in this version (see new).

All the known issues of JARU, together, are in Known issues.