Adafruit GFX Library: Comprehensive Guide for Arduino Graphics Development

Adafruit GFX Library: Comprehensive Guide for Arduino Graphics Development

Overview

The Adafruit_GFX library provides a common syntax and set of graphical functions for all LCD and OLED displays on the Arduino platform. This makes it easy to adapt example programs between different display types, and any new features, performance improvements, and bug fixes are immediately applied to our full color display support.

The Adafruit_GFX library can be installed via the Arduino Library Manager... this is the preferred method. In the Arduino IDE "Tools" menu, select "Manage Libraries..."

Enter "gfx" in the search bar to quickly find it:

Also search and install the Adafruit_BusIO library (or newer Arduino IDE versions automatically install this dependency).

The Adafruit_GFX library always works with additional libraries specific to each display driver type - for example, the 2.8" ST7735 color LCD requires installation of Adafruit_GFX, Adafruit_BusIO, and Adafruit_ST7735 libraries. Currently supported libraries include:

  • RGBmatrixPanel, for our 16x32 and 32x32 RGB LED matrix panels.
  • Adafruit_TFTLCD, for our 2.8" Arduino touch shield and TFT touch screens.
  • Adafruit_HX8340B, for our 2.2" TFT with microSD card slot.
  • Adafruit_ST7735, for our 2.2" TFT with microSD card slot.
  • Adafruit_PCD8544, for the Nokia 5110/3310 monochrome LCD.
  • Adafruit-Graphic-VFD-Display-Library, for our 128x64 graphic VFD (Vacuum Fluorescent Display).

Adafruit-SSD1331-OLED-Driver-Library compatible with Arduino 0.96" 16-bit color OLED w/ microSD card slot.

  • Adafruit_SSD1306, for monochrome 128x64 and 128x32 OLED displays.

These libraries are written in C++ for Arduino, but can be easily ported to any microcontroller by rewriting the low-level pin access functions.

Coordinate System and Units

Pixels - the building blocks of digital images - are located by their horizontal (X) and vertical (Y) coordinates. The coordinate system places the origin (0,0) at the top-left corner, with positive X increasing to the right and positive Y increasing downward. This is opposite to the standard Cartesian coordinate system used in mathematics, but has become established practice in many computer graphics systems (dating back to raster scan CRT graphics that worked top-down). One of four rotation settings can also be applied to use a "portrait" layout instead of a "landscape" format, or if physical mounting orientation restricts the attachment's display direction, where which corner represents the top-left.

Unlike the standard Cartesian coordinate system, points here have dimensions - their width and height are always a whole number of pixels.

Coordinates are always expressed in pixel units; there is no implicit scale for real-world measurements like millimeters or inches, and the size of displayed graphics will be a function of the specific display's dot pitch or pixel density. If your goal is real dimensions, you need to scale your coordinates accordingly. Dot pitch is usually found in the display's datasheet, or by measuring the screen width and dividing the pixel count by this measurement.

For color displays, colors are represented as unsigned 16-bit values. Some displays may actually have more or fewer, but the library operates with 16-bit values... these are easy to use with Arduino and provide consistent data types for all different displays. Primary color components are all "packed" into a single 16-bit variable, where the highest 5 bits convey red, the middle 6 bits green, and the lowest 5 bits blue. The extra space is allocated to green since our eyes are most sensitive to green light. Science!

For the most common primary and secondary colors, we have this useful list that can be included in your own code. Of course, you can choose any of the 65,536 different colors, but the following basic list is probably the simplest start:

// Color definitions
#define BLACK    0x0000
#define BLUE     0x001F
#define RED      0xF800
#define GREEN    0x07E0
#define CYAN     0x07FF
#define MAGENTA  0xF81F
#define YELLOW   0xFFE0 
#define WHITE    0xFFFF

