跳转到主要内容

动态加载图像

本节中,我们将讨论在TouchGFX应用程序中从文件加载图像的一种方法。

在TouchGFX应用程序中使用位图的标准程序是在应用程序中包含位图。 PNG文件会被转换为.cpp文件中的二进制数据,并编译链接到应用程序中。 这是一种应用程序可用在运行时使位图对的简单方法。

在某些应用中,这是不可能或不实际的。 例如,当图像在编译时不可用(可能稍后通过互联网连接下载),或者当您的应用程序Flash空间不足以放入太多位图时。

针对该问题,动态位图(见以下链接)是一个基于RAM的解决方案。 动态位图在运行时创建,应用程序可以自由决定位图的数量、格式、宽度和高度。

由于动态位图存储在RAM中(位图缓存内),应用程序必须在创建动态位图后将像素值从源复制到动态位图中。 创建后,动态位图包含未初始化的像素。

本节描述如何使用动态位图创建应用,其中的一些图形内容读自BMP文件。 一个简单的用例是显示SD卡中BMP文件的应用程序。

Note
请先阅读动态位图

注意,标准位图会被编译到应用中,因此必须在编译时提供。 动态位图特性允许在运行时从文件中读取图像,甚至可以通过互联网连接下载图像。

加载BMP文件示例

下面我们将介绍如何使用BMP加载器从Windows BMP文件加载像素数据。 文章后面将提供加载器代码。

首先在视图中插入图像控件。 此控件用于显示BMP:

class TemplateView : public View
{
private:
Image image;
}

在 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);
...
}

BMP加载器

下面是一个简单的BMP文件加载器的代码。 它仅支持24bpp BMP文件。 您可能需要调整文件系统调用部分,以适应您的系统。

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 <platform/driver/lcd/LCD16bpp.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] =
LCD16bpp::getNativeColorFromRGB(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!");
}
}
}
}
}

此代码用于功能说明。 有一个更优的RGB888读取器可以从文件直接读取数据到动态位图存储内存中(注意跳过行填充字符)。 上面的读取器从BMP文件中将10个像素的数据读到了临时缓冲区。 然后转换成正确格式,再复制到位图。

为动态位图配置内存

您必须先配置TouchGFX,然后才能创建和使用动态位图。 必须先提供一个缓冲区和最大动态位图数量(也适用于仿真器)。

下面是一个STM32F7xx的示例,这里我们在外部RAM中分配了缓冲区:我们想要加载并显示尺寸为320x240的24位位图。 因此,需要的存储空间为320x240x3 = 230400。 我们还需要一点空间用于其它开销,因此为缓冲区分配了232000字节。

FrontendApplication.cpp (extract)
#include <gui/common/FrontendApplication.hpp>
#include <touchgfx/Bitmap.hpp>

FrontendApplication::FrontendApplication(Model& m, FrontendHeap& heap)
: FrontendApplicationBase(m, heap)
{
static uint32_t bmpCache = (uint32_t)(0xC00C0000); // SDRAM
Bitmap::setCache((uint16_t*)bmpCache, 232000, 1);
}

最后的参数是动态位图的最大数量,可根据您的需求进行调整。 注意,在许多平台上,帧缓冲也保存在外部RAM中。 确保没有将位图缓存放在重叠存储区。

Note
注意,如果存储空间不够,dynamicBitmapCreate返回的BitmapId将是BITMAP_INVALID。

正在加载JPEG文件

这里可以找到一个JPEG文件加载器示例,它展示了如何通过LibJPEG使用JPEG文件。 它使用了一个与上面的BMPFileLoader相似的JPEGLoader类。