Resizeable Images + Example Image Loader for Win32 GDI

This commit is contained in:
Kyle Bueche 2025-03-31 22:32:53 -10:00
parent ad49977f1b
commit 8e0d180b4e
7 changed files with 824 additions and 6 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

View File

@ -0,0 +1,4 @@
# to build this, install mingw
gcc image_demo.c -ggdb -oimgdemo -lgdi32 -lmingw32 -lole32 -lwindowscodecs # -mwindows # comment -mwindows out for console output

View File

@ -0,0 +1,386 @@
#include "../../../clay.h"
#include <stdlib.h>
#include <wincodec.h> // Required for windows image decoding
typedef struct {
HBITMAP hBitmap;
struct HBitmapNode* next;
} HBitmapNode;
HBitmapNode* hBitmapNodeHead = NULL;
// Stores bitmap handles so we can clean them up later.
HBITMAP* pushHBitmap(HBITMAP hBitmap)
{
HBitmapNode* newHead = (HBitmapNode *) malloc(sizeof(HBitmapNode));
newHead->hBitmap = hBitmap;
newHead->next = hBitmapNodeHead;
hBitmapNodeHead = newHead;
return &(newHead->hBitmap);
}
// Deallocates Win32 GDI bitmap handle memory, as well as memory used by the linkedlist
void cleanupHBitmaps()
{
HBitmapNode* curr = hBitmapNodeHead;
HBitmapNode* next = NULL;
while(curr != NULL)
{
next = curr->next;
DeleteObject(curr->hBitmap);
free(curr);
curr = next;
}
hBitmapNodeHead = NULL;
}
// This may be slow for loading huge numbers of images,
// consider allocating and initializing interfaces once
// outside of the function if that becomes an issue
//
// Handles PNGs, JPEGs, BMPs, ICOs, TIFFs, HD Photos, and GIFs (only loads the first frame)
// Images need to be destroyed with DeleteObject(myImage) after they are no longer needed
Clay_ImageElementConfig LoadClayImage(LPCWSTR filename)
{
// Windows codec stuff
CoInitialize(NULL);
IWICImagingFactory *imgFactory = NULL;
IWICBitmapDecoder *bmpDecoder = NULL;
IWICBitmapFrameDecode *bmpFrame = NULL;
IWICFormatConverter *formatConverter = NULL;
// Storage stuff
HBITMAP hBitmap = NULL;
void *bits = NULL;
UINT width = 0;
UINT height = 0;
BITMAPINFO bmi = { 0 };
// Start decoding
CoCreateInstance(
&CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IWICImagingFactory,
(void **) &imgFactory);
imgFactory->lpVtbl->CreateDecoderFromFilename(
imgFactory,
filename,
NULL,
GENERIC_READ,
WICDecodeMetadataCacheOnLoad,
&bmpDecoder);
bmpDecoder->lpVtbl->GetFrame(bmpDecoder, 0, &bmpFrame);
imgFactory->lpVtbl->CreateFormatConverter(imgFactory, &formatConverter);
// Convert to a BitBlt-friendly format
formatConverter->lpVtbl->Initialize(
formatConverter,
(IWICBitmapSource *) bmpFrame,
&GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone,
NULL,
0.0,
WICBitmapPaletteTypeCustom);
bmpFrame->lpVtbl->GetSize(bmpFrame, &width, &height);
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -((LONG) height); // Negative makes it a top down bitmap
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
hBitmap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
formatConverter->lpVtbl->CopyPixels(formatConverter, NULL, width * 4, width * height * 4, (BYTE*) bits);
if (formatConverter)
formatConverter->lpVtbl->Release(formatConverter);
if (bmpFrame)
bmpFrame->lpVtbl->Release(bmpFrame);
if (bmpDecoder)
bmpDecoder->lpVtbl->Release(bmpDecoder);
if (imgFactory)
imgFactory->lpVtbl->Release(imgFactory);
CoUninitialize();
// Add to dealloc list
HBITMAP* hBitmap_ptr = pushHBitmap(hBitmap);
Clay_ImageElementConfig clayImage = { .sourceDimensions = { .height = height, .width = width }, .imageData = hBitmap_ptr };
return clayImage;
}
Clay_ImageElementConfig reefImg, vacImg;
const int FONT_ID_BODY_16 = 0;
Clay_Color COLOR_WHITE = { 255, 255, 255, 255};
void RenderHeaderButton(Clay_String text) {
CLAY({
.layout = { .padding = { 16, 16, 8, 8 }},
.backgroundColor = { 140, 140, 140, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(5)
}) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
}
}
void RenderDropdownMenuItem(Clay_String text) {
CLAY({.layout = { .padding = CLAY_PADDING_ALL(16)}}) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
}
}
typedef struct {
Clay_String title;
Clay_ImageElementConfig image;
Clay_String contents;
} Document;
typedef struct {
Document *documents;
uint32_t length;
} DocumentArray;
Document documentsRaw[5];
DocumentArray documents = {
.length = 5,
.documents = documentsRaw
};
typedef struct {
intptr_t offset;
intptr_t memory;
} ClayVideoDemo_Arena;
typedef struct {
int32_t selectedDocumentIndex;
float yOffset;
ClayVideoDemo_Arena frameArena;
} ClayVideoDemo_Data;
typedef struct {
int32_t requestedDocumentIndex;
int32_t* selectedDocumentIndex;
} SidebarClickData;
void HandleSidebarInteraction(
Clay_ElementId elementId,
Clay_PointerData pointerData,
intptr_t userData
) {
SidebarClickData *clickData = (SidebarClickData*)userData;
// If this button was clicked
if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
if (clickData->requestedDocumentIndex >= 0 && clickData->requestedDocumentIndex < documents.length) {
// Select the corresponding document
*clickData->selectedDocumentIndex = clickData->requestedDocumentIndex;
}
}
}
ClayVideoDemo_Data ClayVideoDemo_Initialize() {
reefImg = LoadClayImage(L"reef.jpg");
vacImg = LoadClayImage(L"Vacuum.jpg");
documents.documents[0] = (Document){ .image = reefImg, .title = CLAY_STRING("Squirrels"), .contents = CLAY_STRING("There is an image here. It should be of a reef. Try resizing this image.") };
documents.documents[1] = (Document){ .image = vacImg, .title = CLAY_STRING("Images in this example"), .contents = CLAY_STRING("In order to properly render images, make sure that you don't dereference a null or uninitialized pointer. .image attributes should be pointers to HBITMAP values. This example loads images in the same folder as the executable to HBITMAPS stored in a linkedlist, and then on close, the linkedlist is deallocated, and the HBITMAPS are destroyed. You can of course load and destroy HBITMAPS however you like, just make sure .image attributes are pointers to HBITMAP!\n\nIn this example, there is a custom document struct with an image attribute. The render commands always render the document's image, so we have to make sure we give each document a valid image! Otherwise, undefined behavior could happen, most likely in the form of dereferencing an uninitialized pointer.") };
documents.documents[2] = (Document){ .image = vacImg, .title = CLAY_STRING("How to use Images with GDI + Clay"), .contents = CLAY_STRING("Images can be loaded in a fashion similar to the LoadClayImage function in this example for external files, or they can be compiled into the executable as a resource via a .rc file, and retrieved via windows' LoadImage function. This approach lets you skip lengthy allocations, but requires a bit more compilation messing around stuff.\n\n Load HBITMAPS however you like, and remember to destroy them before exiting the program!") };
documents.documents[3] = (Document){ .image = reefImg, .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") };
documents.documents[4] = (Document){ .image = vacImg, .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") };
ClayVideoDemo_Data data = {
.frameArena = { .memory = (intptr_t)malloc(1024) }
};
return data;
}
ClayVideoDemo_Data ClayVideoDemo_Uninitialize() {
cleanupHBitmaps();
}
Clay_RenderCommandArray ClayVideoDemo_CreateLayout(ClayVideoDemo_Data *data) {
data->frameArena.offset = 0;
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
Clay_Color contentBackgroundColor = { 90, 90, 90, 255 };
// Build UI here
CLAY({ .id = CLAY_ID("OuterContainer"),
.backgroundColor = {43, 41, 51, 255 },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = CLAY_PADDING_ALL(16),
.childGap = 16
}
}) {
// Child elements go inside braces
CLAY({ .id = CLAY_ID("HeaderBar"),
.layout = {
.sizing = {
.height = CLAY_SIZING_FIXED(60),
.width = CLAY_SIZING_GROW(0)
},
.padding = { 16, 16, 0, 0 },
.childGap = 16,
.childAlignment = {
.y = CLAY_ALIGN_Y_CENTER
}
},
.backgroundColor = contentBackgroundColor,
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
// Header buttons go here
CLAY({ .id = CLAY_ID("FileButton"),
.layout = { .padding = { 16, 16, 8, 8 }},
.backgroundColor = {140, 140, 140, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(5)
}) {
CLAY_TEXT(CLAY_STRING("File"), CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
bool fileMenuVisible =
Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileButton")))
||
Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileMenu")));
if (fileMenuVisible) { // Below has been changed slightly to fix the small bug where the menu would dismiss when mousing over the top gap
CLAY({ .id = CLAY_ID("FileMenu"),
.floating = {
.attachTo = CLAY_ATTACH_TO_PARENT,
.attachPoints = {
.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
},
},
.layout = {
.padding = {0, 0, 8, 8 }
}
}) {
CLAY({
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = {
.width = CLAY_SIZING_FIXED(200)
},
},
.backgroundColor = {40, 40, 40, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
// Render dropdown items here
RenderDropdownMenuItem(CLAY_STRING("New"));
RenderDropdownMenuItem(CLAY_STRING("Open"));
RenderDropdownMenuItem(CLAY_STRING("Close"));
}
}
}
}
RenderHeaderButton(CLAY_STRING("Edit"));
CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0) }}}) {}
RenderHeaderButton(CLAY_STRING("Upload"));
RenderHeaderButton(CLAY_STRING("Media"));
RenderHeaderButton(CLAY_STRING("Support"));
}
CLAY({
.id = CLAY_ID("LowerContent"),
.layout = { .sizing = layoutExpand, .childGap = 16 }
}) {
CLAY({
.id = CLAY_ID("Sidebar"),
.backgroundColor = contentBackgroundColor,
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.padding = CLAY_PADDING_ALL(16),
.childGap = 8,
.sizing = {
.width = CLAY_SIZING_FIXED(250),
.height = CLAY_SIZING_GROW(0)
}
}
}) {
for (int i = 0; i < documents.length; i++) {
Document document = documents.documents[i];
Clay_LayoutConfig sidebarButtonLayout = {
.sizing = { .width = CLAY_SIZING_GROW(0) },
.padding = CLAY_PADDING_ALL(16)
};
if (i == data->selectedDocumentIndex) {
CLAY({
.layout = sidebarButtonLayout,
.backgroundColor = {120, 120, 120, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 20,
.textColor = { 255, 255, 255, 255 }
}));
}
} else {
SidebarClickData *clickData = (SidebarClickData *)(data->frameArena.memory + data->frameArena.offset);
*clickData = (SidebarClickData) { .requestedDocumentIndex = i, .selectedDocumentIndex = &data->selectedDocumentIndex };
data->frameArena.offset += sizeof(SidebarClickData);
CLAY({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 120, 120, 120, Clay_Hovered() ? 120 : 0 }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
Clay_OnHover(HandleSidebarInteraction, (intptr_t)clickData);
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 20,
.textColor = { 255, 255, 255, 255 }
}));
}
}
}
}
CLAY({ .id = CLAY_ID("MainContent"),
.backgroundColor = contentBackgroundColor,
.scroll = { .vertical = true },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 16,
.padding = CLAY_PADDING_ALL(16),
.sizing = layoutExpand
}
}) {
Document selectedDocument = documents.documents[data->selectedDocumentIndex];
CLAY_TEXT(selectedDocument.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 24,
.textColor = COLOR_WHITE
}));
CLAY({ .image = selectedDocument.image, .layout = { .sizing = layoutExpand } });
CLAY_TEXT(selectedDocument.contents, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 24,
.textColor = COLOR_WHITE
}));
}
}
}
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
for (int32_t i = 0; i < renderCommands.length; i++) {
Clay_RenderCommandArray_Get(&renderCommands, i)->boundingBox.y += data->yOffset;
}
return renderCommands;
}