For monochrome displays, colors are always simply specified as 1 (set) or 0 (clear). The semantics of set/clear are specific to the display type: for example, "set" pixels are lit on an OLED display, while "set" pixels are typically dark on a reflective LCD display. There may be exceptions, but generally you can expect 0 (clear) to represent the default background state of a newly initialized display, regardless of the result.

Basic Graphics

Each display library for a specific display has its own constructor and initialization functions. These are documented in the tutorials for each display type, or usually in the specific library header file. The remainder of this tutorial will introduce generic graphics functions that are not dependent on the display type.

The following function descriptions are just prototypes - assume the display object is declared and initialized as needed by the device-specific library. Check the example code for each library to see how it is used in practice. For example, where we show print(1234.56), your actual code would put the object name before it, such as it might read as screen.print(1234.56) (if you have declared the display object name as: screen).

  • Draw Pixel

First is the most basic pixel drawer. You can call it and specify parameters: X Y coordinates and a color, which will generate a point on the screen:

void drawPixel(uint16_t x, uint16_t y, uint16_t color);

  • Draw Line

You can also draw a line, specifying the start and end points and color:

void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color);

For horizontal or vertical lines, there are optimized line drawing functions that avoid angle calculations:

void drawFastVLine(uint16_t x0, uint16_t y0, uint16_t length, uint16_t color);
void drawFastHLine(uint8_t x0, uint8_t y0, uint8_t length, uint16_t color);

  • Draw Rectangle

Next, you can draw and fill rectangles and squares using the following program. Each can take the X, Y pair of the top-left corner of the rectangle, width, and height (in pixels) and color. drawRect() only renders the outline of the rectangle (the frame) - the interior is unaffected - while fillRect() fills the entire area with the given color:

void drawRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t color);
void fillRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t color);

To create a solid line rectangle with a contrasting outline, first use fillRect(), then use drawRect() on top of it.

  • Draw Circle

Similarly, for circles, you can draw outlines and fill them. Each function takes an X, Y pair as the center point, radius (in pixels), and color:

void drawCircle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);
void fillCircle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);

  • Draw Rounded Rectangle

For rounded rectangles, both drawing and filling functions are available. Each rectangle starts with X, Y, width, and height (like a regular rectangle), then a corner radius (in pixels), and finally a color value:

void drawRoundRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t radius, uint16_t color);
void fillRoundRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t radius, uint16_t color);

There's also an extra trick here: because the circle drawing functions always draw relative to a central pixel, the final circle diameter will always be an odd number of pixels. If you need a uniformly sized circle (which will place the center point between pixels), you can use a rounded rectangle function: pass the same width and height (both even), and a corner radius exactly half of that value.

  • Draw Triangle

For triangles, there are also drawing and filling functions. Each function requires seven parameters: the X, Y coordinates of the three corners defining the triangle, followed by the color:

void drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);

  • Output Characters and Text

There are two basic string drawing processes for outputting text. The first is for a single character. You can place this character anywhere and use any color. Only one font (to save space) is available, it should be 5x8 pixels, but you can pass an optional size parameter that scales the font (for example, size=2 will render text as 10x16 pixels per character). It is blocky, but having only one font helps keep the program code footprint small.

void drawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg, uint8_t size);

Text is very flexible, but the operation is slightly different. Text size, color, and position are not part of a single process, but are set in separate functions, then using the print() function - this is simple and provides all the string and numeric formatting capabilities of the familiar Serial.print() function!

void setCursor(uint16_t x0, uint16_t y0);
void setTextColor(uint16_t color);
void setTextColor(uint16_t color, uint16_t backgroundcolor);
void setTextSize(uint8_t size);
void setTextWrap(boolean w);

Starting with setCursor(x, y), it will place the top-left corner of the text at any location you need. Initially set it to (0,0) (the top-left corner of the screen). Then use setTextColor(color) to set the text color - white by default. Text is usually drawn in "clear" form - the open parts of each character show the original background content, but if you want the text to obscure what's underneath, you can specify the background color as an optional second parameter to setTextColor(). Finally, setTextSize(size) multiplies the text scale by the given integer factor. Below, you can see the scale factors of 1 (default), 2, and 3. At larger sizes, it looks blocky, because we only provide this library with a single simple font to save space.

