I'm trying to create a function to render a text from font atlas and display it on screen using UpdateLayeredWindow method. I'm pretty new to C and WINAPI, could someone help me out and tell me why the UpdateLayeredWindow is failing here. I have wracked my brain around this but i cant figure it out. Maybe I'm doing something stupid here..
void RenderASCIIText(HWND windowHandle, const FTFontAtlas* atlas, const char* sentence)
{
// Initialize variables
const char* charPtr = sentence;
HBITMAP renderBuffer = { 0 };
RECT renderRegion = { 0 };
uint32_t renderWidth = 0;
uint32_t renderHeight = 0;
uint32_t bufferOffset = 0;
void* bufferArray = NULL;
// Get device context and create a compatible DC
HDC deviceContext = GetDC(NULL);
HDC compatibleDC = CreateCompatibleDC(deviceContext);
// Get the dimensions of the window
if (GetWindowRect(windowHandle, &renderRegion) == FALSE) {
goto cleanup;
}
// Calculate the required size of the buffer based on the text to be rendered
while (*charPtr != '\0')
{
renderWidth += atlas->characters[*charPtr].width;
renderHeight = renderHeight > atlas->characters[*charPtr].height ? renderHeight : atlas->characters[*charPtr].height;
charPtr++;
}
// Set up the bitmap info for the buffer
BITMAPINFO bitmapInfo = {
.bmiHeader.biSize = sizeof(BITMAPINFOHEADER),
.bmiHeader.biWidth = renderWidth,
.bmiHeader.biHeight = renderHeight,
.bmiHeader.biPlanes = 1,
.bmiHeader.biBitCount = 32,
.bmiHeader.biCompression = BI_RGB,
};
// Create the buffer
if ((renderBuffer = CreateDIBSection(deviceContext, &bitmapInfo, DIB_RGB_COLORS, &bufferArray, NULL, 0)) == NULL) {
goto cleanup;
};
// Render the text onto the buffer
charPtr = sentence;
while (*charPtr != '\0')
{
FTCharacter* character = &atlas->characters[*charPtr];
for (int y = character->height - 1; y >= 0; y--)
{
for (int x = 0; x < character->width; x++)
{
uint8_t* pixelDestination = (uint8_t*)(bufferArray)+(y * renderWidth + x + bufferOffset) * 4;
uint32_t pixelIndex = (character->height - 1 - y) * atlas->atlasWidth + x + character->offsetX;
uint8_t pixelSource = (uint8_t) atlas->atlasData[pixelIndex];
pixelDestination[0] = pixelSource;
pixelDestination[1] = pixelSource;
pixelDestination[2] = pixelSource;
pixelDestination[3] = pixelSource;
}
}
bufferOffset += character->width;
charPtr++;
}
// Select the buffer into the compatible DC
HBITMAP hBmpOld = (HBITMAP)SelectObject(compatibleDC, renderBuffer);
// Set up the parameters for updating the window
POINT renderOrigin =
{
.x = renderRegion.left,
.y = renderRegion.top
};
SIZE renderSize =
{
.cx = renderWidth,
.cy = renderHeight
};
POINT sourceOrigin =
{
.x = 0,
.y = 0
};
// Set the blending function to use alpha blending
BLENDFUNCTION blendFunction =
{
.BlendOp = AC_SRC_OVER,
.SourceConstantAlpha = 0,
.AlphaFormat = AC_SRC_ALPHA,
.BlendFlags = 0
};
// Update the layered window with the specified parameters
BOOL result = UpdateLayeredWindow(
windowHandle,
deviceContext,
&renderOrigin,
&renderSize,
compatibleDC,
&sourceOrigin,
RGB(0,0,0),
&blendFunction,
ULW_ALPHA
);
// If the update was unsuccessful, display an error message
if (result == FALSE) {
DWORD error = GetLastError();
LPSTR messageBuffer = NULL;
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
if (size != 0) {
MessageBoxA(NULL, messageBuffer, "Error", MB_OK | MB_ICONERROR);
LocalFree(messageBuffer);
}
}
cleanup: {
return;
}
}
Im using FreeType to create the font atlas, here is some context around the other methods im using to render the font atlas:
// Define a structure to represent a single character in a font
typedef struct FTCharacter {
uint32_t width;
uint32_t height;
uint32_t offsetX;
} FTCharacter;
// Define a structure to represent a font atlas, which is a collection of characters
typedef struct FTFontAtlas {
FTCharacter characters[NUM_OF_ASCII_CHARACTERS];
uint32_t atlasWidth;
uint32_t atlasHeight;
size_t atlasSize;
uint8_t* atlasData;
FT_Error error;
} FTFontAtlas;
int32_t CreateASCIIFontAtlas(FTFontAtlas* atlas, uint32_t fontSize)
{
int32_t result = 0;
// FreeType library and face objects
FT_Library freetypeLibrary = NULL;
FT_Face freetypeFace = NULL;
// Width and height of the atlas
uint32_t atlasWidth = 0;
uint32_t atlasHeight = 0;
// Initialize FreeType library
if (FT_Init_FreeType(&freetypeLibrary)) {
result = -1;
goto cleanup;
}
// Load the font face
if (FT_New_Face(freetypeLibrary, "fonts/arial.ttf", 0, &freetypeFace)) {
result = -1;
goto cleanup;
}
// Set the font size
if (FT_Set_Pixel_Sizes(freetypeFace, 0, fontSize)) {
result = -1;
goto cleanup;
}
// Compute the atlas width and height
for (int i = 0; i < NUM_OF_ASCII_CHARACTERS; i++)
{
if (FT_Load_Char(freetypeFace, i, FT_LOAD_NO_BITMAP)) {
result = -1;
goto cleanup;
}
atlasWidth += freetypeFace->glyph->bitmap.width;
atlasHeight = atlasHeight > freetypeFace->glyph->bitmap.rows ? atlasHeight : freetypeFace->glyph->bitmap.rows;
}
// Allocate memory for the pixel array that will store the atlas data
uint8_t* pixelArray = (uint8_t*)calloc(atlasWidth * atlasHeight, 1);
if (pixelArray == NULL) {
goto cleanup;
}
uint32_t offsetX = 0;
// Render each glyph into the pixel array
for (int i = 0; i < NUM_OF_ASCII_CHARACTERS; i++)
{
if (FT_Load_Char(freetypeFace, i, FT_LOAD_NO_BITMAP)) {
result = -1;
goto cleanup;
}
if (FT_Render_Glyph(freetypeFace->glyph, FT_RENDER_MODE_NORMAL)) {
result = -1;
goto cleanup;
}
// Copy the glyph bitmap data into the atlas pixel array
for (int y = 0; y < freetypeFace->glyph->bitmap.rows; y++)
{
for (int x = 0; x < freetypeFace->glyph->bitmap.width; x++)
{
uint8_t* destination = (uint8_t*)(pixelArray)+(y * atlasWidth + x + offsetX);
*destination = freetypeFace->glyph->bitmap.buffer[y * freetypeFace->glyph->bitmap.width + x];
}
}
// Record the glyph metrics in the character array of the atlas
atlas->characters[i].offsetX = offsetX;
atlas->characters[i].width = freetypeFace->glyph->bitmap.width;
atlas->characters[i].height = freetypeFace->glyph->bitmap.rows;
offsetX += freetypeFace->glyph->bitmap.width;
}
// Set the fields of the atlas struct
atlas->atlasData = pixelArray;
atlas->atlasWidth = atlasWidth;
atlas->atlasHeight = atlasHeight;
atlas->atlasSize = atlasWidth * atlasHeight;
cleanup: {
if (freetypeFace != NULL) {
FT_Done_Face(freetypeFace);
}
if (freetypeLibrary != NULL) {
FT_Done_FreeType(freetypeLibrary);
}
return result;
}
}
Here is how I'm registering the window class and creating the window:
int32_t RegisterMainWindowClass(WNDCLASSEXW* windowClass, HINSTANCE windowInstance)
{
windowClass->cbSize = sizeof(WNDCLASSEXW);
windowClass->style = CS_HREDRAW | CS_VREDRAW;
windowClass->lpfnWndProc = &WindowProc;
windowClass->cbClsExtra = 0;
windowClass->cbWndExtra = 0;
windowClass->hInstance = windowInstance;
windowClass->hIcon = LoadIconW(NULL, IDI_APPLICATION);
windowClass->hCursor = LoadCursorW(NULL, IDC_ARROW);
windowClass->hbrBackground = GetStockObject(NULL_BRUSH);
windowClass->lpszMenuName = NULL;
windowClass->lpszClassName = WIN32_WINDOW_CLASS_NAME;
windowClass->hIconSm = LoadIconW(NULL, IDI_APPLICATION);
if (!RegisterClassExW(windowClass)) {
return -1;
}
return 0;
}
int32_t CreateMainWindow(HWND* windowHandle, HINSTANCE windowInstance)
{
*windowHandle = CreateWindowExW(
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
WIN32_WINDOW_CLASS_NAME,
WIN32_WINDOW_TITLE,
WS_POPUP | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
WIN32_WINDOW_WIDTH,
WIN32_WINDOW_HEIGHT,
NULL, NULL,
windowInstance,
NULL
);
if (*windowHandle == NULL) {
return -1;
}
// Set the window to be transparent
SetLayeredWindowAttributes(*windowHandle, RGB(0, 0, 0), 0, LWA_COLORKEY);
return 0;
}
I was trying to display the rendered bitmap with UpdateLayeredWindow and I tried calling the GetLastError method but the resulting message was completely useless "The parameter is incorrect". error code 87. I tried debugging the code on my own but I cant figure it out.