View File

@ -0,0 +1,220 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "../../../renderers/win32_gdi/clay_renderer_gdi.c"
#define CLAY_IMPLEMENTATION
#include "../../../clay.h"
#include "clay-win32-gdi-image-example.c"
ClayVideoDemo_Data demo_data;
#define APPNAME "Clay GDI Example"
char szAppName[] = APPNAME; // The name of this application
char szTitle[] = APPNAME; // The title bar text
void CenterWindow(HWND hWnd);
long lastMsgTime = 0;
bool ui_debug_mode;
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
// ----------------------- first and last
case WM_CREATE:
CenterWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_MOUSEWHEEL: // scrolling data
{
short zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
// todo: i think GetMessageTime can roll over, so something like if(lastmsgtime > now) ... may be needed
long now = GetMessageTime();
float dt = (now - lastMsgTime) / 1000.00;
lastMsgTime = now;
// little hacky hack to make scrolling *feel* right
if (abs(zDelta) > 100)
{
zDelta = zDelta * .012;
}
Clay_UpdateScrollContainers(true, (Clay_Vector2){.x = 0, .y = zDelta}, dt);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_RBUTTONUP:
case WM_LBUTTONUP:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MOUSEMOVE: // mouse events
{
short mouseX = GET_X_LPARAM(lParam);
short mouseY = GET_Y_LPARAM(lParam);
short mouseButtons = LOWORD(wParam);
Clay_SetPointerState((Clay_Vector2){mouseX, mouseY}, mouseButtons & 0b01);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_SIZE: // resize events
{
RECT r = {0};
if (GetClientRect(hwnd, &r))
{
Clay_Dimensions dim = (Clay_Dimensions){.height = r.bottom - r.top, .width = r.right - r.left};
Clay_SetLayoutDimensions(dim);
}
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_KEYDOWN:
if (VK_ESCAPE == wParam)
{
DestroyWindow(hwnd);
break;
}
if (wParam == VK_F12)
{
Clay_SetDebugModeEnabled(ui_debug_mode = !ui_debug_mode);
break;
}
printf("Key Pressed: %d\r\n", wParam);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
// ----------------------- render
case WM_PAINT:
{
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data);
Clay_Win32_Render(hwnd, renderCommands);
break;
}
// ----------------------- let windows do all other stuff
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
bool didAllocConsole = false;
void HandleClayErrors(Clay_ErrorData errorData)
{
if (!didAllocConsole)
{
didAllocConsole = AllocConsole();
}
printf("Handle Clay Errors: %s\r\n", errorData.errorText.chars);
}
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
WNDCLASS wc;
HWND hwnd;
demo_data = ClayVideoDemo_Initialize();
uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Initialize(clayMemory, (Clay_Dimensions){.width = 800, .height = 600}, (Clay_ErrorHandler){HandleClayErrors}); // This final argument is new since the video was published
Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, NULL);
ZeroMemory(&wc, sizeof wc);
wc.hInstance = hInstance;
wc.lpszClassName = szAppName;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.style = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW;
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
if (FALSE == RegisterClass(&wc))
return 0;
hwnd = CreateWindow(
szAppName,
szTitle,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
800, // CW_USEDEFAULT,
600, // CW_USEDEFAULT,
0,
0,
hInstance,
0);
if (hwnd == NULL)
return 0;
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ClayVideoDemo_Uninitialize(); // Clean up image memory and such
return msg.wParam;
}
void CenterWindow(HWND hwnd_self)
{
HWND hwnd_parent;
RECT rw_self, rc_parent, rw_parent;
int xpos, ypos;
hwnd_parent = GetParent(hwnd_self);
if (NULL == hwnd_parent)
hwnd_parent = GetDesktopWindow();
GetWindowRect(hwnd_parent, &rw_parent);
GetClientRect(hwnd_parent, &rc_parent);
GetWindowRect(hwnd_self, &rw_self);
xpos = rw_parent.left + (rc_parent.right + rw_self.left - rw_self.right) / 2;
ypos = rw_parent.top + (rc_parent.bottom + rw_self.top - rw_self.bottom) / 2;
SetWindowPos(
hwnd_self, NULL,
xpos, ypos, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
//+---------------------------------------------------------------------------

View File

@ -0,0 +1,161 @@
// Must be defined in one file, _before_ #include "clay.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255};
const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
void HandleClayErrors(Clay_ErrorData errorData) {
// See the Clay_ErrorData struct for more information
printf("%s", errorData.errorText.chars);
switch(errorData.errorType) {
// etc
}
}
// Example measure text function
static inline Clay_Dimensions MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData) {
// Clay_TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
// Note: Clay_String->chars is not guaranteed to be null terminated
return (Clay_Dimensions) {
.width = text.length * config->fontSize, // <- this will only work for monospace fonts, see the renderers/ directory for more advanced text measurement
.height = config->fontSize
};
}
// Layout config is just a struct that can be declared statically, or inline
Clay_ElementDeclaration sidebarItemConfig = (Clay_ElementDeclaration) {
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) }
},
.backgroundColor = COLOR_ORANGE
};
// Re-useable components are just normal functions
void SidebarItemComponent() {
CLAY(sidebarItemConfig) {
// children go here...
}
}
// This may be slow for large numbers of images,
// consider allocating and initializing interfaces
// once outside of this function if it becomes an issue
HBITMAP LoadImageToBitmap(LPCWSTR filename)
{
IWICImagingFactory *imgFactory = NULL;
IWICBitmapDecoder *bmpDecoder = NULL;
IWICBitmapFrameDecode *bmpFrame = NULL;
IWICFormatConverter *formatConverter = NULL;
HBITMAP hBitmap = NULL;
void *bits = NULL;
UINT width = 0;
UINT height = 0;
BITMAPINFO bmi = {0};
CoInitialize(NULL); // Lets us use Windows COM correctly
CoCreateInstance(
&CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IWICImagingFactory,
(void **) &imgFactory);
imgFactory->lpVtbl->CreateDecoderFromFilename(
imgFactory,
filename,
NULL,
GENERIC_READ,
WICDecodeMetadataCacheOnLoad,
&bmpDecoder);
bmpDecoder->lpVtbl->GetFrame(decoder, 0, &bmpFrame);
imgFactory->lpVtbl->CreateFormatConverter(factory, &converter);
formatConverter->lpVtbl->Initialize(
formatConverter,
(IWICBitmapSource*) bmpFrame,
&GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone,
NULL,
0.0,
WICBitmapPaletteTypeCustom);
frame->lpVtbl->GetSize(frame, &width, &height);
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -((LONG) height); // Negative makes it a top down bitmap
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
hBitmap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
formatConverter->lpVtble->CopyPixels(converter, NULL, width * 4, width * height * 4, (BYTE*) bits);
if (formatConverter) converter->lpVtbl->Release(formatConverter);
if (bmpFrame) frame->lpVtbl->Release(bmpFrame);
if (bmpDecoder) decoder->lpVtbl->Release(bmpDecoder);
if (imgFactory) factory->lpVtbl->Release(imgFactory);
CoUninitialize();
return hBitmap;
}
int main() {
// Note: malloc is only used here as an example, any allocator that provides
// a pointer to addressable memory of at least totalMemorySize will work
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
// Note: screenWidth and screenHeight will need to come from your environment, Clay doesn't handle window related tasks
Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight }, (Clay_ErrorHandler) { HandleClayErrors });
// Load image resource
hBitmap profilePicture = LoadImage(L"reef.jpg");
while(renderLoop()) { // Will be different for each renderer / environment
// Optional: Update internal layout dimensions to support resizing
Clay_SetLayoutDimensions((Clay_Dimensions) { screenWidth, screenHeight });
// Optional: Update internal pointer position for handling mouseover / click / touch events - needed for scrolling & debug tools
Clay_SetPointerState((Clay_Vector2) { mousePositionX, mousePositionY }, isMouseDown);
// Optional: Update internal pointer position for handling mouseover / click / touch events - needed for scrolling and debug tools
Clay_UpdateScrollContainers(true, (Clay_Vector2) { mouseWheelX, mouseWheelY }, deltaTime);
// All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout
Clay_BeginLayout();
// An example of laying out a UI with a fixed width sidebar and flexible width main content
CLAY({ .id = CLAY_ID("OuterContainer"), .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }, .backgroundColor = {250,250,255,255} }) {
CLAY({
.id = CLAY_ID("SideBar"),
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 },
.backgroundColor = COLOR_LIGHT
}) {
CLAY({ .id = CLAY_ID("ProfilePictureOuter"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) {
CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = profilePicture, .sourceDimensions = {60, 60} } }) {}
CLAY_TEXT(CLAY_STRING("Clay - UI Library"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255, 255, 255, 255} }));
}
// Standard C code like loops etc work inside components
for (int i = 0; i < 5; i++) {
SidebarItemComponent();
}
CLAY({ .id = CLAY_ID("MainContent"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) {}
}
}
// All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
// More comprehensive rendering examples can be found in the renderers/ directory
for (int i = 0; i < renderCommands.length; i++) {
Clay_RenderCommand *renderCommand = &renderCommands.internalArray[i];
switch (renderCommand->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
DrawRectangle( renderCommand->boundingBox, renderCommand->renderData.rectangle.backgroundColor);
}
// ... Implement handling of other command types
}
}
}
}