Note that custom fonts do not support text background color. For these, you need to determine the text area and explicitly draw a filled rectangle before drawing the text.

After setting everything up, you can use print() or println() - just like you use serial print! For example, to print a string, use print("Hello world") - this is the first line in the image above. You can also use print() for numbers and variables - the second line is the output of print(1234.56), and the third line is print(0xDEADBEEF, HEX).

By default, long text that exceeds a line is set to automatically "wrap" to the leftmost column. To disable this behavior (so text runs from the right side of the display - useful for scrolling marquee effects), use setTextWrap(false). Use setTextWrap(true) to restore normal "wrapping" behavior.

See the "Using Fonts" page for additional text features in the latest GFX library.

  • Display Bitmaps

You can draw small monochrome (single-color) bitmaps suitable for sprites and other mini animations or icons:

void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);

This sends a continuous block of bit data to the display, where each "1" bit sets the corresponding pixel to the color specified, and skips each "0" bit. x, y is the top-left corner of the bitmap, w, h is the width and height in pixels.

The bitmap data must be located in program memory using the PROGMEM directive. This is a more advanced function, and is recommended for beginners to use later. For an introduction to PROGMEM usage, see the Arduino tutorial.

Some tools can be used to generate bitmap data.

  • Clear or Fill Screen

The fillScreen() function sets the entire display to the given color, removing all existing content on the display:

void fillScreen(uint16_t color);

  1. Rotate Display

You can also rotate your drawings. Note that this does not rotate already drawn content, but changes the coordinate system for any new drawing. This is very convenient if you have to mount your board or landscape display or upside down to fit a particular case. In most cases, this only needs to be done once in the setup() function.

We can only rotate 0, 90, 180, or 270 degrees - other angles are impossible on hardware and would be too computationally intensive for Arduino in software.

void setRotation(uint8_t rotation);

The rotation parameter can be 0, 1, 2, or 3. For displays that are shields for Arduino, rotation value 0 sets the display to portrait (tall) mode, with the USB port at the top right. Rotation value 2 is also portrait mode, with the USB port at the bottom left. Rotation 1 is landscape mode, with the USB port at the bottom right, and rotation 3 is also landscape mode, but with the USB port at the top left.

For other types of displays, try all four directions to figure out how they end up rotating, as the alignment will vary depending on each display. Generally, rotation is counter-clockwise.

When rotating, the origin (0,0) changes - the idea is that it should be arranged at the top-left corner of the display, so that other graphics functions have consistent meaning (and match the descriptions of all the functions above).

If you need to reference the screen size (which changes between portrait and landscape modes), use width() and height().

uint16_t width(); 
uint16_t height();

Each returns the size of the axis (in pixels), adjusted according to the current rotation setting of the display.

  1. Using Fonts

The latest version of the Adafruit GFX library provides the ability to use alternative fonts, in addition to the built-in standard fixed-size and spacing font. Several alternative fonts are included, and new fonts can also be added.

The included fonts come from the GNU FreeFont project. There are three types: "Serif" (resembling Times New Roman), "Sans" (resembling Helvetica or Arial), and "Mono" (resembling Courier). Each has several styles (bold, italic, etc.) and sizes available. The included fonts are bitmap format, not scalable vector, because it runs on a small microcontroller.

In the "Fonts" folder of Adafruit_GFX, the files included (as of the time of writing) are as follows:

In the "Fonts" folder of Adafruit_GFX, the files included (as of the time of writing) are as follows:

