墨水屏显示模拟器程序解读

程序如下:出处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 bytes
    int totalSize = bytesPerRow * h;
    
    // Allocate memory for converted bitmap
    uint8_t *convertedBitmap = (uint8_t*)malloc(totalSize);
    if (convertedBitmap == NULL) return NULL;
    
    memset(convertedBitmap, 0, totalSize);

    int ePaperBytesPerRow = (w + 7) / 8; // E-paper buffer stride
    
    for (int row = 0; row < h; row++) {
        for (int col = 0; col < w; col++) {
            // Calculate byte and bit position in e-paper buffer
            int 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 buffer
            int isSet = (bitmap[bytePos] >> bitPos) & 0x01;
            
            // Calculate byte and bit position in Windows DIB
            int 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 buffer
            if (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 drawing
    hdc = GetDC(g_hwnd);
    if (!hdc) return;
    
    // Get client area for positioning
    GetClientRect(g_hwnd, &clientRect);
    
    // Calculate position to center the entire bitmap in the window
    int drawX = (clientRect.right - BITMAP_WIDTH * scale) / 2;
    int drawY = (clientRect.bottom - BITMAP_HEIGHT * scale) / 2;
    
    // Create DIB for visible pixels
    BITMAPINFO bmi;
    ZeroMemory(&bmi, sizeof(BITMAPINFO));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = w;
    bmi.bmiHeader.biHeight = -h; // Negative for top-down bitmap
    bmi.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 display
    bmi.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 layer
    StretchDIBits(hdc,
                 drawX + x * scale, drawY + y * scale,  // Destination position
                 w * scale, h * scale,                  // Destination size
                 0, 0,                                 // Source position
                 w, h,                                 // Source size
                 convertedBitmap,                      // Converted bitmap bits
                 &bmi,                                 // Bitmap info
                 DIB_RGB_COLORS,                       // Usage
                 SRCCOPY);                             // Raster operation code
    free(convertedBitmap);

    // Handle color layer if present (red in BWR displays)
    if (color) {
        // Allocate memory for converted color bitmap
        uint8_t *convertedColor = convertBitmap(color, x, y, w, h);
        if (convertedColor) {
            // Set colors for red overlay
            bmi.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 overlay
            StretchDIBits(hdc,
                         drawX + x * scale, drawY + y * scale,  // Destination position
                         w * scale, h * scale,                  // Destination size
                         0, 0,                                 // Source position
                         w, h,                                 // Source size
                         convertedColor,                       // Converted bitmap bits
                         &bmi,                                 // Bitmap info
                         DIB_RGB_COLORS,                       // Usage
                         SRCINVERT);                           // Use XOR operation to blend
                         
            free(convertedColor);
        }
    }
    
    // Release the device context
    ReleaseDC(g_hwnd, hdc);
}

// Window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch (message) {
        case WM_CREATE:
            // Initialize the display time
            g_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 calculations
            RECT clientRect;
            GetClientRect(hwnd, &clientRect);

            // Clear the entire client area with a solid color
            HBRUSH bgBrush = CreateSolidBrush(RGB(240, 240, 240));
            FillRect(hdc, &clientRect, bgBrush);
            DeleteObject(bgBrush);
            
            // Use the stored timestamp
            gui_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 mode
            DrawGUI(&data, DrawBitmap, g_display_mode);
            
            EndPaint(hwnd, &ps);
            return 0;
        }
        
        case WM_KEYDOWN:
            // Toggle display mode with spacebar
            if (wParam == VK_SPACE) {
                if (g_display_mode == MODE_CLOCK)
                    g_display_mode = MODE_CALENDAR;
                else
                    g_display_mode = MODE_CLOCK;
                
                InvalidateRect(hwnd, NULL, TRUE);
            }
            // Toggle BWR mode with R key
            else if (wParam == 'R') {
                g_bwr_mode = !g_bwr_mode;
                InvalidateRect(hwnd, NULL, TRUE);
            }
            // Handle arrow keys for month/day adjustment
            else if (wParam == VK_UP || wParam == VK_DOWN || wParam == VK_LEFT || wParam == VK_RIGHT) {
                // Get the current time structure
                g_tm_time = *localtime(&g_display_time);
                
                // Up/Down adjusts month
                if (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 day
                else if (wParam == VK_RIGHT) {
                    g_tm_time.tm_mday++;
                }
                else if (wParam == VK_LEFT) {
                    g_tm_time.tm_mday--;
                }
                
                // Convert back to time_t
                g_display_time = mktime(&g_tm_time);
                
                // Force redraw
                InvalidateRect(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 class
    WNDCLASSA wc = {0}; // Using WNDCLASSA for ANSI version
    wc.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 strings
    
    if (!RegisterClassA(&wc)) {
        MessageBoxA(NULL, "Window Registration Failed!", "Error", MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }
    
    // Create the window - explicit use of CreateWindowA for ANSI version
    g_hwnd = CreateWindowA(
        "BitmapDemo",
        "Emurator", // Using simple title
        WS_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 window
    ShowWindow(g_hwnd, nCmdShow);
    UpdateWindow(g_hwnd);
    
    // Main message loop
    MSG 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. 资源管理:及时释放内存和设备上下文,确保程序稳定性。  该函数是电子纸模拟器的核心渲染引擎,结合窗口消息处理和用户输入,最终实现了可交互的电子纸界面效果。


  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值