View File

@ -1,3 +1,4 @@
#define WIN32_LEAN_AND_MEAN // Reduces lots of clunk we aren't using here
#include <Windows.h>
#include "../../clay.h"
@ -177,12 +178,58 @@ void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands)
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE:
{
// TODO: Add blending support by replacing StretchBlt with alphablend
// Add corner radius support by setting alpha value of out-of-bounds
// pixels to 0
// Add tinting support by creating an hbitmap with a specific color,
// and alplhablending over the image.
// case CLAY_RENDER_COMMAND_TYPE_IMAGE:
// {
// // TODO: i couldnt get the win 32 api to load a bitmap.... So im punting on this one :(
// break;
// }
Clay_ImageRenderData *config = &renderCommand->renderData.image;
HBITMAP img_hbmMem = *((HBITMAP *) config->imageData);
HDC img_hdcMem = CreateCompatibleDC(NULL);
SelectObject(img_hdcMem, img_hbmMem);
int srcWidth = config->sourceDimensions.width;
int srcHeight = config->sourceDimensions.height;
float widthRatio = boundingBox.width / srcWidth;
float heightRatio = boundingBox.height / srcHeight;
int dstX = boundingBox.x;
int dstY = boundingBox.y;
int dstWidth;
int dstHeight;
// Win32 GDI's StretchBlt can scale our image, but will not preserve aspect ratio
if (widthRatio > heightRatio)
{
dstHeight = boundingBox.height;
dstWidth = (int) (srcWidth * widthRatio);
if (dstWidth > boundingBox.width)
{
dstWidth = boundingBox.width;
}
}
else
{
dstWidth = boundingBox.width;
dstHeight = (int) (srcWidth * heightRatio);
if (dstHeight > boundingBox.height)
{
dstHeight = boundingBox.height;
}
}
SetStretchBltMode(renderer_hdcMem, HALFTONE); // Halftone gives really nice scaled images
SetBrushOrgEx(renderer_hdcMem, dstX, dstY, NULL); // Necessary for halftone alignment
StretchBlt(renderer_hdcMem, dstX, dstY, dstWidth, dstHeight, img_hdcMem, 0, 0, srcWidth, srcHeight, SRCCOPY);
DeleteDC(img_hdcMem);
break;
}
default:
printf("Unhandled render command %d\r\n", renderCommand->commandType);
@ -238,4 +285,4 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay
textSize.height = textHeight;
return textSize;
}
}