FreeMono12pt7b.h		FreeSansBoldOblique12pt7b.h
FreeMono18pt7b.h		FreeSansBoldOblique18pt7b.h
FreeMono24pt7b.h		FreeSansBoldOblique24pt7b.h
FreeMono9pt7b.h			FreeSansBoldOblique9pt7b.h
FreeMonoBold12pt7b.h		FreeSansOblique12pt7b.h
FreeMonoBold18pt7b.h		FreeSansOblique18pt7b.h
FreeMonoBold24pt7b.h		FreeSansOblique24pt7b.h
FreeMonoBold9pt7b.h		FreeSansOblique9pt7b.h
FreeMonoBoldOblique12pt7b.h	FreeSerif12pt7b.h
FreeMonoBoldOblique18pt7b.h	FreeSerif18pt7b.h
FreeMonoBoldOblique24pt7b.h	FreeSerif24pt7b.h
FreeMonoBoldOblique9pt7b.h	FreeSerif9pt7b.h
FreeMonoOblique12pt7b.h		FreeSerifBold12pt7b.h
FreeMonoOblique18pt7b.h		FreeSerifBold18pt7b.h
FreeMonoOblique24pt7b.h		FreeSerifBold24pt7b.h
FreeMonoOblique9pt7b.h		FreeSerifBold9pt7b.h
FreeSans12pt7b.h		FreeSerifBoldItalic12pt7b.h
FreeSans18pt7b.h		FreeSerifBoldItalic18pt7b.h
FreeSans24pt7b.h		FreeSerifBoldItalic24pt7b.h
FreeSans9pt7b.h			FreeSerifBoldItalic9pt7b.h
FreeSansBold12pt7b.h		FreeSerifItalic12pt7b.h
FreeSansBold18pt7b.h		FreeSerifItalic18pt7b.h
FreeSansBold24pt7b.h		FreeSerifItalic24pt7b.h
FreeSansBold9pt7b.h		FreeSerifItalic9pt7b.h

Each filename starts with a prefix (e.g., "FreeMono", "FreeSerif", etc.), followed by style ("bold", "oblique", "normal", etc.), point font size (currently 9, 12, 18, and 24 point sizes are available), and "7b", indicating that these files contain 7-bit characters (ASCII codes from " " to "~"); 8-bit fonts (supporting symbols and/or international characters) are not yet available, but may be added later.

  • Using GFX Fonts in Arduino Example Programs

After #including, indicate the Adafruit_GFX and device-specific libraries, and include the font files you plan to use in the program. For example:

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <Fonts/FreeMonoBoldOblique12pt7b.h>
#include <Fonts/FreeSerif9pt7b.h>

Each font occupies some program storage space; larger fonts usually require more space. This is a limited resource (approximately 32K for font data and all example code on an Arduino Uno), so careful selection is required. If the font is too large, the code will refuse to compile (or in some edge cases, it may compile but not upload to the development board). If this happens, use fewer or smaller fonts, or use the standard built-in font.

In these .h files, there are several data structures, including a main font structure, which usually has the same name as the font file (without the .h). To select a font for subsequent graphics operations, use the setFont() function, passing the address of the structure, for example:

tft.setFont(&FreeMonoBoldOblique12pt7b);

Subsequent calls to tft.print() will now use this font. Most other properties (color, size, etc.) that were previously used with the built-in font are similar here.

To return to the standard fixed-size font, call setFont(), passing NULL or no arguments:

tft.setFont();

Some text properties behave slightly differently with these new fonts. To avoid breaking compatibility with existing code, the "classic" fonts continue to work as before.

For example, when printing with the classic font, the cursor position identifies the top-left corner of the character cell, while with the new fonts, the cursor position indicates the baseline of the subsequent text - the bottom line. The size and width of the characters may differ, and may not necessarily start at the cursor column (as shown below, the character starts one pixel to the left of the cursor, but other characters may be to its right or above).

There is a new font to note about a "defect" - no background color option... you can set this value, but it will be ignored.

This is intentional.

