런타임에서 이미지 로드
이 섹션에서는 파일이나 기타 런타임 입력에서 일부 그래픽 콘텐츠를 읽어와서 동적 비트맵을 사용하여 애플리케이션을 생성하는 방법에 대해 알아보겠습니다. 동적 비트맵을 사용하면, 예를 들어 SD 카드의 이미지 파일도 표시할 수 있습니다.
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에 더욱 최적화된 리더라면 파일에서 동적 비트맵 메모리로 직접 읽어올 수 있습니다(단, 이 경우 row padding을 건너뛰어야 합니다). 위의 리더는 BMP 파일에서 임시 버퍼로 10개의 픽셀을 읽어옵니다. 그러면 픽셀이 올바른 형식으로 변환되면서 비트맵으로 복사됩니다.
동적 비트맵에 적합한 메모리 구성
동적 비트맵을 생성하여 사용하려면 먼저 TouchGFX를 구성해야 합니다. 전제 조건으로, 버퍼 1개와 최대한 많은 동적 비트맵(시뮬레이터에도 사용해야 함)이 필요합니다.
다음은 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
JPEG 파일 로드
LibJPEG에서 JPEG 파일을 사용하는 JPEG 파일 로더 예제는 여기에서 찾아볼 수 있습니다. 이 로더는 위의 BMPFileLoader와 유사한 JPEGLoader 클래스를 사용합니다.