GDI — Bitmaps & Device Contexts
Free API implements a minimal GDI subset sufficient for games that pre-render everything into off-screen bitmaps and blit them to the window each frame. Real GDI drawing primitives (lines, rectangles, text, brushes, pens) are not implemented.
How GDI is backed in Free API
LoadImageA("sprites.bmp") │ SDL_LoadBMP → SDL_ConvertSurface(RGBA32) ▼ HBITMAP — wraps an SDL_Surface (RGBA32, heap-allocated) CreateCompatibleDC(NULL) │ allocates an internal DC record ▼ HDC (memory) — has a "selected bitmap" slot (initially empty) SelectObject(hdcMem, hBmp) │ attaches the bitmap to the DC ▼ HDC (memory) ← points to HBITMAP's SDL_Surface pixels StretchBlt(hdcDst, ...) │ nearest-neighbour copy from src SDL_Surface → dst SDL_Surface ▼ SDL_RenderTexture displayed on screen next frame
Loading bitmaps from disk
LoadImageA
HANDLE WINAPI LoadImageA(
HINSTANCE hInst, // ignored (pass NULL)
LPCSTR name, // file path when LR_LOADFROMFILE is set
UINT type, // must be IMAGE_BITMAP (0)
int cx, // desired width (0 = use natural size)
int cy, // desired height (0 = use natural size)
UINT fuLoad // must include LR_LOADFROMFILE
);
// Returns HBITMAP cast to HANDLE, or NULL on error.
Only IMAGE_BITMAP + LR_LOADFROMFILE is implemented.
Resource loading (without LR_LOADFROMFILE) always returns NULL.
The file is loaded via SDL_LoadBMP and converted to RGBA32 internally.
Paths are normalised (backslashes → slashes, drive letter removed) before
the file is opened, so Windows-style paths work on Linux.
| Flag constant | Value | Meaning |
|---|---|---|
IMAGE_BITMAP | 0 | Required — load as bitmap. |
LR_LOADFROMFILE | 0x0010 | Required — load from file path, not resource. |
LR_CREATEDIBSECTION | 0x2000 | Accepted but ignored — Free API always creates an RGBA32 surface. |
// Load from a relative path (backslash or slash both work) HBITMAP hBackground = (HBITMAP)LoadImageA( NULL, "data\\background.bmp", // or "data/background.bmp" IMAGE_BITMAP, 0, 0, // 0,0 = keep natural size LR_LOADFROMFILE); if (!hBackground) { OutputDebugString("Failed to load background.bmp"); return FALSE; } // Load and scale to a specific size HBITMAP hIcon = (HBITMAP)LoadImageA( NULL, "data\\icon.bmp", IMAGE_BITMAP, 32, 32, LR_LOADFROMFILE);
CreateBitmap
HBITMAP WINAPI CreateBitmap(
int nWidth, // bitmap width in pixels
int nHeight, // bitmap height in pixels
UINT nPlanes, // colour planes (1 for RGB)
UINT nBitCount,// bits per pixel (8, 16, 24, or 32)
const void* lpBits // initial pixel data, or NULL for blank
);
Creates an SDL3 RGBA32 surface. If lpBits is not NULL, raw pixel
data is copied and converted from the specified bit-depth. A blank (all-zero) surface
is created when lpBits is NULL. Supported nBitCount values:
8 (treated as 8-bit indexed, palette not applied), 16 (RGB565), 24 (RGB), 32 (RGBA).
// Create a 320×200 blank canvas HBITMAP hCanvas = CreateBitmap(320, 200, 1, 32, NULL); HDC hdcCanvas = CreateCompatibleDC(NULL); HGDIOBJ hOld = SelectObject(hdcCanvas, hCanvas); // ... draw into hdcCanvas via SetPixel, StretchBlt, etc. ... // Cleanup SelectObject(hdcCanvas, hOld); DeleteDC(hdcCanvas); DeleteObject(hCanvas);
Memory DCs — CreateCompatibleDC, SelectObject, DeleteDC
A device context (DC) is a drawing surface handle. In Free API, DCs come in two flavours: memory DCs (off-screen) and the window surface DC.
| Function | Status | Description |
|---|---|---|
CreateCompatibleDC(HDC hdc) |
PARTIAL | Allocates a new memory DC. The hdc parameter is ignored (no real compatibility with an existing DC). The new DC has no bitmap selected until SelectObject is called. |
SelectObject(HDC hdc, HGDIOBJ h) |
PARTIAL | Selects a bitmap (HBITMAP) into a memory DC and returns the previously selected object. Only bitmaps are supported — brushes, pens, and fonts are not. |
DeleteDC(HDC hdc) |
PARTIAL | Releases the internal DC record. Does NOT free any bitmap that was selected into it — call DeleteObject(hBmp) separately. |
DeleteObject(HGDIOBJ ho) |
PARTIAL | Frees the underlying SDL3 surface. Do not call while the object is still selected in a DC. Returns TRUE on success. |
// ── Create ── HBITMAP hBmp = (HBITMAP)LoadImageA(NULL, "map.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); HDC hdcMem = CreateCompatibleDC(NULL); HGDIOBJ hOld = SelectObject(hdcMem, hBmp); // select in // ── Use ── StretchBlt(hdcDest, 0, 0, 800, 600, hdcMem, 0, 0, 320, 200, SRCCOPY); // ── Cleanup (always in this order) ── SelectObject(hdcMem, hOld); // restore previous selection first DeleteDC(hdcMem); // release DC DeleteObject(hBmp); // free surface
StretchBlt — copying and scaling bitmaps
BOOL WINAPI StretchBlt(
HDC hdcDest, // destination DC
int xDest, int yDest, // destination top-left
int wDest, int hDest, // destination size
HDC hdcSrc, // source DC (must have bitmap selected)
int xSrc, int ySrc, // source top-left
int wSrc, int hSrc, // source size
DWORD rop // raster op — only SRCCOPY supported
);
Supported: SRCCOPY from a memory DC (with a bitmap selected) to the window surface DC, or between two memory DCs. Scaling uses nearest-neighbour interpolation.
Not supported: raster operations other than SRCCOPY, alpha-blending, colour-key transparency, mirroring (negative width/height), or blitting to DCs that have no bitmap selected.
// 1. Copy a sprite (no scaling) StretchBlt( hdcWindow, dstX, dstY, 64, 64, hdcSprites, spriteSheetX, spriteSheetY, 64, 64, SRCCOPY); // 2. Scale up a 320×200 game canvas to fill an 800×600 window StretchBlt( hdcWindow, 0, 0, 800, 600, hdcCanvas, 0, 0, 320, 200, SRCCOPY); // 3. Copy a sub-region (source tile from a tileset) int tileCol = 3, tileRow = 2; int TILE = 32; StretchBlt( hdcDest, screenX, screenY, TILE, TILE, hdcTiles, tileCol * TILE, tileRow * TILE, TILE, TILE, SRCCOPY);
SRCCOPY is defined as 0x00CC0020. It is the only raster
operation code that does anything in Free API. All others are silently accepted but
fall back to a plain copy.
GetPixel / SetPixel & colour macros
| Function / Macro | Status | Description |
|---|---|---|
GetPixel(HDC hdc, int x, int y) |
PARTIAL | Reads the RGB colour at pixel (x, y) from the selected SDL3 surface. Returns CLR_INVALID (0xFFFFFFFF) if out of bounds or no bitmap selected. |
SetPixel(HDC hdc, int x, int y, COLORREF color) |
PARTIAL | Writes an RGB colour to pixel (x, y) in the selected surface. Returns the colour actually written, or CLR_INVALID on error. |
RGB(r, g, b) |
IMPLEMENTED | Macro that packs three byte values into a COLORREF: (r) | (g << 8) | (b << 16). |
GetRValue(col) / GetGValue(col) / GetBValue(col) |
IMPLEMENTED | Extract the red, green, or blue component from a COLORREF. |
CLR_INVALID |
HEADER_ONLY | 0xFFFFFFFF — sentinel returned when a pixel operation fails. |
// Read a pixel COLORREF col = GetPixel(hdcMem, 10, 20); if (col != CLR_INVALID) { BYTE r = GetRValue(col); BYTE g = GetGValue(col); BYTE b = GetBValue(col); } // Write pixels — simple flood-fill loop for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { SetPixel(hdcCanvas, x, y, RGB(255, 0, 0)); // red square } } // Colour constant examples COLORREF red = RGB(255, 0, 0); COLORREF green = RGB( 0, 255, 0); COLORREF blue = RGB( 0, 0, 255); COLORREF white = RGB(255, 255, 255); COLORREF black = RGB( 0, 0, 0); COLORREF yellow = RGB(255, 255, 0);
GetObjectA, GetDeviceCaps & palette
| Function | Status | Description |
|---|---|---|
GetObjectA(HANDLE h, int c, LPVOID pv) |
PARTIAL | When h is an HBITMAP and pv points to a BITMAP struct (c = sizeof(BITMAP)), fills width, height, planes, bitsPerPixel and bits pointer from the underlying SDL surface. Returns the byte count filled, or 0 on error. |
GetDeviceCaps(HDC hdc, int index) |
PARTIAL | Returns 256 when index == SIZEPALETTE; returns 0 for all other indices. Many games use SIZEPALETTE to determine colour depth. |
GetSystemPaletteEntries(HDC, start, count, entries) |
PARTIAL | Fills PALETTEENTRY array with a grayscale ramp (R=G=B=index). Satisfies palette-aware games that expect 256 entries. |
BITMAP bm; if (GetObjectA(hBmp, sizeof(bm), &bm)) { printf("Width=%ld Height=%ld BPP=%d\n", bm.bmWidth, bm.bmHeight, bm.bmBitsPixel); }
Data structures
| Structure | Status | Fields & Notes |
|---|---|---|
BITMAP |
PARTIAL | bmType, bmWidth, bmHeight, bmWidthBytes, bmPlanes, bmBitsPixel, bmBits. Filled by GetObjectA. |
BITMAPINFOHEADER |
STUB | Standard BMP info header layout. Declared for compilation compatibility with code that reads .BMP files manually. |
BITMAPFILEHEADER |
STUB | 14-byte BMP file header. Declared for compilation compatibility. |
RGBQUAD |
STUB | 4-byte BGR+reserved colour entry used in BMP palette tables. |
PALETTEENTRY |
PARTIAL | peRed, peGreen, peBlue, peFlags. Used by GetSystemPaletteEntries. Also shared with DirectDraw compatibility code. |