The TFT_eSPI and LVGL libraries allow you to display images very simply. The TFT_eSPI library easily displays images in XBM format, which is in fact only an array of integers. The LVGL library is more complex to integrate but it is also more powerful. In both cases, you must first convert the images required for the project.
In this tutorial, we will not deal with displaying images stored on an SD card. It is possible – and quite easy – to use the TFT_eSPI and LVGL libraries in the same project, read this article to find out more.
Prepare the images
The TFT_eSPI libraries do not allow you to resize (or at least without effort) the images by programming. It will therefore be necessary to provide an image for each size (icon, wallpaper, etc.), which requires a little preparation and updating time.
The LVGL library has some transformation functions :
- lv_img_set_zoom(img, factor) adjusts the size of the image. 128 is 50% of the size. 512 (max) to get double the initial size.
- lv_img_set_angle(img, angle) rotates the image with a precision of 0.1°
- lv_img_set_antialias(img, true / false) to reduce escalation effects (beware of the drop in performance!)
Free Online Image Converter to XBM Format
The XBM format is a format that describes images in C language, which makes it very easy to integrate XBM images into software written in C. It is a monochrome image format originally designed for the X Window System, especially for the pointer and icon images.
More information about the XBM format on this Wikipedia page.
Converters are quite easy to find online. Here are two free ones:
- Free File Converter can convert images in batch of 5 each time. You will have to manually download each image, but it is already a huge time saver.
- Online Utility does not offer image batch conversion. It will be necessary to reproduce the operation for the conversion of each image.
The interface of Free File Converter is very simple. Manually add the files to convert then select the XBM format in the selection box
Click on Convert then download each XBM file
Convert to XBM with GIMP
The other solution is to install GIMP on your computer. GIMP (for GNU Image Manipulation Program) is a free software dinosaur. In addition to being completely open source and free, it is in many ways comparable to Photoshop.
GIMP is very practical because it will allow us to do several operations on the very useful images:
- Crop / resize image (it’s true that any image editor can do this)
- Adjust colors
- Invert the colors (useful for monochrome icons), a function generally absent from the editors integrated into the OS (Windows, macOS, Linux)
Finally, GIMP can do the conversion to XBM format. To do this, simply go to the File menu then Export as.
Enter the name of the file by directly giving it the xbm extension then save.
A window opens to adjust the export settings. Indicate the prefix that will be used as a variable in the Arduino / ESP32 project.
View an XBM file with GIMP!
There are many XBM files on the internet. Rather than writing an Arduino / ESP32 project to have it rendered, know that GIMP is able to open an XBM file and modify it like any other image format!
How do I store the images on the ESP32’s Flash memory?
To limit the use of the micro-controller RAM, it is preferable to store the project images in the Flash memory. For that one uses the macro PROGMEM.
PROGMEM is part of the pgmspace.h library.
Depending on the editor used (Arduino IDE, PlatformIO), you may have to manually declare the pgmspace.h library before you can use PROGMEM. Just add this line of code at the start of the file in case of a compilation error
#include <pgmspace.h>
Adapt the XBM file for an Arduino project
Open the XBM format file you just downloaded using PlatformIO (recommended).
It contains three variables:
- width the width of the image in pixels
- height the height of the image in pixels
- static char xxxx_bits[] the hexadecimal value of each point in the image.
#define 24767_d7cefe673dc19c3b1994da17f988b26a6931654f33b5f0fd41ceec7d35015fa7_width 200
#define 24767_d7cefe673dc19c3b1994da17f988b26a6931654f33b5f0fd41ceec7d35015fa7_height 200
static char 24767_d7cefe673dc19c3b1994da17f988b26a6931654f33b5f0fd41ceec7d35015fa7_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
....
};
To be able to use this file in your Arduino project, you must make the following adaptations:
- Rename the variables by replacing the identifier for a variable name consistent with the image
- Add #include <pgmspace.h> at the beginning of the file
- Add PROGMEM before the table which will allow the image to be stored in the Flash memory so as not to encumber the RAM of the ESP32 unnecessarily. This is also true on Arduino or ESP8266.
- Rename the .xbm file to .h
Which gives for example
#include <pgmspace.h>
#define clock_width 200
#define clock_height 200
PROGMEM const unsigned char clockxbm[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
...
};
Example of XBM image display on a T-Watch with TFT_eSPI
Here is now an example of Arduino code that displays in the center of the screen the image after conversion to XBM format.
The display of an image in XBM format is done directly using the drawXBitmap() function which takes the following parameters:
- x, y origin of the image
- w, h image size in pixels
- color picture color
- bkcolor background color
To invert the colors (black / white) of the image, all you have to do is invert the color and bkcolor variables.
You can use any color or a predefined constant detailed here.
/* Arduino IDE - un-comment your T-Wach */
//#define LILYGO_WATCH_2019_WITH_TOUCH
//#define LILYGO_WATCH_2019_NO_TOUCH
//#define LILYGO_WATCH_BLOCK
//#define LILYGO_WATCH_2020_V1
/* PlatformIO -> Select your t-watch infile platformio.ini */
#include <Arduino.h>
#include <LilyGoWatch.h>
#include "clock.h"
TTGOClass *ttgo;
void setup() {
Serial.begin(115200);
ttgo = TTGOClass::getWatch();
ttgo->begin();
ttgo->openBL();
ttgo->tft->fillScreen(TFT_BLACK);
ttgo->tft->setTextSize(2);
ttgo->tft->fillScreen(TFT_BLACK);
ttgo->tft->drawString("XBM image", 0,0);
//drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bgcolor)
ttgo->tft->drawXBitmap(20,20, clockxbm, clock_width, clock_height, TFT_WHITE, TFT_BLACK);
}
void loop() {}
Demo with TFT_eSPI on a T-Watch Touch
Here’s what it looks like on a T-Watch Touch.
All models of T-Watch, expansion boards and accessories.
Convert an Image to a True Color C File for LVGL
LVGL provides a free online converter available here .
Although it is possible to adjust the size of the image with the lv_img_set_zoom() function , it is preferable to provide an image close to the desired dimension on the screen. We reserve the zoom to adjust the size of the icons for example.
Converting image to object C does this individually:
- Image File choose the file to convert
- Name The name of the desired variable in the code (preferably in uppercase)
- Color Format choisir True Color with Alpha
- Output format choose C array
Convert
Download and open the generated c file. There is nothing to be done, the LVGL image converter has already prepared everything for you! It is preferable to keep one file per image. If necessary, one file per dimension.
Here is the structure of the C file generated by the LVGL online converter
#include "lvgl/lvgl.h"
#ifndef LV_ATTRIBUTE_MEM_ALIGN
#define LV_ATTRIBUTE_MEM_ALIGN
#endif
#ifndef LV_ATTRIBUTE_IMG_R2D2
#define LV_ATTRIBUTE_IMG_R2D2
#endif
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_IMG_COLORCLOCK uint8_t R2D2_map[] = {
#if LV_COLOR_DEPTH == 1 || LV_COLOR_DEPTH == 8
/*Pixel format: Blue: 2 bit, Green: 3 bit, Red: 3 bit, Alpha 8 bit */
...
#endif
#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP == 0
/*Pixel format: Blue: 5 bit, Green: 6 bit, Red: 5 bit, Alpha 8 bit*/
...
#endif
#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP != 0
/*Pixel format: Blue: 5 bit Green: 6 bit, Red: 5 bit, Alpha 8 bit BUT the 2 color bytes are swapped*/
...
#endif
#if LV_COLOR_DEPTH == 32
/*Pixel format: Blue: 8 bit, Green: 8 bit, Red: 8 bit, Alpha: 8 bit*/
...
#endif
};
const lv_img_dsc_t R2D2 = {
.header.always_zero = 0,
.header.w = 240,
.header.h = 240,
.data_size = 57600 * LV_IMG_PX_SIZE_ALPHA_BYTE,
.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA,
.data = R2D2_map,
};
Sample code to display an image with LVGL on a T-Watch
The LilyGoWatch library comes with 3 wallpaper images directly accessible using the constants WALLPAPER_1_IMG , WALLPAPER_2_IMG and WALLPAPER_3_IMG.
The files are in the folder C imgs the source code.
Displaying an image with LVGL takes a little more work.
/* Arduino IDE - dé-commenter votre T-Watch */
//#define LILYGO_WATCH_2019_WITH_TOUCH
//#define LILYGO_WATCH_2019_NO_TOUCH
//#define LILYGO_WATCH_BLOCK
//#define LILYGO_WATCH_2020_V1
#define LILYGO_WATCH_LVGL
/* PlatformIO -> Selectionner la T-Watch dans le fichier platformio.ini */
#include <Arduino.h>
#include <LilyGoWatch.h>
TTGOClass *ttgo;
// Déclaration des images
LV_IMG_DECLARE(WALLPAPER_1_IMG);
LV_IMG_DECLARE(WALLPAPER_2_IMG);
LV_IMG_DECLARE(WALLPAPER_3_IMG);
LV_IMG_DECLARE(R2D2);
void setup() {
Serial.begin(115200);
ttgo = TTGOClass::getWatch();
ttgo->begin();
// initialise LVGL
ttgo->lvgl_begin();
ttgo->openBL();
// Dans le coin supérieur gauche zoom 50%
lv_obj_t *img1 = lv_img_create(lv_scr_act(), NULL);
lv_img_set_src(img1, &WALLPAPER_1_IMG);
lv_img_set_zoom(img1, 128);
lv_obj_align(img1, NULL, LV_ALIGN_CENTER, -60, -60);
// Dans le coin supérieur droit
lv_obj_t *img2 = lv_img_create(lv_scr_act(), NULL);
lv_img_set_src(img2, &WALLPAPER_2_IMG);
lv_img_set_zoom(img2, 128);
lv_obj_align(img2, NULL, LV_ALIGN_CENTER, 60, -60);
// En bas et à gauche
lv_obj_t *img3 = lv_img_create(lv_scr_act(), NULL);
lv_img_set_src(img3, &WALLPAPER_3_IMG);
lv_img_set_zoom(img3, 128);
lv_obj_align(img3, NULL, LV_ALIGN_CENTER, -60, 60);
// En bas à droite
lv_obj_t *img4 = lv_img_create(lv_scr_act(), NULL);
lv_img_set_src(img4, &R2D2);
lv_img_set_zoom(img4, 128);
lv_obj_align(img4, NULL, LV_ALIGN_CENTER, 60, 60);
}
void loop() {
lv_task_handler();
}
Explanation of C ++ code
LVGL is integrated into the LilyGoWatch library. No need to install it manually in your project. To activate it, just add the LILYGO_WATCH_LVGL constant at the start of the Arduino program.
#define LILYGO_WATCH_LVGL
We start the LVGL library after creating the pointer to the T-Watch object.
ttgo = TTGOClass::getWatch();
ttgo->begin();
ttgo->lvgl_begin();
Before you can use an image, you must declare the C file using the LV_IMG_DECLARE() function.
LV_IMG_DECLARE(WALLPAPER_1_IMG);
We can now create a lv_obj_t object which will contain the image in the current scenelv_scr_act()
lv_obj_t *img1 = lv_img_create(lv_scr_act(), NULL);
We specify the image file of this object
lv_img_set_src(img1, &WALLPAPER_1_IMG);
You can optionally apply transformations to the image. Here we reduce the size by 50%
lv_img_set_zoom(img1, 128);
And finally all that remains is to display it at the desired location on the screen. Here in the upper left corner by calling the function lv_obj_align().
lv_obj_align(img1, NULL, LV_ALIGN_CENTER, -60, -60);
For the screen to be generated, you must call the lv_task_handler() method. To be able to retrieve the user’s actions on the touch screen, it is preferable to place the supervision in the loop() so that it is called regularly.
void loop() {
lv_task_handler();
}
Demo with LVGL on a 2020 T-Watch
Render the image mosaic on the T-Watch 2020 with the LVGL library.
All models of T-Watch, expansion boards and accessories
Conclusion
The TFT_eSPI library is super fast to set up. If you are just starting out and color isn’t essential to your application, this is the library for you.
To display images in color, it is preferable to use the LVGL library. It is much more powerful. In the absence of an interface editor, it will take more time to develop skills and develop the interface of your application.
Updates
2020/11/24 How to view and export images in XBM format with GIMP
23/11/2020 Publication of the article
- T-Watch. Simplified code for shutdown and wake-up with BMA423 or AXP202 of the ESP32
- T-Watch. Sleep and wake-up ESP32 with BMA423 accelerometer or AXP202 button
- T-Watch. Menu, pages, navigation between screens with TFT_eSPI
- T-Watch. Draw Mandelbrot or Julia fractals with an ESP32 and LVGL + TFT_eSPI
- T-Watch. Display XBM (TFT_eSPI) and C++ (LVGL) images. ESP32, Arduino compatible