承接上篇,本文探討基於 ImageMixer 實現圖像拼接(多圖合成到一張畫布)和直接將位圖數據繪制到設備上下文 (DC)。
核心思路:根據混合起始座標 (x, y) 計算目標圖與源圖的交集區域,提取子圖數據結構後調用原 ImageMixer。
BOOL GetSubBitmapData(CONST BitmapData *data, INT x, INT y, INT width, INT height, BitmapData *sub) {
if (x < 0) { width += x; x = 0; }
if (x + width > (INT)data->Width) width = data->Width - x;
if (width <= 0) return FALSE;
if (y < 0) { height += y; y = 0; }
if (y + height > (INT)data->Height) height = data->Height - y;
if (height <= 0) return FALSE;
sub->Width = width;
sub->Height = height;
sub->Stride = data->Stride;
sub->PixelFormat = data->PixelFormat;
sub->Scan0 = (CHAR*)data->Scan0 + y * data->Stride + (x << 2);
sub->Reserved = data->Reserved;
return TRUE;
}
VOID ImageMixer(BitmapData *dest, INT x, INT y, CONST BitmapData *source, INT alpha) {
BitmapData dst, src;
if (GetSubBitmapData(dest, x, y, source->Width, source->Height, &dst)) {
GetSubBitmapData(source, x < 0 ? -x : 0, y < 0 ? -y : 0, dst.Width, dst.Height, &src);
ImageMixer(&dst, &src, alpha);
}
}
void __fastcall TForm1::Button3Click(TObject *Sender) {
BitmapData data, dst, src;
// 建立新畫布 768x256
Gdiplus::Bitmap *newBmp = new Gdiplus::Bitmap(768, 256, PixelFormat32bppARGB);
LockBitmap(newBmp, &data);
// 目標圖放左側
Gdiplus::Bitmap *dest = new Gdiplus::Bitmap(L"d:\\xmas_011.png");
LockBitmap(dest, &dst);
ImageMixer(&data, 0, 0, &dst, 255);
// 源圖放中間
Gdiplus::Bitmap *source = new Gdiplus::Bitmap(L"d:\\Apple.png");
LockBitmap(source, &src);
ImageMixer(&data, dest->GetWidth(), 0, &src, 255);
// 目標與源混合 (不透明度 192)
ImageMixer(&dst, &src, 192);
// 混合結果放右側
ImageMixer(&data, dest->GetWidth() << 1, 0, &dst, 255);
UnlockBitmap(source, &src);
UnlockBitmap(dest, &dst);
UnlockBitmap(newBmp, &data);
// 顯示拼接結果
Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
g->DrawImage(newBmp, 0, 0);
delete g; delete source; delete dest; delete newBmp;
}

運行效果與上篇相同,但本質不同:上篇分多次繪制到窗口,本篇將多圖拼接到一張位圖後一次性顯示。
實現將 BitmapData 直接繪制到 Windows HDC,支持 Alpha 混合、裁剪、格式轉換。
GetBitmapInfoHeader:填充 BITMAPINFOHEADER。GetDCImageData:從 DC 讀取像素到 BitmapData(用於混合時獲取背景)。BitBltImageData:將 BitmapData 寫入 DC。VOID ImageDraw(HDC DC, INT x, INT y, CONST BitmapData *data, float alpha = 1.0f) {
BITMAPINFO bi;
RECT r;
INT alphaI = (INT)(alpha * 255);
LPVOID scan0;
BitmapData dst, src, tmp;
// 獲取 DC 可見區域
if (GetClipBox(DC, &r) <= NULLREGION) return;
// 快速路徑:不透明、無 Alpha、Windows 位圖格式 (負 Stride) 直接 BitBlt
if (alphaI >= 255 && !(data->Reserved & PixelAlphaFlag) && data->Stride < 0) {
GetBitmapInfoHeader(data, &bi);
BitBltImageData(DC, x, y, data, &bi);
return;
}
// 調整座標到可見區域
x -= r.left; y -= r.top;
if (x > 0) { r.left += x; x = 0; }
if (y > 0) { r.top += y; y = 0; }
// 計算實際繪制區域
tmp.Width = r.right - r.left;
tmp.Height = r.bottom - r.top;
tmp.Reserved = 0;
if (!GetSubBitmapData(&tmp, x, y, data->Width, data->Height, &dst)) return;
// 分配 32bpp 緩衝區 (正向 Stride)
dst.Stride = dst.Width << 2;
dst.Scan0 = scan0 = new CHAR[dst.Height * dst.Stride];
// 計算源圖交集區域
if (x < 0) x = -x;
if (y < 0) y = -y;
GetSubBitmapData(data, x, y, dst.Width, dst.Height, &src);
GetBitmapInfoHeader(&src, &bi);
// 需要混合時獲取 DC 背景
if (alphaI < 255 || (data->Reserved & PixelAlphaFlag))
GetDCImageData(DC, r.left, r.top, &dst, &bi);
// 轉換為 Windows 位圖格式 (負 Stride, 底部優先)
dst.Scan0 = (LPBYTE)scan0 + (dst.Height - 1) * dst.Stride;
dst.Stride = -dst.Stride;
// 執行混合
ImageMixer(&dst, &src, alphaI);
// 還原緩衝區格式並寫入 DC
dst.Scan0 = scan0;
BitBltImageData(DC, r.left, r.top, &dst, &bi);
delete[] scan0;
}
void __fastcall TForm1::Button4Click(TObject *Sender) {
Gdiplus::Bitmap *dest = new Gdiplus::Bitmap(L"d:\\xmas_011.png");
Gdiplus::Bitmap *source = new Gdiplus::Bitmap(L"d:\\Apple.png");
BitmapData dst, src;
LockBitmap(dest, &dst);
LockBitmap(source, &src);
ImageDraw(Canvas->Handle, 0, 0, &dst);
ImageDraw(Canvas->Handle, dst.Width, 0, &src);
ImageMixer(&dst, &src, 192);
ImageDraw(Canvas->Handle, dst.Width + src.Width, 0, &dst);
UnlockBitmap(source, &src);
UnlockBitmap(dest, &dst);
delete source; delete dest;
}
顯示效果與 GDI+ Graphics::DrawImage 一致,經優化後速度可超越 GDI+。
摘自 閒人阿發伯的業余編程心得