Loading Images at Runtime
This section describes how dynamic bitmaps can be used to create applications where some of the graphic content is read from files or other input at runtime. The dynamic bitmaps can be used to show e.g. image files from an SD-card.
Note
Recall that standard bitmaps are compiled into the application and therefore must be available at compile time. The Dynamic Bitmap feature allows you to read images from files at runtime, or even download images through an internet connection.
Loading BMP file Example
Here we will see how to use a BMP loader to load pixels from a Windows BMP file. The code for the loader is later in the article.
Insert first an Image widget in the view. This widget will show the BMP:
class TemplateView : public View
{
private:
Image image;
}
Load the image date in setupScreen:
void TemplateView::setupScreen()
{
FILE* f = fopen("image.jpg", "rb");
uint16_t width, height;
//Get the image dimensions from the BMP file
BMPFileLoader::getBMP24Dimensions(f, width, height);
BitmapId bmpId;
//Create (16bit) dynamic bitmap of same dimension
bmpId = Bitmap::dynamicBitmapCreate(width, height, Bitmap::RGB565);
//Load pixels from BMP file to dynamic bitmap
BMPFileLoader::readBMP24File(Bitmap(bmpId), f);
//Make Image show the loaded bitmap
image.setBitmap(Bitmap(bmpId));
//Position image and add to View
image.setXY(20, 20);
add(image);
...
}
The BMP loader
Here is the code for a simple BMP file loader. It only supports 24bpp BMP files. You may have to adjust the file system calls to match your system.
BMPFileLoader.hpp
#include <touchgfx/hal/Types.hpp>
#include <touchgfx/Bitmap.hpp>
using namespace touchgfx;
class BMPFileLoader
{
public:
typedef void* FileHdl;
static void getBMP24Dimensions(FileHdl fileHandle, uint16_t& width, uint16_t& height);
static void readBMP24File(Bitmap bitmap, FileHdl fileHandle);
private:
static int readFile(FileHdl hdl, uint8_t* const buffer, uint32_t length);
static void seekFile(FileHdl hdl, uint32_t offset);
};
BMPFileLoader.cpp
#include <gui/common/BMPFileLoader.hpp>
#include <touchgfx/Color.hpp>
int BMPFileLoader::readFile(FileHdl hdl, uint8_t* const buffer, uint32_t length)
{
uint32_t r = fread(buffer, 1, length, (FILE*)hdl);
if (r != length)
{
return 1;
}
return 0;
}
void BMPFileLoader::seekFile(FileHdl hdl, uint32_t offset)
{
fseek((FILE*)hdl, offset, SEEK_SET);
}
void BMPFileLoader::getBMP24Dimensions(FileHdl fileHandle, uint16_t& width, uint16_t& height)
{
uint8_t data[50];
seekFile(fileHandle, 0);
readFile(fileHandle, data, 26); //read first part of header.
width = data[18] | (data[19] << 8) | (data[20] << 16) | (data[21] << 24);
height = data[22] | (data[23] << 8) | (data[24] << 16) | (data[25] << 24);
}
void BMPFileLoader::readBMP24File(Bitmap bitmap, FileHdl fileHandle)
{
uint8_t data[50];
seekFile(fileHandle, 0);
readFile(fileHandle, data, 26); //read first part of header.
const uint32_t offset = data[10] | (data[11] << 8) | (data[12] << 16) | (data[12] << 24);
const uint32_t width = data[18] | (data[19] << 8) | (data[20] << 16) | (data[21] << 24);
const uint32_t height = data[22] | (data[23] << 8) | (data[24] << 16) | (data[25] << 24);
readFile(fileHandle, data, offset - 26); //read rest of header.
//get dynamic bitmap boundaries
const uint32_t buffer_width = bitmap.getWidth();
const uint32_t buffer_height = bitmap.getHeight();
const uint32_t rowpadding = (4 - ((width * 3) % 4)) % 4;
const Bitmap::BitmapFormat format = bitmap.getFormat();
uint8_t* const buffer8 = Bitmap::dynamicBitmapGetAddress(bitmap.getId());
uint16_t* const buffer16 = (uint16_t*)buffer8;
for (uint32_t y = 0; y < height; y++)
{
for (uint32_t x = 0; x < width; x++)
{
if (x % 10 == 0) //read data every 10 pixels = 30 bytes
{
if (x + 10 <= width) //read 10
{
readFile(fileHandle, data, 10 * 3); //10 pixels
}
else
{
readFile(fileHandle, data, (width - x) * 3 + rowpadding); //rest of line
}
}
//insert pixel, if within dynamic bitmap boundaries
if (x < buffer_width && ((height - y - 1) < buffer_height))
{
switch (format)
{
case Bitmap::RGB565:
buffer16[x + (height - y - 1) * buffer_width] =
touchgfx::Color::getColorFrom24BitRGB(data[(x % 10) * 3 + 2], data[(x % 10) * 3 + 1], data[(x % 10) * 3]);
break;
case Bitmap::RGB888:
{
//24 bit framebuffer
const uint32_t inx = 3 * (x + (height - y - 1) * buffer_width);
buffer8[inx + 0] = data[(x % 10) * 3 + 0];
buffer8[inx + 1] = data[(x % 10) * 3 + 1];
buffer8[inx + 2] = data[(x % 10) * 3 + 2];
break;
}
case Bitmap::ARGB8888:
{
//24 bit framebuffer
const uint32_t inx = 4 * (x + (height - y - 1) * buffer_width);
buffer8[inx + 0] = data[(x % 10) * 3 + 0];
buffer8[inx + 1] = data[(x % 10) * 3 + 1];
buffer8[inx + 2] = data[(x % 10) * 3 + 2];
buffer8[inx + 3] = 255; //solid
break;
}
default:
assert(!"Unsupported bitmap format in BMPFileLoader!");
}
}
}
}
}
This code is for illustrative purposes. A more optimal reader for RGB888 can read directly from the file to the dynamic bitmap memory (remember to skip the row padding). The reader above reads 10 pixels from the BMP file to a temporary buffer. The pixels are then copied to the bitmap while converting to the correct format.
Configure memory for dynamic bitmaps
Before you can create and use dynamic bitmaps you must configure TouchGFX. It is a prerequisite to provide a buffer and the maximum number of dynamic bitmaps (also for the simulator).
Here is an example for STM32F7xx where we allocate a buffer in external RAM: We wish to load and show a 24-bit bitmap of size 320x240. The memory requirement is thus 320x240x3 = 230400. We also need a little space for bookkeeping, so we allocate 232000 bytes for the buffer.
static uint32_t bmpCache = (uint32_t)(0xC00C0000); // SDRAM
void touchgfx_init()
{
HAL& hal = touchgfx_generic_init<STM32F7HAL>(dma, display, tc, 480, 272, (uint16_t*)bmpCache, 232000, 1);
...
}
The final argument is the maximum number of dynamic bitmaps, so adjust this according to your needs.
Note
Loading JPEG files
A JPEG File Loader example can be found here which shows how to use LibJPEG to use JPEG-files. It uses a JPEGLoader class similar to the above BMPFileLoader.