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:
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.
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âŚ
#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:
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:
// 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:
#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:
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 }
Some boards such as the Feather M0 prefer a slower clock speed. Reduce the 25 MHz to 12 MHz if you are see seeing a "SD begin() failed." message on a FeatherWing.
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:
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:
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:
reader.printStatus(stat);
If you need to know the size of a BMP image without actually loading it, thereâs the bmpDimensions()
function:
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
:
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:
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.
There is no BMP-to-flash function. This is on purpose and by design. We do something similar to that in the M4_Eyes project and youâre welcome to look through that code for insights, but generally speaking this is fraught with peril and not something we recommend. SD to screen or to RAM should cover most cases.
Page last edited December 28, 2024
Text editor powered by tinymce.