Background color features are sometimes used with "classic" fonts to overwrite old screen content with new data. This works because the sizes of these characters are consistent; this does not apply to proportional spacing fonts, where there may be an uncertain number of overlapping characters in the same area. Character drawing functions are not set up this way (it is prohibited in terms of memory and speed on AVRs, and the library still supports it).

When using custom fonts, you can replace previously drawn text:

Use getTextBounds() to determine the minimum rectangle containing the string, use fillRect() to erase the area, then draw the new text:

int16_t x1, y1; uint16_t w, h; tft.getTextBounds(string, x, y, &x1, &y1, &w, &h);

getTextBounds requires a string, initial cursor X&Y position (the current cursor position is not changed), and the addresses of two signed and two unsigned 16-bit integers. The last four values will contain the top-left corner and width and height of the area covered by the text - these can be directly passed as parameters to fillRect().

Erasing and redrawing will cause text "flicker," but this is unavoidable. The previous scheme of drawing background pixels in the same channel only produces a series of new problems.

Alternatively:

Create a GFXcanvas1 object (off-screen bitmap) for a fixed-size area, draw the custom text in it, and use drawBitmap() to copy it to the screen.

// In global declarations:
GFXcanvas1 canvas(128, 32); // 128x32 pixel canvas
// In code later:
canvas.println("I like cake");
tft.drawBitmap(x, y, canvas, 128, 32, foreground, background); // Copy to screen

This will be flicker-free, but requires more RAM (approximately 512 bytes for the 128x32 pixel canvas), so it's not always available on 2K AVR boards. Arduino Mega or any other 32-bit processor development board should be able to manage.

Adding New Fonts

If you want to create a new font size not included in the library, or modify a completely new font, we have a command-line tool (in the "fontconvert" folder) that can achieve this. It should work on many Linux or Unix-like systems (covering Pi, Mac OS X, perhaps Cygwin for Windows, etc.).

Building this tool requires a gcc compiler and the FreeType library. Most Linux distributions include both by default. For others, you may need to install developer tools and download and build FreeType from source. Then edit the Makefile before calling "make" to match your setup.

