Trong quá trình phát triển ứng dụng nhúng trên nền tảng iMX31 chạy Windows CE 5.0, vấn đề hiệu suất hiển thị là một thách thức lớn. Cụ thể, trên màn hình có độ phân giải 800x480, việc sử dụng hàm BitBlt() để vẽ lại toàn bộ màn hình tiêu tốn khoảng 120ms, trong khi cùng một thao tác trên phần cứng khác (như Prima) chỉ mất dưới 20ms. Để giải quyết vấn đề này, chúng ta cần tiếp cận trực tiếp vào bộ nhớ màn hình (Direct Screen Access) thay vì phụ thuộc hoàn toàn vào các hàm vẽ GDI chuẩn.
Để thực hiện ghi trực tiếp lên màn hình, hai hàm API chính được sử dụng là CreateDC(TEXT("DISPLAY"),...) để lấy ngữ cảnh thiết bị màn hình và CreateDIBSection() để tạo một vùng nhớ bitmap có thể truy cập trực tiếp dữ liệu điểm ảnh (pixel data).
1. Cơ chế ghi trực tiếp vào bộ nhớ đệm khung hình (Frame Buffer)
Hàm CreateDC tương đối đơn giản, tuy nhiên CreateDIBSection đòi hỏi cài đặt phức tạp hơn để thiết lập kết nối giữa đối tượng bitmap, thiết bị ngữ cảnh (DC) và con trỏ vùng nhớ. Ví dụ dưới đây minh họa cách khởi tạo và sao chép dữ liệu đồ họa trực tiếp vào địa chỉ bộ nhớ vật lý của màn hình (gpbFrameAddress).
void DirectScreenWrite(HDC hdcSource, void* pVideoMemory, int nWidth, int nHeight)
{
BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
// Cấu trúc BITMAPINFOHEADER định dạng ảnh 16bit (565)
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = nWidth;
bitmapInfo.bmiHeader.biHeight = -nHeight; // Sử dụng chiều âm để tạo DIB top-down
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 16;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
HDC hdcMem = CreateCompatibleDC(hdcSource);
BYTE* pPixelBits = NULL;
// Tạo đối tượng DIB Section cho phép truy xuất trực tiếp mảng byte pPixelBits
HBITMAP hDibSection = CreateDIBSection(
hdcMem,
&bitmapInfo,
DIB_RGB_COLORS,
(void**)&pPixelBits,
NULL,
0
);
if (hDibSection != NULL && pPixelBits != NULL) {
HGDIOBJ hOldObj = SelectObject(hdcMem, hDibSection);
// Chuyển dữ liệu từ nguồn (dcSource) vào bộ nhớ DIB Section
BitBlt(hdcMem, 0, 0, nWidth, nHeight, hdcSource, 0, 0, SRCCOPY);
// Sao chép trực tiếp dữ liệu pixel từ DIB Section vào địa chỉ bộ nhớ màn hình
// 2 bytes = 16 bits mỗi pixel
CopyMemory(pVideoMemory, pPixelBits, nWidth * nHeight * 2);
// Khôi phục và dọn dẹp tài nguyên
SelectObject(hdcMem, hOldObj);
DeleteObject(hDibSection);
}
DeleteDC(hdcMem);
}
2. Tiện ích chụp và lưu màn hình (Screen Capture)
Dưới đây là đoạn mã đã được tối ưu hóa để thực hiện chức năng chụp màn hình và lưu lại dưới dạng file ảnh. Đoạn mã này bao gồm việc lấy ngữ cảnh màn hình, tạo bitmap tương thích và xử lý việc ghi dữ liệu ra file BMP với các định dạng màu sắc khác nhau (16-bit, 24-bit, 32-bit).
#include <windows.h>
// Hàm chụp toàn bộ màn hình trả về handle bitmap
HBITMAP CaptureScreenBitmap(int* pnResultWidth, int* pnResultHeight)
{
HDC hdcDesktop = CreateDC(_T("DISPLAY"), NULL, NULL, NULL);
HDC hdcMemory = CreateCompatibleDC(hdcDesktop);
int nScreenWidth = GetDeviceCaps(hdcDesktop, HORZRES);
int nScreenHeight = GetDeviceCaps(hdcDesktop, VERTRES);
if (pnResultWidth) *pnResultWidth = nScreenWidth;
if (pnResultHeight) *pnResultHeight = nScreenHeight;
// Tạo bitmap tương thích với màn hình
HBITMAP hCaptureBitmap = CreateCompatibleBitmap(hdcDesktop, nScreenWidth, nScreenHeight);
HGDIOBJ hOldBitmap = SelectObject(hdcMemory, hCaptureBitmap);
// Thực hiện sao chép pixel từ màn hình thật vào memory DC
BitBlt(hdcMemory, 0, 0, nScreenWidth, nScreenHeight, hdcDesktop, 0, 0, SRCCOPY);
// Hoàn tất và dọn dẹp
SelectObject(hdcMemory, hOldBitmap);
DeleteDC(hdcMemory);
DeleteDC(hdcDesktop);
return hCaptureBitmap;
}
// Hàm lưu bitmap ra file với độ sâu màu mong muốn (16, 24, hoặc 32)
BOOL SaveBitmapToFile(HBITMAP hBitmap, LPCTSTR lpszFilePath, WORD wBitsPerPixel)
{
HDC hdcDisplay = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
HDC hdcOffscreen = CreateCompatibleDC(hdcDisplay);
SelectObject(hdcOffscreen, hBitmap);
BITMAP bmpInfo;
GetObject(hBitmap, sizeof(BITMAP), &bmpInfo);
HANDLE hFile = CreateFile(lpszFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
DeleteDC(hdcOffscreen);
DeleteDC(hdcDisplay);
return FALSE;
}
// Tính toán kích thước file và header
DWORD dwPixelSize = (wBitsPerPixel / 8) * bmpInfo.bmWidth * bmpInfo.bmHeight;
BITMAPFILEHEADER bmfHeader;
ZeroMemory(&bmfHeader, sizeof(BITMAPFILEHEADER));
bmfHeader.bfType = 0x4D42; // 'BM'
bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmfHeader.bfSize = bmfHeader.bfOffBits + dwPixelSize;
BITMAPINFOHEADER bmiHeader;
ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmiHeader.biWidth = bmpInfo.bmWidth;
bmiHeader.biHeight = bmpInfo.bmHeight;
bmiHeader.biPlanes = 1;
bmiHeader.biBitCount = wBitsPerPixel;
if (wBitsPerPixel == 32) {
bmiHeader.biCompression = BI_BITFIELDS;
// Thiết lập mask màu nếu cần thiết cho 32bit
}
DWORD dwBytesWritten = 0;
WriteFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
WriteFile(hFile, &bmiHeader, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
// Sử dụng CreateDIBSection để lấy raw data từ bitmap nguồn
BITMAPINFO biDib;
ZeroMemory(&biDib, sizeof(BITMAPINFO));
biDib.bmiHeader = bmiHeader;
// Nếu là 32-bit và dùng BI_BITFIELDS, cần cấu hình màu ở đây
if (wBitsPerPixel == 32) {
biDib.bmiColors[0].rgbRed = 0xFF;
biDib.bmiColors[0].rgbGreen = 0xFF;
biDib.bmiColors[0].rgbBlue = 0xFF;
}
BYTE* pImageData = new BYTE[dwPixelSize];
HDC hdcDib = CreateCompatibleDC(hdcDisplay);
HBITMAP hDibBitmap = CreateDIBSection(hdcDib, &biDib, DIB_RGB_COLORS, (void**)&pImageData, NULL, 0);
if (hDibBitmap != NULL) {
HGDIOBJ hOld = SelectObject(hdcDib, hDibBitmap);
// Copy từ bitmap nguồn (hBitmap) vào DIB Section mới tạo
BitBlt(hdcDib, 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, hdcOffscreen, 0, 0, SRCCOPY);
// Ghi dữ liệu pixel ra file
WriteFile(hFile, pImageData, dwPixelSize, &dwBytesWritten, NULL);
SelectObject(hdcDib, hOld);
DeleteObject(hDibBitmap);
}
delete[] pImageData;
DeleteDC(hdcDib);
DeleteDC(hdcOffscreen);
DeleteDC(hdcDisplay);
CloseHandle(hFile);
return TRUE;
}