2

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.

Aliza
  • 74
  • 4
  • What I meant by "useless" is that it does not tell me which parameters are invalid – Aliza Mar 21 '23 at 15:53
  • 3
    Ok, I get you, now. I am not sure but you can try to remove the call to `SetLayeredWindowAttributes` and the flag `WS_EX_TRANSPARENT`. As far as I know, these are not required to have an alpha-blended window. – jtxkopt Mar 21 '23 at 15:56
  • I removed the call to SetLayeredWindowAttributes and it fixed it. it still does not render the text, but atleast it no longer returns a false, thank you! – Aliza Mar 21 '23 at 16:10
  • 1
    Note that once SetLayeredWindowAttributes has been called for a layered window, subsequent UpdateLayeredWindow calls will fail until the layering style bit is cleared and set again. – Jonathan Potter Mar 21 '23 at 19:44
  • 1
    The SourceConstantAlpha value is combined with any per-pixel alpha values in the source bitmap. If you set SourceConstantAlpha to 0, it is assumed that your image is transparent. I suggest you could try to set the value of `SourceConstantAlpha` to 255. – Jeaninez - MSFT Mar 22 '23 at 03:08

0 Answers0