Поскольку ты хочешь картинку на форме, а не в меню, и при этом работаешь на чистом C с cl.exe (без C++/WinForms), есть два рабочих пути: простой (BMP без прозрачности) и с прозрачностью (PNG через GDI+).
Вариант 1: BMP на форме (самый простой, без GDI+)
Подходит, если картинка — обычный BMP без прозрачных краёв.
.rc (resources.rc)
#include <windows.h>
IDB_LOGO BITMAP "logo.bmp"
main.c
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define IDB_LOGO 201
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
const char CLASS_NAME[] = "ImgForm";
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0, CLASS_NAME, "Картинка на форме",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
NULL, NULL, hInstance, NULL
);
if (!hwnd) return 0;
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
static HBITMAP hLogo = NULL;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_CREATE: {
HINSTANCE hInst = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
hLogo = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_LOGO));
return 0;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (hLogo) {
HDC hMemDC = CreateCompatibleDC(hdc);
HBITMAP hOld = (HBITMAP)SelectObject(hMemDC, hLogo);
BITMAP bm;
GetObject(hLogo, sizeof(bm), &bm);
// рисуем картинку в точке (50,50)
BitBlt(hdc, 50, 50, bm.bmWidth, bm.bmHeight,
hMemDC, 0, 0, SRCCOPY);
SelectObject(hMemDC, hOld);
DeleteDC(hMemDC);
}
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
if (hLogo) DeleteObject(hLogo);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
Сборка:
rc resources.rc
cl /TC main.c resources.res /link user32.lib gdi32.lib
Картинка logo.bmp должна лежать рядом с исходниками (или указать полный путь).
Вариант 2: PNG с прозрачными краями на форме (через GDI+)
Если нужны прозрачные края — BMP не годится: нужен PNG и GDI+.
Важное: GDI+ в чистом C
GDI+ API сделан под C++, но его можно использовать из C: вместо классов используем указатели на интерфейсы и вызовы через таблицы функций. Для простоты покажу вариант с использованием C++ синтаксиса только для инициализации GDI+ и работы с Image/Graphics, но логика остаётся максимально близкой к C‑подходу. Если критично «строго C без C++», можно использовать AlphaBlend с 32‑bit DIB вручную, но это заметно сложнее.
gdiplus_img.c (упрощённо)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
ULONG_PTR gdiToken;
Gdiplus::Image *pImage = NULL;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
GdiplusStartupInput input = {1};
GdiplusStartup(&gdiToken, &input, NULL);
// PNG с прозрачностью
pImage = Gdiplus::Image::FromFile(L"logo.png");
const char CLASS_NAME[] = "GdiPlusImg";
WNDCLASS wc = {0};
wc.lpfnWndProc = [](HWND h, UINT m, WPARAM w, LPARAM l) -> LRESULT {
if (m == WM_PAINT) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(h, &ps);
Gdiplus::Graphics g(hdc);
if (pImage) g.DrawImage(pImage, 50, 50);
EndPaint(h, &ps);
return 0;
} else if (m == WM_DESTROY) {
delete pImage;
GdiplusShutdown(gdiToken);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(h, m, w, l);
};
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClass(&wc);
CreateWindowEx(0, CLASS_NAME, "PNG с прозрачностью",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
640, 480, NULL, NULL, hInstance, NULL);
ShowWindow(GetLastActivePopup(), nShowCmd);
UpdateWindow(GetLastActivePopup());
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
Сборка:
cl /TP gdiplus_img.c /link gdiplus.lib user32.lib gdi32.lib
/TP — компилировать как C++ (нужно для GDI+ классов). Если принципиально только /TC (чистый C), тогда вместо GDI+ придётся вручную готовить 32‑bit DIB с альфа‑каналом и вызывать AlphaBlend.
PNG logo.png должен лежать рядом с .exe и иметь альфа‑канал.
Что выбрать под твои задачи
Если ты делаешь шахматную программу (учитывая прошлые вопросы про битборды, Syzygy и т. д.) и нужна, например, простая иконка/логотип на форме без сложной графики — Вариант 1 (BMP + BitBlt) проще, быстрее и полностью на чистом C.
Если нужны красивые картинки с прозрачными краями (например, фигуры или декоративные элементы) — Вариант 2 (GDI+).
Практические советы
Формат BMP для Варианта 1: лучше 24‑bit или 32‑bit BMP без сжатия; не используй PNG/JPG напрямую в ресурсе — BITMAP в .rc работает только с BMP.
Позиционирование: в BitBlt меняй координаты (50, 50) на любые нужные. Чтобы центрировать картинку, считай размеры окна в WM_SIZE и перерисовывай.
Ресурсы vs файлы: в Варианте 1 картинка встроена в .exe; в Варианте 2 (GDI+) проще читать файл с диска — так удобнее менять картинки без пересборки.
Учитывая твой интерес к сборке через cl.exe, ресурсам и «чистому C», я могу дать полностью C‑версию с AlphaBlend (без GDI+), если скажешь, что важнее: «строго C» или «максимальная простота кода». Что ближе?
Да, в Win32 на чистом C можно:
обрабатывать события мыши (WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP и т. д.);
обрабатывать нажатия клавиш (WM_KEYDOWN, WM_CHAR);
рисовать PNG с альфа‑каналом (прозрачностью) — но для этого почти всегда используют GDI+ либо AlphaBlend с 32‑bit DIB.
Учитывая твой стек (чистый C, cl.exe, шахматные задачи), покажу рабочий пример, где:
рисуется PNG с прозрачностью через GDI+;
отслеживаются клики мыши и нажатия клавиш;
всё собрано одной командой через cl.exe.
Полный пример: окно, PNG с прозрачностью, события мыши и клавиатуры
main.c
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
ULONG_PTR gdiToken;
Gdiplus::Image *pImage = NULL;
// Глобальные переменные для демонстрации реакции на ввод
int lastX = 0, lastY = 0;
char lastKey = 0;
BOOL mouseDown = FALSE;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_CREATE:
// Инициализация GDI+ уже сделана в WinMain
return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (pImage) {
Gdiplus::Graphics g(hdc);
// Рисуем PNG с прозрачностью
g.DrawImage(pImage, 50, 50);
}
EndPaint(hwnd, &ps);
return 0;
}
// --- События мыши ---
case WM_LBUTTONDOWN: {
lastX = LOWORD(lParam);
lastY = HIWORD(lParam);
mouseDown = TRUE;
// Можно что-то сделать при клике
InvalidateRect(hwnd, NULL, FALSE); // перерисовать, если нужно показать эффект
return 0;
}
case WM_MOUSEMOVE: {
if (mouseDown) {
lastX = LOWORD(lParam);
lastY = HIWORD(lParam);
InvalidateRect(hwnd, NULL, FALSE);
}
return 0;
}
case WM_LBUTTONUP: {
mouseDown = FALSE;
return 0;
}
// --- События клавиатуры ---
case WM_KEYDOWN: {
// wParam = виртуальный код клавиши (VK_*)
// Например, VK_ESCAPE, VK_LEFT, VK_A и т.д.
// Здесь можно реагировать на конкретные клавиши
return 0;
}
case WM_CHAR: {
// wParam = ASCII-код символа (для печатаемых символов)
lastKey = (char)wParam;
InvalidateRect(hwnd, NULL, FALSE);
return 0;
}
case WM_DESTROY: {
delete pImage;
GdiplusShutdown(gdiToken);
PostQuitMessage(0);
return 0;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
// Инициализация GDI+
GdiplusStartupInput input = {1};
GdiplusStartup(&gdiToken, &input, NULL);
// Загрузка PNG с прозрачностью (альфа‑канал)
pImage = Gdiplus::Image::FromFile(L"logo.png");
const char CLASS_NAME[] = "ImgInputDemo";
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0, CLASS_NAME, "PNG + мышь + клава",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
NULL, NULL, hInstance, NULL
);
if (!hwnd) return 0;
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
Как собрать
cl /TP main.c /link gdiplus.lib user32.lib gdi32.lib
/TP обязателен: GDI+ в стандартной поставке — это C++ API (классы Image, Graphics). Если принципиально только чистый C без C++ синтаксиса, тогда вместо GDI+ используют AlphaBlend и 32‑bit DIB вручную — код будет заметно длиннее.
Рядом с .exe должен лежать logo.png с альфа‑каналом.
Какие события реально пригодятся для шахматной программы
Учитывая твои прошлые задачи (битборды, Syzygy, SEE, движок):
WM_LBUTTONDOWN — самый нужный: по нему определяют, куда кликнул пользователь, берут координаты, переводят в клетку доски и генерируют ходы.
WM_MOUSEMOVE — можно подсвечивать клетку под курсором или показывать возможные ходы «в предпросмотре».
WM_KEYDOWN — например, VK_ESCAPE для выхода, VK_F2 для переключения режима, стрелки для навигации по истории ходов и т. п.
WM_CHAR — если делаешь ввод текста (комментарии, PGN и т. д.).
Координаты мыши приходят в lParam:
int x = LOWORD(lParam);
int y = HIWORD(lParam);
Чтобы превратить их в клетку доски, обычно делают:
int cellX = (x - boardX) / cellSize;
int cellY = (y - boardY) / cellSize;
Если нужна строго чистая C‑реализация без C++ (без GDI+)
Тогда для PNG/альфы используют AlphaBlend из msimg32.lib и 32‑bit DIB с альфа‑каналом:
Читают PNG (или заранее готовят 32‑bit BMP с альфа‑каналом).
Создают совместимый DC и битмап.
Вызывают AlphaBlend(&BLENDFUNCTION, ...).
Это полностью на C, но требует аккуратной работы с заголовками DIB и выравниванием строк. Если скажешь, что важнее — «строго C» или «минимум кода» — могу дать вариант именно с AlphaBlend.
Практические советы под твой стек
PNG с прозрачностью — самый простой путь: GDI+ (DrawImage).
Мышь — чаще всего нужен именно клик (WM_LBUTTONDOWN) и координаты.
Клава — для горячих клавиш и отладки (например, «показать битборд», «переключить движок»).
Ресурсы — картинку можно держать как файл рядом с .exe (удобно менять без пересборки), либо встроить в ресурс как RCDATA и читать из него.
Если скажешь, какой именно сценарий тебе нужен (например: «клик по клетке — подсветить возможные ходы», или «нажал F1 — показать подсказку», или «строго C без C++»), дам адаптированный фрагмент под эту задачу.