程序如下:出處https://github.com/tsl0922/EPD-nRF5?tab=readme-ov-file
// GUI emulator for Windows
// This code is a simple Windows GUI application that emulates the display of an e-paper device.
#include <windows.h>
#include <stdint.h>
#include <time.h>
#include "GUI.h"#define BITMAP_WIDTH 400
#define BITMAP_HEIGHT 300
#define WINDOW_WIDTH 400
#define WINDOW_HEIGHT 340
#define WINDOW_TITLE TEXT("Emurator")// Global variables
HINSTANCE g_hInstance;
HWND g_hwnd;
display_mode_t g_display_mode = MODE_CALENDAR; // Default to calendar mode
BOOL g_bwr_mode = TRUE; // Default to BWR mode
time_t g_display_time;
struct tm g_tm_time;// Convert bitmap data from e-paper format to Windows DIB format
static uint8_t *convertBitmap(uint8_t *bitmap, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {int bytesPerRow = ((w + 31) / 32) * 4; // Round up to nearest 4 bytesint totalSize = bytesPerRow * h;// Allocate memory for converted bitmapuint8_t *convertedBitmap = (uint8_t*)malloc(totalSize);if (convertedBitmap == NULL) return NULL;memset(convertedBitmap, 0, totalSize);int ePaperBytesPerRow = (w + 7) / 8; // E-paper buffer stridefor (int row = 0; row < h; row++) {for (int col = 0; col < w; col++) {// Calculate byte and bit position in e-paper bufferint bytePos = row * ePaperBytesPerRow + col / 8;int bitPos = 7 - (col % 8); // MSB first (typical e-paper format)// Check if the bit is set in the e-paper bufferint isSet = (bitmap[bytePos] >> bitPos) & 0x01;// Calculate byte and bit position in Windows DIBint dibBytePos = row * bytesPerRow + col / 8;int dibBitPos = 7 - (col % 8); // MSB first for DIB too// Set the bit in the Windows DIB if it's set in the e-paper bufferif (isSet) {convertedBitmap[dibBytePos] |= (1 << dibBitPos);}}}return convertedBitmap;
}// Implementation of the buffer_callback function
void DrawBitmap(uint8_t *black, uint8_t *color, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {HDC hdc;RECT clientRect;int scale = 1;// Get the device context for immediate drawinghdc = GetDC(g_hwnd);if (!hdc) return;// Get client area for positioningGetClientRect(g_hwnd, &clientRect);// Calculate position to center the entire bitmap in the windowint drawX = (clientRect.right - BITMAP_WIDTH * scale) / 2;int drawY = (clientRect.bottom - BITMAP_HEIGHT * scale) / 2;// Create DIB for visible pixelsBITMAPINFO bmi;ZeroMemory(&bmi, sizeof(BITMAPINFO));bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);bmi.bmiHeader.biWidth = w;bmi.bmiHeader.biHeight = -h; // Negative for top-down bitmapbmi.bmiHeader.biPlanes = 1;bmi.bmiHeader.biBitCount = 1;bmi.bmiHeader.biCompression = BI_RGB;uint8_t *convertedBitmap = convertBitmap(black, x, y, w, h);if (convertedBitmap == NULL) {ReleaseDC(g_hwnd, hdc);return;}// Set colors for black and white displaybmi.bmiColors[0].rgbBlue = 0;bmi.bmiColors[0].rgbGreen = 0;bmi.bmiColors[0].rgbRed = 0;bmi.bmiColors[0].rgbReserved = 0;bmi.bmiColors[1].rgbBlue = 255;bmi.bmiColors[1].rgbGreen = 255;bmi.bmiColors[1].rgbRed = 255;bmi.bmiColors[1].rgbReserved = 0;// Draw the black layerStretchDIBits(hdc,drawX + x * scale, drawY + y * scale, // Destination positionw * scale, h * scale, // Destination size0, 0, // Source positionw, h, // Source sizeconvertedBitmap, // Converted bitmap bits&bmi, // Bitmap infoDIB_RGB_COLORS, // UsageSRCCOPY); // Raster operation codefree(convertedBitmap);// Handle color layer if present (red in BWR displays)if (color) {// Allocate memory for converted color bitmapuint8_t *convertedColor = convertBitmap(color, x, y, w, h);if (convertedColor) {// Set colors for red overlaybmi.bmiColors[0].rgbBlue = 255;bmi.bmiColors[0].rgbGreen = 255;bmi.bmiColors[0].rgbRed = 0;bmi.bmiColors[0].rgbReserved = 0;bmi.bmiColors[1].rgbBlue = 0;bmi.bmiColors[1].rgbGreen = 0;bmi.bmiColors[1].rgbRed = 0;bmi.bmiColors[1].rgbReserved = 0;// Draw red overlayStretchDIBits(hdc,drawX + x * scale, drawY + y * scale, // Destination positionw * scale, h * scale, // Destination size0, 0, // Source positionw, h, // Source sizeconvertedColor, // Converted bitmap bits&bmi, // Bitmap infoDIB_RGB_COLORS, // UsageSRCINVERT); // Use XOR operation to blendfree(convertedColor);}}// Release the device contextReleaseDC(g_hwnd, hdc);
}// Window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {switch (message) {case WM_CREATE:// Initialize the display timeg_display_time = time(NULL) + 8*3600;// Set a timer to update the CLOCK periodically (every second)SetTimer(hwnd, 1, 1000, NULL);return 0;case WM_TIMER:if (g_display_mode == MODE_CLOCK) {g_display_time = time(NULL) + 8*3600;if (g_display_time % 60 == 0) {InvalidateRect(hwnd, NULL, FALSE);}}return 0;case WM_PAINT: {PAINTSTRUCT ps;HDC hdc = BeginPaint(hwnd, &ps);// Get client rect for calculationsRECT clientRect;GetClientRect(hwnd, &clientRect);// Clear the entire client area with a solid colorHBRUSH bgBrush = CreateSolidBrush(RGB(240, 240, 240));FillRect(hdc, &clientRect, bgBrush);DeleteObject(bgBrush);// Use the stored timestampgui_data_t data = {.bwr = g_bwr_mode,.width = BITMAP_WIDTH,.height = BITMAP_HEIGHT,.timestamp = g_display_time,.temperature = 25,.voltage = 3.2f,};// Call DrawGUI to render the interface, passing the BWR modeDrawGUI(&data, DrawBitmap, g_display_mode);EndPaint(hwnd, &ps);return 0;}case WM_KEYDOWN:// Toggle display mode with spacebarif (wParam == VK_SPACE) {if (g_display_mode == MODE_CLOCK)g_display_mode = MODE_CALENDAR;elseg_display_mode = MODE_CLOCK;InvalidateRect(hwnd, NULL, TRUE);}// Toggle BWR mode with R keyelse if (wParam == 'R') {g_bwr_mode = !g_bwr_mode;InvalidateRect(hwnd, NULL, TRUE);}// Handle arrow keys for month/day adjustmentelse if (wParam == VK_UP || wParam == VK_DOWN || wParam == VK_LEFT || wParam == VK_RIGHT) {// Get the current time structureg_tm_time = *localtime(&g_display_time);// Up/Down adjusts monthif (wParam == VK_UP) {g_tm_time.tm_mon++;if (g_tm_time.tm_mon > 11) {g_tm_time.tm_mon = 0;g_tm_time.tm_year++;}}else if (wParam == VK_DOWN) {g_tm_time.tm_mon--;if (g_tm_time.tm_mon < 0) {g_tm_time.tm_mon = 11;g_tm_time.tm_year--;}}// Left/Right adjusts dayelse if (wParam == VK_RIGHT) {g_tm_time.tm_mday++;}else if (wParam == VK_LEFT) {g_tm_time.tm_mday--;}// Convert back to time_tg_display_time = mktime(&g_tm_time);// Force redrawInvalidateRect(hwnd, NULL, TRUE);}return 0;case WM_DESTROY:KillTimer(hwnd, 1);PostQuitMessage(0);return 0;default:return DefWindowProc(hwnd, message, wParam, lParam);}
}// Main entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {g_hInstance = hInstance;// Register window classWNDCLASSA wc = {0}; // Using WNDCLASSA for ANSI versionwc.style = CS_HREDRAW | CS_VREDRAW;wc.lpfnWndProc = WndProc;wc.hInstance = hInstance;wc.hCursor = LoadCursor(NULL, IDC_ARROW);wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.lpszClassName = "BitmapDemo"; // No L prefix - using ANSI stringsif (!RegisterClassA(&wc)) {MessageBoxA(NULL, "Window Registration Failed!", "Error", MB_ICONEXCLAMATION | MB_OK);return 0;}// Create the window - explicit use of CreateWindowA for ANSI versiong_hwnd = CreateWindowA("BitmapDemo","Emurator", // Using simple titleWS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,WINDOW_WIDTH, WINDOW_HEIGHT,NULL, NULL, hInstance, NULL);if (!g_hwnd) {MessageBoxA(NULL, "Window Creation Failed!", "Error", MB_ICONEXCLAMATION | MB_OK);return 0;}// Show windowShowWindow(g_hwnd, nCmdShow);UpdateWindow(g_hwnd);// Main message loopMSG msg;while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}return (int)msg.wParam;
}
代碼功能概述
這段 C 語言代碼是一個 Windows GUI 應用程序,用于模擬電子紙顯示設備的界面。它支持黑白和黑白紅 (BWR) 兩種顯示模式,并能在時鐘和日歷兩種顯示模式間切換。程序通過 Windows API 創建窗口,處理用戶輸入,并模擬電子紙的顯示效果。
主要模塊與功能分析
1. 全局變量與宏定義
#define BITMAP_WIDTH ? 400
#define BITMAP_HEIGHT ?300
#define WINDOW_WIDTH ? 400
#define WINDOW_HEIGHT ?340
#define WINDOW_TITLE ? TEXT("Emurator")
HINSTANCE g_hInstance;
HWND g_hwnd;
display_mode_t g_display_mode = MODE_CALENDAR;
BOOL g_bwr_mode = TRUE;
time_t g_display_time;
struct tm g_tm_time;
定義了顯示區域和窗口的尺寸
聲明了窗口句柄、顯示模式和時間相關變量
默認顯示模式為日歷,默認支持 BWR (黑白紅) 模式
2. 位圖轉換函數 convertBitmap
static uint8_t *convertBitmap(uint8_t *bitmap, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
? ? // 計算行字節數并分配內存
? ? int bytesPerRow = ((w + 31) / 32) * 4;
? ? int totalSize = bytesPerRow * h;
? ? uint8_t *convertedBitmap = (uint8_t*)malloc(totalSize);
? ? memset(convertedBitmap, 0, totalSize);
? ??
? ? // 轉換電子紙格式(MSB優先)到位圖格式
? ? int ePaperBytesPerRow = (w + 7) / 8;
? ? for (int row = 0; row < h; row++) {
? ? ? ? for (int col = 0; col < w; col++) {
? ? ? ? ? ? // 計算電子紙緩沖區中的位位置
? ? ? ? ? ? int bytePos = row * ePaperBytesPerRow + col / 8;
? ? ? ? ? ? int bitPos = 7 - (col % 8);
? ? ? ? ? ? int isSet = (bitmap[bytePos] >> bitPos) & 0x01;
? ? ? ? ? ??
? ? ? ? ? ? // 設置Windows DIB中的對應位
? ? ? ? ? ? int dibBytePos = row * bytesPerRow + col / 8;
? ? ? ? ? ? int dibBitPos = 7 - (col % 8);
? ? ? ? ? ? if (isSet) {
? ? ? ? ? ? ? ? convertedBitmap[dibBytePos] |= (1 << dibBitPos);
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? return convertedBitmap;
}
將電子紙設備使用的位圖格式轉換為 Windows DIB (設備無關位圖) 格式
處理了位序轉換 (MSB 優先) 和行字節對齊問題
支持黑白和彩色 (紅色) 兩層顯示
DrawBitmap 函數詳解
該函數是電子紙模擬器的核心繪制函數,負責將電子紙的位圖數據(黑白層和彩色層)轉換為 Windows 窗口可顯示的位圖,并完成最終渲染。
以下從功能流程、關鍵技術點和代碼細節三方面展開分析:
一、函數功能與流程總覽
輸入參數:
? black:黑白層位圖數據(1 位單色,MSB 優先)
? color:彩色層位圖數據(可選,通常用于紅色顯示)
? x/y:繪制起點坐標(相對于顯示區域)
? w/h:繪制區域的寬度和高度
?核心流程:
1. 獲取繪圖環境:獲取窗口的設備上下文(HDC)和客戶區尺寸。
2. 配置位圖信息:定義 Windows 位圖格式(BITMAPINFO),包括尺寸、位深、顏色表等。
3. 繪制黑白層: ? 調用 convertBitmap 轉換電子紙格式為 Windows 位圖。 ? 使用 StretchDIBits 繪制黑色前景和白色背景。 ?
4. 繪制彩色層(可選): ? 轉換彩色層數據并設置顏色表(紅色通過黃色與黑色異或實現)。 ? 使用 SRCINVERT 光柵操作混合顏色層。 ?
5. 釋放資源:歸還設備上下文,避免內存泄漏。 ?
二、關鍵技術點解析
1. 設備上下文(HDC)與窗口坐標系
? GetDC(g_hwnd):獲取窗口的設備上下文,用于直接在窗口上繪制圖形。
? GetClientRect:獲取窗口客戶區(不含邊框和標題欄)的尺寸,
用于計算位圖居中顯示的位置:
?int drawX = (clientRect.right - BITMAP_WIDTH * scale) / 2;
int drawY = (clientRect.bottom - BITMAP_HEIGHT * scale) / 2;
? ? ?
(代碼中雖未顯式計算 scale,但通過 StretchDIBits 的縮放參數實現自適應顯示)
?2. 位圖信息頭(BITMAPINFO)配置
? BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = w; ? ? ? ? ? ? ?// 位圖寬度(像素)
bmi.bmiHeader.biHeight = -h; ? ? ? ? ? ?// 負高度表示位圖數據從上到下存儲(正向顯示)
bmi.bmiHeader.biPlanes = 1; ? ? ? ? ? ? // 平面數,固定為1
bmi.bmiHeader.biBitCount = 1; ? ? ? ? ? // 1位單色位圖(每個像素占1位,0=黑色,1=白色)
bmi.bmiHeader.biCompression = BI_RGB; ? // 無壓縮,使用RGB顏色表
? ? ?? 負高度的作用:
Windows 位圖有兩種存儲方式:
? 正高度:位圖數據從下到上存儲(原點在左下角)。
? 負高度:位圖數據從上到下存儲(原點在左上角),更符合常規坐標系,便于直接繪制。 ?
?3. 位圖格式轉換(convertBitmap)
? 電子紙格式特點:
? 1 位單色,MSB 優先(最高位為第一個像素)。
? 行字節數為 (寬度 + 7) / 8(向上取整到字節)。 ?
? Windows DIB 格式要求: ? 1 位位圖使用 BI_RGB 壓縮,行字節數需為 4 的倍數(通過 ((w + 31) / 32) * 4 計算)。 ? ? 轉換邏輯: c ? ? ? ?// 電子紙緩沖區中的位位置(MSB優先)
int bitPos = 7 - (col % 8); ?
// Windows DIB 中的位位置(同樣MSB優先)
int dibBitPos = 7 - (col % 8); ?
? ? ?
通過逐行逐位復制,將電子紙的 “位掩碼” 轉換為 Windows 可識別的位圖數據。 ?4. 顏色表與繪制邏輯 ? 黑白層繪制: c ? ? ? ?bmi.bmiColors[0] = RGB(0, 0, 0); // 索引0對應黑色(位圖中值為0的像素)
bmi.bmiColors[1] = RGB(255, 255, 255); // 索引1對應白色(位圖中值為1的像素)
StretchDIBits(..., SRCCOPY); // 直接復制像素,黑色前景覆蓋白色背景
? ? ?
?? 彩色層(紅色)繪制:
? 電子紙彩色層通常為紅色,但 Windows 位圖不支持直接繪制紅色單通道,因此通過 異或(XOR)操作 模擬:
bmi.bmiColors[0] = RGB(255, 255, 0); // 黃色(R=255, G=255, B=0)
bmi.bmiColors[1] = RGB(0, 0, 0); // 黑色(背景)
StretchDIBits(..., SRCINVERT); // 異或操作:黃色 ^ 白色 = 紅色,黃色 ^ 黑色 = 黃色
? ? ?
?? 異或原理:
? 白色背景區域(RGB (255,255,255)):黃色(RGB (255,255,0))與白色異或后為紅色(RGB (255,0,0))。
? 黑色前景區域(RGB (0,0,0)):黃色與黑色異或后保持黃色,但因黑色層已覆蓋,實際不顯示。 ? ? ?三、代碼細節與注意事項
1. 內存管理
? 動態分配與釋放:
uint8_t *convertedBitmap = convertBitmap(...); // 分配內存
// 使用后立即釋放
free(convertedBitmap);?
避免因忘記釋放內存導致程序泄漏。 ?
2. 光柵操作碼(Raster Operation)
? SRCCOPY:直接復制源位圖到目標區域,覆蓋原有像素(用于黑白層)。
? SRCINVERT:源位圖與目標區域像素異或(用于彩色層疊加)。
?3. 電子紙特性模擬
? 分層繪制:電子紙通常支持黑白層和彩色層獨立更新,此處通過兩次 StretchDIBits 調用模擬分層。
? 低刷新率:電子紙實際刷新較慢,但代碼中未模擬延遲,僅通過定時器控制界面更新頻率。 ?四、總結 DrawBitmap 函數通過以下步驟實現電子紙顯示模擬:
1. 格式適配:將電子紙的位掩碼格式轉換為 Windows 位圖,處理位序和行對齊問題。
2. 分層渲染:先繪制黑白層作為基礎,再通過異或操作疊加彩色層(紅色)。
3. 資源管理:及時釋放內存和設備上下文,確保程序穩定性。 ?該函數是電子紙模擬器的核心渲染引擎,結合窗口消息處理和用戶輸入,最終實現了可交互的電子紙界面效果。
??