Loading Images

Loading .BMP images from an SD card (or the flash memory chip on Adafruit “Express” boards) is an option for most of our color displays…though it’s not built into Adafruit_GFX and must be separately installed.

The Adafruit_ImageReader library handles this task. It can be installed through the Arduino Library Manager (Sketch→Include Library→Manage Libraries…). Enter “imageread” in the search field and the library is easy to spot:

graphic_lcds_install-imagereader-lib.png
That’s “imageread,” not “ermahgerd.”

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

There’s one more library required, but it can’t be installed through the Library Manager. The Adafruit fork of the SdFat library needs to be downloaded as a .ZIP file, uncompressed and installed the old-school Arduino library way.

Using the Adafruit_ImageReader Library

The syntax for using this library (and the separate installation above) are admittedly a bit peculiar…it’s a side-effect of the way Arduino handles libraries. We purposefully did not roll this into Adafruit_GFX because any mere mention of an SD card library will incur all of that library’s considerable memory requirements…even if one’s sketch doesn’t use an SD card at all! A majority of graphics projects are self-contained and don’t reference files from a card…not everybody needs this functionality.

There are several example sketches in the Adafruit_ImageReader/examples folder. It’s recommended that you dissect these for ideas how to use the library in your own projects.

They all start with several #includes…

Download: file
#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

One of these lines may vary from one example to the next, depending which display hardware it’s written to support. Above we see it being used with the Adafruit_ILI9341 display library required of certain shields, FeatherWings or breakout boards. Others examples reference Adafruit_HX8357, Adafruit_ST7735, or other color TFT or OLED display libraries…use the right one for the hardware you have.

Most of the examples can work from either an SD card, or the small flash storage drive that’s on certain Adafruit “Express” boards. The code to initialize one or the other is a little different, and the examples check whether USE_SD_CARD is #defined to select one method vs. the other. If you know for a fact that your own project only needs to run on one type or the other, you really only need the corresponding initialization.

For SD card use, these two globals are declared:

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

For a flash filesystem, there are some special declarations made that help us locate the flash device on different Express boards, then declare three globals:

Download: file
  // 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 will be used to access the image-loading functions later.

Then…we declare a display object (called “tft” in most of the examples) the usual way…for example, with the 2.8 inch TFT touch shield for Arduino, it’s:

Download: file
#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);

That all takes place in the global variable section, even before the setup() function.

Now we need to do some work in setup(), and again it’s different for SD cards vs. flash filesystems…

For SD card use, it might look like this:

Download: file
  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 is providing some very basic error handling…checking the return status of SD.begin() and printing a message to the Serial Monitor if there’s a problem.

Using a flash filesystem instead requires two steps:

Download: file
  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 now the same regardless whether using an SD card or flash. That either/or setup required some extra steps but it’s all smooth sailing now…

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

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

This accepts four arguments:

  • A filename in “8.3” format (you shouldn’t need to provide an absolute path (the leading “/”), but there are some issues with the SD library on some cutting-edge boards like the ESP32, so go ahead and include this for good measure).
  • The display object where the image will be drawn (e.g. “tft”). This is the weird syntax previously mentioned…rather than tft.drawBMP(), it’s reader.drawBMP(tft), because reasons.
  • An X and Y coordinate where the top-left corner of the image is positioned (this doesn’t need to be within screen bounds…the library will clip the image as it’s loaded). 0, 0 will draw the image at the top-left corner…so if the image dimensions match the screen dimensions, it will fill the entire screen.

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

  • IMAGE_SUCCESS — Image loaded successfully (or was clipped fully off screen, still considered “successful” in that there was no error).
  • IMAGE_ERR_FILE_NOT_FOUND — Could not open the requested file (check spelling, confirm file actually exists on the card, make sure it conforms to “8.3” file naming convention (e.g. “filename.bmp”).
  • IMAGE_ERR_FORMAT — Not a supported image format. Currently only uncompressed 24-bit color BMPs are supported (more will likely be added over time).
  • IMAGE_ERR_MALLOC — Could not allocate memory for operation (drawBMP() won’t generate this error, but other ImageReader functions might).

Rather than dealing with these values yourself, you can optionally call a function to display a basic diagnostic message to the Serial console:

Download: file
reader.printStatus(stat);

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

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

This accepts three arguments:

  • A filename, same rules as the drawBMP() function.
  • Pointers to two 32-bit integers. On successful completion, their contents will be set to the image width and height in pixels. On any error these values should be ignored (they’re left uninitialized).

This function returns an ImageReturnCode as explained with the drawBMP() function above.

Loading and Using Images in RAM

Depending on image size and other factors, loading an image from SD card to screen may take several seconds. Small images…those that can fit entirely in RAM…can be loaded once and used repeatedly. This can be handy for frequently-used icons or sprites, as it’s usually much easier than converting and embedding an image as an array directly in one’s code…a horrible process.

This introduces another ImageReader function plus a new object type, Adafruit_Image:

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

loadBMP() accepts two arguments:

  • A filename, same rules as the previous functions.
  • An Adafruit_Image object. This is a slightly more flexible type than the bitmaps used by a few drawing functions in the GFX library.

This returns an ImageReturnCode as previously described. If an image is too large to fit in available RAM, a value of IMAGE_ERR_MALLOC will be returned. Color images require two bytes per pixel…for example, a 100x25 pixel image would need 100*25*2 = 5,000 bytes RAM.

On success, the img object will contain the image in RAM.

The loadBMP() function is useful only on microcontrollers with considerable RAM, like the Adafruit “M0” and “M4” boards, or ESP32. Small devices like the Arduino Uno just can’t cut it. It might be marginally useful on the Arduino Mega with very small images.

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

Download: file
img.draw(tft, x, y);

This accepts three arguments:

  • A display object (e.g. “tft” in most of the examples), similar to how drawBMP() worked.
  • An X and Y coordinate for the upper-left corner of the image on the screen, again similar to drawBMP().

We use img.draw(tft,…) rather than tft.drawRGBBitmap(…) (or other bitmap-drawing functions in the Adafruit_GFX library) because in the future we plan to add more flexibility with regard to image file formats and types. The Adafruit_Image object “understands” a bit about the image that’s been loaded and will call the appropriate bitmap-rendering function automatically, you won’t have to handle each separate case on your own.

If the image failed to load for any reason, img.draw() can still be called, it just won’t do anything. But at least the sketch won’t crash.

This guide was first published on Jul 29, 2012. It was last updated on Jul 29, 2012. This page (Loading Images) was last updated on Sep 16, 2019.