fontconvert requires atleast two parameters: a font file name (e.g., a scalable TrueType vector font) and a size, in points (72 points = 1 inch; the code assumes a screen resolution similar to the Adafruit 2.8" TFT display). The output should be redirected to a .h file... you can call it whatever you like, but it should be descriptive:

./fontconvert myfont.ttf 12 > myfont12pt7b.h

The GNU FreeFont files are not included in the stock repository, but are easy to download. Or you can convert any font you like.

The name assigned to the font structure in the file is based on the input file name and font size, not the output. This is why I recommend using a descriptive filename that includes the font base name, size, and "7p". Then the .h filename and font structure name can match.

The generated .h file can be copied into the Adafruit_GFX/Fonts folder, or you can import the file as a new tab in your Arduino sketch using the sketch→Add file… command.

If in the font folder, use the following syntax in #include:

#include <Fonts/myfont12pt7b.h>

If you have a tab in your sketch, use the following syntax:

#include "myfont12pt7b.h"

Loading Images

Loading .bmp images from an SD card (or flash chip on an Adafruit "Express" board) is an option for most color displays... although it is not built into Adafruit_GFX and must be installed separately.

The Adafruit_ImageReader library handles this task. It can be installed via the Arduino Library Manager (in the Arduino IDE "Tools" menu, select "Manage Libraries...") and is easy to find in the search box.

While there, also look for the Adafruit_SPIFlash library and install it similarly.

Another library is required, but cannot be installed via the library manager. The Adafruit branch of the SdFat library needs to be downloaded as a .zip file, unzipped, and installed using the old library installation method.

Link: https://pan.baidu.com/s/1e0F_if19k9J8gz-SFyhpAQ

Extraction code: l6o1

Using the Adafruit_ImageReader Library

The syntax for using this library (and the above standalone installation) is indeed a bit strange... this is a side effect of the way Arduino handles libraries. We intentionally did not include it in Adafruit_GFX, because mentioning an SD card library causes the library to have a significant memory requirement... even if the person's program doesn't use the SD card at all! Most graphics projects are self-contained and don't need to reference files from the card... not everyone needs this feature.

There are several example programs in the Adafruit_ImageReader/examples folder. It is recommended that you carefully analyze these to understand how to use the library in your own projects.

They all start with several #includes...

#include <Adafruit_GFX.h>         // Core graphics library
#include <Adafruit_ILI9341.h>     // Hardware-specific library
#include <SdFat.h>                // SD card & FAT filesystem library
#include <Adafruit_SPIFlash.h>    // SPI / QSPI flash library
#include <Adafruit_ImageReader.h> // Image-reading functions

In different examples, one line may vary, depending on what display hardware it is written to support. In the above, we see it working with the Adafruit_ILI9341 display library, which requires specific kits, FeatherWings series boards, or breakout boards. Other examples refer to Adafruit_HX8357, Adafruit_ST7735, or other color TFT or OLED display libraries... use the correct hardware you have.

Most examples can be used on an SD card or on a small flash drive on an Adafruit "Express" board. The initialization code for one method or another is slightly different, and the examples will check whether USE_SD_CARD is #defined to choose one method or another. If you know your own project only needs to run on one or the other type, you actually only need the corresponding initialization.

When using an SD card, declare the following two global variables:

  SdFat                SD;         // SD card filesystem
  Adafruit_ImageReader reader(SD); // Image-reader object, pass in SD filesys

For a flash file system, there are some special declarations to help locate the flash device on different Express boards, then declare three global variables:

  // SPI or QSPI flash filesystem (i.e. CIRCUITPY drive)
  #if defined(__SAMD51__) || defined(NRF52840_XXAA)
    Adafruit_FlashTransport_QSPI flashTransport(PIN_QSPI_SCK, PIN_QSPI_CS,
      PIN_QSPI_IO0, PIN_QSPI_IO1, PIN_QSPI_IO2, PIN_QSPI_IO3);
  #else
    #if (SPI_INTERFACES_COUNT == 1)
      Adafruit_FlashTransport_SPI flashTransport(SS, &SPI);
    #else
      Adafruit_FlashTransport_SPI flashTransport(SS1, &SPI1);
    #endif
  #endif
  Adafruit_SPIFlash    flash(&flashTransport);
  FatFileSystem        filesys;
  Adafruit_ImageReader reader(filesys); // Image-reader, pass in flash filesys

The "reader" object is later used to access image loading functions.

Then... we declare a display object in the usual way (most examples call it "tft")... for example, the Arduino 2.8" TFT touchscreen:

#define SD_CS   4 // SD card select pin
#define TFT_CS 10 // TFT select pin
#define TFT_DC  9 // TFT display/command pin
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

All of this occurs in the global variable section, even before the setup() function.

Now we need to do some work in setup(), and the SD card and flash file system are different...

When using an SD card, it might look like this:

  if(!SD.begin(SD_CS, SD_SCK_MHZ(25))) { // ESP32 requires 25 MHz limit
    Serial.println(F("SD begin() failed"));
    for(;;); // Fatal error, do not continue
  }

This example provides some very basic error handling... checking the return status of SD.begin() and printing a message to the serial monitor if there is a problem.

Using the flash file system requires two steps:

  if(!flash.begin()) {
    Serial.println(F("flash begin() failed"));
    for(;;);
  }
  if(!filesys.begin(&flash)) {
    Serial.println(F("filesys begin() failed"));
    for(;;);
  }

All other code is the same, regardless of whether it uses an SD card or flash. Setting requires some additional steps, but now everything is smooth...

After the SD (or flash) and TFT begin() functions are called, you can call reader.drawBMP() to load a BMP image from the card onto the screen:

ImageReturnCode stat;
stat = reader.drawBMP("/purple.bmp", tft, 0, 0);

Here, four parameters can be received:

A "8.3" format filename (you don't need to provide an absolute path (the preceding "/"), but in some cutting-edge boards (like ESP32), there are some issues with the SD library, so you still continue to do so, including now this one).

The display object that will draw the image (e.g., "tft"). This is the strange syntax mentioned earlier, it is not tft.drawbmp(), but reader.drawBMP(tft).

The X and Y coordinates of the top-left corner of the image (not required to be within the screen range... the library will clip when the image is loaded). 0,0 will draw the image at the top-left corner... so if the image size matches the screen size, it will fill the entire screen.

This function returns a value of type ImageReturnCode, which you can ignore or use to provide some diagnostic functionality. Possible values are:

IMAGE_SUCCESS - the image was loaded successfully (or was completely deleted, still considered "successful" because there was no error).

IMAGE_ERR_FILE_NOT_FOUND - the requested file could not be opened (check spelling, confirm the file is actually on the card, ensure it conforms to the "8.3" file naming convention (e.g., "filename.bmp").

IMAGE_ERR_FORMAT—unsupported image format. Currently only supports uncompressed 24-bit color BMPs (more may be added later).

IMAGE_ERR_MALLOC - unable to allocate memory for the operation (drawBMP() does not generate this error, but other ImageReader functions may).

You can choose to call a function to display a basic diagnostic message to the serial console, rather than handling these values yourself:

reader.printStatus(stat);

If you need to know the size of a BMP image without actually loading it, there is the bmpDimensions() function:

int32_t width, height;
stat = reader.bmpDimensions("/parrot.bmp", &width, &height);

This function can receive three parameters:

The filename, following the same rules as the drawBMP() function.

Pointers to two 32-bit integers. After success, they will be set to the width and height (in pixels) of the image. Any errors will mean these values should be ignored (they are not initialized).

This function returns ImageReturnCode, as explained above for the drawBMP() function.

Loading and Using Images in RAM

Depending on the image size and other factors, loading an image from an SD card to the screen may take several seconds. Small images, which can be fully loaded into memory, can be loaded once and reused. This is very convenient for frequently used icons or sprites, as it is usually easier than converting and embedding the image as an array directly into the code... a daunting process.

This introduces another ImageReader function and a new object type Adafruit_Image:

Adafruit_Image img;
stat = reader.loadBMP("/wales.bmp", img);

loadBMP() accepts two parameters:

  • Filename, following the same rules as the previous function.
  • An Adafruit_Image object. This type is slightly more flexible than the bitmap used by some drawing functions in the GFX library.

This will return an ImageReturnCode, as described above. If the image is too large to fit into available RAM, it returns the IMAGE_ERR_MALLOC value. Color images require two bytes per pixel... for example, a 100x25 pixel image requires 100252 = 5,000 bytes of RAM.

After success, the img object contains the image in RAM.

The loadBMP() function is useful only for microcontrollers with relatively large RAM, such as Adafruit "M0" and "M4" boards, or the ESP32. Small devices like the Arduino Uno cannot do this. On an Arduino Mega, very small images may be possible.

After loading, use the img.draw() function to display the image on the screen:

img.draw(tft, x, y);

This function has three parameters:

  • A display object (e.g., "tft" in most examples), similar to how drawBMP() works.
  • The X and Y coordinates of the top-left corner of the image on the screen, similar to drawBMP().

We use img.draw(tft, ...) instead of tft.drawrgbbitmap(...) (or other bitmap drawing functions in the Adafruit_GFX library), because we plan to add more flexibility in image file formats and types. The Adafruit_Image object "understands" some information about the loaded image and will automatically call the appropriate bitmap rendering function, so you don't have to handle each individual case yourself.

If the image fails to load for any reason, im.draw() can still be called, it just won't do anything. But at least this example program won't crash.

Tags: Arduino Adafruit GFX Library Graphics Programming Embedded Systems

Posted on Fri, 05 Jun 2026 18:53:15 +0000 by msnhockey